用Next.js实现ChatGPT流式返回(打字机效果)

1,954 阅读1分钟

最近对接ChatGPT API的时候,发现信息都是一次性返回,十分不优雅。但是官网中确是逐句给出的答案,用户体验极佳😌

一开始,以为官方使用的是websocket,但其实是我想多了。多方查找资料,发现官网是通过Server-sent events(SSE)实现的。

废话不多说,直接上代码:

服务端实现

项目使用Next.js构建, API目录下创建ask-question/[...params].ts文件,写上如下代码


import type { NextApiRequest, NextApiResponse } from 'next'
import { Readable } from 'stream'
import { openai } from '@/lib/chatgpt'

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  // 下面这四句是关键,感兴趣的小伙伴可以访问下面地址详细了解
  // https://github.com/vercel/next.js/issues/9965
  res.setHeader('Access-Control-Allow-Origin', '*')
  res.setHeader('Content-Type', 'text/event-stream;charset=utf-8')
  res.setHeader('Cache-Control', 'no-cache, no-transform')
  res.setHeader('X-Accel-Buffering', 'no')

  // 这里是拿到客户端传递过来的参数,分别是”模型名称“ 和 ”输入内容“
  const {
    params: [model, prompt]
  } = req.query as { params: string[] }

  try {
    const streamResponse = await openai.createChatCompletion(
      {
        model,
        messages: [
          {
            role: 'user',
            content: prompt
          }
        ],
        temperature: 0.9,
        top_p: 1,
        max_tokens: 1000,
        frequency_penalty: 0,
        presence_penalty: 0,
        // ** ====> 这里是开启流式返回的属性
        stream: true
      },
      {
        timeout: 100000,
        // ** ====> 这里要设置成stream
        responseType: 'stream'
      }
    )

    const stream = streamResponse.data as unknown as Readable
    stream.on('data', (chunk) => {
      res.write(chunk)
    })

    stream.on('end', () => {
      res.end()
    })
  } catch (error: any) {
    if (error.response) {
      console.log(error.response.status)
      console.log(error.response.data)
    } else {
      console.log(error.message)
    }
  }
}


客户端实现


// 在这里通过SSE发送请求
const eventSource = new EventSource(`/api/ask-question/${model}/${input}`)

let answer = ''

eventSource.addEventListener('message',async (event) => {
  if (event.data === '[DONE]') {
    // 信息返回结束
    return eventSource.close()
  }

  const data = JSON.parse(event.data)
  if (data.choices[0].delta.content) {
    // 这里是服务端返回的信息
    answer += data.choices[0].delta.content
  }
})

eventSource.addEventListener('error', (err) => {
  eventSource.close()
})

完整源码地址:github.com/valcosmos/v…

如发现任何错误,小伙伴可在评论区指出,感谢😬