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

发布于:2025-05-12 ⋅ 阅读:(13) ⋅ 点赞:(0)

前言

在开发基于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项目中顺利实现流畅的流式对话体验,为用户提供不同的交互感受。