Compare commits
10 Commits
v1.0.0
...
b2f6627ec5
| Author | SHA1 | Date | |
|---|---|---|---|
| b2f6627ec5 | |||
| 2ab5a8a41f | |||
| e43c980a5d | |||
| 88b4626d6e | |||
| 90856da773 | |||
| 964cd79ca8 | |||
| 9c5e2c6099 | |||
| 984a641b90 | |||
| c8ff76f299 | |||
| 1fffdf00ee |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -47,6 +47,7 @@ pnpm-debug.log*
|
|||||||
.cache/
|
.cache/
|
||||||
cloned-websites/
|
cloned-websites/
|
||||||
storage/
|
storage/
|
||||||
|
data/postgres/
|
||||||
|
|
||||||
# Estimation Engine Data
|
# Estimation Engine Data
|
||||||
data/crawls/
|
data/crawls/
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use server";
|
"use server";
|
||||||
import { handleServerFunctions as payloadHandleServerFunctions } from "@payloadcms/next/layouts";
|
import { handleServerFunctions as payloadHandleServerFunctions } from "@payloadcms/next/layouts";
|
||||||
import config from "@payload-config";
|
import config from "@payload-config";
|
||||||
// @ts-expect-error - Payload generates this file during the build process
|
// @ts-ignore - Payload generates this file during the build process
|
||||||
import { importMap } from "./admin/importMap";
|
import { importMap } from "./admin/importMap";
|
||||||
|
|
||||||
export const handleServerFunctions = async (args: any) => {
|
export const handleServerFunctions = async (args: any) => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
|||||||
|
|
||||||
import configPromise from "@payload-config";
|
import configPromise from "@payload-config";
|
||||||
import { RootPage, generatePageMetadata } from "@payloadcms/next/views";
|
import { RootPage, generatePageMetadata } from "@payloadcms/next/views";
|
||||||
// @ts-expect-error - Payload generates this file during the build process
|
// @ts-ignore - Payload generates this file during the build process
|
||||||
import { importMap } from "../importMap";
|
import { importMap } from "../importMap";
|
||||||
|
|
||||||
type Args = {
|
type Args = {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { RootLayout } from "@payloadcms/next/layouts";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { handleServerFunctions } from "./actions";
|
import { handleServerFunctions } from "./actions";
|
||||||
// @ts-expect-error - Payload generates this file during the build process
|
// @ts-ignore - Payload generates this file during the build process
|
||||||
import { importMap } from "./admin/importMap";
|
import { importMap } from "./admin/importMap";
|
||||||
|
|
||||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const nextConfig = {
|
|||||||
'@mintel/content-engine',
|
'@mintel/content-engine',
|
||||||
'@mintel/concept-engine',
|
'@mintel/concept-engine',
|
||||||
'@mintel/estimation-engine',
|
'@mintel/estimation-engine',
|
||||||
|
'@mintel/payload-ai',
|
||||||
'@mintel/pdf',
|
'@mintel/pdf',
|
||||||
'canvas',
|
'canvas',
|
||||||
'sharp',
|
'sharp',
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"@mintel/content-engine": "link:../../../at-mintel/packages/content-engine",
|
"@mintel/content-engine": "link:../../../at-mintel/packages/content-engine",
|
||||||
"@mintel/estimation-engine": "link:../../../at-mintel/packages/estimation-engine",
|
"@mintel/estimation-engine": "link:../../../at-mintel/packages/estimation-engine",
|
||||||
"@mintel/meme-generator": "link:../../../at-mintel/packages/meme-generator",
|
"@mintel/meme-generator": "link:../../../at-mintel/packages/meme-generator",
|
||||||
|
"@mintel/payload-ai": "^1.9.7",
|
||||||
"@mintel/pdf": "link:../../../at-mintel/packages/pdf-library",
|
"@mintel/pdf": "link:../../../at-mintel/packages/pdf-library",
|
||||||
"@mintel/thumbnail-generator": "link:../../../at-mintel/packages/thumbnail-generator",
|
"@mintel/thumbnail-generator": "link:../../../at-mintel/packages/thumbnail-generator",
|
||||||
"@next/mdx": "^16.1.6",
|
"@next/mdx": "^16.1.6",
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
"react-tweet": "^3.3.0",
|
"react-tweet": "^3.3.0",
|
||||||
"recharts": "^3.7.0",
|
"recharts": "^3.7.0",
|
||||||
"remotion": "^4.0.414",
|
"remotion": "^4.0.414",
|
||||||
|
"replicate": "^1.4.0",
|
||||||
"require-in-the-middle": "^8.0.1",
|
"require-in-the-middle": "^8.0.1",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"shiki": "^1.24.2",
|
"shiki": "^1.24.2",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { CrmInteractions } from "./src/payload/collections/CrmInteractions";
|
|||||||
import { CrmTopics } from "./src/payload/collections/CrmTopics";
|
import { CrmTopics } from "./src/payload/collections/CrmTopics";
|
||||||
import { Projects } from "./src/payload/collections/Projects";
|
import { Projects } from "./src/payload/collections/Projects";
|
||||||
|
|
||||||
import { AiSettings } from "./src/payload/globals/AiSettings";
|
import { AiSettings } from "@mintel/payload-ai";
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
const filename = fileURLToPath(import.meta.url);
|
||||||
const dirname = path.dirname(filename);
|
const dirname = path.dirname(filename);
|
||||||
@@ -48,7 +48,7 @@ export default buildConfig({
|
|||||||
CrmInteractions,
|
CrmInteractions,
|
||||||
Projects,
|
Projects,
|
||||||
],
|
],
|
||||||
globals: [AiSettings],
|
globals: [AiSettings as any],
|
||||||
email: nodemailerAdapter({
|
email: nodemailerAdapter({
|
||||||
defaultFromAddress: process.env.MAIL_FROM || "info@mintel.me",
|
defaultFromAddress: process.env.MAIL_FROM || "info@mintel.me",
|
||||||
defaultFromName: "Mintel.me",
|
defaultFromName: "Mintel.me",
|
||||||
|
|||||||
@@ -3,10 +3,44 @@ import puppeteer from "puppeteer";
|
|||||||
const targetUrl = process.env.TEST_URL || "http://localhost:3000";
|
const targetUrl = process.env.TEST_URL || "http://localhost:3000";
|
||||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || "secret";
|
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || "secret";
|
||||||
|
|
||||||
|
async function fetchSitemapUrls(baseUrl: string): Promise<string[]> {
|
||||||
|
const sitemapUrl = `${baseUrl.replace(/\/$/, "")}/sitemap.xml`;
|
||||||
|
console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
|
||||||
|
try {
|
||||||
|
const response = await fetch(sitemapUrl);
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
// Simple regex to extract loc tags
|
||||||
|
const matches = text.matchAll(/<loc>(.*?)<\/loc>/g);
|
||||||
|
let urls = Array.from(matches, (m) => m[1]);
|
||||||
|
|
||||||
|
// Normalize to target URL instance
|
||||||
|
const urlPattern = /https?:\/\/[^\/]+/;
|
||||||
|
urls = [...new Set(urls)]
|
||||||
|
.filter((u) => u.startsWith("http"))
|
||||||
|
.map((u) => u.replace(urlPattern, baseUrl.replace(/\/$/, "")))
|
||||||
|
.sort();
|
||||||
|
|
||||||
|
console.log(`✅ Found ${urls.length} target URLs.`);
|
||||||
|
return urls;
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(`❌ Failed to fetch sitemap: ${err.message}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log(`\n🚀 Starting E2E Form Submission Check for: ${targetUrl}`);
|
console.log(`\n🚀 Starting Strict Asset Integrity Check for: ${targetUrl}`);
|
||||||
|
|
||||||
|
let urls = await fetchSitemapUrls(targetUrl);
|
||||||
|
|
||||||
|
if (urls.length === 0) {
|
||||||
|
console.warn(`⚠️ Falling back to just the homepage.`);
|
||||||
|
urls = [targetUrl];
|
||||||
|
}
|
||||||
|
|
||||||
// Launch browser with KLZ pattern: use system chromium via env
|
// Launch browser with KLZ pattern: use system chromium via env
|
||||||
|
console.log(`\n🕷️ Launching Puppeteer Headless Engine...`);
|
||||||
const browser = await puppeteer.launch({
|
const browser = await puppeteer.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
executablePath:
|
executablePath:
|
||||||
@@ -19,31 +53,88 @@ async function main() {
|
|||||||
"--disable-dev-shm-usage",
|
"--disable-dev-shm-usage",
|
||||||
"--disable-gpu",
|
"--disable-gpu",
|
||||||
"--ignore-certificate-errors",
|
"--ignore-certificate-errors",
|
||||||
|
"--disable-web-security",
|
||||||
|
"--disable-features=IsolateOrigins,site-per-process",
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
||||||
// Enable console logging from the page for debugging
|
let hasBrokenAssets = false;
|
||||||
page.on("console", (msg) => console.log(` [PAGE] ${msg.text()}`));
|
let currentScannedUrl = urls[0] || "";
|
||||||
page.on("pageerror", (err: Error) =>
|
|
||||||
console.error(` [PAGE ERROR] ${err.message}`),
|
// Listen for console logging from the page for debugging
|
||||||
);
|
page.on("console", (msg) => {
|
||||||
page.on("requestfailed", (req) =>
|
const type = msg.type();
|
||||||
console.error(
|
// Only capture errors and warnings, not info/logs
|
||||||
` [REQUEST FAILED] ${req.url()} - ${req.failure()?.errorText}`,
|
if (type === "error" || type === "warn") {
|
||||||
),
|
const text = msg.text();
|
||||||
);
|
// Exclude common noise
|
||||||
|
if (
|
||||||
|
text.includes("google-analytics") ||
|
||||||
|
text.includes("googletagmanager") ||
|
||||||
|
text.includes("Fast Refresh")
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
console.log(` [PAGE ${type.toUpperCase()}] ${text}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
page.on("pageerror", (err: Error) => {
|
||||||
|
if (currentScannedUrl.includes("showcase")) return;
|
||||||
|
console.error(` [PAGE EXCEPTION] ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen to ALL network responses to catch broken assets (404/500)
|
||||||
|
page.on("response", (response) => {
|
||||||
|
const status = response.status();
|
||||||
|
// Catch classic 404s and 500s on ANY fetch/image/script
|
||||||
|
if (
|
||||||
|
status >= 400 &&
|
||||||
|
status !== 429 &&
|
||||||
|
status !== 999 &&
|
||||||
|
!response.url().includes("google-analytics") &&
|
||||||
|
!response.url().includes("googletagmanager")
|
||||||
|
) {
|
||||||
|
const type = response.request().resourceType();
|
||||||
|
|
||||||
|
// We explicitly care about images, scripts, stylesheets, and fetches getting 404/500s.
|
||||||
|
if (
|
||||||
|
["image", "script", "stylesheet", "fetch", "xhr", "document"].includes(
|
||||||
|
type,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// Exclude showcase routes from strict sub-asset checking since they proxy external content
|
||||||
|
if (
|
||||||
|
(currentScannedUrl.includes("showcase") ||
|
||||||
|
response.url().includes("showcase")) &&
|
||||||
|
type !== "document"
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(
|
||||||
|
` [REQUEST FAILED] ${response.url()} - Status: ${status} (${type})`,
|
||||||
|
);
|
||||||
|
hasBrokenAssets = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Authenticate through Gatekeeper
|
// Authenticate through Gatekeeper
|
||||||
console.log(`\n🛡️ Authenticating through Gatekeeper...`);
|
console.log(`\n🛡️ Authenticating through Gatekeeper...`);
|
||||||
console.log(` Navigating to: ${targetUrl}`);
|
console.log(` Navigating to: ${urls[0]}`);
|
||||||
|
|
||||||
const response = await page.goto(targetUrl, {
|
const response = await page.goto(urls[0], {
|
||||||
waitUntil: "domcontentloaded",
|
waitUntil: "domcontentloaded",
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Give Gatekeeper a second to redirect if needed
|
||||||
|
console.log(` Waiting for potential Gatekeeper redirect...`);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||||
console.log(` Response status: ${response?.status()}`);
|
console.log(` Response status: ${response?.status()}`);
|
||||||
console.log(` Response URL: ${response?.url()}`);
|
console.log(` Response URL: ${response?.url()}`);
|
||||||
|
|
||||||
@@ -57,38 +148,80 @@ async function main() {
|
|||||||
}),
|
}),
|
||||||
page.click('button[type="submit"]'),
|
page.click('button[type="submit"]'),
|
||||||
]);
|
]);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||||
console.log(`✅ Gatekeeper authentication successful!`);
|
console.log(`✅ Gatekeeper authentication successful!`);
|
||||||
} else {
|
} else {
|
||||||
console.log(`✅ Already authenticated (no Gatekeeper gate detected).`);
|
console.log(`✅ Already authenticated (no Gatekeeper gate detected).`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic smoke test
|
// Scan each page
|
||||||
console.log(`\n🧪 Testing page load...`);
|
console.log(`\n🧪 Testing all ${urls.length} pages...`);
|
||||||
const title = await page.title();
|
for (let i = 0; i < urls.length; i++) {
|
||||||
console.log(`✅ Page Title: ${title}`);
|
const u = urls[i];
|
||||||
|
currentScannedUrl = u;
|
||||||
|
console.log(`\n[${i + 1}/${urls.length}] Scanning: ${u}`);
|
||||||
|
try {
|
||||||
|
await page.goto(u, { waitUntil: "domcontentloaded", timeout: 60000 });
|
||||||
|
|
||||||
if (title.toLowerCase().includes("mintel")) {
|
// Simulate a scroll to bottom to trigger lazy-loads if necessary
|
||||||
console.log(`✅ Basic smoke test passed!`);
|
await page.evaluate(async () => {
|
||||||
} else {
|
await new Promise<void>((resolve) => {
|
||||||
throw new Error(`Page title mismatch: "${title}"`);
|
let totalHeight = 0;
|
||||||
|
const distance = 500;
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
const scrollHeight = document.body.scrollHeight;
|
||||||
|
window.scrollBy(0, distance);
|
||||||
|
totalHeight += distance;
|
||||||
|
// Stop scrolling if we reached the bottom or scrolled for more than 5 seconds
|
||||||
|
if (totalHeight >= scrollHeight || totalHeight > 10000) {
|
||||||
|
clearInterval(timer);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Small delay for final hydration and asynchronous asset loading
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||||
|
|
||||||
|
const title = await page.title();
|
||||||
|
console.log(` ✅ Page Title: ${title}`);
|
||||||
|
|
||||||
|
if (!title) {
|
||||||
|
throw new Error(`Page title is missing.`);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(
|
||||||
|
` ❌ Timeout or navigation error on ${u}: ${err.message}`,
|
||||||
|
);
|
||||||
|
hasBrokenAssets = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(`❌ Test Failed: ${err.message}`);
|
console.error(`\n❌ Fatal Test Error: ${err.message}`);
|
||||||
// Take a screenshot for debugging
|
// Take a screenshot for debugging on crash
|
||||||
try {
|
try {
|
||||||
const screenshotPath = "/tmp/e2e-failure.png";
|
const screenshotPath = "/tmp/e2e-failure.png";
|
||||||
await page.screenshot({ path: screenshotPath, fullPage: true });
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
||||||
console.log(`📸 Screenshot saved to ${screenshotPath}`);
|
console.log(`📸 Screenshot saved to ${screenshotPath}`);
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore screenshot errors */
|
/* ignore */
|
||||||
}
|
}
|
||||||
console.log(` Current URL: ${page.url()}`);
|
hasBrokenAssets = true;
|
||||||
await browser.close();
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
console.log(`\n🎉 SUCCESS: E2E smoke test passed!`);
|
|
||||||
|
if (hasBrokenAssets) {
|
||||||
|
console.error(
|
||||||
|
`\n🚨 The CI build will now fail to prevent bad code from reaching production.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n🎉 SUCCESS: All ${urls.length} pages rendered perfectly with 0 broken assets!`,
|
||||||
|
);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
import { getPayload } from "payload";
|
|
||||||
import configPromise from "../payload.config";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { parseMarkdownToLexical } from "@mintel/payload-ai";
|
|
||||||
|
|
||||||
function parseMatter(content: string) {
|
|
||||||
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
||||||
if (!match) return { data: {}, content };
|
|
||||||
const data: Record<string, any> = {};
|
|
||||||
match[1].split("\n").forEach((line) => {
|
|
||||||
const [key, ...rest] = line.split(":");
|
|
||||||
if (key && rest.length) {
|
|
||||||
const field = key.trim();
|
|
||||||
let val = rest.join(":").trim();
|
|
||||||
if (val.startsWith("[")) {
|
|
||||||
// basic array parsing
|
|
||||||
data[field] = val
|
|
||||||
.slice(1, -1)
|
|
||||||
.split(",")
|
|
||||||
.map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
||||||
} else {
|
|
||||||
data[field] = val.replace(/^["']|["']$/g, "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return { data, content: match[2].trim() };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
const payload = await getPayload({ config: configPromise });
|
|
||||||
const contentDir = path.join(process.cwd(), "content", "blog");
|
|
||||||
const files = fs.readdirSync(contentDir).filter((f) => f.endsWith(".mdx"));
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const filePath = path.join(contentDir, file);
|
|
||||||
const content = fs.readFileSync(filePath, "utf-8");
|
|
||||||
const { data, content: body } = parseMatter(content);
|
|
||||||
|
|
||||||
const slug = file.replace(/\.mdx$/, "");
|
|
||||||
console.log(`Migrating ${slug}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const existing = await payload.find({
|
|
||||||
collection: "posts",
|
|
||||||
where: { slug: { equals: slug } },
|
|
||||||
});
|
|
||||||
|
|
||||||
const lexicalBlocks = parseMarkdownToLexical(body);
|
|
||||||
const lexicalAST = {
|
|
||||||
root: {
|
|
||||||
type: "root",
|
|
||||||
format: "",
|
|
||||||
indent: 0,
|
|
||||||
version: 1,
|
|
||||||
children: lexicalBlocks,
|
|
||||||
direction: "ltr",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle thumbnail mapping
|
|
||||||
let featuredImageId = null;
|
|
||||||
if (data.thumbnail) {
|
|
||||||
try {
|
|
||||||
// Remove leading slash and find local file
|
|
||||||
const localPath = path.join(
|
|
||||||
process.cwd(),
|
|
||||||
"public",
|
|
||||||
data.thumbnail.replace(/^\//, ""),
|
|
||||||
);
|
|
||||||
const fileName = path.basename(localPath);
|
|
||||||
|
|
||||||
if (fs.existsSync(localPath)) {
|
|
||||||
// Check if media already exists in Payload
|
|
||||||
const existingMedia = await payload.find({
|
|
||||||
collection: "media",
|
|
||||||
where: { filename: { equals: fileName } },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingMedia.docs.length > 0) {
|
|
||||||
featuredImageId = existingMedia.docs[0].id;
|
|
||||||
} else {
|
|
||||||
// Upload new media item
|
|
||||||
const fileData = fs.readFileSync(localPath);
|
|
||||||
const { size } = fs.statSync(localPath);
|
|
||||||
|
|
||||||
const newMedia = await payload.create({
|
|
||||||
collection: "media",
|
|
||||||
data: {
|
|
||||||
alt: data.title || fileName,
|
|
||||||
},
|
|
||||||
file: {
|
|
||||||
data: fileData,
|
|
||||||
name: fileName,
|
|
||||||
mimetype: fileName.endsWith(".png")
|
|
||||||
? "image/png"
|
|
||||||
: fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")
|
|
||||||
? "image/jpeg"
|
|
||||||
: "image/webp",
|
|
||||||
size,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
featuredImageId = newMedia.id;
|
|
||||||
console.log(` ↑ Uploaded thumbnail: ${fileName}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(
|
|
||||||
` ⚠ Warning: Could not process thumbnail ${data.thumbnail}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing.docs.length === 0) {
|
|
||||||
await payload.create({
|
|
||||||
collection: "posts",
|
|
||||||
data: {
|
|
||||||
title: data.title || slug,
|
|
||||||
slug,
|
|
||||||
description: data.description || "",
|
|
||||||
date: data.date
|
|
||||||
? new Date(data.date).toISOString()
|
|
||||||
: new Date().toISOString(),
|
|
||||||
tags: (data.tags || []).map((t: string) => ({ tag: t })),
|
|
||||||
content: lexicalAST as any,
|
|
||||||
featuredImage: featuredImageId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(`✔ Inserted ${slug}`);
|
|
||||||
} else {
|
|
||||||
await payload.update({
|
|
||||||
collection: "posts",
|
|
||||||
id: existing.docs[0].id,
|
|
||||||
data: {
|
|
||||||
content: lexicalAST as any,
|
|
||||||
featuredImage: featuredImageId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(`✔ Updated AST and thumbnail for ${slug}`);
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(`✘ FAILED ${slug}: ${err.message}`);
|
|
||||||
if (err.data?.errors) {
|
|
||||||
console.error(
|
|
||||||
` Validation errors:`,
|
|
||||||
JSON.stringify(err.data.errors, null, 2),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Migration complete.");
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
run().catch(console.error);
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
import { getPayload } from "payload";
|
|
||||||
import configPromise from "../payload.config";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import { parseMarkdownToLexical } from "@mintel/payload-ai";
|
|
||||||
|
|
||||||
function extractFrontmatter(content: string) {
|
|
||||||
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
||||||
if (!fmMatch) return {};
|
|
||||||
const fm = fmMatch[1];
|
|
||||||
const titleMatch = fm.match(/title:\s*"?([^"\n]+)"?/);
|
|
||||||
const descMatch = fm.match(/description:\s*"?([^"\n]+)"?/);
|
|
||||||
const tagsMatch = fm.match(/tags:\s*\[(.*?)\]/);
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: titleMatch ? titleMatch[1] : "Untitled Draft",
|
|
||||||
description: descMatch ? descMatch[1] : "No description",
|
|
||||||
tags: tagsMatch
|
|
||||||
? tagsMatch[1].split(",").map((s) => s.trim().replace(/"/g, ""))
|
|
||||||
: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
try {
|
|
||||||
const payload = await getPayload({ config: configPromise });
|
|
||||||
console.log("Payload initialized.");
|
|
||||||
|
|
||||||
const draftsDir = path.resolve(process.cwd(), "content/drafts");
|
|
||||||
const publicBlogDir = path.resolve(process.cwd(), "public/blog");
|
|
||||||
|
|
||||||
if (!fs.existsSync(draftsDir)) {
|
|
||||||
console.log(`Drafts directory not found at ${draftsDir}`);
|
|
||||||
process.exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = fs.readdirSync(draftsDir).filter((f) => f.endsWith(".md"));
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
console.log(`Processing ${file}...`);
|
|
||||||
const filePath = path.join(draftsDir, file);
|
|
||||||
const content = fs.readFileSync(filePath, "utf8");
|
|
||||||
|
|
||||||
const fm = extractFrontmatter(content);
|
|
||||||
const lexicalNodes = parseMarkdownToLexical(content);
|
|
||||||
const lexicalContent = {
|
|
||||||
root: {
|
|
||||||
type: "root",
|
|
||||||
format: "" as const,
|
|
||||||
indent: 0,
|
|
||||||
version: 1,
|
|
||||||
direction: "ltr" as const,
|
|
||||||
children: lexicalNodes,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload thumbnail if exists
|
|
||||||
let featuredImageId = null;
|
|
||||||
const thumbPath = path.join(publicBlogDir, `${file}.png`);
|
|
||||||
if (fs.existsSync(thumbPath)) {
|
|
||||||
console.log(`Uploading thumbnail ${file}.png...`);
|
|
||||||
const fileData = fs.readFileSync(thumbPath);
|
|
||||||
const stat = fs.statSync(thumbPath);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const newMedia = await payload.create({
|
|
||||||
collection: "media",
|
|
||||||
data: {
|
|
||||||
alt: `Thumbnail for ${fm.title}`,
|
|
||||||
},
|
|
||||||
file: {
|
|
||||||
data: fileData,
|
|
||||||
name: `optimized-${file}.png`,
|
|
||||||
mimetype: "image/png",
|
|
||||||
size: stat.size,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
featuredImageId = newMedia.id;
|
|
||||||
} catch (e) {
|
|
||||||
console.log("Failed to upload thumbnail", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tagsArray = fm.tags.map((tag) => ({ tag }));
|
|
||||||
|
|
||||||
const slug = fm.title
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, "-")
|
|
||||||
.replace(/(^-|-$)/g, "")
|
|
||||||
.substring(0, 60);
|
|
||||||
|
|
||||||
// Check if already exists
|
|
||||||
const existing = await payload.find({
|
|
||||||
collection: "posts",
|
|
||||||
where: { slug: { equals: slug } },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existing.totalDocs === 0) {
|
|
||||||
await payload.create({
|
|
||||||
collection: "posts",
|
|
||||||
data: {
|
|
||||||
title: fm.title,
|
|
||||||
slug: slug,
|
|
||||||
description: fm.description,
|
|
||||||
date: new Date().toISOString(),
|
|
||||||
tags: tagsArray,
|
|
||||||
featuredImage: featuredImageId,
|
|
||||||
content: lexicalContent,
|
|
||||||
_status: "published",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log(`Created CMS entry for ${file}.`);
|
|
||||||
count++;
|
|
||||||
} else {
|
|
||||||
console.log(`Post with slug ${slug} already exists. Skipping.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Migration successful! Added ${count} new optimized posts to the database.`,
|
|
||||||
);
|
|
||||||
process.exit(0);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Migration failed:", e);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run();
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// @ts-nocheck
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -70,16 +71,11 @@ const AGBSection = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface AgbsPDFProps {
|
interface AgbsPDFProps {
|
||||||
headerIcon?: string;
|
|
||||||
footerLogo?: string;
|
footerLogo?: string;
|
||||||
mode?: "estimation" | "full";
|
mode?: "estimation" | "full";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AgbsPDF = ({
|
export const AgbsPDF = ({ footerLogo, mode = "full" }: AgbsPDFProps) => {
|
||||||
headerIcon,
|
|
||||||
footerLogo,
|
|
||||||
mode = "full",
|
|
||||||
}: AgbsPDFProps) => {
|
|
||||||
const date = new Date().toLocaleDateString("de-DE", {
|
const date = new Date().toLocaleDateString("de-DE", {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "long",
|
month: "long",
|
||||||
@@ -226,7 +222,7 @@ export const AgbsPDF = ({
|
|||||||
<PDFPage size="A4" style={pdfStyles.page}>
|
<PDFPage size="A4" style={pdfStyles.page}>
|
||||||
<FoldingMarks />
|
<FoldingMarks />
|
||||||
<Header
|
<Header
|
||||||
icon={headerIcon}
|
icon={""}
|
||||||
showAddress={false}
|
showAddress={false}
|
||||||
sender={companyData as any}
|
sender={companyData as any}
|
||||||
recipient={{} as any}
|
recipient={{} as any}
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
"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 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { config } from "../../../content-engine.config";
|
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
import { parseMarkdownToLexical } from "../utils/lexicalParser";
|
|
||||||
import { getPayloadHMR } from "@payloadcms/next/utilities";
|
|
||||||
import configPromise from "@payload-config";
|
|
||||||
|
|
||||||
export async function optimizePostText(
|
|
||||||
draftContent: string,
|
|
||||||
instructions?: string,
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
const payload = await getPayloadHMR({ config: configPromise });
|
|
||||||
const globalAiSettings = await payload.findGlobal({ slug: "ai-settings" });
|
|
||||||
const customSources =
|
|
||||||
globalAiSettings?.customSources?.map((s: any) => s.sourceName) || [];
|
|
||||||
|
|
||||||
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(
|
|
||||||
"OPENROUTER_KEY or OPENROUTER_API_KEY not found in environment.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const importDynamic = new Function(
|
|
||||||
"modulePath",
|
|
||||||
"return import(modulePath)",
|
|
||||||
);
|
|
||||||
const { AiBlogPostOrchestrator } = await importDynamic(
|
|
||||||
"@mintel/content-engine",
|
|
||||||
);
|
|
||||||
|
|
||||||
const orchestrator = new AiBlogPostOrchestrator({
|
|
||||||
apiKey: OPENROUTER_KEY,
|
|
||||||
replicateApiKey: REPLICATE_KEY,
|
|
||||||
model: "google/gemini-3-flash-preview",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch context documents purely from DB
|
|
||||||
const contextDocsData = await payload.find({
|
|
||||||
collection: "context-files",
|
|
||||||
limit: 100,
|
|
||||||
});
|
|
||||||
const projectContext = contextDocsData.docs.map((doc) => doc.content);
|
|
||||||
|
|
||||||
const optimizedMarkdown = await orchestrator.optimizeDocument({
|
|
||||||
content: draftContent,
|
|
||||||
projectContext,
|
|
||||||
availableComponents: config.components,
|
|
||||||
instructions,
|
|
||||||
internalLinks: [],
|
|
||||||
customSources,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The orchestrator currently returns Markdown + JSX tags.
|
|
||||||
// We convert this mixed string into a basic Lexical AST map.
|
|
||||||
|
|
||||||
if (!optimizedMarkdown || typeof optimizedMarkdown !== "string") {
|
|
||||||
throw new Error("AI returned invalid markup.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const blocks = parseMarkdownToLexical(optimizedMarkdown);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
lexicalAST: {
|
|
||||||
root: {
|
|
||||||
type: "root",
|
|
||||||
format: "",
|
|
||||||
indent: 0,
|
|
||||||
version: 1,
|
|
||||||
children: blocks,
|
|
||||||
direction: "ltr",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error("Failed to optimize post:", error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error.message || "An unknown error occurred during optimization.",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,7 @@ export const ArticleBlockquoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für quote ein.",
|
description: "Geben Sie den mehrzeiligen Text für quote ein.",
|
||||||
@@ -37,7 +37,7 @@ export const ArticleBlockquoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für author ein.",
|
description: "Geben Sie den Text für author ein.",
|
||||||
@@ -49,7 +49,7 @@ export const ArticleBlockquoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für role ein.",
|
description: "Geben Sie den Text für role ein.",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const ArticleMemeBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für alt ein.",
|
description: "Geben Sie den Text für alt ein.",
|
||||||
@@ -44,7 +44,7 @@ export const ArticleMemeBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für caption ein.",
|
description: "Geben Sie den Text für caption ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ArticleQuoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für quote ein.",
|
description: "Geben Sie den mehrzeiligen Text für quote ein.",
|
||||||
@@ -39,7 +39,7 @@ export const ArticleQuoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für author ein.",
|
description: "Geben Sie den Text für author ein.",
|
||||||
@@ -51,7 +51,7 @@ export const ArticleQuoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für role ein.",
|
description: "Geben Sie den Text für role ein.",
|
||||||
@@ -63,7 +63,7 @@ export const ArticleQuoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für source ein.",
|
description: "Geben Sie den Text für source ein.",
|
||||||
@@ -75,7 +75,7 @@ export const ArticleQuoteBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für sourceUrl ein.",
|
description: "Geben Sie den Text für sourceUrl ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const BoldNumberBlock: MintelBlock = {
|
|||||||
description: "e.g. 53% or 2.5M€",
|
description: "e.g. 53% or 2.5M€",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -38,7 +38,7 @@ export const BoldNumberBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
@@ -50,7 +50,7 @@ export const BoldNumberBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für source ein.",
|
description: "Geben Sie den Text für source ein.",
|
||||||
@@ -62,7 +62,7 @@ export const BoldNumberBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für sourceUrl ein.",
|
description: "Geben Sie den Text für sourceUrl ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ButtonBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const CarouselBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für caption ein.",
|
description: "Geben Sie den Text für caption ein.",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const ComparisonRowBlock: MintelBlock = {
|
|||||||
description: "Optional overarching description for the comparison.",
|
description: "Optional overarching description for the comparison.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -44,7 +44,7 @@ export const ComparisonRowBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für negativeLabel ein.",
|
description: "Geben Sie den Text für negativeLabel ein.",
|
||||||
@@ -57,7 +57,7 @@ export const ComparisonRowBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für negativeText ein.",
|
description: "Geben Sie den Text für negativeText ein.",
|
||||||
@@ -71,7 +71,7 @@ export const ComparisonRowBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für positiveLabel ein.",
|
description: "Geben Sie den Text für positiveLabel ein.",
|
||||||
@@ -84,7 +84,7 @@ export const ComparisonRowBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für positiveText ein.",
|
description: "Geben Sie den Text für positiveText ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const DiagramFlowBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const DiagramGanttBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const DiagramPieBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const DiagramSequenceBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const DiagramStateBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const DiagramTimelineBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
description: "Geben Sie den mehrzeiligen Text für definition ein.",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const ExternalLinkBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const H2Block: MintelBlock = {
|
|||||||
description: "Geben Sie den Text für die H2-Überschrift ein.",
|
description: "Geben Sie den Text für die H2-Überschrift ein.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const H3Block: MintelBlock = {
|
|||||||
description: "Geben Sie den Text für die H3-Überschrift ein.",
|
description: "Geben Sie den Text für die H3-Überschrift ein.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const HeadingBlock: MintelBlock = {
|
|||||||
description: "Der Text der Überschrift.",
|
description: "Der Text der Überschrift.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const IconListBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
@@ -56,7 +56,7 @@ export const IconListBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für description ein.",
|
description: "Geben Sie den mehrzeiligen Text für description ein.",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const ImageTextBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const LeadMagnetBlock: MintelBlock = {
|
|||||||
description: "The strong headline for the Call-to-Action",
|
description: "The strong headline for the Call-to-Action",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -40,7 +40,7 @@ export const LeadMagnetBlock: MintelBlock = {
|
|||||||
description: "The value proposition text.",
|
description: "The value proposition text.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -53,7 +53,7 @@ export const LeadMagnetBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für buttonText ein.",
|
description: "Geben Sie den Text für buttonText ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const LeadParagraphBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const MarkerBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für text ein.",
|
description: "Geben Sie den Text für text ein.",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const MemeCardBlock: MintelBlock = {
|
|||||||
"The template ID from memegen.link (e.g. 'drake', 'disastergirl')",
|
"The template ID from memegen.link (e.g. 'drake', 'disastergirl')",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ export const MemeCardBlock: MintelBlock = {
|
|||||||
"Pipe-separated captions for the meme (e.g. 'Legacy Code|Mintel Stack'). Maximum 6 words per line.",
|
"Pipe-separated captions for the meme (e.g. 'Legacy Code|Mintel Stack'). Maximum 6 words per line.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const MermaidBlock: MintelBlock = {
|
|||||||
description: "Optional title displayed above the diagram.",
|
description: "Optional title displayed above the diagram.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -59,7 +59,7 @@ export const MermaidBlock: MintelBlock = {
|
|||||||
"The raw Mermaid.js syntax (e.g. graph TD... shadowing, loops, etc.).",
|
"The raw Mermaid.js syntax (e.g. graph TD... shadowing, loops, etc.).",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const MetricBarBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
@@ -50,7 +50,7 @@ export const MetricBarBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für unit ein.",
|
description: "Geben Sie den Text für unit ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ParagraphBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
description: "Geben Sie den mehrzeiligen Text für text ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const PerformanceChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const PremiumComparisonChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
@@ -37,7 +37,7 @@ export const PremiumComparisonChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für subtitle ein.",
|
description: "Geben Sie den Text für subtitle ein.",
|
||||||
@@ -54,7 +54,7 @@ export const PremiumComparisonChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
@@ -82,7 +82,7 @@ export const PremiumComparisonChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für unit ein.",
|
description: "Geben Sie den Text für unit ein.",
|
||||||
@@ -102,7 +102,7 @@ export const PremiumComparisonChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für description ein.",
|
description: "Geben Sie den Text für description ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const RevenueLossCalculatorBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const SectionBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export const StatsDisplayBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
@@ -38,7 +38,7 @@ export const StatsDisplayBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für value ein.",
|
description: "Geben Sie den Text für value ein.",
|
||||||
@@ -50,7 +50,7 @@ export const StatsDisplayBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für subtext ein.",
|
description: "Geben Sie den Text für subtext ein.",
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const StatsGridBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
@@ -43,7 +43,7 @@ export const StatsGridBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für value ein.",
|
description: "Geben Sie den Text für value ein.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const TLDRBlock: MintelBlock = {
|
|||||||
description: "The summary content for the TLDR box.",
|
description: "The summary content for the TLDR box.",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export const TrackedLinkBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const WaterfallChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
@@ -44,7 +44,7 @@ export const WaterfallChartBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für label ein.",
|
description: "Geben Sie den Text für label ein.",
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const WebVitalsScoreBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für description ein.",
|
description: "Geben Sie den Text für description ein.",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const YouTubeEmbedBlock: MintelBlock = {
|
|||||||
admin: {
|
admin: {
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/AiFieldButton#AiFieldButton",
|
"@mintel/payload-ai/components/AiFieldButton#AiFieldButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
description: "Geben Sie den Text für title ein.",
|
description: "Geben Sie den Text für title ein.",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { CollectionConfig } from "payload";
|
import type { CollectionConfig } from "payload";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
import { replicateMediaHandler } from "@mintel/payload-ai";
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url);
|
const filename = fileURLToPath(import.meta.url);
|
||||||
const dirname = path.dirname(filename);
|
const dirname = path.dirname(filename);
|
||||||
@@ -14,6 +15,13 @@ export const Media: CollectionConfig = {
|
|||||||
access: {
|
access: {
|
||||||
read: () => true, // Publicly readable
|
read: () => true, // Publicly readable
|
||||||
},
|
},
|
||||||
|
endpoints: [
|
||||||
|
{
|
||||||
|
path: "/:id/ai-process",
|
||||||
|
method: "post",
|
||||||
|
handler: replicateMediaHandler as any,
|
||||||
|
},
|
||||||
|
],
|
||||||
upload: {
|
upload: {
|
||||||
staticDir: path.resolve(dirname, "../../../../public/media"),
|
staticDir: path.resolve(dirname, "../../../../public/media"),
|
||||||
adminThumbnail: "thumbnail",
|
adminThumbnail: "thumbnail",
|
||||||
@@ -39,6 +47,15 @@ export const Media: CollectionConfig = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
|
{
|
||||||
|
name: "aiProcessButtons",
|
||||||
|
type: "ui",
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: "@mintel/payload-ai/components/AiMediaButtons#AiMediaButtons",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "alt",
|
name: "alt",
|
||||||
type: "text",
|
type: "text",
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const Posts: CollectionConfig = {
|
|||||||
admin: {
|
admin: {
|
||||||
position: "sidebar",
|
position: "sidebar",
|
||||||
components: {
|
components: {
|
||||||
Field: "@/src/payload/components/OptimizeButton#OptimizeButton",
|
Field: "@mintel/payload-ai/components/OptimizeButton#OptimizeButton",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -39,7 +39,7 @@ export const Posts: CollectionConfig = {
|
|||||||
position: "sidebar",
|
position: "sidebar",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/GenerateSlugButton#GenerateSlugButton",
|
"@mintel/payload-ai/components/GenerateSlugButton#GenerateSlugButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -100,7 +100,7 @@ export const Posts: CollectionConfig = {
|
|||||||
position: "sidebar",
|
position: "sidebar",
|
||||||
components: {
|
components: {
|
||||||
afterInput: [
|
afterInput: [
|
||||||
"@/src/payload/components/FieldGenerators/GenerateThumbnailButton#GenerateThumbnailButton",
|
"@mintel/payload-ai/components/GenerateThumbnailButton#GenerateThumbnailButton",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useField, useDocumentInfo, useForm } from "@payloadcms/ui";
|
|
||||||
import { generateSingleFieldAction } from "../../actions/generateField";
|
|
||||||
|
|
||||||
export function AiFieldButton({ path, field }: { path: string; field: any }) {
|
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
|
||||||
const [instructions, setInstructions] = useState("");
|
|
||||||
const [showInstructions, setShowInstructions] = useState(false);
|
|
||||||
|
|
||||||
// Payload hooks
|
|
||||||
const { value, setValue } = useField<string>({ path });
|
|
||||||
const { title } = useDocumentInfo();
|
|
||||||
const { fields } = useForm();
|
|
||||||
|
|
||||||
const extractText = (lexicalRoot: any): string => {
|
|
||||||
if (!lexicalRoot) return "";
|
|
||||||
let text = "";
|
|
||||||
const iterate = (node: any) => {
|
|
||||||
if (node.text) text += node.text + " ";
|
|
||||||
if (node.children) node.children.forEach(iterate);
|
|
||||||
};
|
|
||||||
iterate(lexicalRoot);
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGenerate = async (e: React.MouseEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const lexicalValue = fields?.content?.value as any;
|
|
||||||
const legacyValue = fields?.legacyMdx?.value as string;
|
|
||||||
let draftContent = legacyValue || "";
|
|
||||||
if (!draftContent && lexicalValue?.root) {
|
|
||||||
draftContent = extractText(lexicalValue.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsGenerating(true);
|
|
||||||
try {
|
|
||||||
// Field name is passed as a label usually, fallback to path
|
|
||||||
const fieldName = typeof field?.label === "string" ? field.label : path;
|
|
||||||
const fieldDescription =
|
|
||||||
typeof field?.admin?.description === "string"
|
|
||||||
? field.admin.description
|
|
||||||
: "";
|
|
||||||
|
|
||||||
const res = await generateSingleFieldAction(
|
|
||||||
(title as string) || "",
|
|
||||||
draftContent,
|
|
||||||
fieldName,
|
|
||||||
fieldDescription,
|
|
||||||
instructions,
|
|
||||||
);
|
|
||||||
if (res.success && res.text) {
|
|
||||||
setValue(res.text);
|
|
||||||
} else {
|
|
||||||
alert("Fehler: " + res.error);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
alert("Fehler bei der Generierung.");
|
|
||||||
} finally {
|
|
||||||
setIsGenerating(false);
|
|
||||||
setShowInstructions(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: "8px",
|
|
||||||
marginBottom: "8px",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
gap: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={isGenerating}
|
|
||||||
style={{
|
|
||||||
background: "var(--theme-elevation-150)",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
padding: "4px 12px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
fontSize: "12px",
|
|
||||||
cursor: isGenerating ? "not-allowed" : "pointer",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
opacity: isGenerating ? 0.6 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isGenerating ? "✨ AI arbeitet..." : "✨ AI Ausfüllen"}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setShowInstructions(!showInstructions);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
background: "transparent",
|
|
||||||
border: "none",
|
|
||||||
color: "var(--theme-elevation-500)",
|
|
||||||
fontSize: "12px",
|
|
||||||
cursor: "pointer",
|
|
||||||
textDecoration: "underline",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{showInstructions ? "Prompt verbergen" : "Mit Prompt..."}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{showInstructions && (
|
|
||||||
<textarea
|
|
||||||
value={instructions}
|
|
||||||
onChange={(e) => setInstructions(e.target.value)}
|
|
||||||
placeholder="Eigene Anweisung an AI (z.B. 'als catchy slogan')"
|
|
||||||
disabled={isGenerating}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "6px 8px",
|
|
||||||
fontSize: "12px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
background: "var(--theme-elevation-50)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
}}
|
|
||||||
rows={2}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { useForm, useField } from "@payloadcms/ui";
|
|
||||||
import { generateSlugAction } from "../../actions/generateField";
|
|
||||||
import { Button } from "@payloadcms/ui";
|
|
||||||
|
|
||||||
export function GenerateSlugButton({ path }: { path: string }) {
|
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
|
||||||
const [instructions, setInstructions] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isGenerating) return;
|
|
||||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue =
|
|
||||||
"Slug-Generierung läuft noch. Wenn Sie neu laden, bricht der Vorgang ab!";
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
}, [isGenerating]);
|
|
||||||
|
|
||||||
const { fields, replaceState } = useForm();
|
|
||||||
const { value, initialValue, setValue } = useField({ path });
|
|
||||||
|
|
||||||
const extractText = (lexicalRoot: any): string => {
|
|
||||||
if (!lexicalRoot) return "";
|
|
||||||
let text = "";
|
|
||||||
const iterate = (node: any) => {
|
|
||||||
if (node.text) text += node.text + " ";
|
|
||||||
if (node.children) node.children.forEach(iterate);
|
|
||||||
};
|
|
||||||
iterate(lexicalRoot);
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGenerate = async () => {
|
|
||||||
const title = (fields?.title?.value as string) || "";
|
|
||||||
const lexicalValue = fields?.content?.value as any;
|
|
||||||
const legacyValue = fields?.legacyMdx?.value as string;
|
|
||||||
|
|
||||||
let draftContent = legacyValue || "";
|
|
||||||
if (!draftContent && lexicalValue?.root) {
|
|
||||||
draftContent = extractText(lexicalValue.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsGenerating(true);
|
|
||||||
try {
|
|
||||||
const res = await generateSlugAction(
|
|
||||||
title,
|
|
||||||
draftContent,
|
|
||||||
initialValue as string,
|
|
||||||
instructions,
|
|
||||||
);
|
|
||||||
if (res.success && res.slug) {
|
|
||||||
setValue(res.slug);
|
|
||||||
} else {
|
|
||||||
alert("Fehler: " + res.error);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
alert("Unerwarteter Fehler.");
|
|
||||||
} finally {
|
|
||||||
setIsGenerating(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2 items-center mb-4">
|
|
||||||
<textarea
|
|
||||||
value={instructions}
|
|
||||||
onChange={(e) => setInstructions(e.target.value)}
|
|
||||||
placeholder="Optionale AI Anweisung für den Slug..."
|
|
||||||
disabled={isGenerating}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
minHeight: "40px",
|
|
||||||
padding: "8px 12px",
|
|
||||||
fontSize: "14px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
background: "var(--theme-elevation-50)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
marginBottom: "8px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={isGenerating}
|
|
||||||
className="btn btn--icon-style-none btn--size-medium ml-auto"
|
|
||||||
style={{
|
|
||||||
background: "var(--theme-elevation-150)",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
boxShadow: "0 2px 4px rgba(0,0,0,0.05)",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
opacity: isGenerating ? 0.6 : 1,
|
|
||||||
cursor: isGenerating ? "not-allowed" : "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="btn__content">
|
|
||||||
{isGenerating ? "✨ Generiere (ca 10s)..." : "✨ AI Slug Generieren"}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { useForm, useField } from "@payloadcms/ui";
|
|
||||||
import { generateThumbnailAction } from "../../actions/generateField";
|
|
||||||
import { Button } from "@payloadcms/ui";
|
|
||||||
|
|
||||||
export function GenerateThumbnailButton({ path }: { path: string }) {
|
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
|
||||||
const [instructions, setInstructions] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isGenerating) return;
|
|
||||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue =
|
|
||||||
"Bild-Generierung läuft noch (dies dauert bis zu 2 Minuten). Wenn Sie neu laden, bricht der Vorgang ab!";
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
}, [isGenerating]);
|
|
||||||
|
|
||||||
const { fields } = useForm();
|
|
||||||
const { value, setValue } = useField({ path });
|
|
||||||
|
|
||||||
const extractText = (lexicalRoot: any): string => {
|
|
||||||
if (!lexicalRoot) return "";
|
|
||||||
let text = "";
|
|
||||||
const iterate = (node: any) => {
|
|
||||||
if (node.text) text += node.text + " ";
|
|
||||||
if (node.children) node.children.forEach(iterate);
|
|
||||||
};
|
|
||||||
iterate(lexicalRoot);
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGenerate = async () => {
|
|
||||||
const title = (fields?.title?.value as string) || "";
|
|
||||||
const lexicalValue = fields?.content?.value as any;
|
|
||||||
const legacyValue = fields?.legacyMdx?.value as string;
|
|
||||||
|
|
||||||
let draftContent = legacyValue || "";
|
|
||||||
if (!draftContent && lexicalValue?.root) {
|
|
||||||
draftContent = extractText(lexicalValue.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsGenerating(true);
|
|
||||||
try {
|
|
||||||
const res = await generateThumbnailAction(
|
|
||||||
draftContent,
|
|
||||||
title,
|
|
||||||
instructions,
|
|
||||||
);
|
|
||||||
if (res.success && res.mediaId) {
|
|
||||||
setValue(res.mediaId);
|
|
||||||
} else {
|
|
||||||
alert("Fehler: " + res.error);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
alert("Unerwarteter Fehler.");
|
|
||||||
} finally {
|
|
||||||
setIsGenerating(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex gap-2 items-center mt-2 mb-4">
|
|
||||||
<textarea
|
|
||||||
value={instructions}
|
|
||||||
onChange={(e) => setInstructions(e.target.value)}
|
|
||||||
placeholder="Optionale Thumbnail-Detailanweisung (Farben, Stimmung, etc.)..."
|
|
||||||
disabled={isGenerating}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
minHeight: "40px",
|
|
||||||
padding: "8px 12px",
|
|
||||||
fontSize: "14px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
background: "var(--theme-elevation-50)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
marginBottom: "8px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleGenerate}
|
|
||||||
disabled={isGenerating}
|
|
||||||
className="btn btn--icon-style-none btn--size-medium"
|
|
||||||
style={{
|
|
||||||
background: "var(--theme-elevation-150)",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
boxShadow: "0 2px 4px rgba(0,0,0,0.05)",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
opacity: isGenerating ? 0.6 : 1,
|
|
||||||
cursor: isGenerating ? "not-allowed" : "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="btn__content">
|
|
||||||
{isGenerating
|
|
||||||
? "✨ AI arbeitet (dauert ca. 1-2 Min)..."
|
|
||||||
: "✨ AI Thumbnail Generieren"}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
|
||||||
import { useForm, useDocumentInfo } from "@payloadcms/ui";
|
|
||||||
import { optimizePostText } from "../actions/optimizePost";
|
|
||||||
import { Button } from "@payloadcms/ui";
|
|
||||||
|
|
||||||
export function OptimizeButton() {
|
|
||||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
|
||||||
const [instructions, setInstructions] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isOptimizing) return;
|
|
||||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue =
|
|
||||||
"Lexical Block-Optimierung läuft noch (dies dauert bis zu 45 Sekunden). Wenn Sie neu laden, bricht der Vorgang ab!";
|
|
||||||
};
|
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
||||||
}, [isOptimizing]);
|
|
||||||
|
|
||||||
const { fields, setModified, replaceState } = useForm();
|
|
||||||
const { title } = useDocumentInfo();
|
|
||||||
|
|
||||||
const handleOptimize = async () => {
|
|
||||||
// ... gathering draftContent logic
|
|
||||||
const lexicalValue = fields?.content?.value as any;
|
|
||||||
const legacyValue = fields?.legacyMdx?.value as string;
|
|
||||||
|
|
||||||
let draftContent = legacyValue || "";
|
|
||||||
|
|
||||||
const extractText = (lexicalRoot: any): string => {
|
|
||||||
if (!lexicalRoot) return "";
|
|
||||||
let text = "";
|
|
||||||
const iterate = (node: any) => {
|
|
||||||
if (node.text) text += node.text + " ";
|
|
||||||
if (node.children) node.children.forEach(iterate);
|
|
||||||
};
|
|
||||||
iterate(lexicalRoot);
|
|
||||||
return text;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!draftContent && lexicalValue?.root) {
|
|
||||||
draftContent = extractText(lexicalValue.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!draftContent || draftContent.trim().length < 50) {
|
|
||||||
alert(
|
|
||||||
"Der Entwurf ist zu kurz. Bitte tippe zuerst ein paar Stichpunkte oder einen Rohling ein.",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsOptimizing(true);
|
|
||||||
try {
|
|
||||||
// 2. We inject the title so the AI knows what it's writing about
|
|
||||||
const payloadText = `---\ntitle: "${title}"\n---\n\n${draftContent}`;
|
|
||||||
|
|
||||||
const response = await optimizePostText(payloadText, instructions);
|
|
||||||
|
|
||||||
if (response.success && response.lexicalAST) {
|
|
||||||
// 3. Inject the new Lexical AST directly into the field form state
|
|
||||||
// We use Payload's useForm hook replacing the value of the 'content' field.
|
|
||||||
|
|
||||||
replaceState({
|
|
||||||
...fields,
|
|
||||||
content: {
|
|
||||||
...fields.content,
|
|
||||||
value: response.lexicalAST,
|
|
||||||
initialValue: response.lexicalAST,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
setModified(true);
|
|
||||||
alert(
|
|
||||||
"🎉 Artikel wurde erfolgreich von der AI optimiert und mit Lexical Components angereichert.",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
alert("❌ Fehler: " + response.error);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Optimization failed:", error);
|
|
||||||
alert("Ein unerwarteter Fehler ist aufgetreten.");
|
|
||||||
} finally {
|
|
||||||
setIsOptimizing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mb-8 p-4 bg-slate-50 border border-slate-200 rounded-md">
|
|
||||||
<h3 className="text-sm font-semibold mb-2">AI Post Optimizer</h3>
|
|
||||||
<p className="text-xs text-slate-500 mb-4">
|
|
||||||
Lass Mintel AI deinen Text-Rohentwurf analysieren und automatisch in
|
|
||||||
einen voll formatierten Lexical Artikel mit passenden B2B Komponenten
|
|
||||||
(MemeCards, Mermaids) umwandeln.
|
|
||||||
</p>
|
|
||||||
<textarea
|
|
||||||
value={instructions}
|
|
||||||
onChange={(e) => setInstructions(e.target.value)}
|
|
||||||
placeholder="Optionale Anweisungen an die AI (z.B. 'schreibe etwas lockerer' oder 'fokussiere dich auf SEO')..."
|
|
||||||
disabled={isOptimizing}
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
minHeight: "60px",
|
|
||||||
padding: "8px 12px",
|
|
||||||
fontSize: "14px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
background: "var(--theme-elevation-50)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
marginBottom: "16px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={handleOptimize}
|
|
||||||
disabled={isOptimizing}
|
|
||||||
className="btn btn--icon-style-none btn--size-medium mt-4"
|
|
||||||
style={{
|
|
||||||
background: "var(--theme-elevation-150)",
|
|
||||||
border: "1px solid var(--theme-elevation-200)",
|
|
||||||
color: "var(--theme-text)",
|
|
||||||
boxShadow: "0 2px 4px rgba(0,0,0,0.05)",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
opacity: isOptimizing ? 0.7 : 1,
|
|
||||||
cursor: isOptimizing ? "not-allowed" : "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="btn__content" style={{ fontWeight: 600 }}>
|
|
||||||
{isOptimizing ? "✨ AI arbeitet (ca 30s)..." : "✨ Jetzt optimieren"}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import type { GlobalConfig } from "payload";
|
|
||||||
export const AiSettings: GlobalConfig = {
|
|
||||||
slug: "ai-settings",
|
|
||||||
label: "AI Settings",
|
|
||||||
access: {
|
|
||||||
read: () => true, // Needed if the Next.js frontend or server actions need to fetch it
|
|
||||||
},
|
|
||||||
admin: {
|
|
||||||
group: "Configuration",
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "customSources",
|
|
||||||
type: "array",
|
|
||||||
label: "Custom Trusted Sources",
|
|
||||||
admin: {
|
|
||||||
description:
|
|
||||||
"List of trusted B2B/Tech sources (e.g. 'Vercel Blog', 'Fireship', 'Theo - t3.gg') the AI should prioritize when researching facts or videos. This overrides the hardcoded defaults.",
|
|
||||||
},
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "sourceName",
|
|
||||||
type: "text",
|
|
||||||
required: true,
|
|
||||||
label: "Channel or Publication Name",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -1,640 +0,0 @@
|
|||||||
/**
|
|
||||||
* Converts a Markdown+JSX string into a Lexical AST node array.
|
|
||||||
* Handles all registered Payload blocks and standard markdown formatting.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function propValue(chunk: string, prop: string): string {
|
|
||||||
// Match prop="value" or prop='value' or prop={value}
|
|
||||||
const match =
|
|
||||||
chunk.match(new RegExp(`${prop}=["']([^"']+)["']`)) ||
|
|
||||||
chunk.match(new RegExp(`${prop}=\\{([^}]+)\\}`));
|
|
||||||
return match ? match[1] : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function innerContent(chunk: string, tag: string): string {
|
|
||||||
const match = chunk.match(
|
|
||||||
new RegExp(`<${tag}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${tag}>`),
|
|
||||||
);
|
|
||||||
return match ? match[1].trim() : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function blockNode(blockType: string, fields: Record<string, any>) {
|
|
||||||
return {
|
|
||||||
type: "block",
|
|
||||||
format: "",
|
|
||||||
version: 2,
|
|
||||||
fields: { blockType, ...fields },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseMarkdownToLexical(markdown: string): any[] {
|
|
||||||
const textNode = (text: string) => ({
|
|
||||||
type: "paragraph",
|
|
||||||
format: "",
|
|
||||||
indent: 0,
|
|
||||||
version: 1,
|
|
||||||
children: [{ mode: "normal", type: "text", text, version: 1 }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const nodes: any[] = [];
|
|
||||||
|
|
||||||
// Strip frontmatter
|
|
||||||
let content = markdown;
|
|
||||||
const fm = content.match(/^---\s*\n[\s\S]*?\n---/);
|
|
||||||
if (fm) content = content.replace(fm[0], "").trim();
|
|
||||||
|
|
||||||
// Pre-process: reassemble multi-line JSX tags that got split by double-newline chunking.
|
|
||||||
// This handles tags like <IconList>\n\n<IconListItem ... />\n\n</IconList>
|
|
||||||
content = reassembleMultiLineJSX(content);
|
|
||||||
|
|
||||||
const rawChunks = content.split(/\n\s*\n/);
|
|
||||||
|
|
||||||
for (let chunk of rawChunks) {
|
|
||||||
chunk = chunk.trim();
|
|
||||||
if (!chunk) continue;
|
|
||||||
|
|
||||||
// === Self-closing tags (no children) ===
|
|
||||||
|
|
||||||
// ArticleMeme / MemeCard
|
|
||||||
if (chunk.includes("<ArticleMeme") || chunk.includes("<MemeCard")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("memeCard", {
|
|
||||||
template: propValue(chunk, "template"),
|
|
||||||
captions: propValue(chunk, "captions"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoldNumber
|
|
||||||
if (chunk.includes("<BoldNumber")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("boldNumber", {
|
|
||||||
value: propValue(chunk, "value"),
|
|
||||||
label: propValue(chunk, "label"),
|
|
||||||
source: propValue(chunk, "source"),
|
|
||||||
sourceUrl: propValue(chunk, "sourceUrl"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebVitalsScore
|
|
||||||
if (chunk.includes("<WebVitalsScore")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("webVitalsScore", {
|
|
||||||
lcp: parseFloat(propValue(chunk, "lcp")) || 0,
|
|
||||||
inp: parseFloat(propValue(chunk, "inp")) || 0,
|
|
||||||
cls: parseFloat(propValue(chunk, "cls")) || 0,
|
|
||||||
description: propValue(chunk, "description"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeadMagnet
|
|
||||||
if (chunk.includes("<LeadMagnet")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("leadMagnet", {
|
|
||||||
title: propValue(chunk, "title"),
|
|
||||||
description: propValue(chunk, "description"),
|
|
||||||
buttonText: propValue(chunk, "buttonText") || "Jetzt anfragen",
|
|
||||||
href: propValue(chunk, "href") || "/contact",
|
|
||||||
variant: propValue(chunk, "variant") || "standard",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComparisonRow
|
|
||||||
if (chunk.includes("<ComparisonRow")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("comparisonRow", {
|
|
||||||
description: propValue(chunk, "description"),
|
|
||||||
negativeLabel: propValue(chunk, "negativeLabel"),
|
|
||||||
negativeText: propValue(chunk, "negativeText"),
|
|
||||||
positiveLabel: propValue(chunk, "positiveLabel"),
|
|
||||||
positiveText: propValue(chunk, "positiveText"),
|
|
||||||
reverse: chunk.includes("reverse={true}"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsDisplay
|
|
||||||
if (chunk.includes("<StatsDisplay")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("statsDisplay", {
|
|
||||||
label: propValue(chunk, "label"),
|
|
||||||
value: propValue(chunk, "value"),
|
|
||||||
subtext: propValue(chunk, "subtext"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetricBar
|
|
||||||
if (chunk.includes("<MetricBar")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("metricBar", {
|
|
||||||
label: propValue(chunk, "label"),
|
|
||||||
value: parseFloat(propValue(chunk, "value")) || 0,
|
|
||||||
max: parseFloat(propValue(chunk, "max")) || 100,
|
|
||||||
unit: propValue(chunk, "unit") || "%",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExternalLink
|
|
||||||
if (chunk.includes("<ExternalLink")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("externalLink", {
|
|
||||||
href: propValue(chunk, "href"),
|
|
||||||
label:
|
|
||||||
propValue(chunk, "label") || innerContent(chunk, "ExternalLink"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrackedLink
|
|
||||||
if (chunk.includes("<TrackedLink")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("trackedLink", {
|
|
||||||
href: propValue(chunk, "href"),
|
|
||||||
label:
|
|
||||||
propValue(chunk, "label") || innerContent(chunk, "TrackedLink"),
|
|
||||||
eventName: propValue(chunk, "eventName"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// YouTube
|
|
||||||
if (chunk.includes("<YouTubeEmbed")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("youTubeEmbed", {
|
|
||||||
videoId: propValue(chunk, "videoId"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkedIn
|
|
||||||
if (chunk.includes("<LinkedInEmbed")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("linkedInEmbed", {
|
|
||||||
url: propValue(chunk, "url"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Twitter
|
|
||||||
if (chunk.includes("<TwitterEmbed")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("twitterEmbed", {
|
|
||||||
url: propValue(chunk, "url"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interactive (self-closing, defaults only)
|
|
||||||
if (chunk.includes("<RevenueLossCalculator")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("revenueLossCalculator", {
|
|
||||||
title: propValue(chunk, "title") || "Performance Revenue Simulator",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<PerformanceChart")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("performanceChart", {
|
|
||||||
title: propValue(chunk, "title") || "Website Performance",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<PerformanceROICalculator")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("performanceROICalculator", {
|
|
||||||
baseConversionRate:
|
|
||||||
parseFloat(propValue(chunk, "baseConversionRate")) || 2.5,
|
|
||||||
monthlyVisitors:
|
|
||||||
parseInt(propValue(chunk, "monthlyVisitors")) || 50000,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<LoadTimeSimulator")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("loadTimeSimulator", {
|
|
||||||
initialLoadTime:
|
|
||||||
parseFloat(propValue(chunk, "initialLoadTime")) || 3.5,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<ArchitectureBuilder")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("architectureBuilder", {
|
|
||||||
preset: propValue(chunk, "preset") || "standard",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DigitalAssetVisualizer")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("digitalAssetVisualizer", {
|
|
||||||
assetId: propValue(chunk, "assetId"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Tags with inner content ===
|
|
||||||
|
|
||||||
// TLDR
|
|
||||||
if (chunk.includes("<TLDR>")) {
|
|
||||||
const inner = innerContent(chunk, "TLDR");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(blockNode("mintelTldr", { content: inner }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paragraph (handles <Paragraph>, <Paragraph ...attrs>)
|
|
||||||
if (/<Paragraph[\s>]/.test(chunk)) {
|
|
||||||
const inner = innerContent(chunk, "Paragraph");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(blockNode("mintelP", { text: inner }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// H2 (handles <H2>, <H2 id="...">)
|
|
||||||
if (/<H2[\s>]/.test(chunk)) {
|
|
||||||
const inner = innerContent(chunk, "H2");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("mintelHeading", {
|
|
||||||
text: inner,
|
|
||||||
seoLevel: "h2",
|
|
||||||
displayLevel: "h2",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// H3 (handles <H3>, <H3 id="...">)
|
|
||||||
if (/<H3[\s>]/.test(chunk)) {
|
|
||||||
const inner = innerContent(chunk, "H3");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("mintelHeading", {
|
|
||||||
text: inner,
|
|
||||||
seoLevel: "h3",
|
|
||||||
displayLevel: "h3",
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marker (inline highlight, usually inside Paragraph – store as standalone block)
|
|
||||||
if (chunk.includes("<Marker>") && !chunk.includes("<Paragraph")) {
|
|
||||||
const inner = innerContent(chunk, "Marker");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(blockNode("marker", { text: inner }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LeadParagraph
|
|
||||||
if (chunk.includes("<LeadParagraph>")) {
|
|
||||||
const inner = innerContent(chunk, "LeadParagraph");
|
|
||||||
if (inner) {
|
|
||||||
nodes.push(blockNode("leadParagraph", { text: inner }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArticleBlockquote
|
|
||||||
if (chunk.includes("<ArticleBlockquote")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("articleBlockquote", {
|
|
||||||
quote: innerContent(chunk, "ArticleBlockquote"),
|
|
||||||
author: propValue(chunk, "author"),
|
|
||||||
role: propValue(chunk, "role"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArticleQuote
|
|
||||||
if (chunk.includes("<ArticleQuote")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("articleQuote", {
|
|
||||||
quote:
|
|
||||||
innerContent(chunk, "ArticleQuote") || propValue(chunk, "quote"),
|
|
||||||
author: propValue(chunk, "author"),
|
|
||||||
role: propValue(chunk, "role"),
|
|
||||||
source: propValue(chunk, "source"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mermaid
|
|
||||||
if (chunk.includes("<Mermaid")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("mermaid", {
|
|
||||||
id: propValue(chunk, "id") || `chart-${Date.now()}`,
|
|
||||||
title: propValue(chunk, "title"),
|
|
||||||
showShare: chunk.includes("showShare={true}"),
|
|
||||||
chartDefinition: innerContent(chunk, "Mermaid"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diagram variants (prefer inner definition, fall back to raw chunk text)
|
|
||||||
if (chunk.includes("<DiagramFlow")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramFlow", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramFlow") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DiagramSequence")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramSequence", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramSequence") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DiagramGantt")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramGantt", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramGantt") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DiagramPie")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramPie", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramPie") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DiagramState")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramState", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramState") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (chunk.includes("<DiagramTimeline")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("diagramTimeline", {
|
|
||||||
definition:
|
|
||||||
innerContent(chunk, "DiagramTimeline") ||
|
|
||||||
propValue(chunk, "definition") ||
|
|
||||||
chunk,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Section (wrapping container – unwrap and parse inner content as top-level blocks)
|
|
||||||
if (chunk.includes("<Section")) {
|
|
||||||
const inner = innerContent(chunk, "Section");
|
|
||||||
if (inner) {
|
|
||||||
const innerNodes = parseMarkdownToLexical(inner);
|
|
||||||
nodes.push(...innerNodes);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FAQSection (wrapping container)
|
|
||||||
if (chunk.includes("<FAQSection")) {
|
|
||||||
// FAQSection contains nested H3/Paragraph pairs.
|
|
||||||
// We extract them as individual blocks instead.
|
|
||||||
const faqContent = innerContent(chunk, "FAQSection");
|
|
||||||
if (faqContent) {
|
|
||||||
// Parse nested content recursively
|
|
||||||
const innerNodes = parseMarkdownToLexical(faqContent);
|
|
||||||
nodes.push(...innerNodes);
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// IconList with IconListItems
|
|
||||||
if (chunk.includes("<IconList")) {
|
|
||||||
const items: any[] = [];
|
|
||||||
// Self-closing: <IconListItem icon="Check" title="..." description="..." />
|
|
||||||
const itemMatches = chunk.matchAll(/<IconListItem\s+([^>]*?)\/>/g);
|
|
||||||
for (const m of itemMatches) {
|
|
||||||
const attrs = m[1];
|
|
||||||
const title = (attrs.match(/title=["']([^"']+)["']/) || [])[1] || "";
|
|
||||||
const desc =
|
|
||||||
(attrs.match(/description=["']([^"']+)["']/) || [])[1] || "";
|
|
||||||
items.push({
|
|
||||||
icon: (attrs.match(/icon=["']([^"']+)["']/) || [])[1] || "Check",
|
|
||||||
title: title || "•",
|
|
||||||
description: desc,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Content-wrapped: <IconListItem check>HTML content</IconListItem>
|
|
||||||
const itemMatches2 = chunk.matchAll(
|
|
||||||
/<IconListItem([^>]*)>([\s\S]*?)<\/IconListItem>/g,
|
|
||||||
);
|
|
||||||
for (const m of itemMatches2) {
|
|
||||||
const attrs = m[1] || "";
|
|
||||||
const innerHtml = m[2].trim();
|
|
||||||
// Use title attr if present, otherwise use inner HTML (stripped of tags) as title
|
|
||||||
const titleAttr = (attrs.match(/title=["']([^"']+)["']/) || [])[1];
|
|
||||||
const strippedInner = innerHtml.replace(/<[^>]+>/g, "").trim();
|
|
||||||
items.push({
|
|
||||||
icon: (attrs.match(/icon=["']([^"']+)["']/) || [])[1] || "Check",
|
|
||||||
title: titleAttr || strippedInner || "•",
|
|
||||||
description: "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (items.length > 0) {
|
|
||||||
nodes.push(blockNode("iconList", { items }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsGrid
|
|
||||||
if (chunk.includes("<StatsGrid")) {
|
|
||||||
const stats: any[] = [];
|
|
||||||
const statMatches = chunk.matchAll(/<StatItem\s+([^>]*?)\/>/g);
|
|
||||||
for (const m of statMatches) {
|
|
||||||
const attrs = m[1];
|
|
||||||
stats.push({
|
|
||||||
label: (attrs.match(/label=["']([^"']+)["']/) || [])[1] || "",
|
|
||||||
value: (attrs.match(/value=["']([^"']+)["']/) || [])[1] || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Also try inline props pattern
|
|
||||||
if (stats.length === 0) {
|
|
||||||
const innerStats = innerContent(chunk, "StatsGrid");
|
|
||||||
if (innerStats) {
|
|
||||||
// fallback: store the raw content
|
|
||||||
nodes.push(blockNode("statsGrid", { stats: [] }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nodes.push(blockNode("statsGrid", { stats }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PremiumComparisonChart
|
|
||||||
if (chunk.includes("<PremiumComparisonChart")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("premiumComparisonChart", {
|
|
||||||
title: propValue(chunk, "title"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WaterfallChart
|
|
||||||
if (chunk.includes("<WaterfallChart")) {
|
|
||||||
nodes.push(
|
|
||||||
blockNode("waterfallChart", {
|
|
||||||
title: propValue(chunk, "title"),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reveal (animation wrapper – just pass through)
|
|
||||||
if (chunk.includes("<Reveal")) {
|
|
||||||
const inner = innerContent(chunk, "Reveal");
|
|
||||||
if (inner) {
|
|
||||||
// Parse inner content as regular nodes
|
|
||||||
const innerNodes = parseMarkdownToLexical(inner);
|
|
||||||
nodes.push(...innerNodes);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standalone IconListItem (outside IconList context)
|
|
||||||
if (chunk.includes("<IconListItem")) {
|
|
||||||
// Skip – these should be inside an IconList
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip wrapper divs (like <div className="my-8">)
|
|
||||||
if (/^<div\s/.test(chunk) || chunk === "</div>") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// === Standard Markdown ===
|
|
||||||
// CarouselBlock
|
|
||||||
if (chunk.includes("<Carousel")) {
|
|
||||||
const slides: any[] = [];
|
|
||||||
const slideMatches = chunk.matchAll(/<Slide\s+([^>]*?)\/>/g);
|
|
||||||
for (const m of slideMatches) {
|
|
||||||
const attrs = m[1];
|
|
||||||
slides.push({
|
|
||||||
image: (attrs.match(/image=["']([^"']+)["']/) || [])[1] || "",
|
|
||||||
caption: (attrs.match(/caption=["']([^"']+)["']/) || [])[1] || "",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (slides.length > 0) {
|
|
||||||
nodes.push(blockNode("carousel", { slides }));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Headings
|
|
||||||
const headingMatch = chunk.match(/^(#{1,6})\s+(.*)/);
|
|
||||||
if (headingMatch) {
|
|
||||||
nodes.push({
|
|
||||||
type: "heading",
|
|
||||||
tag: `h${headingMatch[1].length}`,
|
|
||||||
format: "",
|
|
||||||
indent: 0,
|
|
||||||
version: 1,
|
|
||||||
direction: "ltr",
|
|
||||||
children: [
|
|
||||||
{ mode: "normal", type: "text", text: headingMatch[2], version: 1 },
|
|
||||||
],
|
|
||||||
});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default: plain text paragraph
|
|
||||||
nodes.push(textNode(chunk));
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reassembles multi-line JSX tags that span across double-newline boundaries.
|
|
||||||
* E.g. <IconList>\n\n<IconListItem.../>\n\n</IconList> becomes a single chunk.
|
|
||||||
*/
|
|
||||||
function reassembleMultiLineJSX(content: string): string {
|
|
||||||
// Tags that wrap other content across paragraph breaks
|
|
||||||
const wrapperTags = [
|
|
||||||
"IconList",
|
|
||||||
"StatsGrid",
|
|
||||||
"FAQSection",
|
|
||||||
"Section",
|
|
||||||
"Reveal",
|
|
||||||
"Carousel",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const tag of wrapperTags) {
|
|
||||||
const openRegex = new RegExp(`<${tag}[^>]*>`, "g");
|
|
||||||
let match;
|
|
||||||
while ((match = openRegex.exec(content)) !== null) {
|
|
||||||
const openPos = match.index;
|
|
||||||
const closeTag = `</${tag}>`;
|
|
||||||
const closePos = content.indexOf(closeTag, openPos);
|
|
||||||
if (closePos === -1) continue;
|
|
||||||
|
|
||||||
const fullEnd = closePos + closeTag.length;
|
|
||||||
const fullBlock = content.substring(openPos, fullEnd);
|
|
||||||
|
|
||||||
// Replace double newlines inside this block with single newlines
|
|
||||||
// so it stays as one chunk during splitting
|
|
||||||
const collapsed = fullBlock.replace(/\n\s*\n/g, "\n");
|
|
||||||
content =
|
|
||||||
content.substring(0, openPos) + collapsed + content.substring(fullEnd);
|
|
||||||
|
|
||||||
// Adjust regex position
|
|
||||||
openRegex.lastIndex = openPos + collapsed.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@ services:
|
|||||||
POSTGRES_USER: ${postgres_DB_USER:-payload}
|
POSTGRES_USER: ${postgres_DB_USER:-payload}
|
||||||
POSTGRES_PASSWORD: ${postgres_DB_PASSWORD:-payload}
|
POSTGRES_PASSWORD: ${postgres_DB_PASSWORD:-payload}
|
||||||
volumes:
|
volumes:
|
||||||
- payload-db-data:/var/lib/postgresql/data
|
- ./data/postgres:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
- "54321:5432"
|
- "54321:5432"
|
||||||
|
|
||||||
@@ -68,7 +68,6 @@ networks:
|
|||||||
external: true
|
external: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
payload-db-data:
|
|
||||||
node_modules:
|
node_modules:
|
||||||
apps_node_modules:
|
apps_node_modules:
|
||||||
pnpm_store:
|
pnpm_store:
|
||||||
|
|||||||
105
pnpm-lock.yaml
generated
105
pnpm-lock.yaml
generated
@@ -120,6 +120,9 @@ importers:
|
|||||||
"@mintel/meme-generator":
|
"@mintel/meme-generator":
|
||||||
specifier: link:../../../at-mintel/packages/meme-generator
|
specifier: link:../../../at-mintel/packages/meme-generator
|
||||||
version: link:../../../at-mintel/packages/meme-generator
|
version: link:../../../at-mintel/packages/meme-generator
|
||||||
|
"@mintel/payload-ai":
|
||||||
|
specifier: link:../../../at-mintel/packages/payload-ai
|
||||||
|
version: link:../../../at-mintel/packages/payload-ai
|
||||||
"@mintel/pdf":
|
"@mintel/pdf":
|
||||||
specifier: link:../../../at-mintel/packages/pdf-library
|
specifier: link:../../../at-mintel/packages/pdf-library
|
||||||
version: link:../../../at-mintel/packages/pdf-library
|
version: link:../../../at-mintel/packages/pdf-library
|
||||||
@@ -228,6 +231,9 @@ importers:
|
|||||||
html-to-image:
|
html-to-image:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
import-in-the-middle:
|
||||||
|
specifier: ^1.11.0
|
||||||
|
version: 1.15.0
|
||||||
ioredis:
|
ioredis:
|
||||||
specifier: ^5.9.1
|
specifier: ^5.9.1
|
||||||
version: 5.9.2
|
version: 5.9.2
|
||||||
@@ -279,6 +285,12 @@ importers:
|
|||||||
remotion:
|
remotion:
|
||||||
specifier: ^4.0.414
|
specifier: ^4.0.414
|
||||||
version: 4.0.418(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
version: 4.0.418(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||||
|
replicate:
|
||||||
|
specifier: ^1.4.0
|
||||||
|
version: 1.4.0
|
||||||
|
require-in-the-middle:
|
||||||
|
specifier: ^8.0.1
|
||||||
|
version: 8.0.1
|
||||||
sharp:
|
sharp:
|
||||||
specifier: ^0.34.5
|
specifier: ^0.34.5
|
||||||
version: 0.34.5
|
version: 0.34.5
|
||||||
@@ -6254,6 +6266,13 @@ packages:
|
|||||||
integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==,
|
integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abort-controller@3.0.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==,
|
||||||
|
}
|
||||||
|
engines: { node: ">=6.5" }
|
||||||
|
|
||||||
abs-svg-path@0.1.1:
|
abs-svg-path@0.1.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -6882,6 +6901,12 @@ packages:
|
|||||||
integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==,
|
integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer@6.0.3:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==,
|
||||||
|
}
|
||||||
|
|
||||||
busboy@1.6.0:
|
busboy@1.6.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -7137,6 +7162,12 @@ packages:
|
|||||||
}
|
}
|
||||||
engines: { node: ">=8" }
|
engines: { node: ">=8" }
|
||||||
|
|
||||||
|
cjs-module-lexer@1.4.3:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==,
|
||||||
|
}
|
||||||
|
|
||||||
cjs-module-lexer@2.2.0:
|
cjs-module-lexer@2.2.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -9010,6 +9041,13 @@ packages:
|
|||||||
integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==,
|
integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event-target-shim@5.0.1:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==,
|
||||||
|
}
|
||||||
|
engines: { node: ">=6" }
|
||||||
|
|
||||||
eventemitter3@5.0.4:
|
eventemitter3@5.0.4:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -10048,6 +10086,12 @@ packages:
|
|||||||
}
|
}
|
||||||
engines: { node: ">=6" }
|
engines: { node: ">=6" }
|
||||||
|
|
||||||
|
import-in-the-middle@1.15.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==,
|
||||||
|
}
|
||||||
|
|
||||||
import-in-the-middle@2.0.6:
|
import-in-the-middle@2.0.6:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -13015,6 +13059,13 @@ packages:
|
|||||||
integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==,
|
integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process@0.11.10:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==,
|
||||||
|
}
|
||||||
|
engines: { node: ">= 0.6.0" }
|
||||||
|
|
||||||
progress@2.0.3:
|
progress@2.0.3:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -13339,6 +13390,13 @@ packages:
|
|||||||
}
|
}
|
||||||
engines: { node: ">= 6" }
|
engines: { node: ">= 6" }
|
||||||
|
|
||||||
|
readable-stream@4.7.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==,
|
||||||
|
}
|
||||||
|
engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 }
|
||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -13490,6 +13548,14 @@ packages:
|
|||||||
react: ">=16.8.0"
|
react: ">=16.8.0"
|
||||||
react-dom: ">=16.8.0"
|
react-dom: ">=16.8.0"
|
||||||
|
|
||||||
|
replicate@1.4.0:
|
||||||
|
resolution:
|
||||||
|
{
|
||||||
|
integrity: sha512-1ufKejfUVz/azy+5TnzQP7U1+MHVWZ6psnQ06az8byUUnRhT+DZ/MvewzB1NQYBVMgNKR7xPDtTwlcP5nv/5+w==,
|
||||||
|
}
|
||||||
|
engines:
|
||||||
|
{ git: ">=2.11.0", node: ">=18.0.0", npm: ">=7.19.0", yarn: ">=1.7.0" }
|
||||||
|
|
||||||
require-directory@2.1.1:
|
require-directory@2.1.1:
|
||||||
resolution:
|
resolution:
|
||||||
{
|
{
|
||||||
@@ -20389,6 +20455,11 @@ snapshots:
|
|||||||
|
|
||||||
"@xtuc/long@4.2.2": {}
|
"@xtuc/long@4.2.2": {}
|
||||||
|
|
||||||
|
abort-controller@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
event-target-shim: 5.0.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
abs-svg-path@0.1.1: {}
|
abs-svg-path@0.1.1: {}
|
||||||
|
|
||||||
accepts@1.3.8:
|
accepts@1.3.8:
|
||||||
@@ -20768,6 +20839,12 @@ snapshots:
|
|||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
|
|
||||||
|
buffer@6.0.3:
|
||||||
|
dependencies:
|
||||||
|
base64-js: 1.5.1
|
||||||
|
ieee754: 1.2.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
busboy@1.6.0:
|
busboy@1.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
streamsearch: 1.1.0
|
streamsearch: 1.1.0
|
||||||
@@ -20954,6 +21031,8 @@ snapshots:
|
|||||||
|
|
||||||
ci-info@4.4.0: {}
|
ci-info@4.4.0: {}
|
||||||
|
|
||||||
|
cjs-module-lexer@1.4.3: {}
|
||||||
|
|
||||||
cjs-module-lexer@2.2.0: {}
|
cjs-module-lexer@2.2.0: {}
|
||||||
|
|
||||||
classnames@2.5.1: {}
|
classnames@2.5.1: {}
|
||||||
@@ -22269,6 +22348,9 @@ snapshots:
|
|||||||
stream-combiner: 0.0.4
|
stream-combiner: 0.0.4
|
||||||
through: 2.3.8
|
through: 2.3.8
|
||||||
|
|
||||||
|
event-target-shim@5.0.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
eventemitter3@5.0.4: {}
|
eventemitter3@5.0.4: {}
|
||||||
|
|
||||||
events-universal@1.0.1:
|
events-universal@1.0.1:
|
||||||
@@ -22978,6 +23060,13 @@ snapshots:
|
|||||||
parent-module: 1.0.1
|
parent-module: 1.0.1
|
||||||
resolve-from: 4.0.0
|
resolve-from: 4.0.0
|
||||||
|
|
||||||
|
import-in-the-middle@1.15.0:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.15.0
|
||||||
|
acorn-import-attributes: 1.9.5(acorn@8.15.0)
|
||||||
|
cjs-module-lexer: 1.4.3
|
||||||
|
module-details-from-path: 1.0.4
|
||||||
|
|
||||||
import-in-the-middle@2.0.6:
|
import-in-the-middle@2.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.15.0
|
acorn: 8.15.0
|
||||||
@@ -25029,6 +25118,9 @@ snapshots:
|
|||||||
|
|
||||||
process-warning@5.0.0: {}
|
process-warning@5.0.0: {}
|
||||||
|
|
||||||
|
process@0.11.10:
|
||||||
|
optional: true
|
||||||
|
|
||||||
progress@2.0.3: {}
|
progress@2.0.3: {}
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
@@ -25280,6 +25372,15 @@ snapshots:
|
|||||||
string_decoder: 1.3.0
|
string_decoder: 1.3.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
readable-stream@4.7.0:
|
||||||
|
dependencies:
|
||||||
|
abort-controller: 3.0.0
|
||||||
|
buffer: 6.0.3
|
||||||
|
events: 3.3.0
|
||||||
|
process: 0.11.10
|
||||||
|
string_decoder: 1.3.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
readdirp@3.6.0:
|
readdirp@3.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
@@ -25423,6 +25524,10 @@ snapshots:
|
|||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
react-dom: 19.2.4(react@19.2.4)
|
react-dom: 19.2.4(react@19.2.4)
|
||||||
|
|
||||||
|
replicate@1.4.0:
|
||||||
|
optionalDependencies:
|
||||||
|
readable-stream: 4.7.0
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user