'use client'; import React, { useCallback, useEffect, useState } from 'react'; import { AlertCircle, Bot, ChevronDown, Loader2, Pencil, Plus, RefreshCw, Tags, Trash2, } from 'lucide-react'; import { addAgent, createSubagent, deleteAgent, deleteSubagent, listAgents, listSubagents, refreshAgents, updateSubagent, } from '@/lib/api'; import { useChatStore } from '@/lib/store'; import type { UiAgentDescriptor, UiSubagentDescriptor } from '@/types'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Textarea } from '@/components/ui/textarea'; const EMPTY_AGENT_FORM = { id: '', name: '', description: '', base_url: '', endpoint: '', card_url: '', auth_env: '', auth_mode: 'none', auth_audience: '', auth_scopes: '', tags: '', aliases: '', }; const EMPTY_SUBAGENT_FORM = { id: '', name: '', description: '', system_prompt: '', model: '', delegation_mode: 'remote_a2a_only', enabled: true, allow_mcp: true, tags: '', aliases: '', metadata_json: '{}', mcp_servers_json: '{}', }; function formatJson(value: Record): string { return JSON.stringify(value, null, 2); } function parseJsonObject(raw: string, label: string): Record { const probe = raw.trim(); if (!probe) { return {}; } let parsed: unknown; try { parsed = JSON.parse(probe); } catch { throw new Error(`${label} 需要是合法 JSON`); } if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { throw new Error(`${label} 需要是 JSON 对象`); } return parsed as Record; } function parseNestedJsonObject(raw: string, label: string): Record> { const parsed = parseJsonObject(raw, label); for (const [key, value] of Object.entries(parsed)) { if (!value || typeof value !== 'object' || Array.isArray(value)) { throw new Error(`${label} 中的 ${key} 必须是 JSON 对象`); } } return parsed as Record>; } export default function AgentsPage() { const cachedAgents = useChatStore((s) => s.agentRegistry); const setCachedAgents = useChatStore((s) => s.setAgentRegistry); const [agents, setAgents] = useState(cachedAgents); const [subagents, setSubagents] = useState([]); const [loading, setLoading] = useState(cachedAgents.length === 0); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [agentDialogOpen, setAgentDialogOpen] = useState(false); const [subagentDialogOpen, setSubagentDialogOpen] = useState(false); const [agentSubmitting, setAgentSubmitting] = useState(false); const [subagentSubmitting, setSubagentSubmitting] = useState(false); const [agentAdvancedOpen, setAgentAdvancedOpen] = useState(false); const [subagentAdvancedOpen, setSubagentAdvancedOpen] = useState(false); const [agentForm, setAgentForm] = useState(EMPTY_AGENT_FORM); const [subagentForm, setSubagentForm] = useState(EMPTY_SUBAGENT_FORM); const [editingSubagentId, setEditingSubagentId] = useState(null); const load = useCallback(async (background = false) => { if (background) { setRefreshing(true); } else { setLoading(true); } setError(null); try { const [agentData, subagentData] = await Promise.all([ listAgents(), listSubagents(), ]); const nextAgents = Array.isArray(agentData) ? agentData : []; const nextSubagents = Array.isArray(subagentData) ? subagentData : []; setAgents(nextAgents); setSubagents(nextSubagents); setCachedAgents(nextAgents); } catch (err: any) { setError(err.message || '加载智能体失败'); } finally { if (background) { setRefreshing(false); } else { setLoading(false); } } }, [setCachedAgents]); useEffect(() => { void load(cachedAgents.length > 0); }, [cachedAgents.length, load]); const handleRefresh = async () => { setError(null); setRefreshing(true); try { const [agentData, subagentData] = await Promise.all([ refreshAgents(), listSubagents(), ]); const nextAgents = agentData.agents || []; const nextSubagents = Array.isArray(subagentData) ? subagentData : []; setAgents(nextAgents); setSubagents(nextSubagents); setCachedAgents(nextAgents); } catch (err: any) { setError(err.message || '刷新智能体失败'); } finally { setRefreshing(false); } }; const handleAgentDialogOpenChange = (open: boolean) => { setAgentDialogOpen(open); if (!open) { setAgentAdvancedOpen(false); setAgentForm(EMPTY_AGENT_FORM); } }; const handleSubagentDialogOpenChange = (open: boolean) => { setSubagentDialogOpen(open); if (!open) { setSubagentAdvancedOpen(false); setEditingSubagentId(null); setSubagentForm(EMPTY_SUBAGENT_FORM); } }; const handleCreateAgent = async (e: React.FormEvent) => { e.preventDefault(); const hasAddress = [agentForm.base_url, agentForm.endpoint, agentForm.card_url].some((value) => value.trim()); if (!hasAddress) { setError('请至少填写 A2A 部署地址、接口地址或卡片地址'); return; } setAgentSubmitting(true); setError(null); try { await addAgent({ id: agentForm.id || undefined, name: agentForm.name || undefined, description: agentForm.description || undefined, protocol: 'a2a', base_url: agentForm.base_url || undefined, endpoint: agentForm.endpoint || undefined, card_url: agentForm.card_url || undefined, auth_env: agentForm.auth_env || undefined, auth_mode: agentForm.auth_mode || 'none', auth_audience: agentForm.auth_mode === 'none' ? undefined : agentForm.auth_audience || undefined, auth_scopes: agentForm.auth_mode === 'none' ? [] : agentForm.auth_scopes.split(',').map((item) => item.trim()).filter(Boolean), tags: agentForm.tags.split(',').map((item) => item.trim()).filter(Boolean), aliases: agentForm.aliases.split(',').map((item) => item.trim()).filter(Boolean), }); handleAgentDialogOpenChange(false); await load(true); } catch (err: any) { setError(err.message || '新增智能体失败'); } finally { setAgentSubmitting(false); } }; const handleDeleteAgent = async (agentId: string) => { try { await deleteAgent(agentId); await load(true); } catch (err: any) { setError(err.message || '删除智能体失败'); } }; const handleEditSubagent = (subagent: UiSubagentDescriptor) => { setEditingSubagentId(subagent.id); setSubagentForm({ id: subagent.id, name: subagent.name, description: subagent.description, system_prompt: subagent.system_prompt || '', model: subagent.model || '', delegation_mode: subagent.delegation_mode || 'remote_a2a_only', enabled: subagent.enabled, allow_mcp: subagent.allow_mcp, tags: (subagent.tags || []).join(', '), aliases: (subagent.aliases || []).join(', '), metadata_json: formatJson(subagent.metadata || {}), mcp_servers_json: formatJson(subagent.mcp_servers || {}), }); setSubagentDialogOpen(true); }; const handleSaveSubagent = async (e: React.FormEvent) => { e.preventDefault(); if (!subagentForm.id.trim()) { setError('Sub-agent ID 不能为空'); return; } setSubagentSubmitting(true); setError(null); try { const payload = { id: subagentForm.id.trim(), name: subagentForm.name.trim() || subagentForm.id.trim(), description: subagentForm.description.trim() || subagentForm.name.trim() || subagentForm.id.trim(), system_prompt: subagentForm.system_prompt, model: subagentForm.model.trim() || undefined, enabled: subagentForm.enabled, delegation_mode: subagentForm.delegation_mode, allow_mcp: subagentForm.allow_mcp, tags: subagentForm.tags.split(',').map((item) => item.trim()).filter(Boolean), aliases: subagentForm.aliases.split(',').map((item) => item.trim()).filter(Boolean), metadata: parseJsonObject(subagentForm.metadata_json, 'Metadata'), mcp_servers: parseNestedJsonObject(subagentForm.mcp_servers_json, 'MCP Servers'), }; if (editingSubagentId) { await updateSubagent(editingSubagentId, payload); } else { await createSubagent(payload); } handleSubagentDialogOpenChange(false); await load(true); } catch (err: any) { setError(err.message || '保存 Sub-Agent 失败'); } finally { setSubagentSubmitting(false); } }; const handleDeleteManagedSubagent = async (subagentId: string) => { try { await deleteSubagent(subagentId); await load(true); } catch (err: any) { setError(err.message || '删除 Sub-Agent 失败'); } }; if (loading) { return (
); } return (

智能体

管理外部 A2A 智能体,以及持久化的本地 Sub-Agent。

新增工作区智能体
setAgentForm((s) => ({ ...s, base_url: e.target.value }))} placeholder="https://agent.example.com 或 agent.example.com:19090" />

默认只需要填写部署地址。保存时会自动读取 /.well-known 路径并补齐 card 信息。

setAgentForm((s) => ({ ...s, id: e.target.value }))} />
setAgentForm((s) => ({ ...s, name: e.target.value }))} />