import React from 'react'; import { SettingsSection, SettingsStackedField, SETTINGS_FIELDS_STACK_CLASS, SETTINGS_FIELD_LABEL_CLASS, SETTINGS_HELPER_CLASS, SETTINGS_ICON_BUTTON_CLASS, SETTINGS_CONTROL_CLUSTER_CLASS, } from '@/components/sections/shared/SettingsSection'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Icon } from '@/components/icon/Icon'; import { useI18n } from '@/lib/i18n'; import { CUSTOM_PROVIDER_PROTOCOLS, createEmptyCustomProviderForm, createHeaderRow, createModelRow, validateCustomProvider, type CustomProviderFormState, type CustomProviderPersistPlan, type CustomProviderTranslator, type FieldErrors, type HeaderFieldErrors, type ModelFieldErrors, } from './custom-provider-form'; type CustomProviderFormProps = { existingProviderIDs: ReadonlySet; disabledProviders?: readonly string[]; busy?: boolean; mode?: 'create' | 'edit'; initialValues?: CustomProviderFormState; allowExistingAuth?: boolean; authFailureHint?: string | null; onSubmit: (plan: CustomProviderPersistPlan) => void | Promise; onCancel?: () => void; onDisconnect?: () => void | Promise; }; export const CustomProviderForm: React.FC = ({ existingProviderIDs, disabledProviders = [], busy = false, mode = 'create', initialValues, allowExistingAuth = false, authFailureHint = null, onSubmit, onCancel, onDisconnect, }) => { const { t } = useI18n(); const isEdit = mode === 'edit'; const [form, setForm] = React.useState( () => initialValues ?? createEmptyCustomProviderForm(), ); const [err, setErr] = React.useState({}); const [modelErrors, setModelErrors] = React.useState([]); const [headerErrors, setHeaderErrors] = React.useState([]); const seededEditProviderIdRef = React.useRef(null); React.useEffect(() => { if (!initialValues) { return; } // Edit mode: seed once per provider id so parent re-renders (new object // identity for the same snapshot) do not wipe in-progress edits. if (isEdit && seededEditProviderIdRef.current === initialValues.providerID) { return; } seededEditProviderIdRef.current = isEdit ? initialValues.providerID : null; setForm(initialValues); setErr({}); setModelErrors([]); setHeaderErrors([]); }, [initialValues, isEdit]); const setField = (key: keyof Pick, value: string) => { setForm((prev) => ({ ...prev, [key]: value })); setErr((prev) => ({ ...prev, [key]: undefined })); }; const setModel = (index: number, key: 'id' | 'name', value: string) => { setForm((prev) => ({ ...prev, models: prev.models.map((row, rowIndex) => (rowIndex === index ? { ...row, [key]: value } : row)), })); setModelErrors((prev) => { const next = [...prev]; next[index] = { ...(next[index] ?? {}), [key]: undefined }; return next; }); }; const setHeader = (index: number, key: 'key' | 'value', value: string) => { setForm((prev) => ({ ...prev, headers: prev.headers.map((row, rowIndex) => (rowIndex === index ? { ...row, [key]: value } : row)), })); setHeaderErrors((prev) => { const next = [...prev]; next[index] = { ...(next[index] ?? {}), [key]: undefined }; return next; }); }; const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (busy) { return; } const output = validateCustomProvider({ form, t: ((key, vars) => t(key as Parameters[0], vars)) as CustomProviderTranslator, existingProviderIDs, disabledProviders, editingProviderID: isEdit ? form.providerID : undefined, allowExistingAuth: isEdit && allowExistingAuth, }); setErr(output.err); setModelErrors(output.models); setHeaderErrors(output.headers); if (!output.result) { return; } await onSubmit(output.result); }; return (

{t('settings.providers.page.custom.description')}

{authFailureHint ? (

{authFailureHint}

) : null} setField('providerID', event.target.value)} placeholder={t('settings.providers.page.custom.field.providerID.placeholder')} className="h-8 rounded-md px-3 font-mono text-xs" autoFocus={!isEdit} disabled={isEdit || busy} aria-invalid={Boolean(err.providerID)} aria-label={t('settings.providers.page.custom.field.providerID.label')} /> {err.providerID ?

{err.providerID}

: null}
setField('name', event.target.value)} placeholder={t('settings.providers.page.custom.field.name.placeholder')} className="h-8 rounded-md px-3" aria-invalid={Boolean(err.name)} aria-label={t('settings.providers.page.custom.field.name.label')} /> {err.name ?

{err.name}

: null}
setField('baseURL', event.target.value)} placeholder={t('settings.providers.page.custom.field.baseURL.placeholder')} className="h-8 rounded-md px-3 font-mono text-xs" aria-invalid={Boolean(err.baseURL)} aria-label={t('settings.providers.page.custom.field.baseURL.label')} /> {err.baseURL ?

{err.baseURL}

: null}
setField('apiKey', event.target.value)} placeholder={ isEdit && allowExistingAuth ? t('settings.providers.page.custom.field.apiKey.editPlaceholder') : t('settings.providers.page.custom.field.apiKey.placeholder') } className="h-8 rounded-md px-3 font-mono text-xs" aria-invalid={Boolean(err.apiKey)} aria-label={t('settings.providers.page.custom.field.apiKey.label')} /> {err.apiKey ?

{err.apiKey}

: null}
{form.models.map((model, index) => (
setModel(index, 'id', event.target.value)} placeholder={t('settings.providers.page.custom.models.idPlaceholder')} className="mt-1 h-8 rounded-md px-3 font-mono text-xs" aria-label={t('settings.providers.page.custom.models.idLabel')} /> {modelErrors[index]?.id ? (

{modelErrors[index]?.id}

) : null}
setModel(index, 'name', event.target.value)} placeholder={t('settings.providers.page.custom.models.namePlaceholder')} className="mt-1 h-8 rounded-md px-3" aria-label={t('settings.providers.page.custom.models.nameLabel')} /> {modelErrors[index]?.name ? (

{modelErrors[index]?.name}

) : null}
))}

{t('settings.providers.page.custom.headers.description')}

{form.headers.map((header, index) => (
setHeader(index, 'key', event.target.value)} placeholder={t('settings.providers.page.custom.headers.keyPlaceholder')} className="mt-1 h-8 rounded-md px-3 font-mono text-xs" aria-label={t('settings.providers.page.custom.headers.keyLabel')} /> {headerErrors[index]?.key ? (

{headerErrors[index]?.key}

) : null}
setHeader(index, 'value', event.target.value)} placeholder={t('settings.providers.page.custom.headers.valuePlaceholder')} className="mt-1 h-8 rounded-md px-3 font-mono text-xs" aria-label={t('settings.providers.page.custom.headers.valueLabel')} /> {headerErrors[index]?.value ? (

{headerErrors[index]?.value}

) : null}
))}
{onCancel ? ( ) : null} {onDisconnect ? ( ) : null}
); };