smartblock/web/dist/assets/__federation_expose_Editor-5Sa5G2Fp.js
Alex Dunmow 38d5fdf4b1 Extract smartblock into a standalone wasm plugin repo
Convert the bundled backend/internal/plugins/smartblock package into a
standalone reactor-mode wasm plugin:

- package smartblock -> package main; add wasip1 main.go with
  wasmguest.Serve(Registration).
- Add go.mod (module git.dev.alexdunmow.com/block/smartblock) pinning
  block/core v0.18.2; no replace directives.
- Flesh out plugin.mod (scope=ninja, kind=plugin, categories, tags,
  display_name, description).
- Point web/eslint.config.js at ../../../cms/web/eslint.config.js and
  switch @block-ninja/ui from workspace:* to the ^0.1.0 registry version;
  rebuild web/dist.
- Add .gitignore (*.bnp, *.wasm, web/node_modules).

Behavior unchanged: registers the "smart" block; assets served from
web/dist. Builds to smartblock-1.0.0.bnp; check-safety passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 02:48:09 +08:00

444 lines
16 KiB
JavaScript

import { importShared } from "./__federation_fn_import-hlt2XzeI.js";
import { j as jsxRuntimeExports } from "./jsx-runtime-CvJTHeKY.js";
const { useState, useCallback, useMemo } = await importShared("react");
const {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
ScrollArea,
Button,
Input,
Textarea,
Label,
Badge,
Monaco,
Loader2,
Sparkles,
Check,
Code,
History,
Wand2,
Send,
Edit3,
useAIGenerate,
useAIAssist,
} = await importShared("@block-ninja/ui");
function SmartBlockEditor({ content, onChange }) {
const [activeTab, setActiveTab] = useState("generate");
const [prompt, setPrompt] = useState("");
const [assistPrompt, setAssistPrompt] = useState("");
const [lastExplanation, setLastExplanation] = useState("");
const { generate, isLoading: isGenerating } = useAIGenerate();
const { assist, isLoading: isAssisting } = useAIAssist();
const meta = useMemo(() => {
return content._meta ?? { currentGenerationId: "", generations: [] };
}, [content._meta]);
const currentGeneration = useMemo(() => meta.generations?.find((g) => g.id === meta.currentGenerationId), [meta]);
const currentTemplate = currentGeneration?.template || "";
const contentFields = useMemo(() => {
const fields = {};
for (const [key, value] of Object.entries(content)) {
if (!key.startsWith("_")) {
fields[key] = value;
}
}
return fields;
}, [content]);
const handleGenerate = useCallback(async () => {
if (!prompt.trim()) return;
try {
const result = await generate(prompt, currentTemplate);
const newGeneration = {
id: result.generationId,
prompt,
schema: result.schema,
template: result.template,
createdAt: /* @__PURE__ */ new Date().toISOString(),
};
const updatedMeta = {
currentGenerationId: result.generationId,
generations: [...(meta.generations || []), newGeneration],
};
const newContent = { _meta: updatedMeta };
if (result.schema?.properties) {
for (const key of Object.keys(result.schema.properties)) {
newContent[key] = "";
}
}
onChange(newContent);
setLastExplanation(result.explanation);
setPrompt("");
setActiveTab("content");
} catch (error) {
console.error("Generation failed:", error);
}
}, [prompt, currentTemplate, meta, generate, onChange]);
const handleAssist = useCallback(async () => {
if (!assistPrompt.trim() || !currentTemplate) return;
try {
const result = await assist(assistPrompt, currentTemplate, currentGeneration?.schema ? JSON.stringify(currentGeneration.schema) : void 0);
const updatedGenerations = (meta.generations || []).map((g) => (g.id === meta.currentGenerationId ? { ...g, template: result.template } : g));
onChange({
...content,
_meta: { ...meta, generations: updatedGenerations },
});
setLastExplanation(result.explanation);
setAssistPrompt("");
} catch (error) {
console.error("Assist failed:", error);
}
}, [assistPrompt, currentTemplate, currentGeneration, meta, content, assist, onChange]);
const handleTemplateChange = useCallback(
(newTemplate) => {
if (!newTemplate || !currentGeneration) return;
const updatedGenerations = (meta.generations || []).map((g) => (g.id === meta.currentGenerationId ? { ...g, template: newTemplate } : g));
onChange({
...content,
_meta: { ...meta, generations: updatedGenerations },
});
},
[currentGeneration, meta, content, onChange],
);
const handleSwitchGeneration = useCallback(
(generationId) => {
const generation = meta.generations?.find((g) => g.id === generationId);
if (!generation) return;
const newContent = {
_meta: { ...meta, currentGenerationId: generationId },
};
if (generation.schema?.properties) {
for (const key of Object.keys(generation.schema.properties)) {
newContent[key] = content[key] ?? "";
}
}
onChange(newContent);
setActiveTab("content");
},
[meta, content, onChange],
);
const handleFieldChange = useCallback(
(fieldName, value) => {
onChange({ ...content, [fieldName]: value });
},
[content, onChange],
);
const renderContentFields = () => {
const schema = currentGeneration?.schema;
if (!schema?.properties) {
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "text-center py-8 text-muted-foreground",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "Generate content structure first" }),
});
}
const properties = Object.entries(schema.properties);
const requiredFields = new Set(schema.required || []);
return /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, {
className: "pb-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-sm", children: "Content Fields" }),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, {
className: "space-y-4",
children: properties.map(([name, prop]) => {
const propObj = prop;
const isRequired = requiredFields.has(name);
const editor = propObj["x-editor"] || "text";
let inputType = "text";
if (editor === "number") {
inputType = "number";
} else if (editor === "url") {
inputType = "url";
}
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
"div",
{
className: "space-y-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Label, {
htmlFor: name,
children: [
propObj.title || propObj.description || name,
isRequired && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-destructive ml-1", children: "*" }),
],
}),
editor === "textarea" ?
/* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, {
id: name,
value: contentFields[name] || "",
onChange: (e) => handleFieldChange(name, e.target.value),
placeholder: propObj.description,
rows: 4,
})
: /* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
id: name,
type: inputType,
value: contentFields[name] || "",
onChange: (e) => handleFieldChange(name, e.target.value),
placeholder: propObj.description,
}),
],
},
name,
);
}),
}),
],
});
};
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "space-y-4",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Tabs, {
value: activeTab,
onValueChange: setActiveTab,
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsList, {
className: "grid w-full grid-cols-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "generate",
className: "gap-2",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Wand2, { className: "h-4 w-4" }), "Generate"],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "content",
className: "gap-2",
disabled: !currentGeneration,
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Edit3, { className: "h-4 w-4" }), "Content"],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "html",
className: "gap-2",
disabled: !currentGeneration,
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Code, { className: "h-4 w-4" }), "HTML"],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsTrigger, {
value: "history",
className: "gap-2",
disabled: (meta.generations?.length || 0) === 0,
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(History, { className: "h-4 w-4" }), "History"],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, {
value: "generate",
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, {
className: "flex items-center gap-2",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-5 w-5 text-primary" }), "Generate Content Structure"],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, {
children: "Describe the content you want to create. AI will generate both the form fields and HTML template.",
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Textarea, {
placeholder: "E.g., Create a testimonial card with author photo, quote, name, title, and 5-star rating...",
value: prompt,
onChange: (e) => setPrompt(e.target.value),
className: "min-h-[100px]",
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex justify-between items-center",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-sm text-muted-foreground",
children: currentGeneration ? "Generating will create a new version" : "Start by describing your content",
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
action: "create",
entity: "smart-block-generation",
onClick: handleGenerate,
disabled: isGenerating || !prompt.trim(),
children:
isGenerating ?
/* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 mr-2 animate-spin" }), "Generating..."],
})
: /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, {
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Wand2, { className: "h-4 w-4 mr-2" }), "Generate"],
}),
}),
],
}),
],
}),
],
}),
lastExplanation &&
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, {
className: "pb-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-sm", children: "AI Response" }),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, {
children: /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm text-muted-foreground", children: lastExplanation }),
}),
],
}),
!currentGeneration &&
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "text-center py-8 text-muted-foreground",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Wand2, { className: "h-12 w-12 mx-auto mb-4 opacity-50" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { children: "No content structure generated yet" }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm", children: "Enter a prompt above to get started" }),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, { value: "content", children: renderContentFields() }),
/* @__PURE__ */ jsxRuntimeExports.jsxs(TabsContent, {
value: "html",
className: "space-y-4",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, {
className: "pb-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, {
className: "text-sm flex items-center justify-between",
children: [
"HTML Template",
/* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, {
variant: "outline",
children: [Object.keys(currentGeneration?.schema?.properties || {}).length, " fields"],
}),
],
}),
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardContent, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Monaco, { value: currentTemplate, onChange: handleTemplateChange, language: "html", height: "300px", theme: "vs-dark" }),
/* @__PURE__ */ jsxRuntimeExports.jsxs("p", {
className: "text-xs text-muted-foreground mt-2",
children: ["Use ", "{{fieldName}}", " placeholders to insert content values"],
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardHeader, {
className: "pb-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(CardTitle, {
className: "text-sm flex items-center gap-2",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-4 w-4" }), "AI Assistant"],
}),
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, {
className: "space-y-2",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(Input, {
placeholder: "E.g., Make the quote italic and add a shadow...",
value: assistPrompt,
onChange: (e) => setAssistPrompt(e.target.value),
onKeyDown: (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleAssist();
}
},
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(Button, {
action: "edit",
entity: "smart-block-template",
onClick: handleAssist,
disabled: isAssisting || !assistPrompt.trim(),
size: "icon",
children:
isAssisting ?
/* @__PURE__ */ jsxRuntimeExports.jsx(Loader2, { className: "h-4 w-4 animate-spin" })
: /* @__PURE__ */ jsxRuntimeExports.jsx(Send, { className: "h-4 w-4" }),
}),
],
}),
}),
],
}),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(TabsContent, {
value: "history",
children: /* @__PURE__ */ jsxRuntimeExports.jsxs(Card, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs(CardHeader, {
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx(CardTitle, { className: "text-sm", children: "Generation History" }),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardDescription, { children: "Switch between previous generations or regenerate from a prompt" }),
],
}),
/* @__PURE__ */ jsxRuntimeExports.jsx(CardContent, {
children: /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, {
className: "h-[300px]",
children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", {
className: "space-y-2",
children: [...(meta.generations || [])].reverse().map((gen) =>
/* @__PURE__ */ jsxRuntimeExports.jsx(
Button,
{
type: "button",
variant: "ghost",
action: "select",
entity: "smart-block-generation",
className: `w-full h-auto justify-start p-3 rounded-lg border text-left font-normal whitespace-normal transition-colors hover:text-foreground [&_svg]:size-3 ${gen.id === meta.currentGenerationId ? "bg-primary/10 border-primary hover:bg-primary/10" : "hover:bg-muted"}`,
onClick: () => handleSwitchGeneration(gen.id),
children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "w-full flex items-start justify-between gap-2",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", {
className: "flex-1 min-w-0",
children: [
/* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-sm font-medium truncate", children: gen.prompt }),
/* @__PURE__ */ jsxRuntimeExports.jsx("p", {
className: "text-xs text-muted-foreground",
children: new Date(gen.createdAt).toLocaleString(),
}),
],
}),
gen.id === meta.currentGenerationId &&
/* @__PURE__ */ jsxRuntimeExports.jsxs(Badge, {
variant: "default",
className: "shrink-0",
children: [/* @__PURE__ */ jsxRuntimeExports.jsx(Check, { className: "h-3 w-3 mr-1" }), "Active"],
}),
],
}),
},
gen.id,
),
),
}),
}),
}),
],
}),
}),
],
}),
});
}
export { SmartBlockEditor as default };