图文并茂手把手教你基于React多种方案使用实现ChatGPT打字机效果

发布时间 2023-11-12 21:59:20作者: 糖~豆豆

代码仓库

前期准备

  • 前端项目
  • 后端接口(OpenAI接口即可)

启动一个新的 React 项目

  • 如果小伙伴们有现有项目,可跳过此步骤直接进入下一步~
  • Next.js 是一个全栈式的 React 框架。它用途广泛,可以让你创建任意规模的 React 应用——可以是静态博客,也可以是复杂的动态应用。要创建一个新的 Next.js 项目,请在你的终端运行:
npx create-next-app@latest

下载依赖


cd xiaojin-react-chatgpt

npm i

运行项目

npm run dev


引入 antd

安装并引入 antd

npm install antd --save

基础页面准备

  • 我们先使用简单的代码来实现效果
  • 修改src\app\page.js代码如下
"use client";
import { useState } from "react";
import { Input, Button } from "antd";

const { TextArea } = Input;
export default function Home() {
  let [outputValue, setOutputValue] = useState("");
  return (
    <main className="flex min-h-screen text-black flex-col items-center justify-between p-24">
      <h2>Chat GPT 打字机效果</h2>
      <TextArea rows={17} value={outputValue} />
      <Button>发送请求</Button>
    </main>
  );
}

页面效果如下

接口准备

  • 注册一个OpenAI账号(或者使用其他接口也可以)

接口文档示例

Chat聊天-聊天完成对象-参数说明

参数 类型 描述
id string 聊天完成的唯一标识符
choices array 聊天完成选项列表。如果n大于1,可以有多个选项
created integer 创建聊天完成的Unix时间戳(秒)
model string 用于聊天完成的模型
system_fingerprint string 该指纹表示模型运行的后端配置
object string 对象类型,总是 chat.completion
usage object 完成请求的使用统计信息
completion_tokens integer 生成的完成中的标记数
prompt_tokens integer 提示中的标记数
total_tokens integer 请求中使用的标记总数(提示 + 完成)

准备接口参数

 const data = {
      model: "XXX",
      messages: [
        {
          role: "user",
          content: "写一篇1000字关于春天的作文",
        },
      ],
      prompt: "写一篇1000字关于春天的作文",
      temperature: 0.75,
      stream: true,
    };

方案1:使用fetch来处理stream流实现打字机效果

使用流的方式处理 Fetch

  • mdn文档

  • Fetch API 允许你跨网络获取资源,它提供了现代化的 API 去替代 XHR。它有一系列的优点,真正好的是,浏览器最近增加了将 fetch 响应作为可读流使用的能力。

  • Request.body 和 Response.body 属性也是这样,它们将主体内容暴露作为一个可读流的 getter。

调用代码示例

    const response = await fetch(url, {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    });

    const reader = response.body.getReader();
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        console.log("***********************done");
        console.log(value);
        break;
      }
      console.log("--------------------value");
      console.log(value);
    }
  • 在函数中,我们使用 response.body.getReader() 将 reader 锁定到该流,然后遵循我们之前看到的相同的模式——使用 reader 读取每个分块,在再次运行 read() 方法之前,检查 done 是否为 true,如果是 true,处理结束,如果是 false,读取下一个分块并且处理它。
  • 通过循环获取传输的数据

编写页面逻辑代码

  • 我们暂时使用固定参数来进行模拟
  • 写一个简单的demo来演示
"use client";
import { useState } from "react";
import { Input, Button } from "antd";
const { TextArea } = Input;
export default function Home() {
  let [outputValue, setOutputValue] = useState("");
  const send = async () => {
    const url = "http://10.169.112.194:7100/v1/chat/completions";
    const data = {
      model: "chatglm2-6b",
      messages: [
        {
          role: "user",
          content: "写一篇1000字关于春天的作文",
        },
      ],
      prompt: "写一篇1000字关于春天的作文",
      temperature: 0.75,
      stream: true,
    };
    const response = await fetch(url, {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    });

    const reader = response.body.getReader();
    while (true) {
      const { done, value } = await reader.read();
      if (done) {
        console.log("***********************done");
        console.log(value);
        break;
      }
      console.log("--------------------value");
      console.log(value);
    }
  };
  return (
    <main className="flex min-h-screen text-black flex-col items-center justify-between p-24">
      <h2>Chat GPT 打字机效果</h2>
      <TextArea rows={17} value={outputValue} />
      <Button onClick={send}>发送请求</Button>
    </main>
  );
}

点击按钮查看打印结果

  • 我们可以看到打印出来的都是buffer字符串,我们需要对其进行解析才可以得知最终结果

解析buffer

const encode = new TextDecoder("utf-8");
const reader = response.body.getReader();
 while (true) {
      const { done, value } = await reader.read();
      const text = encode.decode(value);
      if (done) {
        console.log("***********************done");
        console.log(text);
        break;
      }
      console.log("--------------------value");
      console.log(text);
    }

查看解析

我们可以看到解析结果格式如下

data: {"id": "chatcmpl-3zmRJUd4TTpm9xP9NbQVHw", "model": "chatglm2-6b", "choices": [{"index": 0, "delta": {"content": "希望"}, "finish_reason": null}]}

使用正则解析数据

我们编写一个函数~~然后打印数据

 const getReaderText = (str) => {
    // 定义正则表达式匹配模式
    let result = str.match(/{"content": "(\S*)"}, "finish_reason":/);
    return result && result[1] ? result[1] : "";
  };

赋值数据到文本框


解决换行问题

解决textarea显示\n但是并没有换行的问题,经过排查发现,是接口返回的数据是\n,所以渲染有异常,因此我们可以用下面的方式去解决.

replaceAll(/\\n/g,'\r\n')

初步实现简易打字机效果

代码块支持(待补充)

自动滚动(等我忙完补充)

方案2:axios请求方式(等我忙完补充)等待补充

今天就写到这里啦~

  • 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
  • 大家要天天开心哦

欢迎大家指出文章需要改正之处~
学无止境,合作共赢

在这里插入图片描述

欢迎路过的小哥哥小姐姐们提出更好的意见哇~~