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

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

การตั้งค่าโครงการ
ก่อนอื่นเราจะต้องการสร้างโปรเจ็กต์ 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 จุดสิ้นสุดมีคุณสมบัติตัวเลือก เรารู้ว่าคำขอการโพลได้ส่งคืนเรื่องราวที่สร้างขึ้นสำเร็จ ดังนั้นส่วนประกอบจึงหยุดการโพลและแสดงข้อความเรื่องราวโดยแบ่งออกเป็นย่อหน้าและแสดงผลแต่ละย่อหน้าแยกกัน
ขอช่วง
ไฟล์:
ก่อนอื่น เราจะสร้างการติดต่อกลับ สำรวจและสร้างไฟล์ รวมถึงการใช้งานไลบรารี Redis และ QStash
ไฟล์:
ก่อนอื่นเราตรวจสอบก่อนว่าวิธีการร้องขอคือ
ต่อไปเราเรียก
จากนั้นเราจะส่งคืนรหัสข้อความของคำขอหากเป็น
ไฟล์:
ก่อนอื่น เราพยายามถอดรหัสเนื้อความของคำขอที่เข้ามา ซึ่งจะเป็นสตริงที่เข้ารหัส base64 และหากสำเร็จ ก็จะเก็บสตริงที่ถอดรหัสไว้ใน Redis ภายใต้คีย์เดียวกับที่ส่งคืนเมื่อเราส่งคำขอเริ่มต้นไปยัง QStash
สุดท้ายนี้ เราจะส่งการตอบกลับพร้อมรหัสสถานะ 200 (บ่งบอกถึงความสำเร็จ) รวมถึงสตริงที่ถอดรหัสแล้ว หากเกิดข้อผิดพลาดใดๆ เราจะตอบกลับด้วยรหัสสถานะ 500 (ระบุข้อผิดพลาดเซิร์ฟเวอร์ภายใน) และข้อความแสดงข้อผิดพลาด
ไฟล์:
ก่อนอื่น เราทำลายโครงสร้าง
หากพบว่าข้อมูลเป็นของคีย์ที่กำหนด ระบบจะส่งการตอบกลับพร้อมรหัสสถานะ 200 (บ่งบอกถึงความสำเร็จ) และข้อมูลที่พบไปด้วย หากมีข้อผิดพลาดเกิดขึ้น เราจะตอบกลับด้วยรหัสสถานะ 500 (ระบุถึงข้อผิดพลาดเซิร์ฟเวอร์ภายใน) และข้อความแสดงข้อผิดพลาดที่เกี่ยวข้อง
ต่อไป เราจะสร้างไฟล์สองไฟล์สำหรับสร้างไคลเอนต์ QStash และ Redis ซึ่งใช้ภายในกระบวนการสร้างเรื่องราว ไฟล์ทั้งสองส่งออกวัตถุที่ใช้ในการโต้ตอบกับบริการภายนอกที่เกี่ยวข้อง
ไฟล์:
ไคลเอ็นต์ QStash เริ่มต้นได้ด้วยโทเค็นที่จัดเก็บไว้ใน
ไฟล์:
ไคลเอ็นต์ Redis เริ่มต้นได้ด้วย URL และโทเค็นที่จัดเก็บไว้ใน
ด้วย API การสำเร็จของ OpenAI รวมถึง QStash และ Redis ของ Upstash การสร้างเรื่องราวที่กำหนดเองโดยใช้การประมวลผลภาษาธรรมชาติจึงเป็นเรื่องง่าย เมื่อทำตามบทช่วยสอนนี้ ตอนนี้คุณควรจะสามารถตั้งค่าระบบของคุณเองสำหรับการสร้างเรื่องราวโดยใช้เครื่องมือเหล่านี้ และทำการเปลี่ยนแปลงและปรับปรุงของคุณเองได้
คุณสามารถดูซอร์สโค้ดทั้งหมดได้ที่นี่
ต่อไปนี้คือแนวคิดบางส่วนเกี่ยวกับสิ่งที่คุณควรทำต่อไปโดยใช้เครื่องมือสร้างเรื่องราวนี้เป็นจุดเริ่มต้น:
มีความเป็นไปได้และคำแนะนำมากมายที่คุณสามารถทำได้ ดังนั้นขอให้สนุกและสนุกกับกระบวนการนี้ คุณยังสามารถใช้งานนี้เป็นฐานสำหรับโปรเจ็กต์อื่น ๆ ที่สามารถใช้ประโยชน์จาก OpenAI, QStash และ Redis ได้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
การสร้างเรื่องราว
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 API160 โทรสำเร็จซึ่งจะใช้ในการโพลเพื่อตรวจสอบว่าคำขอเสร็จสิ้นเมื่อใด หากมีข้อผิดพลาดเกิดขึ้น ระบบจะส่งการตอบกลับพร้อมรหัสสถานะ 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 });
}
}การสำรวจความคิดเห็น
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 (ระบุว่าไม่พบทรัพยากรที่ร้องขอ) และข้อความระบุเช่นนั้นห้องสมุด
212รหัส> ป> import { Client } from "@upstash/qstash";
const qstashClient = new Client({
token: process.env.QSTASH_TOKEN as string,
});
export default qstashClient;222 ตัวแปรสภาพแวดล้อม อ็อบเจ็กต์นี้สามารถใช้เพื่อส่งคำขอ HTTP ไปยังบริการ Upstash QStash235รหัส> ป> 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;249 และ 259 ตัวแปรสภาพแวดล้อมตามลำดับ ออบเจ็กต์นี้สามารถใช้เพื่อจัดเก็บและเรียกข้อมูลในฐานข้อมูล Redis ผ่านทาง Upstash Redis REST APIบทสรุป
การปรับปรุงเพิ่มเติม