@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/{chunk-MW637LK5.js → chunk-FAXV62EG.js} +2 -2
- package/dist/dev/{chunk-2Q3FNCU4.js → chunk-LAGHQ2FH.js} +3 -3
- package/dist/dev/data/{image-L2UC5LX5.js → image-FJMUJMRD.js} +3 -3
- package/dist/dev/index.css.map +2 -2
- package/dist/dev/index.js +180 -117
- package/dist/dev/index.js.map +2 -2
- package/dist/dev/subset-shared.chunk.js +1 -1
- package/dist/dev/subset-worker.chunk.js +1 -1
- package/dist/prod/{chunk-XPV36KCI.js → chunk-7LYXIGC7.js} +2 -2
- package/dist/prod/{chunk-7AVPHWG7.js → chunk-B427J75A.js} +1 -1
- package/dist/prod/data/image-RPDJGGN2.js +1 -0
- package/dist/prod/index.js +35 -35
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/excalidraw/components/ai-chat/canvasTools.d.ts +0 -41
- package/package.json +1 -1
- package/dist/prod/data/image-6DWEFZH4.js +0 -1
- /package/dist/dev/{chunk-MW637LK5.js.map → chunk-FAXV62EG.js.map} +0 -0
- /package/dist/dev/{chunk-2Q3FNCU4.js.map → chunk-LAGHQ2FH.js.map} +0 -0
- /package/dist/dev/data/{image-L2UC5LX5.js.map → image-FJMUJMRD.js.map} +0 -0
package/dist/dev/index.js
CHANGED
|
@@ -67,10 +67,10 @@ import {
|
|
|
67
67
|
serializeAsJSON,
|
|
68
68
|
serializeLibraryAsJSON,
|
|
69
69
|
strokeRectWithRotation_simple
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-LAGHQ2FH.js";
|
|
71
71
|
import {
|
|
72
72
|
define_import_meta_env_default
|
|
73
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23920
|
+
}
|
|
23921
|
+
if (ar <= 1 / 3.5) {
|
|
23921
23922
|
return "vertical-strip";
|
|
23922
|
-
|
|
23923
|
+
}
|
|
23924
|
+
if (ar >= 1.6) {
|
|
23923
23925
|
return "landscape";
|
|
23924
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
24048
|
-
|
|
24049
|
-
|
|
24050
|
-
|
|
24051
|
-
|
|
24052
|
-
|
|
24053
|
-
|
|
24054
|
-
|
|
24055
|
-
|
|
24056
|
-
|
|
24057
|
-
|
|
24058
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
24159
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
}, [
|
|
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(
|
|
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: {
|
|
49975
|
-
|
|
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: {
|
|
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: "
|
|
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: ["
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|