🪄 用 React 玩转「图片识词 + 语音 TTS」:月影大佬的 AI 英语私教是怎么炼成的?


前言:

你好,我是卷王队形中那只还在啃 React 的小白。这次掘金写这篇文章,带你完整拆解一个 AI 小项目:

上传一张图,自动帮你找出一个最简单的英文单词 ,顺带生成解释、例句,最后用 TTS 读给你听!

这不比隔壁花大几千请外教香?


一、项目简介:这玩意儿是干嘛的?

这个小项目核心功能一句话就能讲明白:

"分析用户上传的图片 ➜ 找出最能代表图片的一个英文单词(简单词汇,A1-A2 级别) ➜ 自动生成例句 + 段落解释 + 语音朗读。"

简单粗暴,但背后集合了:

  • 图像识别(用 Moonshot Vision)
  • 自动文本生成(AIGC大语言模型 Chat 完成)
  • TTS(来自火山引擎的文字转语音)

而整个流程用 React + 简单的后端服务就能跑通,关键代码就散落在三个文件里:

  • App.jsx --- 项目入口,状态管理大本营
  • PictureCard.jsx --- 图片上传和语音播放的可爱小卡片
  • /lib/audio.js --- TTS 核心逻辑(把文字变成人声 MP3)

二、项目结构:前端是怎么组织的?

大体目录结构长这样(主要文件):

路径 说明
my-picture-ai-app/ 项目根目录
src/ 源码文件夹
src/App.jsx 应用入口,管理状态和逻辑
src/components/ 组件文件夹
src/components/PictureCard.jsx 图片上传 + 音频播放组件
src/lib/ 公共库文件夹
src/lib/audio.js 文字转语音核心逻辑
src/App.css 全局样式
src/style.css 局部样式

是不是清爽?真·月影大佬手把手模板。


三、核心流程拆解(含关键代码)


1️⃣ App.jsx --- 全局状态调度中心

这里干了几件大事:

1. 定义核心状态

jsx 复制代码
const [word, setWord] = useState('请上传图片');
const [sentence, setSentence] = useState('');
const [explainations, setExplainations] = useState([]);
const [expReply, setExpReply] = useState('');
const [audio, setAudio] = useState('');
const [detailExpand, setDetailExpand] = useState(false);
const [imgPreview, setImgPreview] = useState('默认示例图URL');

意思很简单:

  • word:识别出来的英文单词
  • sentence:例句
  • explainations:段落解释,分句处理后是数组
  • expReply:针对解释的可能对话回复
  • audio:生成的音频 URL
  • detailExpand:是否展开详细信息
  • imgPreview:图片预览 URL

2. 核心函数 uploadImg

这段就是把图片丢给 Moonshot,拿回来 AI 分析结果的全过程。 这一段中imageData由子组件PictureCard中的uploadImgData自定义函数转成了chrome浏览器提供的base64格式的文件然后通过props再'汇报'给父组件,此过程完成了一个单向数据流的操作

jsx 复制代码
const uploadImg = async (imageData) => {
  setImgPreview(imageData);
  setWord('分析中...');

  const endpoint = 'https://api.moonshot.cn/v1/chat/completions';
  const headers = { 
    'Content-Type': 'application/json', 
    'Authorization': `Bearer ${import.meta.env.VITE_KIMI_API_KEY}` 
  };

  const response = await fetch(endpoint, {
    method: 'POST',
    headers,
    body: JSON.stringify({
      model: 'moonshot-v1-8k-vision-preview',
      messages: [ 
        {
          role: 'user',
          content: [
            { type: "image_url", image_url: { "url": imageData } },
            { type: "text", text: userPrompt }
          ]
        }
      ],
      stream: false
    })
  });

  const data = await response.json();
  const replyData = JSON.parse(data.choices[0].message.content);

  setWord(replyData.representative_word);
  setSentence(replyData.example_sentence);
  setExplainations(replyData.explaination.split('\n'));
  setExpReply(replyData.explaination_replys);

  // TTS 生成
  const audioUrl = await generateAudio(replyData.example_sentence);
  setAudio(audioUrl);
};

这里有几个点可以偷学:

  • import.meta.env环境变量,管理密钥,安全一点,实际的在.env.local文件中
  • userPrompt 里直接用 JSON 模板让 LLM 生成结构化输出,避免抓瞎。
  • 图片直接转 Base64 当 URL,后端 Vision 可以吃。

PictureCard.jsx --- 图片上传 + 播放语音

小卡片逻辑超级简单,只有两块:

  • 上传图片 ➜ 转 Base64
  • 点按钮播放音频

上传图片是经典 FileReader 用法:

jsx 复制代码
const uploadImgData = (e) => {
  const file = e.target.files?.[0];//可选链运算符
  if (!file) return;

  const reader = new FileReader();//FileReader API 
  reader.readAsDataURL(file);//API
  reader.onload = () => {
    const data = reader.result;//读取结果
    setImgPreview(data);//图片预览,提升用户体验
    uploadImg(data);//传回给父组件
  };
};

点按钮放音频用 Audio

ini 复制代码
const playAudio = () => {
  const audioEle = new Audio(audio);
  audioEle.play();
};

可见:一整个复古 Vanilla 实现。


3️⃣ audio.js --- 文字转语音核心

TTS 流程也挺朴实无华:

  • 拼接请求体 ➜ 调后端 ➜ 后端给个音频 Base64 ➜ 用 atob 转字节 ➜ BlobURL.createObjectURL ➜ 给 <audio>

里面有个知识点:

jsx 复制代码
const getAudioUrl = (base64Data) => {
  const byteCharacters = atob(base64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset++) {
    byteArrays.push(byteCharacters.charCodeAt(offset));
  }

  const blob = new Blob([new Uint8Array(byteArrays)], { type: 'audio/mp3' });
  return URL.createObjectURL(blob);
};
  • 记住 atob():ASCII to Binary,把data解码成二进制字符串(前端处理二进制老朋友)。
  • 接着charCodeAt():用for循环遍历,会把字符串转成ASCII值。
  • 再用Blob封装被Uint8Array()打包成的真实的二进制数组成浏览器可识别的文件。
  • 最后用return URL.createObjectURL(blob)返回一个临时地址,可以给 直接用。

四、亮点小结

这个项目说复杂不复杂,说简单也有点小巧思:

  • 用 Vision + LLM 做到图像语义理解(Moonshot 8K Vision)
  • 返回 JSON 保证结构化,不怕 LLM 胡说八道
  • TTS 音频用纯前端就能把 Base64 变可播放 URL

对初学者来说:

✅ React 的状态怎么拆?

✅ 事件流 + 状态传递怎么配?

✅ 调用异步接口、处理 Blob、玩文件流?

全在里面!


五、适合谁玩?

  • 学 React 的同学:练状态、练组件通信、练异步。
  • 想做 AI 应用 Demo 的同学:大语言模型 + Vision + TTS,三合一小样板。
  • 想发掘 Moonshot、Kimi 这类国内可用 LLM 的同学:怎么调怎么封装,一看就会。

六、最后一句

上传张图 ➜ 自动学个单词 ➜ 还能听一遍

要是小时候就有这玩意儿,我的四六级词汇量也不至于这么拉胯......

希望这套拆解能帮你看懂背后思路,自己也可以魔改试试:

  • 换个更复杂的 Prompt
  • 加个单词词根解释
  • 换个外语读音
  • 甚至直接做成单词卡片库!

卷就完了,咱在掘金见!


有需要源码或想看其他解读,评论区喊我,一起写起来~

相关推荐
花间相见3 小时前
【终端效率工具01】—— Yazi:Rust 编写的现代化终端文件管理器,告别繁琐操作
前端·ide·git·rust·极限编程
|晴 天|3 小时前
我如何用Vue 3打造一个现代化个人博客系统(性能提升52%)
前端·javascript·vue.js
风止何安啊3 小时前
网页都知道要双向握手才加载!从 URL 到页面渲染,单向喜欢连 DNS 都解析不通
前端·javascript·面试
太极OS3 小时前
给 AI Skill 做 CI/CD:GitHub + ClawHub + Xiaping 同步发布实战
前端
你_好3 小时前
Chrome 内置了 AI 工具协议?WebMCP 抢先体验 + 开源 DevTools 全解析
前端·mcp
GISer_Jing3 小时前
LangChain.js + LangGraph.js 前端AI开发实战指南
前端·javascript·langchain
正在发育ing__4 小时前
从源码看vue的key和状态错乱的patch
前端
黄林晴4 小时前
第一次听到 Tauri 这个词,去学习一下
前端
可可爱爱的你吖4 小时前
蜂鸟云地图简单实现
前端
布局呆星4 小时前
Vue3 :生命周期、DOM 操作与自定义组合式函数
前端·javascript·vue.js