"use client"; import { useCallback, useEffect, useMemo, useState, type ChangeEvent, type ReactElement, type SyntheticEvent, } from "react"; import { FleetSettingsNav } from "@/components/settings/FleetSettingsNav"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; import { fetchFleetAgentConfig, fetchFleetProviders, updateFleetAgentConfig, type FleetProvider, type FleetProviderModel, type UpdateFleetAgentConfigRequest, } from "@/lib/api/fleet-settings"; function getErrorMessage(error: unknown, fallback: string): string { if (error instanceof Error && error.message.trim().length > 0) { return error.message; } return fallback; } function normalizeProviderModels(models: unknown): FleetProviderModel[] { if (!Array.isArray(models)) { return []; } const parsed: FleetProviderModel[] = []; models.forEach((entry) => { if (typeof entry === "string" && entry.trim().length > 0) { parsed.push({ id: entry.trim(), name: entry.trim() }); return; } if (entry && typeof entry === "object") { const record = entry as Record; const id = typeof record.id === "string" ? record.id.trim() : typeof record.name === "string" ? record.name.trim() : ""; if (id.length > 0) { parsed.push({ id, name: id }); } } }); const seen = new Set(); return parsed.filter((model) => { if (seen.has(model.id)) { return false; } seen.add(model.id); return true; }); } function parseModelList(value: string): string[] { const seen = new Set(); return value .split(/\n|,/g) .map((segment) => segment.trim()) .filter((segment) => segment.length > 0) .filter((segment) => { if (seen.has(segment)) { return false; } seen.add(segment); return true; }); } function deriveAvailableModels(providers: FleetProvider[]): string[] { const seen = new Set(); const models: string[] = []; providers.forEach((provider) => { normalizeProviderModels(provider.models).forEach((model) => { if (seen.has(model.id)) { return; } seen.add(model.id); models.push(model.id); }); }); return models.sort((left, right) => left.localeCompare(right)); } export default function AgentConfigSettingsPage(): ReactElement { const [providers, setProviders] = useState([]); const [primaryModel, setPrimaryModel] = useState(""); const [fallbackModelsText, setFallbackModelsText] = useState(""); const [personality, setPersonality] = useState(""); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const availableModels = useMemo(() => deriveAvailableModels(providers), [providers]); const fallbackModels = useMemo(() => parseModelList(fallbackModelsText), [fallbackModelsText]); const modelSelectOptions = useMemo(() => { if (primaryModel.length > 0 && !availableModels.includes(primaryModel)) { return [primaryModel, ...availableModels]; } return availableModels; }, [availableModels, primaryModel]); const loadSettings = useCallback(async (): Promise => { setIsLoading(true); try { const [providerData, agentConfig] = await Promise.all([ fetchFleetProviders(), fetchFleetAgentConfig(), ]); setProviders(providerData); setPrimaryModel(agentConfig.primaryModel ?? ""); setFallbackModelsText(agentConfig.fallbackModels.join("\n")); setPersonality(agentConfig.personality ?? ""); setError(null); } catch (loadError: unknown) { setError(getErrorMessage(loadError, "Failed to load agent configuration.")); } finally { setIsLoading(false); } }, []); useEffect(() => { void loadSettings(); }, [loadSettings]); function appendFallbackModel(model: string): void { const current = parseModelList(fallbackModelsText); if (current.includes(model)) { return; } const next = [...current, model]; setFallbackModelsText(next.join("\n")); } async function handleSave(event: SyntheticEvent): Promise { event.preventDefault(); setError(null); setSuccessMessage(null); const updatePayload: UpdateFleetAgentConfigRequest = { personality: personality.trim(), }; if (primaryModel.trim().length > 0) { updatePayload.primaryModel = primaryModel.trim(); } const parsedFallbacks = parseModelList(fallbackModelsText).filter( (model) => model !== primaryModel.trim() ); if (parsedFallbacks.length > 0) { updatePayload.fallbackModels = parsedFallbacks; } try { setIsSaving(true); await updateFleetAgentConfig(updatePayload); setSuccessMessage("Agent configuration saved."); await loadSettings(); } catch (saveError: unknown) { setError(getErrorMessage(saveError, "Failed to save agent configuration.")); } finally { setIsSaving(false); } } return (

Agent Configuration

Assign primary and fallback models for your agent runtime behavior.

Current Assignment Snapshot of your currently saved model routing configuration. {isLoading ? (

Loading configuration...

) : ( <>

Primary Model

{primaryModel.length > 0 ? primaryModel : "No primary model configured"}

Fallback Models

{fallbackModels.length === 0 ? (

No fallback models configured

) : (
{fallbackModels.map((model) => ( {model} ))}
)}
)}
Update Agent Config Select a primary model and define fallback ordering. Models come from your provider settings.
void handleSave(event)} className="space-y-5">
{availableModels.length === 0 ? (

No models available yet. Add provider models first in Providers settings.

) : null}