在这篇文章中,我们将浏览一个完整的聊天应用程序,并添加一些功能。
我们将使用React和Vite,尽管任何React框架都起作用。我们将使用Convex作为后端,我们的服务器端功能将运行,以及我们将存储应用程序数据的位置。
要看到此操作,代码为here,并且运行版本是here(目前)。您可以克隆它并在读书中使用一些配置自己运行,但请继续阅读以查看有关构建自己的逐步指南。此外,如果您想查看添加auth。
要自己运行,您需要做一个OpenAI account和get an API key。
如果您熟悉凸,可以跳到步骤2。
0. bootstrap vite react应用程序
如果您已经有一个应用程序,我们可以制作一个:
npm create vite@latest
我选择了convex-chatgpt
作为项目名称,React
作为框架,而Javascript
作为变体。
此时,如果我们运行:
npm install
npm run dev
我们有一个本地运行的WebApp。
让我们更改src/App.jsx
列出消息并具有提交消息的表格:
app.jsx
import {useState} from "react";
import "./App.css";
function App() {
const messages = [
{author: "user", body: "Hello, world"},
];
const sendMessage = body =>
console.log("Trying to send: " + body);
const [newMessageText, setNewMessageText] =
useState("");
return (
<div className="App">
{messages.map((message, i) => (
<p key={i}>
<span>{message.author}: </span>
<span style={{ whiteSpace: "pre-wrap" }}>
{message.body ?? "..."}
</span>
</p>
))}
<form onSubmit={(e) => {
e.preventDefault();
setNewMessageText("");
sendMessage(newMessageText);
}}>
<input
value={newMessageText}
onChange={e => setNewMessageText(e.target.value)}
placeholder="Write a message…"
/>
<input type="submit" value="Send" disabled={!newMessageText} />
</form>
</div>
);
}
export default App;
在这一点上,我们有一个应用程序,该应用在尝试发送消息时向控制台显示静态列表和日志。
1.添加凸
这与凸quickstart相似。在新的终端(留下另一个运行npm run dev
):
npm install convex
npx convex init
如果您以前没有使用过凸,请提示您登录,创建一个帐户等。我选择了convex-chatgpt
作为项目名称。
让我们添加发送消息和列表消息的功能。我们将添加在凸(在服务器上)运行的函数,这些函数将使用query和mutation读取并写入数据库。
添加convex/messages.ts
:
import { query, mutation } from "./_generated/server";
export const list = query(async ({ db }) => {
return await db.query("messages").collect();
});
export const send = mutation(async ({ db }, { body }) => {
await db.insert("messages", {
body,
author: "user",
});
const botMessageId = await db.insert("messages", {
author: "assistant",
});
});
在与运行npm run dev
的一个单独终端中,运行npx convex dev
。这将这些功能将这些功能部署到您的新凸后端,并且在编辑功能时,它将自动重新部署。这将要求您将部署URL保存到.env
和.env.local
文件中:这些点在您的生产和开发部署的凸后端。我们将与开发部署合作。
要从内部React访问凸,您需要在应用程序的顶层添加<ConvexProvider>
上下文。编辑main.jsx
:
...
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<ConvexProvider client={convex}>
<App />
</ConvexProvider>
</React.StrictMode>,
);
然后我们可以从App.jsx
中使用这些功能:
function App() {
const messages = useQuery("messages:list") || [];
const sendMessage = useMutation("messages:send");
...
太好了!现在,我们从数据库中写了消息。试试看!那些新的凸面的人会惊讶地发现,添加新消息将自动导致useQuery("messages:list")
挂钩返回新消息。这是凸的魔力的一部分。了解有关它的更多信息here。
您可以在仪表板:npx convex dashboard
中查看数据。
您可能会注意到查询和突变由filename:function
命名。查看有关此here的更多信息。
您会注意到每次发送消息时,都会从助手那里发出消息。接下来,让我们从chatgpt更新该消息。
2.将消息发送到chatgpt API
到目前为止,我们一直在使用query和mutation,它们是与数据库相互作用的凸函数。为了与外部服务互动,我们需要在action中进行此操作 - 这是一个凸功能,它不在确定性环境中,也不是数据库事务的一部分。这使我们释放了副作用的事情,例如向OpenAI的API提出请求。我们将通过几个步骤进行操作。
获取消息发送到chatgpt
在我们的send
突变中,我们可以使用数据库查询获取最新的10条消息,以发送到chatgpt:
export const send = mutation(async ({ db }, { body }) => {
//... insert messages to the table
const messages = await db
.query("messages")
.order("desc")
.filter((q) => q.neq(q.field("body"), undefined))
.take(21);
messages.reverse();
return { messages, botMessageId };
});
此订购消息按降序订购(除非使用其他index),除非创建时间排序),从而滤除没有身体的消息(例如,新的占位符系统消息),并采用第一个21最近的消息)。然后,它颠倒了列表,因此按上升时间顺序排序。我选择了21个,以便我们有10对用户/机器人消息,然后是最新提示。它返回将用作聊天完成API的输入的消息,我们将下一步添加。
创建动作
我们将使用openai
NPM软件包。您可以安装它:
npm install openai
制作一个新文件:convex/openai.js
:
"use node";
import { Configuration, OpenAIApi } from "openai";
import { action } from "../_generated/server";
export const chat = action(async ({ runMutation }, { body }) => {
const { messages, botMessageId } = await runMutation("messages:send", { body });
const fail = async (reason) => throw new Error(reason);
// Grab the API key from environment variables
// Specify this in your dashboard: `npx convex dashboard`
const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) {
await fail("Add your OPENAI_API_KEY as an env variable");
}
const configuration = new Configuration({ apiKey });
const openai = new OpenAIApi(configuration);
const openaiResponse = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: instructions,
},
...messages.map(({ body, author }) => ({
role: author,
content: body,
})),
],
});
if (openaiResponse.status !== 200) {
await fail("OpenAI error: " + openaiResponse.statusText);
}
const body = openaiResponse.data.choices[0].message.content;
console.log("Response: " + body);
});
这将首先使用我们修改的发送突变发送消息,获取消息ID列表和消息ID列表,以使用Bot的消息更新。然后,它将向gpt-3.5-turbo
型号提出请求,并传递一个带有指令的系统消息(目前为硬编码),然后是每条消息。我们将身体和作者领域变成了角色和内容。有关API的更多详细信息,请参见他们的文档here。
将您的API键添加到仪表板
要对OpenAI的请求进行身份验证,您需要将API key添加到凸仪表板中,因此您的操作(在凸面服务器上运行)可以访问它。使用npx convex dashboard
进入仪表板,然后在左图中进入设置,然后添加键的键。当您在那里时,您可以通过切换左图中的下拉列表将其添加为Env变量。
触发UI的动作
我们可以将对useQuery("messages:send")
的呼叫更改为
const sendMessage = useAction("openai:chat");
请注意,该操作是由文件(openai
)和功能(chat
)的路径解决的。 API是相同的(以一个参数为body
),所以它是一个替换!
此时,如果运行它,它将向Chatgpt and Console发送请求。凸面打印凸功能将登录到浏览器的控制台,尽管您还可以在dashboard中看到它们,以向您自己证明它在云中运行。现在让我们在UI中显示它们。
通过回复更新消息
为了将响应获取到聊天消息中,我们希望更新我们添加的空占位符消息。将每个呼吁称为突变作为交易。一旦我们调用了第一个突变,这些消息就会发布到数据库,UI可以在对OpenAI的慢速通话之前显示新消息。一旦获得响应,我们将更新Bot的消息,UI将通过messages:list
查询自动更新。我们将在convex/messages.js
文件中添加一个新突变:
// An `internalMuation` can only be called from other server functions.
export const update = internalMutation(async ({ db }, { messageId, patch }) => {
await db.patch(messageId, patch);
});
这修补了指定的消息。例如,您可以通过{body: "hi"}
将消息的身体更改为hi,例如。自第一个突变返回botMessageId
以来,我们可以从convex/openai.js
中的动作中使用它:
export const chat = action(async ({ runMutation }, { body }) => {
const { messages, botMessageId } = await runMutation("messages:send", { body });
// Call OpenAI
await runMutation("messages:update", {
messageId: botMessageId,
patch: {
body: openaiResponse.data.choices[0].message.content,
// Track how many tokens we're using for various messages
usage: openaiResponse.data.usage,
updatedAt: Date.now(),
// How long it took OpenAI
ms: Number(openaiResponse.headers["openai-processing-ms"]),
}
});
});
当我们在这里时,我们可以将大量有趣的数据与身体一起存储到消息中。凸的数据库足够灵活,可以存储strings, numbers, javascript objects, koude32s, etc.,同时还可以让您钉住schema,以获取打字稿类型,自动完成等。
ð现在您已经回复了您的消息!
概括
在这篇文章中,我们制作了一个全栈网络应用程序,可以与Openai的Chatgpt API聊天。让我们知道in Discord您的想法,如果您想看到一些扩展: