ChatGpt类产品在输出答案的时候有一个明显特征,回答问题的文字是一个一个字蹦出来的。这种展示方式基于人工智能答案生成原理而设计。因为大语言模型在生成答案时就是逐字逐句一点点生成的。如果等整篇答案完全生成后再返回给前端,用户等待的时间就太长了。会严重影响用户体验。
根据ChatGpt介绍可以了解到,前端请求并不是使用webSocket,由于WebSocket需要client和server都持续占用一个socket,server侧成本比较高。ChatGPT使用的是一种折中方案: server-sent event(简称SSE).
SSE 模式下,client只需要向server发送一次请求,server就能持续输出,直到需要结束。 SSE仍然使用HTTP作为应用层传输协议,充分利用HTTP的长连接能力,实现服务端推送能力。
从代码层面来看,SSE模式与单次HTTP请求不同的点有:
- client端需要开启 keep-alive,保证连接不会超时。
- HTTP响应的Header包含 Content-Type=text/event-stream,Cache-Cnotallow=no-cache 等。
- HTTP响应的body一般是 "data: ..." 这样的结构。
- HTTP响应里可能有一些空数据,以避免连接超时。
看到这里,我们可以了解到通过这种SSE请求的方式,前端正是逐字得到的答案,所以在展示时就是按请求返回来展示出陆陆续续得到的文本答案。
了解了原理,本篇主要展示在鸿蒙中模拟展示这种逐字文本展示的AI聊天界面。
先看下效果:
本demo中仅实现UI效果,并未真正发送请求来获得数据。通过预置文本和定时器来实现一个字一个字蹦出来的效果。
聊天页面中外层为Stack布局,底部是输入TextInput和发送按钮,上部按顺序展示用户输入和系统回答文本。
正式项目中需要考虑加入加载动画,请求异常处理,代码以及表格图片等展示组件,聊天区域的滚动展示等问题。
非常简单,就直接贴代码了
ts
build() {
Stack({ alignContent: Alignment.Bottom }) {
Column() {
Row() {
Text('你好呀我是聊天助手,有什么话想对我说吗?希望可以和你一起分享生活中的点点滴滴!')
.fontSize(18)
.fontColor('#1a1c1f')
.padding(12)
.backgroundColor('#f4f6f9')
.margin({top: 12})
.clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
}
.width('100%')
.justifyContent(FlexAlign.Start)
if (this.answer.length > 0) {
Row() {
Text(this.question)
.fontSize(18)
.fontColor('#fefbf9')
.padding(12)
.margin({top: 12})
.backgroundColor('#2e74f7')
.clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
}
.width('100%')
.justifyContent(FlexAlign.End)
Row() {
Text(this.answer)
.fontSize(18)
.fontColor('#1a1c1f')
.padding(12)
.backgroundColor('#f4f6f9')
.margin({top: 12})
.clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
}
.width('100%')
.justifyContent(FlexAlign.Start)
}
}
.height('100%')
.width('100%')
.padding(12)
Row() {
TextInput()
.fontSize(18)
.width('80%')
.fontColor('#fefbf9')
.padding(12)
.clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
Blank().width(12)
Text('发送')
.fontSize(18)
.padding(8)
.fontColor('#fefbf9')
.backgroundColor('#2e74f7')
.clip(new Rect({ width: '100%', height: '100%', radius: 12 }))
.onClick(() => {
this.answer = '';
this.pos = 0;
clearInterval(this.intervalID);
setTimeout(() => {
this.intervalID = setInterval(() => {
if (this.pos < this.text.length) {
this.answer = this.answer.concat(this.text.charAt(this.pos))
this.pos += 1;
} else {
clearInterval(this.intervalID);
}
}, 100)
}, 500);
})
}
.width('100%')
.height(80)
.padding({left: 12, right: 12})
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.End)
}
}
通过本例可以熟悉这些知识:基础组件 ,布局组件 ,定时器等常用鸿蒙开发知识。
边学边写,欢迎留言。感谢各位大佬点赞