当前位置: 首页 > news >正文

智能体项目实现AI对话流式返回效果

1、智能体项目里与AI大模型对话的时候,需要从后端的流式接口里取数据并实现打字机渲染效果。这里涉及到 Markdown 格式的渲染,所以需要配合 marked.js 实现,安装 marked.js :

npm install marked

引用:

import { marked } from 'marked';

2、调用后端流式接口,并处理获取到的数据

         // 建立连接
        createSseConnect(){
            let token = '1215421542125'
            let that = this;
                // 创建一个 AbortController 实例
                that.abortController = new AbortController();
                
                //接口请求参数
                let sendData = {
                    message: that.currSendValue,
                    modelId:'1',
                }
                // 请求配置
                const config = {
                method: "post",
                headers: {
                    "Content-Type": "application/json",
                    Accept: "text/event-stream",
                    clientid: "112121212121212121212",
                    Authorization: "Bearer " + token, // 根据实际情况获取 token
                },
                body: JSON.stringify(sendData),
                signal: that.abortController.signal, // 将 AbortController 的 signal 传递给 fetch
                };
                let url = base.url + '/zntdata/stream';
                // 发起请求
                let thinkingTime = true;
                fetch(url, config)
                .then((response) => {
                const reader = response.body.getReader();
                const decoder = new TextDecoder("utf-8");
                let buffer = "";

                that.currSendValue = ''
                
                // 处理接收到的消息
                function processMessage(message) {
                    message = message.split("data:")[0];
                    
                    const newChars = message.split("");
                    //解析深度思考
                    if (message.includes("<think>")) {
                        thinkingTime = true;
                    }
                    if (message.includes("</think>")) {
                        thinkingTime = false;
                    }
                    if (thinkingTime) {
                        that.charQueue2.push(...newChars);
                    } else {
                        that.charQueue.push(...newChars);
                    }

                    if (message.includes("[DONE]")) {
                        that.dialogId = message.substring(9)
                    }

                    // 启动打字机效果(如果尚未启动)
                    if (!that.isTyping) {
                        that.startTyping();
                    }
                    return false;
                }

                // 读取流式数据
                function readStream() {
                    reader
                    .read()
                    .then(({ done, value }) => {
                        if (done) {
                            console.log("Stream ended",value);
                            return;
                        }

                        // 解码数据并添加到缓冲区
                        buffer += decoder.decode(value);

                        // 处理完整的事件 -- 非统一处理方法,需根据业务需求和接口数据格式处理
                        while (buffer.includes("data:")) {
                            if ( buffer.includes("[DONE]")) {
                               that.dialogId = 
                            buffer.substring(buffer.indexOf('id:')+3).replaceAll('\n','')
                            }
                            const eventEndIndex = buffer.indexOf("data:");
                            let eventData = buffer.slice(0,eventEndIndex);
                            buffer = buffer.slice(eventEndIndex+5);
                            eventData = eventData.substring(0,eventData.lastIndexOf('\n\n')) 
                            const message = eventData;
                            if (eventData) {
                                if (processMessage(message)) return;
                            }
                        }

                        // 继续读取
                        readStream();
                    }).catch((err) => {
                        console.error("Stream error:", err);
                        const lastQuestionIndex = that.dialogueList.length-1
                        that.dialogueList[lastQuestionIndex].content = that.dialogueList[lastQuestionIndex].content + '出错了,暂时无法回答您的问题,请稍后再试。'
                    });
                }

                    // 开始读取流
                    readStream();
                })
            },



         // 启动打字机效果
         startTyping() {
            const that = this;
            that.isTyping = true;
            const lastQuestionIndex = that.dialogueList.length-1

            // 初始间隔时间
            let intervalTime = 30; // 初始速度为 30ms
            const minIntervalTime = 5; // 最小间隔时间,防止速度过快
            const acceleration = 0.9; // 每次加速的比例(0.9 表示每次间隔时间减少 10%)

            // 清除已有定时器
            if (that.typingInterval) clearInterval(that.typingInterval);
            // 定义打字机效果函数
            function typeCharacter() {
                if (that.charQueue.length === 0 && that.charQueue2.length === 0) {
                   clearInterval(that.typingInterval);
                   that.isTyping = false;
                   return;
                }

                if (that.charQueue2.length > 0) {
                  if (that.dialogueList[lastQuestionIndex].content == '正在思考中...') {
                    that.dialogueList[lastQuestionIndex].content = ''
                  }
                   // 取出一个字符并更新界面
                  let char = that.charQueue2.shift();
                  if (char) {
                      that.thinkText += char;
                  }

                that.dialogueList[lastQuestionIndex].thinkText = marked.parse(that.thinkText) //渲染Markdown格式
                }

                if (that.charQueue.length > 0) {
                    let char = that.charQueue.shift();
                    if (char) {
                        that.answerWithFlow += char;
                    }

                    that.dialogueList[lastQuestionIndex].content = marked.parse(that.answerWithFlow) //渲染Markdown格式

                }


                // 滚动到底部
                that.scrollToSending();

                // 加速逻辑:减少间隔时间
                intervalTime = Math.max(minIntervalTime, intervalTime * acceleration);
                clearInterval(that.typingInterval); // 清除旧的定时器
                that.typingInterval = setInterval(typeCharacter, intervalTime); // 设置新的定时器
            }

            // 启动打字机效果
            that.typingInterval = setInterval(typeCharacter, intervalTime);
         },


         //保持最后一段对话实时出现在视口最下面
         scrollToSending() {
            this.$nextTick(() => {
                if (this.$refs.dialogueBox) {
                this.$refs.dialogueBox.scrollTop =
                    this.$refs.dialogueBox.scrollHeight +
                    this.$refs.dialogueBox.offsetHeight;
                }
            });
          },

3、渲染数据。要保持Markdown的格式输出,不能直接使用花括号{{}}渲染数据,需要结合v-html使用。

以上就能实现逐字渲染的一个AI大模型对话需求

相关文章:

  • WebSocket原理详解(二)
  • 数据分析与应用3------数据清洗
  • 通过 Linux 网络命名空间实现路由器的方案与案例
  • PaddleX上线小目标检测模型产线,支持遥感分析、智能监控、智慧交通等领域高效应用
  • 今日八股——C++
  • 天气预报数据分析管理网站基于Spring Boot SSM原创
  • 青少年编程与数学 02-014 高中数学知识点 05课题、概率与统计
  • Docker的备份与恢复
  • 【PostgreSQL】【第3章】PostgreSQL的对象操作
  • C# ini文件全自动界面配置:打开界面时读ini配置到界面各控件,界面上的控件根据ini文件内容自动生成,点保存时把界面各控件的值写到ini里。
  • 建筑物自动化监测解决方案
  • Docker Api开启TLS认证流程
  • HTTP代理:网页加速的隐形引擎
  • 表单的前端数据流向
  • MATLAB之数据分析图系列:从二维到三维(直接套用)
  • 测试团队UI自动化实施方案
  • 【轻松学C:编程小白的大冒险】— 12.2 瑞士军刀出鞘:switch-case 的多分支江湖
  • 有序数组的归并算法思路
  • 如何实现局域网内无痛访问Jupyter Notebook?
  • Linux中常用服务器监测命令(性能测试监控服务器实用指令)
  • 五粮液董事长:茅台1935已脱离千元价位带,五粮液在千元价位已逐步摆脱其他竞品纠缠
  • 中华人民共和国和俄罗斯联邦关于进一步加强合作维护国际法权威的联合声明
  • 洞天寻隐·学林纪丨玉洞桃源:仇英青绿山水画中的洞天与身体
  • 李公明︱一周书记:浪漫主义为什么……仍然重要?
  • 世界人形机器人运动会将在北京“双奥场馆”举行
  • 央行:增加科技创新和技术改造再贷款额度3000亿元