全网最全vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法)

依然在学前端 2024-08-22 09:03:06 阅读 100

补充日志:(我最开始是使用非流式接口实现的,后续业务改造使用流式接口,重点的代码讲解都在下边哈~~)

关于打印机效果后端同样可以实现的逻辑说明:(2024/4/11)

      打印机效果使用前端的框架实现时,其实颇有局限,而且逻辑有点复杂,可以百度的也不是很多,最好是交给后端通同学来做,方案是后端控制接口请求的请求头content-type为text/event-stream;这是SSE热更新方案,相当于建立了一个长连接,前端再对该event事件流返回的数据进行判断和监听处理;简单理解就是可以实现后端一直给前端同学返回数据,我们再进行拼接即可,这样可以直接实现打印机效果啦,

   2. 补丁上述使用流式接口实现打印机效果讲解:(2024/5/10)

       详见下方代码以及讲解!(2-1)

   3. 补充实退出时对会话进行保存(下次进入可以看到之前的对话):(2024/6/7)

       详见下方代码以及讲解!(3-1)

1.与AI问答机器人对话模型效果展示

1-1:(含)提示词问答对话效果

1-2:停止生成对话效果展示

1-3:打字机延迟效果展示

1-4:整体布局样式效果预览

2-1.重点代码描述与解析(以下为前端对于流式接口的功能实现)

 打印机效果使用了流式接口,其中我遇到的比较糟心的问题是

1.什么是流式?怎么使用,我该选择哪一种?如何实现获取流?

答:

(1)流式就是一次链接 服务器可以一直给你发请求,可以主动中断请求也可等待请求完毕 ,其实就是一个长连接 相当于你打开门后 有人一直在给你递苹果(数量不一定多少)

(2)一般来说 ,使用流式,用的都是厂商的api,开放给我们使用,一般都是post请求,由于原生的eventSource 构造函数 只支持get请求 且不可有请求体,一般对于ai问答需要传参的情况,我们不会使用 eventSource构造函数,那么剩下的就是模拟信息流,有fetch,ajax 等

(3)这里我使用的是fetch ,重要的是获取流数据。并将他二进制解码(对于文本数据),然后进行一些js方法操作后,与项目需要的数据类型匹配后进行自己的业务操作

2-1-1 通用业务情况下的fetch

<code>//注意 最好使用 async await 语法糖 包括后续在拿到请求结果后的异步逻辑也最好做同步处理!

const resp = await fetch(baseUrl,{

method: 'POST',

headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify(你的请求体对象数据)

}

const reader = resp.body.getReader() //读取返回结果的流数据方法,内置 .read()方法

const decoder = new TextDecoder() //解码器

//循环拿到所有数据 done读取完毕

while(true){

//解构读取返回数据,在流式接口中,done 为请求结束,value 是未解码前的二进制数据

const {done ,value} = await reader.read()

if(done){

break;

}

const str = decoder.decode(value) //二进制解码

console.log(str) //每次读取的数据,自行处理

}

//上述只是一个简易的通用场景操作讲解,在实际使用中我们会对解码后的流数据进行js处理和解码等

//下方是我的业务(ai大模型问答)流失数据处理代码

2-1-2 此业务的流式代码处理(含流式返回数据详情以及代码讲解等)

//注意 最好使用 async await 语法糖 包括后续在拿到请求结果后的异步逻辑也最好做同步处理!

// 创建AbortController实例,以便中止请求(比如用于停止生成)

this.controller = new AbortController()

this.ok=true//初始化默认 this.ok为false

const resp = await fetch(baseUrl,{

method: 'POST',

headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify(你的请求体对象数据),

signal: this.controller.signal, //与其相关联的signal属性

}

// 众所周知 fetch .then 写法有两个.then ,在开发使用时我们一般第一个抛出的就是 resp

//第二个抛出的是 在resp.ok 为true 时的响应body

if(resp.ok){

const reader = resp.body.getReader() //读取返回结果的流数据方法,内置 .read()方法

const decoder = new TextDecoder() //解码器

//循环拿到所有数据 done读取完毕

while(this.ok){

//解构读取返回数据,在流式接口中,done 为请求结束,value 是未解码前的二进制数据

const {done ,value} = await reader.read()

if(done){

console.log('已传输完毕')

this.controller.abort() //保险起见执行一下中断接收

//这里执行你 数据响应完全的逻辑

break;

}

const str = decoder.decode(value) //二进制解码

console.log(str) //每次读取的数据,自行处理

//这里执行你获取数据中的逻辑

}

}

//上述只是一个简易的通用场景操作讲解,在实际使用中我们会对解码后的流数据进行js处理和解码等

//下方是我的业务(ai大模

在这里很有必要给大家看一下流数据返回的样子

因为业务不一 对于流式返回渲染的数据的 格式不一 ,所以每个人处理的思路不同

在这里给大家说几个值得注意的点: 一就是要处理 流式转为 json 串 后面如何处理根据你自己的业务要求,另外一点 对于需要模板渲染的   \n 需要设置 style="white-space: pre-wrap;"

给大家看一下我的数据处理 ,我的比较麻烦 还有大家 慎用 JSON.Parse(Json.stringfy()) ,我知道这里有的写的不对(不可处理数组),一切都是为了调试,但是也没报错 就不改了哈哈 ,总之~~~你要根据自己返回的数据格式和 你自己的业务需要 筛选出你要的数据就中

2-2.重点代码描述与解析(以下为前端对于非流式接口的功能实现)

1:实现打印机效果

      在开发过程中,ai机器人的回答,是由接口提供的模板数据,那么就涉及到dom渲染;如果要使用打印机效果,就不可以使用v-html!原因是v-htm会将模板中的“<,/>”打印出来!

所以我们就要换一种方式实现了~ Typed.js插件。

       那什么是Typed.js?简单说它就是专为实现打字机效果而设计j的js插件。Typed.js可以让你的文字一个字一个字地出现,就像是在使用打字机一样。这为网页增添了一种生动而有趣的交互方式,使用户体验更加丰富。而在这里使用刚刚好!

装包:

<code># npm 安装

npm install typed.js

# pnpm 安装

pnpm add typed.js

# yarn 安装

yarn add typed.js

#我这里使用的是npm安装#

使用:

<script>

import Typed from 'typed.js'

</script>

DOM:

tips:

对于动态id的设置,可以以后端大模型接口返回的id作为唯一值哈!

Dom上的操作就这些 ,装包,模板使用设置唯一id,接下来就是js上的编写

javaScript(all-see):

<code>//下方代码在点击“发送”按钮后执行

//然后发送ajax请求~获取接口数据

this.$nextTick(() => {

//注意这个 this.typedInstance 我为什么要单独拿它接收一下呢?停止生成用的,先记住这个变量

this.typedInstance = new Typed(`#typed${res.data.id}`, {

//strings: [md.render(res.data.choices[0].message.content)],

//解析了markDown为html,如果你的项目没有markdown就下面的就行,有的话:

//安装 markdown-it (自行根据你是npm还是yran 百度就行)

//引入 import markdownIt from 'markdown-it'

//全局生成实例对象 var md = new markdownIt() 全局!!与导入包的位置相同,下边写就行

strings: [res.data.choices[0].message.content],//延迟打印字符串

typeSpeed: 10,//延迟时间,单位毫秒,也就是打字速度

backSpeed: 0,//删除速度,为0不删除

showCursor: false, // this is a default,是否显示光标

loop: false,//不自动循环

autoInsertCss: false, //是否自动插入CSS样式表,设置为false以避免与Vue的样式冲突

onComplete: () => {

//打印机效果完成时的钩子函数(onComplete--文本已完全显示)

clearInterval(this.timer)//这个定时器我是不停调用,停止文本超出实时top滚动

...//完成时你需要做啥,写你自己的逻辑就行

},

})

this.scrollTop()//调用了定时器,实时更新dom的scrollTop

})

tips:

下面说一下 停止生成的实现 ~上面咱们不是接收了这个实例了吗,还保存起来了,因为要用

停止生成:

//点击停止生成后的操作回调

this.typedInstance.stop()//你没看错!就这一个方法,没了~~

clearInterval(this.timer)//清除实时滚动dom更新定时器(这个已经出现两次了,相信你对它有印象啦)

//当然,你在停止生成时还有其他什么逻辑,自己push就行

机器人回答时,内容超出,实时滚动显示新打印的内容:

//实时获取当前滚动盒子的滚动值,list 为滚动的盒子

scrollTop() {

this.$nextTick(() => {

this.timer = setInterval(() => {

if (this.$refs.list.scrollHeight) {

this.$refs.list.scrollTop = this.$refs.list.scrollHeight

}

}, 100)

})

},

//定时器清除时机:渲染完成-停止生成,到这,明白了吧~~~

3.后续可能涉及到的业务更新:

1.重新生成回答的内容

2.选择对话dom后,实现分享功能,生成一张特定要求的图片

3-1.退出时的会话保存:

mounted(){

//监听浏览器关闭事件

window.addEventListener('beforeunload', this.onBeforeUnload)

},

beforeDestroy() {

// 移除事件监听器

window.removeEventListener('beforeunload', this.onBeforeUnload)

},

methods:{

onBeforeUnload(){

//此方法中调用后端给的保存会话接口即可

}

}

4.写在最后:

markdown-it | markdown-it 中文文档

上面的是 markdown-it 中文文档链接 ,供参考使用

核心的东西就这些啦,如果有其他想问的,评论区留言即可~

博客写作不易,希望大家多多支持,多多捧场~~~

最后给大家分享一下 typed.js 网址,网上比较多,我觉得这个比较好理解一些

Typed.js - Type your heart out (mattboldt.github.io)

icon-default.png?t=N7T8

https://mattboldt.github.io/typed.js/



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。