import * as React from "react"; import { Activity, CheckCircle2, Cpu, FlaskConical, Plus, Power, RefreshCw, Save, Trash2, } from "lucide-react"; import { createModel, deleteModel, listModels, testModel, updateModel, updateModelStatus, } from "@/api"; import { ApiErrorState } from "@/components/shared/ApiErrorState"; import { EmptyState } from "@/components/shared/EmptyState"; import { EntityListItem } from "@/components/shared/EntityListItem"; import { LoadingSpinner } from "@/components/shared/LoadingSpinner"; import { MetricCard } from "@/components/shared/MetricCard"; import { PageHeader } from "@/components/shared/PageHeader"; import { SearchInput } from "@/components/shared/SearchInput"; import { StatusBadge } from "@/components/shared/StatusBadge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Dialog } from "@/components/ui/dialog"; import { Input, Textarea } from "@/components/ui/input"; import { Select } from "@/components/ui/select"; import { toast } from "@/components/ui/toaster"; import { formatDateTime } from "@/lib/utils"; import type { ModelCreateRequest, ModelDefinition, ModelStatus } from "@/types"; type ModelFormState = { code: string; name: string; provider_type: string; provider_base_url: string; provider_api_key: string; model_name: string; status: ModelStatus; description: string; capabilities: string; context_window: string; max_output_tokens: string; default_temperature: string; timeout_seconds: string; }; const emptyForm: ModelFormState = { code: "", name: "", provider_type: "openai_compatible", provider_base_url: "http://127.0.0.1:11434/v1", provider_api_key: "", model_name: "", status: "active", description: "", capabilities: "chat", context_window: "", max_output_tokens: "", default_temperature: "", timeout_seconds: "60", }; export function ModelsPage() { const [models, setModels] = React.useState([]); const [selectedId, setSelectedId] = React.useState(); const [search, setSearch] = React.useState(""); const [statusFilter, setStatusFilter] = React.useState("all"); const [loading, setLoading] = React.useState(true); const [saving, setSaving] = React.useState(false); const [testing, setTesting] = React.useState(false); const [error, setError] = React.useState(); const [createOpen, setCreateOpen] = React.useState(false); const [form, setForm] = React.useState(emptyForm); const [testPrompt, setTestPrompt] = React.useState("Say OK in one short sentence."); const [testOutput, setTestOutput] = React.useState(); const selected = models.find((model) => model.id === selectedId); const providers = Array.from(new Set(models.map((model) => model.provider_type))).sort(); const activeCount = models.filter((model) => model.status === "active").length; const chatReadyCount = models.filter((model) => model.capabilities_json.includes("chat")).length; const filtered = models.filter((model) => { const haystack = `${model.name} ${model.code} ${model.model_name} ${model.provider_type}`.toLowerCase(); const matchesSearch = haystack.includes(search.toLowerCase()); const matchesStatus = statusFilter === "all" || model.status === statusFilter; return matchesSearch && matchesStatus; }); const load = React.useCallback(async () => { setLoading(true); setError(undefined); try { const data = await listModels(); setModels(data); setSelectedId((current) => current ?? data[0]?.id); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load models"); } finally { setLoading(false); } }, []); React.useEffect(() => { void load(); }, [load]); React.useEffect(() => { if (selected) setForm(fromModel(selected)); }, [selected]); async function createFromDialog(payload: ModelCreateRequest) { const created = await createModel(payload); setModels((current) => [created, ...current]); setSelectedId(created.id); setCreateOpen(false); toast.success("Model created"); } async function saveSelected() { if (!selected) return; setSaving(true); try { const payload = toPayload(form); if (!form.provider_api_key.trim()) delete payload.provider_api_key; const updated = await updateModel(selected.id, payload); setModels((current) => current.map((model) => (model.id === updated.id ? updated : model))); toast.success("Model saved"); } catch (err) { toast.error(err instanceof Error ? err.message : "Failed to save model"); } finally { setSaving(false); } } async function toggleSelected() { if (!selected) return; const nextStatus: ModelStatus = selected.status === "active" ? "disabled" : "active"; const updated = await updateModelStatus(selected.id, nextStatus); setModels((current) => current.map((model) => (model.id === updated.id ? updated : model))); toast.success(nextStatus === "active" ? "Model enabled" : "Model disabled"); } async function deleteSelected() { if (!selected) return; await deleteModel(selected.id); setModels((current) => current.filter((model) => model.id !== selected.id)); setSelectedId(models.find((model) => model.id !== selected.id)?.id); setTestOutput(undefined); toast.success("Model deleted"); } async function runTest() { if (!selected) return; setTesting(true); setTestOutput(undefined); try { const result = await testModel(selected.id, { prompt: testPrompt, max_tokens: 128 }); setTestOutput(result.response.content || JSON.stringify(result.response.raw_response_json, null, 2)); toast.success("Model test completed"); } catch (err) { setTestOutput(err instanceof Error ? err.message : "Model test failed"); toast.error("Model test failed"); } finally { setTesting(false); } } if (loading) return ; if (error) return void load()} />; return (
} />
Model Catalog {filtered.length} of {models.length} shown