import { type Component, createSignal, createResource, Show, For, } from 'solid-js'; import { Key, Eye, EyeOff, CheckCircle, XCircle, Trash2, RefreshCw } from 'lucide-solid'; import { apiKeysApi } from '~/api/apiKeys'; import { useI18n } from '~/i18n'; import { useToast } from '~/components/ui/Toast'; import { isApiError } from '~/types'; import type { ProviderConfig, UserApiKey } from '~/types'; interface ApiKeyManagerProps { providers: ProviderConfig[]; } /** * Manages per-provider API keys (BYOK) displayed on the Settings page. * * Renders one {@link ProviderKeyCard} per configured provider. The card * supports creating, testing, and deleting keys, as well as toggling * key visibility (show/hide). The `test` button makes a live validation * call to verify the key works. */ const ApiKeyManager: Component = (props) => { const { t } = useI18n(); const { addToast } = useToast(); const [apiKeys, { refetch }] = createResource(() => apiKeysApi.list()); const getKeyForProvider = (providerName: string): UserApiKey | undefined => { return apiKeys()?.find((k) => k.provider_name === providerName); }; return (

{t('settings.apiKeys.title')}

{t('settings.apiKeys.description')}

{(provider) => ( )}
); }; interface ProviderKeyCardProps { provider: ProviderConfig; apiKey: UserApiKey | undefined; onKeyChanged: () => void; } /** Individual card for a single provider's API key with CRUD and test actions. */ const ProviderKeyCard: Component = (props) => { const { t } = useI18n(); const { addToast } = useToast(); const [keyInput, setKeyInput] = createSignal(''); const [showKey, setShowKey] = createSignal(false); const [editing, setEditing] = createSignal(false); const [saving, setSaving] = createSignal(false); const [testing, setTesting] = createSignal(false); const [confirmDelete, setConfirmDelete] = createSignal(false); const isConfigured = () => !!props.apiKey; const showInput = () => !isConfigured() || editing(); const handleSave = async () => { const key = keyInput().trim(); if (!key) return; setSaving(true); try { await apiKeysApi.create({ provider_name: props.provider.provider_name, api_key: key, }); addToast({ type: 'success', message: t('settings.apiKeys.saved'), duration: 4000, }); setKeyInput(''); setShowKey(false); setEditing(false); props.onKeyChanged(); } catch (err) { const message = isApiError(err) ? err.message : t('settings.apiKeys.saveError'); addToast({ type: 'error', message, duration: 5000 }); } finally { setSaving(false); } }; const handleTest = async () => { setTesting(true); try { const result = await apiKeysApi.test(props.provider.provider_name); if (result.success) { addToast({ type: 'success', message: t('settings.apiKeys.testSuccess'), duration: 4000, }); } else { addToast({ type: 'error', message: t('settings.apiKeys.testFailure', { message: result.message }), duration: 6000, }); } } catch (err) { const message = isApiError(err) ? err.message : t('settings.apiKeys.testFailure', { message: 'Erreur inconnue' }); addToast({ type: 'error', message, duration: 5000 }); } finally { setTesting(false); } }; const handleDelete = async () => { try { await apiKeysApi.remove(props.provider.provider_name); addToast({ type: 'success', message: t('settings.apiKeys.deleted'), duration: 4000, }); setConfirmDelete(false); setEditing(false); props.onKeyChanged(); } catch (err) { const message = isApiError(err) ? err.message : t('settings.apiKeys.deleteError'); addToast({ type: 'error', message, duration: 5000 }); } }; const handleCancel = () => { setEditing(false); setKeyInput(''); setShowKey(false); setConfirmDelete(false); }; return (
{/* Header: Provider name + status badge */}
{props.provider.display_name} {t('settings.apiKeys.notConfigured')} } > {t('settings.apiKeys.configured')}
{/* Action buttons for configured keys */}
} >
{/* Key prefix display */}

{t('settings.apiKeys.keyPrefix', { prefix: props.apiKey!.key_prefix })}

{/* Input form: shown when not configured or editing */}
setKeyInput(e.currentTarget.value)} placeholder={t('settings.apiKeys.inputPlaceholder', { provider: props.provider.display_name, })} />
); }; export default ApiKeyManager;