From 9381ed5dd4c35c8aeec7fa15eec357480ec9de40 Mon Sep 17 00:00:00 2001 From: oudecheng <13802883547@139.com> Date: Thu, 30 Jul 2026 16:55:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(web):=20=E4=B8=93=E5=AE=B6/=E5=AD=90?= =?UTF-8?q?=E4=BB=A3=E7=90=86=20capability=20=E5=AD=97=E6=AE=B5=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E5=8B=BE=E9=80=89=E5=BC=8F=E7=BC=96=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 专家编辑模态框:4 个 textarea 替换为 CheckboxList,技能/工具按来源分组展示 - 新增子代理编辑模态框:非 builtin 子代理显示编辑按钮,支持描述 + 4 个 capability 勾选字段 - 进入 experts/subagents 标签页时自动拉取技能与工具列表 - allowed_* 为空时传 undefined(后端 None=不限),避免空数组触发白名单空集语义 --- web/src/components/Settings/ConfigPage.tsx | 293 +++++++++++++++++++-- 1 file changed, 270 insertions(+), 23 deletions(-) diff --git a/web/src/components/Settings/ConfigPage.tsx b/web/src/components/Settings/ConfigPage.tsx index 3b72a9e..73117d8 100644 --- a/web/src/components/Settings/ConfigPage.tsx +++ b/web/src/components/Settings/ConfigPage.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react' import { Settings, Save, X, Plus, Trash2, AlertTriangle, Loader2, Wifi, - CheckCircle, RefreshCw, UserCheck, Pencil, + CheckCircle, RefreshCw, UserCheck, Pencil, Bot, } from 'lucide-react' // ── Extracted modules ───────────────────────────────── @@ -10,16 +10,19 @@ import type { ProviderConfig, ModelConfig, AgentConfig, McpServerConfig, McpStatusResponse, SkillListResponse, - SubagentListResponse, + SubagentListResponse, SubagentItem, + ToolsListResponse, ExpertItem, ExpertListResponse, + CapabilityPolicy, KnownSource, SchedulerConfig, ChannelConfig, } from './types' import { TABS, inputCls, selectCls, TIMEZONE_OPTIONS } from './constants' -import { Field, Toggle, TagEditor, SectionCard, SourceEditor, MapEntryHeader } from './ui' +import { Field, Toggle, TagEditor, SectionCard, SourceEditor, MapEntryHeader, CheckboxList } from './ui' import { getAppConfig, updateAppConfig, restartGateway, checkHealth } from '../../api/config' import { listSkills, toggleSkill } from '../../api/skills' -import { listSubagents, toggleSubagent } from '../../api/subagents' +import { listTools } from '../../api/tools' +import { listSubagents, toggleSubagent, updateSubagent } from '../../api/subagents' import { listExperts, toggleExpert, createExpert, updateExpert, deleteExpert } from '../../api/experts' import { getMcpStatus } from '../../api/mcp' export { getSelectedExpert, selectExpert } from '../../api/experts' @@ -47,6 +50,8 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage const [mcpStatus, setMcpStatus] = useState(null) const [skillList, setSkillList] = useState(null) const [skillListLoading, setSkillListLoading] = useState(false) + const [toolList, setToolList] = useState(null) + const [toolListLoading, setToolListLoading] = useState(false) const [subagentList, setSubagentList] = useState(null) const [subagentListLoading, setSubagentListLoading] = useState(false) const [expertList, setExpertList] = useState(null) @@ -58,9 +63,23 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage nameField: string description: string body: string + allowedSkills: string[] + deniedSkills: string[] + allowedTools: string[] + deniedTools: string[] } | null>(null) const [editingExpertError, setEditingExpertError] = useState('') const [savingExpert, setSavingExpert] = useState(false) + const [editingSubagent, setEditingSubagent] = useState<{ + name: string + description: string + allowedSkills: string[] + deniedSkills: string[] + allowedTools: string[] + deniedTools: string[] + } | null>(null) + const [editingSubagentError, setEditingSubagentError] = useState('') + const [savingSubagent, setSavingSubagent] = useState(false) const fetchMcpStatus = useCallback(async () => { const data = await getMcpStatus() @@ -74,6 +93,13 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage setSkillListLoading(false) }, []) + const fetchToolList = useCallback(async () => { + setToolListLoading(true) + const data = await listTools() + if (data) setToolList(data) + setToolListLoading(false) + }, []) + const toggleSkillCb = useCallback(async (name: string, scope: string, enabled: boolean) => { return toggleSkill(name, scope, enabled) }, []) @@ -89,6 +115,10 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage return toggleSubagent(name, scope, enabled) }, []) + const updateSubagentCb = useCallback(async (payload: { name: string; description?: string; body?: string; capability?: CapabilityPolicy }) => { + return updateSubagent(payload) + }, []) + const fetchExpertList = useCallback(async () => { setExpertListLoading(true) const data = await listExperts() @@ -100,11 +130,11 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage return toggleExpert(name, scope, enabled) }, []) - const createExpertCb = useCallback(async (payload: { name: string; description: string; body: string; scope: string }) => { + const createExpertCb = useCallback(async (payload: { name: string; description: string; body: string; scope: string; capability?: CapabilityPolicy }) => { return createExpert(payload) }, []) - const updateExpertCb = useCallback(async (payload: { name: string; scope: string; description?: string; body?: string }) => { + const updateExpertCb = useCallback(async (payload: { name: string; scope: string; description?: string; body?: string; capability?: CapabilityPolicy }) => { return updateExpert(payload) }, []) @@ -141,6 +171,14 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage if (activeTab === 'subagents') fetchSubagentList() }, [activeTab, fetchSubagentList]) + // Fetch skills + tools when experts/subagents tab is selected (for capability CheckboxList) + useEffect(() => { + if (activeTab === 'experts' || activeTab === 'subagents') { + if (!skillList) fetchSkillList() + if (!toolList) fetchToolList() + } + }, [activeTab, fetchSkillList, fetchToolList, skillList, toolList]) + // Fetch expert list when experts tab is selected useEffect(() => { if (activeTab === 'experts') fetchExpertList() @@ -568,6 +606,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage /> {renderDiscoveredSubagents()} + {renderSubagentModal()} ) @@ -636,6 +675,18 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage ) } + const handleEditSubagent = (subagent: SubagentItem) => { + setEditingSubagentError('') + setEditingSubagent({ + name: subagent.name, + description: subagent.description, + allowedSkills: subagent.capability?.allowed_skills ?? [], + deniedSkills: subagent.capability?.denied_skills ?? [], + allowedTools: subagent.capability?.allowed_tools ?? [], + deniedTools: subagent.capability?.denied_tools ?? [], + }) + } + return ( {subagentListLoading && subagents.length === 0 ? ( @@ -648,6 +699,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage
{subagents.map(subagent => { const isEnabled = subagent.disabled_in_scopes.length === 0 + const isBuiltin = subagent.source === 'builtin' return (
@@ -656,9 +708,18 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage {subagent.source}

{subagent.description}

- {renderToolTags('允许', subagent.allowed_tools, 'allow')} - {renderToolTags('禁用', subagent.denied_tools, 'deny')} + {renderToolTags('允许', subagent.capability?.allowed_tools, 'allow')} + {renderToolTags('禁用', subagent.capability?.denied_tools, 'deny')}
+ {!isBuiltin && ( + + )} handleToggle(subagent.name, isEnabled)} />
) @@ -691,7 +752,7 @@ export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPage + + + + + ) + } + const renderMcp = () => { const entries = Object.entries(config.mcpServers) const statusFor = (key: string) => mcpStatus?.servers?.find(s => s.key === key)