上一篇文章中,给到Deepseek我的完整可使用的代码,并提问:
你现在是一名小程序前端开发专家,结合代码,将接入SSE请求,获取聊天界面数据的代码,并> 结合官方文档,整理出相关知识点,生成可以发布的技术开发文档,提供给其他开发者参考使用。(结果实在是很惊艳啊!👏👏)本篇文章,我将结合Deepseek和最近一段时间实际开发过程,出一篇小白指南,有需求的小伙伴欢迎转走使用,文章中若有问题欢迎评论区指出。
本文将通过一个完整的聊天场景案例,手把手教你实现以下核心功能:
- 小程序语音输入转文字
- SSE长连接实时对话
- 聊天界面实时更新
- 小程序中一些踩坑记录
开发框架为:Taro v3.5.7
版本
一、语音转文字功能实现
使用微信提供的 微信同声传译插件,暂时不收费555...
1. 准备工作
原生微信小程序,在app.json
中添加插件声明;Taro框架中,在app.config.ts
中添加插件声明.
{
"plugins": {
"WechatSI": {
"version": "0.3.6",
"provider": "wx069ba97219f66d99"
}
}
}
2. 核心代码
有些细节注意点请看代码注释:
import Taro, { requirePlugin, useReady, showModal, showToast } from '@tarojs/taro';
// 初始化语音识别管理器
const plugin = requirePlugin("WechatSI");
const manager = plugin.getRecordRecognitionManager();
// 配置录音事件监听
const initRecord = () => {
manager.onStart = () => console.log('开始录音');
manager.onStop = (res) => {
if (res.result) {
setContent(res.result); // 更新输入框内容
}
};
manager.onError = (err) => {
showToast({ title: `识别失败(${err.errCode})` });
};
};
// 组件卸载清理
useEffect(() => {
return () => {
manager.stop();
};
}, [manager]);
// 切记!!一定要在useReady中对录音事件进行初始化注册
useReady(initRecord);
const startRecording = () => {
setRecordState(true);
manager.start({ lang: 'zh_CN' });
}
const stopRecording = () => {
setRecordState(false);
manager.stop();
}
// 触发录音
<Button
onLongPress={startRecording}
onTouchEnd={stopRecording}
>
{recording ? '录音中...' : '按住说话'}
</Button>
二、SSE长连接实时对话
1. 服务端事件格式要求
必须返回以下格式:
data: 这是一条消息\n\n
2. 核心代码
在使用new TextDecoder('utf-8').decode(chunk.data)
时,验证发现在微信开发者工具中能正常显示文本(Web API本身支持TextDecoder
),但是小程序环境不支持,导致在真机上显示乱码,这里需要安装一个text-encoding
库,并对环境做了区分引用,方便调试开发。
import appModel from "@/src/app.model";
let TextDecoder;
const platform = appModel.systemInfo.platform
if (platform === "devtools") {
// 在开发者工具中使用 Web API
TextDecoder = window.TextDecoder;
} else {
// 在真机环境中使用 library
TextDecoder = require('text-encoding/lib/encoding').TextDecoder;
}
export default TextDecoder;
/** 获取SSE数据 */
const getSSEList = useCallback(async () => {
if (!content || loading) return;
setLoading(true);
addChatItem(content);
try {
let buffer = '';
const task = Taro.request({
url: 'your-sse-endpoint',
method: 'POST',
header: {
'Accept': 'text/event-stream',// 关键配置
},
data: { keyword: content },
enableChunked: true,// 关键配置
});
task.onChunkReceived(chunk => {
const textChunk = new TextDecoder('utf-8').decode(chunk.data);
buffer = processStreamData(buffer + textChunk);
});
setRequestTask(task);
} catch (error) {
showToast({ title: '连接失败', icon: 'none' });
} finally {
setLoading(false);
}
}, [content, loading, addChatItem, processStreamData]);
/** 处理SSE流式数据 */
const processStreamData = useCallback((buffer: string) => {
let processedBuffer = buffer;
const events: string[] = [];
while (true) {
const eventEndIndex = processedBuffer.indexOf('\n\n');
if (eventEndIndex === -1) break;
const eventText = processedBuffer.substring(0, eventEndIndex);
processedBuffer = processedBuffer.substring(eventEndIndex + 2);
events.push(eventText);
}
events.forEach(eventText => {
const dataContent = eventText.split('\n')
.filter(line => line.startsWith('data:'))
.map(line => line.slice(5).trim())
.join('\n');
if (dataContent) {
setAllList(prev => {
const lastItem = prev[prev.length - 1];
return lastItem
? [...prev.slice(0, -1), { ...lastItem, answer: lastItem.answer + dataContent }]
: prev;
});
}
});
return processedBuffer;
}, []);
三、聊天界面实现技巧
1. 聊天记录组件
interface ChatItem {
question: string;
answer: string;
}
const ChatBubble = ({ item }) => (
<View className="chat-item">
<Text className="question">{item.question}</Text>
<Text className="answer">{item.answer}</Text>
</View>
);
// 使用示例
{chatList.map((item, index) => (
<ChatBubble key={index} item={item} />
))}
2. 输入区域
这里遇到的问题是,在真机上键盘弹起时,页面顶走后,键盘挡住了输入框的部分,这里对底部输入区域的bottom定位做了处理,根据键盘高度进行处理,优化键盘遮挡输入框的问题。
/** 处理键盘高度变化 */
const handleKeyboard = {
focus: (e) => {
const height = (e as any).detail?.height || 0;
setFooterBottom(height > 0 ? height : Taro.isBigScreen ? 32 : 0);
},
blur: () => setFooterBottom(Taro.isBigScreen ? 32 : 0)
};
<View className='chat-box' style={{ bottom: `${footerBottom}px` }}>
<Button
onLongPress={handleVoiceInput.start}
onTouchEnd={handleVoiceInput.end}
aria-role='button'
aria-label='长按说话'
className='chat-img-box'
>
{recordState ? '···' : '按住说话'}
</Button>
<View className='textarea-box'>
<Textarea
confirmType='send'
value={content}
disabled={loading}
showConfirmBar={false}
disableDefaultPadding
adjustPosition={false}
autoHeight
onInput={(e) => setContent(e.detail.value)}
placeholderClass='chat-placeholder'
className='textarea-option'
onFocus={handleKeyboard.focus}
onBlur={handleKeyboard.blur}
/>
</View>
<View
className={`submit-btn ${loading ? 'disabled' : ''}`}
onClick={getSSEList}
>
<Image src={sendMessage} className='send-icon' />
</View>
</View>
四、必知注意事项(踩坑总结)
1. 语音识别相关
- 插件限制:必须使用微信官方提供的插件
- 录音权限:需在首次使用时主动获取麦克风权限
- 超时限制:单次录音最长60秒
2. SSE连接相关
问题现象 | 解决方案 |
---|---|
真机显示乱码 | 使用text-encoding 依赖替代TextDecoder |
收到不完整消息 | 正确实现缓冲区管理(参考processStreamData) |
频繁断开连接 | 添加心跳检测机制 |
安卓正常iOS无响应 | 检查响应头Content-Type: text/event-stream |
3. 性能优化
- 防抖处理:避免快速重复发送请求
- 数据分块:建议服务器每发送200-500字符分块一次
- 内存管理:定期清理历史聊天记录
五、完整实现流程图
graph TD
A[用户长按录音] --> B(语音转文字)
B --> C{内容校验}
C -->|有效| D[发起SSE请求]
C -->|无效| E[提示错误]
D --> F[持续接收数据块]
F --> G[解析数据更新界面]
G --> H{对话完成?}
H -->|是| I[关闭连接]
H -->|否| F
六、扩展建议
- 增加加载状态:在等待响应时显示旋转图标
- 实现历史记录:使用本地存储保存聊天记录
- 添加重试机制:网络中断时自动重连
- 支持富文本:使用
rich-text
组件解析带格式内容
通过本文的指导,相信你已经能够实现一个完整的语音交互聊天小程序。建议在实际开发中结合微信开发者工具的调试功能,逐步验证每个功能的可靠性。