小程序实现 ChatGPT 聊天打字兼自动滚动效果 天天看点

一 前言

ChatGPT 已经长时间大火,未来将会是AI的天下。人们需要更多地学习和掌握AI,而不是被AI所取代。

目前市面上已经有很多类似 chatGPT 的智能应用,应用有可能是 web h5 应用,也有可能是小程序或者是 Native 应用。随着 ChatGPT 深入,移动端也会再次火爆起来。

在 ChatGPT 的背景下,我们今天来聊聊在小程序中怎么实现类似 chatGPT 的聊天打字效果,并且实现滚动效果,具体如下:


(资料图片)

这篇文章将深入一下内容:

小程序怎么样实现动态打字效果。怎么实现随打字效果滚动。请求分片知识点。scroll-view 细节处理等。二 实现打字效果1.预热内容—数据请求与接收

开发者可以接入 openAi 提供的接口,实现自定义的问答流程。在聊天会话中,我们问 chatGPT 一句话:

介绍一下跨端开发

那么和平常的请求不同的是,数据并不是一次性返回的,而是采用 stream 流式返回的。我们可以在 Network 中看到 response 的大体结构:

如上可以看到返回的 text 是分片处理的,每次会返回一小段内容,只要前端根据返回这一小段内容就可以了,也就自然形成了打字的效果。

可能会有同学好奇,这种分片的数据结构,前端应该怎么接收呢?实际很简单,我们拿 axios 为例子,开发者可以通过监听onDownloadProgress事件来接受服务端返回的文本片段。具体例子如下:

axios({  method: "post",  url: "https:xxx.xxx,  onDownloadProgress: function({ event  }) {    const xhr = event.target    const { responseText } = xhr    /* 获取返回的内容,本质上是 json 字符串 */    let chunk = responseText    try{        /* 序列化返回的内容 */       const data = JSON.parse(chunk)       /* chatGPT 返回的内容 */       console.log(data.text)    }catch(e){    }  }})

这里描述请求的流程,通过 onDownloadProgress 来监听返回的内容,然后获取到返回的内容,JSON.parse解析内容,这里有一个注意事项,就是对于JSON.parse应该加上 try catch ,防止解析的失败。

2.小程序中接口处理

小程序没有如上 axios 里面监听 stream 流式响应数据的能力,也没有处理 onDownloadProgress 的回调函数。简单来说 onDownloadProgress 的实现,本质上是 axios 在浏览器发起 http 请求,会创建一个 XHR 对象,其用于发送请求和接收响应,在创建 XHR 对象后,axios 会注册一个 progress 事件监听器到 XHR 对象上,用于获取下载的进度信息。

那么小程序中如何实现分片流式下载呢?在小程序中,统一收口到 request 中,在 request 中可以用 RequestTask 的 onChunkReceived 来接收服务端的分片数据。这个方法可以监听 Transfer-Encoding Chunk Received 事件。当接收到新的 chunk 时触发。

我们来看看具体怎么使用:

const requestTask = wx.request({     enableChunked:true,  // 开启分片模式    ...})requestTask.onChunkReceived((res)=>{    // 接收分片的数据})

这样就可以通过分片来实现打字的效果。

3.打字效果实现

接下来我们看一下小程序是如何实现打字效果的,先不考虑返回的数据是 stream 流式结构,先认为返回的数据格式是整个文本,那么应该怎么样处理文本呢。

首先我们聊天的内容如下所示:

如上, 每一个消息都是一个 message-item ,所有的 message 保存到了 messageList 列表中,在 wxml 中如下所示:

    

如上,可以看到 message-item 保存了一条会话内容。

当我们发一条信息的时候,产生一条 message-item 。接下来 chatGPT 返回内容后,也会产生一条 message-item ,要实现打字效果就是这条 message-item 。

我们只需要将这条 message-item 的内容,通过 setData 方式分片渲染就可以了。比如我们想打字实现 ‘您好GPT’,那么分五次 setData 渲染就可以了,比如如下:

您您好您好G您好GP您好GPT

如上就是分五次渲染,每一次渲染的结果。接下来就是代码的实现。

this.handleRequestResolve(data.text)

比如 chatGPT 每次返回一条内容,都用 handleRequestResolve 函数处理返回的内容。看一下 handleRequestResolve 的核心实现。

handleRequestResolve(result){    const timestamp = Date.now();    const index = this.data.messageList.length    const newMessageList = `messageList[${index}]`    const contentCharArr = result.trim().split("")    const content_key = `messageList[${index}].content`    const finished_key = `messageList[${index}].finished`    this.setData({        thinking: false,        [newMessageList]: {            id: timestamp,            role: "assistant",            finished: false        }    })    currentContent = ""    this.showText(0, content_key, finished_key, contentCharArr);}

在 handleRequestResolve 中会构建一条新的 message-item ,然后就是showText展示内容,来看一下 showText 怎么处理内容。

showText(key = 0, content_key, finished_key, value) {     /* 所有内容展示完成 */    if (key >= value.length) {        this.setData({            loading: false,            [finished_key]: true        })        wx.vibrateShort()        return;    }    currentContent = currentContent + value[key]    /* 渲染回话内容 */    this.setData({        [content_key]: currentContent,    })    setTimeout(() => {        /* 递归渲染内容 */        this.showText(key + 1, content_key, finished_key, value);    }, 50);},

这样用递归就实现了打字效果。我们来看一下效果:

通过上面可以看到,在文字打印的过程中,列表不能跟随一起滚动,当文字内容超出一屏幕之后,视图就停止了(本质上数据在后面追加),这是一个很不好的效果。

接下来,我们进行优化处理,让视图可以根据内容自动滚动。

三 如何实现视图跟随内容滚动3.1 实现原理

实现视图跟随内容滚动实际很简单,因为 message-item 的容器本质上就是一个 scroll-view , 那么想要 scroll-view 视图跟随返回内容变化,只需要动态设置 scroll-view 的 scroll-top 值就可以了。

视图跟随内容滚动,本质上就是让 scroll-view 一直自动滚动到底部, 如何要让 scroll-view 一直滚动到底部呢?先看一下如下示意图:

如上可以看到,想让 scroll-view 一直滚动到底部,只需要让 scroll-top 等于 scroll-view 内容高度减去 scroll-view 容器本身高度就可以了。

所以需要我们给 scroll-view 里面的内容,用一个 view 包裹如下:

如上 scroll-view 的类名为content, scroll-view 内部元素的类名为scroll-view-content,接下来可以通过如下代码设置 scroll-top 值了。

handleScollTop() {        return new Promise((resolve) => {            const query = wx.createSelectorQuery()            query.select(".content").boundingClientRect()            query.select(".scroll-view-content").boundingClientRect()            query.exec((res) => {                const scrollViewHeight = res[0].height                const scrollContentHeight = res[1].height                if (scrollContentHeight > scrollViewHeight) {                    const scrollTop = scrollContentHeight - scrollViewHeight                    this.setData({                        scrollTop                    }, () => {                        resolve()                    })                }else{                    resolve()                }            })        })    },

如上通过createSelectorQuery分别获取 scroll-view 和 scroll-view 内部元素的高度,两者的差值就是 scroll-top 值。

接下里在渲染会话内容的时候,渲染之后,调用 handleScollTop 来动态设置 scroll-top 就可以了。

showText(key = 0, content_key, finished_key, value) {    if (key >= value.length) {        this.setData({            loading: false,            [finished_key]: true        })        wx.vibrateShort()        return;    }    currentContent = currentContent + value[key]    this.setData({        [content_key]: currentContent,    },()=>{        this.handleScollTop().then(()=>{            setTimeout(() => {                this.showText(key + 1, content_key, finished_key, value);            }, 20);        })    })},

这里有一个小细节,就是在渲染上一次文本内容之后,需要先校验一下 scroll-top 值,然后再次调用 showText 来渲染会话内容。

我们来看一下效果。

后续优化:本质上不需要在每次 showText 之后都通过 createSelectorQuery 异步获取元素 scroll-top 并再次渲染,这无疑是性能的浪费,实际可以控制 createSelectorQuery 到 setData 设置 scroll-top 值的频率来提升性能。

四 总结

感兴趣的同学可以自己实现一个会话打字效果,其中还有很多小细节这里就不讲了。

标签:

最近更新

小程序实现 ChatGPT 聊天打字兼自动滚动效果 天天看点
2023-06-19 10:21:29
高温“暂歇”两日周三回归,北京端午假期将迎“热浪滚滚”-天天微头条
2023-06-19 09:50:18
世界速读:全球富豪移民排行榜出炉,富豪们都喜欢去哪里?
2023-06-19 09:42:19
信用卡无法提额的原因是什么?怎么能让信用卡快速提额度?-全球报资讯
2023-06-19 09:23:02
世界快看点丨龙元建设:公司股票将于6月19日开市起停牌
2023-06-19 09:27:48
环球热门:杰瑞股份(002353.SZ):2022年公司海外业务目前占比约35%
2023-06-19 09:12:24
天气|世界观察
2023-06-19 09:05:15
【速看料】暴雨蓝色预警:四川南部等地部分地区有大到暴雨
2023-06-19 09:10:03
环球通讯!安頔
2023-06-19 01:01:53
江门交通违章查询网站入口电话_江门交通违章查询网站入口_当前报道
2023-06-18 21:40:43
2年6750万美元,湖人自讨苦吃!生涯20年,詹姆斯该明白一个事实
2023-06-18 21:04:40
Mysteel调研:6月上海建材资源计划投放量环比减少14.23%
2023-06-18 20:32:53
春雪食品:目前汇率及海运费对公司是有利的 全球聚看点
2023-06-18 19:43:18
环球观热点:“1元退市”警报拉响,跨界新能源能否挽救昔日千亿房企?
2023-06-18 18:56:13
盐城市盐都区举行端午节主题系列亲子阅读活动
2023-06-18 18:05:39
【全球时快讯】签合同怎样签字才具有法律
2023-06-18 18:11:35
鲜活饮品回应深交所问询,古茗贡献收入下滑,黄国晃面临价格战|当前消息
2023-06-18 17:43:58
哈洽人物丨封昌红 为哈洽会插上创意设计的翅膀-今日热文
2023-06-18 16:59:58
环球热点评!世界贸易组织副总干事张向晨在第三十二届哈洽会开幕式视频致辞
2023-06-18 16:27:53
热血江湖师徒任务流程细则_热血江湖师徒任务流程_全球通讯
2023-06-18 15:48:40
试机号3d今天开机号最近100期_试机号3d今天开机号-世界快播报
2023-06-18 15:10:37
中国首台(套)大孔径磁共振功能创新应用成果在合肥发布
2023-06-18 14:13:28
世界播报:精彩多多!陕西省文化馆曲江馆区,开放
2023-06-18 13:52:06
不同玫瑰花颜色的含义(1一100朵玫瑰花代表什么意思)|世界今亮点
2023-06-18 13:25:59
当前时讯:周琦迎来大喜讯!有望实现两点目标,球迷翘首期待
2023-06-18 12:35:44
“端午水”上线?贵州有持续性降雨,局地暴雨、大暴雨!-播资讯
2023-06-18 11:41:32
深陷多事之秋绝非偶然,电池行业产能过剩,宁德时代躲不开价格战,“小散”警惕
2023-06-18 10:59:07
浦发银行淄博桓台支行积极开展“普及金融知识万里行”宣传活动
2023-06-18 10:21:10
打胎药吃了多久掉下来还要清宫吗_打胎药吃了多久掉下来
2023-06-18 10:03:10
心痛割爱的意思是什么? 心痛如割的意思
2023-06-18 09:13:59