使用 NextJS 和 Wing 构建你自己的 ChatGPT 图形客户端 🤯

2025-05-28

使用 NextJS 和 Wing 构建你自己的 ChatGPT 图形客户端 🤯

TL;DR

在本文结束时,您将使用 Wing 和 Next.js 构建和部署 ChatGPT 客户端。

该应用程序可以在本地运行(在本地云模拟器中)或将其部署到您自己的云提供商。

舞蹈


介绍

构建 ChatGPT 客户端并将其部署到您自己的云基础设施是确保对数据进行控制的好方法。

将 LLM 部署到您自己的云基础设施可为您的项目提供隐私和安全。

有时,当您使用专有 LLM 平台(如 OpenAI 的 ChatGPT)时,您可能会担心您的数据被存储或处理在远程服务器上,这可能是因为输入平台的数据很敏感,也可能是出于其他隐私原因。

在这种情况下,将 LLM 自托管到您的云基础设施或在您的机器上本地运行它可以让您更好地控制数据的隐私和安全。

Wing是一种面向云的编程语言,它允许您构建和部署基于云的应用程序,而无需担心底层基础架构。
它允许您使用同一种语言定义和管理云基础架构和应用程序代码,从而简化了您在云上的构建方式。Wing
与云无关——使用它构建的应用程序可以编译并部署到各种云平台上。

查看⭐Wing

给我们一颗星


让我们开始吧!

为了继续,您需要:

  • 对 Next.js 有一定的了解
  • 在你的机器上安装 Wing。如果你不知道如何安装,不用担心。我们将在本项目中一起学习。
  • 获取您的 OpenAI API 密钥。

创建您的项目

首先,你需要在机器上安装 Wing。运行以下命令:

npm install -g winglang
Enter fullscreen mode Exit fullscreen mode

通过检查版本来确认安装:

wing -V
Enter fullscreen mode Exit fullscreen mode

创建您的 Next.js 和 Wing 应用程序。

mkdir assistant
cd assistant
npx create-next-app@latest frontend
mkdir backend && cd backend
wing new empty
Enter fullscreen mode Exit fullscreen mode

我们已经在 Assistant 目录中成功创建了 Wing 和 Next.js 项目。我们的 ChatGPT 客户端名为 Assistant。听起来很酷吧?

frontend 和 backend 目录分别包含我们的 Next 和 Wing 应用程序。wing new empty创建三个文件:package.jsonpackage-lock.jsonmain.w。后者是应用程序的入口点。

在 Wing 模拟器中本地运行您的应用程序

Wing 模拟器允许您在本地机器内运行代码、编写单元测试和调试代码,而无需部署到实际的云提供商,从而帮助您更快地进行迭代。

使用以下命令在本地运行 Wing 应用程序:

wing it
Enter fullscreen mode Exit fullscreen mode

您的 Wing 应用程序将在 上运行localhost:3000

安慰

设置后端

  • 让我们安装 Wing 的 OpenAI 和 React 库。OpenAI 库提供了与 LLM 交互的标准接口。React 库允许你将 Wing 后端连接到 Next 应用。
npm i @winglibs/openai @winglibs/react
Enter fullscreen mode Exit fullscreen mode
  • 将这些包导入到你的main.w文件中。我们还要导入所有需要的其他库。
bring openai
bring react
bring cloud
bring ex
bring http
Enter fullscreen mode Exit fullscreen mode

bring是 Wing 中的 import 语句。可以这样理解,Wing 用它来实现与 JavaScriptbring相同的功能。import

cloud是 Wing 的云库。它公开了云 API、存储桶 (Bucket)、计数器 (Counter)、域 (Domain)、端点 (Endpoint)、函数 (Function) 以及许多其他云资源的标准接口。ex它是一个用于连接表 (Tables) 和云 Redis 数据库的标准库,http用于调用不同的 HTTP 方法——从远程资源发送和检索信息。

获取您的 OpenAI API 密钥

我们将gpt-4-turbo在我们的应用程序中使用它,但您可以使用任何 OpenAI 模型。

OpenAI 密钥

  • 设置名称项目权限,然后单击创建密钥。

OpenAI Key2

初始化 OpenAI

创建一个Class用于初始化你的 OpenAI API 的目录。我们希望它能够重复使用。

personality我们将在我们的课程中添加一个,Assistant以便我们在向 AI 助手传递提示时可以决定其个性。

let apiKeySecret = new cloud.Secret(name: "OAIAPIKey") as "OpenAI Secret";

class Assistant {
    personality: str;
    openai: openai.OpenAI;

    new(personality: str) {
        this.openai = new openai.OpenAI(apiKeySecret: apiKeySecret);
        this.personality = personality;
    }

    pub inflight ask(question: str): str {
        let prompt = `you are an assistant with the following personality: ${this.personality}. ${question}`;
        let response = this.openai.createCompletion(prompt, model: "gpt-4-turbo");
        return response.trim();
    }
}
Enter fullscreen mode Exit fullscreen mode

preflightWing 分别使用和概念统一基础设施定义和应用程序逻辑inflight

预检代码(通常是基础设施定义)在编译时运行一次,而飞行中代码将在运行时运行以实现应用程序的行为。

云存储桶、队列和 API 端点都是预检的一些示例。定义预检时无需添加 preflight 关键字,Wing 默认识别这一点。但对于 inflight 块,您需要在其后添加“inflight”一词。

上面的代码中有一个 inflight 块。在 inflight 块中,你可以编写异步运行时代码,这些代码可以通过 inflight API 直接与资源交互。

测试和存储云秘密

让我们来看看如何保护我们的 API 密钥,因为我们肯定要考虑安全性

让我们.env在后端创建一个文件root并传入我们的 API 密钥:

OAIAPIKey = Your_OpenAI_API_key
Enter fullscreen mode Exit fullscreen mode

我们可以通过引用我们的 .env 文件在本地测试我们的 OpenAI API 密钥,然后,由于我们计划部署到 AWS,我们将逐步设置AWS Secrets Manager

AWS 控制台

首先,让我们登录 AWS 控制台。如果您没有账户,可以免费创建一个。

AWS 平台

导航到 Secrets Manager,然后存储我们的 API 密钥值。

AWS Secrets Manager

图片描述

我们已将 API 密钥存储在名为 的云密钥中OAIAPIKey。复制您的密钥,我们将跳转到终端并连接到我们的密钥,该密钥现在存储在 AWS 平台中。

wing secrets
Enter fullscreen mode Exit fullscreen mode

现在,将您的 API 密钥粘贴到终端中作为值。您的密钥现已正确存储,我们可以开始与我们的应用进行交互了。


将人工智能的响应存储在云端。

将 AI 的响应存储在云端,让您可以掌控数据。这些数据驻留在您自己的基础架构上,这与 ChatGPT 等专有平台不同,后者的数据存储在您无法控制的第三方服务器上。您还可以随时检索这些响应。

让我们创建另一个类,使用 Assistant 类来传递 AI 的个性和提示。我们还将每个模型的响应以txt文件形式存储在云存储桶中。

let counter = new cloud.Counter();

class RespondToQuestions {
    id: cloud.Counter;
    gpt: Assistant;
    store: cloud.Bucket;

    new(store: cloud.Bucket) {
        this.gpt = new Assistant("Respondent");
        this.id = new cloud.Counter() as "NextID";
        this.store = store;
    }

    pub inflight sendPrompt(question: str): str {
        let reply = this.gpt.ask("{question}");
        let n = this.id.inc();
        this.store.put("message-{n}.original.txt", reply);
        return reply;
    }
}
Enter fullscreen mode Exit fullscreen mode

我们赋予了助手“回复者”的个性。我们希望它能够回答问题。你也可以让用户在前端发送提示时指定这个个性。

每次生成响应时,计数器都会递增,并将计数器的值传递到n用于在云端存储模型响应的变量中。然而,我们真正想要的是创建一个数据库,用于存储来自前端的用户提示和模型的响应。

让我们定义我们的数据库。

定义我们的数据库

Wingex.Table内置了 NoSQL 数据库来存储和查询数据。

let db = new ex.Table({
    name: "assistant",
    primaryKey: "id",
    columns: {
        question: ex.ColumnType.STRING,
        answer: ex.ColumnType.STRING
    }
});
Enter fullscreen mode Exit fullscreen mode

我们在数据库定义中添加了两列 - 第一列用于存储用户提示,第二列用于存储模型的响应。

创建 API 路由和逻辑

我们希望能够在后端发送和接收数据。让我们创建 POST 和 GET 路由。

let api = new cloud.Api({ cors: true });

api.post("/assistant", inflight((request) => {
    // POST request logic goes here
}));

api.get("/assistant", inflight(() => {
    // GET request logic goes here
}));
Enter fullscreen mode Exit fullscreen mode

let myAssistant = new RespondToQuestions(store) as "Helpful Assistant";

api.post("/assistant", inflight((request) => {
    let prompt = request.body;
    let response = myAssistant.sendPrompt(JSON.stringify(prompt)); 
    let id = counter.inc(); 

    // Insert prompt and response in the database
    db.insert(id, { question: prompt, answer: response });

    return cloud.ApiResponse({
        status: 200
    });
}));

Enter fullscreen mode Exit fullscreen mode

在 POST 路由中,我们希望将从前端收到的用户提示传递到模型并获取响应。提示和响应都将存储在数据库中。cloud.ApiResponse允许您针对用户的请求发送响应。

添加前端发出 GET 请求时检索数据库项目的逻辑。


添加前端发出 GET 请求时检索数据库项目的逻辑。

api.get("/assistant", inflight(() => {
    let questionsAndAnswers = db.list();

    return cloud.ApiResponse({
        body: JSON.stringify(questionsAndAnswers),
        status: 200
    });
}));
Enter fullscreen mode Exit fullscreen mode

我们的后端已准备就绪。让我们在本地云模拟器中测试一下。

跑步wing it

让我们过去向localhost:3000我们的助手问一个问题。

助理回复

我们的问题和 Google 助手的回复都已保存到数据库中。快来看看吧。

表数据

向前端公开你的 API URL

我们需要将后端的 API URL 暴露给 Next 前端。这时之前安装的 React 库就派上用场了。

let website = new react.App({
    projectPath: "../frontend",
    localPort: 4000
});

website.addEnvironment("API_URL", api.url);

Enter fullscreen mode Exit fullscreen mode

将以下内容添加到layout.js您的 Next 应用程序。

import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
    title: "Create Next App",
    description: "Generated by create next app",
};

export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <head>
            <script src="./wing.js" defer></script>
           </head>
            <body className={inter.className}>{children}</body>
        </html>
    );
}
Enter fullscreen mode Exit fullscreen mode

我们现在可以访问API_URL我们的下一个应用程序。

实现前端逻辑

让我们实现前端逻辑来调用我们的后端。

import { useEffect, useState, useCallback } from 'react';
import axios from 'axios';

function App() {

    const [isThinking, setIsThinking] = useState(false);
    const [input, setInput] = useState("");
    const [allInteractions, setAllInteractions] = useState([]);

    const retrieveAllInteractions = useCallback(async (api_url) => {
            await axios ({
              method: "GET",
              url: `${api_url}/assistant`,
            }).then(res => {
              setAllInteractions(res.data)
            })
  }, [])

    const handleSubmit = useCallback(async (e)=> {
        e.preventDefault()

        setIsThinking(!isThinking)


        if(input.trim() === ""){
          alert("Chat cannot be empty")
          setIsThinking(true)

        }

          await axios({
            method: "POST",
            url: `${window.wingEnv.API_URL}/assistant`,
            headers: {
              "Content-Type": "application/json"
            },
            data: input
          })
          setInput("");
          setIsThinking(false);
          await retrieveAllInteractions(window.wingEnv.API_URL);     

  })

    useEffect(() => {
        if (typeof window !== "undefined") {
            retrieveAllInteractions(window.wingEnv.API_URL);
        }
    }, []);

    // Here you would return your component's JSX
    return (
        // JSX content goes here
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

retrieveAllInteractions函数从后端数据库中获取所有问题和答案。该handSubmit函数将用户的提示发送到后端。

让我们添加 JSX 实现。

import { useEffect, useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
    // ...

    return (
        <div className="container">
            <div className="header">
                <h1>My Assistant</h1>
                <p>Ask anything...</p>
            </div>

            <div className="chat-area">
                <div className="chat-area-content">
                    {allInteractions.map((chat) => (
                        <div key={chat.id} className="user-bot-chat">
                            <p className='user-question'>{chat.question}</p>
                            <p className='response'>{chat.answer}</p>
                        </div>
                    ))}
                    <p className={isThinking ? "thinking" : "notThinking"}>Generating response...</p>
                </div>

                <div className="type-area">
                    <input 
                        type="text" 
                        placeholder="Ask me any question" 
                        value={input} 
                        onChange={(e) => setInput(e.target.value)} 
                    />
                    <button onClick={handleSubmit}>Send</button>
                </div>
            </div>
        </div>
    );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

本地运行你的项目

导航到您的后端目录并使用以下命令在本地运行您的 Wing 应用程序

cd ~assistant/backend
wing it
Enter fullscreen mode Exit fullscreen mode

还运行你的 Next.js 前端:

cd ~assistant/frontend
npm run dev
Enter fullscreen mode Exit fullscreen mode

让我们看一下我们的应用程序。

聊天应用程序

让我们从我们的下一个应用程序中向我们的 AI 助手询问几个开发人员问题。

聊天应用程序2

将您的应用程序部署到 AWS

我们已经了解了我们的应用如何在本地运行。Wing 还允许您部署到任何云提供商,包括 AWS。要部署到 AWS,您需要使用您的凭证配置TerraformAWS CLI

  • 使用 编译为 Terraform/AWS tf-aws。该命令指示编译器使用 Terraform 作为配置引擎,将我们所有的资源绑定到默认的 AWS 资源集。
cd ~/assistant/backend
wing compile --platform tf-aws main.w
Enter fullscreen mode Exit fullscreen mode

  • 运行 Terraform Init 并应用
cd ./target/main.tfaws
terraform init
terraform apply
Enter fullscreen mode Exit fullscreen mode

注意:terraform apply需要一些时间才能完成。

您可以在此处找到本教程的完整代码

总结

正如我之前提到的,我们都应该关注我们应用程序的安全性,构建您自己的 ChatGPT 客户端并将其部署到您的云基础设施可以为您的应用程序提供一些非常好的保障

我们在本教程中演示了Wing如何提供一种直接的方法来构建可扩展的云应用程序,而无需担心底层基础设施。

如果您有兴趣构建更多酷炫的东西,Wing 拥有一个活跃的开发者社区,与您携手共建云端愿景。我们期待在那里与您相见。

只需前往我们的Discord打个招呼!

文章来源:https://dev.to/winglang/building-your-own-chatgpt-graphical-client-with-nextjs-and-wing-29jj
PREV
云,为什么这么难?🤷‍♀️
NEXT
使用 LangChain 为您的文档构建一个 QA 机器人 😻