从零实现「拍照记单词」小应用(可复刻版)

从零实现「拍照记单词」小应用(可复刻版)

读完本文,你可以用浏览器单页 + 本地 Node 代理 跑通:看图 → 输出单词卡片 JSON → 语音朗读

其实就是之前几个大模型的组成使用,请务必参考前面文章的文本模型、视觉模型、语音模型。

想直接体验,代码仓库,建立.env.local配置各种参数,然后运行node server.js,然后在浏览器打开index-capture-word.html就可以了。对,三个文件就可以了。

你会学到什么

  • 为什么不要把大模型与 TTS 的密钥写进前端,以及如何用同源代理解决 CORS 与保密。
  • 如何把本地图片变成 data URL ,并塞给支持视觉的 OpenAI 兼容 Chat Completions。
  • 如何用 Prompt 约定 JSON 形状 ,并在前端做一层代码围栏剥离容错解析。
  • 如何把卡片字段拼成朗读稿,调用火山豆包 HTTP 非流式 TTS ,并把 base64 音频 交给 <audio> 播放。

应用长什么样、用户怎样走完流程

  1. 用户选一张图(手机拍照后从相册选文件即可,本质仍是 <input type="file">)。
  2. 前端用 FileReader.readAsDataURL 得到 data:image/jpeg;base64,... 这类字符串,同时用于预览和后续 API。
  3. 点击「生成单词卡片」:把该 data URL 放进多模态消息里的 image_url.url,连同固定 VISION_PROMPT 发给 Moonshot(经本地代理),模型返回一段 JSON 文本。
  4. 页面展示:单词拼写、读音标注、中文释义、英文例句、一段对话。
  5. 点击「朗读卡片」:把上述字段拼成一段适合朗读的文本,请求 TTS 代理,拿到音频后解码为 Blob URL,交给 <audio> 播放。

技术选型与本仓库一致:Moonshot (示例模型 moonshot-v1-8k-vision-preview)做多模态理解与结构化输出;火山豆包 TTS 经 POST /tts/api/v1/tts (由 server.js 转发到 openspeech)合成朗读音频。

整体架构(建议先理解再写代码)

浏览器只配置「代理根地址」,例如 http://127.0.0.1:3000 ,不直接接触 Moonshot / 火山公网域名,也不持有 sk- 密钥:

flowchart LR subgraph browser [浏览器] A[index-capture-word.html] end subgraph proxy [Node server.js] B["/moonshot/v1/*"] C["/tts/api/v1/tts"] end subgraph upstream [公网 API] M[Moonshot] V[火山 openspeech] end A --> B A --> C B --> M C --> V

先决条件

  • Node.js 22+ (本仓库 server.js 使用内置 fetch,无需额外 npm install)。
  • 可用的 Moonshot API Key (支持视觉的模型)与 火山豆包语音 HTTP TTS 凭据(APP_ID、ACCESS_TOKEN、CLUSTER_ID 等,与 server.js 头注释一致)。
  • 仓库根目录的 server.js ,已包含 /moonshot 与 /tts 转发逻辑。

第一步:准备 .env.local

在项目根目录新建或编辑 .env.local (server.js 启动时会加载 .env.local 或 .env)。至少包含:

用途 变量(可与 server.js 中别名互换)
Moonshot MOONSHOT_API_KEY,或 VITE_API_KEY / API_KEY
火山 TTS VOLCENGINE_TTS_APP_ID、VOLCENGINE_TTS_ACCESS_TOKEN、VOLCENGINE_TTS_CLUSTER_ID,或 VITE_APP_ID、VITE_ACCESS_TOKEN、VITE_CLUSTER_ID

密钥不要写进前端仓库的公开配置;.env.local 应留在本机并加入 .gitignore。

第二步:启动代理

在含 server.js 的目录执行:

bash 复制代码
node server.js

默认监听 http://127.0.0.1:3000 (可通过环境变量 PORT 修改)。终端无报错即表示代理就绪。

第三步:静态打开单页并填写代理

含 import type="module" 的页面不要用 file:// 打开(易触发模块或跨域限制)。在仓库根目录用任意静态服务即可,例如:

bash 复制代码
npx serve .

浏览器打开 index-capture-word.html ,在页面「代理 」输入框填入 http://127.0.0.1:3000(与上一步一致;页面内会对末尾斜杠做规范化)。

核心实现拆解(与单页脚本逐段对应)

以下步骤你可完全用 Vue / React / 原生 JS 重写 UI,只要保留数据流与请求体结构,行为就与参考实现一致。

1. 选图 → data URL

  • 监听 <input type="file"> 的 change,取 e.target.files[0]。
  • FileReader.readAsDataURL(file),在 onload 中把 reader.result 存为 imgDataUrl。
  • 同一字符串 绑定到 <img src> 做预览,并作为下一步视觉请求中 image_url.url 的值(Moonshot 接受 data URL 形式的图片)。

2. 生成卡片:POST {proxyBase}/moonshot/v1/chat/completions

  • 请求头:Content-Type: application/json,Accept: application/json。
  • Body:model 使用支持视觉的 id;stream: false ,便于一次取全 content。
  • messages:一条 user 消息,content 为数组 :先 { type: 'image_url', image_url: { url: imgDataUrl } },再 { type: 'text', text: VISION_PROMPT }。
  • 从响应读取 choices[0].message.content,再进入解析步骤。

3. Prompt 与 JSON 契约

在 VISION_PROMPT 中明确要求:只输出合法 JSON ,并列出字段:word、phonetic、meaning_zh、example、dialogue(均为字符串)。字段稳定后,你的卡片 UI 与 buildTtsText 才能长期不改。

4. 容错解析:parseVisionJson

模型有时仍会在 JSON 外包一层 markdown 代码围栏。建议逻辑为:若存在 ```json ... ```,先取内部文本;再在字符串中定位第一个 { 与最后一个 } ,JSON.parse 得到对象。缺字段或 word 为空时,向用户展示可读错误及原文片段,便于调 Prompt。

5. 朗读:POST {proxyBase}/tts/api/v1/tts

  • 用 buildTtsText() (或等价逻辑)把单词、音标、中文义、例句、对话拼成单段文本 ;注意厂商有长度上限,参考实现中对总长做了截断(如 1200 字符),避免合成失败。
  • Body 结构需与当前 server.js 转发及火山 HTTP 文档一致(单页中含 user、audio、request 等字段,如 voice_type、encoding、text、reqid)。
  • 响应中取出 base64 音频 (参考实现使用字段 data),经 atob → Uint8Array → Blob,再 URL.createObjectURL 赋给 <audio>。
  • 再次合成前对旧 URL URL.revokeObjectURL;组件卸载时同样释放,避免泄漏。

6. 数据流与界面门闩(把逻辑钉死)

  • 串行依赖 :先有 imgDataUrl 才能调用视觉接口;只有解析出 word(及你需要的其它字段)后,buildTtsText 与 TTS 才有稳定输入。不要跳过中间态发请求。
  • 按钮是否可点 :与参考页一致时,可用 canAnalyze = !!imgDataUrl 控制「生成单词卡片」,hasCard = !!word.trim() 控制「朗读」。
  • 重新选图:新文件读入后应清空上一轮单词、例句、对话、错误提示,避免旧卡片与新照片叠在一起误导用户。

常见问题

  • CORS / 网络失败 :确认页面来自 http(s) 静态源 ,且请求指向你填写的代理根地址,而不是 file://。
  • 401 / 403 :确认 .env.local 与运行 node server.js 的工作目录 一致;改 env 后重启 server。
  • 视觉返回无法解析 :收紧 Prompt、更换模型,或增强 parseVisionJson 与错误提示。
  • TTS 无数据 :检查响应 JSON 是否返回错误信息、encoding 与解码后的 MIME 是否匹配(如 mp3 → audio/mpeg)。

复刻自检清单

  • .env.local 已同时配置 Moonshot 与火山 TTS。
  • node server.js 启动成功,端口与页面「代理」一致。
  • 通过静态服务打开 index-capture-word.html,非 file://。
  • 选图后预览正常,「生成单词卡片」能填满五个字段。
  • 「朗读卡片」能播放合成语音(或截断后的片段)。

参考

相关推荐
吃饱了得干活1 小时前
Agent 的决策与规划:ReAct、Plan-and-Execute、Reflexion 与 Tree of Thoughts
人工智能·llm·agent
阿明61 小时前
小白入门机器学习基础【AI】
人工智能·机器学习
java资料站1 小时前
十一、评估测试
开发语言·人工智能·python
东方佑2 小时前
从《事件发生与智能》的框架看中国古代命理学
人工智能·深度学习·语言模型·自然语言处理·架构
天远API2 小时前
零信任架构实战:基于天远运营商三要素简版V即时版查询构建自动化司乘安全绑定网关
人工智能·安全·架构·自动化
Henry-SAP2 小时前
SAP PP核心引擎计划策略业务解析
人工智能·云原生·sap·erp
扶风ff2 小时前
新品知识更新太快?用练题簿在线刷题,安排企业培训的小测与复盘
前端·学习·小程序
DP DPharness2 小时前
拆开 dsh-knowledge 的检索链路,看 RRF 融合与锚点续读
人工智能·dpharness
I'm a winner2 小时前
《AI 赋能嵌入式开发:从 0 到全栈工程师》模块1|第4课时
人工智能
对空六课2 小时前
支持注意力分析的热力图工具有哪些?
前端·数据库·数据分析