feat: content engine
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Successful in 1m12s
Monorepo Pipeline / 🧪 Test (push) Successful in 2m59s
Monorepo Pipeline / 🏗️ Build (push) Successful in 6m52s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧹 Lint (push) Successful in 1m12s
Monorepo Pipeline / 🧪 Test (push) Successful in 2m59s
Monorepo Pipeline / 🏗️ Build (push) Successful in 6m52s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
This commit is contained in:
84
packages/thumbnail-generator/src/generator.ts
Normal file
84
packages/thumbnail-generator/src/generator.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import Replicate from "replicate";
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as path from "node:path";
|
||||
|
||||
export interface ThumbnailGeneratorConfig {
|
||||
replicateApiKey: string;
|
||||
}
|
||||
|
||||
export class ThumbnailGenerator {
|
||||
private replicate: Replicate;
|
||||
|
||||
constructor(config: ThumbnailGeneratorConfig) {
|
||||
this.replicate = new Replicate({
|
||||
auth: config.replicateApiKey,
|
||||
});
|
||||
}
|
||||
|
||||
public async generateImage(
|
||||
topic: string,
|
||||
outputPath: string,
|
||||
): Promise<string> {
|
||||
const systemPrompt = `Technical blueprint / architectural illustration — clean lines, monochrome base with one highlighter accent color (yellow, pink, or green). Abstract, geometric, or diagrammatic illustrations only. 'Engineering notebook sketch' — precise, minimal, professional. No text in images. No people or realistic photos.`;
|
||||
|
||||
const prompt = `${systemPrompt}\n\nTopic to illustrate abstractly: ${topic}`;
|
||||
|
||||
console.log(`🎨 Generating thumbnail for topic: "${topic}"...`);
|
||||
|
||||
const output = await this.replicate.run("black-forest-labs/flux-1.1-pro", {
|
||||
input: {
|
||||
prompt,
|
||||
aspect_ratio: "16:9",
|
||||
output_format: "png",
|
||||
output_quality: 90,
|
||||
prompt_upsampling: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Replicate returns a ReadableStream for the output of flux-1.1-pro in newer Node SDKs
|
||||
// Or a string URL in older ones. We handle both.
|
||||
let buffer: Buffer;
|
||||
|
||||
if (output instanceof ReadableStream) {
|
||||
console.log(`⬇️ Downloading generated stream from Replicate...`);
|
||||
const chunks: Uint8Array[] = [];
|
||||
const reader = output.getReader();
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
if (value) chunks.push(value);
|
||||
}
|
||||
buffer = Buffer.concat(chunks);
|
||||
} else if (
|
||||
typeof output === "string" ||
|
||||
(Array.isArray(output) && typeof output[0] === "string")
|
||||
) {
|
||||
const imageUrl = Array.isArray(output) ? output[0] : output;
|
||||
console.log(
|
||||
`⬇️ Downloading generated image from URL: ${imageUrl.substring(0, 50)}...`,
|
||||
);
|
||||
const response = await fetch(imageUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download image: ${response.statusText}`);
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
buffer = Buffer.from(arrayBuffer);
|
||||
} else if (Buffer.isBuffer(output)) {
|
||||
buffer = output;
|
||||
} else if (typeof output === "object") {
|
||||
console.log("Raw output object:", output);
|
||||
throw new Error("Unexpected output format from Replicate.");
|
||||
} else {
|
||||
throw new Error("Unknown output format from Replicate.");
|
||||
}
|
||||
|
||||
const absPath = path.isAbsolute(outputPath)
|
||||
? outputPath
|
||||
: path.resolve(process.cwd(), outputPath);
|
||||
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
||||
await fs.writeFile(absPath, buffer);
|
||||
|
||||
console.log(`✅ Saved thumbnail to: ${absPath}`);
|
||||
return absPath;
|
||||
}
|
||||
}
|
||||
1
packages/thumbnail-generator/src/index.ts
Normal file
1
packages/thumbnail-generator/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./generator";
|
||||
Reference in New Issue
Block a user