打字机效果是在各个AI模型中都会看到的,数据传输主要是用sse,接口返回的数据是带markDown格式的字段(以下简称md字段),本文章主要将在安卓环境下的实现,仅供参考😀
打字机效果
打字机的实现是参考另一个大佬文章中的,直接使用,大佬的是ts版本,我的是js版本
js
// 打字机队列
export class Typewriter {
queue = []
consuming = false
constructor(onConsume) {
this.onConsume = onConsume
}
// 输出速度动态控制
dynamicSpeed() {
const speed = 2000 / this.queue.length
if (speed > 200) {
return 200
} else {
return speed
}
}
// 添加字符串到队列
add(str) {
if (!str) return
str = str.replaceAll('\\n', '\n');
this.queue.push(...str.split(''));
}
// 消费
consume() {
if (this.queue.length > 0) {
const str = this.queue.shift()
str && this.onConsume(str)
}
}
// 消费下一个
next() {
this.consume()
// 根据队列中字符的数量来设置消耗每一帧的速度,用定时器消耗
this.timmer = setTimeout(() => {
this.consume()
if (this.consuming) {
this.next()
}
}, this.dynamicSpeed())
}
// 开始消费队列
start() {
this.consuming = true
this.next()
}
// 结束消费队列
done() {
this.consuming = false
clearTimeout(this.timmer)
// 把queue中剩下的字符一次性消费
this.onConsume(this.queue.join(""))
this.queue = []
}
}
- 引入使用
js
import { Typewriter } from '@/utils/typeWriter.js';
由于uniapp 无法使用sse,需要使用到插件fetch-event-source加上renderjs
使用renderjs
使用rederjs 可参考uniapp官网 renderjs | uni-app官网 (dcloud.net.cn)
但是其中直接获取逻辑层option的值的写法只有在h5页面 才生效,在APP端用不了,那如何取逻辑层的数据呢?
获取逻辑层的数据
由于使用了renderjs,可以使用document
在逻辑层定义数据绑定在一个元素上(这个元素专门拿来传输数据),用document获取元素,再使用getAttribute去获取定义的数据
js
//自定义数据绑定元素
<view id="transmit" :data-url="typeWriterUrl" :data-uid="curUid" :data-trainId="trainId" ></view>
//获取数据
let typeWriterView = document.getElementById('typeWriterView')
let url = transmit.getAttribute("data-url");
let uid = transmit.getAttribute("data-uid");
let fitnessAssessmentId = transmit.getAttribute("data-trainId");
那逻辑层的数据如何驱动renderjs的数据呢 可以绑定数据在元素上,:isInvoke="isInvoke"
使用:change:isInvoke="typeWriterRender.receiveMsg"
renderjs中使用监听事件监听数据的变化
其中typeWriterRender 对应renderjs中声明的module ,receiveMsg是对应监听事件
js
//元素
<view id="transmit" :data-url="typeWriterUrl" :data-uid="curUid" :data-trainId="trainId" :isInvoke="isInvoke" :change:isInvoke="typeWriterRender.receiveMsg"></view>
//renderjs中 receiveMsg 监听事件
receiveMsg(newValue, oldValue, ownerVm, vm) {}
使用fetch-event-source
- 安装fetch-event-source
js
npm install @microsoft/fetch-event-source
- 引入fetch-event-source
js
import { fetchEventSource } from '@microsoft/fetch-event-source';
- 使用fetch-event-source 进行post请求
使用markdown-it
- 安装markdown-it
js
npm install markdown-it
- 使用
js
import MarkdownIt from 'markdown-it';
//初始化
let md = MarkdownIt({
html: true,
linkify: true,
typographer: true,
});
总体实现
js
//new 一个打字机类的对象 生成html
typeWriterSelf = new Typewriter((str) => {
htmlmd += str;
//获取元素
let typeWriterView = document.getElementById('typeWriterView')
//md字段转成html
let html = md.render(htmlmd);
//直接设置html
typeWriterView.innerHTML = html;
});
//初始化
typeWriterSelf.start();
//监听接口返回的数据 添加到打字类中
const ctrl = new AbortController();
fetchEventSource(url + '/xxx/xxx', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid,
fitnessAssessmentId
}),
signal: ctrl.signal,
onmessage(msg) {
if (msg.event === '') {
var result = msg.data;
//获取数据添加
typeWriterSelf.add(result);
} else if (msg.event === 'close') {
ctrl.abort();
}
},
onerror(err) {
console.log('err=>>>',err);
throw err; //必须throw才能停止
}
});