码道 · 大学生英语速记 AI 助教开发全记录
码道 · 大学生英语速记 AI 助教开发全记录
从「一个想法」到「一个能跑能用的 AI 学习产品」,这篇文章完整记录了我使用 HTML5 + CSS3 + JavaScript 纯前端技术栈,接入 GitCode AI 大模型流式接口,打造「大学英语速记」AI 助教的全部过程——包括产品设计、系统提示词协议设计、流式响应实现、前端渲染与交互、测试验证,以及一路踩过的坑。


一、写在前面:为什么做这个项目
大学英语一直是很多同学心里的"老大难"。四六级、考研英语、雅思托福,每一场考试都像一座山。而背单词更是其中最枯燥、最劝退的环节:拿着一本厚厚的单词书,从 abandon 开始背,背到第三天还是 abandon。
我身边的很多同学都有类似的困扰:
- 背单词纯靠死记硬背,没有词根、联想、谐音这些记忆技巧,背了就忘;
- 语法知识碎片化,定语从句、非谓语动词、虚拟语气这些概念分开都认识,放在句子里就懵;
- 长难句无从下手,考研英语里的句子动辄三四十个单词,主谓宾都找不到;
- 写作文全是模板背不下来,或者背下了模板也套不进话题。
传统的解决方案是买课程、买书、找辅导机构,成本高、见效慢。而 2024 年开始,大语言模型的能力突飞猛进,它完全可以当一个随身英语助教。于是我想:能不能用最简单、最轻量、几乎零成本的方式,做一个纯前端的 AI 英语学习助手?不装 App、不建后端、不买服务器,打开浏览器就能用。
这就是「大学英语速记」项目的起点。
1.1 项目要解决的核心问题
经过一番思考,我把项目要解决的问题聚焦在以下四个方面:
第一,让记单词更高效。 传统的单词书只有词、音标和释义,缺少记忆钩子。而 AI 可以给每个单词都配上词根拆解、谐音联想、场景例句,把一个单词变成一组可记忆的信息网络。
第二,让语法学习更直观。 语法不是背出来的,是靠对比和语境理解出来的。AI 可以用对比的方式把两个容易混淆的知识点放在一起讲,比如"定语从句 vs 非谓语动词"。
第三,让学习过程有反馈。 很多同学学英语最大的问题是"以为自己会了"。如果每次学完都能有一道自测题,立刻检验学习效果,学习效率会高很多。
第四,让技术成本趋近于零。 我刻意选择了纯前端方案:不写后端、不买服务器、不用数据库,只靠浏览器原生能力加上一个 AI 大模型接口来完成整个产品。
这四点构成了项目最初的愿景,也直接决定了后面的技术选型。
二、技术选型:为什么是 HTML5 + CSS3 + JavaScript
2.1 没有后端,也能做 AI 产品
在做技术选型的时候,我其实面临很多选择:可以用 React + Node.js,可以用 Vue + Python FastAPI,也可以用 Next.js 全家桶。但最后我选择了最朴素的组合——HTML5 + CSS3 + JavaScript,三个文件搞定一切。
选择纯前端的主要理由有三点:
第一,部署成本最低。 纯静态站点可以免费部署到任何静态托管平台,也可以直接双击 index.html 在本地打开。对于学生和初学者来说,这是最友好的交付方式。
第二,传播最方便。 一个 HTML 文件、一个 CSS 文件、几个 JS 文件,打包起来不到 50KB,放哪都能跑,同学之间互相传着用非常方便。
第三,聚焦核心逻辑。 这个项目的核心不是复杂的服务端架构,而是「AI 对话 + 数据渲染」两件事。用原生 JavaScript 写,反而能让人把注意力完全集中在 AI 接口的对接和前端渲染上,不被框架的复杂度干扰。
2.2 为什么接入 GitCode AI 大模型
做 AI 对话产品,最核心的是模型能力。我选择了 GitCode AI 平台提供的 deepseek-ai/DeepSeek-V4-Flash 模型。
选择它的原因:
- 接口规范:兼容 OpenAI Chat Completions 协议,
/v1/chat/completions端点,所有主流语言都有现成的对接方式; - 流式支持:原生支持 SSE(Server-Sent Events)流式输出,可以让 AI 回复逐字显示,体验接近真实的打字聊天;
- 中文能力强:作为国产开源模型,DeepSeek 系列在中文理解与生成上的表现非常出色,非常适合面向中国大学生的英语学习场景;
- 成本友好:Flash 版本定位轻量快速,适合对话型应用。
2.3 技术架构总览
整个项目的技术架构非常简单清晰:
┌─────────────────────────────────────────────┐
│ 浏览器(前端) │
│ index.html ── 页面骨架 │
│ css/style.css ── 视觉样式 │
│ js/config.js ── API 配置(密钥隔离) │
│ js/system-prompt.js ── 系统提示词(JSON协议) │
│ js/render.js ── 卡片渲染 + 自测题交互 │
│ js/app.js ── 流式请求与对话管理 │
└────────────────────┬────────────────────────┘
│ HTTPS + SSE 流式
▼
┌─────────────────────────────────────────────┐
│ GitCode AI · /v1/chat/completions │
│ DeepSeek-V4-Flash(流式) │
└─────────────────────────────────────────────┘
前端的五个 JS 文件各司其职:配置、提示词、渲染、主逻辑分离,满足单一职责原则,方便维护和扩展。
三、系统提示词设计:让 AI 输出结构化的 JSON
3.1 为什么对话应用需要结构化的返回
这里要讲一个产品设计上的关键决策。
如果只是把聊天窗口接上大模型 API,用户问一句 AI 答一句,那这个项目只是一个"套壳聊天机器人",没有任何竞争力。作为英语学习工具,我们需要的是结构化、可交互、可复用的学习内容。
比如用户说"帮我速记 6 个四级高频词",我们希望 AI 返回的不是一大段散文,而是一个包含以下结构的 JSON:
- 这一组单词的主题是什么?
- 每个单词的音标、释义、例句、记忆技巧是什么?
- 有没有额外的学习建议?
- 能不能出一道自测题来检验学习效果?
这种结构化的数据有两个好处:
- 前端可以把数据渲染成精美的卡片,而不是让用户去读一大段没有层次的文字;
- 数据可以被二次处理,比如用户下次想要复习,可以直接复用之前的结构化数据生成复习卡片。
所以我在系统提示词里做了两件事:一是明确告诉 AI"你是大学英语速记助教",设定角色;二是用 JSON Schema 约束回复格式,并反复强调"只能输出 JSON,不允许输出任何其他文字"。
3.2 JSON 协议的详细设计
我在 js/system-prompt.js 中定义了系统提示词。核心 JSON 结构如下:
{
"type": "word|grammar|sentence|writing|exam|other",
"title": "不超过15字的标题",
"summary": "一句话总结本组内容(20字内)",
"cards": [
{
"head": "主词条/语法点/标题短语",
"phonetic": "音标(单词类必填)",
"meaning": "中文释义/规则讲解",
"example": "英文例句",
"example_cn": "例句中文翻译",
"memory_tip": "速记技巧:词根词缀/联想/谐音/场景"
}
],
"tips": ["拓展速记技巧1", "拓展速记技巧2"],
"quiz": {
"question": "围绕本次内容出1道自测题",
"options": ["A. 选项", "B. 选项", "C. 选项", "D. 选项"],
"answer": 0,
"explain": "答案解析"
}
}
这里我想重点讲几个设计细节:
第一,type 字段是整个渲染体系的调度中心。 我定义了六种类型:
word:单词速记,一次 3~6 个词,每个词都要有音标和记忆技巧;grammar:语法讲解,1~3 个语法点,用对比方式把规则讲透;sentence:长难句剖析,一句句拆主干、找修饰;writing:写作模板,2~4 段直接可套用的句式和框架;exam:考试技巧与真题解析;other:兜底类型,处理闲聊等非英语学习类问题。
前端拿到 type 之后,就可以决定用什么样式、什么图标、什么排版来渲染。这有点像前端路由——同一个数据结构,根据类型分发到不同的"页面组件"。
第二,cards 数组是内容的主体。 每个卡片代表一个独立的词条或知识点。设计上我要求每个卡片必须包含 head(主词条)和 meaning(释义),单词类还必须包含 phonetic(音标)和 memory_tip(记忆技巧)。这是内容的"最小完整性约束"。
第三,quiz 是产品差异化的关键。 我强制要求 AI 每次回复都必须附带一道自测题,包含 question、options、answer(正确答案下标)、explain(解析)。这让产品从"给你讲"变成"讲完考你",形成学习闭环。前端收到后渲染成可点击的单选题,用户点选答案立刻给出对错反馈。
3.3 系统提示词的调优过程
系统提示词不是一次写对的,我经历了多轮调优:
第一版问题:AI 忍不住输出代码块。 一开始我在提示词里说"返回 JSON",结果模型经常用 Markdown 的 ```json 代码块包裹输出。前端解析时就遇到麻烦。后来我在提示词里明确写"不允许输出 JSON 以外的文字、解释、Markdown 代码块标记(如 ```)",并且在前端做了兼容——即使模型真的输出了代码块,也会自动剥离。
第二版问题:字段内容溢出。 有时候模型会把 meaning 写得很长,把 summary 写成一段话。我在提示词里加了括号约束:“不超过2行”“20字内”“不超过15字”,让输出更克制。
第三版问题:tips 字段类型不稳定。 观察实机输出时发现,模型偶尔把 tips 数组里的元素写成对象 {"tip": "..."} 而不是字符串。我做了双重保险——既在提示词里明确数据类型,又在前端渲染时兼容两种形式。
调优原则总结: 系统提示词要做到"程序化约束 + 前端兜底"双保险。不能指望大模型 100% 遵循格式,前端必须有能力处理不完美的情况。
四、Python 到 JavaScript:流式接口的迁移
4.1 原版 Python 实现
GitCode AI 平台给的示例是 Python 代码,使用 requests 库实现流式读取:
import json
import requests
API_URL = "https://api-ai.gitcode.com/v1/chat/completions"
def query(payload):
response = requests.post(API_URL, headers=headers, json=payload, stream=True)
for line in response.iter_lines():
if not line.startswith(b"data:"):
continue
if line.strip() == b"data: [DONE]":
return
yield json.loads(line.decode("utf-8").lstrip("data:"))
chunks = query({...})
for chunk in chunks:
print(chunk["choices"])
这是一个典型的 SSE 流式解析:requests 开启流模式后,逐行读取响应;只处理以 data: 开头的数据行;遇到 data: [DONE] 就停止;每一行都是一段 JSON,用 json.loads 解析后 yield 出去。
我的任务,就是把这套逻辑用 JavaScript 完整复刻到浏览器里。
4.2 JavaScript 的关键差异
JavaScript 前端实现流式请求,有几个 Python 版本没有的问题:
第一,没有 requests 库,用什么发请求? 答案是浏览器原生的 fetch API。注意 fetch 默认拿到的 response.body 是一个 ReadableStream(可读流),这是实现流式的关键。
第二,iter_lines 怎么替代? Python 的 response.iter_lines() 会自动按行切分。而 JavaScript 的流是字节流,需要我们自己维护一个缓冲区,把不完整的行暂存起来,等收到换行符再切分。
第三,编码问题。 Python 的 iter_lines 已经处理了 UTF-8 解码。JS 需要手动用 TextDecoder("utf-8") 处理,并且要注意流式场景下的多字节字符边界问题——一个中文字符可能被拆到两个 chunk 里,缓冲区机制可以自然解决这个问题。
4.3 我的 JavaScript 实现
为了最大程度保持代码的清晰度,我实现了一个异步生成器 streamQuery。JavaScript 同样支持生成器,和 Python 的 yield 一一对应:
async function* streamQuery(payload) {
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${CONFIG.API_KEY}`
};
const response = await fetch(CONFIG.API_URL, {
method: "POST",
headers,
body: JSON.stringify(payload)
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`HTTP ${response.status}: ${errText}`);
}
if (!response.body) throw new Error("当前浏览器不支持流式响应");
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) continue;
let data = trimmed.replace(/^data:\s*/, "");
if (data === "[DONE]") return;
try {
yield JSON.parse(data);
} catch (err) {
// 跳过不完整的 JSON 行
}
}
}
}
这段代码我在几个关键点上做了防御:
- 缓冲区设计:
buffer += decoder.decode(value, {stream: true})维护一个累积缓冲区,split("\n")之后把最后一段(可能不完整)重新放回缓冲区,保证行切分永远不丢数据。 [DONE]处理:注意到这个端点的返回可能是不带空格或带空格的data:[DONE]/data: [DONE],我用正则replace(/^data:\s*/, "")统一剥掉data:前缀和空白,确保两种情况都能识别。- 容错解析:
json.loads在 JS 里对应JSON.parse,但流式场景下偶尔会解析失败,我用 try/catch 静默跳过,保证主流程不中断。
4.4 前端的使用方式
在 app.js 的 sendMessage 里,我用 for await 消费整个流:
for await (const chunk of streamQuery(payload)) {
const delta = chunk.choices && chunk.choices[0] && chunk.choices[0].delta;
if (delta && delta.content) {
fullText += delta.content;
typingUpdate("正在生成回复…");
}
}
收到每个 chunk 时,从 choices[0].delta.content 累加内容。期间界面上显示打字动画;流结束后,把完整内容交给解析器,判断是结构化 JSON 还是普通文本,再走不同的渲染分支。
这里要特别说明:模型支持思维链(reasoning_content),第一个 chunk 里可能只有 reasoning_content 没有 content,所以判断条件里必须加 if (delta && delta.content),只有真正的内容增量才累加,否则会把思维过程拼进正文。
五、前端渲染:从 JSON 到精美卡片
5.1 渲染的整体架构
js/render.js 负责把 AI 返回的 JSON 渲染成 HTML。核心函数是 cardToMarkdown(data),流程是:
- 校验数据是否合法;
- 根据
type字段决定类型标签的文案和图标; - 依次渲染标题区、卡片列表、拓展技巧、自测题;
- 所有输出经过
escapeHtml转义,防止 XSS 注入。
5.2 卡片渲染细节
以单词类型为例,渲染出来的每个卡片包含:
- 词头行:单词本体(加粗高亮)+ 音标(灰色衬线字体);
- 释义行:中文释义;
- 例句区:英文例句(斜体)+ 中文翻译(浅色);
- 速记技巧:左侧橙色色条 + 淡黄背景的记忆技巧框,视觉上与其他内容区分。
拓展技巧区用蓝色左边框的"速记小贴士"盒子,自测题用虚线边框的"考考你"盒子——不同内容区块有鲜明的视觉锚点。
5.3 自测题的交互实现
自测题渲染成四个可点击的选项,每个选项带 data-idx 属性记录下标。点击时调用全局函数 checkAnswer:
function checkAnswer(el, answerIdx) {
const box = el.closest(".quiz-box");
const options = box.querySelectorAll(".quiz-option");
const explain = box.querySelector(".quiz-explain");
const chosen = Number(el.dataset.idx);
options.forEach((o) => o.classList.remove("quiz-correct", "quiz-wrong"));
if (chosen === answerIdx) {
el.classList.add("quiz-correct");
} else {
el.classList.add("quiz-wrong");
options[answerIdx] && options[answerIdx].classList.add("quiz-correct");
}
if (explain) explain.style.display = "block";
}
交互逻辑:选中正确项变绿,选中错误项变红并高亮正确答案,同时展开解析文字。这个"即时反馈"机制让学习变成游戏化的过程,对记忆强化非常有效。
5.4 降级渲染:AI 不听话的时候怎么办
再完美的提示词,也不能保证大模型 100% 按格式输出。所以 parseReply 做了两层降级:
- 如果 AI 用 Markdown 代码块包裹 JSON(
json ...),自动剥离包裹; - 如果整个输出都不是合法 JSON(比如用户闲聊,模型直接回文本),就降级走
renderFallback,把纯文本做简易 Markdown 渲染(加粗、行内代码、换行)。
这样的设计保证了:即使模型输出完全偏离协议,用户也永远看不到"解析失败"的报错弹窗,产品体验始终是平滑的。
六、视觉设计:美感和可用性并重
6.1 色彩体系
我以蓝色为主色调搭建了整套视觉体系:
- 主色
#3b6cf6,渐变到#6a5cf6(紫蓝渐变),用于按钮、Logo、用户气泡、类型标签; - 背景使用
#f0f4fb到#eaf2ff的柔和渐变,让页面不刺眼; - 内容卡片用白色 + 浅蓝描边
#e3e9f4,保证内容区干净利落; - 辅助色:橙色
#f59e0b(记忆技巧)、绿色#22c55e(正确反馈)、红色#ef4444(错误反馈)。
整个配色走"清爽、学院、年轻化"的路线,和"大学"这个用户群体贴合。
6.2 交互体验细节
- 打字动画:AI 思考期间显示三个跳动的小圆点,用 CSS
@keyframes blink实现,模拟实时打字; - 流式占位:生成过程中显示"正在生成回复…",结束后替换为正式内容;
- 快捷提问:四个高频场景按钮(速记高频词、语法辨析、长难句、作文模板),一键填充问题并发送,降低使用门槛;
- 移动端适配:通过媒体查询,在窄屏下隐藏副标题、让快捷栏横向滚动、气泡宽度占 88%,保证手机上也能流畅使用。
七、安全与密钥管理
7.1 密钥绝不上传到仓库
这类项目的安全隐患,多半出在 API Key 泄露。我的处理方案是:
- 真实密钥放在
js/config.js,该文件被.gitignore明确忽略; - 仓库里只提交
js/config.example.js模板,里面的 API_KEY 是占位符; - README 中专门用警示框标注:请复制模板再填入自己的令牌,不要把密钥提交到仓库。
cp js/config.example.js js/config.js # 然后编辑填入真实 Key
7.2 前端注入防护
AI 生成的内容直接插入页面 DOM,这是 XSS 的高危场景。我在渲染函数的输出端点统一做了 escapeHtml 转义(&、<、>、"、'),确保所有文本内容按字面显示,杜绝脚本注入。
这两点防御做完之后,项目的安全性就有了基本保障。当然,纯前端方案天然暴露 API Key 给用户,这在公开部署时仍然是一个权衡取舍,适合个人学习场景;生产级应用建议再加一层后端代理做密钥中转。
八、测试验证:用自动化浏览器跑真实对话
代码写完不能拍脑袋说"能用",我做了两层验证。
8.1 接口层验证
先用 curl 直接打 GitCode AI 接口,验证模型是否按照系统提示词返回合法 JSON:
curl -sN https://api-ai.gitcode.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-d '{"model":"deepseek-ai/DeepSeek-V4-Flash", ...}'
然后用 Node 脚本解析整个流,断言 JSON 合法、type 正确、cards 和 quiz 齐全。实测模型稳定返回 {"type":"word", ...} 的合法结构。
这是我实测的部分返回(节选):
{
"type": "word",
"title": "6个四级高频词速记",
"cards": [
{
"head": "abundant",
"phonetic": "/əˈbʌndənt/",
"meaning": "adj. 丰富的;充裕的",
"example": "The region is abundant in natural resources.",
"example_cn": "该地区自然资源丰富。",
"memory_tip": "词根记忆:ab(加强) + und(波浪) + ant(…的) → 像波浪一样涌来 → 丰富的。"
}
],
"quiz": {
"question": "The country has ____ supplies of fresh water.",
"options": ["A. abundant", "B. abandoned", "C. absent", "D. absurd"],
"answer": 0,
"explain": "abundant 意为“丰富的、充裕的”,符合题意。"
}
}
8.2 浏览器端到端验证
我用 Playwright 驱动真实 Chromium 浏览器,模拟用户点击快捷按钮、等待 AI 回复、点击自测题选项的完整流程:
- 断言页面上出现用户气泡;
- 等待 AI 回复完成(等待
.quiz-box元素出现); - 统计词汇卡片数量和自测选项数量;
- 模拟点击第一个选项,验证对错反馈出现;
- 再发送第二条文本消息,验证多轮对话正常;
- 全程监听控制台错误。
验证结果:结构化卡片渲染成功(6 张词汇卡片 + 4 个自测选项)、选择题交互正常、控制台零报错。
8.3 验证中发现并修复的 Bug
自动化测试帮我发现了两个真实 Bug:
Bug 1:冗余的 renderCard 调用。 一度在 app.js 里残留了一行 if (result.data) renderCard(result.data),但 render.js 里根本没有这个函数定义,导致运行时抛出 ReferenceError: renderCard is not defined。测试日志抓到了它,随即删除。这也印证了自动化测试的重要性——单纯人工看代码很难发现这种问题。
Bug 2:tips 字段类型不稳定。 模型有时把 tips 数组元素返回成对象 {"tip": "..."},我在渲染层加了一行兼容逻辑:typeof t === "string" ? t : (t && t.tip) || "",彻底解决。
九、项目目录结构
最终的项目结构非常精简:
├── index.html # 页面入口(顶栏/聊天区/快捷栏/输入框)
├── favicon.svg # 站点图标
├── css/
│ └── style.css # 全局样式(卡片/自测题/响应式)
├── js/
│ ├── config.example.js # 配置模板(复制为 config.js)
│ ├── config.js # 本地配置(含 API Key,不入库)
│ ├── system-prompt.js # AI 系统提示词(JSON 协议)
│ ├── render.js # JSON → HTML 卡片渲染
│ └── app.js # 聊天逻辑:流式请求 + 解析
├── .gitignore # 忽略本地密钥配置
├── README.md # 项目说明文档
└── docs/blog/ # 博客与文档
五个 JS 文件按职责清晰拆分,每个文件单一职责,新手也能快速看懂。
十、使用方式
10.1 配置
cp js/config.example.js js/config.js
编辑 js/config.js,把 API_KEY 换成自己的 GitCode AI 令牌。
10.2 运行
python3 -m http.server 8080 --directory .
# 或
npx serve .
浏览器打开 http://localhost:8080 即可使用。
10.3 典型使用场景
- 点「速记高频词」:一键获得 6 个高频词 + 记忆技巧 + 自测题;
- 问"定语从句和非谓语动词有什么区别":获得对比式语法解析;
- 点「长难句」:获得主干拆解 + 翻译策略;
- 点「作文模板」:获得万能句型和框架。
十一、踩坑记录
开发过程中积累了一些经验,写在这里供后来者参考:
- SSE 必须用
response.body.getReader(),不要用response.text()一次性读完——那会丢失流式体验; data:前缀可能带空格也可能不带,用正则统一剥离最稳妥;- 多字节字符会被拆到不同 chunk,缓冲区 +
TextDecoder(stream:true)是关键; - 首次 chunk 可能只有
reasoning_content,判断内容增量必须检查delta.content是否存在; - 模型输出的
tips等数组元素类型可能漂移,前端渲染要做类型兼容; - 浏览器直接
file://打开可能因 CORS 失败,本地开发务必起 HTTP 服务; - API Key 必须通过
.gitignore隔离,一旦提交到公开仓库就成了安全事故; - 自动化测试帮助发现了手测发现不了的报错,比如那个残留的
renderCard调用。
十二、总结与展望
12.1 项目亮点回顾
「大学英语速记」用最轻量的技术栈,做出了一个体验完整的 AI 学习产品:
- 纯前端、零依赖、零后端:三个技术,五个 JS 文件,最后打包不过几十 KB;
- 结构化 AI 协议:用系统提示词把大模型的输出约束成 JSON,让 UI 可以渲染成精美的学习卡片;
- 学习闭环:速记 → 技巧 → 自测题 → 解析反馈,一个很完整的学习动线;
- 全流程验证:接口层 + 浏览器端自动化双保险,实测通过;
- 安全合规:密钥隔离、XSS 防御,好事做在前面。
12.2 后续演进方向
这个项目还留有充分的扩展空间,列几个我考虑过的方向:
- 加入记忆曲线复习:利用本地
localStorage存储学过的词,按艾宾浩斯遗忘曲线定时提醒复习; - 支持语音朗读:接入 Web Speech API,让 AI 生成的内容可以发音,练听力练口语;
- 多模型切换:把模型名做成可选项,方便对比不同模型的效果;
- 导出学习笔记:把生成的单词卡片一键导出为 Markdown 或图片,方便打印;
- 用户画像与错题本:记录每次自测题的错误,自动生成错题本延后重测。
12.3 写在最后
这个项目从立项到上线,走完了一个完整的小产品闭环:想法 → 技术选型 → 协议设计 → 编码 → 测试 → 部署 → 文档。它很好地印证了一件事:AI 时代,做产品的最小成本可以非常低。 不需要服务器,不需要团队,一个人、一台电脑、一份热情,加上一个靠谱的大模型接口,就能做出对大家有真实帮助的工具。
如果你也是大学生,或者正想学前端、学 AI 应用开发,我建议你也从这样一个小而美的项目开始:找一个具体的痛点,用最简单的技术栈做出来,跑起来,让真实用户给你反馈。技术能力是在一次又一次"把它做出来"的过程中增长的,而不是看一百遍教程。
愿我们都能在码道上稳步前行,用代码解决真实世界的问题。
项目地址:https://gitcode.com/zhjdwk1018/zmjkk
技术栈:HTML5 · CSS3 · JavaScript · GitCode AI (DeepSeek-V4-Flash) · SSE 流式
本文由「码道 · 大学英语速记」项目作者撰写,记录 AI 学习类 Web 应用从零到一的完整开发过程。> 项目地址:https://gitcode.com/zhjdwk1018/zmjkk
码道 · 大学生英语速记 AI 助教开发全记录
从「一个想法」到「一个能跑能用的 AI 学习产品」,这篇文章完整记录了我使用 HTML5 + CSS3 + JavaScript 纯前端技术栈,接入 GitCode AI 大模型流式接口,打造「大学英语速记」AI 助教的全部过程——包括产品设计、系统提示词协议设计、流式响应实现、前端渲染与交互、测试验证,以及一路踩过的坑。
一、写在前面:为什么做这个项目
大学英语一直是很多同学心里的"老大难"。四六级、考研英语、雅思托福,每一场考试都像一座山。而背单词更是其中最枯燥、最劝退的环节:拿着一本厚厚的单词书,从 abandon 开始背,背到第三天还是 abandon。
我身边的很多同学都有类似的困扰:
- 背单词纯靠死记硬背,没有词根、联想、谐音这些记忆技巧,背了就忘;
- 语法知识碎片化,定语从句、非谓语动词、虚拟语气这些概念分开都认识,放在句子里就懵;
- 长难句无从下手,考研英语里的句子动辄三四十个单词,主谓宾都找不到;
- 写作文全是模板背不下来,或者背下了模板也套不进话题。
传统的解决方案是买课程、买书、找辅导机构,成本高、见效慢。而 2024 年开始,大语言模型的能力突飞猛进,它完全可以当一个随身英语助教。于是我想:能不能用最简单、最轻量、几乎零成本的方式,做一个纯前端的 AI 英语学习助手?不装 App、不建后端、不买服务器,打开浏览器就能用。
这就是「大学英语速记」项目的起点。
1.1 项目要解决的核心问题
经过一番思考,我把项目要解决的问题聚焦在以下四个方面:
第一,让记单词更高效。 传统的单词书只有词、音标和释义,缺少记忆钩子。而 AI 可以给每个单词都配上词根拆解、谐音联想、场景例句,把一个单词变成一组可记忆的信息网络。
第二,让语法学习更直观。 语法不是背出来的,是靠对比和语境理解出来的。AI 可以用对比的方式把两个容易混淆的知识点放在一起讲,比如"定语从句 vs 非谓语动词"。
第三,让学习过程有反馈。 很多同学学英语最大的问题是"以为自己会了"。如果每次学完都能有一道自测题,立刻检验学习效果,学习效率会高很多。
第四,让技术成本趋近于零。 我刻意选择了纯前端方案:不写后端、不买服务器、不用数据库,只靠浏览器原生能力加上一个 AI 大模型接口来完成整个产品。
这四点构成了项目最初的愿景,也直接决定了后面的技术选型。
二、技术选型:为什么是 HTML5 + CSS3 + JavaScript
2.1 没有后端,也能做 AI 产品
在做技术选型的时候,我其实面临很多选择:可以用 React + Node.js,可以用 Vue + Python FastAPI,也可以用 Next.js 全家桶。但最后我选择了最朴素的组合——HTML5 + CSS3 + JavaScript,三个文件搞定一切。
选择纯前端的主要理由有三点:
第一,部署成本最低。 纯静态站点可以免费部署到任何静态托管平台,也可以直接双击 index.html 在本地打开。对于学生和初学者来说,这是最友好的交付方式。
第二,传播最方便。 一个 HTML 文件、一个 CSS 文件、几个 JS 文件,打包起来不到 50KB,放哪都能跑,同学之间互相传着用非常方便。
第三,聚焦核心逻辑。 这个项目的核心不是复杂的服务端架构,而是「AI 对话 + 数据渲染」两件事。用原生 JavaScript 写,反而能让人把注意力完全集中在 AI 接口的对接和前端渲染上,不被框架的复杂度干扰。
2.2 为什么接入 GitCode AI 大模型
做 AI 对话产品,最核心的是模型能力。我选择了 GitCode AI 平台提供的 deepseek-ai/DeepSeek-V4-Flash 模型。
选择它的原因:
- 接口规范:兼容 OpenAI Chat Completions 协议,
/v1/chat/completions端点,所有主流语言都有现成的对接方式; - 流式支持:原生支持 SSE(Server-Sent Events)流式输出,可以让 AI 回复逐字显示,体验接近真实的打字聊天;
- 中文能力强:作为国产开源模型,DeepSeek 系列在中文理解与生成上的表现非常出色,非常适合面向中国大学生的英语学习场景;
- 成本友好:Flash 版本定位轻量快速,适合对话型应用。
2.3 技术架构总览
整个项目的技术架构非常简单清晰:
┌─────────────────────────────────────────────┐
│ 浏览器(前端) │
│ index.html ── 页面骨架 │
│ css/style.css ── 视觉样式 │
│ js/config.js ── API 配置(密钥隔离) │
│ js/system-prompt.js ── 系统提示词(JSON协议) │
│ js/render.js ── 卡片渲染 + 自测题交互 │
│ js/app.js ── 流式请求与对话管理 │
└────────────────────┬────────────────────────┘
│ HTTPS + SSE 流式
▼
┌─────────────────────────────────────────────┐
│ GitCode AI · /v1/chat/completions │
│ DeepSeek-V4-Flash(流式) │
└─────────────────────────────────────────────┘
前端的五个 JS 文件各司其职:配置、提示词、渲染、主逻辑分离,满足单一职责原则,方便维护和扩展。
三、系统提示词设计:让 AI 输出结构化的 JSON
3.1 为什么对话应用需要结构化的返回
这里要讲一个产品设计上的关键决策。
如果只是把聊天窗口接上大模型 API,用户问一句 AI 答一句,那这个项目只是一个"套壳聊天机器人",没有任何竞争力。作为英语学习工具,我们需要的是结构化、可交互、可复用的学习内容。
比如用户说"帮我速记 6 个四级高频词",我们希望 AI 返回的不是一大段散文,而是一个包含以下结构的 JSON:
- 这一组单词的主题是什么?
- 每个单词的音标、释义、例句、记忆技巧是什么?
- 有没有额外的学习建议?
- 能不能出一道自测题来检验学习效果?
这种结构化的数据有两个好处:
- 前端可以把数据渲染成精美的卡片,而不是让用户去读一大段没有层次的文字;
- 数据可以被二次处理,比如用户下次想要复习,可以直接复用之前的结构化数据生成复习卡片。
所以我在系统提示词里做了两件事:一是明确告诉 AI"你是大学英语速记助教",设定角色;二是用 JSON Schema 约束回复格式,并反复强调"只能输出 JSON,不允许输出任何其他文字"。
3.2 JSON 协议的详细设计
我在 js/system-prompt.js 中定义了系统提示词。核心 JSON 结构如下:
{
"type": "word|grammar|sentence|writing|exam|other",
"title": "不超过15字的标题",
"summary": "一句话总结本组内容(20字内)",
"cards": [
{
"head": "主词条/语法点/标题短语",
"phonetic": "音标(单词类必填)",
"meaning": "中文释义/规则讲解",
"example": "英文例句",
"example_cn": "例句中文翻译",
"memory_tip": "速记技巧:词根词缀/联想/谐音/场景"
}
],
"tips": ["拓展速记技巧1", "拓展速记技巧2"],
"quiz": {
"question": "围绕本次内容出1道自测题",
"options": ["A. 选项", "B. 选项", "C. 选项", "D. 选项"],
"answer": 0,
"explain": "答案解析"
}
}
这里我想重点讲几个设计细节:
第一,type 字段是整个渲染体系的调度中心。 我定义了六种类型:
word:单词速记,一次 3~6 个词,每个词都要有音标和记忆技巧;grammar:语法讲解,1~3 个语法点,用对比方式把规则讲透;sentence:长难句剖析,一句句拆主干、找修饰;writing:写作模板,2~4 段直接可套用的句式和框架;exam:考试技巧与真题解析;other:兜底类型,处理闲聊等非英语学习类问题。
前端拿到 type 之后,就可以决定用什么样式、什么图标、什么排版来渲染。这有点像前端路由——同一个数据结构,根据类型分发到不同的"页面组件"。
第二,cards 数组是内容的主体。 每个卡片代表一个独立的词条或知识点。设计上我要求每个卡片必须包含 head(主词条)和 meaning(释义),单词类还必须包含 phonetic(音标)和 memory_tip(记忆技巧)。这是内容的"最小完整性约束"。
第三,quiz 是产品差异化的关键。 我强制要求 AI 每次回复都必须附带一道自测题,包含 question、options、answer(正确答案下标)、explain(解析)。这让产品从"给你讲"变成"讲完考你",形成学习闭环。前端收到后渲染成可点击的单选题,用户点选答案立刻给出对错反馈。
3.3 系统提示词的调优过程
系统提示词不是一次写对的,我经历了多轮调优:
第一版问题:AI 忍不住输出代码块。 一开始我在提示词里说"返回 JSON",结果模型经常用 Markdown 的 ```json 代码块包裹输出。前端解析时就遇到麻烦。后来我在提示词里明确写"不允许输出 JSON 以外的文字、解释、Markdown 代码块标记(如 ```)",并且在前端做了兼容——即使模型真的输出了代码块,也会自动剥离。
第二版问题:字段内容溢出。 有时候模型会把 meaning 写得很长,把 summary 写成一段话。我在提示词里加了括号约束:“不超过2行”“20字内”“不超过15字”,让输出更克制。
第三版问题:tips 字段类型不稳定。 观察实机输出时发现,模型偶尔把 tips 数组里的元素写成对象 {"tip": "..."} 而不是字符串。我做了双重保险——既在提示词里明确数据类型,又在前端渲染时兼容两种形式。
调优原则总结: 系统提示词要做到"程序化约束 + 前端兜底"双保险。不能指望大模型 100% 遵循格式,前端必须有能力处理不完美的情况。
四、Python 到 JavaScript:流式接口的迁移
4.1 原版 Python 实现
GitCode AI 平台给的示例是 Python 代码,使用 requests 库实现流式读取:
import json
import requests
API_URL = "https://api-ai.gitcode.com/v1/chat/completions"
def query(payload):
response = requests.post(API_URL, headers=headers, json=payload, stream=True)
for line in response.iter_lines():
if not line.startswith(b"data:"):
continue
if line.strip() == b"data: [DONE]":
return
yield json.loads(line.decode("utf-8").lstrip("data:"))
chunks = query({...})
for chunk in chunks:
print(chunk["choices"])
这是一个典型的 SSE 流式解析:requests 开启流模式后,逐行读取响应;只处理以 data: 开头的数据行;遇到 data: [DONE] 就停止;每一行都是一段 JSON,用 json.loads 解析后 yield 出去。
我的任务,就是把这套逻辑用 JavaScript 完整复刻到浏览器里。
4.2 JavaScript 的关键差异
JavaScript 前端实现流式请求,有几个 Python 版本没有的问题:
第一,没有 requests 库,用什么发请求? 答案是浏览器原生的 fetch API。注意 fetch 默认拿到的 response.body 是一个 ReadableStream(可读流),这是实现流式的关键。
第二,iter_lines 怎么替代? Python 的 response.iter_lines() 会自动按行切分。而 JavaScript 的流是字节流,需要我们自己维护一个缓冲区,把不完整的行暂存起来,等收到换行符再切分。
第三,编码问题。 Python 的 iter_lines 已经处理了 UTF-8 解码。JS 需要手动用 TextDecoder("utf-8") 处理,并且要注意流式场景下的多字节字符边界问题——一个中文字符可能被拆到两个 chunk 里,缓冲区机制可以自然解决这个问题。
4.3 我的 JavaScript 实现
为了最大程度保持代码的清晰度,我实现了一个异步生成器 streamQuery。JavaScript 同样支持生成器,和 Python 的 yield 一一对应:
async function* streamQuery(payload) {
const headers = {
"Content-Type": "application/json",
"Authorization": `Bearer ${CONFIG.API_KEY}`
};
const response = await fetch(CONFIG.API_URL, {
method: "POST",
headers,
body: JSON.stringify(payload)
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`HTTP ${response.status}: ${errText}`);
}
if (!response.body) throw new Error("当前浏览器不支持流式响应");
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith("data:")) continue;
let data = trimmed.replace(/^data:\s*/, "");
if (data === "[DONE]") return;
try {
yield JSON.parse(data);
} catch (err) {
// 跳过不完整的 JSON 行
}
}
}
}
这段代码我在几个关键点上做了防御:
- 缓冲区设计:
buffer += decoder.decode(value, {stream: true})维护一个累积缓冲区,split("\n")之后把最后一段(可能不完整)重新放回缓冲区,保证行切分永远不丢数据。 [DONE]处理:注意到这个端点的返回可能是不带空格或带空格的data:[DONE]/data: [DONE],我用正则replace(/^data:\s*/, "")统一剥掉data:前缀和空白,确保两种情况都能识别。- 容错解析:
json.loads在 JS 里对应JSON.parse,但流式场景下偶尔会解析失败,我用 try/catch 静默跳过,保证主流程不中断。
4.4 前端的使用方式
在 app.js 的 sendMessage 里,我用 for await 消费整个流:
for await (const chunk of streamQuery(payload)) {
const delta = chunk.choices && chunk.choices[0] && chunk.choices[0].delta;
if (delta && delta.content) {
fullText += delta.content;
typingUpdate("正在生成回复…");
}
}
收到每个 chunk 时,从 choices[0].delta.content 累加内容。期间界面上显示打字动画;流结束后,把完整内容交给解析器,判断是结构化 JSON 还是普通文本,再走不同的渲染分支。
这里要特别说明:模型支持思维链(reasoning_content),第一个 chunk 里可能只有 reasoning_content 没有 content,所以判断条件里必须加 if (delta && delta.content),只有真正的内容增量才累加,否则会把思维过程拼进正文。
五、前端渲染:从 JSON 到精美卡片
5.1 渲染的整体架构
js/render.js 负责把 AI 返回的 JSON 渲染成 HTML。核心函数是 cardToMarkdown(data),流程是:
- 校验数据是否合法;
- 根据
type字段决定类型标签的文案和图标; - 依次渲染标题区、卡片列表、拓展技巧、自测题;
- 所有输出经过
escapeHtml转义,防止 XSS 注入。
5.2 卡片渲染细节
以单词类型为例,渲染出来的每个卡片包含:
- 词头行:单词本体(加粗高亮)+ 音标(灰色衬线字体);
- 释义行:中文释义;
- 例句区:英文例句(斜体)+ 中文翻译(浅色);
- 速记技巧:左侧橙色色条 + 淡黄背景的记忆技巧框,视觉上与其他内容区分。
拓展技巧区用蓝色左边框的"速记小贴士"盒子,自测题用虚线边框的"考考你"盒子——不同内容区块有鲜明的视觉锚点。
5.3 自测题的交互实现
自测题渲染成四个可点击的选项,每个选项带 data-idx 属性记录下标。点击时调用全局函数 checkAnswer:
function checkAnswer(el, answerIdx) {
const box = el.closest(".quiz-box");
const options = box.querySelectorAll(".quiz-option");
const explain = box.querySelector(".quiz-explain");
const chosen = Number(el.dataset.idx);
options.forEach((o) => o.classList.remove("quiz-correct", "quiz-wrong"));
if (chosen === answerIdx) {
el.classList.add("quiz-correct");
} else {
el.classList.add("quiz-wrong");
options[answerIdx] && options[answerIdx].classList.add("quiz-correct");
}
if (explain) explain.style.display = "block";
}
交互逻辑:选中正确项变绿,选中错误项变红并高亮正确答案,同时展开解析文字。这个"即时反馈"机制让学习变成游戏化的过程,对记忆强化非常有效。
5.4 降级渲染:AI 不听话的时候怎么办
再完美的提示词,也不能保证大模型 100% 按格式输出。所以 parseReply 做了两层降级:
- 如果 AI 用 Markdown 代码块包裹 JSON(
json ...),自动剥离包裹; - 如果整个输出都不是合法 JSON(比如用户闲聊,模型直接回文本),就降级走
renderFallback,把纯文本做简易 Markdown 渲染(加粗、行内代码、换行)。
这样的设计保证了:即使模型输出完全偏离协议,用户也永远看不到"解析失败"的报错弹窗,产品体验始终是平滑的。
六、视觉设计:美感和可用性并重
6.1 色彩体系
我以蓝色为主色调搭建了整套视觉体系:
- 主色
#3b6cf6,渐变到#6a5cf6(紫蓝渐变),用于按钮、Logo、用户气泡、类型标签; - 背景使用
#f0f4fb到#eaf2ff的柔和渐变,让页面不刺眼; - 内容卡片用白色 + 浅蓝描边
#e3e9f4,保证内容区干净利落; - 辅助色:橙色
#f59e0b(记忆技巧)、绿色#22c55e(正确反馈)、红色#ef4444(错误反馈)。
整个配色走"清爽、学院、年轻化"的路线,和"大学"这个用户群体贴合。
6.2 交互体验细节
- 打字动画:AI 思考期间显示三个跳动的小圆点,用 CSS
@keyframes blink实现,模拟实时打字; - 流式占位:生成过程中显示"正在生成回复…",结束后替换为正式内容;
- 快捷提问:四个高频场景按钮(速记高频词、语法辨析、长难句、作文模板),一键填充问题并发送,降低使用门槛;
- 移动端适配:通过媒体查询,在窄屏下隐藏副标题、让快捷栏横向滚动、气泡宽度占 88%,保证手机上也能流畅使用。
七、安全与密钥管理
7.1 密钥绝不上传到仓库
这类项目的安全隐患,多半出在 API Key 泄露。我的处理方案是:
- 真实密钥放在
js/config.js,该文件被.gitignore明确忽略; - 仓库里只提交
js/config.example.js模板,里面的 API_KEY 是占位符; - README 中专门用警示框标注:请复制模板再填入自己的令牌,不要把密钥提交到仓库。
cp js/config.example.js js/config.js # 然后编辑填入真实 Key
7.2 前端注入防护
AI 生成的内容直接插入页面 DOM,这是 XSS 的高危场景。我在渲染函数的输出端点统一做了 escapeHtml 转义(&、<、>、"、'),确保所有文本内容按字面显示,杜绝脚本注入。
这两点防御做完之后,项目的安全性就有了基本保障。当然,纯前端方案天然暴露 API Key 给用户,这在公开部署时仍然是一个权衡取舍,适合个人学习场景;生产级应用建议再加一层后端代理做密钥中转。
八、测试验证:用自动化浏览器跑真实对话
代码写完不能拍脑袋说"能用",我做了两层验证。
8.1 接口层验证
先用 curl 直接打 GitCode AI 接口,验证模型是否按照系统提示词返回合法 JSON:
curl -sN https://api-ai.gitcode.com/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-d '{"model":"deepseek-ai/DeepSeek-V4-Flash", ...}'
然后用 Node 脚本解析整个流,断言 JSON 合法、type 正确、cards 和 quiz 齐全。实测模型稳定返回 {"type":"word", ...} 的合法结构。
这是我实测的部分返回(节选):
{
"type": "word",
"title": "6个四级高频词速记",
"cards": [
{
"head": "abundant",
"phonetic": "/əˈbʌndənt/",
"meaning": "adj. 丰富的;充裕的",
"example": "The region is abundant in natural resources.",
"example_cn": "该地区自然资源丰富。",
"memory_tip": "词根记忆:ab(加强) + und(波浪) + ant(…的) → 像波浪一样涌来 → 丰富的。"
}
],
"quiz": {
"question": "The country has ____ supplies of fresh water.",
"options": ["A. abundant", "B. abandoned", "C. absent", "D. absurd"],
"answer": 0,
"explain": "abundant 意为“丰富的、充裕的”,符合题意。"
}
}
8.2 浏览器端到端验证
我用 Playwright 驱动真实 Chromium 浏览器,模拟用户点击快捷按钮、等待 AI 回复、点击自测题选项的完整流程:
- 断言页面上出现用户气泡;
- 等待 AI 回复完成(等待
.quiz-box元素出现); - 统计词汇卡片数量和自测选项数量;
- 模拟点击第一个选项,验证对错反馈出现;
- 再发送第二条文本消息,验证多轮对话正常;
- 全程监听控制台错误。
验证结果:结构化卡片渲染成功(6 张词汇卡片 + 4 个自测选项)、选择题交互正常、控制台零报错。
8.3 验证中发现并修复的 Bug
自动化测试帮我发现了两个真实 Bug:
Bug 1:冗余的 renderCard 调用。 一度在 app.js 里残留了一行 if (result.data) renderCard(result.data),但 render.js 里根本没有这个函数定义,导致运行时抛出 ReferenceError: renderCard is not defined。测试日志抓到了它,随即删除。这也印证了自动化测试的重要性——单纯人工看代码很难发现这种问题。
Bug 2:tips 字段类型不稳定。 模型有时把 tips 数组元素返回成对象 {"tip": "..."},我在渲染层加了一行兼容逻辑:typeof t === "string" ? t : (t && t.tip) || "",彻底解决。
九、项目目录结构
最终的项目结构非常精简:
├── index.html # 页面入口(顶栏/聊天区/快捷栏/输入框)
├── favicon.svg # 站点图标
├── css/
│ └── style.css # 全局样式(卡片/自测题/响应式)
├── js/
│ ├── config.example.js # 配置模板(复制为 config.js)
│ ├── config.js # 本地配置(含 API Key,不入库)
│ ├── system-prompt.js # AI 系统提示词(JSON 协议)
│ ├── render.js # JSON → HTML 卡片渲染
│ └── app.js # 聊天逻辑:流式请求 + 解析
├── .gitignore # 忽略本地密钥配置
├── README.md # 项目说明文档
└── docs/blog/ # 博客与文档
五个 JS 文件按职责清晰拆分,每个文件单一职责,新手也能快速看懂。
十、使用方式
10.1 配置
cp js/config.example.js js/config.js
编辑 js/config.js,把 API_KEY 换成自己的 GitCode AI 令牌。
10.2 运行
python3 -m http.server 8080 --directory .
# 或
npx serve .
浏览器打开 http://localhost:8080 即可使用。
10.3 典型使用场景
- 点「速记高频词」:一键获得 6 个高频词 + 记忆技巧 + 自测题;
- 问"定语从句和非谓语动词有什么区别":获得对比式语法解析;
- 点「长难句」:获得主干拆解 + 翻译策略;
- 点「作文模板」:获得万能句型和框架。
十一、踩坑记录
开发过程中积累了一些经验,写在这里供后来者参考:
- SSE 必须用
response.body.getReader(),不要用response.text()一次性读完——那会丢失流式体验; data:前缀可能带空格也可能不带,用正则统一剥离最稳妥;- 多字节字符会被拆到不同 chunk,缓冲区 +
TextDecoder(stream:true)是关键; - 首次 chunk 可能只有
reasoning_content,判断内容增量必须检查delta.content是否存在; - 模型输出的
tips等数组元素类型可能漂移,前端渲染要做类型兼容; - 浏览器直接
file://打开可能因 CORS 失败,本地开发务必起 HTTP 服务; - API Key 必须通过
.gitignore隔离,一旦提交到公开仓库就成了安全事故; - 自动化测试帮助发现了手测发现不了的报错,比如那个残留的
renderCard调用。
十二、总结与展望
12.1 项目亮点回顾
「大学英语速记」用最轻量的技术栈,做出了一个体验完整的 AI 学习产品:
- 纯前端、零依赖、零后端:三个技术,五个 JS 文件,最后打包不过几十 KB;
- 结构化 AI 协议:用系统提示词把大模型的输出约束成 JSON,让 UI 可以渲染成精美的学习卡片;
- 学习闭环:速记 → 技巧 → 自测题 → 解析反馈,一个很完整的学习动线;
- 全流程验证:接口层 + 浏览器端自动化双保险,实测通过;
- 安全合规:密钥隔离、XSS 防御,好事做在前面。
12.2 后续演进方向
这个项目还留有充分的扩展空间,列几个我考虑过的方向:
- 加入记忆曲线复习:利用本地
localStorage存储学过的词,按艾宾浩斯遗忘曲线定时提醒复习; - 支持语音朗读:接入 Web Speech API,让 AI 生成的内容可以发音,练听力练口语;
- 多模型切换:把模型名做成可选项,方便对比不同模型的效果;
- 导出学习笔记:把生成的单词卡片一键导出为 Markdown 或图片,方便打印;
- 用户画像与错题本:记录每次自测题的错误,自动生成错题本延后重测。
12.3 写在最后
这个项目从立项到上线,走完了一个完整的小产品闭环:想法 → 技术选型 → 协议设计 → 编码 → 测试 → 部署 → 文档。它很好地印证了一件事:AI 时代,做产品的最小成本可以非常低。 不需要服务器,不需要团队,一个人、一台电脑、一份热情,加上一个靠谱的大模型接口,就能做出对大家有真实帮助的工具。
如果你也是大学生,或者正想学前端、学 AI 应用开发,我建议你也从这样一个小而美的项目开始:找一个具体的痛点,用最简单的技术栈做出来,跑起来,让真实用户给你反馈。技术能力是在一次又一次"把它做出来"的过程中增长的,而不是看一百遍教程。
愿我们都能在码道上稳步前行,用代码解决真实世界的问题。
项目地址:https://gitcode.com/zhjdwk1018/zmjkk
技术栈:HTML5 · CSS3 · JavaScript · GitCode AI (DeepSeek-V4-Flash) · SSE 流式
本文由「码道 · 大学英语速记」项目作者撰写,记录 AI 学习类 Web 应用从零到一的完整开发过程。
更多推荐


所有评论(0)