小程序实现 ChatGPT 聊天打字兼自动滚动效果
时间:2023-06-19 10:48:23来源:前端Sharing
一 前言

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 聊天打字兼自动滚动效果

    一前言ChatGPT已经长时间大火,未来将会是AI的天下。人们需要更多地学

  • 全球短讯!双友社区小龙虾烧烤音乐节 6月20日,狂欢“虾”夜,嗨爆北城!

    夏日的傍晚,晚风来去吹香远。以烟火美食为特色,以超燃音乐为引力,打

  • 三人加盟火箭!休城瞄准两大巨星,哈登无缘顶薪,西部该乱了

    三人加盟火箭!休城瞄准两大巨星,哈登无缘顶薪,西部该乱了,休城,火箭

  • ipv4无网络访问权限怎么解决手机_ipv4无网络访问权限怎么解决

    1、IPV4无网络访问权限就进行组策略更新:1 打开 "程序 ",在搜索窗口中

  • 猪全价料怎样拌保健药(猪用全价料配方)

    猪全价料的含义和作用猪全价料是一种以玉米、大豆和鱼粉等为主要原材料

  • 肝疼警告!iGame RTX 4060 Ti Ultra 高帧爽玩《暗黑破坏神 4》

    暴雪的招牌IP《暗黑破坏神》时隔十一年,迎来了第四部续作,相比起前作

  • 深圳第二高级中学在哪里 深圳第二高级中学|天天视点

    今天来聊聊关于深圳第二高级中学在哪里,深圳第二高级中学的文章,现在

  • 恒信东方:接受宝盈基金等机构调研|今热点

    恒信东方(SZ300081,收盘价:10 98元)发布公告称,2023年6月13日-16

  • 【新视野】2023浙江国寿客户节暨“700健行”活动火热开启!

    “没有全民健康,就没有全面小康。”6月16日,为推进落实“健康中国”

  • 美总统参选人称意识到或将被中情局枪杀:正采取措施加以预防|全球今头条

    本文转自【海外网】;据《纽约每日新闻》15日报道,美国总统参选人小罗

  • 美媒列三方交易,拉文湖人联手詹眉,德罗赞辅佐库里 焦点快播

    所以美媒列出了湖人、勇士、公牛之间的一笔三方交易,让公牛获得年轻天

  • 广纳贤才 前瞻布局 中国电信拥抱量子科技新未来

    C114讯6月16日消息近日,中电信量子信息科技集团有限公司(以下简称“

  • 当前观察:无尽剑路西法口诀_无尽剑路西法

    1、你干掉NERO第一关干掉的火牛就有了一杀完火牛BOSS就得到了杀了老牛

  • 换一台电视?海尔618:换一种家庭娱乐新生活

    这个618,各大平台上关于“电视推荐”的攻略内容相当多了:有的说这款

  • 世界观点:开挖人行道未设置警示标志? 市北区敦化路街道快速行动,责令施工单位立即停工整改

    近日,市北区敦化路街道综合行政执法中队迅速查处一起违规道路施工存在

  • 《原神》离垢者肃心旅宴新强敌 重武装兆载永劫龙兽

    《原神》“离垢者肃心旅宴”活动已经于6月14日正式开启,每天都有强敌

  • 旅游
    • 预期收益和风险之间的关系是什么?预期收益是否能作为损失的依据? 环球最资讯

    • 最新快讯!信用卡逾期会被银行起诉吗?信用卡逾期了怎么协商还款?

    • 全球即时看!仙佑集团膏药供应链管理与企业运营 建设文化特色及其传承

    • 天天速递!理财现金分红是什么意思?股票现金分红怎么扣税?