Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m6s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m52s
Monorepo Pipeline / 🧹 Lint (push) Successful in 3m1s
Monorepo Pipeline / 🚀 Release (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
190 lines
5.8 KiB
TypeScript
190 lines
5.8 KiB
TypeScript
import { PayloadRequest } from "payload";
|
|
import * as fs from "node:fs/promises";
|
|
import * as path from "node:path";
|
|
import * as os from "node:os";
|
|
|
|
async function getOrchestrator() {
|
|
const OPENROUTER_KEY =
|
|
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
|
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
|
|
|
if (!OPENROUTER_KEY) {
|
|
throw new Error(
|
|
"Missing OPENROUTER_API_KEY in .env (Required for AI generation)",
|
|
);
|
|
}
|
|
|
|
const importDynamic = new Function("modulePath", "return import(modulePath)");
|
|
const { AiBlogPostOrchestrator } = await importDynamic(
|
|
"@mintel/content-engine",
|
|
);
|
|
|
|
return new AiBlogPostOrchestrator({
|
|
apiKey: OPENROUTER_KEY,
|
|
replicateApiKey: REPLICATE_KEY,
|
|
model: "google/gemini-3-flash-preview",
|
|
});
|
|
}
|
|
|
|
export const generateSlugEndpoint = async (req: PayloadRequest) => {
|
|
try {
|
|
let body: any = {};
|
|
try {
|
|
if (req.body) body = (await req.json?.()) || {};
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
const { title, draftContent, oldSlug, instructions } = body;
|
|
const orchestrator = await getOrchestrator();
|
|
const newSlug = await orchestrator.generateSlug(
|
|
draftContent,
|
|
title,
|
|
instructions,
|
|
);
|
|
|
|
if (oldSlug && oldSlug !== newSlug) {
|
|
await req.payload.create({
|
|
collection: "redirects" as any,
|
|
data: {
|
|
from: oldSlug,
|
|
to: newSlug,
|
|
},
|
|
});
|
|
}
|
|
|
|
return Response.json({ success: true, slug: newSlug });
|
|
} catch (e: any) {
|
|
return Response.json({ success: false, error: e.message }, { status: 500 });
|
|
}
|
|
};
|
|
|
|
export const generateThumbnailEndpoint = async (req: PayloadRequest) => {
|
|
try {
|
|
let body: any = {};
|
|
try {
|
|
if (req.body) body = (await req.json?.()) || {};
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
const { draftContent, title, instructions } = body;
|
|
const OPENROUTER_KEY =
|
|
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
|
const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
|
|
|
|
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY in .env");
|
|
if (!REPLICATE_KEY) throw new Error("Missing REPLICATE_API_KEY in .env");
|
|
|
|
const importDynamic = new Function(
|
|
"modulePath",
|
|
"return import(modulePath)",
|
|
);
|
|
const { AiBlogPostOrchestrator } = await importDynamic(
|
|
"@mintel/content-engine",
|
|
);
|
|
const { ThumbnailGenerator } = await importDynamic(
|
|
"@mintel/thumbnail-generator",
|
|
);
|
|
|
|
const orchestrator = new AiBlogPostOrchestrator({
|
|
apiKey: OPENROUTER_KEY,
|
|
replicateApiKey: REPLICATE_KEY,
|
|
model: "google/gemini-3-flash-preview",
|
|
});
|
|
|
|
const tg = new ThumbnailGenerator({ replicateApiKey: REPLICATE_KEY });
|
|
|
|
const prompt = await orchestrator.generateVisualPrompt(
|
|
draftContent || title || "Technology",
|
|
instructions,
|
|
);
|
|
|
|
const tmpPath = path.join(os.tmpdir(), `mintel-thumb-${Date.now()}.png`);
|
|
await tg.generateImage(prompt, tmpPath);
|
|
|
|
const fileData = await fs.readFile(tmpPath);
|
|
const stat = await fs.stat(tmpPath);
|
|
const fileName = path.basename(tmpPath);
|
|
|
|
const newMedia = await req.payload.create({
|
|
collection: "media" as any,
|
|
data: {
|
|
alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail",
|
|
},
|
|
file: {
|
|
data: fileData,
|
|
name: fileName,
|
|
mimetype: "image/png",
|
|
size: stat.size,
|
|
},
|
|
});
|
|
|
|
await fs.unlink(tmpPath).catch(() => {});
|
|
|
|
return Response.json({ success: true, mediaId: newMedia.id });
|
|
} catch (e: any) {
|
|
return Response.json({ success: false, error: e.message }, { status: 500 });
|
|
}
|
|
};
|
|
|
|
export const generateSingleFieldEndpoint = async (req: PayloadRequest) => {
|
|
try {
|
|
let body: any = {};
|
|
try {
|
|
if (req.body) body = (await req.json?.()) || {};
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
const {
|
|
documentTitle,
|
|
documentContent,
|
|
fieldName,
|
|
fieldDescription,
|
|
instructions,
|
|
} = body;
|
|
|
|
const OPENROUTER_KEY =
|
|
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
|
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY");
|
|
|
|
const contextDocsData = await req.payload.find({
|
|
collection: "context-files" as any,
|
|
limit: 100,
|
|
});
|
|
const projectContext = contextDocsData.docs
|
|
.map((doc: any) => `--- ${doc.filename} ---\n${doc.content}`)
|
|
.join("\n\n");
|
|
|
|
const prompt = `You are an expert AI assistant perfectly trained for generating exact data values for CMS components.
|
|
PROJECT STRATEGY & CONTEXT:
|
|
${projectContext}
|
|
|
|
DOCUMENT TITLE: ${documentTitle}
|
|
DOCUMENT DRAFT:\n${documentContent}\n
|
|
YOUR TASK: Generate the exact value for a specific field named "${fieldName}".
|
|
${fieldDescription ? `FIELD DESCRIPTION / CONSTRAINTS: ${fieldDescription}\n` : ""}
|
|
${instructions ? `EDITOR INSTRUCTIONS for this field: ${instructions}\n` : ""}
|
|
CRITICAL RULES:
|
|
1. Respond ONLY with the requested content value.
|
|
2. NO markdown wrapping blocks (like \`\`\`mermaid or \`\`\`html) around the output! Just the raw code or text.
|
|
3. If the field implies a diagram or flow, output RAW Mermaid.js code.
|
|
4. If it's standard text, write professional B2B German. No quotes, no conversational filler.`;
|
|
|
|
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: `Bearer ${OPENROUTER_KEY}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: JSON.stringify({
|
|
model: "google/gemini-3-flash-preview",
|
|
messages: [{ role: "user", content: prompt }],
|
|
}),
|
|
});
|
|
const data = await res.json();
|
|
const text = data.choices?.[0]?.message?.content?.trim() || "";
|
|
return Response.json({ success: true, text });
|
|
} catch (e: any) {
|
|
return Response.json({ success: false, error: e.message }, { status: 500 });
|
|
}
|
|
};
|