easy-email-pro-theme 1.59.10 → 1.60.0

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/lib/index.js CHANGED
@@ -58,17 +58,17 @@ import { classnames, useEditorActions, useDragNodePath, useStandaloneElementEdit
58
58
  import { useSlate, ReactEditor, useSlateStatic, useSelected } from "slate-react";
59
59
  import * as React$2 from "react";
60
60
  import React__default, { useRef, useState, useEffect, useCallback, useMemo, createContext, useContext, useLayoutEffect, memo, useReducer, cloneElement, forwardRef, createElement, Suspense, Component } from "react";
61
- import { NodeUtils, BlockManager, ElementType, EditorCore, t, ElementCategory, classnames as classnames$1, StandardType, PluginManager, EditorAuth, mjmlToJsonCore, ConditionOperator, ConditionOperatorSymbol, EmailRenderProvider, components, HtmlNodeAdapter, assertAiAgentFeatureEnabled, imageReplacementTarget, imageDecisionChoices, canAutoReplaceImageTarget, imageTargetAttributeKey, normalizeToolCallsPayload, freezeSelectedTargets, selectedTargetFromContext, resolveAiChatResult, choiceImageAttributeKey, I18nManager } from "easy-email-pro-core";
61
+ import { NodeUtils, BlockManager, ElementType, EditorCore, t, StandardType, ElementCategory, classnames as classnames$1, PluginManager, EditorAuth, mjmlToJsonCore, ConditionOperator, ConditionOperatorSymbol, EmailRenderProvider, components, HtmlNodeAdapter, assertAiAgentFeatureEnabled, imageReplacementTarget, imageDecisionChoices, canAutoReplaceImageTarget, imageTargetAttributeKey, normalizeToolCallsPayload, freezeSelectedTargets, selectedTargetFromContext, resolveAiChatResult, choiceImageAttributeKey, I18nManager } from "easy-email-pro-core";
62
62
  import { parseEasyEmailAiStream, resolveAiChatResult as resolveAiChatResult2 } from "easy-email-pro-core";
63
- import { cloneDeep, get, isEqual, set, omit as omit$2, merge as merge$1, debounce as debounce$2, isUndefined as isUndefined$1, uniqueId, isFunction as isFunction$5, isString as isString$2, isNumber as isNumber$1, upperFirst, sum, flatMap } from "lodash";
63
+ import { cloneDeep, omit as omit$2, get, set, isEqual, merge as merge$1, debounce as debounce$2, isUndefined as isUndefined$1, uniqueId, isFunction as isFunction$5, isString as isString$2, isNumber as isNumber$1, upperFirst, sum, flatMap } from "lodash";
64
64
  import { Editor, Range, Node, Transforms, Path, Text as Text$4, createEditor } from "slate";
65
65
  import { nanoid } from "nanoid";
66
66
  import { Form, Input, Modal, Collapse, Space, Empty, Card, Grid, Button as Button$2, Drawer, Switch, Typography as Typography$1, Tabs, Radio, Tooltip, Divider as Divider$2, Alert, Popconfirm, Message, PageHeader as PageHeader$1, Spin, Layout as Layout$2, ConfigProvider, Popover, ColorPicker as ColorPicker$2, Select as Select$1, Slider, InputNumber, Tag, Link as Link$3, List as List$1, Skeleton, Trigger } from "@arco-design/web-react";
67
+ import { HistoryEditor } from "slate-history";
67
68
  import { IconPlus, IconCopy, IconDelete, IconEdit, IconLeft, IconDownload, IconUndo, IconRedo, IconMinus, IconEye, IconSubscribeAdd, IconClose, IconCheckCircleFill, IconDragArrow, IconPalette, IconLock, IconUnlock, IconImage, IconQuestionCircle, IconLink, IconCloud, IconDragDotVertical, IconSend, IconDown, IconCheck, IconSearch } from "@arco-design/web-react/icon";
68
69
  import { unstable_batchedUpdates, createPortal } from "react-dom";
69
70
  import mjml from "mjml-browser";
70
71
  import { getImageUrlsForAmp, mjml2amp } from "mjml2amp";
71
- import { HistoryEditor } from "slate-history";
72
72
  import { renderToStaticMarkup } from "react-dom/server";
73
73
  const styleText$h = `@font-face {
74
74
  font-family: "iconfont"; /* Project id 4003152 */
@@ -1366,6 +1366,49 @@ const DraggingProvider = ({
1366
1366
  }, [dragHandle, isDragging]);
1367
1367
  return /* @__PURE__ */ React__default.createElement(DraggingProviderContext.Provider, { value }, children);
1368
1368
  };
1369
+ function setEditorFieldValue(editor, path2, name, value) {
1370
+ const node = Node.get(editor, path2);
1371
+ if (NodeUtils.isTextNode(node)) {
1372
+ Transforms.insertText(editor, String(value != null ? value : ""), { at: path2 });
1373
+ return;
1374
+ }
1375
+ if (name === "children") {
1376
+ if (!Array.isArray(value)) {
1377
+ throw new Error("children must be an array");
1378
+ }
1379
+ const children = node.children || [];
1380
+ Editor.withoutNormalizing(editor, () => {
1381
+ for (let index2 = children.length - 1; index2 >= 0; index2 -= 1) {
1382
+ Transforms.removeNodes(editor, { at: [...path2, index2] });
1383
+ }
1384
+ if (value.length) {
1385
+ Transforms.insertNodes(editor, cloneDeep(value), {
1386
+ at: [...path2, 0]
1387
+ });
1388
+ }
1389
+ });
1390
+ return;
1391
+ }
1392
+ if (name === "") {
1393
+ editor.replaceNode({ path: path2, node: value });
1394
+ return;
1395
+ }
1396
+ const cloneNode = cloneDeep(omit$2(node, "children"));
1397
+ if (value === void 0) {
1398
+ const dotIndex = name.lastIndexOf(".");
1399
+ if (dotIndex > -1) {
1400
+ const parent2 = get(cloneNode, name.substring(0, dotIndex));
1401
+ if (parent2 && typeof parent2 === "object") {
1402
+ delete parent2[name.substring(dotIndex + 1)];
1403
+ }
1404
+ } else {
1405
+ delete cloneNode[name];
1406
+ }
1407
+ } else {
1408
+ set(cloneNode, name, value);
1409
+ }
1410
+ Transforms.setNodes(editor, cloneNode, { at: path2 });
1411
+ }
1369
1412
  const loop$1 = () => {
1370
1413
  };
1371
1414
  const getNodePathName = (path2, name) => {
@@ -1432,58 +1475,30 @@ const EditorContextProvider = (props) => {
1432
1475
  getFieldValue(path2, name)
1433
1476
  );
1434
1477
  });
1435
- const setFieldValue = useEventCallback(
1478
+ const setFieldValueTransactional = useEventCallback(
1436
1479
  (path2, name, val) => {
1437
1480
  if (!path2) {
1438
1481
  form.setFieldValue(name, val);
1439
1482
  return;
1440
1483
  }
1484
+ setEditorFieldValue(editor, path2, name, val);
1485
+ form.setFieldValue(getNodePathName(path2, name), val);
1486
+ }
1487
+ );
1488
+ const setFieldValue = useEventCallback(
1489
+ (path2, name, val) => {
1441
1490
  try {
1442
- const node = Node.get(editor, path2);
1443
- if (NodeUtils.isTextNode(node)) {
1444
- Transforms.insertText(editor, val, {
1445
- at: path2
1446
- });
1447
- } else {
1448
- if (name === "children") {
1449
- const cloneNode = cloneDeep(node);
1450
- set(cloneNode, name, val);
1451
- editor.replaceNode({
1452
- path: path2,
1453
- node: cloneNode
1454
- });
1455
- } else if (name === "") {
1456
- editor.replaceNode({
1457
- path: path2,
1458
- node: val
1459
- });
1460
- } else {
1461
- const cloneNode = cloneDeep(omit$2(node, "children"));
1462
- if (val === void 0) {
1463
- const dotIndex = name.lastIndexOf(".");
1464
- if (dotIndex > -1) {
1465
- const parentName = name.substring(0, dotIndex);
1466
- const parent2 = get(cloneNode, parentName);
1467
- if (parent2) {
1468
- parent2[name.substring(dotIndex + 1)] = void 0;
1469
- }
1470
- } else {
1471
- cloneNode[name] = void 0;
1472
- }
1473
- } else {
1474
- set(cloneNode, name, val);
1475
- }
1476
- Transforms.setNodes(editor, cloneNode, {
1477
- at: path2
1478
- });
1479
- }
1480
- }
1481
- form.setFieldValue(getNodePathName(path2, name), val);
1491
+ setFieldValueTransactional(path2, name, val);
1482
1492
  } catch (error2) {
1483
1493
  console.log(error2);
1484
1494
  }
1485
1495
  }
1486
1496
  );
1497
+ const syncValues = useEventCallback(
1498
+ (newValues) => {
1499
+ form.setFieldsValue(newValues);
1500
+ }
1501
+ );
1487
1502
  const reset = useEventCallback((newValues) => {
1488
1503
  if (isEqual(newValues, values))
1489
1504
  return;
@@ -1551,6 +1566,8 @@ const EditorContextProvider = (props) => {
1551
1566
  inited,
1552
1567
  pageDataVariables,
1553
1568
  setFieldValue,
1569
+ setFieldValueTransactional,
1570
+ syncValues,
1554
1571
  getFieldDirty,
1555
1572
  valid,
1556
1573
  reset,
@@ -1564,6 +1581,8 @@ const EditorContextProvider = (props) => {
1564
1581
  inited,
1565
1582
  pageDataVariables,
1566
1583
  setFieldValue,
1584
+ setFieldValueTransactional,
1585
+ syncValues,
1567
1586
  getFieldDirty,
1568
1587
  valid,
1569
1588
  reset,
@@ -2333,6 +2352,335 @@ const useBlocksDrawer = () => {
2333
2352
  parentCategory: context.blocksDrawerParentCategory
2334
2353
  };
2335
2354
  };
2355
+ const MAX_AI_BATCH_TARGETS = 200;
2356
+ const STANDARD_TYPES = new Set(Object.values(StandardType));
2357
+ const CHANGE_FIELDS = [
2358
+ "text",
2359
+ "attributes",
2360
+ "mobileAttributes",
2361
+ "data",
2362
+ "visibility"
2363
+ ];
2364
+ function isObject$c(value) {
2365
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
2366
+ }
2367
+ function isSafeFieldKey$1(key2) {
2368
+ return typeof key2 === "string" && key2.length > 0 && key2.length <= 80 && !key2.includes("__proto__") && !key2.includes("prototype") && !key2.includes("constructor") && /^[a-zA-Z0-9_.-]+$/.test(key2);
2369
+ }
2370
+ function validateRecordKeys(value) {
2371
+ if (!isObject$c(value))
2372
+ throw new Error("batch changes must be objects");
2373
+ Object.keys(value).forEach((key2) => {
2374
+ if (!isSafeFieldKey$1(key2))
2375
+ throw new Error(`unsafe field key: ${key2}`);
2376
+ });
2377
+ }
2378
+ function validateFilter(filter2) {
2379
+ if (filter2.field !== "text" && filter2.field !== "attribute" && filter2.field !== "mobileAttribute" && filter2.field !== "data") {
2380
+ throw new Error(`unsupported batch filter field: ${String(filter2.field)}`);
2381
+ }
2382
+ if (filter2.operator !== "equals" && filter2.operator !== "contains" && filter2.operator !== "exists") {
2383
+ throw new Error(
2384
+ `unsupported batch filter operator: ${String(filter2.operator)}`
2385
+ );
2386
+ }
2387
+ if (filter2.field !== "text" && !isSafeFieldKey$1(filter2.key)) {
2388
+ throw new Error("batch filter key is required and must be safe");
2389
+ }
2390
+ if (filter2.operator === "contains" && typeof filter2.value !== "string") {
2391
+ throw new Error("batch contains filter requires a string value");
2392
+ }
2393
+ }
2394
+ function validateBatchUpdateCall(call) {
2395
+ var _a;
2396
+ if (call.name !== "batch_update") {
2397
+ throw new Error("batch_update tool call is required");
2398
+ }
2399
+ if (call.selector.scope !== "template" && call.selector.scope !== "descendants") {
2400
+ throw new Error("invalid batch selector scope");
2401
+ }
2402
+ if (!Array.isArray(call.selector.types) || call.selector.types.length === 0) {
2403
+ throw new Error("at least one standard block type is required");
2404
+ }
2405
+ call.selector.types.forEach((type) => {
2406
+ if (!STANDARD_TYPES.has(type)) {
2407
+ throw new Error(`unsupported standard block type: ${type}`);
2408
+ }
2409
+ });
2410
+ if (call.selector.scope === "descendants" && !call.selector.root) {
2411
+ throw new Error("descendants scope requires a root target");
2412
+ }
2413
+ (call.selector.where || []).forEach(validateFilter);
2414
+ const changes2 = call.changes || {};
2415
+ if (!CHANGE_FIELDS.some((field) => changes2[field] !== void 0)) {
2416
+ throw new Error("at least one batch change is required");
2417
+ }
2418
+ if (changes2.text !== void 0 && typeof changes2.text !== "string") {
2419
+ throw new Error("batch text change must be a string");
2420
+ }
2421
+ if (changes2.attributes !== void 0)
2422
+ validateRecordKeys(changes2.attributes);
2423
+ if (changes2.mobileAttributes !== void 0)
2424
+ validateRecordKeys(changes2.mobileAttributes);
2425
+ if (changes2.data !== void 0)
2426
+ validateRecordKeys(changes2.data);
2427
+ if (changes2.visibility !== void 0 && (!isObject$c(changes2.visibility) || typeof changes2.visibility.desktop !== "boolean" || typeof changes2.visibility.mobile !== "boolean")) {
2428
+ throw new Error("batch visibility change is invalid");
2429
+ }
2430
+ if (((_a = changes2.visibility) == null ? void 0 : _a.desktop) === false && changes2.visibility.mobile === false) {
2431
+ throw new Error("at least one device must remain visible");
2432
+ }
2433
+ }
2434
+ function ownValue(record, key2) {
2435
+ if (!isObject$c(record) || !Object.prototype.hasOwnProperty.call(record, key2)) {
2436
+ return { exists: false, value: void 0 };
2437
+ }
2438
+ return { exists: true, value: record[key2] };
2439
+ }
2440
+ function filterValue(element, filter2) {
2441
+ if (filter2.field === "text") {
2442
+ return { exists: true, value: Node.string(element) };
2443
+ }
2444
+ const key2 = filter2.key;
2445
+ if (filter2.field === "attribute")
2446
+ return ownValue(element.attributes, key2);
2447
+ if (filter2.field === "mobileAttribute")
2448
+ return ownValue(element.mobileAttributes, key2);
2449
+ return ownValue(element.data, key2);
2450
+ }
2451
+ function matchesFilter(element, filter2) {
2452
+ const current = filterValue(element, filter2);
2453
+ if (filter2.operator === "exists")
2454
+ return current.exists;
2455
+ if (filter2.operator === "contains") {
2456
+ return typeof current.value === "string" && current.value.includes(filter2.value);
2457
+ }
2458
+ return current.exists && Object.is(current.value, filter2.value);
2459
+ }
2460
+ function resolveBatchUpdateTargets(editor, call, resolveRoot) {
2461
+ validateBatchUpdateCall(call);
2462
+ let at = [];
2463
+ if (call.selector.scope === "descendants") {
2464
+ const root2 = (resolveRoot == null ? void 0 : resolveRoot(call.selector.root)) || null;
2465
+ if (!root2)
2466
+ throw new Error("batch root target not found");
2467
+ at = root2;
2468
+ }
2469
+ const acceptedTypes = new Set(call.selector.types);
2470
+ const filters = call.selector.where || [];
2471
+ const targets = Array.from(
2472
+ Editor.nodes(editor, {
2473
+ at,
2474
+ match: (node) => {
2475
+ if (!NodeUtils.isElement(node))
2476
+ return false;
2477
+ const element = node;
2478
+ return acceptedTypes.has(element.type) && filters.every((filter2) => matchesFilter(element, filter2));
2479
+ }
2480
+ })
2481
+ ).map(([node, path2]) => ({
2482
+ element: node,
2483
+ path: [...path2]
2484
+ }));
2485
+ return { call, targets };
2486
+ }
2487
+ function assertBatchTargetLimit(batches, limit = MAX_AI_BATCH_TARGETS) {
2488
+ const count = batches.reduce(
2489
+ (total, batch) => total + batch.targets.length,
2490
+ 0
2491
+ );
2492
+ if (count > limit) {
2493
+ throw new Error(`batch target limit exceeded: ${count}/${limit}`);
2494
+ }
2495
+ }
2496
+ function getBatchRecordUpdate(current, key2, nextValue) {
2497
+ const value = nextValue === null ? void 0 : nextValue;
2498
+ const hasCurrent = Object.prototype.hasOwnProperty.call(current, key2);
2499
+ return {
2500
+ changed: !(value === void 0 && !hasCurrent) && !Object.is(current[key2], value),
2501
+ value
2502
+ };
2503
+ }
2504
+ function applyResolvedBatchUpdate(resolved, applyField) {
2505
+ const result = { applied: 0, skipped: 0, errors: [] };
2506
+ if (!resolved.targets.length)
2507
+ return __spreadProps(__spreadValues({}, result), { skipped: 1 });
2508
+ for (const target of resolved.targets) {
2509
+ let changed = false;
2510
+ for (const field of CHANGE_FIELDS) {
2511
+ if (resolved.call.changes[field] === void 0)
2512
+ continue;
2513
+ const fieldResult = applyField(
2514
+ target,
2515
+ field,
2516
+ resolved.call.changes[field]
2517
+ );
2518
+ if (typeof fieldResult === "boolean") {
2519
+ changed = fieldResult || changed;
2520
+ } else {
2521
+ changed = fieldResult.changed || changed;
2522
+ if (fieldResult.error) {
2523
+ result.errors.push(`${target.element.type}: ${fieldResult.error}`);
2524
+ }
2525
+ }
2526
+ }
2527
+ if (changed)
2528
+ result.applied += 1;
2529
+ else
2530
+ result.skipped += 1;
2531
+ }
2532
+ return result;
2533
+ }
2534
+ const changes = /* @__PURE__ */ new WeakMap();
2535
+ const operationChange = Symbol("aiExternalHistoryChange");
2536
+ const setters = /* @__PURE__ */ new WeakMap();
2537
+ const installed = /* @__PURE__ */ new WeakSet();
2538
+ function mergeChanges(earlier, later) {
2539
+ if (!earlier)
2540
+ return later;
2541
+ if (!later)
2542
+ return earlier;
2543
+ return { before: earlier.before, after: later.after };
2544
+ }
2545
+ function getAiExternalHistory(batch) {
2546
+ if (!batch)
2547
+ return void 0;
2548
+ return changes.get(batch) || batch.operations.map(
2549
+ (operation) => operation[operationChange]
2550
+ ).find(Boolean);
2551
+ }
2552
+ function installAiExternalHistory(editor, setValue) {
2553
+ setters.set(editor, setValue);
2554
+ if (installed.has(editor))
2555
+ return;
2556
+ installed.add(editor);
2557
+ const undo = editor.undo.bind(editor);
2558
+ const redo = editor.redo.bind(editor);
2559
+ const writeHistory = editor.writeHistory.bind(editor);
2560
+ let activeChange;
2561
+ editor.writeHistory = (stack, batch) => {
2562
+ const beforeLength = editor.history[stack].length;
2563
+ const previousBatch = editor.history[stack].at(-1);
2564
+ const previousChange = getAiExternalHistory(previousBatch);
2565
+ const incomingChange = mergeChanges(
2566
+ getAiExternalHistory(batch),
2567
+ activeChange
2568
+ );
2569
+ writeHistory(stack, batch);
2570
+ const actualBatch = editor.history[stack].at(-1);
2571
+ if (!actualBatch)
2572
+ return;
2573
+ const replacedPrevious = editor.history[stack].length === beforeLength && actualBatch !== previousBatch;
2574
+ const change = replacedPrevious ? mergeChanges(previousChange, incomingChange) : incomingChange;
2575
+ if (change)
2576
+ attachAiExternalHistory(actualBatch, change);
2577
+ };
2578
+ editor.undo = () => {
2579
+ var _a;
2580
+ const batch = editor.history.undos.at(-1);
2581
+ const change = getAiExternalHistory(batch);
2582
+ activeChange = change;
2583
+ try {
2584
+ undo();
2585
+ } finally {
2586
+ activeChange = void 0;
2587
+ }
2588
+ if (change)
2589
+ (_a = setters.get(editor)) == null ? void 0 : _a(change.before);
2590
+ };
2591
+ editor.redo = () => {
2592
+ var _a;
2593
+ const batch = editor.history.redos.at(-1);
2594
+ const change = getAiExternalHistory(batch);
2595
+ activeChange = change;
2596
+ try {
2597
+ redo();
2598
+ } finally {
2599
+ activeChange = void 0;
2600
+ }
2601
+ if (change)
2602
+ (_a = setters.get(editor)) == null ? void 0 : _a(change.after);
2603
+ };
2604
+ }
2605
+ function attachAiExternalHistory(batch, change) {
2606
+ changes.set(batch, change);
2607
+ batch.operations.forEach((operation) => {
2608
+ Object.assign(operation, { [operationChange]: change });
2609
+ });
2610
+ }
2611
+ function runAiEditorTransaction(editor, apply2, options2 = {}) {
2612
+ var _a;
2613
+ const undosBefore = [...editor.history.undos];
2614
+ const redosBefore = [...editor.history.redos];
2615
+ const undoStart = editor.history.undos.length;
2616
+ try {
2617
+ let result;
2618
+ HistoryEditor.withoutMerging(editor, () => {
2619
+ Editor.withoutNormalizing(editor, () => {
2620
+ result = apply2();
2621
+ });
2622
+ });
2623
+ const newBatches = editor.history.undos.splice(undoStart);
2624
+ const externalChange = ((_a = options2.getExternalChange) == null ? void 0 : _a.call(options2)) || null;
2625
+ const hasExternalChange = Boolean(
2626
+ externalChange && externalChange.before !== externalChange.after
2627
+ );
2628
+ const finalBatch = newBatches.length ? {
2629
+ operations: newBatches.flatMap((batch) => batch.operations),
2630
+ selectionBefore: newBatches[0].selectionBefore
2631
+ } : hasExternalChange ? { operations: [], selectionBefore: editor.selection } : null;
2632
+ if (finalBatch) {
2633
+ editor.history.undos.push(finalBatch);
2634
+ if (hasExternalChange && externalChange) {
2635
+ attachAiExternalHistory(finalBatch, externalChange);
2636
+ }
2637
+ if (!newBatches.length) {
2638
+ editor.history.redos = [];
2639
+ }
2640
+ }
2641
+ return result;
2642
+ } catch (error2) {
2643
+ while (editor.history.undos.length > undoStart) {
2644
+ HistoryEditor.undo(editor);
2645
+ }
2646
+ editor.history.undos = undosBefore;
2647
+ editor.history.redos = redosBefore;
2648
+ throw error2;
2649
+ }
2650
+ }
2651
+ function validateAiSubject(value) {
2652
+ if (typeof value !== "string") {
2653
+ throw new Error(t("subject must be a string"));
2654
+ }
2655
+ if (!value.trim()) {
2656
+ throw new Error(t("subject cannot be empty"));
2657
+ }
2658
+ return value;
2659
+ }
2660
+ function isAiSubjectChanged(current, next) {
2661
+ return current !== next;
2662
+ }
2663
+ function subjectAfterApplyingTemplate(current, template) {
2664
+ if (!template || typeof template !== "object" || !("subject" in template)) {
2665
+ return current;
2666
+ }
2667
+ return validateAiSubject(template.subject);
2668
+ }
2669
+ function emailAfterApplyingAiTemplate(current, template) {
2670
+ if (!template || typeof template !== "object")
2671
+ return current;
2672
+ const candidate = template;
2673
+ if (!candidate.content || typeof candidate.content !== "object") {
2674
+ return current;
2675
+ }
2676
+ return __spreadProps(__spreadValues({}, current), {
2677
+ subject: subjectAfterApplyingTemplate(
2678
+ typeof current.subject === "string" ? current.subject : "",
2679
+ candidate
2680
+ ),
2681
+ content: candidate.content
2682
+ });
2683
+ }
2336
2684
  const MAX_TOOL_CALLS = 30;
2337
2685
  const MAX_REPLACE_CHILDREN_NODES = 160;
2338
2686
  function isObject$b(value) {
@@ -2423,6 +2771,10 @@ function targetFromElement(element, fallback) {
2423
2771
  return fallback;
2424
2772
  }
2425
2773
  function describeAppliedChange(call) {
2774
+ if (call.name === "update_subject")
2775
+ return t("Subject updated");
2776
+ if (call.name === "batch_update")
2777
+ return t("Batch updated");
2426
2778
  if (call.name === "update_text")
2427
2779
  return t("Text updated");
2428
2780
  if (call.name === "update_attribute")
@@ -2511,13 +2863,21 @@ function normalizeElementBlock(block) {
2511
2863
  children: Array.isArray(normalized.children) ? normalized.children : []
2512
2864
  }));
2513
2865
  }
2866
+ function createBlockForInsert(block) {
2867
+ const normalized = normalizeBlock(block);
2868
+ const definition = BlockManager.getBlockByType(normalized.type);
2869
+ return assignIdsToElementTree(definition.create(normalized));
2870
+ }
2514
2871
  function useApplyAiToolCalls() {
2515
2872
  const editor = useSlate();
2516
2873
  const lock = useLockState();
2517
2874
  const selectedNodePath = useSelectedNodePath();
2518
2875
  const setSelectedNodePath = useSetSelectedNodePath();
2519
- const { setFieldValue, reset } = useEditorContext();
2520
- const { addBlock, copyBlock, deleteBlock, moveDown, moveUp } = useElementInteract();
2876
+ const setHoverNodePath = useSetHoverNodePath();
2877
+ const { setFieldValueTransactional, syncValues, reset, values } = useEditorContext();
2878
+ installAiExternalHistory(editor, (subject) => {
2879
+ setFieldValueTransactional(null, "subject", subject);
2880
+ });
2521
2881
  const resolveTargetPath = useEventCallback((target) => {
2522
2882
  if ("selected" in target)
2523
2883
  return selectedNodePath;
@@ -2561,6 +2921,57 @@ function useApplyAiToolCalls() {
2561
2921
  return [node, path2];
2562
2922
  }
2563
2923
  );
2924
+ const applyBatchField = useEventCallback(
2925
+ (target, field, value) => {
2926
+ const node = Node.get(editor, target.path);
2927
+ if (!NodeUtils.isElement(node))
2928
+ return false;
2929
+ const element = node;
2930
+ if (field === "text") {
2931
+ if (typeof value !== "string" || !canReplaceWithPlainText(element)) {
2932
+ return {
2933
+ changed: false,
2934
+ error: t("target has no editable plain text")
2935
+ };
2936
+ }
2937
+ if (Node.string(element) === value)
2938
+ return false;
2939
+ setFieldValueTransactional(
2940
+ target.path,
2941
+ "children",
2942
+ textToChildren(value)
2943
+ );
2944
+ return true;
2945
+ }
2946
+ if (field === "visibility") {
2947
+ if (!isObject$b(value))
2948
+ return false;
2949
+ const visible = value.desktop === true && value.mobile === false ? "desktop" : value.desktop === false && value.mobile === true ? "mobile" : void 0;
2950
+ if (element.visible === visible)
2951
+ return false;
2952
+ setFieldValueTransactional(target.path, "visible", visible);
2953
+ return true;
2954
+ }
2955
+ if (!isObject$b(value))
2956
+ return false;
2957
+ const fieldRoot = field;
2958
+ const source = element[fieldRoot];
2959
+ const current = isObject$b(source) ? source : {};
2960
+ let changed = false;
2961
+ Object.entries(value).forEach(([key2, nextValue]) => {
2962
+ const update = getBatchRecordUpdate(current, key2, nextValue);
2963
+ if (!update.changed)
2964
+ return;
2965
+ setFieldValueTransactional(
2966
+ target.path,
2967
+ `${fieldRoot}.${key2}`,
2968
+ update.value
2969
+ );
2970
+ changed = true;
2971
+ });
2972
+ return changed;
2973
+ }
2974
+ );
2564
2975
  return useEventCallback((toolCalls) => {
2565
2976
  const result = {
2566
2977
  applied: 0,
@@ -2575,63 +2986,272 @@ function useApplyAiToolCalls() {
2575
2986
  errors: [t("editor is locked")]
2576
2987
  };
2577
2988
  }
2989
+ if (toolCalls.some((call) => call.name === "apply_template") && toolCalls.length > 1) {
2990
+ return {
2991
+ applied: 0,
2992
+ skipped: toolCalls.length,
2993
+ errors: [t("apply_template cannot be mixed with other tools")],
2994
+ changes: []
2995
+ };
2996
+ }
2578
2997
  const calls = toolCalls.slice(0, MAX_TOOL_CALLS);
2579
2998
  if (toolCalls.length > MAX_TOOL_CALLS) {
2580
2999
  result.errors.push(`${t("tool calls truncated to")} ${MAX_TOOL_CALLS}`);
2581
3000
  }
2582
- Editor.withoutNormalizing(editor, () => {
3001
+ const resolvedBatches = /* @__PURE__ */ new Map();
3002
+ const resolvedTargets = /* @__PURE__ */ new Map();
3003
+ const preflightErrors = [];
3004
+ try {
2583
3005
  calls.forEach((call, index2) => {
2584
- try {
2585
- if (call.name === "apply_template") {
2586
- reset(call.template);
2587
- result.applied += 1;
2588
- return;
3006
+ if (call.name === "update_subject") {
3007
+ validateAiSubject(call.subject);
3008
+ return;
3009
+ }
3010
+ if (call.name === "apply_template") {
3011
+ if (!isObject$b(call.template)) {
3012
+ throw new Error(t("template must be an object"));
2589
3013
  }
2590
- const targetEntry = getTargetElement(call.target);
2591
- if (!targetEntry) {
2592
- result.skipped += 1;
2593
- result.errors.push(
2594
- `${call.name}: ${t("target not found")} (${targetLabel(call.target)})`
3014
+ if (calls.length > 1) {
3015
+ throw new Error(
3016
+ t("apply_template cannot be mixed with other tools")
2595
3017
  );
2596
- return;
2597
3018
  }
2598
- const [element, path2] = targetEntry;
2599
- const recordChange = () => {
2600
- var _a, _b;
3019
+ return;
3020
+ }
3021
+ if (call.name === "batch_update") {
3022
+ resolvedBatches.set(
3023
+ index2,
3024
+ resolveBatchUpdateTargets(
3025
+ editor,
3026
+ call,
3027
+ (target) => resolveTargetPath(target) || null
3028
+ )
3029
+ );
3030
+ return;
3031
+ }
3032
+ const targetEntry = getTargetElement(call.target);
3033
+ if (!targetEntry) {
3034
+ throw new Error(
3035
+ `${t("target not found")} (${targetLabel(call.target)})`
3036
+ );
3037
+ }
3038
+ const [element, path2] = targetEntry;
3039
+ resolvedTargets.set(index2, targetEntry);
3040
+ if (call.name === "update_text" && !canReplaceWithPlainText(element)) {
3041
+ throw new Error(
3042
+ hasAnyTextNode(element) ? t(
3043
+ "target contains mixed content; use a more specific text node"
3044
+ ) : t("target has no editable text")
3045
+ );
3046
+ }
3047
+ if ((call.name === "update_attribute" || call.name === "update_mobile_attribute" || call.name === "update_data") && !isSafeFieldKey(call.key)) {
3048
+ throw new Error(t("invalid field key"));
3049
+ }
3050
+ if (call.name === "update_attributes") {
3051
+ if (!isObject$b(call.attributes)) {
3052
+ throw new Error(t("attributes must be an object"));
3053
+ }
3054
+ Object.keys(call.attributes).forEach((key2) => {
3055
+ if (!isSafeFieldKey(key2)) {
3056
+ throw new Error(`${t("invalid attribute key")}: ${key2}`);
3057
+ }
3058
+ });
3059
+ }
3060
+ if (call.name === "replace_children") {
3061
+ const total = countNodes(call.children);
3062
+ if (total > MAX_REPLACE_CHILDREN_NODES) {
3063
+ throw new Error(
3064
+ `${t("replace_children is too large")} (${total} ${t("nodes")})`
3065
+ );
3066
+ }
3067
+ normalizeChildren(call.children);
3068
+ }
3069
+ if (call.name === "set_visibility" && call.desktop === false && call.mobile === false) {
3070
+ throw new Error(t("at least one device must remain visible"));
3071
+ }
3072
+ if (call.name === "update_link" && typeof call.href !== "string") {
3073
+ throw new Error(t("href is required"));
3074
+ }
3075
+ if ((call.name === "replace_block" || call.name === "move_up" || call.name === "move_down" || call.name === "duplicate" || call.name === "delete_node" || call.name === "add_block_after" || call.name === "add_block_before") && isProtectedForStructureEdit(element)) {
3076
+ throw new Error(t("protected node cannot be structurally edited"));
3077
+ }
3078
+ if (call.name === "replace_block")
3079
+ normalizeElementBlock(call.block);
3080
+ if (call.name === "add_block_after" || call.name === "add_block_before") {
3081
+ createBlockForInsert(call.block);
3082
+ }
3083
+ Node.get(editor, path2);
3084
+ });
3085
+ assertBatchTargetLimit(Array.from(resolvedBatches.values()));
3086
+ } catch (error2) {
3087
+ preflightErrors.push(
3088
+ error2 instanceof Error ? error2.message : String(error2)
3089
+ );
3090
+ }
3091
+ if (preflightErrors.length) {
3092
+ return {
3093
+ applied: 0,
3094
+ skipped: calls.length,
3095
+ errors: preflightErrors,
3096
+ changes: []
3097
+ };
3098
+ }
3099
+ const targetRefs = /* @__PURE__ */ new Map();
3100
+ const batchRefs = /* @__PURE__ */ new Map();
3101
+ resolvedTargets.forEach(([, path2], index2) => {
3102
+ targetRefs.set(index2, Editor.pathRef(editor, path2));
3103
+ });
3104
+ resolvedBatches.forEach((resolved, index2) => {
3105
+ batchRefs.set(
3106
+ index2,
3107
+ resolved.targets.map((target) => Editor.pathRef(editor, target.path))
3108
+ );
3109
+ });
3110
+ const originalValues = structuredClone(values);
3111
+ const subjectBefore = values.subject;
3112
+ let subjectAfter = subjectBefore;
3113
+ try {
3114
+ runAiEditorTransaction(
3115
+ editor,
3116
+ () => {
3117
+ calls.forEach((call, index2) => {
3118
+ var _a, _b, _c, _d;
3119
+ if (call.name === "update_subject") {
3120
+ const nextSubject = validateAiSubject(call.subject);
3121
+ if (!isAiSubjectChanged(subjectAfter, nextSubject)) {
3122
+ result.skipped += 1;
3123
+ return;
3124
+ }
3125
+ setFieldValueTransactional(null, "subject", nextSubject);
3126
+ subjectAfter = nextSubject;
3127
+ result.applied += 1;
3128
+ if (!((_a = result.changes) == null ? void 0 : _a.some((change) => change.id === "subject"))) {
3129
+ (_b = result.changes) == null ? void 0 : _b.push({
3130
+ id: "subject",
3131
+ label: t("Subject"),
3132
+ description: describeAppliedChange(call),
3133
+ target: { type: "page" }
3134
+ });
3135
+ }
3136
+ return;
3137
+ }
3138
+ if (call.name === "apply_template") {
3139
+ const nextTemplate = emailAfterApplyingAiTemplate(
3140
+ values,
3141
+ call.template
3142
+ );
3143
+ subjectAfter = subjectAfterApplyingTemplate(
3144
+ subjectAfter,
3145
+ nextTemplate
3146
+ );
3147
+ reset(nextTemplate);
3148
+ result.applied += 1;
3149
+ return;
3150
+ }
3151
+ if (call.name === "batch_update") {
3152
+ const resolved = resolvedBatches.get(index2);
3153
+ if (!resolved)
3154
+ throw new Error(t("batch targets not resolved"));
3155
+ const refs = batchRefs.get(index2) || [];
3156
+ const targets = resolved.targets.map((target, targetIndex) => {
3157
+ var _a2;
3158
+ const path22 = (_a2 = refs[targetIndex]) == null ? void 0 : _a2.current;
3159
+ if (!path22)
3160
+ throw new Error(t("batch target no longer exists"));
3161
+ return __spreadProps(__spreadValues({}, target), { path: path22 });
3162
+ });
3163
+ const batchResult = applyResolvedBatchUpdate(
3164
+ __spreadProps(__spreadValues({}, resolved), { targets }),
3165
+ applyBatchField
3166
+ );
3167
+ result.applied += batchResult.applied;
3168
+ result.skipped += batchResult.skipped;
3169
+ result.errors.push(...batchResult.errors);
3170
+ if (batchResult.applied > 0) {
3171
+ const fallbackTarget = resolved.call.selector.root || {
3172
+ type: resolved.call.selector.types[0]
3173
+ };
3174
+ (_c = result.changes) == null ? void 0 : _c.push({
3175
+ id: `batch:${index2}`,
3176
+ label: resolved.call.selector.types.join(", "),
3177
+ description: describeAppliedChange(call),
3178
+ target: fallbackTarget
3179
+ });
3180
+ }
3181
+ return;
3182
+ }
3183
+ const path2 = (_d = targetRefs.get(index2)) == null ? void 0 : _d.current;
3184
+ if (!path2)
3185
+ throw new Error(t("target no longer exists"));
3186
+ const node = Node.get(editor, path2);
3187
+ if (!NodeUtils.isElement(node)) {
3188
+ throw new Error(t("target is no longer an element"));
3189
+ }
3190
+ const element = node;
2601
3191
  const [blockElement, blockPath] = closestBlockEntry(
2602
3192
  editor,
2603
3193
  path2
2604
3194
  ) || [element, path2];
2605
- const key2 = changeKey(blockPath, describeAppliedChange(call));
2606
- if ((_a = result.changes) == null ? void 0 : _a.some((change) => change.id === key2))
2607
- return;
2608
- (_b = result.changes) == null ? void 0 : _b.push({
2609
- id: key2,
3195
+ const appliedChange = {
3196
+ id: changeKey(blockPath, describeAppliedChange(call)),
2610
3197
  label: elementLabel(blockElement),
2611
3198
  description: describeAppliedChange(call),
2612
3199
  target: targetFromElement(blockElement, call.target),
2613
3200
  path: [...blockPath]
2614
- });
2615
- };
2616
- if (call.name === "update_text") {
2617
- if (!canReplaceWithPlainText(element)) {
2618
- throw new Error(
2619
- hasAnyTextNode(element) ? t(
2620
- "target contains mixed content; use a more specific text node"
2621
- ) : t("target has no editable text")
3201
+ };
3202
+ const recordChange = () => {
3203
+ var _a2, _b2;
3204
+ if ((_a2 = result.changes) == null ? void 0 : _a2.some((change) => change.id === appliedChange.id)) {
3205
+ return;
3206
+ }
3207
+ (_b2 = result.changes) == null ? void 0 : _b2.push(appliedChange);
3208
+ };
3209
+ if (call.name === "update_text") {
3210
+ if (!canReplaceWithPlainText(element)) {
3211
+ throw new Error(
3212
+ hasAnyTextNode(element) ? t(
3213
+ "target contains mixed content; use a more specific text node"
3214
+ ) : t("target has no editable text")
3215
+ );
3216
+ }
3217
+ setFieldValueTransactional(
3218
+ path2,
3219
+ "children",
3220
+ textToChildren(call.text)
2622
3221
  );
3222
+ result.applied += 1;
3223
+ recordChange();
3224
+ return;
2623
3225
  }
2624
- setFieldValue(path2, "children", textToChildren(call.text));
2625
- result.applied += 1;
2626
- recordChange();
2627
- return;
2628
- }
2629
- if (call.name === "update_attribute") {
2630
- if (!isSafeFieldKey(call.key))
2631
- throw new Error(t("invalid attribute key"));
2632
- if (call.key.startsWith("mobileAttributes.")) {
3226
+ if (call.name === "update_attribute") {
3227
+ if (!isSafeFieldKey(call.key))
3228
+ throw new Error(t("invalid attribute key"));
3229
+ if (call.key.startsWith("mobileAttributes.")) {
3230
+ const mobileKey = normalizeMobileAttributeKey(call.key);
3231
+ setFieldValueTransactional(
3232
+ path2,
3233
+ `mobileAttributes.${mobileKey}`,
3234
+ call.value === null ? void 0 : call.value
3235
+ );
3236
+ result.applied += 1;
3237
+ recordChange();
3238
+ return;
3239
+ }
3240
+ const attributeKey = normalizeAttributeKey(call.key);
3241
+ setFieldValueTransactional(
3242
+ path2,
3243
+ `attributes.${attributeKey}`,
3244
+ call.value === null ? void 0 : call.value
3245
+ );
3246
+ result.applied += 1;
3247
+ recordChange();
3248
+ return;
3249
+ }
3250
+ if (call.name === "update_mobile_attribute") {
3251
+ if (!isSafeFieldKey(call.key))
3252
+ throw new Error(t("invalid mobile attribute key"));
2633
3253
  const mobileKey = normalizeMobileAttributeKey(call.key);
2634
- setFieldValue(
3254
+ setFieldValueTransactional(
2635
3255
  path2,
2636
3256
  `mobileAttributes.${mobileKey}`,
2637
3257
  call.value === null ? void 0 : call.value
@@ -2640,164 +3260,182 @@ function useApplyAiToolCalls() {
2640
3260
  recordChange();
2641
3261
  return;
2642
3262
  }
2643
- const attributeKey = normalizeAttributeKey(call.key);
2644
- setFieldValue(
2645
- path2,
2646
- `attributes.${attributeKey}`,
2647
- call.value === null ? void 0 : call.value
2648
- );
2649
- result.applied += 1;
2650
- recordChange();
2651
- return;
2652
- }
2653
- if (call.name === "update_mobile_attribute") {
2654
- if (!isSafeFieldKey(call.key))
2655
- throw new Error(t("invalid mobile attribute key"));
2656
- const mobileKey = normalizeMobileAttributeKey(call.key);
2657
- setFieldValue(
2658
- path2,
2659
- `mobileAttributes.${mobileKey}`,
2660
- call.value === null ? void 0 : call.value
2661
- );
2662
- result.applied += 1;
2663
- recordChange();
2664
- return;
2665
- }
2666
- if (call.name === "update_attributes") {
2667
- if (!isObject$b(call.attributes)) {
2668
- throw new Error(t("attributes must be an object"));
3263
+ if (call.name === "update_attributes") {
3264
+ if (!isObject$b(call.attributes)) {
3265
+ throw new Error(t("attributes must be an object"));
3266
+ }
3267
+ Object.entries(call.attributes).forEach(([key2, value]) => {
3268
+ if (!isSafeFieldKey(key2)) {
3269
+ throw new Error(`${t("invalid attribute key")}: ${key2}`);
3270
+ }
3271
+ const fieldRoot = call.scope === "mobile" || key2.startsWith("mobileAttributes.") ? "mobileAttributes" : "attributes";
3272
+ const normalizedKey = fieldRoot === "mobileAttributes" ? normalizeMobileAttributeKey(key2) : normalizeAttributeKey(key2);
3273
+ setFieldValueTransactional(
3274
+ path2,
3275
+ `${fieldRoot}.${normalizedKey}`,
3276
+ value === null ? void 0 : value
3277
+ );
3278
+ });
3279
+ result.applied += 1;
3280
+ recordChange();
3281
+ return;
2669
3282
  }
2670
- Object.entries(call.attributes).forEach(([key2, value]) => {
2671
- if (!isSafeFieldKey(key2)) {
2672
- throw new Error(`${t("invalid attribute key")}: ${key2}`);
3283
+ if (call.name === "update_data") {
3284
+ if (!isSafeFieldKey(call.key))
3285
+ throw new Error(t("invalid data key"));
3286
+ setFieldValueTransactional(
3287
+ path2,
3288
+ `data.${call.key}`,
3289
+ call.value === null ? void 0 : call.value
3290
+ );
3291
+ result.applied += 1;
3292
+ recordChange();
3293
+ return;
3294
+ }
3295
+ if (call.name === "replace_children") {
3296
+ const total = countNodes(call.children);
3297
+ if (total > MAX_REPLACE_CHILDREN_NODES) {
3298
+ throw new Error(
3299
+ `${t("replace_children is too large")} (${total} ${t("nodes")})`
3300
+ );
2673
3301
  }
2674
- const fieldRoot = call.scope === "mobile" || key2.startsWith("mobileAttributes.") ? "mobileAttributes" : "attributes";
2675
- const normalizedKey = fieldRoot === "mobileAttributes" ? normalizeMobileAttributeKey(key2) : normalizeAttributeKey(key2);
2676
- setFieldValue(
3302
+ setFieldValueTransactional(
2677
3303
  path2,
2678
- `${fieldRoot}.${normalizedKey}`,
2679
- value === null ? void 0 : value
3304
+ "children",
3305
+ normalizeChildren(call.children)
2680
3306
  );
2681
- });
2682
- result.applied += 1;
2683
- recordChange();
2684
- return;
2685
- }
2686
- if (call.name === "update_data") {
2687
- if (!isSafeFieldKey(call.key))
2688
- throw new Error(t("invalid data key"));
2689
- setFieldValue(
2690
- path2,
2691
- `data.${call.key}`,
2692
- call.value === null ? void 0 : call.value
2693
- );
2694
- result.applied += 1;
2695
- recordChange();
2696
- return;
2697
- }
2698
- if (call.name === "replace_children") {
2699
- const total = countNodes(call.children);
2700
- if (total > MAX_REPLACE_CHILDREN_NODES) {
3307
+ result.applied += 1;
3308
+ recordChange();
3309
+ return;
3310
+ }
3311
+ if (call.name === "set_visibility") {
3312
+ if (call.desktop === false && call.mobile === false) {
3313
+ throw new Error(t("at least one device must remain visible"));
3314
+ }
3315
+ const visible = call.desktop === true && call.mobile === false ? "desktop" : call.desktop === false && call.mobile === true ? "mobile" : void 0;
3316
+ setFieldValueTransactional(path2, "visible", visible);
3317
+ result.applied += 1;
3318
+ recordChange();
3319
+ return;
3320
+ }
3321
+ if (call.name === "update_link") {
3322
+ if (typeof call.href !== "string")
3323
+ throw new Error(t("href is required"));
3324
+ setFieldValueTransactional(path2, "attributes.href", call.href);
3325
+ if (typeof call.blank === "boolean") {
3326
+ setFieldValueTransactional(
3327
+ path2,
3328
+ "attributes.target",
3329
+ call.blank ? "_blank" : "_self"
3330
+ );
3331
+ }
3332
+ if (typeof call.title === "string") {
3333
+ setFieldValueTransactional(
3334
+ path2,
3335
+ "attributes.title",
3336
+ call.title
3337
+ );
3338
+ }
3339
+ result.applied += 1;
3340
+ recordChange();
3341
+ return;
3342
+ }
3343
+ if (call.name === "select_node") {
3344
+ setSelectedNodePath(path2);
3345
+ result.applied += 1;
3346
+ recordChange();
3347
+ return;
3348
+ }
3349
+ if (isProtectedForStructureEdit(element)) {
2701
3350
  throw new Error(
2702
- `${t("replace_children is too large")} (${total} ${t("nodes")})`
3351
+ t("protected node cannot be structurally edited")
2703
3352
  );
2704
3353
  }
2705
- setFieldValue(path2, "children", normalizeChildren(call.children));
2706
- result.applied += 1;
2707
- recordChange();
2708
- return;
2709
- }
2710
- if (call.name === "set_visibility") {
2711
- if (call.desktop === false && call.mobile === false) {
2712
- throw new Error(t("at least one device must remain visible"));
3354
+ if (call.name === "replace_block") {
3355
+ const replacement = normalizeElementBlock(call.block);
3356
+ Transforms.removeNodes(editor, { at: path2 });
3357
+ Transforms.insertNodes(editor, replacement, { at: path2 });
3358
+ result.applied += 1;
3359
+ recordChange();
3360
+ return;
2713
3361
  }
2714
- const visible = call.desktop === true && call.mobile === false ? "desktop" : call.desktop === false && call.mobile === true ? "mobile" : void 0;
2715
- setFieldValue(path2, "visible", visible);
2716
- result.applied += 1;
2717
- recordChange();
2718
- return;
2719
- }
2720
- if (call.name === "update_link") {
2721
- if (typeof call.href !== "string")
2722
- throw new Error(t("href is required"));
2723
- setFieldValue(path2, "attributes.href", call.href);
2724
- if (typeof call.blank === "boolean") {
2725
- setFieldValue(
2726
- path2,
2727
- "attributes.target",
2728
- call.blank ? "_blank" : "_self"
3362
+ if (call.name === "move_up") {
3363
+ const previous = Path.previous(path2);
3364
+ Node.get(editor, previous);
3365
+ Transforms.moveNodes(editor, { at: path2, to: previous });
3366
+ setSelectedNodePath(previous);
3367
+ result.applied += 1;
3368
+ recordChange();
3369
+ return;
3370
+ }
3371
+ if (call.name === "move_down") {
3372
+ const next = Path.next(path2);
3373
+ Node.get(editor, next);
3374
+ Transforms.moveNodes(editor, { at: path2, to: next });
3375
+ setSelectedNodePath(next);
3376
+ result.applied += 1;
3377
+ recordChange();
3378
+ return;
3379
+ }
3380
+ if (call.name === "duplicate") {
3381
+ const next = Path.next(path2);
3382
+ Transforms.insertNodes(
3383
+ editor,
3384
+ assignIdsToElementTree(element, true),
3385
+ { at: next }
2729
3386
  );
3387
+ setSelectedNodePath(next);
3388
+ result.applied += 1;
3389
+ recordChange();
3390
+ return;
2730
3391
  }
2731
- if (typeof call.title === "string") {
2732
- setFieldValue(path2, "attributes.title", call.title);
3392
+ if (call.name === "delete_node") {
3393
+ editor.removeNode({ path: path2 });
3394
+ setSelectedNodePath(null);
3395
+ setHoverNodePath(null);
3396
+ result.applied += 1;
3397
+ recordChange();
3398
+ return;
2733
3399
  }
2734
- result.applied += 1;
2735
- recordChange();
2736
- return;
2737
- }
2738
- if (call.name === "select_node") {
2739
- setSelectedNodePath(path2);
2740
- result.applied += 1;
2741
- recordChange();
2742
- return;
2743
- }
2744
- if (isProtectedForStructureEdit(element)) {
2745
- throw new Error(t("protected node cannot be structurally edited"));
2746
- }
2747
- if (call.name === "replace_block") {
2748
- const replacement = normalizeElementBlock(call.block);
2749
- Transforms.removeNodes(editor, { at: path2 });
2750
- Transforms.insertNodes(editor, replacement, { at: path2 });
2751
- result.applied += 1;
2752
- recordChange();
2753
- return;
2754
- }
2755
- if (call.name === "move_up") {
2756
- moveUp(path2);
2757
- result.applied += 1;
2758
- recordChange();
2759
- return;
2760
- }
2761
- if (call.name === "move_down") {
2762
- moveDown(path2);
2763
- result.applied += 1;
2764
- recordChange();
2765
- return;
2766
- }
2767
- if (call.name === "duplicate") {
2768
- copyBlock(path2);
2769
- result.applied += 1;
2770
- recordChange();
2771
- return;
2772
- }
2773
- if (call.name === "delete_node") {
2774
- deleteBlock(path2);
2775
- result.applied += 1;
2776
- recordChange();
2777
- return;
2778
- }
2779
- if (call.name === "add_block_after") {
2780
- addBlock(normalizeBlock(call.block), path2, true);
2781
- result.applied += 1;
2782
- recordChange();
2783
- return;
2784
- }
2785
- if (call.name === "add_block_before") {
2786
- addBlock(normalizeBlock(call.block), path2, false);
2787
- result.applied += 1;
2788
- recordChange();
2789
- return;
2790
- }
2791
- result.skipped += 1;
2792
- result.errors.push(`${call.name}: ${t("unsupported tool")}`);
2793
- } catch (error2) {
2794
- result.skipped += 1;
2795
- result.errors.push(
2796
- `${index2 + 1}. ${call.name}: ${error2 instanceof Error ? error2.message : String(error2)}`
2797
- );
3400
+ if (call.name === "add_block_after") {
3401
+ const next = Path.next(path2);
3402
+ Transforms.insertNodes(editor, createBlockForInsert(call.block), {
3403
+ at: next
3404
+ });
3405
+ setSelectedNodePath(next);
3406
+ result.applied += 1;
3407
+ recordChange();
3408
+ return;
3409
+ }
3410
+ if (call.name === "add_block_before") {
3411
+ Transforms.insertNodes(editor, createBlockForInsert(call.block), {
3412
+ at: path2
3413
+ });
3414
+ setSelectedNodePath(path2);
3415
+ result.applied += 1;
3416
+ recordChange();
3417
+ return;
3418
+ }
3419
+ result.skipped += 1;
3420
+ result.errors.push(`${call.name}: ${t("unsupported tool")}`);
3421
+ });
3422
+ },
3423
+ {
3424
+ getExternalChange: () => subjectBefore === subjectAfter ? null : { before: subjectBefore, after: subjectAfter }
2798
3425
  }
2799
- });
2800
- });
3426
+ );
3427
+ } catch (error2) {
3428
+ syncValues(originalValues);
3429
+ return {
3430
+ applied: 0,
3431
+ skipped: calls.length,
3432
+ errors: [error2 instanceof Error ? error2.message : String(error2)],
3433
+ changes: []
3434
+ };
3435
+ } finally {
3436
+ targetRefs.forEach((ref) => ref.unref());
3437
+ batchRefs.forEach((refs) => refs.forEach((ref) => ref.unref()));
3438
+ }
2801
3439
  return result;
2802
3440
  });
2803
3441
  }
@@ -26220,14 +26858,14 @@ var getNative$1 = _getNative, root$2 = _root;
26220
26858
  var Set$2 = getNative$1(root$2, "Set");
26221
26859
  var _Set = Set$2;
26222
26860
  var getNative = _getNative, root$1 = _root;
26223
- var WeakMap$1 = getNative(root$1, "WeakMap");
26224
- var _WeakMap = WeakMap$1;
26225
- var DataView = _DataView, Map$1 = _Map, Promise$1 = _Promise, Set$1 = _Set, WeakMap = _WeakMap, baseGetTag$1 = _baseGetTag, toSource = _toSource;
26861
+ var WeakMap$2 = getNative(root$1, "WeakMap");
26862
+ var _WeakMap = WeakMap$2;
26863
+ var DataView = _DataView, Map$1 = _Map, Promise$1 = _Promise, Set$1 = _Set, WeakMap$1 = _WeakMap, baseGetTag$1 = _baseGetTag, toSource = _toSource;
26226
26864
  var mapTag$3 = "[object Map]", objectTag$2 = "[object Object]", promiseTag = "[object Promise]", setTag$3 = "[object Set]", weakMapTag$1 = "[object WeakMap]";
26227
26865
  var dataViewTag$2 = "[object DataView]";
26228
- var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map$1), promiseCtorString = toSource(Promise$1), setCtorString = toSource(Set$1), weakMapCtorString = toSource(WeakMap);
26866
+ var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map$1), promiseCtorString = toSource(Promise$1), setCtorString = toSource(Set$1), weakMapCtorString = toSource(WeakMap$1);
26229
26867
  var getTag$3 = baseGetTag$1;
26230
- if (DataView && getTag$3(new DataView(new ArrayBuffer(1))) != dataViewTag$2 || Map$1 && getTag$3(new Map$1()) != mapTag$3 || Promise$1 && getTag$3(Promise$1.resolve()) != promiseTag || Set$1 && getTag$3(new Set$1()) != setTag$3 || WeakMap && getTag$3(new WeakMap()) != weakMapTag$1) {
26868
+ if (DataView && getTag$3(new DataView(new ArrayBuffer(1))) != dataViewTag$2 || Map$1 && getTag$3(new Map$1()) != mapTag$3 || Promise$1 && getTag$3(Promise$1.resolve()) != promiseTag || Set$1 && getTag$3(new Set$1()) != setTag$3 || WeakMap$1 && getTag$3(new WeakMap$1()) != weakMapTag$1) {
26231
26869
  getTag$3 = function(value) {
26232
26870
  var result = baseGetTag$1(value), Ctor = result == objectTag$2 ? value.constructor : void 0, ctorString = Ctor ? toSource(Ctor) : "";
26233
26871
  if (ctorString) {
@@ -41921,6 +42559,15 @@ function EasyEmailProAiAgent({
41921
42559
  "×"
41922
42560
  ), /* @__PURE__ */ React__default.createElement("img", { src: previewAttachment.url, alt: previewAttachment.name, onClick: (event) => event.stopPropagation() })) : null);
41923
42561
  }
42562
+ function formatAiApplyResult(result, summary) {
42563
+ const lines = summary ? [summary] : [t("Updated the email preview.")];
42564
+ lines.push(
42565
+ result.skipped > 0 ? `${t("Updated")} ${result.applied} ${t("blocks")}; ${t("skipped")} ${result.skipped}.` : `${t("Updated")} ${result.applied} ${t("blocks")}.`
42566
+ );
42567
+ if (result.errors.length)
42568
+ lines.push(result.errors.join("\n"));
42569
+ return lines.join("\n\n");
42570
+ }
41924
42571
  const doneStatuses = ["FINISHED", "ERROR"];
41925
42572
  function getInitialConversation() {
41926
42573
  return [
@@ -42234,18 +42881,13 @@ function useAiAgentSession(options2) {
42234
42881
  ((_a = result == null ? void 0 : result.errors) == null ? void 0 : _a.filter(Boolean)) || []
42235
42882
  );
42236
42883
  updateMessage(activeRun.assistantMessageId, {
42237
- content: errors.length ? `${payload.summary || t("Some local changes were not applied.")}
42238
-
42239
- ${t(
42240
- "Some operations were not applied"
42241
- )}:
42242
- ${errors.join("\n")}` : toolCallsText(__spreadProps(__spreadValues({}, payload), { toolCalls }), result),
42884
+ content: result ? formatAiApplyResult(__spreadProps(__spreadValues({}, result), { errors }), payload.summary) : toolCallsText(__spreadProps(__spreadValues({}, payload), { toolCalls }), result),
42243
42885
  status: "ready"
42244
42886
  });
42245
42887
  setActivity(
42246
42888
  errors.length ? t("Some local changes were not applied") : t("Updated the email preview.")
42247
42889
  );
42248
- if ((result == null ? void 0 : result.applied) === void 0 || result.applied > 0) {
42890
+ if (!result || result.applied > 0) {
42249
42891
  scheduleAfterApplySnapshot(
42250
42892
  activeRun.assistantMessageId,
42251
42893
  payload.summary