UniApp + Dify 实战:详解 SSE 流式响应的解析与前端渲染

UniApp + Dify 实战:详解 SSE 流式响应的解析与前端渲染

1. 理解核心机制:拼接而非替换

Dify 的 streaming 模式下,服务器会不断推送形如 data: {"event": "message", "answer": "字"} 的数据包。
核心逻辑是: 收到一个包,解析出 answer 字段,将其**追加(Append)**到当前正在显示的对话变量后,而不是直接替换。

2. 关键数据解析逻辑

Dify 返回的数据流格式如下:

data: {"event": "message", "answer": "我", ...}\n\n data: {"event": "message", "answer": "是", ...}\n\n data: {"event": "message_end", ...}\n\n 

处理难点:

  1. 前缀处理:每行数据都以 data: 开头,解析 JSON 前必须去掉。
  2. 粘包处理:有时候一次网络请求回调会收到多条 data,需要用 \n\n 分割。
  3. 事件区分:必须判断 event 字段。
    • message: 文本块,核心展示内容
    • message_replace: 敏感词替换,需要替换整段文本。
    • message_end: 结束标志。
    • ping: 心跳,忽略即可。

3. UniApp 代码实现方案

在 UniApp 中(特别是微信小程序端),不能直接使用浏览器原生的 EventSource。推荐使用 uni.requestenableChunked: true 参数。

以下是一个完整的处理示例代码:

// 假设这是发送消息的方法sendMessage(userQuery){const that =this;// 1. 在界面先创建一个空的回答占位(为了立刻显示 loading 或光标)this.messageList.push({ role:'user', content: userQuery });this.messageList.push({ role:'assistant', content:''// 初始为空,稍后拼接});// 获取当前正在更新的这条消息在数组中的索引const currentMsgIndex =this.messageList.length -1;// 2. 发起请求const requestTask = uni.request({ url:'http://47.243.127.167:4010/v1/chat-messages', method:'POST', header:{'Authorization':'Bearer {API_KEY}',// 替换为真实 Key'Content-Type':'application/json'}, data:{ inputs:{}, query: userQuery, response_mode:"streaming",// 必须是 streaming user:"uni-user-123", conversation_id: that.conversationId ||""// 如果是连续对话,需传入}, enableChunked:true,// 【关键】开启流式传输支持success:(res)=>{// 这里是请求完成后的回调,流式通常不在这里处理数据}});// 3. 监听流式数据头(可选) requestTask.onHeadersReceived((headers)=>{// console.log('Header received', headers);});// 4. 【核心】监听分片数据 requestTask.onChunkReceived((res)=>{// res.data 是 ArrayBuffer,需要转换const arrayBuffer = res.data;// 小程序/App端需要 TextDecoder,或者使用第三方库转换// 如果环境不支持 TextDecoder,需使用类似 text-encoding 的 polyfillconst uint8Array =newUint8Array(arrayBuffer);let text ="";// 简易转换 (注意:中文可能乱码,生产环境建议用专业库如 fast-text-encoding)// 微信小程序基础库高版本已支持 TextDecodertry{const decoder =newTextDecoder('utf-8'); text = decoder.decode(uint8Array,{ stream:true});}catch(e){// 兼容写法,逐字节处理(此处仅为示意,建议引入库) text = String.fromCharCode.apply(null, uint8Array);// 实际开发请务必处理 UTF-8 多字节中文乱码问题 text =decodeURIComponent(escape(text));}// 5. 处理 Dify 返回的原始数据字符串 that.processDifyStream(text, currentMsgIndex);});},// 处理 Dify 数据流的专用函数processDifyStream(chunkText, msgIndex){// Dify 的数据块以 \n\n 分隔const lines = chunkText.split('\n\n'); lines.forEach(line=>{// 去掉 data: 前缀if(line.startsWith('data: ')){const jsonStr = line.replace('data: ','');try{const data =JSON.parse(jsonStr);// 根据 Dify 文档判断 event 类型if(data.event ==='message'){// 【关键步骤】拼接 answer 字段到当前消息this.messageList[msgIndex].content += data.answer;// 保存 conversation_id 以便下一轮对话if(!this.conversationId && data.conversation_id){this.conversationId = data.conversation_id;}}elseif(data.event ==='message_replace'){// 内容审查替换,直接覆盖this.messageList[msgIndex].content = data.answer;}elseif(data.event ==='message_end'){ console.log('生成结束', data);// 可以在这里处理 metadata,比如 token 消耗}elseif(data.event ==='error'){ console.error('Dify 报错:', data);this.messageList[msgIndex].content +="\n[出错: "+ data.message +"]";}// 【重要】强制触发 Vue 视图更新(如果在某些层级深的结构中)// 这一步在 Vue2 中可能不需要,但在某些 UniApp 场景下需要// this.$forceUpdate(); }catch(e){// JSON 解析失败通常是因为数据包不完整(被截断),// 生产环境需要做一个 buffer 缓存上一块未解析完的字符串// 暂时忽略或存入 buffer console.log('JSON parse error (ignore partial chunk):', e);}}});}

4. 常见坑排查清单

如果还是展示不出来,请按以下顺序检查:

  1. ArrayBuffer 解码乱码
    • UniApp 的 onChunkReceived 返回的是 ArrayBuffer。如果不进行 UTF-8 解码直接转字符串,中文会显示乱码或空白。
    • 解决:确保使用了 TextDecoder 或者 decodeURIComponent(escape(String.fromCharCode(...))) 这种方式正确解码。
  2. Vue 响应式失效
    • 如果在 onChunkReceived 这种异步回调中,this 指向可能丢失。
    • 解决:确保在外部定义了 const that = this;,或者使用箭头函数。
    • 解决:如果是 Vue 2,修改数组索引可能不会触发视图更新。使用 this.$set(this.messageList, index, newValue) 或者直接修改对象属性 this.messageList[index].content += '...' 通常是有效的,但要确保 messageList 是在 data 中定义的。
  3. Markdown 渲染
    • Dify 输出的是 Markdown 格式(包含 **加粗**Code Block 等)。
    • 如果直接用 <text>{{ content }}</text>,只能显示纯文本。
    • 建议:在 UniApp 中引入 mp-htmltowxml 等组件来渲染 Markdown,这样能正确展示代码块和格式。
  4. JSON 解析报错
    • 流式传输网络抖动时,JSON 可能会被截断(比如 {"answer": "你好 后面断了)。
    • 解决:需要实现一个 buffer 变量,如果 JSON.parse 失败,将当前字符串存起来,等下一个 chunk 来了拼接到头部再解析。

Read more

介绍终身机器人学习的数据集LIBERO

介绍终身机器人学习的数据集LIBERO

1 LIBERO的作用 LIBERO是一个用于研究多任务和终身机器人学习中知识迁移的综合基准测试平台,LIBERO是基于robosuite框架构建的。它专注于机器人操作任务,这些任务需要两类知识: 1. 陈述性知识:关于物体和空间关系的知识 2. 程序性知识:关于运动和行为的知识 2 核心原理 任务生成与基准设计 LIBERO提供了一个程序化生成管道,原则上可以生成无限数量的操作任务。系统包含130个任务,分为四个任务套件,每个套件都有受控的分布偏移: * LIBERO-Spatial/Object/Goal:专注于特定类型知识的迁移 * LIBERO-100:包含需要迁移纠缠知识的100个操作任务 学习框架 系统采用模仿学习作为主要学习方法,因为任务使用稀疏奖励函数(任务完成时获得+1奖励)。LIBERO提供高质量的人类遥操作演示数据集用于训练。 算法与策略架构 LIBERO实现了三种视觉运动策略网络: * bc_rnn_policy:基于RNN的行为克隆策略 * bc_transformer_policy:基于Transformer的行为克隆策略

Trae x 图片素描MCP一键将普通图片转换为多风格素描效果

Trae x 图片素描MCP一键将普通图片转换为多风格素描效果

目录 * 前言 * 一、核心工具与优势解析 * 二、操作步骤:从安装到生成素描效果 * 第一步:获取MCP配置代码 * 第二步:下载 * 第三步:在 Trae 中导入 MCP 配置并建立连接 * 第四步:核心功能调用 * 三、三大素描风格差异化应用 * 四.总结 前言 在设计创作、社交媒体分享、教育演示等场景中,素描风格的图片往往能以简洁的线条突出主体特征,带来独特的艺术质感。然而,传统素描效果制作需借助专业设计软件(如Photoshop、Procreate),不仅操作复杂,还需掌握一定的绘画技巧,难以满足普通用户快速生成素描的需求。 为解决这一痛点,本文将介绍蓝耘MCP广场提供的图片素描MCP工具(工具ID:3423)。该工具基于MCP(Model Context Protocol)协议开发,支持单张/批量图片转换、3种素描风格切换及自定义参数调节,兼容多种图片格式与中文路径,无需专业设计能力,

Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座

Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 discord_interactions 的鸿蒙化适配指南 - 在 OpenHarmony 打造高效的社交机器人交互底座 在现代社交应用与办公协同工具的开发中,集成强大的机器人(Bot)交互能力是提升活跃度的关键。discord_interactions 库为 Flutter 开发者提供了一套完整的、遵循 Discord 官方协议的交互模型,涵盖了从 Slash Commands(斜杠命令)到 Webhook 签名验证的核心功能。本文将深入解析如何在 OpenHarmony(鸿蒙)环境下,结合鸿蒙的安全机制与网络特性,完美适配 discord_interactions 到你的鸿蒙应用中。 前言 随着鸿蒙系统(HarmonyOS)进入原生应用开发的新纪元,跨平台社交工具的适配需求日益增长。discord_interactions 作为一个纯

Fanuc机器人与PLC的Ethernet/IP通信

Fanuc机器人与PLC通过Ethernet/IP实现高速通信的技术实践 在现代智能制造产线中,机器人与上位控制系统之间的实时、稳定通信是保障生产节拍和设备协同的关键。Fanuc作为工业机器人领域的主流厂商,其控制系统虽然封闭性强,但通过标准工业以太网协议如Ethernet/IP,依然能够实现与第三方PLC(如罗克韦尔ControlLogix、西门子S7等)的高效数据交互。 尤其是在汽车焊装线、装配工站或物料搬运系统中,我们经常遇到这样的需求:用Allen-Bradley PLC统一调度多台Fanuc机器人执行不同动作序列,并实时监控其运行状态、报警信息及I/O反馈。这种场景下,传统的硬接线DI/DO方式已难以满足复杂逻辑与高响应要求,而基于Ethernet/IP的通信方案则展现出显著优势——不仅布线简化,更支持结构化数据传输和远程控制。 那么,如何让一台Fanuc LR Mate 200iD或M-20iA真正“听懂”ControlLogix控制器发出的指令?这背后涉及硬件配置、网络参数设置、标签映射以及KAREL程序的协同配合。本文将结合实际工程案例,深入剖析这一集成过程中的关