/** * LLM Connections settings tab (#143) * * Register bring-your-own provider keys so the server can run prompts/evaluators. * The key is sent once on create and never displayed again (only the last 4). */ import React, { useState } from 'react'; import { useApi } from '../../hooks/useApi'; import { listConnections, createConnection, deleteConnection, testConnection, type LlmConnection, } from '../../api/llm-connections'; const PROVIDERS = ['openai', 'anthropic', 'azure', 'bedrock', 'vertex', 'custom']; export function LlmConnectionsTab(): React.ReactElement { const { data, loading, error, refetch } = useApi(() => listConnections(), []); const connections: LlmConnection[] = data?.connections ?? []; const [provider, setProvider] = useState('openai'); const [name, setName] = useState(''); const [apiKey, setApiKey] = useState(''); const [baseUrl, setBaseUrl] = useState(''); const [defaultModel, setDefaultModel] = useState(''); const [submitting, setSubmitting] = useState(false); const [formError, setFormError] = useState(null); const [testResult, setTestResult] = useState>({}); async function onCreate(e: React.FormEvent): Promise { e.preventDefault(); setSubmitting(true); setFormError(null); try { await createConnection({ provider, name: name.trim(), apiKey, baseUrl: baseUrl.trim() || undefined, defaultModel: defaultModel.trim() || undefined, }); setName(''); setApiKey(''); setBaseUrl(''); setDefaultModel(''); refetch(); } catch (err) { setFormError(err instanceof Error ? err.message : String(err)); } finally { setSubmitting(false); } } async function onTest(id: string): Promise { setTestResult((r) => ({ ...r, [id]: 'testing…' })); try { const res = await testConnection(id); setTestResult((r) => ({ ...r, [id]: res.ok ? `✓ ok (${res.model ?? 'model'})` : `✗ ${res.error ?? 'failed'}` })); } catch (err) { setTestResult((r) => ({ ...r, [id]: `✗ ${err instanceof Error ? err.message : String(err)}` })); } } async function onDelete(id: string): Promise { await deleteConnection(id); refetch(); } return (

Provider credentials the server uses to run prompts and evaluators (Playground, server-side scoring). Keys are encrypted at rest and never shown again — only the last 4 characters.

{/* Add form */}
{formError &&
{formError}
}
{/* List */} {loading &&
Loading…
} {error &&
Failed to load connections: {error}
} {!loading && connections.length === 0 &&
No connections yet.
} {connections.length > 0 && (
{connections.map((c) => (
{c.name} ({c.provider})
••••{c.keyLast4} {c.defaultModel && <> · {c.defaultModel}} {c.baseUrl && <> · {c.baseUrl}}
{testResult[c.id] &&
{testResult[c.id]}
}
))}
)}
); } export default LlmConnectionsTab;