# Step 69: 工具调用 UI 打磨
一句话导读:把工具结果从“一行成功提示”升级成可折叠工具卡,并引入
marked做完整 Markdown 渲染。
# 一、这一步做了什么(What)
step69 打磨 Web 前端:tool_result 事件增加 argSummary 和 preview,前端渲染为 <details> 工具卡;Markdown 从手写正则换成第三方库 marked;同时加流式光标、权限卡片样式和缓存控制。
# 二、面试官视角:为什么要做?(Why)
面试题:工具调用已经能显示成功/失败了,为什么还要做可折叠工具卡?
因为 agent 的可解释性很大程度来自工具过程。用户需要知道模型跑了什么命令、读了哪个文件、输出大概是什么;但又不能让长输出淹没对话。
| 一行提示 | 工具卡 |
|---|---|
| 只知道 Bash 成功 | 能看到命令摘要和输出预览 |
| 长结果会刷屏 | 默认折叠,需要时展开 |
| 难排错 | 输入/输出上下文可查 |
# 三、原理:它是怎么工作的(How)
QueryEngine.ts 在产生 tool_result 时带上摘要:
yield {
type: "tool_result",
name,
ok,
elapsed,
argSummary,
preview,
};
2
3
4
5
6
7
8
前端用 <details>:
<details class="tool-card">
<summary>✓ Bash (31ms) echo hi</summary>
<pre>hi</pre>
</details>
2
3
4
Markdown 改为服务端托管 marked:
if (path === "/vendor/marked.js") {
// 返回 node_modules/marked/... 的浏览器构建
}
2
3
浏览器里:
function md(raw) {
return marked.parse(raw, { breaks: true });
}
2
3
# 四、深入追问(面试常见 follow-up)
Q:为什么不把完整工具输出都塞进标题?
A:标题要可扫描,只放摘要;完整输出进折叠内容,避免长日志破坏聊天节奏。
Q:marked 有什么安全问题?
A:marked 默认不消毒 HTML。生产环境要配 DOMPurify;教学版本地 demo 明确标注该缺口。
Q:为什么要本地托管 marked,而不是 CDN?
A:离线可用、版本可控,也符合工具型应用少依赖外网的原则。
Q:为什么 <summary> 还会踩坑?
A:浏览器对 summary 默认样式和自定义布局有差异。强制 display:flex 能避免折叠标题变成空条。
# 五、踩坑 / 设计权衡
README 记录了几个真实排障:旧进程占端口导致一直访问旧服务、/?v=69 因路径精确匹配落到兜底、浏览器缓存旧 HTML。这些都不是 AI 问题,是 Web 工程日常。
# 六、与真实源码的对照
| 我们的实现 | Claude Code 源码 |
|---|---|
<details> 工具卡 | 完整工具消息组件 |
argSummary/preview | 结构化工具输入输出、diff、复制 |
marked 渲染 Markdown | Markdown + 高亮 + 消毒 |
| 简单视觉打磨 | 设计系统、主题、复杂交互状态 |
# 七、一句话总结
工具 UI 的目标是“过程可见但不喧宾夺主”:摘要用于扫描,详情用于排错,Markdown 交给成熟库。
# 下一节预告
下一篇是 step70:抽出 createCore(),让终端和 Web 共用同一套工具装配,解决功能漂移。