# Step 68: 最简 web 前端
一句话导读:用一个自包含 HTML 页面连上 WebSocket,把 EngineEvent 渲染成聊天气泡、流式文本、工具提示和权限卡片。
# 一、这一步做了什么(What)
step68 新增 server/public/index.html,并让 wsServer.ts 托管它。页面连接 ws://location.host,发送 prompt,接收 event、permission_request、done 等消息,渲染成浏览器聊天界面。
这一步终于让 mini Claude Code 从终端走到浏览器。
# 二、面试官视角:为什么要做?(Why)
面试题:有了 WebSocket 协议,为什么还要做前端页面?用客户端发 JSON 不也能测吗?
协议通了只说明后端可用;真正的产品体验在 UI 层:用户要看到流式输出、工具调用、权限风险、token/cost 反馈,并能在权限点做决定。Web 前端是把 agent 事件流翻译成人能操作的界面。
| WS 消息 | UI 表现 |
|---|---|
| text/thinking | AI 气泡逐字追加 |
| tool_calls | 工具 chip |
| tool_result | 成功/失败与耗时 |
| round | token/cost/cache |
| permission_request | 允许/拒绝按钮 |
# 三、原理:它是怎么工作的(How)
前端发送:
ws.send(JSON.stringify({ type: "prompt", text }));
接收事件:
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.type === "event") renderEvent(m.ev);
if (m.type === "permission_request") renderPermission(m);
};
2
3
4
5
权限按钮:
ws.send(JSON.stringify({
type: "permission_response",
id: req.id,
allow: true
}));
2
3
4
5
数据流:
用户输入
→ ws prompt
→ QueryEngine 事件流
→ ws event
→ DOM 追加/更新气泡
→ permission_request 时渲染按钮
→ permission_response 回到服务端
2
3
4
5
6
7
# 四、深入追问(面试常见 follow-up)
Q:为什么页面和 WS 服务同端口?
A:同源避免 CORS 和 WS 跨域配置,教学版更聚焦事件协议。
Q:流式打字机需要额外定时器吗?
A:不需要。后端本来逐段推 text 事件,前端把内容 append 到当前 AI 气泡即可。
Q:权限卡片为什么要显示 risk?
A:risk 来自 Bash 分类器,UI 可以用颜色表达危险程度,让用户审批时有上下文。
Q:浏览器实测的价值是什么?
A:真实 DOM、真实 WS、真实点击事件会暴露纯协议测试看不到的问题,比如按钮状态、输入锁定、重连。
# 五、踩坑 / 设计权衡
step68 纯手写 HTML/JS,没有框架、没有 Markdown 完整渲染、没有会话列表。它要验证的是端到端闭环:输入 → 模型 → 工具审批 → 输出。
# 六、与真实源码的对照
| 我们的实现 | Claude Code 源码 |
|---|---|
| 单文件 HTML/JS | 完整 React Web 前端 |
| 简单气泡/权限卡 | 组件化消息、权限、工具 UI |
| 本地 WS | bridge/remote control channel |
| 前端内存历史 | 持久化 session transcript |
# 七、一句话总结
step68 把抽象事件流变成了可操作 UI:agent 的每个阶段都能被浏览器看见、点击和反馈。
# 下一节预告
下一篇是 step69:打磨工具调用 UI,并用 marked 替换脆弱的手写 Markdown 正则。