用React打造AI单词拍照应用:从组件设计到功能实现

112 阅读6分钟

一、项目介绍

大家好,今天给大家分享一个我最近开发的小项目——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;
}

六、项目亮点

  1. 组件化设计 :将UI拆分为独立可复用的组件,提高代码复用性和维护性
  2. 状态管理 :合理使用React的useState钩子管理应用状态
  3. API集成 :无缝集成AI视觉模型和TTS语音合成API
  4. 用户体验 :提供图片预览、加载状态提示、音频播放等功能
  5. 安全性 :使用环境变量存储敏感信息,避免硬编码API密钥

七、遇到的问题与解决方案

1. 图片上传预览

问题 :如何在上传前预览图片? 解决方案 :使用FileReader API将图片文件转换为base64格式,再通过img标签显示

2. 音频资源管理

问题 :如何处理音频资源释放,避免内存泄漏? 解决方案 :使用URL.createObjectURL创建临时URL,播放完成后使用URL.revokeObjectURL释放资源

3. 异步操作处理

问题 :API调用和文件读取都是异步操作,如何保证执行顺序? 解决方案 :使用async/await语法糖,使异步代码更易读和维护

八、总结

这个AI单词拍照应用虽然小巧,但涵盖了React开发的多个重要方面:组件设计、状态管理、异步操作、API集成等。通过这个项目,我加深了对React组件化思想和单向数据流的理解,也学会了如何将AI能力集成到前端应用中。

希望这篇文章能帮助大家更好地理解React应用开发,如果你有任何问题或建议,欢迎在评论区留言讨论!