import OpenAI from "openai"; export interface MemeSuggestion { template: string; captions: string[]; explanation: string; } /** * Mapping of common meme names to memegen.link template IDs. * See https://api.memegen.link/templates for the full list. */ export const MEMEGEN_TEMPLATES: Record = { drake: "drake", "drake hotline bling": "drake", "distracted boyfriend": "db", distracted: "db", "expanding brain": "brain", expanding: "brain", "this is fine": "fine", fine: "fine", clown: "clown-applying-makeup", "clown applying makeup": "clown-applying-makeup", "two buttons": "daily-struggle", "daily struggle": "daily-struggle", ds: "daily-struggle", gru: "gru", "change my mind": "cmm", "always has been": "ahb", "uno reverse": "uno", "disaster girl": "disastergirl", "is this a pigeon": "pigeon", "roll safe": "rollsafe", rollsafe: "rollsafe", "surprised pikachu": "pikachu", "batman slapping robin": "slap", "left exit 12": "exit", "one does not simply": "mordor", "panik kalm panik": "panik", }; /** * Resolve a human-readable meme name to a memegen.link template ID. * Falls back to slugified version of the name. */ export function resolveTemplateId(name: string): string { if (!name) return "drake"; const normalized = name.toLowerCase().trim(); // Check if it's already a valid memegen ID const validIds = new Set(Object.values(MEMEGEN_TEMPLATES)); if (validIds.has(normalized)) return normalized; // Check mapping if (MEMEGEN_TEMPLATES[normalized]) return MEMEGEN_TEMPLATES[normalized]; // STRICT FALLBACK: Prevent 404 image errors on the frontend return "drake"; } export class MemeGenerator { private openai: OpenAI; constructor( apiKey: string, baseUrl: string = "https://openrouter.ai/api/v1", ) { this.openai = new OpenAI({ apiKey, baseURL: baseUrl, defaultHeaders: { "HTTP-Referer": "https://mintel.me", "X-Title": "Mintel AI Meme Generator", }, }); } async generateMemeIdeas(content: string): Promise { const templateList = Object.keys(MEMEGEN_TEMPLATES) .filter((k, i, arr) => arr.indexOf(k) === i) .slice(0, 20) .join(", "); const response = await this.openai.chat.completions.create({ model: "google/gemini-2.5-flash", messages: [ { role: "system", content: `You are a high-end Meme Architect for "Mintel.me", a boutique digital architecture studio. Your persona is Marc Mintel: a technical expert, performance-obsessed, and "no-BS" digital architect. Your Goal: Analyze the blog post content and suggest 3 high-fidelity, highly sarcastic, and provocative technical memes that would appeal to (and trigger) CEOs, CTOs, and high-level marketing engineers. Meme Guidelines: 1. Tone: Extremely sarcastic, provocative, and "triggering". It must mock typical B2B SaaS/Agency mediocrity. Pure sarcasm that forces people to share it because it hurts (e.g. throwing 20k ads at an 8-second loading page, blaming weather for bounce rates). 2. Language: Use German for the captions. Use biting technical/business terms (e.g., "ROI-Killer", "Tracking-Müll", "WordPress-Hölle", "Marketing-Budget verbrennen"). 3. Quality: Must be ruthless. Avoid generic "Low Effort" memes. The humor should stem from the painful reality of bad tech decisions. IMPORTANT: Use ONLY template IDs from this list for the "template" field: ${templateList} Return ONLY a JSON object: { "memes": [ { "template": "memegen_template_id", "captions": ["Top caption", "Bottom caption"], "explanation": "Brief context on why this fits the strategy" } ] } IMPORTANT: Return ONLY the JSON object. No markdown wrappers.`, }, { role: "user", content, }, ], response_format: { type: "json_object" }, }); const body = response.choices[0].message.content || '{"memes": []}'; let result; try { result = JSON.parse(body); } catch { console.error("Failed to parse AI response", body); return []; } // Normalize template IDs const memes: MemeSuggestion[] = (result.memes || []).map( (m: MemeSuggestion) => ({ ...m, template: resolveTemplateId(m.template), }), ); return memes; } }