一、项目介绍
大家好,今天给大家分享一个我最近开发的小项目——AI单词拍照应用。这是一个基于React的移动应用,通过拍照识别图片内容,自动生成对应的英文单词、例句和发音,帮助用户更直观地学习英语单词。整个项目采用了组件化设计思想,代码结构清晰,功能完整,非常适合React初学者参考学习。
二、项目结构
先来看一下项目的整体结构:
shotword/
├── public/
├── src/
│ ├── components/
│ │ └── PictureCard/
│ │ ├── index.jsx # 图片上传卡片组件
│ │ └── style.css # 组件样式
│ ├── lib/
│ │ └── audio.js # 音频处理工具
│ ├── App.jsx # 根组件
│ └── main.jsx # 入口文件
└── README.md # 项目说明
项目采用了典型的React组件化架构,将功能拆分为不同的组件和工具函数,使代码更易于维护和扩展。
三、核心组件解析
1. App根组件
App.jsx 是整个应用的核心,负责状态管理和业务逻辑处理。
主要功能:
- 管理应用的所有状态(单词、例句、解释、音频等)
- 处理图片上传和AI接口调用
- 生成音频并管理音频播放
- 控制详情面板的展开/折叠
关键代码分析:
function App() {
// 状态管理
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('https://res.bearbobo.com/resource/upload/W44yyxvl/upload-ih56twxirei.png')
// 图片上传处理函数
const uploadImg = async (imageData) => {
setImgPreview(imageData);
const endpoint = 'https://api.moonshot.cn/v1/chat/completions';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${import.meta.env.VITE_KIMI_API_KEY}`
};
setWord('分析中...');
// 调用AI API
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify({
model: 'moonshot-v1-8k-vision-preview',
messages: [ ... ],
stream: false
})
})
// 处理API响应
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);
// 生成音频
const audioUrl = await generateAudio(replyData.example_sentence);
setAudio(audioUrl);
}
// 渲染UI
return (
<div className="container">
<PictureCard
audio={audio}
word={word}
uploadImg={uploadImg}
/>
{/* 其他UI元素 */}
</div>
)
}
核心功能实现
图片上传与AI处理
uploadImg 方法是App组件的核心功能,负责处理图片上传、调用AI接口并更新状态:
const uploadImg = async (imageData) => {
setImgPreview(imageData);
const endpoint = 'https://api.moonshot.cn/v1/chat/completions';
const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${import.meta.env.VITE_KIMI_API_KEY}`
};
setWord('分析中...');
const response = await fetch(endpoint, {
method: 'POST',
headers: 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);
// 生成音频
const audioUrl = await generateAudio(replyData.example_sentence);
setAudio(audioUrl);
}
这个方法展示了完整的异步数据处理流程:接收图片数据→调用AI API→解析响应→更新状态→生成音频。
用户提示词设计
为了让AI模型返回结构化的结果,App组件定义了清晰的提示词模板:
const userPrompt = `分析图片内容,找出最能描述图片的一个英文单词,尽量选择更简单的A1~A2的词汇。
返回JSON数据:
{
"image_discription": "图片描述",
"representative_word": "图片代表的英文单词",
"example_sentence": "结合英文单词和图片描述,给出一个简单的例句",
"explaination": "结合图片解释英文单词,段落以Look at...开头,将段落分句,每一句单独一行,解释的最后给一个日常生活有关的问句",
"explaination_replys": ["根据explaination给出的回复1", "根据explaination给出的回复2"]
}`;
这种结构化的提示词设计确保了AI返回的数据格式一致,便于后续处理。
2. PictureCard组件
index.jsx 是项目的核心功能组件,负责图片上传和预览。
主要功能:
- 图片选择和上传
- 图片预览显示
- 单词展示
- 音频播放控制
const PictureCard = (props) => {
// 解构参数
const {
word,
audio,
uploadImg
} = props
// 本地状态管理
const [imgPreview, setImgPreview] = useState('https://res.bearbobo.com/resource/upload/W44yyxvl/upload-ih56twxirei.png')
// 图片上传处理
const uploadImgData = (e) => {
const file = (e.target).files?.[0];
if (!file) { return; }
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const data = reader.result;
setImgPreview(data);
uploadImg(data); // 调用父组件传递的回调函数
resolve(data);
}
reader.onerror = (error) => { reject(error); };
})
}
// 音频播放
const playAudio = () => {
const voice = new Audio(audio);
voice.play();
}
return (
<div className="card">
<input
id="selectImage"
type="file"
accept=".jpg,.jpeg,.png,.gif"
onChange={uploadImgData}
/>
<label htmlFor="selectImage" className="upload">
<img src={imgPreview} alt="preview" />
</label>
<div className="word">{word}</div>
{audio && (
<div className="playAudio" onClick={playAudio}>
<img width="20px" src="https://res.bearbobo.com/resource/upload/Omq2HFs8/playA-3iob5qyckpa.png" alt="logo" />
</div>
)}
</div>
)
}
这个组件展示了React组件通信的典型方式:通过props接收父组件传递的数据和回调函数,当用户上传图片时,通过回调函数将图片数据传递给父组件处理。
3. 音频处理工具
audio.js 提供了文字转语音功能,将AI生成的例句转换为音频。
关键代码分析:
// 将base64音频数据转换为可播放的URL
export const generateAudio = async (text) =>{
const token = import.meta.env.VITE_AUDIO_ACCESS_TOKEN;
// 其他环境变量...
// 调用TTS API
const res = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(payload)
})
const data = await res.json();
const audioUrl = getAudioUrl(data.data);
return audioUrl;
}
这个工具函数展示了如何安全地使用环境变量存储API密钥,以及如何处理音频数据。
四、核心技术点
1. React组件通信
项目采用了React的单向数据流模式:
- 父组件(App)通过props向子组件(PictureCard)传递数据和回调函数
- 子组件通过调用回调函数将数据传递回父组件
- 状态集中管理在父组件,保证数据一致性
README.md中提到:"state (私有数据状态)、props (父组件传递的数据状态) 都是数据,子组件修改状态,通过回调函数通知父组件,父组件修改状态"
2. 图片处理
使用HTML5的FileReader API实现图片预览:
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const data = reader.result;
setImgPreview(data);
}
3. AI接口调用
项目使用了月之暗面的视觉模型API,通过fetch调用:
const response = await fetch(endpoint, {
method: 'POST',
headers: headers,
body: JSON.stringify({
model: 'moonshot-v1-8k-vision-preview',
messages: [ ... ]
})
})
4. 音频处理
将API返回的base64音频数据转换为可播放的URL:
var blob = new Blob([new Uint8Array(byteArrays)], { type: 'audio/mp3' });
return URL.createObjectURL(blob);
五、样式设计
项目使用CSS实现了响应式设计,适配移动设备:
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
margin: 0;
padding: 0;
width: 100vw;
height: 100vh;
font-size: .85rem;
background: linear-gradient(180deg, rgb(235, 189, 161) 0%, rgb(71, 49, 32) 100%);
}
.card {
border-radius: 8px;
padding: 20px;
margin-top: 40px;
height: 280px;
box-shadow: rgb(63,38,21) 0 3px 0px 0;
background-color: rgb(105,78,62);
box-sizing: border-box;
}
六、项目亮点
- 组件化设计 :将UI拆分为独立可复用的组件,提高代码复用性和维护性
- 状态管理 :合理使用React的useState钩子管理应用状态
- API集成 :无缝集成AI视觉模型和TTS语音合成API
- 用户体验 :提供图片预览、加载状态提示、音频播放等功能
- 安全性 :使用环境变量存储敏感信息,避免硬编码API密钥
七、遇到的问题与解决方案
1. 图片上传预览
问题 :如何在上传前预览图片? 解决方案 :使用FileReader API将图片文件转换为base64格式,再通过img标签显示
2. 音频资源管理
问题 :如何处理音频资源释放,避免内存泄漏? 解决方案 :使用URL.createObjectURL创建临时URL,播放完成后使用URL.revokeObjectURL释放资源
3. 异步操作处理
问题 :API调用和文件读取都是异步操作,如何保证执行顺序? 解决方案 :使用async/await语法糖,使异步代码更易读和维护
八、总结
这个AI单词拍照应用虽然小巧,但涵盖了React开发的多个重要方面:组件设计、状态管理、异步操作、API集成等。通过这个项目,我加深了对React组件化思想和单向数据流的理解,也学会了如何将AI能力集成到前端应用中。
希望这篇文章能帮助大家更好地理解React应用开发,如果你有任何问题或建议,欢迎在评论区留言讨论!