译者 | 李睿
审校 | 重楼
为什么要构建人工智能梗图生成器?
梗图(Meme)堪称互联网时代的“全民语言”。无论是想调侃朋友,还是想表达编程让人崩溃的无奈,梗图总能精准地表达其意境。然而,人工制作一张梗图需要花费很长时间。首先需要找到合适的图片,然后构思出幽默并且贴合情境的配文,还要巧妙地将图片与文字融为一体,并且看起来不能像孩子的随意涂鸦。
不过好在已经有了OpenAI和DeepSeek等工具,不仅可以自动化创作幽默内容,还能自动化生成当前流行的格式,让用户几秒钟内就能创作出梗图。以下是生成梗图的方法:
- 为了从梗图中生成引人入胜的配文,采用了一种特定于情境的方法。
- 构建了一个超级简单直观的拖放式设计界面。
- 找到降低API费用的新方法,能够有效地控制预算。
- 允许用户保存他们最喜欢的梗图,并添加了文本转图片的功能。
用户喜欢使用的工具
在深入研究代码的细节之前,首先探讨技术堆栈。在不知道需要使用什么工具的情况下就开始制造是不切实际的行为。
- React + TypeScript。React为用户带来了流畅、响应迅速的用户界面,而TypeScript 可以帮助制作者捕捉到许多以前可能会出现的错误。
- OpenAI/DeepSeek API。只要有预算,其他问题就不足为虑,因为Division-04能够使用GPT-4 Turbo随心所欲地生成犀利而有趣的配文。当预算有限时,DeepSeek就能发挥重要的作用。
- Fabric.js。使用Fabric.js库,可以轻松地拖动包含文本的图像,而不是感觉难以掌控。
- Vercel。在部署项目时,即使处在业务高峰时段,Vercel的边缘缓存功能也能很好地缓解压力。
- Redis。Redis的入门门槛较低,实现过程简便易行,同时还能有效防范API滥用现象,避免触发速率限制 。
步骤1:设置自己的人工智能大脑
显然,人工智能从互联网上复制的短语并不适用于梗图。梗图需要将态度、措辞和一定程度的克制相结合。这就引出了一个更根本的问题——究竟该如何引导人工智能学会讲笑话。其答案或许在于调整人工智能本身的提示。
以下是用于创建字幕的代码片段:
复制1 // src/services/aiService.ts 2 type MemePrompt = { 3 template: string; // e.g., "Distracted Soul" 4 context: string; // e.g., "When your code works on the first try" 5 }; 6 7 const generateMemeCaption = async ({ template, context }: MemePrompt) => { 8 const prompt = ` 9 Generate a sarcastic meme caption for the "${template}" template about "${context}". 10 Rules: 11 - Use Gen-Z slang (e.g., "rizz", "sigma") 12 - Max 12 words 13 - Add emojis related to the context 14 `; 15 16 const response = await openai.chat.completions.create({ 17 model: "gpt-4-turbo", 18 messages: [{ role: "user", content: prompt }], 19 temperature: 0.9, // Higher = riskier jokes 20 max_tokens: 50, 21 }); 22 23 return stripEmojis(response.choices[0].message.content); // No NSFW stuff allowed 24 };
专业提示:如果为了营造幽默效果,可将相关参数值设定在0.7至0.9的范围内,但出于安全考虑,需要确保始终通过OpenAI的调节端点来调节反应。
步骤2:构建梗图画布
如果使用过 HTML5 Canvas API,就会明白处理它们并非易事。幸运的是,Fabric.js可以实现这一功能。它直接在React中提供了类似photoshop的控件,并额外附带了拖放功能。
以下是简化版的Canvas组件:
复制1 // src/components/MemeCanvas.tsx 2 import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react"; 3 4 export default function MemeCanvas() { 5 const { editor, onReady } = useFabricJSEditor(); 6 const [textColor, setTextColor] = useState("#FFFFFF"); 7 8 const addTextLayer = (text: string) => { 9 editor?.addText(text, { 10 fill: textColor, 11 fontFamily: "Impact", 12 fontSize: 40, 13 stroke: "#000000", 14 strokeWidth: 2, 15 shadow: "rgba(0,0,0,0.5) 2px 2px 2px", 16 }); 17 }; 18 19 return ( 20 <> 21 <button onClick={() => addTextLayer("Why React, why?!")}>Add Default Text</button> 22 <input type="color" onChange={(e) => setTextColor(e.target.value)} /> 23 <FabricJSCanvas className="canvas" onReady={onReady} /> 24 </> 25 ); 26 }
该功能有以下一些优势:
- 释放文本图层,以便在文档的任何位置拖动。
- 使用高级颜色选择器添加描边和阴影效果。
- 双击可以编辑文本,以简化编辑过程。
步骤3:速率限制
试想一下这样的场景:在应用程序发布之后,很多人也萌生了制作梗图的想法,这听起来是不是很有趣?然而,当看到 OpenAI 的账单竟超过比特币价格时,或许就不会这么认为。
为了解决这个问题,在Redis中设置了滑动窗口速率限制。以下介绍在Vercel Edge Functions上的具体实现方法:
复制1 // src/app/api/generate-caption/route.ts 2 import { Ratelimit } from "@upstash/ratelimit"; 3 import { Redis } from "@upstash/redis"; 4 5 const ratelimit = new Ratelimit({ 6 redis: Redis.fromEnv(), 7 limiter: Ratelimit.slidingWindow(15, "86400s"), // 15 requests/day per IP 8 }); 9 10 export async function POST(request: Request) { 11 const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; 12 const { success } = await ratelimit.limit(ip); 13 14 if (!success) { 15 return new Response("Slow down, meme lord! Daily limit reached.", { 16 status: 429, 17 }); 18 } 19 20 // Proceed with OpenAI call 21 }
节省成本的妙招
- 缓存流行的提示,例如“热线来电”以及“拉取请求获得批准”等。
- 使用CloudFlare缓存生成的图像。
由DALL-E 3生成的人工智能梗图
有时候,人们会认识到选择完美的梗图模板是一项不可能完成的任务。
复制1 // src/services/aiService.ts 2 const generateCustomMemeImage = async (prompt: string) => { 3 const response = await openai.images.generate({ 4 model: "dall-e-3", 5 prompt: ` 6 A meme template about "${prompt}". 7 Style: Flat vector, bold outlines, no text. 8 Background: Solid pastel color. 9 `, 10 size: "1024x1024", 11 quality: "hd", 12 }); 13 14 return response.data[0].url; 15 }
更改输出
- 提示:“两个开发人员就采用Redux和Zustand框架进行争辩。”
- 最终产品:将Redux和Zustand这两个卡通人物的争论,以两个在紫色背景上动态呈现的图标形式进行展示。
梗图的历史记录功能(Zustad + LocalStorage)
为了让用户能够保存梗图,在Zustand的帮助下添加了梗图的历史记录功能。
复制1 // src/stores/memeHistory.ts 2 import { create } from "zustand"; 3 import { persist } from "zustand/middleware"; 4 5 type Meme = { 6 id: string; 7 imageUrl: string; 8 caption: string; 9 timestamp: number; 10 }; 11 12 interface MemeHistoryState { 13 memes: Meme[]; 14 saveMeme: (meme: Omit<Meme, "id" | "timestamp">) => void; 15 } 16 17 export const useMemeHistory = create<MemeHistoryState>()( 18 persist( 19 (set, get) => ({ 20 memes: [], 21 saveMeme: (meme) => { 22 const newMeme = { 23 ...meme, 24 id: crypto.randomUUID(), 25 timestamp: Date.now(), 26 }; 27 set({ memes: [newMeme, ...get().memes].slice(0, 100) }); 28 }, 29 }), 30 { name: "meme-history" } 31 ) 32);
用户操作指引
- 首先创建一个梗图,然后点击保存。
- 梗图将在本地保存,并将以网络格式呈现。
- 已经保存的梗图可以通过点击在编辑器中重新加载。
结束语
构建人工智能梗图生成器,不仅可以帮助开发人员加深对编程的理解,还可以帮助他们掌握应对各类突发状况的技巧。然而,从实施严格的速率限制到承受Reddit网站的流量激增,这一过程并不轻松。
因此,开发人员可以从零基础起步,根据收到的反馈不断改进人工智能梗图生成器。也许他们制作的梗图会大受欢迎,并从中获得令人满意的回报。
原文标题:From Zero to Meme Hero: How I Built an AI-Powered Meme Generator in React,作者:Mohit Menghnani