代码仓库
前期准备
- 前端项目
- 后端接口(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账号(或者使用其他接口也可以)
接口文档示例
- 参考 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
-
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请求方式(等我忙完补充)等待补充
今天就写到这里啦~
- 小伙伴们,( ̄ω ̄( ̄ω ̄〃 ( ̄ω ̄〃)ゝ我们明天再见啦~~
- 大家要天天开心哦
欢迎大家指出文章需要改正之处~
学无止境,合作共赢