import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import axios from 'axios'
import { SparkleIcon, XIcon } from '@phosphor-icons/react'
import { Alert, Button, TextArea } from '@/ui'
import { useAiSuggestField } from '@/api/mutations'
import { cn } from '@/lib/utils'
/**
* AI Schema Assist — per-field suggest panel.
*
*
Expanded inline (не modal) — admin вводит описание → preview JSON suggestion
* → accept/reject. Accept callback получает {fieldName, schema} как plain JSON
* fragment, caller интегрирует в свой schema state.
*
*
Graceful degradation:
*
* - 404 от endpoint (AI disabled на бэке) → caller должен hide button entirely
* - 503 circuit_open → banner «AI временно недоступен», retry в 5 мин
* - 422 bad_output → error message, suggest другой prompt
* - 429 rate_limit → «слишком много запросов, подожди минуту»
*
*/
type Props = {
/** Current schema serialized as JSON object — будет передан как context */
existingSchema: Record
/** Called когда юзер accepted suggestion */
onAccept: (suggestion: { fieldName: string; schema: Record }) => void
/** Optional close handler — hide panel */
onClose?: () => void
}
export function AiFieldSuggestPanel({ existingSchema, onAccept, onClose }: Props) {
const { t } = useTranslation()
const [prompt, setPrompt] = useState('')
const [suggestion, setSuggestion] = useState<{
fieldName: string
schema: Record
} | null>(null)
const mutation = useAiSuggestField()
const handleSuggest = () => {
if (!prompt.trim()) return
setSuggestion(null)
mutation.mutate(
{ existingSchema, prompt },
{
onSuccess: (data) => setSuggestion(data),
},
)
}
const handleAccept = () => {
if (!suggestion) return
onAccept(suggestion)
setPrompt('')
setSuggestion(null)
}
const handleRetry = () => {
setSuggestion(null)
handleSuggest()
}
const errorMessage = mutation.error ? formatError(mutation.error, t) : null
return (
{t('aiSuggest.title', { defaultValue: 'AI: добавить поле' })}
{onClose && (
)}
{!suggestion && (
)}
{errorMessage && (
{errorMessage}
)}
{suggestion && (
{t('aiSuggest.preview', { defaultValue: 'Предложение' })}
{suggestion.fieldName}
{JSON.stringify(suggestion.schema, null, 2)}
)}
)
}
function formatError(
err: unknown,
t: (k: string, opts?: Record) => string,
): string {
if (axios.isAxiosError(err)) {
const status = err.response?.status
if (status === 503) {
return t('aiSuggest.error.circuit', {
defaultValue: 'AI временно недоступен. Попробуй через несколько минут.',
})
}
if (status === 429) {
return t('aiSuggest.error.rate', {
defaultValue: 'Слишком много запросов. Подожди минуту.',
})
}
if (status === 422) {
return t('aiSuggest.error.bad_output', {
defaultValue: 'AI вернул невалидный ответ. Попробуй переформулировать prompt.',
})
}
if (status === 502) {
return t('aiSuggest.error.upstream', {
defaultValue: 'LLM endpoint недоступен. Жди restore или попробуй позже.',
})
}
if (status === 404) {
return t('aiSuggest.error.disabled', {
defaultValue: 'AI отключён в этой инсталляции.',
})
}
}
return t('aiSuggest.error.generic', { defaultValue: 'Не удалось получить предложение.' })
}