@tangle-network/agent-app 0.45.66 → 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 (36) 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/studio/generation.d.ts +14 -23
  12. package/dist/studio/index.d.ts +1 -0
  13. package/dist/studio/index.js +27 -15
  14. package/dist/studio/model-options.d.ts +61 -0
  15. package/dist/studio-react/composer-option-controls.d.ts +140 -0
  16. package/dist/studio-react/generation-notice.d.ts +18 -0
  17. package/dist/studio-react/index.d.ts +5 -9
  18. package/dist/studio-react/index.js +912 -630
  19. package/dist/studio-react/index.js.map +1 -1
  20. package/dist/studio-react/studio-composer.d.ts +34 -0
  21. package/dist/studio-react/studio.css +51 -0
  22. package/dist/theme/tokens.css +18 -0
  23. package/dist/web-react/index.js +11 -11
  24. package/package.json +1 -1
  25. package/dist/chunk-23J72UUA.js.map +0 -1
  26. package/dist/chunk-E2CWFUUP.js +0 -250
  27. package/dist/chunk-E2CWFUUP.js.map +0 -1
  28. package/dist/chunk-MLG6XKPV.js +0 -44
  29. package/dist/chunk-MLG6XKPV.js.map +0 -1
  30. package/dist/studio-react/avatar-composer.d.ts +0 -8
  31. package/dist/studio-react/composer-hero.d.ts +0 -10
  32. package/dist/studio-react/composer-shell.d.ts +0 -18
  33. package/dist/studio-react/image-composer.d.ts +0 -8
  34. package/dist/studio-react/speech-composer.d.ts +0 -4
  35. package/dist/studio-react/transcription-composer.d.ts +0 -12
  36. package/dist/studio-react/video-composer.d.ts +0 -10
@@ -1,17 +1,11 @@
1
1
  import {
2
- IMAGE_QUALITIES,
3
- IMAGE_SIZE_HINT,
4
- IMAGE_SIZE_PATTERN,
5
- MAX_IMAGE_COUNT,
6
- MIN_IMAGE_COUNT,
7
- VIDEO_ASPECT_RATIOS,
8
- VIDEO_DURATIONS,
9
- VIDEO_RESOLUTIONS,
10
2
  buildGenerationRequestBody,
3
+ curateComposerModels,
11
4
  failedOptimisticGeneration,
12
5
  generationError,
13
6
  generationStatus,
14
7
  generationVaultPath,
8
+ imageToVideoSibling,
15
9
  isGenerationType,
16
10
  isLocalGeneration,
17
11
  latestBatchOf,
@@ -20,20 +14,30 @@ import {
20
14
  modelMessage,
21
15
  normalizeImageCount,
22
16
  optimisticGeneration,
23
- outputPathFor,
17
+ optionChoices,
24
18
  preferredModelId,
19
+ reconcileOptionValues,
25
20
  relativeTime,
26
- selectedModelsWithDefaults,
27
- userSafeGenerationMessage
28
- } from "../chunk-E2CWFUUP.js";
21
+ resolveComposerOptions,
22
+ supportsCustomImageSize,
23
+ textToVideoSibling,
24
+ userSafeGenerationMessage,
25
+ validateCustomImageSize
26
+ } from "../chunk-2RTYQU4W.js";
29
27
  import {
30
- ProviderLogo
31
- } 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";
32
36
 
33
37
  // src/studio-react/studio-workspace.tsx
34
- import { useRef as useRef3, useState as useState3 } from "react";
38
+ import { useRef as useRef3, useState as useState4 } from "react";
35
39
  import { useSearchParams } from "react-router";
36
- import { Button as Button5 } from "@tangle-network/sandbox-ui/primitives";
40
+ import { Button as Button4 } from "@tangle-network/sandbox-ui/primitives";
37
41
  import { Images as Images2 } from "lucide-react";
38
42
 
39
43
  // src/studio-react/use-studio-generations.ts
@@ -92,274 +96,651 @@ function useStudioGenerations(loaderGenerations, options = {}) {
92
96
  return { mergedGenerations, latestBatch, onGenerated };
93
97
  }
94
98
 
95
- // src/studio-react/composer-hero.tsx
96
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
97
- import {
98
- Button,
99
- Input as Input4,
100
- Label as Label2,
101
- Textarea,
102
- Select,
103
- SelectContent,
104
- SelectItem,
105
- SelectTrigger,
106
- SelectValue
107
- } from "@tangle-network/sandbox-ui/primitives";
108
- 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";
109
102
 
110
- // src/studio-react/type-config.ts
111
- import { FileText, Image, Mic, User, Video } from "lucide-react";
112
- var MEDIA_SCRIM = "bg-black/60 text-white border-white/10";
113
- var IMAGE = { label: "Image", icon: Image, color: MEDIA_SCRIM };
114
- var TYPE_CONFIG = {
115
- image: IMAGE,
116
- video: { label: "Video", icon: Video, color: MEDIA_SCRIM },
117
- avatar: { label: "Avatar", icon: User, color: MEDIA_SCRIM },
118
- speech: { label: "Audio", icon: Mic, color: MEDIA_SCRIM },
119
- transcription: { label: "Transcript", icon: FileText, color: MEDIA_SCRIM }
120
- };
121
- function typeConfigFor(type) {
122
- 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"}`;
123
117
  }
124
-
125
- // src/studio-react/composer-shell.tsx
126
- import { Label } from "@tangle-network/sandbox-ui/primitives";
127
- import { ChevronRight, Minus, Plus } from "lucide-react";
128
- import { jsx, jsxs } from "react/jsx-runtime";
129
- function Field({
130
- label,
131
- htmlFor,
132
- 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,
133
155
  children
134
156
  }) {
135
- return /* @__PURE__ */ jsxs("div", { className, children: [
136
- /* @__PURE__ */ jsx(
137
- Label,
138
- {
139
- htmlFor,
140
- className: "text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground",
141
- children: label
142
- }
143
- ),
144
- children
145
- ] });
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
+ );
194
+ }
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]);
146
210
  }
147
- function Stepper({
211
+ function OptionPill({
212
+ label,
148
213
  value,
149
- min,
150
- max,
151
- onChange
214
+ choices,
215
+ onSelect,
216
+ bandRef,
217
+ custom
152
218
  }) {
153
- return /* @__PURE__ */ jsxs("div", { className: "flex h-9 items-center justify-between rounded-md border border-input bg-background px-1.5", children: [
154
- /* @__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(
155
231
  "button",
156
232
  {
157
233
  type: "button",
158
- "aria-label": "Decrease",
159
- onClick: () => onChange(Math.max(min, value - 1)),
160
- disabled: value <= min,
161
- 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",
162
- 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
+ ]
163
247
  }
164
248
  ),
165
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: value }),
166
- /* @__PURE__ */ jsx(
167
- "button",
249
+ /* @__PURE__ */ jsxs(
250
+ PopoverSurface,
168
251
  {
169
- type: "button",
170
- "aria-label": "Increase",
171
- onClick: () => onChange(Math.min(max, value + 1)),
172
- disabled: value >= max,
173
- 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",
174
- 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
+ ]
175
290
  }
176
291
  )
177
292
  ] });
178
293
  }
179
- function ComposerDisclosure({ summary, children }) {
180
- return /* @__PURE__ */ jsxs("details", { className: "group rounded-lg border border-border bg-background transition-colors", children: [
181
- /* @__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: [
182
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground transition-transform group-open:rotate-90" }),
183
- summary
294
+ function CustomSizeForm({
295
+ initial,
296
+ onApply,
297
+ onCancel
298
+ }) {
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",
330
+ {
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
339
+ }
340
+ )
184
341
  ] }),
185
- /* @__PURE__ */ jsx("div", { className: "border-t border-border px-3 py-3", children })
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
+ ] })
186
363
  ] });
187
364
  }
188
- function NativeSelect(props) {
189
- return /* @__PURE__ */ jsx("select", { ...props, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm" });
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
+ );
190
380
  }
191
-
192
- // src/studio-react/image-composer.tsx
193
- import { Input } from "@tangle-network/sandbox-ui/primitives";
194
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
195
- function ImageComposer({
196
- size,
197
- quality,
198
- imageCount,
199
- onSizeChange,
200
- onQualityChange,
201
- onImageCountChange
381
+ function ReferencePill({
382
+ url,
383
+ onAttach,
384
+ onRemove,
385
+ pick,
386
+ bandRef
202
387
  }) {
203
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
204
- /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-2 gap-3", children: [
205
- /* @__PURE__ */ jsx2(Field, { label: "Size", children: /* @__PURE__ */ jsx2(
206
- Input,
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",
207
400
  {
208
- value: size,
209
- onChange: (event) => onSizeChange(event.target.value),
210
- pattern: IMAGE_SIZE_PATTERN,
211
- placeholder: IMAGE_SIZE_HINT,
212
- className: "bg-background"
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 })
213
406
  }
214
- ) }),
215
- /* @__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)) }) })
216
- ] }),
217
- /* @__PURE__ */ jsx2(Field, { label: "Images", children: /* @__PURE__ */ jsx2(
218
- Stepper,
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",
219
424
  {
220
- value: imageCount,
221
- min: MIN_IMAGE_COUNT,
222
- max: MAX_IMAGE_COUNT,
223
- onChange: (value) => onImageCountChange(normalizeImageCount(value))
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
+ ]
224
443
  }
225
- ) })
444
+ ),
445
+ !pick && /* @__PURE__ */ jsxs(
446
+ PopoverSurface,
447
+ {
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
+ ]
493
+ }
494
+ )
226
495
  ] });
227
496
  }
228
-
229
- // src/studio-react/video-composer.tsx
230
- import { Input as Input2 } from "@tangle-network/sandbox-ui/primitives";
231
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
232
- function VideoComposer({
233
- duration,
234
- resolution,
235
- aspectRatio,
236
- referenceImageUrl,
237
- onDurationChange,
238
- onResolutionChange,
239
- onAspectRatioChange,
240
- onReferenceImageUrlChange
497
+ function ModelPill({
498
+ models,
499
+ value,
500
+ displayName,
501
+ provider,
502
+ onSelect,
503
+ bandRef
241
504
  }) {
242
- return /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-3", children: [
243
- /* @__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)) }) }),
244
- /* @__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)) }) }),
245
- /* @__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)) }) }),
246
- /* @__PURE__ */ jsx3(Field, { label: "Reference image URL", children: /* @__PURE__ */ jsx3(Input2, { value: referenceImageUrl, onChange: (event) => onReferenceImageUrlChange(event.target.value), className: "bg-background" }) })
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",
512
+ {
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
+ ]
525
+ }
526
+ ),
527
+ /* @__PURE__ */ jsxs(
528
+ PopoverSurface,
529
+ {
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
+ ]
561
+ }
562
+ )
247
563
  ] });
248
564
  }
249
-
250
- // src/studio-react/speech-composer.tsx
251
- import { Input as Input3 } from "@tangle-network/sandbox-ui/primitives";
252
- import { jsx as jsx4 } from "react/jsx-runtime";
253
- function SpeechComposer({
254
- voice,
255
- onVoiceChange
256
- }) {
257
- return /* @__PURE__ */ jsx4(Field, { label: "Voice", children: /* @__PURE__ */ jsx4(Input3, { value: voice, onChange: (event) => onVoiceChange(event.target.value), className: "bg-background" }) });
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);
258
578
  }
259
579
 
260
- // src/studio-react/composer-hero.tsx
261
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
262
- var COMPOSER_TYPES = ["image", "video", "speech"];
263
- var SUGGESTIONS = [
264
- {
265
- label: "Storyboard frame",
266
- type: "image",
267
- prompt: "A vertical storyboard frame for a product launch teaser \u2014 dark cinematic lighting, sleek studio aesthetic, 9:16 composition."
268
- },
269
- {
270
- label: "Launch teaser clip",
271
- type: "video",
272
- prompt: "A 6-second vertical launch teaser: animated product reveal, dynamic camera move, hero music swell."
273
- },
274
- {
275
- label: "Scratch voiceover",
276
- type: "speech",
277
- prompt: "A confident 12-second scratch voiceover for a product launch teaser: warm tone, conversational pace."
278
- }
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 }
279
586
  ];
280
- 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({
281
624
  workspaceId,
282
- align = "start",
283
- surfaceClassName = "bg-card",
284
- onGenerated
625
+ onGenerated,
626
+ variant = "home",
627
+ pickReferenceImage,
628
+ className
285
629
  }) {
286
- const [type, setType] = useState2("image");
287
- const [prompt, setPrompt] = useState2("");
288
- const [negativePrompt, setNegativePrompt] = useState2("");
289
- const [outputPath, setOutputPath] = useState2(outputPathFor("image"));
290
- const [size, setSize] = useState2("1536x1024");
291
- const [quality, setQuality] = useState2("high");
292
- const [imageCount, setImageCount] = useState2(1);
293
- const [duration, setDuration] = useState2("6");
294
- const [resolution, setResolution] = useState2("720p");
295
- const [aspectRatio, setAspectRatio] = useState2("16:9");
296
- const [referenceImageUrl, setReferenceImageUrl] = useState2("");
297
- const [voice, setVoice] = useState2("alloy");
298
- const [mediaModels, setMediaModels] = useState2(null);
299
- const [mediaModelsLoading, setMediaModelsLoading] = useState2(false);
300
- const [mediaModelsError, setMediaModelsError] = useState2(null);
301
- 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);
302
644
  const submitLockRef = useRef2(false);
303
- const [isSubmitting, setIsSubmitting] = useState2(false);
304
- const [error, setError] = useState2(null);
305
- const modelsForType = mediaModels?.models[type] ?? [];
306
- const selectedModel = selectedModels[type] ?? preferredModelId(type, mediaModels) ?? "";
307
- const selectedModelOption = modelsForType.find((model) => model.id === selectedModel);
308
- const modelReady = Boolean(selectedModelOption) && selectedModelOption?.status !== "unavailable" && !mediaModelsLoading && !mediaModelsError;
309
- const hasRequiredInput = Boolean(prompt.trim());
310
- const canSubmit = Boolean(workspaceId) && modelReady && hasRequiredInput && !isSubmitting;
311
- useEffect2(() => {
645
+ const bandRef = useRef2(null);
646
+ useEffect3(() => {
312
647
  if (!workspaceId) return;
313
648
  let cancelled = false;
314
- setMediaModelsLoading(true);
315
- setMediaModelsError(null);
649
+ setCatalogLoading(true);
650
+ setCatalogError(null);
316
651
  fetch(`/api/media-models?workspaceId=${encodeURIComponent(workspaceId)}`).then(async (res) => {
317
652
  const data = await res.json();
318
653
  if (!res.ok) throw new Error(data.error ?? "Could not load media models");
319
654
  return data;
320
655
  }).then((data) => {
321
656
  if (cancelled) return;
322
- setMediaModels(data);
323
- setSelectedModels((current) => selectedModelsWithDefaults(current, data));
657
+ setCatalog(data);
324
658
  }).catch(() => {
325
659
  if (cancelled) return;
326
- setMediaModels(null);
327
- setMediaModelsError("Could not load media models");
660
+ setCatalog(null);
661
+ setCatalogError("Could not load media models");
328
662
  }).finally(() => {
329
- if (!cancelled) setMediaModelsLoading(false);
663
+ if (!cancelled) setCatalogLoading(false);
330
664
  });
331
665
  return () => {
332
666
  cancelled = true;
333
667
  };
334
668
  }, [workspaceId]);
335
- function changeType(next) {
336
- setType(next);
337
- 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);
338
723
  }
339
724
  async function generate() {
340
- if (!workspaceId || submitLockRef.current) return;
725
+ if (!workspaceId || submitLockRef.current || isSubmitting) return;
341
726
  const promptText = prompt.trim();
342
- if (!promptText) {
343
- setError("prompt is required");
344
- return;
345
- }
346
- if (!selectedModelOption || selectedModelOption.status === "unavailable") {
347
- setError(`Select an available ${typeConfigFor(type).label} model`);
727
+ if (!promptText) return;
728
+ if (!modelReady) {
729
+ if (!catalogLoading) setError("Select an available model");
348
730
  return;
349
731
  }
350
732
  submitLockRef.current = true;
351
733
  setIsSubmitting(true);
352
734
  setError(null);
353
735
  const clientRequestId = crypto.randomUUID();
354
- const requestedImageCount = type === "image" ? normalizeImageCount(imageCount) : 1;
355
- if (type === "image" && requestedImageCount !== imageCount) setImageCount(requestedImageCount);
356
- 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({
357
738
  type,
358
739
  prompt: promptText,
359
- model: selectedModel,
740
+ model: modelId,
360
741
  clientRequestId,
361
742
  outputIndex: type === "image" ? outputIndex : void 0,
362
- outputCount: type === "image" ? requestedImageCount : void 0
743
+ outputCount: type === "image" ? imageCount : void 0
363
744
  }));
364
745
  localGenerations.slice().reverse().forEach(onGenerated);
365
746
  setPrompt("");
@@ -369,13 +750,29 @@ function ComposerHero({
369
750
  workspaceId,
370
751
  clientRequestId,
371
752
  type,
372
- model: selectedModel,
373
- prompt,
374
- negativePrompt,
375
- outputPath,
376
- image: { size, quality, count: requestedImageCount },
377
- video: { duration, resolution, aspectRatio, referenceImageUrl },
378
- speech: { voice }
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
775
+ }
379
776
  });
380
777
  const res = await fetch("/api/generate", {
381
778
  method: "POST",
@@ -397,192 +794,164 @@ function ComposerHero({
397
794
  setIsSubmitting(false);
398
795
  }
399
796
  }
400
- const mediaTypes = COMPOSER_TYPES.map((key) => [key, typeConfigFor(key)]);
401
- return /* @__PURE__ */ jsxs4("section", { className: `rounded-2xl border border-border p-5 shadow-sm ${surfaceClassName}`, children: [
402
- /* @__PURE__ */ jsx5("div", { className: `mb-5 ${align === "center" ? "text-center" : "text-left"}`, children: /* @__PURE__ */ jsx5(
403
- "h2",
404
- {
405
- className: `font-semibold tracking-tight text-foreground transition-all duration-300 ${align === "center" ? "text-xl" : "text-base"}`,
406
- children: "Media Generation"
407
- }
408
- ) }),
409
- /* @__PURE__ */ jsx5("div", { role: "tablist", "aria-label": "Generation type", className: "grid grid-cols-3 gap-1.5", children: mediaTypes.map(([key, cfg]) => {
410
- const Icon = cfg.icon;
411
- const active = type === key;
412
- return /* @__PURE__ */ jsxs4(
413
- "button",
414
- {
415
- type: "button",
416
- role: "tab",
417
- "aria-selected": active,
418
- onClick: () => changeType(key),
419
- 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"}`,
420
- children: [
421
- /* @__PURE__ */ jsx5(Icon, { className: "h-[17px] w-[17px] shrink-0" }),
422
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: cfg.label })
423
- ]
424
- },
425
- key
426
- );
427
- }) }),
428
- /* @__PURE__ */ jsxs4("div", { className: "mt-5", children: [
429
- /* @__PURE__ */ jsx5(
430
- Label2,
431
- {
432
- htmlFor: "studio-prompt",
433
- className: "mb-1.5 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground",
434
- children: "Prompt"
435
- }
436
- ),
437
- /* @__PURE__ */ jsxs4("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: [
438
- /* @__PURE__ */ jsx5(
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(
439
807
  "textarea",
440
808
  {
441
- id: "studio-prompt",
442
809
  value: prompt,
443
810
  onChange: (event) => setPrompt(event.target.value),
444
811
  onKeyDown: (event) => {
445
- if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
446
- event.preventDefault();
447
- void generate();
448
- }
812
+ if (event.key !== "Enter" || event.shiftKey) return;
813
+ event.preventDefault();
814
+ void generate();
449
815
  },
450
- rows: 4,
451
- placeholder: "A vertical hero frame for a product launch teaser, dark cinematic lighting...",
452
- 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"
453
820
  }
454
821
  ),
455
- /* @__PURE__ */ jsx5("div", { className: "flex flex-wrap items-center gap-1.5 px-2.5 pb-2.5", children: SUGGESTIONS.map((suggestion) => /* @__PURE__ */ jsx5(
456
- "button",
457
- {
458
- type: "button",
459
- onClick: () => {
460
- setPrompt(suggestion.prompt);
461
- changeType(suggestion.type);
462
- },
463
- 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",
464
- children: suggestion.label
465
- },
466
- suggestion.label
467
- )) })
468
- ] })
469
- ] }),
470
- /* @__PURE__ */ jsxs4("div", { className: "mt-5 space-y-3", children: [
471
- /* @__PURE__ */ jsxs4("div", { className: "space-y-1.5", children: [
472
- /* @__PURE__ */ jsx5(Field, { label: "Model", htmlFor: "studio-media-model", children: /* @__PURE__ */ jsxs4(
473
- Select,
474
- {
475
- value: selectedModel || void 0,
476
- onValueChange: (value) => setSelectedModels((current) => ({ ...current, [type]: value })),
477
- disabled: mediaModelsLoading || Boolean(mediaModelsError) || modelsForType.length === 0,
478
- children: [
479
- /* @__PURE__ */ jsx5(SelectTrigger, { id: "studio-media-model", className: "h-9 w-full bg-background", children: selectedModelOption ? /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
480
- selectedModelOption.provider && /* @__PURE__ */ jsx5(ProviderLogo, { provider: selectedModelOption.provider, size: 14 }),
481
- /* @__PURE__ */ jsx5("span", { className: "truncate", children: selectedModelOption.name || selectedModelOption.id }),
482
- selectedModelOption.provider && /* @__PURE__ */ jsxs4("span", { className: "shrink-0 text-muted-foreground", children: [
483
- "\xB7 ",
484
- selectedModelOption.provider
485
- ] }),
486
- selectedModelOption.status !== "available" && /* @__PURE__ */ jsx5("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 })
487
- ] }) : /* @__PURE__ */ jsx5(SelectValue, { placeholder: mediaModelsLoading ? "Loading models\u2026" : "Select a model" }) }),
488
- /* @__PURE__ */ jsx5(SelectContent, { className: "bg-background", children: modelsForType.map((model) => /* @__PURE__ */ jsx5(SelectItem, { value: model.id, disabled: model.status === "unavailable", children: /* @__PURE__ */ jsxs4("span", { className: "flex w-full items-center justify-between gap-3", children: [
489
- /* @__PURE__ */ jsxs4("span", { className: "flex min-w-0 items-center gap-2", children: [
490
- model.provider && /* @__PURE__ */ jsx5(ProviderLogo, { provider: model.provider, size: 14 }),
491
- /* @__PURE__ */ jsxs4("span", { className: "truncate", children: [
492
- model.name || model.id,
493
- model.provider ? ` \xB7 ${model.provider}` : ""
494
- ] })
495
- ] }),
496
- model.status !== "available" && /* @__PURE__ */ jsx5("span", { className: "shrink-0 text-xs capitalize text-muted-foreground", children: model.status })
497
- ] }) }, model.id)) })
498
- ]
499
- }
500
- ) }),
501
- (mediaModelsError || modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length)) && /* @__PURE__ */ jsx5("p", { className: `text-xs ${mediaModelsError || selectedModelOption?.status === "unavailable" ? "text-destructive" : "text-muted-foreground"}`, children: mediaModelsError ?? modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length) })
502
- ] }),
503
- type === "image" && /* @__PURE__ */ jsx5(
504
- ImageComposer,
505
- {
506
- size,
507
- quality,
508
- imageCount,
509
- onSizeChange: setSize,
510
- onQualityChange: setQuality,
511
- onImageCountChange: setImageCount
512
- }
513
- ),
514
- type === "video" && /* @__PURE__ */ jsx5(
515
- VideoComposer,
516
- {
517
- duration,
518
- resolution,
519
- aspectRatio,
520
- referenceImageUrl,
521
- onDurationChange: setDuration,
522
- onResolutionChange: setResolution,
523
- onAspectRatioChange: setAspectRatio,
524
- onReferenceImageUrlChange: setReferenceImageUrl
525
- }
526
- ),
527
- type === "speech" && /* @__PURE__ */ jsx5(SpeechComposer, { voice, onVoiceChange: setVoice })
528
- ] }),
529
- /* @__PURE__ */ jsx5("div", { className: "mt-4 space-y-2", children: /* @__PURE__ */ jsx5(ComposerDisclosure, { summary: "Advanced options", children: /* @__PURE__ */ jsxs4("div", { className: "grid gap-3 sm:grid-cols-2", children: [
530
- /* @__PURE__ */ jsx5(Field, { label: "Negative prompt", children: /* @__PURE__ */ jsx5(
531
- Textarea,
532
- {
533
- value: negativePrompt,
534
- onChange: (event) => setNegativePrompt(event.target.value),
535
- rows: 2,
536
- placeholder: "Avoid artifacts, off-style composition...",
537
- className: "bg-background"
538
- }
539
- ) }),
540
- /* @__PURE__ */ jsx5(Field, { label: "Save to", children: /* @__PURE__ */ jsx5(Input4, { value: outputPath, onChange: (event) => setOutputPath(event.target.value), className: "bg-background" }) })
541
- ] }) }) }),
542
- error && /* @__PURE__ */ jsx5("div", { className: "mt-4 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive", children: error }),
543
- /* @__PURE__ */ jsxs4(
544
- Button,
545
- {
546
- onClick: generate,
547
- disabled: !canSubmit,
548
- size: "lg",
549
- className: "mt-5 w-full disabled:bg-muted disabled:text-muted-foreground disabled:opacity-100",
550
- children: [
551
- /* @__PURE__ */ jsx5(Sparkles, { className: "mr-2 h-4 w-4" }),
552
- "Generate",
553
- /* @__PURE__ */ jsx5("span", { className: "ml-2 text-xs opacity-60", children: "\u2318\u21B5" })
554
- ]
555
- }
556
- )
557
- ] });
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`)
877
+ ] }),
878
+ /* @__PURE__ */ jsx2(
879
+ "button",
880
+ {
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 })
888
+ }
889
+ )
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` }];
558
910
  }
559
911
 
560
912
  // src/studio-react/studio-header.tsx
561
- import { Button as Button2 } from "@tangle-network/sandbox-ui/primitives";
913
+ import { Button } from "@tangle-network/sandbox-ui/primitives";
562
914
  import { Images } from "lucide-react";
563
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
915
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
564
916
  function StudioHeader({
565
917
  count,
566
918
  onOpenLibrary,
567
919
  canGenerate
568
920
  }) {
569
- return /* @__PURE__ */ jsx6("header", { className: "sticky top-0 z-20 border-b border-border bg-card", children: /* @__PURE__ */ jsxs5("div", { className: "mx-auto flex w-full max-w-3xl items-center justify-between gap-3 px-4 py-3 sm:px-6", children: [
570
- /* @__PURE__ */ jsxs5("div", { className: "min-w-0", children: [
571
- /* @__PURE__ */ jsx6("h1", { className: "text-lg font-semibold tracking-tight text-foreground", children: "Studio" }),
572
- /* @__PURE__ */ jsx6("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." })
573
925
  ] }),
574
- /* @__PURE__ */ jsxs5(Button2, { size: "sm", variant: "outline", onClick: onOpenLibrary, className: "shrink-0", children: [
575
- /* @__PURE__ */ jsx6(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" }),
576
928
  "Library",
577
- count > 0 && /* @__PURE__ */ jsx6("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 })
578
930
  ] })
579
931
  ] }) });
580
932
  }
581
933
 
582
934
  // src/studio-react/result-canvas.tsx
583
935
  import { Badge } from "@tangle-network/sandbox-ui/primitives";
584
- import { ArrowRight, Sparkles as Sparkles2 } from "lucide-react";
585
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
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";
586
955
  function ResultCanvas({
587
956
  batch,
588
957
  onOpenLibrary,
@@ -590,10 +959,10 @@ function ResultCanvas({
590
959
  }) {
591
960
  const cardClass = "rounded-xl border border-border bg-card shadow-sm";
592
961
  if (batch.length === 0) {
593
- return /* @__PURE__ */ jsxs6("section", { className: `${cardClass} flex min-h-[180px] flex-col items-center justify-center gap-2 p-8 text-center`, children: [
594
- /* @__PURE__ */ jsx7("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx7(Sparkles2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
595
- /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground", children: "Your creations appear here" }),
596
- /* @__PURE__ */ jsx7("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." })
597
966
  ] });
598
967
  }
599
968
  const cfg = typeConfigFor(batch[0]?.type ?? "image");
@@ -601,17 +970,17 @@ function ResultCanvas({
601
970
  const statuses = batch.map(generationStatus);
602
971
  const runLabel = statuses.some((s) => s === "pending" || s === "running") ? "Generating\u2026" : statuses.every((s) => s === "failed") ? "Last run failed" : "Latest creation";
603
972
  const isWorking = statuses.some((s) => s === "pending" || s === "running");
604
- return /* @__PURE__ */ jsxs6("section", { className: `${cardClass} p-5`, children: [
605
- /* @__PURE__ */ jsxs6("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
606
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
607
- /* @__PURE__ */ jsxs6(Badge, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
608
- /* @__PURE__ */ jsx7(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" }),
609
978
  cfg.label
610
979
  ] }),
611
- /* @__PURE__ */ jsx7("span", { className: "text-sm font-medium text-foreground", children: runLabel }),
612
- isWorking && /* @__PURE__ */ jsx7("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" })
613
982
  ] }),
614
- /* @__PURE__ */ jsxs6(
983
+ /* @__PURE__ */ jsxs4(
615
984
  "button",
616
985
  {
617
986
  type: "button",
@@ -619,22 +988,22 @@ function ResultCanvas({
619
988
  className: "flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:text-primary/80",
620
989
  children: [
621
990
  "View in Library",
622
- /* @__PURE__ */ jsx7(ArrowRight, { className: "h-3.5 w-3.5" })
991
+ /* @__PURE__ */ jsx4(ArrowRight, { className: "h-3.5 w-3.5" })
623
992
  ]
624
993
  }
625
994
  )
626
995
  ] }),
627
- /* @__PURE__ */ jsx7("div", { className: `grid gap-3 ${batch.length === 1 ? "grid-cols-1" : "grid-cols-2"}`, children: batch.map((generation) => /* @__PURE__ */ jsx7(
996
+ /* @__PURE__ */ jsx4("div", { className: `grid gap-3 ${batch.length === 1 ? "grid-cols-1" : "grid-cols-2"}`, children: batch.map((generation) => /* @__PURE__ */ jsx4(
628
997
  "button",
629
998
  {
630
999
  type: "button",
631
1000
  onClick: () => onSelect(generation),
632
1001
  className: "studio-bloom group relative aspect-video overflow-hidden rounded-lg border border-border bg-secondary transition-colors hover:border-primary/40",
633
- children: /* @__PURE__ */ jsx7(CanvasTile, { generation })
1002
+ children: /* @__PURE__ */ jsx4(CanvasTile, { generation })
634
1003
  },
635
1004
  generation.id
636
1005
  )) }),
637
- batch.length > 1 && !isWorking && /* @__PURE__ */ jsxs6("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: [
638
1007
  batch.length,
639
1008
  " results from this run"
640
1009
  ] })
@@ -645,58 +1014,58 @@ function CanvasTile({ generation }) {
645
1014
  const Icon = cfg.icon;
646
1015
  const status = generationStatus(generation);
647
1016
  if (status === "pending" || status === "running") {
648
- return /* @__PURE__ */ jsx7("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" });
649
1018
  }
650
1019
  if (status === "failed") {
651
- return /* @__PURE__ */ jsxs6("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4 text-center", children: [
652
- /* @__PURE__ */ jsx7(Icon, { className: "h-6 w-6 text-destructive/50" }),
653
- /* @__PURE__ */ jsx7("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" })
654
1023
  ] });
655
1024
  }
656
1025
  if (generation.type === "image" && generation.result) {
657
- return /* @__PURE__ */ jsx7("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" });
658
1027
  }
659
1028
  if ((generation.type === "video" || generation.type === "avatar") && generation.result) {
660
- return /* @__PURE__ */ jsx7("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" });
661
1030
  }
662
1031
  if (generation.type === "speech" && generation.result) {
663
- return /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsx7("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" }) });
664
1033
  }
665
1034
  if (generation.type === "transcription" && generation.result) {
666
- return /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 overflow-y-auto p-4", children: /* @__PURE__ */ jsx7("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 }) });
667
1036
  }
668
- return /* @__PURE__ */ jsx7("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx7(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" }) });
669
1038
  }
670
1039
 
671
1040
  // src/studio-react/library-drawer.tsx
672
- import { useMemo as useMemo2 } from "react";
1041
+ import { useMemo as useMemo3 } from "react";
673
1042
  import { Link as Link2 } from "react-router";
674
1043
  import {
675
- Button as Button4,
1044
+ Button as Button3,
676
1045
  Tabs,
677
1046
  TabsList,
678
1047
  TabsTrigger
679
1048
  } from "@tangle-network/sandbox-ui/primitives";
680
- import { ArrowLeft, DollarSign, FolderOpen as FolderOpen2, X } from "lucide-react";
1049
+ import { ArrowLeft, DollarSign, FolderOpen as FolderOpen2, X as X2 } from "lucide-react";
681
1050
 
682
1051
  // src/studio-react/studio-sheet.tsx
683
1052
  import * as Dialog from "@radix-ui/react-dialog";
684
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1053
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
685
1054
  function StudioSheet({
686
1055
  open,
687
1056
  onOpenChange,
688
1057
  title,
689
1058
  children
690
1059
  }) {
691
- return /* @__PURE__ */ jsx8(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs7(Dialog.Portal, { children: [
692
- /* @__PURE__ */ jsx8(Dialog.Overlay, { className: "studio-sheet-overlay fixed inset-0 z-50 bg-black/50" }),
693
- /* @__PURE__ */ jsxs7(
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(
694
1063
  Dialog.Content,
695
1064
  {
696
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",
697
1066
  "aria-describedby": void 0,
698
1067
  children: [
699
- /* @__PURE__ */ jsx8(Dialog.Title, { className: "sr-only", children: title }),
1068
+ /* @__PURE__ */ jsx5(Dialog.Title, { className: "sr-only", children: title }),
700
1069
  children
701
1070
  ]
702
1071
  }
@@ -706,11 +1075,11 @@ function StudioSheet({
706
1075
 
707
1076
  // src/studio-react/generation-grid.tsx
708
1077
  import { EmptyState } from "@tangle-network/sandbox-ui/primitives";
709
- import { Film, Sparkles as Sparkles3 } from "lucide-react";
1078
+ import { Film, Sparkles as Sparkles2 } from "lucide-react";
710
1079
 
711
1080
  // src/studio-react/generation-card.tsx
712
1081
  import { Card, CardContent, Badge as Badge2 } from "@tangle-network/sandbox-ui/primitives";
713
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1082
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
714
1083
  function GenerationCard({
715
1084
  generation,
716
1085
  onSelect
@@ -718,26 +1087,26 @@ function GenerationCard({
718
1087
  const cfg = typeConfigFor(generation.type);
719
1088
  const Icon = cfg.icon;
720
1089
  const status = generationStatus(generation);
721
- return /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => onSelect(generation), className: "group text-left animate-row-in", children: /* @__PURE__ */ jsxs8(Card, { className: "overflow-hidden transition-all group-hover:border-primary/50 group-hover:shadow-md", children: [
722
- /* @__PURE__ */ jsxs8("div", { className: "relative aspect-video bg-secondary", children: [
723
- generation.type === "image" && generation.result ? /* @__PURE__ */ jsx9("img", { src: generation.result, alt: generation.prompt, className: "h-full w-full object-cover" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx9("video", { src: generation.result, className: "h-full w-full object-cover", muted: true }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx9("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx9(Icon, { className: "h-6 w-6 text-muted-foreground/40" }) }) : generation.type === "transcription" && generation.result ? /* @__PURE__ */ jsx9("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsx9("p", { className: "line-clamp-5 text-xs text-muted-foreground", children: generation.result }) }) : status === "pending" || status === "running" ? /* @__PURE__ */ jsx9("div", { className: "shimmer h-full w-full" }) : /* @__PURE__ */ jsx9("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx9(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }),
724
- /* @__PURE__ */ jsx9("div", { className: "absolute left-2 top-2", children: /* @__PURE__ */ jsxs8(Badge2, { variant: "outline", className: `${cfg.color} gap-1 text-xs`, children: [
725
- /* @__PURE__ */ jsx9(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" }),
726
1095
  cfg.label
727
1096
  ] }) }),
728
- /* @__PURE__ */ jsx9(GenerationStatusBadge, { generation })
1097
+ /* @__PURE__ */ jsx6(GenerationStatusBadge, { generation })
729
1098
  ] }),
730
- /* @__PURE__ */ jsxs8(CardContent, { className: "p-3", children: [
731
- /* @__PURE__ */ jsx9("p", { className: "mb-2 line-clamp-2 text-sm font-medium text-foreground", children: generation.prompt }),
732
- status === "failed" && /* @__PURE__ */ jsx9("p", { className: "mb-2 line-clamp-2 text-xs text-destructive", children: generationError(generation) }),
733
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
734
- generation.model && /* @__PURE__ */ jsx9("span", { className: "truncate text-xs text-muted-foreground", children: generation.model }),
735
- /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-2", children: [
736
- generation.cost != null && /* @__PURE__ */ jsxs8("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: [
737
1106
  "$",
738
1107
  generation.cost.toFixed(3)
739
1108
  ] }),
740
- /* @__PURE__ */ jsx9("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) })
741
1110
  ] })
742
1111
  ] })
743
1112
  ] })
@@ -751,11 +1120,11 @@ function GenerationStatusBadge({
751
1120
  if (status === "succeeded") return null;
752
1121
  const label = status === "failed" ? "Failed" : status === "running" ? "Running" : "Pending";
753
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";
754
- return /* @__PURE__ */ jsx9("div", { className: inline ? "" : "absolute bottom-2 left-2", children: /* @__PURE__ */ jsx9(Badge2, { 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 }) });
755
1124
  }
756
1125
 
757
1126
  // src/studio-react/generation-grid.tsx
758
- import { jsx as jsx10 } from "react/jsx-runtime";
1127
+ import { jsx as jsx7 } from "react/jsx-runtime";
759
1128
  function filterGenerations(generations, typeFilter) {
760
1129
  if (!typeFilter || !isGenerationType(typeFilter)) return generations;
761
1130
  return generations.filter((generation) => generation.type === typeFilter);
@@ -766,24 +1135,24 @@ function GenerationGrid({
766
1135
  onSelect
767
1136
  }) {
768
1137
  if (generations.length === 0) {
769
- return /* @__PURE__ */ jsx10(
1138
+ return /* @__PURE__ */ jsx7(
770
1139
  EmptyState,
771
1140
  {
772
- icon: typeFilter ? /* @__PURE__ */ jsx10(Film, { className: "h-8 w-8 text-muted-foreground/30" }) : /* @__PURE__ */ jsx10(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" }),
773
1142
  title: typeFilter ? `No ${TYPE_CONFIG[typeFilter]?.label ?? typeFilter} generations` : "No generations yet",
774
1143
  description: typeFilter ? "Try a different filter or change the type in the composer." : "Everything you and the agent create lives here.",
775
1144
  className: "py-16"
776
1145
  }
777
1146
  );
778
1147
  }
779
- return /* @__PURE__ */ jsx10("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: generations.map((generation) => /* @__PURE__ */ jsx10(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)) });
780
1149
  }
781
1150
 
782
1151
  // src/studio-react/generation-detail.tsx
783
1152
  import { Link } from "react-router";
784
- import { Badge as Badge3, Button as Button3 } from "@tangle-network/sandbox-ui/primitives";
1153
+ import { Badge as Badge3, Button as Button2 } from "@tangle-network/sandbox-ui/primitives";
785
1154
  import { FolderOpen } from "lucide-react";
786
- import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1155
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
787
1156
  function GenerationDetail({
788
1157
  generation,
789
1158
  vaultHref,
@@ -793,50 +1162,50 @@ function GenerationDetail({
793
1162
  const Icon = cfg.icon;
794
1163
  const vaultPath = generationVaultPath(generation);
795
1164
  const href = vaultHref && vaultPath ? vaultHref(vaultPath) : null;
796
- return /* @__PURE__ */ jsxs9("div", { className: "space-y-4", children: [
797
- /* @__PURE__ */ jsx11("div", { className: "overflow-hidden rounded-lg bg-secondary", children: generation.type === "image" && generation.result ? /* @__PURE__ */ jsx11("img", { src: generation.result, alt: generation.prompt, className: "max-h-[420px] w-full object-contain" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx11("video", { src: generation.result, controls: true, className: "max-h-[420px] w-full" }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx11("div", { className: "p-6", children: /* @__PURE__ */ jsx11("audio", { src: generation.result, controls: true, className: "w-full" }) }) : /* @__PURE__ */ jsx11("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx11(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }) }),
798
- /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-2", children: [
799
- /* @__PURE__ */ jsxs9(Badge3, { variant: "outline", className: `${cfg.color} gap-1`, children: [
800
- /* @__PURE__ */ jsx11(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" }),
801
1170
  cfg.label
802
1171
  ] }),
803
- /* @__PURE__ */ jsx11(GenerationStatusBadge, { generation, inline: true }),
804
- generationError(generation) && /* @__PURE__ */ jsx11("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) })
805
1174
  ] }),
806
- /* @__PURE__ */ jsxs9("div", { children: [
807
- /* @__PURE__ */ jsx11("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Prompt" }),
808
- /* @__PURE__ */ jsx11("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 })
809
1178
  ] }),
810
- /* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap items-center gap-4", children: [
811
- generation.model && /* @__PURE__ */ jsxs9("div", { children: [
812
- /* @__PURE__ */ jsx11("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Model" }),
813
- /* @__PURE__ */ jsx11("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 })
814
1183
  ] }),
815
- generation.cost != null && /* @__PURE__ */ jsxs9("div", { children: [
816
- /* @__PURE__ */ jsx11("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Cost" }),
817
- /* @__PURE__ */ jsxs9("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: [
818
1187
  "$",
819
1188
  generation.cost.toFixed(3)
820
1189
  ] })
821
1190
  ] }),
822
- generation.createdAt && /* @__PURE__ */ jsxs9("div", { children: [
823
- /* @__PURE__ */ jsx11("span", { className: "block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Created" }),
824
- /* @__PURE__ */ jsx11("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" }) })
825
1194
  ] })
826
1195
  ] }),
827
- generation.type === "transcription" && generation.result && /* @__PURE__ */ jsxs9("div", { children: [
828
- /* @__PURE__ */ jsx11("span", { className: "mb-1 block text-xs font-semibold uppercase tracking-[0.05em] text-muted-foreground", children: "Transcription" }),
829
- /* @__PURE__ */ jsx11("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 })
830
1199
  ] }),
831
- href && /* @__PURE__ */ jsx11("div", { className: "border-t border-border pt-2", children: /* @__PURE__ */ jsx11(Link, { to: href, prefetch: "intent", onClick: onNavigate, children: /* @__PURE__ */ jsxs9(Button3, { size: "sm", variant: "outline", children: [
832
- /* @__PURE__ */ jsx11(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" }),
833
1202
  "Open in Vault"
834
1203
  ] }) }) })
835
1204
  ] });
836
1205
  }
837
1206
 
838
1207
  // src/studio-react/library-drawer.tsx
839
- import { Fragment, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1208
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
840
1209
  function LibraryDrawer({
841
1210
  open,
842
1211
  onOpenChange,
@@ -848,71 +1217,71 @@ function LibraryDrawer({
848
1217
  selected,
849
1218
  onSelect
850
1219
  }) {
851
- const visible = useMemo2(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
852
- return /* @__PURE__ */ jsxs10(StudioSheet, { open, onOpenChange, title: "Asset library", children: [
853
- /* @__PURE__ */ jsxs10("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3", children: [
854
- selected ? /* @__PURE__ */ jsxs10(
1220
+ const visible = useMemo3(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
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(
855
1224
  "button",
856
1225
  {
857
1226
  type: "button",
858
1227
  onClick: () => onSelect(null),
859
1228
  className: "flex items-center gap-1.5 text-sm font-medium text-foreground transition-colors hover:text-primary",
860
1229
  children: [
861
- /* @__PURE__ */ jsx12(ArrowLeft, { className: "h-4 w-4" }),
1230
+ /* @__PURE__ */ jsx9(ArrowLeft, { className: "h-4 w-4" }),
862
1231
  "Back to library"
863
1232
  ]
864
1233
  }
865
- ) : /* @__PURE__ */ jsx12("h2", { className: "text-sm font-semibold text-foreground", children: "Asset library" }),
866
- /* @__PURE__ */ jsx12(
1234
+ ) : /* @__PURE__ */ jsx9("h2", { className: "text-sm font-semibold text-foreground", children: "Asset library" }),
1235
+ /* @__PURE__ */ jsx9(
867
1236
  "button",
868
1237
  {
869
1238
  type: "button",
870
1239
  "aria-label": "Close library",
871
1240
  onClick: () => onOpenChange(false),
872
1241
  className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
873
- children: /* @__PURE__ */ jsx12(X, { className: "h-4 w-4" })
1242
+ children: /* @__PURE__ */ jsx9(X2, { className: "h-4 w-4" })
874
1243
  }
875
1244
  )
876
1245
  ] }),
877
- selected ? /* @__PURE__ */ jsx12("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx12(
1246
+ selected ? /* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx9(
878
1247
  GenerationDetail,
879
1248
  {
880
1249
  generation: selected,
881
1250
  vaultHref,
882
1251
  onNavigate: () => onOpenChange(false)
883
1252
  }
884
- ) }) : /* @__PURE__ */ jsxs10(Fragment, { children: [
885
- /* @__PURE__ */ jsxs10("div", { className: "shrink-0 border-b border-border px-4 pt-3", children: [
886
- /* @__PURE__ */ jsxs10("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
887
- /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
888
- /* @__PURE__ */ jsxs10("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: [
889
1258
  visible.length,
890
1259
  " generation",
891
1260
  visible.length !== 1 ? "s" : ""
892
1261
  ] }),
893
- /* @__PURE__ */ jsxs10("span", { className: "flex items-center gap-0.5", children: [
894
- /* @__PURE__ */ jsx12(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" }),
895
1264
  totalCost.toFixed(2),
896
1265
  " spent"
897
1266
  ] })
898
1267
  ] }),
899
- vaultHref && /* @__PURE__ */ jsx12(Link2, { to: vaultHref(), prefetch: "intent", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsxs10(Button4, { size: "sm", variant: "outline", children: [
900
- /* @__PURE__ */ jsx12(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" }),
901
1270
  "Vault"
902
1271
  ] }) })
903
1272
  ] }),
904
- /* @__PURE__ */ jsx12(Tabs, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs10(TabsList, { className: "h-9 w-full justify-start gap-1 overflow-x-auto bg-transparent", children: [
905
- /* @__PURE__ */ jsx12(TabsTrigger, { value: "all", className: "text-xs", children: "All" }),
906
- Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx12(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))
907
1276
  ] }) })
908
1277
  ] }),
909
- /* @__PURE__ */ jsx12("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx12(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 }) })
910
1279
  ] })
911
1280
  ] });
912
1281
  }
913
1282
 
914
1283
  // src/studio-react/studio-workspace.tsx
915
- import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
1284
+ import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
916
1285
  function StudioWorkspace({
917
1286
  generations,
918
1287
  totalCost,
@@ -923,8 +1292,8 @@ function StudioWorkspace({
923
1292
  }) {
924
1293
  const [searchParams, setSearchParams] = useSearchParams();
925
1294
  const typeFilter = searchParams.get("type");
926
- const [drawerOpen, setDrawerOpen] = useState3(false);
927
- const [selected, setSelected] = useState3(null);
1295
+ const [drawerOpen, setDrawerOpen] = useState4(false);
1296
+ const [selected, setSelected] = useState4(null);
928
1297
  const canvasRef = useRef3(null);
929
1298
  const canGenerate = role !== "viewer";
930
1299
  const resolvedVaultHref = vaultHref ?? (workspaceId ? (filePath) => filePath ? `/app/${workspaceId}/vault?file=${encodeURIComponent(filePath)}` : `/app/${workspaceId}/vault` : void 0);
@@ -944,8 +1313,8 @@ function StudioWorkspace({
944
1313
  setSelected(generation);
945
1314
  setDrawerOpen(true);
946
1315
  }
947
- return /* @__PURE__ */ jsxs11("div", { className: "min-h-0 flex-1 overflow-y-auto bg-background", children: [
948
- /* @__PURE__ */ jsx13(
1316
+ return /* @__PURE__ */ jsxs9("div", { className: "min-h-0 flex-1 overflow-y-auto bg-background", children: [
1317
+ /* @__PURE__ */ jsx10(
949
1318
  StudioHeader,
950
1319
  {
951
1320
  count: mergedGenerations.length,
@@ -953,9 +1322,9 @@ function StudioWorkspace({
953
1322
  onOpenLibrary: openLibrary
954
1323
  }
955
1324
  ),
956
- /* @__PURE__ */ jsx13("div", { className: "mx-auto w-full max-w-3xl space-y-4 px-4 py-6 sm:px-6", children: canGenerate ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
957
- /* @__PURE__ */ jsx13(
958
- 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,
959
1328
  {
960
1329
  workspaceId,
961
1330
  onGenerated: (generation) => {
@@ -966,7 +1335,7 @@ function StudioWorkspace({
966
1335
  }
967
1336
  }
968
1337
  ),
969
- /* @__PURE__ */ jsx13("div", { ref: canvasRef, children: /* @__PURE__ */ jsx13(
1338
+ /* @__PURE__ */ jsx10("div", { ref: canvasRef, children: /* @__PURE__ */ jsx10(
970
1339
  ResultCanvas,
971
1340
  {
972
1341
  batch: latestBatch,
@@ -974,16 +1343,16 @@ function StudioWorkspace({
974
1343
  onSelect: openDetail
975
1344
  }
976
1345
  ) })
977
- ] }) : /* @__PURE__ */ jsxs11("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: [
978
- /* @__PURE__ */ jsx13("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx13(Images2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
979
- /* @__PURE__ */ jsx13("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" }),
980
- /* @__PURE__ */ jsx13("p", { className: "max-w-sm text-xs text-muted-foreground", children: "Team generations appear in the library." }),
981
- /* @__PURE__ */ jsxs11(Button5, { size: "sm", variant: "outline", onClick: openLibrary, children: [
982
- /* @__PURE__ */ jsx13(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" }),
983
1352
  "Open library"
984
1353
  ] })
985
1354
  ] }) }),
986
- /* @__PURE__ */ jsx13(
1355
+ /* @__PURE__ */ jsx10(
987
1356
  LibraryDrawer,
988
1357
  {
989
1358
  open: drawerOpen,
@@ -1006,15 +1375,32 @@ function StudioWorkspace({
1006
1375
  ] });
1007
1376
  }
1008
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
+
1009
1395
  // src/studio-react/library-panel.tsx
1010
- import { useMemo as useMemo3 } from "react";
1396
+ import { useMemo as useMemo4 } from "react";
1011
1397
  import {
1012
1398
  Tabs as Tabs2,
1013
1399
  TabsList as TabsList2,
1014
1400
  TabsTrigger as TabsTrigger2
1015
1401
  } from "@tangle-network/sandbox-ui/primitives";
1016
1402
  import { DollarSign as DollarSign2 } from "lucide-react";
1017
- import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1403
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
1018
1404
  function LibraryPanel({
1019
1405
  generations,
1020
1406
  totalCost,
@@ -1022,11 +1408,11 @@ function LibraryPanel({
1022
1408
  onFilterChange,
1023
1409
  onSelect
1024
1410
  }) {
1025
- const visible = useMemo3(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
1026
- return /* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-col", children: [
1027
- /* @__PURE__ */ jsxs12("div", { className: "mb-4 flex flex-wrap items-center justify-between gap-3", children: [
1028
- /* @__PURE__ */ jsx14(Tabs2, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs12(TabsList2, { className: "h-9 justify-start gap-1 overflow-x-auto overflow-y-hidden rounded-lg border border-border bg-background p-1", children: [
1029
- /* @__PURE__ */ jsx14(
1411
+ const visible = useMemo4(() => filterGenerations(generations, typeFilter), [generations, typeFilter]);
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(
1030
1416
  TabsTrigger2,
1031
1417
  {
1032
1418
  value: "all",
@@ -1034,7 +1420,7 @@ function LibraryPanel({
1034
1420
  children: "All"
1035
1421
  }
1036
1422
  ),
1037
- Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx14(
1423
+ Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx12(
1038
1424
  TabsTrigger2,
1039
1425
  {
1040
1426
  value: key,
@@ -1044,158 +1430,54 @@ function LibraryPanel({
1044
1430
  key
1045
1431
  ))
1046
1432
  ] }) }),
1047
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1048
- /* @__PURE__ */ jsxs12("span", { children: [
1433
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1434
+ /* @__PURE__ */ jsxs11("span", { children: [
1049
1435
  visible.length,
1050
1436
  " generation",
1051
1437
  visible.length !== 1 ? "s" : ""
1052
1438
  ] }),
1053
- /* @__PURE__ */ jsxs12("span", { className: "flex items-center gap-0.5", children: [
1054
- /* @__PURE__ */ jsx14(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" }),
1055
1441
  totalCost.toFixed(2),
1056
1442
  " spent"
1057
1443
  ] })
1058
1444
  ] })
1059
1445
  ] }),
1060
- /* @__PURE__ */ jsx14(GenerationGrid, { generations: visible, typeFilter, onSelect })
1446
+ /* @__PURE__ */ jsx12(GenerationGrid, { generations: visible, typeFilter, onSelect })
1061
1447
  ] });
1062
1448
  }
1063
1449
 
1064
1450
  // src/studio-react/generation-detail-modal.tsx
1065
1451
  import { Dialog as Dialog2, DialogContent, DialogHeader, DialogTitle } from "@tangle-network/sandbox-ui/primitives";
1066
- import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1452
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
1067
1453
  function GenerationDetailModal({
1068
1454
  generation,
1069
1455
  vaultHref,
1070
1456
  onClose
1071
1457
  }) {
1072
1458
  const cfg = generation ? typeConfigFor(generation.type) : null;
1073
- return /* @__PURE__ */ jsx15(Dialog2, { open: generation != null, onOpenChange: (open) => {
1459
+ return /* @__PURE__ */ jsx13(Dialog2, { open: generation != null, onOpenChange: (open) => {
1074
1460
  if (!open) onClose();
1075
- }, children: /* @__PURE__ */ jsxs13(DialogContent, { className: "max-h-[85vh] max-w-2xl overflow-y-auto border-card-edge bg-popover shadow-[var(--shadow-overlay)]", children: [
1076
- /* @__PURE__ */ jsx15(DialogHeader, { children: /* @__PURE__ */ jsx15(DialogTitle, { className: "text-left text-[15px] font-semibold", children: cfg ? `${cfg.label} generation` : "Generation" }) }),
1077
- generation && /* @__PURE__ */ jsx15(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 })
1078
1464
  ] }) });
1079
1465
  }
1080
-
1081
- // src/studio-react/avatar-composer.tsx
1082
- import { Input as Input5 } from "@tangle-network/sandbox-ui/primitives";
1083
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1084
- function AvatarComposer({
1085
- audioUrl,
1086
- imageUrl,
1087
- avatarId,
1088
- onAudioUrlChange,
1089
- onImageUrlChange,
1090
- onAvatarIdChange
1091
- }) {
1092
- return /* @__PURE__ */ jsxs14("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
1093
- /* @__PURE__ */ jsx16(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx16(
1094
- Input5,
1095
- {
1096
- id: "studio-audio-url",
1097
- value: audioUrl,
1098
- onChange: (event) => onAudioUrlChange(event.target.value),
1099
- placeholder: "https://cdn.example.com/source-audio.mp3",
1100
- className: "bg-background"
1101
- }
1102
- ) }),
1103
- /* @__PURE__ */ jsx16(Field, { label: "Image URL", htmlFor: "studio-avatar-image-url", children: /* @__PURE__ */ jsx16(
1104
- Input5,
1105
- {
1106
- id: "studio-avatar-image-url",
1107
- value: imageUrl,
1108
- onChange: (event) => onImageUrlChange(event.target.value),
1109
- placeholder: "https://cdn.example.com/portrait.png",
1110
- className: "bg-background"
1111
- }
1112
- ) }),
1113
- /* @__PURE__ */ jsx16(Field, { label: "Avatar ID", htmlFor: "studio-avatar-id", children: /* @__PURE__ */ jsx16(
1114
- Input5,
1115
- {
1116
- id: "studio-avatar-id",
1117
- value: avatarId,
1118
- onChange: (event) => onAvatarIdChange(event.target.value),
1119
- placeholder: "Optional provider avatar id",
1120
- className: "bg-background"
1121
- }
1122
- ) })
1123
- ] });
1124
- }
1125
-
1126
- // src/studio-react/transcription-composer.tsx
1127
- import { Input as Input6 } from "@tangle-network/sandbox-ui/primitives";
1128
- import { Fragment as Fragment3, jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1129
- function TranscriptionComposer({
1130
- audioUrl,
1131
- language,
1132
- onAudioUrlChange,
1133
- onLanguageChange
1134
- }) {
1135
- return /* @__PURE__ */ jsxs15("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
1136
- /* @__PURE__ */ jsx17(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx17(
1137
- Input6,
1138
- {
1139
- id: "studio-audio-url",
1140
- value: audioUrl,
1141
- onChange: (event) => onAudioUrlChange(event.target.value),
1142
- placeholder: "https://cdn.example.com/source-audio.mp3",
1143
- className: "bg-background"
1144
- }
1145
- ) }),
1146
- /* @__PURE__ */ jsx17(Field, { label: "Language", htmlFor: "studio-transcription-language", children: /* @__PURE__ */ jsx17(
1147
- Input6,
1148
- {
1149
- id: "studio-transcription-language",
1150
- value: language,
1151
- onChange: (event) => onLanguageChange(event.target.value),
1152
- placeholder: "en",
1153
- className: "bg-background"
1154
- }
1155
- ) })
1156
- ] });
1157
- }
1158
- function TranscriptionOptions({
1159
- responseFormat,
1160
- temperature,
1161
- onResponseFormatChange,
1162
- onTemperatureChange
1163
- }) {
1164
- return /* @__PURE__ */ jsxs15(Fragment3, { children: [
1165
- /* @__PURE__ */ jsx17(Field, { label: "Response format", children: /* @__PURE__ */ jsxs15(NativeSelect, { value: responseFormat, onChange: (event) => onResponseFormatChange(event.target.value), children: [
1166
- /* @__PURE__ */ jsx17("option", { value: "json", children: "JSON" }),
1167
- /* @__PURE__ */ jsx17("option", { value: "text", children: "Text" }),
1168
- /* @__PURE__ */ jsx17("option", { value: "srt", children: "SRT" }),
1169
- /* @__PURE__ */ jsx17("option", { value: "verbose_json", children: "Verbose JSON" }),
1170
- /* @__PURE__ */ jsx17("option", { value: "vtt", children: "VTT" })
1171
- ] }) }),
1172
- /* @__PURE__ */ jsx17(Field, { label: "Temperature", children: /* @__PURE__ */ jsx17(Input6, { type: "number", min: "0", max: "1", step: "0.1", value: temperature, onChange: (event) => onTemperatureChange(event.target.value), className: "bg-background" }) })
1173
- ] });
1174
- }
1175
1466
  export {
1176
- AvatarComposer,
1177
- ComposerDisclosure,
1178
- ComposerHero,
1179
- Field,
1180
1467
  GenerationCard,
1181
1468
  GenerationDetail,
1182
1469
  GenerationDetailModal,
1183
1470
  GenerationGrid,
1471
+ GenerationNoticeChip,
1184
1472
  GenerationStatusBadge,
1185
- ImageComposer,
1186
1473
  LibraryDrawer,
1187
1474
  LibraryPanel,
1188
- NativeSelect,
1189
1475
  ResultCanvas,
1190
- SpeechComposer,
1191
- Stepper,
1476
+ StudioComposer,
1192
1477
  StudioHeader,
1193
1478
  StudioSheet,
1194
1479
  StudioWorkspace,
1195
1480
  TYPE_CONFIG,
1196
- TranscriptionComposer,
1197
- TranscriptionOptions,
1198
- VideoComposer,
1199
1481
  filterGenerations,
1200
1482
  typeConfigFor,
1201
1483
  useStudioGenerations