如何用OpenAI、ChatGPT、Node.js和React搭建一个AI聊天机器人

如何用OpenAI、ChatGPT、Node.js和React搭建一个AI聊天机器人

人工智能(AI)最近一直在掀起波澜,ChatGPTChat completions彻底改变了互联网。

你可以用它做很多事情:起草电子邮件或其他文章,回答关于一组文件的问题,创建对话代理,给你的软件一个自然语言界面,辅导各种科目,翻译语言,等等。本教程使用Chat completions功能建立一个AI聊天应用程序的基本知识,使每个程序员都能轻松上手。它并不像看起来那样艰难。在你跟随本教程时,你会看到这一点。

您将学到以下内容:

  • 如何只用Node.js创建一个CLI聊天应用程序。
  • 如何只用React建立一个聊天应用。
  • 如何结合React和Node.js来创建更好的聊天AI软件。

本教程将以 gpt-3.5-turbo 模型为基础。

前提条件

本教程需要JavaScript、CSS、React和Node.js的基本知识。你还需要一个OpenAI平台的账户,chatGPT就在这个平台上。它是免费的,所以你可以在这里创建一个。

如何用Node.js创建一个CLI聊天AI应用程序

本节将重点介绍创建一个只在终端使用Node.js运行的聊天应用程序。

首先,为该项目创建一个目录:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir nodejs-chatgpt-tutorial
mkdir nodejs-chatgpt-tutorial
mkdir nodejs-chatgpt-tutorial

导航到该文件夹:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cd nodejs-chatgpt-tutorial
cd nodejs-chatgpt-tutorial
cd nodejs-chatgpt-tutorial

初始化该项目:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm init -y
npm init -y
npm init -y

这将创建一个 package.json 文件来跟踪项目的细节

在该文件中添加以下一行代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"type": "module"
"type": "module"
"type": "module"

这将使你能够使用ES6模块的导入语句。用以下命令安装OpenAI

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm i openai
npm i openai
npm i openai

创建一个文件,所有的代码都在其中。命名为 index.js

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
touch index.js
touch index.js
touch index.js

OpenAI模块导入 ConfigurationOpenAIApi ,从 readline 模块导入readline

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Configuration, OpenAIApi } from "openai";
import readline from "readline";
import { Configuration, OpenAIApi } from "openai"; import readline from "readline";
import { Configuration, OpenAIApi } from "openai";
import readline from "readline";

像这样建立OpenAI的配置:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});
const configuration = new Configuration({ organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw", apiKey: "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", });
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});

这段代码创建了一个 Configuration 对象的新实例。在它里面,你将输入你的 organizationapiKey 的值。你可以在设置中找到你的组织的详细信息,在API密钥中找到你的apiKey信息。如果你没有现有的API Key,你可以创建它。在配置后输入以下代码,创建一个新的OpenAI API实例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const openai = new OpenAIApi(configuration);
const openai = new OpenAIApi(configuration);
const openai = new OpenAIApi(configuration);

你将在整个项目中使用它。

输入下面的代码来测试 createChatCompletion 函数:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "Hello" }],
})
.then((res) => {
console.log(res.data.choices[0].message.content);
})
.catch((e) => {
console.log(e);
});
openai .createChatCompletion({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: "Hello" }], }) .then((res) => { console.log(res.data.choices[0].message.content); }) .catch((e) => { console.log(e); });
openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: "Hello" }],
})
.then((res) => {
console.log(res.data.choices[0].message.content);
})
.catch((e) => {
console.log(e);
});

这段代码调用 createChatCompletion 函数,触发一个端点( https://api.openai.com/v1/chat/completions )。该函数接受一个参数对象(使用中的chatGPT model 和用户与AI之间的 messages 数组。我们将在下一节中研究如何使用 messages 数组来保存聊天历史并改进应用程序)。每个消息都是一个对象,包含 role(即谁发送了该消息。如果是来自人工智能,该值可以是助理,如果是来自人类的消息,该值可以是用户)和 content(发送的信息)。最后,代码打印了来自人工智能的响应( res.data.choice[0].message.content )。用这个命令在终端运行该文件:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
node index
node index
node index

这将在几秒钟后返回人工智能的响应。这就是创建聊天机器人的全部内容!但通过请求用户输入信息而不是将信息内容硬编码到代码中,使应用程序更具互动性将是很有帮助的。readline模块将在这方面帮助我们。要使其具有互动性,请删除你最后输入的代码,并添加以下内容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const userInterface = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const userInterface = readline.createInterface({ input: process.stdin, output: process.stdout, });
const userInterface = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

这段代码在终端创建了一个用户界面,允许用户输入他们的问题。

接下来,用下面的代码提示用户输入一个信息:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
userInterface.prompt();
userInterface.prompt();
userInterface.prompt();

最后,输入以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
userInterface.on("line", async (input) => {
await openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: input }],
})
.then((res) => {
console.log(res.data.choices[0].message.content);
userInterface.prompt();
})
.catch((e) => {
console.log(e);
});
});
userInterface.on("line", async (input) => { await openai .createChatCompletion({ model: "gpt-3.5-turbo", messages: [{ role: "user", content: input }], }) .then((res) => { console.log(res.data.choices[0].message.content); userInterface.prompt(); }) .catch((e) => { console.log(e); }); });
userInterface.on("line", async (input) => {
await openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: input }],
})
.then((res) => {
console.log(res.data.choices[0].message.content);
userInterface.prompt();
})
.catch((e) => {
console.log(e);
});
});

在上面的代码中

  • 当用户输入东西并点击 Enter 时,上面的代码会触发一个回调函数。
  • 它将用户输入的任何内容作为 input
  •  input 的内容现在被用作 content
  • 在显示人工智能的响应后,在 then 块中提示用户输入另一条信息。

查看GitHub上的所有代码。运行该文件并与人工智能进行对话。它将看起来像下面的图片:

与AI的CLI聊天

与AI的CLI聊天

很好! 这是一个交互式CLI聊天。这对少数人(如工程师)很有用,但它有很好的安全性,因为它是在服务器端。但其他可能不了解如何使用CLI应用程序的人呢?他们将需要一些更容易使用的、具有更好的用户界面(UI)和用户体验(UX)的东西。下一节将重点介绍使用React构建这种应用程序。

如何使用React创建一个聊天应用程序

本节旨在帮助前端开发者快速掌握ChatGPT API,以创建一个聊天应用程序,并构建一个更好的用户界面,给用户带来更好的体验。你可以把在这里获得的知识应用于其他前端框架或库。

首先要做的是设置一个基本的React模板。我将使用Vite来实现这一目的。你可以用Vite来搭建任何现代JavaScript前端项目的脚手架。使用下面的命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm create vite@latest
npm create vite@latest
npm create vite@latest

该命令将提示你为你的项目创建一个名称和文件夹,并选择一个框架或库(本教程使用React)。之后,你将导航到该文件夹,并运行以下命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install
npm run dev
npm install npm run dev
npm install
npm run dev

这些命令将安装必要的依赖性,并在 5173 端口启动本地服务器,接下来,用以下命令安装OpenAI

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm i openai
npm i openai
npm i openai

这个模块提供了我们创建聊天应用程序所需的所有权限。现在我们准备开始写代码了!导航到 src/App.jsx 文件,删除其所有内容。然后添加以下导入语句:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useState } from "react";
import { Configuration, OpenAIApi } from "openai";
import { useState } from "react"; import { Configuration, OpenAIApi } from "openai";
import { useState } from "react";
import { Configuration, OpenAIApi } from "openai";

上面的代码导入了用于设置配置值的 Configuration 和用于让我们访问Chat completions的 OpenAIApi 。之后,像这样建立配置:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});
const configuration = new Configuration({ organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw", apiKey: "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", });
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});

这段代码创建了一个 Configuration 对象的新实例。在它里面,你输入你的 organization 和 apiKey 的值。你可以在设置中找到你的组织的详细信息,你的apiKey信息在API密钥中。如果你没有现有的API密钥,你可以创建它。在配置后输入以下代码,创建一个新的OpenAI API实例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const openai = new OpenAIApi(configuration);
const openai = new OpenAIApi(configuration);
const openai = new OpenAIApi(configuration);

我们将在整个项目中使用它。创建并导出一个默认函数:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function App() {
return (
<main>
<h1>Chat AI Tutorial</h1>
<main/>
);
}
export default App;
function App() { return ( <main> <h1>Chat AI Tutorial</h1> <main/> ); } export default App;
function App() {
return (
<main>
<h1>Chat AI Tutorial</h1>
<main/>
);
}
export default App;

这个函数将容纳其余的代码。在 return 语句之前设置以下状态:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const [message, setMessage] = useState("");
const [chats, setChats] = useState([]);
const [isTyping, setIsTyping] = useState(false);
const [message, setMessage] = useState(""); const [chats, setChats] = useState([]); const [isTyping, setIsTyping] = useState(false);
  const [message, setMessage] = useState("");
const [chats, setChats] = useState([]);
const [isTyping, setIsTyping] = useState(false);
  • message 将保存从应用程序发送至人工智能的信息。
  • chats 数组将记录双方(用户和人工智能)发送的所有信息。
  • isTyping 变量将通知用户,机器人是否正在打字。

在h1标签下输入以下几行代码

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<div className={isTyping ? "" : "hide"}>
<p>
<i>{isTyping ? "Typing" : ""}</i>
</p>
</div>
<div className={isTyping ? "" : "hide"}> <p> <i>{isTyping ? "Typing" : ""}</i> </p> </div>
      <div className={isTyping ? "" : "hide"}>
<p>
<i>{isTyping ? "Typing" : ""}</i>
</p>
</div>

上面的代码将显示 Typing ,只要用户在等待AI的响应。创建一个表单,用户可以在其中输入信息,将下面的代码添加到 main 元素中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<form action="" onSubmit={(e) => chat(e, message)}>
<input
type="text"
name="message"
value={message}
placeholder="Type a message here and hit Enter..."
onChange={(e) => setMessage(e.target.value)}
/>
</form>
<form action="" onSubmit={(e) => chat(e, message)}> <input type="text" name="message" value={message} placeholder="Type a message here and hit Enter..." onChange={(e) => setMessage(e.target.value)} /> </form>
      <form action="" onSubmit={(e) => chat(e, message)}>
<input
type="text"
name="message"
value={message}
placeholder="Type a message here and hit Enter..."
onChange={(e) => setMessage(e.target.value)}
/>
</form>

这段代码创建了一个有一个输入的表单。每当点击 Enter 键提交表单时,就会触发 chat 函数。聊天函数将接受两(2)个参数(emessage),像这样:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const chat = async (e, message) => {
}
const chat = async (e, message) => { }
const chat = async (e, message) => {
}

在该函数中输入以下几行:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
e.preventDefault();
if (!message) return;
setIsTyping(true);
e.preventDefault(); if (!message) return; setIsTyping(true);
    e.preventDefault();
if (!message) return;
setIsTyping(true);

上面的代码防止 form 重新加载网页,检查提交前是否输入了信息,并将 isTyping 设置为 true ,以表明应用程序已经开始处理所提供的输入。ChatGPT有一个信息的格式。它采取以下模式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{role: user | assistant, content: message to be sent
{role: user | assistant, content: message to be sent
{role: user | assistant, content: message to be sent

每条信息(content)都必须显示谁发送的。当聊天是来自人工智能时,角色是 assistant,但如果是来自人类,则是 user 。因此,在发送消息之前,一定要正确地格式化它,并像这样把它添加到数组(chats)中:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
let msgs = chats;
msgs.push({ role: "user", content: message });
setChats(msgs);
setMessage("");
let msgs = chats; msgs.push({ role: "user", content: message }); setChats(msgs); setMessage("");
    let msgs = chats;
msgs.push({ role: "user", content: message });
setChats(msgs);
setMessage("");

上面的最后一行清除了输入,以便用户输入另一个音符。现在我们将通过使用下面的代码触发 createChatCompletion 函数来调用 createChatCompletion 端点:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
await openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content:
"You are a EbereGPT. You can help with graphic design tasks",
},
...chats,
],
})
await openai .createChatCompletion({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a EbereGPT. You can help with graphic design tasks", }, ...chats, ], })
  await openai
.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content:
"You are a EbereGPT. You can help with graphic design tasks",
},
...chats,
],
})

createChatCompletion 函数至少需要两(2)个参数(model 和 messages):

  • 模型指定了正在使用的chatGPT的版本。
  • 消息是迄今为止用户和人工智能之间的所有消息的列表,以及一个系统消息,让人工智能了解它能提供什么样的帮助。
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
{
role: "system",
content:
"You are a EbereGPT. You can help with graphic design tasks",
}
{ role: "system", content: "You are a EbereGPT. You can help with graphic design tasks", }
          {
role: "system",
content:
"You are a EbereGPT. You can help with graphic design tasks",
}

你可以把内容改成任何适合你的东西。messages 不一定要在数组中包含一个以上的对象。它可以只是一条消息。但是当它是一个数组时,它提供了一个消息历史,人工智能可以依靠它在未来给出更好的回复,而且它使用户打字更少,因为可能没有必要一直过度描述。 createChatCompletion 函数返回一个承诺。所以使用 then...catch... 块来获取响应。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
.then((res) => {
msgs.push(res.data.choices[0].message);
setChats(msgs);
setIsTyping(false);
})
.catch((error) => {
console.log(error);
});
.then((res) => { msgs.push(res.data.choices[0].message); setChats(msgs); setIsTyping(false); }) .catch((error) => { console.log(error); });
      .then((res) => {
msgs.push(res.data.choices[0].message);
setChats(msgs);
setIsTyping(false);
})
.catch((error) => {
console.log(error);
});

这段代码将从人工智能返回的消息添加到聊天数组中,并将 isTyping 设置为 false ,表示人工智能已经完成了回复。你现在应该在每次发送消息时收到反馈(Typing):

聊天应用程序在人工智能即将作出反应时给予反馈

聊天应用程序在人工智能即将作出反应时给予反馈

现在是显示聊天历史给用户看的时候了。在 h1 标签下面输入以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<section>
{chats && chats.length
? chats.map((chat, index) => (
<p key={index} className={chat.role === "user" ? "user_msg" : ""}>
<span>
<b>{chat.role.toUpperCase()}</b>
</span>
<span>:</span>
<span>{chat.content}</span>
</p>
))
: ""}
</section>
<section> {chats && chats.length ? chats.map((chat, index) => ( <p key={index} className={chat.role === "user" ? "user_msg" : ""}> <span> <b>{chat.role.toUpperCase()}</b> </span> <span>:</span> <span>{chat.content}</span> </p> )) : ""} </section>
      <section>
{chats && chats.length
? chats.map((chat, index) => (
<p key={index} className={chat.role === "user" ? "user_msg" : ""}>
<span>
<b>{chat.role.toUpperCase()}</b>
</span>
<span>:</span>
<span>{chat.content}</span>
</p>
))
: ""}
</section>

上面的代码循环浏览 chats ,并将它们一个接一个地显示给用户。它把 role 的大写字母和消息的 content 并排输出。以下是输出结果的样子:

聊天机器人在没有CSS的情况下按预期工作

聊天机器人在没有CSS的情况下按预期工作

这看起来很酷!但添加一些造型会让它看起来像WhatsAppMessenger一样吸引人。用以下内容替换 src/index.css 文件的内容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
text-align: center;
position: sticky;
top: 0;
background-color: #242424;
}
main{
max-width: 500px;
margin: auto;
}
p{
background-color: darkslategray;
max-width: 70%;
padding: 15px;
border-radius: 50px;
}
p span{
margin: 5px;
}
p span:first-child{
margin-right: 0;
}
.user_msg{
text-align: right;
margin-left: 30%;
display: flex;
flex-direction: row-reverse;
}
.hide {
visibility: hidden;
display: none;
}
form{
text-align: center;
position: sticky;
bottom: 0;
}
input{
width: 100%;
height: 40px;
border: none;
padding: 10px;
font-size: 1.2rem;
}
input:focus{
outline: none;
}
:root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } h1 { font-size: 3.2em; line-height: 1.1; text-align: center; position: sticky; top: 0; background-color: #242424; } main{ max-width: 500px; margin: auto; } p{ background-color: darkslategray; max-width: 70%; padding: 15px; border-radius: 50px; } p span{ margin: 5px; } p span:first-child{ margin-right: 0; } .user_msg{ text-align: right; margin-left: 30%; display: flex; flex-direction: row-reverse; } .hide { visibility: hidden; display: none; } form{ text-align: center; position: sticky; bottom: 0; } input{ width: 100%; height: 40px; border: none; padding: 10px; font-size: 1.2rem; } input:focus{ outline: none; }
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
text-align: center;
position: sticky;
top: 0;
background-color: #242424;
}
main{
max-width: 500px;
margin: auto;
}
p{
background-color: darkslategray;
max-width: 70%;
padding: 15px;
border-radius: 50px;
}
p span{
margin: 5px;
}
p span:first-child{
margin-right: 0;
}
.user_msg{
text-align: right;
margin-left: 30%;
display: flex;
flex-direction: row-reverse;
}
.hide {
visibility: hidden;
display: none;
}
form{
text-align: center;
position: sticky;
bottom: 0;
}
input{
width: 100%;
height: 40px;
border: none;
padding: 10px;
font-size: 1.2rem;
}
input:focus{
outline: none;
}

并删除 src/App.css 文件中的所有样式。

你可以在GitHub上找到完整的代码。现在应用程序应该有一个新的外观:

聊天机器人如期使用CSS工作

聊天机器人如期使用CSS工作

用React和ChatGPT创建一个聊天机器人的工作就这样结束了。它并不像听起来那么困难。但像这样的前端应用最好是用于演示,而不是生产。这样创建应用程序的问题是,前端将API密钥暴露给网络攻击。

要解决这个问题,明智的做法可能是将API Key和Organisation Id保存在云端某个安全的地方并引用它,或者为你的应用程序建立一个具有更好安全性的后端。下面的部分将致力于解决这个问题。

如何结合React和Node.js来制作一个全栈式的聊天AI软件

本节现在将加入前几节的力量,建立一个更安全的应用程序,同时表现出更好的用户界面和用户体验。

我们将改进Node部分,使用服务器来暴露一个端点供前端使用,并简化前端与后台的交互,而不是直接联系OpenAI

如何设置项目

这一部分将创建项目所需的文件夹和文件。创建项目目录:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir react-node-chatgpt-tutorial
mkdir react-node-chatgpt-tutorial
mkdir react-node-chatgpt-tutorial

导航到该文件夹:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
cd react-node-chatgpt-tutorial
cd react-node-chatgpt-tutorial
cd react-node-chatgpt-tutorial

使用Vite安装React,并将文件夹命名为 frontend 。使用这个命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm create vite@latest
npm create vite@latest
npm create vite@latest

之后,你将浏览到该文件夹并运行以下命令:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install
npm run dev
npm install npm run dev
npm install
npm run dev

这些命令将安装必要的依赖,并在 5173 端口启动本地服务器。创建后台文件夹:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir backend
mkdir backend
mkdir backend

现在导航到后端文件夹,用这个命令初始化项目:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm init -y
npm init -y
npm init -y

这将创建一个 package.json 文件来跟踪项目的细节。在该文件中添加以下一行代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
"type": "module"
"type": "module"
"type": "module"

这将使ES6模块导入语句的使用成为可能。用下面的命令安装OpenAI和其他依赖项:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm i openai body-parser cors express
npm i openai body-parser cors express
npm i openai body-parser cors express

创建一个文件,所有的代码都在其中。命名为 index.js

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
touch index.js
touch index.js
touch index.js

这就完成了项目的设置。现在有两个文件夹(frontend 和 backend)。

如何搭建服务器

这一部分将着重于创建一个本地服务器,以监听 8000 端口。

首先要做的是像这样导入必要的模块:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Configuration, OpenAIApi } from "openai";
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
import { Configuration, OpenAIApi } from "openai"; import express from "express"; import bodyParser from "body-parser"; import cors from "cors";
import { Configuration, OpenAIApi } from "openai";
import express from "express";
import bodyParser from "body-parser";
import cors from "cors";

接下来,设置 express 、监听 port、用于接收输入的 body-parser 以及允许前端和后端自由通信的 cors 。使用下面的代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const app = express();
const port = 8000;
app.use(bodyParser.json());
app.use(cors());
const app = express(); const port = 8000; app.use(bodyParser.json()); app.use(cors());
const app = express();
const port = 8000;
app.use(bodyParser.json());
app.use(cors());

最后,输入以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
app.listen(port, () => {
console.log(`listening on port ${port}`);
});
app.listen(port, () => { console.log(`listening on port ${port}`); });
app.listen(port, () => {
console.log(`listening on port ${port}`);
});

这就完成了服务器的设置。当你运行 index.js 时,你应该得到以下输出:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
listening on port 8000
listening on port 8000
listening on port 8000

如何创建端点

在这一部分,我们将建立一个端点,该端点将使用请求体接收来自前端的消息,并向调用者返回一个响应。开始时,我们要像前几节那样建立配置参数:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});
const openai = new OpenAIApi(configuration);
const configuration = new Configuration({ organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw", apiKey: "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", }); const openai = new OpenAIApi(configuration);
const configuration = new Configuration({
organization: "org-0nmrFWw6wSm6xIJXSbx4FpTw",
apiKey: "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",
});
const openai = new OpenAIApi(configuration);

接下来,使用下面的代码创建一个异步POST路由:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
app.post("/", async (request, response) => {
});
app.post("/", async (request, response) => { });
app.post("/", async (request, response) => {
});

这个端点将使用 http://localhost:8000/,在回调函数中,输入以下代码,从请求体( request.body )接收 chats 的输入:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { chats } = request.body;
const { chats } = request.body;
const { chats } = request.body;

现在像我们在React部分做的那样,调用 createChatCompletion 端点:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const result = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "You are a EbereGPT. You can help with graphic design tasks",
},
...chats,
],
});
const result = await openai.createChatCompletion({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: "You are a EbereGPT. You can help with graphic design tasks", }, ...chats, ], });
  const result = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: "You are a EbereGPT. You can help with graphic design tasks",
},
...chats,
],
});

这里的区别是,我们没有使用 then...catch... 块,而是将其分配给一个变量( result ),并使用 response.json() 返回响应,如以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
response.json({
output: result.data.choices[0].message,
});
response.json({ output: result.data.choices[0].message, });
  response.json({
output: result.data.choices[0].message,
});

GitHub上找到这部分的代码。以下是在Postman上测试时的输出:

来自Postman的输出

来自Postman的输出

代码的后端部分就这样结束了。下一部分将使用刚刚创建的端点( http://localhost:8000/ )连接前端和后端。

如何从前端连接到后端

这一部分把我们带到前台,在那里我们将创建一个表单。该表单将通过API端点向后端发送消息,并通过相同的媒介接收响应。导航到 frontend/src/App.jsx 文件并输入以下代码:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useState } from "react";
function App() {
const [message, setMessage] = useState("");
const [chats, setChats] = useState([]);
const [isTyping, setIsTyping] = useState(false);
const chat = async (e, message) => {
e.preventDefault();
if (!message) return;
setIsTyping(true);
let msgs = chats;
msgs.push({ role: "user", content: message });
setChats(msgs);
setMessage("");
alert(message);
};
return (
<main>
<h1>FullStack Chat AI Tutorial</h1>
<section>
{chats && chats.length
? chats.map((chat, index) => (
<p key={index} className={chat.role === "user" ? "user_msg" : ""}>
<span>
<b>{chat.role.toUpperCase()}</b>
</span>
<span>:</span>
<span>{chat.content}</span>
</p>
))
: ""}
</section>
<div className={isTyping ? "" : "hide"}>
<p>
<i>{isTyping ? "Typing" : ""}</i>
</p>
</div>
<form action="" onSubmit={(e) => chat(e, message)}>
<input
type="text"
name="message"
value={message}
placeholder="Type a message here and hit Enter..."
onChange={(e) => setMessage(e.target.value)}
/>
</form>
</main>
);
}
export default App;
import { useState } from "react"; function App() { const [message, setMessage] = useState(""); const [chats, setChats] = useState([]); const [isTyping, setIsTyping] = useState(false); const chat = async (e, message) => { e.preventDefault(); if (!message) return; setIsTyping(true); let msgs = chats; msgs.push({ role: "user", content: message }); setChats(msgs); setMessage(""); alert(message); }; return ( <main> <h1>FullStack Chat AI Tutorial</h1> <section> {chats && chats.length ? chats.map((chat, index) => ( <p key={index} className={chat.role === "user" ? "user_msg" : ""}> <span> <b>{chat.role.toUpperCase()}</b> </span> <span>:</span> <span>{chat.content}</span> </p> )) : ""} </section> <div className={isTyping ? "" : "hide"}> <p> <i>{isTyping ? "Typing" : ""}</i> </p> </div> <form action="" onSubmit={(e) => chat(e, message)}> <input type="text" name="message" value={message} placeholder="Type a message here and hit Enter..." onChange={(e) => setMessage(e.target.value)} /> </form> </main> ); } export default App;
import { useState } from "react";
function App() {
const [message, setMessage] = useState("");
const [chats, setChats] = useState([]);
const [isTyping, setIsTyping] = useState(false);
const chat = async (e, message) => {
e.preventDefault();
if (!message) return;
setIsTyping(true);
let msgs = chats;
msgs.push({ role: "user", content: message });
setChats(msgs);
setMessage("");
alert(message);
};
return (
<main>
<h1>FullStack Chat AI Tutorial</h1>
<section>
{chats && chats.length
? chats.map((chat, index) => (
<p key={index} className={chat.role === "user" ? "user_msg" : ""}>
<span>
<b>{chat.role.toUpperCase()}</b>
</span>
<span>:</span>
<span>{chat.content}</span>
</p>
))
: ""}
</section>
<div className={isTyping ? "" : "hide"}>
<p>
<i>{isTyping ? "Typing" : ""}</i>
</p>
</div>
<form action="" onSubmit={(e) => chat(e, message)}>
<input
type="text"
name="message"
value={message}
placeholder="Type a message here and hit Enter..."
onChange={(e) => setMessage(e.target.value)}
/>
</form>
</main>
);
}
export default App;

这段代码与上一节的代码相似。但我们删除了OpenAI的配置,因为我们在本节中不再需要它们。

在这一点上,每当表单被提交时,就会弹出一个警报。这一点一会儿就会改变。在聊天函数中,去掉 alert 信息,然后输入以下内容:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
fetch("http://localhost:8000/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chats,
}),
})
.then((response) => response.json())
.then((data) => {
msgs.push(data.output);
setChats(msgs);
setIsTyping(false);
})
.catch((error) => {
console.log(error);
});
fetch("http://localhost:8000/", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ chats, }), }) .then((response) => response.json()) .then((data) => { msgs.push(data.output); setChats(msgs); setIsTyping(false); }) .catch((error) => { console.log(error); });
fetch("http://localhost:8000/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chats,
}),
})
.then((response) => response.json())
.then((data) => {
msgs.push(data.output);
setChats(msgs);
setIsTyping(false);
})
.catch((error) => {
console.log(error);
});

上面的代码调用了我们创建的端点,并传入 chats 数组供其处理。然后它返回一个响应,该响应被添加到 chats 中并显示在用户界面上:

样式设计前的全栈聊天UI

样式设计前的全栈聊天UI

如果你在 frontend/src/index.css 文件中添加以下样式,UI会看起来更好:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: 无;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
html, body{
scroll-behavior: smooth;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
text-align: center;
position: sticky;
top: 0;
background-color: #242424;
}
main{
max-width: 800px;
margin: auto;
}
p{
background-color: darkslategray;
max-width: 70%;
padding: 15px;
border-radius: 50px;
}
p span{
margin: 5px;
}
p span:first-child{
margin-right: 0;
}
.user_msg{
text-align: right;
margin-left: 30%;
display: flex;
flex-direction: row-reverse;
}
.hide {
visibility: hidden;
display: none;
}
form{
text-align: center;
position: sticky;
bottom: 0;
}
input{
width: 100%;
height: 40px;
border: none;
padding: 10px;
font-size: 1.2rem;
background-color: rgb(28, 23, 23);
}
input:focus{
outline: none;
}
:root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: 无; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; } html, body{ scroll-behavior: smooth; } h1 { font-size: 3.2em; line-height: 1.1; text-align: center; position: sticky; top: 0; background-color: #242424; } main{ max-width: 800px; margin: auto; } p{ background-color: darkslategray; max-width: 70%; padding: 15px; border-radius: 50px; } p span{ margin: 5px; } p span:first-child{ margin-right: 0; } .user_msg{ text-align: right; margin-left: 30%; display: flex; flex-direction: row-reverse; } .hide { visibility: hidden; display: none; } form{ text-align: center; position: sticky; bottom: 0; } input{ width: 100%; height: 40px; border: none; padding: 10px; font-size: 1.2rem; background-color: rgb(28, 23, 23); } input:focus{ outline: none; }
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: 无;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
html, body{
scroll-behavior: smooth;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
text-align: center;
position: sticky;
top: 0;
background-color: #242424;
}
main{
max-width: 800px;
margin: auto;
}
p{
background-color: darkslategray;
max-width: 70%;
padding: 15px;
border-radius: 50px;
}
p span{
margin: 5px;
}
p span:first-child{
margin-right: 0;
}
.user_msg{
text-align: right;
margin-left: 30%;
display: flex;
flex-direction: row-reverse;
}
.hide {
visibility: hidden;
display: none;
}
form{
text-align: center;
position: sticky;
bottom: 0;
}
input{
width: 100%;
height: 40px;
border: none;
padding: 10px;
font-size: 1.2rem;
background-color: rgb(28, 23, 23);
}
input:focus{
outline: none;
}

并删除 frontend/src/App.css 文件中的所有样式。

这一部分的代码在GitHub上。现在,这里是最终的输出:

全栈式聊天机器人如期使用CSS工作

全栈式聊天机器人如期使用CSS工作

恭喜你完成了这个项目!全栈聊天机器人的工作更多,但它帮助我们分离了关注点,建立了一个更安全和有吸引力的应用程序,并为用户提供了更好的体验。所以,这些努力是值得的。你可以在GitHub上找到这一部分的代码。

小结

本教程希望向你展示,任何具有基本编程知识的人都可以构建人工智能驱动的软件。你学会了如何使用React和Nodejs构建一个聊天机器人,我们还讨论了每种技术的利弊。最后,我们建立了一个既实用、安全又有视觉吸引力的解决方案。读完本教程后,你现在可以探索AI的功能,如图像处理和音频互动。花点时间浏览一下文档,看看你可以如何扩展我们在这里涉及的内容。

评论留言