@vivix-labs/project-studio 0.0.0-stage → 1.0.0-beta
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/Inter-LICENSE.txt +93 -0
- package/dist/UI-LICENSE.txt +21 -0
- package/dist/assets/35a50-G5EJUNOC.svg +6 -0
- package/dist/assets/369ca-BISNKZ3M.svg +6 -0
- package/dist/assets/441bc-BCMNQ72W.svg +6 -0
- package/dist/assets/483f0-T6ZAGTDG.svg +6 -0
- package/dist/assets/6911b-ESLYKSC6.svg +6 -0
- package/dist/assets/Inter-LICENSE.txt +93 -0
- package/dist/assets/UI-system-LICENSE.txt +21 -0
- package/dist/assets/add-JY533O73.svg +5 -0
- package/dist/assets/area-P2WOMBT5.svg +9 -0
- package/dist/assets/b70bb-BTTOKPOH.svg +6 -0
- package/dist/assets/b9919-3S3O6YOT.svg +6 -0
- package/dist/assets/calendar-JI6USFRJ.svg +1 -0
- package/dist/assets/check-JSRRIXN5.svg +5 -0
- package/dist/assets/check-on-content-FQUVG3IZ.svg +5 -0
- package/dist/assets/chevron-down-K7YKS2EY.svg +5 -0
- package/dist/assets/chevron-left-JQCLK2WZ.svg +3 -0
- package/dist/assets/chevron-right-TNE35DBJ.svg +3 -0
- package/dist/assets/chevron-up-VBHYAJVB.svg +5 -0
- package/dist/assets/clear-XNEZJKFC.svg +5 -0
- package/dist/assets/close-GO5F7YFS.svg +10 -0
- package/dist/assets/dccd4-CZJXWICW.svg +6 -0
- package/dist/assets/dd163-3S3O6YOT.svg +6 -0
- package/dist/assets/draw-FOFX5TOM.svg +6 -0
- package/dist/assets/e8aed-CZJXWICW.svg +6 -0
- package/dist/assets/ea990-CZJXWICW.svg +6 -0
- package/dist/assets/expand-IAV75AFA.svg +6 -0
- package/dist/assets/fullscreen-L2JCXIN2.svg +6 -0
- package/dist/assets/grid-TLG5JYWB.svg +8 -0
- package/dist/assets/import-AFBUV2VV.svg +4 -0
- package/dist/assets/inter-latin-400-normal-55WT6UWF.woff2 +0 -0
- package/dist/assets/inter-latin-500-normal-W62DVTXI.woff2 +0 -0
- package/dist/assets/inter-latin-600-normal-R6Y2SZCR.woff2 +0 -0
- package/dist/assets/list-5VUAOZUH.svg +9 -0
- package/dist/assets/magic-OMME7FOV.svg +9 -0
- package/dist/assets/more-AEJ5MHWV.svg +5 -0
- package/dist/assets/mute-SXOSCR2U.svg +6 -0
- package/dist/assets/pause-2E4UY4FW.svg +6 -0
- package/dist/assets/play-WIIT3XQK.svg +5 -0
- package/dist/assets/reset-JESMTEMV.svg +6 -0
- package/dist/assets/search-JUOSGQRM.svg +6 -0
- package/dist/assets/select-VU7WR7B4.svg +7 -0
- package/dist/assets/sound-file-YYY6VNYN.svg +3 -0
- package/dist/assets/volume-2TYGJL6F.svg +6 -0
- package/dist/client.bundle.js +1373 -0
- package/dist/client.css +1900 -0
- package/dist/client.d.ts +19 -0
- package/dist/client.js +3 -0
- package/dist/events.d.ts +102 -0
- package/dist/events.js +244 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +3502 -0
- package/dist/shared-BUdviVVD.d.ts +263 -0
- package/dist/shared.d.ts +510 -0
- package/dist/shared.js +228 -0
- package/package.json +50 -3
- package/schemas/billing-notice.json +16 -0
- package/schemas/task-notice.json +57 -0
- package/studio-plugin.json +40 -0
- package/README.md +0 -3
|
@@ -0,0 +1,1373 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __esm = (fn, res) => function __init() {
|
|
4
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
|
+
};
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// packages/project-studio/src/cost.ts
|
|
12
|
+
function taskCostLabel(task) {
|
|
13
|
+
const cost = task.cost;
|
|
14
|
+
if (cost?.status === "settled" && cost.credits != null) return `${cost.credits} credits`;
|
|
15
|
+
if (cost?.status === "pending") return "Pending settlement";
|
|
16
|
+
return "-";
|
|
17
|
+
}
|
|
18
|
+
var init_cost = __esm({
|
|
19
|
+
"packages/project-studio/src/cost.ts"() {
|
|
20
|
+
"use strict";
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// packages/ui-system/dist/icon.js
|
|
25
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
26
|
+
function Icon({ name, size }) {
|
|
27
|
+
return _jsx("span", { "aria-hidden": "true", className: "vx-icon", "data-icon": name, style: size === void 0 ? void 0 : { "--vx-icon-size": `${size}px` } });
|
|
28
|
+
}
|
|
29
|
+
var init_icon = __esm({
|
|
30
|
+
"packages/ui-system/dist/icon.js"() {
|
|
31
|
+
"use strict";
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// packages/ui-system/dist/button-variants.js
|
|
36
|
+
var buttonVariants;
|
|
37
|
+
var init_button_variants = __esm({
|
|
38
|
+
"packages/ui-system/dist/button-variants.js"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
buttonVariants = {
|
|
41
|
+
"text-filled-sm": { size: 32, appearance: "filled", shape: "text", width: 215 },
|
|
42
|
+
"text-filled-leading-sm": { size: 32, appearance: "filled", shape: "text" },
|
|
43
|
+
"text-ghost-sm": { size: 32, appearance: "ghost", shape: "text" },
|
|
44
|
+
"text-on-content-sm": { size: 32, appearance: "on-content", shape: "text" },
|
|
45
|
+
"text-tonal-sm": { size: 32, appearance: "tonal", shape: "text" },
|
|
46
|
+
// The medium recipes are shared by all dialog footer actions.
|
|
47
|
+
"text-filled-md": { size: 40, appearance: "filled", shape: "text" },
|
|
48
|
+
"text-tonal-md": { size: 40, appearance: "tonal", shape: "text" },
|
|
49
|
+
"text-ghost-md": { size: 40, appearance: "ghost", shape: "text" },
|
|
50
|
+
"icon-ghost-md": { size: 40, appearance: "ghost", shape: "icon" },
|
|
51
|
+
"text-filled-lg": { size: 44, appearance: "filled", shape: "text", width: 412 },
|
|
52
|
+
"text-tonal-lg": { size: 44, appearance: "tonal", shape: "text", width: 412 },
|
|
53
|
+
"text-outline-lg": {
|
|
54
|
+
size: 44,
|
|
55
|
+
appearance: "outline",
|
|
56
|
+
shape: "text",
|
|
57
|
+
width: 412
|
|
58
|
+
},
|
|
59
|
+
"pill-filled": { size: 44, appearance: "filled", shape: "pill" },
|
|
60
|
+
"pill-plain": { size: 48, appearance: "plain", shape: "pill" },
|
|
61
|
+
"pill-small": { size: 32, appearance: "plain", shape: "pill" },
|
|
62
|
+
"icon-outline-sm": { size: 32, appearance: "outline", shape: "icon" },
|
|
63
|
+
"icon-on-content": { size: 24, appearance: "on-content", shape: "icon" },
|
|
64
|
+
"icon-outline-lg": { size: 48, appearance: "outline", shape: "icon" },
|
|
65
|
+
// Compact bare action: reuse the transparent/default and surface-hover ghost treatment.
|
|
66
|
+
"icon-bare-xs": { size: 24, appearance: "ghost", shape: "icon" },
|
|
67
|
+
"icon-ghost-sm": { size: 32, appearance: "ghost", shape: "icon" },
|
|
68
|
+
"icon-ghost-lg": { size: 48, appearance: "ghost", shape: "icon" },
|
|
69
|
+
"icon-circle-sm": { size: 32, appearance: "filled", shape: "circle" },
|
|
70
|
+
"icon-circle-lg": { size: 44, appearance: "filled", shape: "circle" }
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// packages/ui-system/dist/button.js
|
|
76
|
+
import { jsx as _jsx2, jsxs as _jsxs } from "react/jsx-runtime";
|
|
77
|
+
function ButtonFrame({ label, variant, loading = false, width, children, disabled, type = "button", style, className, ...props }) {
|
|
78
|
+
const spec = buttonVariants[variant];
|
|
79
|
+
const buttonStyle = "width" in spec ? { ...style, "--vx-button-width": `${spec.width}px` } : style;
|
|
80
|
+
return _jsxs("button", { ...props, type, className: className ? `vx-button ${className}` : "vx-button", "data-variant": variant, "data-appearance": spec.appearance, "data-shape": spec.shape, "data-size": spec.size, "data-width": width, "data-state": loading ? "loading" : disabled ? "disabled" : "default", style: buttonStyle, disabled: disabled || loading, "aria-label": label, "aria-busy": loading || void 0, children: [_jsx2("span", { className: "vx-button-content", children }), loading && _jsx2("span", { className: "vx-button-loading", "aria-hidden": "true" })] });
|
|
81
|
+
}
|
|
82
|
+
function Button({ variant = "text-filled-sm", leadingIcon, trailingIcon, ...props }) {
|
|
83
|
+
return _jsxs(ButtonFrame, { ...props, variant, children: [leadingIcon && _jsx2(Icon, { name: leadingIcon }), _jsx2("span", { className: "vx-button-label", children: props.label }), trailingIcon && _jsx2("span", { className: "vx-button-trailing", children: _jsx2(Icon, { name: trailingIcon }) })] });
|
|
84
|
+
}
|
|
85
|
+
var init_button = __esm({
|
|
86
|
+
"packages/ui-system/dist/button.js"() {
|
|
87
|
+
"use strict";
|
|
88
|
+
init_icon();
|
|
89
|
+
init_button_variants();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// packages/ui-system/dist/dropdown.js
|
|
94
|
+
import { jsx as _jsx3, jsxs as _jsxs2 } from "react/jsx-runtime";
|
|
95
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
96
|
+
function Dropdown({ label, options, value, onValueChange, placeholder = "Select an option", size = "medium", width = "fixed", disabled = false }) {
|
|
97
|
+
const id = useId();
|
|
98
|
+
const activeOption = useRef(null);
|
|
99
|
+
const search = useRef({ query: "", time: 0 });
|
|
100
|
+
const [open, setOpen] = useState(false);
|
|
101
|
+
const [highlighted, setHighlighted] = useState(null);
|
|
102
|
+
const enabled = options.filter((option) => !option.disabled);
|
|
103
|
+
const canOpen = !disabled && enabled.length > 0;
|
|
104
|
+
const expanded = canOpen && open;
|
|
105
|
+
const selected = options.find((option) => option.value === value);
|
|
106
|
+
const active = enabled.find((option) => option.value === highlighted) ?? enabled.find((option) => option.value === value) ?? enabled[0];
|
|
107
|
+
const activeIndex = active ? enabled.indexOf(active) : -1;
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (expanded)
|
|
110
|
+
activeOption.current?.scrollIntoView({ block: "nearest" });
|
|
111
|
+
}, [expanded, active?.value]);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!canOpen) {
|
|
114
|
+
setOpen(false);
|
|
115
|
+
setHighlighted(null);
|
|
116
|
+
}
|
|
117
|
+
}, [canOpen]);
|
|
118
|
+
function close() {
|
|
119
|
+
search.current = { query: "", time: 0 };
|
|
120
|
+
setHighlighted(null);
|
|
121
|
+
setOpen(false);
|
|
122
|
+
}
|
|
123
|
+
function choose(option) {
|
|
124
|
+
close();
|
|
125
|
+
if (option && !option.disabled && option.value !== value)
|
|
126
|
+
onValueChange(option.value);
|
|
127
|
+
}
|
|
128
|
+
function handleKey(event) {
|
|
129
|
+
if (!canOpen || event.nativeEvent.isComposing)
|
|
130
|
+
return;
|
|
131
|
+
const { key } = event;
|
|
132
|
+
if (key === "Tab") {
|
|
133
|
+
if (expanded)
|
|
134
|
+
choose(active);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (key === "Escape") {
|
|
138
|
+
if (!expanded)
|
|
139
|
+
return;
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
close();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (key === " " || key === "Enter") {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
if (expanded)
|
|
148
|
+
choose(active);
|
|
149
|
+
else
|
|
150
|
+
setOpen(true);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (key === "ArrowDown" || key === "ArrowUp" || key === "Home" || key === "End") {
|
|
154
|
+
event.preventDefault();
|
|
155
|
+
if (event.altKey && key === "ArrowUp" && expanded) {
|
|
156
|
+
choose(active);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setOpen(true);
|
|
160
|
+
let index = activeIndex;
|
|
161
|
+
if (key === "Home")
|
|
162
|
+
index = 0;
|
|
163
|
+
else if (key === "End")
|
|
164
|
+
index = enabled.length - 1;
|
|
165
|
+
else if (expanded && !event.altKey) {
|
|
166
|
+
const step = key === "ArrowDown" ? 1 : -1;
|
|
167
|
+
index = Math.max(0, Math.min(activeIndex + step, enabled.length - 1));
|
|
168
|
+
}
|
|
169
|
+
setHighlighted(enabled[index]?.value ?? null);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey)
|
|
173
|
+
return;
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
const char = key.toLocaleLowerCase();
|
|
176
|
+
const previous = performance.now() - search.current.time < 500 ? search.current.query : "";
|
|
177
|
+
const repeated = previous.length > 0 && [...previous].every((letter) => letter === char);
|
|
178
|
+
const query = repeated ? char : previous + char;
|
|
179
|
+
search.current = { query, time: performance.now() };
|
|
180
|
+
const start = previous && !repeated ? activeIndex : activeIndex + 1;
|
|
181
|
+
const ordered = [...enabled.slice(start), ...enabled.slice(0, start)];
|
|
182
|
+
const match = ordered.find((option) => option.label.toLocaleLowerCase().startsWith(query));
|
|
183
|
+
setOpen(true);
|
|
184
|
+
if (match)
|
|
185
|
+
setHighlighted(match.value);
|
|
186
|
+
}
|
|
187
|
+
return _jsxs2("div", { className: "vx-dropdown", "data-size": size, "data-width": width, children: [_jsxs2("button", { type: "button", role: "combobox", className: "vx-dropdown-trigger", "aria-label": label, "aria-expanded": expanded, "aria-controls": `${id}-listbox`, "aria-activedescendant": expanded && active ? `${id}-option-${options.indexOf(active)}` : void 0, disabled: !canOpen, onClick: () => expanded ? close() : setOpen(true), onBlur: close, onKeyDown: handleKey, children: [_jsxs2("span", { className: "vx-dropdown-value", children: [selected?.icon && _jsx3(Icon, { name: selected.icon }), _jsx3("span", { children: selected?.label ?? placeholder })] }), _jsx3(Icon, { name: expanded ? "chevron-up" : "chevron-down" })] }), _jsx3("ul", { id: `${id}-listbox`, role: "listbox", "aria-label": label, hidden: !expanded, children: options.map((option, index) => _jsxs2("li", { id: `${id}-option-${index}`, ref: active === option ? activeOption : void 0, role: "option", "aria-selected": option.value === value, "aria-disabled": option.disabled || void 0, "data-active": active === option, onMouseDown: (event) => event.preventDefault(), onClick: () => {
|
|
188
|
+
if (!option.disabled)
|
|
189
|
+
choose(option);
|
|
190
|
+
}, children: [option.icon && _jsx3(Icon, { name: option.icon }), option.label, width === "hug" && option.value === value && _jsx3(Icon, { name: "check" })] }, option.value)) })] });
|
|
191
|
+
}
|
|
192
|
+
var init_dropdown = __esm({
|
|
193
|
+
"packages/ui-system/dist/dropdown.js"() {
|
|
194
|
+
"use strict";
|
|
195
|
+
init_icon();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// packages/ui-system/dist/segmented-control.js
|
|
200
|
+
import { jsx as _jsx4, jsxs as _jsxs3 } from "react/jsx-runtime";
|
|
201
|
+
import { useId as useId2 } from "react";
|
|
202
|
+
function SegmentedControl({ label, items, value, onValueChange, size = "medium", width = "fixed", iconOnly = false }) {
|
|
203
|
+
const id = useId2();
|
|
204
|
+
return _jsx4("div", { role: "radiogroup", "aria-label": label, className: "vx-segment", "data-size": size, "data-width": width, "data-icon-only": iconOnly, children: items.map((item) => _jsxs3("label", { children: [_jsx4("input", { type: "radio", name: id, value: item.value, disabled: item.disabled, checked: item.value === value, "aria-label": item.label, onChange: () => onValueChange(item.value) }), _jsxs3("span", { className: "vx-segment-item", children: [item.icon && _jsx4(Icon, { name: item.icon }), (!iconOnly || !item.icon) && item.label] })] }, item.value)) });
|
|
205
|
+
}
|
|
206
|
+
var init_segmented_control = __esm({
|
|
207
|
+
"packages/ui-system/dist/segmented-control.js"() {
|
|
208
|
+
"use strict";
|
|
209
|
+
init_icon();
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// packages/ui-system/dist/input.js
|
|
214
|
+
import { jsx as _jsx5, jsxs as _jsxs4 } from "react/jsx-runtime";
|
|
215
|
+
function TextInput({ label, value, onValueChange, size = "medium", ...props }) {
|
|
216
|
+
return _jsxs4("label", { className: "vx-field", "data-size": size, children: [_jsx5("span", { children: label }), _jsx5("input", { ...props, type: "text", value, onChange: (event) => onValueChange(event.currentTarget.value) })] });
|
|
217
|
+
}
|
|
218
|
+
var init_input = __esm({
|
|
219
|
+
"packages/ui-system/dist/input.js"() {
|
|
220
|
+
"use strict";
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// packages/ui-system/dist/index.js
|
|
225
|
+
var init_dist = __esm({
|
|
226
|
+
"packages/ui-system/dist/index.js"() {
|
|
227
|
+
"use strict";
|
|
228
|
+
init_button();
|
|
229
|
+
init_dropdown();
|
|
230
|
+
init_segmented_control();
|
|
231
|
+
init_input();
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// packages/project-studio/src/ui/reference-picker.tsx
|
|
236
|
+
import * as React from "react";
|
|
237
|
+
function referenceRoles(model, operation) {
|
|
238
|
+
if (model?.modality === "image" && model.requires_explicit_source_roles === false && Array.isArray(model.source_roles) && model.source_roles.length === 0 && operation === "image.edit" && model.operations?.some((o) => o.name === operation)) {
|
|
239
|
+
return [
|
|
240
|
+
{
|
|
241
|
+
role: "implicit-image",
|
|
242
|
+
kind: "image",
|
|
243
|
+
min_items: 1,
|
|
244
|
+
max_items: 1,
|
|
245
|
+
supports_weight: false,
|
|
246
|
+
derived: true
|
|
247
|
+
}
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
return model?.source_roles || [];
|
|
251
|
+
}
|
|
252
|
+
function roleLabel(role) {
|
|
253
|
+
const labels = {
|
|
254
|
+
"implicit-image": "Reference image",
|
|
255
|
+
"first-frame": "First frame",
|
|
256
|
+
"last-frame": "Last frame",
|
|
257
|
+
reference: "Reference file",
|
|
258
|
+
"reference-image": "Reference image",
|
|
259
|
+
"reference-video": "Reference video",
|
|
260
|
+
"reference-audio": "Reference audio",
|
|
261
|
+
style: "Style reference"
|
|
262
|
+
};
|
|
263
|
+
return labels[role.replaceAll("_", "-")] || role.replaceAll("_", " ").replaceAll("-", " ");
|
|
264
|
+
}
|
|
265
|
+
function Thumbnail({ bridge, projectId, studio, asset, version }) {
|
|
266
|
+
const [url, setUrl] = React.useState("");
|
|
267
|
+
const [error, setError] = React.useState("");
|
|
268
|
+
React.useEffect(() => {
|
|
269
|
+
let current = true;
|
|
270
|
+
setUrl("");
|
|
271
|
+
setError("");
|
|
272
|
+
if (asset && version)
|
|
273
|
+
bridge.project(projectId, studio, "files.resolveLocation", {
|
|
274
|
+
ref: { storeId: asset.storeId, objectId: asset.objectId, versionId: version.versionId }
|
|
275
|
+
}).then((value) => {
|
|
276
|
+
if (current) setUrl(value.previewUrl);
|
|
277
|
+
}).catch((e) => {
|
|
278
|
+
if (current) setError(e.message);
|
|
279
|
+
});
|
|
280
|
+
return () => {
|
|
281
|
+
current = false;
|
|
282
|
+
};
|
|
283
|
+
}, [projectId, studio, asset?.objectId, version?.versionId]);
|
|
284
|
+
if (!url) return /* @__PURE__ */ React.createElement("span", { className: "sub" }, error || "Loading\u2026");
|
|
285
|
+
if (asset.data.kind === "image") return /* @__PURE__ */ React.createElement("img", { src: url, alt: asset.data.name });
|
|
286
|
+
if (asset.data.kind === "video")
|
|
287
|
+
return /* @__PURE__ */ React.createElement("video", { src: url, preload: "metadata", muted: true, "aria-label": asset.data.name });
|
|
288
|
+
return /* @__PURE__ */ React.createElement("span", { className: "sub" }, asset.data.kind === "audio" ? "\u266A Audio" : asset.data.name);
|
|
289
|
+
}
|
|
290
|
+
function ReferenceInputs({
|
|
291
|
+
bridge,
|
|
292
|
+
projectId,
|
|
293
|
+
studio,
|
|
294
|
+
capability,
|
|
295
|
+
draft,
|
|
296
|
+
changeDraft,
|
|
297
|
+
attempt,
|
|
298
|
+
onInputKinds
|
|
299
|
+
}) {
|
|
300
|
+
const [assets, setAssets] = React.useState([]);
|
|
301
|
+
const [search, setSearch] = React.useState("");
|
|
302
|
+
const [nextOffset, setNextOffset] = React.useState(null);
|
|
303
|
+
const uploadInput = React.useRef(null);
|
|
304
|
+
const [picker, setPicker] = React.useState(false);
|
|
305
|
+
const [editing, setEditing] = React.useState(null);
|
|
306
|
+
const [assetId, setAssetId] = React.useState("");
|
|
307
|
+
const [versionId, setVersionId] = React.useState("");
|
|
308
|
+
const [role, setRole] = React.useState("");
|
|
309
|
+
const [fixedRole, setFixedRole] = React.useState("");
|
|
310
|
+
const sequence = React.useRef(0);
|
|
311
|
+
const scope = React.useRef("");
|
|
312
|
+
const scopeKey = JSON.stringify([projectId, studio, search]);
|
|
313
|
+
scope.current = scopeKey;
|
|
314
|
+
const load = async (offset = 0) => {
|
|
315
|
+
const current = ++sequence.current;
|
|
316
|
+
const page = await bridge.project(projectId, studio, "assets.list", {
|
|
317
|
+
search,
|
|
318
|
+
limit: 50,
|
|
319
|
+
offset
|
|
320
|
+
});
|
|
321
|
+
if (sequence.current !== current || scope.current !== scopeKey) return;
|
|
322
|
+
setAssets(
|
|
323
|
+
(old) => offset ? [...old, ...page.items.filter((a) => !old.some((x) => x.objectId === a.objectId))] : page.items
|
|
324
|
+
);
|
|
325
|
+
setNextOffset(page.nextOffset);
|
|
326
|
+
};
|
|
327
|
+
React.useEffect(() => {
|
|
328
|
+
void attempt(() => load());
|
|
329
|
+
}, [projectId, studio, search]);
|
|
330
|
+
let inputs = [], error = "";
|
|
331
|
+
try {
|
|
332
|
+
inputs = JSON.parse(draft.inputs);
|
|
333
|
+
if (!Array.isArray(inputs)) throw Error("Reference file could not be read. Select it again.");
|
|
334
|
+
} catch (e) {
|
|
335
|
+
error = e.message;
|
|
336
|
+
}
|
|
337
|
+
const referenceOperation = capability?.modality === "image" && capability.operations?.some((operation) => operation.name === "image.edit") ? "image.edit" : draft.capabilityId;
|
|
338
|
+
const roles = referenceRoles(capability, referenceOperation);
|
|
339
|
+
const asset = assets.find((a) => a.objectId === assetId);
|
|
340
|
+
const compatibleRoles = roles.filter(
|
|
341
|
+
(r) => kinds(r).includes(asset?.data.kind) && (!fixedRole || roleId(r) === fixedRole)
|
|
342
|
+
);
|
|
343
|
+
const selectedRole = compatibleRoles.find((r) => roleId(r) === role);
|
|
344
|
+
React.useEffect(() => {
|
|
345
|
+
if (asset && compatibleRoles.length === 1 && role !== roleId(compatibleRoles[0]))
|
|
346
|
+
setRole(roleId(compatibleRoles[0]));
|
|
347
|
+
}, [assetId, capability, fixedRole, role]);
|
|
348
|
+
React.useEffect(() => {
|
|
349
|
+
if (error) return;
|
|
350
|
+
let current = true;
|
|
351
|
+
const missing = [...new Set(inputs.map((i) => i.ref?.objectId))].filter(
|
|
352
|
+
(id) => id && !assets.some((a) => a.objectId === id)
|
|
353
|
+
);
|
|
354
|
+
if (missing.length)
|
|
355
|
+
void attempt(async () => {
|
|
356
|
+
const records = await Promise.all(
|
|
357
|
+
missing.map((id) => bridge.project(projectId, studio, "assets.get", { assetId: id }))
|
|
358
|
+
);
|
|
359
|
+
if (current)
|
|
360
|
+
setAssets((old) => [
|
|
361
|
+
...old,
|
|
362
|
+
...records.filter((a) => !old.some((x) => x.objectId === a.objectId))
|
|
363
|
+
]);
|
|
364
|
+
});
|
|
365
|
+
onInputKinds(inputs.map((i) => assets.find((a) => a.objectId === i.ref?.objectId)?.data.kind));
|
|
366
|
+
return () => {
|
|
367
|
+
current = false;
|
|
368
|
+
};
|
|
369
|
+
}, [projectId, studio, assets, draft.inputs]);
|
|
370
|
+
const update = (value) => changeDraft({
|
|
371
|
+
inputs: JSON.stringify(
|
|
372
|
+
value.map((item, order) => ({ ...item, order })),
|
|
373
|
+
null,
|
|
374
|
+
2
|
|
375
|
+
)
|
|
376
|
+
});
|
|
377
|
+
const open = (targetRole = "", index = null) => {
|
|
378
|
+
const existing = index === null ? null : inputs[index];
|
|
379
|
+
setEditing(index);
|
|
380
|
+
setFixedRole(targetRole);
|
|
381
|
+
setRole(existing?.role || targetRole);
|
|
382
|
+
setAssetId(existing?.ref?.objectId || "");
|
|
383
|
+
setVersionId(existing?.ref?.versionId || "");
|
|
384
|
+
setPicker(true);
|
|
385
|
+
};
|
|
386
|
+
const pick = (record) => {
|
|
387
|
+
setAssetId(record.objectId);
|
|
388
|
+
setVersionId(record.versions.at(-1).versionId);
|
|
389
|
+
const choices = roles.filter(
|
|
390
|
+
(r) => kinds(r).includes(record.data.kind) && (!fixedRole || roleId(r) === fixedRole)
|
|
391
|
+
);
|
|
392
|
+
setRole(choices.length === 1 ? roleId(choices[0]) : "");
|
|
393
|
+
};
|
|
394
|
+
const count = inputs.filter((item, index) => index !== editing && item.role === role).length;
|
|
395
|
+
const atLimit = selectedRole && selectedRole.max_items != null && count >= selectedRole.max_items;
|
|
396
|
+
const apply = () => void attempt(async () => {
|
|
397
|
+
if (!asset || !versionId || !selectedRole || atLimit) return;
|
|
398
|
+
const ref2 = { storeId: asset.storeId, objectId: asset.objectId, versionId };
|
|
399
|
+
await bridge.project(projectId, studio, "files.resolveLocation", { ref: ref2 });
|
|
400
|
+
const value = { ...editing === null ? { weight: null } : inputs[editing], ref: ref2, role };
|
|
401
|
+
update(
|
|
402
|
+
editing === null ? [...inputs, value] : inputs.map((item, i) => i === editing ? value : item)
|
|
403
|
+
);
|
|
404
|
+
setPicker(false);
|
|
405
|
+
});
|
|
406
|
+
const upload = (files) => {
|
|
407
|
+
if (!files?.length) return;
|
|
408
|
+
const file = files[0];
|
|
409
|
+
void attempt(async () => {
|
|
410
|
+
const staged = await bridge.stage(file, projectId);
|
|
411
|
+
const created = await bridge.project(
|
|
412
|
+
projectId,
|
|
413
|
+
studio,
|
|
414
|
+
"assets.import",
|
|
415
|
+
{
|
|
416
|
+
name: file.name,
|
|
417
|
+
stagingFileIds: [staged.stagingFileId]
|
|
418
|
+
},
|
|
419
|
+
{ requestKey: crypto.randomUUID() }
|
|
420
|
+
);
|
|
421
|
+
const record = await bridge.project(projectId, studio, "assets.get", {
|
|
422
|
+
assetId: created.asset.objectId
|
|
423
|
+
});
|
|
424
|
+
setAssets((old) => [record, ...old.filter((a) => a.objectId !== record.objectId)]);
|
|
425
|
+
setSearch("");
|
|
426
|
+
pick(record);
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const tile = (input, index) => {
|
|
430
|
+
const record = assets.find((a) => a.objectId === input.ref?.objectId);
|
|
431
|
+
const version = record?.versions.find((v) => v.versionId === input.ref?.versionId);
|
|
432
|
+
const declared = roles.find((r) => roleId(r) === input.role);
|
|
433
|
+
return /* @__PURE__ */ React.createElement("div", { className: "reference-tile", key: index }, /* @__PURE__ */ React.createElement(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
className: "reference-thumb",
|
|
437
|
+
"aria-label": `Replace Reference file ${index + 1}`,
|
|
438
|
+
onClick: () => open("", index)
|
|
439
|
+
},
|
|
440
|
+
/* @__PURE__ */ React.createElement(
|
|
441
|
+
Thumbnail,
|
|
442
|
+
{
|
|
443
|
+
bridge,
|
|
444
|
+
projectId,
|
|
445
|
+
studio,
|
|
446
|
+
asset: record,
|
|
447
|
+
version
|
|
448
|
+
}
|
|
449
|
+
)
|
|
450
|
+
), /* @__PURE__ */ React.createElement(
|
|
451
|
+
"button",
|
|
452
|
+
{
|
|
453
|
+
className: "reference-remove",
|
|
454
|
+
"aria-label": `Remove Reference file ${index + 1}`,
|
|
455
|
+
onClick: () => update(inputs.filter((_, i) => i !== index))
|
|
456
|
+
},
|
|
457
|
+
"\xD7"
|
|
458
|
+
), /* @__PURE__ */ React.createElement("div", { className: "reference-caption" }, /* @__PURE__ */ React.createElement("strong", null, record?.data.name || "Reference file"), /* @__PURE__ */ React.createElement(
|
|
459
|
+
"button",
|
|
460
|
+
{
|
|
461
|
+
className: "plain version-button",
|
|
462
|
+
"aria-label": `Reference file ${index + 1} version`,
|
|
463
|
+
onClick: () => open("", index)
|
|
464
|
+
},
|
|
465
|
+
version ? `v${version.versionNumber}` : "Version",
|
|
466
|
+
" \xB7 ",
|
|
467
|
+
roleLabel(input.role)
|
|
468
|
+
)), !declared && /* @__PURE__ */ React.createElement("p", { className: "reference-warning" }, "Choose a reference supported by this model."), declared?.supports_weight && /* @__PURE__ */ React.createElement("label", { className: "small" }, "Reference weight", /* @__PURE__ */ React.createElement(
|
|
469
|
+
"input",
|
|
470
|
+
{
|
|
471
|
+
type: "number",
|
|
472
|
+
step: "any",
|
|
473
|
+
"aria-label": `Reference file ${index + 1} weight`,
|
|
474
|
+
value: input.weight ?? "",
|
|
475
|
+
onChange: (e) => update(
|
|
476
|
+
inputs.map(
|
|
477
|
+
(r, i) => i === index ? { ...r, weight: e.target.value === "" ? null : Number(e.target.value) } : r
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
)), inputs.length > 1 && /* @__PURE__ */ React.createElement("div", { className: "reference-actions" }, /* @__PURE__ */ React.createElement(
|
|
482
|
+
"button",
|
|
483
|
+
{
|
|
484
|
+
disabled: index === 0,
|
|
485
|
+
onClick: () => {
|
|
486
|
+
const value = [...inputs];
|
|
487
|
+
[value[index - 1], value[index]] = [value[index], value[index - 1]];
|
|
488
|
+
update(value);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"Move earlier"
|
|
492
|
+
), /* @__PURE__ */ React.createElement(
|
|
493
|
+
"button",
|
|
494
|
+
{
|
|
495
|
+
disabled: index === inputs.length - 1,
|
|
496
|
+
onClick: () => {
|
|
497
|
+
const value = [...inputs];
|
|
498
|
+
[value[index + 1], value[index]] = [value[index], value[index + 1]];
|
|
499
|
+
update(value);
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
"Move later"
|
|
503
|
+
)));
|
|
504
|
+
};
|
|
505
|
+
if (!roles.length && !inputs.length) return null;
|
|
506
|
+
const frames = roles.filter(frameRole);
|
|
507
|
+
return /* @__PURE__ */ React.createElement("section", { className: "reference-section" }, /* @__PURE__ */ React.createElement("div", { className: "reference-heading" }, /* @__PURE__ */ React.createElement("h3", null, "Reference file"), /* @__PURE__ */ React.createElement("span", { className: "sub" }, "Added ", inputs.length)), error ? /* @__PURE__ */ React.createElement("p", { role: "alert", className: "error" }, error) : /* @__PURE__ */ React.createElement(React.Fragment, null, frames.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "reference-slot-group" }, frames.map((r) => {
|
|
508
|
+
const id = roleId(r), index = inputs.findIndex((item) => item.role === id);
|
|
509
|
+
return /* @__PURE__ */ React.createElement("div", { className: "reference-slot", key: id }, /* @__PURE__ */ React.createElement("span", null, roleLabel(id), !r.min_items ? " (optional)" : ""), index >= 0 ? tile(inputs[index], index) : /* @__PURE__ */ React.createElement(
|
|
510
|
+
"button",
|
|
511
|
+
{
|
|
512
|
+
className: "reference-add",
|
|
513
|
+
"aria-label": `Choose ${roleLabel(id)}`,
|
|
514
|
+
onClick: () => open(id)
|
|
515
|
+
},
|
|
516
|
+
/* @__PURE__ */ React.createElement("b", null, "+"),
|
|
517
|
+
"Add file"
|
|
518
|
+
));
|
|
519
|
+
})), /* @__PURE__ */ React.createElement("div", { className: "reference-strip" }, inputs.map(
|
|
520
|
+
(input, index) => frames.some((r) => roleId(r) === input.role) ? null : tile(input, index)
|
|
521
|
+
), (roles.some((r) => !frameRole(r)) || !roles.length) && /* @__PURE__ */ React.createElement("button", { className: "reference-add", disabled: !roles.length, onClick: () => open() }, /* @__PURE__ */ React.createElement("b", null, "+"), "Add reference")), /* @__PURE__ */ React.createElement("p", { className: "reference-help" }, "Choose project files or upload from your computer. Select a thumbnail to replace it.")), picker && /* @__PURE__ */ React.createElement(
|
|
522
|
+
"div",
|
|
523
|
+
{
|
|
524
|
+
className: "reference-picker-backdrop",
|
|
525
|
+
onKeyDown: (e) => {
|
|
526
|
+
if (e.key === "Escape") setPicker(false);
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
/* @__PURE__ */ React.createElement(
|
|
530
|
+
"section",
|
|
531
|
+
{
|
|
532
|
+
className: "reference-picker",
|
|
533
|
+
role: "dialog",
|
|
534
|
+
"aria-modal": "true",
|
|
535
|
+
"aria-label": "Choose Reference file"
|
|
536
|
+
},
|
|
537
|
+
/* @__PURE__ */ React.createElement("div", { className: "reference-picker-header" }, /* @__PURE__ */ React.createElement("h2", null, editing === null ? "Add reference" : "Replace Reference file"), /* @__PURE__ */ React.createElement(
|
|
538
|
+
Button,
|
|
539
|
+
{
|
|
540
|
+
label: "Close",
|
|
541
|
+
variant: "text-ghost-md",
|
|
542
|
+
width: "hug",
|
|
543
|
+
onClick: () => setPicker(false)
|
|
544
|
+
}
|
|
545
|
+
)),
|
|
546
|
+
/* @__PURE__ */ React.createElement("div", { className: "reference-picker-toolbar" }, /* @__PURE__ */ React.createElement(
|
|
547
|
+
TextInput,
|
|
548
|
+
{
|
|
549
|
+
label: "Search project files",
|
|
550
|
+
placeholder: "Search file names",
|
|
551
|
+
value: search,
|
|
552
|
+
onValueChange: setSearch
|
|
553
|
+
}
|
|
554
|
+
), /* @__PURE__ */ React.createElement(
|
|
555
|
+
Button,
|
|
556
|
+
{
|
|
557
|
+
label: "Upload file",
|
|
558
|
+
variant: "text-tonal-md",
|
|
559
|
+
width: "hug",
|
|
560
|
+
onClick: () => uploadInput.current?.click()
|
|
561
|
+
}
|
|
562
|
+
), /* @__PURE__ */ React.createElement(
|
|
563
|
+
"input",
|
|
564
|
+
{
|
|
565
|
+
ref: uploadInput,
|
|
566
|
+
hidden: true,
|
|
567
|
+
type: "file",
|
|
568
|
+
accept: "image/*,video/*,audio/*",
|
|
569
|
+
onChange: (e) => upload(e.target.files)
|
|
570
|
+
}
|
|
571
|
+
)),
|
|
572
|
+
/* @__PURE__ */ React.createElement("div", { className: "reference-picker-grid" }, assets.filter(
|
|
573
|
+
(a) => a.versions?.length && roles.some(
|
|
574
|
+
(r) => kinds(r).includes(a.data.kind) && (!fixedRole || roleId(r) === fixedRole)
|
|
575
|
+
)
|
|
576
|
+
).map((a) => /* @__PURE__ */ React.createElement(
|
|
577
|
+
"button",
|
|
578
|
+
{
|
|
579
|
+
key: a.objectId,
|
|
580
|
+
className: `reference-picker-card ${assetId === a.objectId ? "chosen" : ""}`,
|
|
581
|
+
onClick: () => pick(a)
|
|
582
|
+
},
|
|
583
|
+
/* @__PURE__ */ React.createElement("div", { className: "reference-thumb" }, /* @__PURE__ */ React.createElement(
|
|
584
|
+
Thumbnail,
|
|
585
|
+
{
|
|
586
|
+
bridge,
|
|
587
|
+
projectId,
|
|
588
|
+
studio,
|
|
589
|
+
asset: a,
|
|
590
|
+
version: a.versions.at(-1)
|
|
591
|
+
}
|
|
592
|
+
)),
|
|
593
|
+
/* @__PURE__ */ React.createElement("strong", null, a.data.name),
|
|
594
|
+
/* @__PURE__ */ React.createElement("small", null, a.data.kind, " \xB7 v", a.versions.at(-1).versionNumber)
|
|
595
|
+
))),
|
|
596
|
+
!assets.some(
|
|
597
|
+
(a) => a.versions?.length && roles.some(
|
|
598
|
+
(r) => kinds(r).includes(a.data.kind) && (!fixedRole || roleId(r) === fixedRole)
|
|
599
|
+
)
|
|
600
|
+
) && /* @__PURE__ */ React.createElement("p", { className: "reference-picker-empty" }, "No matching files. Upload a file from your computer."),
|
|
601
|
+
nextOffset !== null && /* @__PURE__ */ React.createElement("button", { onClick: () => void attempt(() => load(nextOffset)) }, "Load more files"),
|
|
602
|
+
asset && /* @__PURE__ */ React.createElement("div", { className: "reference-picker-selection" }, /* @__PURE__ */ React.createElement("label", null, "File version", /* @__PURE__ */ React.createElement(
|
|
603
|
+
Dropdown,
|
|
604
|
+
{
|
|
605
|
+
label: "File version",
|
|
606
|
+
value: versionId,
|
|
607
|
+
onValueChange: setVersionId,
|
|
608
|
+
options: asset.versions.map((v) => ({
|
|
609
|
+
value: v.versionId,
|
|
610
|
+
label: `v${v.versionNumber}${v.versionId === asset.versions.at(-1)?.versionId ? " \xB7 Latest" : ""}`
|
|
611
|
+
}))
|
|
612
|
+
}
|
|
613
|
+
)), compatibleRoles.length > 1 && /* @__PURE__ */ React.createElement("label", null, "Reference purpose", /* @__PURE__ */ React.createElement(
|
|
614
|
+
Dropdown,
|
|
615
|
+
{
|
|
616
|
+
label: "Reference purpose",
|
|
617
|
+
value: role,
|
|
618
|
+
onValueChange: setRole,
|
|
619
|
+
options: [
|
|
620
|
+
{ value: "", label: "Choose purpose" },
|
|
621
|
+
...compatibleRoles.map((r) => ({
|
|
622
|
+
value: roleId(r),
|
|
623
|
+
label: roleLabel(roleId(r))
|
|
624
|
+
}))
|
|
625
|
+
]
|
|
626
|
+
}
|
|
627
|
+
))),
|
|
628
|
+
atLimit && /* @__PURE__ */ React.createElement("p", { className: "reference-warning" }, "This purpose supports up to ", selectedRole.max_items, " files. Replace an existing reference."),
|
|
629
|
+
/* @__PURE__ */ React.createElement("div", { className: "reference-picker-footer" }, /* @__PURE__ */ React.createElement("span", { className: "sub" }, "This fixed version will be used as the reference."), /* @__PURE__ */ React.createElement(
|
|
630
|
+
Button,
|
|
631
|
+
{
|
|
632
|
+
label: editing === null ? "Use file" : "Use version",
|
|
633
|
+
variant: "text-filled-md",
|
|
634
|
+
width: "hug",
|
|
635
|
+
disabled: !asset || !versionId || !selectedRole || atLimit || Boolean(error),
|
|
636
|
+
onClick: apply
|
|
637
|
+
}
|
|
638
|
+
))
|
|
639
|
+
)
|
|
640
|
+
));
|
|
641
|
+
}
|
|
642
|
+
var roleId, kinds, frameRole;
|
|
643
|
+
var init_reference_picker = __esm({
|
|
644
|
+
"packages/project-studio/src/ui/reference-picker.tsx"() {
|
|
645
|
+
"use strict";
|
|
646
|
+
init_dist();
|
|
647
|
+
roleId = (role) => role.role || role.name;
|
|
648
|
+
kinds = (role) => role.kind ? [role.kind] : role.media_types || role.media_kinds || role.kinds || role.accepted_kinds || [];
|
|
649
|
+
frameRole = (role) => ["first-frame", "last-frame"].includes(roleId(role)?.replaceAll("_", "-"));
|
|
650
|
+
}
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// packages/project-studio/src/ui/generation-inputs.tsx
|
|
654
|
+
import * as React2 from "react";
|
|
655
|
+
function TypedParameters({ capability, draft, changeDraft, inputKinds }) {
|
|
656
|
+
let parameters, inputs;
|
|
657
|
+
try {
|
|
658
|
+
parameters = JSON.parse(draft.parameters);
|
|
659
|
+
inputs = JSON.parse(draft.inputs);
|
|
660
|
+
} catch {
|
|
661
|
+
return /* @__PURE__ */ React2.createElement("p", { className: "reference-warning" }, "Draft settings could not be read. Choose the model again.");
|
|
662
|
+
}
|
|
663
|
+
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters) || !Array.isArray(inputs))
|
|
664
|
+
return /* @__PURE__ */ React2.createElement("p", { className: "reference-warning" }, "Choose the model again to restore generation settings.");
|
|
665
|
+
const mode = inputKinds.includes("video") ? "video" : inputKinds.includes("image") ? "image" : inputKinds.includes("audio") ? "audio" : "text";
|
|
666
|
+
const operation = capability?.operations?.find((o) => o.name === draft.capabilityId);
|
|
667
|
+
const all = (operation?.params || []).filter(
|
|
668
|
+
(p) => names[p.name] && ["string", "number", "integer", "boolean"].includes(p.type) && (!p.input_modes || p.input_modes.includes(mode))
|
|
669
|
+
);
|
|
670
|
+
const definitions = all.filter(
|
|
671
|
+
(p) => p.name !== "size" || !all.some((v) => v.name === "aspect-ratio")
|
|
672
|
+
);
|
|
673
|
+
const common = definitions.filter(
|
|
674
|
+
(p) => ["aspect-ratio", "quality", "resolution", "duration"].includes(p.name) || p.required
|
|
675
|
+
);
|
|
676
|
+
const advanced = definitions.filter((p) => !common.includes(p));
|
|
677
|
+
const set = (name, value) => {
|
|
678
|
+
const next = { ...parameters };
|
|
679
|
+
if (value === void 0) delete next[name];
|
|
680
|
+
else next[name] = value;
|
|
681
|
+
changeDraft({ parameters: JSON.stringify(next, null, 2) });
|
|
682
|
+
};
|
|
683
|
+
const field = (p) => {
|
|
684
|
+
const value = parameters[p.name] ?? p.default;
|
|
685
|
+
const choices = p.enum || (p.type === "boolean" ? [true, false] : null);
|
|
686
|
+
const label = names[p.name];
|
|
687
|
+
return /* @__PURE__ */ React2.createElement("div", { className: "vx-field", key: p.name }, /* @__PURE__ */ React2.createElement("span", null, label, p.required ? " *" : ""), choices ? /* @__PURE__ */ React2.createElement(
|
|
688
|
+
Dropdown,
|
|
689
|
+
{
|
|
690
|
+
label,
|
|
691
|
+
value: value === void 0 ? null : JSON.stringify(coerce(p, value)),
|
|
692
|
+
placeholder: p.default !== void 0 ? "Default" : "Automatic",
|
|
693
|
+
options: [
|
|
694
|
+
{ value: "", label: p.default !== void 0 ? "Default" : "Automatic" },
|
|
695
|
+
...choices.map((v) => ({
|
|
696
|
+
value: JSON.stringify(v),
|
|
697
|
+
label: choiceLabels[String(v)] || String(v)
|
|
698
|
+
}))
|
|
699
|
+
],
|
|
700
|
+
onValueChange: (value2) => set(p.name, value2 === "" ? void 0 : JSON.parse(value2))
|
|
701
|
+
}
|
|
702
|
+
) : ["number", "integer"].includes(p.type) ? /* @__PURE__ */ React2.createElement(
|
|
703
|
+
"input",
|
|
704
|
+
{
|
|
705
|
+
"aria-label": label,
|
|
706
|
+
type: "number",
|
|
707
|
+
min: p.min,
|
|
708
|
+
max: p.max,
|
|
709
|
+
step: p.type === "integer" ? 1 : "any",
|
|
710
|
+
value: value ?? "",
|
|
711
|
+
onChange: (e) => set(p.name, e.target.value === "" ? void 0 : Number(e.target.value))
|
|
712
|
+
}
|
|
713
|
+
) : /* @__PURE__ */ React2.createElement(
|
|
714
|
+
"input",
|
|
715
|
+
{
|
|
716
|
+
"aria-label": label,
|
|
717
|
+
value: value ?? "",
|
|
718
|
+
minLength: p.min_length,
|
|
719
|
+
maxLength: p.max_length,
|
|
720
|
+
onChange: (e) => set(p.name, e.target.value)
|
|
721
|
+
}
|
|
722
|
+
));
|
|
723
|
+
};
|
|
724
|
+
return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("div", { className: "generation-settings-grid" }, common.map(field)), advanced.length > 0 && /* @__PURE__ */ React2.createElement("details", { className: "generation-more" }, /* @__PURE__ */ React2.createElement("summary", null, "More settings"), /* @__PURE__ */ React2.createElement("div", { className: "generation-settings-grid" }, advanced.map(field))));
|
|
725
|
+
}
|
|
726
|
+
function coerce(p, value) {
|
|
727
|
+
if (p.type === "integer" || p.type === "number") return Number(value);
|
|
728
|
+
if (p.type === "boolean" && typeof value === "string") return value === "true";
|
|
729
|
+
return value;
|
|
730
|
+
}
|
|
731
|
+
var names, choiceLabels;
|
|
732
|
+
var init_generation_inputs = __esm({
|
|
733
|
+
"packages/project-studio/src/ui/generation-inputs.tsx"() {
|
|
734
|
+
"use strict";
|
|
735
|
+
init_dist();
|
|
736
|
+
init_reference_picker();
|
|
737
|
+
names = {
|
|
738
|
+
"aspect-ratio": "Aspect ratio",
|
|
739
|
+
quality: "Quality",
|
|
740
|
+
resolution: "Resolution",
|
|
741
|
+
duration: "Duration (seconds)",
|
|
742
|
+
size: "Size",
|
|
743
|
+
background: "Background",
|
|
744
|
+
"input-fidelity": "Reference fidelity",
|
|
745
|
+
"output-format": "Output format",
|
|
746
|
+
seed: "Seed",
|
|
747
|
+
"enhance-prompt": "Enhance prompt",
|
|
748
|
+
"camera-fixed": "Fixed camera",
|
|
749
|
+
"voice-id": "Voice ID",
|
|
750
|
+
language: "Language",
|
|
751
|
+
"speech-rate": "Speech rate",
|
|
752
|
+
"pitch-rate": "Pitch",
|
|
753
|
+
volume: "Volume",
|
|
754
|
+
"voice-name": "Voice name",
|
|
755
|
+
prefix: "Voice name",
|
|
756
|
+
"voice-description": "Voice description",
|
|
757
|
+
"preview-text": "Preview text",
|
|
758
|
+
gender: "Gender",
|
|
759
|
+
age: "Age",
|
|
760
|
+
emotion: "Emotion",
|
|
761
|
+
"sample-rate": "Sample rate",
|
|
762
|
+
"bit-rate": "Bit rate",
|
|
763
|
+
speed: "Speech rate",
|
|
764
|
+
"audio-format": "Audio format"
|
|
765
|
+
};
|
|
766
|
+
choiceLabels = {
|
|
767
|
+
low: "Low",
|
|
768
|
+
medium: "Standard",
|
|
769
|
+
high: "High",
|
|
770
|
+
auto: "Automatic",
|
|
771
|
+
transparent: "Transparent",
|
|
772
|
+
opaque: "Opaque",
|
|
773
|
+
standard: "Standard",
|
|
774
|
+
lite: "Fast",
|
|
775
|
+
true: "On",
|
|
776
|
+
false: "Off"
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
// packages/project-studio/src/ads-shared.ts
|
|
782
|
+
import { z } from "vivix-studio/sdk/shared";
|
|
783
|
+
var fixedRef, copy, clipScript, adBrief, adStoryboard;
|
|
784
|
+
var init_ads_shared = __esm({
|
|
785
|
+
"packages/project-studio/src/ads-shared.ts"() {
|
|
786
|
+
"use strict";
|
|
787
|
+
fixedRef = z.object({ storeId: z.uuid(), objectId: z.uuid(), versionId: z.uuid() }).strict();
|
|
788
|
+
copy = z.string().max(8e3).default("");
|
|
789
|
+
clipScript = z.object({
|
|
790
|
+
title: z.string().trim().min(1).max(200),
|
|
791
|
+
visual: copy,
|
|
792
|
+
voiceover: copy,
|
|
793
|
+
onScreenText: copy,
|
|
794
|
+
cta: copy,
|
|
795
|
+
plannedDurationMs: z.number().int().positive().nullable().default(null)
|
|
796
|
+
}).strict();
|
|
797
|
+
adBrief = z.object({
|
|
798
|
+
product: copy,
|
|
799
|
+
category: copy,
|
|
800
|
+
audience: copy,
|
|
801
|
+
sellingPoints: copy,
|
|
802
|
+
channel: copy,
|
|
803
|
+
aspectRatio: z.string().max(40).nullable().default(null),
|
|
804
|
+
targetDurationMs: z.number().int().positive().nullable().default(null),
|
|
805
|
+
body: copy,
|
|
806
|
+
productRefs: z.array(fixedRef).max(100).default([]),
|
|
807
|
+
modelRefs: z.array(fixedRef).max(100).default([])
|
|
808
|
+
}).strict();
|
|
809
|
+
adStoryboard = z.object({
|
|
810
|
+
brief: adBrief,
|
|
811
|
+
clips: z.array(
|
|
812
|
+
z.object({
|
|
813
|
+
assetId: z.uuid(),
|
|
814
|
+
script: clipScript,
|
|
815
|
+
edits: z.array(z.object({ sourceVersionId: z.uuid(), editRef: fixedRef }).strict()).max(200)
|
|
816
|
+
}).strict()
|
|
817
|
+
).max(200)
|
|
818
|
+
}).strict().superRefine((value, context) => {
|
|
819
|
+
if (new Set(value.clips.map((c) => c.assetId)).size !== value.clips.length)
|
|
820
|
+
context.addIssue({ code: "custom", message: "A clip can appear only once in a storyboard." });
|
|
821
|
+
for (const clip of value.clips)
|
|
822
|
+
if (new Set(clip.edits.map((e) => e.sourceVersionId)).size !== clip.edits.length)
|
|
823
|
+
context.addIssue({
|
|
824
|
+
code: "custom",
|
|
825
|
+
message: "Each candidate has one current edit binding."
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
// packages/project-studio/src/shared.ts
|
|
832
|
+
import { z as z2, defineOperation } from "vivix-studio/sdk/shared";
|
|
833
|
+
function generationOperation(model, kind2, referenceCount, action = "") {
|
|
834
|
+
if (!model || model.modality !== kind2) return "";
|
|
835
|
+
const operations = model.operations || [];
|
|
836
|
+
if (kind2 === "image") {
|
|
837
|
+
const name = referenceCount ? "image.edit" : "image.generate";
|
|
838
|
+
return operations.some((operation) => operation.name === name) ? name : "";
|
|
839
|
+
}
|
|
840
|
+
if (operations.length === 1) return operations[0].name;
|
|
841
|
+
return operations.some((operation) => operation.name === action) ? action : "";
|
|
842
|
+
}
|
|
843
|
+
function generationParameters(model, operation, raw, kinds2) {
|
|
844
|
+
let parameters;
|
|
845
|
+
try {
|
|
846
|
+
parameters = JSON.parse(raw);
|
|
847
|
+
} catch {
|
|
848
|
+
return raw;
|
|
849
|
+
}
|
|
850
|
+
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) return raw;
|
|
851
|
+
const mode = kinds2.includes("video") ? "video" : kinds2.includes("image") ? "image" : kinds2.includes("audio") ? "audio" : "text";
|
|
852
|
+
const declared = model?.operations?.flatMap((item) => item.params || []) || [];
|
|
853
|
+
const active = (model?.operations?.find((item) => item.name === operation)?.params || []).filter((parameter) => !parameter.input_modes || parameter.input_modes.includes(mode));
|
|
854
|
+
return JSON.stringify(
|
|
855
|
+
Object.fromEntries(
|
|
856
|
+
Object.entries(parameters).filter(
|
|
857
|
+
([name]) => active.some((parameter) => parameter.name === name) || !declared.some((parameter) => parameter.name === name)
|
|
858
|
+
)
|
|
859
|
+
)
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
var studioConfig, versionRef, tags, kind, projectData, assetData, anchor, generationInput, projectSettingsInput, generationSubmitOperation, favoriteData, sqlScalar, globalQueryInput;
|
|
863
|
+
var init_shared = __esm({
|
|
864
|
+
"packages/project-studio/src/shared.ts"() {
|
|
865
|
+
"use strict";
|
|
866
|
+
init_cost();
|
|
867
|
+
init_ads_shared();
|
|
868
|
+
studioConfig = z2.object({
|
|
869
|
+
role: z2.enum(["project", "coordinator"]).default("project"),
|
|
870
|
+
dataInstance: z2.string().min(1).default("user:data"),
|
|
871
|
+
apiInstance: z2.string().min(1).default("api"),
|
|
872
|
+
libraryInstance: z2.string().min(1).nullable().default(null)
|
|
873
|
+
}).strict();
|
|
874
|
+
versionRef = z2.object({ storeId: z2.uuid(), objectId: z2.uuid(), versionId: z2.uuid() }).strict();
|
|
875
|
+
tags = z2.array(z2.string().trim().min(1).max(64)).max(50).transform((x) => [...new Set(x)]).default([]);
|
|
876
|
+
kind = z2.enum(["text", "image", "video", "audio", "file"]);
|
|
877
|
+
projectData = z2.object({
|
|
878
|
+
name: z2.string().trim().min(1).max(120),
|
|
879
|
+
description: z2.string().max(4e3).default(""),
|
|
880
|
+
status: z2.enum(["draft", "active", "archived"]).default("draft"),
|
|
881
|
+
aspectRatio: z2.string().nullable().default(null),
|
|
882
|
+
coverRef: versionRef.nullable().default(null),
|
|
883
|
+
briefRef: z2.object({ storeId: z2.uuid(), objectId: z2.uuid() }).strict().nullable().default(null),
|
|
884
|
+
tags
|
|
885
|
+
}).strict();
|
|
886
|
+
assetData = z2.object({
|
|
887
|
+
projectId: z2.uuid(),
|
|
888
|
+
name: z2.string().trim().min(1).max(200),
|
|
889
|
+
kind,
|
|
890
|
+
purpose: z2.enum(["reference", "script", "shot", "music", "voice", "subtitle", "final", "other"]).default("other"),
|
|
891
|
+
origin: z2.enum(["upload", "library", "generated", "authored"]),
|
|
892
|
+
orderKey: z2.number().int().nullable().default(null),
|
|
893
|
+
tags,
|
|
894
|
+
lifecycle: z2.enum(["draft", "ready", "archived"]).default("draft"),
|
|
895
|
+
sourceRef: versionRef.nullable().default(null),
|
|
896
|
+
adoptedVersionId: z2.uuid().nullable().default(null)
|
|
897
|
+
}).strict();
|
|
898
|
+
anchor = z2.discriminatedUnion("kind", [
|
|
899
|
+
z2.object({ kind: z2.literal("whole") }).strict(),
|
|
900
|
+
z2.object({
|
|
901
|
+
kind: z2.literal("timeRange"),
|
|
902
|
+
startMs: z2.number().int().nonnegative(),
|
|
903
|
+
endMs: z2.number().int().nonnegative()
|
|
904
|
+
}).strict()
|
|
905
|
+
]);
|
|
906
|
+
generationInput = z2.object({
|
|
907
|
+
targetAssetId: z2.uuid().optional(),
|
|
908
|
+
taskName: z2.string().trim().min(1).max(200).optional(),
|
|
909
|
+
context: z2.record(z2.string().min(1).max(64), z2.string().min(1).max(200)).optional(),
|
|
910
|
+
expectedRevision: z2.number().int().positive().optional(),
|
|
911
|
+
newAsset: z2.object({
|
|
912
|
+
name: z2.string().trim().min(1).max(200),
|
|
913
|
+
kind: z2.enum(["image", "video", "audio"])
|
|
914
|
+
}).strict().optional(),
|
|
915
|
+
modelId: z2.string().min(1),
|
|
916
|
+
capabilityId: z2.string().min(1),
|
|
917
|
+
prompt: z2.string().max(32e3),
|
|
918
|
+
parameters: z2.record(z2.string(), z2.unknown()).default({}),
|
|
919
|
+
outputCount: z2.number().int().min(1).max(100).default(1),
|
|
920
|
+
inputs: z2.array(
|
|
921
|
+
z2.object({
|
|
922
|
+
ref: versionRef,
|
|
923
|
+
role: z2.string().min(1),
|
|
924
|
+
order: z2.number().int().nonnegative(),
|
|
925
|
+
weight: z2.number().finite().nullable().default(null)
|
|
926
|
+
}).strict()
|
|
927
|
+
).max(100).default([]),
|
|
928
|
+
baseVersionId: z2.uuid().optional(),
|
|
929
|
+
changeRequest: z2.string().trim().min(1).max(4e3).optional(),
|
|
930
|
+
preserveRequest: z2.string().max(4e3).default(""),
|
|
931
|
+
anchor: anchor.default({ kind: "whole" }),
|
|
932
|
+
retryOfTaskId: z2.uuid().optional(),
|
|
933
|
+
acknowledgeUnknown: z2.boolean().optional()
|
|
934
|
+
}).strict();
|
|
935
|
+
projectSettingsInput = z2.object({
|
|
936
|
+
projectId: z2.uuid(),
|
|
937
|
+
expectedRevision: z2.number().int().positive(),
|
|
938
|
+
changes: projectData.pick({ name: true, description: true, tags: true, aspectRatio: true, coverRef: true }).extend({
|
|
939
|
+
description: projectData.shape.description.unwrap(),
|
|
940
|
+
tags: projectData.shape.tags.unwrap(),
|
|
941
|
+
aspectRatio: projectData.shape.aspectRatio.unwrap(),
|
|
942
|
+
coverRef: projectData.shape.coverRef.unwrap()
|
|
943
|
+
}).partial().strict()
|
|
944
|
+
}).strict();
|
|
945
|
+
generationSubmitOperation = defineOperation({
|
|
946
|
+
id: "generation.submit",
|
|
947
|
+
title: "Generate",
|
|
948
|
+
description: "Submit one media generation without approval, retaining its paid request identity.",
|
|
949
|
+
input: generationInput,
|
|
950
|
+
output: z2.any(),
|
|
951
|
+
effects: ["write", "paid"],
|
|
952
|
+
confirm: "none"
|
|
953
|
+
});
|
|
954
|
+
favoriteData = z2.object({ ref: versionRef }).strict();
|
|
955
|
+
sqlScalar = z2.union([z2.string(), z2.number().finite(), z2.null()]);
|
|
956
|
+
globalQueryInput = z2.object({
|
|
957
|
+
sql: z2.string().trim().min(1),
|
|
958
|
+
params: z2.union([z2.array(sqlScalar), z2.record(z2.string(), sqlScalar)]).optional(),
|
|
959
|
+
timeoutMs: z2.number().int().positive().optional(),
|
|
960
|
+
maxRows: z2.number().int().positive().optional(),
|
|
961
|
+
limit: z2.number().int().min(1).max(200).optional(),
|
|
962
|
+
offset: z2.number().int().nonnegative().optional(),
|
|
963
|
+
source: z2.enum(["projects", "assets", "tasks"]),
|
|
964
|
+
catalog: z2.array(z2.object({ id: z2.string().min(1), name: z2.string(), available: z2.boolean() }).strict()).optional()
|
|
965
|
+
}).strict();
|
|
966
|
+
}
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// packages/project-studio/src/ui/generation-operation.ts
|
|
970
|
+
var init_generation_operation = __esm({
|
|
971
|
+
"packages/project-studio/src/ui/generation-operation.ts"() {
|
|
972
|
+
"use strict";
|
|
973
|
+
init_shared();
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
// packages/project-studio/src/ui/panels.tsx
|
|
978
|
+
import * as React3 from "react";
|
|
979
|
+
function GenerationPanel(props) {
|
|
980
|
+
const {
|
|
981
|
+
bridge,
|
|
982
|
+
projectId,
|
|
983
|
+
api,
|
|
984
|
+
studio,
|
|
985
|
+
selected,
|
|
986
|
+
draft,
|
|
987
|
+
changeDraft,
|
|
988
|
+
models,
|
|
989
|
+
capability,
|
|
990
|
+
busy,
|
|
991
|
+
submitting = busy,
|
|
992
|
+
submitGeneration,
|
|
993
|
+
attempt,
|
|
994
|
+
setCapability,
|
|
995
|
+
shotGeneration = false,
|
|
996
|
+
resultDescription = "Generated results are saved to this project."
|
|
997
|
+
} = props;
|
|
998
|
+
const [inputKinds, setInputKinds] = React3.useState([]);
|
|
999
|
+
const [loadingModel, setLoadingModel] = React3.useState(false);
|
|
1000
|
+
const modelRequest = React3.useRef(0);
|
|
1001
|
+
const statusId = React3.useId();
|
|
1002
|
+
const kind2 = selected?.data.kind || draft.kind;
|
|
1003
|
+
const currentModel = capability?.model === draft.modelId && capability.modality === kind2 ? capability : null;
|
|
1004
|
+
let references = [];
|
|
1005
|
+
let validReferences = true;
|
|
1006
|
+
try {
|
|
1007
|
+
references = JSON.parse(draft.inputs);
|
|
1008
|
+
if (!Array.isArray(references)) throw Error("Invalid references.");
|
|
1009
|
+
} catch {
|
|
1010
|
+
validReferences = false;
|
|
1011
|
+
}
|
|
1012
|
+
const operation = generationOperation(
|
|
1013
|
+
currentModel,
|
|
1014
|
+
kind2,
|
|
1015
|
+
references?.length || 0,
|
|
1016
|
+
draft.capabilityId
|
|
1017
|
+
);
|
|
1018
|
+
const generationDraft = { ...draft, capabilityId: operation };
|
|
1019
|
+
const chooseAction = kind2 !== "image" && currentModel?.operations?.length > 1;
|
|
1020
|
+
const actionLabel = kind2 === "audio" ? "Audio action" : "Video action";
|
|
1021
|
+
const countParameter = currentModel?.operations?.find((o) => o.name === operation)?.params?.find((p) => ["n", "num-outputs", "output-count"].includes(p.name));
|
|
1022
|
+
const minOutputs = countParameter?.min ?? 1;
|
|
1023
|
+
const maxOutputs = countParameter?.max ?? (countParameter ? 100 : 1);
|
|
1024
|
+
const roles = referenceRoles(currentModel, operation);
|
|
1025
|
+
const requiredReference = roles.find((role) => {
|
|
1026
|
+
const id = role.role || role.name;
|
|
1027
|
+
return references.filter((reference) => reference?.role === id).length < (role.min_items || 0);
|
|
1028
|
+
});
|
|
1029
|
+
const modelListed = models.some(
|
|
1030
|
+
(model) => model.model === draft.modelId && model.modality === kind2
|
|
1031
|
+
);
|
|
1032
|
+
const promptLabel = shotGeneration ? kind2 === "video" ? "Video prompt" : "Image prompt" : "Prompt";
|
|
1033
|
+
const imageOperationMessage = references.length ? "This model does not support reference images. Remove them or select another model." : "This model needs a reference image. Add an image or select another model.";
|
|
1034
|
+
const validationMessage = () => {
|
|
1035
|
+
if (!draft.modelId) return "Select a model.";
|
|
1036
|
+
if (loadingModel) return "Loading model settings\u2026";
|
|
1037
|
+
if (!currentModel || !modelListed || currentModel.available === false)
|
|
1038
|
+
return "This model is unavailable. Select another model.";
|
|
1039
|
+
if (!operation)
|
|
1040
|
+
return kind2 === "image" ? imageOperationMessage : "Select an action supported by this model.";
|
|
1041
|
+
if (!draft.prompt?.trim() && kind2 !== "audio") return `Enter a ${kind2} prompt.`;
|
|
1042
|
+
if (!validReferences) return "Select the reference files again.";
|
|
1043
|
+
if (references.some(
|
|
1044
|
+
(reference) => !roles.some((role) => (role.role || role.name) === reference?.role)
|
|
1045
|
+
))
|
|
1046
|
+
return "Remove or replace the references this model does not support.";
|
|
1047
|
+
if (requiredReference)
|
|
1048
|
+
return `Add the required reference: ${roleLabel(requiredReference.role || requiredReference.name)}.`;
|
|
1049
|
+
if (!Number.isInteger(draft.outputCount) || draft.outputCount < minOutputs || draft.outputCount > maxOutputs)
|
|
1050
|
+
return `Enter an output count from ${minOutputs} to ${maxOutputs}.`;
|
|
1051
|
+
return "";
|
|
1052
|
+
};
|
|
1053
|
+
const blocked = shotGeneration ? validationMessage() : "";
|
|
1054
|
+
const selectModel = (modelId) => {
|
|
1055
|
+
const request = ++modelRequest.current;
|
|
1056
|
+
changeDraft({
|
|
1057
|
+
modelId,
|
|
1058
|
+
capabilityId: "",
|
|
1059
|
+
parameters: "{}",
|
|
1060
|
+
...shotGeneration && { outputCount: 1 }
|
|
1061
|
+
});
|
|
1062
|
+
setCapability(null);
|
|
1063
|
+
setLoadingModel(Boolean(modelId));
|
|
1064
|
+
if (!modelId) return;
|
|
1065
|
+
void attempt(async () => {
|
|
1066
|
+
try {
|
|
1067
|
+
const details = await bridge.project(projectId, api, "capabilities.get", { modelId });
|
|
1068
|
+
if (modelRequest.current === request) setCapability(details);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
if (modelRequest.current === request) throw error;
|
|
1071
|
+
} finally {
|
|
1072
|
+
if (modelRequest.current === request) setLoadingModel(false);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
};
|
|
1076
|
+
return /* @__PURE__ */ React3.createElement("div", { className: "vx-generation-panel" }, /* @__PURE__ */ React3.createElement("p", { className: "vx-generation-hint" }, shotGeneration ? kind2 === "video" ? "Describe the movement, camera, and timing. Review the result before adding it as a candidate." : "Describe the composition, characters, and lighting. Review the result before using it on the storyboard." : "Describe the result you want to create."), !shotGeneration && /* @__PURE__ */ React3.createElement(
|
|
1077
|
+
TextInput,
|
|
1078
|
+
{
|
|
1079
|
+
label: "Task name",
|
|
1080
|
+
value: draft.taskName || "",
|
|
1081
|
+
onValueChange: (taskName) => changeDraft({ taskName })
|
|
1082
|
+
}
|
|
1083
|
+
), !selected && !shotGeneration && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
|
|
1084
|
+
TextInput,
|
|
1085
|
+
{
|
|
1086
|
+
label: "File name",
|
|
1087
|
+
value: draft.newName,
|
|
1088
|
+
onValueChange: (newName) => changeDraft({ newName })
|
|
1089
|
+
}
|
|
1090
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1091
|
+
SegmentedControl,
|
|
1092
|
+
{
|
|
1093
|
+
label: "Creation type",
|
|
1094
|
+
value: draft.kind,
|
|
1095
|
+
onValueChange: (value) => changeDraft({ kind: value }),
|
|
1096
|
+
items: ["image", "video", "audio"].map((value) => ({
|
|
1097
|
+
value,
|
|
1098
|
+
label: { image: "Image", video: "Video", audio: "Audio" }[value]
|
|
1099
|
+
}))
|
|
1100
|
+
}
|
|
1101
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "vx-field" }, /* @__PURE__ */ React3.createElement("span", null, "Model"), /* @__PURE__ */ React3.createElement(
|
|
1102
|
+
Dropdown,
|
|
1103
|
+
{
|
|
1104
|
+
label: "Model",
|
|
1105
|
+
value: draft.modelId || null,
|
|
1106
|
+
placeholder: "Choose a model",
|
|
1107
|
+
options: [
|
|
1108
|
+
{ value: "", label: "Choose a model" },
|
|
1109
|
+
...models.filter((m) => m.modality === (selected?.data.kind || draft.kind)).map((m) => ({ value: m.model, label: m.model }))
|
|
1110
|
+
],
|
|
1111
|
+
onValueChange: selectModel
|
|
1112
|
+
}
|
|
1113
|
+
)), chooseAction && /* @__PURE__ */ React3.createElement("div", { className: "vx-field" }, /* @__PURE__ */ React3.createElement("span", null, actionLabel), /* @__PURE__ */ React3.createElement(
|
|
1114
|
+
Dropdown,
|
|
1115
|
+
{
|
|
1116
|
+
label: actionLabel,
|
|
1117
|
+
value: operation || null,
|
|
1118
|
+
placeholder: "Choose a purpose",
|
|
1119
|
+
options: [
|
|
1120
|
+
{ value: "", label: "Choose an action" },
|
|
1121
|
+
...currentModel.operations.map((item) => ({
|
|
1122
|
+
value: item.name,
|
|
1123
|
+
label: {
|
|
1124
|
+
"voice.generate": "Generate speech",
|
|
1125
|
+
"voice.clone": "Clone voice",
|
|
1126
|
+
"voice.design": "Design voice",
|
|
1127
|
+
"voice.create": "Create voice",
|
|
1128
|
+
"video.generate": "Generate video",
|
|
1129
|
+
"video.edit": "Edit video",
|
|
1130
|
+
"video.extend": "Extend video"
|
|
1131
|
+
}[item.name] || item.name
|
|
1132
|
+
}))
|
|
1133
|
+
],
|
|
1134
|
+
onValueChange: (capabilityId) => changeDraft({ capabilityId, ...shotGeneration && { outputCount: 1 } })
|
|
1135
|
+
}
|
|
1136
|
+
)), !shotGeneration && currentModel && !operation && kind2 === "image" && /* @__PURE__ */ React3.createElement("p", { className: "reference-warning", role: "alert" }, imageOperationMessage), /* @__PURE__ */ React3.createElement("label", { className: "vx-field" }, /* @__PURE__ */ React3.createElement("span", null, promptLabel), /* @__PURE__ */ React3.createElement(
|
|
1137
|
+
"textarea",
|
|
1138
|
+
{
|
|
1139
|
+
"aria-label": promptLabel,
|
|
1140
|
+
rows: 6,
|
|
1141
|
+
value: draft.prompt,
|
|
1142
|
+
onChange: (e) => changeDraft({ prompt: e.target.value })
|
|
1143
|
+
}
|
|
1144
|
+
)), /* @__PURE__ */ React3.createElement(
|
|
1145
|
+
ReferenceInputs,
|
|
1146
|
+
{
|
|
1147
|
+
bridge,
|
|
1148
|
+
projectId,
|
|
1149
|
+
studio,
|
|
1150
|
+
capability: currentModel,
|
|
1151
|
+
draft: generationDraft,
|
|
1152
|
+
changeDraft,
|
|
1153
|
+
attempt,
|
|
1154
|
+
onInputKinds: setInputKinds
|
|
1155
|
+
}
|
|
1156
|
+
), /* @__PURE__ */ React3.createElement(
|
|
1157
|
+
TypedParameters,
|
|
1158
|
+
{
|
|
1159
|
+
capability: currentModel,
|
|
1160
|
+
draft: generationDraft,
|
|
1161
|
+
changeDraft,
|
|
1162
|
+
inputKinds
|
|
1163
|
+
}
|
|
1164
|
+
), (!shotGeneration || countParameter) && /* @__PURE__ */ React3.createElement("label", { className: "vx-field vx-generation-count" }, /* @__PURE__ */ React3.createElement("span", null, "Number of results"), /* @__PURE__ */ React3.createElement(
|
|
1165
|
+
"input",
|
|
1166
|
+
{
|
|
1167
|
+
"aria-label": "Number of results",
|
|
1168
|
+
type: "number",
|
|
1169
|
+
min: minOutputs,
|
|
1170
|
+
max: maxOutputs,
|
|
1171
|
+
disabled: !countParameter,
|
|
1172
|
+
value: draft.outputCount,
|
|
1173
|
+
onChange: (e) => changeDraft({ outputCount: Number(e.target.value) })
|
|
1174
|
+
}
|
|
1175
|
+
)), /* @__PURE__ */ React3.createElement("div", { className: "vx-generation-submit" }, /* @__PURE__ */ React3.createElement("p", { className: "vx-generation-hint", id: statusId, role: blocked ? "status" : void 0 }, blocked || resultDescription), /* @__PURE__ */ React3.createElement(
|
|
1176
|
+
Button,
|
|
1177
|
+
{
|
|
1178
|
+
label: submitting || shotGeneration && busy ? "Submitting\u2026" : "Generate",
|
|
1179
|
+
"aria-describedby": statusId,
|
|
1180
|
+
loading: submitting || shotGeneration && busy,
|
|
1181
|
+
width: "hug",
|
|
1182
|
+
variant: "text-filled-md",
|
|
1183
|
+
disabled: busy || loadingModel || Boolean(blocked) || !draft.modelId || !draft.prompt?.trim() && kind2 !== "audio" || !operation || !validReferences || !currentModel || !models.some(
|
|
1184
|
+
(m) => m.model === draft.modelId && m.modality === (selected?.data.kind || draft.kind)
|
|
1185
|
+
),
|
|
1186
|
+
onClick: () => void submitGeneration({
|
|
1187
|
+
capabilityId: operation,
|
|
1188
|
+
parameters: generationParameters(
|
|
1189
|
+
currentModel,
|
|
1190
|
+
operation,
|
|
1191
|
+
draft.parameters,
|
|
1192
|
+
inputKinds
|
|
1193
|
+
)
|
|
1194
|
+
})
|
|
1195
|
+
}
|
|
1196
|
+
)));
|
|
1197
|
+
}
|
|
1198
|
+
function ReviewPanel(props) {
|
|
1199
|
+
const {
|
|
1200
|
+
selected,
|
|
1201
|
+
locations,
|
|
1202
|
+
Preview,
|
|
1203
|
+
tasks,
|
|
1204
|
+
attempt,
|
|
1205
|
+
setLocations,
|
|
1206
|
+
projectCall,
|
|
1207
|
+
generation,
|
|
1208
|
+
setSelected,
|
|
1209
|
+
openForm
|
|
1210
|
+
} = props;
|
|
1211
|
+
return selected ? /* @__PURE__ */ React3.createElement("div", { className: "split" }, /* @__PURE__ */ React3.createElement("div", { className: "compare" }, selected.versions.map((v) => /* @__PURE__ */ React3.createElement("section", { className: "candidate", key: v.versionId }, /* @__PURE__ */ React3.createElement("h3", null, "v", v.versionNumber, " ", selected.data.adoptedVersionId === v.versionId ? "\xB7 Adopted" : ""), /* @__PURE__ */ React3.createElement(Preview, { kind: selected.data.kind, location: locations[v.versionId] }), !locations[v.versionId] && /* @__PURE__ */ React3.createElement(
|
|
1212
|
+
"button",
|
|
1213
|
+
{
|
|
1214
|
+
onClick: () => void attempt(
|
|
1215
|
+
async () => setLocations({
|
|
1216
|
+
...locations,
|
|
1217
|
+
[v.versionId]: await projectCall("files.resolveLocation", {
|
|
1218
|
+
ref: ref(selected, v)
|
|
1219
|
+
})
|
|
1220
|
+
})
|
|
1221
|
+
)
|
|
1222
|
+
},
|
|
1223
|
+
"Load version"
|
|
1224
|
+
), /* @__PURE__ */ React3.createElement("div", { className: "sub" }, v.payload.durationMs !== null ? v.payload.durationMs + " ms" : "", " \xB7 ", v.origin), /* @__PURE__ */ React3.createElement("div", { className: "actions" }, /* @__PURE__ */ React3.createElement("button", { onClick: () => generation(v) }, "Iterate from v", v.versionNumber), /* @__PURE__ */ React3.createElement(
|
|
1225
|
+
"button",
|
|
1226
|
+
{
|
|
1227
|
+
onClick: () => void attempt(async () => {
|
|
1228
|
+
await projectCall(
|
|
1229
|
+
"choices.adopt",
|
|
1230
|
+
{
|
|
1231
|
+
assetId: selected.objectId,
|
|
1232
|
+
versionId: v.versionId,
|
|
1233
|
+
expectedRevision: selected.revision
|
|
1234
|
+
},
|
|
1235
|
+
{ requestKey: freshKey() }
|
|
1236
|
+
);
|
|
1237
|
+
const current = await projectCall("assets.get", {
|
|
1238
|
+
assetId: selected.objectId
|
|
1239
|
+
});
|
|
1240
|
+
setSelected(current);
|
|
1241
|
+
})
|
|
1242
|
+
},
|
|
1243
|
+
"Adopt"
|
|
1244
|
+
))))), /* @__PURE__ */ React3.createElement("section", null, /* @__PURE__ */ React3.createElement("div", { className: "eyebrow" }, "Comments and iteration"), /* @__PURE__ */ React3.createElement("p", { className: "sub" }, "Choose a fixed version. Enter changes and preservation requirements before submitting."), tasks.filter((t) => t.data.kind === "iterate").map((t) => /* @__PURE__ */ React3.createElement("article", { className: "history", key: t.objectId }, /* @__PURE__ */ React3.createElement("span", { className: "sub" }, t.data.state, " \xB7", " ", t.data.inputSnapshot.anchor.kind === "timeRange" ? `${t.data.inputSnapshot.anchor.startMs / 1e3}${t.data.inputSnapshot.anchor.endMs !== t.data.inputSnapshot.anchor.startMs ? "\u2013" + t.data.inputSnapshot.anchor.endMs / 1e3 : ""} s` : "Whole version"), /* @__PURE__ */ React3.createElement("p", null, t.data.inputSnapshot.changeRequest), /* @__PURE__ */ React3.createElement("p", { className: "sub" }, "Preserve: ", t.data.inputSnapshot.preserveRequest), /* @__PURE__ */ React3.createElement("button", { onClick: () => openForm("Task", t) }, "View task"))))) : /* @__PURE__ */ React3.createElement("div", { className: "empty" }, "Select an asset in the asset table to compare its versions.");
|
|
1245
|
+
}
|
|
1246
|
+
function TaskDetails({ form }) {
|
|
1247
|
+
const snapshot = form.data.inputSnapshot;
|
|
1248
|
+
return /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("h3", null, form.data.name || "Generation"), /* @__PURE__ */ React3.createElement("p", { className: "status" }, form.data.state), /* @__PURE__ */ React3.createElement("dl", null, /* @__PURE__ */ React3.createElement("dt", null, "Actual cost"), /* @__PURE__ */ React3.createElement("dd", null, taskCostLabel(form.data)), form.data.cost?.checkedAt && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("dt", null, "Cost updated"), /* @__PURE__ */ React3.createElement("dd", null, new Date(form.data.cost.checkedAt).toLocaleString())), /* @__PURE__ */ React3.createElement("dt", null, "Model"), /* @__PURE__ */ React3.createElement("dd", null, snapshot.modelId), /* @__PURE__ */ React3.createElement("dt", null, "Prompt"), /* @__PURE__ */ React3.createElement("dd", null, snapshot.originalPrompt), form.projection && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("dt", null, "Remote result"), /* @__PURE__ */ React3.createElement("dd", null, form.projection.remoteOutcome.status), /* @__PURE__ */ React3.createElement("dt", null, "Local saving"), /* @__PURE__ */ React3.createElement("dd", null, form.projection.localPersistence), /* @__PURE__ */ React3.createElement("dt", null, "Outputs returned"), /* @__PURE__ */ React3.createElement("dd", null, form.projection.remoteOutcome.resultCount ?? "Unknown", " /", " ", form.projection.remoteOutcome.requestedCount ?? "Unknown")), /* @__PURE__ */ React3.createElement("dt", null, "Outputs saved"), /* @__PURE__ */ React3.createElement("dd", null, form.data.receivedOutputs, " / ", form.data.expectedOutputs)), form.data.cost?.error && /* @__PURE__ */ React3.createElement("p", { role: "status" }, form.data.cost.error.message), form.data.error && /* @__PURE__ */ React3.createElement("p", { role: "alert" }, form.data.error.message));
|
|
1249
|
+
}
|
|
1250
|
+
function ref(object, version) {
|
|
1251
|
+
return { storeId: object.storeId, objectId: object.objectId, versionId: version.versionId };
|
|
1252
|
+
}
|
|
1253
|
+
var freshKey;
|
|
1254
|
+
var init_panels = __esm({
|
|
1255
|
+
"packages/project-studio/src/ui/panels.tsx"() {
|
|
1256
|
+
"use strict";
|
|
1257
|
+
init_cost();
|
|
1258
|
+
init_dist();
|
|
1259
|
+
init_generation_inputs();
|
|
1260
|
+
init_generation_operation();
|
|
1261
|
+
init_reference_picker();
|
|
1262
|
+
freshKey = () => crypto.randomUUID();
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// packages/ui-system/dist/styles.css
|
|
1267
|
+
var init_styles = __esm({
|
|
1268
|
+
"packages/ui-system/dist/styles.css"() {
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
// packages/project-studio/src/ui/generation-form.css
|
|
1273
|
+
var init_generation_form = __esm({
|
|
1274
|
+
"packages/project-studio/src/ui/generation-form.css"() {
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
// packages/project-studio/src/ui/generation-ui.tsx
|
|
1279
|
+
var generation_ui_exports = {};
|
|
1280
|
+
__export(generation_ui_exports, {
|
|
1281
|
+
default: () => GenerationPanel
|
|
1282
|
+
});
|
|
1283
|
+
var init_generation_ui = __esm({
|
|
1284
|
+
"packages/project-studio/src/ui/generation-ui.tsx"() {
|
|
1285
|
+
"use strict";
|
|
1286
|
+
init_styles();
|
|
1287
|
+
init_generation_form();
|
|
1288
|
+
init_panels();
|
|
1289
|
+
}
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// packages/project-studio/src/client.tsx
|
|
1293
|
+
init_panels();
|
|
1294
|
+
import * as React4 from "react";
|
|
1295
|
+
|
|
1296
|
+
// packages/project-studio/src/property-metadata.ts
|
|
1297
|
+
function isInternalMediaProvenance(value) {
|
|
1298
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1299
|
+
const media = value;
|
|
1300
|
+
const uuid = (value2) => typeof value2 === "string" && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(value2);
|
|
1301
|
+
if (media.kind !== "video" && media.kind !== "sequence" || !uuid(media.jobId)) return false;
|
|
1302
|
+
if (!Array.isArray(media.inputs) || !media.inputs.length) return false;
|
|
1303
|
+
return media.inputs.every((input) => {
|
|
1304
|
+
if (!input || typeof input !== "object") return false;
|
|
1305
|
+
const { file, recipe } = input;
|
|
1306
|
+
return uuid(file?.fileId) && uuid(file?.source?.storeId) && uuid(file?.source?.objectId) && uuid(file?.source?.versionId) && recipe && typeof recipe === "object" && recipe.trim && recipe.crop;
|
|
1307
|
+
}) && !!media.tools && typeof media.tools === "object" && typeof media.tools.ffmpegVersion === "string" && typeof media.tools.ffprobeVersion === "string";
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// packages/project-studio/src/client.tsx
|
|
1311
|
+
init_shared();
|
|
1312
|
+
var StyledGenerationPanel = React4.lazy(() => Promise.resolve().then(() => (init_generation_ui(), generation_ui_exports)));
|
|
1313
|
+
function GenerationPanel2(props) {
|
|
1314
|
+
return /* @__PURE__ */ React4.createElement(React4.Suspense, { fallback: /* @__PURE__ */ React4.createElement("p", null, "Loading generation settings\u2026") }, /* @__PURE__ */ React4.createElement(StyledGenerationPanel, { ...props }));
|
|
1315
|
+
}
|
|
1316
|
+
function AssetPreview({ location, kind: kind2 }) {
|
|
1317
|
+
if (!location) return /* @__PURE__ */ React4.createElement("div", { className: "empty-preview" }, "Select a readable version");
|
|
1318
|
+
const url = location.previewUrl;
|
|
1319
|
+
return kind2 === "video" ? /* @__PURE__ */ React4.createElement("video", { controls: true, src: url }) : kind2 === "audio" ? /* @__PURE__ */ React4.createElement("audio", { controls: true, src: url }) : kind2 === "image" ? /* @__PURE__ */ React4.createElement("img", { src: url, alt: "Selected version" }) : kind2 === "text" ? /* @__PURE__ */ React4.createElement(TextPreview, { url }) : /* @__PURE__ */ React4.createElement("a", { href: url }, "Open file");
|
|
1320
|
+
}
|
|
1321
|
+
function TextPreview({ url }) {
|
|
1322
|
+
const [text, setText] = React4.useState("");
|
|
1323
|
+
React4.useEffect(() => {
|
|
1324
|
+
let current = true;
|
|
1325
|
+
fetch(url).then((r) => {
|
|
1326
|
+
if (!r.ok) throw Error("Text is unavailable.");
|
|
1327
|
+
return r.text();
|
|
1328
|
+
}).then((t) => {
|
|
1329
|
+
if (current) setText(t);
|
|
1330
|
+
}).catch((e) => {
|
|
1331
|
+
if (current) setText(e.message);
|
|
1332
|
+
});
|
|
1333
|
+
return () => {
|
|
1334
|
+
current = false;
|
|
1335
|
+
};
|
|
1336
|
+
}, [url]);
|
|
1337
|
+
return /* @__PURE__ */ React4.createElement("pre", { className: "text-preview" }, text);
|
|
1338
|
+
}
|
|
1339
|
+
function AssetProperties({ record }) {
|
|
1340
|
+
return /* @__PURE__ */ React4.createElement("dl", null, /* @__PURE__ */ React4.createElement("dt", null, "Type"), /* @__PURE__ */ React4.createElement("dd", null, record.data.kind), /* @__PURE__ */ React4.createElement("dt", null, "Tags"), /* @__PURE__ */ React4.createElement("dd", null, record.data.tags.map((t) => /* @__PURE__ */ React4.createElement("span", { className: "tag", key: t }, t))), /* @__PURE__ */ React4.createElement("dt", null, "Latest"), /* @__PURE__ */ React4.createElement("dd", null, record.versions?.at(-1)?.versionNumber || "No content"), /* @__PURE__ */ React4.createElement("dt", null, "Adopted"), /* @__PURE__ */ React4.createElement("dd", null, record.versions?.find((v) => v.versionId === record.data.adoptedVersionId)?.versionNumber || "None"), /* @__PURE__ */ React4.createElement("dt", null, "Availability"), /* @__PURE__ */ React4.createElement("dd", null, record.availability), /* @__PURE__ */ React4.createElement("dt", null, "Revision"), /* @__PURE__ */ React4.createElement("dd", null, record.revision), Object.entries(record.metadata).filter(([key, value]) => key !== "media" || !isInternalMediaProvenance(value)).map(([key, value]) => /* @__PURE__ */ React4.createElement(React4.Fragment, { key }, /* @__PURE__ */ React4.createElement("dt", null, key), /* @__PURE__ */ React4.createElement("dd", null, /* @__PURE__ */ React4.createElement("details", null, /* @__PURE__ */ React4.createElement("summary", null, typeof value === "object" ? JSON.stringify(value).slice(0, 60) : String(value)), /* @__PURE__ */ React4.createElement("pre", null, JSON.stringify(value, null, 2)))))));
|
|
1341
|
+
}
|
|
1342
|
+
var dependencies = (config) => [
|
|
1343
|
+
config.apiInstance,
|
|
1344
|
+
config.dataInstance,
|
|
1345
|
+
...config.libraryInstance ? [config.libraryInstance] : []
|
|
1346
|
+
];
|
|
1347
|
+
function activate(client) {
|
|
1348
|
+
if (client.config.role === "coordinator") return;
|
|
1349
|
+
let active = true;
|
|
1350
|
+
for (const [id, component] of Object.entries({
|
|
1351
|
+
"studio.generation": GenerationPanel2,
|
|
1352
|
+
"studio.review": ReviewPanel,
|
|
1353
|
+
"studio.task": TaskDetails,
|
|
1354
|
+
"studio.preview": AssetPreview,
|
|
1355
|
+
"studio.properties": AssetProperties
|
|
1356
|
+
}))
|
|
1357
|
+
client.registerUI({
|
|
1358
|
+
id,
|
|
1359
|
+
title: id,
|
|
1360
|
+
kind: "component",
|
|
1361
|
+
component: (props) => active ? React4.createElement(component, props) : null
|
|
1362
|
+
});
|
|
1363
|
+
client.onDispose(() => {
|
|
1364
|
+
active = false;
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
export {
|
|
1368
|
+
AssetPreview,
|
|
1369
|
+
AssetProperties,
|
|
1370
|
+
studioConfig as Config,
|
|
1371
|
+
activate as default,
|
|
1372
|
+
dependencies
|
|
1373
|
+
};
|