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

默认分类1年前 (2023)发布 admin
1,571 0
ChatGPT国内版

一 前言

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

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

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

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

二 实现打字效果1.预热内容—数据请求与接收

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

介绍一下跨端开发

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

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

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

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){

    }
  }
})

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

2.小程序中接口处理

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

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

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

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

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

3.打字效果实现

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

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

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

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

<view wx:for="{{messageList}}" wx:key="id" id="item-{{item.id}}">
    <message-item 
        data-index="{{index}}" 
        role="{{item.role}}" 
        content="{{item.content}}"
        finished="{{item.finished}}" 
        bind:share="handleMessageShare" 
    />

</view>

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

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

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

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

this.handleRequestResolve(data.text)

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

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({
        thinkingfalse,
        [newMessageList]: {
            id: timestamp,
            role'assistant',
            finishedfalse
        }
    })
    currentContent = ''
    this.showText(0, content_key, finished_key, contentCharArr);
}

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

 showText(key = 0, content_key, finished_key, value) {
     /* 所有内容展示完成 */
    if (key >= value.length) {
        this.setData({
            loadingfalse,
            [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 实现原理

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

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

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

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

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

如上 -view 的类名为 , -view 内部元素的类名为 -view-,接下来可以通过如下代码设置 -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()
                }
            })
        })
    },

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

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

showText(key = 0, content_key, finished_key, value) {
    if (key >= value.length) {
        this.setData({
            loadingfalse,
            [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);
        })
    })
},

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

我们来看一下效果。

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

四 总结

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

号外,号外,号外

移动端小册上新,助力进阶大前端工程师

目前前端日常开发的工作,已经从传统的 web 浏览器端,进入了移动端时代,移动端需求日益增多。

学好移动端开发变的非常重要,这里我写了一本《大前端跨端开发指南》的小册,本小册从小程序到 RN 再到 DSL ,全方位讲解前端跨端技术。

适合的人群如下:

为了感谢大家对我的信任,弄了几个五折码 奉上,先到先得。

© 版权声明
广告也精彩

相关文章

暂无评论

暂无评论...