防水网站建设百度seo优化推广公司
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-1that.dialogueList[lastQuestionIndex].content = that.dialogueList[lastQuestionIndex].content + '出错了,暂时无法回答您的问题,请稍后再试。'});}// 开始读取流readStream();})},// 启动打字机效果startTyping() {const that = this;that.isTyping = true;const lastQuestionIndex = that.dialogueList.length-1// 初始间隔时间let intervalTime = 30; // 初始速度为 30msconst 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大模型对话需求