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 <noreply@paperclip.ing>
This commit is contained in:
CTO (LegalAI)
2026-04-09 13:42:57 +00:00
parent 4473e32f9c
commit 2a7db07d46

View File

@@ -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<ApiKeyInfo[]>([]);
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 (
<div className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-sm ${activeKey ? 'border-green-200 bg-green-50 text-green-700' : 'border-amber-200 bg-amber-50 text-amber-700'}`}>
<span className={`inline-block w-2 h-2 rounded-full shrink-0 ${activeKey ? 'bg-green-500' : 'bg-amber-500'}`} />
{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.`}
<div className="space-y-2">
<label className="block text-sm text-muted mb-1">API-Schlüssel</label>
<div className="flex gap-2">
<input
type="password"
value={apiKeyInput}
onChange={(e) => 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"
/>
<button
type="button"
onClick={handleSaveApiKey}
disabled={savingKey || !apiKeyInput}
className="rounded-lg bg-primary px-3 py-2 text-sm font-medium text-white hover:bg-primary/90 disabled:opacity-50"
>
{savingKey ? '...' : activeKey ? 'Ersetzen' : 'Speichern'}
</button>
</div>
<div className={`flex items-center gap-2 rounded-lg border px-3 py-2 text-sm ${activeKey ? 'border-green-200 bg-green-50 text-green-700' : 'border-amber-200 bg-amber-50 text-amber-700'}`}>
<span className={`inline-block w-2 h-2 rounded-full shrink-0 ${activeKey ? 'bg-green-500' : 'bg-amber-500'}`} />
{activeKey
? `API-Schlüssel für ${providerLabel} ist konfiguriert (${activeKey.keyHint}).`
: `Kein API-Schlüssel für ${providerLabel} hinterlegt.`}
</div>
</div>
);
})()}