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

前端动画:别再用 jQuery animate 了

前端动画:别再用 jQuery animate 了 毒舌时刻 这动画效果做得跟幻灯片似的,一点都不流畅。 各位前端同行,咱们今天聊聊前端动画。别告诉我你还在使用 jQuery animate,那感觉就像在没有减震器的情况下开车——能开,但颠簸得要命。 为什么你需要现代前端动画 最近看到一个项目,动画效果卡顿,代码复杂难以维护。我就想问:你是在做动画还是在做卡顿展示? 反面教材 // 反面教材:使用 jQuery animate // index.html <!DOCTYPE html> <html> <head> <title>jQuery Animation</title> <script src=

10秒上手中文语音识别,科哥构建的WebUI太友好了

10秒上手中文语音识别,科哥构建的WebUI太友好了 你有没有过这样的时刻:会议刚结束,录音文件堆在文件夹里发呆;采访素材躺在硬盘里吃灰;想把一段语音快速转成文字,却卡在环境配置、模型下载、代码调试的迷宫里?别折腾了——今天这个工具,真能让你10秒打开网页、30秒上传音频、1分钟拿到准确文字稿。 这不是概念演示,也不是简化版demo,而是基于阿里FunASR生态中性能顶尖的Speech Seaco Paraformer ASR模型,由开发者“科哥”亲手封装、反复打磨的WebUI镜像。它不依赖Python环境、不碰CUDA编译、不写一行代码,所有操作都在浏览器里完成。更关键的是:它专为中文场景优化,对“人工智能”“大模型”“端到端”这类高频术语识别稳得一批,还支持热词定制——这才是真正能进工作流的语音识别工具。 下面我就带你从零开始,不讲原理、不列参数、不堆术语,只说你点哪里、传什么、看什么、怎么用得更准。 1. 三步启动:不用装、不用配、

从零搭建SpringBoot+Vue+Netty+WebSocket+WebRTC视频聊天系统

在实时通信场景中,音视频聊天是最核心的需求之一,比如在线会议、远程面试、社交视频等。本文将手把手教你搭建一套基于SpringBoot+Vue+Netty+WebSocket+WebRTC的全栈视频聊天系统,全程保留完整可运行代码,无需修改即可直接部署测试,同时拆解核心技术原理,让你不仅能“跑通项目”,更能“理解底层逻辑”。 本文适合有一定Java和Vue基础的开发者,核心目标是实现“两端内网设备实时视频通话”,无需第三方音视频SDK,完全基于原生技术栈开发,兼顾实用性与可扩展性。 一、核心技术栈原理铺垫 在动手开发前,我们先理清核心技术的作用,尤其是WebRTC相关的关键概念——很多开发者踩坑,本质是没搞懂NAT穿透和信令交互的逻辑。 1.1 WebRTC:浏览器原生的实时通信“利器” WebRTC(Web Real-Time Communication)是浏览器内置的实时通信技术标准,无需安装任何插件,就能让网页直接实现音视频采集、编码、传输和渲染。简单说,它帮我们搞定了“音视频流怎么从本地设备传到对方设备”的核心问题,是整个视频聊天的“核心引擎”

原生多模态AI架构:统一训练与跨模态推理的系统实现与性能优化

原生多模态AI架构:统一训练与跨模态推理的系统实现与性能优化

人们眼中的天才之所以卓越非凡,并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆·格拉德威尔 🌟 Hello,我是Xxtaoaooo! 🌈 “代码是逻辑的诗篇,架构是思想的交响” 在人工智能快速发展的今天,多模态AI已经从实验室走向了产业应用的前沿。从GPT-4V到Gemini,从CLIP到ImageBind,业界对多模态模型的探索正在经历从"拼接式融合"到"原生统一"的范式转变。传统的多模态方案往往采用预训练单模态模型后再进行跨模态对齐,这种方式虽然实现简单,但在模态间的深度语义理解、计算效率和推理一致性上存在明显瓶颈。本文将深入探讨原生多模态AI架构的核心设计理念,从统一编码空间的构建、跨模态注意力机制的实现,到分布式训练优化和推理加速策略,系统性地剖析如何构建一个高性能的原生多模态AI系统。 文章将首先解析原生多模态架构与传统方案的本质区别,阐述统一Token空间的设计哲学;随后深入到技术实现层面,详细讲解多模态Transformer的架构设计、跨模态注意力的计算优化、以及混合精度训练的工程实践;在性能优化部分