@orangecatai/adgen-canvas 0.0.32 → 0.0.34
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-VL47Q2RH.js → chunk-DB6EMYXB.js} +2 -2
- package/dist/dev/{chunk-OWDDSUUQ.js → chunk-JBHZ6CNN.js} +3 -3
- package/dist/dev/data/{image-5Y3GORNT.js → image-IIQSXNFT.js} +3 -3
- package/dist/dev/index.css +716 -703
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +9700 -8140
- package/dist/dev/index.js.map +4 -4
- package/dist/dev/subset-shared.chunk.js +1 -1
- package/dist/dev/subset-worker.chunk.js +1 -1
- package/dist/prod/{chunk-452N23ZE.js → chunk-KFCCNTO2.js} +1 -1
- package/dist/prod/{chunk-AQSA3HNR.js → chunk-T2MY3VHD.js} +2 -2
- package/dist/prod/data/image-3RDLE3VN.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +167 -105
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/excalidraw/components/ImageGeneratorPanel.d.ts +15 -0
- package/dist/types/excalidraw/components/ai-chat/imagePayload.d.ts +53 -0
- package/dist/types/excalidraw/components/auto-resize/AutoResizePanel.d.ts +3 -4
- package/dist/types/excalidraw/components/auto-resize/autoResizeEngine.d.ts +17 -14
- package/dist/types/excalidraw/components/auto-resize/backgroundExtend.d.ts +105 -0
- package/dist/types/excalidraw/components/auto-resize/frameSource.d.ts +16 -0
- package/dist/types/excalidraw/components/frameToolbarVariant.d.ts +34 -1
- package/dist/types/excalidraw/types.d.ts +23 -4
- package/dist/types/excalidraw/utils/brandContextUtils.d.ts +11 -6
- package/dist/types/excalidraw/utils/imageApi.d.ts +52 -6
- package/dist/types/excalidraw/utils/imageCompression.d.ts +46 -0
- package/dist/types/excalidraw/utils/referenceBudget.d.ts +138 -0
- package/package.json +1 -1
- package/dist/prod/data/image-5D6ZUEOG.js +0 -1
- /package/dist/dev/{chunk-VL47Q2RH.js.map → chunk-DB6EMYXB.js.map} +0 -0
- /package/dist/dev/{chunk-OWDDSUUQ.js.map → chunk-JBHZ6CNN.js.map} +0 -0
- /package/dist/dev/data/{image-5Y3GORNT.js.map → image-IIQSXNFT.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a,b,c,d}from"./chunk-Z5NKEFVG.js";import"./chunk-
|
|
1
|
+
import{a,b,c,d}from"./chunk-Z5NKEFVG.js";import"./chunk-KFCCNTO2.js";import"./chunk-SRAX5OIU.js";export{a as Commands,b as subsetToBase64,c as subsetToBinary,d as toBase64};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as r,c as t}from"./chunk-Z5NKEFVG.js";import"./chunk-
|
|
1
|
+
import{a as r,c as t}from"./chunk-Z5NKEFVG.js";import"./chunk-KFCCNTO2.js";import"./chunk-SRAX5OIU.js";var s=import.meta.url?new URL(import.meta.url):void 0;typeof window>"u"&&typeof self<"u"&&(self.onmessage=async e=>{switch(e.data.command){case r.Subset:let a=await t(e.data.arrayBuffer,e.data.codePoints);self.postMessage(a,{transfer:[a]});break}});export{s as WorkerUrl};
|
|
@@ -87,6 +87,21 @@ export declare function getDimensions(model: ModelName, ratio: string, resolutio
|
|
|
87
87
|
width: number;
|
|
88
88
|
height: number;
|
|
89
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Turn the panel's picked ratio + resolution into the request fields the model
|
|
92
|
+
* actually accepts.
|
|
93
|
+
*
|
|
94
|
+
* gpt-image-* is the awkward one: its `aspect_ratio` enum does NOT accept 4:5
|
|
95
|
+
* or 5:4 at the 2K/4K tiers, and `supportsImageSize: false` meant the picked
|
|
96
|
+
* resolution was dropped on the floor — so "4:5 at 2K" silently asked for a
|
|
97
|
+
* ratio the API rejects and gave no size at all. The panel already knows the
|
|
98
|
+
* exact pixel dimensions for every ratio/resolution pair, so hand those over
|
|
99
|
+
* as an authoritative `size` and skip the enum entirely.
|
|
100
|
+
*/
|
|
101
|
+
export declare function resolveRequestSize(model: ModelName, ratio: string, resolution: Resolution): {
|
|
102
|
+
aspectRatio: string | null;
|
|
103
|
+
imageSize: string | null;
|
|
104
|
+
};
|
|
90
105
|
export declare const ImageGeneratorPanel: ({ element, app, onBeforeImageGen, onAfterImageGen, }: {
|
|
91
106
|
element: NonDeletedExcalidrawElement & ExcalidrawFrameLikeElement;
|
|
92
107
|
app: AppClassProperties;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Payload-size normalisation for images sent to the chat API.
|
|
3
|
+
*
|
|
4
|
+
* Every `image_url` content block in a chat request travels as a base64 data
|
|
5
|
+
* URL embedded in JSON. Base64 inflates bytes by 4/3, and the host proxy runs
|
|
6
|
+
* on Vercel, whose platform-level request cap is 4.5 MB — enforced at the edge
|
|
7
|
+
* *before* the route handler runs, so it surfaces as an opaque 413 that never
|
|
8
|
+
* reaches any of our error handling.
|
|
9
|
+
*
|
|
10
|
+
* Rather than budget-and-drop (which loses the user's images, the one thing
|
|
11
|
+
* they actually asked the model to look at), every image is downscaled to a
|
|
12
|
+
* size where the whole request comfortably fits. A 1280px-long-edge JPEG at
|
|
13
|
+
* q0.78 lands around 100-150KB, so a request carrying the full complement of
|
|
14
|
+
* user uploads + brand references + frame screenshot sits near 1MB — roughly
|
|
15
|
+
* a quarter of the cap, with room for prompt text and tool schemas.
|
|
16
|
+
*
|
|
17
|
+
* This runs at payload-assembly time, not upload time, which is deliberate:
|
|
18
|
+
* conversation history persisted before this existed still holds raw
|
|
19
|
+
* multi-megabyte data URLs, and those are re-sent on every subsequent turn.
|
|
20
|
+
* Normalising here fixes existing conversations, not just new uploads.
|
|
21
|
+
*/
|
|
22
|
+
import { dataUrlWireBytes, isRasterImageDataUrl, shrinkImageDataUrl, shrinkImageDataUrls } from "../../utils/imageCompression";
|
|
23
|
+
export { dataUrlWireBytes, isRasterImageDataUrl, shrinkImageDataUrl, shrinkImageDataUrls, };
|
|
24
|
+
/**
|
|
25
|
+
* Normalise every image in an outgoing request so the body cannot exceed the
|
|
26
|
+
* platform request cap.
|
|
27
|
+
*
|
|
28
|
+
* Three escalating passes, so the common case costs nothing and the worst case
|
|
29
|
+
* still succeeds rather than 413ing:
|
|
30
|
+
*
|
|
31
|
+
* 1. Downscale every image to {@link MAX_EDGE_PX}. Almost always sufficient —
|
|
32
|
+
* a full request of 9 images lands near 1MB.
|
|
33
|
+
* 2. If still over (many images, or images that resisted re-encoding),
|
|
34
|
+
* re-encode at successively lower quality.
|
|
35
|
+
* 3. Only if *still* over, drop the oldest images — newest are the ones the
|
|
36
|
+
* user is actually asking about. Images on the final message are never
|
|
37
|
+
* dropped.
|
|
38
|
+
*
|
|
39
|
+
* Mutates the parts in place and returns the message array for convenience.
|
|
40
|
+
*/
|
|
41
|
+
export declare function normalizeRequestImages<T extends {
|
|
42
|
+
role: string;
|
|
43
|
+
}>(messages: T[]): Promise<T[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Build a user-facing message from a failed chat-proxy response.
|
|
46
|
+
*
|
|
47
|
+
* The previous handling assumed every non-OK status came from OpenRouter and
|
|
48
|
+
* carried an OpenRouter-shaped JSON error. Platform-level rejections (notably
|
|
49
|
+
* Vercel's 413 at the edge) return plain text, so `response.json()` threw and
|
|
50
|
+
* the raw fallback `OpenRouter error 413` reached the user — naming the wrong
|
|
51
|
+
* system and offering no action.
|
|
52
|
+
*/
|
|
53
|
+
export declare function describeChatError(response: Response): Promise<string>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./AutoResizePanel.scss";
|
|
2
2
|
import type { ExcalidrawFrameLikeElement, NonDeletedExcalidrawElement } from "@orangecatai/element/types";
|
|
3
3
|
import type { AppClassProperties } from "../../types";
|
|
4
|
-
export declare const AutoResizePanel: ({ element, app, onClose, onBeforeAutoResize, onAfterAutoResize, onRunningChange, autoResizeUrl,
|
|
4
|
+
export declare const AutoResizePanel: ({ element, app, onClose, onBeforeAutoResize, onAfterAutoResize, onRunningChange, autoResizeUrl, chatModel, reviewerModel, outpaintModel, }: {
|
|
5
5
|
element: NonDeletedExcalidrawElement & ExcalidrawFrameLikeElement;
|
|
6
6
|
app: AppClassProperties;
|
|
7
7
|
onClose: () => void;
|
|
@@ -12,8 +12,7 @@ export declare const AutoResizePanel: ({ element, app, onClose, onBeforeAutoResi
|
|
|
12
12
|
onAfterAutoResize?: () => void;
|
|
13
13
|
onRunningChange?: (running: boolean) => void;
|
|
14
14
|
autoResizeUrl: string;
|
|
15
|
-
customResizeUrl?: string;
|
|
16
|
-
tier1CropUrl?: string;
|
|
17
15
|
chatModel: string;
|
|
18
|
-
|
|
16
|
+
reviewerModel?: string;
|
|
17
|
+
outpaintModel?: string;
|
|
19
18
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -152,6 +152,19 @@ export declare function enforceMinProductBox({ x, y, width, height, targetW, tar
|
|
|
152
152
|
width: number;
|
|
153
153
|
height: number;
|
|
154
154
|
};
|
|
155
|
+
/**
|
|
156
|
+
* Checks generated HTML against the strict contract in `buildAutoResizePrompt`
|
|
157
|
+
* BEFORE handing it to the parser.
|
|
158
|
+
*
|
|
159
|
+
* The parser is deliberately narrow — absolutely-positioned divs, no flex, no
|
|
160
|
+
* grid, no transforms — because that is what maps cleanly onto Excalidraw
|
|
161
|
+
* elements. Models drift off strict DOM contracts, and a violation shows up as
|
|
162
|
+
* silently mangled geometry rather than an error. Catching it here buys one
|
|
163
|
+
* targeted retry instead of a wrong layout.
|
|
164
|
+
*
|
|
165
|
+
* Returns the list of violations; empty means the HTML conforms.
|
|
166
|
+
*/
|
|
167
|
+
export declare function validateHtmlContract(html: string, expectedChildCount: number): string[];
|
|
155
168
|
export declare function buildAutoResizeImagePrompt(srcW: number, srcH: number, tgtW: number, tgtH: number, aspectRatio: string, brandImages?: {
|
|
156
169
|
logo?: string;
|
|
157
170
|
fontPreview?: string;
|
|
@@ -171,10 +184,11 @@ export declare function runAutoResize(opts: {
|
|
|
171
184
|
targetDimensions: TargetDimension[];
|
|
172
185
|
app: AppClassProperties;
|
|
173
186
|
autoResizeUrl: string;
|
|
174
|
-
customResizeUrl?: string;
|
|
175
|
-
tier1CropUrl?: string;
|
|
176
187
|
chatModel?: string;
|
|
177
|
-
|
|
188
|
+
/** Vision model for the layout critique loop. Falls back to `chatModel`. */
|
|
189
|
+
reviewerModel?: string;
|
|
190
|
+
/** Image model used ONLY to outpaint photographic backgrounds. */
|
|
191
|
+
outpaintModel?: string;
|
|
178
192
|
customFontMap?: Record<string, number>;
|
|
179
193
|
onBeforeAutoResize?: () => Promise<{
|
|
180
194
|
allowed: boolean;
|
|
@@ -185,16 +199,5 @@ export declare function runAutoResize(opts: {
|
|
|
185
199
|
signal?: AbortSignal;
|
|
186
200
|
/** Pass the result of preCreateAllFrames() to skip redundant frame creation */
|
|
187
201
|
preCreatedFrameInfos?: PreCreatedFrameInfo[];
|
|
188
|
-
/**
|
|
189
|
-
* Brand assets handed to the image model so the recomposed ad keeps the brand
|
|
190
|
-
* logo, typeface, and (for font-aware models) the real font file. Sourced from
|
|
191
|
-
* `app.state.brandContext` by the panel.
|
|
192
|
-
*/
|
|
193
|
-
brandImages?: {
|
|
194
|
-
logo?: string;
|
|
195
|
-
fontPreview?: string;
|
|
196
|
-
fontUrl?: string;
|
|
197
|
-
name?: string;
|
|
198
|
-
};
|
|
199
202
|
}): Promise<DimensionResult[]>;
|
|
200
203
|
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export type BgKind = "solid" | "texture" | "panels" | "gradient" | "photo";
|
|
2
|
+
export type PixelBuffer = {
|
|
3
|
+
data: Uint8ClampedArray;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export type Rect = {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
w: number;
|
|
11
|
+
h: number;
|
|
12
|
+
};
|
|
13
|
+
/** Where a panel boundary sits, as a fraction (0-1) along `axis`. */
|
|
14
|
+
export type BgSplit = {
|
|
15
|
+
axis: "x" | "y";
|
|
16
|
+
at: number;
|
|
17
|
+
};
|
|
18
|
+
export type BgClassification = {
|
|
19
|
+
kind: BgKind;
|
|
20
|
+
/** Present only for `kind: "panels"`. */
|
|
21
|
+
split?: BgSplit;
|
|
22
|
+
/** Present for `kind: "solid"` — the plate's average colour as hex. */
|
|
23
|
+
solidColor?: string;
|
|
24
|
+
/** Per-region verdicts; length 2 when a split was found, else 1. */
|
|
25
|
+
regions: RegionStats[];
|
|
26
|
+
};
|
|
27
|
+
export type RegionStats = {
|
|
28
|
+
rect: Rect;
|
|
29
|
+
/** Mean luminance stdev within patches — how "busy" the region is. */
|
|
30
|
+
textureEnergy: number;
|
|
31
|
+
/** Spread of patch MEANS — how much the region changes across itself. */
|
|
32
|
+
meanSpread: number;
|
|
33
|
+
/** True when patch means march in one direction along x or y. */
|
|
34
|
+
monotonic: boolean;
|
|
35
|
+
meanColor: {
|
|
36
|
+
r: number;
|
|
37
|
+
g: number;
|
|
38
|
+
b: number;
|
|
39
|
+
};
|
|
40
|
+
verdict: "solid" | "texture" | "gradient" | "photo";
|
|
41
|
+
};
|
|
42
|
+
export declare function toHex({ r, g, b, }: {
|
|
43
|
+
r: number;
|
|
44
|
+
g: number;
|
|
45
|
+
b: number;
|
|
46
|
+
}): string;
|
|
47
|
+
/**
|
|
48
|
+
* Finds the single strongest panel boundary, if any. Deliberately detects at
|
|
49
|
+
* most ONE split — that covers the dominant real-world ad layout (a two-panel
|
|
50
|
+
* composition) without overfitting to noise in busier plates.
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectSplit(buf: PixelBuffer): BgSplit | null;
|
|
53
|
+
/** Patch-grid statistics for one region, and the verdict they imply. */
|
|
54
|
+
export declare function analyzeRegion(buf: PixelBuffer, rect: Rect): RegionStats;
|
|
55
|
+
/** Full classification: split detection, then per-region analysis. */
|
|
56
|
+
export declare function classifyBackground(buf: PixelBuffer): BgClassification;
|
|
57
|
+
export declare function loadImageElement(dataUrl: string): Promise<HTMLImageElement>;
|
|
58
|
+
/**
|
|
59
|
+
* Reads an image into a pixel buffer, downscaled to at most `maxSide` on its
|
|
60
|
+
* long edge. Classification only needs coarse statistics, and a 4K plate would
|
|
61
|
+
* otherwise cost tens of megabytes of ImageData for no extra accuracy.
|
|
62
|
+
*/
|
|
63
|
+
export declare function imageToPixelBuffer(dataUrl: string, maxSide?: number): Promise<PixelBuffer>;
|
|
64
|
+
export type BackgroundSource = {
|
|
65
|
+
type: "solid";
|
|
66
|
+
color: string;
|
|
67
|
+
elementId?: string;
|
|
68
|
+
} | {
|
|
69
|
+
type: "image";
|
|
70
|
+
dataUrl: string;
|
|
71
|
+
elementId?: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: "none";
|
|
74
|
+
};
|
|
75
|
+
export type BackgroundResult = {
|
|
76
|
+
kind: "none";
|
|
77
|
+
} | {
|
|
78
|
+
kind: "solid";
|
|
79
|
+
color: string;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "image";
|
|
82
|
+
dataUrl: string;
|
|
83
|
+
strategy: BgKind | "scaled";
|
|
84
|
+
};
|
|
85
|
+
export type BuildBackgroundOpts = {
|
|
86
|
+
/**
|
|
87
|
+
* Called ONLY for photographic plates that cannot be extended locally.
|
|
88
|
+
* Supplied by the caller so this module stays free of network concerns and
|
|
89
|
+
* fully unit-testable. Returning null falls back to edge-extension.
|
|
90
|
+
*/
|
|
91
|
+
outpaint?: (args: {
|
|
92
|
+
dataUrl: string;
|
|
93
|
+
targetWidth: number;
|
|
94
|
+
targetHeight: number;
|
|
95
|
+
}) => Promise<string | null>;
|
|
96
|
+
/** When false, skip analysis and just cover-scale (small AR delta). */
|
|
97
|
+
needsExtension: boolean;
|
|
98
|
+
onProgress?: (phase: string) => void;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Produces a background sized EXACTLY to the target frame. The caller inserts
|
|
102
|
+
* the result 1:1 — no cover-crop afterwards, unlike the old image-gen path
|
|
103
|
+
* where the model returned approximate dimensions.
|
|
104
|
+
*/
|
|
105
|
+
export declare function buildTargetBackground(bg: BackgroundSource, tgtW: number, tgtH: number, opts: BuildBackgroundOpts): Promise<BackgroundResult>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ExcalidrawElement, ExcalidrawFrameLikeElement } from "@orangecatai/element/types";
|
|
2
|
+
/** Live (non-deleted) children of a frame. */
|
|
3
|
+
export declare function getLiveFrameChildren(elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement): readonly ExcalidrawElement[];
|
|
4
|
+
/**
|
|
5
|
+
* True when this frame holds separable layers Auto Resize can re-arrange.
|
|
6
|
+
*
|
|
7
|
+
* Any ONE of these is sufficient:
|
|
8
|
+
* - more than one live child → there is something to arrange
|
|
9
|
+
* - a child tagged `decomposeType` → came from "Make editable"
|
|
10
|
+
* - a text child → real editable copy, so the frame is not a flat render
|
|
11
|
+
*
|
|
12
|
+
* False for the flat case: zero or one child (typically one full-bleed image).
|
|
13
|
+
*/
|
|
14
|
+
export declare function isStructuredFrame(elements: readonly ExcalidrawElement[], frame: ExcalidrawFrameLikeElement): boolean;
|
|
15
|
+
/** Human-readable reason shown when Auto Resize is unavailable. */
|
|
16
|
+
export declare const AUTO_RESIZE_FLAT_FRAME_REASON = "Make this ad editable first \u2014 Auto Resize needs separate layers to re-lay-out.";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ElementsMapOrArray, ExcalidrawElement, ExcalidrawFrameLikeElement, ExcalidrawVideoElement } from "@orangecatai/element/types";
|
|
1
|
+
import type { ElementsMapOrArray, ExcalidrawElement, ExcalidrawFrameLikeElement, ExcalidrawImageElement, ExcalidrawVideoElement } from "@orangecatai/element/types";
|
|
2
2
|
export type FrameToolbarVariant = "frame" | "video";
|
|
3
3
|
export type FrameToolbarVariantResult = {
|
|
4
4
|
variant: FrameToolbarVariant;
|
|
@@ -34,3 +34,36 @@ export declare const getFrameToolbarVariant: (frame: ExcalidrawFrameLikeElement,
|
|
|
34
34
|
* toolbar, so the child never surfaces an action the frame does not have.
|
|
35
35
|
*/
|
|
36
36
|
export declare const getResizeTargetFrame: (element: ExcalidrawElement, elements: readonly ExcalidrawElement[]) => ExcalidrawFrameLikeElement | null;
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the image a *frame's* toolbar should act on.
|
|
39
|
+
*
|
|
40
|
+
* A generated ad covers its frame edge to edge, so whether a click lands on the
|
|
41
|
+
* frame or on the artwork is arbitrary from the user's side — but only the
|
|
42
|
+
* artwork carries the image actions (Quick Edit, and the host's Make editable).
|
|
43
|
+
* This lets a selected frame offer those same actions, targeting its artwork,
|
|
44
|
+
* so they no longer come and go with where the user happened to click. It is
|
|
45
|
+
* the mirror of `getResizeTargetFrame`, which lets a selected image reach the
|
|
46
|
+
* frame's Resize.
|
|
47
|
+
*
|
|
48
|
+
* When a frame holds several images (artwork plus a logo overlay, say), the
|
|
49
|
+
* largest one is the artwork.
|
|
50
|
+
*/
|
|
51
|
+
export declare const getFrameChildImage: (frame: ExcalidrawFrameLikeElement, elements: ElementsMapOrArray) => ExcalidrawImageElement | null;
|
|
52
|
+
export type AutoResizeAvailability = {
|
|
53
|
+
/** False when the frame is a flat render with nothing to re-arrange. */
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
/** Tooltip text — the reason when disabled, the action when enabled. */
|
|
56
|
+
title: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Whether Auto Resize can act on this frame, and what to say if it cannot.
|
|
60
|
+
*
|
|
61
|
+
* Auto Resize re-lays-out an ad by repositioning its individual layers, so it
|
|
62
|
+
* needs a frame that HAS layers. A freshly generated ad is one flat image; the
|
|
63
|
+
* button stays visible but disabled, pointing at "Make editable", rather than
|
|
64
|
+
* silently vanishing.
|
|
65
|
+
*
|
|
66
|
+
* Shared by both entry points (a selected frame, and a selected image resolved
|
|
67
|
+
* to its parent frame) so the rule cannot drift between them.
|
|
68
|
+
*/
|
|
69
|
+
export declare const getAutoResizeAvailability: (frame: ExcalidrawFrameLikeElement | null, elements: readonly ExcalidrawElement[]) => AutoResizeAvailability;
|
|
@@ -767,12 +767,31 @@ export interface ExcalidrawProps {
|
|
|
767
767
|
agentImageModel?: string;
|
|
768
768
|
/**
|
|
769
769
|
* OpenRouter model tag used by the auto-resize engine for HTML layout
|
|
770
|
-
* generation
|
|
771
|
-
*
|
|
772
|
-
* @default "
|
|
773
|
-
* @example "openai/gpt-4o"
|
|
770
|
+
* generation — the LLM call that decides how to reflow the ad's layers into
|
|
771
|
+
* the target dimensions. Must be a chat model.
|
|
772
|
+
* @default "x-ai/grok-4.6"
|
|
774
773
|
*/
|
|
775
774
|
chatModel?: string;
|
|
775
|
+
/**
|
|
776
|
+
* Vision model for the auto-resize layout critique loop. Falls back to
|
|
777
|
+
* `chatModel` when omitted, so pass a cheap fast vision model here — the
|
|
778
|
+
* reviewer only compares a screenshot against the brief, it does not carry
|
|
779
|
+
* an agentic conversation.
|
|
780
|
+
* @example "openai/gpt-5.6-luna"
|
|
781
|
+
*/
|
|
782
|
+
reviewerModel?: string;
|
|
783
|
+
/**
|
|
784
|
+
* Image model used ONLY to outpaint a photographic background when the
|
|
785
|
+
* aspect-ratio change is too large to cover-crop. Solid, textured and
|
|
786
|
+
* two-panel backgrounds are extended locally on a canvas and never reach a
|
|
787
|
+
* model at all.
|
|
788
|
+
*
|
|
789
|
+
* Must support extreme aspect ratios (4:1, 1:4, 8:1, 1:8) and be callable
|
|
790
|
+
* from `chat/completions`. `openai/gpt-image-2` satisfies NEITHER — it is
|
|
791
|
+
* generation-only on OpenRouter and its ratio enum stops at 3:1 / 1:3.
|
|
792
|
+
* @default "google/gemini-3.1-flash-image-preview"
|
|
793
|
+
*/
|
|
794
|
+
outpaintModel?: string;
|
|
776
795
|
/**
|
|
777
796
|
* Show the Library sidebar trigger button. Defaults to `false`.
|
|
778
797
|
* Set to `true` to show the library icon in the top-right area.
|
|
@@ -19,12 +19,17 @@ export declare function buildBrandContextMessage(ctx: BrandContext): string;
|
|
|
19
19
|
*/
|
|
20
20
|
export declare function buildImageGenSafePrompt(prompt: string, brandContext: BrandContext, opts?: {
|
|
21
21
|
/**
|
|
22
|
-
* True when the user attached their own reference images
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
22
|
+
* True when the user attached their own reference images (or, on quick
|
|
23
|
+
* edit, when there is a source image).
|
|
24
|
+
*
|
|
25
|
+
* This no longer suppresses the logo and font mandates. Brand assets are
|
|
26
|
+
* always attached now — the budget in utils/referenceBudget sheds them only
|
|
27
|
+
* under real payload pressure, not because the user supplied something —
|
|
28
|
+
* so the mandates still point at images the model can see.
|
|
29
|
+
*
|
|
30
|
+
* What it still does is demote brand *direction*: the "this ad is for brand
|
|
31
|
+
* X, everything must reflect it" line, which genuinely does fight a
|
|
32
|
+
* user-chosen reference for subject and style.
|
|
28
33
|
*/
|
|
29
34
|
hasUserReferences?: boolean;
|
|
30
35
|
}): string;
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
import { MAX_USER_REFERENCE_IMAGES } from "./referenceBudget";
|
|
2
|
+
export { MAX_USER_REFERENCE_IMAGES };
|
|
3
|
+
/** True for OpenAI's Images-API models, which are routed and shaped differently. */
|
|
4
|
+
export declare const isOpenAIImageModel: (modelId: string) => boolean;
|
|
5
|
+
/** Nearest preset ratio to `frameW / frameH`. */
|
|
6
|
+
export declare function nearestPresetAspectRatio(frameW: number, frameH: number): string;
|
|
1
7
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
8
|
+
* Picks the output size for a frame.
|
|
9
|
+
*
|
|
10
|
+
* For gpt-image-* this returns an exact `"1088x1360"` size matching the frame's
|
|
11
|
+
* real aspect ratio, so a 1080×1350 (4:5) frame no longer comes back as a 3:4
|
|
12
|
+
* image that has to be letterboxed to 1013×1350. It travels in the request's
|
|
13
|
+
* `size` field, NOT `resolution` — see the Images API body below. `aspectRatio`
|
|
14
|
+
* is null in that case: an exact size is authoritative and OpenRouter 400s if a
|
|
15
|
+
* ratio is sent alongside it (4:5 is not a valid preset anyway).
|
|
16
|
+
*
|
|
17
|
+
* Every other model keeps the preset-snapping behaviour it has always had.
|
|
5
18
|
*/
|
|
6
|
-
export declare
|
|
19
|
+
export declare function resolveImageOutputSize(modelId: string, frameW: number, frameH: number): {
|
|
20
|
+
aspectRatio: string | null;
|
|
21
|
+
imageSize: string | null;
|
|
22
|
+
};
|
|
7
23
|
/**
|
|
8
24
|
* Describes each attached reference image by its real 1-based position, and
|
|
9
25
|
* states the precedence rule between them.
|
|
@@ -20,8 +36,18 @@ export declare const MAX_USER_REFERENCE_IMAGES = 6;
|
|
|
20
36
|
* Returns "" when there is nothing worth numbering, so prompts that carry no
|
|
21
37
|
* attachments are left byte-identical to before.
|
|
22
38
|
*/
|
|
23
|
-
export declare function buildReferenceManifest(userReferenceCount: number, brandLabels: string[]
|
|
24
|
-
|
|
39
|
+
export declare function buildReferenceManifest(userReferenceCount: number, brandLabels: string[],
|
|
40
|
+
/** The brand logo survived the budget and is attached. */
|
|
41
|
+
logoIsAttached?: boolean,
|
|
42
|
+
/** A source image (quick edit / resize) is attached below the user's refs. */
|
|
43
|
+
hasSourceImage?: boolean): string;
|
|
44
|
+
export declare function callOpenRouterImageAPI(prompt: string, modelId: string,
|
|
45
|
+
/**
|
|
46
|
+
* Preset ratio label ("4:5" is NOT one). Pass null when `imageSize` carries an
|
|
47
|
+
* exact `WIDTHxHEIGHT` resolution — that is authoritative, and sending a
|
|
48
|
+
* conflicting (or unsupported) ratio alongside it only muddies the request.
|
|
49
|
+
*/
|
|
50
|
+
aspectRatio: string | null, imageSize: string | null, textOutput: boolean, imageGenUrl: string, referenceImageDataUrl?: string | string[], signal?: AbortSignal, brandImages?: {
|
|
25
51
|
logo?: string;
|
|
26
52
|
fontPreview?: string;
|
|
27
53
|
/** Optional second font preview (brand body typeface) shown as a reference. */
|
|
@@ -56,4 +82,24 @@ export declare function callOpenRouterImageAPI(prompt: string, modelId: string,
|
|
|
56
82
|
* manifest, so those callers are unaffected by this option.
|
|
57
83
|
*/
|
|
58
84
|
referenceMode?: "user-priority" | "legacy-positional";
|
|
85
|
+
/**
|
|
86
|
+
* The image being edited or resized. Ranks directly below the user's own
|
|
87
|
+
* references and above the logo — on those surfaces the source IS the
|
|
88
|
+
* subject, so it must never be shed, and the logo becomes droppable.
|
|
89
|
+
*/
|
|
90
|
+
sourceImageDataUrl?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the logo is part of the protected core. True on generate flows
|
|
93
|
+
* (the floor is 6 user references + the logo); false on quick edit and
|
|
94
|
+
* resize, where the source image outranks it.
|
|
95
|
+
*/
|
|
96
|
+
protectLogo?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* How many brand-bank assets / ad references the host proxy may append.
|
|
99
|
+
* The SDK cannot see those images, so it reserves
|
|
100
|
+
* {@link HOST_ASSET_BYTE_RESERVATION} of budget per slot and tells the host
|
|
101
|
+
* how many survived; the host compresses each one to fit its reservation.
|
|
102
|
+
*/
|
|
103
|
+
hostBrandAssetSlots?: number;
|
|
104
|
+
hostAdReferenceSlots?: number;
|
|
59
105
|
}): Promise<string>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared image-compression primitives.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `components/ai-chat/imagePayload` so the image-generation
|
|
5
|
+
* surfaces (canvas chat, generator panel, quick edit, auto-resize) can use the
|
|
6
|
+
* same downscaling the chat request path has always used. Previously only chat
|
|
7
|
+
* shrank its images, which is why an image-gen request could still carry a raw
|
|
8
|
+
* 1 MB PNG straight at the host proxy's 4.5 MB platform cap.
|
|
9
|
+
*
|
|
10
|
+
* Everything here is best-effort and never throws: on any failure the original
|
|
11
|
+
* data URL is returned unchanged, and the caller's budget pass is the backstop.
|
|
12
|
+
*/
|
|
13
|
+
/** Long-edge cap in CSS pixels. Well above what vision models resolve. */
|
|
14
|
+
export declare const MAX_EDGE_PX = 1280;
|
|
15
|
+
/** JPEG quality for re-encoded images. */
|
|
16
|
+
export declare const JPEG_QUALITY = 0.78;
|
|
17
|
+
/**
|
|
18
|
+
* Images already smaller than this are passed through untouched — re-encoding
|
|
19
|
+
* a 40KB thumbnail costs a canvas round-trip and can make it *larger*.
|
|
20
|
+
*/
|
|
21
|
+
export declare const PASSTHROUGH_BYTES: number;
|
|
22
|
+
/** Estimated wire size of a data URL: the base64 payload is what gets sent. */
|
|
23
|
+
export declare function dataUrlWireBytes(dataUrl: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a data URL is a raster format safe to send as an `image_url` block.
|
|
26
|
+
* SVG is commonly rejected outright by vision models, and canvas re-encoding
|
|
27
|
+
* of an SVG is unreliable across browsers, so it is neither shrunk nor sent.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isRasterImageDataUrl(dataUrl: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Downscale + re-encode a raster data URL so it is safe to embed in a request.
|
|
32
|
+
*
|
|
33
|
+
* Never throws: on any failure (decode error, no canvas context, non-raster
|
|
34
|
+
* input) the original data URL is returned unchanged.
|
|
35
|
+
*/
|
|
36
|
+
export declare function shrinkImageDataUrl(dataUrl: string): Promise<string>;
|
|
37
|
+
/** Shrink a batch in parallel, preserving order. */
|
|
38
|
+
export declare function shrinkImageDataUrls(dataUrls: string[]): Promise<string[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Force a re-encode at a specific long-edge cap, ignoring the passthrough rule.
|
|
41
|
+
* Used to escalate compression when a request is still over budget after the
|
|
42
|
+
* standard downscale — always preferable to dropping a reference outright.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reencodeAtEdge(dataUrl: string, edge: number, quality?: number): Promise<string>;
|
|
45
|
+
/** Escalation ladder used when the standard downscale is not enough. */
|
|
46
|
+
export declare const ESCALATION_EDGES: readonly [960, 720, 512];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranked slot allocation for the reference images attached to an image
|
|
3
|
+
* generation request.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this exists
|
|
6
|
+
*
|
|
7
|
+
* The previous rule was a single boolean: if the user attached ANY reference,
|
|
8
|
+
* the brand logo, both font specimens, and every host-injected brand asset were
|
|
9
|
+
* deleted. Unconditionally — no size check, no count check. That made
|
|
10
|
+
* "replace their logo with ours" unsatisfiable, because the logo the prompt
|
|
11
|
+
* asked for was never attached.
|
|
12
|
+
*
|
|
13
|
+
* The real constraint was never "brand assets compete with the user" — it is
|
|
14
|
+
* the host proxy's platform request cap (Vercel rejects bodies over 4.5 MB at
|
|
15
|
+
* the edge with an opaque 413). Competition is solved by ORDER and by the
|
|
16
|
+
* prompt's precedence rules, not by deletion.
|
|
17
|
+
*
|
|
18
|
+
* ## The contract
|
|
19
|
+
*
|
|
20
|
+
* Priority, highest first:
|
|
21
|
+
*
|
|
22
|
+
* 1. `user` — references the user attached themselves (max 6)
|
|
23
|
+
* 2. `source` — the image being edited or resized (quick edit, resize)
|
|
24
|
+
* 3. `logo` — the brand logo
|
|
25
|
+
* 4. `brandAsset` — product / lifestyle photos from the brand bank
|
|
26
|
+
* 5. `adReference` — existing ad creatives and template thumbnails
|
|
27
|
+
*
|
|
28
|
+
* Everything is sent when it fits. Truncation happens ONLY when the count or
|
|
29
|
+
* byte budget is exceeded, and always from the bottom up — never touching
|
|
30
|
+
* `user`, and never touching `logo` unless the caller marks it droppable
|
|
31
|
+
* (quick edit and resize, where the source image matters more).
|
|
32
|
+
*
|
|
33
|
+
* Brand assets and ad references degrade gracefully: surplus copies go first,
|
|
34
|
+
* so one of each survives as long as possible, and only then is a whole tier
|
|
35
|
+
* given up — ad references before brand assets, per the priority order.
|
|
36
|
+
*
|
|
37
|
+
* The floor is therefore 6 user references + the logo.
|
|
38
|
+
*
|
|
39
|
+
* ## Compression first, always
|
|
40
|
+
*
|
|
41
|
+
* Dropping a reference is the last resort, not the first. `prepareReferences`
|
|
42
|
+
* downscales everything, then escalates compression on the largest offenders,
|
|
43
|
+
* and only starts shedding slots if the payload is *still* over budget.
|
|
44
|
+
*/
|
|
45
|
+
/** Reference tiers in descending priority. Index === priority rank. */
|
|
46
|
+
export declare const REFERENCE_TIERS: readonly ["user", "source", "logo", "brandAsset", "adReference"];
|
|
47
|
+
export type ReferenceTier = typeof REFERENCE_TIERS[number];
|
|
48
|
+
export interface ReferenceSlot {
|
|
49
|
+
tier: ReferenceTier;
|
|
50
|
+
/**
|
|
51
|
+
* The image itself. Empty string marks a *placeholder* — a slot the host
|
|
52
|
+
* proxy will fill from the brand database, which the SDK can only reserve
|
|
53
|
+
* room for because it never sees the bytes. `bytes` carries the reservation.
|
|
54
|
+
*/
|
|
55
|
+
dataUrl: string;
|
|
56
|
+
/** Human label used in the reference manifest. */
|
|
57
|
+
label?: string;
|
|
58
|
+
/** Wire-byte estimate. Derived from `dataUrl` when omitted. */
|
|
59
|
+
bytes?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface DroppedSlot {
|
|
62
|
+
tier: ReferenceTier;
|
|
63
|
+
label?: string;
|
|
64
|
+
reason: "count" | "bytes";
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Hard cap on user-supplied reference images per generation. Six covers every
|
|
68
|
+
* realistic ad brief and keeps the request inside the limits of the models we
|
|
69
|
+
* route to.
|
|
70
|
+
*/
|
|
71
|
+
export declare const MAX_USER_REFERENCE_IMAGES = 6;
|
|
72
|
+
/**
|
|
73
|
+
* Ceiling on total attached references. 6 user + 1 source + 1 logo + 2 brand
|
|
74
|
+
* assets + 2 ad references. Comfortably under the ~20 images-per-request cap
|
|
75
|
+
* observed on OpenRouter.
|
|
76
|
+
*/
|
|
77
|
+
export declare const MAX_REFERENCE_IMAGES = 12;
|
|
78
|
+
/**
|
|
79
|
+
* Wire-byte budget for the attached images alone.
|
|
80
|
+
*
|
|
81
|
+
* Vercel rejects at 4.5 MB. This leaves ~1.3 MB of headroom for the prompt,
|
|
82
|
+
* the brand context block, and the JSON envelope — all of which the caller can
|
|
83
|
+
* further reserve against via `reservedBytes`.
|
|
84
|
+
*/
|
|
85
|
+
export declare const REFERENCE_WIRE_BUDGET_BYTES: number;
|
|
86
|
+
/**
|
|
87
|
+
* What the SDK assumes a host-injected brand asset costs on the wire, and the
|
|
88
|
+
* ceiling the host compresses each one down to before injecting it. Keeping
|
|
89
|
+
* both sides on the same number is what makes the SDK's reservation honest.
|
|
90
|
+
*/
|
|
91
|
+
export declare const HOST_ASSET_BYTE_RESERVATION: number;
|
|
92
|
+
/** Sort into priority order, stable within a tier. */
|
|
93
|
+
export declare function orderByPriority(slots: ReferenceSlot[]): ReferenceSlot[];
|
|
94
|
+
/**
|
|
95
|
+
* The order in which slots may be shed, first to go listed first.
|
|
96
|
+
*
|
|
97
|
+
* `user` and `source` never appear — they are the request. `logo` appears only
|
|
98
|
+
* when `protectLogo` is false, and always last, so it outlives every brand
|
|
99
|
+
* asset.
|
|
100
|
+
*/
|
|
101
|
+
export declare function shedOrder(slots: ReferenceSlot[], protectLogo: boolean): ReferenceSlot[];
|
|
102
|
+
export interface AllocateOptions {
|
|
103
|
+
/** Count ceiling. Defaults to {@link MAX_REFERENCE_IMAGES}. */
|
|
104
|
+
maxImages?: number;
|
|
105
|
+
/** Byte ceiling for images. Defaults to {@link REFERENCE_WIRE_BUDGET_BYTES}. */
|
|
106
|
+
maxBytes?: number;
|
|
107
|
+
/** Non-image payload (prompt, schemas) to charge against `maxBytes`. */
|
|
108
|
+
reservedBytes?: number;
|
|
109
|
+
/**
|
|
110
|
+
* False on surfaces where the source image outranks the logo — quick edit and
|
|
111
|
+
* resize. On generate flows the logo is part of the protected core.
|
|
112
|
+
*/
|
|
113
|
+
protectLogo?: boolean;
|
|
114
|
+
}
|
|
115
|
+
export interface Allocation {
|
|
116
|
+
kept: ReferenceSlot[];
|
|
117
|
+
dropped: DroppedSlot[];
|
|
118
|
+
bytes: number;
|
|
119
|
+
/**
|
|
120
|
+
* True when the protected core alone still exceeds the budget. The caller
|
|
121
|
+
* should send anyway — a 413 with the user's own images is a better failure
|
|
122
|
+
* than silently generating without them.
|
|
123
|
+
*/
|
|
124
|
+
overBudget: boolean;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Pure allocation: order, cap the user tier, then shed bottom-up until the
|
|
128
|
+
* count and byte budgets are met. No compression — see `prepareReferences`.
|
|
129
|
+
*/
|
|
130
|
+
export declare function allocateReferences(slots: ReferenceSlot[], options?: AllocateOptions): Allocation;
|
|
131
|
+
/**
|
|
132
|
+
* Compress everything, escalate on the largest offenders, and only then shed
|
|
133
|
+
* slots. This is the entry point every image-generation surface should use.
|
|
134
|
+
*
|
|
135
|
+
* Placeholder slots (`dataUrl === ""`) are carried through untouched — the host
|
|
136
|
+
* compresses those on its side against {@link HOST_ASSET_BYTE_RESERVATION}.
|
|
137
|
+
*/
|
|
138
|
+
export declare function prepareReferences(slots: ReferenceSlot[], options?: AllocateOptions): Promise<Allocation>;
|