💡 背景:为什么做一个“不给答案”的导师?
在辅导大一新生高数时,我发现一个痛点:直接把解题步骤丢给 AI,学生只会复制粘贴,遇到考试依然不会。真正的数学思维是“启发式”的。
因此,我开发了【高数追问者】。它的核心原则是:只问不答。它不会直接给你公式,而是通过反问、拆解概念、追问细节,一步步引导你自己推导出结果。同时,它支持拍照识题,并且能记住你的年级和对话上下文。
🏗️ 技术架构:极简的前端单文件 + 后端大模型
为了降低部署成本并保证响应速度,我没有使用笨重的框架,而是采用了最经典的前后端分离:
· 前端:纯原生 HTML/CSS/JS(单文件),无任何第三方依赖(Vue/React 都不用),极致轻量。
· 后端:轻量级 Python 服务(如 FastAPI),负责对接大模型 API(支持视觉模型处理图片)、维护会话状态。
· 存储:利用浏览器 localStorage 持久化对话历史。
🛠️ 核心功能与前端工程化实践
在开发过程中,我踩了不少坑,也沉淀了一些“原生 JS”的最佳实践:
- 移动端极致适配
· 防文字挤压:在 Flex 布局的 Header 中,如果不加 min-width: 0,标题文字会被挤成竖排。我通过 min-width: 0 + white-space: nowrap 完美解决了窄屏下的排版问题。
· 输入框高度动态重置:移动端转屏或拉伸窗口时,textarea 的基准高度会变化。我合并了三个 resize 监听器,统一由 onResize 处理,并根据媒体查询动态更新 INPUT_H 变量,避免了死板的魔法数字。
- 本地状态与失败回滚(Rollback)
· 对话持久化:对话历史按 session_id 存入 localStorage,上限裁剪至 60 条,防止长会话拖垮页面。图片由于体积大,绝不存本地,刷新后只保留一条虚线框的占位文字,提示用户“图片未保留”。
· 优雅的失败回滚:当网络请求失败或超时时,前端不仅要“报错”,还要“恢复现场”。我会将用户刚发送的文字、甚至刚刚选好的图片预览全部还原到输入框,并清空已经进入 history 的失败记录,让用户可以直接点击“重发”。
· 职责边界:在处理错误时,回滚函数 rollbackUserTurn 只负责恢复数据和 UI 状态,绝不写错误提示语。具体的错误提示(如 429 限流、401 Key 无效)由知道错误原因的请求发起方(send 函数)来追加,避免了错误信息被覆盖。
- 前端图片压缩策略
由于后端识别数学公式需要足够清晰,但手机直出照片动辄 3-5MB,直接传会给带宽和模型都带来压力。
我写了一个 compressImage 函数:
· 使用 Canvas 按面积(MAX_PIXELS = 1600 * 1600)而非长边进行约束(因为超长截图按长边压缩会把公式糊掉)。
· 铺白底(防止透明 PNG 转 JPEG 时变成黑底)。
· 压缩至长宽约 1600 内,体积通常降至 200-400KB,公式清晰度完全不受影响。
- 隐私模式下的容错(防白屏)
在 Safari 无痕模式或某些隐私设置下,直接调用 localStorage.getItem 可能会抛出异常,导致整个 JS 脚本崩溃(页面白屏)。我封装了 safeGet 和 safeSet 函数,对所有本地存储操作进行 try-catch 包裹,确保存储不可用时应用依然能正常运行。
🧠 后端逻辑(LLM 提示词设计)
为了实现“只问不答”,后端的 Prompt 设计至关重要。模型被严格约束:
· 六层合规拦截:拦截给答案、拦截敏感词、拦截肯定词。
· 话题锚点:苏格拉底式的核心是“追问”。模型会记住当前讨论的公式和上下文,如果学生试图跳跃话题,模型会温和地把他拉回来。
· 图片占位符:前端在发送图片时,会在 history 中写入 (请看这张图片中的题目) 的占位文本。这样大模型不仅能看到图片,还能在后续的纯文字多轮对话中,理解“我们正在讨论刚才那张图”。
🔒 安全与已知限制(重要说明)
由于这是一个纯前端的项目,我必须坦诚地说明:目前 API 请求的暗号(Token)是写在前端代码里的。
这不能作为真正的鉴权边界,它只能挡住普通的好奇用户,无法防止恶意抓包调用。
后续计划:如果参与公测的用户量变大,我会将服务迁移到云端,在服务端增加 Origin 校验和 IP 限流,并将真实 API Key 收归环境变量。
目前,【高数追问者】已经完成首个稳定版本的迭代,代码整洁度、移动端体验、异常兜底都已打磨完毕。
👉 在线体验链接:[https://acd929ab53145511f.app.workbuddy.host/]
欢迎各位开发者、同学体验,如果你有更好的 Prompt 优化思路,或者发现了前端的 Bug,欢迎在评论区与我交流!