From a27ea18dfb42802f022ebde0970af6e9906db229 Mon Sep 17 00:00:00 2001 From: Arvin Xu Date: Fri, 22 May 2026 15:12:21 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20style(builtin-tool):=20switch=20?= =?UTF-8?q?Task=20inspector=20copy=20by=20phase=20(#15104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Inspector chips stay in chat history, so a settled TaskCreate row that still reads "Creating task" looks like the call is still running. Split lobe-claude-code task labels into .loading / .completed pairs and pick based on isArgumentsStreaming || isLoading. Documented the rule in the builtin-tool ui skill so new tools follow the same convention. Co-authored-by: Claude Opus 4.7 --- .agents/skills/builtin-tool/references/ui.md | 24 +++++----- locales/en-US/plugin.json | 11 +++-- locales/zh-CN/plugin.json | 9 ++-- .../src/client/Inspector/Task.tsx | 44 +++++++++++-------- src/locales/default/plugin.ts | 11 +++-- 5 files changed, 58 insertions(+), 41 deletions(-) diff --git a/.agents/skills/builtin-tool/references/ui.md b/.agents/skills/builtin-tool/references/ui.md index da6220a7d4..2e08b87563 100644 --- a/.agents/skills/builtin-tool/references/ui.md +++ b/.agents/skills/builtin-tool/references/ui.md @@ -25,17 +25,18 @@ The two reference tools to read end-to-end: 1. **先保证折叠态可读。** 每个 API 都必须有 Inspector;用户不展开也应该能看懂 “正在做什么 / 对什么做 / 当前结果是什么”。Inspector 不应该只展示函数名和原始参数。 2. **Inspector 是一句话,不是详情页。** 优先表达动作、关键对象、数量、状态,例如 “分析图片 3 张”“搜索 12 个结果”“读取 config.json”。长文本、列表和结构化结果放到 Render 或 Portal。 3. **Inspector 要覆盖执行生命周期。** `args` 还在 streaming、工具执行中、执行完成、执行失败时都应该有稳定展示;必要时同时读取 `args`、`partialArgs` 和 `pluginState`,避免出现空白、跳变或只显示半截参数。 -4. **只有结构化结果才需要 Render。** 如果工具结果只是自然语言总结,通常不需要 Render;如果结果包含列表、媒体、文件、表格、代码、diff、地图、时间线、权限请求等结构,就应该提供 Render。 -5. **Render 要帮助用户检查结果,而不是复述参数。** Render 的主体应该围绕工具产物组织:可预览、可比较、可筛选、可定位。参数只作为上下文辅助出现,不要把 Render 做成一块更大的 args dump。 -6. **参数和结果要一起参与渲染。** 好的 Tool UI 通常同时用 `args` 解释意图,用 `pluginState` 展示真实执行结果;但 `pluginState` 只放结果域数据,不要反向塞入可以从 `args` 推导出的内容。 -7. **慢操作要有 Placeholder。** 如果工具通常需要等待网络、文件系统、模型或外部进程,Placeholder 应该先占住最终 Render 的版式,让用户知道即将看到什么,而不是只显示一个泛化 loading。 -8. **Streaming 只用于连续产物。** 搜索列表、日志、长文本、文件分析、分阶段计划适合 Streaming;一次性小结果不需要强行做 Streaming。Streaming UI 要能渐进追加,并且完成后自然过渡到最终 Render。 -9. **有风险的动作必须 Intervention。** 写文件、删除、发送、安装、执行命令、外部可见操作、权限敏感操作,都应该在执行前给出可理解的确认界面;确认文案要说明影响范围,而不是只问 “是否继续”。 -10. **错误、空态和截断都是正式状态。** Render 不能在失败、无结果、超长结果时退化成空白。错误要说明发生在哪一步;空态要告诉用户没有产物;超长内容要明确 “展示前 N 项 / 还有 N 项”。 -11. **信息密度要克制。** 默认展示最有判断价值的部分:标题、来源、状态、摘要、少量关键字段。大对象、长列表、原文、调试数据放进可展开区域或 Portal,避免把聊天流撑成后台管理页。 -12. **视觉上融入聊天流。** Tool UI 应该使用 `@lobehub/ui` / base-ui、`Flexbox`、`createStaticStyles` 和 `cssVar.*`,遵循现有间距、圆角、颜色、字号;不要为单个工具发明一套独立视觉语言。 -13. **Devtools fixture 是验收入口。** 新增或修改 Tool UI 时,应在 `/devtools` 里准备覆盖典型态、loading/streaming、空态、错误态、长内容态的 fixture;一个 API 如果在真实聊天里会出现,就不应该在 devtools 中缺席。 -14. **先做用户会看的 UI,再做调试 UI。** Raw JSON、trace、schema、内部 id 可以存在,但应默认收起或放到调试区;主界面先回答用户最关心的问题:工具做了什么,结果值不值得信任,下一步能做什么。 +4. **文案要随状态切换时态。** 同一个动作在 loading 与 completed 两个阶段必须用不同的措辞:执行中用现在进行时(“正在创建任务 / Creating task / 正在搜索”),执行完成后切到完成态(“已创建任务 / Task created / 已找到 N 条”)。Inspector chip 会一直留在聊天记录里——如果一直挂着 “正在 xxx”,几小时后回看历史时会读起来像还在跑。约定的 i18n 形式是 `.loading` / `.completed` 一对键(见 `lobe-agent.apiName.callSubAgent.{loading,completed}` 与 `lobe-claude-code.task.{create,list,update,get}.{loading,completed}`),渲染时按 `isArgumentsStreaming || isLoading` 决定取哪一个。只读 / 查询类(“查看任务”这种本来就是名词性的)可以共用一个键。 +5. **只有结构化结果才需要 Render。** 如果工具结果只是自然语言总结,通常不需要 Render;如果结果包含列表、媒体、文件、表格、代码、diff、地图、时间线、权限请求等结构,就应该提供 Render。 +6. **Render 要帮助用户检查结果,而不是复述参数。** Render 的主体应该围绕工具产物组织:可预览、可比较、可筛选、可定位。参数只作为上下文辅助出现,不要把 Render 做成一块更大的 args dump。 +7. **参数和结果要一起参与渲染。** 好的 Tool UI 通常同时用 `args` 解释意图,用 `pluginState` 展示真实执行结果;但 `pluginState` 只放结果域数据,不要反向塞入可以从 `args` 推导出的内容。 +8. **慢操作要有 Placeholder。** 如果工具通常需要等待网络、文件系统、模型或外部进程,Placeholder 应该先占住最终 Render 的版式,让用户知道即将看到什么,而不是只显示一个泛化 loading。 +9. **Streaming 只用于连续产物。** 搜索列表、日志、长文本、文件分析、分阶段计划适合 Streaming;一次性小结果不需要强行做 Streaming。Streaming UI 要能渐进追加,并且完成后自然过渡到最终 Render。 +10. **有风险的动作必须 Intervention。** 写文件、删除、发送、安装、执行命令、外部可见操作、权限敏感操作,都应该在执行前给出可理解的确认界面;确认文案要说明影响范围,而不是只问 “是否继续”。 +11. **错误、空态和截断都是正式状态。** Render 不能在失败、无结果、超长结果时退化成空白。错误要说明发生在哪一步;空态要告诉用户没有产物;超长内容要明确 “展示前 N 项 / 还有 N 项”。 +12. **信息密度要克制。** 默认展示最有判断价值的部分:标题、来源、状态、摘要、少量关键字段。大对象、长列表、原文、调试数据放进可展开区域或 Portal,避免把聊天流撑成后台管理页。 +13. **视觉上融入聊天流。** Tool UI 应该使用 `@lobehub/ui` / base-ui、`Flexbox`、`createStaticStyles` 和 `cssVar.*`,遵循现有间距、圆角、颜色、字号;不要为单个工具发明一套独立视觉语言。 +14. **Devtools fixture 是验收入口。** 新增或修改 Tool UI 时,应在 `/devtools` 里准备覆盖典型态、loading/streaming、空态、错误态、长内容态的 fixture;一个 API 如果在真实聊天里会出现,就不应该在 devtools 中缺席。 +15. **先做用户会看的 UI,再做调试 UI。** Raw JSON、trace、schema、内部 id 可以存在,但应默认收起或放到调试区;主界面先回答用户最关心的问题:工具做了什么,结果值不值得信任,下一步能做什么。 --- @@ -200,6 +201,7 @@ export default SearchInspector; - Read both `args?.X` and `partialArgs?.X` together — `args` is final, `partialArgs` is in-stream. - Use chips/tags for distinct facets (identifier, name, parent, status, count). Each chip should clip with `text-overflow: ellipsis` and have a `max-width` so long values don't blow out the chat bubble. - Append `pluginState`-derived suffixes only **after** loading finishes — count or "(no results)" should not appear while still searching. +- **Switch copy by phase.** If the verb implies an ongoing action ("Creating", "Searching", "Listing"), define `.loading` and `.completed` keys and select via `isArgumentsStreaming || isLoading ? loadingKey : completedKey`. Inspector chips persist in chat history — leaving "Creating task" frozen on a finished call reads as if the tool is still running. Read-only labels that are already noun-form ("View task") can keep a single key. See `CallSubAgentInspector` for the canonical two-key pattern. ### Inspector registry — `client/Inspector/index.ts` diff --git a/locales/en-US/plugin.json b/locales/en-US/plugin.json index 6bbf2421e2..e6997a03c7 100644 --- a/locales/en-US/plugin.json +++ b/locales/en-US/plugin.json @@ -90,13 +90,16 @@ "builtins.lobe-agent.title": "Lobe Agent", "builtins.lobe-claude-code.agent.instruction": "Instruction", "builtins.lobe-claude-code.agent.result": "Result", - "builtins.lobe-claude-code.task.createLabel": "Creating task: ", - "builtins.lobe-claude-code.task.getLabel": "Inspecting task #{{taskId}}", - "builtins.lobe-claude-code.task.listLabel": "Listing tasks", + "builtins.lobe-claude-code.task.create.completed": "Task created: ", + "builtins.lobe-claude-code.task.create.loading": "Creating task: ", + "builtins.lobe-claude-code.task.getLabel": "View task #{{taskId}}", + "builtins.lobe-claude-code.task.list.completed": "Tasks listed", + "builtins.lobe-claude-code.task.list.loading": "Listing tasks", + "builtins.lobe-claude-code.task.update.completed": "Task #{{taskId}} updated", + "builtins.lobe-claude-code.task.update.loading": "Updating task #{{taskId}}", "builtins.lobe-claude-code.task.updateCompleted": "Completed", "builtins.lobe-claude-code.task.updateDeleted": "Deleted", "builtins.lobe-claude-code.task.updateInProgress": "Started", - "builtins.lobe-claude-code.task.updateLabel": "Updating task #{{taskId}}", "builtins.lobe-claude-code.task.updatePending": "Reset", "builtins.lobe-claude-code.todoWrite.allDone": "All tasks completed", "builtins.lobe-claude-code.todoWrite.currentStep": "Current step", diff --git a/locales/zh-CN/plugin.json b/locales/zh-CN/plugin.json index 7919e7baed..fa07b44496 100644 --- a/locales/zh-CN/plugin.json +++ b/locales/zh-CN/plugin.json @@ -90,13 +90,16 @@ "builtins.lobe-agent.title": "Lobe Agent", "builtins.lobe-claude-code.agent.instruction": "指令", "builtins.lobe-claude-code.agent.result": "结果", - "builtins.lobe-claude-code.task.createLabel": "正在创建任务:", + "builtins.lobe-claude-code.task.create.completed": "已创建任务:", + "builtins.lobe-claude-code.task.create.loading": "正在创建任务:", "builtins.lobe-claude-code.task.getLabel": "查看任务 #{{taskId}}", - "builtins.lobe-claude-code.task.listLabel": "正在列出任务", + "builtins.lobe-claude-code.task.list.completed": "已列出任务", + "builtins.lobe-claude-code.task.list.loading": "正在列出任务", + "builtins.lobe-claude-code.task.update.completed": "已更新任务 #{{taskId}}", + "builtins.lobe-claude-code.task.update.loading": "正在更新任务 #{{taskId}}", "builtins.lobe-claude-code.task.updateCompleted": "已完成", "builtins.lobe-claude-code.task.updateDeleted": "已删除", "builtins.lobe-claude-code.task.updateInProgress": "开始执行", - "builtins.lobe-claude-code.task.updateLabel": "正在更新任务 #{{taskId}}", "builtins.lobe-claude-code.task.updatePending": "重置为待办", "builtins.lobe-claude-code.todoWrite.allDone": "全部任务已完成", "builtins.lobe-claude-code.todoWrite.currentStep": "当前步骤", diff --git a/packages/builtin-tool-claude-code/src/client/Inspector/Task.tsx b/packages/builtin-tool-claude-code/src/client/Inspector/Task.tsx index 680ace2533..b6fc345563 100644 --- a/packages/builtin-tool-claude-code/src/client/Inspector/Task.tsx +++ b/packages/builtin-tool-claude-code/src/client/Inspector/Task.tsx @@ -176,18 +176,20 @@ export const TaskInspector = memo +
{stats.total > 0 && ( @@ -253,22 +255,26 @@ export const TaskInspector = memo { if (apiName === ClaudeCodeApiName.TaskUpdate) { const taskId = (resolvedArgs as TaskUpdateArgs | undefined)?.taskId; - return taskId - ? t('builtins.lobe-claude-code.task.updateLabel', { taskId }) - : t('builtins.lobe-claude-code.todoWrite.todos'); + if (!taskId) return t('builtins.lobe-claude-code.todoWrite.todos'); + return t( + inFlight + ? 'builtins.lobe-claude-code.task.update.loading' + : 'builtins.lobe-claude-code.task.update.completed', + { taskId }, + ); } - return t('builtins.lobe-claude-code.task.listLabel'); + return t( + inFlight + ? 'builtins.lobe-claude-code.task.list.loading' + : 'builtins.lobe-claude-code.task.list.completed', + ); })(); return ( -
+
{fallback}
); diff --git a/src/locales/default/plugin.ts b/src/locales/default/plugin.ts index 8087044a73..802e4ec615 100644 --- a/src/locales/default/plugin.ts +++ b/src/locales/default/plugin.ts @@ -62,13 +62,16 @@ export default { 'builtins.lobe-agent.title': 'Lobe Agent', 'builtins.lobe-claude-code.agent.instruction': 'Instruction', 'builtins.lobe-claude-code.agent.result': 'Result', - 'builtins.lobe-claude-code.task.createLabel': 'Creating task: ', - 'builtins.lobe-claude-code.task.getLabel': 'Inspecting task #{{taskId}}', - 'builtins.lobe-claude-code.task.listLabel': 'Listing tasks', + 'builtins.lobe-claude-code.task.create.completed': 'Task created: ', + 'builtins.lobe-claude-code.task.create.loading': 'Creating task: ', + 'builtins.lobe-claude-code.task.getLabel': 'View task #{{taskId}}', + 'builtins.lobe-claude-code.task.list.completed': 'Tasks listed', + 'builtins.lobe-claude-code.task.list.loading': 'Listing tasks', + 'builtins.lobe-claude-code.task.update.completed': 'Task #{{taskId}} updated', + 'builtins.lobe-claude-code.task.update.loading': 'Updating task #{{taskId}}', 'builtins.lobe-claude-code.task.updateCompleted': 'Completed', 'builtins.lobe-claude-code.task.updateDeleted': 'Deleted', 'builtins.lobe-claude-code.task.updateInProgress': 'Started', - 'builtins.lobe-claude-code.task.updateLabel': 'Updating task #{{taskId}}', 'builtins.lobe-claude-code.task.updatePending': 'Reset', 'builtins.lobe-claude-code.todoWrite.allDone': 'All tasks completed', 'builtins.lobe-claude-code.todoWrite.currentStep': 'Current step',