实现类似chart GPT文字流式输出的效果

文章目录


前言

该效果需要使用到SSE通信方式,对此不太熟悉的小伙伴,可以先看一下我的这篇文章,先大致了解一下SSE的基本用法

实现思路

利用了MVVM架构模式原理,数据双向流,逻辑层中的数据更新会引发视图层dom的重新渲染,首先先通过ref声明一个响应式变量用于获取到用户的历史对话记录,对话框组件遍历的就是这个变量;
因为openAI返回数据的形式是逐个字符的返回(以[start]开始,以[DONE]结束,因此当时是定义了一个中间临时变量来接收数据,通过复合赋值运算符(+=)来实现实时更新上面提到的响应式变量,最后就可以在视图层上看到文字随接口返回数据的频率流式输出的效果,也可以达到历史对话记录实时更新的更新的一个目的。

具体的实现步骤:

  1. 首先获取本地的聊天记录,声明响应式变量testMessage接收,使用之前封装的对话框组件,遍历本地对话记录
  2. 创建一个临时中间变量message,用来实时接收接口返回的数据
  3. 先通过axios调用第一个接口获取到发送会话请求的key值
  4. 拿到key值后携带向chart GPT提出的内容通过SSE通信方式创建通信实例对象(new EventSource),向openAI接口发起请求
  5. 实时监控通信过程,动态更新中间临时变量message,然后在push到用于存储本地会话记录的变量中,这样中间临时变量更新,也会触发视图层对应的对话框重新渲染,从而实现文字流式输出的效果,同时也更新了本地的会话记录。
  6. 当检测到返回的数据为[DONE]时,停止监控,结束通信。
    // 接收流式输出
    let message = ref('')
    sendingPrompt(data).then(res => {

      if (window.EventSource == null) {
        alert('The browser does not support Server-Sent Events');
      } else {
        //请求
        var eventSource = new EventSource(`/api/gpt/send/?key=${res.data}`);

        eventSource.onopen = function () {
          console.log('connection is established');

        };
        eventSource.onerror = function (error) {
          console.log('报错了');
        };
        // 请求成功后先创建一个空的对象
        testMessage.value.push({
          id: timeStamp.value,
          content: "",
          role: 'assistant'
        })
        eventSource.onmessage = function (event) {

          if (event.data !== "[DONE]") {
            message.value += event.data
            testMessage.value[testMessage.value.length - 1].content = message.value.replace(/\\n/g, '\n\n').replace(/(?<=\|)\n\n/g, '\n').replace(/```[\w\n][\S\ \n]+?```/g, function (match) {
              return match.replace(/\n\n/g, '\n')
            })

          }
          retryWord.value = false
          pauseWord.value = true
          if (termination.value) {
            retryWord.value = true
            pauseWord.value = false
            let sessions = JSON.parse(window.localStorage.getItem('sessions'))
            sessions[historySessionIndex].messages = testMessage.value
            window.localStorage.setItem('sessions', JSON.stringify(sessions))
            emits('submit')
            eventSource.close();
          }
          if (event.data === "[DONE]") {
            retryWord.value = true
            pauseWord.value = false
            let sessions = JSON.parse(window.localStorage.getItem('sessions'))
            sessions[historySessionIndex].messages = testMessage.value
            window.localStorage.setItem('sessions', JSON.stringify(sessions))
            emits('submit')
            eventSource.close();
            console.log('connection is closed');
          }
        }
      }
    }).catch(err => {
      ElMessage.error(err.response.data.data)
    })
    textarea.value = ''
    // 通知父组件历史列表重新获取
    emits('submit')
  }