From 2a7db07d4633f15e7c23f57ff63457bdf27ed21c Mon Sep 17 00:00:00 2001 From: "CTO (LegalAI)" Date: Thu, 9 Apr 2026 13:42:57 +0000 Subject: [PATCH] fix: add API key input field to AI provider settings form When selecting Anthropic or OpenAI as provider, the form now shows an inline API key input field that creates or replaces the key via the existing CRUD API. Includes key hint display and status indicator. Resolves AIIA-49 Co-Authored-By: Paperclip --- .../(dashboard)/einstellungen/ai-settings.tsx | 79 +++++++++++++++++-- 1 file changed, 74 insertions(+), 5 deletions(-) diff --git a/src/app/(dashboard)/einstellungen/ai-settings.tsx b/src/app/(dashboard)/einstellungen/ai-settings.tsx index 2c23165..d8e9167 100644 --- a/src/app/(dashboard)/einstellungen/ai-settings.tsx +++ b/src/app/(dashboard)/einstellungen/ai-settings.tsx @@ -10,7 +10,9 @@ interface AISettings { } interface ApiKeyInfo { + id: string; provider: string; + keyHint: string; isActive: boolean; } @@ -25,6 +27,8 @@ export default function AISettingsForm() { const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); const [loading, setLoading] = useState(true); const [apiKeys, setApiKeys] = useState([]); + const [apiKeyInput, setApiKeyInput] = useState(''); + const [savingKey, setSavingKey] = useState(false); useEffect(() => { fetch('/api/settings/ai') @@ -107,12 +111,77 @@ export default function AISettingsForm() { const activeKey = apiKeys.find( (k) => k.provider === settings.provider && k.isActive, ); + const providerLabel = settings.provider === 'anthropic' ? 'Anthropic' : 'OpenAI'; + + async function handleSaveApiKey() { + if (!apiKeyInput || apiKeyInput.length < 8) { + setMessage({ type: 'error', text: 'API-Schlüssel muss mindestens 8 Zeichen lang sein.' }); + return; + } + setSavingKey(true); + setMessage(null); + try { + let res: Response; + if (activeKey) { + res = await fetch(`/api/settings/api-keys/${activeKey.id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ apiKey: apiKeyInput }), + }); + } else { + res = await fetch('/api/settings/api-keys', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ provider: settings.provider, apiKey: apiKeyInput }), + }); + } + if (res.ok) { + const updated = await res.json(); + setApiKeys((prev) => { + if (activeKey) { + return prev.map((k) => (k.id === activeKey.id ? { ...k, ...updated } : k)); + } + return [...prev, updated]; + }); + setApiKeyInput(''); + setMessage({ type: 'success', text: `API-Schlüssel für ${providerLabel} gespeichert.` }); + } else { + const data = await res.json(); + setMessage({ type: 'error', text: data.error ?? 'Fehler beim Speichern des Schlüssels.' }); + } + } catch { + setMessage({ type: 'error', text: 'Netzwerkfehler beim Speichern des Schlüssels.' }); + } finally { + setSavingKey(false); + } + } + return ( -
- - {activeKey - ? `API-Schlüssel für ${settings.provider === 'anthropic' ? 'Anthropic' : 'OpenAI'} ist konfiguriert.` - : `Kein API-Schlüssel für ${settings.provider === 'anthropic' ? 'Anthropic' : 'OpenAI'} hinterlegt. Bitte unten unter „API-Schlüssel" einen Schlüssel hinzufügen.`} +
+ +
+ setApiKeyInput(e.target.value)} + placeholder={activeKey ? `Aktuell: ${activeKey.keyHint} — neuen Schlüssel eingeben zum Ersetzen` : `${providerLabel} API-Schlüssel eingeben`} + className="flex-1 rounded-lg border border-card-border bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted/50" + /> + +
+
+ + {activeKey + ? `API-Schlüssel für ${providerLabel} ist konfiguriert (${activeKey.keyHint}).` + : `Kein API-Schlüssel für ${providerLabel} hinterlegt.`} +
); })()}