From 9f621371b802b5e68a5b143b2085cb6363b3db72 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Fri, 4 Sep 2026 07:52:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(frontend):=20=E6=B1=89=E5=8C=96MCP=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=B1=95=E7=A4=BA=E5=B9=B6=E6=8A=98=E5=8F=A0=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/development/前端壳子与接口层开发说明.md | 8 ++++ frontend/src/features/agent/AgentView.vue | 12 ++---- .../src/features/agent/ToolOption.spec.ts | 19 +++++++++ frontend/src/features/agent/ToolOption.vue | 40 +++++++++++++++++++ frontend/src/features/agent/labels.spec.ts | 15 +++++++ frontend/src/features/agent/labels.ts | 28 ++++++++++++- 6 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 frontend/src/features/agent/ToolOption.spec.ts create mode 100644 frontend/src/features/agent/ToolOption.vue diff --git a/docs/development/前端壳子与接口层开发说明.md b/docs/development/前端壳子与接口层开发说明.md index a7146f3..5e5aa28 100644 --- a/docs/development/前端壳子与接口层开发说明.md +++ b/docs/development/前端壳子与接口层开发说明.md @@ -219,3 +219,11 @@ Vite 当前会提示 Chat 与 Workspace 的部分异步 Chunk 超过 500 kB, - Plugin/Skill/Provider 无记录时显示空状态,模型发现失败时允许使用真实的手动模型 ID。 验证:前端 81 项测试、类型检查与生产构建通过;后端 454 项测试通过。新增测试覆盖空初始状态、离线错误、真实统计与权限、测试 Provider 过滤、真实聊天历史及旧流隔离。本次未调用真实付费推理 API。 + +### MCP 工具中文展示补充 + +Agent 工具列表按 `mcp..` 的远程工具名匹配中文展示,支持 `web_search`(网页搜索)、`understand_image`(图像理解),并补充 `text.uppercase`(文本转大写)。此映射只影响界面,工具调用与权限选择仍使用完整原始 ID。 + +卡片默认显示三行摘要,完整服务原文可展开查看,展开操作不会改变工具选择。服务已提供中文说明时优先保留;未收录的 MCP 工具明确提示暂无中文说明,不将本地摘要当作服务协议或自动翻译结果。原始说明及其中的参数规则完整保留。 + +验证:前端 84 项测试、类型检查与生产构建通过。新增回归覆盖不同服务器命名空间、未知工具、服务中文说明、原文完整性,以及选择工具时保留原始 ID。 diff --git a/frontend/src/features/agent/AgentView.vue b/frontend/src/features/agent/AgentView.vue index 11ed477..283f44d 100644 --- a/frontend/src/features/agent/AgentView.vue +++ b/frontend/src/features/agent/AgentView.vue @@ -5,7 +5,8 @@ import { useAgentStore } from '@/stores/agent' import { useProviderStore } from '@/stores/provider' import { useSkillStore } from '@/stores/skill' import type { AgentEvent } from '@/contracts' -import { eventLabel, localizeDetails, permissionLabel, runStatusLabel, toolDescription, toolLabel } from './labels' +import { eventLabel, localizeDetails, permissionLabel, runStatusLabel, toolLabel } from './labels' +import ToolOption from './ToolOption.vue' const route = useRoute() const router = useRouter() @@ -88,7 +89,7 @@ function eventText(event: AgentEvent) {
-
+
@@ -114,12 +115,7 @@ function eventText(event: AgentEvent) { diff --git a/frontend/src/features/agent/labels.spec.ts b/frontend/src/features/agent/labels.spec.ts index 123ac25..fcb568a 100644 --- a/frontend/src/features/agent/labels.spec.ts +++ b/frontend/src/features/agent/labels.spec.ts @@ -9,6 +9,21 @@ import { } from './labels' describe('智能体页面中文标签', () => { + it('按 MCP 远程工具名匹配中文,不依赖服务器 ID', () => { + for (const server of ['9ca7ee21603a', 'another-server']) { + expect(toolLabel(`mcp.${server}.web_search`)).toBe('网页搜索') + expect(toolLabel(`mcp.${server}.understand_image`)).toBe('图像理解') + expect(toolDescription(`mcp.${server}.web_search`, 'Search the web')).toContain('搜索关键词') + } + expect(toolLabel('text.uppercase')).toBe('文本转大写') + expect(toolDescription('text.uppercase', 'Convert input text to uppercase.')).toContain('大写') + }) + + it('保留服务端中文,未知工具不编造翻译或套用内置工具语义', () => { + expect(toolDescription('mcp.server.web_search', '仅搜索指定站点。')).toBe('仅搜索指定站点。') + expect(toolDescription('mcp.server.custom_action', 'Private action')).toContain('暂无中文说明') + expect(toolLabel('mcp.server.notes.delete')).toBe('MCP 工具 · notes.delete') + }) it('转换运行状态和事件名称', () => { expect(runStatusLabel('waiting_permission')).toBe('等待授权') expect(eventLabel('ToolCall')).toBe('调用工具') diff --git a/frontend/src/features/agent/labels.ts b/frontend/src/features/agent/labels.ts index c6646d6..0ed56b5 100644 --- a/frontend/src/features/agent/labels.ts +++ b/frontend/src/features/agent/labels.ts @@ -42,6 +42,7 @@ const toolLabels: Record = { 'tasks.list': '列出任务', 'attachments.read': '读取附件', 'audio.transcribe': '音频转写', + 'text.uppercase': '文本转大写', } const toolDescriptions: Record = { @@ -58,7 +59,25 @@ const toolDescriptions: Record = { 'tasks.update': '更新已有任务。', 'tasks.list': '列出已持久化的任务。', 'attachments.read': '读取由宿主管理的 UTF-8 附件。', - 'audio.transcribe': '读取音频附件已有的宿主转写结果。', + 'audio.transcribe': '将音频转写为文本,按模型路由使用 API 或本地后端。', + 'text.uppercase': '将输入文本中的字母转换为大写。', +} + +// MCP IDs contain a server-specific namespace. Localize the remote tool name +// for presentation only; requests must keep using the complete original ID. +const mcpTools: Record = { + web_search: { + label: '网页搜索', + description: '搜索实时或外部网页信息。输入搜索关键词;结果包含标题、链接、摘要等信息。时效性问题可在关键词中加入日期,完整参数以服务原文为准。', + }, + understand_image: { + label: '图像理解', + description: '根据提示词分析图片、描述内容或提取信息。输入分析要求和图片地址或本地路径;支持的格式与路径规则请查看服务原文。', + }, +} + +function mcpName(name: string): string | undefined { + return /^mcp\.[^.]+\.(.+)$/.exec(name)?.[1] } const permissionLabels: Record = { @@ -105,10 +124,17 @@ export function eventLabel(event: AgentEventType): string { } export function toolLabel(name: string): string { + const remote = mcpName(name) + if (remote) return mcpTools[remote]?.label ?? `MCP 工具 · ${remote}` return toolLabels[name] ?? name } export function toolDescription(name: string, fallback: string): string { + const remote = mcpName(name) + if (remote) { + if (/\p{Script=Han}/u.test(fallback)) return fallback + return mcpTools[remote]?.description ?? '暂无中文说明,请展开查看服务原文。' + } return toolDescriptions[name] ?? fallback }