@orangecatai/adgen-canvas 0.0.7 → 0.0.8

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-2Q3FNCU4.js";
70
+ } from "./chunk-LAGHQ2FH.js";
71
71
  import {
72
72
  define_import_meta_env_default
73
- } from "./chunk-MW637LK5.js";
73
+ } from "./chunk-FAXV62EG.js";
74
74
  import {
75
75
  en_default
76
76
  } from "./chunk-IFMURN5W.js";
@@ -9765,7 +9765,7 @@ var exportCanvas = async (type, elements, appState, files, {
9765
9765
  let blob = canvasToBlob(tempCanvas);
9766
9766
  if (appState.exportEmbedScene) {
9767
9767
  blob = blob.then(
9768
- (blob2) => import("./data/image-L2UC5LX5.js").then(
9768
+ (blob2) => import("./data/image-FJMUJMRD.js").then(
9769
9769
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9770
9770
  blob: blob2,
9771
9771
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -23539,8 +23539,9 @@ function detectEllipse(style, width, height) {
23539
23539
  const raw = style.borderTopLeftRadius || "0";
23540
23540
  if (raw.includes("%")) {
23541
23541
  const pct = parseFloat(raw);
23542
- if (!isNaN(pct) && pct >= 50)
23542
+ if (!isNaN(pct) && pct >= 50) {
23543
23543
  return true;
23544
+ }
23544
23545
  } else {
23545
23546
  const px = parseFloat(raw);
23546
23547
  if (!isNaN(px) && px > 0 && px >= width / 2 && px >= height / 2) {
@@ -23553,13 +23554,13 @@ function isGradient(bgImage) {
23553
23554
  return bgImage !== "none" && (bgImage.includes("linear-gradient") || bgImage.includes("radial-gradient"));
23554
23555
  }
23555
23556
  function extractGradientColor(bgImage) {
23556
- const matches = [
23557
- ...bgImage.matchAll(/(#[\da-fA-F]{3,8}|rgba?\([^)]+\))/g)
23558
- ];
23559
- if (matches.length === 0)
23557
+ const matches = [...bgImage.matchAll(/(#[\da-fA-F]{3,8}|rgba?\([^)]+\))/g)];
23558
+ if (matches.length === 0) {
23560
23559
  return "#1a1a2e";
23561
- if (matches.length === 1)
23560
+ }
23561
+ if (matches.length === 1) {
23562
23562
  return colorToHex(matches[0][1]);
23563
+ }
23563
23564
  if (bgImage.includes("repeating")) {
23564
23565
  return colorToHex(matches[0][1]);
23565
23566
  }
@@ -23594,8 +23595,9 @@ function getFullText(el) {
23594
23595
  for (const node of el.childNodes) {
23595
23596
  if (node.nodeType === Node.TEXT_NODE) {
23596
23597
  const t2 = node.textContent || "";
23597
- if (t2.trim())
23598
+ if (t2.trim()) {
23598
23599
  hasInlineContent = true;
23600
+ }
23599
23601
  text += t2;
23600
23602
  } else if (node.nodeType === Node.ELEMENT_NODE) {
23601
23603
  const child = node;
@@ -23604,8 +23606,9 @@ function getFullText(el) {
23604
23606
  hasInlineContent = true;
23605
23607
  } else if (INLINE_TAGS.has(child.tagName)) {
23606
23608
  const t2 = child.textContent || "";
23607
- if (t2.trim())
23609
+ if (t2.trim()) {
23608
23610
  hasInlineContent = true;
23611
+ }
23609
23612
  text += t2;
23610
23613
  }
23611
23614
  }
@@ -23681,9 +23684,7 @@ async function htmlToExcalidrawElements(html, frameX, frameY, frameWidth, frameH
23681
23684
  const y = Math.round(frameY + (rect.top - rootRect.top));
23682
23685
  const w = Math.round(rect.width);
23683
23686
  const h = Math.round(rect.height);
23684
- const globalOpacity = Math.round(
23685
- parseFloat(style.opacity ?? "1") * 100
23686
- );
23687
+ const globalOpacity = Math.round(parseFloat(style.opacity ?? "1") * 100);
23687
23688
  const bgImage = style.backgroundImage || "none";
23688
23689
  const bgColor = style.backgroundColor || "transparent";
23689
23690
  const borderColor = style.borderColor || "transparent";
@@ -23867,10 +23868,9 @@ function computeCropLoss(sourceAR, targetAR) {
23867
23868
  if (sourceAR >= targetAR) {
23868
23869
  const usedWidth = targetAR * 1;
23869
23870
  return 1 - usedWidth / sourceAR;
23870
- } else {
23871
- const usedHeight = sourceAR / targetAR;
23872
- return 1 - usedHeight;
23873
23871
  }
23872
+ const usedHeight = sourceAR / targetAR;
23873
+ return 1 - usedHeight;
23874
23874
  }
23875
23875
  function needsBackgroundRegeneration(srcW, srcH, tgtW, tgtH) {
23876
23876
  const srcAR = srcW / srcH;
@@ -23915,14 +23915,18 @@ function fitImageToFrame(imgW, imgH, frameW, frameH) {
23915
23915
  }
23916
23916
  function classifyBannerType(w, h) {
23917
23917
  const ar = w / h;
23918
- if (ar >= 3.5)
23918
+ if (ar >= 3.5) {
23919
23919
  return "horizontal-strip";
23920
- if (ar <= 1 / 3.5)
23920
+ }
23921
+ if (ar <= 1 / 3.5) {
23921
23922
  return "vertical-strip";
23922
- if (ar >= 1.6)
23923
+ }
23924
+ if (ar >= 1.6) {
23923
23925
  return "landscape";
23924
- if (ar <= 0.65)
23926
+ }
23927
+ if (ar <= 0.65) {
23925
23928
  return "portrait";
23929
+ }
23926
23930
  return "square";
23927
23931
  }
23928
23932
  function getBannerLayoutGuidance(srcW, srcH, tgtW, tgtH) {
@@ -23934,7 +23938,13 @@ HORIZONTAL STRIP BANNER (${tgtW}\xD7${tgtH}) \u2014 VERY WIDE AND SHORT:
23934
23938
  \u2022 This is an extreme format. Height is tiny so you must be ruthless with vertical space.
23935
23939
  \u2022 Arrange elements in a horizontal flow \u2014 LEFT to RIGHT.
23936
23940
  \u2022 Brand/logo: anchor hard left (x: 8\u201320px, vertically centred).
23937
- \u2022 Headline text: place in the centre-left region; REDUCE font size aggressively (aim for ~${Math.max(10, Math.round(tgtH * 0.35))}\u2013${Math.max(14, Math.round(tgtH * 0.5))}px). Single line only \u2014 no wrapping.
23941
+ \u2022 Headline text: place in the centre-left region; REDUCE font size aggressively (aim for ~${Math.max(
23942
+ 10,
23943
+ Math.round(tgtH * 0.35)
23944
+ )}\u2013${Math.max(
23945
+ 14,
23946
+ Math.round(tgtH * 0.5)
23947
+ )}px). Single line only \u2014 no wrapping.
23938
23948
  \u2022 CTA button / call-to-action text: anchor hard right with right padding 8\u201316px, vertically centred.
23939
23949
  \u2022 Product image (if present): place centre or centre-right; height = ~80% of canvas height.
23940
23950
  \u2022 NOTHING should be taller than the canvas. All elements must fit within ${tgtH}px height.
@@ -23942,9 +23952,14 @@ HORIZONTAL STRIP BANNER (${tgtW}\xD7${tgtH}) \u2014 VERY WIDE AND SHORT:
23942
23952
  "vertical-strip": `
23943
23953
  VERTICAL STRIP BANNER (${tgtW}\xD7${tgtH}) \u2014 NARROW AND TALL:
23944
23954
  \u2022 Stack all elements VERTICALLY, reading from TOP to BOTTOM.
23945
- \u2022 Brand/logo: top-centre, small (height \u2248 ${Math.round(tgtH * 0.07)}px). Leave 8\u201316px top padding.
23955
+ \u2022 Brand/logo: top-centre, small (height \u2248 ${Math.round(
23956
+ tgtH * 0.07
23957
+ )}px). Leave 8\u201316px top padding.
23946
23958
  \u2022 Hero/product image: centre of canvas, width \u2248 85% of canvas width, maintain aspect ratio.
23947
- \u2022 Headline text: below the image, centred, font size \u2248 ${Math.max(12, Math.round(tgtW * 0.1))}\u2013${Math.max(16, Math.round(tgtW * 0.14))}px, text-align: centre.
23959
+ \u2022 Headline text: below the image, centred, font size \u2248 ${Math.max(
23960
+ 12,
23961
+ Math.round(tgtW * 0.1)
23962
+ )}\u2013${Math.max(16, Math.round(tgtW * 0.14))}px, text-align: centre.
23948
23963
  \u2022 Sub-headline or body text: below headline, smaller font, centred.
23949
23964
  \u2022 CTA button: at the bottom (bottom 12% of canvas), centred, ~80% width.
23950
23965
  \u2022 Leave \u22658px horizontal padding on each side \u2014 nothing should touch the edges.`,
@@ -23971,7 +23986,10 @@ LANDSCAPE FORMAT (${tgtW}\xD7${tgtH}):
23971
23986
  \u2022 Product image: right half or left half (check source for preference).
23972
23987
  \u2022 Headline + subtext: on the opposite side from image.
23973
23988
  \u2022 CTA: below the text block.
23974
- \u2022 Scale font sizes to \u2265${Math.max(16, Math.round(tgtH * 0.05))}px for readability.`
23989
+ \u2022 Scale font sizes to \u2265${Math.max(
23990
+ 16,
23991
+ Math.round(tgtH * 0.05)
23992
+ )}px for readability.`
23975
23993
  };
23976
23994
  let result = guidance[type] ?? guidance.square;
23977
23995
  if (srcType !== type) {
@@ -23995,18 +24013,35 @@ function buildAutoResizePrompt(info, targetW, targetH) {
23995
24013
  const pctW = (el.width / srcW * 100).toFixed(1);
23996
24014
  const pctH = (el.height / srcH * 100).toFixed(1);
23997
24015
  if (el.kind === "text") {
23998
- return `[${i + 1}] TEXT \u2014 source pos: x=${Math.round(el.relX)}px (${pctX}% of W), y=${Math.round(el.relY)}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(el.height)}px (${pctW}%\xD7${pctH}% of canvas)
24016
+ return `[${i + 1}] TEXT \u2014 source pos: x=${Math.round(
24017
+ el.relX
24018
+ )}px (${pctX}% of W), y=${Math.round(
24019
+ el.relY
24020
+ )}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(
24021
+ el.height
24022
+ )}px (${pctW}%\xD7${pctH}% of canvas)
23999
24023
  content: "${el.text.replace(/"/g, '\\"').slice(0, 200)}"
24000
24024
  style: font-size: ${el.fontSize}px, color: ${el.color}, text-align: ${el.textAlign}, opacity: ${Math.round(el.opacity)}%`;
24001
24025
  } else if (el.kind === "image") {
24002
24026
  const placeholder = `IMG_PLACEHOLDER_${i}`;
24003
24027
  imagePlaceholders.set(placeholder, el.dataUrl);
24004
- return `[${i + 1}] IMAGE \u2014 source pos: x=${Math.round(el.relX)}px (${pctX}% of W), y=${Math.round(el.relY)}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(el.height)}px (${pctW}%\xD7${pctH}% of canvas)
24028
+ return `[${i + 1}] IMAGE \u2014 source pos: x=${Math.round(
24029
+ el.relX
24030
+ )}px (${pctX}% of W), y=${Math.round(
24031
+ el.relY
24032
+ )}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(
24033
+ el.height
24034
+ )}px (${pctW}%\xD7${pctH}% of canvas)
24005
24035
  Place this image as: background-image: url(${placeholder}); background-size: cover; background-position: center; opacity: ${(el.opacity / 100).toFixed(2)}`;
24006
- } else {
24007
- return `[${i + 1}] SHAPE/RECT \u2014 source pos: x=${Math.round(el.relX)}px (${pctX}% of W), y=${Math.round(el.relY)}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(el.height)}px (${pctW}%\xD7${pctH}% of canvas)
24008
- style: background-color: ${el.backgroundColor}, border-radius: ${el.borderRadius}px, opacity: ${Math.round(el.opacity)}%`;
24009
24036
  }
24037
+ return `[${i + 1}] SHAPE/RECT \u2014 source pos: x=${Math.round(
24038
+ el.relX
24039
+ )}px (${pctX}% of W), y=${Math.round(
24040
+ el.relY
24041
+ )}px (${pctY}% of H), size: ${Math.round(el.width)}\xD7${Math.round(
24042
+ el.height
24043
+ )}px (${pctW}%\xD7${pctH}% of canvas)
24044
+ style: background-color: ${el.backgroundColor}, border-radius: ${el.borderRadius}px, opacity: ${Math.round(el.opacity)}%`;
24010
24045
  }).join("\n\n");
24011
24046
  const layoutGuidance = getBannerLayoutGuidance(srcW, srcH, targetW, targetH);
24012
24047
  const bgCss = background.type === "solid" ? `background-color: ${background.color};` : "";
@@ -24044,20 +24079,23 @@ Return ONLY the raw HTML. No markdown, no code fences, no explanation.`;
24044
24079
  return { prompt, imagePlaceholders };
24045
24080
  }
24046
24081
  async function callOpenRouterForHtml(prompt, apiKey, model, signal) {
24047
- const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
24048
- method: "POST",
24049
- signal,
24050
- headers: {
24051
- Authorization: `Bearer ${apiKey}`,
24052
- "Content-Type": "application/json"
24053
- },
24054
- body: JSON.stringify({
24055
- model,
24056
- messages: [{ role: "user", content: prompt }],
24057
- stream: false,
24058
- temperature: 0.3
24059
- })
24060
- });
24082
+ const response = await fetch(
24083
+ "https://openrouter.ai/api/v1/chat/completions",
24084
+ {
24085
+ method: "POST",
24086
+ signal,
24087
+ headers: {
24088
+ Authorization: `Bearer ${apiKey}`,
24089
+ "Content-Type": "application/json"
24090
+ },
24091
+ body: JSON.stringify({
24092
+ model,
24093
+ messages: [{ role: "user", content: prompt }],
24094
+ stream: false,
24095
+ temperature: 0.3
24096
+ })
24097
+ }
24098
+ );
24061
24099
  if (!response.ok) {
24062
24100
  let msg = `OpenRouter API error ${response.status}`;
24063
24101
  try {
@@ -24076,8 +24114,9 @@ async function callOpenRouterForHtml(prompt, apiKey, model, signal) {
24076
24114
  }
24077
24115
  function findInsertAfterFrame(elements, frameId) {
24078
24116
  const frameIdx = elements.findIndex((el) => el.id === frameId);
24079
- if (frameIdx < 0)
24117
+ if (frameIdx < 0) {
24080
24118
  return elements.length;
24119
+ }
24081
24120
  let end = frameIdx + 1;
24082
24121
  for (let i = frameIdx + 1; i < elements.length; i++) {
24083
24122
  const el = elements[i];
@@ -24129,12 +24168,19 @@ async function runAutoResizeForDimension(opts) {
24129
24168
  let bgImageDataUrl = null;
24130
24169
  if (info.background.type === "image") {
24131
24170
  const srcBgUrl = info.background.dataUrl;
24132
- const needsRegen = needsBackgroundRegeneration(srcFrame.width, srcFrame.height, tgtW, tgtH);
24171
+ const needsRegen = needsBackgroundRegeneration(
24172
+ srcFrame.width,
24173
+ srcFrame.height,
24174
+ tgtW,
24175
+ tgtH
24176
+ );
24133
24177
  debug(`[AutoResize] ${debugLabel} \u2014 background: needsRegen=${needsRegen}`);
24134
24178
  if (needsRegen) {
24135
24179
  onProgress?.("Regenerating background image\u2026");
24136
24180
  const ar = nearestGeminiAspectRatio(tgtW, tgtH);
24137
- debug(`[AutoResize] ${debugLabel} \u2014 calling Gemini for background regen (ar=${ar})`);
24181
+ debug(
24182
+ `[AutoResize] ${debugLabel} \u2014 calling Gemini for background regen (ar=${ar})`
24183
+ );
24138
24184
  bgImageDataUrl = await callGeminiAPI(
24139
24185
  `You are given an input background image. Preserve the original scene identity, composition, color palette, and style. Outpaint only beyond the existing edges so the final image fits aspect ratio ${ar}. Keep the center subject and key content unchanged. Ensure all surfaces extend with natural, seamless continuity \u2014 no visible seams, repeated patterns, or tiling artifacts. For areas far from the center, gradually soften into ambient bokeh rather than extending sharp textures. Do not add text, logos, watermarks, UI, symbols, or new unrelated objects. Avoid altering brand-relevant details.`,
24140
24186
  agentImageModel || DEFAULT_BG_REGEN_MODEL,
@@ -24155,13 +24201,15 @@ async function runAutoResizeForDimension(opts) {
24155
24201
  }
24156
24202
  if (bgImageDataUrl) {
24157
24203
  onProgress?.("Placing background\u2026");
24158
- const { width: imgNatW, height: imgNatH } = await getImageNaturalDimensions(bgImageDataUrl);
24159
- const { relX, relY, width: imgW, height: imgH } = fitImageToFrame(
24160
- imgNatW,
24161
- imgNatH,
24162
- tgtW,
24163
- tgtH
24204
+ const { width: imgNatW, height: imgNatH } = await getImageNaturalDimensions(
24205
+ bgImageDataUrl
24164
24206
  );
24207
+ const {
24208
+ relX,
24209
+ relY,
24210
+ width: imgW,
24211
+ height: imgH
24212
+ } = fitImageToFrame(imgNatW, imgNatH, tgtW, tgtH);
24165
24213
  const bgFileId = nanoid();
24166
24214
  const bgEl = newImageElement({
24167
24215
  type: "image",
@@ -24194,7 +24242,9 @@ async function runAutoResizeForDimension(opts) {
24194
24242
  ]);
24195
24243
  }
24196
24244
  onProgress?.("Generating layout\u2026");
24197
- debug(`[AutoResize] ${debugLabel} \u2014 calling OpenRouter for HTML layout (model=${chatModel})`);
24245
+ debug(
24246
+ `[AutoResize] ${debugLabel} \u2014 calling OpenRouter for HTML layout (model=${chatModel})`
24247
+ );
24198
24248
  const { prompt, imagePlaceholders } = buildAutoResizePrompt(info, tgtW, tgtH);
24199
24249
  let html = await callOpenRouterForHtml(
24200
24250
  prompt,
@@ -24205,7 +24255,9 @@ async function runAutoResizeForDimension(opts) {
24205
24255
  if (!html || html.length < 20) {
24206
24256
  throw new Error("LLM returned empty HTML");
24207
24257
  }
24208
- debug(`[AutoResize] ${debugLabel} \u2014 HTML received (${html.length} chars), substituting ${imagePlaceholders.size} image placeholder(s)\u2026`);
24258
+ debug(
24259
+ `[AutoResize] ${debugLabel} \u2014 HTML received (${html.length} chars), substituting ${imagePlaceholders.size} image placeholder(s)\u2026`
24260
+ );
24209
24261
  for (const [placeholder, dataUrl] of imagePlaceholders) {
24210
24262
  html = html.split(`url(${placeholder})`).join(`url(${dataUrl})`);
24211
24263
  }
@@ -24334,7 +24386,9 @@ async function runAutoResizeForDimension(opts) {
24334
24386
  if (imageFiles.length > 0) {
24335
24387
  app.addFiles(imageFiles);
24336
24388
  }
24337
- debug(`[AutoResize] ${debugLabel} \u2014 inserted ${newEls.length} elements (${bgEls.length} bg, ${fgEls.length} fg)`);
24389
+ debug(
24390
+ `[AutoResize] ${debugLabel} \u2014 inserted ${newEls.length} elements (${bgEls.length} bg, ${fgEls.length} fg)`
24391
+ );
24338
24392
  return { frameId };
24339
24393
  }
24340
24394
  function preCreateAllFrames(targetDimensions, sourceFrame, app) {
@@ -24351,7 +24405,13 @@ function preCreateAllFrames(targetDimensions, sourceFrame, app) {
24351
24405
  locked: false,
24352
24406
  name: dim.label ?? `${dim.width}\xD7${dim.height}`
24353
24407
  });
24354
- return { frame: newFrame, x: startX, y, width: dim.width, height: dim.height };
24408
+ return {
24409
+ frame: newFrame,
24410
+ x: startX,
24411
+ y,
24412
+ width: dim.width,
24413
+ height: dim.height
24414
+ };
24355
24415
  });
24356
24416
  const currentEls = app.getSceneElements();
24357
24417
  const newFrameEls = frameInfos.map((fi) => fi.frame);
@@ -24384,9 +24444,7 @@ async function runAutoResize(opts) {
24384
24444
  } = opts;
24385
24445
  const results = new Array(targetDimensions.length);
24386
24446
  const elements = app.getSceneElements();
24387
- const sourceFrame = elements.find(
24388
- (el) => el.id === sourceFrameId
24389
- );
24447
+ const sourceFrame = elements.find((el) => el.id === sourceFrameId);
24390
24448
  if (!sourceFrame) {
24391
24449
  return targetDimensions.map(() => ({
24392
24450
  status: "error",
@@ -24401,11 +24459,15 @@ async function runAutoResize(opts) {
24401
24459
  }));
24402
24460
  const BATCH_SIZE = 3;
24403
24461
  const total = targetDimensions.length;
24404
- debug(`[AutoResize] Starting ${total} dimension(s), batch size ${BATCH_SIZE}`);
24462
+ debug(
24463
+ `[AutoResize] Starting ${total} dimension(s), batch size ${BATCH_SIZE}`
24464
+ );
24405
24465
  for (let batchStart = 0; batchStart < total; batchStart += BATCH_SIZE) {
24406
24466
  const batchEnd = Math.min(batchStart + BATCH_SIZE, total);
24407
24467
  const batchNum = Math.floor(batchStart / BATCH_SIZE) + 1;
24408
- debug(`[AutoResize] Batch ${batchNum}: dimensions ${batchStart + 1}\u2013${batchEnd} of ${total}`);
24468
+ debug(
24469
+ `[AutoResize] Batch ${batchNum}: dimensions ${batchStart + 1}\u2013${batchEnd} of ${total}`
24470
+ );
24409
24471
  await Promise.allSettled(
24410
24472
  targetDimensions.slice(batchStart, batchEnd).map(async (dim, batchIdx) => {
24411
24473
  const i = batchStart + batchIdx;
@@ -24423,7 +24485,9 @@ async function runAutoResize(opts) {
24423
24485
  debug(`[AutoResize] ${debugLabel} \u2014 credits OK`);
24424
24486
  }
24425
24487
  onProgress?.(i, total, "bg-regen", label);
24426
- debug(`[AutoResize] ${debugLabel} \u2014 starting at (${placements[i].x}, ${placements[i].y})`);
24488
+ debug(
24489
+ `[AutoResize] ${debugLabel} \u2014 starting at (${placements[i].x}, ${placements[i].y})`
24490
+ );
24427
24491
  try {
24428
24492
  const { frameId } = await runAutoResizeForDimension({
24429
24493
  info,
@@ -24593,9 +24657,7 @@ var ErrorIcon = () => /* @__PURE__ */ jsxs47("svg", { width: "11", height: "11",
24593
24657
  ),
24594
24658
  /* @__PURE__ */ jsx87("circle", { cx: "6", cy: "8.5", r: "0.65", fill: "currentColor" })
24595
24659
  ] });
24596
- var CustomDimInput = ({
24597
- onAdd
24598
- }) => {
24660
+ var CustomDimInput = ({ onAdd }) => {
24599
24661
  const [w, setW] = useState29("");
24600
24662
  const [h, setH] = useState29("");
24601
24663
  const handleAdd = () => {
@@ -24682,8 +24744,9 @@ var AutoResizePanel = ({
24682
24744
  };
24683
24745
  const addCustom = (dim) => {
24684
24746
  const key = dim.label;
24685
- if (selected.has(key))
24747
+ if (selected.has(key)) {
24686
24748
  return;
24749
+ }
24687
24750
  setExtras((prev) => [...prev, dim]);
24688
24751
  setSelected((prev) => /* @__PURE__ */ new Set([...prev, key]));
24689
24752
  };
@@ -24714,8 +24777,9 @@ var AutoResizePanel = ({
24714
24777
  };
24715
24778
  }, []);
24716
24779
  const handleGenerate = useCallback15(async () => {
24717
- if (allDimensions.length === 0)
24780
+ if (allDimensions.length === 0) {
24718
24781
  return;
24782
+ }
24719
24783
  setRunning(true);
24720
24784
  setGlobalError(null);
24721
24785
  const initialStates = {};
@@ -24738,9 +24802,7 @@ var AutoResizePanel = ({
24738
24802
  }
24739
24803
  }
24740
24804
  const elements = app.getSceneElements();
24741
- const sourceFrame = elements.find(
24742
- (el) => el.id === element.id
24743
- );
24805
+ const sourceFrame = elements.find((el) => el.id === element.id);
24744
24806
  let preCreatedFrames = [];
24745
24807
  if (sourceFrame) {
24746
24808
  preCreatedFrames = preCreateAllFrames(allDimensions, sourceFrame, app);
@@ -24793,7 +24855,18 @@ var AutoResizePanel = ({
24793
24855
  abortRef.current = null;
24794
24856
  cleanupShimmer();
24795
24857
  }
24796
- }, [allDimensions, app, element.id, updateDimState, cleanupShimmer, onBeforeAutoResize, onAfterAutoResize, openRouterApiKey, chatModel, agentImageModel]);
24858
+ }, [
24859
+ allDimensions,
24860
+ app,
24861
+ element.id,
24862
+ updateDimState,
24863
+ cleanupShimmer,
24864
+ onBeforeAutoResize,
24865
+ onAfterAutoResize,
24866
+ openRouterApiKey,
24867
+ chatModel,
24868
+ agentImageModel
24869
+ ]);
24797
24870
  const handleCancel = () => {
24798
24871
  abortRef.current?.abort();
24799
24872
  setRunning(false);
@@ -25377,7 +25450,9 @@ var FrameToolbar = ({
25377
25450
  onClose: () => setShowAutoResize(false),
25378
25451
  onBeforeAutoResize: app.props.onBeforeAutoResize,
25379
25452
  onAfterAutoResize: app.props.onAfterAutoResize,
25380
- openRouterApiKey: resolveOpenRouterApiKey(app.props.openRouterApiKey),
25453
+ openRouterApiKey: resolveOpenRouterApiKey(
25454
+ app.props.openRouterApiKey
25455
+ ),
25381
25456
  chatModel: app.props.chatModel ?? "google/gemini-2.0-flash-001",
25382
25457
  agentImageModel: app.props.agentImageModel
25383
25458
  }
@@ -25397,8 +25472,9 @@ var AutoResizeShimmerLayer = () => {
25397
25472
  const [, forceUpdate] = useReducer((n) => n + 1, 0);
25398
25473
  useEffect32(() => autoResizeStore.subscribe(forceUpdate), []);
25399
25474
  const frames = autoResizeStore.getLoadingFrames();
25400
- if (frames.length === 0)
25475
+ if (frames.length === 0) {
25401
25476
  return null;
25477
+ }
25402
25478
  const zoomVal = appState.zoom.value;
25403
25479
  return /* @__PURE__ */ jsx89(Fragment13, { children: frames.map((frame) => {
25404
25480
  const { x: viewportX, y: viewportY } = sceneCoordsToViewportCoords6(
@@ -49971,8 +50047,14 @@ var BANNER_TOOLS = [
49971
50047
  type: "string",
49972
50048
  description: "Detailed generation prompt. When using a reference image, describe what you want Gemini to produce \u2014 e.g. 'use this building as the subject, wide cinematic crop with open sky on the left for text overlay'."
49973
50049
  },
49974
- x: { type: "number", description: "X position relative to the frame's top-left corner" },
49975
- y: { type: "number", description: "Y position relative to the frame's top-left corner" },
50050
+ x: {
50051
+ type: "number",
50052
+ description: "X position relative to the frame's top-left corner"
50053
+ },
50054
+ y: {
50055
+ type: "number",
50056
+ description: "Y position relative to the frame's top-left corner"
50057
+ },
49976
50058
  width: { type: "number", description: "Image width in pixels" },
49977
50059
  height: { type: "number", description: "Image height in pixels" },
49978
50060
  referenceImageIndex: {
@@ -50061,39 +50143,12 @@ var BANNER_TOOLS = [
50061
50143
  parameters: {
50062
50144
  type: "object",
50063
50145
  properties: {
50064
- elementId: { type: "string", description: "ID of the element to delete" }
50065
- },
50066
- required: ["elementId"],
50067
- additionalProperties: false
50068
- }
50069
- }
50070
- },
50071
- {
50072
- type: "function",
50073
- function: {
50074
- name: "generate_image",
50075
- description: "Generate an image using AI (Gemini) and place it onto the canvas. Always call this for any photo or image content \u2014 even when the user has attached a reference image. The attached image is passed to Gemini as visual reference; Gemini generates the final output. Use referenceImageIndex to tell Gemini which user attachment to reference. IMPORTANT: your prompt must describe a purely visual scene \u2014 NO text, NO typography, NO words, NO captions, NO watermarks. All copy is handled by the HTML layer. A 'no text' safety suffix will be auto-appended, but you must also describe composition clearly (e.g. which side should be left open/empty for the HTML text).",
50076
- parameters: {
50077
- type: "object",
50078
- properties: {
50079
- frameId: {
50080
- type: "string",
50081
- description: "ID of the parent frame to insert the image into"
50082
- },
50083
- prompt: {
50146
+ elementId: {
50084
50147
  type: "string",
50085
- description: "Detailed generation prompt. When using a reference image, describe what you want Gemini to produce \u2014 e.g. 'use this building as the subject, wide cinematic crop with open sky on the left for text overlay'."
50086
- },
50087
- x: { type: "number", description: "X position relative to the frame's top-left corner" },
50088
- y: { type: "number", description: "Y position relative to the frame's top-left corner" },
50089
- width: { type: "number", description: "Image width in pixels" },
50090
- height: { type: "number", description: "Image height in pixels" },
50091
- referenceImageIndex: {
50092
- type: "number",
50093
- description: "0-based index of the user's attached image to pass to Gemini as reference. Always use 0 when the user has attached an image."
50148
+ description: "ID of the element to delete"
50094
50149
  }
50095
50150
  },
50096
- required: ["frameId", "prompt", "x", "y", "width", "height"],
50151
+ required: ["elementId"],
50097
50152
  additionalProperties: false
50098
50153
  }
50099
50154
  }
@@ -50707,7 +50762,14 @@ async function execGenerateHtmlBanner(args, ctx) {
50707
50762
  }
50708
50763
  let parsedEls;
50709
50764
  try {
50710
- parsedEls = await htmlToExcalidrawElements(html, frameX, frameY, frameW, frameH, ctx.customFontMap);
50765
+ parsedEls = await htmlToExcalidrawElements(
50766
+ html,
50767
+ frameX,
50768
+ frameY,
50769
+ frameW,
50770
+ frameH,
50771
+ ctx.customFontMap
50772
+ );
50711
50773
  } catch (err) {
50712
50774
  const msg = err instanceof Error ? err.message : "HTML parse failed";
50713
50775
  return {
@@ -50717,7 +50779,9 @@ async function execGenerateHtmlBanner(args, ctx) {
50717
50779
  };
50718
50780
  }
50719
50781
  if (parsedEls.length === 0) {
50720
- const bgMatch = html.match(/\.(?:bg|canvas)[^{]*\{[^}]*background(?:-color)?:\s*(#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))/);
50782
+ const bgMatch = html.match(
50783
+ /\.(?:bg|canvas)[^{]*\{[^}]*background(?:-color)?:\s*(#[0-9a-fA-F]{3,8}|rgba?\([^)]+\))/
50784
+ );
50721
50785
  const fallbackBg = bgMatch ? bgMatch[1] : null;
50722
50786
  if (fallbackBg) {
50723
50787
  const bgRect = newElement6({
@@ -50833,7 +50897,9 @@ async function execGenerateHtmlBanner(args, ctx) {
50833
50897
  const frameIdx = currentEls.findIndex((el) => el.id === frame.id);
50834
50898
  const existingChildren = getFrameChildren8(currentEls, frame.id);
50835
50899
  const firstImageChildIdx = existingChildren.length > 0 ? currentEls.findIndex((el) => el.id === existingChildren[0].id) : frameIdx + 1;
50836
- const afterAllChildrenIdx = existingChildren.length > 0 ? currentEls.findIndex((el) => el.id === existingChildren[existingChildren.length - 1].id) + 1 : frameIdx + 1;
50900
+ const afterAllChildrenIdx = existingChildren.length > 0 ? currentEls.findIndex(
50901
+ (el) => el.id === existingChildren[existingChildren.length - 1].id
50902
+ ) + 1 : frameIdx + 1;
50837
50903
  const withNew = [
50838
50904
  ...currentEls.slice(0, firstImageChildIdx),
50839
50905
  ...bgEls,
@@ -50848,10 +50914,7 @@ async function execGenerateHtmlBanner(args, ctx) {
50848
50914
  ctx.excalidrawAPI.addFiles(imageFiles);
50849
50915
  }
50850
50916
  ctx.excalidrawAPI.scrollToContent(frame, { fitToViewport: false });
50851
- const screenshot = await captureFrameScreenshot(
50852
- ctx.excalidrawAPI,
50853
- frame.id
50854
- );
50917
+ const screenshot = await captureFrameScreenshot(ctx.excalidrawAPI, frame.id);
50855
50918
  return {
50856
50919
  success: true,
50857
50920
  data: {
@@ -51554,14 +51617,14 @@ ${f.textContent}`).join("\n\n")}`;
51554
51617
  htmlBannerFailures++;
51555
51618
  if (htmlBannerFailures >= 2 && imageGenData) {
51556
51619
  const frameW = Math.round(
51557
- augmentedToolCtx.excalidrawAPI.getSceneElements().find(
51558
- (el) => el.id === imageGenData.frameId
51559
- )?.width ?? 512
51620
+ augmentedToolCtx.excalidrawAPI.getSceneElements().find((el) => el.id === imageGenData.frameId)?.width ?? 512
51560
51621
  );
51561
51622
  messages.push({
51562
51623
  role: "user",
51563
51624
  content: `generate_html_banner has failed ${htmlBannerFailures} times. STOP trying generate_html_banner. Instead, build the text layer using primitive tools in this order:
51564
- 1. Call add_rectangle to create the background zone (left side of the frame: x=0, y=0, width=${imageGenData.x > 0 ? imageGenData.x : Math.round(frameW * 0.45)}, height=${Math.round(augmentedToolCtx.excalidrawAPI.getSceneElements().find((el) => el.id === imageGenData.frameId)?.height ?? 512)} inside frameId="${imageGenData.frameId}")
51625
+ 1. Call add_rectangle to create the background zone (left side of the frame: x=0, y=0, width=${imageGenData.x > 0 ? imageGenData.x : Math.round(frameW * 0.45)}, height=${Math.round(
51626
+ augmentedToolCtx.excalidrawAPI.getSceneElements().find((el) => el.id === imageGenData.frameId)?.height ?? 512
51627
+ )} inside frameId="${imageGenData.frameId}")
51565
51628
  2. Call add_text for each text element (headline, subheadline, CTA label) inside the same frameId
51566
51629
  3. Call add_rectangle for the CTA button background with a border-radius
51567
51630