@tangle-network/agent-app 0.45.65 → 0.46.1

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.
Files changed (39) hide show
  1. package/dist/assistant/index.js +3 -3
  2. package/dist/chat-react/index.js +2 -2
  3. package/dist/chunk-2RTYQU4W.js +441 -0
  4. package/dist/chunk-2RTYQU4W.js.map +1 -0
  5. package/dist/{chunk-23J72UUA.js → chunk-DINGA2MO.js} +94 -560
  6. package/dist/chunk-DINGA2MO.js.map +1 -0
  7. package/dist/{chunk-AWM4H5XR.js → chunk-MBKNKAN2.js} +6 -4
  8. package/dist/{chunk-AWM4H5XR.js.map → chunk-MBKNKAN2.js.map} +1 -1
  9. package/dist/chunk-YDOQE47G.js +518 -0
  10. package/dist/chunk-YDOQE47G.js.map +1 -0
  11. package/dist/stories/studio/fixtures.d.ts +0 -4
  12. package/dist/studio/generation.d.ts +16 -74
  13. package/dist/studio/index.d.ts +2 -2
  14. package/dist/studio/index.js +27 -25
  15. package/dist/studio/model-options.d.ts +61 -0
  16. package/dist/studio-react/composer-option-controls.d.ts +140 -0
  17. package/dist/studio-react/generation-notice.d.ts +18 -0
  18. package/dist/studio-react/index.d.ts +6 -12
  19. package/dist/studio-react/index.js +902 -827
  20. package/dist/studio-react/index.js.map +1 -1
  21. package/dist/studio-react/studio-composer.d.ts +34 -0
  22. package/dist/studio-react/studio-workspace.d.ts +2 -4
  23. package/dist/studio-react/studio.css +51 -0
  24. package/dist/theme/tokens.css +18 -0
  25. package/dist/web-react/index.js +11 -11
  26. package/package.json +1 -1
  27. package/dist/chunk-23J72UUA.js.map +0 -1
  28. package/dist/chunk-HSBJB46C.js +0 -305
  29. package/dist/chunk-HSBJB46C.js.map +0 -1
  30. package/dist/chunk-MLG6XKPV.js +0 -44
  31. package/dist/chunk-MLG6XKPV.js.map +0 -1
  32. package/dist/studio-react/avatar-composer.d.ts +0 -8
  33. package/dist/studio-react/composer-hero.d.ts +0 -12
  34. package/dist/studio-react/composer-shell.d.ts +0 -18
  35. package/dist/studio-react/image-composer.d.ts +0 -8
  36. package/dist/studio-react/publish-package-composer.d.ts +0 -18
  37. package/dist/studio-react/speech-composer.d.ts +0 -4
  38. package/dist/studio-react/transcription-composer.d.ts +0 -12
  39. package/dist/studio-react/video-composer.d.ts +0 -10
@@ -1,44 +1,43 @@
1
1
  import {
2
- CADENCES,
3
- DESTINATIONS,
4
- IMAGE_QUALITIES,
5
- IMAGE_SIZE_HINT,
6
- IMAGE_SIZE_PATTERN,
7
- MAX_IMAGE_COUNT,
8
- MIN_IMAGE_COUNT,
9
- VIDEO_ASPECT_RATIOS,
10
- VIDEO_DURATIONS,
11
- VIDEO_RESOLUTIONS,
12
2
  buildGenerationRequestBody,
13
- buildPublishPackage,
3
+ curateComposerModels,
14
4
  failedOptimisticGeneration,
15
5
  generationError,
16
6
  generationStatus,
17
7
  generationVaultPath,
18
- isDestinationConnected,
8
+ imageToVideoSibling,
19
9
  isGenerationType,
20
10
  isLocalGeneration,
21
- isPublishPackage,
22
11
  latestBatchOf,
23
12
  mergeLiveGeneration,
24
13
  mergeLoaderAndLive,
25
14
  modelMessage,
26
15
  normalizeImageCount,
27
16
  optimisticGeneration,
28
- outputPathFor,
17
+ optionChoices,
29
18
  preferredModelId,
19
+ reconcileOptionValues,
30
20
  relativeTime,
31
- selectedModelsWithDefaults,
32
- userSafeGenerationMessage
33
- } from "../chunk-HSBJB46C.js";
21
+ resolveComposerOptions,
22
+ supportsCustomImageSize,
23
+ textToVideoSibling,
24
+ userSafeGenerationMessage,
25
+ validateCustomImageSize
26
+ } from "../chunk-2RTYQU4W.js";
34
27
  import {
35
- ProviderLogo
36
- } from "../chunk-MLG6XKPV.js";
28
+ CheckGlyph,
29
+ ChevronDown,
30
+ OVERLAY_SHADOW,
31
+ POPOVER_OPTION_FOCUS,
32
+ PopoverSurface,
33
+ ProviderLogo,
34
+ usePopover
35
+ } from "../chunk-YDOQE47G.js";
37
36
 
38
37
  // src/studio-react/studio-workspace.tsx
39
- import { useRef as useRef3, useState as useState3 } from "react";
38
+ import { useRef as useRef3, useState as useState4 } from "react";
40
39
  import { useSearchParams } from "react-router";
41
- import { Button as Button6 } from "@tangle-network/sandbox-ui/primitives";
40
+ import { Button as Button4 } from "@tangle-network/sandbox-ui/primitives";
42
41
  import { Images as Images2 } from "lucide-react";
43
42
 
44
43
  // src/studio-react/use-studio-generations.ts
@@ -97,477 +96,651 @@ function useStudioGenerations(loaderGenerations, options = {}) {
97
96
  return { mergedGenerations, latestBatch, onGenerated };
98
97
  }
99
98
 
100
- // src/studio-react/composer-hero.tsx
101
- import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
102
- import {
103
- Badge as Badge2,
104
- Button as Button2,
105
- Input as Input7,
106
- Label as Label2,
107
- Textarea as Textarea2,
108
- Select,
109
- SelectContent,
110
- SelectItem,
111
- SelectTrigger,
112
- SelectValue
113
- } from "@tangle-network/sandbox-ui/primitives";
114
- import { useIntegrations } from "@tangle-network/sandbox-ui/integrations";
115
- import { Sparkles } from "lucide-react";
99
+ // src/studio-react/studio-composer.tsx
100
+ import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef2, useState as useState3 } from "react";
101
+ import { AudioLines, ArrowUp, Image as ImageIcon, Video } from "lucide-react";
116
102
 
117
- // src/studio-react/type-config.ts
118
- import { FileText, Image, Mic, User, Video } from "lucide-react";
119
- var MEDIA_SCRIM = "bg-black/60 text-white border-white/10";
120
- var IMAGE = { label: "Image", icon: Image, color: MEDIA_SCRIM };
121
- var TYPE_CONFIG = {
122
- image: IMAGE,
123
- video: { label: "Video", icon: Video, color: MEDIA_SCRIM },
124
- avatar: { label: "Avatar", icon: User, color: MEDIA_SCRIM },
125
- speech: { label: "Audio", icon: Mic, color: MEDIA_SCRIM },
126
- transcription: { label: "Transcript", icon: FileText, color: MEDIA_SCRIM }
127
- };
128
- function typeConfigFor(type) {
129
- return TYPE_CONFIG[type] ?? IMAGE;
103
+ // src/studio-react/composer-option-controls.tsx
104
+ import {
105
+ useCallback as useCallback2,
106
+ useEffect as useEffect2,
107
+ useId,
108
+ useState as useState2
109
+ } from "react";
110
+ import { ImagePlus, Volume2, VolumeX, X } from "lucide-react";
111
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
112
+ var PILL = "inline-flex h-7 flex-none items-center gap-1.5 whitespace-nowrap rounded-full border border-border bg-card px-2.5 text-[12.5px] font-medium text-foreground transition hover:bg-accent";
113
+ var MENU_PANEL = `flex min-w-[184px] flex-col overflow-y-auto rounded-xl border border-border bg-popover p-1 text-popover-foreground ${OVERLAY_SHADOW}`;
114
+ var MENU_HEADER = "px-2.5 pb-1 pt-1.5 text-[10.5px] font-semibold uppercase tracking-[0.08em] text-muted-foreground";
115
+ function menuRowClass(selected) {
116
+ return `flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-[13px] transition ${POPOVER_OPTION_FOCUS} ${selected ? "bg-primary/10 font-medium" : "hover:bg-accent"}`;
130
117
  }
131
-
132
- // src/studio-react/composer-shell.tsx
133
- import { Label } from "@tangle-network/sandbox-ui/primitives";
134
- import { ChevronRight, Minus, Plus } from "lucide-react";
135
- import { jsx, jsxs } from "react/jsx-runtime";
136
- function Field({
137
- label,
138
- htmlFor,
139
- className = "space-y-1.5",
118
+ var MAX_FADE = 32;
119
+ function MediaTypeSegments({
120
+ value,
121
+ segments,
122
+ onChange
123
+ }) {
124
+ return /* @__PURE__ */ jsx(
125
+ "div",
126
+ {
127
+ role: "group",
128
+ "aria-label": "Media type",
129
+ className: "flex flex-none items-center gap-0.5 rounded-full border border-border bg-muted p-[3px]",
130
+ children: segments.map(({ type, label, icon: Icon }) => {
131
+ const active = type === value;
132
+ return /* @__PURE__ */ jsxs(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ "aria-pressed": active,
137
+ "aria-label": label,
138
+ title: label,
139
+ onClick: () => onChange(type),
140
+ className: `inline-flex h-7 items-center gap-1.5 whitespace-nowrap rounded-full text-[12.5px] transition ${active ? "bg-card px-2.5 font-medium text-foreground shadow-sm" : "px-2 text-muted-foreground hover:text-foreground"}`,
141
+ children: [
142
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0", strokeWidth: 1.5 }),
143
+ active && /* @__PURE__ */ jsx("span", { children: label })
144
+ ]
145
+ },
146
+ type
147
+ );
148
+ })
149
+ }
150
+ );
151
+ }
152
+ function ComposerBand({
153
+ bandRef,
154
+ resetKey,
140
155
  children
141
156
  }) {
142
- return /* @__PURE__ */ jsxs("div", { className, children: [
143
- /* @__PURE__ */ jsx(
144
- Label,
145
- {
146
- htmlFor,
147
- className: "text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground",
148
- children: label
149
- }
150
- ),
151
- children
152
- ] });
157
+ const sync = useCallback2(() => {
158
+ const band = bandRef.current;
159
+ if (!band) return;
160
+ const hiddenStart = band.scrollLeft;
161
+ const hiddenEnd = Math.max(0, band.scrollWidth - band.clientWidth - band.scrollLeft);
162
+ const atStart = hiddenStart > 1;
163
+ const atEnd = hiddenEnd > 1;
164
+ band.dataset.overflow = atStart && atEnd ? "both" : atStart ? "start" : atEnd ? "end" : "none";
165
+ band.style.setProperty("--fade-start", `${atStart ? Math.min(MAX_FADE, hiddenStart) : 0}px`);
166
+ band.style.setProperty("--fade-end", `${atEnd ? Math.min(MAX_FADE, hiddenEnd) : 0}px`);
167
+ }, [bandRef]);
168
+ useEffect2(sync);
169
+ useEffect2(() => {
170
+ const band = bandRef.current;
171
+ window.addEventListener("resize", sync);
172
+ const observer = typeof ResizeObserver === "undefined" || !band ? null : new ResizeObserver(sync);
173
+ if (band) observer?.observe(band);
174
+ return () => {
175
+ window.removeEventListener("resize", sync);
176
+ observer?.disconnect();
177
+ };
178
+ }, [bandRef, sync]);
179
+ useEffect2(() => {
180
+ const band = bandRef.current;
181
+ if (band) band.scrollLeft = 0;
182
+ sync();
183
+ }, [bandRef, resetKey, sync]);
184
+ return /* @__PURE__ */ jsx(
185
+ "div",
186
+ {
187
+ ref: bandRef,
188
+ "data-overflow": "none",
189
+ onScroll: sync,
190
+ className: "studio-band flex min-w-0 flex-1 flex-nowrap items-center gap-2 overflow-x-auto",
191
+ children
192
+ }
193
+ );
153
194
  }
154
- function Stepper({
195
+ function useCloseWhenScrolledOut(open, setOpen, triggerRef, bandRef) {
196
+ useEffect2(() => {
197
+ const band = bandRef.current;
198
+ if (!open || !band) return;
199
+ const onScroll = () => {
200
+ const trigger = triggerRef.current;
201
+ if (!trigger) return;
202
+ const anchor = trigger.getBoundingClientRect();
203
+ const box = band.getBoundingClientRect();
204
+ const centre = anchor.left + anchor.width / 2;
205
+ if (centre < box.left || centre > box.right) setOpen(false);
206
+ };
207
+ band.addEventListener("scroll", onScroll);
208
+ return () => band.removeEventListener("scroll", onScroll);
209
+ }, [bandRef, open, setOpen, triggerRef]);
210
+ }
211
+ function OptionPill({
212
+ label,
155
213
  value,
156
- min,
157
- max,
158
- onChange
214
+ choices,
215
+ onSelect,
216
+ bandRef,
217
+ custom
159
218
  }) {
160
- return /* @__PURE__ */ jsxs("div", { className: "flex h-9 items-center justify-between rounded-md border border-input bg-background px-1.5", children: [
161
- /* @__PURE__ */ jsx(
219
+ const [open, setOpen] = useState2(false);
220
+ const [customOpen, setCustomOpen] = useState2(false);
221
+ const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen);
222
+ const panelId = useId();
223
+ useCloseWhenScrolledOut(open, setOpen, triggerRef, bandRef);
224
+ const selected = choices.find((choice) => choice.value === value);
225
+ const close = () => {
226
+ setOpen(false);
227
+ setCustomOpen(false);
228
+ };
229
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-flex flex-none", children: [
230
+ /* @__PURE__ */ jsxs(
162
231
  "button",
163
232
  {
164
233
  type: "button",
165
- "aria-label": "Decrease",
166
- onClick: () => onChange(Math.max(min, value - 1)),
167
- disabled: value <= min,
168
- className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
169
- children: /* @__PURE__ */ jsx(Minus, { className: "h-3.5 w-3.5" })
234
+ ...triggerProps,
235
+ "aria-controls": open ? panelId : void 0,
236
+ title: label,
237
+ "aria-label": `${label}: ${selected?.label ?? "not set"}`,
238
+ onClick: () => {
239
+ setCustomOpen(false);
240
+ setOpen(!open);
241
+ },
242
+ className: PILL,
243
+ children: [
244
+ /* @__PURE__ */ jsx("span", { children: selected?.label ?? "\u2014" }),
245
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })
246
+ ]
170
247
  }
171
248
  ),
172
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: value }),
173
- /* @__PURE__ */ jsx(
174
- "button",
249
+ /* @__PURE__ */ jsxs(
250
+ PopoverSurface,
175
251
  {
176
- type: "button",
177
- "aria-label": "Increase",
178
- onClick: () => onChange(Math.min(max, value + 1)),
179
- disabled: value >= max,
180
- className: "flex h-6 w-6 items-center justify-center rounded text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40",
181
- children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
252
+ open,
253
+ id: panelId,
254
+ role: customOpen ? void 0 : "menu",
255
+ triggerRef,
256
+ panelRef,
257
+ className: MENU_PANEL,
258
+ children: [
259
+ /* @__PURE__ */ jsx("div", { className: MENU_HEADER, children: label }),
260
+ customOpen && custom ? custom.render({ close }) : /* @__PURE__ */ jsxs(Fragment, { children: [
261
+ choices.map((choice) => /* @__PURE__ */ jsxs(
262
+ "button",
263
+ {
264
+ type: "button",
265
+ role: "menuitemradio",
266
+ "aria-checked": choice.value === value,
267
+ onClick: () => {
268
+ onSelect(choice.value);
269
+ close();
270
+ },
271
+ className: menuRowClass(choice.value === value),
272
+ children: [
273
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: choice.label }),
274
+ choice.value === value && /* @__PURE__ */ jsx(CheckGlyph, { className: "ml-auto h-3.5 w-3.5 shrink-0 text-primary" })
275
+ ]
276
+ },
277
+ String(choice.value)
278
+ )),
279
+ custom && /* @__PURE__ */ jsx(
280
+ "button",
281
+ {
282
+ type: "button",
283
+ onClick: () => setCustomOpen(true),
284
+ className: `${menuRowClass(false)} text-muted-foreground`,
285
+ children: custom.label
286
+ }
287
+ )
288
+ ] })
289
+ ]
182
290
  }
183
291
  )
184
292
  ] });
185
293
  }
186
- function ComposerDisclosure({ summary, children }) {
187
- return /* @__PURE__ */ jsxs("details", { className: "group rounded-lg border border-border bg-background transition-colors", children: [
188
- /* @__PURE__ */ jsxs("summary", { className: "flex cursor-pointer list-none items-center gap-1.5 px-3 py-2.5 text-xs font-medium text-foreground/80 transition-colors hover:text-foreground [&::-webkit-details-marker]:hidden", children: [
189
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground transition-transform group-open:rotate-90" }),
190
- summary
191
- ] }),
192
- /* @__PURE__ */ jsx("div", { className: "border-t border-border px-3 py-3", children })
193
- ] });
194
- }
195
- function NativeSelect(props) {
196
- return /* @__PURE__ */ jsx("select", { ...props, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm" });
197
- }
198
-
199
- // src/studio-react/image-composer.tsx
200
- import { Input } from "@tangle-network/sandbox-ui/primitives";
201
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
202
- function ImageComposer({
203
- size,
204
- quality,
205
- imageCount,
206
- onSizeChange,
207
- onQualityChange,
208
- onImageCountChange
294
+ function CustomSizeForm({
295
+ initial,
296
+ onApply,
297
+ onCancel
209
298
  }) {
210
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
211
- /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-3", children: [
212
- /* @__PURE__ */ jsx2(Field, { label: "Size", children: /* @__PURE__ */ jsx2(
213
- Input,
299
+ const parsed = /^(\d+)x(\d+)$/.exec(initial ?? "");
300
+ const [width, setWidth] = useState2(parsed?.[1] ?? "");
301
+ const [height, setHeight] = useState2(parsed?.[2] ?? "");
302
+ const [error, setError] = useState2(null);
303
+ const fieldClass = "h-8 w-[74px] rounded-md border border-input bg-background px-2 text-[13px] tabular-nums";
304
+ function apply() {
305
+ const verdict = validateCustomImageSize(Number(width), Number(height));
306
+ if (!verdict.ok) {
307
+ setError(verdict.reason);
308
+ return;
309
+ }
310
+ onApply(`${Number(width)}x${Number(height)}`);
311
+ }
312
+ return /* @__PURE__ */ jsxs("div", { className: "flex w-[228px] flex-col gap-2 p-1.5", children: [
313
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
314
+ /* @__PURE__ */ jsx(
315
+ "input",
316
+ {
317
+ type: "number",
318
+ value: width,
319
+ "aria-label": "Custom width",
320
+ onChange: (event) => {
321
+ setWidth(event.target.value);
322
+ setError(null);
323
+ },
324
+ className: fieldClass
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-muted-foreground", children: "\xD7" }),
328
+ /* @__PURE__ */ jsx(
329
+ "input",
214
330
  {
215
- value: size,
216
- onChange: (event) => onSizeChange(event.target.value),
217
- pattern: IMAGE_SIZE_PATTERN,
218
- placeholder: IMAGE_SIZE_HINT,
219
- className: "bg-background"
331
+ type: "number",
332
+ value: height,
333
+ "aria-label": "Custom height",
334
+ onChange: (event) => {
335
+ setHeight(event.target.value);
336
+ setError(null);
337
+ },
338
+ className: fieldClass
220
339
  }
221
- ) }),
222
- /* @__PURE__ */ jsx2(Field, { label: "Quality", children: /* @__PURE__ */ jsx2(NativeSelect, { value: quality, onChange: (event) => onQualityChange(event.target.value), children: IMAGE_QUALITIES.map((option) => /* @__PURE__ */ jsx2("option", { value: option, children: option.charAt(0).toUpperCase() + option.slice(1) }, option)) }) })
340
+ )
223
341
  ] }),
224
- /* @__PURE__ */ jsx2(Field, { label: "Images", children: /* @__PURE__ */ jsx2(
225
- Stepper,
226
- {
227
- value: imageCount,
228
- min: MIN_IMAGE_COUNT,
229
- max: MAX_IMAGE_COUNT,
230
- onChange: (value) => onImageCountChange(normalizeImageCount(value))
231
- }
232
- ) })
233
- ] });
234
- }
235
-
236
- // src/studio-react/video-composer.tsx
237
- import { Input as Input2 } from "@tangle-network/sandbox-ui/primitives";
238
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
239
- function VideoComposer({
240
- duration,
241
- resolution,
242
- aspectRatio,
243
- referenceImageUrl,
244
- onDurationChange,
245
- onResolutionChange,
246
- onAspectRatioChange,
247
- onReferenceImageUrlChange
248
- }) {
249
- return /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-3", children: [
250
- /* @__PURE__ */ jsx3(Field, { label: "Duration (s)", children: /* @__PURE__ */ jsx3(NativeSelect, { value: duration, onChange: (event) => onDurationChange(event.target.value), children: VIDEO_DURATIONS.map((option) => /* @__PURE__ */ jsx3("option", { value: option, children: option }, option)) }) }),
251
- /* @__PURE__ */ jsx3(Field, { label: "Resolution", children: /* @__PURE__ */ jsx3(NativeSelect, { value: resolution, onChange: (event) => onResolutionChange(event.target.value), children: VIDEO_RESOLUTIONS.map((option) => /* @__PURE__ */ jsx3("option", { value: option, children: option }, option)) }) }),
252
- /* @__PURE__ */ jsx3(Field, { label: "Aspect ratio", children: /* @__PURE__ */ jsx3(NativeSelect, { value: aspectRatio, onChange: (event) => onAspectRatioChange(event.target.value), children: VIDEO_ASPECT_RATIOS.map((option) => /* @__PURE__ */ jsx3("option", { value: option, children: option }, option)) }) }),
253
- /* @__PURE__ */ jsx3(Field, { label: "Reference image URL", children: /* @__PURE__ */ jsx3(Input2, { value: referenceImageUrl, onChange: (event) => onReferenceImageUrlChange(event.target.value), className: "bg-background" }) })
342
+ error ? /* @__PURE__ */ jsx("p", { className: "text-[12px] text-destructive", children: error }) : /* @__PURE__ */ jsx("p", { className: "text-[12px] text-muted-foreground", children: "Multiples of 16, long edge up to 3840, ratio between 1:3 and 3:1." }),
343
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
344
+ /* @__PURE__ */ jsx(
345
+ "button",
346
+ {
347
+ type: "button",
348
+ onClick: onCancel,
349
+ className: "rounded-md px-2.5 py-1 text-[12.5px] font-medium text-muted-foreground transition hover:bg-accent hover:text-foreground",
350
+ children: "Cancel"
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsx(
354
+ "button",
355
+ {
356
+ type: "button",
357
+ onClick: apply,
358
+ className: "rounded-md bg-primary px-2.5 py-1 text-[12.5px] font-medium text-primary-foreground transition hover:opacity-90",
359
+ children: "Apply"
360
+ }
361
+ )
362
+ ] })
254
363
  ] });
255
364
  }
256
-
257
- // src/studio-react/speech-composer.tsx
258
- import { Input as Input3 } from "@tangle-network/sandbox-ui/primitives";
259
- import { jsx as jsx4 } from "react/jsx-runtime";
260
- function SpeechComposer({
261
- voice,
262
- onVoiceChange
263
- }) {
264
- return /* @__PURE__ */ jsx4(Field, { label: "Voice", children: /* @__PURE__ */ jsx4(Input3, { value: voice, onChange: (event) => onVoiceChange(event.target.value), className: "bg-background" }) });
365
+ function AudioTogglePill({ on, onToggle }) {
366
+ return /* @__PURE__ */ jsxs(
367
+ "button",
368
+ {
369
+ type: "button",
370
+ "aria-pressed": on,
371
+ title: "Audio",
372
+ onClick: () => onToggle(!on),
373
+ className: `${PILL} ${on ? "" : "text-muted-foreground"}`,
374
+ children: [
375
+ on ? /* @__PURE__ */ jsx(Volume2, { className: "h-4 w-4 shrink-0", strokeWidth: 1.5 }) : /* @__PURE__ */ jsx(VolumeX, { className: "h-4 w-4 shrink-0", strokeWidth: 1.5 }),
376
+ /* @__PURE__ */ jsx("span", { children: on ? "Audio on" : "Audio off" })
377
+ ]
378
+ }
379
+ );
265
380
  }
266
-
267
- // src/studio-react/avatar-composer.tsx
268
- import { Input as Input4 } from "@tangle-network/sandbox-ui/primitives";
269
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
270
- function AvatarComposer({
271
- audioUrl,
272
- imageUrl,
273
- avatarId,
274
- onAudioUrlChange,
275
- onImageUrlChange,
276
- onAvatarIdChange
381
+ function ReferencePill({
382
+ url,
383
+ onAttach,
384
+ onRemove,
385
+ pick,
386
+ bandRef
277
387
  }) {
278
- return /* @__PURE__ */ jsxs4("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
279
- /* @__PURE__ */ jsx5(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx5(
280
- Input4,
281
- {
282
- id: "studio-audio-url",
283
- value: audioUrl,
284
- onChange: (event) => onAudioUrlChange(event.target.value),
285
- placeholder: "https://cdn.example.com/source-audio.mp3",
286
- className: "bg-background"
287
- }
288
- ) }),
289
- /* @__PURE__ */ jsx5(Field, { label: "Image URL", htmlFor: "studio-avatar-image-url", children: /* @__PURE__ */ jsx5(
290
- Input4,
388
+ const [open, setOpen] = useState2(false);
389
+ const [draft, setDraft] = useState2("");
390
+ const [error, setError] = useState2(null);
391
+ const { containerRef, triggerRef, panelRef } = usePopover(open, setOpen);
392
+ const panelId = useId();
393
+ useCloseWhenScrolledOut(open, setOpen, triggerRef, bandRef);
394
+ if (url) {
395
+ return /* @__PURE__ */ jsxs("div", { className: `${PILL} border-primary bg-primary/10 pr-1.5 text-primary hover:bg-primary/10`, children: [
396
+ /* @__PURE__ */ jsx("img", { src: url, alt: "", className: "h-[18px] w-[18px] shrink-0 rounded-[5px] object-cover" }),
397
+ /* @__PURE__ */ jsx("span", { children: "Reference" }),
398
+ /* @__PURE__ */ jsx(
399
+ "button",
400
+ {
401
+ type: "button",
402
+ "aria-label": "Remove reference image",
403
+ onClick: onRemove,
404
+ className: "ml-0.5 inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-full transition hover:bg-primary/20",
405
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3", strokeWidth: 2 })
406
+ }
407
+ )
408
+ ] });
409
+ }
410
+ function attach() {
411
+ const value = draft.trim();
412
+ if (!/^https?:\/\//i.test(value)) {
413
+ setError("Enter a URL starting with http:// or https://");
414
+ return;
415
+ }
416
+ onAttach(value);
417
+ setDraft("");
418
+ setError(null);
419
+ setOpen(false);
420
+ }
421
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-flex flex-none", children: [
422
+ /* @__PURE__ */ jsxs(
423
+ "button",
291
424
  {
292
- id: "studio-avatar-image-url",
293
- value: imageUrl,
294
- onChange: (event) => onImageUrlChange(event.target.value),
295
- placeholder: "https://cdn.example.com/portrait.png",
296
- className: "bg-background"
425
+ type: "button",
426
+ ref: triggerRef,
427
+ ...pick ? {} : { "aria-haspopup": true, "aria-expanded": open, "aria-controls": open ? panelId : void 0 },
428
+ title: "Reference image",
429
+ onClick: () => {
430
+ if (!pick) {
431
+ setOpen(!open);
432
+ return;
433
+ }
434
+ void pick().then((picked) => {
435
+ if (picked) onAttach(picked);
436
+ });
437
+ },
438
+ className: PILL,
439
+ children: [
440
+ /* @__PURE__ */ jsx(ImagePlus, { className: "h-4 w-4 shrink-0 text-muted-foreground", strokeWidth: 1.5 }),
441
+ /* @__PURE__ */ jsx("span", { children: "Reference image" })
442
+ ]
297
443
  }
298
- ) }),
299
- /* @__PURE__ */ jsx5(Field, { label: "Avatar ID", htmlFor: "studio-avatar-id", children: /* @__PURE__ */ jsx5(
300
- Input4,
444
+ ),
445
+ !pick && /* @__PURE__ */ jsxs(
446
+ PopoverSurface,
301
447
  {
302
- id: "studio-avatar-id",
303
- value: avatarId,
304
- onChange: (event) => onAvatarIdChange(event.target.value),
305
- placeholder: "Optional provider avatar id",
306
- className: "bg-background"
448
+ open,
449
+ id: panelId,
450
+ triggerRef,
451
+ panelRef,
452
+ className: MENU_PANEL,
453
+ children: [
454
+ /* @__PURE__ */ jsx("div", { className: MENU_HEADER, children: "Reference image" }),
455
+ /* @__PURE__ */ jsxs("div", { className: "flex w-[260px] flex-col gap-2 p-1.5", children: [
456
+ /* @__PURE__ */ jsx(
457
+ "input",
458
+ {
459
+ type: "url",
460
+ value: draft,
461
+ "aria-label": "Reference image URL",
462
+ placeholder: "https://\u2026",
463
+ onChange: (event) => {
464
+ setDraft(event.target.value);
465
+ setError(null);
466
+ },
467
+ className: "h-8 w-full rounded-md border border-input bg-background px-2 text-[13px]"
468
+ }
469
+ ),
470
+ error && /* @__PURE__ */ jsx("p", { className: "text-[12px] text-destructive", children: error }),
471
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-1.5", children: [
472
+ /* @__PURE__ */ jsx(
473
+ "button",
474
+ {
475
+ type: "button",
476
+ onClick: () => setOpen(false),
477
+ className: "rounded-md px-2.5 py-1 text-[12.5px] font-medium text-muted-foreground transition hover:bg-accent hover:text-foreground",
478
+ children: "Cancel"
479
+ }
480
+ ),
481
+ /* @__PURE__ */ jsx(
482
+ "button",
483
+ {
484
+ type: "button",
485
+ onClick: attach,
486
+ className: "rounded-md bg-primary px-2.5 py-1 text-[12.5px] font-medium text-primary-foreground transition hover:opacity-90",
487
+ children: "Attach"
488
+ }
489
+ )
490
+ ] })
491
+ ] })
492
+ ]
307
493
  }
308
- ) })
494
+ )
309
495
  ] });
310
496
  }
311
-
312
- // src/studio-react/transcription-composer.tsx
313
- import { Input as Input5 } from "@tangle-network/sandbox-ui/primitives";
314
- import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
315
- function TranscriptionComposer({
316
- audioUrl,
317
- language,
318
- onAudioUrlChange,
319
- onLanguageChange
497
+ function ModelPill({
498
+ models,
499
+ value,
500
+ displayName,
501
+ provider,
502
+ onSelect,
503
+ bandRef
320
504
  }) {
321
- return /* @__PURE__ */ jsxs5("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
322
- /* @__PURE__ */ jsx6(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx6(
323
- Input5,
505
+ const [open, setOpen] = useState2(false);
506
+ const { containerRef, triggerRef, panelRef, triggerProps } = usePopover(open, setOpen);
507
+ const panelId = useId();
508
+ useCloseWhenScrolledOut(open, setOpen, triggerRef, bandRef);
509
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative inline-flex flex-none", children: [
510
+ /* @__PURE__ */ jsxs(
511
+ "button",
324
512
  {
325
- id: "studio-audio-url",
326
- value: audioUrl,
327
- onChange: (event) => onAudioUrlChange(event.target.value),
328
- placeholder: "https://cdn.example.com/source-audio.mp3",
329
- className: "bg-background"
513
+ type: "button",
514
+ ...triggerProps,
515
+ "aria-controls": open ? panelId : void 0,
516
+ title: "Model",
517
+ "aria-label": `Model: ${displayName}`,
518
+ onClick: () => setOpen(!open),
519
+ className: PILL,
520
+ children: [
521
+ provider && /* @__PURE__ */ jsx(ProviderLogo, { provider, size: 14 }),
522
+ /* @__PURE__ */ jsx("span", { className: "max-w-[168px] truncate", children: displayName }),
523
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-muted-foreground" })
524
+ ]
330
525
  }
331
- ) }),
332
- /* @__PURE__ */ jsx6(Field, { label: "Language", htmlFor: "studio-transcription-language", children: /* @__PURE__ */ jsx6(
333
- Input5,
526
+ ),
527
+ /* @__PURE__ */ jsxs(
528
+ PopoverSurface,
334
529
  {
335
- id: "studio-transcription-language",
336
- value: language,
337
- onChange: (event) => onLanguageChange(event.target.value),
338
- placeholder: "en",
339
- className: "bg-background"
530
+ open,
531
+ id: panelId,
532
+ role: "menu",
533
+ triggerRef,
534
+ panelRef,
535
+ className: `${MENU_PANEL} max-w-[320px]`,
536
+ children: [
537
+ /* @__PURE__ */ jsx("div", { className: MENU_HEADER, children: "Model" }),
538
+ models.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-2.5 py-2 text-[13px] text-muted-foreground", children: "No models are available for this media type." }),
539
+ models.map((model) => /* @__PURE__ */ jsxs(
540
+ "button",
541
+ {
542
+ type: "button",
543
+ role: "menuitemradio",
544
+ "aria-checked": model.id === value,
545
+ disabled: model.status === "unavailable",
546
+ onClick: () => {
547
+ onSelect(model.id);
548
+ setOpen(false);
549
+ },
550
+ className: `${menuRowClass(model.id === value)} disabled:opacity-50`,
551
+ children: [
552
+ model.provider && /* @__PURE__ */ jsx(ProviderLogo, { provider: model.provider, size: 14 }),
553
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: model.name || model.id }),
554
+ model.status !== "available" && /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 text-[11px] capitalize text-muted-foreground", children: model.status }),
555
+ model.id === value && /* @__PURE__ */ jsx(CheckGlyph, { className: `${model.status === "available" ? "ml-auto " : ""}h-3.5 w-3.5 shrink-0 text-primary` })
556
+ ]
557
+ },
558
+ model.id
559
+ ))
560
+ ]
340
561
  }
341
- ) })
342
- ] });
343
- }
344
- function TranscriptionOptions({
345
- responseFormat,
346
- temperature,
347
- onResponseFormatChange,
348
- onTemperatureChange
349
- }) {
350
- return /* @__PURE__ */ jsxs5(Fragment, { children: [
351
- /* @__PURE__ */ jsx6(Field, { label: "Response format", children: /* @__PURE__ */ jsxs5(NativeSelect, { value: responseFormat, onChange: (event) => onResponseFormatChange(event.target.value), children: [
352
- /* @__PURE__ */ jsx6("option", { value: "json", children: "JSON" }),
353
- /* @__PURE__ */ jsx6("option", { value: "text", children: "Text" }),
354
- /* @__PURE__ */ jsx6("option", { value: "srt", children: "SRT" }),
355
- /* @__PURE__ */ jsx6("option", { value: "verbose_json", children: "Verbose JSON" }),
356
- /* @__PURE__ */ jsx6("option", { value: "vtt", children: "VTT" })
357
- ] }) }),
358
- /* @__PURE__ */ jsx6(Field, { label: "Temperature", children: /* @__PURE__ */ jsx6(Input5, { type: "number", min: "0", max: "1", step: "0.1", value: temperature, onChange: (event) => onTemperatureChange(event.target.value), className: "bg-background" }) })
562
+ )
359
563
  ] });
360
564
  }
361
-
362
- // src/studio-react/publish-package-composer.tsx
363
- import { Link } from "react-router";
364
- import { Badge, Button, Input as Input6, Textarea } from "@tangle-network/sandbox-ui/primitives";
365
- import { ProviderIcon } from "@tangle-network/sandbox-ui/integrations";
366
- import { AlertTriangle, Check } from "lucide-react";
367
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
368
- function PublishPackageComposer({
369
- caption,
370
- postDescription,
371
- mentions,
372
- cadence,
373
- selectedDestinations,
374
- connections,
375
- connectionError,
376
- connectionsLoading,
377
- integrationsHref,
378
- canManageIntegrations,
379
- onCaptionChange,
380
- onDescriptionChange,
381
- onMentionsChange,
382
- onCadenceChange,
383
- onDestinationToggle
384
- }) {
385
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
386
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-3", children: [
387
- connectionError ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 rounded-md border border-warning/25 bg-warning/10 px-2.5 py-1.5 text-xs text-warning", children: [
388
- /* @__PURE__ */ jsx7(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
389
- "Couldn't load connected apps. Check Integrations."
390
- ] }) : /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: connectionsLoading ? "Checking connected apps\u2026" : "Saved with the generated asset." }),
391
- integrationsHref && canManageIntegrations && /* @__PURE__ */ jsx7(Link, { to: integrationsHref, prefetch: "intent", className: "shrink-0", children: /* @__PURE__ */ jsx7(Button, { variant: "outline", size: "sm", children: "Connect" }) })
392
- ] }),
393
- /* @__PURE__ */ jsx7("div", { className: "grid gap-2", children: DESTINATIONS.map((destination) => {
394
- const connected = isDestinationConnected(destination, connections);
395
- const active = connected && selectedDestinations.includes(destination.id);
396
- return /* @__PURE__ */ jsxs6(
397
- "button",
398
- {
399
- type: "button",
400
- disabled: !connected,
401
- "aria-pressed": active,
402
- onClick: () => onDestinationToggle(destination.id),
403
- className: `rounded-md border p-2 text-left transition-colors disabled:cursor-not-allowed disabled:opacity-60 ${active ? "border-primary bg-primary/10 ring-1 ring-primary/20" : "border-border bg-background hover:bg-accent"}`,
404
- children: [
405
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
406
- /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1.5 text-xs font-medium text-foreground", children: [
407
- active && /* @__PURE__ */ jsx7(Check, { className: "h-3.5 w-3.5 text-primary" }),
408
- /* @__PURE__ */ jsx7(ProviderIcon, { id: destination.providerIds[0] ?? destination.id, displayName: destination.label, size: 16, className: "rounded" }),
409
- destination.label
410
- ] }),
411
- /* @__PURE__ */ jsx7(Badge, { variant: active || connected ? "default" : "outline", children: active ? "Selected" : connected ? "Ready" : "Not connected" })
412
- ] }),
413
- /* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-muted-foreground", children: destination.fields })
414
- ]
415
- },
416
- destination.id
417
- );
418
- }) }),
419
- /* @__PURE__ */ jsx7(Field, { label: "Caption", className: "space-y-2", children: /* @__PURE__ */ jsx7(Textarea, { value: caption, onChange: (event) => onCaptionChange(event.target.value), rows: 3, placeholder: "Write or generate the caption for selected destinations...", className: "bg-background" }) }),
420
- /* @__PURE__ */ jsx7(Field, { label: "Description / CTA", className: "space-y-2", children: /* @__PURE__ */ jsx7(Textarea, { value: postDescription, onChange: (event) => onDescriptionChange(event.target.value), rows: 2, placeholder: "Release note, product context, link, or approval instruction...", className: "bg-background" }) }),
421
- /* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-2 gap-3", children: [
422
- /* @__PURE__ */ jsx7(Field, { label: "Mentions", className: "space-y-2", children: /* @__PURE__ */ jsx7(Input6, { value: mentions, onChange: (event) => onMentionsChange(event.target.value), placeholder: "@creator, @partner", className: "bg-background" }) }),
423
- /* @__PURE__ */ jsx7(Field, { label: "Cadence", className: "space-y-2", children: /* @__PURE__ */ jsx7(NativeSelect, { value: cadence, onChange: (event) => onCadenceChange(event.target.value), children: CADENCES.map((option) => /* @__PURE__ */ jsx7("option", { value: option, children: option }, option)) }) })
424
- ] })
425
- ] });
565
+ function optionValueLabel(param, value) {
566
+ if (typeof value === "boolean") return value ? "On" : "Off";
567
+ const text = String(value);
568
+ if (param === "n") return `\xD7${text}`;
569
+ if (param === "speed") return `${text}\xD7`;
570
+ if (text === "auto") return "Auto";
571
+ if (param === "duration") return /^\d+(\.\d+)?$/.test(text) ? `${text}s` : text;
572
+ if (param === "size") {
573
+ const size = /^(\d+)x(\d+)$/.exec(text);
574
+ return size ? `${size[1]}\xD7${size[2]}` : text;
575
+ }
576
+ if (param === "resolution" || param === "aspect_ratio") return text;
577
+ return text.charAt(0).toUpperCase() + text.slice(1);
426
578
  }
427
579
 
428
- // src/studio-react/composer-hero.tsx
429
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
430
- var HUB_BASE_URL = "/api/integrations/hub";
431
- var SUGGESTIONS = [
432
- {
433
- label: "Storyboard frame",
434
- type: "image",
435
- prompt: "A vertical storyboard frame for a product launch teaser \u2014 dark cinematic lighting, sleek studio aesthetic, 9:16 composition."
436
- },
437
- {
438
- label: "Launch teaser clip",
439
- type: "video",
440
- prompt: "A 6-second vertical launch teaser: animated product reveal, dynamic camera move, hero music swell."
441
- },
442
- {
443
- label: "Scratch voiceover",
444
- type: "speech",
445
- prompt: "A confident 12-second scratch voiceover for a product launch teaser: warm tone, conversational pace."
446
- },
447
- {
448
- label: "Transcribe dailies",
449
- type: "transcription",
450
- prompt: "Transcribe the latest dailies recording with timestamps, speaker labels, and key story beats highlighted."
451
- }
580
+ // src/studio-react/studio-composer.tsx
581
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
582
+ var SEGMENTS = [
583
+ { type: "image", label: "Image", icon: ImageIcon },
584
+ { type: "video", label: "Video", icon: Video },
585
+ { type: "speech", label: "Audio", icon: AudioLines }
452
586
  ];
453
- function ComposerHero({
587
+ var PILL_ORDER = {
588
+ image: ["size", "quality", "n"],
589
+ video: ["duration", "resolution", "aspect_ratio", "mode"],
590
+ speech: ["voice", "speed"]
591
+ };
592
+ var PARAM_LABELS = {
593
+ size: "Size",
594
+ quality: "Quality",
595
+ n: "Count",
596
+ duration: "Duration",
597
+ resolution: "Resolution",
598
+ aspect_ratio: "Aspect",
599
+ mode: "Mode",
600
+ voice: "Voice",
601
+ speed: "Speed"
602
+ };
603
+ var SPEED_PRESETS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4];
604
+ function choicesFor(param, meta) {
605
+ const { min, max } = meta;
606
+ if (param === "speed" && !meta.values && min != null && max != null) {
607
+ return SPEED_PRESETS.filter((speed) => speed >= min && speed <= max);
608
+ }
609
+ return optionChoices(meta);
610
+ }
611
+ function visibleParams(type, options) {
612
+ if (!options) return [];
613
+ return PILL_ORDER[type].filter((param) => {
614
+ const meta = options[param];
615
+ return Boolean(meta) && meta?.supported !== false;
616
+ });
617
+ }
618
+ function defaultModelId(type, catalog, curated) {
619
+ const preferred = preferredModelId(type, catalog);
620
+ if (preferred && curated.some((model) => model.id === preferred)) return preferred;
621
+ return curated.find((model) => model.status !== "unavailable")?.id ?? curated[0]?.id ?? "";
622
+ }
623
+ function StudioComposer({
454
624
  workspaceId,
455
- integrationsHref,
456
- canManageIntegrations,
457
- align = "start",
458
- surfaceClassName = "bg-card",
459
- onGenerated
625
+ onGenerated,
626
+ variant = "home",
627
+ pickReferenceImage,
628
+ className
460
629
  }) {
461
- const [type, setType] = useState2("image");
462
- const [prompt, setPrompt] = useState2("");
463
- const [negativePrompt, setNegativePrompt] = useState2("");
464
- const [outputPath, setOutputPath] = useState2(outputPathFor("image"));
465
- const [caption, setCaption] = useState2("");
466
- const [postDescription, setPostDescription] = useState2("");
467
- const [mentions, setMentions] = useState2("");
468
- const [cadence, setCadence] = useState2(CADENCES[0] ?? "Manual approval");
469
- const [selectedDestinations, setSelectedDestinations] = useState2([]);
470
- const [size, setSize] = useState2("1536x1024");
471
- const [quality, setQuality] = useState2("high");
472
- const [imageCount, setImageCount] = useState2(1);
473
- const [duration, setDuration] = useState2("6");
474
- const [resolution, setResolution] = useState2("720p");
475
- const [aspectRatio, setAspectRatio] = useState2("16:9");
476
- const [referenceImageUrl, setReferenceImageUrl] = useState2("");
477
- const [voice, setVoice] = useState2("alloy");
478
- const [avatarAudioUrl, setAvatarAudioUrl] = useState2("");
479
- const [avatarImageUrl, setAvatarImageUrl] = useState2("");
480
- const [avatarId, setAvatarId] = useState2("");
481
- const [transcriptionAudioUrl, setTranscriptionAudioUrl] = useState2("");
482
- const [transcriptionLanguage, setTranscriptionLanguage] = useState2("");
483
- const [transcriptionResponseFormat, setTranscriptionResponseFormat] = useState2("json");
484
- const [transcriptionTemperature, setTranscriptionTemperature] = useState2("0");
485
- const [mediaModels, setMediaModels] = useState2(null);
486
- const [mediaModelsLoading, setMediaModelsLoading] = useState2(false);
487
- const [mediaModelsError, setMediaModelsError] = useState2(null);
488
- const [selectedModels, setSelectedModels] = useState2({});
630
+ const [type, setType] = useState3("image");
631
+ const [prompt, setPrompt] = useState3("");
632
+ const [catalog, setCatalog] = useState3(null);
633
+ const [catalogLoading, setCatalogLoading] = useState3(false);
634
+ const [catalogError, setCatalogError] = useState3(null);
635
+ const [selectedModels, setSelectedModels] = useState3({});
636
+ const [optionValues, setOptionValues] = useState3({
637
+ image: {},
638
+ video: {},
639
+ speech: {}
640
+ });
641
+ const [referenceImageUrl, setReferenceImageUrl] = useState3(null);
642
+ const [isSubmitting, setIsSubmitting] = useState3(false);
643
+ const [error, setError] = useState3(null);
489
644
  const submitLockRef = useRef2(false);
490
- const [isSubmitting, setIsSubmitting] = useState2(false);
491
- const [error, setError] = useState2(null);
492
- const modelsForType = mediaModels?.models[type] ?? [];
493
- const selectedModel = selectedModels[type] ?? preferredModelId(type, mediaModels) ?? "";
494
- const selectedModelOption = modelsForType.find((model) => model.id === selectedModel);
495
- const modelReady = Boolean(selectedModelOption) && selectedModelOption?.status !== "unavailable" && !mediaModelsLoading && !mediaModelsError;
496
- const hasRequiredInput = type === "transcription" ? Boolean(transcriptionAudioUrl.trim()) : type === "avatar" ? Boolean(avatarAudioUrl.trim()) : Boolean(prompt.trim());
497
- const canSubmit = Boolean(workspaceId) && modelReady && hasRequiredInput && !isSubmitting;
498
- const integrations = useIntegrations({ apiBaseUrl: HUB_BASE_URL });
499
- const selectedConnectedDestinations = useMemo2(() => selectedDestinations.filter((destinationId) => {
500
- const destination = DESTINATIONS.find((item) => item.id === destinationId);
501
- return Boolean(destination && isDestinationConnected(destination, integrations.connections));
502
- }), [integrations.connections, selectedDestinations]);
503
- useEffect2(() => {
645
+ const bandRef = useRef2(null);
646
+ useEffect3(() => {
504
647
  if (!workspaceId) return;
505
648
  let cancelled = false;
506
- setMediaModelsLoading(true);
507
- setMediaModelsError(null);
649
+ setCatalogLoading(true);
650
+ setCatalogError(null);
508
651
  fetch(`/api/media-models?workspaceId=${encodeURIComponent(workspaceId)}`).then(async (res) => {
509
652
  const data = await res.json();
510
653
  if (!res.ok) throw new Error(data.error ?? "Could not load media models");
511
654
  return data;
512
655
  }).then((data) => {
513
656
  if (cancelled) return;
514
- setMediaModels(data);
515
- setSelectedModels((current) => selectedModelsWithDefaults(current, data));
657
+ setCatalog(data);
516
658
  }).catch(() => {
517
659
  if (cancelled) return;
518
- setMediaModels(null);
519
- setMediaModelsError("Could not load media models");
660
+ setCatalog(null);
661
+ setCatalogError("Could not load media models");
520
662
  }).finally(() => {
521
- if (!cancelled) setMediaModelsLoading(false);
663
+ if (!cancelled) setCatalogLoading(false);
522
664
  });
523
665
  return () => {
524
666
  cancelled = true;
525
667
  };
526
668
  }, [workspaceId]);
527
- function changeType(next) {
528
- setType(next);
529
- setOutputPath(outputPathFor(next));
669
+ const laneModels = useMemo2(() => catalog?.models[type] ?? [], [catalog, type]);
670
+ const curatedModels = useMemo2(() => curateComposerModels(type, laneModels), [laneModels, type]);
671
+ const retained = selectedModels[type];
672
+ const retainedUsable = retained !== void 0 && (!catalog || Boolean(textToVideoSibling(retained)) || curatedModels.some((model) => model.id === retained && model.status !== "unavailable"));
673
+ const modelId = retainedUsable ? retained : defaultModelId(type, catalog, curatedModels);
674
+ const modelOption = laneModels.find((model) => model.id === modelId);
675
+ const options = useMemo2(
676
+ () => modelId ? resolveComposerOptions({
677
+ type,
678
+ modelId,
679
+ provider: modelOption?.provider,
680
+ catalogOptions: modelOption?.options
681
+ }) : void 0,
682
+ [modelId, modelOption, type]
683
+ );
684
+ useEffect3(() => {
685
+ setOptionValues((current) => {
686
+ const next = reconcileOptionValues(options, current[type], {
687
+ allowCustomSize: supportsCustomImageSize(modelId)
688
+ });
689
+ const unchanged = Object.keys(next).length === Object.keys(current[type]).length && Object.entries(next).every(([key, value]) => current[type][key] === value);
690
+ return unchanged ? current : { ...current, [type]: next };
691
+ });
692
+ }, [modelId, options, type]);
693
+ const values = optionValues[type];
694
+ const params = visibleParams(type, options);
695
+ const audioMeta = type === "video" ? options?.audio : void 0;
696
+ const audioSupported = Boolean(audioMeta) && audioMeta?.supported !== false;
697
+ const unlistedSibling = !modelOption && Boolean(textToVideoSibling(modelId));
698
+ const referenceSupported = type === "video" && Boolean(imageToVideoSibling(modelId) ?? textToVideoSibling(modelId));
699
+ const modelReady = (Boolean(modelOption) || unlistedSibling) && modelOption?.status !== "unavailable" && !catalogLoading && !catalogError;
700
+ const canSubmit = Boolean(workspaceId) && modelReady && Boolean(prompt.trim()) && !isSubmitting;
701
+ function selectModel(id) {
702
+ setSelectedModels((current) => ({ ...current, [type]: id }));
703
+ }
704
+ function chooseModel(id) {
705
+ if (!referenceImageUrl || type !== "video") return selectModel(id);
706
+ const sibling = imageToVideoSibling(id);
707
+ if (sibling) return selectModel(sibling);
708
+ if (!textToVideoSibling(id)) setReferenceImageUrl(null);
709
+ return selectModel(id);
710
+ }
711
+ function setOption(param, value) {
712
+ setOptionValues((current) => ({ ...current, [type]: { ...current[type], [param]: value } }));
713
+ }
714
+ function attachReference(url) {
715
+ setReferenceImageUrl(url);
716
+ const sibling = imageToVideoSibling(modelId);
717
+ if (sibling) selectModel(sibling);
718
+ }
719
+ function removeReference() {
720
+ setReferenceImageUrl(null);
721
+ const sibling = textToVideoSibling(modelId);
722
+ if (sibling) selectModel(sibling);
530
723
  }
531
724
  async function generate() {
532
- if (!workspaceId || submitLockRef.current) return;
725
+ if (!workspaceId || submitLockRef.current || isSubmitting) return;
533
726
  const promptText = prompt.trim();
534
- const avatarAudioUrlText = avatarAudioUrl.trim();
535
- const transcriptionAudioUrlText = transcriptionAudioUrl.trim();
536
- if (type !== "transcription" && type !== "avatar" && !promptText) {
537
- setError("prompt is required");
538
- return;
539
- }
540
- if (type === "avatar" && !avatarAudioUrlText) {
541
- setError("audioUrl is required");
542
- return;
543
- }
544
- if (type === "transcription" && !transcriptionAudioUrlText) {
545
- setError("audioUrl is required");
546
- return;
547
- }
548
- if (!selectedModelOption || selectedModelOption.status === "unavailable") {
549
- setError(`Select an available ${typeConfigFor(type).label} model`);
550
- return;
551
- }
552
- if (cadence === "Publish now" && selectedConnectedDestinations.length === 0) {
553
- setError("Select a connected destination to publish now");
727
+ if (!promptText) return;
728
+ if (!modelReady) {
729
+ if (!catalogLoading) setError("Select an available model");
554
730
  return;
555
731
  }
556
732
  submitLockRef.current = true;
557
733
  setIsSubmitting(true);
558
734
  setError(null);
559
735
  const clientRequestId = crypto.randomUUID();
560
- const requestedImageCount = type === "image" ? normalizeImageCount(imageCount) : 1;
561
- if (type === "image" && requestedImageCount !== imageCount) setImageCount(requestedImageCount);
562
- const localGenerations = Array.from({ length: requestedImageCount }, (_, outputIndex) => optimisticGeneration({
736
+ const imageCount = type === "image" ? normalizeImageCount(values.n ?? 1) : 1;
737
+ const localGenerations = Array.from({ length: imageCount }, (_, outputIndex) => optimisticGeneration({
563
738
  type,
564
- // avatar hides the prompt field (promptText is stale); transcription's is
565
- // an optional vocab hint. Never surface the source audio URL as the prompt.
566
- prompt: type === "avatar" ? "" : promptText,
567
- model: selectedModel,
739
+ prompt: promptText,
740
+ model: modelId,
568
741
  clientRequestId,
569
742
  outputIndex: type === "image" ? outputIndex : void 0,
570
- outputCount: type === "image" ? requestedImageCount : void 0
743
+ outputCount: type === "image" ? imageCount : void 0
571
744
  }));
572
745
  localGenerations.slice().reverse().forEach(onGenerated);
573
746
  setPrompt("");
@@ -577,26 +750,28 @@ function ComposerHero({
577
750
  workspaceId,
578
751
  clientRequestId,
579
752
  type,
580
- model: selectedModel,
581
- prompt,
582
- negativePrompt,
583
- outputPath,
584
- publishPackage: buildPublishPackage({
585
- caption,
586
- postDescription,
587
- mentions,
588
- cadence,
589
- destinations: selectedConnectedDestinations
590
- }),
591
- image: { size, quality, count: requestedImageCount },
592
- video: { duration, resolution, aspectRatio, referenceImageUrl },
593
- speech: { voice },
594
- avatar: { audioUrl: avatarAudioUrl, imageUrl: avatarImageUrl, avatarId },
595
- transcription: {
596
- audioUrl: transcriptionAudioUrl,
597
- language: transcriptionLanguage,
598
- responseFormat: transcriptionResponseFormat,
599
- temperature: transcriptionTemperature
753
+ model: modelId,
754
+ prompt: promptText,
755
+ // `duration`, `audio` and `speed` go on the wire in the TYPE the model
756
+ // published them in — a `duration` of `'5'` that arrives as `5` is a
757
+ // different request, and the three providers behind this lane disagree
758
+ // about which is right. An absent value is omitted, never defaulted.
759
+ image: {
760
+ size: asText(values.size),
761
+ quality: asText(values.quality),
762
+ count: imageCount
763
+ },
764
+ video: {
765
+ duration: asWireScalar(values.duration),
766
+ resolution: asText(values.resolution),
767
+ aspectRatio: asText(values.aspect_ratio),
768
+ referenceImageUrl: referenceSupported && referenceImageUrl ? referenceImageUrl : void 0,
769
+ audio: audioSupported && typeof values.audio === "boolean" ? values.audio : void 0,
770
+ mode: asText(values.mode)
771
+ },
772
+ speech: {
773
+ voice: asText(values.voice),
774
+ speed: typeof values.speed === "number" ? values.speed : void 0
600
775
  }
601
776
  });
602
777
  const res = await fetch("/api/generate", {
@@ -619,257 +794,164 @@ function ComposerHero({
619
794
  setIsSubmitting(false);
620
795
  }
621
796
  }
622
- const mediaTypes = Object.entries(TYPE_CONFIG);
623
- return /* @__PURE__ */ jsxs7("section", { className: `rounded-2xl border border-border p-5 shadow-sm ${surfaceClassName}`, children: [
624
- /* @__PURE__ */ jsx8("div", { className: `mb-5 ${align === "center" ? "text-center" : "text-left"}`, children: /* @__PURE__ */ jsx8(
625
- "h2",
626
- {
627
- className: `font-semibold tracking-tight text-foreground transition-all duration-300 ${align === "center" ? "text-xl" : "text-base"}`,
628
- children: "Media Generation"
629
- }
630
- ) }),
631
- /* @__PURE__ */ jsx8("div", { role: "tablist", "aria-label": "Generation type", className: "grid grid-cols-5 gap-1.5", children: mediaTypes.map(([key, cfg]) => {
632
- const Icon = cfg.icon;
633
- const active = type === key;
634
- return /* @__PURE__ */ jsxs7(
635
- "button",
636
- {
637
- type: "button",
638
- role: "tab",
639
- "aria-selected": active,
640
- onClick: () => changeType(key),
641
- className: `flex flex-col items-center gap-1.5 rounded-lg border px-1 pb-2 pt-2.5 text-xs transition-all ${active ? "border-primary/30 bg-primary/10 font-semibold text-primary" : "border-border bg-background font-medium text-muted-foreground hover:border-foreground/20 hover:text-foreground"}`,
642
- children: [
643
- /* @__PURE__ */ jsx8(Icon, { className: "h-[17px] w-[17px] shrink-0" }),
644
- /* @__PURE__ */ jsx8("span", { className: "truncate", children: cfg.label })
645
- ]
646
- },
647
- key
648
- );
649
- }) }),
650
- type !== "avatar" && /* @__PURE__ */ jsxs7("div", { className: "mt-5", children: [
651
- /* @__PURE__ */ jsx8(
652
- Label2,
653
- {
654
- htmlFor: "studio-prompt",
655
- className: "mb-1.5 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground",
656
- children: "Prompt"
657
- }
658
- ),
659
- /* @__PURE__ */ jsxs7("div", { className: "rounded-xl border border-border bg-background transition-colors focus-within:border-primary/30 focus-within:ring-[3px] focus-within:ring-primary/10", children: [
660
- /* @__PURE__ */ jsx8(
797
+ const statusMessage = unlistedSibling ? null : modelMessage(modelOption, catalogLoading, curatedModels.length);
798
+ const notice = catalogError ?? error ?? statusMessage;
799
+ const noticeIsProblem = Boolean(catalogError || error || modelOption?.status === "unavailable");
800
+ return /* @__PURE__ */ jsxs2(
801
+ "section",
802
+ {
803
+ "data-variant": variant,
804
+ className: `rounded-[14px] border border-border bg-card px-2.5 pb-[9px] pt-2 shadow-sm transition focus-within:border-primary focus-within:ring-[3px] focus-within:ring-ring/30 ${className ?? ""}`,
805
+ children: [
806
+ /* @__PURE__ */ jsx2(
661
807
  "textarea",
662
808
  {
663
- id: "studio-prompt",
664
809
  value: prompt,
665
810
  onChange: (event) => setPrompt(event.target.value),
666
811
  onKeyDown: (event) => {
667
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
668
- event.preventDefault();
669
- void generate();
670
- }
812
+ if (event.key !== "Enter" || event.shiftKey) return;
813
+ event.preventDefault();
814
+ void generate();
671
815
  },
672
- rows: 4,
673
- placeholder: type === "transcription" ? "Optional vocabulary, speaker names, timestamp style, or context..." : "A vertical hero frame for a product launch teaser, dark cinematic lighting...",
674
- className: "block min-h-[96px] w-full resize-none border-0 bg-transparent px-3.5 pb-1.5 pt-3 text-sm leading-relaxed outline-none placeholder:text-muted-foreground"
816
+ rows: 3,
817
+ "aria-label": "Prompt",
818
+ placeholder: "Describe what you want to generate\u2026",
819
+ className: "block min-h-[66px] w-full resize-none border-0 bg-transparent px-1.5 pb-1 pt-1.5 text-[14.5px] leading-relaxed outline-none placeholder:text-muted-foreground"
675
820
  }
676
821
  ),
677
- /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap items-center gap-1.5 px-2.5 pb-2.5", children: SUGGESTIONS.map((suggestion) => /* @__PURE__ */ jsx8(
678
- "button",
679
- {
680
- type: "button",
681
- onClick: () => {
682
- setPrompt(suggestion.prompt);
683
- changeType(suggestion.type);
684
- },
685
- className: "rounded-full border border-border bg-background px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/30 hover:text-primary",
686
- children: suggestion.label
687
- },
688
- suggestion.label
689
- )) })
690
- ] }),
691
- type === "transcription" && /* @__PURE__ */ jsx8("p", { className: "mt-2 text-xs text-muted-foreground", children: "Optional \u2014 biases spelling, vocabulary, and speaker names (not an instruction)." })
692
- ] }),
693
- type === "avatar" && /* @__PURE__ */ jsx8(
694
- AvatarComposer,
695
- {
696
- audioUrl: avatarAudioUrl,
697
- imageUrl: avatarImageUrl,
698
- avatarId,
699
- onAudioUrlChange: setAvatarAudioUrl,
700
- onImageUrlChange: setAvatarImageUrl,
701
- onAvatarIdChange: setAvatarId
702
- }
703
- ),
704
- type === "transcription" && /* @__PURE__ */ jsx8(
705
- TranscriptionComposer,
706
- {
707
- audioUrl: transcriptionAudioUrl,
708
- language: transcriptionLanguage,
709
- onAudioUrlChange: setTranscriptionAudioUrl,
710
- onLanguageChange: setTranscriptionLanguage
711
- }
712
- ),
713
- /* @__PURE__ */ jsxs7("div", { className: "mt-5 space-y-3", children: [
714
- /* @__PURE__ */ jsxs7("div", { className: "space-y-1.5", children: [
715
- /* @__PURE__ */ jsx8(Field, { label: "Model", htmlFor: "studio-media-model", children: /* @__PURE__ */ jsxs7(
716
- Select,
717
- {
718
- value: selectedModel || void 0,
719
- onValueChange: (value) => setSelectedModels((current) => ({ ...current, [type]: value })),
720
- disabled: mediaModelsLoading || Boolean(mediaModelsError) || modelsForType.length === 0,
721
- children: [
722
- /* @__PURE__ */ jsx8(SelectTrigger, { id: "studio-media-model", className: "h-9 w-full bg-background", children: selectedModelOption ? /* @__PURE__ */ jsxs7("span", { className: "flex min-w-0 items-center gap-2", children: [
723
- selectedModelOption.provider && /* @__PURE__ */ jsx8(ProviderLogo, { provider: selectedModelOption.provider, size: 14 }),
724
- /* @__PURE__ */ jsx8("span", { className: "truncate", children: selectedModelOption.name || selectedModelOption.id }),
725
- selectedModelOption.provider && /* @__PURE__ */ jsxs7("span", { className: "shrink-0 text-muted-foreground", children: [
726
- "\xB7 ",
727
- selectedModelOption.provider
728
- ] }),
729
- selectedModelOption.status !== "available" && /* @__PURE__ */ jsx8("span", { className: "shrink-0 rounded-full bg-warning/10 px-1.5 py-0.5 text-xs font-semibold uppercase tracking-[0.05em] text-warning", children: selectedModelOption.status })
730
- ] }) : /* @__PURE__ */ jsx8(SelectValue, { placeholder: mediaModelsLoading ? "Loading models\u2026" : "Select a model" }) }),
731
- /* @__PURE__ */ jsx8(SelectContent, { className: "bg-background", children: modelsForType.map((model) => /* @__PURE__ */ jsx8(SelectItem, { value: model.id, disabled: model.status === "unavailable", children: /* @__PURE__ */ jsxs7("span", { className: "flex w-full items-center justify-between gap-3", children: [
732
- /* @__PURE__ */ jsxs7("span", { className: "flex min-w-0 items-center gap-2", children: [
733
- model.provider && /* @__PURE__ */ jsx8(ProviderLogo, { provider: model.provider, size: 14 }),
734
- /* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
735
- model.name || model.id,
736
- model.provider ? ` \xB7 ${model.provider}` : ""
737
- ] })
738
- ] }),
739
- model.status !== "available" && /* @__PURE__ */ jsx8("span", { className: "shrink-0 text-xs capitalize text-muted-foreground", children: model.status })
740
- ] }) }, model.id)) })
741
- ]
742
- }
743
- ) }),
744
- (mediaModelsError || modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length)) && /* @__PURE__ */ jsx8("p", { className: `text-xs ${mediaModelsError || selectedModelOption?.status === "unavailable" ? "text-destructive" : "text-muted-foreground"}`, children: mediaModelsError ?? modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length) })
745
- ] }),
746
- type === "image" && /* @__PURE__ */ jsx8(
747
- ImageComposer,
748
- {
749
- size,
750
- quality,
751
- imageCount,
752
- onSizeChange: setSize,
753
- onQualityChange: setQuality,
754
- onImageCountChange: setImageCount
755
- }
756
- ),
757
- type === "video" && /* @__PURE__ */ jsx8(
758
- VideoComposer,
759
- {
760
- duration,
761
- resolution,
762
- aspectRatio,
763
- referenceImageUrl,
764
- onDurationChange: setDuration,
765
- onResolutionChange: setResolution,
766
- onAspectRatioChange: setAspectRatio,
767
- onReferenceImageUrlChange: setReferenceImageUrl
768
- }
769
- ),
770
- type === "speech" && /* @__PURE__ */ jsx8(SpeechComposer, { voice, onVoiceChange: setVoice })
771
- ] }),
772
- /* @__PURE__ */ jsxs7("div", { className: "mt-4 space-y-2", children: [
773
- /* @__PURE__ */ jsx8(ComposerDisclosure, { summary: "Advanced options", children: /* @__PURE__ */ jsxs7("div", { className: "grid gap-3 sm:grid-cols-2", children: [
774
- /* @__PURE__ */ jsx8(Field, { label: "Negative prompt", children: /* @__PURE__ */ jsx8(
775
- Textarea2,
776
- {
777
- value: negativePrompt,
778
- onChange: (event) => setNegativePrompt(event.target.value),
779
- rows: 2,
780
- placeholder: "Avoid artifacts, off-style composition...",
781
- className: "bg-background"
782
- }
783
- ) }),
784
- /* @__PURE__ */ jsx8(Field, { label: "Save to", children: /* @__PURE__ */ jsx8(Input7, { value: outputPath, onChange: (event) => setOutputPath(event.target.value), className: "bg-background" }) }),
785
- type === "transcription" && /* @__PURE__ */ jsx8(
786
- TranscriptionOptions,
787
- {
788
- responseFormat: transcriptionResponseFormat,
789
- temperature: transcriptionTemperature,
790
- onResponseFormatChange: setTranscriptionResponseFormat,
791
- onTemperatureChange: setTranscriptionTemperature
792
- }
793
- )
794
- ] }) }),
795
- /* @__PURE__ */ jsx8(
796
- ComposerDisclosure,
797
- {
798
- summary: /* @__PURE__ */ jsxs7(Fragment2, { children: [
799
- "Schedule a post",
800
- selectedConnectedDestinations.length > 0 && /* @__PURE__ */ jsx8(Badge2, { variant: "outline", className: "ml-1 text-xs", children: selectedConnectedDestinations.length })
822
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
823
+ /* @__PURE__ */ jsx2(MediaTypeSegments, { value: type, segments: SEGMENTS, onChange: setType }),
824
+ /* @__PURE__ */ jsxs2(ComposerBand, { bandRef, resetKey: type, children: [
825
+ /* @__PURE__ */ jsx2(
826
+ ModelPill,
827
+ {
828
+ models: curatedModels,
829
+ value: modelId,
830
+ displayName: modelOption?.name || modelId || "Select a model",
831
+ provider: modelOption?.provider,
832
+ onSelect: chooseModel,
833
+ bandRef
834
+ }
835
+ ),
836
+ params.map((param) => /* @__PURE__ */ jsx2("div", { className: "studio-pill-in flex-none", children: /* @__PURE__ */ jsx2(
837
+ OptionPill,
838
+ {
839
+ label: PARAM_LABELS[param] ?? param,
840
+ value: values[param],
841
+ choices: choicesForPill(param, options?.[param], values[param]),
842
+ onSelect: (value) => setOption(param, value),
843
+ bandRef,
844
+ custom: param === "size" && supportsCustomImageSize(modelId) ? {
845
+ label: "Custom\u2026",
846
+ render: ({ close }) => /* @__PURE__ */ jsx2(
847
+ CustomSizeForm,
848
+ {
849
+ initial: asText(values.size),
850
+ onApply: (size) => {
851
+ setOption("size", size);
852
+ close();
853
+ },
854
+ onCancel: close
855
+ }
856
+ )
857
+ } : void 0
858
+ }
859
+ ) }, `${type}-${param}`)),
860
+ audioSupported && /* @__PURE__ */ jsx2("div", { className: "studio-pill-in flex-none", children: /* @__PURE__ */ jsx2(
861
+ AudioTogglePill,
862
+ {
863
+ on: values.audio === true,
864
+ onToggle: (on) => setOption("audio", on)
865
+ }
866
+ ) }, `${type}-audio`),
867
+ referenceSupported && /* @__PURE__ */ jsx2("div", { className: "studio-pill-in flex-none", children: /* @__PURE__ */ jsx2(
868
+ ReferencePill,
869
+ {
870
+ url: referenceImageUrl,
871
+ onAttach: attachReference,
872
+ onRemove: removeReference,
873
+ pick: pickReferenceImage,
874
+ bandRef
875
+ }
876
+ ) }, `${type}-reference`)
801
877
  ] }),
802
- children: /* @__PURE__ */ jsx8(
803
- PublishPackageComposer,
878
+ /* @__PURE__ */ jsx2(
879
+ "button",
804
880
  {
805
- caption,
806
- postDescription,
807
- mentions,
808
- cadence,
809
- selectedDestinations,
810
- connections: integrations.connections,
811
- connectionError: integrations.error,
812
- connectionsLoading: integrations.isLoading,
813
- integrationsHref,
814
- canManageIntegrations,
815
- onCaptionChange: setCaption,
816
- onDescriptionChange: setPostDescription,
817
- onMentionsChange: setMentions,
818
- onCadenceChange: setCadence,
819
- onDestinationToggle: (destination) => {
820
- const destinationConfig = DESTINATIONS.find((item) => item.id === destination);
821
- if (!destinationConfig || !isDestinationConnected(destinationConfig, integrations.connections)) return;
822
- setSelectedDestinations((current) => current.includes(destination) ? current.filter((item) => item !== destination) : [...current, destination]);
823
- }
881
+ type: "button",
882
+ "aria-label": "Generate",
883
+ title: "Generate",
884
+ disabled: !canSubmit,
885
+ onClick: () => void generate(),
886
+ className: "studio-send ml-auto inline-flex h-8 w-8 flex-none items-center justify-center rounded-full text-white transition disabled:opacity-40",
887
+ children: /* @__PURE__ */ jsx2(ArrowUp, { className: "h-4 w-4", strokeWidth: 2 })
824
888
  }
825
889
  )
826
- }
827
- )
828
- ] }),
829
- error && /* @__PURE__ */ jsx8("div", { className: "mt-4 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive", children: error }),
830
- /* @__PURE__ */ jsxs7(
831
- Button2,
832
- {
833
- onClick: generate,
834
- disabled: !canSubmit,
835
- size: "lg",
836
- className: "mt-5 w-full disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
837
- children: [
838
- /* @__PURE__ */ jsx8(Sparkles, { className: "mr-2 h-4 w-4" }),
839
- "Generate",
840
- /* @__PURE__ */ jsx8("span", { className: "ml-2 text-xs opacity-60", children: "\u2318\u21B5" })
841
- ]
842
- }
843
- )
844
- ] });
890
+ ] }),
891
+ notice && /* @__PURE__ */ jsx2("p", { className: `px-1.5 pt-1.5 text-[12px] ${noticeIsProblem ? "text-destructive" : "text-muted-foreground"}`, children: notice })
892
+ ]
893
+ }
894
+ );
895
+ }
896
+ function asText(value) {
897
+ return typeof value === "string" ? value : typeof value === "number" ? String(value) : void 0;
898
+ }
899
+ function asWireScalar(value) {
900
+ return typeof value === "string" || typeof value === "number" ? value : void 0;
901
+ }
902
+ function choicesForPill(param, meta, value) {
903
+ if (!meta) return [];
904
+ const choices = choicesFor(param, meta).map((choice) => ({
905
+ value: choice,
906
+ label: optionValueLabel(param, choice)
907
+ }));
908
+ if (value === void 0 || choices.some((choice) => choice.value === value)) return choices;
909
+ return [...choices, { value, label: `${optionValueLabel(param, value)} \xB7 custom` }];
845
910
  }
846
911
 
847
912
  // src/studio-react/studio-header.tsx
848
- import { Button as Button3 } from "@tangle-network/sandbox-ui/primitives";
913
+ import { Button } from "@tangle-network/sandbox-ui/primitives";
849
914
  import { Images } from "lucide-react";
850
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
915
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
851
916
  function StudioHeader({
852
917
  count,
853
918
  onOpenLibrary,
854
919
  canGenerate
855
920
  }) {
856
- return /* @__PURE__ */ jsx9("header", { className: "sticky top-0 z-20 border-b border-border bg-card", children: /* @__PURE__ */ jsxs8("div", { className: "mx-auto flex w-full max-w-3xl items-center justify-between gap-3 px-4 py-3 sm:px-6", children: [
857
- /* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
858
- /* @__PURE__ */ jsx9("h1", { className: "text-lg font-semibold tracking-tight text-foreground", children: "Studio" }),
859
- /* @__PURE__ */ jsx9("p", { className: "truncate text-xs text-muted-foreground", children: canGenerate ? "Generate images, video, voice, avatars, and transcripts." : "Browse your team's generations." })
921
+ return /* @__PURE__ */ jsx3("header", { className: "sticky top-0 z-20 border-b border-border bg-card", children: /* @__PURE__ */ jsxs3("div", { className: "mx-auto flex w-full max-w-3xl items-center justify-between gap-3 px-4 py-3 sm:px-6", children: [
922
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
923
+ /* @__PURE__ */ jsx3("h1", { className: "text-lg font-semibold tracking-tight text-foreground", children: "Studio" }),
924
+ /* @__PURE__ */ jsx3("p", { className: "truncate text-xs text-muted-foreground", children: canGenerate ? "Generate images, video, voice, avatars, and transcripts." : "Browse your team's generations." })
860
925
  ] }),
861
- /* @__PURE__ */ jsxs8(Button3, { size: "sm", variant: "outline", onClick: onOpenLibrary, className: "shrink-0", children: [
862
- /* @__PURE__ */ jsx9(Images, { className: "mr-1.5 h-4 w-4" }),
926
+ /* @__PURE__ */ jsxs3(Button, { size: "sm", variant: "outline", onClick: onOpenLibrary, className: "shrink-0", children: [
927
+ /* @__PURE__ */ jsx3(Images, { className: "mr-1.5 h-4 w-4" }),
863
928
  "Library",
864
- count > 0 && /* @__PURE__ */ jsx9("span", { className: "ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/15 px-1.5 text-xs font-semibold text-primary", children: count })
929
+ count > 0 && /* @__PURE__ */ jsx3("span", { className: "ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/15 px-1.5 text-xs font-semibold text-primary", children: count })
865
930
  ] })
866
931
  ] }) });
867
932
  }
868
933
 
869
934
  // src/studio-react/result-canvas.tsx
870
- import { Badge as Badge3 } from "@tangle-network/sandbox-ui/primitives";
871
- import { ArrowRight, Sparkles as Sparkles2 } from "lucide-react";
872
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
935
+ import { Badge } from "@tangle-network/sandbox-ui/primitives";
936
+ import { ArrowRight, Sparkles } from "lucide-react";
937
+
938
+ // src/studio-react/type-config.ts
939
+ import { FileText, Image, Mic, User, Video as Video2 } from "lucide-react";
940
+ var MEDIA_SCRIM = "bg-black/60 text-white border-white/10";
941
+ var IMAGE = { label: "Image", icon: Image, color: MEDIA_SCRIM };
942
+ var TYPE_CONFIG = {
943
+ image: IMAGE,
944
+ video: { label: "Video", icon: Video2, color: MEDIA_SCRIM },
945
+ avatar: { label: "Avatar", icon: User, color: MEDIA_SCRIM },
946
+ speech: { label: "Audio", icon: Mic, color: MEDIA_SCRIM },
947
+ transcription: { label: "Transcript", icon: FileText, color: MEDIA_SCRIM }
948
+ };
949
+ function typeConfigFor(type) {
950
+ return TYPE_CONFIG[type] ?? IMAGE;
951
+ }
952
+
953
+ // src/studio-react/result-canvas.tsx
954
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
873
955
  function ResultCanvas({
874
956
  batch,
875
957
  onOpenLibrary,
@@ -877,10 +959,10 @@ function ResultCanvas({
877
959
  }) {
878
960
  const cardClass = "rounded-xl border border-border bg-card shadow-sm";
879
961
  if (batch.length === 0) {
880
- return /* @__PURE__ */ jsxs9("section", { className: `${cardClass} flex min-h-[180px] flex-col items-center justify-center gap-2 p-8 text-center`, children: [
881
- /* @__PURE__ */ jsx10("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx10(Sparkles2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
882
- /* @__PURE__ */ jsx10("p", { className: "text-sm font-medium text-foreground", children: "Your creations appear here" }),
883
- /* @__PURE__ */ jsx10("p", { className: "max-w-xs text-xs text-muted-foreground", children: "Generate above \u2014 results appear here and are saved to your library." })
962
+ return /* @__PURE__ */ jsxs4("section", { className: `${cardClass} flex min-h-[180px] flex-col items-center justify-center gap-2 p-8 text-center`, children: [
963
+ /* @__PURE__ */ jsx4("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx4(Sparkles, { className: "h-6 w-6 text-muted-foreground/40" }) }),
964
+ /* @__PURE__ */ jsx4("p", { className: "text-sm font-medium text-foreground", children: "Your creations appear here" }),
965
+ /* @__PURE__ */ jsx4("p", { className: "max-w-xs text-xs text-muted-foreground", children: "Generate above \u2014 results appear here and are saved to your library." })
884
966
  ] });
885
967
  }
886
968
  const cfg = typeConfigFor(batch[0]?.type ?? "image");
@@ -888,17 +970,17 @@ function ResultCanvas({
888
970
  const statuses = batch.map(generationStatus);
889
971
  const runLabel = statuses.some((s) => s === "pending" || s === "running") ? "Generating\u2026" : statuses.every((s) => s === "failed") ? "Last run failed" : "Latest creation";
890
972
  const isWorking = statuses.some((s) => s === "pending" || s === "running");
891
- return /* @__PURE__ */ jsxs9("section", { className: `${cardClass} p-5`, children: [
892
- /* @__PURE__ */ jsxs9("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
893
- /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
894
- /* @__PURE__ */ jsxs9(Badge3, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
895
- /* @__PURE__ */ jsx10(Icon, { className: "h-3 w-3" }),
973
+ return /* @__PURE__ */ jsxs4("section", { className: `${cardClass} p-5`, children: [
974
+ /* @__PURE__ */ jsxs4("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
975
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
976
+ /* @__PURE__ */ jsxs4(Badge, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
977
+ /* @__PURE__ */ jsx4(Icon, { className: "h-3 w-3" }),
896
978
  cfg.label
897
979
  ] }),
898
- /* @__PURE__ */ jsx10("span", { className: "text-sm font-medium text-foreground", children: runLabel }),
899
- isWorking && /* @__PURE__ */ jsx10("span", { className: "h-2 w-2 animate-pulse rounded-full bg-primary" })
980
+ /* @__PURE__ */ jsx4("span", { className: "text-sm font-medium text-foreground", children: runLabel }),
981
+ isWorking && /* @__PURE__ */ jsx4("span", { className: "h-2 w-2 animate-pulse rounded-full bg-primary" })
900
982
  ] }),
901
- /* @__PURE__ */ jsxs9(
983
+ /* @__PURE__ */ jsxs4(
902
984
  "button",
903
985
  {
904
986
  type: "button",
@@ -906,22 +988,22 @@ function ResultCanvas({
906
988
  className: "flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:text-primary/80",
907
989
  children: [
908
990
  "View in Library",
909
- /* @__PURE__ */ jsx10(ArrowRight, { className: "h-3.5 w-3.5" })
991
+ /* @__PURE__ */ jsx4(ArrowRight, { className: "h-3.5 w-3.5" })
910
992
  ]
911
993
  }
912
994
  )
913
995
  ] }),
914
- /* @__PURE__ */ jsx10("div", { className: `grid gap-3 ${batch.length === 1 ? "grid-cols-1" : "grid-cols-2"}`, children: batch.map((generation) => /* @__PURE__ */ jsx10(
996
+ /* @__PURE__ */ jsx4("div", { className: `grid gap-3 ${batch.length === 1 ? "grid-cols-1" : "grid-cols-2"}`, children: batch.map((generation) => /* @__PURE__ */ jsx4(
915
997
  "button",
916
998
  {
917
999
  type: "button",
918
1000
  onClick: () => onSelect(generation),
919
1001
  className: "studio-bloom group relative aspect-video overflow-hidden rounded-lg border border-border bg-secondary transition-colors hover:border-primary/40",
920
- children: /* @__PURE__ */ jsx10(CanvasTile, { generation })
1002
+ children: /* @__PURE__ */ jsx4(CanvasTile, { generation })
921
1003
  },
922
1004
  generation.id
923
1005
  )) }),
924
- batch.length > 1 && !isWorking && /* @__PURE__ */ jsxs9("p", { className: "mt-2 text-xs text-muted-foreground", children: [
1006
+ batch.length > 1 && !isWorking && /* @__PURE__ */ jsxs4("p", { className: "mt-2 text-xs text-muted-foreground", children: [
925
1007
  batch.length,
926
1008
  " results from this run"
927
1009
  ] })
@@ -932,58 +1014,58 @@ function CanvasTile({ generation }) {
932
1014
  const Icon = cfg.icon;
933
1015
  const status = generationStatus(generation);
934
1016
  if (status === "pending" || status === "running") {
935
- return /* @__PURE__ */ jsx10("div", { className: "studio-shimmer absolute inset-0 h-full w-full" });
1017
+ return /* @__PURE__ */ jsx4("div", { className: "studio-shimmer absolute inset-0 h-full w-full" });
936
1018
  }
937
1019
  if (status === "failed") {
938
- return /* @__PURE__ */ jsxs9("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4 text-center", children: [
939
- /* @__PURE__ */ jsx10(Icon, { className: "h-6 w-6 text-destructive/50" }),
940
- /* @__PURE__ */ jsx10("p", { className: "line-clamp-3 text-xs text-destructive", children: generationError(generation) ?? "Generation failed" })
1020
+ return /* @__PURE__ */ jsxs4("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4 text-center", children: [
1021
+ /* @__PURE__ */ jsx4(Icon, { className: "h-6 w-6 text-destructive/50" }),
1022
+ /* @__PURE__ */ jsx4("p", { className: "line-clamp-3 text-xs text-destructive", children: generationError(generation) ?? "Generation failed" })
941
1023
  ] });
942
1024
  }
943
1025
  if (generation.type === "image" && generation.result) {
944
- return /* @__PURE__ */ jsx10("img", { src: generation.result, alt: generation.prompt, className: "absolute inset-0 h-full w-full object-contain" });
1026
+ return /* @__PURE__ */ jsx4("img", { src: generation.result, alt: generation.prompt, className: "absolute inset-0 h-full w-full object-contain" });
945
1027
  }
946
1028
  if ((generation.type === "video" || generation.type === "avatar") && generation.result) {
947
- return /* @__PURE__ */ jsx10("video", { src: generation.result, controls: true, className: "absolute inset-0 h-full w-full object-contain" });
1029
+ return /* @__PURE__ */ jsx4("video", { src: generation.result, controls: true, className: "absolute inset-0 h-full w-full object-contain" });
948
1030
  }
949
1031
  if (generation.type === "speech" && generation.result) {
950
- return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsx10("audio", { src: generation.result, controls: true, className: "w-full" }) });
1032
+ return /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsx4("audio", { src: generation.result, controls: true, className: "w-full" }) });
951
1033
  }
952
1034
  if (generation.type === "transcription" && generation.result) {
953
- return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 overflow-y-auto p-4", children: /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap text-xs text-foreground", children: generation.result }) });
1035
+ return /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 overflow-y-auto p-4", children: /* @__PURE__ */ jsx4("p", { className: "whitespace-pre-wrap text-xs text-foreground", children: generation.result }) });
954
1036
  }
955
- return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx10(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) });
1037
+ return /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx4(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) });
956
1038
  }
957
1039
 
958
1040
  // src/studio-react/library-drawer.tsx
959
1041
  import { useMemo as useMemo3 } from "react";
960
- import { Link as Link3 } from "react-router";
1042
+ import { Link as Link2 } from "react-router";
961
1043
  import {
962
- Button as Button5,
1044
+ Button as Button3,
963
1045
  Tabs,
964
1046
  TabsList,
965
1047
  TabsTrigger
966
1048
  } from "@tangle-network/sandbox-ui/primitives";
967
- import { ArrowLeft, DollarSign, FolderOpen as FolderOpen2, X } from "lucide-react";
1049
+ import { ArrowLeft, DollarSign, FolderOpen as FolderOpen2, X as X2 } from "lucide-react";
968
1050
 
969
1051
  // src/studio-react/studio-sheet.tsx
970
1052
  import * as Dialog from "@radix-ui/react-dialog";
971
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1053
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
972
1054
  function StudioSheet({
973
1055
  open,
974
1056
  onOpenChange,
975
1057
  title,
976
1058
  children
977
1059
  }) {
978
- return /* @__PURE__ */ jsx11(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs10(Dialog.Portal, { children: [
979
- /* @__PURE__ */ jsx11(Dialog.Overlay, { className: "studio-sheet-overlay fixed inset-0 z-50 bg-black/50" }),
980
- /* @__PURE__ */ jsxs10(
1060
+ return /* @__PURE__ */ jsx5(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs5(Dialog.Portal, { children: [
1061
+ /* @__PURE__ */ jsx5(Dialog.Overlay, { className: "studio-sheet-overlay fixed inset-0 z-50 bg-black/50" }),
1062
+ /* @__PURE__ */ jsxs5(
981
1063
  Dialog.Content,
982
1064
  {
983
1065
  className: "studio-sheet-content fixed inset-y-0 right-0 z-50 flex w-[min(92vw,30rem)] flex-col border-l border-card-edge bg-popover shadow-[var(--shadow-overlay)] focus:outline-none",
984
1066
  "aria-describedby": void 0,
985
1067
  children: [
986
- /* @__PURE__ */ jsx11(Dialog.Title, { className: "sr-only", children: title }),
1068
+ /* @__PURE__ */ jsx5(Dialog.Title, { className: "sr-only", children: title }),
987
1069
  children
988
1070
  ]
989
1071
  }
@@ -993,12 +1075,11 @@ function StudioSheet({
993
1075
 
994
1076
  // src/studio-react/generation-grid.tsx
995
1077
  import { EmptyState } from "@tangle-network/sandbox-ui/primitives";
996
- import { Film, Sparkles as Sparkles3 } from "lucide-react";
1078
+ import { Film, Sparkles as Sparkles2 } from "lucide-react";
997
1079
 
998
1080
  // src/studio-react/generation-card.tsx
999
- import { Card, CardContent, Badge as Badge4 } from "@tangle-network/sandbox-ui/primitives";
1000
- import { Send } from "lucide-react";
1001
- import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1081
+ import { Card, CardContent, Badge as Badge2 } from "@tangle-network/sandbox-ui/primitives";
1082
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1002
1083
  function GenerationCard({
1003
1084
  generation,
1004
1085
  onSelect
@@ -1006,35 +1087,27 @@ function GenerationCard({
1006
1087
  const cfg = typeConfigFor(generation.type);
1007
1088
  const Icon = cfg.icon;
1008
1089
  const status = generationStatus(generation);
1009
- const publishPackage = generation.metadata?.publishPackage;
1010
- return /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => onSelect(generation), className: "group text-left animate-row-in", children: /* @__PURE__ */ jsxs11(Card, { className: "overflow-hidden transition-all group-hover:border-primary/50 group-hover:shadow-md", children: [
1011
- /* @__PURE__ */ jsxs11("div", { className: "relative aspect-video bg-secondary", children: [
1012
- generation.type === "image" && generation.result ? /* @__PURE__ */ jsx12("img", { src: generation.result, alt: generation.prompt, className: "h-full w-full object-cover" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx12("video", { src: generation.result, className: "h-full w-full object-cover", muted: true }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx12(Icon, { className: "h-6 w-6 text-muted-foreground/40" }) }) : generation.type === "transcription" && generation.result ? /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsx12("p", { className: "line-clamp-5 text-xs text-muted-foreground", children: generation.result }) }) : status === "pending" || status === "running" ? /* @__PURE__ */ jsx12("div", { className: "shimmer h-full w-full" }) : /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx12(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }),
1013
- /* @__PURE__ */ jsx12("div", { className: "absolute left-2 top-2", children: /* @__PURE__ */ jsxs11(Badge4, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
1014
- /* @__PURE__ */ jsx12(Icon, { className: "h-3 w-3" }),
1090
+ return /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => onSelect(generation), className: "group text-left animate-row-in", children: /* @__PURE__ */ jsxs6(Card, { className: "overflow-hidden transition-all group-hover:border-primary/50 group-hover:shadow-md", children: [
1091
+ /* @__PURE__ */ jsxs6("div", { className: "relative aspect-video bg-secondary", children: [
1092
+ generation.type === "image" && generation.result ? /* @__PURE__ */ jsx6("img", { src: generation.result, alt: generation.prompt, className: "h-full w-full object-cover" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx6("video", { src: generation.result, className: "h-full w-full object-cover", muted: true }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx6("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx6(Icon, { className: "h-6 w-6 text-muted-foreground/40" }) }) : generation.type === "transcription" && generation.result ? /* @__PURE__ */ jsx6("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsx6("p", { className: "line-clamp-5 text-xs text-muted-foreground", children: generation.result }) }) : status === "pending" || status === "running" ? /* @__PURE__ */ jsx6("div", { className: "shimmer h-full w-full" }) : /* @__PURE__ */ jsx6("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx6(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }),
1093
+ /* @__PURE__ */ jsx6("div", { className: "absolute left-2 top-2", children: /* @__PURE__ */ jsxs6(Badge2, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
1094
+ /* @__PURE__ */ jsx6(Icon, { className: "h-3 w-3" }),
1015
1095
  cfg.label
1016
1096
  ] }) }),
1017
- /* @__PURE__ */ jsx12(GenerationStatusBadge, { generation })
1097
+ /* @__PURE__ */ jsx6(GenerationStatusBadge, { generation })
1018
1098
  ] }),
1019
- /* @__PURE__ */ jsxs11(CardContent, { className: "p-3", children: [
1020
- /* @__PURE__ */ jsx12("p", { className: "mb-2 line-clamp-2 text-sm font-medium text-foreground", children: generation.prompt }),
1021
- status === "failed" && /* @__PURE__ */ jsx12("p", { className: "mb-2 line-clamp-2 text-xs text-destructive", children: generationError(generation) }),
1022
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between", children: [
1023
- generation.model && /* @__PURE__ */ jsx12("span", { className: "truncate text-xs text-muted-foreground", children: generation.model }),
1024
- /* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 items-center gap-2", children: [
1025
- generation.cost != null && /* @__PURE__ */ jsxs11("span", { className: "text-xs text-muted-foreground", children: [
1099
+ /* @__PURE__ */ jsxs6(CardContent, { className: "p-3", children: [
1100
+ /* @__PURE__ */ jsx6("p", { className: "mb-2 line-clamp-2 text-sm font-medium text-foreground", children: generation.prompt }),
1101
+ status === "failed" && /* @__PURE__ */ jsx6("p", { className: "mb-2 line-clamp-2 text-xs text-destructive", children: generationError(generation) }),
1102
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
1103
+ generation.model && /* @__PURE__ */ jsx6("span", { className: "truncate text-xs text-muted-foreground", children: generation.model }),
1104
+ /* @__PURE__ */ jsxs6("div", { className: "flex shrink-0 items-center gap-2", children: [
1105
+ generation.cost != null && /* @__PURE__ */ jsxs6("span", { className: "text-xs text-muted-foreground", children: [
1026
1106
  "$",
1027
1107
  generation.cost.toFixed(3)
1028
1108
  ] }),
1029
- /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: relativeTime(generation.createdAt) })
1109
+ /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted-foreground", children: relativeTime(generation.createdAt) })
1030
1110
  ] })
1031
- ] }),
1032
- isPublishPackage(publishPackage) && /* @__PURE__ */ jsxs11("div", { className: "mt-3 rounded-md border border-border bg-secondary p-2", children: [
1033
- /* @__PURE__ */ jsxs11("div", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-foreground", children: [
1034
- /* @__PURE__ */ jsx12(Send, { className: "h-3 w-3" }),
1035
- (publishPackage.destinations ?? []).map((id) => DESTINATIONS.find((destination) => destination.id === id)?.label ?? id).join(", ") || "Publish package"
1036
- ] }),
1037
- /* @__PURE__ */ jsx12("p", { className: "line-clamp-2 text-xs text-muted-foreground", children: publishPackage.caption || "Caption pending" })
1038
1111
  ] })
1039
1112
  ] })
1040
1113
  ] }) });
@@ -1047,11 +1120,11 @@ function GenerationStatusBadge({
1047
1120
  if (status === "succeeded") return null;
1048
1121
  const label = status === "failed" ? "Failed" : status === "running" ? "Running" : "Pending";
1049
1122
  const className = status === "failed" ? "border-destructive/30 bg-[color-mix(in_srgb,hsl(var(--destructive))_10%,hsl(var(--card)))] text-destructive" : "border-warning/30 bg-[color-mix(in_srgb,hsl(var(--warning))_10%,hsl(var(--card)))] text-warning";
1050
- return /* @__PURE__ */ jsx12("div", { className: inline ? "" : "absolute bottom-2 left-2", children: /* @__PURE__ */ jsx12(Badge4, { variant: "outline", className: `${className} text-xs`, children: label }) });
1123
+ return /* @__PURE__ */ jsx6("div", { className: inline ? "" : "absolute bottom-2 left-2", children: /* @__PURE__ */ jsx6(Badge2, { variant: "outline", className: `${className} text-xs`, children: label }) });
1051
1124
  }
1052
1125
 
1053
1126
  // src/studio-react/generation-grid.tsx
1054
- import { jsx as jsx13 } from "react/jsx-runtime";
1127
+ import { jsx as jsx7 } from "react/jsx-runtime";
1055
1128
  function filterGenerations(generations, typeFilter) {
1056
1129
  if (!typeFilter || !isGenerationType(typeFilter)) return generations;
1057
1130
  return generations.filter((generation) => generation.type === typeFilter);
@@ -1062,24 +1135,24 @@ function GenerationGrid({
1062
1135
  onSelect
1063
1136
  }) {
1064
1137
  if (generations.length === 0) {
1065
- return /* @__PURE__ */ jsx13(
1138
+ return /* @__PURE__ */ jsx7(
1066
1139
  EmptyState,
1067
1140
  {
1068
- icon: typeFilter ? /* @__PURE__ */ jsx13(Film, { className: "h-8 w-8 text-muted-foreground/30" }) : /* @__PURE__ */ jsx13(Sparkles3, { className: "h-10 w-10 text-muted-foreground/30" }),
1141
+ icon: typeFilter ? /* @__PURE__ */ jsx7(Film, { className: "h-8 w-8 text-muted-foreground/30" }) : /* @__PURE__ */ jsx7(Sparkles2, { className: "h-10 w-10 text-muted-foreground/30" }),
1069
1142
  title: typeFilter ? `No ${TYPE_CONFIG[typeFilter]?.label ?? typeFilter} generations` : "No generations yet",
1070
1143
  description: typeFilter ? "Try a different filter or change the type in the composer." : "Everything you and the agent create lives here.",
1071
1144
  className: "py-16"
1072
1145
  }
1073
1146
  );
1074
1147
  }
1075
- return /* @__PURE__ */ jsx13("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: generations.map((generation) => /* @__PURE__ */ jsx13(GenerationCard, { generation, onSelect }, generation.id)) });
1148
+ return /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: generations.map((generation) => /* @__PURE__ */ jsx7(GenerationCard, { generation, onSelect }, generation.id)) });
1076
1149
  }
1077
1150
 
1078
1151
  // src/studio-react/generation-detail.tsx
1079
- import { Link as Link2 } from "react-router";
1080
- import { Badge as Badge5, Button as Button4 } from "@tangle-network/sandbox-ui/primitives";
1152
+ import { Link } from "react-router";
1153
+ import { Badge as Badge3, Button as Button2 } from "@tangle-network/sandbox-ui/primitives";
1081
1154
  import { FolderOpen } from "lucide-react";
1082
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1155
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1083
1156
  function GenerationDetail({
1084
1157
  generation,
1085
1158
  vaultHref,
@@ -1089,50 +1162,50 @@ function GenerationDetail({
1089
1162
  const Icon = cfg.icon;
1090
1163
  const vaultPath = generationVaultPath(generation);
1091
1164
  const href = vaultHref && vaultPath ? vaultHref(vaultPath) : null;
1092
- return /* @__PURE__ */ jsxs12("div", { className: "space-y-4", children: [
1093
- /* @__PURE__ */ jsx14("div", { className: "overflow-hidden rounded-lg bg-secondary", children: generation.type === "image" && generation.result ? /* @__PURE__ */ jsx14("img", { src: generation.result, alt: generation.prompt, className: "max-h-[420px] w-full object-contain" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx14("video", { src: generation.result, controls: true, className: "max-h-[420px] w-full" }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx14("div", { className: "p-6", children: /* @__PURE__ */ jsx14("audio", { src: generation.result, controls: true, className: "w-full" }) }) : /* @__PURE__ */ jsx14("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx14(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }) }),
1094
- /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", children: [
1095
- /* @__PURE__ */ jsxs12(Badge5, { variant: "outline", className: `${cfg.color} gap-1`, children: [
1096
- /* @__PURE__ */ jsx14(Icon, { className: "h-3 w-3" }),
1165
+ return /* @__PURE__ */ jsxs7("div", { className: "space-y-4", children: [
1166
+ /* @__PURE__ */ jsx8("div", { className: "overflow-hidden rounded-lg bg-secondary", children: generation.type === "image" && generation.result ? /* @__PURE__ */ jsx8("img", { src: generation.result, alt: generation.prompt, className: "max-h-[420px] w-full object-contain" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx8("video", { src: generation.result, controls: true, className: "max-h-[420px] w-full" }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx8("div", { className: "p-6", children: /* @__PURE__ */ jsx8("audio", { src: generation.result, controls: true, className: "w-full" }) }) : /* @__PURE__ */ jsx8("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx8(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }) }),
1167
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-2", children: [
1168
+ /* @__PURE__ */ jsxs7(Badge3, { variant: "outline", className: `${cfg.color} gap-1`, children: [
1169
+ /* @__PURE__ */ jsx8(Icon, { className: "h-3 w-3" }),
1097
1170
  cfg.label
1098
1171
  ] }),
1099
- /* @__PURE__ */ jsx14(GenerationStatusBadge, { generation, inline: true }),
1100
- generationError(generation) && /* @__PURE__ */ jsx14("span", { className: "text-xs text-destructive", children: generationError(generation) })
1172
+ /* @__PURE__ */ jsx8(GenerationStatusBadge, { generation, inline: true }),
1173
+ generationError(generation) && /* @__PURE__ */ jsx8("span", { className: "text-xs text-destructive", children: generationError(generation) })
1101
1174
  ] }),
1102
- /* @__PURE__ */ jsxs12("div", { children: [
1103
- /* @__PURE__ */ jsx14("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Prompt" }),
1104
- /* @__PURE__ */ jsx14("p", { className: "text-sm text-foreground", children: generation.prompt })
1175
+ /* @__PURE__ */ jsxs7("div", { children: [
1176
+ /* @__PURE__ */ jsx8("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Prompt" }),
1177
+ /* @__PURE__ */ jsx8("p", { className: "text-sm text-foreground", children: generation.prompt })
1105
1178
  ] }),
1106
- /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-4", children: [
1107
- generation.model && /* @__PURE__ */ jsxs12("div", { children: [
1108
- /* @__PURE__ */ jsx14("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Model" }),
1109
- /* @__PURE__ */ jsx14("span", { className: "text-xs text-foreground", children: generation.model })
1179
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-4", children: [
1180
+ generation.model && /* @__PURE__ */ jsxs7("div", { children: [
1181
+ /* @__PURE__ */ jsx8("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Model" }),
1182
+ /* @__PURE__ */ jsx8("span", { className: "text-xs text-foreground", children: generation.model })
1110
1183
  ] }),
1111
- generation.cost != null && /* @__PURE__ */ jsxs12("div", { children: [
1112
- /* @__PURE__ */ jsx14("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Cost" }),
1113
- /* @__PURE__ */ jsxs12("span", { className: "text-xs text-foreground", children: [
1184
+ generation.cost != null && /* @__PURE__ */ jsxs7("div", { children: [
1185
+ /* @__PURE__ */ jsx8("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Cost" }),
1186
+ /* @__PURE__ */ jsxs7("span", { className: "text-xs text-foreground", children: [
1114
1187
  "$",
1115
1188
  generation.cost.toFixed(3)
1116
1189
  ] })
1117
1190
  ] }),
1118
- generation.createdAt && /* @__PURE__ */ jsxs12("div", { children: [
1119
- /* @__PURE__ */ jsx14("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Created" }),
1120
- /* @__PURE__ */ jsx14("span", { className: "text-xs text-foreground", children: new Date(generation.createdAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }) })
1191
+ generation.createdAt && /* @__PURE__ */ jsxs7("div", { children: [
1192
+ /* @__PURE__ */ jsx8("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Created" }),
1193
+ /* @__PURE__ */ jsx8("span", { className: "text-xs text-foreground", children: new Date(generation.createdAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short" }) })
1121
1194
  ] })
1122
1195
  ] }),
1123
- generation.type === "transcription" && generation.result && /* @__PURE__ */ jsxs12("div", { children: [
1124
- /* @__PURE__ */ jsx14("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Transcription" }),
1125
- /* @__PURE__ */ jsx14("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap rounded-lg bg-secondary p-4 font-mono text-[13px] tabular-nums text-foreground", children: generation.result })
1196
+ generation.type === "transcription" && generation.result && /* @__PURE__ */ jsxs7("div", { children: [
1197
+ /* @__PURE__ */ jsx8("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Transcription" }),
1198
+ /* @__PURE__ */ jsx8("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap rounded-lg bg-secondary p-4 font-mono text-[13px] tabular-nums text-foreground", children: generation.result })
1126
1199
  ] }),
1127
- href && /* @__PURE__ */ jsx14("div", { className: "border-t border-border pt-2", children: /* @__PURE__ */ jsx14(Link2, { to: href, prefetch: "intent", onClick: onNavigate, children: /* @__PURE__ */ jsxs12(Button4, { size: "sm", variant: "outline", children: [
1128
- /* @__PURE__ */ jsx14(FolderOpen, { className: "mr-1.5 h-3.5 w-3.5" }),
1200
+ href && /* @__PURE__ */ jsx8("div", { className: "border-t border-border pt-2", children: /* @__PURE__ */ jsx8(Link, { to: href, prefetch: "intent", onClick: onNavigate, children: /* @__PURE__ */ jsxs7(Button2, { size: "sm", variant: "outline", children: [
1201
+ /* @__PURE__ */ jsx8(FolderOpen, { className: "mr-1.5 h-3.5 w-3.5" }),
1129
1202
  "Open in Vault"
1130
1203
  ] }) }) })
1131
1204
  ] });
1132
1205
  }
1133
1206
 
1134
1207
  // src/studio-react/library-drawer.tsx
1135
- import { Fragment as Fragment3, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1208
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1136
1209
  function LibraryDrawer({
1137
1210
  open,
1138
1211
  onOpenChange,
@@ -1145,88 +1218,85 @@ function LibraryDrawer({
1145
1218
  onSelect
1146
1219
  }) {
1147
1220
  const visible = useMemo3(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
1148
- return /* @__PURE__ */ jsxs13(StudioSheet, { open, onOpenChange, title: "Asset library", children: [
1149
- /* @__PURE__ */ jsxs13("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3", children: [
1150
- selected ? /* @__PURE__ */ jsxs13(
1221
+ return /* @__PURE__ */ jsxs8(StudioSheet, { open, onOpenChange, title: "Asset library", children: [
1222
+ /* @__PURE__ */ jsxs8("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3", children: [
1223
+ selected ? /* @__PURE__ */ jsxs8(
1151
1224
  "button",
1152
1225
  {
1153
1226
  type: "button",
1154
1227
  onClick: () => onSelect(null),
1155
1228
  className: "flex items-center gap-1.5 text-sm font-medium text-foreground transition-colors hover:text-primary",
1156
1229
  children: [
1157
- /* @__PURE__ */ jsx15(ArrowLeft, { className: "h-4 w-4" }),
1230
+ /* @__PURE__ */ jsx9(ArrowLeft, { className: "h-4 w-4" }),
1158
1231
  "Back to library"
1159
1232
  ]
1160
1233
  }
1161
- ) : /* @__PURE__ */ jsx15("h2", { className: "text-sm font-semibold text-foreground", children: "Asset library" }),
1162
- /* @__PURE__ */ jsx15(
1234
+ ) : /* @__PURE__ */ jsx9("h2", { className: "text-sm font-semibold text-foreground", children: "Asset library" }),
1235
+ /* @__PURE__ */ jsx9(
1163
1236
  "button",
1164
1237
  {
1165
1238
  type: "button",
1166
1239
  "aria-label": "Close library",
1167
1240
  onClick: () => onOpenChange(false),
1168
1241
  className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1169
- children: /* @__PURE__ */ jsx15(X, { className: "h-4 w-4" })
1242
+ children: /* @__PURE__ */ jsx9(X2, { className: "h-4 w-4" })
1170
1243
  }
1171
1244
  )
1172
1245
  ] }),
1173
- selected ? /* @__PURE__ */ jsx15("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx15(
1246
+ selected ? /* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx9(
1174
1247
  GenerationDetail,
1175
1248
  {
1176
1249
  generation: selected,
1177
1250
  vaultHref,
1178
1251
  onNavigate: () => onOpenChange(false)
1179
1252
  }
1180
- ) }) : /* @__PURE__ */ jsxs13(Fragment3, { children: [
1181
- /* @__PURE__ */ jsxs13("div", { className: "shrink-0 border-b border-border px-4 pt-3", children: [
1182
- /* @__PURE__ */ jsxs13("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
1183
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1184
- /* @__PURE__ */ jsxs13("span", { children: [
1253
+ ) }) : /* @__PURE__ */ jsxs8(Fragment2, { children: [
1254
+ /* @__PURE__ */ jsxs8("div", { className: "shrink-0 border-b border-border px-4 pt-3", children: [
1255
+ /* @__PURE__ */ jsxs8("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
1256
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1257
+ /* @__PURE__ */ jsxs8("span", { children: [
1185
1258
  visible.length,
1186
1259
  " generation",
1187
1260
  visible.length !== 1 ? "s" : ""
1188
1261
  ] }),
1189
- /* @__PURE__ */ jsxs13("span", { className: "flex items-center gap-0.5", children: [
1190
- /* @__PURE__ */ jsx15(DollarSign, { className: "h-3 w-3" }),
1262
+ /* @__PURE__ */ jsxs8("span", { className: "flex items-center gap-0.5", children: [
1263
+ /* @__PURE__ */ jsx9(DollarSign, { className: "h-3 w-3" }),
1191
1264
  totalCost.toFixed(2),
1192
1265
  " spent"
1193
1266
  ] })
1194
1267
  ] }),
1195
- vaultHref && /* @__PURE__ */ jsx15(Link3, { to: vaultHref(), prefetch: "intent", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsxs13(Button5, { size: "sm", variant: "outline", children: [
1196
- /* @__PURE__ */ jsx15(FolderOpen2, { className: "mr-1.5 h-3.5 w-3.5" }),
1268
+ vaultHref && /* @__PURE__ */ jsx9(Link2, { to: vaultHref(), prefetch: "intent", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsxs8(Button3, { size: "sm", variant: "outline", children: [
1269
+ /* @__PURE__ */ jsx9(FolderOpen2, { className: "mr-1.5 h-3.5 w-3.5" }),
1197
1270
  "Vault"
1198
1271
  ] }) })
1199
1272
  ] }),
1200
- /* @__PURE__ */ jsx15(Tabs, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs13(TabsList, { className: "h-9 w-full justify-start gap-1 overflow-x-auto bg-transparent", children: [
1201
- /* @__PURE__ */ jsx15(TabsTrigger, { value: "all", className: "text-xs", children: "All" }),
1202
- Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx15(TabsTrigger, { value: key, className: "text-xs", children: cfg.label }, key))
1273
+ /* @__PURE__ */ jsx9(Tabs, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs8(TabsList, { className: "h-9 w-full justify-start gap-1 overflow-x-auto bg-transparent", children: [
1274
+ /* @__PURE__ */ jsx9(TabsTrigger, { value: "all", className: "text-xs", children: "All" }),
1275
+ Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx9(TabsTrigger, { value: key, className: "text-xs", children: cfg.label }, key))
1203
1276
  ] }) })
1204
1277
  ] }),
1205
- /* @__PURE__ */ jsx15("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx15(GenerationGrid, { generations: visible, typeFilter, onSelect }) })
1278
+ /* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx9(GenerationGrid, { generations: visible, typeFilter, onSelect }) })
1206
1279
  ] })
1207
1280
  ] });
1208
1281
  }
1209
1282
 
1210
1283
  // src/studio-react/studio-workspace.tsx
1211
- import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1284
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1212
1285
  function StudioWorkspace({
1213
1286
  generations,
1214
1287
  totalCost,
1215
1288
  workspaceId,
1216
1289
  role,
1217
1290
  generationsEndpoint,
1218
- vaultHref,
1219
- integrationsHref
1291
+ vaultHref
1220
1292
  }) {
1221
1293
  const [searchParams, setSearchParams] = useSearchParams();
1222
1294
  const typeFilter = searchParams.get("type");
1223
- const [drawerOpen, setDrawerOpen] = useState3(false);
1224
- const [selected, setSelected] = useState3(null);
1295
+ const [drawerOpen, setDrawerOpen] = useState4(false);
1296
+ const [selected, setSelected] = useState4(null);
1225
1297
  const canvasRef = useRef3(null);
1226
1298
  const canGenerate = role !== "viewer";
1227
- const canManageIntegrations = role === "owner" || role === "admin";
1228
1299
  const resolvedVaultHref = vaultHref ?? (workspaceId ? (filePath) => filePath ? `/app/${workspaceId}/vault?file=${encodeURIComponent(filePath)}` : `/app/${workspaceId}/vault` : void 0);
1229
- const resolvedIntegrationsHref = integrationsHref ?? (workspaceId ? `/app/${workspaceId}/integrations` : void 0);
1230
1300
  const { mergedGenerations, latestBatch, onGenerated } = useStudioGenerations(generations, {
1231
1301
  workspaceId,
1232
1302
  generationsEndpoint
@@ -1243,8 +1313,8 @@ function StudioWorkspace({
1243
1313
  setSelected(generation);
1244
1314
  setDrawerOpen(true);
1245
1315
  }
1246
- return /* @__PURE__ */ jsxs14("div", { className: "min-h-0 flex-1 overflow-y-auto bg-background", children: [
1247
- /* @__PURE__ */ jsx16(
1316
+ return /* @__PURE__ */ jsxs9("div", { className: "min-h-0 flex-1 overflow-y-auto bg-background", children: [
1317
+ /* @__PURE__ */ jsx10(
1248
1318
  StudioHeader,
1249
1319
  {
1250
1320
  count: mergedGenerations.length,
@@ -1252,13 +1322,11 @@ function StudioWorkspace({
1252
1322
  onOpenLibrary: openLibrary
1253
1323
  }
1254
1324
  ),
1255
- /* @__PURE__ */ jsx16("div", { className: "mx-auto w-full max-w-3xl space-y-4 px-4 py-6 sm:px-6", children: canGenerate ? /* @__PURE__ */ jsxs14(Fragment4, { children: [
1256
- /* @__PURE__ */ jsx16(
1257
- ComposerHero,
1325
+ /* @__PURE__ */ jsx10("div", { className: "mx-auto w-full max-w-3xl space-y-4 px-4 py-6 sm:px-6", children: canGenerate ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
1326
+ /* @__PURE__ */ jsx10(
1327
+ StudioComposer,
1258
1328
  {
1259
1329
  workspaceId,
1260
- integrationsHref: resolvedIntegrationsHref,
1261
- canManageIntegrations,
1262
1330
  onGenerated: (generation) => {
1263
1331
  onGenerated(generation);
1264
1332
  requestAnimationFrame(() => {
@@ -1267,7 +1335,7 @@ function StudioWorkspace({
1267
1335
  }
1268
1336
  }
1269
1337
  ),
1270
- /* @__PURE__ */ jsx16("div", { ref: canvasRef, children: /* @__PURE__ */ jsx16(
1338
+ /* @__PURE__ */ jsx10("div", { ref: canvasRef, children: /* @__PURE__ */ jsx10(
1271
1339
  ResultCanvas,
1272
1340
  {
1273
1341
  batch: latestBatch,
@@ -1275,16 +1343,16 @@ function StudioWorkspace({
1275
1343
  onSelect: openDetail
1276
1344
  }
1277
1345
  ) })
1278
- ] }) : /* @__PURE__ */ jsxs14("section", { className: "flex flex-col items-center justify-center gap-3 rounded-xl border border-border bg-card p-10 text-center shadow-sm", children: [
1279
- /* @__PURE__ */ jsx16("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx16(Images2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
1280
- /* @__PURE__ */ jsx16("p", { className: "text-sm font-medium text-foreground", children: mergedGenerations.length > 0 ? `${mergedGenerations.length} asset${mergedGenerations.length !== 1 ? "s" : ""} in this workspace` : "No generations yet" }),
1281
- /* @__PURE__ */ jsx16("p", { className: "max-w-sm text-xs text-muted-foreground", children: "Team generations appear in the library." }),
1282
- /* @__PURE__ */ jsxs14(Button6, { size: "sm", variant: "outline", onClick: openLibrary, children: [
1283
- /* @__PURE__ */ jsx16(Images2, { className: "mr-1.5 h-4 w-4" }),
1346
+ ] }) : /* @__PURE__ */ jsxs9("section", { className: "flex flex-col items-center justify-center gap-3 rounded-xl border border-border bg-card p-10 text-center shadow-sm", children: [
1347
+ /* @__PURE__ */ jsx10("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx10(Images2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
1348
+ /* @__PURE__ */ jsx10("p", { className: "text-sm font-medium text-foreground", children: mergedGenerations.length > 0 ? `${mergedGenerations.length} asset${mergedGenerations.length !== 1 ? "s" : ""} in this workspace` : "No generations yet" }),
1349
+ /* @__PURE__ */ jsx10("p", { className: "max-w-sm text-xs text-muted-foreground", children: "Team generations appear in the library." }),
1350
+ /* @__PURE__ */ jsxs9(Button4, { size: "sm", variant: "outline", onClick: openLibrary, children: [
1351
+ /* @__PURE__ */ jsx10(Images2, { className: "mr-1.5 h-4 w-4" }),
1284
1352
  "Open library"
1285
1353
  ] })
1286
1354
  ] }) }),
1287
- /* @__PURE__ */ jsx16(
1355
+ /* @__PURE__ */ jsx10(
1288
1356
  LibraryDrawer,
1289
1357
  {
1290
1358
  open: drawerOpen,
@@ -1307,6 +1375,23 @@ function StudioWorkspace({
1307
1375
  ] });
1308
1376
  }
1309
1377
 
1378
+ // src/studio-react/generation-notice.tsx
1379
+ import { Info } from "lucide-react";
1380
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1381
+ function GenerationNoticeChip({ className }) {
1382
+ return /* @__PURE__ */ jsxs10(
1383
+ "p",
1384
+ {
1385
+ className: `inline-block rounded-full border px-3.5 py-1.5 text-center text-[12.5px] font-medium leading-relaxed text-foreground shadow-sm ${className ?? ""}`,
1386
+ style: { background: "var(--studio-notice-bg)", borderColor: "var(--studio-notice-border)" },
1387
+ children: [
1388
+ /* @__PURE__ */ jsx11(Info, { className: "mr-1.5 inline-block h-[14px] w-[14px] align-[-2px] text-primary", strokeWidth: 1.5, "aria-hidden": true }),
1389
+ "Each prompt starts a new generation \u2014 this chat does not remember the last one."
1390
+ ]
1391
+ }
1392
+ );
1393
+ }
1394
+
1310
1395
  // src/studio-react/library-panel.tsx
1311
1396
  import { useMemo as useMemo4 } from "react";
1312
1397
  import {
@@ -1315,7 +1400,7 @@ import {
1315
1400
  TabsTrigger as TabsTrigger2
1316
1401
  } from "@tangle-network/sandbox-ui/primitives";
1317
1402
  import { DollarSign as DollarSign2 } from "lucide-react";
1318
- import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1403
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1319
1404
  function LibraryPanel({
1320
1405
  generations,
1321
1406
  totalCost,
@@ -1324,10 +1409,10 @@ function LibraryPanel({
1324
1409
  onSelect
1325
1410
  }) {
1326
1411
  const visible = useMemo4(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
1327
- return /* @__PURE__ */ jsxs15("div", { className: "flex min-w-0 flex-col", children: [
1328
- /* @__PURE__ */ jsxs15("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [
1329
- /* @__PURE__ */ jsx17(Tabs2, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs15(TabsList2, { className: "h-9 justify-start gap-1 overflow-x-auto overflow-y-hidden rounded-lg border border-border bg-background p-1", children: [
1330
- /* @__PURE__ */ jsx17(
1412
+ return /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-col", children: [
1413
+ /* @__PURE__ */ jsxs11("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [
1414
+ /* @__PURE__ */ jsx12(Tabs2, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs11(TabsList2, { className: "h-9 justify-start gap-1 overflow-x-auto overflow-y-hidden rounded-lg border border-border bg-background p-1", children: [
1415
+ /* @__PURE__ */ jsx12(
1331
1416
  TabsTrigger2,
1332
1417
  {
1333
1418
  value: "all",
@@ -1335,7 +1420,7 @@ function LibraryPanel({
1335
1420
  children: "All"
1336
1421
  }
1337
1422
  ),
1338
- Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx17(
1423
+ Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx12(
1339
1424
  TabsTrigger2,
1340
1425
  {
1341
1426
  value: key,
@@ -1345,64 +1430,54 @@ function LibraryPanel({
1345
1430
  key
1346
1431
  ))
1347
1432
  ] }) }),
1348
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1349
- /* @__PURE__ */ jsxs15("span", { children: [
1433
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1434
+ /* @__PURE__ */ jsxs11("span", { children: [
1350
1435
  visible.length,
1351
1436
  " generation",
1352
1437
  visible.length !== 1 ? "s" : ""
1353
1438
  ] }),
1354
- /* @__PURE__ */ jsxs15("span", { className: "flex items-center gap-0.5", children: [
1355
- /* @__PURE__ */ jsx17(DollarSign2, { className: "h-3 w-3" }),
1439
+ /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-0.5", children: [
1440
+ /* @__PURE__ */ jsx12(DollarSign2, { className: "h-3 w-3" }),
1356
1441
  totalCost.toFixed(2),
1357
1442
  " spent"
1358
1443
  ] })
1359
1444
  ] })
1360
1445
  ] }),
1361
- /* @__PURE__ */ jsx17(GenerationGrid, { generations: visible, typeFilter, onSelect })
1446
+ /* @__PURE__ */ jsx12(GenerationGrid, { generations: visible, typeFilter, onSelect })
1362
1447
  ] });
1363
1448
  }
1364
1449
 
1365
1450
  // src/studio-react/generation-detail-modal.tsx
1366
1451
  import { Dialog as Dialog2, DialogContent, DialogHeader, DialogTitle } from "@tangle-network/sandbox-ui/primitives";
1367
- import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1452
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1368
1453
  function GenerationDetailModal({
1369
1454
  generation,
1370
1455
  vaultHref,
1371
1456
  onClose
1372
1457
  }) {
1373
1458
  const cfg = generation ? typeConfigFor(generation.type) : null;
1374
- return /* @__PURE__ */ jsx18(Dialog2, { open: generation != null, onOpenChange: (open) => {
1459
+ return /* @__PURE__ */ jsx13(Dialog2, { open: generation != null, onOpenChange: (open) => {
1375
1460
  if (!open) onClose();
1376
- }, children: /* @__PURE__ */ jsxs16(DialogContent, { className: "max-h-[85vh] max-w-2xl overflow-y-auto border-card-edge bg-popover shadow-[var(--shadow-overlay)]", children: [
1377
- /* @__PURE__ */ jsx18(DialogHeader, { children: /* @__PURE__ */ jsx18(DialogTitle, { className: "text-left text-[15px] font-semibold", children: cfg ? `${cfg.label} generation` : "Generation" }) }),
1378
- generation && /* @__PURE__ */ jsx18(GenerationDetail, { generation, vaultHref, onNavigate: onClose })
1461
+ }, children: /* @__PURE__ */ jsxs12(DialogContent, { className: "max-h-[85vh] max-w-2xl overflow-y-auto border-card-edge bg-popover shadow-[var(--shadow-overlay)]", children: [
1462
+ /* @__PURE__ */ jsx13(DialogHeader, { children: /* @__PURE__ */ jsx13(DialogTitle, { className: "text-left text-[15px] font-semibold", children: cfg ? `${cfg.label} generation` : "Generation" }) }),
1463
+ generation && /* @__PURE__ */ jsx13(GenerationDetail, { generation, vaultHref, onNavigate: onClose })
1379
1464
  ] }) });
1380
1465
  }
1381
1466
  export {
1382
- AvatarComposer,
1383
- ComposerDisclosure,
1384
- ComposerHero,
1385
- Field,
1386
1467
  GenerationCard,
1387
1468
  GenerationDetail,
1388
1469
  GenerationDetailModal,
1389
1470
  GenerationGrid,
1471
+ GenerationNoticeChip,
1390
1472
  GenerationStatusBadge,
1391
- ImageComposer,
1392
1473
  LibraryDrawer,
1393
1474
  LibraryPanel,
1394
- NativeSelect,
1395
- PublishPackageComposer,
1396
1475
  ResultCanvas,
1397
- SpeechComposer,
1398
- Stepper,
1476
+ StudioComposer,
1399
1477
  StudioHeader,
1400
1478
  StudioSheet,
1401
1479
  StudioWorkspace,
1402
1480
  TYPE_CONFIG,
1403
- TranscriptionComposer,
1404
- TranscriptionOptions,
1405
- VideoComposer,
1406
1481
  filterGenerations,
1407
1482
  typeConfigFor,
1408
1483
  useStudioGenerations