import { useState, useRef, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { apiFetch } from '@/lib/api' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Switch } from '@/components/ui/switch' import { PageHeader } from '@/components/page-header' import type { ApiKey, Platform } from '../../../shared/types' import { Pencil, ExternalLink } from 'lucide-react' import { formatSqliteUtcToLocalTime } from '@/lib/utils' // Small "Get API key" external link shown next to a provider (#137). function GetKeyLink({ url }: { url: string }) { if (!url) return null return ( Get API key ) } // `url` points to each provider's key-management / signup page so the Keys page // can show a "Get API key" shortcut (#137). OpenCode Zen's key is free from // opencode.ai/auth — no card needed; billing only applies to paid models (#128). // `keyless: true` providers (Kilo's anonymous free tier) need no API key — the // form disables the key field and submits a sentinel the backend stores so // routing treats the platform as configured. const PLATFORMS: { value: Platform; label: string; url: string; keyless?: boolean }[] = [ { value: 'google', label: 'Google AI Studio', url: 'https://aistudio.google.com/apikey' }, { value: 'groq', label: 'Groq', url: 'https://console.groq.com/keys' }, { value: 'cerebras', label: 'Cerebras', url: 'https://cloud.cerebras.ai' }, { value: 'nvidia', label: 'NVIDIA NIM', url: 'https://build.nvidia.com/settings/api-keys' }, { value: 'mistral', label: 'Mistral', url: 'https://console.mistral.ai/api-keys/' }, { value: 'openrouter', label: 'OpenRouter', url: 'https://openrouter.ai/keys' }, { value: 'github', label: 'GitHub Models', url: 'https://github.com/settings/tokens' }, { value: 'cohere', label: 'Cohere', url: 'https://dashboard.cohere.com/api-keys' }, { value: 'cloudflare', label: 'Cloudflare Workers AI', url: 'https://dash.cloudflare.com' }, { value: 'zhipu', label: 'Zhipu AI (Z.ai)', url: 'https://z.ai/manage-apikey/apikey-list' }, { value: 'ollama', label: 'Ollama Cloud', url: 'https://ollama.com/settings/keys' }, { value: 'kilo', label: 'Kilo Gateway (no key needed)', url: 'https://app.kilo.ai', keyless: true }, { value: 'pollinations', label: 'Pollinations (anon ok)', url: 'https://pollinations.ai' }, { value: 'llm7', label: 'LLM7 (anon ok)', url: 'https://llm7.io' }, { value: 'huggingface', label: 'HuggingFace Router', url: 'https://huggingface.co/settings/tokens' }, { value: 'opencode', label: 'OpenCode Zen (free key)', url: 'https://opencode.ai/auth' }, ] // 'custom' is configured through its own form (base URL + model), not the // generic key dropdown — but it still appears in the grouped provider list. const CUSTOM_GROUP: { value: Platform; label: string; url: string } = { value: 'custom', label: 'Custom (OpenAI-compatible)', url: '', } const statusDot: Record = { healthy: 'bg-emerald-500', rate_limited: 'bg-amber-500', invalid: 'bg-rose-500', error: 'bg-rose-500', unknown: 'bg-muted-foreground/40', } const statusLabel: Record = { healthy: 'healthy', rate_limited: 'rate-limited', invalid: 'invalid', error: 'error', unknown: 'unchecked', } interface HealthPlatform { platform: string totalKeys: number healthyKeys: number rateLimitedKeys: number invalidKeys: number errorKeys: number unknownKeys: number } interface HealthData { platforms: HealthPlatform[] keys: { id: number; platform: string; status: string; lastCheckedAt: string | null }[] } function UnifiedKeySection() { const queryClient = useQueryClient() const [showKey, setShowKey] = useState(false) const [copied, setCopied] = useState(false) const { data, isError } = useQuery<{ apiKey: string }>({ queryKey: ['unified-key'], queryFn: () => apiFetch('/api/settings/api-key'), }) const regenerate = useMutation({ mutationFn: () => apiFetch('/api/settings/api-key/regenerate', { method: 'POST' }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['unified-key'] }), }) const apiKey = data?.apiKey ?? '' const masked = apiKey ? apiKey.slice(0, 13) + '•'.repeat(32) : '…' const baseUrl = import.meta.env.DEV ? `http://${window.location.hostname}:${__SERVER_PORT__}/v1` : `${window.location.origin}/v1` function copy() { navigator.clipboard.writeText(apiKey) setCopied(true) setTimeout(() => setCopied(false), 1500) } return (

Your unified API key

Use this as your OpenAI api_key; it authenticates requests to this proxy.

{isError ? (
Can't reach the server on {baseUrl.replace('/v1', '')}. Make sure the backend is running. npm run dev starts both, and the server logs print under the server prefix.
) : (
{showKey ? apiKey : masked}
)}
Base URL {baseUrl} Chat /v1/chat/completions Responses /v1/responses Embeddings /v1/embeddings (model: "auto" or a family from the Embeddings tab)
) } function CustomProviderSection() { const queryClient = useQueryClient() const [baseUrl, setBaseUrl] = useState('') const [model, setModel] = useState('') const [displayName, setDisplayName] = useState('') const [apiKey, setApiKey] = useState('') const addCustom = useMutation({ mutationFn: (body: { baseUrl: string; model: string; displayName?: string; apiKey?: string }) => apiFetch('/api/keys/custom', { method: 'POST', body: JSON.stringify(body) }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['keys'] }) queryClient.invalidateQueries({ queryKey: ['health'] }) queryClient.invalidateQueries({ queryKey: ['fallback'] }) queryClient.invalidateQueries({ queryKey: ['models'] }) setModel('') setDisplayName('') }, }) const submit = (e: React.FormEvent) => { e.preventDefault() if (!baseUrl || !model) return addCustom.mutate({ baseUrl, model, displayName: displayName || undefined, apiKey: apiKey || undefined }) } return (

Add a custom OpenAI-compatible model

Point at any OpenAI-compatible endpoint: llama.cpp, LM Studio, vLLM, a local Ollama, or a remote gateway. Add each model you want routed; they all share the one endpoint. The API key is optional (most local servers don't need one).

setBaseUrl(e.target.value)} placeholder="http://127.0.0.1:11434/v1" className="font-mono text-xs" />
setModel(e.target.value)} placeholder="qwen3:4b" className="w-[180px] font-mono text-xs" />
setDisplayName(e.target.value)} placeholder="optional" className="w-[150px]" />
setApiKey(e.target.value)} placeholder="optional" className="w-[150px] font-mono text-xs" />
{addCustom.isError && (

{(addCustom.error as Error).message}

)}
) } export default function KeysPage() { const queryClient = useQueryClient() const [platform, setPlatform] = useState('') const [apiKey, setApiKey] = useState('') const [accountId, setAccountId] = useState('') const [label, setLabel] = useState('') const [editingKeyId, setEditingKeyId] = useState(null) const [editingLabel, setEditingLabel] = useState('') const editInputRef = useRef(null) const { data: keys = [], isLoading } = useQuery({ queryKey: ['keys'], queryFn: () => apiFetch('/api/keys'), }) const { data: healthData } = useQuery({ queryKey: ['health'], queryFn: () => apiFetch('/api/health'), refetchInterval: 30000, }) const addKey = useMutation({ mutationFn: (body: { platform: string; key: string; label?: string }) => apiFetch('/api/keys', { method: 'POST', body: JSON.stringify(body) }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['keys'] }) queryClient.invalidateQueries({ queryKey: ['health'] }) queryClient.invalidateQueries({ queryKey: ['fallback'] }) setPlatform('') setApiKey('') setAccountId('') setLabel('') }, }) const deleteKey = useMutation({ mutationFn: (id: number) => apiFetch(`/api/keys/${id}`, { method: 'DELETE' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['keys'] }) queryClient.invalidateQueries({ queryKey: ['health'] }) }, }) const checkAll = useMutation({ mutationFn: () => apiFetch('/api/health/check-all', { method: 'POST' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['health'] }) queryClient.invalidateQueries({ queryKey: ['keys'] }) }, }) const checkKey = useMutation({ mutationFn: (keyId: number) => apiFetch(`/api/health/check/${keyId}`, { method: 'POST' }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['health'] }) queryClient.invalidateQueries({ queryKey: ['keys'] }) }, }) const togglePlatform = useMutation({ mutationFn: ({ platform, enabled }: { platform: string; enabled: boolean }) => apiFetch(`/api/keys/platform/${platform}`, { method: 'PATCH', body: JSON.stringify({ enabled }), }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['keys'] }) queryClient.invalidateQueries({ queryKey: ['health'] }) queryClient.invalidateQueries({ queryKey: ['fallback'] }) }, }) const updateKey = useMutation({ mutationFn: ({ id, label }: { id: number; label: string }) => apiFetch(`/api/keys/${id}`, { method: 'PATCH', body: JSON.stringify({ label }), }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['keys'] }) setEditingKeyId(null) setEditingLabel('') }, }) function startEditing(key: ApiKey) { setEditingKeyId(key.id) setEditingLabel(key.label) } function cancelEditing() { setEditingKeyId(null) setEditingLabel('') } function saveEditing(id: number) { if (editingLabel !== undefined) { updateKey.mutate({ id, label: editingLabel }) } } useEffect(() => { if (editingKeyId !== null && editInputRef.current) { editInputRef.current.focus() } }, [editingKeyId]) const needsAccountId = platform === 'cloudflare' const isKeyless = PLATFORMS.find(p => p.value === platform)?.keyless ?? false const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!platform) return if (!isKeyless && !apiKey) return if (needsAccountId && !accountId) return // Keyless providers submit an empty key; the backend stores a sentinel. const key = isKeyless ? '' : (needsAccountId ? `${accountId}:${apiKey}` : apiKey) addKey.mutate({ platform, key, label: label || undefined }) } const healthKeyMap = new Map() for (const k of healthData?.keys ?? []) healthKeyMap.set(k.id, k) const grouped = [...PLATFORMS, CUSTOM_GROUP].map(p => ({ ...p, keys: keys.filter(k => k.platform === p.value), })).filter(p => p.keys.length > 0) return (
0 && ( ) } />

Add a provider key

{(() => { const sel = PLATFORMS.find(p => p.value === platform) return sel?.url ?
: null })()}
{needsAccountId && (
setAccountId(e.target.value)} placeholder="a1b2c3d4…" className="w-[200px] font-mono text-xs" />
)}
setApiKey(e.target.value)} placeholder={isKeyless ? 'No API key needed' : (needsAccountId ? 'Bearer token' : 'paste key here')} className="font-mono text-xs" disabled={isKeyless} /> {isKeyless && (

No API key needed: this provider's free tier is anonymous (rate-limited per IP).

)}
setLabel(e.target.value)} placeholder="optional" className="w-[160px]" />
{addKey.isError && (

{(addKey.error as Error).message}

)}

Configured providers

{isLoading ? (

Loading…

) : keys.length === 0 ? (

No provider keys yet. Add one above to start routing.

) : (
{grouped.map(group => (
k.enabled)} onCheckedChange={(checked) => togglePlatform.mutate({ platform: group.value, enabled: checked }) } disabled={togglePlatform.isPending} />

{group.label}

{group.keys.length} key{group.keys.length === 1 ? '' : 's'}
{group.keys.map(k => { const h = healthKeyMap.get(k.id) const status = h?.status ?? k.status const lastChecked = h?.lastCheckedAt const isEditing = editingKeyId === k.id return (
{k.maskedKey} {isEditing ? ( setEditingLabel(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') saveEditing(k.id) if (e.key === 'Escape') cancelEditing() }} onBlur={() => saveEditing(k.id)} className="h-6 w-[160px] text-xs" disabled={updateKey.isPending} /> ) : ( <> {k.label && {k.label}} )} {statusLabel[status] ?? status}
{lastChecked && ( {formatSqliteUtcToLocalTime(lastChecked, { hour: '2-digit', minute: '2-digit' })} )} {!isEditing && ( )}
) })}
))}
)}
) }