微信小程序语音聊天功能开发指南:从语音转文字到实时对话(一)

4 阅读3分钟

上一篇文章中,给到Deepseek我的完整可使用的代码,并提问:
你现在是一名小程序前端开发专家,结合代码,将接入SSE请求,获取聊天界面数据的代码,并> 结合官方文档,整理出相关知识点,生成可以发布的技术开发文档,提供给其他开发者参考使用。(结果实在是很惊艳啊!👏👏)

本篇文章,我将结合Deepseek和最近一段时间实际开发过程,出一篇小白指南,有需求的小伙伴欢迎转走使用,文章中若有问题欢迎评论区指出。

本文将通过一个完整的聊天场景案例,手把手教你实现以下核心功能:

  1. 小程序语音输入转文字
  2. SSE长连接实时对话
  3. 聊天界面实时更新
  4. 小程序中一些踩坑记录

开发框架为: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

六、扩展建议

  1. 增加加载状态:在等待响应时显示旋转图标
  2. 实现历史记录:使用本地存储保存聊天记录
  3. 添加重试机制:网络中断时自动重连
  4. 支持富文本:使用rich-text组件解析带格式内容

通过本文的指导,相信你已经能够实现一个完整的语音交互聊天小程序。建议在实际开发中结合微信开发者工具的调试功能,逐步验证每个功能的可靠性。