在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项目中顺利实现流畅的流式对话体验,为用户提供不同的交互感受。
相关推荐
HollowGoods3 小时前
MySQL多表联查的深度解析从基础语法到性能优化实战
websocket
Ivan-Tan6 小时前
从MySQL到ClickHouse超大规模数据分析的架构迁移实践与性能对比
文心一言
2501_915918418 小时前
iOS 26 App 性能测试|性能评测|iOS 26 性能对比:实战策略
android·macos·ios·小程序·uni-app·cocoa·iphone
用户9047066835719 小时前
uniapp Vue3版本,用pinia存储持久化插件pinia-plugin-persistedstate对微信小程序的配置
前端·uni-app
杰瑞学AI19 小时前
我的全栈学习之旅:FastAPI (持续更新!!!)
后端·python·websocket·学习·http·restful·fastapi
乔冠宇20 小时前
uniapp创建ts项目tsconfig.json报错的问题
uni-app
细节控菜鸡20 小时前
【2025最新】uniapp 中基于 request 封装实现多文件上传完整指南
uni-app
fakaifa20 小时前
【全开源】企业微信SCRM社群营销高级版系统+uniapp前端
uni-app·开源·企业微信·scrm·源码下载·企业微信scrm
棋子一名1 天前
跑马灯组件 Vue2/Vue3/uni-app/微信小程序
微信小程序·小程序·uni-app·vue·js
雪碧聊技术1 天前
关于springboot定时任务和websocket的思考
websocket·springboot定时任务