AI语音助手 React 组件使用js-audio-recorder实现,将获取到的语音转成base64发送给后端,后端接口返回文本内容
页面效果:
js代码:
import React, { useState, useRef, useEffect } from 'react';
import { Layout, List, Input, Button, Avatar, Space, Typography, message } from 'antd';
import { SendOutlined, UserOutlined, RobotOutlined, AudioOutlined, StopOutlined } from '@ant-design/icons';
import JsAudioRecorder from 'js-audio-recorder';
import './style.less';const { Header, Content, Footer } = Layout;
const { Text } = Typography;const ChatInterface = () => {const [messages, setMessages] = useState([{ sender: 'assistant', content: '你好!我是AI助手,有什么可以帮你的吗?' },]);const [inputValue, setInputValue] = useState('');const [isRecording, setIsRecording] = useState(false);const messagesEndRef = useRef(null);const recorderRef = useRef(null);// 初始化录音器useEffect(() => {recorderRef.current = new JsAudioRecorder({sampleBits: 16,sampleRate: 16000,numChannels: 1,});return () => {if (recorderRef.current) {recorderRef.current.destroy();}};}, []);// 开始/停止录音const toggleRecording = () => {if (isRecording) {stopRecording();} else {startRecording();}};// 开始录音const startRecording = () => {recorderRef.current.start().then(() => {setIsRecording(true);message.success('录音中...');}).catch((err) => {message.error('录音失败: ' + err.message);});};// 停止录音并发送const stopRecording = () => {try {recorderRef.current.stop();setIsRecording(false);message.success('录音结束,处理中...');const blob = recorderRef.current.getWAVBlob();console.log(blob);const reader = new FileReader();reader.onloadend = () => {const base64Data = reader.result.split(',')[1];console.log(base64Data);sendAudioToAPI(base64Data);};reader.onerror = () => {message.error('音频转换失败');};reader.readAsDataURL(blob);} catch (err) {message.error('停止录音失败: ' + err.message);}};// 模拟API调用const sendAudioToAPI = (base64Data) => {setTimeout(() => {const mockResponse = { text: '这是语音识别后的文本(模拟数据)' };setInputValue(mockResponse.text);message.success('语音识别完成!');}, 1500);};const handleSend = () => {if (!inputValue || inputValue.trim() === '') {message.warning('消息不能为空!');return;}// 添加用户消息setMessages([...messages, { sender: 'user', content: inputValue }]);setInputValue('');// 模拟AI回复setTimeout(() => {setMessages(prev => [...prev, { sender: 'assistant', content: `这是对你"${inputValue}"的回复。` }]);}, 1000);};// 自动滚动到底部useEffect(() => {messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });}, [messages]);return (<Layout className="chat-layout"><Header className="chat-header"><Text strong style={{ color: 'white', fontSize: '18px' }}>AI聊天助手(支持语音输入)</Text></Header><Content className="chat-content"><div className="message-container">{messages.map((item, index) => (<divkey={index}className={`message-wrapper ${item.sender}`}><div className={`message-bubble ${item.sender}`}><div className="message-avatar"><Avataricon={item.sender === 'user' ? <UserOutlined /> : <RobotOutlined />}style={{ backgroundColor: item.sender === 'user' ? '#1890ff' : '#52c41a' }}/></div><div className="message-content"><div className="message-sender">{item.sender === 'user' ? '你' : 'AI助手'}</div><div className="message-text">{item.content}</div></div></div></div>))}<div ref={messagesEndRef} /></div></Content><Footer className="chat-footer"><Space.Compact style={{ width: '100%' }}><Buttontype={isRecording ? 'danger' : 'default'}icon={isRecording ? <StopOutlined /> : <AudioOutlined />}onClick={toggleRecording}/><Inputplaceholder={isRecording ? '正在录音...' : '输入消息或语音...'}value={inputValue}onChange={(e) => setInputValue(e.target.value)}onPressEnter={handleSend}disabled={isRecording}/><Buttontype="primary"icon={<SendOutlined />}onClick={handleSend}disabled={isRecording}>发送</Button></Space.Compact></Footer></Layout>);
};export default ChatInterface;
less代码:
/* 整体布局 */
.chat-layout {height: 100vh;display: flex;flex-direction: column;background-color: #f5f5f5;
}.chat-header {background-color: #1e88e5;padding: 0 24px;display: flex;align-items: center;height: 64px;box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}.chat-content {flex: 1;overflow-y: auto;padding: 16px;background-color: #eaeaea;
}.chat-footer {padding: 12px 16px;background: #f0f2f5;border-top: 1px solid #e8e8e8;
}/* 消息容器 */
.message-container {display: flex;flex-direction: column;gap: 12px;
}/* 消息包装器 */
.message-wrapper {display: flex;
}.message-wrapper.user {justify-content: flex-end;
}.message-wrapper.assistant {justify-content: flex-start;
}/* 消息气泡 */
.message-bubble {display: flex;max-width: 80%;gap: 8px;
}.message-bubble.user {flex-direction: row-reverse;
}/* 消息内容 */
.message-content {display: flex;flex-direction: column;max-width: calc(100% - 40px);
}.message-sender {font-size: 12px;color: #666;margin-bottom: 4px;
}.message-text {padding: 10px 14px;border-radius: 18px;line-height: 1.5;word-break: break-word;
}/* 用户消息样式 */
.message-wrapper.user .message-text {background-color: #1890ff;color: white;border-top-right-radius: 4px;
}/* AI消息样式 */
.message-wrapper.assistant .message-text {background-color: white;color: #333;border-top-left-radius: 4px;box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}/* 第一条欢迎消息全宽 */
.message-wrapper.assistant:first-child .message-bubble {max-width: 100%;
}.message-wrapper.assistant:first-child .message-text {background-color: #f6ffed;border-radius: 8px;padding: 12px 16px;
}/* 头像样式 */
.message-avatar {display: flex;align-items: flex-end;padding-bottom: 24px;
}/* 移动端适配 */
@media (max-width: 768px) {.message-bubble {max-width: 90%;}.chat-footer {padding: 8px;}
}