自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

小程序實現(xiàn) ChatGPT 聊天打字兼自動滾動效果

人工智能
本質上不需要在每次 showText 之后都通過 createSelectorQuery 異步獲取元素 scroll-top 并再次渲染,這無疑是性能的浪費,實際可以控制 createSelectorQuery 到 setData 設置 scroll-top 值的頻率來提升性能。

一 前言

ChatGPT 已經長時間大火,未來將會是AI的天下。人們需要更多地學習和掌握AI,而不是被AI所取代。

目前市面上已經有很多類似 chatGPT 的智能應用,應用有可能是 web h5 應用,也有可能是小程序或者是 Native 應用。隨著 ChatGPT 深入,移動端也會再次火爆起來。

在 ChatGPT 的背景下,我們今天來聊聊在小程序中怎么實現(xiàn)類似 chatGPT 的聊天打字效果,并且實現(xiàn)滾動效果,具體如下:

圖片

這篇文章將深入一下內容:

  • 小程序怎么樣實現(xiàn)動態(tài)打字效果。
  • 怎么實現(xiàn)隨打字效果滾動。
  • 請求分片知識點。
  • scroll-view 細節(jié)處理等。

二 實現(xiàn)打字效果

1.預熱內容—數(shù)據(jù)請求與接收

開發(fā)者可以接入 openAi 提供的接口,實現(xiàn)自定義的問答流程。在聊天會話中,我們問 chatGPT 一句話:

介紹一下跨端開發(fā)

那么和平常的請求不同的是,數(shù)據(jù)并不是一次性返回的,而是采用 stream 流式返回的。我們可以在 Network 中看到 response 的大體結構:

圖片

如上可以看到返回的 text 是分片處理的,每次會返回一小段內容,只要前端根據(jù)返回這一小段內容就可以了,也就自然形成了打字的效果。

可能會有同學好奇,這種分片的數(shù)據(jù)結構,前端應該怎么接收呢?實際很簡單,我們拿 axios 為例子,開發(fā)者可以通過監(jiān)聽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 來監(jiān)聽返回的內容,然后獲取到返回的內容,JSON.parse 解析內容,這里有一個注意事項,就是對于 JSON.parse 應該加上 try catch ,防止解析的失敗。

2.小程序中接口處理

小程序沒有如上 axios 里面監(jiān)聽 stream 流式響應數(shù)據(jù)的能力,也沒有處理 onDownloadProgress 的回調函數(shù)。簡單來說 onDownloadProgress 的實現(xiàn),本質上是 axios 在瀏覽器發(fā)起 http 請求,會創(chuàng)建一個 XHR 對象,其用于發(fā)送請求和接收響應,在創(chuàng)建 XHR 對象后,axios 會注冊一個 progress 事件監(jiān)聽器到 XHR 對象上,用于獲取下載的進度信息。

那么小程序中如何實現(xiàn)分片流式下載呢?在小程序中,統(tǒng)一收口到 request 中,在 request 中可以用 RequestTask 的 onChunkReceived 來接收服務端的分片數(shù)據(jù)。這個方法可以監(jiān)聽 Transfer-Encoding Chunk Received 事件。當接收到新的 chunk 時觸發(fā)。

我們來看看具體怎么使用:

const requestTask = wx.request({ 
    enableChunked:true,  // 開啟分片模式
    ...
})
requestTask.onChunkReceived((res)=>{
    // 接收分片的數(shù)據(jù)
})

這樣就可以通過分片來實現(xiàn)打字的效果。

3.打字效果實現(xiàn)

接下來我們看一下小程序是如何實現(xiàn)打字效果的,先不考慮返回的數(shù)據(jù)是 stream 流式結構,先認為返回的數(shù)據(jù)格式是整個文本,那么應該怎么樣處理文本呢。

首先我們聊天的內容如下所示:

圖片

如上, 每一個消息都是一個 message-item ,所有的 message 保存到了 messageList 列表中,在 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>

如上,可以看到 message-item 保存了一條會話內容。

當我們發(fā)一條信息的時候,產生一條 message-item 。接下來 chatGPT 返回內容后,也會產生一條 message-item ,要實現(xiàn)打字效果就是這條 message-item 。

我們只需要將這條 message-item 的內容,通過 setData 方式分片渲染就可以了。比如我們想打字實現(xiàn) ‘您好GPT’,那么分五次 setData 渲染就可以了,比如如下:

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

如上就是分五次渲染,每一次渲染的結果。接下來就是代碼的實現(xiàn)。

this.handleRequestResolve(data.text)

比如 chatGPT 每次返回一條內容,都用 handleRequestResolve 函數(shù)處理返回的內容。看一下 handleRequestResolve 的核心實現(xiàn)。

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

這樣用遞歸就實現(xiàn)了打字效果。我們來看一下效果:

圖片

通過上面可以看到,在文字打印的過程中,列表不能跟隨一起滾動,當文字內容超出一屏幕之后,視圖就停止了(本質上數(shù)據(jù)在后面追加),這是一個很不好的效果。

接下來,我們進行優(yōu)化處理,讓視圖可以根據(jù)內容自動滾動。

三 如何實現(xiàn)視圖跟隨內容滾動

3.1 實現(xiàn)原理

實現(xiàn)視圖跟隨內容滾動實際很簡單,因為 message-item 的容器本質上就是一個 scroll-view , 那么想要 scroll-view 視圖跟隨返回內容變化,只需要動態(tài)設置 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 來動態(tài)設置 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);
        })
    })
},

這里有一個小細節(jié),就是在渲染上一次文本內容之后,需要先校驗一下 scroll-top 值,然后再次調用 showText 來渲染會話內容。

我們來看一下效果。

圖片

后續(xù)優(yōu)化: 本質上不需要在每次 showText 之后都通過 createSelectorQuery 異步獲取元素 scroll-top 并再次渲染,這無疑是性能的浪費,實際可以控制 createSelectorQuery 到 setData 設置 scroll-top 值的頻率來提升性能。

四 總結

感興趣的同學可以自己實現(xiàn)一個會話打字效果,其中還有很多小細節(jié)這里就不講了。

責任編輯:武曉燕 來源: 前端Sharing
相關推薦

2023-03-28 08:05:37

2022-07-26 08:02:33

Android微信程序

2023-11-22 07:47:34

2024-05-28 09:21:25

2011-09-01 13:17:46

JQuery滾動

2011-06-27 13:57:42

JavaScript

2022-06-15 22:15:47

CSS視覺還原

2022-06-23 06:42:06

CSSJS 監(jiān)聽

2022-07-05 08:26:10

Python報表自動化郵箱

2023-03-03 00:08:36

微軟機器人聊天

2024-04-15 09:22:48

CSS鎖定overflow

2017-01-09 10:42:56

微信小程序

2011-09-15 10:05:09

Android應用iTriage健康顧問

2013-11-11 09:26:50

編程思考

2020-12-09 07:54:17

Vue插件鼠標

2023-10-12 07:40:54

Minium自動化框架

2011-09-02 10:14:10

JQuery滾動Xslider

2024-04-03 15:20:08

2023-04-01 10:16:57

ChatGPT-4程序員失業(yè)

2021-08-31 20:07:48

微信騰訊移動應用
點贊
收藏

51CTO技術棧公眾號