import { useEffect, useState } from 'react' import { AiturkLogin } from '@/components/aiturk-login' import { Button } from '@/components/ui/button' import { Checkbox } from '@/components/ui/checkbox' import { Input } from '@/components/ui/input' import { activateCustomEndpoint, deleteCustomEndpoint, getCustomEndpoints, saveCustomEndpoint, validateCustomEndpoint } from '@/hermes' import { useI18n } from '@/i18n' import { useAiturkCopy } from '@/i18n/aiturk' import { AITURK_API_BASE_URL } from '@/lib/aiturk-provider' import { triggerHaptic } from '@/lib/haptics' import { Check, Globe, Loader2, Plus, Save, Trash2, Zap } from '@/lib/icons' import { cn } from '@/lib/utils' import { confirm } from '@/store/confirm' import { notify, notifyError } from '@/store/notifications' import type { CustomEndpoint, CustomEndpointUpdate } from '@/types/hermes' import { EmptyState, Pill, SectionHeading, SettingsContent, SettingsSkeleton } from './primitives' interface CustomEndpointsSettingsProps { onConfigSaved?: () => void onMainModelChanged?: (provider: string, model: string) => void } interface EndpointForm { apiKey: string baseUrl: string contextLength: string discoverModels: boolean id: string makeDefault: boolean model: string name: string } const EMPTY_FORM: EndpointForm = { apiKey: '', baseUrl: '', contextLength: '', discoverModels: true, id: '', makeDefault: true, model: '', name: '' } function formFromEndpoint(endpoint: CustomEndpoint): EndpointForm { return { apiKey: '', baseUrl: endpoint.base_url, contextLength: endpoint.context_length ? String(endpoint.context_length) : '', discoverModels: endpoint.discover_models, id: endpoint.id, makeDefault: Boolean(endpoint.is_current), model: endpoint.model, name: endpoint.name } } function toPayload(form: EndpointForm, models?: string[]): CustomEndpointUpdate { const contextLength = Number.parseInt(form.contextLength, 10) return { id: form.id.trim() || undefined, name: form.name.trim(), base_url: form.baseUrl.trim(), model: form.model.trim(), api_key: form.apiKey.trim() || undefined, context_length: Number.isFinite(contextLength) && contextLength > 0 ? contextLength : undefined, discover_models: form.discoverModels, make_default: form.makeDefault, models: models?.length ? models : undefined } } export function CustomEndpointsSettings({ onConfigSaved, onMainModelChanged }: CustomEndpointsSettingsProps) { const { t } = useI18n() const aiturkCopy = useAiturkCopy() const c = aiturkCopy.custom const loginCopy = aiturkCopy.login const loginLabel = loginCopy.settingsSignIn const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [testing, setTesting] = useState(false) const [activating, setActivating] = useState(null) const [deleting, setDeleting] = useState(null) const [endpoints, setEndpoints] = useState([]) const [form, setForm] = useState(EMPTY_FORM) const [discoveredModels, setDiscoveredModels] = useState([]) async function refresh() { const data = await getCustomEndpoints() setEndpoints(data.endpoints) } useEffect(() => { let cancelled = false async function load() { try { const data = await getCustomEndpoints() if (cancelled) { return } setEndpoints(data.endpoints) const current = data.endpoints.find(endpoint => endpoint.is_current) ?? data.endpoints[0] if (current) { setForm(formFromEndpoint(current)) setDiscoveredModels(current.models) } } catch (err) { notifyError(err, c.loadFailed) } finally { if (!cancelled) { setLoading(false) } } } void load() return () => { cancelled = true } }, []) // One-click sign-in saved the TurkServis provider in the main process: // reload the list and tell the app its default model changed. async function handleAiturkConnected() { try { const data = await getCustomEndpoints() setEndpoints(data.endpoints) const current = data.endpoints.find(endpoint => endpoint.id === 'turkservis') if (current) { setForm(formFromEndpoint(current)) setDiscoveredModels(current.models) if (current.is_current) { onMainModelChanged?.(current.id, current.model) } } triggerHaptic('success') onConfigSaved?.() notify({ kind: 'success', message: loginCopy.connected }) } catch (err) { notifyError(err, c.loadFailed) } } async function handleSave() { try { setSaving(true) const response = await saveCustomEndpoint(toPayload(form, discoveredModels)) setEndpoints(response.endpoints) const saved = response.endpoints.find(endpoint => endpoint.id === response.id) if (saved) { setForm(formFromEndpoint(saved)) setDiscoveredModels(saved.models) } if (saved && saved.is_current) { onMainModelChanged?.(saved.id, saved.model) } triggerHaptic('success') onConfigSaved?.() notify({ kind: 'success', message: c.saved }) } catch (err) { notifyError(err, c.saveFailed) } finally { setSaving(false) } } async function handleValidate() { try { setTesting(true) const response = await validateCustomEndpoint(toPayload(form)) setDiscoveredModels(response.models) if (response.ok) { if (!form.model && response.models[0]) { setForm(current => ({ ...current, model: response.models[0] })) } notify({ kind: 'success', message: response.models.length ? c.found(response.models.length) : c.reachable }) } else { notify({ kind: response.reachable ? 'warning' : 'error', message: response.message || c.validationFailed }) } } catch (err) { notifyError(err, c.validationFailed) } finally { setTesting(false) } } async function handleActivate(endpoint: CustomEndpoint) { try { setActivating(endpoint.id) const response = await activateCustomEndpoint(endpoint.id) await refresh() onConfigSaved?.() onMainModelChanged?.(response.provider, response.model) triggerHaptic('success') } catch (err) { notifyError(err, c.activationFailed) } finally { setActivating(null) } } async function handleDelete(endpoint: CustomEndpoint) { if (!(await confirm({ destructive: true, title: c.confirmDelete(endpoint.name) }))) { return } try { setDeleting(endpoint.id) const response = await deleteCustomEndpoint(endpoint.id) setEndpoints(response.endpoints) if (form.id === endpoint.id) { setForm(EMPTY_FORM) setDiscoveredModels([]) } onConfigSaved?.() triggerHaptic('success') } catch (err) { notifyError(err, c.deleteFailed) } finally { setDeleting(null) } } if (loading) { return } const allModelOptions = Array.from(new Set([...discoveredModels, form.model].filter(Boolean))) const canSave = form.name.trim() && form.baseUrl.trim() && form.model.trim() return (
{endpoints.length ? ( endpoints.map(endpoint => (
{endpoint.source !== 'direct-config' && ( )}
)) ) : ( )}
) }