@orangecatai/adgen-canvas 0.0.26 → 0.0.28
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-HAGQZMXY.js → chunk-OTDOAN2W.js} +3 -3
- package/dist/dev/{chunk-M6SLPQMN.js → chunk-WELJ6MTO.js} +2 -2
- package/dist/dev/data/{image-TSTRTL65.js → image-F5HBZTBB.js} +3 -3
- package/dist/dev/index.css +377 -0
- package/dist/dev/index.css.map +3 -3
- package/dist/dev/index.js +4506 -2591
- 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-FABLWJ5B.js → chunk-INDX7AB6.js} +1 -1
- package/dist/prod/{chunk-PIJUOUUJ.js → chunk-RI6ZX32X.js} +2 -2
- package/dist/prod/data/image-P4AE4WIT.js +1 -0
- package/dist/prod/index.css +1 -1
- package/dist/prod/index.js +69 -63
- 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 +25 -0
- package/dist/types/excalidraw/components/App.d.ts +9 -0
- package/dist/types/excalidraw/components/CampaignDropdown.d.ts +14 -0
- package/dist/types/excalidraw/components/FrameToolbar.d.ts +11 -2
- package/dist/types/excalidraw/components/ImageGeneratorPanel.d.ts +14 -0
- package/dist/types/excalidraw/components/VideoChainPanel.d.ts +58 -0
- package/dist/types/excalidraw/components/VideoElement.d.ts +26 -1
- package/dist/types/excalidraw/components/ai-chat/canvasTools.d.ts +272 -3
- package/dist/types/excalidraw/components/frameToolbarVariant.d.ts +23 -0
- package/dist/types/excalidraw/components/video/videoActions.d.ts +37 -0
- package/dist/types/excalidraw/index.d.ts +1 -0
- package/dist/types/excalidraw/types.d.ts +61 -0
- package/dist/types/excalidraw/utils/brandContextUtils.d.ts +21 -0
- package/dist/types/excalidraw/utils/videoApi.d.ts +81 -0
- package/dist/types/excalidraw/utils/videoDurationStops.d.ts +14 -0
- package/dist/types/excalidraw/utils/videoStitch.d.ts +24 -0
- package/package.json +2 -1
- package/dist/prod/data/image-D6B2C7FH.js +0 -1
- /package/dist/dev/{chunk-HAGQZMXY.js.map → chunk-OTDOAN2W.js.map} +0 -0
- /package/dist/dev/{chunk-M6SLPQMN.js.map → chunk-WELJ6MTO.js.map} +0 -0
- /package/dist/dev/data/{image-TSTRTL65.js.map → image-F5HBZTBB.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-INDX7AB6.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-INDX7AB6.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};
|
|
@@ -43,6 +43,21 @@ export type AIChatPanelProps = {
|
|
|
43
43
|
* `chatUrl`. Keeps the provider key out of the browser.
|
|
44
44
|
*/
|
|
45
45
|
imageGenUrl?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Host proxy URL for the agent's generate_video / generate_video_chain
|
|
48
|
+
* tools. Same contract as `imageGenUrl` — the host injects the provider key,
|
|
49
|
+
* gates credits, and returns a `{ jobId }` to poll. Without this pair the
|
|
50
|
+
* video tools report themselves as unconfigured instead of failing mid-run.
|
|
51
|
+
*/
|
|
52
|
+
videoGenUrl?: string;
|
|
53
|
+
/** Host status endpoint for a submitted video job; the jobId is appended as a path segment. */
|
|
54
|
+
videoGenStatusUrl?: string;
|
|
55
|
+
/** Host shot-splitter URL. Required for the agent's generate_video_chain tool. */
|
|
56
|
+
videoPlanUrl?: string;
|
|
57
|
+
/** Host upload URL for a stitched long-form video. Falls back to an in-memory object URL. */
|
|
58
|
+
videoUploadUrl?: string;
|
|
59
|
+
/** Host proxy that re-fetches presigned S3 video URLs server-side (browser CORS workaround). */
|
|
60
|
+
videoDownloadProxyUrl?: string;
|
|
46
61
|
excalidrawAPI?: ExcalidrawImperativeAPI;
|
|
47
62
|
initialMessages?: ChatMessage[];
|
|
48
63
|
initialSessionId?: string;
|
|
@@ -76,6 +91,16 @@ export type AIChatPanelProps = {
|
|
|
76
91
|
error?: string;
|
|
77
92
|
}>;
|
|
78
93
|
onAfterImageGen?: () => void;
|
|
94
|
+
/**
|
|
95
|
+
* Credit gate for a video generated by the chat agent. Called once per
|
|
96
|
+
* generated video — and once per segment of a long-form chain, since each
|
|
97
|
+
* segment is separately billed.
|
|
98
|
+
*/
|
|
99
|
+
onBeforeVideoGen?: () => Promise<{
|
|
100
|
+
allowed: boolean;
|
|
101
|
+
error?: string;
|
|
102
|
+
}>;
|
|
103
|
+
onAfterVideoGen?: () => void;
|
|
79
104
|
/**
|
|
80
105
|
* OpenRouter model tag for the chat system (both plain chat and agent loop).
|
|
81
106
|
* @default "minimax/minimax-m3"
|
|
@@ -259,6 +259,15 @@ declare class App extends React.Component<AppProps, AppState> {
|
|
|
259
259
|
private updateEmbedValidationStatus;
|
|
260
260
|
private updateEmbeddables;
|
|
261
261
|
private renderEmbeddables;
|
|
262
|
+
/**
|
|
263
|
+
* True when this video's parent frame already offers Download and Campaign
|
|
264
|
+
* through the video variant of the frame toolbar. The in-video toolbar is
|
|
265
|
+
* suppressed in that case so the two actions never appear twice at once.
|
|
266
|
+
*
|
|
267
|
+
* Videos with no such frame — pasted, dragged in, agent-placed — keep their
|
|
268
|
+
* own toolbar as the only way to reach those actions.
|
|
269
|
+
*/
|
|
270
|
+
private frameToolbarOwnsVideo;
|
|
262
271
|
private renderVideos;
|
|
263
272
|
private getFrameNameDOMId;
|
|
264
273
|
frameNameBoundsCache: FrameNameBoundsCache;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import "./CampaignDropdown.scss";
|
|
2
|
+
import type { CampaignSummary } from "../types";
|
|
3
|
+
export declare const CampaignDropdown: ({ onFetchCampaigns, onCreateCampaign, onAdd, disabled, disabledReason, }: {
|
|
4
|
+
onFetchCampaigns?: () => Promise<{
|
|
5
|
+
campaigns: CampaignSummary[];
|
|
6
|
+
}>;
|
|
7
|
+
onCreateCampaign?: (name: string) => Promise<CampaignSummary>;
|
|
8
|
+
onAdd?: (campaignId: string) => Promise<{
|
|
9
|
+
ok: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}>;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
disabledReason?: string;
|
|
14
|
+
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import "./FrameToolbar.scss";
|
|
2
|
-
import type {
|
|
2
|
+
import type { FrameToolbarVariant } from "./frameToolbarVariant";
|
|
3
|
+
import type { ExcalidrawFrameLikeElement, ExcalidrawVideoElement, NonDeletedExcalidrawElement } from "@orangecatai/element/types";
|
|
3
4
|
import type { AppClassProperties } from "../types";
|
|
4
|
-
export declare const FrameToolbar: ({ element, app, }: {
|
|
5
|
+
export declare const FrameToolbar: ({ element, app, variant, videoElement, }: {
|
|
5
6
|
element: NonDeletedExcalidrawElement & ExcalidrawFrameLikeElement;
|
|
6
7
|
app: AppClassProperties;
|
|
8
|
+
/**
|
|
9
|
+
* "frame" is the full toolbar. "video" drops Resize and Template — both are
|
|
10
|
+
* image-only — and swaps the export and campaign actions for video ones.
|
|
11
|
+
* See `getFrameToolbarVariant`, which resolves this from the frame.
|
|
12
|
+
*/
|
|
13
|
+
variant?: FrameToolbarVariant;
|
|
14
|
+
/** The frame's ready video child. Required when variant is "video". */
|
|
15
|
+
videoElement?: ExcalidrawVideoElement | null;
|
|
7
16
|
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -65,6 +65,20 @@ export declare const MODEL_CONFIG: {
|
|
|
65
65
|
readonly supportedResolutions: readonly ["1K", "2K", "4K"];
|
|
66
66
|
readonly supportedRatios: readonly ["1:1", "21:9", "16:9", "3:2", "4:3", "5:4", "4:5", "3:4", "2:3", "9:16"];
|
|
67
67
|
};
|
|
68
|
+
readonly "gpt image 1 mini": {
|
|
69
|
+
readonly modelId: "openai/gpt-image-1-mini";
|
|
70
|
+
readonly textOutput: false;
|
|
71
|
+
readonly supportsImageSize: false;
|
|
72
|
+
readonly supportedResolutions: readonly ["1K"];
|
|
73
|
+
readonly supportedRatios: readonly ["1:1", "2:3", "3:2"];
|
|
74
|
+
};
|
|
75
|
+
readonly "gpt image 2": {
|
|
76
|
+
readonly modelId: "openai/gpt-image-2";
|
|
77
|
+
readonly textOutput: false;
|
|
78
|
+
readonly supportsImageSize: false;
|
|
79
|
+
readonly supportedResolutions: readonly ["1K", "2K"];
|
|
80
|
+
readonly supportedRatios: readonly ["21:9", "16:9", "4:3", "3:2", "1:1", "9:16", "3:4", "2:3", "5:4", "4:5"];
|
|
81
|
+
};
|
|
68
82
|
};
|
|
69
83
|
export type ModelName = keyof typeof MODEL_CONFIG;
|
|
70
84
|
export declare const MODEL_NAMES: ModelName[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { type VideoJobParams, type ChainSegmentBeat } from "../utils/videoApi";
|
|
3
|
+
import "./VideoChainPanel.scss";
|
|
4
|
+
import type { ExcalidrawFrameLikeElement, NonDeletedExcalidrawElement } from "@orangecatai/element/types";
|
|
5
|
+
import type { AppClassProperties } from "../types";
|
|
6
|
+
export type ChainSegmentStatus = "queued" | "generating" | "done" | "failed";
|
|
7
|
+
export type { ChainSegmentBeat };
|
|
8
|
+
export interface ChainSegmentState {
|
|
9
|
+
index: number;
|
|
10
|
+
prompt: string;
|
|
11
|
+
status: ChainSegmentStatus;
|
|
12
|
+
beat?: ChainSegmentBeat;
|
|
13
|
+
jobId?: string;
|
|
14
|
+
videoS3Key?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
export type ChainPhase = "idle" | "planning" | "generating" | "stitching" | "done" | "error";
|
|
18
|
+
export interface VideoChainPanelHandle {
|
|
19
|
+
/**
|
|
20
|
+
* Runs the whole plan → generate → stitch pipeline as one motion. If the
|
|
21
|
+
* last run left a failed segment, resumes from it instead of re-planning.
|
|
22
|
+
* Otherwise (no prior chain, or a fully-completed one) discards any
|
|
23
|
+
* previous plan and starts fresh — a changed concept/duration should not
|
|
24
|
+
* silently resume stale segments.
|
|
25
|
+
*/
|
|
26
|
+
start: () => void;
|
|
27
|
+
stop: () => void;
|
|
28
|
+
}
|
|
29
|
+
export interface VideoChainPanelProps {
|
|
30
|
+
element: NonDeletedExcalidrawElement & ExcalidrawFrameLikeElement;
|
|
31
|
+
app: AppClassProperties;
|
|
32
|
+
/** The overall ad concept — sent to the shot-splitter as-is. */
|
|
33
|
+
concept: string;
|
|
34
|
+
/** How many segments the currently-selected duration implies (>1, or this panel wouldn't be mounted). */
|
|
35
|
+
segmentCount: number;
|
|
36
|
+
/** Every segment's duration — always the model's native single-generation max. */
|
|
37
|
+
segmentDurationSeconds: number;
|
|
38
|
+
/** Every video-gen field held fixed across the whole chain (model, ratio, resolution, audio toggle, seed, ...). */
|
|
39
|
+
baseParams: Omit<VideoJobParams, "prompt" | "firstFrameDataUrl">;
|
|
40
|
+
/** Applied to each segment's shot-writer prompt before submission (e.g. brand tone wrapping). Defaults to identity. */
|
|
41
|
+
wrapPrompt?: (segmentPrompt: string) => string;
|
|
42
|
+
/** First frame for segment 0, e.g. a user-uploaded starting image. */
|
|
43
|
+
initialFirstFrameDataUrl?: string;
|
|
44
|
+
videoGenUrl: string;
|
|
45
|
+
videoGenStatusUrl: string;
|
|
46
|
+
videoPlanUrl?: string;
|
|
47
|
+
videoUploadUrl?: string;
|
|
48
|
+
/** Fetches a presigned S3 video URL server-side to work around browser CORS — see resolveFetchUrl. */
|
|
49
|
+
videoDownloadProxyUrl?: string;
|
|
50
|
+
onBeforeVideoGen?: () => Promise<{
|
|
51
|
+
allowed: boolean;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>;
|
|
54
|
+
onAfterVideoGen?: () => void;
|
|
55
|
+
/** Lets the parent reflect chain progress in its own Generate/Stop button and shimmer overlay. */
|
|
56
|
+
onPhaseChange?: (phase: ChainPhase) => void;
|
|
57
|
+
}
|
|
58
|
+
export declare const VideoChainPanel: React.ForwardRefExoticComponent<VideoChainPanelProps & React.RefAttributes<VideoChainPanelHandle>>;
|
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import type { ExcalidrawVideoElement } from "@orangecatai/element/types";
|
|
2
|
+
import type { CampaignSummary } from "../types";
|
|
2
3
|
import "./VideoElement.scss";
|
|
3
4
|
interface VideoElementProps {
|
|
4
5
|
element: ExcalidrawVideoElement;
|
|
5
6
|
isActive: boolean;
|
|
6
7
|
videoDownloadProxyUrl?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Whether to render the in-video Download / Campaign toolbar.
|
|
10
|
+
*
|
|
11
|
+
* Defaults to true. `App.tsx` passes false when the video sits inside a
|
|
12
|
+
* video-generator frame whose frame toolbar already offers both actions, so
|
|
13
|
+
* the two surfaces never appear at once. Videos with no such frame (pasted,
|
|
14
|
+
* dragged in, agent-placed) keep this toolbar as their only access.
|
|
15
|
+
*/
|
|
16
|
+
showToolbar?: boolean;
|
|
17
|
+
onFetchCampaigns?: () => Promise<{
|
|
18
|
+
campaigns: CampaignSummary[];
|
|
19
|
+
}>;
|
|
20
|
+
onCreateCampaign?: (name: string) => Promise<CampaignSummary>;
|
|
21
|
+
onAddToCampaign?: (payload: {
|
|
22
|
+
kind: "video";
|
|
23
|
+
campaignId: string;
|
|
24
|
+
videoUrl: string;
|
|
25
|
+
mimeType: string;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
}) => Promise<{
|
|
29
|
+
ok: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
}>;
|
|
7
32
|
}
|
|
8
|
-
export declare const VideoElement: ({ element, videoDownloadProxyUrl, }: VideoElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
export declare const VideoElement: ({ element, videoDownloadProxyUrl, showToolbar, onFetchCampaigns, onCreateCampaign, onAddToCampaign, }: VideoElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
34
|
export {};
|
|
@@ -1,5 +1,85 @@
|
|
|
1
1
|
import type { ExcalidrawElement } from "@orangecatai/element/types";
|
|
2
|
-
import type { ExcalidrawImperativeAPI } from "../../types";
|
|
2
|
+
import type { ExcalidrawImperativeAPI, BrandContext } from "../../types";
|
|
3
|
+
/** The only video model the chat agent can drive. */
|
|
4
|
+
export declare const CHAT_VIDEO_MODEL = "google/veo-3.1-fast";
|
|
5
|
+
export declare const VIDEO_TOOLS: ({
|
|
6
|
+
type: "function";
|
|
7
|
+
function: {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
parameters: {
|
|
11
|
+
type: string;
|
|
12
|
+
properties: {
|
|
13
|
+
first_frame_element_id: {
|
|
14
|
+
readonly type: "string";
|
|
15
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
16
|
+
};
|
|
17
|
+
last_frame_element_id: {
|
|
18
|
+
readonly type: "string";
|
|
19
|
+
readonly description: "ID of an existing IMAGE element on the canvas to use as the video's closing frame. Optional.";
|
|
20
|
+
};
|
|
21
|
+
first_frame_attachment_index: {
|
|
22
|
+
readonly type: "number";
|
|
23
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
24
|
+
};
|
|
25
|
+
last_frame_attachment_index: {
|
|
26
|
+
readonly type: "number";
|
|
27
|
+
readonly description: "0-based index of an attached chat image to use as the closing frame. Optional.";
|
|
28
|
+
};
|
|
29
|
+
frameId: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
prompt: {
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
};
|
|
37
|
+
duration: {
|
|
38
|
+
type: string;
|
|
39
|
+
enum: (4 | 6 | 8)[];
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
narrative_prompt?: undefined;
|
|
43
|
+
total_duration?: undefined;
|
|
44
|
+
};
|
|
45
|
+
required: string[];
|
|
46
|
+
additionalProperties: boolean;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
type: "function";
|
|
51
|
+
function: {
|
|
52
|
+
name: string;
|
|
53
|
+
description: string;
|
|
54
|
+
parameters: {
|
|
55
|
+
type: string;
|
|
56
|
+
properties: {
|
|
57
|
+
frameId: {
|
|
58
|
+
type: string;
|
|
59
|
+
description: string;
|
|
60
|
+
};
|
|
61
|
+
narrative_prompt: {
|
|
62
|
+
type: string;
|
|
63
|
+
description: string;
|
|
64
|
+
};
|
|
65
|
+
total_duration: {
|
|
66
|
+
type: string;
|
|
67
|
+
description: string;
|
|
68
|
+
};
|
|
69
|
+
first_frame_element_id: {
|
|
70
|
+
readonly type: "string";
|
|
71
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
72
|
+
};
|
|
73
|
+
first_frame_attachment_index: {
|
|
74
|
+
readonly type: "number";
|
|
75
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
required: string[];
|
|
79
|
+
additionalProperties: boolean;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
})[];
|
|
3
83
|
export declare const CANVAS_TOOLS: readonly [{
|
|
4
84
|
readonly type: "function";
|
|
5
85
|
readonly function: {
|
|
@@ -167,7 +247,84 @@ export declare const CANVAS_TOOLS: readonly [{
|
|
|
167
247
|
readonly additionalProperties: false;
|
|
168
248
|
};
|
|
169
249
|
};
|
|
170
|
-
}, {
|
|
250
|
+
}, ...({
|
|
251
|
+
type: "function";
|
|
252
|
+
function: {
|
|
253
|
+
name: string;
|
|
254
|
+
description: string;
|
|
255
|
+
parameters: {
|
|
256
|
+
type: string;
|
|
257
|
+
properties: {
|
|
258
|
+
first_frame_element_id: {
|
|
259
|
+
readonly type: "string";
|
|
260
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
261
|
+
};
|
|
262
|
+
last_frame_element_id: {
|
|
263
|
+
readonly type: "string";
|
|
264
|
+
readonly description: "ID of an existing IMAGE element on the canvas to use as the video's closing frame. Optional.";
|
|
265
|
+
};
|
|
266
|
+
first_frame_attachment_index: {
|
|
267
|
+
readonly type: "number";
|
|
268
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
269
|
+
};
|
|
270
|
+
last_frame_attachment_index: {
|
|
271
|
+
readonly type: "number";
|
|
272
|
+
readonly description: "0-based index of an attached chat image to use as the closing frame. Optional.";
|
|
273
|
+
};
|
|
274
|
+
frameId: {
|
|
275
|
+
type: string;
|
|
276
|
+
description: string;
|
|
277
|
+
};
|
|
278
|
+
prompt: {
|
|
279
|
+
type: string;
|
|
280
|
+
description: string;
|
|
281
|
+
};
|
|
282
|
+
duration: {
|
|
283
|
+
type: string;
|
|
284
|
+
enum: (4 | 6 | 8)[];
|
|
285
|
+
description: string;
|
|
286
|
+
};
|
|
287
|
+
narrative_prompt?: undefined;
|
|
288
|
+
total_duration?: undefined;
|
|
289
|
+
};
|
|
290
|
+
required: string[];
|
|
291
|
+
additionalProperties: boolean;
|
|
292
|
+
};
|
|
293
|
+
};
|
|
294
|
+
} | {
|
|
295
|
+
type: "function";
|
|
296
|
+
function: {
|
|
297
|
+
name: string;
|
|
298
|
+
description: string;
|
|
299
|
+
parameters: {
|
|
300
|
+
type: string;
|
|
301
|
+
properties: {
|
|
302
|
+
frameId: {
|
|
303
|
+
type: string;
|
|
304
|
+
description: string;
|
|
305
|
+
};
|
|
306
|
+
narrative_prompt: {
|
|
307
|
+
type: string;
|
|
308
|
+
description: string;
|
|
309
|
+
};
|
|
310
|
+
total_duration: {
|
|
311
|
+
type: string;
|
|
312
|
+
description: string;
|
|
313
|
+
};
|
|
314
|
+
first_frame_element_id: {
|
|
315
|
+
readonly type: "string";
|
|
316
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
317
|
+
};
|
|
318
|
+
first_frame_attachment_index: {
|
|
319
|
+
readonly type: "number";
|
|
320
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
321
|
+
};
|
|
322
|
+
};
|
|
323
|
+
required: string[];
|
|
324
|
+
additionalProperties: boolean;
|
|
325
|
+
};
|
|
326
|
+
};
|
|
327
|
+
})[], {
|
|
171
328
|
readonly type: "function";
|
|
172
329
|
readonly function: {
|
|
173
330
|
readonly name: "update_element";
|
|
@@ -268,7 +425,84 @@ export declare const CANVAS_TOOLS: readonly [{
|
|
|
268
425
|
* agent knows it can use those numbers in `add_text` calls.
|
|
269
426
|
*/
|
|
270
427
|
export declare function getCanvasTools(extraFontIds?: number[]): typeof CANVAS_TOOLS;
|
|
271
|
-
export declare const BANNER_TOOLS: readonly [{
|
|
428
|
+
export declare const BANNER_TOOLS: readonly [...({
|
|
429
|
+
type: "function";
|
|
430
|
+
function: {
|
|
431
|
+
name: string;
|
|
432
|
+
description: string;
|
|
433
|
+
parameters: {
|
|
434
|
+
type: string;
|
|
435
|
+
properties: {
|
|
436
|
+
first_frame_element_id: {
|
|
437
|
+
readonly type: "string";
|
|
438
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
439
|
+
};
|
|
440
|
+
last_frame_element_id: {
|
|
441
|
+
readonly type: "string";
|
|
442
|
+
readonly description: "ID of an existing IMAGE element on the canvas to use as the video's closing frame. Optional.";
|
|
443
|
+
};
|
|
444
|
+
first_frame_attachment_index: {
|
|
445
|
+
readonly type: "number";
|
|
446
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
447
|
+
};
|
|
448
|
+
last_frame_attachment_index: {
|
|
449
|
+
readonly type: "number";
|
|
450
|
+
readonly description: "0-based index of an attached chat image to use as the closing frame. Optional.";
|
|
451
|
+
};
|
|
452
|
+
frameId: {
|
|
453
|
+
type: string;
|
|
454
|
+
description: string;
|
|
455
|
+
};
|
|
456
|
+
prompt: {
|
|
457
|
+
type: string;
|
|
458
|
+
description: string;
|
|
459
|
+
};
|
|
460
|
+
duration: {
|
|
461
|
+
type: string;
|
|
462
|
+
enum: (4 | 6 | 8)[];
|
|
463
|
+
description: string;
|
|
464
|
+
};
|
|
465
|
+
narrative_prompt?: undefined;
|
|
466
|
+
total_duration?: undefined;
|
|
467
|
+
};
|
|
468
|
+
required: string[];
|
|
469
|
+
additionalProperties: boolean;
|
|
470
|
+
};
|
|
471
|
+
};
|
|
472
|
+
} | {
|
|
473
|
+
type: "function";
|
|
474
|
+
function: {
|
|
475
|
+
name: string;
|
|
476
|
+
description: string;
|
|
477
|
+
parameters: {
|
|
478
|
+
type: string;
|
|
479
|
+
properties: {
|
|
480
|
+
frameId: {
|
|
481
|
+
type: string;
|
|
482
|
+
description: string;
|
|
483
|
+
};
|
|
484
|
+
narrative_prompt: {
|
|
485
|
+
type: string;
|
|
486
|
+
description: string;
|
|
487
|
+
};
|
|
488
|
+
total_duration: {
|
|
489
|
+
type: string;
|
|
490
|
+
description: string;
|
|
491
|
+
};
|
|
492
|
+
first_frame_element_id: {
|
|
493
|
+
readonly type: "string";
|
|
494
|
+
readonly description: "ID of an existing IMAGE element already on the canvas to use as the video's opening frame (image-to-video). Use this to animate an image the user can see on the canvas.";
|
|
495
|
+
};
|
|
496
|
+
first_frame_attachment_index: {
|
|
497
|
+
readonly type: "number";
|
|
498
|
+
readonly description: "0-based index of an image the user ATTACHED to the chat, to use as the opening frame. Use this instead of first_frame_element_id when the source image was uploaded in chat rather than placed on the canvas.";
|
|
499
|
+
};
|
|
500
|
+
};
|
|
501
|
+
required: string[];
|
|
502
|
+
additionalProperties: boolean;
|
|
503
|
+
};
|
|
504
|
+
};
|
|
505
|
+
})[], {
|
|
272
506
|
readonly type: "function";
|
|
273
507
|
readonly function: {
|
|
274
508
|
readonly name: "create_frame";
|
|
@@ -696,6 +930,41 @@ export type ToolExecutionContext = {
|
|
|
696
930
|
name: string;
|
|
697
931
|
dataUrl?: string;
|
|
698
932
|
}>;
|
|
933
|
+
/**
|
|
934
|
+
* Full brand identity. The agent already receives this as a system message,
|
|
935
|
+
* but the video tools also append it to the generation prompt deterministically
|
|
936
|
+
* via buildVideoBrandPrompt — the same wrapper VideoGeneratorPanel uses — so
|
|
937
|
+
* chat-generated footage is as on-brand as panel-generated footage instead of
|
|
938
|
+
* depending on whatever the model chose to restate.
|
|
939
|
+
*/
|
|
940
|
+
brandContext?: BrandContext;
|
|
941
|
+
/** Host proxy URL for submitting a video-gen job. Required for generate_video. */
|
|
942
|
+
videoGenUrl?: string;
|
|
943
|
+
/** Host proxy URL polled for job status (the jobId is appended as a path segment). */
|
|
944
|
+
videoGenStatusUrl?: string;
|
|
945
|
+
/** Host shot-splitter URL. Required for generate_video_chain. */
|
|
946
|
+
videoPlanUrl?: string;
|
|
947
|
+
/** Host upload URL for the stitched chain output. Falls back to an object URL. */
|
|
948
|
+
videoUploadUrl?: string;
|
|
949
|
+
/** Host proxy that re-fetches presigned S3 video URLs server-side (CORS workaround). */
|
|
950
|
+
videoDownloadProxyUrl?: string;
|
|
951
|
+
/**
|
|
952
|
+
* Credit gate for a single video generation. Called once per generated
|
|
953
|
+
* video — including once per segment of a chain, since each segment is its
|
|
954
|
+
* own billed generation (matching the standalone panel's contract).
|
|
955
|
+
*/
|
|
956
|
+
onBeforeVideoGen?: () => Promise<{
|
|
957
|
+
allowed: boolean;
|
|
958
|
+
error?: string;
|
|
959
|
+
}>;
|
|
960
|
+
/** Fired after each successful video generation (host persists / refreshes). */
|
|
961
|
+
onAfterVideoGen?: () => void;
|
|
962
|
+
/**
|
|
963
|
+
* Pushes an interim status line to the chat while a long-running tool is
|
|
964
|
+
* mid-flight. Video generation blocks for minutes, so it repeatedly calls
|
|
965
|
+
* this to keep the panel's status indicator moving instead of frozen.
|
|
966
|
+
*/
|
|
967
|
+
onStatus?: (message: string) => void;
|
|
699
968
|
onSearchBrandAssets?: (query: string, type?: string) => Promise<Array<{
|
|
700
969
|
id: string;
|
|
701
970
|
name: string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ElementsMapOrArray, ExcalidrawFrameLikeElement, ExcalidrawVideoElement } from "@orangecatai/element/types";
|
|
2
|
+
export type FrameToolbarVariant = "frame" | "video";
|
|
3
|
+
export type FrameToolbarVariantResult = {
|
|
4
|
+
variant: FrameToolbarVariant;
|
|
5
|
+
/** The frame's ready video child. Only ever set for the "video" variant. */
|
|
6
|
+
videoElement: ExcalidrawVideoElement | null;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Decides which frame toolbar — if any — a selected frame should show.
|
|
10
|
+
*
|
|
11
|
+
* - `"frame"` plain frames (agent-created, manual): the full toolbar.
|
|
12
|
+
* - `"video"` a video-generator frame holding a ready video: sizing, download,
|
|
13
|
+
* campaign. No Resize or Template — both are image-only.
|
|
14
|
+
* - `null` image-generator frames, and video-generator frames that are
|
|
15
|
+
* empty, still generating, or holding a video the host could not
|
|
16
|
+
* act on.
|
|
17
|
+
*
|
|
18
|
+
* A video counts as ready only when it is saved, has a URL, and that URL is not
|
|
19
|
+
* a blob: URL. That last condition is the same guard the in-video toolbar
|
|
20
|
+
* applies: a blob URL cannot be fetched server-side, so offering Download or
|
|
21
|
+
* Campaign on one would present an action that is guaranteed to fail.
|
|
22
|
+
*/
|
|
23
|
+
export declare const getFrameToolbarVariant: (frame: ExcalidrawFrameLikeElement, elements: ElementsMapOrArray) => FrameToolbarVariantResult | null;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ExcalidrawVideoElement } from "@orangecatai/element/types";
|
|
2
|
+
import type { AddToCampaignPayload } from "../../types";
|
|
3
|
+
/**
|
|
4
|
+
* Video actions shared by the two surfaces that offer them: the in-video
|
|
5
|
+
* toolbar (`VideoElement`) and the video variant of `FrameToolbar`.
|
|
6
|
+
*
|
|
7
|
+
* These live here rather than in either component because the blob-URL rule
|
|
8
|
+
* below is easy to get wrong and impossible to notice when it drifts — a
|
|
9
|
+
* blob URL sent to the host surfaces as a server-side failure, not a visible
|
|
10
|
+
* client error.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* blob: URLs are local-preview-only. They are assigned while an upload is still
|
|
14
|
+
* in flight and are resolvable only in the tab that created them, so they must
|
|
15
|
+
* never be handed to the server — neither the download proxy nor the campaign
|
|
16
|
+
* copy step can fetch one.
|
|
17
|
+
*/
|
|
18
|
+
export declare const isBlobVideoUrl: (videoUrl: string | null) => boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Resolves the URL to use for `<video src>`. Routing through the proxy avoids
|
|
21
|
+
* presigned-URL expiry on long-lived canvases.
|
|
22
|
+
*/
|
|
23
|
+
export declare const resolveVideoPlaybackUrl: (videoUrl: string | null, videoDownloadProxyUrl?: string) => string | null;
|
|
24
|
+
/** Triggers a browser download of the video file itself — never a canvas export. */
|
|
25
|
+
export declare const downloadVideo: ({ videoUrl, mimeType, videoDownloadProxyUrl, }: {
|
|
26
|
+
videoUrl: string | null;
|
|
27
|
+
mimeType: ExcalidrawVideoElement["mimeType"];
|
|
28
|
+
videoDownloadProxyUrl?: string;
|
|
29
|
+
}) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Builds the host payload for adding a video to a campaign.
|
|
32
|
+
* Returns null when the video is not in a state the host can accept, so callers
|
|
33
|
+
* can surface "not ready yet" instead of issuing a request that would fail.
|
|
34
|
+
*/
|
|
35
|
+
export declare const buildVideoCampaignPayload: (element: ExcalidrawVideoElement, campaignId: string) => Extract<AddToCampaignPayload, {
|
|
36
|
+
kind: "video";
|
|
37
|
+
}> | null;
|
|
@@ -57,4 +57,5 @@ export type { AppState, BinaryFiles, BinaryFileData, ExcalidrawProps, Excalidraw
|
|
|
57
57
|
export type { ExcalidrawElement, NonDeletedExcalidrawElement, NonDeleted, OrderedExcalidrawElement, ExcalidrawElementType, ExcalidrawVideoElement, } from "@orangecatai/element/types";
|
|
58
58
|
export type { ImageEditToolbarCallbacks } from "./components/ImageEditToolbar";
|
|
59
59
|
export type { CustomFontSpec, BrandContext } from "./types";
|
|
60
|
+
export type { CampaignSummary, AddToCampaignPayload } from "./types";
|
|
60
61
|
export type { ReviewerFeedback, ReviewIssue, } from "./components/ai-chat/reviewerAgent";
|
|
@@ -521,6 +521,38 @@ export type BrandContext = {
|
|
|
521
521
|
*/
|
|
522
522
|
headlineFontUrl?: string;
|
|
523
523
|
};
|
|
524
|
+
/** A campaign the user can save a frame/video snapshot into. */
|
|
525
|
+
export type CampaignSummary = {
|
|
526
|
+
id: string;
|
|
527
|
+
name: string;
|
|
528
|
+
};
|
|
529
|
+
/**
|
|
530
|
+
* Payload passed to `onAddToCampaign`. `image` carries a rendered PNG blob
|
|
531
|
+
* of the exported frame; `video` carries the video element's existing URL
|
|
532
|
+
* (the host copies the file server-side — it never round-trips the browser).
|
|
533
|
+
*/
|
|
534
|
+
export type AddToCampaignPayload = {
|
|
535
|
+
kind: "image";
|
|
536
|
+
campaignId: string;
|
|
537
|
+
blob: Blob;
|
|
538
|
+
mimeType: string;
|
|
539
|
+
width: number;
|
|
540
|
+
height: number;
|
|
541
|
+
} | {
|
|
542
|
+
kind: "video";
|
|
543
|
+
campaignId: string;
|
|
544
|
+
videoUrl: string;
|
|
545
|
+
mimeType: string;
|
|
546
|
+
width: number;
|
|
547
|
+
height: number;
|
|
548
|
+
/**
|
|
549
|
+
* The video element's existing first-frame JPEG data URL, if it has one
|
|
550
|
+
* (`ExcalidrawVideoElement.thumbnailDataUrl`). The host decodes and
|
|
551
|
+
* stores this so the campaign preview never has to fall back to a
|
|
552
|
+
* generic placeholder for canvas-added videos.
|
|
553
|
+
*/
|
|
554
|
+
thumbnailDataUrl?: string;
|
|
555
|
+
};
|
|
524
556
|
export interface ExcalidrawProps {
|
|
525
557
|
onChange?: (elements: readonly OrderedExcalidrawElement[], appState: AppState, files: BinaryFiles) => void;
|
|
526
558
|
onIncrement?: (event: DurableIncrement | EphemeralIncrement) => void;
|
|
@@ -675,6 +707,35 @@ export interface ExcalidrawProps {
|
|
|
675
707
|
onAfterVideoGen?: () => void;
|
|
676
708
|
/** Host proxy URL for video downloads — works around S3 CORS / presigned-URL expiry. */
|
|
677
709
|
videoDownloadProxyUrl?: string;
|
|
710
|
+
/**
|
|
711
|
+
* Host proxy URL for the long-form video shot-splitting LLM call (POST).
|
|
712
|
+
* Body: `{ concept, segmentCount, segmentDurationSeconds }`. Response:
|
|
713
|
+
* `{ styleAnchor, segments }`. Without this prop, the "Long-form" chaining
|
|
714
|
+
* mode is not offered in the video generator panel.
|
|
715
|
+
*/
|
|
716
|
+
videoPlanUrl?: string;
|
|
717
|
+
/**
|
|
718
|
+
* Fetches the user's campaigns for the "Add to campaign" dropdown on the
|
|
719
|
+
* frame toolbar and the video element toolbar. Without this prop, the
|
|
720
|
+
* campaign button is not rendered.
|
|
721
|
+
*/
|
|
722
|
+
onFetchCampaigns?: () => Promise<{
|
|
723
|
+
campaigns: CampaignSummary[];
|
|
724
|
+
}>;
|
|
725
|
+
/**
|
|
726
|
+
* Creates a new campaign from the inline "New campaign" row in the
|
|
727
|
+
* dropdown. Returns the created campaign so the SDK can auto-select it.
|
|
728
|
+
*/
|
|
729
|
+
onCreateCampaign?: (name: string) => Promise<CampaignSummary>;
|
|
730
|
+
/**
|
|
731
|
+
* Saves a snapshot of the selected frame (exported to PNG) or video
|
|
732
|
+
* element (by its existing URL) into the chosen campaign. This is a
|
|
733
|
+
* free action (no credit gate) since no AI call is involved.
|
|
734
|
+
*/
|
|
735
|
+
onAddToCampaign?: (payload: AddToCampaignPayload) => Promise<{
|
|
736
|
+
ok: boolean;
|
|
737
|
+
error?: string;
|
|
738
|
+
}>;
|
|
678
739
|
/**
|
|
679
740
|
* Called before a background removal is run (deduct credits, check quota).
|
|
680
741
|
* Return `{ allowed: false, error: "..." }` to block the operation.
|