import { useEffect, useState } from 'react'; import { Check, KeyRound, Plus, Trash2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import type { ProviderStatus } from '../lib/api'; import type { ProvidersManager } from './useProviders'; /** * Providers, as one line at the top of the Generate panel. * * It was a whole panel — a card per provider, each with its own key field and three buttons, filling * a quarter of the screen for something set once and then ignored. What it actually has to answer is * "which one is this generation going to use", and that is a dropdown. * * Adding one opens a dialog rather than expanding the menu: it is three fields, one of them a secret, * and a menu that turns into a form cannot be dismissed by clicking away without losing what was typed. */ export const ProvidersBar = ({ manager }: { manager: ProvidersManager }) => { const [dialog, setDialog] = useState(null); const active = manager.providers.find((row) => row.is_default && row.configured && row.enabled) ?? manager.providers.find((row) => row.configured && row.enabled) ?? manager.providers[0]; return (
{manager.providers.length === 0 && ( No providers configured )} {manager.providers.map((provider) => ( void manager.makeDefault(provider.id)} className="gap-2" > {provider.id === active?.id ? : } {label(provider)} {!provider.configured && ( )} ))} setDialog({ mode: 'add' })} className="gap-2"> Add provider {manager.error && {manager.error}} setDialog(null)} />
); }; const label = (provider: ProviderStatus) => `${provider.adapter} · ${provider.model}`; type DialogState = null | { mode: 'add' } | { mode: 'key'; provider: ProviderStatus }; /** Add a provider, or set the key on one that has none. Same dialog, since the key field is shared. */ const ProviderDialog = ({ state, manager, onClose, }: { state: DialogState; manager: ProvidersManager; onClose: () => void; }) => { const [adapter, setAdapter] = useState(''); const [model, setModel] = useState(''); const [apiKey, setApiKey] = useState(''); // Seeded when the dialog opens, not on every render: the adapter list arrives asynchronously, and // resetting on each render would fight the user's own choice as soon as it did. useEffect(() => { if (state?.mode !== 'add') return; const first = manager.adapters[0]; setAdapter(first?.name ?? ''); setModel(first?.default_model ?? ''); setApiKey(''); }, [state, manager.adapters]); useEffect(() => { if (state?.mode === 'key') setApiKey(''); }, [state]); const submit = async () => { if (!state) return; const ok = state.mode === 'add' ? await manager.add(adapter, model, apiKey) : apiKey.trim() ? await manager.setKey(state.provider.id, apiKey) : false; if (ok) onClose(); }; return ( !next && onClose()}> {state?.mode === 'key' ? 'Set API key' : 'Add a provider'} {state?.mode === 'key' ? `${label(state.provider)} — the key is stored encrypted and never sent back.` : 'An adapter and a model. The same adapter can back several providers — two keys, two accounts.'}
{ ev.preventDefault(); void submit(); }} > {state?.mode === 'add' && ( <> )}
); };