阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

人工智慧時代已經來臨。身為開發者,要想取得成功,在你的投資組合中加入一些人工智慧驅動的專案是很棒的。

今天,我們將建立一個由人工智慧驅動的部落格平台,該平台具有一些很棒的功能,例如研究、自動完成和 Copilot。

在這裡建立了該專案的初始版本。一位評論者提出了一些非常酷的建議,可以將其提升到一個新的水平。

圖片描述

所以我們決定建造它!

長話短說

我們正在建立一個由人工智慧驅動的部落格平台 Pt。二

圖片描述


CopilotKit:建構應用內人工智慧副駕駛的框架

CopilotKit是一個開源的AI副駕駛平台。我們可以輕鬆地將強大的人工智慧整合到您的 React 應用程式中。

建造:

  • ChatBot:上下文感知的應用內聊天機器人,可以在應用程式內執行操作 💬

  • CopilotTextArea:人工智慧驅動的文字字段,具有上下文感知自動完成和插入功能📝

  • 聯合代理:應用程式內人工智慧代理,可以與您的應用程式和使用者互動🤖

上傳圖片

{% cta https://github.com/CopilotKit/CopilotKit %} Star CopilotKit ⭐️ {% endcta %}

現在回到文章!


先決條件

要完全理解本教程,您需要對 React 或 Next.js 有基本的了解。

以下是建立人工智慧部落格所需的工具:

  • Quill 富文本編輯器- 一個文字編輯器,可讓您輕鬆設定文字格式、新增圖片、新增程式碼以及在 Web 應用程式中建立自訂互動式內容。

  • Supabase - 一項 PostgreSQL 託管服務,為您提供專案所需的所有後端功能。

  • Langchain - 提供了一個框架,使人工智慧代理能夠搜尋網路並研究任何主題。

  • OpenAI API - 提供 API 金鑰,讓您能夠使用 ChatGPT 模型執行各種任務。

  • Tavily AI - 一個搜尋引擎,使人工智慧代理能夠在應用程式中進行研究並存取即時知識。

  • CopilotKit - 一個開源副駕駛框架,用於建立自訂 AI 聊天機器人、應用程式內 AI 代理程式和文字區域。

專案設定和套件安裝

首先,透過在終端機中執行以下程式碼片段來建立 Next.js 應用程式:

npx create-next-app@latest aiblogapp

選擇您首選的配置設定。在本教學中,我們將使用 TypeScript 和 Next.js App Router。

圖片描述

接下來,安裝 Quill 富文本編輯器、Supabase 和 Langchain 軟體包及其相依性。

npm install quill react-quill @supabase/supabase-js @supabase/ssr @supabase/auth-helpers-nextjs @langchain/langgraph

最後,安裝 CopilotKit 軟體套件。這些套件使我們能夠從 React 狀態檢索資料並將 AI copilot 新增至應用程式。

npm install @copilotkit/react-ui @copilotkit/react-textarea @copilotkit/react-core @copilotkit/backend

恭喜!您現在已準備好建立由人工智慧驅動的部落格。

建構部落格前端

在本節中,我將引導您完成使用靜態內容建立部落格前端以定義部落格使用者介面的過程。

部落格的前端將由四個頁面組成:主頁、貼文頁面、建立貼文頁面和登入/註冊頁面。

首先,請在程式碼編輯器中前往/[root]/src/app並建立一個名為components的資料夾。在 Components 資料夾中,建立五個名為Header.tsxPosts.tsxPost.tsxComment.tsxQuillEditor.tsx的文件

Header.tsx檔案中,新增以下程式碼,定義一個名為Header的功能元件,該元件將呈現部落格的導覽列。

"use client";

import Link from "next/link";

export default function Header() {
  return (
    <>
      <header className="flex flex-wrap sm:justify-start sm:flex-nowrap z-50 w-full bg-gray-800 border-b border-gray-200 text-sm py-3 sm:py-0 ">
        <nav
          className="relative max-w-7xl w-full mx-auto px-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8"
          aria-label="Global">
          <div className="flex items-center justify-between">
            <Link
              className="flex-none text-xl text-white font-semibold "
              href="/"
              aria-label="Brand">
              AIBlog
            </Link>
          </div>
          <div id="navbar-collapse-with-animation" className="">
            <div className="flex flex-col gap-y-4 gap-x-0 mt-5 sm:flex-row sm:items-center sm:justify-end sm:gap-y-0 sm:gap-x-7 sm:mt-0 sm:ps-7">
              <Link
                className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
                href="/createpost">
                Create Post
              </Link>

              <form action={""}>
                <button
                  className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
                  type="submit">
                  Logout
                </button>
              </form>

              <Link
                className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
                href="/login">
                Login
              </Link>
            </div>
          </div>
        </nav>
      </header>
    </>
  );
}

Posts.tsx檔案中,新增以下程式碼,定義一個名為Posts的功能元件,該元件呈現部落格平台主頁,該首頁將顯示已發佈文章的清單。

"use client";

import React, { useEffect, useState } from "react";
import Image from "next/image";
import Link from "next/link";

export default function Posts() {
  const [articles, setArticles] = useState<any[]>([]);

  return (
    <div className="max-w-[85rem] h-full  px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
      <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
        <Link
          key={""}
          className="group flex flex-col h-full bg-gray-800 border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 "
          href="">
          <div className="aspect-w-16 aspect-h-11">
            <Image
              className="object-cover h-48 w-96 rounded-xl"
              src={`https://source.unsplash.com/featured/?${encodeURIComponent(
                "Hello World"
              )}`}
              width={500}
              height={500}
              alt="Image Description"
            />
          </div>
          <div className="my-6">
            <h3 className="text-xl font-semibold text-white ">Hello World</h3>
          </div>
        </Link>
      </div>
    </div>
  );
}

QuillEditor.tsx檔案中,新增以下程式碼,用於動態匯入 QuillEditor 元件、定義 Quill 編輯器工具列的模組配置以及定義 Quill 編輯器的文字格式。

// Import the dynamic function from the "next/dynamic" package
import dynamic from "next/dynamic";
// Import the CSS styles for the Quill editor's "snow" theme
import "react-quill/dist/quill.snow.css";

// Export a dynamically imported QuillEditor component
export const QuillEditor = dynamic(() => import("react-quill"), { ssr: false });

// Define modules configuration for the Quill editor toolbar
export const quillModules = {
  toolbar: [
    // Specify headers with different levels
    [{ header: [1, 2, 3, false] }],
    // Specify formatting options like bold, italic, etc.
    ["bold", "italic", "underline", "strike", "blockquote"],
    // Specify list options: ordered and bullet
    [{ list: "ordered" }, { list: "bullet" }],
    // Specify options for links and images
    ["link", "image"],
    // Specify alignment options
    [{ align: [] }],
    // Specify color options
    [{ color: [] }],
    // Specify code block option
    ["code-block"],
    // Specify clean option for removing formatting
    ["clean"],
  ],
};

// Define supported formats for the Quill editor
export const quillFormats = [
  "header",
  "bold",
  "italic",
  "underline",
  "strike",
  "blockquote",
  "list",
  "bullet",
  "link",
  "image",
  "align",
  "color",
  "code-block",
];

Post.tsx檔案中,新增以下程式碼,定義一個名為CreatePost功能元件,該元件將用於呈現文章建立表單。

"use client";

// Importing React hooks and components
import { useRef, useState } from "react";
import { QuillEditor } from "./QuillEditor";
import { quillModules } from "./QuillEditor";
import { quillFormats } from "./QuillEditor";
import "react-quill/dist/quill.snow.css";

// Define the CreatePost component
export default function CreatePost() {
  // Initialize state variables for article outline, copilot text, and article title
  const [articleOutline, setArticleOutline] = useState("");
  const [copilotText, setCopilotText] = useState("");
  const [articleTitle, setArticleTitle] = useState("");

  // State variable to track if research task is running
  const [publishTaskRunning, setPublishTaskRunning] = useState(false);

  // Handle changes to the editor content
  const handleEditorChange = (newContent: any) => {
    setCopilotText(newContent);
  };

  return (
    <>
      {/* Main */}
      <div className="p-3 max-w-3xl mx-auto min-h-screen">
        <h1 className="text-center text-white text-3xl my-7 font-semibold">
          Create a post
        </h1>

        {/* Form for creating a post */}
        <form action={""} className="flex flex-col gap-4 mb-2 mt-2">
          <div className="flex flex-col gap-4 sm:flex-row justify-between mb-2">
            {/* Input field for article title */}
            <input
              type="text"
              id="title"
              name="title"
              placeholder="Title"
              value={articleTitle}
              onChange={(event) => setArticleTitle(event.target.value)}
              className="flex-1 block w-full rounded-lg border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500"
            />
          </div>

          {/* Hidden textarea for article content */}
          <textarea
            className="p-4 w-full aspect-square font-bold text-xl bg-slate-800 text-white rounded-lg resize-none hidden"
            id="content"
            name="content"
            value={copilotText}
            placeholder="Write your article content here"
            onChange={(event) => setCopilotText(event.target.value)}
          />

          {/* Quill editor component */}
          <QuillEditor
            onChange={handleEditorChange}
            modules={quillModules}
            formats={quillFormats}
            className="h-80 mb-12 text-white"
          />
          {/* Submit button for publishing the post */}
          <button
            type="submit"
            disabled={publishTaskRunning}
            className={`bg-blue-500 text-white font-bold py-2 px-4 rounded ${
              publishTaskRunning
                ? "opacity-50 cursor-not-allowed"
                : "hover:bg-blue-700"
            }`}
            onClick={async () => {
              try {
                setPublishTaskRunning(true);
              } finally {
                setPublishTaskRunning(false);
              }
            }}>
            {publishTaskRunning ? "Publishing..." : "Publish"}
          </button>
        </form>
      </div>
    </>
  );
}

Comment.tsx檔案中,新增以下程式碼,定義一個名為 Comment 的功能元件,該元件呈現貼文評論表單和貼文評論。

// Client-side rendering
"use client";

// Importing React and Next.js components
import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";

// Define the Comment component
export default function Comment() {
  // State variables for comment, comments, and article content
  const [comment, setComment] = useState("");
  const [comments, setComments] = useState<any[]>([]);
  const [articleContent, setArticleContent] = useState("");

  return (
    <div className="max-w-2xl mx-auto w-full p-3">
      {/* Form for submitting a comment */}
      <form action={""} className="border border-teal-500 rounded-md p-3 mb-4">
        {/* Textarea for entering a comment */}
        <textarea
          id="content"
          name="content"
          placeholder="Add a comment..."
          rows={3}
          onChange={(e) => setComment(e.target.value)}
          value={comment}
          className="hidden"
        />

        {/* Submit button */}
        <div className="flex justify-between items-center mt-5">
          <button
            type="submit"
            className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
            Submit
          </button>
        </div>
      </form>

      {/* Comments section */}
      <p className="text-white mb-2">Comments:</p>

      {/* Comment item (currently hardcoded) */}
      <div key={""} className="flex p-4 border-b dark:border-gray-600 text-sm">
        <div className="flex-shrink-0 mr-3">
          {/* Profile picture */}
          <Image
            className="w-10 h-10 rounded-full bg-gray-200"
            src={`(link unavailable){encodeURIComponent(
              "Silhouette"
            )}`}
            width={500}
            height={500}
            alt="Profile Picture"
          />
        </div>
        <div className="flex-1">
          <div className="flex items-center mb-1">
            {/* Username (currently hardcoded as "Anonymous") */}
            <span className="font-bold text-white mr-1 text-xs truncate">
              Anonymous
            </span>
          </div>
          {/* Comment text (currently hardcoded as "No Comments") */}
          <p className="text-gray-500 pb-2">No Comments</p>
        </div>
      </div>
    </div>
  );
}

接下來,前往/[root]/src/app並建立一個名為[slug]的資料夾。在 [slug] 資料夾中,建立一個page.tsx檔案。

然後將以下程式碼新增至匯入CommentHeader元件的檔案中,並定義一個名為Post功能元件,該元件將呈現導覽列、貼文內容、評論表單和貼文評論。

import Header from "../components/Header";
import Comment from "../components/Comment";

export default async function Post() {
  return (
    <>
      <Header />
      <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen">
        <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl">
          Hello World
        </h1>
        <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs">
          <span></span>
          <span className="italic">0 mins read</span>
        </div>
        <div className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2">
          No Post Content
        </div>
        <Comment />
      </main>
    </>
  );
}

之後,轉到/[root]/src/app並建立一個名為createpost的資料夾。在createpost資料夾中,建立一個名為page.tsx檔案。

然後將以下程式碼新增至匯入CreatePostHeader元件的檔案中,並定義一個名為WriteArticle的功能元件,該元件將呈現導覽列和貼文建立表單。

import CreatePost from "../components/Post";
import Header from "../components/Header";
import { redirect } from "next/navigation";

export default async function WriteArticle() {
  return (
    <>
      <Header />
      <CreatePost />
    </>
  );
}

接下來,前往/[root]/src/page.tsx文件,新增以下程式碼,導入PostsHeader元件並定義名為Home功能元件。

import Header from "./components/Header";
import Posts from "./components/Posts";

export default async function Home() {
  return (
    <>
      <Header />
      <Posts />
    </>
  );
}

之後,請轉到next.config.mjs檔案並將其重新命名為next.config.js 。然後加入以下程式碼,允許您使用 Unsplash 中的圖像作為已發布文章的封面圖像。

module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "source.unsplash.com",
      },
    ],
  },
};

接下來,刪除 globals.css 檔案中的 CSS 程式碼並新增以下 CSS 程式碼。

@tailwind base;
@tailwind components;
@tailwind utilities;

body {
  height: 100vh;
  background-color: rgb(16, 23, 42);
}

.ql-editor {
  font-size: 1.25rem;
}

.post-content p {
  margin-bottom: 0.5rem;
}

.post-content h1 {
  font-size: 1.5rem;
  font-weight: 600;
  font-family: sans-serif;
  margin: 1.5rem 0;
}

.post-content h2 {
  font-size: 1.4rem;
  font-family: sans-serif;
  margin: 1.5rem 0;
}

.post-content a {
  color: rgb(73, 149, 199);
  text-decoration: none;
}

.post-content a:hover {
  text-decoration: underline;
}

最後,在命令列上執行命令npm run dev ,然後導航到 http://localhost:3000/

現在您應該在瀏覽器上查看部落格平台前端,如下所示。

圖片描述

使用 CopilotKit 將 AI 功能整合到部落格中

在本節中,您將學習如何為部落格加入 AI 副駕駛,以使用 CopilotKit 執行部落格主題研究和內容自動建議。

CopilotKit 提供前端和後端套件。它們使您能夠插入 React 狀態並使用 AI 代理在後端處理應用程式資料。

首先,讓我們將 CopilotKit React 元件加入到部落格前端。

將 CopilotKit 新增至部落格前端

在這裡,我將引導您完成將部落格與 CopilotKit 前端整合的過程,以方便部落格文章研究和文章大綱生成。

首先,使用下面的程式碼片段在/[root]/src/app/components/Post.tsx檔案頂部匯入useMakeCopilotReadableuseCopilotActionCopilotTextareaHTMLCopilotTextAreaElement自訂掛鉤。

import {
    useMakeCopilotReadable,
    useCopilotAction,
  } from "@copilotkit/react-core";
  import {
    CopilotTextarea,
    HTMLCopilotTextAreaElement,
  } from "@copilotkit/react-textarea";

在 CreatePost 函數內的狀態變數下方,加入以下程式碼,該程式碼使用useMakeCopilotReadable掛鉤來新增將作為應用程式內聊天機器人的上下文產生的文章大綱。鉤子使副駕駛可以閱讀文章大綱。

useMakeCopilotReadable(
    "Blog article outline: " + JSON.stringify(articleOutline)
  );

useMakeCopilotReadable掛鉤下方,使用以下程式碼建立一個名為copilotTextareaRef的引用,該引用指向名為HTMLCopilotTextAreaElement的文字區域元素。

const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null);

在上面的程式碼下方,加入以下程式碼,該程式碼使用useCopilotAction掛鉤來設定名為researchBlogArticleTopic的操作,該操作將啟用對部落格文章的給定主題的研究。

此操作採用兩個參數,稱為articleTitlearticleOutline ,這兩個參數可以產生文章標題和大綱。

該操作包含一個處理程序函數,該函數根據給定主題生成文章標題和大綱。

在處理函數內部, articleOutline狀態會使用新產生的大綱進行更新,而articleTitle狀態會使用新產生的標題進行更新,如下所示。

// Define a Copilot action
useCopilotAction(
  {
    // Action name and description
    name: "researchBlogArticleTopic",
    description: "Research a given topic for a blog article.",

    // Parameters for the action
    parameters: [
      {
        // Parameter 1: articleTitle
        name: "articleTitle",
        type: "string",
        description: "Title for a blog article.",
        required: true, // This parameter is required
      },
      {
        // Parameter 2: articleOutline
        name: "articleOutline",
        type: "string",
        description: "Outline for a blog article that shows what the article covers.",
        required: true, // This parameter is required
      },
    ],

    // Handler function for the action
    handler: async ({ articleOutline, articleTitle }) => {
      // Set the article outline and title using state setters
      setArticleOutline(articleOutline);
      setArticleTitle(articleTitle);
    },
  },
  [] // Dependencies (empty array means no dependencies)
);

在上面的程式碼下方,前往表單元件並新增以下CopilotTextarea元件,該元件可讓您為文章內容新增文字完成、插入和編輯。

<CopilotTextarea
            className="p-4 h-72 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            value={articleOutline}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleTitle,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />

之後,請前往/[root]/src/app/createpost/page.tsx檔案並使用下面的程式碼匯入頂部的 CopilotKit 前端套件和樣式。

import { CopilotKit } from "@copilotkit/react-core";
import { CopilotPopup } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";

然後使用CopilotKit包裝CopilPopupCreatePost元件,如下圖所示。 CopilotKit元件指定 CopilotKit 後端端點 ( /api/copilotkit/ ) 的 URL,而CopilotPopup則呈現應用程式內聊天機器人,您可以提示您研究文章的任何主題。

export default async function WriteArticle() {
  return (
    <>
      <Header />
      <CopilotKit url="/api/copilotkit">
        <CopilotPopup
          instructions="Help the user research a blog article topic."
          defaultOpen={true}
          labels={{
            title: "Blog Article Research AI Assistant",
            initial:
              "Hi! 👋 I can help you research any topic for a blog article.",
          }}
        />
        <CreatePost />
      </CopilotKit>
    </>
  );
}

之後,使用下面的程式碼片段在/[root]/src/app/components/Comment.tsx檔案頂部導入useMakeCopilotReadableCopilotKitCopilotTextareaHTMLCopilotTextAreaElement自訂掛鉤。

import { useMakeCopilotReadable, CopilotKit } from "@copilotkit/react-core";
import {
  CopilotTextarea,
  HTMLCopilotTextAreaElement,
} from "@copilotkit/react-textarea";

在 Comment 函數內的狀態變數下方,新增以下程式碼,程式碼使用useMakeCopilotReadable掛鉤將貼文內容新增為評論內容自動建議的上下文。

useMakeCopilotReadable(
    "Blog article content: " + JSON.stringify(articleContent)
  );

  const copilotTextareaRef = useRef<HTMLCopilotTextAreaElement>(null);

然後將CopilotTextarea元件加入評論表單中,並使用CopilotKit包裹表單,如下所示。

<CopilotKit url="/api/copilotkit">
        <form
          action={""}
          className="border border-teal-500 rounded-md p-3 mb-4">
          <textarea
            id="content"
            name="content"
            placeholder="Add a comment..."
            rows={3}
            onChange={(e) => setComment(e.target.value)}
            value={comment}
            className="hidden"
          />

          <CopilotTextarea
            className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            onChange={(event) => setComment(event.target.value)}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleContent,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />

          <div className="flex justify-between items-center mt-5">
            <button
              type="submit"
              className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
              Submit
            </button>
          </div>
        </form>
      </CopilotKit>

之後,執行開發伺服器並導航到 http://localhost:3000/createpost。您應該會看到彈出的應用程式內聊天機器人和 CopilotTextarea 已整合到部落格中。

圖片描述

恭喜!您已成功將 CopilotKit 新增至部落格前端

將 CopilotKit 後端加入博客

在這裡,我將引導您完成部落格與 CopilotKit 後端的整合過程,CopilotKit 後端處理來自前端的請求,並提供函數呼叫和各種 LLM 後端(例如 GPT)。

此外,我們將整合一個名為 Tavily 的人工智慧代理,它可以研究網路上的任何主題。

首先,在根目錄中建立一個名為.env.local的檔案。然後在保存ChatGPTTavily Search API 金鑰的檔案中加入下面的環境變數。

OPENAI_API_KEY="Your ChatGPT API key"
TAVILY_API_KEY="Your Tavily Search API key"

若要取得 ChatGPT API 金鑰,請導覽至 https://platform.openai.com/api-keys

圖片描述

若要取得 Tavilly Search API 金鑰,請導覽至 https://app.tavily.com/home

圖片描述

之後,轉到/[root]/src/app並建立一個名為api的資料夾。在api資料夾中,建立一個名為copilotkit的資料夾。

copilotkit資料夾中,建立一個名為research.ts的檔案。然後導航到該 Research.ts gist 文件,複製程式碼,並將其新增至research.ts檔案中

接下來,在/[root]/src/app/api/copilotkit資料夾中建立一個名為route.ts的檔案。該文件將包含設定後端功能來處理 POST 請求的程式碼。它有條件地包括對給定主題進行研究的“研究”操作。

現在在文件頂部導入以下模組。

import { CopilotBackend, OpenAIAdapter } from "@copilotkit/backend"; // For backend functionality with CopilotKit.
import { researchWithLangGraph } from "./research"; // Import a custom function for conducting research.
import { AnnotatedFunction } from "@copilotkit/shared"; // For annotating functions with metadata.

在上面的程式碼下面,定義一個執行時間環境變數和一個名為researchAction的函數,該函數使用下面的程式碼研究某個主題。

// Define a runtime environment variable, indicating the environment where the code is expected to run.
export const runtime = "edge";

// Define an annotated function for research. This object includes metadata and an implementation for the function.
const researchAction: AnnotatedFunction<any> = {
  name: "research", // Function name.
  description: "Call this function to conduct research on a certain topic. Respect other notes about when to call this function", // Function description.
  argumentAnnotations: [ // Annotations for arguments that the function accepts.
    {
      name: "topic", // Argument name.
      type: "string", // Argument type.
      description: "The topic to research. 5 characters or longer.", // Argument description.
      required: true, // Indicates that the argument is required.
    },
  ],
  implementation: async (topic) => { // The actual function implementation.
    console.log("Researching topic: ", topic); // Log the research topic.
    return await researchWithLangGraph(topic); // Call the research function and return its result.
  },
};

然後在上面的程式碼下加入下面的程式碼來定義處理POST請求的非同步函數。

// Define an asynchronous function that handles POST requests.
export async function POST(req: Request): Promise<Response> {
  const actions: AnnotatedFunction<any>[] = []; // Initialize an array to hold actions.

  // Check if a specific environment variable is set, indicating access to certain functionality.
  if (process.env["TAVILY_API_KEY"]) {
    actions.push(researchAction); // Add the research action to the actions array if the condition is true.
  }

  // Instantiate CopilotBackend with the actions defined above.
  const copilotKit = new CopilotBackend({
    actions: actions,
  });

  // Use the CopilotBackend instance to generate a response for the incoming request using an OpenAIAdapter.
  return copilotKit.response(req, new OpenAIAdapter());
}

恭喜!您已成功將 CopilotKit 後端新增至 Blog

使用 Supabase 將資料庫整合到博客

在本節中,我將引導您完成將部落格與 Supabase 資料庫整合以插入和獲取部落格文章和評論資料的過程。

首先,導覽至supabase.com並點擊主頁上的「啟動您的專案」按鈕。

圖片描述

然後新建一個專案,名為AiBloggingPlatform,如下圖所示。

圖片描述

建立專案後,將 Supabase URL 和 API 金鑰新增至env.local檔案中的環境變數中,如下所示。

NEXT_PUBLIC_SUPABASE_URL=”Your Supabase URL”
NEXT_PUBLIC_SUPABASE_ANON_KEY=”Your Supabase API Key”

為部落格設定 Supabase 身份驗證

在這裡,我將引導您完成為部落格設定身份驗證的過程,使用戶能夠註冊、登入或登出。

首先,前往/[root]/src/並建立一個名為utils資料夾。在utils資料夾中,建立一個名為supabase的資料夾。

然後在supabase資料夾中建立兩個名為client.tsserver.ts檔案。

之後,導航到此supabase 文件連結,複製此處提供的程式碼,並將其貼上到您在supabase資料夾中建立的相應文件中。

圖片描述

接下來,在專案根目錄中建立一個名為middleware.ts的文件,並在/[root]/src/utils/supabase資料夾中建立另一個同名文件。

之後,導航到此supabase 文件連結,複製此處提供的程式碼,並將其貼上到相應的middleware.ts文件中。

圖片描述

接下來,前往/[root]/src/app/login資料夾並建立一個名為actions.ts的檔案。之後,導航到此supabase 文件連結,複製此處提供的程式碼,並將其貼上到actions.ts

圖片描述

之後,更改電子郵件範本以支援身份驗證流程。為此,請前往 Supabase 儀表板中的驗證範本頁面。

Confirm signup模板中,將{{ .ConfirmationURL }}更改為{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=signup如下所示,然後按一下儲存按鈕。

圖片描述

之後,建立路由處理程序以使用電子郵件進行身份驗證確認為此**,請前往** /[root]/src/app/並建立一個名為auth的資料夾。然後在auth資料夾中建立一個名為confirm資料夾。

confirm資料夾中,建立一個名為route.ts檔案並導航到此supabase文件連結,複製那裡提供的程式碼,並將其貼到route.ts檔案中。

圖片描述

之後,請前往/[root]/src/app/login/page.tsx檔案並使用下面的程式碼片段匯入 Supabase signuplogin功能。

import { login, signup } from "./actions";

在註冊/登入表單中,按一下登入和註冊按鈕時,使用 formAction 呼叫 Supabase signuplogin函數,如下所示。

<button
    className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
    formAction={login}>
    Log in
</button>
<button
    className="bg-blue-500 text-white font-bold py-2 px-4 rounded"
    formAction={signup}>
    Sign up
</button>

之後,執行開發伺服器並導航至http://localhost:3000/login 。如下所示新增電子郵件和密碼,然後按一下「註冊」按鈕。

圖片描述

然後轉到您用於註冊的電子郵件的收件匣,然後按一下確認您的電子郵件按鈕,如下所示。

圖片描述

之後,請前往 Supabase 儀表板中的「身份驗證用戶」頁面,您應該會看到新建立的用戶,如下所示。

圖片描述

接下來,設定註銷功能。為此,請前往/[root]/src/app並建立一個名為logout的資料夾。然後建立一個名為route.ts的檔案並將以下程式碼加入該檔案。

// Server-side code
"use server";

// Importing Next.js functions
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

// Importing Supabase client creation function from utils
import { createClient } from "@/utils/supabase/server";

// Exporting the logout function
export async function logout() {
  // Creating a Supabase client instance
  const supabase = createClient();

  // Signing out of Supabase auth
  const { error } = await supabase.auth.signOut();

  // If there's an error, redirect to the error page
  if (error) {
    redirect("/error");
  }

  // Revalidate the "/" path for the "layout" cache
  revalidatePath("/", "layout");

  // Redirect to the homepage
  redirect("/");
}

之後,請前往/[root]/src/app/components/Header.tsx檔案並使用下面的程式碼片段匯入 Supabase 登出功能。

import { logout } from "../logout/actions";

然後在action參數中加入註銷功能,如下所示。

<form action={logout}>
    <button
        className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
        type="submit">
        Logout
    </button>
</form>

如果按一下「登出」按鈕,已登入的使用者將被登出。

設定部落格的使用者角色和權限

在這裡,我將引導您完成設定使用者角色和權限的過程,以控制不同使用者可以在部落格上執行的操作。

首先,前往/[root]/src/app/components/Header.tsx檔案並導入 Supabase createClient 函數。

import { createClient } from "@/utils/supabase/client";

然後導入useStateuseEffect掛鉤,並使用下面的程式碼片段定義一個名為 user 的類型。

import { useEffect, useState } from "react";

type User = {};

在 Header 功能元件內,新增以下程式碼,程式碼使用useState掛鉤來儲存useradmin資料,並使用useEffect掛鉤在元件安裝時從 Supabase auth 取得使用者資料。 getUser函數檢查錯誤並相應地設定使用者和管理員狀態。

// State variables for user and admin
const [user, setUser] = useState<User | null>(null);
const [admin, setAdmin] = useState<User | null>(null);

// useEffect hook to fetch user data on mount
useEffect(() => {
  // Define an async function to get the user
  async function getUser() {
    // Create a Supabase client instance
    const supabase = createClient();

    // Get the user data from Supabase auth
    const { data, error } = await supabase.auth.getUser();

    // If there's an error or no user data, log a message
    if (error || !data?.user) {
      console.log("No User");
    } 
    // If user data is available, set the user state
    else {
      setUser(data.user);
    }

    // Define the email of the signed-up user
    const userEmail = "email of signed-up user";

    // Check if the user is an admin (email matches)
    if (!data?.user || data.user?.email !== userEmail) {
      console.log("No Admin");
    } 
    // If the user is an admin, set the admin state
    else {
      setAdmin(data.user);
    }
  }
  // Call the getUser function
  getUser();
}, []); // Dependency array is empty, so the effect runs only once on mount

之後,更新導覽列程式碼,如下所示。更新後的程式碼會根據是否有登入使用者或登入使用者是管理員來控制將呈現哪些按鈕。

<div id="navbar-collapse-with-animation" className="">
  {/* Navbar content container */}
  <div className="flex flex-col gap-y-4 gap-x-0 mt-5 sm:flex-row sm:items-center sm:justify-end sm:gap-y-0 sm:gap-x-7 sm:mt-0 sm:ps-7">
    {/* Conditional rendering for admin link */}
    {admin ? (
      // If admin is true, show the "Create Post" link
      <Link
        className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
        href="/createpost">
        Create Post
      </Link>
    ) : (
      // If admin is false, render an empty div
      <div></div>
    )}

    {/* Conditional rendering for user link/logout button */}
    {user ? (
      // If user is true, show the logout button
      <form action={logout}>
        <button
          className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
          type="submit">
          Logout
        </button>
      </form>
    ) : (
      // If user is false, show the "Login" link
      <Link
        className="flex items-center font-medium text-gray-500 border-2 border-indigo-600 text-center p-2 rounded-md hover:text-blue-600 sm:border-s sm:my-6 "
        href="/login">
        Login
      </Link>
    )}
  </div>
</div>

如果導航到http://localhost:3000,您應該會看到僅呈現「建立貼文」和「登出」按鈕,因為使用者已登入並設定為管理員。

圖片描述

之後,請前往/[root]/src/app/createpost/page.tsx檔案並匯入 Supabase createClient 函數。

import { createClient } from "@/utils/supabase/client";

在 WriteArticle 功能元件中,新增以下程式碼,使用 Supabase createClient 函數取得登入用戶,並驗證用戶的電子郵件是否與設定為管理員的用戶的電子郵件相同。

// Define the email of the user you want to make admin
const userEmail = "email of admin user";

// Create a Supabase client instance
const supabase = createClient();

// Get the user data from Supabase auth
const { data, error } = await supabase.auth.getUser();

// Check for errors or if the user data doesn't match the expected email
if (error || !data?.user || data?.user.email !== userEmail) {
  // If any of the conditions are true, redirect to the homepage
  redirect("/");
}

現在只有設定為admin的使用者才能存取http://localhost:3000/createpost頁面,如下所示。

圖片描述

使用 Supabase 功能設定插入和取得發布資料

在這裡,我將引導您完成使用 Supabase 資料庫向部落格設定插入和取得資料功能的過程。

首先,請前往 Supabase 儀表板中的SQL 編輯器頁面。然後將下列 SQL 程式碼新增至編輯器中,然後按一下 CTRL + Enter 鍵建立一個名為articles 的表。

articles 表包含 id、title、slug、content 和created_at 欄位。

create table if not exists
  articles (
    id bigint primary key generated always as identity,
    title text,
    slug text,
    content text,
    created_at timestamp 
  );

建立表格後,您應該會收到一條成功訊息,如下所示。

圖片描述

之後,請前往/[root]/src/utils/supabase資料夾並建立一個名為AddArticle.ts的檔案。然後將以下程式碼新增至該檔案中,將部落格文章資料插入 Supabase 資料庫。

// Server-side code
"use server";

// Importing Supabase auth helpers and Next.js functions
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

// Exporting the addArticle function
export async function addArticle(formData: any) {
  // Extracting form data
  const title = formData.get("title");
  const content = formData.get("content");
  const slug = formData
    .get("title")
    .split(" ")
    .join("-")
    .toLowerCase()
    .replace(/[^a-zA-Z0-9-]/g, ""); // Generating a slug from the title
  const created_at = formData.get(new Date().toDateString()); // Getting the current date

  // Creating a cookie store
  const cookieStore = cookies();

  // Creating a Supabase client instance with cookie store
  const supabase = createServerComponentClient({ cookies: () => cookieStore });

  // Inserting data into the "articles" table
  const { data, error } = await supabase.from("articles").insert([
    {
      title,
      content,
      slug,
      created_at,
    },
  ]);

  // Handling errors
  if (error) {
    console.error("Error inserting data", error);
    return;
  }

  // Redirecting to the homepage
  redirect("/");

  // Returning a success message
  return { message: "Success" };
}

接下來,前往/[root]/src/app/components/Post.tsx檔案並導入 addArticle 函數。


import { addArticle } from "@/utils/supabase/AddArticle";

然後加入addArticle函數作為表單動作參數,如下所示。

<form
   action={addArticle}
   className="w-full h-full gap-10 flex flex-col items-center p-10">

</form>

之後,導覽至 http://localhost:3000/createpost 並向右側的聊天機器人提供類似「研究有關 JavaScript 框架的部落格文章主題」的提示。

聊天機器人將開始研究該主題,然後產生部落格標題和大綱,如下所示。

圖片描述

當您開始在 CopilotKitTextarea 上書寫時,您應該會看到內容自動建議,如下所示。

{% 嵌入 https://youtu.be/2oMNV1acKIs %}

如果內容符合您的喜好,請將其複製並貼上到 Quill 富文本編輯器。然後開始編輯它,如下所示。

{% 嵌入 https://youtu.be/-r3woCeq4vs %}

然後點擊底部的發布按鈕即可發布文章。前往 Supabase 上專案的儀表板並導航至「表格編輯器」部分。點擊文章表,您應該會看到您的文章資料已插入到 Supabase 資料庫中,如下所示。

圖片描述

接下來,前往/[root]/src/app/components/Posts.tsx檔案並導入 createClient 函數。

import { createClient } from "@/utils/supabase/client";

在 Posts 功能元件內,加入以下程式碼,該程式碼使用 useState 掛鉤來儲存文章資料,並使用 useEffect 掛鉤在元件安裝時從 Supabase 取得文章。 fetchArticles 函數建立一個 Supabase 用戶端實例,取得文章,並在資料可用時更新狀態。

// State variable for articles
const [articles, setArticles] = useState<any[]>([]);

// useEffect hook to fetch articles on mount
useEffect(() => {
  // Define an async function to fetch articles
  const fetchArticles = async () => {
    // Create a Supabase client instance
    const supabase = createClient();

    // Fetch articles from the "articles" table
    const { data, error } = await supabase.from("articles").select("*");

    // If data is available, update the articles state
    if (data) {
      setArticles(data);
    }
  };

  // Call the fetchArticles function
  fetchArticles();
}, []); // Dependency array is empty, so the effect runs only once on mount

之後,如下所示更新元素程式碼,以在部落格主頁上呈現已發佈的文章。

// Return a div element with a max width, full height, padding, and horizontal margin
return (
  <div className="max-w-[85rem] h-full  px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
    // Create a grid container with dynamic number of columns based on screen size
    <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
      // Map over the articles array and render a Link component for each item
      {articles?.map((post) => (
        <Link
          // Assign a unique key prop to each Link component
          key={post.id}
          // Apply styles for the Link component
          className="group flex flex-col h-full bg-gray-800 border border-gray-200 hover:border-transparent hover:shadow-lg transition-all duration-300 rounded-xl p-5 "
          // Set the href prop to the post slug
          href={`/${post.slug}`}>
          // Create a container for the image
          <div className="aspect-w-16 aspect-h-11">
            // Render an Image component with a dynamic src based on the post title
            <Image
              className="object-cover h-48 w-96 rounded-xl"
              src={`(link unavailable){encodeURIComponent(
                post.title
              )}`}
              // Set the width and height props for the Image component
              width={500}
              height={500}
              // Set the alt prop for accessibility
              alt="Image Description"
            />
          </div>
          // Create a container for the post title
          <div className="my-6">
            // Render an h3 element with the post title
            <h3 className="text-xl font-semibold text-white ">
              {post.title}
            </h3>
          </div>
        </Link>
      ))}
    </div>
  </div>
);

然後導航到http://localhost:3000 ,您應該會看到您發布的文章,如下所示。

圖片描述

接下來,前往/[root]/src/app/[slug]/page.tsx檔案並匯入 createClient 函數。

import { createClient } from "@/utils/supabase/client";

在導入下方,定義一個名為「getArticleContent」的非同步函數,該函數根據 slug 參數從 supabase 資料庫檢索文章資料,如下所示。

// Define an asynchronous function to retrieve article content
async function getArticleContent(params: any) {
  // Extract the slug parameter from the input params object
  const { slug } = params;

  // Create a Supabase client instance
  const supabase = createClient();

  // Query the "articles" table in Supabase
  // Select all columns (*)
  // Filter by the slug column matching the input slug
  // Retrieve a single record (not an array)
  const { data, error } = await supabase
    .from("articles")
    .select("*")
    .eq("slug", slug)
    .single();

  // Return the retrieved article data
  return data;
}

之後,如下所示更新功能元件Post,以渲染文章內容。

export default async function Post({ params }: { params: any }) {
  // Fetch the post content using the getArticleContent function
  const post = await getArticleContent(params);

  // Return the post component
  return (
    // Fragment component to wrap multiple elements
    <>
      // Header component
      <Header />
      // Main container with max width and height
      <main className="p-3 flex flex-col max-w-6xl mx-auto min-h-screen">
        // Post title
        <h1 className="text-3xl text-white mt-10 p-3 text-center font-serif max-w-2xl mx-auto lg:text-4xl">
          {post && post.title} // Display post title if available
        </h1>
        // Post metadata (author, date, etc.)
        <div className="flex justify-between text-white p-3 border-b border-slate-500 mx-auto w-full max-w-2xl text-xs">
          <span></span>
          // Estimated reading time
          <span className="italic">
            {post && (post.content.length / 1000).toFixed(0)} mins read
          </span>
        </div>
        // Post content
        <div
          className="p-3 max-w-2xl text-white mx-auto w-full post-content border-b border-slate-500 mb-2"
          // Use dangerouslySetInnerHTML to render HTML content
          dangerouslySetInnerHTML={{ __html: post && post.content }}></div>
        // Comment component
        <Comment />
      </main>
    </>
  );
}

導覽至http://localhost:3000並點擊部落格主頁上顯示的文章。然後您應該被重定向到文章的內容,如下所示。

圖片描述

使用 Supabase 功能設定插入和取得評論資料

在這裡,我將引導您完成使用 Supabase 資料庫為部落格內容評論設定插入和獲取資料功能的過程。

首先,請前往 Supabase 儀表板中的SQL 編輯器頁面。然後將以下 SQL 程式碼新增至編輯器中,然後按一下 CTRL + Enter 鍵建立一個名為 comments 的表。評論表有 id、content 和 postId 欄位。

create table if not exists
  comments (
    id bigint primary key generated always as identity,
    postId text,
    content text,
  );

建立表格後,您應該會收到一條成功訊息,如下所示。

圖片描述

之後,請前往/[root]/src/utils/supabase資料夾並建立一個名為AddComment.ts的檔案。然後將以下程式碼新增至該檔案中,將部落格文章評論資料插入 Supabase 資料庫。

// Importing necessary functions and modules for server-side operations
"use server";
import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

// Define an asynchronous function named 'addComment' that takes form data as input
export async function addComment(formData: any) {
  // Extract postId and content from the provided form data
  const postId = formData.get("postId");
  const content = formData.get("content");

  // Retrieve cookies from the HTTP headers
  const cookieStore = cookies();

  // Create a Supabase client configured with the provided cookies
  const supabase = createServerComponentClient({ cookies: () => cookieStore });

  // Insert the article data into the 'comments' table on Supabase
  const { data, error } = await supabase.from("comments").insert([
    {
      postId,
      content,
    },
  ]);

  // Check for errors during the insertion process
  if (error) {
    console.error("Error inserting data", error);
    return;
  }

  // Redirect the user to the home page after successfully adding the article
    redirect("/");

  // Return a success message
  return { message: "Success" };
}

接下來,到/[root]/src/app/components/Comment.tsx文件,導入 addArticle createClient 函數。


import { addComment } from "@/utils/supabase/AddComment";

import { createClient } from "@/utils/supabase/client";

然後將postId作為prop參數加入Comment功能元件中。

export default function Comment({ postId }: { postId: any }) {}

在函數內部,新增以下程式碼,程式碼使用useEffect掛鉤在元件安裝或postId更改時從 Supabase 取得評論和文章內容。 fetchComments函數取得所有評論,而fetchArticleContent函數則取得具有目前postId的文章內容。

useEffect(() => {
  // Define an async function to fetch comments
  const fetchComments = async () => {
    // Create a Supabase client instance
    const supabase = createClient();
    // Fetch comments from the "comments" table
    const { data, error } = await supabase.from("comments").select("*");
    // If data is available, update the comments state
    if (data) {
      setComments(data);
    }
  };

  // Define an async function to fetch article content
  const fetchArticleContent = async () => {
    // Create a Supabase client instance
    const supabase = createClient();
    // Fetch article content from the "articles" table
    // Filter by the current postId
    const { data, error } = await supabase
      .from("articles")
      .select("*")
      .eq("id", postId)
      .single();
    // If the fetched article ID matches the current postId
    if (data?.id == postId) {
      // Update the article content state
      setArticleContent(data.content);
    }
  };

  // Call the fetch functions
  fetchArticleContent();
  fetchComments();
}, [postId]); // Dependency array includes postId, so the effect runs when postId changes

然後新增addComment函數作為表單操作參數,如下所示。

<form
          action={addComment}
          className="border border-teal-500 rounded-md p-3 mb-4">
          <textarea
            id="content"
            name="content"
            placeholder="Add a comment..."
            rows={3}
            onChange={(e) => setComment(e.target.value)}
            value={comment}
            className="hidden"
          />

          <CopilotTextarea
            className="p-4 w-full rounded-lg mb-2 border text-sm border-gray-600 bg-gray-700 text-white placeholder-gray-400 focus:border-cyan-500 focus:ring-cyan-500 resize-none"
            ref={copilotTextareaRef}
            placeholder="Start typing for content autosuggestion."
            onChange={(event) => setComment(event.target.value)}
            rows={5}
            autosuggestionsConfig={{
              textareaPurpose: articleContent,
              chatApiConfigs: {
                suggestionsApiConfig: {
                  forwardedParams: {
                    max_tokens: 5,
                    stop: ["\n", ".", ","],
                  },
                },
                insertionApiConfig: {},
              },
              debounceTime: 250,
            }}
          />
          <input
            type="text"
            id="postId"
            name="postId"
            value={postId}
            className="hidden"
          />
          <div className="flex justify-between items-center mt-5">
            <button
              type="submit"
              className="bg-blue-500 text-white font-bold py-2 px-4 rounded">
              Submit
            </button>
          </div>
        </form>

在表單元素下方,新增以下用於呈現貼文評論的程式碼。

{comments?.map(
          (postComment: any) =>
            postComment.postId == postId && (
              <div
                key={postComment.id}
                className="flex p-4 border-b dark:border-gray-600 text-sm">
                <div className="flex-shrink-0 mr-3">
                  <Image
                    className="w-10 h-10 rounded-full bg-gray-200"
                    src={`https://source.unsplash.com/featured/?${encodeURIComponent(
                      "Silhouette"
                    )}`}
                    width={500}
                    height={500}
                    alt="Profile Picture"
                  />
                </div>
                <div className="flex-1">
                  <div className="flex items-center mb-1">
                    <span className="font-bold text-white mr-1 text-xs truncate">
                      Anonymous
                    </span>
                  </div>
                  <p className="text-gray-500 pb-2">{postComment.content}</p>
                </div>
              </div>
            )
        )}

接下來,前往/[root]/src/app/[slug]/page.tsx檔案並將 postId 作為 prop 新增至 Comment 元件。

<Comment postId={post && [post.id](http://post.id/)} />

前往已發佈的文章內容頁面並開始在文字區域中輸入評論。您應該在鍵入時獲得內容自動建議。

圖片描述

然後點擊提交按鈕以加入您的評論。前往 Supabase 上專案的儀表板並導航到表格編輯器部分。點擊評論表,您應該會看到您的評論資料已插入到 Supabase 資料庫中,如下所示。

圖片描述

返回您發表評論的文章內容頁面,您應該會看到您的評論,如下所示。

圖片描述

恭喜!您已完成本教學的專案。

結論

CopilotKit是一款令人難以置信的工具,可讓您在幾分鐘內將 AI Copilot 加入到您的產品中。無論您是對人工智慧聊天機器人和助理感興趣,還是對複雜任務的自動化感興趣,CopilotKit 都能讓您輕鬆實現。

如果您需要建立 AI 產品或將 AI 工具整合到您的軟體應用程式中,您應該考慮 CopilotKit。

您可以在 GitHub 上找到本教學的源程式碼:https://github.com/TheGreatBonnie/aiblogapp


原文出處:https://dev.to/copilotkit/im-building-an-ai-powered-blog-nextjs-langchain-supabase-5145


共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈