🧞♂️ 生成器已解锁:使用 ChatGPT 和 NextJS 创建表情包 🚀 💥
TL;DR
TL;DR
在本教程中,你将学习如何在 Next.js 中构建一个 meme 生成器。我们将使用:
- Trigger.dev 用于在后台处理 meme 的创建。
- Supabase 用于存储和保存模因
- 重新发送以将生成的 meme 发送给用户。
NextJS 的后台作业管理
Trigger.dev 是一个开源库,可让您使用 NextJS、Remix、Astro 等为您的应用程序创建和监控长时间运行的作业!
如果您能花 10 秒钟给我们一颗星,我将非常感激 💖
https://github.com/triggerdotdev/trigger.dev
让我们开始吧🔥
在这里,我将引导您创建 meme 生成器应用程序的用户界面。
通过运行下面的代码片段创建一个新的 Next.js 项目。
npx create-next-app memes-generator
首先,您需要创建一个主页,其中包含一个表单,用户可以在其中输入 meme 的受众和主题。您还可以添加另一个字段,用于输入用户的电子邮件地址,以便在 meme 制作完成后接收。
在表单下方,用户应该能够查看最近生成的 meme,如下所示。
当用户提交表单时,他们将被重定向到提交页面,同时应用程序在后台生成 meme 并将其发送到用户的电子邮件中。
主页🏠
在继续之前,请运行下面的代码片段来安装 React Tag Input 包。它使我们能够为一个值提供多个输入。
npm install react-tag-input
将下面的代码片段复制到index.js
文件中。
"use client";
import { Space_Grotesk } from "next/font/google";
import { useState } from "react";
import { WithContext as ReactTags } from "react-tag-input";
import { useRouter } from "next/navigation";
import ViewMemes from "@/components/ViewMemes";
const KeyCodes = {
comma: 188,
enter: 13,
};
const inter = Space_Grotesk({ subsets: ["latin"] });
const delimiters = [KeyCodes.comma, KeyCodes.enter];
export default function Home() {
const [audience, setAudience] = useState("");
const [email, setEmail] = useState("");
const [memes, setMemes] = useState([]);
const router = useRouter();
const [topics, setTopics] = useState([
{ id: "Developers", text: "Developers" },
]);
const handleDelete = (i) =>
setTopics(topics.filter((topic, index) => index !== i));
const handleAddition = (topic) => setTopics([...topics, topic]);
const handleSubmit = (e) => {
e.preventDefault();
console.log({
audience,
topics,
email,
});
router.push("/submit");
};
return (
<main className={`w-full min-h-screen ${inter.className}`}>
<header className="bg-[#F8F0E5] min-h-[95vh] flex items-center justify-center flex-col">
<h2 className="text-4xl font-bold mb-2 text-[#0F2C59]">Meme Magic</h2>
<h3 className="text-lg opacity-60 mb-8">
Creating memes with a touch of magic
</h3>
<form
className="flex flex-col md:w-[70%] w-[95%]"
onSubmit={handleSubmit}
>
<label htmlFor="audience">Audience</label>
<input
type="text"
name="audience"
value={audience}
required
className="px-4 py-2 rounded mb-4"
onChange={(e) => setAudience(e.target.value)}
/>
<label htmlFor="email">Your email address</label>
<input
type="email"
name="email"
value={email}
required
className="px-4 py-2 rounded mb-4"
onChange={(e) => setEmail(e.target.value)}
/>
<ReactTags
tags={topics}
delimiters={delimiters}
handleDelete={handleDelete}
handleAddition={handleAddition}
inputFieldPosition="top"
autocomplete
placeholder="Enter a topic for the meme and press enter"
/>
<button
type="submit"
className="bg-[#0F2C59] text-[#fff] mt-[30px] py-4 rounded text-lg font-bold hover:bg-[#151d2b]"
>
GENERATE MEME
</button>
</form>
</header>
<ViewMemes memes={memes} />
</main>
);
}
该代码片段创建了输入字段,允许用户输入要生成的 meme 的电子邮件地址、受众和主题。该<ReactTags>
组件允许我们输入多个值作为 meme 主题。
接下来,创建一个包含该文件的组件文件夹ViewMemes.jsx
,其中显示所有最近生成的模因。
import React from "react";
import Image from "next/image";
const ViewMemes = ({ memes }) => {
return (
<section className="h-[100vh] px-4 py-10 flex items-center flex-col">
<h2 className="text-3xl font-bold mb-8">Recent Memes</h2>
<div className="w-full flex flex-wrap md:space-x-4">
{memes.map((meme) => (
<div
className="md:w-[30%] w-full m-2 flex flex-col items-center"
key={meme.id}
>
<Image
src={`${meme.meme_url}`}
alt={meme.name}
className="hover:scale-105 rounded"
width={400}
height={400}
/>
</div>
))}
</div>
</section>
);
};
export default ViewMemes;
表情包提交🐤
创建一个submit.js
文件,在用户提交表单后向用户显示“谢谢”消息。
import Link from "next/link";
import React from "react";
export default function Submit() {
return (
<div className="w-full h-screen flex flex-col items-center justify-center">
<h2 className="font-extrabold text-4xl mb-4 text-[#DAC0A3]">
Thank You!
</h2>
<p className="mb-6">
Your newly generated meme has been sent to your email.
</p>
<Link href="/">
<p className="text-[#662549]">Go Home</p>
</Link>
</div>
);
}
在后台生成表情包🐥
在本节中,我将指导您将 Trigger.dev 添加到 Next.js 项目。首先,您需要访问 Trigger.dev 主页 并创建一个新帐户。
接下来,为您的工作创建一个组织和项目名称。
最后,按照向您显示的步骤进行操作。
如果这不是您的初始 Trigger.dev 项目,请按照以下步骤操作。
点击Environments & API Keys
项目仪表板侧边栏菜单。
复制您的 DEV 服务器 API 密钥,并运行以下代码片段来安装 Trigger.dev。请仔细按照说明操作。
npx @trigger.dev/cli@latest init
启动您的 Next.js 项目。
npm run dev
另外,在另一个终端中,运行下面的代码片段以在 Trigger.dev 和 Next.js 项目之间创建隧道。
npx @trigger.dev/cli@latest dev
最后,将jobs/examples.js
文件重命名为jobs/functions.js
。所有作业都在这里处理。
恭喜!🎉您已成功将 Trigger.dev 添加到您的 Next.js 应用程序。
如何使用 OpenAI 和 ImgFlip 生成 meme 模板和标题
在本节中,您将学习如何从 ImgFlip获取 meme 模板以及如何通过 Trigger.dev 从OpenAI 生成 meme 标题 。
在我们继续之前,更新index.js
文件以将用户的输入发送到服务器。
//👇🏻 runs when a user submits the form
const handleSubmit = (e) => {
e.preventDefault();
if (topics.length > 0) {
let topic = "";
topics.forEach((tag) => {
topic += tag.text + ", ";
});
//👇🏻 sends the data to the backend
postData(topic);
router.push("/submit");
}
};
//👇🏻 sends the data to the backend server
const postData = async (topic) => {
try {
const data = await fetch("/api/api", {
method: "POST",
body: JSON.stringify({
audience,
topic,
email,
}),
headers: {
"Content-Type": "application/json",
},
});
const response = await data.json();
console.log(response);
} catch (err) {
console.error(err);
}
};
当用户提交表单时,该postData
函数将用户的输入发送到服务器上的端点。/api/api
接下来,在接受用户数据的文件夹api.js
中创建一个文件。api
export default function handler(req, res) {
const { topic, audience, email } = req.body;
if (topic && audience && email) {
console.log({ audience, topic, email });
}
res.status(200).json({ message: "Successful" });
}
模因背景工作👨🏻🔧
触发器通信有三种方式:webhook、schedule 和 event。webhook 在特定事件发生时实时触发作业;schedule 用于重复执行任务;event 则在发送有效负载时触发作业。
在这里,我将指导您使用事件触发器在应用程序中执行作业。
在文件中jobs/functions.js
,client.defineJob
按如下所示更新函数。
client.defineJob({
id: "generate-meme",
name: "Generate Meme",
version: "0.0.1",
trigger: eventTrigger({
name: "generate.meme",
}),
run: async (payload, io, ctx) => {
const { audience, topic, email } = payload;
// This logs a message to the console and adds an entry to the run dashboard
await io.logger.info("Meme request received!✅");
await io.logger.info("Meme generation in progress 🤝");
// Wrap your code in io.runTask to get automatic error handling and logging
const selectedTemplate = await io.runTask("fetch-meme", async () => {
const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
const memesData = await fetchAllMeme.json();
const memes = memesData.data.memes;
const randInt = Math.floor(Math.random() * 101);
return memes[randInt];
});
const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;
const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;
await io.logger.info("✨ Yay! You've gotten a template for the meme ✨", {
userPrompt,
sysPrompt,
selectedTemplate,
email,
});
},
});
上面的代码片段创建了一项新作业,从 ImgFlip 获取随机 meme 模板,创建一个用户,并创建一个系统提示,让 OpenAI 根据用户提供的数据生成 meme 标题。
要触发此作业,请更新/api/api.js
文件以将用户的数据作为有效负载发送到作业。
import { client } from "@/trigger";
export default function handler(req, res) {
const { topic, audience, email } = req.body;
try {
async function fetchMeme() {
if (topic && audience && email) {
await client.sendEvent({
name: "generate.meme",
payload: {
audience,
topic,
email,
},
});
}
}
fetchMeme();
} catch (err) {
return res.status(400).json({ message: err });
}
}
上面的代码片段通过其名称触发作业 -generate.meme
并将受众、主题和用户的电子邮件作为有效负载发送给作业。
一旦成功,您应该在 Trigger.dev 仪表板上看到作业的状态并监控其活动。
Trigger.dev 还允许我们在作业中嵌套作业;也就是说,您可以在一个作业中触发另一个作业。这样,您就可以触发一个作业,该作业将执行其自身内嵌套的所有作业。
在上一个作业中,我们将检索到的数据记录到控制台;接下来,让我们在作业中触发另一个作业。
client.defineJob({
id: "generate-meme",
name: "Generate Meme",
version: "0.0.1",
trigger: eventTrigger({
name: "generate.meme",
}),
run: async (payload, io, ctx) => {
const { audience, topic, email } = payload;
// This logs a message to the console and adds an entry to the run dashboard
await io.logger.info("Meme request received!✅");
await io.logger.info("Meme generation in progress 🤝");
// Wrap your code in io.runTask to get automatic error handling and logging
const selectedTemplate = await io.runTask("fetch-meme", async () => {
const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
const memesData = await fetchAllMeme.json();
const memes = memesData.data.memes;
const randInt = Math.floor(Math.random() * 101);
return memes[randInt];
});
const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;
const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;
// Trigger any job listening for the gpt.text event
await io.sendEvent("generate-gpt-text", {
name: "gpt.text",
payload: {
userPrompt,
sysPrompt,
selectedTemplate,
email,
},
});
},
});
上面的代码片段将从作业中检索到的数据作为有效负载发送到一个事件,您可以在该事件中生成 meme 标题。每个事件都有一个唯一的名称,任何由名称匹配的事件触发的作业都会运行。您可以使用该io.sendEvent
方法发送事件。
现在我们选择好了模板,并生成了提示,接下来就可以通过监听gpt.text
事件来实现生成表情包文本的作业了。
使用 ChatGPT 生成 meme 文本 🤖
在这里,您将学习如何使用 Trigger.dev OpenAI 集成与 OpenAI 进行通信。我们将使用函数调用为 meme 模板生成字幕。在继续之前, 请先创建一个 OpenAI 帐户 并生成 API 密钥。
单击View API key
下拉菜单即可创建 API 密钥。
接下来,通过运行下面的代码片段来安装 OpenAI 包。
npm install @trigger.dev/openai
将您的 OpenAI API 密钥添加到.env.local
文件中。
OPENAI_API_KEY=<your_api_key>
将 OpenAI 包导入jobs/functions.js
文件。
import { OpenAI } from "@trigger.dev/openai";
const openai = new OpenAI({ id: "openai", apiKey: process.env.OPENAI_API_KEY });
最后,通过OpenAI 函数调用创建生成 meme 标题的作业 。
client.defineJob({
id: "chatgpt-meme-text",
name: "ChatGPT Meme Text",
version: "0.0.1",
trigger: eventTrigger({
name: "gpt.text",
}),
run: async (payload, io, ctx) => {
const { userPrompt, sysPrompt, selectedTemplate, email } = payload;
await io.logger.info("✨ Talking to ChatGPT 🫂");
const messages = [
{ role: "system", content: sysPrompt },
{ role: "user", content: userPrompt },
];
const functions = [
{
name: "generateMemeImage",
description:
"Generate meme via the imgflip API based on the given idea",
parameters: {
type: "object",
properties: {
text0: {
type: "string",
description: "The text for the top caption of the meme",
},
text1: {
type: "string",
description: "The text for the bottom caption of the meme",
},
},
required: ["templateName", "text0", "text1"],
},
},
];
const response = await openai.chat.completions.create("create-meme", {
model: "gpt-3.5-turbo",
messages,
functions,
function_call: "auto",
});
const responseMessage = response.choices[0];
const texts = extractSentencesInQuotes(responseMessage.message.content);
await io.logger.info("✨ Yay! You've gotten a text for your meme ✨", {
texts,
});
await io.sendEvent("caption-save-meme", {
name: "caption.save.meme",
payload: {
texts: ["Text0", "Text1"],
selectedTemplate,
email,
},
});
},
});
上面的代码片段通过OpenAI 函数调用功能为 meme 生成标题 ,提取标题并将其作为有效载荷传递给另一个作业。有效载荷包含标题、选定的 meme 模板和用户的电子邮件。
该extractSentencesInQuotes
函数从 OpenAI 返回的数据中检索标题。
const extractSentencesInQuotes = (inputString) => {
const sentenceRegex = /"([^"]+)"/g;
const sentences = [];
let match;
while ((match = sentenceRegex.exec(inputString))) {
sentences.push(match[1]);
}
return sentences;
};
到目前为止,我们已经能够从 ImgFlip中选择一个 meme 模板,并通过OpenAI 生成 meme 标题 。在接下来的部分中,您将学习如何将标题添加到 meme 模板中。
设置表情包标题📝
ImgFlip API 提供了一个/caption_image
接受所需参数的端点,例如模板 ID、用户名、密码和标题 -text0
和text1
。Text0
是 meme 的顶部文本,Text1
是 meme 的底部文本。
在ImgFlip上创建一个帐户 ,并将您的用户名和密码保存到.env.local
文件中。
IMGFLIP_USERNAME=<your_username>
IMGFLIP_PW=<your_password>
向文件添加一项新作业jobs/functions.js
,为 meme 添加标题并将其保存到Supabase。
client.defineJob({
id: "caption-save-meme",
name: "Caption and Save Meme",
version: "0.0.1",
trigger: eventTrigger({
name: "caption.save.meme",
}),
run: async (payload, io, ctx) => {
const { texts, selectedTemplate, email } = payload;
await io.logger.info("Received meme template and texts 🎉");
const formatData = new URLSearchParams({
template_id: selectedTemplate.id,
username: process.env.IMGFLIP_USERNAME,
password: process.env.IMGFLIP_PW,
text0: texts[0],
text1: texts[1],
});
const captionedMeme = await io.runTask("caption-meme", async () => {
const response = await fetch("https://api.imgflip.com/caption_image", {
method: "POST",
body: formatData.toString(),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
return await response.json();
});
await io.logger.info("✨ Yay! Your meme has been captioned! ✨", {
captionedMeme,
});
},
});
上面的代码片段接受所需的参数,并将其发送到 ImgFlip 提供的端点。该端点以 URL 编码格式接收 meme ID、标题文本和用户凭据。
在接下来的部分中,您将学习如何将带标题的 meme 保存到 Supabase。
保存表情包并展示它们💾
Supabase 是一款开源的 Firebase 替代方案,可让您为软件应用程序添加身份验证、文件存储、Postgres 和实时数据库。使用 Supabase,您可以在几分钟内构建安全且可扩展的应用程序。
在本节中,我将指导您从 Next.js 应用程序设置和与 Supabase 后端进行交互。
访问 Supabase 主页 并创建一个新的组织和项目。
接下来,创建一个包含以下列的新表,如下所示,并向数据库表中添加一些虚拟数据。
该表存储从应用程序接收的id
、name
和属性。是自动生成的,并保存数据时间戳。meme_url
created_at
运行下面的代码片段将 Supabase 包安装到 Next.js 中。
npm install @supabase/supabase-js
点击API
侧边栏菜单,将项目的URL和API复制到.env.local
文件中。
NEXT_PUBLIC_SUPABASE_URL=<public_supabase_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<supabase_anon_key>
最后,创建一个src/supbaseClient.js
文件并为应用程序创建Supabase客户端。
import { createClient } from "@supabase/supabase-js";
const supabaseURL = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseURL, supabaseAnonKey, {
auth: { persistSession: false },
});
恭喜!🎉您已成功将 Supabase 添加到 Next.js 应用程序。
获取所有可用的 meme
api/all-memes
在 Next.js 服务器上创建一个端点,从 Supabase 检索所有模因。
import { supabase } from "@/supabaseClient";
export default function handler(req, res) {
const fetchMemes = async () => {
try {
const { data, error } = await supabase
.from("memes")
.select("*")
.order("created_at", { ascending: false });
res.status(200).json({ data });
} catch (err) {
res.status(400).json({ error: err });
}
};
fetchMemes();
}
然后,当用户访问应用程序的主页时向端点发送请求。
useEffect(() => {
fetchMemes();
}, []);
const fetchMemes = async () => {
try {
const request = await fetch("/api/all-memes");
const response = await request.json();
setMemes(response.data);
} catch (err) {
console.error(err);
}
};
将 meme 保存到 Supabase
将 Supabase 从@/supabaseClient
文件导入到@/jobs/functions.js
文件中。
import { supabase } from "@/supabaseClient";
更新最近添加的作业以将 meme 保存到 Supabase。
client.defineJob({
id: "caption-save-meme",
name: "Caption and Save Meme",
version: "0.0.1",
trigger: eventTrigger({
name: "caption.save.meme",
}),
run: async (payload, io, ctx) => {
const { texts, selectedTemplate, email } = payload;
await io.logger.info("Received meme template and texts 🎉");
const formatData = new URLSearchParams({
template_id: selectedTemplate.id,
username: process.env.IMGFLIP_USERNAME,
password: process.env.IMGFLIP_PW,
text0: texts[0],
text1: texts[1],
});
const captionedMeme = await io.runTask("caption-meme", async () => {
const response = await fetch("https://api.imgflip.com/caption_image", {
method: "POST",
body: formatData.toString(),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
return await response.json();
});
await io.logger.info("✨ Yay! Your meme has been captioned! ✨", {
captionedMeme,
});
await supabase.from("memes").insert({
id: selectedTemplate.id,
name: selectedTemplate.name,
meme_url: captionedMeme.data.url,
});
await io.sendEvent("email-meme", {
name: "send.meme",
payload: {
email,
meme_url: `http://localhost:3000/memes/${selectedTemplate.id}`,
},
});
await io.logger.info(
"✨ Yay! Your meme has been saved to the database! ✨"
);
},
});
上面的代码片段为 meme 模板添加了一个标题,并将新创建的 meme 保存到 Supabase。它还会触发另一个任务,将 meme 页面的 URL 发送到用户的邮箱。您将在下一节中学习如何执行此操作。
完成后发送电子邮件🎬
Resend 是一个电子邮件 API,可让您轻松发送文本、附件和电子邮件模板。借助 Resend,您可以大规模构建、测试和发送事务性电子邮件。
在这里,您将学习如何通过“重新发送”功能发送电子邮件。请前往 注册页面并创建帐户。
创建一个API Key
并将其保存到.env.local
Next.js 项目中的文件中。
RESEND_API_KEY=<place_your_API_key>
安装 Trigger.dev Resend 集成:
npm i @trigger.dev/resend
将 Resend 导入@/jobs/functions.js
文件,如下所示。
import { Resend } from "@trigger.dev/resend";
const resend = new Resend({ id: "resend", apiKey: process.env.RESEND_API_KEY });
添加最后一项工作,该工作从上一项工作中接收 meme URL 和用户的电子邮件,并在完成后将新生成的 meme 发送给用户。
client.defineJob({
id: "send-meme",
name: "Send Meme",
version: "0.0.1",
trigger: eventTrigger({
name: "send.meme",
}),
run: async (payload, io, ctx) => {
const { meme_url, email } = payload;
await io.logger.info("Sending meme to the user 🎉");
await resend.sendEmail("📧", {
from: "onboarding@resend.dev",
to: [email],
subject: "Your meme is ready!",
text: `Hey there, Your meme is ready.\n Access it here: ${meme_url}`,
});
await io.logger.info("✨ Yay! Your meme has been emailed to the user! ✨");
},
});
用户会收到一封包含以下格式链接的电子邮件:http://localhost:3000/memes/${meme.id}
。此链接会将用户重定向到应用程序中显示 meme 的特定页面。我们将在下一节中创建此页面。
显示新生成的 meme
创建一个memes/[id].js
文件,从 URL 接受 meme ID,通过其 ID 从 Supabase 获取 meme,并在页面上显示该 meme。
将下面的代码片段复制到memes/[id].js
文件中。
"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import { usePathname } from "next/navigation";
import Link from "next/link";
export default function Meme() {
const params = usePathname();
const [meme, setMeme] = useState({});
useEffect(() => {
const fetchMeme = async () => {
if (params) {
const id = params.split("/")[2];
const request = await fetch("/api/meme", {
method: "POST",
body: JSON.stringify({ id }),
headers: {
"Content-Type": "application/json",
},
});
const response = await request.json();
setMeme(response.data[0]);
}
};
fetchMeme();
}, [params]);
return (
<div className="w-full min-h-screen flex flex-col items-center justify-center">
{meme?.meme_url && (
<Image
src={meme.meme_url}
alt={meme.name}
className="hover:scale-105 rounded"
width={500}
height={500}
/>
)}
<Link href="/" className="mt-6 text-blue-500">
Go back home
</Link>
</div>
);
}
上面的代码片段从 URL 中检索 meme ID,通过端点将其发送到服务器/api/meme
,并接收 meme 详细信息作为响应。
接下来,创建/api/meme
端点。
import { supabase } from "@/supabaseClient";
export default function handler(req, res) {
const getMeme = async () => {
const { id } = req.body;
try {
const { data, error } = await supabase
.from("memes")
.select("*")
.eq("id", parseInt(id));
res.status(200).json({ data });
} catch (err) {
res.status(400).json({ error: err });
}
};
getMeme();
}
上面的代码片段从 Supabase 获取属性与 ID 匹配的数据。
恭喜!您已完成项目。
结论
到目前为止,您已经学习了如何使用 Trigger 在代码库中创建作业、从 OpenAI 生成文本、从 Supabase 保存和检索数据以及通过 Resend 发送电子邮件。
本教程的源代码可以在这里找到:
https://github.com/triggerdotdev/blog/tree/main/memes-generator
感谢您的阅读!
如果您能花 10 秒钟给我们一颗星,我将非常感激 💖
https://github.com/triggerdotdev/trigger.dev