@orangecatai/adgen-canvas 0.0.13 → 0.0.15

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-SL25BHFI.js";
70
+ } from "./chunk-FGSZO5WI.js";
71
71
  import {
72
72
  define_import_meta_env_default
73
- } from "./chunk-SNMJI5ML.js";
73
+ } from "./chunk-EL4HTLYE.js";
74
74
  import {
75
75
  en_default
76
76
  } from "./chunk-IFMURN5W.js";
@@ -84,7 +84,7 @@ import {
84
84
  } from "./chunk-XDFCUUT6.js";
85
85
 
86
86
  // index.tsx
87
- import React64, { useEffect as useEffect62 } from "react";
87
+ import React64, { useEffect as useEffect62, useMemo as useMemo15 } from "react";
88
88
  import { DEFAULT_UI_OPTIONS, isShallowEqual as isShallowEqual10 } from "@orangecatai/common";
89
89
 
90
90
  // components/App.tsx
@@ -5250,7 +5250,8 @@ import React10, {
5250
5250
  useState as useState6,
5251
5251
  useRef as useRef9,
5252
5252
  useEffect as useEffect10,
5253
- useCallback as useCallback3
5253
+ useCallback as useCallback3,
5254
+ useSyncExternalStore
5254
5255
  } from "react";
5255
5256
  import {
5256
5257
  arrayToList,
@@ -5526,6 +5527,10 @@ var FontPickerList = React10.memo(
5526
5527
  const stylesPanelMode = useStylesPanelMode();
5527
5528
  const [searchTerm, setSearchTerm] = useState6("");
5528
5529
  const inputRef = useRef9(null);
5530
+ const customFontVersion = useSyncExternalStore(
5531
+ Fonts.subscribeToCustomFonts,
5532
+ Fonts.getCustomFontVersion
5533
+ );
5529
5534
  const allFonts = useMemo(
5530
5535
  () => Array.from(Fonts.registered.entries()).filter(
5531
5536
  ([_, { metadata }]) => !metadata.private && !metadata.fallback
@@ -5548,7 +5553,7 @@ var FontPickerList = React10.memo(
5548
5553
  }).sort(
5549
5554
  (a, b) => a.text.toLowerCase() > b.text.toLowerCase() ? 1 : -1
5550
5555
  ),
5551
- []
5556
+ [customFontVersion]
5552
5557
  );
5553
5558
  const sceneFamilies = useMemo(
5554
5559
  () => new Set(fonts.getSceneFamilies()),
@@ -9765,7 +9770,7 @@ var exportCanvas = async (type, elements, appState, files, {
9765
9770
  let blob = canvasToBlob(tempCanvas);
9766
9771
  if (appState.exportEmbedScene) {
9767
9772
  blob = blob.then(
9768
- (blob2) => import("./data/image-AVAYTNBV.js").then(
9773
+ (blob2) => import("./data/image-AVR5GHYZ.js").then(
9769
9774
  ({ encodePngMetadata: encodePngMetadata2 }) => encodePngMetadata2({
9770
9775
  blob: blob2,
9771
9776
  metadata: serializeAsJSON(elements, appState, files, "local")
@@ -26139,13 +26144,31 @@ var TemplateBuilderPanelHost = ({
26139
26144
  }
26140
26145
  const frame = framesToSave[0];
26141
26146
  const frameChildren = getFrameChildren5(allElements, frame.id);
26147
+ const files = app.files;
26148
+ const templateFiles = {};
26149
+ for (const el of frameChildren) {
26150
+ const imgEl = el;
26151
+ if (el.type === "image" && imgEl.fileId) {
26152
+ const file2 = files[imgEl.fileId];
26153
+ if (file2?.dataURL) {
26154
+ templateFiles[imgEl.fileId] = {
26155
+ dataURL: file2.dataURL,
26156
+ mimeType: file2.mimeType
26157
+ };
26158
+ }
26159
+ }
26160
+ }
26142
26161
  onSaveTemplate({
26143
26162
  name: panelState.templateName,
26144
26163
  campaignTag: panelState.campaignTag || void 0,
26145
26164
  label: `${Math.round(frame.width)}x${Math.round(frame.height)}`,
26146
26165
  width: Math.round(frame.width),
26147
26166
  height: Math.round(frame.height),
26148
- canvasJson: JSON.stringify({ frame: { ...frame }, elements: frameChildren })
26167
+ canvasJson: JSON.stringify({
26168
+ frame: { ...frame },
26169
+ elements: frameChildren,
26170
+ ...Object.keys(templateFiles).length > 0 ? { files: templateFiles } : {}
26171
+ })
26149
26172
  });
26150
26173
  templateBuilderStore.close();
26151
26174
  };
@@ -27486,15 +27509,15 @@ var ImageEditToolbar = ({
27486
27509
  },
27487
27510
  tool.id
27488
27511
  )),
27489
- !!element.customData?.brandAssetId && /* @__PURE__ */ jsxs52(
27512
+ !!callbacks?.onSwapAsset && /* @__PURE__ */ jsxs52(
27490
27513
  "button",
27491
27514
  {
27492
27515
  type: "button",
27493
27516
  className: "iet__tool-btn",
27494
27517
  onClick: () => {
27495
- void callbacks?.onSwapAsset?.(element.id);
27518
+ void callbacks.onSwapAsset(element.id);
27496
27519
  },
27497
- title: "Swap brand asset",
27520
+ title: "Swap image from asset bank",
27498
27521
  disabled: isToolbarBusy,
27499
27522
  children: [
27500
27523
  /* @__PURE__ */ jsx94(SwapAssetIcon, {}),
@@ -51373,7 +51396,8 @@ function execAddRectangle(args, ctx) {
51373
51396
  fillStyle: args.fillStyle || "solid",
51374
51397
  strokeColor: args.strokeColor || "transparent",
51375
51398
  strokeWidth: args.strokeWidth ?? 0,
51376
- opacity: args.opacity ?? 100
51399
+ opacity: args.opacity ?? 100,
51400
+ roughness: 0
51377
51401
  });
51378
51402
  const elements = ctx.excalidrawAPI.getSceneElements();
51379
51403
  const frameIndex = elements.findIndex((el) => el.id === frameId);
@@ -51426,15 +51450,17 @@ function execAddText(args, ctx) {
51426
51450
  statusMessage: "Failed: missing frameId"
51427
51451
  };
51428
51452
  }
51453
+ const resolvedFontFamily = ctx.brandBodyFontId ?? ctx.brandHeadlineFontId ?? args.fontFamily ?? 2;
51429
51454
  const textEl = newTextElement6({
51430
51455
  x: args.x,
51431
51456
  y: args.y,
51432
51457
  text: args.text,
51433
51458
  fontSize: args.fontSize || 20,
51434
- fontFamily: args.fontFamily || 2,
51459
+ fontFamily: resolvedFontFamily,
51435
51460
  strokeColor: args.strokeColor || "#000000",
51436
51461
  textAlign: args.textAlign || "left",
51437
- width: args.width
51462
+ width: args.width,
51463
+ roughness: 0
51438
51464
  });
51439
51465
  const elements = ctx.excalidrawAPI.getSceneElements();
51440
51466
  const frameIndex = elements.findIndex((el) => el.id === frameId);
@@ -51477,7 +51503,8 @@ async function execGenerateImage(args, ctx) {
51477
51503
  const height = args.height;
51478
51504
  const referenceImageIndex = args.referenceImageIndex;
51479
51505
  const referenceImageDataUrl = referenceImageIndex !== void 0 ? ctx.fileAttachments?.[referenceImageIndex]?.dataUrl : void 0;
51480
- const safePrompt = `${prompt} \u2014 NO text, NO typography, NO words, NO captions, NO watermarks, NO overlaid copy. Pure clean visual image only.`;
51506
+ const fontHint = ctx.brandHeadlineFontName ? ` Brand typography: ${ctx.brandHeadlineFontName}.` : ctx.brandBodyFontName ? ` Brand typography: ${ctx.brandBodyFontName}.` : "";
51507
+ const safePrompt = `${prompt}${fontHint} \u2014 NO text, NO typography, NO words, NO captions, NO watermarks, NO overlaid copy. Pure clean visual image only.`;
51481
51508
  if (!frameId || !prompt) {
51482
51509
  return {
51483
51510
  success: false,
@@ -51902,8 +51929,7 @@ async function execGenerateHtmlBanner(args, ctx) {
51902
51929
  y: Math.round(frameY),
51903
51930
  width: frameW,
51904
51931
  height: frameH,
51905
- elementCount: newEls.length,
51906
- screenshot: screenshot ?? void 0
51932
+ elementCount: newEls.length
51907
51933
  },
51908
51934
  statusMessage: `Banner updated: ${newEls.length} elements in frame "${args.name || "Banner"}" (${frameW}\xD7${frameH})`,
51909
51935
  screenshot: screenshot ?? void 0
@@ -51940,7 +51966,7 @@ async function execFinalizeAd(args, ctx) {
51940
51966
  const screenshot = await captureFrameScreenshot(ctx.excalidrawAPI, frameId);
51941
51967
  return {
51942
51968
  success: true,
51943
- data: { frameId, screenshot: screenshot ?? void 0 },
51969
+ data: { frameId },
51944
51970
  statusMessage: "Ad finalized, ready for review",
51945
51971
  screenshot: screenshot ?? void 0
51946
51972
  };
@@ -52043,6 +52069,18 @@ async function execLoadTemplateIntoFrame(args, ctx) {
52043
52069
  ];
52044
52070
  const merged = syncMovedIndices7(mergedArr, arrayToMap30(mergedArr));
52045
52071
  ctx.excalidrawAPI.updateScene({ elements: merged });
52072
+ const templateFiles = parsed.files;
52073
+ if (templateFiles && Object.keys(templateFiles).length > 0) {
52074
+ ctx.excalidrawAPI.addFiles(
52075
+ Object.entries(templateFiles).map(([fileId, file2]) => ({
52076
+ id: fileId,
52077
+ dataURL: file2.dataURL,
52078
+ mimeType: file2.mimeType,
52079
+ created: Date.now(),
52080
+ lastRetrieved: Date.now()
52081
+ }))
52082
+ );
52083
+ }
52046
52084
  if (templateId) {
52047
52085
  const frameEl = ctx.excalidrawAPI.getSceneElements().find((el) => el.id === frameId);
52048
52086
  if (frameEl) {
@@ -52246,18 +52284,20 @@ async function execFillTemplateSlots(args, ctx) {
52246
52284
  return imageReplacements.get(index);
52247
52285
  }
52248
52286
  let mutation = {};
52249
- const makeTextMutation = (value) => {
52287
+ const makeTextMutation = (value, useHeadlineFont = false) => {
52250
52288
  const { fontSize, wrappedText, height, autoResize } = fitFontSize(value, el);
52289
+ const brandFontId = useHeadlineFont ? ctx.brandHeadlineFontId ?? ctx.brandBodyFontId : ctx.brandBodyFontId;
52251
52290
  return {
52252
52291
  text: wrappedText,
52253
52292
  originalText: value,
52254
52293
  fontSize,
52255
52294
  height,
52256
- autoResize
52295
+ autoResize,
52296
+ ...brandFontId !== void 0 ? { fontFamily: brandFontId } : {}
52257
52297
  };
52258
52298
  };
52259
52299
  if (slotType === "headline" && args.headline) {
52260
- mutation = makeTextMutation(args.headline);
52300
+ mutation = makeTextMutation(args.headline, true);
52261
52301
  } else if (slotType === "subhead" && args.subhead) {
52262
52302
  mutation = makeTextMutation(args.subhead);
52263
52303
  } else if (slotType === "cta" && args.cta) {
@@ -52483,7 +52523,11 @@ When the design needs a full-frame photorealistic background (a scene, landscape
52483
52523
  Use hex (\`#rrggbb\`) or \`rgba(r,g,b,a)\`. No named colors (no \`red\`, \`blue\`). CSS gradients (\`linear-gradient\`, \`radial-gradient\`) are allowed.
52484
52524
 
52485
52525
  ### 7. Fonts
52486
- If the Brand Identity context specifies fonts, use those exact font-family names in your HTML \`<style>\` block (e.g. \`font-family: "Brand Font Name", Arial, sans-serif\`). Otherwise default to web-safe: \`Arial\`, \`"Helvetica Neue"\`, \`Helvetica\`, \`sans-serif\`. Do not use serif fonts unless the brand explicitly requires them.
52526
+ **MANDATORY \u2014 Brand font enforcement:**
52527
+ - If the Brand Identity context specifies fonts with a registered fontFamilyId, you MUST use those fonts everywhere. Do NOT use any other font \u2014 no Arial, no Helvetica, no web-safe fallbacks as primary fonts.
52528
+ - In HTML \`<style>\` blocks: use the exact brand font-family name (e.g. \`font-family: "Brand Font Name", sans-serif\`). The canvas renderer maps this name to the registered font automatically.
52529
+ - For \`add_text\` calls: the system enforces the brand fontFamilyId automatically \u2014 you do not need to pass a fontFamily param.
52530
+ - Only fall back to web-safe fonts (\`Arial\`, \`"Helvetica Neue"\`, \`sans-serif\`) when the Brand Identity context has no font specified at all.
52487
52531
 
52488
52532
  ### 9. No unsupported features
52489
52533
  No \`transform\`, \`animation\`, \`transition\`, \`filter\`, \`clip-path\`, \`<img>\` tags, or JavaScript. No \`box-shadow\`.
@@ -52582,8 +52626,10 @@ ASSET USE MANDATE: If search_brand_assets() returns any results, you MUST incorp
52582
52626
 
52583
52627
  IMAGE GENERATION SCOPE: generate_image is only for supplementary content images (backgrounds, lifestyle photography) where no brand asset matches AND the brief explicitly requires a photo or illustration. For solid-color backgrounds or brand-asset-only ads, skip generate_image entirely.
52584
52628
 
52585
- TEMPLATE BANK: When the user references a campaign, asks to "use a template", or requests multiple ad sizes, use this workflow:
52586
- 1. Call \`list_brand_templates()\` to see available templates
52629
+ TEMPLATE BANK: Whenever the user asks to create, make, or build an ad \u2014 even without mentioning templates \u2014 call \`list_brand_templates()\` as your very first action before asking any clarifying questions. Use the returned catalog to ask smarter, more specific questions (e.g. "I see you have a Noida Campaign template \u2014 should I use that, or build from scratch?"). If multiple templates could match, ask which one to use. If none match or the user prefers no template, proceed with HTML generation. Only skip this tool call for messages that are clearly not ad-creation requests (greetings, questions, edits to existing canvas elements, etc.).
52630
+
52631
+ Template workflow once you know which template to use:
52632
+ 1. Call \`list_brand_templates()\` to see available templates (already done above)
52587
52633
  2. Call \`load_template_into_frame(frameId, variantId)\` to insert the template
52588
52634
  3. Call \`get_frame_elements(frameId)\` to inspect which slot types exist (headline, subhead, cta, background, product_image, logo, etc.)
52589
52635
  4. For each **image slot** found (product_image, logo): call \`search_brand_assets()\` to look up the asset. Pass the returned \`blobUrl\` as \`product_image_url\` or \`logo_url\` to \`fill_template_slots\`. Only call \`generate_image\` if no matching asset exists in the bank.
@@ -52706,16 +52752,18 @@ function buildBrandContextMessage(ctx) {
52706
52752
  const { headline, body } = ctx.typography;
52707
52753
  if (headline?.family) {
52708
52754
  const sizeHint = headline.sizeScale ? ` (suggested size: ${SIZE_SCALE_MAP[headline.sizeScale] ?? headline.sizeScale})` : "";
52709
- const idNote = headline.fontFamilyId ? ` \u2014 custom font registered in canvas; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS` : ` \u2014 no custom font file loaded; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52755
+ const idNote = headline.fontFamilyId ? ` \u2014 MANDATORY: use \`font-family: "${headline.family}", sans-serif\` in ALL HTML CSS for headlines. Do NOT use any other font.` : ` \u2014 no custom font file loaded; use \`font-family: "${headline.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52756
+ const fileNote = headline.fontName ? ` [file: ${headline.fontName}]` : "";
52710
52757
  lines.push(
52711
- `**Headline font**: ${headline.family}${headline.weight ? ` weight ${headline.weight}` : ""}${sizeHint}${idNote}`
52758
+ `**Headline font**: ${headline.family}${headline.weight ? ` weight ${headline.weight}` : ""}${sizeHint}${idNote}${fileNote}`
52712
52759
  );
52713
52760
  }
52714
52761
  if (body?.family) {
52715
52762
  const sizeHint = body.sizeScale ? ` (suggested size: ${SIZE_SCALE_MAP[body.sizeScale] ?? body.sizeScale})` : "";
52716
- const idNote = body.fontFamilyId ? ` \u2014 custom font registered in canvas; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS` : ` \u2014 no custom font file loaded; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52763
+ const idNote = body.fontFamilyId ? ` \u2014 MANDATORY: use \`font-family: "${body.family}", sans-serif\` in ALL HTML CSS for body copy, subheads, CTAs, and any other text. Do NOT use any other font.` : ` \u2014 no custom font file loaded; use \`font-family: "${body.family}", Arial, sans-serif\` in your HTML CSS (will render as Arial on canvas)`;
52764
+ const fileNote = body.fontName ? ` [file: ${body.fontName}]` : "";
52717
52765
  lines.push(
52718
- `**Body font**: ${body.family}${body.weight ? ` weight ${body.weight}` : ""}${sizeHint}${idNote}`
52766
+ `**Body font**: ${body.family}${body.weight ? ` weight ${body.weight}` : ""}${sizeHint}${idNote}${fileNote}`
52719
52767
  );
52720
52768
  }
52721
52769
  }
@@ -52814,7 +52862,11 @@ async function runAgentLoop(opts) {
52814
52862
  const augmentedToolCtx = {
52815
52863
  ...toolCtx,
52816
52864
  ...Object.keys(customFontMap).length ? { customFontMap } : {},
52817
- ...allImageAttachments.length ? { fileAttachments: allImageAttachments } : {}
52865
+ ...allImageAttachments.length ? { fileAttachments: allImageAttachments } : {},
52866
+ ...brandContext?.typography?.headline?.fontName ? { brandHeadlineFontName: brandContext.typography.headline.fontName } : {},
52867
+ ...brandContext?.typography?.body?.fontName ? { brandBodyFontName: brandContext.typography.body.fontName } : {},
52868
+ ...brandContext?.typography?.headline?.fontFamilyId ? { brandHeadlineFontId: brandContext.typography.headline.fontFamilyId } : {},
52869
+ ...brandContext?.typography?.body?.fontFamilyId ? { brandBodyFontId: brandContext.typography.body.fontFamilyId } : {}
52818
52870
  };
52819
52871
  const messages = [{ role: "system", content: SYSTEM_PROMPT }];
52820
52872
  if (brandContext) {
@@ -52941,9 +52993,7 @@ ${f.textContent}`).join("\n\n")}`;
52941
52993
  statusMessage: error || "Insufficient credits for image generation"
52942
52994
  };
52943
52995
  const gatedAction = {
52944
- toolName: name,
52945
- args: parsedArgs,
52946
- result: gatedResult
52996
+ result: { success: gatedResult.success, statusMessage: gatedResult.statusMessage }
52947
52997
  };
52948
52998
  toolActions.push(gatedAction);
52949
52999
  onUpdate({
@@ -52969,9 +53019,7 @@ ${f.textContent}`).join("\n\n")}`;
52969
53019
  statusMessage: "Blocked: template already provides text slots"
52970
53020
  };
52971
53021
  const blockedAction = {
52972
- toolName: name,
52973
- args: parsedArgs,
52974
- result: blockedResult
53022
+ result: { success: blockedResult.success, statusMessage: blockedResult.statusMessage }
52975
53023
  };
52976
53024
  toolActions.push(blockedAction);
52977
53025
  onUpdate({
@@ -53006,9 +53054,7 @@ ${f.textContent}`).join("\n\n")}`;
53006
53054
  }
53007
53055
  }
53008
53056
  const action = {
53009
- toolName: name,
53010
- args: parsedArgs,
53011
- result
53057
+ result: { success: result.success, statusMessage: result.statusMessage }
53012
53058
  };
53013
53059
  toolActions.push(action);
53014
53060
  onUpdate({
@@ -55026,11 +55072,8 @@ var ExcalidrawBase = (props) => {
55026
55072
  document.removeEventListener("touchmove", handleTouchMove);
55027
55073
  };
55028
55074
  }, []);
55029
- useEffect62(() => {
55030
- if (!customFonts?.length) {
55031
- return;
55032
- }
55033
- for (const spec of customFonts) {
55075
+ useMemo15(() => {
55076
+ for (const spec of customFonts ?? []) {
55034
55077
  Fonts.registerCustomFont(spec);
55035
55078
  }
55036
55079
  }, [customFonts]);