页面效果:

js代码:
javascript
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) => (
<div
key={index}
className={`message-wrapper ${item.sender}`}
>
<div className={`message-bubble ${item.sender}`}>
<div className="message-avatar">
<Avatar
icon={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%' }}>
<Button
type={isRecording ? 'danger' : 'default'}
icon={isRecording ? <StopOutlined /> : <AudioOutlined />}
onClick={toggleRecording}
/>
<Input
placeholder={isRecording ? '正在录音...' : '输入消息或语音...'}
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onPressEnter={handleSend}
disabled={isRecording}
/>
<Button
type="primary"
icon={<SendOutlined />}
onClick={handleSend}
disabled={isRecording}
>
发送
</Button>
</Space.Compact>
</Footer>
</Layout>
);
};
export default ChatInterface;
less代码:
css
/* 整体布局 */
.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;
}
}