From 72d6f9ab660b5bbf6a791f4d338a88f989ad55ec Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Mon, 20 Jul 2026 09:24:40 +0800 Subject: [PATCH] Phase 4.1+: mermaid diagrams + YAML workflow buttons + agents API (v2026.07.19.11) --- VERSION | 2 +- frontend/src/api/index.ts | 3 + frontend/src/pages/admin/Agents.tsx | 1098 +++++++- frontend/src/pages/front/ChatPage.css | 3092 +++++++++++----------- frontend/src/pages/front/ChatPage.tsx | 3470 +++++++++++++------------ 5 files changed, 4324 insertions(+), 3341 deletions(-) diff --git a/VERSION b/VERSION index e0a5db2..95300b3 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2026.07.19.10 +v2026.07.19.11 diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 412c786..fbaadf5 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -894,6 +894,9 @@ export const adminAPI = { // Phase 3.3: 多 Agent 编排图 (orchestration) getAgentOrchestration: (id: string) => get(`/admin/agents/${id}/orchestration`), setAgentOrchestration: (id: string, orchestration: any) => put(`/admin/agents/${id}/orchestration`, { orchestration }), + importYamlOrch: (id: string, yaml: string) => post(`/admin/agents/${id}/orchestration/yaml/import`, { yaml_text: yaml }), + exportYamlOrch: (id: string) => post(`/admin/agents/${id}/orchestration/yaml/export`), + getYamlTemplates: () => get('/admin/agents/orchestration/yaml/templates'), getAdminAvailableSkills: (params?: any) => { diff --git a/frontend/src/pages/admin/Agents.tsx b/frontend/src/pages/admin/Agents.tsx index b99d1d0..0a6a798 100644 --- a/frontend/src/pages/admin/Agents.tsx +++ b/frontend/src/pages/admin/Agents.tsx @@ -1,905 +1,1837 @@ 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); + // Phase 3.3: orchestration graph editor (JSON text + error) + const [orchestrationText, setOrchestrationText] = useState(''); + const [orchestrationError, setOrchestrationError] = useState(''); + + // 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); + setOrchestrationText(''); + setOrchestrationError(''); + setShowModal(true); + }; + + // Open edit + const openEdit = async (agent: Agent) => { + setEditingAgent(agent); + await loadAvailable(); + await openEditLoadPolicy(agent.id); + await openEditLoadOrchestration(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); + } + }; + // Phase 3.3: 载入已有编排图 + const openEditLoadOrchestration = async (agentId: string) => { + try { + const oRes = await adminAPI.getAgentOrchestration(agentId) as any; + const og = oRes?.data?.orchestration; + setOrchestrationText(og ? JSON.stringify(og, null, 2) : ''); + setOrchestrationError(''); + } catch { + setOrchestrationText(''); + } + }; + 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 }))} /> +
+
+ +
+
+ +