| import { useEffect, useMemo, useState } from 'react' |
| import { Check } from 'lucide-react' |
| import Modal from './Modal' |
| import Button from './Button' |
|
|
| export default function ModelDialog({ |
| open, |
| onOpenChange, |
| registry, |
| currentProvider, |
| currentModel, |
| onSelect, |
| }) { |
| const providerIds = useMemo(() => Object.keys(registry || {}), [registry]) |
| const [activeProvider, setActiveProvider] = useState(currentProvider || providerIds[0] || 'groq') |
| const provider = registry?.[activeProvider] |
| const models = provider?.models || [] |
| const providerEnabled = provider?.enabled !== false |
|
|
| useEffect(() => { |
| if (!open || !providerIds.length) return |
| setActiveProvider(currentProvider || providerIds[0]) |
| }, [open, currentProvider, providerIds]) |
|
|
| return ( |
| <Modal |
| open={open} |
| onOpenChange={onOpenChange} |
| size="lg" |
| title="Models" |
| > |
| <div className="grid gap-4 lg:grid-cols-[200px_minmax(0,1fr)]"> |
| <div className="space-y-2"> |
| {providerIds.map((providerId) => ( |
| <button |
| key={providerId} |
| type="button" |
| onClick={() => setActiveProvider(providerId)} |
| className={`w-full rounded-lg border px-3 py-2.5 text-left text-sm transition ${ |
| activeProvider === providerId |
| ? 'border-border bg-secondary text-foreground' |
| : 'border-border bg-panel text-muted-foreground hover:bg-secondary hover:text-foreground' |
| }`} |
| > |
| <div className="flex items-center justify-between gap-2"> |
| <p className="truncate font-medium">{registry[providerId].label}</p> |
| <span |
| className={`h-2 w-2 rounded-full ${ |
| registry[providerId].enabled !== false |
| ? 'bg-accent' |
| : 'bg-danger' |
| }`} |
| aria-label={registry[providerId].enabled !== false ? 'Connected' : 'Setup needed'} |
| /> |
| </div> |
| </button> |
| ))} |
| </div> |
| |
| <div className="space-y-3"> |
| {providerEnabled ? null : ( |
| <div className="rounded-lg border border-danger/25 bg-danger/5 px-4 py-3 text-sm text-muted-foreground"> |
| {provider?.reason || 'This provider is not configured for the current deployment.'} |
| </div> |
| )} |
| {models.map((model) => { |
| const active = currentProvider === activeProvider && currentModel === model.id |
| return ( |
| <button |
| key={model.id} |
| type="button" |
| disabled={!providerEnabled} |
| onClick={() => { |
| if (!providerEnabled) return |
| onSelect(activeProvider, model.id) |
| onOpenChange(false) |
| }} |
| className={`w-full rounded-lg border px-4 py-3 text-left transition disabled:cursor-not-allowed disabled:opacity-60 ${ |
| active |
| ? 'border-border bg-secondary' |
| : 'border-border bg-panel hover:bg-secondary' |
| }`} |
| > |
| <div className="flex items-center justify-between gap-3"> |
| <div className="min-w-0"> |
| <p className="truncate font-medium text-foreground">{model.name}</p> |
| <p className="truncate text-sm text-muted-foreground">{model.id}</p> |
| </div> |
| {active ? ( |
| <div className="rounded-lg bg-accent p-2 text-accent-foreground"> |
| <Check className="h-4 w-4" /> |
| </div> |
| ) : null} |
| </div> |
| </button> |
| ) |
| })} |
| {!models.length ? ( |
| <div className="rounded-lg border border-border bg-panel px-4 py-10 text-center text-sm text-muted-foreground"> |
| No models available for this provider. |
| </div> |
| ) : null} |
| </div> |
| </div> |
| <div className="mt-5 flex justify-end"> |
| <Button variant="ghost" onClick={() => onOpenChange(false)}> |
| Close |
| </Button> |
| </div> |
| </Modal> |
| ) |
| } |
|
|