@orangecatai/adgen-canvas 0.0.33 → 0.0.35
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-BSCN3CPW.js → chunk-AVI2TDX6.js} +2 -2
- package/dist/dev/{chunk-O66HSY73.js → chunk-KJZTX4HK.js} +9 -6
- package/dist/dev/{chunk-O66HSY73.js.map → chunk-KJZTX4HK.js.map} +3 -3
- package/dist/dev/data/{image-ADAEIYVZ.js → image-JWQCODTZ.js} +3 -3
- package/dist/dev/index.css +714 -704
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +9779 -8212
- 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-MBLHWEZ7.js → chunk-R3CESCMK.js} +3 -3
- package/dist/prod/{chunk-DB27YYNM.js → chunk-TE4JIU3E.js} +1 -1
- package/dist/prod/data/image-EQCUXFMW.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +165 -103
- package/dist/prod/subset-shared.chunk.js +1 -1
- package/dist/prod/subset-worker.chunk.js +1 -1
- package/dist/types/element/src/frame.d.ts +16 -0
- package/dist/types/excalidraw/components/ImageGeneratorPanel.d.ts +15 -0
- package/dist/types/excalidraw/components/ai-chat/imagePayload.d.ts +2 -19
- 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/components/video/pendingVideoGenerations.d.ts +46 -0
- package/dist/types/excalidraw/renderer/staticSvgScene.d.ts +1 -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-ALVZ6E6S.js +0 -1
- /package/dist/dev/{chunk-BSCN3CPW.js.map → chunk-AVI2TDX6.js.map} +0 -0
- /package/dist/dev/data/{image-ADAEIYVZ.js.map → image-JWQCODTZ.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-TE4JIU3E.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-TE4JIU3E.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};
|
|
@@ -20,6 +20,22 @@ export declare const groupsAreCompletelyOutOfFrame: (elements: readonly NonDelet
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const groupByFrameLikes: (elements: readonly ExcalidrawElement[]) => Map<string, ExcalidrawElement[]>;
|
|
22
22
|
export declare const getFrameChildren: (allElements: ElementsMapOrArray, frameId: string) => ExcalidrawElement[];
|
|
23
|
+
/**
|
|
24
|
+
* Frames are containers, not artwork: a frame drawn above the elements sitting
|
|
25
|
+
* on or near it hides them behind its fill and steals clicks meant for them,
|
|
26
|
+
* which reads to the user as "my image disappeared". Element order is z-order,
|
|
27
|
+
* so this pushes every frame-like element below every other element.
|
|
28
|
+
*
|
|
29
|
+
* Applied at the presentation layer only — rendering and hit testing — never to
|
|
30
|
+
* the scene itself: the scene array is mirrored by fractional indices that the
|
|
31
|
+
* store, history and collab reconciliation all reason about, and rewriting them
|
|
32
|
+
* on every scene update fights undo/redo. Relative order within each bucket is
|
|
33
|
+
* preserved, so frame-vs-frame and image-vs-image stacking survives untouched.
|
|
34
|
+
*
|
|
35
|
+
* Returns the passed array unchanged (same reference) when it already satisfies
|
|
36
|
+
* the invariant, which is the common case.
|
|
37
|
+
*/
|
|
38
|
+
export declare const orderFramesToBack: <T extends ExcalidrawElement>(elements: readonly T[]) => readonly T[];
|
|
23
39
|
export declare const getFrameLikeElements: (allElements: ExcalidrawElementsIncludingDeleted) => ExcalidrawFrameLikeElement[];
|
|
24
40
|
/**
|
|
25
41
|
* Returns ExcalidrawFrameElements and non-frame-children elements.
|
|
@@ -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;
|
|
@@ -19,25 +19,8 @@
|
|
|
19
19
|
* multi-megabyte data URLs, and those are re-sent on every subsequent turn.
|
|
20
20
|
* Normalising here fixes existing conversations, not just new uploads.
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
|
-
export
|
|
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. Callers treat this as
|
|
35
|
-
* best-effort normalisation, with `enforceImagePayloadBudget` as the backstop
|
|
36
|
-
* for anything that could not be shrunk.
|
|
37
|
-
*/
|
|
38
|
-
export declare function shrinkImageDataUrl(dataUrl: string): Promise<string>;
|
|
39
|
-
/** Shrink a batch in parallel, preserving order. */
|
|
40
|
-
export declare function shrinkImageDataUrls(dataUrls: string[]): Promise<string[]>;
|
|
22
|
+
import { dataUrlWireBytes, isRasterImageDataUrl, shrinkImageDataUrl, shrinkImageDataUrls } from "../../utils/imageCompression";
|
|
23
|
+
export { dataUrlWireBytes, isRasterImageDataUrl, shrinkImageDataUrl, shrinkImageDataUrls, };
|
|
41
24
|
/**
|
|
42
25
|
* Normalise every image in an outgoing request so the body cannot exceed the
|
|
43
26
|
* platform request cap.
|
|
@@ -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;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pending video generation state — module-level so a run survives the panel
|
|
3
|
+
* unmounting. VideoGeneratorPanel is rendered only while its frame is
|
|
4
|
+
* selected (see App.tsx), so clicking empty canvas mid-generation tears the
|
|
5
|
+
* component down; without this store the spinner, progress and prompt would
|
|
6
|
+
* vanish and reselecting the frame would show an idle panel even though the
|
|
7
|
+
* request is still in flight.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors image-generation/pendingGenerations.ts. Flow:
|
|
10
|
+
* 1. Generate → startPendingVideoGeneration(frameId, prompt, controller).
|
|
11
|
+
* 2. The panel seeds its local state from getPendingVideoState(frameId) on
|
|
12
|
+
* mount and subscribes for the rest, so a remount picks the run back up.
|
|
13
|
+
* 3. Status/progress updates flow through the store, so the in-flight closure
|
|
14
|
+
* (which may belong to an already-unmounted instance) and whichever panel
|
|
15
|
+
* is currently mounted stay in sync.
|
|
16
|
+
* 4. On success → clearVideoGenerationState(frameId) so reselecting the frame
|
|
17
|
+
* shows a clean prompt. On error the entry is kept so the message survives.
|
|
18
|
+
* 5. Stop button → abortPendingVideoGeneration(frameId).
|
|
19
|
+
*
|
|
20
|
+
* Subscribers are tracked separately from entries so a panel can subscribe
|
|
21
|
+
* before any generation has started on its frame.
|
|
22
|
+
*/
|
|
23
|
+
export type PendingVideoStatus = "submitting" | "polling" | "done" | "error";
|
|
24
|
+
export interface PendingVideoState {
|
|
25
|
+
prompt: string;
|
|
26
|
+
status: PendingVideoStatus;
|
|
27
|
+
progress: number;
|
|
28
|
+
error: string | null;
|
|
29
|
+
}
|
|
30
|
+
export type VideoGenerationUpdateCallback = (state: PendingVideoState | null) => void;
|
|
31
|
+
export declare function startPendingVideoGeneration(frameId: string, prompt: string, controller: AbortController): void;
|
|
32
|
+
export declare function setPendingVideoStatus(frameId: string, status: PendingVideoStatus): void;
|
|
33
|
+
export declare function setPendingVideoProgress(frameId: string, progress: number): void;
|
|
34
|
+
/**
|
|
35
|
+
* Terminal update. `null` → success and `"aborted"` → stopped both drop the
|
|
36
|
+
* entry (nothing left to show); any other string parks the entry in "error"
|
|
37
|
+
* so a panel remounted afterwards still explains what happened.
|
|
38
|
+
*/
|
|
39
|
+
export declare function finishPendingVideoGeneration(frameId: string, error: string | null): void;
|
|
40
|
+
export declare function subscribeVideoGeneration(frameId: string, fn: VideoGenerationUpdateCallback): void;
|
|
41
|
+
export declare function unsubscribeVideoGeneration(frameId: string, fn: VideoGenerationUpdateCallback): void;
|
|
42
|
+
export declare function clearVideoGenerationState(frameId: string): void;
|
|
43
|
+
export declare function abortPendingVideoGeneration(frameId: string): void;
|
|
44
|
+
export declare function hasPendingVideoGeneration(frameId: string): boolean;
|
|
45
|
+
export declare function getPendingVideoState(frameId: string): PendingVideoState | null;
|
|
46
|
+
export declare function getPendingVideoPrompt(frameId: string): string;
|
|
@@ -2,4 +2,4 @@ import type { NonDeletedExcalidrawElement } from "@orangecatai/element/types";
|
|
|
2
2
|
import type { RenderableElementsMap, SVGRenderConfig } from "../scene/types";
|
|
3
3
|
import type { BinaryFiles } from "../types";
|
|
4
4
|
import type { RoughSVG } from "roughjs/bin/svg";
|
|
5
|
-
export declare const renderSceneToSvg: (
|
|
5
|
+
export declare const renderSceneToSvg: (unorderedElements: readonly NonDeletedExcalidrawElement[], elementsMap: RenderableElementsMap, rsvg: RoughSVG, svgRoot: SVGElement, files: BinaryFiles, renderConfig: SVGRenderConfig) => void;
|
|
@@ -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];
|