前端对接fastGPT流式数据+打字机效果

2024-04-12 07:44

本文主要是介绍前端对接fastGPT流式数据+打字机效果,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

首先在对接api时 参数要设置stream: true,

      const data = {chatId: 'abc',stream: true,//这里true返回流式数据detail: false,variables: {uid: 'sfdsdf',name: 'zhaoyunyao,'},messages: [{ content: text, role: 'user' }]};

不要用axios发请求 不然处理不了流式数据 我这里使用fetch

        const response = await fetch(`${url}`, {method: 'post',headers: headers,body: JSON.stringify(data)});const reader = response.body.getReader();//创建了一个读取器对象,用于从响应主体中读取数据。response.body 是一个 ReadableStream 对象,通过调用 getReader() 方法可以获取一个读取器对象,以便逐步读取响应的内容。// 循环读取响应流while (true) {const { done, value } = await reader.read();if (done) break;// 将ArrayBuffer转为文本const chunk = new TextDecoder('utf-8').decode(value);// 处理文本为json格式const jsonArr = chunk.trim().replace(/\n/g, '').split('data: ').splice(1)for (let index = 0; index < jsonArr.length; index++) {const json = jsonArr[index];try {if (JSON.parse(json).choices) {const text = JSON.parse(json).choices[0].delta.content ?? ''content += text.replace(/^\n/g, '')} else {content = "内部出了问题o(╥﹏╥)o"}} catch {// 处理转json不报错}}obj.content = content //这里的content就是最终输出的文本}

然后我们再加一个打字机的光标 用html+css实现

              <div class="chat-item-details">{{ item.content }}/** 这里的span就是光标 **/<span class="cursor-blink" v-show="item.awaitReply">  </span></div>

再写上对应的css 

              .cursor-blink {display: inline-block;height: 16px;margin-bottom: -3px;width: 2px;animation: blink 1s infinite steps(1, start);}/*这里设置动画blink*/@keyframes blink {0%,100% {background-color: #000;color: #aaa;}50% {background-color: #bbb;/* not #aaa because it's seem there is Google Chrome bug */color: #000;}}

最后呈现的效果

上图呈现的差不多是打字机的效果了 不过呢 但在传输过程中每次停顿后会跳出一串内容然后又停顿一会,阅读体验有些不流畅, 就像玩游戏时帧数低卡顿的感觉, 我们用一个队列让它逐字地展示出来,并且根据传输速度控制输出的速度

  1. 需要一个打字机队列
  2. 队列提供入队和消费功能
  3. 需要一个动态时间来控制文字输出

 

    // 打字机队列// 添加队列addQueue(str,obj) {obj.queue.push(...str.split(''))},// 消费队列consume(obj) {if (obj.queue.length > 0) {let str = obj.queue.shift()str && this.onConsume(str,obj)} else if (obj.isDone) {obj.consuming = falseclearTimeout(obj.timmer)obj.awaitReply = falsethis.scrollBottom()}},// 消费间隔time(obj) {let time = 1000 / obj.queue.lengthreturn time > 100 ? 100 : time},// 消费下一个next(obj) {this.consume(obj)obj.timmer = setTimeout(() => {if (obj.consuming) {this.next(obj)}}, this.time(obj))},start(obj) {obj.consuming = trueobj.isDone=falsethis.next(obj)},done(obj) {obj.isDone=true},onConsume(str,obj) {obj.content += str},

 加了过后的效果

最后附上完整代码

export default {data() {return {key: "xxx",AppId: "xx",text: "",readonly: false,messages: [{ content: "您好,我是小环!请问需要什么帮助呢?", role: 'assistant', awaitReply: false },],userImg: this.$store.getters.avatar,username: this.$store.getters.nickname,awaitReply: false,timmer: null,obj: null,queue: [],consuming: false,isDone: false}},mounted() {const messageTextarea = document.getElementById('messageTextarea');messageTextarea.addEventListener('keydown', (event) => {// 如果按下的是回车键(Enter)if (event.key === 'Enter' && !event.ctrlKey) {event.preventDefault(); // 阻止默认的换行行为// 在这里可以添加发送消息的逻辑this.send();} else if (event.key === 'Enter' && event.ctrlKey) {const cursorPosition = messageTextarea.selectionStart; // 获取光标位置const textBeforeCursor = messageTextarea.value.substring(0, cursorPosition); // 获取光标前的文本const textAfterCursor = messageTextarea.value.substring(cursorPosition); // 获取光标后的文本messageTextarea.value = textBeforeCursor + '\n' + textAfterCursor; // 在光标位置插入换行符messageTextarea.selectionStart = cursorPosition + 1; // 设置光标位置为插入换行符后的位置messageTextarea.selectionEnd = cursorPosition + 1;}});},methods: {// 打字机队列// 添加队列addQueue(str, obj) {obj.queue.push(...str.split(''))},// 消费队列consume(obj) {if (obj.queue.length > 0) {let str = obj.queue.shift()str && this.onConsume(str, obj)} else if (obj.isDone) {obj.consuming = falseclearTimeout(obj.timmer)obj.awaitReply = falsethis.scrollBottom()}},// 消费间隔time(obj) {let time = 500 / obj.queue.lengthreturn time > 50 ? 50 : time},// 消费下一个next(obj) {this.consume(obj)obj.timmer = setTimeout(() => {if (obj.consuming) {this.next(obj)}}, this.time(obj))},start(obj) {obj.consuming = trueobj.isDone = falsethis.next(obj)},done(obj) {obj.isDone = true},onConsume(str, obj) {obj.content += str},async send() {if (this.text === "" || /^\s+$/.test(this.text)) {this.$message.warning('请输入内容')return}const text = this.textthis.text = ""const url = 'https://api.fastgpt.in/api/v1/chat/completions';this.messages.push({ role: 'user', content: text });let obj = { content: "", role: 'assistant', awaitReply: true, queue: [], consuming: false, isDone: false, timmer: null }this.messages.push(obj);this.scrollBottom()const data = {// 这里可以设置请求参数chatId: 'abc',stream: true,detail: false,variables: {uid: 'sfdsdf',name: 'zhaoyunyao,'},messages: [{ content: text, role: 'user' }]};const headers = {// 这里可以设置请求头Authorization: `Bearer ${this.key}`,"Content-Type": "application/json"};try {const response = await fetch(`${url}`, {method: 'post',headers: headers,body: JSON.stringify(data)});const reader = response.body.getReader();//let content = ""// 开始打字机队列this.start(obj)// 循环读取响应流while (true) {const { done, value } = await reader.read();if (done) break;// 将ArrayBuffer转为文本const chunk = new TextDecoder('utf-8').decode(value);// 处理文本为json格式const jsonArr = chunk.trim().replace(/\n/g, '').split('data: ').splice(1)for (let index = 0; index < jsonArr.length; index++) {const json = jsonArr[index];try {if (JSON.parse(json).choices) {const text = JSON.parse(json).choices[0].delta.content ?? ''this.addQueue(text.replace(/^\n/g, ''), obj)} else {this.addQueue('内部出了问题o(╥﹏╥)o', obj)}} catch {// 处理转json不报错}}this.scrollBottom()}} catch (error) {console.error('请求错误:', error);}this.done(obj)},// 滚到最底部scrollBottom() {setTimeout(() => {const mainChat = this.$refs.mainChatmainChat.scrollTop = mainChat.scrollHeight}, 0)},}
}

这篇关于前端对接fastGPT流式数据+打字机效果的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/896523

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter

Vue3视频播放组件 vue3-video-play使用方式

《Vue3视频播放组件vue3-video-play使用方式》vue3-video-play是Vue3的视频播放组件,基于原生video标签开发,支持MP4和HLS流,提供全局/局部引入方式,可监听... 目录一、安装二、全局引入三、局部引入四、基本使用五、事件监听六、播放 HLS 流七、更多功能总结在 v

JS纯前端实现浏览器语音播报、朗读功能的完整代码

《JS纯前端实现浏览器语音播报、朗读功能的完整代码》在现代互联网的发展中,语音技术正逐渐成为改变用户体验的重要一环,下面:本文主要介绍JS纯前端实现浏览器语音播报、朗读功能的相关资料,文中通过代码... 目录一、朗读单条文本:① 语音自选参数,按钮控制语音:② 效果图:二、朗读多条文本:① 语音有默认值:②

vue监听属性watch的用法及使用场景详解

《vue监听属性watch的用法及使用场景详解》watch是vue中常用的监听器,它主要用于侦听数据的变化,在数据发生变化的时候执行一些操作,:本文主要介绍vue监听属性watch的用法及使用场景... 目录1. 监听属性 watch2. 常规用法3. 监听对象和route变化4. 使用场景附Watch 的

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造