第四幕 给中学生的 UI 不是"简化版"
第四幕 给中学生的 UI 不是"简化版"
面向中学生的 UI,不是把功能藏起来
很多人做"学生版"软件,思路是"简化版":按钮少一点、选项少一点、功能砍一点。做完发现学生根本不用。简化是把该露的露出来。
先说实话:这个项目的前端由团队里的同学负责实现。我这边只有一个参考版本,充其量是我这个后端拿来"当中学生"测试产品用的。它演示了 Markdown 渲染、KaTeX 公式、XSS 防注入、Iconify 图标、动画效果这些需求,但前端还有很多各种小问题,离能给学生用还差得远。
这一幕讲的是我"当中学生"时要求前端必须做到的事,不是一份产品说明书。
参考版现在还有这些毛病
既然是拿来当中学生测的,说几个我自己撞到的问题:
- 上传图片/PDF 后直接就进批改流程了。万一我只是想传个文件问问"这是什么",没得选。
- 点"修改消息",编辑框出现在页面最底下,不在气泡里,我得先找到它再改。
- 显式调用工具时,输入框里会同时出现两个"工具":一个是武装好的工具徽章,一个是一段文字。只要一碰那段文字,武装状态就没了,调用立刻失效。
这些都属于"能用,但不顺手"。参考版的价值是验证机制和需求,不是当最终产品。
OOBE:第一次打开,不许让用户面对空白
应用第一次启动时,settings 是空的:没有模型 key、没有配置。如果直接丢给学生一个聊天框,他只会困惑。
所以做了分步引导向导,Windows 安装向导那种一页一页的风格:
- 欢迎页
- 配置主模型(DeepSeek)
- 配置视觉模型(SiliconFlow)
- 完成页
关键设计:每点一次"下一步",就真的测一次 API 连通性。用你刚填的 key 发一个真实请求,通了才让你进下一步。填错了当场告诉你"鉴权失败",而不是等学生进主界面后才发现模型永远不回话。
测试连接用的是临时 key:只对这一次请求生效,不落盘。配置保存走单独的设置接口,落盘后模型服务热替换,下一轮调用就用新配置。
显式工具调用:让用户能"指名道姓"地使唤 Agent
LLM 工具调用有个尴尬:模型自己决定调不调工具,用户只能"暗示"。但中学生经常有明确的意图:"我要批改这份作业""我要删掉这条记忆"。于是加了显式工具调用:
- 用户在输入框输入
Plugin::Tool(比如grading::upload),前端弹出工具候选框; - 按 Tab 确认,工具名保留在输入框里,后面显示淡淡的
<可选参数>占位; - 发送时携带
force_tool,kernel 开回合并让模型首轮强制调用该工具(Responses API 的tool_choice,整回合关闭思考); - 工具结果回填后,模型继续生成回复。输出仍然全部走聊天框的 LLM 侧,显式调用不绕过模型,只是给它加了一个"必须先用这个工具"的要求。
两个细节。
候选数据来自后端:工具清单、标题、分组、图标、参数说明全部由 list_tools 提供,前端不写死。加一个新工具,前端自动出现,不用改 UI 代码。
反悔要顺滑:按 Tab 武装了工具后,你按 Backspace 把工具名删掉,武装状态也要跟着解除。不能让用户以为还在待调用状态。
用户不用进入什么"工具页面"填参数,一切都在聊天框里完成。
上传附件:学生不该看到文件路径
第一版设计里,上传是"用户给模型一个文件路径"。我很快把这个设计毙了:学生根本不知道路径是什么。
现在的流程:
- 点"选择作业文件"按钮(系统文件对话框);
- 文件同时生成两份副本:一份进系统临时目录(给 kernel 处理,白名单
mistake-agent-前缀,处理完即删),一份进数据根目录的uploads/(持久副本); - 前端直接把图片渲染出来,PDF 显示一个文件卡片;点击图片看大图、点 PDF 用系统默认程序打开;
- 附件不随临时目录删除而消失,聊天记录里随时能重新打开。
持久副本还带路径白名单:uploads/ 目录 canonicalize 校验,防符号链接逃逸。学生只看见"我传了张图片",路径是内核内部的事。
Markdown + LaTeX:让公式真的长成公式
模型输出数学、物理、化学内容时,提示词强制要求 LaTeX 标记:行内 $...$、独立 $$...$$、化学式 \mathrm{}。前端用 marked + KaTeX 渲染,所以:
输入(模型输出):二次函数顶点公式是 $y=a(x-h)^2+k$
渲染效果:二次函数顶点公式是 y = a(x-h)² + k(真实公式排版)思维链(reasoning)默认折叠,点一下展开。学生看结论,想看过程也能看。
安全:模型输出是内容,不是可信 HTML
Markdown 渲染最危险的坑:模型输出是外部输入。你把它当 HTML 渲染,等于把提示词注入变成了 XSS 通道。模型说一句 <img src=x onerror=...>,就能在用户机器上跑脚本。
所以渲染链路是:marked 解析 → DOMPurify 消毒 → KaTeX 渲染公式 → 插入 DOM。消毒开 nonStandard: true,把非标准标签也过滤掉。公式走 KaTeX 自己的安全通道,不经过 HTML。
任何渲染模型输出的应用都该有这个底线。
聊天记录:一棵从第一天长到现在的大树
还有个体验要求:切换会话时,前端不许清空聊天记录。聊天记录是所有会话按时间合并的一条大长串,从第一天用到现在,构成一棵消息树;分支用 < / > 翻看。
用户切换会话应该无感知,上上个会话的内容依然在上下文里,前端也不该因为切换就把画面清空重来。
收个尾
OOBE:第一次打开不面对空白,每一步都真测连通性。显式工具调用:指名道姓使唤 Agent,Tab 确认、Backspace 反悔,数据来自后端不写死。附件:学生只见图片/PDF,不见路径,临时 + 持久双副本。渲染:Markdown + KaTeX,DOMPurify 兜底,模型输出永远是不可信输入。历史:一棵大树,不因切换而消失。
没有终章,但有个未来展望
四幕到这里就讲完了,但项目没有结局。Windows 安装包还没打、工具并行调用在计划里、第三方插件和技能系统排在后面、家长端报表也在路上。
还有一个更大的打算:把 Mistake Agent 的核心抠出来,做成一个独立的 Agent 框架。内核、两段式插件契约、CallerPolicy、会话调度这些机制,其实和"错题"这个业务没关系,它们是一个 Agent 该有的骨架。我想把它抽成通用框架,去掉错题、批改这些业务,只留调度、插件、信任边界,让它成为我简历上"自己写过一个 Agent 框架"的那一行。
这件事做成的标准我也想好了:一个新的 Agent 项目,只写业务插件,不改内核一行,就能跑起来。
这个系列会跟着项目一起长。如果你也在做 Agent 项目,希望这几幕能帮你少踩几个坑,尤其是"控制消息和对话内容分开"那条,还有那句"9.7617 才是真实可用的余额"。
