Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

สร้างเครื่องสร้างเรื่องราวที่ขับเคลื่อนด้วย AI ด้วย OpenAI, Upstash และ Next.js

สำหรับโพสต์บนบล็อกนี้ เราจะตั้งสมมติฐานบางประการก่อนดำเนินการต่อ แต่คุณควรมี:

  • บัญชี Upstash ที่คุณได้สร้างอินสแตนซ์ Redis และ QStash
  • บัญชี OpenAI ที่สามารถเข้าถึงคีย์ API ของคุณ
  • โปรเจ็กต์ Next.js ที่เราจะสร้างฟังก์ชันสร้างเรื่องราว
  • บัญชี Vercel เพื่อปรับใช้โครงการของคุณ

บทนำ

คุณเคยต้องการที่จะสร้างเรื่องราวของคุณเองโดยใช้ AI หรือไม่? ด้วย API การสำเร็จของ OpenAI และ QStash และ Redis ของ Upstash ตอนนี้การสร้างเรื่องราวที่คุณกำหนดเองได้ง่ายกว่าที่เคยโดยใช้การประมวลผลภาษาธรรมชาติ ในบทช่วยสอนนี้ เราจะอธิบายขั้นตอนการตั้งค่าและการใช้เครื่องมือเหล่านี้เพื่อสร้างเรื่องราวที่ไม่ซ้ำใครและน่าสนใจ

สร้างเครื่องสร้างเรื่องราวที่ขับเคลื่อนด้วย AI ด้วย OpenAI, Upstash และ Next.js

ดูภาพแอปเพิ่มเติม:

  • สร้างแบบฟอร์มเรื่องราว
  • การสร้างสถานะเรื่องราว
  • แสดงเรื่องราวที่สร้างขึ้น

สถาปัตยกรรม

คุณมีแนวโน้มที่จะได้รับความเข้าใจที่ดีเกี่ยวกับวิธีการตั้งค่าแอปจากการดูโค้ด แต่เมื่อพิจารณาภาพรวมในระดับที่สูงขึ้นเล็กน้อย จะมีรูปภาพด้านล่างแสดงบางส่วนของขั้นตอนการใช้งานแอปพลิเคชันและวิธีการสื่อสาร

สร้างเครื่องสร้างเรื่องราวที่ขับเคลื่อนด้วย AI ด้วย OpenAI, Upstash และ Next.js

การตั้งค่าโครงการ

ก่อนอื่นเราจะต้องการสร้างโปรเจ็กต์ Next.js ซึ่งสามารถทำได้โดยการรันสิ่งต่อไปนี้เพื่อสร้างโปรเจ็กต์ Next.js ใหม่ด้วย TypeScript คุณสามารถดูขั้นตอนในการตั้งค่า Next.js ได้ที่นี่

เพื่อประโยชน์ของบทช่วยสอนนี้ เรายังได้ติดตั้ง Tailwind CSS (แบบฟอร์มและการพิมพ์ด้วย) ไว้ด้วย แต่นั่นเป็นทางเลือกโดยสิ้นเชิงและสำหรับการจัดรูปแบบแบบฟอร์มส่วนหน้าเท่านั้น

ต่อไปเราจะต้องการติดตั้งไลบรารี QStash และ Redis ของ Upstash ผ่านทางสิ่งต่อไปนี้:

npm install @upstash/qstash
npm install @upstash/redis

ตอนนี้คุณจะต้องการสร้าง 02 ไฟล์และเติมด้วยคีย์ต่อไปนี้ (และค่าจากตำแหน่งที่เกี่ยวข้อง)

SITE_URL=https://your-project-url.vercel.app
OPENAI_API_KEY=
QSTASH_TOKEN=
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

คุณสามารถค้นหาโทเค็น QStash และ Redis ได้ในคอนโซล Upstash, คีย์ OpenAI API ที่นี่ และ URL เว็บไซต์ของคุณในแดชบอร์ด Vercel เมื่อคุณสร้างโปรเจ็กต์และปรับใช้โปรเจ็กต์ Next.js พื้นฐานแล้ว

การตั้งค่าส่วนหน้า

ต่อไปเราจะสร้างหน้าและแบบฟอร์มสำหรับป้อนข้อความแจ้งเรื่องราว คุณจะต้องมีช่องข้อความสำหรับข้อความแจ้งและปุ่มส่ง

การสร้างเรื่องราว

ไฟล์:19

import { RefObject, useRef, useState } from "react";
import Head from "next/head";
 
import useInterval from "../hooks/useInterval";
 
export default function Home() {
 const [generating, setGenerating] = useState<boolean>(false);
 const [messageId, setMessageId] = useState<string | null>(null);
 const [story, setStory] = useState<string[]>([]);
 const themeRef: RefObject<HTMLInputElement> = useRef(null);
 const characterRef: RefObject<HTMLInputElement> = useRef(null);
 const moralRef: RefObject<HTMLInputElement> = useRef(null);
 
 useInterval(
 async () => {
 await fetch(`/api/poll?id=${messageId}`)
 .then((res: any) => res.json())
 .then((data: any) => {
 if (!data.choices) {
 return;
 }
 
 setGenerating(false);
 setMessageId(null);
 
 setStory(data.choices[0].text.split("\n\n"));
 })
 .catch((err: any) => console.error(err));
 },
 messageId ? 1000 : null,
 );
 
 async function generateStory(event: any) {
 event.preventDefault();
 
 setGenerating(true);
 
 await fetch("/api/create", {
 method: "POST",
 body: JSON.stringify({
 theme: themeRef.current?.value,
 character: characterRef.current?.value,
 moral: moralRef.current?.value,
 }),
 headers: { "Content-Type": "application/json" },
 })
 .then((res: any) => res.json())
 .then((data: any) => setMessageId(data.id))
 .catch((err: any) => console.error(err));
 }
 
 return (
 <>
 <Head>
 <title>StoryTime</title>
 <meta
 name="description"
 content="A simple Next.js application which allows you to create stories using AI."
 />
 <meta name="viewport" content="width=device-width, initial-scale=1" />
 <link rel="icon" href="/favicon.ico" />
 </Head>
 <main>
 <div className="my-16 flex flex-col items-center justify-center md:my-32">
 <h1 className="text-5xl font-black">StoryTime</h1>
 
 {story.length > 0 && (
 <div className="mx-auto mt-10 max-w-3xl">
 <div className="prose lg:prose-xl w-full">
 {story.map((paragraph: string, index: number) => (
 <p key={index}>{paragraph}</p>
 ))}
 </div>
 
 <div className="text-center">
 <button
 type="button"
 onClick={() => setStory([])}
 className="mt-6 inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2"
 >
 Start Over
 </button>
 </div>
 </div>
 )}
 
 {story.length == 0 && (
 <form
 onSubmit={generateStory}
 className="mt-10 flex w-full max-w-lg flex-col items-center"
 >
 <div className="w-full space-y-4">
 <div>
 <label htmlFor="theme" className="text-sm font-semibold">
 My story is about
 </label>
 <input
 name="theme"
 id="theme"
 type="text"
 className="mt-0.5 block w-full rounded-md border-gray-300 shadow-sm focus:border-gray-500 focus:ring-gray-500"
 placeholder="two friends going on an adventure"
 ref={themeRef}
 required
 />
 </div>
 <div>
 <label htmlFor="character" className="text-sm font-semibold">
 My main character is
 </label>
 <input
 name="character"
 id="character"
 type="text"
 className="mt-0.5 block w-full rounded-md border-gray-300 shadow-sm focus:border-gray-500 focus:ring-gray-500"
 placeholder="a dog named Spot"
 ref={characterRef}
 required
 />
 </div>
 <div>
 <label htmlFor="moral" className="text-sm font-semibold">
 The moral of my story is
 </label>
 <input
 name="moral"
 id="moral"
 type="text"
 className="mt-0.5 block w-full rounded-md border-gray-300 shadow-sm focus:border-gray-500 focus:ring-gray-500"
 placeholder="to always be kind"
 ref={moralRef}
 required
 />
 </div>
 </div>
 
 <button
 type="submit"
 disabled={generating}
 className="mt-6 inline-flex items-center rounded-full border border-transparent bg-gray-900 px-6 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 disabled:opacity-50"
 >
 {generating ? "Generating..." : "Generate"}
 </button>
 </form>
 )}
 </div>
 </main>
 </>
 );
}

ไฟล์นี้กำหนดองค์ประกอบ React ที่แสดงแบบฟอร์มที่อนุญาตให้ผู้ใช้ป้อนธีม ตัวละคร และศีลธรรมสำหรับเรื่องราว เมื่อส่งแบบฟอร์มแล้ว ระบบจะส่ง POST ขอไปที่ 24 จุดสิ้นสุดที่มีธีมที่ป้อน ลักษณะนิสัย และค่านิยมทางศีลธรรมเป็นร่างกาย

จากนั้นส่วนประกอบจะเข้าสู่สถานะการสำรวจโดยที่ส่ง GET ขอไปที่ 39 จุดสิ้นสุดทุกวินาทีพร้อมกับตัวระบุข้อความที่ได้รับในคำขอสร้างเรื่องราวก่อนหน้า ซึ่งช่วยให้เราสามารถติดตามคำขอสร้างเรื่องราวไปยังเรื่องราวที่เรากำลังสำรวจเพื่อตรวจสอบเมื่อ OpenAI สร้างเสร็จแล้ว

เมื่อมีการตอบกลับจาก 41 จุดสิ้นสุดมีคุณสมบัติตัวเลือก เรารู้ว่าคำขอการโพลได้ส่งคืนเรื่องราวที่สร้างขึ้นสำเร็จ ดังนั้นส่วนประกอบจึงหยุดการโพลและแสดงข้อความเรื่องราวโดยแบ่งออกเป็นย่อหน้าและแสดงผลแต่ละย่อหน้าแยกกัน

ขอช่วง

ไฟล์:54

import { useEffect, useRef } from "react";
 
function useInterval(callback: () => void, delay: number | null) {
 const savedCallback = useRef(callback);
 
 useEffect(() => {
 savedCallback.current = callback;
 }, [callback]);
 
 useEffect(() => {
 if (!delay && delay !== 0) {
 return;
 }
 
 const id = setInterval(() => savedCallback.current(), delay);
 
 return () => clearInterval(id);
 }, [delay]);
}
 
export default useInterval;

60 hook ใช้ 74 และ 81 hooks เพื่อจัดการช่วงเวลาและฟังก์ชันการโทรกลับที่ทำงานได้อย่างราบรื่นกับวงจรชีวิตของส่วนประกอบ React ตลอดจนมอบวิธีที่สะดวกในการจัดการช่วงเวลาและการโทรกลับภายในส่วนประกอบ React เพิ่มประสิทธิภาพการทำงาน และทำให้โค้ดเบสสามารถบำรุงรักษาได้มากขึ้นอีกเล็กน้อย คุณสามารถค้นหาข้อมูลเพิ่มเติมเกี่ยวกับเบ็ดนี้ได้ที่นี่และที่นี่

การตั้งค่า API

ก่อนอื่น เราจะสร้างการติดต่อกลับ สำรวจและสร้างไฟล์ รวมถึงการใช้งานไลบรารี Redis และ QStash

การสร้างเรื่องราว

ไฟล์:92

import type { NextApiRequest, NextApiResponse } from "next";
 
import qstashClient from "../../lib/qstash";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 if (req.method !== "POST") {
 return res.status(400).json({
 message: `Invalid request method: ${req.method}.`,
 });
 }
 
 const { theme, character, moral }: any = req.body;
 
 qstashClient
 .publishJSON({
 url: "https://api.openai.com/v1/completions",
 method: "POST",
 headers: {
 Authorization: `Bearer ${process.env.QSTASH_TOKEN}`,
 "Content-Type": "application/json",
 "Upstash-Callback": `${process.env.SITE_URL}/api/callback`,
 "Upstash-Forward-Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
 },
 body: {
 model: "text-davinci-003",
 prompt: `Write a children's story about ${theme}, which has a main character who is ${character} with the moral of the story being ${moral}.`,
 max_tokens: 500,
 temperature: 0.75,
 },
 })
 .then((data: any) => {
 return res.status(202).json({ id: data.messageId });
 })
 .catch((error: any) => {
 return res.status(500).json({ message: error.message });
 });
}

ก่อนอื่นเราตรวจสอบก่อนว่าวิธีการร้องขอคือ 107 และส่งการตอบกลับด้วยรหัสสถานะ 400 (บ่งชี้ถึงข้อผิดพลาดของไคลเอ็นต์) หากไม่เป็นเช่นนั้น จากนั้นเราจะดำเนินการทำลายโครงสร้างแก่นเรื่อง คุณลักษณะ และขอบเขตทางศีลธรรมออกจากเนื้อหาของคำขอ

ต่อไปเราเรียก 114 วิธีการบน 124 วัตถุซึ่งส่ง 133 ร้องขอ OpenAI API ด้วยเนื้อหา JSON ที่มีข้อความแจ้งให้สร้างเรื่องราวของเด็กตามคุณค่าของธีม ตัวละคร และคุณธรรม นอกจากนี้ยังตั้งค่าส่วนหัวหลายรายการ รวมถึงส่วนหัวการอนุญาตที่มีโทเค็นเก็บไว้ใน 146 ตัวแปรสภาพแวดล้อม และส่วนหัวการอนุญาตที่ส่งต่อสำหรับการผ่าน 156 ซึ่งจะใช้ร่วมกับคำขอ OpenAI API

จากนั้นเราจะส่งคืนรหัสข้อความของคำขอหากเป็น 160 โทรสำเร็จซึ่งจะใช้ในการโพลเพื่อตรวจสอบว่าคำขอเสร็จสิ้นเมื่อใด หากมีข้อผิดพลาดเกิดขึ้น ระบบจะส่งการตอบกลับพร้อมรหัสสถานะ 500 (ระบุข้อผิดพลาดเซิร์ฟเวอร์ภายใน) และข้อความแสดงข้อผิดพลาดที่เกี่ยวข้อง

โทรกลับ

ไฟล์:174

import type { NextApiRequest, NextApiResponse } from "next";
 
import redis from "../../lib/redis";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 const { body }: any = req;
 
 try {
 const decoded = Buffer.from(body.body, "base64").toString("utf-8");
 
 await redis.set(body.sourceMessageId, decoded);
 
 return res.status(200).send(decoded);
 } catch (error) {
 return res.status(500).json({ error });
 }
}

ก่อนอื่น เราพยายามถอดรหัสเนื้อความของคำขอที่เข้ามา ซึ่งจะเป็นสตริงที่เข้ารหัส base64 และหากสำเร็จ ก็จะเก็บสตริงที่ถอดรหัสไว้ใน Redis ภายใต้คีย์เดียวกับที่ส่งคืนเมื่อเราส่งคำขอเริ่มต้นไปยัง QStash

สุดท้ายนี้ เราจะส่งการตอบกลับพร้อมรหัสสถานะ 200 (บ่งบอกถึงความสำเร็จ) รวมถึงสตริงที่ถอดรหัสแล้ว หากเกิดข้อผิดพลาดใดๆ เราจะตอบกลับด้วยรหัสสถานะ 500 (ระบุข้อผิดพลาดเซิร์ฟเวอร์ภายใน) และข้อความแสดงข้อผิดพลาด

การสำรวจความคิดเห็น

ไฟล์:189

import type { NextApiRequest, NextApiResponse } from "next";
 
import redis from "../../lib/redis";
 
export default async function handler(
 req: NextApiRequest,
 res: NextApiResponse,
) {
 const { id }: any = req.query;
 
 try {
 const data = await redis.get(id);
 
 if (!data) {
 return res
 .status(404)
 .json({ message: "Data for supplied ID not found" });
 }
 
 return res.status(200).json(data);
 } catch (error: any) {
 return res.status(500).json({ message: error.message });
 }
}

ก่อนอื่น เราทำลายโครงสร้าง 190 จากวัตถุแบบสอบถามของคำขอ จากนั้นเราพยายามดึงข้อมูลที่จัดเก็บไว้ใน Redis ภายใต้ 209 ที่ถูกทำลาย และหากไม่พบข้อมูล ระบบจะส่งการตอบกลับพร้อมรหัสสถานะ 404 (ระบุว่าไม่พบทรัพยากรที่ร้องขอ) และข้อความระบุเช่นนั้น

หากพบว่าข้อมูลเป็นของคีย์ที่กำหนด ระบบจะส่งการตอบกลับพร้อมรหัสสถานะ 200 (บ่งบอกถึงความสำเร็จ) และข้อมูลที่พบไปด้วย หากมีข้อผิดพลาดเกิดขึ้น เราจะตอบกลับด้วยรหัสสถานะ 500 (ระบุถึงข้อผิดพลาดเซิร์ฟเวอร์ภายใน) และข้อความแสดงข้อผิดพลาดที่เกี่ยวข้อง

ห้องสมุด

ต่อไป เราจะสร้างไฟล์สองไฟล์สำหรับสร้างไคลเอนต์ QStash และ Redis ซึ่งใช้ภายในกระบวนการสร้างเรื่องราว ไฟล์ทั้งสองส่งออกวัตถุที่ใช้ในการโต้ตอบกับบริการภายนอกที่เกี่ยวข้อง

ไฟล์:212

import { Client } from "@upstash/qstash";
 
const qstashClient = new Client({
 token: process.env.QSTASH_TOKEN as string,
});
 
export default qstashClient;

ไคลเอ็นต์ QStash เริ่มต้นได้ด้วยโทเค็นที่จัดเก็บไว้ใน 222 ตัวแปรสภาพแวดล้อม อ็อบเจ็กต์นี้สามารถใช้เพื่อส่งคำขอ HTTP ไปยังบริการ Upstash QStash

ไฟล์:235

import { Redis } from "@upstash/redis";
 
const redis = new Redis({
 url: process.env.UPSTASH_REDIS_REST_URL as string,
 token: process.env.UPSTASH_REDIS_REST_TOKEN as string,
});
 
export default redis;

ไคลเอ็นต์ Redis เริ่มต้นได้ด้วย URL และโทเค็นที่จัดเก็บไว้ใน 249 และ 259 ตัวแปรสภาพแวดล้อมตามลำดับ ออบเจ็กต์นี้สามารถใช้เพื่อจัดเก็บและเรียกข้อมูลในฐานข้อมูล Redis ผ่านทาง Upstash Redis REST API

บทสรุป

ด้วย API การสำเร็จของ OpenAI รวมถึง QStash และ Redis ของ Upstash การสร้างเรื่องราวที่กำหนดเองโดยใช้การประมวลผลภาษาธรรมชาติจึงเป็นเรื่องง่าย เมื่อทำตามบทช่วยสอนนี้ ตอนนี้คุณควรจะสามารถตั้งค่าระบบของคุณเองสำหรับการสร้างเรื่องราวโดยใช้เครื่องมือเหล่านี้ และทำการเปลี่ยนแปลงและปรับปรุงของคุณเองได้

คุณสามารถดูซอร์สโค้ดทั้งหมดได้ที่นี่

การปรับปรุงเพิ่มเติม

ต่อไปนี้คือแนวคิดบางส่วนเกี่ยวกับสิ่งที่คุณควรทำต่อไปโดยใช้เครื่องมือสร้างเรื่องราวนี้เป็นจุดเริ่มต้น:

  • อัปเดตสไตล์ส่วนหน้าให้สวยงามน่าดึงดูดและมีสีสันมากขึ้น
  • เพิ่มการสร้างภาพ Dall-E โดยใช้ OpenAI ลงในเรื่องราวตามข้อความแจ้งที่กำหนด
  • เชื่อมต่อเอาต์พุตกับบริการพิมพ์หนังสือผ่าน API เพื่อให้ผู้ใช้สามารถสั่งซื้อหนังสือที่เป็นเล่มได้

มีความเป็นไปได้และคำแนะนำมากมายที่คุณสามารถทำได้ ดังนั้นขอให้สนุกและสนุกกับกระบวนการนี้ คุณยังสามารถใช้งานนี้เป็นฐานสำหรับโปรเจ็กต์อื่น ๆ ที่สามารถใช้ประโยชน์จาก OpenAI, QStash และ Redis ได้