Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
192 lines
5.6 KiB
TypeScript
192 lines
5.6 KiB
TypeScript
"use server";
|
|
|
|
import { config } from "../../../content-engine.config";
|
|
import { getPayloadHMR } from "@payloadcms/next/utilities";
|
|
import configPromise from "@payload-config";
|
|
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 async function generateSlugAction(
|
|
title: string,
|
|
draftContent: string,
|
|
oldSlug?: string,
|
|
instructions?: string,
|
|
) {
|
|
try {
|
|
const orchestrator = await getOrchestrator();
|
|
const newSlug = await orchestrator.generateSlug(
|
|
draftContent,
|
|
title,
|
|
instructions,
|
|
);
|
|
|
|
if (oldSlug && oldSlug !== newSlug) {
|
|
const payload = await getPayloadHMR({ config: configPromise });
|
|
await payload.create({
|
|
collection: "redirects",
|
|
data: {
|
|
from: oldSlug,
|
|
to: newSlug,
|
|
},
|
|
});
|
|
}
|
|
|
|
return { success: true, slug: newSlug };
|
|
} catch (e: any) {
|
|
return { success: false, error: e.message };
|
|
}
|
|
}
|
|
|
|
export async function generateThumbnailAction(
|
|
draftContent: string,
|
|
title?: string,
|
|
instructions?: string,
|
|
) {
|
|
try {
|
|
const payload = await getPayloadHMR({ config: configPromise });
|
|
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 (Required for Thumbnails)",
|
|
);
|
|
}
|
|
|
|
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 payload.create({
|
|
collection: "media",
|
|
data: {
|
|
alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail",
|
|
},
|
|
file: {
|
|
data: fileData,
|
|
name: fileName,
|
|
mimetype: "image/png",
|
|
size: stat.size,
|
|
},
|
|
});
|
|
|
|
// Cleanup temp file
|
|
await fs.unlink(tmpPath).catch(() => {});
|
|
|
|
return { success: true, mediaId: newMedia.id };
|
|
} catch (e: any) {
|
|
return { success: false, error: e.message };
|
|
}
|
|
}
|
|
export async function generateSingleFieldAction(
|
|
documentTitle: string,
|
|
documentContent: string,
|
|
fieldName: string,
|
|
fieldDescription: string,
|
|
instructions?: string,
|
|
) {
|
|
try {
|
|
const OPENROUTER_KEY =
|
|
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
|
|
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY");
|
|
|
|
const payload = await getPayloadHMR({ config: configPromise });
|
|
|
|
// Fetch context documents from DB
|
|
const contextDocsData = await payload.find({
|
|
collection: "context-files",
|
|
limit: 100,
|
|
});
|
|
const projectContext = contextDocsData.docs
|
|
.map((doc) => `--- ${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 { success: true, text };
|
|
} catch (e: any) {
|
|
return { success: false, error: e.message };
|
|
}
|
|
}
|