feat: extract reusable @mintel/payload-ai package
This commit is contained in:
163
packages/payload-ai/src/components/AiMediaButtons.tsx
Normal file
163
packages/payload-ai/src/components/AiMediaButtons.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useDocumentInfo, toast } from "@payloadcms/ui";
|
||||
|
||||
type Action = "upscale" | "recover";
|
||||
|
||||
interface ActionState {
|
||||
loading: boolean;
|
||||
resultId?: string | number;
|
||||
}
|
||||
|
||||
export const AiMediaButtons: React.FC = () => {
|
||||
const { id } = useDocumentInfo();
|
||||
|
||||
const [upscale, setUpscale] = useState<ActionState>({ loading: false });
|
||||
const [recover, setRecover] = useState<ActionState>({ loading: false });
|
||||
|
||||
if (!id) return null; // Only show on existing documents
|
||||
|
||||
const runAction = async (action: Action) => {
|
||||
const setter = action === "upscale" ? setUpscale : setRecover;
|
||||
setter({ loading: true });
|
||||
|
||||
const label = action === "upscale" ? "AI Upscale" : "AI Recover";
|
||||
|
||||
toast.info(
|
||||
`${label} started – this can take 30–90 seconds, please wait…`,
|
||||
);
|
||||
|
||||
try {
|
||||
// The API path is hardcoded here and assuming that's where the host app registers the endpoint.
|
||||
const response = await fetch(`/api/media/${id}/ai-process`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ action }),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || `${label} failed`);
|
||||
}
|
||||
|
||||
setter({ loading: false, resultId: result.mediaId });
|
||||
|
||||
toast.success(
|
||||
`✅ ${label} erfolgreich! Neues Bild (ID: ${result.mediaId}) wurde gespeichert.`,
|
||||
);
|
||||
} catch (err: any) {
|
||||
console.error(`[AiMediaButtons] ${action} error:`, err);
|
||||
toast.error(
|
||||
err instanceof Error ? err.message : `${label} fehlgeschlagen`,
|
||||
);
|
||||
setter({ loading: false });
|
||||
}
|
||||
};
|
||||
|
||||
const buttonStyle: React.CSSProperties = {
|
||||
background: "var(--theme-elevation-150)",
|
||||
border: "1px solid var(--theme-elevation-200)",
|
||||
color: "var(--theme-text)",
|
||||
padding: "8px 14px",
|
||||
borderRadius: "4px",
|
||||
fontSize: "13px",
|
||||
fontWeight: 500,
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
transition: "opacity 0.15s ease",
|
||||
};
|
||||
|
||||
const disabledStyle: React.CSSProperties = {
|
||||
opacity: 0.55,
|
||||
cursor: "not-allowed",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
gap: "10px",
|
||||
marginBottom: "1.5rem",
|
||||
marginTop: "0.5rem",
|
||||
}}
|
||||
>
|
||||
{/* AI Upscale */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={upscale.loading || recover.loading}
|
||||
onClick={() => runAction("upscale")}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
...(upscale.loading || recover.loading ? disabledStyle : { cursor: "pointer" }),
|
||||
}}
|
||||
>
|
||||
{upscale.loading ? "⏳ AI Upscale läuft…" : "✨ AI Upscale"}
|
||||
</button>
|
||||
{upscale.resultId && (
|
||||
<a
|
||||
href={`/admin/collections/media/${upscale.resultId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--theme-elevation-500)",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
>
|
||||
→ Neues Bild öffnen (ID: {upscale.resultId})
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* AI Recover */}
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={upscale.loading || recover.loading}
|
||||
onClick={() => runAction("recover")}
|
||||
style={{
|
||||
...buttonStyle,
|
||||
...(upscale.loading || recover.loading ? disabledStyle : { cursor: "pointer" }),
|
||||
}}
|
||||
>
|
||||
{recover.loading ? "⏳ AI Recover läuft…" : "🔄 AI Recover"}
|
||||
</button>
|
||||
{recover.resultId && (
|
||||
<a
|
||||
href={`/admin/collections/media/${recover.resultId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
color: "var(--theme-elevation-500)",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
>
|
||||
→ Neues Bild öffnen (ID: {recover.resultId})
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
style={{
|
||||
width: "100%",
|
||||
fontSize: "0.8rem",
|
||||
color: "var(--theme-elevation-500)",
|
||||
margin: 0,
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
<strong>AI Upscale</strong> verbessert die Auflösung via{" "}
|
||||
<code>google/upscaler</code>. <strong>AI Recover</strong> restauriert
|
||||
alte/beschädigte Fotos via{" "}
|
||||
<code>microsoft/bringing-old-photos-back-to-life</code>. Das
|
||||
Ergebnis wird als neues Medium gespeichert.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user