import React from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { NumberInput } from '@/components/ui/number-input'; import { Textarea } from '@/components/ui/textarea'; import { toast } from '@/components/ui'; import { useAgentsStore, type AgentConfig, type AgentMutationResult, type AgentScope } from '@/stores/useAgentsStore'; import { useShallow } from 'zustand/react/shallow'; import { ModelSelector } from './ModelSelector'; import { useI18n } from '@/lib/i18n'; import { parseModelIdentifier } from '@/lib/modelIdentifier'; import { useConfigStore } from '@/stores/useConfigStore'; import { SettingsPageLayout } from '@/components/sections/shared/SettingsPageLayout'; import { SettingsSection, SettingsFieldRow, SettingsStackedField, SettingsChipGroup, SETTINGS_SELECT_SIZE, SETTINGS_SELECT_ROW_TRIGGER_CLASS, SETTINGS_ICON_BUTTON_CLASS, SETTINGS_CUSTOM_TRIGGER_CLASS, } from '@/components/sections/shared/SettingsSection'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Icon } from '@/components/icon/Icon'; import { AgentPermissionsEditor } from './AgentPermissionsEditor'; type AgentVariantProvider = { id: string; models?: Array<{ id?: string; variants?: Record; }>; }; const getVariantOptionsForModel = ( providers: AgentVariantProvider[], modelValue: string, ): string[] => { const parsedModel = parseModelIdentifier(modelValue); if (!parsedModel) { return []; } const provider = providers.find((item) => item.id === parsedModel.providerId); const model = provider?.models?.find((item) => item.id === parsedModel.modelId); return model?.variants ? Object.keys(model.variants) : []; }; export const AgentsPage: React.FC = () => { const { t } = useI18n(); const providers = useConfigStore((state) => state.providers) as AgentVariantProvider[]; const { selectedAgentName, getAgentByName, createAgent, updateAgent, agents, agentDraft, setAgentDraft, } = useAgentsStore(useShallow((s) => ({ selectedAgentName: s.selectedAgentName, getAgentByName: s.getAgentByName, createAgent: s.createAgent, updateAgent: s.updateAgent, agents: s.agents, agentDraft: s.agentDraft, setAgentDraft: s.setAgentDraft, }))); const selectedAgent = selectedAgentName ? getAgentByName(selectedAgentName) : null; const isNewAgent = Boolean(agentDraft && agentDraft.name === selectedAgentName && !selectedAgent); const [draftName, setDraftName] = React.useState(''); const [draftScope, setDraftScope] = React.useState('user'); const [description, setDescription] = React.useState(''); const [mode, setMode] = React.useState<'primary' | 'subagent' | 'all'>('subagent'); const [model, setModel] = React.useState(''); const [variant, setVariant] = React.useState(''); const [temperature, setTemperature] = React.useState(undefined); const [topP, setTopP] = React.useState(undefined); const [prompt, setPrompt] = React.useState(''); const [isSaving, setIsSaving] = React.useState(false); const initialStateRef = React.useRef<{ draftName: string; draftScope: AgentScope; description: string; mode: 'primary' | 'subagent' | 'all'; model: string; variant: string; temperature: number | undefined; topP: number | undefined; prompt: string; } | null>(null); const variantOptions = React.useMemo(() => getVariantOptionsForModel(providers, model), [model, providers]); const hasVariantOptions = variantOptions.length > 0; const selectedVariantValue = variant || '__default'; const shouldUseVariantSelect = hasVariantOptions; const variantSelectOptions = React.useMemo(() => ( variant && !variantOptions.includes(variant) ? [variant, ...variantOptions] : variantOptions ), [variant, variantOptions]); React.useEffect(() => { if (isNewAgent && agentDraft) { const draftNameValue = agentDraft.name || ''; const draftScopeValue = agentDraft.scope || 'user'; const descriptionValue = agentDraft.description || ''; const modeValue = agentDraft.mode || 'subagent'; const modelValue = agentDraft.model || ''; const variantValue = agentDraft.variant || ''; const temperatureValue = agentDraft.temperature ?? undefined; const topPValue = agentDraft.top_p ?? undefined; const promptValue = agentDraft.prompt || ''; setDraftName(draftNameValue); setDraftScope(draftScopeValue); setDescription(descriptionValue); setMode(modeValue); setModel(modelValue); setVariant(variantValue); setTemperature(temperatureValue); setTopP(topPValue); setPrompt(promptValue); initialStateRef.current = { draftName: draftNameValue, draftScope: draftScopeValue, description: descriptionValue, mode: modeValue, model: modelValue, variant: variantValue, temperature: temperatureValue, topP: topPValue, prompt: promptValue, }; return; } if (selectedAgent && selectedAgentName === selectedAgent.name) { const descriptionValue = selectedAgent.description || ''; const modeValue = selectedAgent.mode || 'subagent'; const modelValue = selectedAgent.model?.providerID && selectedAgent.model?.modelID ? `${selectedAgent.model.providerID}/${selectedAgent.model.modelID}` : ''; const variantValue = selectedAgent.variant || ''; const temperatureValue = selectedAgent.temperature ?? undefined; const topPValue = selectedAgent.topP ?? undefined; const promptValue = selectedAgent.prompt || ''; setDescription(descriptionValue); setMode(modeValue); setModel(modelValue); setVariant(variantValue); setTemperature(temperatureValue); setTopP(topPValue); setPrompt(promptValue); initialStateRef.current = { draftName: '', draftScope: 'user', description: descriptionValue, mode: modeValue, model: modelValue, variant: variantValue, temperature: temperatureValue, topP: topPValue, prompt: promptValue, }; } }, [agentDraft, isNewAgent, selectedAgent, selectedAgentName]); const isDirty = React.useMemo(() => { const initial = initialStateRef.current; if (!initial) { return false; } if (isNewAgent) { if (draftName !== initial.draftName) return true; if (draftScope !== initial.draftScope) return true; } if (description !== initial.description) return true; if (mode !== initial.mode) return true; if (model !== initial.model) return true; if (variant !== initial.variant) return true; if (temperature !== initial.temperature) return true; if (topP !== initial.topP) return true; if (prompt !== initial.prompt) return true; return false; }, [description, draftName, draftScope, isNewAgent, mode, model, prompt, temperature, topP, variant]); const handleSave = async () => { const agentName = isNewAgent ? draftName.trim().replace(/\s+/g, '-') : selectedAgentName?.trim(); if (!agentName) { toast.error(t('settings.agents.sidebar.toast.agentNameRequired')); return; } // Check for duplicate name when creating new agent if (isNewAgent && agents.some((a) => a.name === agentName)) { toast.error(t('settings.agents.sidebar.toast.agentExists')); return; } setIsSaving(true); try { const trimmedModel = model.trim(); const trimmedVariant = variant.trim(); const trimmedPrompt = prompt.trim(); const config: AgentConfig = { name: agentName, ...(description.trim() ? { description: description.trim() } : {}), mode, model: trimmedModel === '' ? null : trimmedModel, variant: trimmedVariant === '' ? null : trimmedVariant || undefined, temperature: temperature ?? null, top_p: topP ?? null, prompt: trimmedPrompt || (isNewAgent ? undefined : null), ...(isNewAgent && draftScope ? { scope: draftScope } : {}), }; let result: AgentMutationResult; if (isNewAgent) { result = await createAgent(config); if (result.ok) { setAgentDraft(null); // Clear draft after successful creation } } else { result = await updateAgent(agentName, config); } if (result.ok) { if (result.requiresManualRestart) { toast.warning(t('settings.agents.page.toast.savedManualRestart')); } else { toast.success(isNewAgent ? t('settings.agents.page.toast.created') : t('settings.agents.page.toast.updated')); } } else { toast.error(isNewAgent ? t('settings.agents.page.toast.createFailed') : t('settings.agents.page.toast.updateFailed')); } } catch (error) { console.error('Error saving agent:', error); const message = error instanceof Error && error.message ? error.message : t('settings.agents.page.toast.saveUnexpectedError'); toast.error(message); } finally { setIsSaving(false); } }; if (!selectedAgentName) { return (

{t('settings.agents.page.empty.title')}

{t('settings.agents.page.empty.description')}

); } return ( {isNewAgent && (
@ setDraftName(e.target.value)} placeholder={t('settings.agents.page.field.agentNamePlaceholder')} className="h-7 w-40 px-2" />
)}