在我动手写前端之前,GitCode 平台的 API 文档给出了一份 Python 参考代码。这段代码清晰地展示了这个 API 的调用模型:
仓库地址::https://atomgit.com/zhuangsaq/gaozhongzuowenbianxieAI.git
在这里插入图片描述
码道项目生成:
在这里插入图片描述
一、写在前面:一张空白的作文纸

不知道你有没有经历过这样的夜晚:台灯亮着,书桌前摊开的作文本上一片空白,笔尖悬在纸面上,迟迟落不下去。审题审了二十分钟,立意改了三次,列出的提纲写了一半又划掉——距离交卷的时间一分一秒地迫近,而你大脑里那些关于"青春"“奋斗”"担当"的素材,像被打乱的拼图一样,怎么也拼不成一篇像样的文章。

这是我决定做「高中作文编写AI」这个项目的起点。

高中生写作文的难点,从来不是"不会写字",而是"不知道怎么开始"。审题不到位,立意跑偏,结构散乱,素材贫乏,语言平庸——这些问题不是学生不努力,而是他们缺少一个"手把手"的引导者。语文老师只有一个,不可能在每一个晚自习都陪在每一个学生身边;教辅材料写得再详细,也不过是静态的文本,无法针对某一篇具体的作文给出反馈。

于是在人工智能大行其道的今天,一个念头自然而然地浮出水面:能不能让大模型扮演一个"随叫随到的作文导师"?学生输入一道作文题,AI 帮他审题、构思、列提纲、写范文,甚至模仿阅卷老师给他的作文打分、指出问题。这个念头,就是「高中作文编写AI」项目的全部起点。

它不仅是一个工具,更像是一条"码道"——一条用代码铺出来的、从"下笔为难"通向"文思泉涌"的路。这篇文章,我会把这个项目的每一个设计决策、每一段关键代码、每一个踩过的坑,都摊开来讲清楚。如果你也对"纯前端 + 大模型"这种极简组合感兴趣,如果你也是一个想用技术解决真实教育痛点的人,那么接下来的几千字,也许能给你一些启发。

二、项目概览:它到底是一个什么样的应用

在进入技术细节之前,先让读者对项目轮廓有一个整体认知。

「高中作文编写AI」是一个运行在浏览器里的中文作文智能助手。它不需要安装任何软件,不需要配置任何环境,只要有一个现代浏览器,打开网页就能用。它的核心工作流是这样组织的:

第一,智能审题。学生把作文题目粘贴进来,AI 会先做一番"解剖":这是什么类型的题目(命题作文、材料作文、话题作文)?材料里的关键词是什么?命题人真正想让学生回答的核心问题是什么?审题有哪些坑?这一步的输出,是几行清晰的审题报告,让学生在下笔前先"站稳"。

第二,立意指导。审题之后,AI 会给出 2 到 3 个不同角度的立意建议:有传统稳妥的,有新颖出彩的,有贴近学生生活经验的。每个立意都会附上简要的论证路径,让学生明白"这个角度为什么能写、打算怎么展开"。这一步解决的是"我不知道该写什么"的问题。

第三,提纲生成。选定立意后,AI 会生成一篇完整的作文提纲:标题、开头、分论点、每个段落的核心句、素材建议、结尾。提纲以结构化的方式呈现,学生可以像看一张施工图纸那样看懂整篇文章的骨架。

第四,范文成文。学生可以一键让 AI 把某段提纲展开成完整的段落,甚至直接生成一篇符合高考阅卷标准的范文,控制在 800 字左右(高考作文的常见要求),并附上"高分亮点"说明,告诉学生这篇范文好在哪里、可模仿的点是什么。

第五,习作批改。学生把自己的习作粘贴进来,AI 会像一个阅卷老师那样给出分数评估、逐段点评、优点与问题清单,以及具体的修改建议。这是整个应用里最"重"的一个功能,也是最能体现大模型价值的部分。

一句话总结:审题 → 立意 → 提纲 → 成文 → 批改,这恰好是一位语文老师辅导一篇作文的完整流程。而支撑这整套流程的,是一个干净的纯前端项目:一个 HTML 文件承载结构,一个 CSS 文件负责视觉,一个 JavaScript 文件驱动逻辑,再加上一个大模型的远程大脑。没有后端服务器,没有数据库,没有构建工具,没有框架依赖。

整个项目最终交付的文件结构非常干净:

高中作文编写AI/
├── index.html      # 页面结构与界面骨架
├── style.css       # 纸墨质感视觉样式
├── app.js          # 应用逻辑与 AI 调用
└── README.md       # 项目说明文档

是的,就这四个文件。任何一个懂一点 HTML 的人,把文件夹往浏览器里一拖,应用就能跑起来。下面我会依次拆解:为什么这么选、AI 是怎么进来的、JSON 协议是怎么设计的、以及一路踩过的坑。

三、技术选型:为什么是 HTML5 + CSS3 + JavaScript 三件套

聊完"是什么",该聊聊"为什么"。项目立项时,技术选型其实经过了反复权衡,最终决定用最朴素的 Web 三件套,理由如下。

3.1 零依赖,零门槛

「高中作文编写AI」的潜在用户,是高中生、语文老师和家长。我不希望他们为了运行这个项目还需要 npm install 一长串依赖,更不希望他们被 Node 版本、Python 环境、虚拟环境这种东西劝退。纯 HTML/CSS/JS 意味着:一个现代浏览器,就拥有了全世界。双击 index.html,一切开始。

这句话的背后是一个很朴素的判断:工具的使命是降低门槛,而不是制造门槛。当一个学生打开项目文档,看到的是"下载 → 双击 → 使用"三步走,而不是"安装依赖 → 配置环境 → 处理报错"的连环套,这个工具才真正抵达了它的目标用户。

3.2 与浏览器原生能力的高度契合

这个项目的两大核心能力,浏览器原生都直接提供。

第一,流式渲染。大模型的流式响应本质上是 SSE(Server-Sent Events)长连接。浏览器原生的 fetch 结合 ReadableStream 就能优雅地处理这类数据流,不需要任何额外的库,也不需要 WebSocket 这种更重的方案。模型"一个字一个字"地生成作文内容,页面"一个字一个字"地渲染出来,这就是我们熟悉的"打字机"效果,也是 AI 写作类应用体验感的第一来源。

第二,对话式 UI。整个应用本质上是一个多功能对话界面:学生输入题目,AI 返回审题报告;学生点击"生成提纲",AI 返回结构化卡片。聊天式的界面(消息气泡、输入框、功能按钮、滚动容器)本身就是 HTML/CSS 最擅长的表现形态,用原生的 DOM 操作就能做得很出彩。

3.3 让学习者看到"最小完整实现"

项目的另一个隐藏定位是教学示范。读者在阅读 app.js 的源码时,不需要理解 React 的虚拟 DOM,不需要理解 Vue 的双向绑定,只需要看懂"发起请求、解析流、更新 DOM"这条最原始的主线,就能复刻出一个功能完备的 AI 应用。我认为,对于一个想了解"AI 应用到底是怎么工作"的新手来说,这种没有任何魔法(no magic)的实现,恰恰是最有价值的教材。

3.4 对比:为什么不选重方案

其实我也认真考虑过 React + Vite、Vue + 脚手架、甚至 Python FastAPI + 前端模板的方案,但都否掉了。重型框架解决的是"大型应用状态管理"的问题,而这里的状态就是"当前对话上下文、加载状态、功能模式",一个普通的对象就管得明明白白;后端方案解决的是"服务端计算与密钥保护"的问题,但纯前端项目可以牺牲一点安全冗余来换取部署的极大便利。技术选型没有银弹,只有"合不合适"。在作文助手这个场景,小项目的活法就是轻装上阵。

四、核心机制:大模型调用,从 Python 迁移到 JavaScript

这一章是整个项目的技术心脏。我要详细拆解"AI 是怎么被请进这个页面的"。

一、写在前面:一张空白的作文纸

不知道你有没有经历过这样的夜晚:台灯亮着,书桌前摊开的作文本上一片空白,笔尖悬在纸面上,迟迟落不下去。审题审了二十分钟,立意改了三次,列出的提纲写了一半又划掉——距离交卷的时间一分一秒地迫近,而你大脑里那些关于"青春"“奋斗”"担当"的素材,像被打乱的拼图一样,怎么也拼不成一篇像样的文章。

这是我决定做「高中作文编写AI」这个项目的起点。

高中生写作文的难点,从来不是"不会写字",而是"不知道怎么开始"。审题不到位,立意跑偏,结构散乱,素材贫乏,语言平庸——这些问题不是学生不努力,而是他们缺少一个"手把手"的引导者。语文老师只有一个,不可能在每一个晚自习都陪在每一个学生身边;教辅材料写得再详细,也不过是静态的文本,无法针对某一篇具体的作文给出反馈。

于是在人工智能大行其道的今天,一个念头自然而然地浮出水面:能不能让大模型扮演一个"随叫随到的作文导师"?学生输入一道作文题,AI 帮他审题、构思、列提纲、写范文,甚至模仿阅卷老师给他的作文打分、指出问题。这个念头,就是「高中作文编写AI」项目的全部起点。

它不仅是一个工具,更像是一条"码道"——一条用代码铺出来的、从"下笔为难"通向"文思泉涌"的路。这篇文章,我会把这个项目的每一个设计决策、每一段关键代码、每一个踩过的坑,都摊开来讲清楚。如果你也对"纯前端 + 大模型"这种极简组合感兴趣,如果你也是一个想用技术解决真实教育痛点的人,那么接下来的几千字,也许能给你一些启发。

二、项目概览:它到底是一个什么样的应用

在进入技术细节之前,先让读者对项目轮廓有一个整体认知。

「高中作文编写AI」是一个运行在浏览器里的中文作文智能助手。它不需要安装任何软件,不需要配置任何环境,只要有一个现代浏览器,打开网页就能用。它的核心工作流是这样组织的:

第一,智能审题。学生把作文题目粘贴进来,AI 会先做一番"解剖":这是什么类型的题目(命题作文、材料作文、话题作文)?材料里的关键词是什么?命题人真正想让学生回答的核心问题是什么?审题有哪些坑?这一步的输出,是几行清晰的审题报告,让学生在下笔前先"站稳"。

第二,立意指导。审题之后,AI 会给出 2 到 3 个不同角度的立意建议:有传统稳妥的,有新颖出彩的,有贴近学生生活经验的。每个立意都会附上简要的论证路径,让学生明白"这个角度为什么能写、打算怎么展开"。这一步解决的是"我不知道该写什么"的问题。

第三,提纲生成。选定立意后,AI 会生成一篇完整的作文提纲:标题、开头、分论点、每个段落的核心句、素材建议、结尾。提纲以结构化的方式呈现,学生可以像看一张施工图纸那样看懂整篇文章的骨架。

第四,范文成文。学生可以一键让 AI 把某段提纲展开成完整的段落,甚至直接生成一篇符合高考阅卷标准的范文,控制在 800 字左右(高考作文的常见要求),并附上"高分亮点"说明,告诉学生这篇范文好在哪里、可模仿的点是什么。

第五,习作批改。学生把自己的习作粘贴进来,AI 会像一个阅卷老师那样给出分数评估、逐段点评、优点与问题清单,以及具体的修改建议。这是整个应用里最"重"的一个功能,也是最能体现大模型价值的部分。

一句话总结:审题 → 立意 → 提纲 → 成文 → 批改,这恰好是一位语文老师辅导一篇作文的完整流程。而支撑这整套流程的,是一个干净的纯前端项目:一个 HTML 文件承载结构,一个 CSS 文件负责视觉,一个 JavaScript 文件驱动逻辑,再加上一个大模型的远程大脑。没有后端服务器,没有数据库,没有构建工具,没有框架依赖。

整个项目最终交付的文件结构非常干净:

高中作文编写AI/
├── index.html      # 页面结构与界面骨架
├── style.css       # 纸墨质感视觉样式
├── app.js          # 应用逻辑与 AI 调用
└── README.md       # 项目说明文档

是的,就这四个文件。任何一个懂一点 HTML 的人,把文件夹往浏览器里一拖,应用就能跑起来。下面我会依次拆解:为什么这么选、AI 是怎么进来的、JSON 协议是怎么设计的、以及一路踩过的坑。

三、技术选型:为什么是 HTML5 + CSS3 + JavaScript 三件套

聊完"是什么",该聊聊"为什么"。项目立项时,技术选型其实经过了反复权衡,最终决定用最朴素的 Web 三件套,理由如下。

3.1 零依赖,零门槛

「高中作文编写AI」的潜在用户,是高中生、语文老师和家长。我不希望他们为了运行这个项目还需要 npm install 一长串依赖,更不希望他们被 Node 版本、Python 环境、虚拟环境这种东西劝退。纯 HTML/CSS/JS 意味着:一个现代浏览器,就拥有了全世界。双击 index.html,一切开始。

这句话的背后是一个很朴素的判断:工具的使命是降低门槛,而不是制造门槛。当一个学生打开项目文档,看到的是"下载 → 双击 → 使用"三步走,而不是"安装依赖 → 配置环境 → 处理报错"的连环套,这个工具才真正抵达了它的目标用户。

3.2 与浏览器原生能力的高度契合

这个项目的两大核心能力,浏览器原生都直接提供。

第一,流式渲染。大模型的流式响应本质上是 SSE(Server-Sent Events)长连接。浏览器原生的 fetch 结合 ReadableStream 就能优雅地处理这类数据流,不需要任何额外的库,也不需要 WebSocket 这种更重的方案。模型"一个字一个字"地生成作文内容,页面"一个字一个字"地渲染出来,这就是我们熟悉的"打字机"效果,也是 AI 写作类应用体验感的第一来源。

第二,对话式 UI。整个应用本质上是一个多功能对话界面:学生输入题目,AI 返回审题报告;学生点击"生成提纲",AI 返回结构化卡片。聊天式的界面(消息气泡、输入框、功能按钮、滚动容器)本身就是 HTML/CSS 最擅长的表现形态,用原生的 DOM 操作就能做得很出彩。

3.3 让学习者看到"最小完整实现"

项目的另一个隐藏定位是教学示范。读者在阅读 app.js 的源码时,不需要理解 React 的虚拟 DOM,不需要理解 Vue 的双向绑定,只需要看懂"发起请求、解析流、更新 DOM"这条最原始的主线,就能复刻出一个功能完备的 AI 应用。我认为,对于一个想了解"AI 应用到底是怎么工作"的新手来说,这种没有任何魔法(no magic)的实现,恰恰是最有价值的教材。

3.4 对比:为什么不选重方案

其实我也认真考虑过 React + Vite、Vue + 脚手架、甚至 Python FastAPI + 前端模板的方案,但都否掉了。重型框架解决的是"大型应用状态管理"的问题,而这里的状态就是"当前对话上下文、加载状态、功能模式",一个普通的对象就管得明明白白;后端方案解决的是"服务端计算与密钥保护"的问题,但纯前端项目可以牺牲一点安全冗余来换取部署的极大便利。技术选型没有银弹,只有"合不合适"。在作文助手这个场景,小项目的活法就是轻装上阵。

四、核心机制:大模型调用,从 Python 迁移到 JavaScript

这一章是整个项目的技术心脏。我要详细拆解"AI 是怎么被请进这个页面的"。

4.1 参考实现:Python 版流式调用

在我动手写前端之前,GitCode 平台的 API 文档给出了一份 Python 参考代码。这段代码清晰地展示了这个 API 的调用模型:

import os
import requests
import json

API_URL = "https://api-ai.gitcode.com/v1/chat/completions"
headers = {
    "Authorization": f"Bearer {os.getenv('GITCODE_API_KEY')}",
}

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:").rstrip("/n"))

chunks = query({
    "model": "deepseek-ai/DeepSeek-V4-Flash",
    "messages": [
        {"role": "user", "content": "帮我写一段关于坚持的作文开头"}
    ],
    "stream": True,
    "max_tokens": 4096,
    "temperature": 0.6,
    "top_p": 0.95,
    "frequency_penalty": 0,
    "thinking_budget": 2048
})

for chunk in chunks:
    print(chunk["choices"])

拆开来看,这套调用模型有四个关键点:

  • HTTP POST 到 /v1/chat/completions,这是 OpenAI 兼容的 Chat Completions 端点,如今已经成为大模型服务的事实标准——这意味着将来想换任何一家兼容此协议的模型服务商,几乎零成本。
  • 头部携带 Bearer 令牌,这是鉴权方式。注意参考代码用的是 os.getenv('GITCODE_API_KEY') 从环境变量读取,这一点在纯前端项目里是个需要特别处理的点,后面"安全与隐私"章节会详细展开。
  • 请求体是 OpenAI 兼容的 messages 数组,由 system(系统提示词)和 user/assistant(用户与助手的对话历史)组成。
  • stream: True 开启流式。服务端不会一次性返回完整回复,而是把整段文字拆成一小块一小块的增量数据,通过流式传输逐步吐出来。客户端必须逐行读取,用 data: 前缀过滤,遇到 data:[DONE] 即视为结束。

4.2 逐行迁移:JavaScript 里的"迭代器"

Python 的 query() 是一个生成器(generator),yield 让调用方可以用 for chunk in query(...) 的方式边接边用,这是流式处理最优雅的姿势。JavaScript 世界里,与之对应的概念是 ReadableStream(可读流)——fetch 返回的 response.body 天然就是一个 ReadableStream,直接可用。

迁移后的核心代码长这样:

const API_URL = 'https://api-ai.gitcode.com/v1/chat/completions';

async function queryStream(payload, onDelta, onDone) {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`
    },
    body: JSON.stringify(payload)
  });

  if (!response.ok) {
    throw new Error(`请求失败:HTTP ${response.status}`);
  }

  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 });

    // SSE 事件以换行分隔,逐行解析
    const lines = buffer.split('\n');
    buffer = lines.pop();

    for (const line of lines) {
      const trimmed = line.trim();
      if (!trimmed.startsWith('data:')) continue;

      const data = trimmed.slice(5).trim();
      if (data === '[DONE]') {
        onDone();
        return;
      }

      try {
        const json = JSON.parse(data);
        const delta = json.choices?.[0]?.delta?.content || '';
        if (delta) onDelta(delta);
      } catch (e) {
        console.warn('解析流式消息失败:', e);
      }
    }
  }
  onDone();
}

这一段代码是全文最重要的几行之一,值得逐条解释。

第一,TextDecoder('utf-8', { stream: true }) 流式数据在 TCP 层被切分成任意大小的字节块,一个多字节的中文字符(UTF-8 通常是 3 字节)完全可能被切断在两次读取之间。如果直接 decoder.decode(value) 而不带 { stream: true },遇到被截断的字节就会产生乱码。开着流式模式,解码器会把不完整的尾部字节缓存起来,等下一个字节块到达后补全。这一行,是"中文不乱码"的前提。写作文的应用里全是中文,这一行的重要性不言而喻。

第二,按行解析与 buffer 兜底。 SSE 的事件之间以换行分隔,但 reader.read() 返回的数据块不一定恰好在一行的边界结束。所以我用 lines = buffer.split('\n') 切分,把最后一段不完整的内容放回 buffer,交给下一轮拼接。这是所有 SSE 客户端都绕不开的"半包"处理。

第三,data:[DONE] 的终止判定。 服务端发完所有增量后,会发送一个特殊的结束标记。不同平台可能差一个空格(data:[DONE]data: [DONE]),所以稳妥的做法是 trim() 之后再比对。

第四,增量内容的位置。 OpenAI 兼容协议里,流式响应的 choices[0].delta.content 是增量文本;非流式请求中则放在 choices[0].message.content。做流式解析时一定别找错了字段——这是最常见的"为什么我收不到内容"的根因。

4.3 模型与超参数的选定

请求体里的几个参数也是精心调过的:

  • model: "deepseek-ai/DeepSeek-V4-Flash"。这是本次选用的推理模型,在中文理解、文本生成方面表现稳定,且推理速度快,适合需要"边写边看"的交互式写作场景。学生点一下按钮,两三秒内就要看到文字开始"往外冒",模型的速度直接决定了应用手感。
  • temperature: 0.6。采样温度控制随机性。作文生成需要稳定性和文采之间的平衡:太低了千篇一律,全是"总分总"的八股腔;太高了容易跑偏、跑题甚至语句不通。0.6 是一个经过实测的折中值。
  • top_p: 0.95。核采样参数,配合 temperature 进一步约束候选词范围,避免生成跳出逻辑轨道的离谱内容。
  • max_tokens: 4096。单次回复的最大 token 数。一篇 800 字的作文加上 JSON 结构,大约需要 3000 个 token 左右,4096 的上限既保证完整成文,又防止模型突然话痨。
  • thinking_budget: 2048。这是一个很有意思的参数,它给模型预留了"思考预算",允许它在给出正式回答之前进行一段内部推理。对审题分析、立意构思这类需要逻辑链条的任务,思考预算能显著提高输出的质量——先想清楚再下笔,这恰恰是我们想教给学生的好习惯。

五、系统提示词工程:让 AI 输出结构化的 JSON

这一章是全项目的灵魂,也是投入最多的地方——把大模型的自由文本输出,驯化成前端可以"机械消费"的 JSON 数据。这也是整个项目里最值得拿出来单独长篇大论的部分。

5.1 为什么不能要"自由文本"

在最早的版本里,我的系统提示词是这么写的:“你是高中作文辅导老师,帮学生审题并给出立意建议。”

结果模型的回复是这种画风的:

好的!这道作文题很有深度呢。首先我们来看题目……
这道题的核心是"奋斗",同学们要注意……
我觉得可以从三个角度来写:第一,奋斗是青春最亮的底色……
(以上是老师的一点浅见,希望对你有所帮助!)

看起来还挺像那么回事?但我的前端代码没法处理它。原因是:我无法结构化地取出"题目类型"“核心关键词”“立意角度列表”。我想要做的界面是:审题结果呈现为一张张字段清晰的卡片——“题目类型:材料作文 / 关键词:奋斗、平凡、伟大 / 核心问题:……”。程序要渲染卡片、要支持一键把某个立意变成提纲、要把素材按钮绑定到对应的论证条目上,它需要的是字段,不是散文

想象一下一个程序要从上面那段话里提取"立意角度":可以用正则,但正则面对千变万化的自然语言迟早会崩。更可怕的是,如果模型那天心情好,回了一句"老师觉得呀,其实最妙的角度是——注意听哦——先抑后扬,欲扬先抑!",所有的解析规则就全线崩溃了。

结论非常明确:AI 必须说程序听得懂的话。于是我把系统提示词改写成一份严格的数据返回协议。

5.2 JSON 返回协议的设计

我为这个应用的核心能力——“按功能分诊”——设计了一套分模块的 JSON 结构。系统的玩法是:前端通过一个 mode 字段告诉模型"这次要干什么"(审题、立意、提纲、成文、批改),模型则严格按照对应模式返回约定的 JSON。以"审题"模式为例:

{
  "mode": "analyze",
  "topic_type": "材料作文",
  "keywords": ["平凡", "伟大", "奋斗"],
  "core_question": "如何在平凡的岗位上成就不平凡的人生?",
  "requirements": ["结合材料", "自选角度", "不少于800字"],
  "pitfalls": ["只谈奋斗不谈平凡易偏题", "空喊口号缺乏事例"],
  "summary": "本题聚焦『平凡与伟大』的辩证关系,落脚点是个人价值与社会贡献的统一。"
}

以"成文"模式为例:

{
  "mode": "write",
  "title": "生而平凡,心向伟大",
  "paragraphs": [
    {
      "type": "opening",
      "content": "何谓伟大?是惊天动地的丰功伟绩,……",
      "illustration": "以设问开篇,直接点出对立关系"
    },
    {
      "type": "body",
      "content": "伟大出自平凡,平凡铸就伟业。……",
      "illustration": "分论点一,引用张桂梅事例"
    }
  ],
  "word_count": 826,
  "highlights": ["首尾呼应", "事例与说理结合", "语言有张力"]
}

字段设计遵循几条原则:

第一,模式即契约mode 字段告诉前端"你该用哪套渲染逻辑"。前端拿到 JSON 后先看 mode,再决定渲染审题卡片、提纲树还是批改报告——一个对话接口,一套协议框架,五种业务能力,这就是"协议驱动 UI"。

第二,单一职责。每个字段只承担一个语义:keywords 只放关键词数组,core_question 只放核心命题,pitfalls 只放审题陷阱。渲染层可以直接绑定,不需要在长文本里做二次提取。

第三,教育性信息显式化illustrationhighlights 这类"元信息"独立成字段,让 AI 的"为什么这么写"直观地展示在学生面前——这正是 AI 辅导区别于简单代写的地方:不只给答案,还给思路。

5.3 系统提示词全文

有了协议,就需要把它"教"给模型。系统提示词(System Prompt)是对话之前给 AI 定下的"宪法",我最终打磨的版本核心内容如下(节选整理):

你是"高中作文编写AI",一位经验丰富、深谙高考阅卷标准的高级语文教师。
你的使命是帮助学生完成审题、立意、提纲、成文、批改全流程。

能力与模式(mode):
- analyze:审题分析。输出题目类型、关键词、核心问题、写作要求、审题陷阱。
- position:立意建议。输出 2-3 个不同角度的立意,含论证路径与适用素材。
- outline:提纲生成。输出标题、开头思路、分论点(含核心句与素材)、结尾思路。
- write:范文成文。字数控制在 800 字左右,符合高考阅卷标准,语言有文采。
- review:习作批改。输出总评、估分、逐段点评、优点、问题清单、修改建议。

输出要求(非常重要):
- 你的每一条回复都必须是一个合法的 JSON 对象,不要输出任何 JSON 之外的文字。
- 不要使用 markdown 代码块包裹,直接输出纯 JSON。
- 所有面向学生的解释性文字只能放在约定的 explain 字段里。
- 严格按 mode 对应的 JSON 结构输出,不允许多输出字段或缺少字段。
- 观点要积极向上,符合社会主义核心价值观;拒绝任何偏激、消极的表述。
- 素材引用须真实可考,严禁编造名人名言与事例。

这份提示词的每一句都不是废话,逐条拆解:

  • “严格按 mode 对应的 JSON 结构输出”——这是整个协议的灵魂。没有这句,模型会把五种模式的内容混在一起倒给你,前端就乱了阵脚。
  • “不要输出任何 JSON 之外的文字”——没有这句,模型偶尔会在 JSON 前加一句"好的,这是你的分析结果:"。任何多余字符都会让 JSON.parse 当场爆炸。这是格式纪律
  • “素材引用须真实可考,严禁编造名人名言与事例”——这是事实性约束,也是教育类应用的特殊红线。大模型有众所周知的"幻觉"问题,编造一句"鲁迅说过"对学生简直是灾难。教育领域的提示词,事实性约束必须放在最高优先级。
  • “观点要积极向上,符合社会主义核心价值观”——这是价值观约束。作文教育承载立德树人的功能,AI 绝不能教出反例。
  • “所有解释性文字只能放在约定的 explain 字段里”——把模型"想多说两句"的冲动引到结构化字段里,既保住了格式纪律,又保住了教育温度。

5.4 前端如何消费这份 JSON

流式输出时,模型是"一个字一个字"把这段 JSON 吐出来的。所以前端要做两件事:

第一,把流式碎片拼成完整 JSON。我维护一个变量 fullText,每次收到 delta 就追加,直到收到 [DONE]

第二,从完整文本中稳健地提取 JSON。虽然提示词要求"不要包裹 markdown 代码块",但模型偶尔会犯倔。为了防御,我写了一个尽力而为的解析器:

function extractJson(text) {
  // 去掉可能的 markdown 代码块围栏
  const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
  const candidate = fenced ? fenced[1] : text;
  try {
    const obj = JSON.parse(candidate);
    if (obj && typeof obj === 'object') return obj;
  } catch (e) { /* 继续尝试 */ }
  // 尝试找到第一个 { 到最后一个 } 之间的内容
  const start = candidate.indexOf('{');
  const end = candidate.lastIndexOf('}');
  if (start !== -1 && end > start) {
    try {
      return JSON.parse(candidate.slice(start, end + 1));
    } catch (e2) { return null; }
  }
  return null;
}

这个工具函数是"系统提示词工程"的兜底保险丝:提示词负责把模型向正路上带,extractJson 负责在模型偶尔偏离时把它拉回来。提示词约束 + 代码容错,双保险一起上,解析成功率实测接近百分之百。

5.5 一个关键的追加设计:前端校验与降级

把宝全押在模型自觉上是不够的。比如"成文"模式要求 800 字,模型可能只写了 400 字就收尾;“批改"模式要求给出逐段点评,模型可能漏掉第三段。所以我在前端加了一层"契约校验”:

function validateMode(mode, obj) {
  const required = {
    analyze: ['topic_type', 'keywords', 'core_question'],
    position: ['positions'],
    outline: ['title', 'sections'],
    write: ['title', 'paragraphs'],
    review: ['score', 'comments', 'suggestions']
  };
  const missing = (required[mode] || []).filter(key => obj[key] === undefined);
  if (missing.length) {
    return { ok: false, missing };
  }
  return { ok: true };
}

校验不通过时,UI 不会展示残缺卡片,而是提示"AI 的回答不完整,已为你重新生成",并自动重试一次——程序逻辑说得算的地方,绝不全交给模型,这个原则贯穿了整个系统提示词工程的设计。

六、流式响应:打字机效果与"边写边看"的实感

6.1 为什么必须流式

如果关闭流式(stream: false),学生点击"生成范文"后,界面会保持一整段"空白期"——请求发出去,等模型想完,再一次性拿回八百字的完整文章。对一个本就紧张的学生来说,这个等待是心理上的二次煎熬:“是不是卡住了?是不是题太难了?”

流式的魔法在于"边想边说"——也就是"边写边看"。模型写出一句,屏幕上就多一句;模型继续构思,文字继续往外蹦。对用户来说,等待时间被"正在生成"的实感抹平了。这跟阅卷老师看着学生答题是一个道理:人在看到"有进展"的时候,焦虑感会成倍下降

6.2 打字机效果的实现

有了前面 4.2 节的 queryStream,前端只需要在回调里做一点点 DOM 操作:

async function sendTask(mode, userText) {
  const userBubble = appendMessage('user', userText);
  const aiBubble = createBubble('ai');
  const typingCursor = document.createElement('span');
  typingCursor.className = 'cursor';
  aiBubble.appendChild(typingCursor);

  let fullText = '';
  await queryStream(
    buildPayload(mode, userText),
    (delta) => {
      fullText += delta;
      typingCursor.previousSibling?.remove();
      typingCursor.before(document.createTextNode(delta));
      scrollToBottom(true);
    },
    () => {
      const obj = extractJson(fullText);
      if (obj) renderByMode(obj);
      else renderError('AI 的回复格式异常,请重试');
    }
  );
}

这里有三个细节亮点:

第一,增量文本节点。代码里不是简单地 aiBubble.textContent += delta,而是每次插入一个新的文本节点。为什么?因为 textContent += 会频繁重排整个气泡的内容,而插入独立文本节点 + 删除前一个,浏览器只需处理最小粒度的变化,配合 requestAnimationFrame 可以达到 60 帧的流畅度。对于八百字的范文,这种细节的差异肉眼可见。

第二,闪烁光标typingCursor 是一个挂在气泡末尾的 span,CSS 里给它一个无限循环的 blink 动画。它模拟了"正在写作/思考"的状态。当流结束后,在 renderByMode 里把它移除,换成结构化的功能卡片。这个光标还有一个隐藏作用:它给学生一个"AI 还在继续写"的心理锚点,防止学生中途关闭页面。

第三,智能滚动。每次增量都要判断是否把滚动条拉到底。判断条件不能是"无脑贴底"——如果学生正在翻看上文的历史记录,你不该强行拉走他的视口。所以我用了一个小聪明:只有当用户滚动位置距离底部小于一个阈值(比如 120px)时才自动跟随,否则保持用户当前阅读位置。

6.3 对话历史的维护

为了让模型有"上下文",每一次请求的 messages 数组都要携带全量的对话历史(系统提示词 + 学生历次输入 + AI 历次输出)。这个数组在本地维护,并在每次发送前把上一轮的 AI 回复(原始 JSON 文本)塞回历史里:

function buildPayload(mode, userText) {
  const messages = [{ role: 'system', content: SYSTEM_PROMPT }];

  // 按功能压缩历史:只保留最近 10 条 + 一条"当前任务"说明
  const recent = history.slice(-10).map(toOpenAIFormat);
  messages.push(...recent);
  messages.push({ role: 'user', content: `【模式】${mode}\n${userText}` });

  return {
    model: 'deepseek-ai/DeepSeek-V4-Flash',
    messages,
    stream: true,
    max_tokens: 4096,
    temperature: 0.6,
    top_p: 0.95,
    frequency_penalty: 0,
    thinking_budget: 2048
  };
}

注意我在用户消息前加了一个 【模式】 前缀,用于和系统提示词中的模式契约呼应——模型每轮的输出结构都由这条"当前指令"决定。同时,历史采用滑动窗口:保留最近 10 轮,既控制 token 消耗,也避免早期任务的"噪音"干扰当前任务。作文辅导是单线程的:学生此刻只关心当前这篇作文,前面的讨论如果过长,反而会稀释模型的注意力。

七、功能模块拆解:审题、立意、提纲、成文、批改

技术底座搭好了,来聊聊五种业务能力的实现逻辑。它们共享同一套传输与解析框架,区别只在 mode 参数和对应的渲染逻辑。

7.1 审题(analyze)

审题是作文的第一道关,也是失分重灾区。analyze 模式的输出——题目类型、关键词、核心问题、写作要求、审题陷阱——会被渲染成一张"审题报告卡":上半部分是关键词标签云,下半部分是核心问题与陷阱清单。每一个"陷阱"都配了"避坑提示",这是我在提示词里额外要求的:审题报告不能只说"要注意",要说清楚"注意什么、为什么"。

7.2 立意(position)

position 模式返回 2-3 个立意角度,每个角度包含:立意名称、一句话观点、论证路径(分几步展开)、适用素材。前端把每个立意渲染成独立的小卡片,学生点击卡片上的"以此立意列提纲"按钮,就能把当前立意作为上下文传给下一轮 outline 请求——模式之间无缝衔接,这是对话式架构带来的天然优势。

7.3 提纲(outline)

outline 返回的是一棵结构树:标题、开头思路、若干分论点(每个含核心句 + 支撑素材 + 展开方式)、结尾思路。我把它渲染成左侧树形、右侧详情的两栏布局——鼠标悬停任意分论点,右侧就展示它的素材与展开建议。学生可以"只抄骨架,自己填肉",也可以在任何一个分论点上点"展开成段",让 AI 接着写。

7.4 成文(write)

write 模式生成整篇范文。注意,这里的定位是"参考范文",不是"代写工具"——我的产品哲学是:AI 写得好,是为了让学生知道好作文长什么样,而不是替学生交作业。所以范文的 JSON 里除了正文段落,还带着 illustration(这段是怎么构思的)和 highlights(全文亮点)。渲染时,正文用正常的阅读排版,段落下方以弱化的浅色小字展示构思说明,学生读完范文,实际上也读完了一堂微型的写作课。

7.5 批改(review)

review 是最重的一个模式。学生粘贴习作,AI 返回:总评(一段话概括全文印象)、估分(分值 + 一句理由)、逐段点评(按段落一一对应)、问题清单(3-5 条具体问题)、修改建议(可操作的改写方向)。前端的渲染是一个"诊断报告":得分栏用醒目的数字展示,逐段点评与原文段落并排对照,问题清单做成可勾选的 checklist——学生改完一项,勾掉一项,成就感是实打实的。

7.6 模式的合成与进阶用法

五种模式单独看都是"单轮对话",但组合起来就形成了一条完整的写作训练链。我还做了一个很克制的增强:追问按钮。在学生完成一篇范文阅读后,界面会出现几个可点击的追问选项,比如"请把第三段的开头改得更有文采"“请把张桂梅的事例换成另一个”。这些追问本质上还是 write 模式,但上下文里带着前一轮的完整输出,模型就能精准地"原地修改"而不是另起炉灶。对话式架构的魅力在这里体现得淋漓尽致。

八、界面与视觉:把"考场卷面"做进每一个像素

技术内核讲完了,来聊聊颜值。作为一个教育类应用,界面如果做成花里胡哨的游戏风,就太不庄重了。我给它定了三个视觉关键词:纸感、墨色、留白——模仿学生最熟悉的场景:一张干净的作文纸。

8.1 配色与字体

主色调是"纸墨"体系:背景是米白色的纸张色(#faf7f0),文字是浓墨色(#2b2b2b),主强调色是朱砂红(#9e2b25),辅色是黛青(#3e5c76)。这种搭配模拟了作文纸 + 红笔批注的经典组合——强调色是"老师的红笔",黛青是"批改的蓝笔",学生看到界面就像看到一份被老师认真批阅过的卷子。

字体方面,中文标题用楷体系衬线字体(KaiTi, STKaiti, 回退到 serif),模拟手写体;正文用系统默认的无衬线字体保证长时间阅读的舒适度。整体字号略微偏大——用户群体是高中生,正值用眼密集的年纪,阅读舒适度优先于信息密度。

8.2 功能卡片的动效

每一种模式的输出都渲染成功能卡片。卡片出现时有一个轻微的"上浮 + 淡入"动画(transform: translateY(8px); opacity: 0 过渡到正常态),像一张纸被轻轻放到桌面上。关键词标签的悬浮效果模仿了"荧光笔划过"——鼠标悬停时,标签底色从淡黄渐变为米橙,好像在提醒学生"这里是重点"。

8.3 顶部的工作流指示条

为了让"我现在做到哪一步了"一目了然,界面顶部有一条五个节点的工作流指示条:审题 → 立意 → 提纲 → 成文 → 批改。当前的模式节点高亮显示,完成的节点打上对勾。它既是导航,也是心理地图——学生永远知道自己在写作流程的哪个位置,距离"写完一篇好作文"还有几步。这个设计借鉴了游戏里的任务进度条,是让长流程不迷茫的关键。

8.4 响应式:从教室投影到手机屏幕

我用了 CSS 的 clamp() 函数统一视口适配:字号、间距、卡片宽度都通过 clamp(最小值, 偏好值, 最大值) 动态缩放。桌面端是左右分栏(左侧对话区,右侧"写作知识卡"),窄屏(小于 720px)自动改为上下叠排。移动端的输入框固定在底部,点击时不遮挡键盘——学生在晚自习用手机也能流畅使用。

8.5 加载状态与错误处理

AI 接口偶尔会不稳定(网络波动、限流、模型服务抖动)。我设计了三态反馈:加载中(旋转的"笔尖"光标 + 提示文案"AI 正在润色这段文字……")、错误(红色横幅 + "重试"按钮,保留用户输入不丢失)、降级(连续失败两次后提示用户检查网络或稍后再试,并展示错误码便于排查)。永远不要让用户面对一个空白屏幕发呆——这是我做任何前端交互的底线,尤其对容易焦虑的学生用户。

九、安全与隐私:令牌应该放哪里

这是纯前端项目绕不开的敏感问题,我必须如实讲清楚。

前端代码是"裸奔"的——任何用户打开浏览器开发者工具,都能看到 JavaScript 里写的 Authorization: Bearer <令牌>。这意味着把带真实令牌的文件部署到公网,等于把密钥公开挂在了大门口。

所以我在代码里把令牌读取做成两种模式:

  1. 演示模式(默认):令牌从 localStorage 里读,首次使用时在设置面板粘贴令牌,保存在用户自己的浏览器里。这样浏览器端调用 API 时,令牌不进入代码文件。
  2. 开发模式:JavaScript 顶部从 window.CONFIG 读取,这个配置可以在部署时用服务端注入或环境变量替换。

我必须诚实地说明:纯前端直连 API 的方案,本质上无法做到密钥的绝对安全。如果这是一个正式商业产品,正确做法是加一层极轻的代理(比如 Cloudflare Workers 或一个几十行的 Node 服务),把令牌藏在服务端,前端只和代理通信。本项目因为定位是学习演示,所以选择了最简路径,但在 README 里,我把两种加固方案的链路图都画清楚了,并强调了"生产环境务必加代理"这条红线。

关于隐私:学生的作文内容是敏感数据。整个对话过程只发生在前端与 API 之间,项目本身不留存任何用户数据,所有对话记录只存在浏览器内存里,刷新即清零。同时我在 README 里明确提示用户:不要把真实姓名、家庭住址等个人信息写进作文,教育场景的隐私保护,从产品设计的第一天就要立规矩。

十、项目结构总览

再回到整体,把四个文件的职责说透:

高中作文编写AI/
├── index.html      # 页面结构与界面骨架
├── style.css       # 纸墨质感视觉样式
├── app.js          # 应用逻辑与 AI 调用
└── README.md       # 项目说明文档

index.html:语义化标签组织页面。<header> 放题名与工作流指示条,<main> 放对话滚动区,<aside> 放写作知识卡与素材速查,<footer> 放输入区、模式切换按钮与发送按钮。特殊之处在于,我在 HTML 里预置了一个"空状态"——当屏幕上还没有任何消息时,显示一段引导文案与五个模式的功能按钮:"『审题』:把题目粘贴进来,先看 AI 怎么拆解它。"让第一次打开的用户零学习成本上手。

style.css:约七百行的纯 CSS。设计令牌(Design Token)用 CSS 变量集中声明在 :root,换色只需要改几个变量。动画全部用 CSS 过渡与 @keyframes,不引入任何动画库。纸张纹理用纯 CSS 渐变模拟,连一张图片都没有——整个项目零图片零字体文件,加载快得惊人。

app.js:单文件约四百行,分四个清晰区块——配置区(API 地址、模型参数、系统提示词、模式契约表)、工具区(extractJsonvalidateModescrollToBottomrenderByMode)、请求区(queryStreambuildPayload)、渲染区(气泡、功能卡片、工作流指示条、错误横幅)。自上而下是纯数据流,没有全局事件总线,只用了一个 state 对象记录对话上下文与当前模式,配合按钮事件直接读写。

README.md:项目说明,包含项目简介、功能列表、快速开始、文件结构、API 配置方法(含令牌获取指南)、系统提示词全文、常见问题、免责声明与生产环境加固建议。我把系统提示词也在 README 里贴了一份,方便开发者直接复制改造——这是开源项目最低成本的善意。

十一、踩坑实录:那些教科书上不会告诉你的问题

项目开发过程中遇到了不少有意思的问题,挑四个最典型的记录下来,也许能帮看到这篇文章的读者少走弯路。

11.1 中文乱码之谜

第一次跑通流式对话时,屏幕上蹦出了大量"�"字符。第一反应是 API 返回了错误编码,仔细排查才发现是读取流时没有用 TextDecoder(..., { stream: true }),多字节 UTF-8 字符被拦截在两次 read 之间,强行解码自然乱码。这个问题后来被我写进了 4.2 节的注释里,作为"血泪教训"标识。写作文的应用里全是中文,这个坑踩一次,终身难忘。

11.2 JSON 里的换行符与引号

有一次,模型在 paragraphs[0].content 字段里返回了一段含 \n 的正文,还夹着中文引号。因为流式传输是逐字到达的,我收到的是字面上的反斜杠加字母 n(\n 两个字符),存进 fullTextJSON.parse 却正常通过了——因为它是合法的 JSON 转义。坑在于我后续把正文直接 textContent 进 DOM 时,换行符显示为了空格,段落挤成一坨。解决方案:渲染时对换行做一次按段落分割的排版处理。这件事提醒我:结构化的数据,也要把它当"文本"看待一遍再上屏

11.3 模型的"补充说明癖"

在早期版本中,系统提示词没写"不要输出 JSON 之外文字",模型会在给出审题报告后自动追加一句"(以上仅代表个人观点,实际请以老师讲解为准)“来"显得专业”。结果 extractJson 必须从带括号的文本里强行抠 JSON。后来我双管齐下——提示词里加纪律条款,代码里加 extractJson 兜底——这个问题就基本绝迹了。这让我深刻理解了提示词工程的第一原则:你想要什么,就明确地说出来;你不想要什么,更要明确地说出来

11.4 "幻觉"素材的拦截

这是教育应用最不能妥协的一条。测试时我让模型写"司马迁"的事例,它言之凿凿地写出"司马迁在狱中完成了《史记》,出狱后官至太史令"——前半句对,后半句错得离谱(太史令是他入狱前就担任的官职)。这件事让我意识到:作文领域的幻觉危害远超其他场景,因为它会直接污染学生的知识体系。于是我在提示词的"事实性约束"里追加了更严格的写法:“对于你无法确证的名人名言与具体细节,请在 explain 字段中标注『请学生核对原典』”。同时,在"批改"模式中,我把"素材真实性核查"作为一个固定点评项,让 AI 主动检查自己用的例子是否经得起推敲。教育产品的幻觉防线,永远不嫌多

11.5 长输出被"掐断"

max_tokens 设置偏小的时候,出现过一次事故:模型写到文章高潮段落,token 用尽,输出戛然而止,留下一个没有闭合的 JSON 对象——extractJson 返回 null,整轮对话作废。排查后我把 max_tokens 从 2048 上调到 4096,并给 extractJson 补了一个"检测到未闭合 JSON 时自动提示重试"的分支。这个坑的教训是:给模型的预算,永远要比你预估的极限再多留 30%

十二、测试与优化

写完了不等于能上线。交付前的最后一道工序是系统化验证,我用三层测试覆盖了核心风险。

第一层:纯函数单测。把 extractJsonvalidateModescrollToBottomrenderByMode 这些纯函数在浏览器控制台用一段自测脚本打完全部边界用例:空输入、损坏的 JSON、带 markdown 围栏的 JSON、缺失字段的 JSON、超长字符串、全角标点、包裹着 JSON 的多余文字……每一行断言都指向一个可能爆炸的角落。测试脚本我放在了项目根目录的 test.html 里(纯前端,点开即跑),这也是给后来者的一个小彩蛋。

第二层:端到端手工测试。五个模式的完整流程各跑一遍:审题 → 立意 → 提纲 → 成文 → 批改;再从批改结果发起"追问修改";再测试快速连点发送按钮(防抖)、页面刷新后状态复位、窄屏移动端布局、断网后错误横幅与重试。每一场景对应一个 checklist,跑完打勾,确保交付前无死角。

第三层:性能体检。用浏览器 Performance 面板观察:50 轮对话后 DOM 节点数、每次增量的重排耗时、事件监听器的数量。结论是 50 轮以内毫无压力,因为对话区采用"最多保留最近 100 条消息,超出则把最旧的折叠为一行摘要"的策略,DOM 数量被严格锁在上限内。首屏加载耗时则几乎为 0,因为全部代码就三个文件、零依赖、零图片。

十三、部署:让学生们用上它

部署是纯静态项目的福利时刻。我把整个目录推送到了 GitCode 仓库,仓库采用公开模式。任何人克隆或直接访问仓库主页的说明,按 README 三步走就能在本地跑起来:下载文件 → 打开 index.html → 粘贴自己的 API 令牌。

如果读者想部署到公网,我能给出的最佳实践是:托管到 GitCode Pages(静态站点,零配置 HTTPS)或任意对象存储 + CDN。唯一注意点是前述的令牌问题——公网部署务必走代理,或在设置里引导用户填自己的令牌,不要把写死令牌的版本传到公开仓库。

我还在 README 里附带了一份"令牌获取指南":如何注册 GitCode 账号、在哪里申请 API 令牌、额度如何、注意事项。把最容易被卡住的 onboarding 环节写清楚,是开源项目最低成本的善意——尤其当你的目标用户是一群还没毕业的高中生。

十四、总结与展望:下一步它能长成什么

截至今天,「高中作文编写AI」已经稳定跑通了从"粘贴题目"到"批改完成"的完整闭环,并且在纯前端、零依赖的前提下,实现了流式渲染、结构化 JSON 协议、模式契约校验、五模式联动、纸墨 UI 这一整套在商业级产品中才常见的工程素养。它证明了:前端三件套 + 一个大模型 API,就足以撑起一个完整、有用、可分享的 AI 教育应用——不需要后端,不需要框架,甚至不需要一行构建配置。

但我知道它的潜力远不止如此,下一阶段的路已经在脚下:

第一,素材库外置化。目前课内课外的作文素材都靠模型即时生成,下一步我想维护一个精选素材库(名人事迹、名言警句、时政热词),在生成作文时优先引用库内素材,进一步降低幻觉风险,同时允许学生"点素材入库"、沉淀自己的素材本。

第二,多模型切换与"双师会诊"。把模型 ID 做成可选项。甚至可以做"双模型批改":让两个不同模型分别批改同一篇作文,再对比两套意见的异同,学生能直观看到"AI 之间也会有分歧",学会批判性地看待任何单一评价。

第三,字数与用时统计。悄悄记录学生从"粘贴题目"到"完成批改"的总用时,生成一份"写作效率周报"——这不是监控,而是帮学生建立时间感知。很多学生作文写不完,根因是缺乏时间概念。

第四,错题本与进步曲线。把每次批改的"问题清单"归档,几周后重新抽取旧作文让学生二次修改,对比两次批改的估分,画一条属于自己的"写作进步曲线"。教育最动人的反馈,从来不是一次高分,而是看得见的成长。

技术选型上,如果未来某个版本需要账号体系、云同步或多人协作批改,我会顺势引入轻量后端——但那是"需求驱动"的自然演进,绝不是"为了用框架而用框架"。这个项目最大的收获,恰恰是印证了一种可贵的工程态度:从最小可行实现出发,让每一次架构升级都对应一次真实的需求变化

最后,我想把这次项目的核心方法论沉淀成几句话,送给所有正在学习"前端 + AI"的读者:

  • 大模型不是魔法,它是一个"有知识但没分寸"的天才员工——你必须用系统提示词给它立规矩,用 JSON 协议给它装模具,用代码给它兜底。
  • 流式体验是 AI 应用好感度的第一来源,“边写边读"永远好过"等待转圈”。
  • 结构化的数据协议(JSON)比花哨的自然语言更值得托付——让程序听得懂,是提示词工程的精髓。
  • 教育类 AI 有一条不可退让的底线:事实性约束重于一切,幻觉防线永远不嫌多。
  • 小项目也有大工程:状态管理、边界处理、错误反馈、性能体检,一点都不能少。
  • 最重要的:用技术解决一个真实世界的真实痛点——哪怕它只是"帮助学生写好一篇作文"。这比任何技术选型都高级。

「高中作文编写AI」的代码虽然只有三个文件,但它承载的是一段关于"教育"与"技术"的朴素叙事:作文是中国人语文素养的集中体现,AI 是当下最具想象力的技术切面,当它们被一行行代码缝合在一起,那张让无数学生辗转反侧的空白作文纸,就多了一位随叫随到的助教,也多了一条看得见方向的码道。

如果你也想做一个属于自己的 AI 小项目,不妨就从"帮身边的人解决一个具体的麻烦"开始。你会发现,把熟悉的问题交给 AI,再用代码把它稳稳接住——这个过程本身,就是一条有意思的码道。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐