前言:
你好,我是卷王队形中那只还在啃 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
:生成的音频 URLdetailExpand
:是否展开详细信息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
转字节 ➜Blob
➜URL.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
- 加个单词词根解释
- 换个外语读音
- 甚至直接做成单词卡片库!
卷就完了,咱在掘金见!