Files
mintel.me/apps/web/src/payload/actions/generateField.ts
Marc Mintel 6864903cff
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
fix(web): remove redundant prop-types and unblock lint pipeline
2026-02-24 11:38:43 +01:00

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 };
}
}