js实现大模型的输出效果

296 阅读3分钟

需求说明

这个需求以后会是常用的,随着大模型的不断推进,大模型的需求开发会越来越多,今天就写一个简单的dom来实现大语言模型的输出效果。

实现思路

如果是纯文本的话,没什么难度,但是现在好多大模型返回的数据经过前端解析过后还带有标签,例如p,ol,li,ui,li,a。等,要处理这种带有标签的输出,使用vue,和react,等框架就无能为力了。我们需要手工操作文档了。

要有loading动画

不同的大模型loading是不一样的。首先要定义你自己的loading动画,注意这个loading需要是inline-block元素。基本思路是当遇到开始标签的时候,我们创建标签,然后将loading动画,从上级别标签删除,插入到新建的标签中,当遇到结束标签的时候,就删除当前loading动画,并且将他插入的上级目录中。

输出字符串

注意随着输出流的不断进来,需要打印的字符串不断向后追加。并且每输出一个字符,要从当前字符串去掉一个字符。当输出字符串为0的时候,将输出状态置为false,检查当前是否正在加载中,如果加载中,等到有数据就继续输出,如果加载完成,输出结束。这里有个最坑的点,后面的输入会影响前面的解析。 (也许是当前项目是这样)所以,需要节流控制。

输出结束

前端页面大部分都是基于vue构建的,所以在输入结束后,使用vue更新dom,将loading输出的内容删除掉。做到用户无感知切换。

实现效果

动画1.gif

动画2.gif

核心代码

			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)
			},

联系方式

以上是核心代码并不能保证可以跑起来,如果需要可以联系我。 1732845345088.png