import React, { useState, useEffect } from 'react'; import { adminAPI } from '@/api'; interface Agent { id: string; name: string; type: string; source: string; publisher_id: string; publisher_name?: string; description?: string; icon?: string; tutorial?: string; price: number; is_member_free: boolean; join_promotion: boolean; status: string; rating_avg: number; review_count: number; install_count: number; skill_content?: string; agent_endpoint?: string; config?: AgentConfig; linked_skills?: LinkedSkill[]; created_at?: string; updated_at?: string; } interface AgentConfig { system_prompt?: string; temperature?: number; max_tokens?: number; model_id?: string; skills?: string[]; tools?: string[]; memory_enabled?: boolean; reasoning_enabled?: boolean; } interface LinkedSkill { id: string; skill_name: string; state: string; version: number; } interface AgentStats { total_agents: number; published_agents: number; pending_agents: number; official_agents: number; user_agents: number; total_installs: number; average_rating: number; by_source: Record; } interface AvailableModel { id: string; name: string; provider: string; model_type?: string; } const AGENT_TYPES = [ { id: 'skill', label: '技能' }, { id: 'agent', label: 'Agent' }, { id: 'mcp', label: 'MCP' }, ]; const Agents: React.FC = () => { const [agents, setAgents] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [stats, setStats] = useState(null); // Filters const [keyword, setKeyword] = useState(''); const [filterStatus, setFilterStatus] = useState(''); const [filterSource, setFilterSource] = useState(''); // Modal const [showModal, setShowModal] = useState(false); const [editingAgent, setEditingAgent] = useState(null); const [form, setForm] = useState({ name: '', description: '', icon: '', price: '0', is_member_free: false, system_prompt: '', temperature: '0.7', max_tokens: '2048', model_id: '', skills: [] as string[], memory_enabled: false, reasoning_enabled: false, }); // Detail drawer const [showDetail, setShowDetail] = useState(false); const [detailAgent, setDetailAgent] = useState(null); // Available skills for linking const [availableSkills, setAvailableSkills] = useState([]); const [availableModels, setAvailableModels] = useState([]); // Available MCP servers for tool_policy sandbox allowlist const [availableMcps, setAvailableMcps] = useState([]); // Tool policy (sandbox) — null means disabled / no policy const [toolPolicy, setToolPolicy] = useState(null); // Toast const [toast, setToast] = useState<{ msg: string; type: 'success' | 'error' } | null>(null); const showToast = (msg: string, type: 'success' | 'error' = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; // Load agents const loadAgents = async () => { setLoading(true); try { const params: any = { page, page_size: 15 }; if (keyword) params.keyword = keyword; if (filterStatus) params.status = filterStatus; if (filterSource) params.source = filterSource; const result = await adminAPI.getAdminAgents(params) as any; const data = result.data || result; setAgents(data.items || []); setTotal(data.total || 0); } catch (e: any) { showToast(`加载失败: ${e.message || e}`, 'error'); } finally { setLoading(false); } }; // Load stats const loadStats = async () => { try { const result = await adminAPI.getAdminAgentsStats() as any; const data = result.data || result; setStats(data); } catch {} }; // Load available skills & models const loadAvailable = async () => { try { const [skillsResult, modelsResult, mcpsResult] = await Promise.all([ adminAPI.getAdminAvailableSkills({ page_size: 50 }) as any, adminAPI.getAdminAvailableModels() as any, adminAPI.getAdminMcpServers({ page_size: 50 }) as any, ]); const skills = (skillsResult.data || skillsResult).items || []; const models = (modelsResult.data || modelsResult) || []; const mcps = (mcpsResult.data || mcpsResult).items || []; setAvailableSkills(skills); setAvailableModels(models); setAvailableMcps(mcps); } catch {} }; useEffect(() => { loadAgents(); }, [page, keyword, filterStatus, filterSource]); useEffect(() => { loadStats(); }, []); // Search const handleSearch = () => { setPage(1); loadAgents(); }; // Open create const openCreate = async () => { setEditingAgent(null); await loadAvailable(); setForm({ name: '', description: '', icon: '', price: '0', is_member_free: false, system_prompt: '你是一个智能助手。', temperature: '0.7', max_tokens: '2048', model_id: '', skills: [], memory_enabled: false, reasoning_enabled: false, }); setToolPolicy(null); setShowModal(true); }; // Open edit const openEdit = async (agent: Agent) => { setEditingAgent(agent); await loadAvailable(); await openEditLoadPolicy(agent.id); const cfg = agent.config || {}; setForm({ name: agent.name, description: agent.description || '', icon: agent.icon || '', price: String(agent.price), is_member_free: agent.is_member_free, system_prompt: cfg.system_prompt || '', temperature: String(cfg.temperature || 0.7), max_tokens: String(cfg.max_tokens || 2048), model_id: cfg.model_id || '', skills: cfg.skills || [], memory_enabled: cfg.memory_enabled || false, reasoning_enabled: cfg.reasoning_enabled || false, }); setShowModal(true); }; // Fetch existing tool_policy (sandbox) for this agent const openEditLoadPolicy = async (agentId: string) => { try { const tpRes = await adminAPI.getAgentToolPolicy(agentId) as any; setToolPolicy(tpRes?.data?.tool_policy || null); } catch { setToolPolicy(null); } }; const openDetail = async (agent: Agent) => { setDetailAgent(agent); setShowDetail(true); }; // Save agent const handleSave = async () => { if (!form.name.trim()) { showToast('Agent 名称不能为空', 'error'); return; } const payload: any = { name: form.name, description: form.description, icon: form.icon || undefined, price: parseFloat(form.price) || 0, is_member_free: form.is_member_free, config: { system_prompt: form.system_prompt, temperature: parseFloat(form.temperature) || 0.7, max_tokens: parseInt(form.max_tokens) || 2048, model_id: form.model_id || undefined, skills: form.skills, memory_enabled: form.memory_enabled, reasoning_enabled: form.reasoning_enabled, }, }; try { if (editingAgent) { await adminAPI.updateAdminAgent(editingAgent.id, payload); // Phase 3.1: persist tool_policy sandbox (null clears it) try { await adminAPI.setAgentToolPolicy(editingAgent.id, toolPolicy); showToast('Agent 已更新'); } catch (pe: any) { showToast(`Agent 已更新,但策略保存失败: ${pe.message || pe}`, 'error'); } } else { await adminAPI.createAdminAgent(payload); showToast('Agent 创建成功'); } setShowModal(false); loadAgents(); loadStats(); } catch (e: any) { showToast(`保存失败: ${e.message || e}`, 'error'); } }; // Toggle agent const handleToggle = async (agent: Agent) => { const isActive = agent.status === 'published'; try { await adminAPI.toggleAdminAgent(agent.id, !isActive); showToast(`Agent 已${isActive ? '禁用' : '启用'}`); loadAgents(); loadStats(); } catch (e: any) { showToast(`操作失败: ${e.message || e}`, 'error'); } }; // Delete agent const handleDelete = async (agent: Agent) => { if (!confirm(`确定要删除 Agent "${agent.name}" 吗?此操作不可恢复。`)) return; try { await adminAPI.deleteAdminAgent(agent.id); showToast('Agent 已删除'); loadAgents(); loadStats(); } catch (e: any) { showToast(`删除失败: ${e.message || e}`, 'error'); } }; // Toggle skill in selection const toggleSkill = (skillId: string) => { setForm(f => ({ ...f, skills: f.skills.includes(skillId) ? f.skills.filter(s => s !== skillId) : [...f.skills, skillId], })); }; const PAGE_SIZE = 15; const totalPages = Math.ceil(total / PAGE_SIZE); const statusColor = (status: string) => { if (status === 'published') return { bg: '#D1FAE5', color: '#10B981' }; if (status === 'pending') return { bg: '#FEF3C7', color: '#F59E0B' }; return { bg: '#FEE2E2', color: '#EF4444' }; }; return (
{/* Toast */} {toast && (
{toast.msg}
)} {/* Header */}

Agent 管理

管理 AI Agent/Skill/MCP,支持配置、关联技能和模型

{/* Stats */} {stats && (
{stats.total_agents}
Agent 总数
{stats.published_agents}
已发布
{stats.pending_agents}
待发布
{stats.total_installs.toLocaleString()}
总安装量
{stats.average_rating.toFixed(1)}
平均评分
{stats.official_agents} / {stats.user_agents}
官方 / 用户
)} {/* Filters */}
setKeyword(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSearch()} style={{ maxWidth: 280 }} /> {(keyword || filterStatus || filterSource) && ( )}
{/* Table */}
{loading ? ( ) : agents.length === 0 ? ( ) : agents.map(agent => { const sc = statusColor(agent.status); return ( ); })}
智能体 类型 来源 状态 价格 安装量 评分 更新时间 操作
加载中...
暂无 Agent
{agent.icon ? ( ) : (
🤖
)}
{agent.name}
{agent.description && (
{agent.description}
)}
{AGENT_TYPES.find(t => t.id === agent.type)?.label || agent.type} {agent.source === 'official' ? '官方' : '用户'} {agent.status === 'published' ? '已发布' : agent.status === 'pending' ? '待发布' : '已下架'} {agent.price === 0 ? 免费 : `¥${agent.price}`} {agent.install_count.toLocaleString()} {agent.rating_avg > 0 ? `★ ${agent.rating_avg}` : '-'} {agent.updated_at ? agent.updated_at.slice(0, 16).replace('T', ' ') : '-'}
{/* Pagination */} {totalPages > 1 && (
共 {total} 条,第 {page}/{totalPages} 页
{[...Array(Math.min(5, totalPages))].map((_, i) => { const p = Math.max(1, Math.min(page - 2, totalPages - 4)) + i; return p <= totalPages && ( ); })}
)} {/* Create/Edit Modal */} {showModal && (
setShowModal(false)}>
e.stopPropagation()}>

{editingAgent ? '编辑 Agent' : '创建 Agent'}

setForm(f => ({ ...f, name: e.target.value }))} placeholder="例如: 智能代码审查助手" />
setForm(f => ({ ...f, icon: e.target.value }))} placeholder="https://..." />
setForm(f => ({ ...f, price: e.target.value }))} />