需求说明
这个需求以后会是常用的,随着大模型的不断推进,大模型的需求开发会越来越多,今天就写一个简单的dom来实现大语言模型的输出效果。
实现思路
如果是纯文本的话,没什么难度,但是现在好多大模型返回的数据经过前端解析过后还带有标签,例如p,ol,li,ui,li,a。等,要处理这种带有标签的输出,使用vue,和react,等框架就无能为力了。我们需要手工操作文档了。
要有loading动画
不同的大模型loading是不一样的。首先要定义你自己的loading动画,注意这个loading需要是inline-block元素。基本思路是当遇到开始标签的时候,我们创建标签,然后将loading动画,从上级别标签删除,插入到新建的标签中,当遇到结束标签的时候,就删除当前loading动画,并且将他插入的上级目录中。
输出字符串
注意随着输出流的不断进来,需要打印的字符串不断向后追加。并且每输出一个字符,要从当前字符串去掉一个字符。当输出字符串为0的时候,将输出状态置为false,检查当前是否正在加载中,如果加载中,等到有数据就继续输出,如果加载完成,输出结束。这里有个最坑的点,后面的输入会影响前面的解析。 (也许是当前项目是这样)所以,需要节流控制。
输出结束
前端页面大部分都是基于vue构建的,所以在输入结束后,使用vue更新dom,将loading输出的内容删除掉。做到用户无感知切换。
实现效果
核心代码
setStartContent(content){
content= content.slice(0,30);
var regex = /ahref="([^"]*)"/;
var match = content.match(regex);
if(match){
let hrefIndex = content.indexOf("ahref=");
this.timeContent = this.getHaveContent(content.slice(0,hrefIndex))
}else{
this.timeContent = this.getHaveContent(content);
}
this.timeHaveContent = this.timeContent;
this.interTime = 150;
this.inputCurrentChart();
},
createDot(outCont){
let d_out = document.createElement('div');
d_out.classList.add('dot-box');
let dot1 = document.createElement('div');
dot1.classList.add('dot', 'dot1');
d_out.appendChild(dot1);
let dot2 = document.createElement('div');
dot2.classList.add('dot', 'dot2');
d_out.appendChild(dot2);
let dot3 = document.createElement('div');
dot3.classList.add('dot', 'dot3');
d_out.appendChild(dot3);
outCont.appendChild(d_out);
return d_out;
},
setChatContent() {
const needContent = this.getNeedContent();
this.timeContent = this.getHaveContent(this.timeContent);
this.timeContent += needContent;
this.timeHaveContent = this.getHaveContent(this.allChatContent);
this.interTime = 80;
if(this.timeContent == needContent ){
this.inputCurrentChart();
}
},
getNeedContent(){
let needContent = this.allChatContent.replace(this.timeHaveContent, '');
if(needContent.length < this.allChatContent.length){
return needContent;
}else{
if(this.timeHaveContent.length == 0){
return needContent;
}else{
this.qudiaoFu = this.getHaveContent(this.timeHaveContent);
if(this.qudiaoFu.length == this.this.timeHaveContent.length){
this.timeHaveContent = this.timeHaveContent.slice(0,-1);
}else{
this.timeHaveContent = this.qudiaoFu;
}
return this.getNeedContent();
}
}
},
getHaveContent(content){
let endTarget = content.match(/<\/[^>]+>$/); //删除结尾的结束标签
let lastT = content.match(/[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+$/); //删除结尾的特殊字符。
if(endTarget){
let d_l = endTarget[0].length;
let a_l = content.length;
return this.getHaveContent(content.slice(0,a_l-d_l));
}else if(lastT){
let d_l = lastT[0].length;
let a_l = content.length;
return this.getHaveContent(content.slice(0,a_l-d_l));
}else{
return content;
}
},
inputCurrentChart() {
this.scrollToBottom();
if(this.timeContent.length == 0){
if(this.chatType == 'Tilng'){
if(this.loading){
return;
}else{
return this.endAnswer();
}
}else{
if(this.Tabtype == 'conclusion'){
this.Tabtype = 'legalAnalysis';
return this.goNextType('legalAnalysis');
}else if(this.Tabtype == 'legalAnalysis'){
this.Tabtype = 'resolution';
return this.goNextType('resolution');
}else if(this.Tabtype == 'resolution'){
this.Tabtype = 'reference';
return this.goNextType('reference');
}else if(this.Tabtype == 'reference'){
return this.endAnswer();
}else{
return this.endAnswer();
}
}
}
let chat = this.timeContent.charAt(0);
if (chat != '<') {
let textNode = document.createTextNode(chat);
this.currentActiveElement.insertBefore(textNode, this.domDots);
this.timeContent = this.timeContent.slice(1);
window.setTimeout(this.inputCurrentChart, this.interTime)
} else {
let chat2 = this.timeContent.charAt(1);
let endTagIdx = this.timeContent.indexOf(">");
this.removeDot();
if (chat2 == '/') {
//说明标签结束
this.timeContent = this.timeContent.slice(endTagIdx + 1);
this.currentActiveElement = this.currentActiveElement.parentElement;
this.addDot(this.currentActiveElement)
} else {
//说明要新建标签
let tagName = this.timeContent.slice(1, endTagIdx);
this.timeContent = this.timeContent.slice(endTagIdx + 1);
if (tagName.charAt(0) == 'a') {
//对于a标签特殊处理
let herfUrl = /href=(["'])(.*?)\1/i.exec(tagName)[2];
this.appendElement(this.currentActiveElement, 'a', herfUrl);
} else {
this.appendElement(this.currentActiveElement, tagName);
}
this.addDot(this.currentActiveElement)
}
window.setTimeout(this.inputCurrentChart, 0)
}
},
appendElement(parent, ele_type, url) {
const addEle = document.createElement(ele_type);
if (ele_type == 'a') {
addEle.href = url;
}
parent.appendChild(addEle);
this.currentActiveElement = addEle;
},
removeDot() {
this.domDots.remove();
},
addDot(parent) {
parent.appendChild(this.domDots)
},
联系方式
以上是核心代码并不能保证可以跑起来,如果需要可以联系我。