在uni-app中实现类似文心一言的流式对话功能:从fetch到websocket的实践

这里写自定义目录标题

前言

在开发基于uni-app的应用时,我们常常需要实现一些复杂的交互功能,比如类似文心一言的对话系统。这种对话系统的核心体验之一就是流式输出效果------用户发送问题后,回答内容像打字机一样逐字显示,而不是一次性弹出完整结果。这种效果在H5环境下可以通过fetch API的流式处理实现,但在uni-app中由于环境限制,我们需要采用websocket方案来实现类似效果。

一、为什么不能直接使用fetch实现流式效果?

在标准的H5开发中,我们可以通过fetch API结合ReadableStream API实现流式数据处理:

复制代码
fetch(url)
  .then(response => response.body.getReader())
  .then(reader => {
    // 逐块读取数据并更新UI
  });

但uni-app的运行环境对原生API有所限制,虽然uni-app提供了uni.request作为fetch的替代方案,但它并不支持流式读取响应体。这意味着我们无法像在纯H5中那样直接通过HTTP请求实现逐字节接收数据,从而无法实现流畅的打字机效果。

二、websocket:uni-app中的流式数据解决方案

鉴于HTTP请求的限制,websocket成为了实现实时流式数据传输的理想选择。websocket协议提供全双工通信通道,允许服务器主动向客户端推送数据,非常适合这种需要实时更新UI的场景。

2.1、uni-app中的websocket API概览

uni-app提供了完整的websocket支持,关键API包括:

  • uni.connectSocket(Object): 初始化websocket连接

  • uni.onSocketOpen(Object): 监听websocket连接打开事件

  • uni.onSocketMessage(Object): 监听websocket收到消息事件

  • uni.sendSocketMessage(Object): 通过websocket发送消息

  • uni.closeSocket(Object): 关闭websocket连接

2.2、websocket实现流式对话的完整流程

步骤1:建立websocket连接

在用户进入对话页面时,我们需要建立websocket连接:

复制代码
// 建立连接
uni.connectSocket({
  url: 'wss://your-api-domain.com/socket',
  header: {
    'content-type': 'application/json'
  },
  success: function(res) {
    console.log('连接成功', res);
  },
  fail: function(err) {
    console.error('连接失败', err);
    // 这里可以实现重连逻辑
  }
});

// 监听连接打开事件
uni.onSocketOpen(function (res) {
  console.log('WebSocket连接已打开:' + res);
  // 可以在这里发送初始握手消息
});

步骤2:处理收到的消息

当服务器通过websocket推送数据时,我们需要逐帧处理并更新UI:

复制代码
// 监听websocket消息
let buffer = ''; // 用于缓存未完成的消息
uni.onSocketMessage(function (res) {
  const data = res.data;
  
  // 如果是逐帧发送的文本数据
  if (typeof data === 'string') {
    buffer += data;
    
    // 检查是否是完整消息(根据实际协议判断)
    if (data.endsWith('</end>')) { // 假设使用特殊标记表示消息结束
      const finalMessage = buffer.replace('</end>', '');
      processCompleteMessage(finalMessage);
      buffer = '';
    } else {
      // 更新UI显示部分消息
      updateMessageDisplay(buffer);
    }
  }
});

步骤3:实现打字机效果的UI更新

为了实现流畅的打字机效果,我们需要在UI层做些处理:

复制代码
function updateMessageDisplay(text) {
  // 创建一个临时节点用于文本拆分
  const words = text.split('');
  
  // 清空当前消息显示区域
  this.setData({
    currentMessage: ''
  });
  
  // 使用setTimeout逐字显示
  let index = 0;
  const timer = setInterval(() => {
    this.setData({
      currentMessage: words.slice(0, index + 1).join('')
    });
    
    index++;
    if (index >= words.length) {
      clearInterval(timer);
    }
  }, 100); // 调整这里控制打字速度
}

步骤4:发送用户消息

当用户点击发送按钮时,通过websocket发送消息:

复制代码
// 发送用户消息
uni.sendSocketMessage({
  data: JSON.stringify({
    type: 'user_message',
    content: userInput,
    sessionId: this.sessionId
  }),
  success: function() {
    console.log('消息发送成功');
  },
  fail: function(err) {
    console.error('消息发送失败', err);
  }
});

步骤5:优雅地关闭连接

在页面卸载或对话结束时,及时关闭websocket连接:

复制代码
uni.closeSocket({
  success: function(res) {
    console.log('连接已关闭', res);
  }
});

三、完整代码示例

复制代码
// 对话页面的生命周期方法
onLoad() {
  this.initWebSocket();
},

initWebSocket() {
  // 建立连接
  uni.connectSocket({
    url: 'wss://your-api-domain.com/socket',
    header: {
      'content-type': 'application/json'
    }
  });
  
  // 监听连接打开
  uni.onSocketOpen(() => {
    console.log('WebSocket连接已打开');
    // 发送身份认证等初始化消息
    uni.sendSocketMessage({
      data: JSON.stringify({
        type: 'auth',
        token: this.userToken
      })
    });
  });
  
  // 监听消息
  let buffer = '';
  uni.onSocketMessage((res) => {
    const data = res.data;
    
    if (typeof data === 'string') {
      buffer += data;
      
      if (data.endsWith('</end>')) {
        const finalMessage = buffer.replace('</end>', '');
        this.processCompleteMessage(finalMessage);
        buffer = '';
      } else {
        this.updateMessageDisplay(buffer);
      }
    }
  });
  
  // 监听错误
  uni.onSocketError((err) => {
    console.error('WebSocket发生错误:', err);
    // 实现重连逻辑
    setTimeout(() => this.initWebSocket(), 5000);
  });
},

processCompleteMessage(message) {
  // 处理完整消息,如添加到消息列表
  this.messages.push({
    type: 'ai',
    content: message
  });
  this.scrollToBottom();
},

updateMessageDisplay(text) {
  const words = text.split('');
  this.setData({
    currentTypingMessage: ''
  });
  
  let index = 0;
  const timer = setInterval(() => {
    this.setData({
      currentTypingMessage: words.slice(0, index + 1).join('')
    });
    
    index++;
    if (index >= words.length) {
      clearInterval(timer);
    }
  }, 100);
},

// 用户发送消息
sendMessage() {
  const userInput = this.inputText.trim();
  if (!userInput) return;
  
  this.messages.push({
    type: 'user',
    content: userInput
  });
  
  uni.sendSocketMessage({
    data: JSON.stringify({
      type: 'user_message',
      content: userInput,
      sessionId: this.sessionId
    })
  });
  
  this.inputText = '';
  this.scrollToBottom();
},

在uni-app中实现类似文心一言的流式对话功能,虽然不能直接使用H5的fetch流式API,但通过websocket可以实现类似甚至更好的效果。完整的实现需要处理好以下几个关键点:

  • websocket连接的生命周期管理
  • 消息的分片接收与拼接
  • UI层的打字机效果实现
  • 异常处理与重连机制
  • 通过本文提供的方法和代码示例,您应该能够在uni-app项目中顺利实现流畅的流式对话体验,为用户提供不同的交互感受。
相关推荐
*拯17 分钟前
Uniapp Android/IOS 获取手机通讯录
android·ios·uni-app
gaojianqiao12341 小时前
uniapp引入七鱼客服微信小程序SDK
微信小程序·uni-app
全栈小53 小时前
【文心智能体】使用文心一言来给智能体设计一段稳定调用工作流的提示词
文心一言·智能体·文心智能体
假客套8 小时前
2025 后端自学UNIAPP【项目实战:旅游项目】3、API接口请求封装,封装后的简单测试以及实际使用
uni-app·旅游项目实战
JAVA叶知秋16 小时前
uniapp自定义底部导航栏h5有效果小程序无效的解决方案
小程序·uni-app
moxiaoran575321 小时前
uni-app学习笔记(二)--vue页面代码的构成和新建页面
笔记·学习·uni-app
xsh21921 小时前
HTTP 和 WebSocket 的区别
websocket·网络协议·http
一只程序熊1 天前
【uniapp】errMsg: “navigateTo:fail timeout“
服务器·前端·uni-app
沙尘暴炒饭1 天前
用uniapp在微信小程序实现画板(电子签名)功能,使用canvas实现功能
微信小程序·小程序·uni-app