@orangecatai/adgen-canvas 0.0.29 → 0.0.31
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-FOD3MMRJ.js → chunk-3SKNYE32.js} +2 -2
- package/dist/dev/{chunk-MKNEZM6D.js → chunk-N472V5Q6.js} +3 -3
- package/dist/dev/data/{image-GKX5OVTE.js → image-C5LHCG7R.js} +3 -3
- package/dist/dev/index.css +3 -0
- package/dist/dev/index.css.map +2 -2
- package/dist/dev/index.js +269 -51
- package/dist/dev/index.js.map +3 -3
- package/dist/dev/subset-shared.chunk.js +1 -1
- package/dist/dev/subset-worker.chunk.js +1 -1
- package/dist/prod/{chunk-45NCDXHS.js → chunk-7Z7DBJZ7.js} +1 -1
- package/dist/prod/{chunk-3ZPJQ4VC.js → chunk-ZRY46W7L.js} +2 -2
- package/dist/prod/data/image-NBSHNEUL.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +67 -54
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/excalidraw/components/AIChatPanel.d.ts +11 -1
- package/dist/types/excalidraw/components/ai-chat/agentLoop.d.ts +18 -1
- package/dist/types/excalidraw/components/ai-chat/canvasTools.d.ts +43 -0
- package/dist/types/excalidraw/components/ai-chat/reviewerAgent.d.ts +14 -1
- package/package.json +1 -1
- package/dist/prod/data/image-6RLFQVTK.js +0 -1
- /package/dist/dev/{chunk-FOD3MMRJ.js.map → chunk-3SKNYE32.js.map} +0 -0
- /package/dist/dev/{chunk-MKNEZM6D.js.map → chunk-N472V5Q6.js.map} +0 -0
- /package/dist/dev/data/{image-GKX5OVTE.js.map → image-C5LHCG7R.js.map} +0 -0
package/dist/dev/index.js
CHANGED
|
@@ -66,10 +66,10 @@ import {
|
|
|
66
66
|
serializeAsJSON,
|
|
67
67
|
serializeLibraryAsJSON,
|
|
68
68
|
strokeRectWithRotation_simple
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-N472V5Q6.js";
|
|
70
70
|
import {
|
|
71
71
|
define_import_meta_env_default
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-3SKNYE32.js";
|
|
73
73
|
import {
|
|
74
74
|
en_default
|
|
75
75
|
} from "./chunk-LIELFD5T.js";
|
|
@@ -9796,7 +9796,7 @@ var exportCanvas = async (type, elements, appState, files, {
|
|
|
9796
9796
|
let blob = canvasToBlob(tempCanvas);
|
|
9797
9797
|
if (appState.exportEmbedScene) {
|
|
9798
9798
|
blob = blob.then(
|
|
9799
|
-
(blob2) => import("./data/image-
|
|
9799
|
+
(blob2) => import("./data/image-C5LHCG7R.js").then(
|
|
9800
9800
|
({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
|
|
9801
9801
|
blob: blob2,
|
|
9802
9802
|
metadata: serializeAsJSON(elements, appState, files, "local")
|
|
@@ -24225,6 +24225,7 @@ async function runReviewerAgent(opts) {
|
|
|
24225
24225
|
// imagePlacement retained in opts type for backward compatibility but unused
|
|
24226
24226
|
// in the image-first flow (the ad is a single full-frame generated image).
|
|
24227
24227
|
userBrief,
|
|
24228
|
+
originalImageDataUrl,
|
|
24228
24229
|
brandContext,
|
|
24229
24230
|
chatUrl,
|
|
24230
24231
|
reviewerModel = "google/gemini-2.5-flash",
|
|
@@ -24244,6 +24245,7 @@ async function runReviewerAgent(opts) {
|
|
|
24244
24245
|
const fontPreviewIsBase64 = typeof fontPreviewDataUrl === "string" && fontPreviewDataUrl.startsWith("data:");
|
|
24245
24246
|
const bodyFontPreviewDataUrl = brandContext?.bodyFontPreviewDataUrl;
|
|
24246
24247
|
const bodyFontPreviewIsBase64 = typeof bodyFontPreviewDataUrl === "string" && bodyFontPreviewDataUrl.startsWith("data:");
|
|
24248
|
+
const originalImageIsBase64 = typeof originalImageDataUrl === "string" && originalImageDataUrl.startsWith("data:");
|
|
24247
24249
|
const contextLines = [
|
|
24248
24250
|
`## Ad Brief (what the user requested)`,
|
|
24249
24251
|
userBrief,
|
|
@@ -24251,6 +24253,13 @@ async function runReviewerAgent(opts) {
|
|
|
24251
24253
|
`## Frame dimensions`,
|
|
24252
24254
|
`Width: ${frame.width}px, Height: ${frame.height}px`
|
|
24253
24255
|
];
|
|
24256
|
+
if (originalImageIsBase64) {
|
|
24257
|
+
contextLines.push(
|
|
24258
|
+
``,
|
|
24259
|
+
`## Original reference image (what the user uploaded)`,
|
|
24260
|
+
`The image the user originally attached is included below (immediately after the ad screenshot). Judge fidelity against it: the generated ad should preserve the subject, composition, and style of this reference unless the brief explicitly asked for a different treatment. Flag it as an issue if the ad has drifted from this reference in ways the brief did not request (wrong subject, different color scheme, altered composition).`
|
|
24261
|
+
);
|
|
24262
|
+
}
|
|
24254
24263
|
if (brandContext) {
|
|
24255
24264
|
const brandSummary = buildReviewerBrandSummary(brandContext);
|
|
24256
24265
|
if (brandSummary) {
|
|
@@ -24261,7 +24270,7 @@ async function runReviewerAgent(opts) {
|
|
|
24261
24270
|
contextLines.push(
|
|
24262
24271
|
``,
|
|
24263
24272
|
`## Brand logo (reference)`,
|
|
24264
|
-
`The brand logo is attached
|
|
24273
|
+
`The brand logo is attached below (after the ad screenshot${originalImageIsBase64 ? " and the original reference image" : ""}). Use it to evaluate whether the logo in the ad matches the brand's actual logo in colour, shape, and typographic treatment.`
|
|
24265
24274
|
);
|
|
24266
24275
|
} else if (logoIsExternalUrl) {
|
|
24267
24276
|
contextLines.push(
|
|
@@ -24292,6 +24301,12 @@ async function runReviewerAgent(opts) {
|
|
|
24292
24301
|
image_url: { url: screenshotDataUrl }
|
|
24293
24302
|
}
|
|
24294
24303
|
];
|
|
24304
|
+
if (originalImageIsBase64 && originalImageDataUrl) {
|
|
24305
|
+
userContent.push({
|
|
24306
|
+
type: "image_url",
|
|
24307
|
+
image_url: { url: originalImageDataUrl }
|
|
24308
|
+
});
|
|
24309
|
+
}
|
|
24295
24310
|
if (logoIsBase64 && logoDataUrl) {
|
|
24296
24311
|
userContent.push({
|
|
24297
24312
|
type: "image_url",
|
|
@@ -26049,7 +26064,7 @@ var SIZE_SCALE_MAP = {
|
|
|
26049
26064
|
};
|
|
26050
26065
|
function buildBrandContextMessage(ctx) {
|
|
26051
26066
|
const lines = [
|
|
26052
|
-
"## Brand Identity \u2014 apply
|
|
26067
|
+
"## Brand Identity \u2014 you MUST apply every guideline below to all ads you create, by default"
|
|
26053
26068
|
];
|
|
26054
26069
|
if (ctx.colors) {
|
|
26055
26070
|
const { primary, secondary, accent, background } = ctx.colors;
|
|
@@ -26124,6 +26139,10 @@ function buildBrandContextMessage(ctx) {
|
|
|
26124
26139
|
lines.push(`**Imagery direction**: ${parts.join(", ")}`);
|
|
26125
26140
|
}
|
|
26126
26141
|
}
|
|
26142
|
+
lines.push(
|
|
26143
|
+
"",
|
|
26144
|
+
"**Precedence**: these are defaults, not absolutes. If the user's request explicitly states or clearly implies something different from a guideline above \u2014 a different color, mood, style, subject treatment, or tone \u2014 follow the user's explicit instruction for that ad instead of the brand guideline it conflicts with. Do not silently override what the user asked for in favor of brand consistency; only apply a guideline where the user's request is silent on that point."
|
|
26145
|
+
);
|
|
26127
26146
|
return lines.join("\n");
|
|
26128
26147
|
}
|
|
26129
26148
|
function buildImageGenSafePrompt(prompt, brandContext, dimensions) {
|
|
@@ -26153,7 +26172,9 @@ function buildImageGenSafePrompt(prompt, brandContext, dimensions) {
|
|
|
26153
26172
|
}
|
|
26154
26173
|
const brandImageHint = brandImageInstructions.length > 0 ? ` ${brandImageInstructions.join(" ")}` : "";
|
|
26155
26174
|
const brandNameHint = brandContext.name ? ` This ad is for the brand "${brandContext.name}" \u2014 all visual elements, copy, and style must reflect this brand exclusively.` : "";
|
|
26156
|
-
return `${prompt}${brandNameHint}${fontHint}${brandImageHint} Render all specified copy as crisp, correctly-spelled, legible text within the image. Maintain generous padding and whitespace \u2014 at minimum 4\u20135% of the frame width as margin on all sides; copy blocks, the logo, and the CTA must each have clear breathing room and must not feel cramped or crowded. Professional advertising quality
|
|
26175
|
+
return `${prompt}${brandNameHint}${fontHint}${brandImageHint} Render all specified copy as crisp, correctly-spelled, legible text within the image. Maintain generous padding and whitespace \u2014 at minimum 4\u20135% of the frame width as margin on all sides; copy blocks, the logo, and the CTA must each have clear breathing room and must not feel cramped or crowded. Professional advertising quality.
|
|
26176
|
+
|
|
26177
|
+
${PRECEDENCE_LINE}`;
|
|
26157
26178
|
}
|
|
26158
26179
|
var clean = (value) => {
|
|
26159
26180
|
const trimmed = value?.trim();
|
|
@@ -56705,6 +56726,39 @@ function execAddText(args, ctx) {
|
|
|
56705
56726
|
statusMessage: `Added text "${args.text.slice(0, 40)}${args.text.length > 40 ? "\u2026" : ""}"`
|
|
56706
56727
|
};
|
|
56707
56728
|
}
|
|
56729
|
+
var frameGenerationMemory = /* @__PURE__ */ new Map();
|
|
56730
|
+
var chatCreatedFrames = [];
|
|
56731
|
+
function getChatCreatedFramesContext() {
|
|
56732
|
+
if (chatCreatedFrames.length === 0)
|
|
56733
|
+
return "";
|
|
56734
|
+
const lines = chatCreatedFrames.map(
|
|
56735
|
+
(f, i) => `${i + 1}. frameId="${f.frameId}"${i === chatCreatedFrames.length - 1 ? " (most recent)" : ""} \u2014 ${f.summary}`
|
|
56736
|
+
);
|
|
56737
|
+
return [
|
|
56738
|
+
`## Frames generated in this conversation (not all frames on the canvas \u2014 the canvas may contain other unrelated images/content)`,
|
|
56739
|
+
...lines,
|
|
56740
|
+
``,
|
|
56741
|
+
`When the user refers to an image ambiguously \u2014 "the previous one," "that image," "the above ad," "fix it" \u2014 without an @mention or a distinguishing description, they mean the MOST RECENT frame in this list. Do NOT call list_frames() to search the whole canvas for it; use the frameId given here directly. Only search more broadly (list_frames) if the user's wording clearly points at something NOT in this list, or if this list has more than one plausible match and the user's description (e.g. "the dog one," "the second one") disambiguates between entries here.`
|
|
56742
|
+
].join("\n");
|
|
56743
|
+
}
|
|
56744
|
+
function resetAndSeedFrameGenerationMemory(messages) {
|
|
56745
|
+
frameGenerationMemory.clear();
|
|
56746
|
+
chatCreatedFrames.length = 0;
|
|
56747
|
+
for (const msg of messages) {
|
|
56748
|
+
const g = msg.generatedFrame;
|
|
56749
|
+
if (!g || frameGenerationMemory.has(g.frameId))
|
|
56750
|
+
continue;
|
|
56751
|
+
frameGenerationMemory.set(g.frameId, {
|
|
56752
|
+
originalPrompt: g.prompt,
|
|
56753
|
+
referenceImageIndex: g.referenceImageIndex,
|
|
56754
|
+
userBrief: g.userBrief
|
|
56755
|
+
});
|
|
56756
|
+
chatCreatedFrames.push({
|
|
56757
|
+
frameId: g.frameId,
|
|
56758
|
+
summary: g.prompt.slice(0, 80) + (g.prompt.length > 80 ? "\u2026" : "")
|
|
56759
|
+
});
|
|
56760
|
+
}
|
|
56761
|
+
}
|
|
56708
56762
|
async function execGenerateImage(args, ctx) {
|
|
56709
56763
|
const frameId = args.frameId;
|
|
56710
56764
|
const prompt = args.prompt;
|
|
@@ -56713,7 +56767,19 @@ async function execGenerateImage(args, ctx) {
|
|
|
56713
56767
|
const width = args.width;
|
|
56714
56768
|
const height = args.height;
|
|
56715
56769
|
const referenceImageIndex = args.referenceImageIndex;
|
|
56716
|
-
const
|
|
56770
|
+
const priorGeneration = frameGenerationMemory.get(frameId);
|
|
56771
|
+
const effectiveReferenceImageIndex = referenceImageIndex !== void 0 ? referenceImageIndex : priorGeneration ? priorGeneration.referenceImageIndex : ctx.fileAttachments && ctx.fileAttachments.length > 0 ? 0 : void 0;
|
|
56772
|
+
const referenceImageDataUrl = effectiveReferenceImageIndex !== void 0 ? ctx.fileAttachments?.[effectiveReferenceImageIndex]?.dataUrl : void 0;
|
|
56773
|
+
const isRegeneration = !!priorGeneration && priorGeneration.originalPrompt !== prompt;
|
|
56774
|
+
const originalPromptAnchor = isRegeneration ? `
|
|
56775
|
+
|
|
56776
|
+
ORIGINAL AD BRIEF (this is a revision, not a new ad \u2014 keep the same subject, composition, and color palette as this original brief unless a fix above explicitly changes it):
|
|
56777
|
+
"${priorGeneration.originalPrompt}"` : "";
|
|
56778
|
+
const userBrief = priorGeneration?.userBrief ?? ctx.originalUserBrief;
|
|
56779
|
+
const userBriefAnchor = userBrief ? `
|
|
56780
|
+
|
|
56781
|
+
USER'S EXACT REQUEST (verbatim, do not paraphrase, simplify, or substitute the specific subject, action, pose, expression, or scene described here \u2014 depict it faithfully even if the description above worded it differently):
|
|
56782
|
+
"${userBrief}"` : "";
|
|
56717
56783
|
const fontNames = [ctx.brandHeadlineFontName, ctx.brandBodyFontName].filter(
|
|
56718
56784
|
(n, i, arr) => Boolean(n) && arr.indexOf(n) === i
|
|
56719
56785
|
);
|
|
@@ -56748,7 +56814,7 @@ LAYOUT & SPACING RULES \u2014 these are non-negotiable:
|
|
|
56748
56814
|
6. NO CLUTTER: The overall composition must feel open and airy \u2014 fewer elements with more space between them is always better than many elements squeezed together.
|
|
56749
56815
|
7. ASPECT RATIO: Do NOT squish, stretch, or distort any element. Maintain the natural proportions of all visuals, logos, and text at all times.
|
|
56750
56816
|
|
|
56751
|
-
Render all text as crisp, correctly-spelled, legible copy. Professional advertising quality \u2014 strong visual hierarchy, brand-consistent, generous whitespace throughout
|
|
56817
|
+
Render all text as crisp, correctly-spelled, legible copy. Professional advertising quality \u2014 strong visual hierarchy, brand-consistent, generous whitespace throughout.${originalPromptAnchor}${userBriefAnchor}`;
|
|
56752
56818
|
if (!frameId || !prompt) {
|
|
56753
56819
|
return {
|
|
56754
56820
|
success: false,
|
|
@@ -56892,6 +56958,19 @@ Render all text as crisp, correctly-spelled, legible copy. Professional advertis
|
|
|
56892
56958
|
lastRetrieved: Date.now()
|
|
56893
56959
|
}
|
|
56894
56960
|
]);
|
|
56961
|
+
if (!priorGeneration) {
|
|
56962
|
+
frameGenerationMemory.set(frameId, {
|
|
56963
|
+
originalPrompt: prompt,
|
|
56964
|
+
referenceImageIndex: effectiveReferenceImageIndex,
|
|
56965
|
+
userBrief
|
|
56966
|
+
});
|
|
56967
|
+
chatCreatedFrames.push({
|
|
56968
|
+
frameId,
|
|
56969
|
+
summary: prompt.slice(0, 80) + (prompt.length > 80 ? "\u2026" : "")
|
|
56970
|
+
});
|
|
56971
|
+
} else if (effectiveReferenceImageIndex !== void 0) {
|
|
56972
|
+
priorGeneration.referenceImageIndex = effectiveReferenceImageIndex;
|
|
56973
|
+
}
|
|
56895
56974
|
return {
|
|
56896
56975
|
success: true,
|
|
56897
56976
|
data: {
|
|
@@ -56901,7 +56980,8 @@ Render all text as crisp, correctly-spelled, legible copy. Professional advertis
|
|
|
56901
56980
|
x: Math.round(absX),
|
|
56902
56981
|
y: Math.round(absY),
|
|
56903
56982
|
width: Math.round(width),
|
|
56904
|
-
height: Math.round(height)
|
|
56983
|
+
height: Math.round(height),
|
|
56984
|
+
...effectiveReferenceImageIndex !== void 0 ? { referenceImageIndex: effectiveReferenceImageIndex } : {}
|
|
56905
56985
|
},
|
|
56906
56986
|
statusMessage: `Generated image from prompt "${prompt.slice(0, 50)}${prompt.length > 50 ? "\u2026" : ""}"`
|
|
56907
56987
|
};
|
|
@@ -58374,6 +58454,18 @@ function isStreamingResponse(response) {
|
|
|
58374
58454
|
}
|
|
58375
58455
|
|
|
58376
58456
|
// components/ai-chat/agentLoop.ts
|
|
58457
|
+
function isRasterImageDataUrl(dataUrl) {
|
|
58458
|
+
return /^data:image\/(png|jpe?g|gif|webp);base64,/i.test(dataUrl);
|
|
58459
|
+
}
|
|
58460
|
+
function dataUrlByteSize(dataUrl) {
|
|
58461
|
+
const commaIdx = dataUrl.indexOf(",");
|
|
58462
|
+
const b64 = commaIdx >= 0 ? dataUrl.slice(commaIdx + 1) : dataUrl;
|
|
58463
|
+
return Math.floor(b64.length * 3 / 4);
|
|
58464
|
+
}
|
|
58465
|
+
var MAX_REQUEST_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
58466
|
+
var BRAND_CONTEXT_IMAGE_BUDGET = MAX_REQUEST_IMAGE_BYTES / 2;
|
|
58467
|
+
var PER_BRAND_IMAGE_BYTE_CAP = 600 * 1024;
|
|
58468
|
+
var MAX_TOTAL_IMAGES = 18;
|
|
58377
58469
|
var SYSTEM_PROMPT = `You are an expert advertising art director embedded inside a canvas editor. You create finished advertisement creatives by generating a single complete image \u2014 visuals AND all copy baked directly into the image \u2014 using an AI image model. You do NOT assemble ads from separate text/shape layers.
|
|
58378
58470
|
|
|
58379
58471
|
## Core principle \u2014 ONE image IS the ad
|
|
@@ -58389,7 +58481,7 @@ Check: is the user asking to change something in an ad that already exists on th
|
|
|
58389
58481
|
Triggers: "change the CTA", "update the headline", "change the button text", "make the headline say X", "use a different background", "change the colors", "different photo", etc.
|
|
58390
58482
|
|
|
58391
58483
|
**For a from-scratch (image-generated) ad, the copy is baked into the image \u2014 it CANNOT be patched element-by-element.** To change anything (text or visuals):
|
|
58392
|
-
1. **Find the frame** \u2014 use the frameId from an @mention or
|
|
58484
|
+
1. **Find the frame** \u2014 use the frameId from an @mention if present. Otherwise, for a vague reference like "the previous one", "that image", "the above ad", or "fix it", use the most recent frameId from the "Frames generated in this conversation" list provided to you \u2014 do NOT call \`list_frames()\` for this, the canvas may contain other unrelated frames that list excludes on purpose. Only call \`list_frames()\` when that list is empty or genuinely doesn't cover what the user means.
|
|
58393
58485
|
2. **Duplicate the frame to a fresh, non-overlapping position** (you keep the original intact).
|
|
58394
58486
|
3. Call \`generate_image\` on the duplicate with the SAME prompt as the original, modified with the requested change (new copy text, new visual direction, etc.).
|
|
58395
58487
|
4. Call \`finalize_ad\` on the new frame.
|
|
@@ -58431,11 +58523,13 @@ The \`generate_image\` prompt is the ONLY creative call \u2014 it must produce t
|
|
|
58431
58523
|
- Sign-off / brand name: exact words.
|
|
58432
58524
|
Spell every word exactly as given; do not paraphrase on-image text.
|
|
58433
58525
|
|
|
58434
|
-
2. **BRAND COLORS** (use exact hex values from the Brand Identity context when present): which color is the background, the headline, the CTA button, accents.
|
|
58526
|
+
2. **BRAND COLORS** (use exact hex values from the Brand Identity context when present, by default): which color is the background, the headline, the CTA button, accents.
|
|
58435
58527
|
|
|
58436
|
-
3. **TYPOGRAPHY STYLE** (described in words; the image model cannot load font files): describe the feel \u2014 e.g. "bold condensed sans-serif headline, clean body text", matching the brand typography from the Brand Identity context.
|
|
58528
|
+
3. **TYPOGRAPHY STYLE** (described in words; the image model cannot load font files): describe the feel \u2014 e.g. "bold condensed sans-serif headline, clean body text", matching the brand typography from the Brand Identity context, by default.
|
|
58437
58529
|
|
|
58438
|
-
|
|
58530
|
+
**Brand guidelines vs. the user's request \u2014 precedence:** Brand Identity guidelines (colors, typography, visual style, tone) are defaults that apply where the user hasn't said otherwise. If the user's message explicitly asks for something that conflicts with a guideline \u2014 a different color, a different mood or style, a different tone \u2014 follow the user's explicit instruction for that ad. Do not silently revert to brand colors/style/tone over what the user actually asked for. This does not apply to the brand logo or brand font files (item 6 below) \u2014 those are always reproduced faithfully regardless of the rest of the prompt, since omitting or altering the logo isn't a stylistic choice the user is likely to have intended just by not mentioning it.
|
|
58531
|
+
|
|
58532
|
+
4. **VISUAL DIRECTION:** composition and layout (where the subject sits, where copy sits), lighting, mood, and emotional tone from the brief and \`imagery\` / \`visualStyle\`. If the user described a specific subject, action, pose, expression, object, or animal (e.g. "boy playing guitar, dog watching with one ear up, amused"), you MUST depict that exact scene \u2014 do not invent, substitute, or simplify it into something else (e.g. do not turn "playing guitar" into "walking"). Preserve every specific detail the user gave you; only add the brand/style/spacing layers on top of it.
|
|
58439
58533
|
|
|
58440
58534
|
5. **DIMENSIONS / ASPECT:** describe the format so the composition fits (e.g. "square 1080\xD71080 social post", "landscape 1200\xD7628 banner", "portrait 1080\xD71920 story").
|
|
58441
58535
|
|
|
@@ -58611,8 +58705,12 @@ async function runAgentLoop(opts) {
|
|
|
58611
58705
|
allImageAttachments.push(img);
|
|
58612
58706
|
}
|
|
58613
58707
|
}
|
|
58708
|
+
const fullUserBrief = userMessages.filter(
|
|
58709
|
+
(m) => m.role === "user" && typeof m.content === "string" && m.content.trim().length > 0
|
|
58710
|
+
).map((m) => m.content).join("\n\n");
|
|
58614
58711
|
const augmentedToolCtx = {
|
|
58615
58712
|
...toolCtx,
|
|
58713
|
+
...fullUserBrief ? { originalUserBrief: fullUserBrief } : {},
|
|
58616
58714
|
// Lets long-running tools (video gen) push interim progress lines into the
|
|
58617
58715
|
// same status indicator the loop itself writes to.
|
|
58618
58716
|
onStatus: (message) => onUpdate({ type: "status", message }),
|
|
@@ -58645,6 +58743,12 @@ async function runAgentLoop(opts) {
|
|
|
58645
58743
|
${elementContext}`
|
|
58646
58744
|
});
|
|
58647
58745
|
}
|
|
58746
|
+
const chatFramesContext = getChatCreatedFramesContext();
|
|
58747
|
+
if (chatFramesContext) {
|
|
58748
|
+
messages.push({ role: "system", content: chatFramesContext });
|
|
58749
|
+
}
|
|
58750
|
+
let brandContextBytesUsed = 0;
|
|
58751
|
+
let brandContextImagesUsed = 0;
|
|
58648
58752
|
{
|
|
58649
58753
|
const lastUserText = [...userMessages].reverse().find((m) => m.role === "user")?.content ?? "";
|
|
58650
58754
|
if (lastUserText && (toolCtx.onSearchBrandAssets || toolCtx.onSearchAdCreatives || toolCtx.onListBrandTemplates)) {
|
|
@@ -58658,9 +58762,11 @@ ${elementContext}`
|
|
|
58658
58762
|
toolCtx.onSearchAdCreatives ? toolCtx.onSearchAdCreatives(lastUserText) : Promise.resolve([]),
|
|
58659
58763
|
toolCtx.onListBrandTemplates ? toolCtx.onListBrandTemplates(keywords) : Promise.resolve([])
|
|
58660
58764
|
]);
|
|
58661
|
-
const top2Assets = (assetRes.status === "fulfilled" ? assetRes.value : []).filter((a) => a.previewDataUrl).slice(0, 2);
|
|
58662
|
-
const top2Creatives = (creativeRes.status === "fulfilled" ? creativeRes.value : []).filter((c) => c.previewDataUrl).slice(0, 2);
|
|
58663
|
-
const top2Templates = (templateRes.status === "fulfilled" ? templateRes.value : []).filter(
|
|
58765
|
+
const top2Assets = (assetRes.status === "fulfilled" ? assetRes.value : []).filter((a) => a.previewDataUrl && isRasterImageDataUrl(a.previewDataUrl)).slice(0, 2);
|
|
58766
|
+
const top2Creatives = (creativeRes.status === "fulfilled" ? creativeRes.value : []).filter((c) => c.previewDataUrl && isRasterImageDataUrl(c.previewDataUrl)).slice(0, 2);
|
|
58767
|
+
const top2Templates = (templateRes.status === "fulfilled" ? templateRes.value : []).filter(
|
|
58768
|
+
(t2) => t2.thumbnailDataUrl && isRasterImageDataUrl(t2.thumbnailDataUrl)
|
|
58769
|
+
).slice(0, 2);
|
|
58664
58770
|
console.log(
|
|
58665
58771
|
`[agent:pre-fetch] brand assets=${top2Assets.length}/${assetRes.status === "fulfilled" ? assetRes.value.length : "err"} ad-creatives=${top2Creatives.length}/${creativeRes.status === "fulfilled" ? creativeRes.value.length : "err"} templates=${top2Templates.length}/${templateRes.status === "fulfilled" ? templateRes.value.length : "err"}`
|
|
58666
58772
|
);
|
|
@@ -58678,8 +58784,27 @@ ${elementContext}`
|
|
|
58678
58784
|
dataUrl: t2.thumbnailDataUrl
|
|
58679
58785
|
}))
|
|
58680
58786
|
];
|
|
58681
|
-
|
|
58682
|
-
|
|
58787
|
+
const budgetedPreviews = [];
|
|
58788
|
+
for (const p of previews) {
|
|
58789
|
+
const size = dataUrlByteSize(p.dataUrl);
|
|
58790
|
+
if (size > PER_BRAND_IMAGE_BYTE_CAP) {
|
|
58791
|
+
console.warn(
|
|
58792
|
+
`[agent:pre-fetch] dropping "${p.label}" \u2014 ${Math.round(size / 1024)}KB exceeds the per-image cap (${Math.round(PER_BRAND_IMAGE_BYTE_CAP / 1024)}KB)`
|
|
58793
|
+
);
|
|
58794
|
+
continue;
|
|
58795
|
+
}
|
|
58796
|
+
if (brandContextBytesUsed + size > BRAND_CONTEXT_IMAGE_BUDGET || brandContextImagesUsed >= MAX_TOTAL_IMAGES) {
|
|
58797
|
+
console.warn(
|
|
58798
|
+
`[agent:pre-fetch] dropping "${p.label}" \u2014 brand-context image budget exhausted`
|
|
58799
|
+
);
|
|
58800
|
+
continue;
|
|
58801
|
+
}
|
|
58802
|
+
brandContextBytesUsed += size;
|
|
58803
|
+
brandContextImagesUsed += 1;
|
|
58804
|
+
budgetedPreviews.push(p);
|
|
58805
|
+
}
|
|
58806
|
+
if (budgetedPreviews.length > 0) {
|
|
58807
|
+
budgetedPreviews.forEach(
|
|
58683
58808
|
(p) => console.log(
|
|
58684
58809
|
`[agent:pre-fetch] injecting "${p.label}" as base64 image \u2014 ${Math.round(p.dataUrl.length / 1024)}KB (${p.dataUrl.slice(
|
|
58685
58810
|
0,
|
|
@@ -58690,7 +58815,7 @@ ${elementContext}`
|
|
|
58690
58815
|
messages.push({
|
|
58691
58816
|
role: "system",
|
|
58692
58817
|
content: `BRAND REFERENCES (auto-fetched, up to 2 per category):
|
|
58693
|
-
${
|
|
58818
|
+
${budgetedPreviews.map((p) => `\u2022 ${p.label}`).join(
|
|
58694
58819
|
"\n"
|
|
58695
58820
|
)}
|
|
58696
58821
|
|
|
@@ -58701,9 +58826,9 @@ Visual previews follow in the next message. Use them as context: match their sty
|
|
|
58701
58826
|
content: [
|
|
58702
58827
|
{
|
|
58703
58828
|
type: "text",
|
|
58704
|
-
text: `Brand reference previews (${
|
|
58829
|
+
text: `Brand reference previews (${budgetedPreviews.length} total):`
|
|
58705
58830
|
},
|
|
58706
|
-
...
|
|
58831
|
+
...budgetedPreviews.map((p) => ({
|
|
58707
58832
|
type: "image_url",
|
|
58708
58833
|
image_url: { url: p.dataUrl }
|
|
58709
58834
|
}))
|
|
@@ -58711,17 +58836,57 @@ Visual previews follow in the next message. Use them as context: match their sty
|
|
|
58711
58836
|
});
|
|
58712
58837
|
messages.push({
|
|
58713
58838
|
role: "assistant",
|
|
58714
|
-
content: `Got it \u2014 I can see ${
|
|
58839
|
+
content: `Got it \u2014 I can see ${budgetedPreviews.length} brand reference(s): ${budgetedPreviews.map((p) => p.label).join(", ")}. I'll use these as visual context for the ad.`
|
|
58715
58840
|
});
|
|
58716
58841
|
}
|
|
58717
58842
|
}
|
|
58718
58843
|
}
|
|
58844
|
+
const frameScreenshotBytes = frameScreenshot ? dataUrlByteSize(frameScreenshot) : 0;
|
|
58845
|
+
const currentSendImagesForBudget = fileAttachments?.filter((a) => a.type === "image" && a.dataUrl) ?? [];
|
|
58846
|
+
const currentSendImageBytes = currentSendImagesForBudget.reduce(
|
|
58847
|
+
(sum2, a) => sum2 + dataUrlByteSize(a.dataUrl),
|
|
58848
|
+
0
|
|
58849
|
+
);
|
|
58850
|
+
const currentSendImageCount = currentSendImagesForBudget.length + (frameScreenshot ? 1 : 0);
|
|
58851
|
+
const remainingBudgetBytes = MAX_REQUEST_IMAGE_BYTES - brandContextBytesUsed;
|
|
58852
|
+
const remainingBudgetImages = MAX_TOTAL_IMAGES - brandContextImagesUsed;
|
|
58853
|
+
if (currentSendImageBytes + frameScreenshotBytes > remainingBudgetBytes || currentSendImageCount > remainingBudgetImages) {
|
|
58854
|
+
const message = "This message is too large to process \u2014 try attaching fewer or smaller images.";
|
|
58855
|
+
console.warn(
|
|
58856
|
+
`[agent:budget] aborting \u2014 current-send content alone (${Math.round(
|
|
58857
|
+
(currentSendImageBytes + frameScreenshotBytes) / 1024
|
|
58858
|
+
)}KB, ${currentSendImageCount} image(s)) exceeds the remaining request budget`
|
|
58859
|
+
);
|
|
58860
|
+
onUpdate({ type: "final", message });
|
|
58861
|
+
return { reply: message, toolActions: [] };
|
|
58862
|
+
}
|
|
58863
|
+
let historicalBudgetBytes = remainingBudgetBytes - currentSendImageBytes - frameScreenshotBytes;
|
|
58864
|
+
let historicalBudgetImages = remainingBudgetImages - currentSendImageCount;
|
|
58865
|
+
const droppedHistoricalMessageIndices = /* @__PURE__ */ new Set();
|
|
58866
|
+
for (let i = userMessages.length - 1; i >= 0; i--) {
|
|
58867
|
+
const imgs = userMessages[i].imageAttachments;
|
|
58868
|
+
if (!imgs || imgs.length === 0)
|
|
58869
|
+
continue;
|
|
58870
|
+
const bytes = imgs.reduce(
|
|
58871
|
+
(sum2, img) => sum2 + dataUrlByteSize(img.dataUrl),
|
|
58872
|
+
0
|
|
58873
|
+
);
|
|
58874
|
+
if (bytes <= historicalBudgetBytes && imgs.length <= historicalBudgetImages) {
|
|
58875
|
+
historicalBudgetBytes -= bytes;
|
|
58876
|
+
historicalBudgetImages -= imgs.length;
|
|
58877
|
+
} else {
|
|
58878
|
+
droppedHistoricalMessageIndices.add(i);
|
|
58879
|
+
console.warn(
|
|
58880
|
+
`[agent:budget] dropping ${imgs.length} historical image(s) from an earlier turn (${Math.round(bytes / 1024)}KB) \u2014 request budget exceeded`
|
|
58881
|
+
);
|
|
58882
|
+
}
|
|
58883
|
+
}
|
|
58719
58884
|
for (let i = 0; i < userMessages.length; i++) {
|
|
58720
58885
|
const msg = userMessages[i];
|
|
58721
58886
|
const isLast = i === userMessages.length - 1;
|
|
58722
58887
|
const lastMsgImages = isLast && fileAttachments ? fileAttachments.filter((a) => a.type === "image" && a.dataUrl) : [];
|
|
58723
58888
|
const lastMsgPdfs = isLast && fileAttachments ? fileAttachments.filter((a) => a.type === "pdf" && a.dataUrl) : [];
|
|
58724
|
-
const historicalImages2 = msg.imageAttachments ?? [];
|
|
58889
|
+
const historicalImages2 = droppedHistoricalMessageIndices.has(i) ? [] : msg.imageAttachments ?? [];
|
|
58725
58890
|
const hasImages = historicalImages2.length > 0 || lastMsgImages.length > 0;
|
|
58726
58891
|
const hasFrameScreenshot = isLast && !!frameScreenshot;
|
|
58727
58892
|
const hasTextFiles = isLast && fileAttachments && fileAttachments.some((a) => a.type === "text" || a.type === "docx");
|
|
@@ -58807,7 +58972,16 @@ ${f.textContent}`).join("\n\n")}`;
|
|
|
58807
58972
|
if (lastCreatedFrameId) {
|
|
58808
58973
|
adGenShimmerStore.stopGenerating(lastCreatedFrameId);
|
|
58809
58974
|
}
|
|
58810
|
-
return {
|
|
58975
|
+
return {
|
|
58976
|
+
reply,
|
|
58977
|
+
toolActions,
|
|
58978
|
+
frameGeneration: imageGenData ? {
|
|
58979
|
+
frameId: imageGenData.frameId,
|
|
58980
|
+
prompt: imageGenData.prompt,
|
|
58981
|
+
referenceImageIndex: imageGenData.referenceImageIndex,
|
|
58982
|
+
userBrief: fullUserBrief
|
|
58983
|
+
} : void 0
|
|
58984
|
+
};
|
|
58811
58985
|
}
|
|
58812
58986
|
messages.push({
|
|
58813
58987
|
role: "assistant",
|
|
@@ -58936,7 +59110,9 @@ ${f.textContent}`).join("\n\n")}`;
|
|
|
58936
59110
|
x: result.data.x,
|
|
58937
59111
|
y: result.data.y,
|
|
58938
59112
|
width: result.data.width,
|
|
58939
|
-
height: result.data.height
|
|
59113
|
+
height: result.data.height,
|
|
59114
|
+
referenceImageIndex: result.data.referenceImageIndex,
|
|
59115
|
+
prompt: parsedArgs.prompt ?? ""
|
|
58940
59116
|
};
|
|
58941
59117
|
if (onAfterImageGen) {
|
|
58942
59118
|
onAfterImageGen();
|
|
@@ -58998,25 +59174,45 @@ Use the frameId="${imageGenData.frameId}" for all elements. Keep all elements wi
|
|
|
58998
59174
|
]);
|
|
58999
59175
|
const assetSearchResult = result;
|
|
59000
59176
|
if (searchToolNames.has(name) && result.success && assetSearchResult.previewDataUrls?.length) {
|
|
59001
|
-
const
|
|
59002
|
-
|
|
59003
|
-
|
|
59004
|
-
|
|
59005
|
-
|
|
59006
|
-
|
|
59007
|
-
|
|
59008
|
-
|
|
59009
|
-
|
|
59010
|
-
|
|
59011
|
-
|
|
59012
|
-
|
|
59013
|
-
|
|
59014
|
-
|
|
59015
|
-
|
|
59016
|
-
|
|
59017
|
-
|
|
59018
|
-
|
|
59019
|
-
}
|
|
59177
|
+
const budgetedPreviews = [];
|
|
59178
|
+
for (const p of assetSearchResult.previewDataUrls) {
|
|
59179
|
+
const size = dataUrlByteSize(p.dataUrl);
|
|
59180
|
+
if (size > PER_BRAND_IMAGE_BYTE_CAP) {
|
|
59181
|
+
console.warn(
|
|
59182
|
+
`[agent:mid-loop] dropping "${p.name}" \u2014 ${Math.round(size / 1024)}KB exceeds the per-image cap`
|
|
59183
|
+
);
|
|
59184
|
+
continue;
|
|
59185
|
+
}
|
|
59186
|
+
if (brandContextBytesUsed + size > BRAND_CONTEXT_IMAGE_BUDGET || brandContextImagesUsed >= MAX_TOTAL_IMAGES) {
|
|
59187
|
+
console.warn(
|
|
59188
|
+
`[agent:mid-loop] dropping "${p.name}" \u2014 brand-context image budget exhausted`
|
|
59189
|
+
);
|
|
59190
|
+
continue;
|
|
59191
|
+
}
|
|
59192
|
+
brandContextBytesUsed += size;
|
|
59193
|
+
brandContextImagesUsed += 1;
|
|
59194
|
+
budgetedPreviews.push(p);
|
|
59195
|
+
}
|
|
59196
|
+
if (budgetedPreviews.length > 0) {
|
|
59197
|
+
console.log(
|
|
59198
|
+
`[agent:mid-loop] ${name} injecting ${budgetedPreviews.length} preview image(s) as base64 vision content: ${budgetedPreviews.map(
|
|
59199
|
+
(p) => `"${p.name}" (${Math.round(p.dataUrl.length / 1024)}KB)`
|
|
59200
|
+
).join(", ")}`
|
|
59201
|
+
);
|
|
59202
|
+
messages.push({
|
|
59203
|
+
role: "user",
|
|
59204
|
+
content: [
|
|
59205
|
+
{
|
|
59206
|
+
type: "text",
|
|
59207
|
+
text: `Visual previews for the ${budgetedPreviews.length} result(s) from ${name}:`
|
|
59208
|
+
},
|
|
59209
|
+
...budgetedPreviews.map((p) => ({
|
|
59210
|
+
type: "image_url",
|
|
59211
|
+
image_url: { url: p.dataUrl }
|
|
59212
|
+
}))
|
|
59213
|
+
]
|
|
59214
|
+
});
|
|
59215
|
+
}
|
|
59020
59216
|
}
|
|
59021
59217
|
if (name === "generate_image" && result.success && imageGenData) {
|
|
59022
59218
|
const autoFinalizeId = `auto_finalize_${Date.now()}`;
|
|
@@ -59074,6 +59270,7 @@ Use the frameId="${imageGenData.frameId}" for all elements. Keep all elements wi
|
|
|
59074
59270
|
let feedback;
|
|
59075
59271
|
try {
|
|
59076
59272
|
const frameEl = augmentedToolCtx.excalidrawAPI.getSceneElements().find((e) => e.id === imageGenData.frameId);
|
|
59273
|
+
const originalImageDataUrl = imageGenData.referenceImageIndex !== void 0 ? allImageAttachments[imageGenData.referenceImageIndex]?.dataUrl : void 0;
|
|
59077
59274
|
feedback = await runReviewerAgent({
|
|
59078
59275
|
screenshotDataUrl: bannerScreenshot,
|
|
59079
59276
|
htmlSource: "",
|
|
@@ -59088,7 +59285,12 @@ Use the frameId="${imageGenData.frameId}" for all elements. Keep all elements wi
|
|
|
59088
59285
|
width: imageGenData.width,
|
|
59089
59286
|
height: imageGenData.height
|
|
59090
59287
|
},
|
|
59091
|
-
|
|
59288
|
+
// Full conversation's user turns, not just userMessages[0] —
|
|
59289
|
+
// the actual creative brief is frequently NOT the first
|
|
59290
|
+
// message (e.g. "hi" -> clarifying questions -> the message
|
|
59291
|
+
// that actually describes the ad). See fullUserBrief above.
|
|
59292
|
+
userBrief: fullUserBrief,
|
|
59293
|
+
originalImageDataUrl,
|
|
59092
59294
|
brandContext,
|
|
59093
59295
|
chatUrl,
|
|
59094
59296
|
reviewerModel: reviewerModel ?? chatModel,
|
|
@@ -59123,7 +59325,7 @@ Use the frameId="${imageGenData.frameId}" for all elements. Keep all elements wi
|
|
|
59123
59325
|
"",
|
|
59124
59326
|
`Overall verdict: ${feedback.summary}`,
|
|
59125
59327
|
"",
|
|
59126
|
-
`Apply ALL fixes by calling generate_image AGAIN on frameId="${imageGenData.frameId}" with a revised prompt
|
|
59328
|
+
`Apply ALL fixes by calling generate_image AGAIN on frameId="${imageGenData.frameId}" with a revised prompt. This is a REVISION of the existing ad, not a new one: start from the exact prompt you used last time and change ONLY what the fixes above require \u2014 keep the same subject, composition, color palette, and any reference image untouched otherwise. Do NOT invent a new color scheme, layout, or brand style. If a fix mentions a specific position (e.g. "top-right", "bottom-left"), use that exact position. Prioritise critical issues first. Do NOT call generate_html_banner or add separate text/shape layers \u2014 the ad is a single generated image.`
|
|
59127
59329
|
].join("\n");
|
|
59128
59330
|
messages.push({
|
|
59129
59331
|
role: "user",
|
|
@@ -59188,7 +59390,16 @@ Describe the final ad to the user and stop.`
|
|
|
59188
59390
|
if (lastCreatedFrameId) {
|
|
59189
59391
|
adGenShimmerStore.stopGenerating(lastCreatedFrameId);
|
|
59190
59392
|
}
|
|
59191
|
-
return {
|
|
59393
|
+
return {
|
|
59394
|
+
reply: fallbackReply,
|
|
59395
|
+
toolActions,
|
|
59396
|
+
frameGeneration: imageGenData ? {
|
|
59397
|
+
frameId: imageGenData.frameId,
|
|
59398
|
+
prompt: imageGenData.prompt,
|
|
59399
|
+
referenceImageIndex: imageGenData.referenceImageIndex,
|
|
59400
|
+
userBrief: fullUserBrief
|
|
59401
|
+
} : void 0
|
|
59402
|
+
};
|
|
59192
59403
|
} catch (err) {
|
|
59193
59404
|
if (lastCreatedFrameId) {
|
|
59194
59405
|
adGenShimmerStore.stopGenerating(lastCreatedFrameId);
|
|
@@ -59663,9 +59874,11 @@ var AIChatPanel = React69.forwardRef(
|
|
|
59663
59874
|
const [prompt, setPrompt] = useState60("");
|
|
59664
59875
|
const [isLoading, setIsLoading] = useState60(false);
|
|
59665
59876
|
const [statusText, setStatusText] = useState60("");
|
|
59666
|
-
const [messages, setMessages] = useState60(
|
|
59667
|
-
initialMessages ?? []
|
|
59668
|
-
|
|
59877
|
+
const [messages, setMessages] = useState60(() => {
|
|
59878
|
+
const initial = initialMessages ?? [];
|
|
59879
|
+
resetAndSeedFrameGenerationMemory(initial);
|
|
59880
|
+
return initial;
|
|
59881
|
+
});
|
|
59669
59882
|
const [sessions, setSessions] = useState60(
|
|
59670
59883
|
initialSessions ?? []
|
|
59671
59884
|
);
|
|
@@ -59771,6 +59984,7 @@ var AIChatPanel = React69.forwardRef(
|
|
|
59771
59984
|
saveCurrentSession();
|
|
59772
59985
|
const newSessionId = genId();
|
|
59773
59986
|
setMessages([]);
|
|
59987
|
+
resetAndSeedFrameGenerationMemory([]);
|
|
59774
59988
|
setCurrentSessionId(newSessionId);
|
|
59775
59989
|
setIsLoadingSession(false);
|
|
59776
59990
|
setHistoryOpen(false);
|
|
@@ -59788,9 +60002,11 @@ var AIChatPanel = React69.forwardRef(
|
|
|
59788
60002
|
setError(null);
|
|
59789
60003
|
if (session.messages.length > 0) {
|
|
59790
60004
|
setMessages(session.messages);
|
|
60005
|
+
resetAndSeedFrameGenerationMemory(session.messages);
|
|
59791
60006
|
setIsLoadingSession(false);
|
|
59792
60007
|
} else {
|
|
59793
60008
|
setMessages([]);
|
|
60009
|
+
resetAndSeedFrameGenerationMemory([]);
|
|
59794
60010
|
setIsLoadingSession(true);
|
|
59795
60011
|
if (onSessionSwitch) {
|
|
59796
60012
|
onSessionSwitch(session.id);
|
|
@@ -60389,7 +60605,8 @@ var AIChatPanel = React69.forwardRef(
|
|
|
60389
60605
|
content: result.reply,
|
|
60390
60606
|
timestamp: /* @__PURE__ */ new Date(),
|
|
60391
60607
|
toolActions: result.toolActions.length > 0 ? result.toolActions : void 0,
|
|
60392
|
-
reviewFeedback: lastReviewFeedbackRef.current ?? void 0
|
|
60608
|
+
reviewFeedback: lastReviewFeedbackRef.current ?? void 0,
|
|
60609
|
+
generatedFrame: result.frameGeneration
|
|
60393
60610
|
};
|
|
60394
60611
|
const updatedMessagesAgent = [...nextMessages, assistantMsgAgent];
|
|
60395
60612
|
setMessages(updatedMessagesAgent);
|
|
@@ -60463,6 +60680,7 @@ var AIChatPanel = React69.forwardRef(
|
|
|
60463
60680
|
});
|
|
60464
60681
|
if (targetSessionId === currentSessionIdRef.current) {
|
|
60465
60682
|
setMessages(msgs);
|
|
60683
|
+
resetAndSeedFrameGenerationMemory(msgs);
|
|
60466
60684
|
setIsLoadingSession(false);
|
|
60467
60685
|
}
|
|
60468
60686
|
},
|