@mjml-agent-editor/editor 0.1.12 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/dist/canvas.cjs +1103 -119
- package/dist/canvas.cjs.map +1 -1
- package/dist/canvas.d.cts +2 -0
- package/dist/canvas.d.ts +2 -0
- package/dist/canvas.js +936 -83
- package/dist/canvas.js.map +1 -1
- package/dist/{chunk-4N2OJT22.js → chunk-3RCKZYM4.js} +136 -32
- package/dist/chunk-3RCKZYM4.js.map +1 -0
- package/dist/index.cjs +94 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -2
- package/dist/index.d.ts +66 -2
- package/dist/index.js +3 -1
- package/package.json +8 -5
- package/src/components/editor/editor-theme.css +29 -0
- package/dist/chunk-4N2OJT22.js.map +0 -1
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from "./chunk-V6RR4VRD.js";
|
|
19
19
|
|
|
20
20
|
// src/stores.tsx
|
|
21
|
-
import { createContext, useContext, useMemo } from "react";
|
|
21
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
22
22
|
|
|
23
23
|
// src/labels.ts
|
|
24
24
|
var DEFAULT_LABELS = {
|
|
@@ -49,6 +49,29 @@ var DEFAULT_LABELS = {
|
|
|
49
49
|
rteBackgroundColor: "Background color",
|
|
50
50
|
documentLoadFailed: "Failed to load the document.",
|
|
51
51
|
documentSaveFailed: "Failed to save changes.",
|
|
52
|
+
retry: "Retry",
|
|
53
|
+
chooseImage: "Choose image",
|
|
54
|
+
imagePickerTitle: "Choose an image",
|
|
55
|
+
imagePickerDrop: "Drag images here, or",
|
|
56
|
+
imagePickerBrowse: "Browse files",
|
|
57
|
+
imagePickerUrlPlaceholder: "\u2026or paste an image URL",
|
|
58
|
+
imagePickerUseUrl: "Use URL",
|
|
59
|
+
imagePickerGallery: "Your images",
|
|
60
|
+
imagePickerEmpty: "No images yet \u2014 the ones you upload will be kept here.",
|
|
61
|
+
imagePickerQuota: (used, limit) => `${used} of ${limit} uploads used`,
|
|
62
|
+
imagePickerQuotaFull: "Upload limit reached \u2014 pick an image below or paste a URL.",
|
|
63
|
+
imagePickerDelete: "Delete image",
|
|
64
|
+
imagePickerConfirmDelete: "Click again to delete",
|
|
65
|
+
imagePickerLoadFailed: "Failed to load your images.",
|
|
66
|
+
imageUploadFailed: "Failed to upload the image.",
|
|
67
|
+
imageDeleteFailed: "Failed to delete the image.",
|
|
68
|
+
imagePickerUnsupported: (fileName) => `${fileName} is not a supported image type.`,
|
|
69
|
+
imageFieldRemove: "Remove image",
|
|
70
|
+
colorPickerOpen: "Pick a color",
|
|
71
|
+
colorPickerEyedropper: "Pick from the screen",
|
|
72
|
+
colorPickerDocument: "In this email",
|
|
73
|
+
colorPickerPresets: "Presets",
|
|
74
|
+
colorPickerTransparent: "Transparent",
|
|
52
75
|
commentOnSection: "Comment on the whole section",
|
|
53
76
|
closeComments: "Close",
|
|
54
77
|
noCommentsYet: "No comments yet \u2014 add the first one.",
|
|
@@ -60,6 +83,7 @@ var DEFAULT_LABELS = {
|
|
|
60
83
|
agentWorking: "Agent is working\u2026",
|
|
61
84
|
reasoning: "Reasoning",
|
|
62
85
|
completedSteps: (count, failed) => failed > 0 ? `Completed steps (${count}, ${failed} failed)` : `Completed steps (${count})`,
|
|
86
|
+
stepLimitReached: "The agent ran out of steps for this turn. Ask it to continue.",
|
|
63
87
|
applyComments: "Apply changes from comments",
|
|
64
88
|
chatPlaceholder: "Describe what you want to change\u2026 (Enter = send, Shift+Enter = new line)",
|
|
65
89
|
stop: "Stop",
|
|
@@ -90,13 +114,27 @@ function mergeLabels(overrides) {
|
|
|
90
114
|
import { jsx } from "react/jsx-runtime";
|
|
91
115
|
var StoreContext = createContext(null);
|
|
92
116
|
var LabelContext = createContext(null);
|
|
117
|
+
var TurnContext = createContext({
|
|
118
|
+
running: false,
|
|
119
|
+
setRunning: () => {
|
|
120
|
+
},
|
|
121
|
+
locked: false,
|
|
122
|
+
setLocked: () => {
|
|
123
|
+
}
|
|
124
|
+
});
|
|
93
125
|
function EditorStoreProvider({
|
|
94
126
|
stores,
|
|
95
127
|
labels,
|
|
96
128
|
children
|
|
97
129
|
}) {
|
|
98
130
|
const merged = useMemo(() => mergeLabels(labels), [labels]);
|
|
99
|
-
|
|
131
|
+
const [running, setRunning] = useState(false);
|
|
132
|
+
const [locked, setLocked] = useState(false);
|
|
133
|
+
const turn = useMemo(() => ({ running, setRunning, locked, setLocked }), [running, locked]);
|
|
134
|
+
return /* @__PURE__ */ jsx(StoreContext.Provider, { value: stores, children: /* @__PURE__ */ jsx(LabelContext.Provider, { value: merged, children: /* @__PURE__ */ jsx(TurnContext.Provider, { value: turn, children }) }) });
|
|
135
|
+
}
|
|
136
|
+
function useAgentTurn() {
|
|
137
|
+
return useContext(TurnContext);
|
|
100
138
|
}
|
|
101
139
|
function useLabels() {
|
|
102
140
|
return useContext(LabelContext) ?? mergeLabels();
|
|
@@ -116,12 +154,15 @@ function useDocumentStore() {
|
|
|
116
154
|
function useCommentStore() {
|
|
117
155
|
return useStores().comments;
|
|
118
156
|
}
|
|
157
|
+
function useImageLibrary() {
|
|
158
|
+
return useStores().images;
|
|
159
|
+
}
|
|
119
160
|
|
|
120
161
|
// src/index.ts
|
|
121
162
|
import { STARTER_MJML } from "@mjml-agent-editor/core";
|
|
122
163
|
|
|
123
164
|
// src/components/editor/EditorHeader.tsx
|
|
124
|
-
import { useEffect, useState } from "react";
|
|
165
|
+
import { useEffect, useState as useState2 } from "react";
|
|
125
166
|
import { toast } from "sonner";
|
|
126
167
|
import {
|
|
127
168
|
Undo2,
|
|
@@ -420,6 +461,22 @@ function DialogTitle({ className, ...props }) {
|
|
|
420
461
|
}
|
|
421
462
|
);
|
|
422
463
|
}
|
|
464
|
+
function DialogDescription({
|
|
465
|
+
className,
|
|
466
|
+
...props
|
|
467
|
+
}) {
|
|
468
|
+
return /* @__PURE__ */ jsx6(
|
|
469
|
+
DialogPrimitive.Description,
|
|
470
|
+
{
|
|
471
|
+
"data-slot": "dialog-description",
|
|
472
|
+
className: cn(
|
|
473
|
+
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
474
|
+
className
|
|
475
|
+
),
|
|
476
|
+
...props
|
|
477
|
+
}
|
|
478
|
+
);
|
|
479
|
+
}
|
|
423
480
|
|
|
424
481
|
// src/components/editor/EditorHeader.tsx
|
|
425
482
|
import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -465,17 +522,18 @@ function EditorHeader({
|
|
|
465
522
|
}) {
|
|
466
523
|
const documents = useDocumentStore();
|
|
467
524
|
const labels = useLabels();
|
|
468
|
-
const [name, setName] =
|
|
469
|
-
const [state, setState] =
|
|
525
|
+
const [name, setName] = useState2("");
|
|
526
|
+
const [state, setState] = useState2({
|
|
470
527
|
device: "Desktop",
|
|
471
528
|
canUndo: false,
|
|
472
529
|
canRedo: false,
|
|
473
530
|
saveStatus: "idle",
|
|
474
531
|
zoom: 100,
|
|
475
|
-
contentWidth: "600px"
|
|
532
|
+
contentWidth: "600px",
|
|
533
|
+
readOnly: false
|
|
476
534
|
});
|
|
477
|
-
const [code, setCode] =
|
|
478
|
-
const [preview, setPreview] =
|
|
535
|
+
const [code, setCode] = useState2({ mjml: "", html: "" });
|
|
536
|
+
const [preview, setPreview] = useState2("");
|
|
479
537
|
useEffect(() => {
|
|
480
538
|
documents.get(docId).then((d) => setName(d.name)).catch(() => {
|
|
481
539
|
});
|
|
@@ -569,6 +627,7 @@ function EditorHeader({
|
|
|
569
627
|
{
|
|
570
628
|
variant: "ghost",
|
|
571
629
|
size: "sm",
|
|
630
|
+
disabled: state.readOnly,
|
|
572
631
|
className: cn(darkGhost, "gap-1.5 rounded-md border border-panel-border max-xl:hidden"),
|
|
573
632
|
children: [
|
|
574
633
|
/* @__PURE__ */ jsx7("span", { className: "text-panel-muted-fg", children: labels.contentWidth }),
|
|
@@ -801,7 +860,7 @@ function EditorHeader({
|
|
|
801
860
|
// src/components/chat/ChatPanel.tsx
|
|
802
861
|
import { useChat } from "@ai-sdk/react";
|
|
803
862
|
import { DefaultChatTransport } from "ai";
|
|
804
|
-
import { useEffect as useEffect2, useRef, useState as
|
|
863
|
+
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
805
864
|
import { toast as toast2 } from "sonner";
|
|
806
865
|
import {
|
|
807
866
|
Brain,
|
|
@@ -815,6 +874,27 @@ import {
|
|
|
815
874
|
ChevronRight
|
|
816
875
|
} from "lucide-react";
|
|
817
876
|
|
|
877
|
+
// src/lib/refusal.ts
|
|
878
|
+
function refusal(error) {
|
|
879
|
+
if (!(error instanceof Error)) return null;
|
|
880
|
+
const status = error.status;
|
|
881
|
+
const deliberate = typeof status === "number" ? status >= 400 && status < 500 : null;
|
|
882
|
+
if (deliberate === false) return null;
|
|
883
|
+
if (error.message.startsWith("{")) {
|
|
884
|
+
try {
|
|
885
|
+
const parsed = JSON.parse(error.message);
|
|
886
|
+
return typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : null;
|
|
887
|
+
} catch {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
const message = error.message.trim();
|
|
892
|
+
return deliberate && message !== "" && !message.startsWith("<") ? error.message : null;
|
|
893
|
+
}
|
|
894
|
+
function saveToastId(docId) {
|
|
895
|
+
return `document-save-${docId}`;
|
|
896
|
+
}
|
|
897
|
+
|
|
818
898
|
// src/components/ui/message.tsx
|
|
819
899
|
import "react";
|
|
820
900
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
@@ -1199,16 +1279,6 @@ function toBlocks(parts) {
|
|
|
1199
1279
|
}
|
|
1200
1280
|
return blocks;
|
|
1201
1281
|
}
|
|
1202
|
-
function refusal(error) {
|
|
1203
|
-
const body = error instanceof Error ? error.message : "";
|
|
1204
|
-
if (!body.startsWith("{")) return null;
|
|
1205
|
-
try {
|
|
1206
|
-
const parsed = JSON.parse(body);
|
|
1207
|
-
return typeof parsed.error === "string" && parsed.error.trim() !== "" ? parsed.error : null;
|
|
1208
|
-
} catch {
|
|
1209
|
-
return null;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
1282
|
function ToolMarker({ tool }) {
|
|
1213
1283
|
const labels = useLabels();
|
|
1214
1284
|
const name = tool.type.slice(5);
|
|
@@ -1270,8 +1340,10 @@ function ChatPanel({
|
|
|
1270
1340
|
onSectionEditEnd
|
|
1271
1341
|
}) {
|
|
1272
1342
|
const labels = useLabels();
|
|
1273
|
-
const [input, setInput] =
|
|
1343
|
+
const [input, setInput] = useState3("");
|
|
1274
1344
|
const taRef = useRef(null);
|
|
1345
|
+
const sending = useRef(false);
|
|
1346
|
+
const [flushing, setFlushing] = useState3(false);
|
|
1275
1347
|
const times = useRef(/* @__PURE__ */ new Map());
|
|
1276
1348
|
const timeFor = (id) => {
|
|
1277
1349
|
if (!times.current.has(id)) {
|
|
@@ -1335,6 +1407,9 @@ function ChatPanel({
|
|
|
1335
1407
|
}
|
|
1336
1408
|
}, [messages, onLiveUpdate, onSectionEditStart, onSectionEditEnd]);
|
|
1337
1409
|
const busy = status === "submitted" || status === "streaming";
|
|
1410
|
+
const { setRunning, locked } = useAgentTurn();
|
|
1411
|
+
useEffect2(() => setRunning(busy || flushing), [busy, flushing, setRunning]);
|
|
1412
|
+
useEffect2(() => () => setRunning(false), [setRunning]);
|
|
1338
1413
|
const lastMsg = messages[messages.length - 1];
|
|
1339
1414
|
const toolRunning = lastMsg?.role === "assistant" && lastMsg.parts.some((p) => {
|
|
1340
1415
|
if (!p.type.startsWith("tool-")) return false;
|
|
@@ -1349,8 +1424,19 @@ function ChatPanel({
|
|
|
1349
1424
|
};
|
|
1350
1425
|
const send = async (text) => {
|
|
1351
1426
|
const trimmed = text.trim();
|
|
1352
|
-
if (!trimmed || busy) return;
|
|
1353
|
-
|
|
1427
|
+
if (!trimmed || busy || locked || sending.current) return;
|
|
1428
|
+
sending.current = true;
|
|
1429
|
+
setFlushing(true);
|
|
1430
|
+
try {
|
|
1431
|
+
await onBeforeSend();
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
console.error(error);
|
|
1434
|
+
toast2.error(refusal(error) ?? labels.documentSaveFailed, { id: saveToastId(docId) });
|
|
1435
|
+
return;
|
|
1436
|
+
} finally {
|
|
1437
|
+
sending.current = false;
|
|
1438
|
+
setFlushing(false);
|
|
1439
|
+
}
|
|
1354
1440
|
void sendMessage({ text: trimmed });
|
|
1355
1441
|
setInput("");
|
|
1356
1442
|
if (taRef.current) taRef.current.style.height = "auto";
|
|
@@ -1367,6 +1453,7 @@ function ChatPanel({
|
|
|
1367
1453
|
const isLast = mi === messages.length - 1;
|
|
1368
1454
|
const blocks = toBlocks(message.parts);
|
|
1369
1455
|
const hasText = blocks.some((block) => block.kind === "text");
|
|
1456
|
+
const metadata = message.metadata;
|
|
1370
1457
|
return /* @__PURE__ */ jsx14(MessageScrollerItem, { messageId: message.id, children: /* @__PURE__ */ jsxs4("div", { className: cn("flex w-full gap-2", isUser && "flex-row-reverse"), children: [
|
|
1371
1458
|
!isUser && /* @__PURE__ */ jsx14("span", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-brand/15 text-brand", children: /* @__PURE__ */ jsx14(Sparkles, { className: "size-3.5" }) }),
|
|
1372
1459
|
/* @__PURE__ */ jsxs4(
|
|
@@ -1415,7 +1502,11 @@ function ChatPanel({
|
|
|
1415
1502
|
i
|
|
1416
1503
|
);
|
|
1417
1504
|
}),
|
|
1418
|
-
isLast && !isUser && busy && !toolRunning && !hasText && /* @__PURE__ */ jsx14(BusyMarker, {})
|
|
1505
|
+
isLast && !isUser && busy && !toolRunning && !hasText && /* @__PURE__ */ jsx14(BusyMarker, {}),
|
|
1506
|
+
metadata?.stepLimitReached && /* @__PURE__ */ jsxs4(Marker, { className: "text-panel-muted-fg", children: [
|
|
1507
|
+
/* @__PURE__ */ jsx14(MarkerIcon, { children: /* @__PURE__ */ jsx14(TriangleAlert, {}) }),
|
|
1508
|
+
/* @__PURE__ */ jsx14(MarkerContent, { children: labels.stepLimitReached })
|
|
1509
|
+
] })
|
|
1419
1510
|
] }) }),
|
|
1420
1511
|
blocks.length > 0 && /* @__PURE__ */ jsx14("span", { className: "px-1 text-[10px] text-panel-muted-fg", children: timeFor(message.id) })
|
|
1421
1512
|
]
|
|
@@ -1432,7 +1523,7 @@ function ChatPanel({
|
|
|
1432
1523
|
Button,
|
|
1433
1524
|
{
|
|
1434
1525
|
className: "mb-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
|
|
1435
|
-
disabled: busy,
|
|
1526
|
+
disabled: busy || flushing || locked,
|
|
1436
1527
|
onClick: () => void send(APPLY_COMMENTS_PROMPT),
|
|
1437
1528
|
children: [
|
|
1438
1529
|
/* @__PURE__ */ jsx14(Sparkles, {}),
|
|
@@ -1491,7 +1582,7 @@ function ChatPanel({
|
|
|
1491
1582
|
{
|
|
1492
1583
|
type: "submit",
|
|
1493
1584
|
size: "sm",
|
|
1494
|
-
disabled: busy || !input.trim(),
|
|
1585
|
+
disabled: busy || flushing || locked || !input.trim(),
|
|
1495
1586
|
className: "bg-brand text-brand-fg hover:bg-brand/90",
|
|
1496
1587
|
children: [
|
|
1497
1588
|
/* @__PURE__ */ jsx14(Send, {}),
|
|
@@ -1510,7 +1601,7 @@ function ChatPanel({
|
|
|
1510
1601
|
|
|
1511
1602
|
// src/components/comments/CanvasComments.tsx
|
|
1512
1603
|
import { useEditorMaybe } from "@grapesjs/react";
|
|
1513
|
-
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as
|
|
1604
|
+
import { useCallback, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState4 } from "react";
|
|
1514
1605
|
import { Check as Check2, MessageSquarePlus, X as X2 } from "lucide-react";
|
|
1515
1606
|
import { Fragment as Fragment2, jsx as jsx15, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1516
1607
|
var keyOf = (c) => c.objectId ? `obj-${c.objectId}` : `sec-${c.sectionId}`;
|
|
@@ -1526,11 +1617,11 @@ function CanvasComments({
|
|
|
1526
1617
|
const labels = useLabels();
|
|
1527
1618
|
const editor = useEditorMaybe();
|
|
1528
1619
|
const containerRef = useRef2(null);
|
|
1529
|
-
const [comments, setComments] =
|
|
1530
|
-
const [positions, setPositions] =
|
|
1531
|
-
const [activeKey, setActiveKey] =
|
|
1532
|
-
const [composeInfo, setComposeInfo] =
|
|
1533
|
-
const [body, setBody] =
|
|
1620
|
+
const [comments, setComments] = useState4([]);
|
|
1621
|
+
const [positions, setPositions] = useState4({});
|
|
1622
|
+
const [activeKey, setActiveKey] = useState4(null);
|
|
1623
|
+
const [composeInfo, setComposeInfo] = useState4(null);
|
|
1624
|
+
const [body, setBody] = useState4("");
|
|
1534
1625
|
const reload = useCallback(() => {
|
|
1535
1626
|
commentStore.list(docId).then(setComments).catch(console.error);
|
|
1536
1627
|
}, [docId]);
|
|
@@ -1779,13 +1870,26 @@ export {
|
|
|
1779
1870
|
DEFAULT_LABELS,
|
|
1780
1871
|
mergeLabels,
|
|
1781
1872
|
EditorStoreProvider,
|
|
1873
|
+
useAgentTurn,
|
|
1782
1874
|
useLabels,
|
|
1783
1875
|
useDocumentStore,
|
|
1784
1876
|
useCommentStore,
|
|
1877
|
+
useImageLibrary,
|
|
1878
|
+
Popover,
|
|
1879
|
+
PopoverTrigger,
|
|
1880
|
+
PopoverAnchor,
|
|
1881
|
+
PopoverContent,
|
|
1882
|
+
Dialog,
|
|
1883
|
+
DialogContent,
|
|
1884
|
+
DialogHeader,
|
|
1885
|
+
DialogTitle,
|
|
1886
|
+
DialogDescription,
|
|
1785
1887
|
EditorHeader,
|
|
1888
|
+
refusal,
|
|
1889
|
+
saveToastId,
|
|
1786
1890
|
Markdown,
|
|
1787
1891
|
ChatPanel,
|
|
1788
1892
|
CanvasComments,
|
|
1789
1893
|
STARTER_MJML
|
|
1790
1894
|
};
|
|
1791
|
-
//# sourceMappingURL=chunk-
|
|
1895
|
+
//# sourceMappingURL=chunk-3RCKZYM4.js.map
|