feat: extract reusable @mintel/payload-ai package
This commit is contained in:
136
packages/payload-ai/src/components/OptimizeButton.tsx
Normal file
136
packages/payload-ai/src/components/OptimizeButton.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useForm, useDocumentInfo } from "@payloadcms/ui";
|
||||
import { optimizePostText } from "../actions/optimizePost.js";
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user