diff --git a/src/components/panels/agent-detail-tabs.tsx b/src/components/panels/agent-detail-tabs.tsx index 3d573c0..10cfe61 100644 --- a/src/components/panels/agent-detail-tabs.tsx +++ b/src/components/panels/agent-detail-tabs.tsx @@ -1189,16 +1189,66 @@ export function ConfigTab({ const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const [jsonInput, setJsonInput] = useState('') + const [availableModels, setAvailableModels] = useState([]) + const [newFallbackModel, setNewFallbackModel] = useState('') useEffect(() => { setConfig(agent.config || {}) setJsonInput(JSON.stringify(agent.config || {}, null, 2)) }, [agent.config]) + useEffect(() => { + const loadAvailableModels = async () => { + try { + const response = await fetch('/api/status?action=models') + if (!response.ok) return + const data = await response.json() + const models = Array.isArray(data.models) ? data.models : [] + const names = models + .map((model: any) => String(model.name || model.alias || '').trim()) + .filter(Boolean) + setAvailableModels(Array.from(new Set(names))) + } catch { + // Ignore model suggestions if unavailable. + } + } + loadAvailableModels() + }, []) + + const updateModelConfig = (updater: (current: { primary?: string; fallbacks?: string[] }) => { primary?: string; fallbacks?: string[] }) => { + setConfig((prev: any) => { + const nextModel = updater({ ...(prev?.model || {}) }) + const dedupedFallbacks = [...new Set((nextModel.fallbacks || []).map((value) => value.trim()).filter(Boolean))] + return { + ...prev, + model: { + ...nextModel, + fallbacks: dedupedFallbacks, + }, + } + }) + } + + const addFallbackModel = () => { + const trimmed = newFallbackModel.trim() + if (!trimmed) return + updateModelConfig((current) => ({ + ...current, + fallbacks: [...(current.fallbacks || []), trimmed], + })) + setNewFallbackModel('') + } + const handleSave = async (writeToGateway: boolean = false) => { setSaving(true) setError(null) try { + if (!showJson) { + const primary = String(config?.model?.primary || '').trim() + if (!primary) { + throw new Error('Primary model is required') + } + } const response = await fetch(`/api/agents/${agent.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, @@ -1225,6 +1275,18 @@ export function ConfigTab({ const tools = config.tools || {} const subagents = config.subagents || {} const memorySearch = config.memorySearch || {} + const sandboxMode = sandbox.mode || sandbox.sandboxMode || sandbox.sandbox_mode || config.sandboxMode || 'not configured' + const sandboxWorkspace = sandbox.workspaceAccess || sandbox.workspace_access || sandbox.workspace || config.workspaceAccess || 'not configured' + const sandboxNetwork = sandbox?.docker?.network || sandbox.network || sandbox.dockerNetwork || sandbox.docker_network || 'none' + const identityName = identity.name || agent.name || 'not configured' + const identityTheme = identity.theme || agent.role || 'not configured' + const identityEmoji = identity.emoji || '?' + const identityPreview = identity.content || '' + const toolAllow = Array.isArray(tools.allow) ? tools.allow : [] + const toolDeny = Array.isArray(tools.deny) ? tools.deny : [] + const toolRawPreview = typeof tools.raw === 'string' ? tools.raw : '' + const modelPrimary = model.primary || '' + const modelFallbacks = Array.isArray(model.fallbacks) ? model.fallbacks : [] return (
@@ -1283,66 +1345,142 @@ export function ConfigTab({ {/* Model */}
Model
-
-
Primary: {model.primary || 'N/A'}
- {model.fallbacks && model.fallbacks.length > 0 && ( -
- Fallbacks: -
- {model.fallbacks.map((fb: string, i: number) => ( - {fb.split('/').pop()} + {editing ? ( +
+
+ + updateModelConfig((current) => ({ ...current, primary: e.target.value }))} + list="agent-model-suggestions" + placeholder="anthropic/claude-sonnet-4-20250514" + className="w-full bg-surface-1 text-foreground rounded px-3 py-2 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-primary/50" + /> + + {availableModels.map((name) => ( + +
+
+ +
+ {modelFallbacks.map((fallback: string, index: number) => ( +
+ { + const next = [...modelFallbacks] + next[index] = e.target.value + updateModelConfig((current) => ({ ...current, fallbacks: next })) + }} + list="agent-model-suggestions" + className="flex-1 bg-surface-1 text-foreground rounded px-3 py-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-primary/50" + /> + +
+ ))} +
+ setNewFallbackModel(e.target.value)} + list="agent-model-suggestions" + placeholder="Add fallback model" + className="flex-1 bg-surface-1 text-foreground rounded px-3 py-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-primary/50" + /> + +
- )} -
+
+ ) : ( +
+
Primary: {modelPrimary || 'not configured'}
+ {modelFallbacks.length > 0 && ( +
+ Fallbacks: +
+ {modelFallbacks.map((fb: string, i: number) => ( + {fb.split('/').pop()} + ))} +
+
+ )} +
+ )}
{/* Identity */}
Identity
- {identity.emoji || '?'} + {identityEmoji}
-
{identity.name || 'N/A'}
-
{identity.theme || 'N/A'}
+
{identityName}
+
{identityTheme}
+ {identityPreview && ( +
+                {identityPreview}
+              
+ )}
{/* Sandbox */}
Sandbox
-
Mode: {sandbox.mode || 'N/A'}
-
Workspace: {sandbox.workspaceAccess || 'N/A'}
-
Network: {sandbox.docker?.network || 'none'}
+
Mode: {sandboxMode}
+
Workspace: {sandboxWorkspace}
+
Network: {sandboxNetwork}
{/* Tools */}
Tools
- {tools.allow && tools.allow.length > 0 && ( + {toolAllow.length > 0 && (
- Allow ({tools.allow.length}): + Allow ({toolAllow.length}):
- {tools.allow.map((tool: string) => ( + {toolAllow.map((tool: string) => ( {tool} ))}
)} - {tools.deny && tools.deny.length > 0 && ( + {toolDeny.length > 0 && (
- Deny ({tools.deny.length}): + Deny ({toolDeny.length}):
- {tools.deny.map((tool: string) => ( + {toolDeny.map((tool: string) => ( {tool} ))}
)} + {toolAllow.length === 0 && toolDeny.length === 0 && !toolRawPreview && ( +
No tools configured
+ )} + {toolRawPreview && ( +
+                {toolRawPreview}
+              
+ )}
{/* Subagents */}