Personal Blog

轶哥

轶哥的头像

妄图改变世界的全栈程序员。

Article

Vue3对接ChatGPT4接口(打字机效果)

目录
  1. Vue3代码
  2. 渲染Markdown
  3. 调用示例

本篇文章将介绍如何在 Vue3 中实现与 ChatGPT4 API 的对接。具体的后端 API 对接方式,还请参考这篇文章:《对接ChatGPT3.5/4的API实现打字机效果》。

Vue3代码

<script setup lang="ts">
import { ref } from 'vue'
import Text from './Text.vue'
import { Message } from './GPTComponentType'
import { defineComponent } from 'vue'

defineComponent({
  name: 'GPT'
})

const props = defineProps({
  url: {
    type: String,
    required: true
  },
  auth: {
    type: String,
    required: true
  }
})

const output = ref<string>('')

let abortController: AbortController = new AbortController()
let shouldCancelReadChunk: boolean[] = []

const getGPTAnswer = async (messages: Message[]) => {
  abortController.abort() // 取消当前请求
  abortController = new AbortController() // 创建新的 AbortController

  // 将 shouldCancelReadChunk 的值设置为 true
  shouldCancelReadChunk.forEach((_, idx) => {
    shouldCancelReadChunk[idx] = true
  })
  shouldCancelReadChunk.push(false)
  let index = shouldCancelReadChunk.length - 1

  try {
    let result = ''
    output.value = '请求中...'
    const res = await fetch(props.url, {
      method: "POST",
      body: JSON.stringify({
        auth: props.auth,
        messages
      }),
      headers: {
        "Content-Type": "application/json"
      },
      signal: abortController.signal // 绑定 AbortSignal 到请求
    })
    if (res.body == null) {
      return
    }

    const reader = res.body.getReader()
    const decoder = new TextDecoder("utf-8")
    const readChunk = async (): Promise<void> => {
      // 在循环内检查 shouldCancelReadChunk 的值
      if (shouldCancelReadChunk[index]) {
        // 如果需要取消,则提前返回并停止执行 readChunk
        return
      }
      const { value, done } = await reader.read()

      if (!done) {
        const dataString = decoder.decode(value)
        const lines = dataString.trim().split("data:")
        for (const line of lines) {
          // 在循环内检查 shouldCancelReadChunk 的值
          if (shouldCancelReadChunk[index]) {
            // 如果需要取消,则提前返回并停止执行
            return
          }
          if (line != '') {
            const text = line.replace("data:", "").trim()
            try {
              const data = JSON.parse(text)
              if (data.choices[0].delta.content) {
                result += data.choices[0].delta.content
                output.value = result
              }
              if (data.choices[0].finish_reason === 'length') {
                messages.push({
                  role: 'assistant',
                  content: result
                })
                await getGPTAnswer(messages)
                return
              } else if (data.choices[0].finish_reason === 'stop') {
                output.value = result
                return
              }
            } catch (error: any) {
              if (error && error.code && error.code === 20) {
                console.log('请求被用户终止')
                return
              }
              if (text.trim() === '[DONE]') {
                output.value = result
                return
              }
              console.error(error)
              return
            }
          }
        }
        await readChunk() // 如果没有触发结束标识,则继续读取
      } else {
        console.log('done')
      }
    }
    await readChunk()
  } catch (error: any) {
    if (error.name === 'AbortError') {
      console.log('Request was canceled')
    } else {
      console.error(error)
    }
  }
}

defineExpose({
  getGPTAnswer
})
</script>


<template>
  <div class="gpt">
    <Text :text="output" />
  </div>
</template>

<style>
.gpt {
  width: 100%;
}
</style>

GPTComponentType.ts:

import { ComponentPublicInstance } from 'vue'

export type Message = {
  role: 'user' | 'assistant'
  content: string
}

export type GPTComponentType = ComponentPublicInstance<{
  getGPTAnswer: (messages: Message[]) => Promise<void>
}>

实现原理:

  1. 使用 Vue 3 的 Composition API,我们导入了必要的依赖和定义了组件、props 以及响应式变量。
  2. 为了处理多个请求并能够取消正在进行的请求,我们使用了 AbortController。
  3. 在 getGPTAnswer 函数中,通过 POST 请求发送用户消息,并将结果保存在一个可观察的变量 output 中。
  4. 当从服务器接收到数据时,我们将每条消息解析为 JSON 格式,检查是否有输出内容,如果有,则将其添加到 result 变量中。
  5. 如果在解析过程中发生错误,我们会在控制台上显示错误信息。
  6. 为了在请求过程中实现取消功能,我们使用了一个名为 shouldCancelReadChunk 的数组来跟踪每个请求的取消状态。当数组中的值为真时,请求将被取消。

代码处理了重复调用getGPTAnswer方法的情况,当重复调用时会中止上一轮未完成的请求。

渲染Markdown

需要特别注意的是,上述代码并未对Markdown做出渲染,可以在 Text 组件中使用开源库 'markdown-it' 和 'highlight.js' 来实现实时 Markdown 渲染。由于Text.vue的实现相对简单,在此不再赘述。

调用示例

Vue3代码:

<script setup lang="ts">
import { ref } from 'vue'
import GPT from './components/GPT.vue'
import type { GPTComponentType, Message } from './components/GPTComponentType'

// (messages: any[]) => Promise<void>
const gpt = ref<GPTComponentType | null>(null)
const url = 'https://example.com'
const auth = ''
const content = ref('')

const callGPTAnswer = async () => {
  const messages: Message[] = [
    // 您可以根据需要添加您自己的消息列表
    {
      role: 'user',
      content: content.value
    }
  ]
  await gpt.value!.getGPTAnswer(messages)
}
</script>

<template>
  <div class="container">
    <textarea v-model="content" rows="8"></textarea>
    <button @click="callGPTAnswer">获取 GPT 回答</button>
    <GPT ref="gpt" :url="url" :auth="auth" />
  </div>
</template>

通过以上步骤,您可以在 Vue3 项目中实现 ChatGPT4 API 的对接。

打赏
交流区

暂无内容

尚未登陆
发布
  上一篇 (Windows安装rsync命令一键脚本)
下一篇 (强行修改笔记本风扇转速的方法)