Compare commits

...

6 Commits

Author SHA1 Message Date
1670b8e5ef chore: bump payload-ai 1.9.15
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧪 Test (push) Successful in 57s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m21s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m22s
Monorepo Pipeline / 🐳 Build Production Runtime (push) Successful in 37s
Monorepo Pipeline / 🐳 Build Build-Base (push) Successful in 43s
Monorepo Pipeline / 🚀 Release (push) Successful in 2m34s
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Successful in 3m9s
🏥 Server Maintenance / 🧹 Prune & Clean (push) Failing after 4s
2026-03-03 15:10:07 +01:00
1c43d12e4d fix(payload-ai): convert server actions to api endpoints, drop @payload-config dependency
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 1m20s
Monorepo Pipeline / 🏗️ Build (push) Successful in 3m22s
Monorepo Pipeline / 🧹 Lint (push) Successful in 3m33s
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
2026-03-03 14:58:35 +01:00
5cf9922822 feat: add local Qdrant-based memory MCP and dev setup 2026-03-03 13:40:13 +01:00
9a4a95feea fix(packages): remove private flag from all engine packages
All checks were successful
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 2s
Monorepo Pipeline / 🧪 Test (push) Successful in 59s
Monorepo Pipeline / 🧹 Lint (push) Successful in 2m18s
Monorepo Pipeline / 🏗️ Build (push) Successful in 2m18s
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
2026-03-03 13:39:38 +01:00
d3902c4c77 fix(ci): use NPM_TOKEN instead of REGISTRY_PASS for Gitea docker registry login 2026-03-03 13:35:12 +01:00
21ec8a33ae fix(ci): use explicit registry token instead of GITHUB_TOKEN for docker login 2026-03-03 12:54:13 +01:00
23 changed files with 174 additions and 149 deletions

View File

@@ -203,8 +203,8 @@ jobs:
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: git.infra.mintel.me registry: git.infra.mintel.me
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.NPM_TOKEN }}
- name: 🏗️ Build & Push ${{ matrix.name }} - name: 🏗️ Build & Push ${{ matrix.name }}
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5

4
.gitignore vendored
View File

@@ -47,3 +47,7 @@ directus/uploads/directus-health-file
data/crawls/ data/crawls/
packages/estimation-engine/out/ packages/estimation-engine/out/
apps/web/out/estimations/ apps/web/out/estimations/
# Memory MCP
data/qdrant/
packages/memory-mcp/models/

16
docker-compose.mcps.yml Normal file
View File

@@ -0,0 +1,16 @@
services:
qdrant:
image: qdrant/qdrant:latest
container_name: qdrant-mcp
ports:
- "6333:6333"
- "6334:6334"
volumes:
- ./data/qdrant:/qdrant/storage
restart: unless-stopped
networks:
- mcp-network
networks:
mcp-network:
driver: bridge

12
fix-private.mjs Normal file
View File

@@ -0,0 +1,12 @@
import fs from 'fs';
import glob from 'glob';
const files = glob.sync('/Users/marcmintel/Projects/at-mintel/packages/*/package.json');
files.forEach(f => {
const content = fs.readFileSync(f, 'utf8');
if (content.includes('"private": true,')) {
console.log(`Fixing ${f}`);
const newContent = content.replace(/\s*"private": true,?\n/g, '\n');
fs.writeFileSync(f, newContent);
}
});

View File

@@ -8,7 +8,7 @@
"dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'", "dev:gatekeeper": "bash -c 'trap \"COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down\" EXIT INT TERM; docker network create infra 2>/dev/null || true && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml down && COMPOSE_PROJECT_NAME=gatekeeper docker-compose -f docker-compose.gatekeeper.yml up --build --remove-orphans'",
"dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d", "dev:mcps:up": "docker-compose -f docker-compose.mcps.yml up -d",
"dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down", "dev:mcps:down": "docker-compose -f docker-compose.mcps.yml down",
"dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" run dev", "dev:mcps:watch": "pnpm -r --filter=\"./packages/*-mcp\" exec tsc -w",
"dev:mcps": "npm run dev:mcps:up && npm run dev:mcps:watch", "dev:mcps": "npm run dev:mcps:up && npm run dev:mcps:watch",
"lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint", "lint": "pnpm -r --filter='./packages/**' --filter='./apps/**' lint",
"test": "pnpm -r test", "test": "pnpm -r test",

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/concept-engine", "name": "@mintel/concept-engine",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"description": "AI-powered web project concept generation and analysis", "description": "AI-powered web project concept generation and analysis",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/estimation-engine", "name": "@mintel/estimation-engine",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/gatekeeper", "name": "@mintel/gatekeeper",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",

View File

@@ -181,8 +181,8 @@ jobs:
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: git.infra.mintel.me registry: git.infra.mintel.me
username: ${{ github.actor }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.NPM_TOKEN }}
- name: 🏗️ Docker Build & Push - name: 🏗️ Docker Build & Push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@@ -262,7 +262,7 @@ jobs:
set -e set -e
cd "/home/deploy/sites/${{ github.event.repository.name }}" cd "/home/deploy/sites/${{ github.event.repository.name }}"
chmod 600 "$ENV_FILE" chmod 600 "$ENV_FILE"
echo "${{ secrets.GITHUB_TOKEN }}" | docker login git.infra.mintel.me -u "${{ github.actor }}" --password-stdin echo "${{ secrets.NPM_TOKEN }}" | docker login git.infra.mintel.me -u "${{ github.repository_owner }}" --password-stdin
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" pull docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" pull
docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" up -d --remove-orphans docker compose -p "$PROJECT_NAME" --env-file "$ENV_FILE" up -d --remove-orphans
docker system prune -f --filter "until=24h" docker system prune -f --filter "until=24h"

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/journaling", "name": "@mintel/journaling",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",
"module": "./dist/index.js", "module": "./dist/index.js",

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/page-audit", "name": "@mintel/page-audit",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"description": "AI-powered website IST-analysis using DataForSEO and Gemini", "description": "AI-powered website IST-analysis using DataForSEO and Gemini",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -0,0 +1,2 @@
@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm/
//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=263e7f75d8ada27f3a2e71fd6bd9d95298d48a4d

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/payload-ai", "name": "@mintel/payload-ai",
"version": "1.9.10", "version": "1.9.15",
"private": true,
"description": "Reusable Payload CMS AI Extensions", "description": "Reusable Payload CMS AI Extensions",
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -1,6 +1,8 @@
import type { Config, Plugin } from 'payload' import type { Config, Plugin } from 'payload'
import { AIChatPermissionsCollection } from './collections/AIChatPermissions.js' import { AIChatPermissionsCollection } from './collections/AIChatPermissions.js'
import type { PayloadChatPluginConfig } from './types.js' import type { PayloadChatPluginConfig } from './types.js'
import { optimizePostEndpoint } from './endpoints/optimizeEndpoint.js'
import { generateSlugEndpoint, generateThumbnailEndpoint, generateSingleFieldEndpoint } from './endpoints/generateEndpoints.js'
export const payloadChatPlugin = export const payloadChatPlugin =
(pluginOptions: PayloadChatPluginConfig): Plugin => (pluginOptions: PayloadChatPluginConfig): Plugin =>
@@ -48,6 +50,26 @@ export const payloadChatPlugin =
return Response.json({ message: "Chat endpoint active" }) return Response.json({ message: "Chat endpoint active" })
}, },
}, },
{
path: '/api/mintel-ai/optimize',
method: 'post',
handler: optimizePostEndpoint,
},
{
path: '/api/mintel-ai/generate-slug',
method: 'post',
handler: generateSlugEndpoint,
},
{
path: '/api/mintel-ai/generate-thumbnail',
method: 'post',
handler: generateThumbnailEndpoint,
},
{
path: '/api/mintel-ai/generate-single-field',
method: 'post',
handler: generateSingleFieldEndpoint,
},
] ]
// 3. Inject Chat React Component into Admin UI // 3. Inject Chat React Component into Admin UI

View File

@@ -2,8 +2,6 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useField, useDocumentInfo, useForm } from "@payloadcms/ui"; import { useField, useDocumentInfo, useForm } from "@payloadcms/ui";
import { generateSingleFieldAction } from "../../actions/generateField.js";
export function AiFieldButton({ path, field }: { path: string; field: any }) { export function AiFieldButton({ path, field }: { path: string; field: any }) {
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [instructions, setInstructions] = useState(""); const [instructions, setInstructions] = useState("");
@@ -44,19 +42,26 @@ export function AiFieldButton({ path, field }: { path: string; field: any }) {
? field.admin.description ? field.admin.description
: ""; : "";
const res = await generateSingleFieldAction( const resData = await fetch("/api/api/mintel-ai/generate-single-field", {
(title as string) || "", method: "POST",
draftContent, headers: { "Content-Type": "application/json" },
fieldName, body: JSON.stringify({
fieldDescription, documentTitle: (title as string) || "",
instructions, documentContent: draftContent,
); fieldName,
fieldDescription,
instructions,
}),
});
const res = await resData.json();
if (res.success && res.text) { if (res.success && res.text) {
setValue(res.text); setValue(res.text);
} else { } else {
alert("Fehler: " + res.error); alert("Fehler: " + res.error);
} }
} catch (e) { } catch (e: any) {
console.error(e)
alert("Fehler bei der Generierung."); alert("Fehler bei der Generierung.");
} finally { } finally {
setIsGenerating(false); setIsGenerating(false);

View File

@@ -2,8 +2,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useForm, useField } from "@payloadcms/ui"; import { useForm, useField } from "@payloadcms/ui";
import { generateSlugAction } from "../../actions/generateField.js";
export function GenerateSlugButton({ path }: { path: string }) { export function GenerateSlugButton({ path }: { path: string }) {
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [instructions, setInstructions] = useState(""); const [instructions, setInstructions] = useState("");
@@ -45,18 +43,24 @@ export function GenerateSlugButton({ path }: { path: string }) {
setIsGenerating(true); setIsGenerating(true);
try { try {
const res = await generateSlugAction( const resData = await fetch("/api/api/mintel-ai/generate-slug", {
title, method: "POST",
draftContent, headers: { "Content-Type": "application/json" },
initialValue as string, body: JSON.stringify({
instructions, title,
); draftContent,
oldSlug: initialValue as string,
instructions,
}),
});
const res = await resData.json();
if (res.success && res.slug) { if (res.success && res.slug) {
setValue(res.slug); setValue(res.slug);
} else { } else {
alert("Fehler: " + res.error); alert("Fehler: " + res.error);
} }
} catch (e) { } catch (e: any) {
console.error(e); console.error(e);
alert("Unerwarteter Fehler."); alert("Unerwarteter Fehler.");
} finally { } finally {

View File

@@ -2,8 +2,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useForm, useField } from "@payloadcms/ui"; import { useForm, useField } from "@payloadcms/ui";
import { generateThumbnailAction } from "../../actions/generateField.js";
export function GenerateThumbnailButton({ path }: { path: string }) { export function GenerateThumbnailButton({ path }: { path: string }) {
const [isGenerating, setIsGenerating] = useState(false); const [isGenerating, setIsGenerating] = useState(false);
const [instructions, setInstructions] = useState(""); const [instructions, setInstructions] = useState("");
@@ -45,17 +43,23 @@ export function GenerateThumbnailButton({ path }: { path: string }) {
setIsGenerating(true); setIsGenerating(true);
try { try {
const res = await generateThumbnailAction( const resData = await fetch("/api/api/mintel-ai/generate-thumbnail", {
draftContent, method: "POST",
title, headers: { "Content-Type": "application/json" },
instructions, body: JSON.stringify({
); draftContent,
title,
instructions,
}),
});
const res = await resData.json();
if (res.success && res.mediaId) { if (res.success && res.mediaId) {
setValue(res.mediaId); setValue(res.mediaId);
} else { } else {
alert("Fehler: " + res.error); alert("Fehler: " + res.error);
} }
} catch (e) { } catch (e: any) {
console.error(e); console.error(e);
alert("Unerwarteter Fehler."); alert("Unerwarteter Fehler.");
} finally { } finally {

View File

@@ -2,7 +2,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useForm, useDocumentInfo } from "@payloadcms/ui"; import { useForm, useDocumentInfo } from "@payloadcms/ui";
import { optimizePostText } from "../actions/optimizePost.js";
import { Button } from "@payloadcms/ui"; import { Button } from "@payloadcms/ui";
export function OptimizeButton() { export function OptimizeButton() {
@@ -57,7 +56,12 @@ export function OptimizeButton() {
// 2. We inject the title so the AI knows what it's writing about // 2. We inject the title so the AI knows what it's writing about
const payloadText = `---\ntitle: "${title}"\n---\n\n${draftContent}`; const payloadText = `---\ntitle: "${title}"\n---\n\n${draftContent}`;
const response = await optimizePostText(payloadText, instructions); const res = await fetch("/api/api/mintel-ai/optimize", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ draftContent: payloadText, instructions }),
});
const response = await res.json();
if (response.success && response.lexicalAST) { if (response.success && response.lexicalAST) {
// 3. Inject the new Lexical AST directly into the field form state // 3. Inject the new Lexical AST directly into the field form state

View File

@@ -1,8 +1,4 @@
"use server"; import { PayloadRequest } from "payload";
import { getPayloadHMR } from "@payloadcms/next/utilities";
// @ts-ignore - dynamic config resolution from next.js payload plugin
import configPromise from "@payload-config";
import * as fs from "node:fs/promises"; import * as fs from "node:fs/promises";
import * as path from "node:path"; import * as path from "node:path";
import * as os from "node:os"; import * as os from "node:os";
@@ -30,13 +26,9 @@ async function getOrchestrator() {
}); });
} }
export async function generateSlugAction( export const generateSlugEndpoint = async (req: PayloadRequest) => {
title: string,
draftContent: string,
oldSlug?: string,
instructions?: string,
) {
try { try {
const { title, draftContent, oldSlug, instructions } = (await req.json?.() || {}) as any;
const orchestrator = await getOrchestrator(); const orchestrator = await getOrchestrator();
const newSlug = await orchestrator.generateSlug( const newSlug = await orchestrator.generateSlug(
draftContent, draftContent,
@@ -45,9 +37,8 @@ export async function generateSlugAction(
); );
if (oldSlug && oldSlug !== newSlug) { if (oldSlug && oldSlug !== newSlug) {
const payload = await getPayloadHMR({ config: configPromise as any }); await req.payload.create({
await payload.create({ collection: "redirects" as any,
collection: "redirects",
data: { data: {
from: oldSlug, from: oldSlug,
to: newSlug, to: newSlug,
@@ -55,42 +46,25 @@ export async function generateSlugAction(
}); });
} }
return { success: true, slug: newSlug }; return Response.json({ success: true, slug: newSlug });
} catch (e: any) { } catch (e: any) {
return { success: false, error: e.message }; return Response.json({ success: false, error: e.message }, { status: 500 });
} }
} }
export async function generateThumbnailAction( export const generateThumbnailEndpoint = async (req: PayloadRequest) => {
draftContent: string,
title?: string,
instructions?: string,
) {
try { try {
const payload = await getPayloadHMR({ config: configPromise as any }); const { draftContent, title, instructions } = (await req.json?.() || {}) as any;
const OPENROUTER_KEY = const OPENROUTER_KEY =
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY; process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
const REPLICATE_KEY = process.env.REPLICATE_API_KEY; const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
if (!OPENROUTER_KEY) { if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY in .env");
throw new Error("Missing OPENROUTER_API_KEY in .env"); if (!REPLICATE_KEY) throw new Error("Missing REPLICATE_API_KEY in .env");
}
if (!REPLICATE_KEY) {
throw new Error(
"Missing REPLICATE_API_KEY in .env (Required for Thumbnails)",
);
}
const importDynamic = new Function( const importDynamic = new Function("modulePath", "return import(modulePath)");
"modulePath", const { AiBlogPostOrchestrator } = await importDynamic("@mintel/content-engine");
"return import(modulePath)", const { ThumbnailGenerator } = await importDynamic("@mintel/thumbnail-generator");
);
const { AiBlogPostOrchestrator } = await importDynamic(
"@mintel/content-engine",
);
const { ThumbnailGenerator } = await importDynamic(
"@mintel/thumbnail-generator",
);
const orchestrator = new AiBlogPostOrchestrator({ const orchestrator = new AiBlogPostOrchestrator({
apiKey: OPENROUTER_KEY, apiKey: OPENROUTER_KEY,
@@ -112,8 +86,8 @@ export async function generateThumbnailAction(
const stat = await fs.stat(tmpPath); const stat = await fs.stat(tmpPath);
const fileName = path.basename(tmpPath); const fileName = path.basename(tmpPath);
const newMedia = await payload.create({ const newMedia = await req.payload.create({
collection: "media", collection: "media" as any,
data: { data: {
alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail", alt: title ? `Thumbnail for ${title}` : "AI Generated Thumbnail",
}, },
@@ -125,31 +99,24 @@ export async function generateThumbnailAction(
}, },
}); });
// Cleanup temp file
await fs.unlink(tmpPath).catch(() => { }); await fs.unlink(tmpPath).catch(() => { });
return { success: true, mediaId: newMedia.id }; return Response.json({ success: true, mediaId: newMedia.id });
} catch (e: any) { } catch (e: any) {
return { success: false, error: e.message }; return Response.json({ success: false, error: e.message }, { status: 500 });
} }
} }
export async function generateSingleFieldAction(
documentTitle: string, export const generateSingleFieldEndpoint = async (req: PayloadRequest) => {
documentContent: string,
fieldName: string,
fieldDescription: string,
instructions?: string,
) {
try { try {
const { documentTitle, documentContent, fieldName, fieldDescription, instructions } = (await req.json?.() || {}) as any;
const OPENROUTER_KEY = const OPENROUTER_KEY =
process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY; process.env.OPENROUTER_KEY || process.env.OPENROUTER_API_KEY;
if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY"); if (!OPENROUTER_KEY) throw new Error("Missing OPENROUTER_API_KEY");
const payload = await getPayloadHMR({ config: configPromise as any }); const contextDocsData = await req.payload.find({
collection: "context-files" as any,
// Fetch context documents from DB
const contextDocsData = await payload.find({
collection: "context-files",
limit: 100, limit: 100,
}); });
const projectContext = contextDocsData.docs const projectContext = contextDocsData.docs
@@ -184,8 +151,8 @@ CRITICAL RULES:
}); });
const data = await res.json(); const data = await res.json();
const text = data.choices?.[0]?.message?.content?.trim() || ""; const text = data.choices?.[0]?.message?.content?.trim() || "";
return { success: true, text }; return Response.json({ success: true, text });
} catch (e: any) { } catch (e: any) {
return { success: false, error: e.message }; return Response.json({ success: false, error: e.message }, { status: 500 });
} }
} }

View File

@@ -1,17 +1,15 @@
"use server"; import { PayloadRequest } from 'payload'
import { parseMarkdownToLexical } from "../utils/lexicalParser.js";
import { parseMarkdownToLexical } from "../utils/lexicalParser"; export const optimizePostEndpoint = async (req: PayloadRequest) => {
import { getPayloadHMR } from "@payloadcms/next/utilities";
// @ts-ignore - dynamic config resolution from next.js payload plugin
import configPromise from "@payload-config";
export async function optimizePostText(
draftContent: string,
instructions?: string,
) {
try { try {
const payload = await getPayloadHMR({ config: configPromise as any }); const { draftContent, instructions } = (await req.json?.() || {}) as { draftContent: string; instructions?: string };
const globalAiSettings = (await payload.findGlobal({ slug: "ai-settings" })) as any;
if (!draftContent) {
return Response.json({ error: 'Missing draftContent' }, { status: 400 })
}
const globalAiSettings = (await req.payload.findGlobal({ slug: "ai-settings" })) as any;
const customSources = const customSources =
globalAiSettings?.customSources?.map((s: any) => s.sourceName) || []; globalAiSettings?.customSources?.map((s: any) => s.sourceName) || [];
@@ -20,18 +18,12 @@ export async function optimizePostText(
const REPLICATE_KEY = process.env.REPLICATE_API_KEY; const REPLICATE_KEY = process.env.REPLICATE_API_KEY;
if (!OPENROUTER_KEY) { if (!OPENROUTER_KEY) {
throw new Error( return Response.json({ error: "OPENROUTER_KEY not found in environment." }, { status: 500 })
"OPENROUTER_KEY or OPENROUTER_API_KEY not found in environment.",
);
} }
const importDynamic = new Function( // Dynamically import to avoid bundling it into client components that might accidentally import this file
"modulePath", const importDynamic = new Function("modulePath", "return import(modulePath)");
"return import(modulePath)", const { AiBlogPostOrchestrator } = await importDynamic("@mintel/content-engine");
);
const { AiBlogPostOrchestrator } = await importDynamic(
"@mintel/content-engine",
);
const orchestrator = new AiBlogPostOrchestrator({ const orchestrator = new AiBlogPostOrchestrator({
apiKey: OPENROUTER_KEY, apiKey: OPENROUTER_KEY,
@@ -39,9 +31,8 @@ export async function optimizePostText(
model: "google/gemini-3-flash-preview", model: "google/gemini-3-flash-preview",
}); });
// Fetch context documents purely from DB const contextDocsData = await req.payload.find({
const contextDocsData = await payload.find({ collection: "context-files" as any,
collection: "context-files",
limit: 100, limit: 100,
}); });
const projectContext = contextDocsData.docs.map((doc: any) => doc.content); const projectContext = contextDocsData.docs.map((doc: any) => doc.content);
@@ -49,19 +40,19 @@ export async function optimizePostText(
const optimizedMarkdown = await orchestrator.optimizeDocument({ const optimizedMarkdown = await orchestrator.optimizeDocument({
content: draftContent, content: draftContent,
projectContext, projectContext,
availableComponents: [], // Removed hardcoded config.components dependency availableComponents: [],
instructions, instructions,
internalLinks: [], internalLinks: [],
customSources, customSources,
}); });
if (!optimizedMarkdown || typeof optimizedMarkdown !== "string") { if (!optimizedMarkdown || typeof optimizedMarkdown !== "string") {
throw new Error("AI returned invalid markup."); return Response.json({ error: "AI returned invalid markup." }, { status: 500 })
} }
const blocks = parseMarkdownToLexical(optimizedMarkdown); const blocks = parseMarkdownToLexical(optimizedMarkdown);
return { return Response.json({
success: true, success: true,
lexicalAST: { lexicalAST: {
root: { root: {
@@ -73,12 +64,12 @@ export async function optimizePostText(
direction: "ltr", direction: "ltr",
}, },
}, },
}; })
} catch (error: any) { } catch (error: any) {
console.error("Failed to optimize post:", error); console.error("Failed to optimize post in endpoint:", error);
return { return Response.json({
success: false, success: false,
error: error.message || "An unknown error occurred during optimization.", error: error.message || "An unknown error occurred during optimization.",
}; }, { status: 500 })
} }
} }

View File

@@ -3,16 +3,14 @@
* Primary entry point for reusing Mintel AI extensions in Payload CMS. * Primary entry point for reusing Mintel AI extensions in Payload CMS.
*/ */
export * from './globals/AiSettings'; export * from './globals/AiSettings.js';
export * from './actions/generateField'; export * from './components/FieldGenerators/AiFieldButton.js';
export * from './actions/optimizePost'; export * from './components/AiMediaButtons.js';
export * from './components/FieldGenerators/AiFieldButton'; export * from './components/OptimizeButton.js';
export * from './components/AiMediaButtons'; export * from './components/FieldGenerators/GenerateThumbnailButton.js';
export * from './components/OptimizeButton'; export * from './components/FieldGenerators/GenerateSlugButton.js';
export * from './components/FieldGenerators/GenerateThumbnailButton'; export * from './utils/lexicalParser.js';
export * from './components/FieldGenerators/GenerateSlugButton'; export * from './endpoints/replicateMediaEndpoint.js';
export * from './utils/lexicalParser';
export * from './endpoints/replicateMediaEndpoint';
export * from './chatPlugin.js'; export * from './chatPlugin.js';
export * from './types.js'; export * from './types.js';
export * from './endpoints/chatEndpoint.js'; export * from './endpoints/chatEndpoint.js';

View File

@@ -1,5 +1,8 @@
declare module "@payload-config" { export type PayloadChatPluginConfig = {
import { Config } from "payload"; enabled?: boolean
const configPromise: Promise<any>; /** Render the chat bubble on the bottom right? Defaults to true */
export default configPromise; renderChatBubble?: boolean
allowedCollections?: string[]
mcpServers?: any[]
} }

View File

@@ -1,7 +1,6 @@
{ {
"name": "@mintel/seo-engine", "name": "@mintel/seo-engine",
"version": "1.9.10", "version": "1.9.10",
"private": true,
"description": "AI-powered SEO keyword and topic cluster evaluation engine", "description": "AI-powered SEO keyword and topic cluster evaluation engine",
"type": "module", "type": "module",
"main": "./dist/index.js", "main": "./dist/index.js",