@mjml-agent-editor/editor 0.1.14 → 0.1.16

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/dist/index.cjs CHANGED
@@ -112,11 +112,13 @@ var DEFAULT_LABELS = {
112
112
  agentWorking: "Agent is working\u2026",
113
113
  reasoning: "Reasoning",
114
114
  completedSteps: (count, failed) => failed > 0 ? `Completed steps (${count}, ${failed} failed)` : `Completed steps (${count})`,
115
+ stepLimitReached: "The agent ran out of steps for this turn. Ask it to continue.",
115
116
  applyComments: "Apply changes from comments",
116
117
  chatPlaceholder: "Describe what you want to change\u2026 (Enter = send, Shift+Enter = new line)",
117
118
  stop: "Stop",
118
119
  send: "Send",
119
120
  chatError: "Agent chat error.",
121
+ agentUnavailable: "The agent is unavailable right now. You can still edit the email and leave comments.",
120
122
  toolLabels: {
121
123
  get_document: "Reading document",
122
124
  get_section: "Reading section",
@@ -1076,6 +1078,11 @@ function saveToastId(docId) {
1076
1078
  return `document-save-${docId}`;
1077
1079
  }
1078
1080
 
1081
+ // src/lib/turn-gate.ts
1082
+ function canStartTurn(text, gate) {
1083
+ return !gate.unavailable && !gate.busy && !gate.flushing && !gate.locked && text.trim() !== "";
1084
+ }
1085
+
1079
1086
  // src/components/ui/textarea.tsx
1080
1087
  var React11 = require("react");
1081
1088
  var import_jsx_runtime14 = require("react/jsx-runtime");
@@ -1605,6 +1612,7 @@ function ChatPanel({
1605
1612
  initialMessages,
1606
1613
  transport,
1607
1614
  resume,
1615
+ agentUnavailable = false,
1608
1616
  onBeforeSend,
1609
1617
  onAgentFinish,
1610
1618
  onLiveUpdate,
@@ -1612,6 +1620,9 @@ function ChatPanel({
1612
1620
  onSectionEditEnd
1613
1621
  }) {
1614
1622
  const labels = useLabels();
1623
+ const unavailableId = (0, import_react5.useId)();
1624
+ const footerRef = (0, import_react5.useRef)(null);
1625
+ const statusRef = (0, import_react5.useRef)(null);
1615
1626
  const [input, setInput] = (0, import_react5.useState)("");
1616
1627
  const taRef = (0, import_react5.useRef)(null);
1617
1628
  const sending = (0, import_react5.useRef)(false);
@@ -1680,6 +1691,7 @@ function ChatPanel({
1680
1691
  }, [messages, onLiveUpdate, onSectionEditStart, onSectionEditEnd]);
1681
1692
  const busy = status === "submitted" || status === "streaming";
1682
1693
  const { setRunning, locked } = useAgentTurn();
1694
+ const gate = { unavailable: agentUnavailable, busy, flushing, locked };
1683
1695
  (0, import_react5.useEffect)(() => setRunning(busy || flushing), [busy, flushing, setRunning]);
1684
1696
  (0, import_react5.useEffect)(() => () => setRunning(false), [setRunning]);
1685
1697
  const lastMsg = messages[messages.length - 1];
@@ -1694,24 +1706,40 @@ function ChatPanel({
1694
1706
  el.style.height = "auto";
1695
1707
  el.style.height = `${Math.min(el.scrollHeight, 160)}px`;
1696
1708
  };
1709
+ (0, import_react5.useLayoutEffect)(() => {
1710
+ const active = document.activeElement;
1711
+ if (agentUnavailable && active?.matches(":disabled") && footerRef.current?.contains(active)) {
1712
+ statusRef.current?.focus();
1713
+ } else if (!agentUnavailable && active === statusRef.current) {
1714
+ taRef.current?.focus();
1715
+ }
1716
+ }, [agentUnavailable]);
1717
+ const [pending, setPending] = (0, import_react5.useState)(null);
1718
+ (0, import_react5.useEffect)(() => {
1719
+ if (pending === null) return;
1720
+ setPending(null);
1721
+ sending.current = false;
1722
+ setFlushing(false);
1723
+ if (agentUnavailable || pending.docId !== docId) return;
1724
+ void sendMessage({ text: pending.text });
1725
+ setInput("");
1726
+ if (taRef.current) taRef.current.style.height = "auto";
1727
+ }, [pending, agentUnavailable, docId, sendMessage]);
1697
1728
  const send = async (text) => {
1729
+ if (!canStartTurn(text, gate) || sending.current) return;
1698
1730
  const trimmed = text.trim();
1699
- if (!trimmed || busy || locked || sending.current) return;
1700
1731
  sending.current = true;
1701
1732
  setFlushing(true);
1702
1733
  try {
1703
1734
  await onBeforeSend();
1704
1735
  } catch (error) {
1736
+ sending.current = false;
1737
+ setFlushing(false);
1705
1738
  console.error(error);
1706
1739
  import_sonner2.toast.error(refusal(error) ?? labels.documentSaveFailed, { id: saveToastId(docId) });
1707
1740
  return;
1708
- } finally {
1709
- sending.current = false;
1710
- setFlushing(false);
1711
1741
  }
1712
- void sendMessage({ text: trimmed });
1713
- setInput("");
1714
- if (taRef.current) taRef.current.style.height = "auto";
1742
+ setPending({ text: trimmed, docId });
1715
1743
  };
1716
1744
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex h-full flex-col", children: [
1717
1745
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(MessageScroller, { className: "flex-1", children: [
@@ -1725,6 +1753,7 @@ function ChatPanel({
1725
1753
  const isLast = mi === messages.length - 1;
1726
1754
  const blocks = toBlocks(message.parts);
1727
1755
  const hasText = blocks.some((block) => block.kind === "text");
1756
+ const metadata = message.metadata;
1728
1757
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerItem, { messageId: message.id, children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: cn("flex w-full gap-2", isUser && "flex-row-reverse"), children: [
1729
1758
  !isUser && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-brand/15 text-brand", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Sparkles, { className: "size-3.5" }) }),
1730
1759
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
@@ -1773,7 +1802,11 @@ function ChatPanel({
1773
1802
  i
1774
1803
  );
1775
1804
  }),
1776
- isLast && !isUser && busy && !toolRunning && !hasText && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BusyMarker, {})
1805
+ isLast && !isUser && busy && !toolRunning && !hasText && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(BusyMarker, {}),
1806
+ metadata?.stepLimitReached && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(Marker, { className: "text-panel-muted-fg", children: [
1807
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MarkerIcon, { children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.TriangleAlert, {}) }),
1808
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MarkerContent, { children: labels.stepLimitReached })
1809
+ ] })
1777
1810
  ] }) }),
1778
1811
  blocks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "px-1 text-[10px] text-panel-muted-fg", children: timeFor(message.id) })
1779
1812
  ]
@@ -1785,12 +1818,17 @@ function ChatPanel({
1785
1818
  ] }) }),
1786
1819
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(MessageScrollerButton, { direction: "end" })
1787
1820
  ] }) }),
1788
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "border-t border-panel-border p-3", children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { ref: footerRef, className: "border-t border-panel-border p-3", children: [
1822
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { ref: statusRef, id: unavailableId, role: "status", tabIndex: -1, className: "rounded-lg", children: agentUnavailable && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { className: "mb-2 flex items-start gap-2 rounded-lg border border-panel-border bg-panel-elevated/60 px-3 py-2 text-xs text-panel-fg", children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.CirclePause, { className: "mt-px size-3.5 shrink-0" }),
1824
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: labels.agentUnavailable })
1825
+ ] }) }),
1789
1826
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
1790
1827
  Button,
1791
1828
  {
1792
1829
  className: "mb-2 w-full bg-brand text-brand-fg hover:bg-brand/90",
1793
- disabled: busy || flushing || locked,
1830
+ disabled: !canStartTurn(APPLY_COMMENTS_PROMPT, gate),
1831
+ "aria-describedby": agentUnavailable ? unavailableId : void 0,
1794
1832
  onClick: () => void send(APPLY_COMMENTS_PROMPT),
1795
1833
  children: [
1796
1834
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Sparkles, {}),
@@ -1813,8 +1851,10 @@ function ChatPanel({
1813
1851
  {
1814
1852
  ref: taRef,
1815
1853
  rows: 2,
1816
- className: "max-h-40 min-h-14 resize-none border-0 bg-transparent px-3 pt-2.5 text-panel-fg shadow-none placeholder:text-panel-muted-fg focus-visible:ring-0",
1817
- placeholder: labels.chatPlaceholder,
1854
+ className: "max-h-40 min-h-14 resize-none border-0 bg-transparent px-3 pt-2.5 text-panel-fg shadow-none placeholder:text-panel-muted-fg focus-visible:ring-0 disabled:bg-transparent",
1855
+ placeholder: agentUnavailable ? void 0 : labels.chatPlaceholder,
1856
+ disabled: agentUnavailable,
1857
+ "aria-describedby": agentUnavailable ? unavailableId : void 0,
1818
1858
  value: input,
1819
1859
  onChange: (e) => {
1820
1860
  setInput(e.target.value);
@@ -1849,7 +1889,8 @@ function ChatPanel({
1849
1889
  {
1850
1890
  type: "submit",
1851
1891
  size: "sm",
1852
- disabled: busy || flushing || locked || !input.trim(),
1892
+ disabled: !canStartTurn(input, gate),
1893
+ "aria-describedby": agentUnavailable ? unavailableId : void 0,
1853
1894
  className: "bg-brand text-brand-fg hover:bg-brand/90",
1854
1895
  children: [
1855
1896
  /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(import_lucide_react5.Send, {}),