Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
Build & Deploy / 🩺 Smoke Test (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
92 lines
3.3 KiB
TypeScript
92 lines
3.3 KiB
TypeScript
import { ThumbnailGenerator } from "@mintel/thumbnail-generator";
|
|
import * as path from "node:path";
|
|
import * as fs from "node:fs/promises";
|
|
|
|
async function run() {
|
|
const apiKey =
|
|
process.env.REPLICATE_API_TOKEN || process.env.REPLICATE_API_KEY;
|
|
if (!apiKey) {
|
|
console.error("❌ Missing REPLICATE_API_TOKEN in environment.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const targetFile = process.argv[2];
|
|
if (!targetFile) {
|
|
console.error(
|
|
"❌ Usage: npx tsx scripts/generate-thumbnail.ts <file-or-topic>",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
let topic = targetFile;
|
|
let filename = "thumbnail.png";
|
|
|
|
// Try to parse the topic from the MDX frontmatter if a file is provided
|
|
if (targetFile.endsWith(".mdx")) {
|
|
try {
|
|
const content = await fs.readFile(targetFile, "utf8");
|
|
const titleMatch = content.match(/title:\s*"?([^"\n]+)"?/);
|
|
topic = titleMatch ? titleMatch[1] : path.basename(targetFile, ".mdx");
|
|
filename = `${path.basename(targetFile, ".mdx")}.png`;
|
|
} catch (e) {
|
|
console.warn(
|
|
`⚠️ Could not read ${targetFile} as a file. Using literal argument as topic.`,
|
|
);
|
|
topic = targetFile;
|
|
}
|
|
}
|
|
|
|
console.log(`Generating abstract thumbnail for topic: "${topic}"`);
|
|
|
|
const generator = new ThumbnailGenerator({ replicateApiKey: apiKey });
|
|
const isRoot = process.cwd().endsWith("mintel.me");
|
|
const baseDir = isRoot
|
|
? path.join(process.cwd(), "apps", "web")
|
|
: process.cwd();
|
|
|
|
const outputPath = path.join(baseDir, "public", "blog", filename);
|
|
|
|
// Check if thumbnail already exists to avoid redundant generation
|
|
try {
|
|
await fs.access(outputPath);
|
|
console.log(`⏭️ Thumbnail already exists, skipping: ${filename}`);
|
|
return;
|
|
} catch {
|
|
// File does not exist, proceed with generation
|
|
}
|
|
|
|
const inspirationPath = path.join(
|
|
baseDir,
|
|
"public",
|
|
"blog",
|
|
"inspiration.png",
|
|
);
|
|
let hasInspiration = false;
|
|
try {
|
|
await fs.access(inspirationPath);
|
|
hasInspiration = true;
|
|
} catch {
|
|
hasInspiration = false;
|
|
}
|
|
|
|
let customPrompt = `Extremely clean, flat, abstract geometric illustration. Use the provided image prompt ONLY as a STRICT style, color, and texture reference. Do not copy the image content, just the aesthetic. Characteristics: Flat vector design, 2D only (no 3D), tech/startup/agency aesthetics, highly professional, abstract data representations, extensive use of whitespace. No text, no chaotic lines, no humans.`;
|
|
|
|
if (topic.includes("Service") || topic.includes("Sorglos")) {
|
|
customPrompt +=
|
|
" Make the core composition out of interconnected nodes, continuous circular loops, and smooth flowing paths representing uninterrupted service and integration. DO NOT use sharp blocks.";
|
|
} else if (topic.includes("Zero") || topic.includes("Overhead")) {
|
|
customPrompt +=
|
|
" Make the core composition out of stark, reductive, ultra-minimalist solid geometric blocks and hard sharp angles. This represents cutting away excess and absolute lean efficiency. DO NOT use circles or flowing paths.";
|
|
}
|
|
|
|
await generator.generateImage(topic, outputPath, {
|
|
systemPrompt: customPrompt,
|
|
imagePrompt: hasInspiration ? inspirationPath : undefined,
|
|
});
|
|
}
|
|
|
|
run().catch((e) => {
|
|
console.error("❌ Thumbnail generation failed:", e);
|
|
process.exit(1);
|
|
});
|