@orangecatai/adgen-canvas 0.0.14 → 0.0.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/dev/index.js CHANGED
@@ -67,10 +67,10 @@ import {
67
67
  serializeAsJSON,
68
68
  serializeLibraryAsJSON,
69
69
  strokeRectWithRotation_simple
70
- } from "./chunk-W3WRQE6Q.js";
70
+ } from "./chunk-UJP3DIYP.js";
71
71
  import {
72
72
  define_import_meta_env_default
73
- } from "./chunk-3OPVOEOA.js";
73
+ } from "./chunk-4VEZT6GV.js";
74
74
  import {
75
75
  en_default
76
76
  } from "./chunk-IFMURN5W.js";
@@ -9770,7 +9770,7 @@ var exportCanvas = async (type, elements, appState, files, {
9770
9770
  let blob = canvasToBlob(tempCanvas);
9771
9771
  if (appState.exportEmbedScene) {
9772
9772
  blob = blob.then(
9773
- (blob2) => import("./data/image-NKXZX2BO.js").then(
9773
+ (blob2) => import("./data/image-PZBTJSVV.js").then(
9774
9774
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9775
9775
  blob: blob2,
9776
9776
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -22300,7 +22300,13 @@ function notify2() {
22300
22300
  }
22301
22301
  var templateBuilderStore = {
22302
22302
  open(sourceFrameId) {
22303
- state2 = { ...state2, isOpen: true, templateName: "", campaignTag: "", sourceFrameId: sourceFrameId ?? null };
22303
+ state2 = {
22304
+ ...state2,
22305
+ isOpen: true,
22306
+ templateName: "",
22307
+ campaignTag: "",
22308
+ sourceFrameId: sourceFrameId ?? null
22309
+ };
22304
22310
  notify2();
22305
22311
  },
22306
22312
  close() {
@@ -22493,10 +22499,54 @@ var CheckIcon = () => /* @__PURE__ */ jsx83(
22493
22499
  }
22494
22500
  );
22495
22501
  var TemplateIcon = () => /* @__PURE__ */ jsxs45("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", children: [
22496
- /* @__PURE__ */ jsx83("rect", { x: "1", y: "1", width: "5", height: "5", rx: "1", stroke: "currentColor", strokeWidth: "1.4" }),
22497
- /* @__PURE__ */ jsx83("rect", { x: "8", y: "1", width: "5", height: "5", rx: "1", stroke: "currentColor", strokeWidth: "1.4" }),
22498
- /* @__PURE__ */ jsx83("rect", { x: "1", y: "8", width: "5", height: "5", rx: "1", stroke: "currentColor", strokeWidth: "1.4" }),
22499
- /* @__PURE__ */ jsx83("rect", { x: "8", y: "8", width: "5", height: "5", rx: "1", stroke: "currentColor", strokeWidth: "1.4" })
22502
+ /* @__PURE__ */ jsx83(
22503
+ "rect",
22504
+ {
22505
+ x: "1",
22506
+ y: "1",
22507
+ width: "5",
22508
+ height: "5",
22509
+ rx: "1",
22510
+ stroke: "currentColor",
22511
+ strokeWidth: "1.4"
22512
+ }
22513
+ ),
22514
+ /* @__PURE__ */ jsx83(
22515
+ "rect",
22516
+ {
22517
+ x: "8",
22518
+ y: "1",
22519
+ width: "5",
22520
+ height: "5",
22521
+ rx: "1",
22522
+ stroke: "currentColor",
22523
+ strokeWidth: "1.4"
22524
+ }
22525
+ ),
22526
+ /* @__PURE__ */ jsx83(
22527
+ "rect",
22528
+ {
22529
+ x: "1",
22530
+ y: "8",
22531
+ width: "5",
22532
+ height: "5",
22533
+ rx: "1",
22534
+ stroke: "currentColor",
22535
+ strokeWidth: "1.4"
22536
+ }
22537
+ ),
22538
+ /* @__PURE__ */ jsx83(
22539
+ "rect",
22540
+ {
22541
+ x: "8",
22542
+ y: "8",
22543
+ width: "5",
22544
+ height: "5",
22545
+ rx: "1",
22546
+ stroke: "currentColor",
22547
+ strokeWidth: "1.4"
22548
+ }
22549
+ )
22500
22550
  ] });
22501
22551
  var AutoResizeIcon = () => /* @__PURE__ */ jsxs45("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
22502
22552
  /* @__PURE__ */ jsx83(
@@ -22549,7 +22599,9 @@ var FrameToolbar = ({
22549
22599
  const [localWidth, setLocalWidth] = useState27(Math.round(element.width));
22550
22600
  const [localHeight, setLocalHeight] = useState27(Math.round(element.height));
22551
22601
  const [panelState, setPanelState] = useState27(autoResizePanelStore.getState());
22552
- const [builderState, setBuilderState] = useState27(templateBuilderStore.getState());
22602
+ const [builderState, setBuilderState] = useState27(
22603
+ templateBuilderStore.getState()
22604
+ );
22553
22605
  const dropdownRef = useRef24(null);
22554
22606
  useEffect29(() => {
22555
22607
  return autoResizePanelStore.subscribe(setPanelState);
@@ -22882,6 +22934,12 @@ import { useEffect as useEffect33, useRef as useRef28, useState as useState31 }
22882
22934
  import { sceneCoordsToViewportCoords as sceneCoordsToViewportCoords7 } from "@orangecatai/common";
22883
22935
  import { getElementAbsoluteCoords as getElementAbsoluteCoords10 } from "@orangecatai/element";
22884
22936
 
22937
+ // utils/openRouterApiKey.ts
22938
+ function resolveOpenRouterApiKey(propKey) {
22939
+ const normalizedPropKey = propKey?.trim();
22940
+ return (normalizedPropKey ? normalizedPropKey : void 0) ?? (typeof import.meta !== "undefined" && define_import_meta_env_default?.VITE_APP_OPENROUTER_API_KEY ? define_import_meta_env_default.VITE_APP_OPENROUTER_API_KEY : "") ?? "";
22941
+ }
22942
+
22885
22943
  // components/auto-resize/AutoResizePanel.tsx
22886
22944
  import { useCallback as useCallback16, useEffect as useEffect32, useRef as useRef27, useState as useState30 } from "react";
22887
22945
 
@@ -24674,11 +24732,19 @@ function extractFrameInfo(elements, frame, files) {
24674
24732
  if (el.type === "image") {
24675
24733
  const fileData = files[el.fileId];
24676
24734
  if (fileData?.dataURL) {
24677
- background = { type: "image", dataUrl: fileData.dataURL, elementId: el.id };
24735
+ background = {
24736
+ type: "image",
24737
+ dataUrl: fileData.dataURL,
24738
+ elementId: el.id
24739
+ };
24678
24740
  continue;
24679
24741
  }
24680
24742
  } else if (el.type === "rectangle" && el.backgroundColor && el.backgroundColor !== "transparent") {
24681
- background = { type: "solid", color: el.backgroundColor, elementId: el.id };
24743
+ background = {
24744
+ type: "solid",
24745
+ color: el.backgroundColor,
24746
+ elementId: el.id
24747
+ };
24682
24748
  continue;
24683
24749
  }
24684
24750
  }
@@ -24797,8 +24863,9 @@ function containImageInBbox(imgW, imgH, bboxW, bboxH) {
24797
24863
  async function captureFrameScreenshotFromApp(app, frameId) {
24798
24864
  const elements = app.getSceneElements();
24799
24865
  const frame = elements.find((el) => el.id === frameId);
24800
- if (!frame)
24866
+ if (!frame) {
24801
24867
  return null;
24868
+ }
24802
24869
  const children = getFrameChildren4(elements, frameId);
24803
24870
  try {
24804
24871
  const canvas = await exportToCanvas(
@@ -24910,8 +24977,9 @@ async function tagImageElementsWithVision(info, screenshotDataUrl, openRouterApi
24910
24977
  const imageEls = info.foreground.filter(
24911
24978
  (el) => el.kind === "image" && !el.slotType
24912
24979
  );
24913
- if (imageEls.length === 0)
24980
+ if (imageEls.length === 0) {
24914
24981
  return;
24982
+ }
24915
24983
  const srcW = info.frame.width;
24916
24984
  const srcH = info.frame.height;
24917
24985
  const elementList = imageEls.map((el, i) => {
@@ -24960,8 +25028,9 @@ Return ONLY a JSON object mapping the element number (string key) to its role. E
24960
25028
  })
24961
25029
  }
24962
25030
  );
24963
- if (!response.ok)
25031
+ if (!response.ok) {
24964
25032
  return;
25033
+ }
24965
25034
  const data = await response.json();
24966
25035
  const raw = data?.choices?.[0]?.message?.content ?? "";
24967
25036
  const parsed = JSON.parse(raw);
@@ -25325,13 +25394,15 @@ async function runAutoResizeForDimension(opts) {
25325
25394
  newEls.push({ ...el, frameId });
25326
25395
  } else if (pel.kind === "image") {
25327
25396
  const fileId2 = nanoid();
25328
- const { width: natW, height: natH } = await getImageNaturalDimensions(pel.dataUrl);
25329
- const { relX, relY, width: imgW, height: imgH } = containImageInBbox(
25330
- natW,
25331
- natH,
25332
- pel.width,
25333
- pel.height
25397
+ const { width: natW, height: natH } = await getImageNaturalDimensions(
25398
+ pel.dataUrl
25334
25399
  );
25400
+ const {
25401
+ relX,
25402
+ relY,
25403
+ width: imgW,
25404
+ height: imgH
25405
+ } = containImageInBbox(natW, natH, pel.width, pel.height);
25335
25406
  const el = newImageElement({
25336
25407
  type: "image",
25337
25408
  x: pel.x + relX,
@@ -25401,7 +25472,9 @@ async function runAutoResizeForDimension(opts) {
25401
25472
  }
25402
25473
  const screenshot = await captureFrameScreenshotFromApp(app, frameId);
25403
25474
  if (!screenshot) {
25404
- debug(`[AutoResize] ${debugLabel} \u2014 screenshot unavailable, skipping reviewer`);
25475
+ debug(
25476
+ `[AutoResize] ${debugLabel} \u2014 screenshot unavailable, skipping reviewer`
25477
+ );
25405
25478
  break;
25406
25479
  }
25407
25480
  onProgress?.("Reviewing layout\u2026");
@@ -25410,7 +25483,9 @@ async function runAutoResizeForDimension(opts) {
25410
25483
  screenshotDataUrl: screenshot,
25411
25484
  htmlSource: html,
25412
25485
  frame: { id: frameId, width: tgtW, height: tgtH },
25413
- userBrief: `Auto-resize ad from ${Math.round(srcFrame.width)}\xD7${Math.round(srcFrame.height)} to ${tgtW}\xD7${tgtH}`,
25486
+ userBrief: `Auto-resize ad from ${Math.round(
25487
+ srcFrame.width
25488
+ )}\xD7${Math.round(srcFrame.height)} to ${tgtW}\xD7${tgtH}`,
25414
25489
  apiKey: openRouterApiKey,
25415
25490
  reviewerModel: chatModel,
25416
25491
  signal,
@@ -25418,7 +25493,9 @@ async function runAutoResizeForDimension(opts) {
25418
25493
  });
25419
25494
  reviewRounds++;
25420
25495
  if (feedback.approved) {
25421
- debug(`[AutoResize] ${debugLabel} \u2014 reviewer approved (round ${reviewRounds})`);
25496
+ debug(
25497
+ `[AutoResize] ${debugLabel} \u2014 reviewer approved (round ${reviewRounds})`
25498
+ );
25422
25499
  break;
25423
25500
  }
25424
25501
  debug(
@@ -25494,7 +25571,10 @@ async function runAutoResize(opts) {
25494
25571
  }));
25495
25572
  }
25496
25573
  const info = extractFrameInfo(elements, sourceFrame, app.files);
25497
- const sourceScreenshot = await captureFrameScreenshotFromApp(app, sourceFrameId);
25574
+ const sourceScreenshot = await captureFrameScreenshotFromApp(
25575
+ app,
25576
+ sourceFrameId
25577
+ );
25498
25578
  if (sourceScreenshot) {
25499
25579
  await tagImageElementsWithVision(
25500
25580
  info,
@@ -25590,16 +25670,95 @@ var PRESETS = [
25590
25670
  { label: "160\xD7600", width: 160, height: 600, desc: "Wide Skyscraper" }
25591
25671
  ];
25592
25672
  var ResizeIcon = () => /* @__PURE__ */ jsxs48("svg", { width: "14", height: "14", viewBox: "0 0 16 16", fill: "none", children: [
25593
- /* @__PURE__ */ jsx89("rect", { x: "1.5", y: "1.5", width: "7", height: "7", rx: "1", stroke: "currentColor", strokeWidth: "1.4" }),
25594
- /* @__PURE__ */ jsx89("rect", { x: "7.5", y: "7.5", width: "7", height: "7", rx: "1", stroke: "currentColor", strokeWidth: "1.4", strokeDasharray: "2 1", opacity: "0.6" }),
25595
- /* @__PURE__ */ jsx89("path", { d: "M9.5 6.5L13.5 6.5M13.5 6.5L13.5 2.5M13.5 2.5L9.5 2.5", stroke: "currentColor", strokeWidth: "1.3", strokeLinecap: "round", strokeLinejoin: "round" })
25673
+ /* @__PURE__ */ jsx89(
25674
+ "rect",
25675
+ {
25676
+ x: "1.5",
25677
+ y: "1.5",
25678
+ width: "7",
25679
+ height: "7",
25680
+ rx: "1",
25681
+ stroke: "currentColor",
25682
+ strokeWidth: "1.4"
25683
+ }
25684
+ ),
25685
+ /* @__PURE__ */ jsx89(
25686
+ "rect",
25687
+ {
25688
+ x: "7.5",
25689
+ y: "7.5",
25690
+ width: "7",
25691
+ height: "7",
25692
+ rx: "1",
25693
+ stroke: "currentColor",
25694
+ strokeWidth: "1.4",
25695
+ strokeDasharray: "2 1",
25696
+ opacity: "0.6"
25697
+ }
25698
+ ),
25699
+ /* @__PURE__ */ jsx89(
25700
+ "path",
25701
+ {
25702
+ d: "M9.5 6.5L13.5 6.5M13.5 6.5L13.5 2.5M13.5 2.5L9.5 2.5",
25703
+ stroke: "currentColor",
25704
+ strokeWidth: "1.3",
25705
+ strokeLinecap: "round",
25706
+ strokeLinejoin: "round"
25707
+ }
25708
+ )
25596
25709
  ] });
25597
- var CloseIcon2 = () => /* @__PURE__ */ jsx89("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx89("path", { d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) });
25598
- var CheckIcon3 = () => /* @__PURE__ */ jsx89("svg", { width: "11", height: "11", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx89("path", { d: "M2 6.5L4.5 9L10 3", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
25599
- var SpinnerIcon2 = () => /* @__PURE__ */ jsx89("svg", { className: "arp-spinner", width: "13", height: "13", viewBox: "0 0 14 14", fill: "none", children: /* @__PURE__ */ jsx89("circle", { cx: "7", cy: "7", r: "5.5", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeDasharray: "20 14" }) });
25710
+ var CloseIcon2 = () => /* @__PURE__ */ jsx89("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx89(
25711
+ "path",
25712
+ {
25713
+ d: "M1.5 1.5L10.5 10.5M10.5 1.5L1.5 10.5",
25714
+ stroke: "currentColor",
25715
+ strokeWidth: "1.5",
25716
+ strokeLinecap: "round"
25717
+ }
25718
+ ) });
25719
+ var CheckIcon3 = () => /* @__PURE__ */ jsx89("svg", { width: "11", height: "11", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx89(
25720
+ "path",
25721
+ {
25722
+ d: "M2 6.5L4.5 9L10 3",
25723
+ stroke: "currentColor",
25724
+ strokeWidth: "1.6",
25725
+ strokeLinecap: "round",
25726
+ strokeLinejoin: "round"
25727
+ }
25728
+ ) });
25729
+ var SpinnerIcon2 = () => /* @__PURE__ */ jsx89(
25730
+ "svg",
25731
+ {
25732
+ className: "arp-spinner",
25733
+ width: "13",
25734
+ height: "13",
25735
+ viewBox: "0 0 14 14",
25736
+ fill: "none",
25737
+ children: /* @__PURE__ */ jsx89(
25738
+ "circle",
25739
+ {
25740
+ cx: "7",
25741
+ cy: "7",
25742
+ r: "5.5",
25743
+ stroke: "currentColor",
25744
+ strokeWidth: "1.5",
25745
+ strokeLinecap: "round",
25746
+ strokeDasharray: "20 14"
25747
+ }
25748
+ )
25749
+ }
25750
+ );
25600
25751
  var ErrorIcon = () => /* @__PURE__ */ jsxs48("svg", { width: "11", height: "11", viewBox: "0 0 12 12", fill: "none", children: [
25601
25752
  /* @__PURE__ */ jsx89("circle", { cx: "6", cy: "6", r: "4.5", stroke: "currentColor", strokeWidth: "1.3" }),
25602
- /* @__PURE__ */ jsx89("path", { d: "M6 3.5V6.5", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round" }),
25753
+ /* @__PURE__ */ jsx89(
25754
+ "path",
25755
+ {
25756
+ d: "M6 3.5V6.5",
25757
+ stroke: "currentColor",
25758
+ strokeWidth: "1.4",
25759
+ strokeLinecap: "round"
25760
+ }
25761
+ ),
25603
25762
  /* @__PURE__ */ jsx89("circle", { cx: "6", cy: "8.5", r: "0.65", fill: "currentColor" })
25604
25763
  ] });
25605
25764
  var CustomDimInput = ({ onAdd }) => {
@@ -25615,10 +25774,41 @@ var CustomDimInput = ({ onAdd }) => {
25615
25774
  }
25616
25775
  };
25617
25776
  return /* @__PURE__ */ jsxs48("div", { className: "arp-custom-row", children: [
25618
- /* @__PURE__ */ jsx89("input", { className: "arp-custom-input", type: "number", placeholder: "W", value: w, min: 1, onChange: (e) => setW(e.target.value), onPointerDown: (e) => e.stopPropagation() }),
25777
+ /* @__PURE__ */ jsx89(
25778
+ "input",
25779
+ {
25780
+ className: "arp-custom-input",
25781
+ type: "number",
25782
+ placeholder: "W",
25783
+ value: w,
25784
+ min: 1,
25785
+ onChange: (e) => setW(e.target.value),
25786
+ onPointerDown: (e) => e.stopPropagation()
25787
+ }
25788
+ ),
25619
25789
  /* @__PURE__ */ jsx89("span", { className: "arp-custom-x", children: "\xD7" }),
25620
- /* @__PURE__ */ jsx89("input", { className: "arp-custom-input", type: "number", placeholder: "H", value: h, min: 1, onChange: (e) => setH(e.target.value), onPointerDown: (e) => e.stopPropagation() }),
25621
- /* @__PURE__ */ jsx89("button", { className: "arp-custom-add", onClick: handleAdd, disabled: !w || !h, title: "Add custom dimension", children: "+ Add" })
25790
+ /* @__PURE__ */ jsx89(
25791
+ "input",
25792
+ {
25793
+ className: "arp-custom-input",
25794
+ type: "number",
25795
+ placeholder: "H",
25796
+ value: h,
25797
+ min: 1,
25798
+ onChange: (e) => setH(e.target.value),
25799
+ onPointerDown: (e) => e.stopPropagation()
25800
+ }
25801
+ ),
25802
+ /* @__PURE__ */ jsx89(
25803
+ "button",
25804
+ {
25805
+ className: "arp-custom-add",
25806
+ onClick: handleAdd,
25807
+ disabled: !w || !h,
25808
+ title: "Add custom dimension",
25809
+ children: "+ Add"
25810
+ }
25811
+ )
25622
25812
  ] });
25623
25813
  };
25624
25814
  var AutoResizePanel = ({
@@ -25649,17 +25839,19 @@ var AutoResizePanel = ({
25649
25839
  const togglePreset = (label) => {
25650
25840
  setSelected((prev) => {
25651
25841
  const next = new Set(prev);
25652
- if (next.has(label))
25842
+ if (next.has(label)) {
25653
25843
  next.delete(label);
25654
- else
25844
+ } else {
25655
25845
  next.add(label);
25846
+ }
25656
25847
  return next;
25657
25848
  });
25658
25849
  };
25659
25850
  const addCustom = (dim) => {
25660
25851
  const key = dim.label;
25661
- if (selected.has(key))
25852
+ if (selected.has(key)) {
25662
25853
  return;
25854
+ }
25663
25855
  setExtras((prev) => [...prev, dim]);
25664
25856
  setSelected((prev) => /* @__PURE__ */ new Set([...prev, key]));
25665
25857
  };
@@ -25671,9 +25863,15 @@ var AutoResizePanel = ({
25671
25863
  return next;
25672
25864
  });
25673
25865
  };
25674
- const updateDimState = useCallback16((label, update) => {
25675
- setDimStates((prev) => ({ ...prev, [label]: { ...prev[label], ...update } }));
25676
- }, []);
25866
+ const updateDimState = useCallback16(
25867
+ (label, update) => {
25868
+ setDimStates((prev) => ({
25869
+ ...prev,
25870
+ [label]: { ...prev[label], ...update }
25871
+ }));
25872
+ },
25873
+ []
25874
+ );
25677
25875
  const cleanupShimmer = useCallback16(() => {
25678
25876
  autoResizeStore.clear();
25679
25877
  }, []);
@@ -25684,8 +25882,9 @@ var AutoResizePanel = ({
25684
25882
  };
25685
25883
  }, []);
25686
25884
  const handleGenerate = useCallback16(async () => {
25687
- if (allDimensions.length === 0)
25885
+ if (allDimensions.length === 0) {
25688
25886
  return;
25887
+ }
25689
25888
  setRunning(true);
25690
25889
  onRunningChange?.(true);
25691
25890
  setGlobalError(null);
@@ -25734,12 +25933,14 @@ var AutoResizePanel = ({
25734
25933
  const dimLabel = allDimensions[idx]?.label ?? label;
25735
25934
  if (phase === "done") {
25736
25935
  updateDimState(dimLabel, { status: "done" });
25737
- if (preCreatedFrames[idx])
25936
+ if (preCreatedFrames[idx]) {
25738
25937
  autoResizeStore.markComplete(preCreatedFrames[idx].frameId);
25938
+ }
25739
25939
  } else if (phase === "error") {
25740
25940
  updateDimState(dimLabel, { status: "error", error: label });
25741
- if (preCreatedFrames[idx])
25941
+ if (preCreatedFrames[idx]) {
25742
25942
  autoResizeStore.markComplete(preCreatedFrames[idx].frameId);
25943
+ }
25743
25944
  } else if (phase === "bg-regen") {
25744
25945
  updateDimState(dimLabel, { status: "bg-regen" });
25745
25946
  } else {
@@ -25751,7 +25952,9 @@ var AutoResizePanel = ({
25751
25952
  } catch (err) {
25752
25953
  if (err instanceof DOMException && err.name === "AbortError" || controller.signal.aborted) {
25753
25954
  } else {
25754
- setGlobalError(err instanceof Error ? err.message : "An unexpected error occurred");
25955
+ setGlobalError(
25956
+ err instanceof Error ? err.message : "An unexpected error occurred"
25957
+ );
25755
25958
  }
25756
25959
  } finally {
25757
25960
  setRunning(false);
@@ -25777,7 +25980,9 @@ var AutoResizePanel = ({
25777
25980
  onRunningChange?.(false);
25778
25981
  cleanupShimmer();
25779
25982
  };
25780
- const doneCount = Object.values(dimStates).filter((s) => s.status === "done").length;
25983
+ const doneCount = Object.values(dimStates).filter(
25984
+ (s) => s.status === "done"
25985
+ ).length;
25781
25986
  const totalCount = allDimensions.length;
25782
25987
  const hasResults = doneCount > 0;
25783
25988
  return /* @__PURE__ */ jsx89(Fragment13, { children: /* @__PURE__ */ jsxs48(
@@ -25798,7 +26003,9 @@ var AutoResizePanel = ({
25798
26003
  className: "arp-header__close",
25799
26004
  onClick: () => {
25800
26005
  if (running) {
25801
- if (window.confirm("This will cancel the generation. Are you sure?")) {
26006
+ if (window.confirm(
26007
+ "This will cancel the generation. Are you sure?"
26008
+ )) {
25802
26009
  handleCancel();
25803
26010
  onClose();
25804
26011
  }
@@ -25835,7 +26042,14 @@ var AutoResizePanel = ({
25835
26042
  state3.status === "waiting" && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--waiting", children: "\u2026" }),
25836
26043
  (state3.status === "bg-regen" || state3.status === "layout" || state3.status === "inserting") && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--running", children: /* @__PURE__ */ jsx89(SpinnerIcon2, {}) }),
25837
26044
  state3.status === "done" && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--done", children: /* @__PURE__ */ jsx89(CheckIcon3, {}) }),
25838
- state3.status === "error" && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--error", title: state3.error, children: /* @__PURE__ */ jsx89(ErrorIcon, {}) })
26045
+ state3.status === "error" && /* @__PURE__ */ jsx89(
26046
+ "span",
26047
+ {
26048
+ className: "arp-status arp-status--error",
26049
+ title: state3.error,
26050
+ children: /* @__PURE__ */ jsx89(ErrorIcon, {})
26051
+ }
26052
+ )
25839
26053
  ] })
25840
26054
  ]
25841
26055
  },
@@ -25847,9 +26061,23 @@ var AutoResizePanel = ({
25847
26061
  return /* @__PURE__ */ jsxs48("div", { className: "arp-extra-tag", children: [
25848
26062
  /* @__PURE__ */ jsx89("span", { children: d.label }),
25849
26063
  state3?.status === "done" && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--done", children: /* @__PURE__ */ jsx89(CheckIcon3, {}) }),
25850
- state3?.status === "error" && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--error", title: state3.error, children: /* @__PURE__ */ jsx89(ErrorIcon, {}) }),
26064
+ state3?.status === "error" && /* @__PURE__ */ jsx89(
26065
+ "span",
26066
+ {
26067
+ className: "arp-status arp-status--error",
26068
+ title: state3.error,
26069
+ children: /* @__PURE__ */ jsx89(ErrorIcon, {})
26070
+ }
26071
+ ),
25851
26072
  (state3?.status === "bg-regen" || state3?.status === "layout") && /* @__PURE__ */ jsx89("span", { className: "arp-status arp-status--running", children: /* @__PURE__ */ jsx89(SpinnerIcon2, {}) }),
25852
- !running && /* @__PURE__ */ jsx89("button", { className: "arp-extra-tag__remove", onClick: () => removeExtra(d.label), children: "\xD7" })
26073
+ !running && /* @__PURE__ */ jsx89(
26074
+ "button",
26075
+ {
26076
+ className: "arp-extra-tag__remove",
26077
+ onClick: () => removeExtra(d.label),
26078
+ children: "\xD7"
26079
+ }
26080
+ )
25853
26081
  ] }, d.label);
25854
26082
  }) }),
25855
26083
  !running && /* @__PURE__ */ jsxs48(Fragment13, { children: [
@@ -25858,7 +26086,13 @@ var AutoResizePanel = ({
25858
26086
  ] }),
25859
26087
  globalError && /* @__PURE__ */ jsx89("div", { className: "arp-error", children: globalError }),
25860
26088
  running && totalCount > 0 && /* @__PURE__ */ jsxs48("div", { className: "arp-progress", children: [
25861
- /* @__PURE__ */ jsx89("div", { className: "arp-progress__bar-bg", children: /* @__PURE__ */ jsx89("div", { className: "arp-progress__bar-fill", style: { width: `${doneCount / totalCount * 100}%` } }) }),
26089
+ /* @__PURE__ */ jsx89("div", { className: "arp-progress__bar-bg", children: /* @__PURE__ */ jsx89(
26090
+ "div",
26091
+ {
26092
+ className: "arp-progress__bar-fill",
26093
+ style: { width: `${doneCount / totalCount * 100}%` }
26094
+ }
26095
+ ) }),
25862
26096
  /* @__PURE__ */ jsxs48("span", { className: "arp-progress__text", children: [
25863
26097
  doneCount,
25864
26098
  "/",
@@ -25867,20 +26101,30 @@ var AutoResizePanel = ({
25867
26101
  ] })
25868
26102
  ] }),
25869
26103
  /* @__PURE__ */ jsx89("div", { className: "arp-actions", children: running ? /* @__PURE__ */ jsx89("button", { className: "arp-btn arp-btn--cancel", onClick: handleCancel, children: "Cancel" }) : hasResults ? /* @__PURE__ */ jsxs48(Fragment13, { children: [
25870
- /* @__PURE__ */ jsx89("button", { className: "arp-btn arp-btn--primary", onClick: handleGenerate, disabled: allDimensions.length === 0, children: "Generate more" }),
26104
+ /* @__PURE__ */ jsx89(
26105
+ "button",
26106
+ {
26107
+ className: "arp-btn arp-btn--primary",
26108
+ onClick: handleGenerate,
26109
+ disabled: allDimensions.length === 0,
26110
+ children: "Generate more"
26111
+ }
26112
+ ),
25871
26113
  /* @__PURE__ */ jsx89("button", { className: "arp-btn arp-btn--ghost", onClick: onClose, children: "Done" })
25872
- ] }) : /* @__PURE__ */ jsx89("button", { className: "arp-btn arp-btn--primary", onClick: handleGenerate, disabled: allDimensions.length === 0, children: allDimensions.length === 0 ? "Select sizes above" : `Generate ${allDimensions.length} size${allDimensions.length !== 1 ? "s" : ""}` }) })
26114
+ ] }) : /* @__PURE__ */ jsx89(
26115
+ "button",
26116
+ {
26117
+ className: "arp-btn arp-btn--primary",
26118
+ onClick: handleGenerate,
26119
+ disabled: allDimensions.length === 0,
26120
+ children: allDimensions.length === 0 ? "Select sizes above" : `Generate ${allDimensions.length} size${allDimensions.length !== 1 ? "s" : ""}`
26121
+ }
26122
+ ) })
25873
26123
  ]
25874
26124
  }
25875
26125
  ) });
25876
26126
  };
25877
26127
 
25878
- // utils/openRouterApiKey.ts
25879
- function resolveOpenRouterApiKey(propKey) {
25880
- const normalizedPropKey = propKey?.trim();
25881
- return (normalizedPropKey ? normalizedPropKey : void 0) ?? (typeof import.meta !== "undefined" && define_import_meta_env_default?.VITE_APP_OPENROUTER_API_KEY ? define_import_meta_env_default.VITE_APP_OPENROUTER_API_KEY : "") ?? "";
25882
- }
25883
-
25884
26128
  // components/auto-resize/AutoResizePanelHost.tsx
25885
26129
  import { jsx as jsx90 } from "react/jsx-runtime";
25886
26130
  var AutoResizePanelHost = ({ app }) => {
@@ -25970,12 +26214,22 @@ import { arrayToMap as arrayToMap23 } from "@orangecatai/common";
25970
26214
  var SLOT_TYPES = [
25971
26215
  { id: "background", label: "Background", color: "#94a3b8", kind: "rect" },
25972
26216
  { id: "logo", label: "Logo", color: "#f97316", kind: "image" },
25973
- { id: "product_image", label: "Product Image", color: "#22c55e", kind: "image" },
26217
+ {
26218
+ id: "product_image",
26219
+ label: "Product Image",
26220
+ color: "#22c55e",
26221
+ kind: "image"
26222
+ },
25974
26223
  { id: "headline", label: "Headline", color: "#3b82f6", kind: "text" },
25975
26224
  { id: "subhead", label: "Subhead", color: "#8b5cf6", kind: "text" },
25976
26225
  { id: "cta", label: "CTA", color: "#ef4444", kind: "text" },
25977
26226
  { id: "signoff", label: "Signoff", color: "#64748b", kind: "text" },
25978
- { id: "auxiliary_image", label: "Aux Image", color: "#14b8a6", kind: "image" }
26227
+ {
26228
+ id: "auxiliary_image",
26229
+ label: "Aux Image",
26230
+ color: "#14b8a6",
26231
+ kind: "image"
26232
+ }
25979
26233
  ];
25980
26234
 
25981
26235
  // components/template-builder/TemplateBuilderTray.tsx
@@ -26004,7 +26258,11 @@ var TemplateBuilderTray = ({ app }) => {
26004
26258
  strokeColor: "transparent",
26005
26259
  opacity: 60
26006
26260
  });
26007
- newEl = { ...base, frameId: frame.id, customData: { slotType: slot.id, isSlot: true } };
26261
+ newEl = {
26262
+ ...base,
26263
+ frameId: frame.id,
26264
+ customData: { slotType: slot.id, isSlot: true }
26265
+ };
26008
26266
  } else if (slot.kind === "text") {
26009
26267
  const base = newTextElement3({
26010
26268
  text: slot.label,
@@ -26013,7 +26271,11 @@ var TemplateBuilderTray = ({ app }) => {
26013
26271
  fontSize: 32,
26014
26272
  strokeColor: slot.color
26015
26273
  });
26016
- newEl = { ...base, frameId: frame.id, customData: { slotType: slot.id, isSlot: true } };
26274
+ newEl = {
26275
+ ...base,
26276
+ frameId: frame.id,
26277
+ customData: { slotType: slot.id, isSlot: true }
26278
+ };
26017
26279
  } else {
26018
26280
  const base = newElement5({
26019
26281
  type: "rectangle",
@@ -26026,7 +26288,11 @@ var TemplateBuilderTray = ({ app }) => {
26026
26288
  strokeColor: slot.color,
26027
26289
  opacity: 40
26028
26290
  });
26029
- newEl = { ...base, frameId: frame.id, customData: { slotType: slot.id, isSlot: true } };
26291
+ newEl = {
26292
+ ...base,
26293
+ frameId: frame.id,
26294
+ customData: { slotType: slot.id, isSlot: true }
26295
+ };
26030
26296
  }
26031
26297
  const elements = app.getSceneElements();
26032
26298
  const newElements = [...elements, newEl];
@@ -26132,25 +26398,48 @@ var TemplateBuilderPanelHost = ({
26132
26398
  }
26133
26399
  if (framesToSave.length === 0) {
26134
26400
  framesToSave = allElements.filter((el) => {
26135
- if (el.isDeleted || !isFrameLikeElement11(el))
26401
+ if (el.isDeleted || !isFrameLikeElement11(el)) {
26136
26402
  return false;
26403
+ }
26137
26404
  const children = getFrameChildren5(allElements, el.id);
26138
- return children.some((child) => child.customData?.isSlot === true);
26405
+ return children.some(
26406
+ (child) => child.customData?.isSlot === true
26407
+ );
26139
26408
  });
26140
26409
  }
26141
26410
  if (framesToSave.length === 0) {
26142
- alert("No frame found to save. Please select a frame on the canvas first.");
26411
+ alert(
26412
+ "No frame found to save. Please select a frame on the canvas first."
26413
+ );
26143
26414
  return;
26144
26415
  }
26145
26416
  const frame = framesToSave[0];
26146
26417
  const frameChildren = getFrameChildren5(allElements, frame.id);
26418
+ const files = app.files;
26419
+ const templateFiles = {};
26420
+ for (const el of frameChildren) {
26421
+ const imgEl = el;
26422
+ if (el.type === "image" && imgEl.fileId) {
26423
+ const file2 = files[imgEl.fileId];
26424
+ if (file2?.dataURL) {
26425
+ templateFiles[imgEl.fileId] = {
26426
+ dataURL: file2.dataURL,
26427
+ mimeType: file2.mimeType
26428
+ };
26429
+ }
26430
+ }
26431
+ }
26147
26432
  onSaveTemplate({
26148
26433
  name: panelState.templateName,
26149
26434
  campaignTag: panelState.campaignTag || void 0,
26150
26435
  label: `${Math.round(frame.width)}x${Math.round(frame.height)}`,
26151
26436
  width: Math.round(frame.width),
26152
26437
  height: Math.round(frame.height),
26153
- canvasJson: JSON.stringify({ frame: { ...frame }, elements: frameChildren })
26438
+ canvasJson: JSON.stringify({
26439
+ frame: { ...frame },
26440
+ elements: frameChildren,
26441
+ ...Object.keys(templateFiles).length > 0 ? { files: templateFiles } : {}
26442
+ })
26154
26443
  });
26155
26444
  templateBuilderStore.close();
26156
26445
  };
@@ -51027,8 +51316,15 @@ var BANNER_TOOLS = [
51027
51316
  parameters: {
51028
51317
  type: "object",
51029
51318
  properties: {
51030
- query: { type: "string", description: "Search query \u2014 name or tag keywords" },
51031
- type: { type: "string", enum: ["product", "logo", "lifestyle", "auxiliary"], description: "Optional filter by asset type" }
51319
+ query: {
51320
+ type: "string",
51321
+ description: "Search query \u2014 name or tag keywords"
51322
+ },
51323
+ type: {
51324
+ type: "string",
51325
+ enum: ["product", "logo", "lifestyle", "auxiliary"],
51326
+ description: "Optional filter by asset type"
51327
+ }
51032
51328
  },
51033
51329
  required: ["query"],
51034
51330
  additionalProperties: false
@@ -51039,11 +51335,29 @@ var BANNER_TOOLS = [
51039
51335
  type: "function",
51040
51336
  function: {
51041
51337
  name: "list_brand_templates",
51042
- description: "List available brand templates. Call this when the user mentions a campaign, template, or asks for multiple sizes.",
51338
+ description: `List available brand templates by keyword search across template names, campaign tags, and labels.
51339
+
51340
+ MANDATORY SEARCH PROTOCOL \u2014 follow exactly, no shortcuts:
51341
+
51342
+ ATTEMPT 1: Generate TWO sets of 10 keywords each from the user's request:
51343
+ - Set A (campaign tag variations): 10 synonyms/abbreviations/phrasings of the campaign topic (e.g. for "noida datacenter": ["datacenter", "data center", "dc", "noida dc", "noida datacenter", "data centre", "server farm", "cloud infra", "hyperscale", "colocation"])
51344
+ - Set B (template name variations): 10 different name keywords the template might have (e.g. ["noida", "datacenter campaign", "new datacenter", "dc launch", "infrastructure", "cloud", "enterprise", "b2b", "tech", "facility"])
51345
+ Merge all 20 into the keywords array. Call list_brand_templates with all 20.
51346
+
51347
+ IF templates returned is empty \u2192 ATTEMPT 2 (MANDATORY \u2014 do NOT skip):
51348
+ Generate a completely DIFFERENT set of 20 keywords \u2014 do not reuse any from attempt 1. Think laterally: different synonyms, related industry terms, the city name, brand-adjacent words, seasonal terms. Call list_brand_templates again with the new 20.
51349
+
51350
+ ONLY after both attempts return empty \u2192 tell the user no matching template was found.
51351
+
51352
+ If no search context exists, call with an empty keywords array to list all templates.`,
51043
51353
  parameters: {
51044
51354
  type: "object",
51045
51355
  properties: {
51046
- campaignTag: { type: "string", description: "Optional campaign tag to filter templates" }
51356
+ keywords: {
51357
+ type: "array",
51358
+ items: { type: "string" },
51359
+ description: "Up to 20 keyword variations to search across template names, campaign tags, and labels. All are OR-matched case-insensitively. Generate 10 tag synonyms + 10 name variations from the user's request."
51360
+ }
51047
51361
  },
51048
51362
  required: [],
51049
51363
  additionalProperties: false
@@ -51058,7 +51372,10 @@ var BANNER_TOOLS = [
51058
51372
  parameters: {
51059
51373
  type: "object",
51060
51374
  properties: {
51061
- variantId: { type: "string", description: "The template ID to fetch" }
51375
+ variantId: {
51376
+ type: "string",
51377
+ description: "The template ID to fetch"
51378
+ }
51062
51379
  },
51063
51380
  required: ["variantId"],
51064
51381
  additionalProperties: false
@@ -51073,8 +51390,14 @@ var BANNER_TOOLS = [
51073
51390
  parameters: {
51074
51391
  type: "object",
51075
51392
  properties: {
51076
- frameId: { type: "string", description: "The frame ID to load the template into" },
51077
- variantId: { type: "string", description: "The template ID to load (from list_brand_templates)" }
51393
+ frameId: {
51394
+ type: "string",
51395
+ description: "The frame ID to load the template into"
51396
+ },
51397
+ variantId: {
51398
+ type: "string",
51399
+ description: "The template ID to load (from list_brand_templates)"
51400
+ }
51078
51401
  },
51079
51402
  required: ["frameId", "variantId"],
51080
51403
  additionalProperties: false
@@ -51089,18 +51412,45 @@ var BANNER_TOOLS = [
51089
51412
  parameters: {
51090
51413
  type: "object",
51091
51414
  properties: {
51092
- frameId: { type: "string", description: "The frame ID containing slot elements" },
51093
- headline: { type: "string", description: "Text for the headline slot" },
51415
+ frameId: {
51416
+ type: "string",
51417
+ description: "The frame ID containing slot elements"
51418
+ },
51419
+ headline: {
51420
+ type: "string",
51421
+ description: "Text for the headline slot"
51422
+ },
51094
51423
  subhead: { type: "string", description: "Text for the subhead slot" },
51095
51424
  cta: { type: "string", description: "Text for the CTA slot" },
51096
51425
  signoff: { type: "string", description: "Text for the signoff slot" },
51097
- product_image_url: { type: "string", description: "URL for the product image slot (from search_brand_assets blobUrl, or a generated image data URL)" },
51098
- product_image_asset_id: { type: "string", description: "Asset ID for the product image (from search_brand_assets [id:...] field). Required when using a brand asset so the image can be swapped later." },
51099
- logo_url: { type: "string", description: "URL for the logo slot (from search_brand_assets blobUrl)" },
51100
- logo_asset_id: { type: "string", description: "Asset ID for the logo (from search_brand_assets [id:...] field). Required when using a brand asset." },
51101
- auxiliary_image_url: { type: "string", description: "URL for the auxiliary image slot" },
51102
- auxiliary_image_asset_id: { type: "string", description: "Asset ID for the auxiliary image (from search_brand_assets [id:...] field). Required when using a brand asset." },
51103
- background_color: { type: "string", description: "Hex color (e.g. #000000) for the background slot" }
51426
+ product_image_url: {
51427
+ type: "string",
51428
+ description: "URL for the product image slot (from search_brand_assets blobUrl, or a generated image data URL)"
51429
+ },
51430
+ product_image_asset_id: {
51431
+ type: "string",
51432
+ description: "Asset ID for the product image (from search_brand_assets [id:...] field). Required when using a brand asset so the image can be swapped later."
51433
+ },
51434
+ logo_url: {
51435
+ type: "string",
51436
+ description: "URL for the logo slot (from search_brand_assets blobUrl)"
51437
+ },
51438
+ logo_asset_id: {
51439
+ type: "string",
51440
+ description: "Asset ID for the logo (from search_brand_assets [id:...] field). Required when using a brand asset."
51441
+ },
51442
+ auxiliary_image_url: {
51443
+ type: "string",
51444
+ description: "URL for the auxiliary image slot"
51445
+ },
51446
+ auxiliary_image_asset_id: {
51447
+ type: "string",
51448
+ description: "Asset ID for the auxiliary image (from search_brand_assets [id:...] field). Required when using a brand asset."
51449
+ },
51450
+ background_color: {
51451
+ type: "string",
51452
+ description: "Hex color (e.g. #000000) for the background slot"
51453
+ }
51104
51454
  },
51105
51455
  required: ["frameId"],
51106
51456
  additionalProperties: false
@@ -51432,12 +51782,13 @@ function execAddText(args, ctx) {
51432
51782
  statusMessage: "Failed: missing frameId"
51433
51783
  };
51434
51784
  }
51785
+ const resolvedFontFamily = ctx.brandBodyFontId ?? ctx.brandHeadlineFontId ?? args.fontFamily ?? 2;
51435
51786
  const textEl = newTextElement6({
51436
51787
  x: args.x,
51437
51788
  y: args.y,
51438
51789
  text: args.text,
51439
51790
  fontSize: args.fontSize || 20,
51440
- fontFamily: args.fontFamily || 2,
51791
+ fontFamily: resolvedFontFamily,
51441
51792
  strokeColor: args.strokeColor || "#000000",
51442
51793
  textAlign: args.textAlign || "left",
51443
51794
  width: args.width,
@@ -51910,8 +52261,7 @@ async function execGenerateHtmlBanner(args, ctx) {
51910
52261
  y: Math.round(frameY),
51911
52262
  width: frameW,
51912
52263
  height: frameH,
51913
- elementCount: newEls.length,
51914
- screenshot: screenshot ?? void 0
52264
+ elementCount: newEls.length
51915
52265
  },
51916
52266
  statusMessage: `Banner updated: ${newEls.length} elements in frame "${args.name || "Banner"}" (${frameW}\xD7${frameH})`,
51917
52267
  screenshot: screenshot ?? void 0
@@ -51935,12 +52285,19 @@ async function execSearchBrandAssets(args, ctx) {
51935
52285
  const list = assets.map((a) => `- ${a.name} (${a.assetType}) [id:${a.id}]: ${a.blobUrl}`).join("\n");
51936
52286
  return {
51937
52287
  success: true,
51938
- data: { assets, summary: `Found ${assets.length} brand asset(s):
51939
- ${list}` },
52288
+ data: {
52289
+ assets,
52290
+ summary: `Found ${assets.length} brand asset(s):
52291
+ ${list}`
52292
+ },
51940
52293
  statusMessage: `Found ${assets.length} assets`
51941
52294
  };
51942
52295
  } catch {
51943
- return { success: false, error: "Failed to search brand assets.", statusMessage: "Asset search failed" };
52296
+ return {
52297
+ success: false,
52298
+ error: "Failed to search brand assets.",
52299
+ statusMessage: "Asset search failed"
52300
+ };
51944
52301
  }
51945
52302
  }
51946
52303
  async function execFinalizeAd(args, ctx) {
@@ -51948,20 +52305,29 @@ async function execFinalizeAd(args, ctx) {
51948
52305
  const screenshot = await captureFrameScreenshot(ctx.excalidrawAPI, frameId);
51949
52306
  return {
51950
52307
  success: true,
51951
- data: { frameId, screenshot: screenshot ?? void 0 },
52308
+ data: { frameId },
51952
52309
  statusMessage: "Ad finalized, ready for review",
51953
52310
  screenshot: screenshot ?? void 0
51954
52311
  };
51955
52312
  }
51956
52313
  async function execListBrandTemplates(args, ctx) {
51957
- const campaignTag = args.campaignTag;
52314
+ let keywords;
52315
+ if (Array.isArray(args.keywords) && args.keywords.length > 0) {
52316
+ keywords = args.keywords;
52317
+ } else if (args.query || args.campaignTag) {
52318
+ keywords = [args.query ?? args.campaignTag];
52319
+ }
51958
52320
  if (!ctx.onListBrandTemplates) {
51959
52321
  return { success: true, statusMessage: "No template list configured" };
51960
52322
  }
51961
52323
  try {
51962
- const templates = await ctx.onListBrandTemplates(campaignTag);
52324
+ const templates = await ctx.onListBrandTemplates(keywords);
51963
52325
  if (templates.length === 0) {
51964
- return { success: true, data: { templates: [] }, statusMessage: "No templates found" };
52326
+ return {
52327
+ success: true,
52328
+ data: { templates: [] },
52329
+ statusMessage: "No templates found"
52330
+ };
51965
52331
  }
51966
52332
  const list = templates.map((t2) => {
51967
52333
  const dims = t2.width && t2.height ? ` (${t2.width}x${t2.height})` : "";
@@ -51973,13 +52339,21 @@ async function execListBrandTemplates(args, ctx) {
51973
52339
  statusMessage: `Found ${templates.length} template(s)`
51974
52340
  };
51975
52341
  } catch {
51976
- return { success: false, error: "Failed to list brand templates.", statusMessage: "Template list failed" };
52342
+ return {
52343
+ success: false,
52344
+ error: "Failed to list brand templates.",
52345
+ statusMessage: "Template list failed"
52346
+ };
51977
52347
  }
51978
52348
  }
51979
52349
  async function execGetTemplateVariant(args, ctx) {
51980
52350
  const templateId = args.variantId;
51981
52351
  if (!ctx.onGetTemplateVariant) {
51982
- return { success: false, error: "Template fetching not configured.", statusMessage: "No template fetch configured" };
52352
+ return {
52353
+ success: false,
52354
+ error: "Template fetching not configured.",
52355
+ statusMessage: "No template fetch configured"
52356
+ };
51983
52357
  }
51984
52358
  try {
51985
52359
  const { canvasJson } = await ctx.onGetTemplateVariant(templateId);
@@ -51993,14 +52367,22 @@ async function execGetTemplateVariant(args, ctx) {
51993
52367
  statusMessage: "Template fetched"
51994
52368
  };
51995
52369
  } catch {
51996
- return { success: false, error: "Failed to fetch template.", statusMessage: "Template fetch failed" };
52370
+ return {
52371
+ success: false,
52372
+ error: "Failed to fetch template.",
52373
+ statusMessage: "Template fetch failed"
52374
+ };
51997
52375
  }
51998
52376
  }
51999
52377
  async function execLoadTemplateIntoFrame(args, ctx) {
52000
52378
  const frameId = args.frameId;
52001
52379
  const templateId = args.variantId;
52002
52380
  if (!ctx.onGetTemplateVariant) {
52003
- return { success: false, error: "Template fetching not configured.", statusMessage: "No template fetch configured" };
52381
+ return {
52382
+ success: false,
52383
+ error: "Template fetching not configured.",
52384
+ statusMessage: "No template fetch configured"
52385
+ };
52004
52386
  }
52005
52387
  try {
52006
52388
  const cachedJson = ctx._templateJsonCache?.get(templateId);
@@ -52025,7 +52407,11 @@ async function execLoadTemplateIntoFrame(args, ctx) {
52025
52407
  const allElements = ctx.excalidrawAPI.getSceneElements();
52026
52408
  const targetFrame = allElements.find((el) => el.id === frameId);
52027
52409
  if (!targetFrame) {
52028
- return { success: false, error: `Frame ${frameId} not found.`, statusMessage: "Frame not found" };
52410
+ return {
52411
+ success: false,
52412
+ error: `Frame ${frameId} not found.`,
52413
+ statusMessage: "Frame not found"
52414
+ };
52029
52415
  }
52030
52416
  const withoutOldChildren = allElements.filter(
52031
52417
  (el) => el.frameId !== frameId
@@ -52051,6 +52437,18 @@ async function execLoadTemplateIntoFrame(args, ctx) {
52051
52437
  ];
52052
52438
  const merged = syncMovedIndices7(mergedArr, arrayToMap30(mergedArr));
52053
52439
  ctx.excalidrawAPI.updateScene({ elements: merged });
52440
+ const templateFiles = parsed.files;
52441
+ if (templateFiles && Object.keys(templateFiles).length > 0) {
52442
+ ctx.excalidrawAPI.addFiles(
52443
+ Object.entries(templateFiles).map(([fileId, file2]) => ({
52444
+ id: fileId,
52445
+ dataURL: file2.dataURL,
52446
+ mimeType: file2.mimeType,
52447
+ created: Date.now(),
52448
+ lastRetrieved: Date.now()
52449
+ }))
52450
+ );
52451
+ }
52054
52452
  if (templateId) {
52055
52453
  const frameEl = ctx.excalidrawAPI.getSceneElements().find((el) => el.id === frameId);
52056
52454
  if (frameEl) {
@@ -52062,7 +52460,11 @@ async function execLoadTemplateIntoFrame(args, ctx) {
52062
52460
  });
52063
52461
  }
52064
52462
  }
52065
- return { success: true, data: { frameId }, statusMessage: "Template loaded" };
52463
+ return {
52464
+ success: true,
52465
+ data: { frameId },
52466
+ statusMessage: "Template loaded"
52467
+ };
52066
52468
  } catch (e) {
52067
52469
  return {
52068
52470
  success: false,
@@ -52112,7 +52514,12 @@ function fitFontSize(text, slotEl) {
52112
52514
  }
52113
52515
  }
52114
52516
  if (bestFs !== -1) {
52115
- return { fontSize: bestFs, wrappedText: bestWrapped, height: bestHeight, autoResize: false };
52517
+ return {
52518
+ fontSize: bestFs,
52519
+ wrappedText: bestWrapped,
52520
+ height: bestHeight,
52521
+ autoResize: false
52522
+ };
52116
52523
  }
52117
52524
  const minCheck = testFit(MIN_FONT_SIZE3);
52118
52525
  return {
@@ -52152,8 +52559,9 @@ async function execFillTemplateSlots(args, ctx) {
52152
52559
  const imageJobs = [];
52153
52560
  allElements.forEach((el, index) => {
52154
52561
  const elAny = el;
52155
- if (elAny.frameId !== frameId)
52562
+ if (elAny.frameId !== frameId) {
52156
52563
  return;
52564
+ }
52157
52565
  const slotType = elAny.customData?.slotType;
52158
52566
  const url = slotType ? imageUrlBySlotType[slotType] : void 0;
52159
52567
  if (url) {
@@ -52172,8 +52580,9 @@ async function execFillTemplateSlots(args, ctx) {
52172
52580
  imageJobs.map(async (job) => {
52173
52581
  try {
52174
52582
  const res = await fetch(job.url);
52175
- if (!res.ok)
52583
+ if (!res.ok) {
52176
52584
  return;
52585
+ }
52177
52586
  const blob = await res.blob();
52178
52587
  const dataURL = await new Promise((resolve, reject) => {
52179
52588
  const reader = new FileReader();
@@ -52190,8 +52599,9 @@ async function execFillTemplateSlots(args, ctx) {
52190
52599
  await Promise.all(
52191
52600
  imageJobs.map(
52192
52601
  (job) => new Promise((resolve) => {
52193
- if (!job.dataURL)
52602
+ if (!job.dataURL) {
52194
52603
  return resolve();
52604
+ }
52195
52605
  const img = new Image();
52196
52606
  img.onload = () => {
52197
52607
  job.naturalWidth = img.naturalWidth;
@@ -52205,8 +52615,9 @@ async function execFillTemplateSlots(args, ctx) {
52205
52615
  );
52206
52616
  const imageReplacements = /* @__PURE__ */ new Map();
52207
52617
  for (const job of imageJobs) {
52208
- if (!job.fileId)
52618
+ if (!job.fileId) {
52209
52619
  continue;
52620
+ }
52210
52621
  const { x, y, width, height } = containFit(
52211
52622
  job.el.x,
52212
52623
  job.el.y,
@@ -52244,28 +52655,35 @@ async function execFillTemplateSlots(args, ctx) {
52244
52655
  let filledCount = 0;
52245
52656
  const updatedElements = allElements.map((el, index) => {
52246
52657
  const elWithFrame = el;
52247
- if (elWithFrame.frameId !== frameId)
52658
+ if (elWithFrame.frameId !== frameId) {
52248
52659
  return el;
52660
+ }
52249
52661
  const slotType = elWithFrame.customData?.slotType;
52250
- if (!slotType)
52662
+ if (!slotType) {
52251
52663
  return el;
52664
+ }
52252
52665
  if (imageReplacements.has(index)) {
52253
52666
  filledCount++;
52254
52667
  return imageReplacements.get(index);
52255
52668
  }
52256
52669
  let mutation = {};
52257
- const makeTextMutation = (value) => {
52258
- const { fontSize, wrappedText, height, autoResize } = fitFontSize(value, el);
52670
+ const makeTextMutation = (value, useHeadlineFont = false) => {
52671
+ const { fontSize, wrappedText, height, autoResize } = fitFontSize(
52672
+ value,
52673
+ el
52674
+ );
52675
+ const brandFontId = useHeadlineFont ? ctx.brandHeadlineFontId ?? ctx.brandBodyFontId : ctx.brandBodyFontId;
52259
52676
  return {
52260
52677
  text: wrappedText,
52261
52678
  originalText: value,
52262
52679
  fontSize,
52263
52680
  height,
52264
- autoResize
52681
+ autoResize,
52682
+ ...brandFontId !== void 0 ? { fontFamily: brandFontId } : {}
52265
52683
  };
52266
52684
  };
52267
52685
  if (slotType === "headline" && args.headline) {
52268
- mutation = makeTextMutation(args.headline);
52686
+ mutation = makeTextMutation(args.headline, true);
52269
52687
  } else if (slotType === "subhead" && args.subhead) {
52270
52688
  mutation = makeTextMutation(args.subhead);
52271
52689
  } else if (slotType === "cta" && args.cta) {
@@ -52491,7 +52909,11 @@ When the design needs a full-frame photorealistic background (a scene, landscape
52491
52909
  Use hex (\`#rrggbb\`) or \`rgba(r,g,b,a)\`. No named colors (no \`red\`, \`blue\`). CSS gradients (\`linear-gradient\`, \`radial-gradient\`) are allowed.
52492
52910
 
52493
52911
  ### 7. Fonts
52494
- If the Brand Identity context specifies fonts, use those exact font-family names in your HTML \`<style>\` block (e.g. \`font-family: "Brand Font Name", Arial, sans-serif\`). Otherwise default to web-safe: \`Arial\`, \`"Helvetica Neue"\`, \`Helvetica\`, \`sans-serif\`. Do not use serif fonts unless the brand explicitly requires them.
52912
+ **MANDATORY \u2014 Brand font enforcement:**
52913
+ - If the Brand Identity context specifies fonts with a registered fontFamilyId, you MUST use those fonts everywhere. Do NOT use any other font \u2014 no Arial, no Helvetica, no web-safe fallbacks as primary fonts.
52914
+ - In HTML \`<style>\` blocks: use the exact brand font-family name (e.g. \`font-family: "Brand Font Name", sans-serif\`). The canvas renderer maps this name to the registered font automatically.
52915
+ - For \`add_text\` calls: the system enforces the brand fontFamilyId automatically \u2014 you do not need to pass a fontFamily param.
52916
+ - Only fall back to web-safe fonts (\`Arial\`, \`"Helvetica Neue"\`, \`sans-serif\`) when the Brand Identity context has no font specified at all.
52495
52917
 
52496
52918
  ### 9. No unsupported features
52497
52919
  No \`transform\`, \`animation\`, \`transition\`, \`filter\`, \`clip-path\`, \`<img>\` tags, or JavaScript. No \`box-shadow\`.
@@ -52590,8 +53012,10 @@ ASSET USE MANDATE: If search_brand_assets() returns any results, you MUST incorp
52590
53012
 
52591
53013
  IMAGE GENERATION SCOPE: generate_image is only for supplementary content images (backgrounds, lifestyle photography) where no brand asset matches AND the brief explicitly requires a photo or illustration. For solid-color backgrounds or brand-asset-only ads, skip generate_image entirely.
52592
53014
 
52593
- TEMPLATE BANK: When the user references a campaign, asks to "use a template", or requests multiple ad sizes, use this workflow:
52594
- 1. Call \`list_brand_templates()\` to see available templates
53015
+ TEMPLATE BANK: Whenever the user asks to create, make, or build an ad \u2014 even without mentioning templates \u2014 call \`list_brand_templates()\` as your very first action before asking any clarifying questions. Use the returned catalog to ask smarter, more specific questions (e.g. "I see you have a Noida Campaign template \u2014 should I use that, or build from scratch?"). If multiple templates could match, ask which one to use. If none match or the user prefers no template, proceed with HTML generation. Only skip this tool call for messages that are clearly not ad-creation requests (greetings, questions, edits to existing canvas elements, etc.).
53016
+
53017
+ Template workflow once you know which template to use:
53018
+ 1. Call \`list_brand_templates()\` to see available templates (already done above)
52595
53019
  2. Call \`load_template_into_frame(frameId, variantId)\` to insert the template
52596
53020
  3. Call \`get_frame_elements(frameId)\` to inspect which slot types exist (headline, subhead, cta, background, product_image, logo, etc.)
52597
53021
  4. For each **image slot** found (product_image, logo): call \`search_brand_assets()\` to look up the asset. Pass the returned \`blobUrl\` as \`product_image_url\` or \`logo_url\` to \`fill_template_slots\`. Only call \`generate_image\` if no matching asset exists in the bank.
@@ -52714,7 +53138,7 @@ function buildBrandContextMessage(ctx) {
52714
53138
  const { headline, body } = ctx.typography;
52715
53139
  if (headline?.family) {
52716
53140
  const sizeHint = headline.sizeScale ? ` (suggested size: ${SIZE_SCALE_MAP[headline.sizeScale] ?? headline.sizeScale})` : "";
52717
- const idNote = headline.fontFamilyId ? ` \u2014 custom font registered in canvas; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS` : ` \u2014 no custom font file loaded; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
53141
+ const idNote = headline.fontFamilyId ? ` \u2014 MANDATORY: use \`font-family: "${headline.family}", sans-serif\` in ALL HTML CSS for headlines. Do NOT use any other font.` : ` \u2014 no custom font file loaded; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52718
53142
  const fileNote = headline.fontName ? ` [file: ${headline.fontName}]` : "";
52719
53143
  lines.push(
52720
53144
  `**Headline font**: ${headline.family}${headline.weight ? ` weight ${headline.weight}` : ""}${sizeHint}${idNote}${fileNote}`
@@ -52722,7 +53146,7 @@ function buildBrandContextMessage(ctx) {
52722
53146
  }
52723
53147
  if (body?.family) {
52724
53148
  const sizeHint = body.sizeScale ? ` (suggested size: ${SIZE_SCALE_MAP[body.sizeScale] ?? body.sizeScale})` : "";
52725
- const idNote = body.fontFamilyId ? ` \u2014 custom font registered in canvas; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS` : ` \u2014 no custom font file loaded; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
53149
+ const idNote = body.fontFamilyId ? ` \u2014 MANDATORY: use \`font-family: "${body.family}", sans-serif\` in ALL HTML CSS for body copy, subheads, CTAs, and any other text. Do NOT use any other font.` : ` \u2014 no custom font file loaded; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52726
53150
  const fileNote = body.fontName ? ` [file: ${body.fontName}]` : "";
52727
53151
  lines.push(
52728
53152
  `**Body font**: ${body.family}${body.weight ? ` weight ${body.weight}` : ""}${sizeHint}${idNote}${fileNote}`
@@ -52826,7 +53250,9 @@ async function runAgentLoop(opts) {
52826
53250
  ...Object.keys(customFontMap).length ? { customFontMap } : {},
52827
53251
  ...allImageAttachments.length ? { fileAttachments: allImageAttachments } : {},
52828
53252
  ...brandContext?.typography?.headline?.fontName ? { brandHeadlineFontName: brandContext.typography.headline.fontName } : {},
52829
- ...brandContext?.typography?.body?.fontName ? { brandBodyFontName: brandContext.typography.body.fontName } : {}
53253
+ ...brandContext?.typography?.body?.fontName ? { brandBodyFontName: brandContext.typography.body.fontName } : {},
53254
+ ...brandContext?.typography?.headline?.fontFamilyId ? { brandHeadlineFontId: brandContext.typography.headline.fontFamilyId } : {},
53255
+ ...brandContext?.typography?.body?.fontFamilyId ? { brandBodyFontId: brandContext.typography.body.fontFamilyId } : {}
52830
53256
  };
52831
53257
  const messages = [{ role: "system", content: SYSTEM_PROMPT }];
52832
53258
  if (brandContext) {
@@ -52953,9 +53379,10 @@ ${f.textContent}`).join("\n\n")}`;
52953
53379
  statusMessage: error || "Insufficient credits for image generation"
52954
53380
  };
52955
53381
  const gatedAction = {
52956
- toolName: name,
52957
- args: parsedArgs,
52958
- result: gatedResult
53382
+ result: {
53383
+ success: gatedResult.success,
53384
+ statusMessage: gatedResult.statusMessage
53385
+ }
52959
53386
  };
52960
53387
  toolActions.push(gatedAction);
52961
53388
  onUpdate({
@@ -52981,9 +53408,10 @@ ${f.textContent}`).join("\n\n")}`;
52981
53408
  statusMessage: "Blocked: template already provides text slots"
52982
53409
  };
52983
53410
  const blockedAction = {
52984
- toolName: name,
52985
- args: parsedArgs,
52986
- result: blockedResult
53411
+ result: {
53412
+ success: blockedResult.success,
53413
+ statusMessage: blockedResult.statusMessage
53414
+ }
52987
53415
  };
52988
53416
  toolActions.push(blockedAction);
52989
53417
  onUpdate({
@@ -52994,7 +53422,10 @@ ${f.textContent}`).join("\n\n")}`;
52994
53422
  messages.push({
52995
53423
  role: "tool",
52996
53424
  tool_call_id: toolCall.id,
52997
- content: JSON.stringify({ success: false, error: blockedResult.error })
53425
+ content: JSON.stringify({
53426
+ success: false,
53427
+ error: blockedResult.error
53428
+ })
52998
53429
  });
52999
53430
  continue;
53000
53431
  }
@@ -53018,9 +53449,10 @@ ${f.textContent}`).join("\n\n")}`;
53018
53449
  }
53019
53450
  }
53020
53451
  const action = {
53021
- toolName: name,
53022
- args: parsedArgs,
53023
- result
53452
+ result: {
53453
+ success: result.success,
53454
+ statusMessage: result.statusMessage
53455
+ }
53024
53456
  };
53025
53457
  toolActions.push(action);
53026
53458
  onUpdate({
@@ -54451,7 +54883,12 @@ var AIChatPanel = React63.forwardRef(
54451
54883
  actions: msg.toolActions
54452
54884
  }
54453
54885
  ),
54454
- msg.reviewFeedback && /* @__PURE__ */ jsx192(ReviewCritiqueCard, { feedback: msg.reviewFeedback }),
54886
+ msg.reviewFeedback && /* @__PURE__ */ jsx192(
54887
+ ReviewCritiqueCard,
54888
+ {
54889
+ feedback: msg.reviewFeedback
54890
+ }
54891
+ ),
54455
54892
  /* @__PURE__ */ jsx192(
54456
54893
  MessageContent,
54457
54894
  {
@@ -54745,44 +55182,65 @@ function ToolActionsDisplay({ actions: actions2 }) {
54745
55182
  function ReviewCritiqueCard({ feedback }) {
54746
55183
  const [expanded, setExpanded] = useState55(false);
54747
55184
  const severityClass = (severity) => {
54748
- if (severity === "critical")
55185
+ if (severity === "critical") {
54749
55186
  return "acp-review-issue--critical";
54750
- if (severity === "major")
55187
+ }
55188
+ if (severity === "major") {
54751
55189
  return "acp-review-issue--major";
55190
+ }
54752
55191
  return "acp-review-issue--minor";
54753
55192
  };
54754
55193
  const approved = feedback.approved;
54755
- return /* @__PURE__ */ jsxs106("div", { className: `acp-review-card ${approved ? "acp-review-card--ok" : "acp-review-card--issues"}`, children: [
54756
- /* @__PURE__ */ jsxs106(
54757
- "button",
54758
- {
54759
- className: "acp-review-toggle",
54760
- onClick: () => setExpanded((v) => !v),
54761
- children: [
54762
- /* @__PURE__ */ jsx192("span", { className: `acp-review-badge ${approved ? "acp-review-badge--ok" : "acp-review-badge--issues"}`, children: approved ? "\u2713 Design approved" : `${feedback.issues.length} issue${feedback.issues.length !== 1 ? "s" : ""} found` }),
54763
- /* @__PURE__ */ jsx192("span", { className: "acp-review-summary", children: feedback.summary }),
54764
- !approved && feedback.issues.length > 0 && /* @__PURE__ */ jsx192(
54765
- ChevronRight,
54766
- {
54767
- size: 12,
54768
- className: `acp-tool-chevron ${expanded ? "acp-tool-chevron--open" : ""}`
54769
- }
54770
- )
54771
- ]
54772
- }
54773
- ),
54774
- expanded && !approved && feedback.issues.length > 0 && /* @__PURE__ */ jsx192("div", { className: "acp-review-issues", children: feedback.issues.map((iss, i) => /* @__PURE__ */ jsxs106("div", { className: `acp-review-issue ${severityClass(iss.severity)}`, children: [
54775
- /* @__PURE__ */ jsxs106("div", { className: "acp-review-issue-header", children: [
54776
- /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-severity", children: iss.severity }),
54777
- /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-element", children: iss.element }),
54778
- /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-problem", children: iss.problem })
54779
- ] }),
54780
- /* @__PURE__ */ jsxs106("div", { className: "acp-review-issue-fix", children: [
54781
- "\u2192 ",
54782
- iss.preciseInstruction
54783
- ] })
54784
- ] }, i)) })
54785
- ] });
55194
+ return /* @__PURE__ */ jsxs106(
55195
+ "div",
55196
+ {
55197
+ className: `acp-review-card ${approved ? "acp-review-card--ok" : "acp-review-card--issues"}`,
55198
+ children: [
55199
+ /* @__PURE__ */ jsxs106(
55200
+ "button",
55201
+ {
55202
+ className: "acp-review-toggle",
55203
+ onClick: () => setExpanded((v) => !v),
55204
+ children: [
55205
+ /* @__PURE__ */ jsx192(
55206
+ "span",
55207
+ {
55208
+ className: `acp-review-badge ${approved ? "acp-review-badge--ok" : "acp-review-badge--issues"}`,
55209
+ children: approved ? "\u2713 Design approved" : `${feedback.issues.length} issue${feedback.issues.length !== 1 ? "s" : ""} found`
55210
+ }
55211
+ ),
55212
+ /* @__PURE__ */ jsx192("span", { className: "acp-review-summary", children: feedback.summary }),
55213
+ !approved && feedback.issues.length > 0 && /* @__PURE__ */ jsx192(
55214
+ ChevronRight,
55215
+ {
55216
+ size: 12,
55217
+ className: `acp-tool-chevron ${expanded ? "acp-tool-chevron--open" : ""}`
55218
+ }
55219
+ )
55220
+ ]
55221
+ }
55222
+ ),
55223
+ expanded && !approved && feedback.issues.length > 0 && /* @__PURE__ */ jsx192("div", { className: "acp-review-issues", children: feedback.issues.map((iss, i) => /* @__PURE__ */ jsxs106(
55224
+ "div",
55225
+ {
55226
+ className: `acp-review-issue ${severityClass(iss.severity)}`,
55227
+ children: [
55228
+ /* @__PURE__ */ jsxs106("div", { className: "acp-review-issue-header", children: [
55229
+ /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-severity", children: iss.severity }),
55230
+ /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-element", children: iss.element }),
55231
+ /* @__PURE__ */ jsx192("span", { className: "acp-review-issue-problem", children: iss.problem })
55232
+ ] }),
55233
+ /* @__PURE__ */ jsxs106("div", { className: "acp-review-issue-fix", children: [
55234
+ "\u2192 ",
55235
+ iss.preciseInstruction
55236
+ ] })
55237
+ ]
55238
+ },
55239
+ i
55240
+ )) })
55241
+ ]
55242
+ }
55243
+ );
54786
55244
  }
54787
55245
  async function callPlainChatAPI(messages, apiKey, signal, opts) {
54788
55246
  const baseModel = opts?.chatModel || "google/gemini-3.1-flash-lite-preview";