一、项目概述
1.1 应用简介
本项目是一个基于 React 的 Web 应用,用户上传一张图片后,系统会通过 Kimi Vision API 分析图片内容,提取出一个代表性的英文单词,并生成对应的例句、解释和音频。整个过程实现了图像识别 → 文字理解 → 语音合成的全流程闭环。
1.2 技术栈
- 前端框架:React + Vite
- 状态管理:React 内置
useState、useEffect - 样式处理:CSS 模块化 + 响应式布局
- AI 接口:
- Kimi Vision API(图像识别)
- 火山引擎 TTS API(文字转语音)
- 环境变量:
.env.local文件配置敏感信息
二、项目结构与组件划分
2.1 目录结构
src/
├── App.jsx // 根组件
├── components/ // 组件目录
│ └── PictureCard.jsx // 图片上传与展示组件
├── lib/ // 工具包
│ └── audio.js // 音频生成逻辑
├── index.css // 全局样式
└── main.jsx // 应用入口
2.2 组件划分原则
1. App.jsx(根组件)
- 负责全局状态管理(如图片 Base64 数据、单词、例句、音频 URL)
- 调用 AI 接口获取数据
- 向子组件传递 props 并接收回调
2. PictureCard.jsx(子组件)
- 负责图片上传、预览和音频播放功能
- 通过回调函数将图片 Base64 数据传给父组件
- 展示 AI 返回的单词信息及语音按钮
3. 单向数据流设计
- 子组件负责上传图片并返回 Base64 编码
- 父组件接收编码并调用 AI 接口
- 获取结果后更新状态并通过 props 传递给子组件渲染
三、核心功能实现详解
3.1 图片上传与 Base64 编码
在 PictureCard.jsx 中实现图片上传功能,并将其转换为 Base64 编码格式:
import React, { useState } from 'react';
const PictureCard = ({ uploadImg }) => {
const [imgPreview, setImgPreview] = useState('https://res.bearbobo.com/resource/upload/W44yyxvl/upload-ih56twxirei.png');
const handleImageUpload = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onloadend = () => {
setImgPreview(reader.result);
uploadImg(reader.result); // 回调通知父组件
};
reader.readAsDataURL(file);
};
return (
<div className="upload">
<input type="file" accept=".jpg,.jpeg,.png,.gif" onChange={handleImageUpload} />
<img src={imgPreview} alt="preview" />
</div>
);
};
export default PictureCard;
说明:使用
FileReader实现图片文件读取,并将其转换为 Base64 编码格式,便于后续上传至 AI 服务端。
3.2 图像识别与数据解析
在 App.jsx 中调用 Kimi Vision API 进行图像识别:
import React, { useState } from 'react';
import PictureCard from './components/PictureCard';
import './index.css';
function App() {
const [imgPreview, setImgPreview] = useState('');
const [word, setWord] = useState('');
const [sentence, setSentence] = useState('');
const [audioUrl, setAudioUrl] = useState('');
const [detailExpand, setDetailExpand] = useState(false);
const userPrompt = `
请分析这张图片,提取出一个最可能的英文单词,
并给出一个包含这个单词的英文例句。
输出格式为 JSON:
{
"representative_word": "单词",
"example_sentence": "例句"
}
`;
const uploadImg = async (imageData) => {
setImgPreview(imageData);
const endpoint = 'https://api.moonshot.cn/v1/chat/completions';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.VITE_KIMI_API_KEY}`
};
setWord('分析中...');
try {
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);
// 生成语音
const url = await generateAudio(replyData.example_sentence);
setAudioUrl(url);
} catch (error) {
console.error('Error uploading image:', error);
}
};
const playAudio = () => {
if (audioUrl) {
const audio = new Audio(audioUrl);
audio.play();
}
};
return (
<div className="container">
<h1>📸 拍图识词</h1>
<PictureCard uploadImg={uploadImg} />
<div className="word">{word}</div>
<div className="playAudio" onClick={playAudio}>
<img width="20px" src="https://res.bearbobo.com/resource/upload/Omq2HFs8/playA-3iob5qyckpa.png" alt="logo" />
</div>
<button onClick={() => setDetailExpand(!detailExpand)}>Talk about it</button>
{detailExpand && (
<div className="expand">
<img src={imgPreview} alt="preview" />
<div className="explaination">
<p>{sentence}</p>
</div>
</div>
)}
</div>
);
}
export default App;
说明:使用
fetch请求 Kimi Vision API,将 Base64 图片作为参数传入,并解析返回的 JSON 数据。同时触发语音合成接口生成音频。
3.3 文字转语音(TTS)
在 lib/audio.js 中封装 TTS 功能:
export const generateAudio = async (text) => {
const token = process.env.VITE_AUDIO_ACCESS_TOKEN;
const appId = process.env.VITE_AUDIO_APP_ID;
const clusterId = process.env.VITE_AUDIO_CLUSTER_ID;
const voiceName = process.env.VITE_AUDIO_VOICE_NAME;
const payload = {
app: { appid: appId, token, cluster: clusterId },
user: { uid: 'bearbobo' },
audio: {
voice_name: voiceName,
encoding: 'mp3_16k',
speed: 0,
volume: 50,
pitch: 0,
background_speech: ''
},
request: {
reqid: Math.random().toString(36).substring(7),
text,
text_type: 'plain',
operation: 'query'
}
};
const res = await fetch('/tts/api/v1/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer;${token}`,
},
body: JSON.stringify(payload),
});
const data = await res.json();
return getAudioUrl(data.data);
};
const getAudioUrl = (base64Data) => {
var byteCharacters = atob(base64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset++) {
byteArrays.push(byteCharacters.charCodeAt(offset));
}
var blob = new Blob([new Uint8Array(byteArrays)], { type: 'audio/mp3' });
return URL.createObjectURL(blob);
};
说明:该模块负责将文本发送至火山引擎 TTS 接口,接收 Base64 格式的音频数据并转换为浏览器可播放的 URL。
四、项目难点与优化
4.1 难点总结
- Base64 编码体积大:影响页面加载速度和 API 请求效率,需压缩或分段上传
- API 调用失败处理:网络不稳定时需增加重试机制和错误提示
- 音频播放兼容性问题:不同浏览器对 Audio 对象支持不一致,需做兼容处理
4.2 性能优化建议
- 使用
useMemo和useCallback减少不必要的重复渲染 - 对 AI 请求做节流或缓存处理
- 图片上传前进行本地压缩(如使用
compressorjs)
五、结语
通过本次实战项目,我们成功打造了一个融合 AI 视觉识别与语音合成的 React 应用。它不仅展示了现代前端开发如何结合 AI 技术,还为我们打开了通往教育科技、智能助手等领域的无限可能。
未来,我们可以进一步扩展功能,例如支持多语言识别、加入离线模式、集成大模型对话能力等,让这个应用真正成为一个“懂你”的英语学习伙伴。
如果你也想尝试做一个“能看、能说”的 AI 应用,不妨动手跟着这篇教程一起实践吧!