@tangle-network/agent-app 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-DTS5TZRN.js +293 -0
- package/dist/chunk-DTS5TZRN.js.map +1 -0
- package/dist/completion-Kaqp_tWk.d.ts +41 -0
- package/dist/intakes/api.d.ts +2 -2
- package/dist/intakes/drizzle.d.ts +2 -2
- package/dist/intakes/index.d.ts +108 -32
- package/dist/intakes/index.js +66 -0
- package/dist/intakes/index.js.map +1 -1
- package/dist/intakes-react/index.d.ts +1 -1
- package/dist/intakes-react/lazy.d.ts +1 -1
- package/dist/{model-Cuj7d-xT.d.ts → model-Bzi5i63l.d.ts} +1 -1
- package/dist/studio/index.d.ts +132 -0
- package/dist/studio/index.js +59 -0
- package/dist/studio/index.js.map +1 -0
- package/dist/studio-react/index.d.ts +193 -0
- package/dist/studio-react/index.js +1216 -0
- package/dist/studio-react/index.js.map +1 -0
- package/dist/studio-react/studio.css +73 -0
- package/package.json +36 -4
|
@@ -0,0 +1,1216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CADENCES,
|
|
3
|
+
DESTINATIONS,
|
|
4
|
+
MAX_IMAGE_COUNT,
|
|
5
|
+
MIN_IMAGE_COUNT,
|
|
6
|
+
buildGenerationRequestBody,
|
|
7
|
+
buildPublishPackage,
|
|
8
|
+
failedOptimisticGeneration,
|
|
9
|
+
generationError,
|
|
10
|
+
generationStatus,
|
|
11
|
+
generationVaultPath,
|
|
12
|
+
isDestinationConnected,
|
|
13
|
+
isGenerationType,
|
|
14
|
+
isLocalGeneration,
|
|
15
|
+
isPublishPackage,
|
|
16
|
+
latestBatchOf,
|
|
17
|
+
mergeLiveGeneration,
|
|
18
|
+
mergeLoaderAndLive,
|
|
19
|
+
modelMessage,
|
|
20
|
+
normalizeImageCount,
|
|
21
|
+
optimisticGeneration,
|
|
22
|
+
outputPathFor,
|
|
23
|
+
preferredModelId,
|
|
24
|
+
relativeTime,
|
|
25
|
+
selectedModelsWithDefaults,
|
|
26
|
+
userSafeGenerationMessage
|
|
27
|
+
} from "../chunk-DTS5TZRN.js";
|
|
28
|
+
|
|
29
|
+
// src/studio-react/studio-workspace.tsx
|
|
30
|
+
import { useRef as useRef3, useState as useState3 } from "react";
|
|
31
|
+
import { useSearchParams } from "react-router";
|
|
32
|
+
import { Button as Button6 } from "@tangle-network/sandbox-ui/primitives";
|
|
33
|
+
import { Images as Images2 } from "lucide-react";
|
|
34
|
+
|
|
35
|
+
// src/studio-react/use-studio-generations.ts
|
|
36
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
37
|
+
import { useRevalidator } from "react-router";
|
|
38
|
+
function useStudioGenerations(loaderGenerations, options = {}) {
|
|
39
|
+
const { workspaceId, generationsEndpoint = "/api/generations" } = options;
|
|
40
|
+
const revalidator = useRevalidator();
|
|
41
|
+
const [liveGenerations, setLiveGenerations] = useState([]);
|
|
42
|
+
const mergedGenerations = useMemo(
|
|
43
|
+
() => mergeLoaderAndLive(loaderGenerations, liveGenerations),
|
|
44
|
+
[loaderGenerations, liveGenerations]
|
|
45
|
+
);
|
|
46
|
+
const latestBatch = useMemo(() => latestBatchOf(mergedGenerations), [mergedGenerations]);
|
|
47
|
+
const runningGenerationIds = useMemo(() => mergedGenerations.filter((gen) => {
|
|
48
|
+
const status = generationStatus(gen);
|
|
49
|
+
return status === "pending" || status === "running";
|
|
50
|
+
}).map((gen) => gen.id).filter((id) => !id.startsWith("local-")), [mergedGenerations]);
|
|
51
|
+
const runningIdsRef = useRef(runningGenerationIds);
|
|
52
|
+
runningIdsRef.current = runningGenerationIds;
|
|
53
|
+
const revalidateRef = useRef(revalidator.revalidate);
|
|
54
|
+
revalidateRef.current = revalidator.revalidate;
|
|
55
|
+
const runningKey = runningGenerationIds.join(",");
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (!workspaceId || !runningKey) return;
|
|
58
|
+
let cancelled = false;
|
|
59
|
+
const poll = async () => {
|
|
60
|
+
const responses = await Promise.all(runningIdsRef.current.map(async (id) => {
|
|
61
|
+
const res = await fetch(`${generationsEndpoint}?workspaceId=${encodeURIComponent(workspaceId)}&id=${encodeURIComponent(id)}`);
|
|
62
|
+
if (!res.ok) return null;
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
return data.generation ?? null;
|
|
65
|
+
}));
|
|
66
|
+
if (cancelled) return;
|
|
67
|
+
const refreshed = responses.filter((gen) => Boolean(gen));
|
|
68
|
+
if (refreshed.length > 0) {
|
|
69
|
+
setLiveGenerations((current) => refreshed.reduce(mergeLiveGeneration, current));
|
|
70
|
+
}
|
|
71
|
+
if (refreshed.some((gen) => generationStatus(gen) !== "running" && generationStatus(gen) !== "pending")) {
|
|
72
|
+
revalidateRef.current();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const interval = window.setInterval(() => {
|
|
76
|
+
void poll();
|
|
77
|
+
}, 4e3);
|
|
78
|
+
void poll();
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
window.clearInterval(interval);
|
|
82
|
+
};
|
|
83
|
+
}, [runningKey, workspaceId, generationsEndpoint]);
|
|
84
|
+
const onGenerated = useCallback((generation) => {
|
|
85
|
+
setLiveGenerations((current) => mergeLiveGeneration(current, generation));
|
|
86
|
+
if (!isLocalGeneration(generation)) revalidateRef.current();
|
|
87
|
+
}, []);
|
|
88
|
+
return { mergedGenerations, latestBatch, onGenerated };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/studio-react/composer-hero.tsx
|
|
92
|
+
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
93
|
+
import {
|
|
94
|
+
Badge as Badge2,
|
|
95
|
+
Button as Button2,
|
|
96
|
+
Input as Input7,
|
|
97
|
+
Label as Label2,
|
|
98
|
+
Textarea as Textarea2,
|
|
99
|
+
Select,
|
|
100
|
+
SelectContent,
|
|
101
|
+
SelectItem,
|
|
102
|
+
SelectTrigger,
|
|
103
|
+
SelectValue
|
|
104
|
+
} from "@tangle-network/sandbox-ui/primitives";
|
|
105
|
+
import { useIntegrations } from "@tangle-network/sandbox-ui/integrations";
|
|
106
|
+
import { Sparkles } from "lucide-react";
|
|
107
|
+
|
|
108
|
+
// src/studio-react/type-config.ts
|
|
109
|
+
import { Film, FileText, Image, Mic, Video } from "lucide-react";
|
|
110
|
+
var IMAGE = { label: "Image", icon: Image, color: "bg-blue-500/10 text-blue-600 border-blue-500/20" };
|
|
111
|
+
var TYPE_CONFIG = {
|
|
112
|
+
image: IMAGE,
|
|
113
|
+
video: { label: "Video", icon: Video, color: "bg-red-500/10 text-red-600 border-red-500/20" },
|
|
114
|
+
avatar: { label: "Avatar", icon: Film, color: "bg-purple-500/10 text-purple-600 border-purple-500/20" },
|
|
115
|
+
speech: { label: "Audio", icon: Mic, color: "bg-orange-500/10 text-orange-600 border-orange-500/20" },
|
|
116
|
+
transcription: { label: "Transcript", icon: FileText, color: "bg-emerald-500/10 text-emerald-600 border-emerald-500/20" }
|
|
117
|
+
};
|
|
118
|
+
function typeConfigFor(type) {
|
|
119
|
+
return TYPE_CONFIG[type] ?? IMAGE;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/studio-react/composer-shell.tsx
|
|
123
|
+
import { Label } from "@tangle-network/sandbox-ui/primitives";
|
|
124
|
+
import { ChevronRight } from "lucide-react";
|
|
125
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
126
|
+
function Field({
|
|
127
|
+
label,
|
|
128
|
+
htmlFor,
|
|
129
|
+
className = "space-y-1.5",
|
|
130
|
+
children
|
|
131
|
+
}) {
|
|
132
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
133
|
+
/* @__PURE__ */ jsx(Label, { htmlFor, className: "text-sm", children: label }),
|
|
134
|
+
children
|
|
135
|
+
] });
|
|
136
|
+
}
|
|
137
|
+
function ComposerDisclosure({ summary, children }) {
|
|
138
|
+
return /* @__PURE__ */ jsxs("details", { className: "group rounded-lg border border-border bg-muted/30 transition-colors open:bg-muted/40", children: [
|
|
139
|
+
/* @__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: [
|
|
140
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5 text-muted-foreground transition-transform group-open:rotate-90" }),
|
|
141
|
+
summary
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { className: "border-t border-border px-3 py-3", children })
|
|
144
|
+
] });
|
|
145
|
+
}
|
|
146
|
+
function NativeSelect(props) {
|
|
147
|
+
return /* @__PURE__ */ jsx("select", { ...props, className: "h-9 w-full rounded-md border border-input bg-background px-3 text-sm" });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/studio-react/image-composer.tsx
|
|
151
|
+
import { Input } from "@tangle-network/sandbox-ui/primitives";
|
|
152
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
153
|
+
function ImageComposer({
|
|
154
|
+
size,
|
|
155
|
+
quality,
|
|
156
|
+
imageCount,
|
|
157
|
+
onSizeChange,
|
|
158
|
+
onQualityChange,
|
|
159
|
+
onImageCountChange
|
|
160
|
+
}) {
|
|
161
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
162
|
+
/* @__PURE__ */ jsx2(Field, { label: "Size", children: /* @__PURE__ */ jsx2(Input, { value: size, onChange: (event) => onSizeChange(event.target.value) }) }),
|
|
163
|
+
/* @__PURE__ */ jsx2(Field, { label: "Quality", children: /* @__PURE__ */ jsx2(Input, { value: quality, onChange: (event) => onQualityChange(event.target.value) }) }),
|
|
164
|
+
/* @__PURE__ */ jsx2(Field, { label: "Images", children: /* @__PURE__ */ jsx2(Input, { type: "number", min: MIN_IMAGE_COUNT, max: MAX_IMAGE_COUNT, value: imageCount, onChange: (event) => onImageCountChange(normalizeImageCount(event.target.value)) }) })
|
|
165
|
+
] });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/studio-react/video-composer.tsx
|
|
169
|
+
import { Input as Input2 } from "@tangle-network/sandbox-ui/primitives";
|
|
170
|
+
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
171
|
+
function VideoComposer({
|
|
172
|
+
duration,
|
|
173
|
+
resolution,
|
|
174
|
+
aspectRatio,
|
|
175
|
+
referenceImageUrl,
|
|
176
|
+
onDurationChange,
|
|
177
|
+
onResolutionChange,
|
|
178
|
+
onAspectRatioChange,
|
|
179
|
+
onReferenceImageUrlChange
|
|
180
|
+
}) {
|
|
181
|
+
return /* @__PURE__ */ jsxs3(Fragment2, { children: [
|
|
182
|
+
/* @__PURE__ */ jsx3(Field, { label: "Duration (s)", children: /* @__PURE__ */ jsx3(Input2, { value: duration, onChange: (event) => onDurationChange(event.target.value) }) }),
|
|
183
|
+
/* @__PURE__ */ jsx3(Field, { label: "Resolution", children: /* @__PURE__ */ jsx3(Input2, { value: resolution, onChange: (event) => onResolutionChange(event.target.value) }) }),
|
|
184
|
+
/* @__PURE__ */ jsx3(Field, { label: "Aspect ratio", children: /* @__PURE__ */ jsx3(Input2, { value: aspectRatio, onChange: (event) => onAspectRatioChange(event.target.value) }) }),
|
|
185
|
+
/* @__PURE__ */ jsx3(Field, { label: "Reference image URL", children: /* @__PURE__ */ jsx3(Input2, { value: referenceImageUrl, onChange: (event) => onReferenceImageUrlChange(event.target.value) }) })
|
|
186
|
+
] });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/studio-react/speech-composer.tsx
|
|
190
|
+
import { Input as Input3 } from "@tangle-network/sandbox-ui/primitives";
|
|
191
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
192
|
+
function SpeechComposer({
|
|
193
|
+
voice,
|
|
194
|
+
onVoiceChange
|
|
195
|
+
}) {
|
|
196
|
+
return /* @__PURE__ */ jsx4(Field, { label: "Voice", children: /* @__PURE__ */ jsx4(Input3, { value: voice, onChange: (event) => onVoiceChange(event.target.value) }) });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/studio-react/avatar-composer.tsx
|
|
200
|
+
import { Input as Input4 } from "@tangle-network/sandbox-ui/primitives";
|
|
201
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
202
|
+
function AvatarComposer({
|
|
203
|
+
audioUrl,
|
|
204
|
+
imageUrl,
|
|
205
|
+
avatarId,
|
|
206
|
+
onAudioUrlChange,
|
|
207
|
+
onImageUrlChange,
|
|
208
|
+
onAvatarIdChange
|
|
209
|
+
}) {
|
|
210
|
+
return /* @__PURE__ */ jsxs4("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
|
|
211
|
+
/* @__PURE__ */ jsx5(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx5(
|
|
212
|
+
Input4,
|
|
213
|
+
{
|
|
214
|
+
id: "studio-audio-url",
|
|
215
|
+
value: audioUrl,
|
|
216
|
+
onChange: (event) => onAudioUrlChange(event.target.value),
|
|
217
|
+
placeholder: "https://cdn.example.com/source-audio.mp3"
|
|
218
|
+
}
|
|
219
|
+
) }),
|
|
220
|
+
/* @__PURE__ */ jsx5(Field, { label: "Image URL", htmlFor: "studio-avatar-image-url", children: /* @__PURE__ */ jsx5(
|
|
221
|
+
Input4,
|
|
222
|
+
{
|
|
223
|
+
id: "studio-avatar-image-url",
|
|
224
|
+
value: imageUrl,
|
|
225
|
+
onChange: (event) => onImageUrlChange(event.target.value),
|
|
226
|
+
placeholder: "https://cdn.example.com/portrait.png"
|
|
227
|
+
}
|
|
228
|
+
) }),
|
|
229
|
+
/* @__PURE__ */ jsx5(Field, { label: "Avatar ID", htmlFor: "studio-avatar-id", children: /* @__PURE__ */ jsx5(
|
|
230
|
+
Input4,
|
|
231
|
+
{
|
|
232
|
+
id: "studio-avatar-id",
|
|
233
|
+
value: avatarId,
|
|
234
|
+
onChange: (event) => onAvatarIdChange(event.target.value),
|
|
235
|
+
placeholder: "Optional provider avatar id"
|
|
236
|
+
}
|
|
237
|
+
) })
|
|
238
|
+
] });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/studio-react/transcription-composer.tsx
|
|
242
|
+
import { Input as Input5 } from "@tangle-network/sandbox-ui/primitives";
|
|
243
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
244
|
+
function TranscriptionComposer({
|
|
245
|
+
audioUrl,
|
|
246
|
+
language,
|
|
247
|
+
onAudioUrlChange,
|
|
248
|
+
onLanguageChange
|
|
249
|
+
}) {
|
|
250
|
+
return /* @__PURE__ */ jsxs5("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
|
|
251
|
+
/* @__PURE__ */ jsx6(Field, { label: "Audio URL", htmlFor: "studio-audio-url", children: /* @__PURE__ */ jsx6(
|
|
252
|
+
Input5,
|
|
253
|
+
{
|
|
254
|
+
id: "studio-audio-url",
|
|
255
|
+
value: audioUrl,
|
|
256
|
+
onChange: (event) => onAudioUrlChange(event.target.value),
|
|
257
|
+
placeholder: "https://cdn.example.com/source-audio.mp3"
|
|
258
|
+
}
|
|
259
|
+
) }),
|
|
260
|
+
/* @__PURE__ */ jsx6(Field, { label: "Language", htmlFor: "studio-transcription-language", children: /* @__PURE__ */ jsx6(
|
|
261
|
+
Input5,
|
|
262
|
+
{
|
|
263
|
+
id: "studio-transcription-language",
|
|
264
|
+
value: language,
|
|
265
|
+
onChange: (event) => onLanguageChange(event.target.value),
|
|
266
|
+
placeholder: "en"
|
|
267
|
+
}
|
|
268
|
+
) })
|
|
269
|
+
] });
|
|
270
|
+
}
|
|
271
|
+
function TranscriptionOptions({
|
|
272
|
+
responseFormat,
|
|
273
|
+
temperature,
|
|
274
|
+
onResponseFormatChange,
|
|
275
|
+
onTemperatureChange
|
|
276
|
+
}) {
|
|
277
|
+
return /* @__PURE__ */ jsxs5(Fragment3, { children: [
|
|
278
|
+
/* @__PURE__ */ jsx6(Field, { label: "Response format", children: /* @__PURE__ */ jsxs5(NativeSelect, { value: responseFormat, onChange: (event) => onResponseFormatChange(event.target.value), children: [
|
|
279
|
+
/* @__PURE__ */ jsx6("option", { value: "json", children: "JSON" }),
|
|
280
|
+
/* @__PURE__ */ jsx6("option", { value: "text", children: "Text" }),
|
|
281
|
+
/* @__PURE__ */ jsx6("option", { value: "srt", children: "SRT" }),
|
|
282
|
+
/* @__PURE__ */ jsx6("option", { value: "verbose_json", children: "Verbose JSON" }),
|
|
283
|
+
/* @__PURE__ */ jsx6("option", { value: "vtt", children: "VTT" })
|
|
284
|
+
] }) }),
|
|
285
|
+
/* @__PURE__ */ jsx6(Field, { label: "Temperature", children: /* @__PURE__ */ jsx6(Input5, { type: "number", min: "0", max: "1", step: "0.1", value: temperature, onChange: (event) => onTemperatureChange(event.target.value) }) })
|
|
286
|
+
] });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/studio-react/publish-package-composer.tsx
|
|
290
|
+
import { Link } from "react-router";
|
|
291
|
+
import { Badge, Button, Input as Input6, Textarea } from "@tangle-network/sandbox-ui/primitives";
|
|
292
|
+
import { AlertTriangle, CalendarClock, Check } from "lucide-react";
|
|
293
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
294
|
+
function PublishPackageComposer({
|
|
295
|
+
caption,
|
|
296
|
+
postDescription,
|
|
297
|
+
mentions,
|
|
298
|
+
cadence,
|
|
299
|
+
selectedDestinations,
|
|
300
|
+
connections,
|
|
301
|
+
connectionError,
|
|
302
|
+
connectionsLoading,
|
|
303
|
+
integrationsHref,
|
|
304
|
+
canManageIntegrations,
|
|
305
|
+
onCaptionChange,
|
|
306
|
+
onDescriptionChange,
|
|
307
|
+
onMentionsChange,
|
|
308
|
+
onCadenceChange,
|
|
309
|
+
onDestinationToggle
|
|
310
|
+
}) {
|
|
311
|
+
return /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
|
|
312
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-3", children: [
|
|
313
|
+
connectionError ? /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 rounded-md border border-warning/20 bg-warning/5 px-2.5 py-1.5 text-xs text-warning", children: [
|
|
314
|
+
/* @__PURE__ */ jsx7(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
315
|
+
"Couldn't load connected apps. Check Integrations."
|
|
316
|
+
] }) : /* @__PURE__ */ jsx7("p", { className: "text-xs text-muted-foreground", children: connectionsLoading ? "Checking connected apps\u2026" : "Stage captions, destinations, cadence. Saved with the generated asset." }),
|
|
317
|
+
integrationsHref && canManageIntegrations && /* @__PURE__ */ jsx7(Link, { to: integrationsHref, prefetch: "intent", className: "shrink-0", children: /* @__PURE__ */ jsx7(Button, { variant: "outline", size: "sm", children: "Connect" }) })
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ jsx7("div", { className: "grid gap-2", children: DESTINATIONS.map((destination) => {
|
|
320
|
+
const connected = isDestinationConnected(destination, connections);
|
|
321
|
+
const active = connected && selectedDestinations.includes(destination.id);
|
|
322
|
+
return /* @__PURE__ */ jsxs6(
|
|
323
|
+
"button",
|
|
324
|
+
{
|
|
325
|
+
type: "button",
|
|
326
|
+
disabled: !connected,
|
|
327
|
+
"aria-pressed": active,
|
|
328
|
+
onClick: () => onDestinationToggle(destination.id),
|
|
329
|
+
className: `rounded-md border p-2 text-left transition-colors disabled:cursor-not-allowed disabled:opacity-60 ${active ? "border-primary bg-primary/5 ring-1 ring-primary/20" : "border-border hover:bg-muted/50"}`,
|
|
330
|
+
children: [
|
|
331
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
|
|
332
|
+
/* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-1.5 text-xs font-medium text-foreground", children: [
|
|
333
|
+
active && /* @__PURE__ */ jsx7(Check, { className: "h-3.5 w-3.5 text-primary" }),
|
|
334
|
+
destination.label
|
|
335
|
+
] }),
|
|
336
|
+
/* @__PURE__ */ jsx7(Badge, { variant: active || connected ? "default" : "outline", children: active ? "Selected" : connected ? "Ready" : "Not connected" })
|
|
337
|
+
] }),
|
|
338
|
+
/* @__PURE__ */ jsx7("p", { className: "mt-1 text-xs text-muted-foreground", children: destination.fields })
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
destination.id
|
|
342
|
+
);
|
|
343
|
+
}) }),
|
|
344
|
+
/* @__PURE__ */ jsx7(Field, { label: "Caption", className: "space-y-2", children: /* @__PURE__ */ jsx7(Textarea, { value: caption, onChange: (event) => onCaptionChange(event.target.value), rows: 3, placeholder: "Write or generate the caption for selected destinations..." }) }),
|
|
345
|
+
/* @__PURE__ */ jsx7(Field, { label: "Description / CTA", className: "space-y-2", children: /* @__PURE__ */ jsx7(Textarea, { value: postDescription, onChange: (event) => onDescriptionChange(event.target.value), rows: 2, placeholder: "Release note, product context, link, or approval instruction..." }) }),
|
|
346
|
+
/* @__PURE__ */ jsxs6("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
347
|
+
/* @__PURE__ */ jsx7(Field, { label: "Mentions", className: "space-y-2", children: /* @__PURE__ */ jsx7(Input6, { value: mentions, onChange: (event) => onMentionsChange(event.target.value), placeholder: "@creator, @partner" }) }),
|
|
348
|
+
/* @__PURE__ */ jsx7(Field, { label: "Cadence", className: "space-y-2", children: /* @__PURE__ */ jsx7(NativeSelect, { value: cadence, onChange: (event) => onCadenceChange(event.target.value), children: CADENCES.map((option) => /* @__PURE__ */ jsx7("option", { value: option, children: option }, option)) }) })
|
|
349
|
+
] }),
|
|
350
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 rounded-md bg-muted/40 p-2 text-xs text-muted-foreground", children: [
|
|
351
|
+
/* @__PURE__ */ jsx7(CalendarClock, { className: "h-3.5 w-3.5" }),
|
|
352
|
+
"Publish packages stay attached to generated media and can run through connected GTM apps."
|
|
353
|
+
] })
|
|
354
|
+
] });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/studio-react/composer-hero.tsx
|
|
358
|
+
import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
359
|
+
var HUB_BASE_URL = "/api/integrations/hub";
|
|
360
|
+
var SUGGESTIONS = [
|
|
361
|
+
{
|
|
362
|
+
label: "Storyboard frame",
|
|
363
|
+
type: "image",
|
|
364
|
+
prompt: "A vertical storyboard frame for a product launch teaser \u2014 dark cinematic lighting, sleek studio aesthetic, 9:16 composition."
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
label: "Launch teaser clip",
|
|
368
|
+
type: "video",
|
|
369
|
+
prompt: "A 6-second vertical launch teaser: animated product reveal, dynamic camera move, hero music swell."
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
label: "Scratch voiceover",
|
|
373
|
+
type: "speech",
|
|
374
|
+
prompt: "A confident 12-second scratch voiceover for a product launch teaser: warm tone, conversational pace."
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
label: "Transcribe dailies",
|
|
378
|
+
type: "transcription",
|
|
379
|
+
prompt: "Transcribe the latest dailies recording with timestamps, speaker labels, and key story beats highlighted."
|
|
380
|
+
}
|
|
381
|
+
];
|
|
382
|
+
function ComposerHero({
|
|
383
|
+
workspaceId,
|
|
384
|
+
integrationsHref,
|
|
385
|
+
canManageIntegrations,
|
|
386
|
+
onGenerated
|
|
387
|
+
}) {
|
|
388
|
+
const [type, setType] = useState2("image");
|
|
389
|
+
const [prompt, setPrompt] = useState2("");
|
|
390
|
+
const [negativePrompt, setNegativePrompt] = useState2("");
|
|
391
|
+
const [outputPath, setOutputPath] = useState2(outputPathFor("image"));
|
|
392
|
+
const [caption, setCaption] = useState2("");
|
|
393
|
+
const [postDescription, setPostDescription] = useState2("");
|
|
394
|
+
const [mentions, setMentions] = useState2("");
|
|
395
|
+
const [cadence, setCadence] = useState2(CADENCES[0] ?? "Manual approval");
|
|
396
|
+
const [selectedDestinations, setSelectedDestinations] = useState2([]);
|
|
397
|
+
const [size, setSize] = useState2("1536x1024");
|
|
398
|
+
const [quality, setQuality] = useState2("high");
|
|
399
|
+
const [imageCount, setImageCount] = useState2(1);
|
|
400
|
+
const [duration, setDuration] = useState2("6");
|
|
401
|
+
const [resolution, setResolution] = useState2("720p");
|
|
402
|
+
const [aspectRatio, setAspectRatio] = useState2("16:9");
|
|
403
|
+
const [referenceImageUrl, setReferenceImageUrl] = useState2("");
|
|
404
|
+
const [voice, setVoice] = useState2("alloy");
|
|
405
|
+
const [avatarAudioUrl, setAvatarAudioUrl] = useState2("");
|
|
406
|
+
const [avatarImageUrl, setAvatarImageUrl] = useState2("");
|
|
407
|
+
const [avatarId, setAvatarId] = useState2("");
|
|
408
|
+
const [transcriptionAudioUrl, setTranscriptionAudioUrl] = useState2("");
|
|
409
|
+
const [transcriptionLanguage, setTranscriptionLanguage] = useState2("");
|
|
410
|
+
const [transcriptionResponseFormat, setTranscriptionResponseFormat] = useState2("json");
|
|
411
|
+
const [transcriptionTemperature, setTranscriptionTemperature] = useState2("0");
|
|
412
|
+
const [mediaModels, setMediaModels] = useState2(null);
|
|
413
|
+
const [mediaModelsLoading, setMediaModelsLoading] = useState2(false);
|
|
414
|
+
const [mediaModelsError, setMediaModelsError] = useState2(null);
|
|
415
|
+
const [selectedModels, setSelectedModels] = useState2({});
|
|
416
|
+
const submitLockRef = useRef2(false);
|
|
417
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
418
|
+
const [error, setError] = useState2(null);
|
|
419
|
+
const modelsForType = mediaModels?.models[type] ?? [];
|
|
420
|
+
const selectedModel = selectedModels[type] ?? preferredModelId(type, mediaModels) ?? "";
|
|
421
|
+
const selectedModelOption = modelsForType.find((model) => model.id === selectedModel);
|
|
422
|
+
const modelReady = Boolean(selectedModelOption) && selectedModelOption?.status !== "unavailable" && !mediaModelsLoading && !mediaModelsError;
|
|
423
|
+
const hasRequiredInput = type === "transcription" ? Boolean(transcriptionAudioUrl.trim()) : type === "avatar" ? Boolean(avatarAudioUrl.trim()) : Boolean(prompt.trim());
|
|
424
|
+
const canSubmit = Boolean(workspaceId) && modelReady && hasRequiredInput && !isSubmitting;
|
|
425
|
+
const integrations = useIntegrations({ apiBaseUrl: HUB_BASE_URL });
|
|
426
|
+
const selectedConnectedDestinations = useMemo2(() => selectedDestinations.filter((destinationId) => {
|
|
427
|
+
const destination = DESTINATIONS.find((item) => item.id === destinationId);
|
|
428
|
+
return Boolean(destination && isDestinationConnected(destination, integrations.connections));
|
|
429
|
+
}), [integrations.connections, selectedDestinations]);
|
|
430
|
+
useEffect2(() => {
|
|
431
|
+
if (!workspaceId) return;
|
|
432
|
+
let cancelled = false;
|
|
433
|
+
setMediaModelsLoading(true);
|
|
434
|
+
setMediaModelsError(null);
|
|
435
|
+
fetch(`/api/media-models?workspaceId=${encodeURIComponent(workspaceId)}`).then(async (res) => {
|
|
436
|
+
const data = await res.json();
|
|
437
|
+
if (!res.ok) throw new Error(data.error ?? "Could not load media models");
|
|
438
|
+
return data;
|
|
439
|
+
}).then((data) => {
|
|
440
|
+
if (cancelled) return;
|
|
441
|
+
setMediaModels(data);
|
|
442
|
+
setSelectedModels((current) => selectedModelsWithDefaults(current, data));
|
|
443
|
+
}).catch((err) => {
|
|
444
|
+
if (cancelled) return;
|
|
445
|
+
setMediaModels(null);
|
|
446
|
+
setMediaModelsError(err instanceof Error ? err.message : "Could not load media models");
|
|
447
|
+
}).finally(() => {
|
|
448
|
+
if (!cancelled) setMediaModelsLoading(false);
|
|
449
|
+
});
|
|
450
|
+
return () => {
|
|
451
|
+
cancelled = true;
|
|
452
|
+
};
|
|
453
|
+
}, [workspaceId]);
|
|
454
|
+
function changeType(next) {
|
|
455
|
+
setType(next);
|
|
456
|
+
setOutputPath(outputPathFor(next));
|
|
457
|
+
}
|
|
458
|
+
async function generate() {
|
|
459
|
+
if (!workspaceId || submitLockRef.current) return;
|
|
460
|
+
const promptText = prompt.trim();
|
|
461
|
+
const avatarAudioUrlText = avatarAudioUrl.trim();
|
|
462
|
+
const transcriptionAudioUrlText = transcriptionAudioUrl.trim();
|
|
463
|
+
if (type !== "transcription" && type !== "avatar" && !promptText) {
|
|
464
|
+
setError("prompt is required");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (type === "avatar" && !avatarAudioUrlText) {
|
|
468
|
+
setError("audioUrl is required");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (type === "transcription" && !transcriptionAudioUrlText) {
|
|
472
|
+
setError("audioUrl is required");
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (!selectedModelOption || selectedModelOption.status === "unavailable") {
|
|
476
|
+
setError(`Select an available ${typeConfigFor(type).label} model`);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (cadence === "Publish now" && selectedConnectedDestinations.length === 0) {
|
|
480
|
+
setError("Select a connected destination to publish now");
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
submitLockRef.current = true;
|
|
484
|
+
setIsSubmitting(true);
|
|
485
|
+
setError(null);
|
|
486
|
+
const clientRequestId = crypto.randomUUID();
|
|
487
|
+
const requestedImageCount = type === "image" ? normalizeImageCount(imageCount) : 1;
|
|
488
|
+
if (type === "image" && requestedImageCount !== imageCount) setImageCount(requestedImageCount);
|
|
489
|
+
const localGenerations = Array.from({ length: requestedImageCount }, (_, outputIndex) => optimisticGeneration({
|
|
490
|
+
type,
|
|
491
|
+
// avatar hides the prompt field (promptText is stale); transcription's is
|
|
492
|
+
// an optional vocab hint. Never surface the source audio URL as the prompt.
|
|
493
|
+
prompt: type === "avatar" ? "" : promptText,
|
|
494
|
+
model: selectedModel,
|
|
495
|
+
clientRequestId,
|
|
496
|
+
outputIndex: type === "image" ? outputIndex : void 0,
|
|
497
|
+
outputCount: type === "image" ? requestedImageCount : void 0
|
|
498
|
+
}));
|
|
499
|
+
localGenerations.slice().reverse().forEach(onGenerated);
|
|
500
|
+
setPrompt("");
|
|
501
|
+
let receivedServerGeneration = false;
|
|
502
|
+
try {
|
|
503
|
+
const body = buildGenerationRequestBody({
|
|
504
|
+
workspaceId,
|
|
505
|
+
clientRequestId,
|
|
506
|
+
type,
|
|
507
|
+
model: selectedModel,
|
|
508
|
+
prompt,
|
|
509
|
+
negativePrompt,
|
|
510
|
+
outputPath,
|
|
511
|
+
publishPackage: buildPublishPackage({
|
|
512
|
+
caption,
|
|
513
|
+
postDescription,
|
|
514
|
+
mentions,
|
|
515
|
+
cadence,
|
|
516
|
+
destinations: selectedConnectedDestinations
|
|
517
|
+
}),
|
|
518
|
+
image: { size, quality, count: requestedImageCount },
|
|
519
|
+
video: { duration, resolution, aspectRatio, referenceImageUrl },
|
|
520
|
+
speech: { voice },
|
|
521
|
+
avatar: { audioUrl: avatarAudioUrl, imageUrl: avatarImageUrl, avatarId },
|
|
522
|
+
transcription: {
|
|
523
|
+
audioUrl: transcriptionAudioUrl,
|
|
524
|
+
language: transcriptionLanguage,
|
|
525
|
+
responseFormat: transcriptionResponseFormat,
|
|
526
|
+
temperature: transcriptionTemperature
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
const res = await fetch("/api/generate", {
|
|
530
|
+
method: "POST",
|
|
531
|
+
headers: { "Content-Type": "application/json" },
|
|
532
|
+
body: JSON.stringify(body)
|
|
533
|
+
});
|
|
534
|
+
const data = await res.json();
|
|
535
|
+
const serverGenerations = data.generations?.length ? data.generations : data.generation ? [data.generation] : [];
|
|
536
|
+
if (serverGenerations.length > 0) {
|
|
537
|
+
receivedServerGeneration = true;
|
|
538
|
+
serverGenerations.slice().reverse().forEach(onGenerated);
|
|
539
|
+
}
|
|
540
|
+
if (!res.ok || serverGenerations.length === 0) throw new Error(data.error ?? "Generation failed");
|
|
541
|
+
} catch (err) {
|
|
542
|
+
if (!receivedServerGeneration) localGenerations.map(failedOptimisticGeneration).forEach(onGenerated);
|
|
543
|
+
setError(err instanceof Error ? userSafeGenerationMessage(err.message) : "Generation failed");
|
|
544
|
+
} finally {
|
|
545
|
+
submitLockRef.current = false;
|
|
546
|
+
setIsSubmitting(false);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const mediaTypes = Object.entries(TYPE_CONFIG);
|
|
550
|
+
const hasInlineParams = type === "image" || type === "video" || type === "speech";
|
|
551
|
+
return /* @__PURE__ */ jsxs7("section", { className: "rounded-xl border border-border bg-card p-5 shadow-sm", children: [
|
|
552
|
+
/* @__PURE__ */ jsx8(Field, { label: "Media type", className: "space-y-1.5", children: /* @__PURE__ */ jsx8(
|
|
553
|
+
"div",
|
|
554
|
+
{
|
|
555
|
+
role: "tablist",
|
|
556
|
+
"aria-label": "Generation type",
|
|
557
|
+
className: "grid grid-cols-5 gap-1 rounded-lg border border-border bg-muted/40 p-1",
|
|
558
|
+
children: mediaTypes.map(([key, cfg]) => {
|
|
559
|
+
const Icon = cfg.icon;
|
|
560
|
+
const active = type === key;
|
|
561
|
+
return /* @__PURE__ */ jsxs7(
|
|
562
|
+
"button",
|
|
563
|
+
{
|
|
564
|
+
type: "button",
|
|
565
|
+
role: "tab",
|
|
566
|
+
"aria-selected": active,
|
|
567
|
+
onClick: () => changeType(key),
|
|
568
|
+
className: `flex items-center justify-center gap-1.5 rounded-md px-2 py-2 text-xs font-medium transition-all ${active ? "bg-primary/15 text-primary shadow-sm ring-1 ring-inset ring-primary/30" : "text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground"}`,
|
|
569
|
+
children: [
|
|
570
|
+
/* @__PURE__ */ jsx8(Icon, { className: "h-4 w-4 shrink-0" }),
|
|
571
|
+
/* @__PURE__ */ jsx8("span", { className: "truncate", children: cfg.label })
|
|
572
|
+
]
|
|
573
|
+
},
|
|
574
|
+
key
|
|
575
|
+
);
|
|
576
|
+
})
|
|
577
|
+
}
|
|
578
|
+
) }),
|
|
579
|
+
type !== "avatar" && /* @__PURE__ */ jsxs7("div", { className: "mt-4 space-y-2", children: [
|
|
580
|
+
/* @__PURE__ */ jsx8(Label2, { htmlFor: "studio-prompt", className: "block text-sm font-medium", children: "What do you want to create?" }),
|
|
581
|
+
/* @__PURE__ */ jsx8(
|
|
582
|
+
Textarea2,
|
|
583
|
+
{
|
|
584
|
+
id: "studio-prompt",
|
|
585
|
+
value: prompt,
|
|
586
|
+
onChange: (event) => setPrompt(event.target.value),
|
|
587
|
+
onKeyDown: (event) => {
|
|
588
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
589
|
+
event.preventDefault();
|
|
590
|
+
void generate();
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
rows: 4,
|
|
594
|
+
placeholder: type === "transcription" ? "Optional vocabulary, speaker names, timestamp style, or context..." : "A vertical hero frame for a product launch teaser, dark cinematic lighting...",
|
|
595
|
+
className: "resize-none text-sm"
|
|
596
|
+
}
|
|
597
|
+
),
|
|
598
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
599
|
+
/* @__PURE__ */ jsx8("span", { children: "Try:" }),
|
|
600
|
+
SUGGESTIONS.map((suggestion) => /* @__PURE__ */ jsx8(
|
|
601
|
+
"button",
|
|
602
|
+
{
|
|
603
|
+
type: "button",
|
|
604
|
+
onClick: () => {
|
|
605
|
+
setPrompt(suggestion.prompt);
|
|
606
|
+
changeType(suggestion.type);
|
|
607
|
+
},
|
|
608
|
+
className: "rounded-full border border-border bg-background px-2.5 py-1 text-xs text-foreground/80 transition-colors hover:border-primary/40 hover:bg-muted",
|
|
609
|
+
children: suggestion.label
|
|
610
|
+
},
|
|
611
|
+
suggestion.label
|
|
612
|
+
))
|
|
613
|
+
] }),
|
|
614
|
+
type === "transcription" && /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted-foreground", children: "Optional \u2014 biases the transcript's spelling, vocabulary, and speaker names. It isn't an instruction for the model to follow." })
|
|
615
|
+
] }),
|
|
616
|
+
type === "avatar" && /* @__PURE__ */ jsx8(
|
|
617
|
+
AvatarComposer,
|
|
618
|
+
{
|
|
619
|
+
audioUrl: avatarAudioUrl,
|
|
620
|
+
imageUrl: avatarImageUrl,
|
|
621
|
+
avatarId,
|
|
622
|
+
onAudioUrlChange: setAvatarAudioUrl,
|
|
623
|
+
onImageUrlChange: setAvatarImageUrl,
|
|
624
|
+
onAvatarIdChange: setAvatarId
|
|
625
|
+
}
|
|
626
|
+
),
|
|
627
|
+
type === "transcription" && /* @__PURE__ */ jsx8(
|
|
628
|
+
TranscriptionComposer,
|
|
629
|
+
{
|
|
630
|
+
audioUrl: transcriptionAudioUrl,
|
|
631
|
+
language: transcriptionLanguage,
|
|
632
|
+
onAudioUrlChange: setTranscriptionAudioUrl,
|
|
633
|
+
onLanguageChange: setTranscriptionLanguage
|
|
634
|
+
}
|
|
635
|
+
),
|
|
636
|
+
/* @__PURE__ */ jsxs7("div", { className: "mt-4 grid gap-3 sm:grid-cols-2", children: [
|
|
637
|
+
/* @__PURE__ */ jsx8(
|
|
638
|
+
Field,
|
|
639
|
+
{
|
|
640
|
+
label: "Model",
|
|
641
|
+
htmlFor: "studio-media-model",
|
|
642
|
+
className: `space-y-1.5 ${hasInlineParams ? "" : "sm:col-span-2"}`,
|
|
643
|
+
children: /* @__PURE__ */ jsxs7(
|
|
644
|
+
Select,
|
|
645
|
+
{
|
|
646
|
+
value: selectedModel || void 0,
|
|
647
|
+
onValueChange: (value) => setSelectedModels((current) => ({ ...current, [type]: value })),
|
|
648
|
+
disabled: mediaModelsLoading || Boolean(mediaModelsError) || modelsForType.length === 0,
|
|
649
|
+
children: [
|
|
650
|
+
/* @__PURE__ */ jsx8(SelectTrigger, { id: "studio-media-model", className: "h-9 w-full", children: /* @__PURE__ */ jsx8(SelectValue, { placeholder: mediaModelsLoading ? "Loading models\u2026" : "Select a model" }) }),
|
|
651
|
+
/* @__PURE__ */ jsx8(SelectContent, { children: modelsForType.map((model) => /* @__PURE__ */ jsx8(SelectItem, { value: model.id, disabled: model.status === "unavailable", children: /* @__PURE__ */ jsxs7("span", { className: "flex w-full items-center justify-between gap-3", children: [
|
|
652
|
+
/* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
|
|
653
|
+
model.name || model.id,
|
|
654
|
+
model.provider ? ` \xB7 ${model.provider}` : ""
|
|
655
|
+
] }),
|
|
656
|
+
model.status !== "available" && /* @__PURE__ */ jsx8("span", { className: "shrink-0 text-[10px] capitalize text-muted-foreground", children: model.status })
|
|
657
|
+
] }) }, model.id)) })
|
|
658
|
+
]
|
|
659
|
+
}
|
|
660
|
+
)
|
|
661
|
+
}
|
|
662
|
+
),
|
|
663
|
+
type === "image" && /* @__PURE__ */ jsx8(
|
|
664
|
+
ImageComposer,
|
|
665
|
+
{
|
|
666
|
+
size,
|
|
667
|
+
quality,
|
|
668
|
+
imageCount,
|
|
669
|
+
onSizeChange: setSize,
|
|
670
|
+
onQualityChange: setQuality,
|
|
671
|
+
onImageCountChange: setImageCount
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
type === "video" && /* @__PURE__ */ jsx8(
|
|
675
|
+
VideoComposer,
|
|
676
|
+
{
|
|
677
|
+
duration,
|
|
678
|
+
resolution,
|
|
679
|
+
aspectRatio,
|
|
680
|
+
referenceImageUrl,
|
|
681
|
+
onDurationChange: setDuration,
|
|
682
|
+
onResolutionChange: setResolution,
|
|
683
|
+
onAspectRatioChange: setAspectRatio,
|
|
684
|
+
onReferenceImageUrlChange: setReferenceImageUrl
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
type === "speech" && /* @__PURE__ */ jsx8(SpeechComposer, { voice, onVoiceChange: setVoice })
|
|
688
|
+
] }),
|
|
689
|
+
(mediaModelsError || modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length)) && /* @__PURE__ */ jsx8("p", { className: `mt-2 text-xs ${mediaModelsError || selectedModelOption?.status === "unavailable" ? "text-destructive" : "text-muted-foreground"}`, children: mediaModelsError ?? modelMessage(selectedModelOption, mediaModelsLoading, modelsForType.length) }),
|
|
690
|
+
error && /* @__PURE__ */ jsx8("div", { className: "mt-3 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive", children: error }),
|
|
691
|
+
/* @__PURE__ */ jsxs7("div", { className: "mt-4 space-y-2", children: [
|
|
692
|
+
/* @__PURE__ */ jsx8(ComposerDisclosure, { summary: "Advanced options", children: /* @__PURE__ */ jsxs7("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
693
|
+
/* @__PURE__ */ jsx8(Field, { label: "Negative prompt", children: /* @__PURE__ */ jsx8(
|
|
694
|
+
Textarea2,
|
|
695
|
+
{
|
|
696
|
+
value: negativePrompt,
|
|
697
|
+
onChange: (event) => setNegativePrompt(event.target.value),
|
|
698
|
+
rows: 2,
|
|
699
|
+
placeholder: "Avoid artifacts, off-style composition..."
|
|
700
|
+
}
|
|
701
|
+
) }),
|
|
702
|
+
/* @__PURE__ */ jsx8(Field, { label: "Save to", children: /* @__PURE__ */ jsx8(Input7, { value: outputPath, onChange: (event) => setOutputPath(event.target.value) }) }),
|
|
703
|
+
type === "transcription" && /* @__PURE__ */ jsx8(
|
|
704
|
+
TranscriptionOptions,
|
|
705
|
+
{
|
|
706
|
+
responseFormat: transcriptionResponseFormat,
|
|
707
|
+
temperature: transcriptionTemperature,
|
|
708
|
+
onResponseFormatChange: setTranscriptionResponseFormat,
|
|
709
|
+
onTemperatureChange: setTranscriptionTemperature
|
|
710
|
+
}
|
|
711
|
+
)
|
|
712
|
+
] }) }),
|
|
713
|
+
/* @__PURE__ */ jsx8(
|
|
714
|
+
ComposerDisclosure,
|
|
715
|
+
{
|
|
716
|
+
summary: /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
717
|
+
"Schedule a post",
|
|
718
|
+
selectedConnectedDestinations.length > 0 && /* @__PURE__ */ jsx8(Badge2, { variant: "outline", className: "ml-1 text-[10px]", children: selectedConnectedDestinations.length })
|
|
719
|
+
] }),
|
|
720
|
+
children: /* @__PURE__ */ jsx8(
|
|
721
|
+
PublishPackageComposer,
|
|
722
|
+
{
|
|
723
|
+
caption,
|
|
724
|
+
postDescription,
|
|
725
|
+
mentions,
|
|
726
|
+
cadence,
|
|
727
|
+
selectedDestinations,
|
|
728
|
+
connections: integrations.connections,
|
|
729
|
+
connectionError: integrations.error,
|
|
730
|
+
connectionsLoading: integrations.isLoading,
|
|
731
|
+
integrationsHref,
|
|
732
|
+
canManageIntegrations,
|
|
733
|
+
onCaptionChange: setCaption,
|
|
734
|
+
onDescriptionChange: setPostDescription,
|
|
735
|
+
onMentionsChange: setMentions,
|
|
736
|
+
onCadenceChange: setCadence,
|
|
737
|
+
onDestinationToggle: (destination) => {
|
|
738
|
+
const destinationConfig = DESTINATIONS.find((item) => item.id === destination);
|
|
739
|
+
if (!destinationConfig || !isDestinationConnected(destinationConfig, integrations.connections)) return;
|
|
740
|
+
setSelectedDestinations((current) => current.includes(destination) ? current.filter((item) => item !== destination) : [...current, destination]);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
)
|
|
746
|
+
] }),
|
|
747
|
+
/* @__PURE__ */ jsxs7(Button2, { onClick: generate, disabled: !canSubmit, size: "lg", className: "mt-4 w-full", children: [
|
|
748
|
+
/* @__PURE__ */ jsx8(Sparkles, { className: "mr-2 h-4 w-4" }),
|
|
749
|
+
"Generate",
|
|
750
|
+
/* @__PURE__ */ jsx8("span", { className: "ml-2 text-[10px] opacity-60", children: "\u2318\u21B5" })
|
|
751
|
+
] })
|
|
752
|
+
] });
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/studio-react/studio-header.tsx
|
|
756
|
+
import { Button as Button3 } from "@tangle-network/sandbox-ui/primitives";
|
|
757
|
+
import { Images } from "lucide-react";
|
|
758
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
759
|
+
function StudioHeader({
|
|
760
|
+
count,
|
|
761
|
+
onOpenLibrary,
|
|
762
|
+
canGenerate
|
|
763
|
+
}) {
|
|
764
|
+
return /* @__PURE__ */ jsx9("header", { className: "sticky top-0 z-20 border-b border-border bg-background/80 backdrop-blur-md", children: /* @__PURE__ */ jsxs8("div", { className: "mx-auto flex w-full max-w-3xl items-center justify-between gap-3 px-4 py-3 sm:px-6", children: [
|
|
765
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
|
|
766
|
+
/* @__PURE__ */ jsx9("h1", { className: "text-lg font-semibold tracking-tight text-foreground", children: "Studio" }),
|
|
767
|
+
/* @__PURE__ */ jsx9("p", { className: "truncate text-xs text-muted-foreground", children: canGenerate ? "Generate images, video, voice, avatars, and transcripts." : "Browse images, video, voice, avatars, and transcripts your team has created." })
|
|
768
|
+
] }),
|
|
769
|
+
/* @__PURE__ */ jsxs8(Button3, { size: "sm", variant: "outline", onClick: onOpenLibrary, className: "shrink-0", children: [
|
|
770
|
+
/* @__PURE__ */ jsx9(Images, { className: "mr-1.5 h-4 w-4" }),
|
|
771
|
+
"Library",
|
|
772
|
+
count > 0 && /* @__PURE__ */ jsx9("span", { className: "ml-2 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary/15 px-1.5 text-[10px] font-semibold text-primary", children: count })
|
|
773
|
+
] })
|
|
774
|
+
] }) });
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/studio-react/result-canvas.tsx
|
|
778
|
+
import { Badge as Badge3 } from "@tangle-network/sandbox-ui/primitives";
|
|
779
|
+
import { ArrowRight, Sparkles as Sparkles2 } from "lucide-react";
|
|
780
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
781
|
+
function ResultCanvas({
|
|
782
|
+
batch,
|
|
783
|
+
onOpenLibrary,
|
|
784
|
+
onSelect
|
|
785
|
+
}) {
|
|
786
|
+
const cardClass = "rounded-xl border border-border bg-card shadow-sm";
|
|
787
|
+
if (batch.length === 0) {
|
|
788
|
+
return /* @__PURE__ */ jsxs9("section", { className: `${cardClass} flex min-h-[180px] flex-col items-center justify-center gap-2 p-8 text-center`, children: [
|
|
789
|
+
/* @__PURE__ */ jsx10("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx10(Sparkles2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
|
|
790
|
+
/* @__PURE__ */ jsx10("p", { className: "text-sm font-medium text-foreground", children: "Your creations appear here" }),
|
|
791
|
+
/* @__PURE__ */ jsx10("p", { className: "max-w-xs text-xs text-muted-foreground", children: "Write a prompt above and hit Generate \u2014 your latest result blooms here, and everything lives in your library." })
|
|
792
|
+
] });
|
|
793
|
+
}
|
|
794
|
+
const cfg = typeConfigFor(batch[0]?.type ?? "image");
|
|
795
|
+
const statuses = batch.map(generationStatus);
|
|
796
|
+
const runLabel = statuses.some((s) => s === "pending" || s === "running") ? "Generating\u2026" : statuses.every((s) => s === "failed") ? "Last run failed" : "Latest creation";
|
|
797
|
+
const isWorking = statuses.some((s) => s === "pending" || s === "running");
|
|
798
|
+
return /* @__PURE__ */ jsxs9("section", { className: `${cardClass} p-5`, children: [
|
|
799
|
+
/* @__PURE__ */ jsxs9("div", { className: "mb-3 flex items-center justify-between gap-3", children: [
|
|
800
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
801
|
+
/* @__PURE__ */ jsx10(Badge3, { variant: "outline", className: `${cfg.color} text-[10px]`, children: cfg.label }),
|
|
802
|
+
/* @__PURE__ */ jsx10("span", { className: "text-sm font-medium text-foreground", children: runLabel }),
|
|
803
|
+
isWorking && /* @__PURE__ */ jsx10("span", { className: "h-2 w-2 animate-pulse rounded-full bg-primary" })
|
|
804
|
+
] }),
|
|
805
|
+
/* @__PURE__ */ jsxs9(
|
|
806
|
+
"button",
|
|
807
|
+
{
|
|
808
|
+
type: "button",
|
|
809
|
+
onClick: onOpenLibrary,
|
|
810
|
+
className: "flex items-center gap-1 text-xs font-medium text-primary transition-colors hover:text-primary/80",
|
|
811
|
+
children: [
|
|
812
|
+
"View in Library",
|
|
813
|
+
/* @__PURE__ */ jsx10(ArrowRight, { className: "h-3.5 w-3.5" })
|
|
814
|
+
]
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] }),
|
|
818
|
+
/* @__PURE__ */ jsx10("div", { className: `grid gap-3 ${batch.length === 1 ? "grid-cols-1" : "grid-cols-2"}`, children: batch.map((generation) => /* @__PURE__ */ jsx10(
|
|
819
|
+
"button",
|
|
820
|
+
{
|
|
821
|
+
type: "button",
|
|
822
|
+
onClick: () => onSelect(generation),
|
|
823
|
+
className: "studio-bloom group relative aspect-video overflow-hidden rounded-lg border border-border bg-muted/40 transition-colors hover:border-primary/40",
|
|
824
|
+
children: /* @__PURE__ */ jsx10(CanvasTile, { generation })
|
|
825
|
+
},
|
|
826
|
+
generation.id
|
|
827
|
+
)) }),
|
|
828
|
+
batch.length > 1 && /* @__PURE__ */ jsxs9("p", { className: "mt-2 text-xs text-muted-foreground", children: [
|
|
829
|
+
batch.length,
|
|
830
|
+
" results from this run"
|
|
831
|
+
] })
|
|
832
|
+
] });
|
|
833
|
+
}
|
|
834
|
+
function CanvasTile({ generation }) {
|
|
835
|
+
const cfg = typeConfigFor(generation.type);
|
|
836
|
+
const Icon = cfg.icon;
|
|
837
|
+
const status = generationStatus(generation);
|
|
838
|
+
if (status === "pending" || status === "running") {
|
|
839
|
+
return /* @__PURE__ */ jsx10("div", { className: "studio-shimmer absolute inset-0 h-full w-full" });
|
|
840
|
+
}
|
|
841
|
+
if (status === "failed") {
|
|
842
|
+
return /* @__PURE__ */ jsxs9("div", { className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4 text-center", children: [
|
|
843
|
+
/* @__PURE__ */ jsx10(Icon, { className: "h-6 w-6 text-destructive/50" }),
|
|
844
|
+
/* @__PURE__ */ jsx10("p", { className: "line-clamp-3 text-xs text-destructive", children: generationError(generation) ?? "Generation failed" })
|
|
845
|
+
] });
|
|
846
|
+
}
|
|
847
|
+
if (generation.type === "image" && generation.result) {
|
|
848
|
+
return /* @__PURE__ */ jsx10("img", { src: generation.result, alt: generation.prompt, className: "absolute inset-0 h-full w-full object-contain" });
|
|
849
|
+
}
|
|
850
|
+
if ((generation.type === "video" || generation.type === "avatar") && generation.result) {
|
|
851
|
+
return /* @__PURE__ */ jsx10("video", { src: generation.result, controls: true, className: "absolute inset-0 h-full w-full object-contain" });
|
|
852
|
+
}
|
|
853
|
+
if (generation.type === "speech" && generation.result) {
|
|
854
|
+
return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center p-4", children: /* @__PURE__ */ jsx10("audio", { src: generation.result, controls: true, className: "w-full" }) });
|
|
855
|
+
}
|
|
856
|
+
if (generation.type === "transcription" && generation.result) {
|
|
857
|
+
return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 overflow-y-auto p-4", children: /* @__PURE__ */ jsx10("p", { className: "whitespace-pre-wrap text-xs text-foreground", children: generation.result }) });
|
|
858
|
+
}
|
|
859
|
+
return /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx10(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) });
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// src/studio-react/library-drawer.tsx
|
|
863
|
+
import { useMemo as useMemo3 } from "react";
|
|
864
|
+
import { Link as Link3 } from "react-router";
|
|
865
|
+
import {
|
|
866
|
+
Button as Button5,
|
|
867
|
+
EmptyState,
|
|
868
|
+
Tabs,
|
|
869
|
+
TabsList,
|
|
870
|
+
TabsTrigger
|
|
871
|
+
} from "@tangle-network/sandbox-ui/primitives";
|
|
872
|
+
import { ArrowLeft, DollarSign, Film as Film2, FolderOpen as FolderOpen2, Sparkles as Sparkles3, X } from "lucide-react";
|
|
873
|
+
|
|
874
|
+
// src/studio-react/studio-sheet.tsx
|
|
875
|
+
import * as Dialog from "@radix-ui/react-dialog";
|
|
876
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
877
|
+
function StudioSheet({
|
|
878
|
+
open,
|
|
879
|
+
onOpenChange,
|
|
880
|
+
title,
|
|
881
|
+
children
|
|
882
|
+
}) {
|
|
883
|
+
return /* @__PURE__ */ jsx11(Dialog.Root, { open, onOpenChange, children: /* @__PURE__ */ jsxs10(Dialog.Portal, { children: [
|
|
884
|
+
/* @__PURE__ */ jsx11(Dialog.Overlay, { className: "studio-sheet-overlay fixed inset-0 z-50 bg-black/50 backdrop-blur-sm" }),
|
|
885
|
+
/* @__PURE__ */ jsxs10(
|
|
886
|
+
Dialog.Content,
|
|
887
|
+
{
|
|
888
|
+
className: "studio-sheet-content fixed inset-y-0 right-0 z-50 flex w-[min(92vw,30rem)] flex-col border-l border-border bg-card shadow-lg focus:outline-none",
|
|
889
|
+
"aria-describedby": void 0,
|
|
890
|
+
children: [
|
|
891
|
+
/* @__PURE__ */ jsx11(Dialog.Title, { className: "sr-only", children: title }),
|
|
892
|
+
children
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
)
|
|
896
|
+
] }) });
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/studio-react/generation-card.tsx
|
|
900
|
+
import { Card, CardContent, Badge as Badge4 } from "@tangle-network/sandbox-ui/primitives";
|
|
901
|
+
import { Send } from "lucide-react";
|
|
902
|
+
import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
903
|
+
function GenerationCard({
|
|
904
|
+
generation,
|
|
905
|
+
onSelect
|
|
906
|
+
}) {
|
|
907
|
+
const cfg = typeConfigFor(generation.type);
|
|
908
|
+
const Icon = cfg.icon;
|
|
909
|
+
const status = generationStatus(generation);
|
|
910
|
+
const publishPackage = generation.metadata?.publishPackage;
|
|
911
|
+
return /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => onSelect(generation), className: "group text-left animate-row-in", children: /* @__PURE__ */ jsxs11(Card, { className: "overflow-hidden transition-all group-hover:border-primary/50 group-hover:shadow-md", children: [
|
|
912
|
+
/* @__PURE__ */ jsxs11("div", { className: "relative aspect-video bg-muted/50", children: [
|
|
913
|
+
generation.type === "image" && generation.result ? /* @__PURE__ */ jsx12("img", { src: generation.result, alt: generation.prompt, className: "h-full w-full object-cover" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx12("video", { src: generation.result, className: "h-full w-full object-cover", muted: true }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
914
|
+
/* @__PURE__ */ jsx12(Icon, { className: "h-6 w-6 text-muted-foreground/40" }),
|
|
915
|
+
/* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: "Audio" })
|
|
916
|
+
] }) }) : generation.type === "transcription" && generation.result ? /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center p-4", children: /* @__PURE__ */ jsx12("p", { className: "line-clamp-5 text-xs text-muted-foreground", children: generation.result }) }) : status === "pending" || status === "running" ? /* @__PURE__ */ jsx12("div", { className: "shimmer h-full w-full" }) : /* @__PURE__ */ jsx12("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx12(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }),
|
|
917
|
+
/* @__PURE__ */ jsx12("div", { className: "absolute left-2 top-2", children: /* @__PURE__ */ jsx12(Badge4, { variant: "outline", className: `${cfg.color} text-[10px] backdrop-blur-sm`, children: cfg.label }) }),
|
|
918
|
+
/* @__PURE__ */ jsx12(GenerationStatusBadge, { generation })
|
|
919
|
+
] }),
|
|
920
|
+
/* @__PURE__ */ jsxs11(CardContent, { className: "p-3", children: [
|
|
921
|
+
/* @__PURE__ */ jsx12("p", { className: "mb-2 line-clamp-2 text-xs text-foreground", children: generation.prompt }),
|
|
922
|
+
status === "failed" && /* @__PURE__ */ jsx12("p", { className: "mb-2 line-clamp-2 text-[10px] text-destructive", children: generationError(generation) }),
|
|
923
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between", children: [
|
|
924
|
+
generation.model && /* @__PURE__ */ jsx12("span", { className: "truncate text-[10px] text-muted-foreground", children: generation.model }),
|
|
925
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex shrink-0 items-center gap-2", children: [
|
|
926
|
+
generation.cost != null && /* @__PURE__ */ jsxs11("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
927
|
+
"$",
|
|
928
|
+
generation.cost.toFixed(3)
|
|
929
|
+
] }),
|
|
930
|
+
/* @__PURE__ */ jsx12("span", { className: "text-[10px] text-muted-foreground", children: relativeTime(generation.createdAt) })
|
|
931
|
+
] })
|
|
932
|
+
] }),
|
|
933
|
+
isPublishPackage(publishPackage) && /* @__PURE__ */ jsxs11("div", { className: "mt-3 rounded-md border border-border bg-muted/30 p-2", children: [
|
|
934
|
+
/* @__PURE__ */ jsxs11("div", { className: "mb-1 flex items-center gap-1.5 text-[10px] font-medium text-foreground", children: [
|
|
935
|
+
/* @__PURE__ */ jsx12(Send, { className: "h-3 w-3" }),
|
|
936
|
+
(publishPackage.destinations ?? []).join(", ") || "Publish package"
|
|
937
|
+
] }),
|
|
938
|
+
/* @__PURE__ */ jsx12("p", { className: "line-clamp-2 text-[10px] text-muted-foreground", children: publishPackage.caption || "Caption pending" })
|
|
939
|
+
] })
|
|
940
|
+
] })
|
|
941
|
+
] }) });
|
|
942
|
+
}
|
|
943
|
+
function GenerationStatusBadge({
|
|
944
|
+
generation,
|
|
945
|
+
inline = false
|
|
946
|
+
}) {
|
|
947
|
+
const status = generationStatus(generation);
|
|
948
|
+
if (status === "succeeded") return null;
|
|
949
|
+
const label = status === "failed" ? "Failed" : status === "running" ? "Running" : "Pending";
|
|
950
|
+
const className = status === "failed" ? "border-destructive/25 bg-destructive/10 text-destructive" : "border-warning/25 bg-warning/10 text-warning";
|
|
951
|
+
return /* @__PURE__ */ jsx12("div", { className: inline ? "" : "absolute bottom-2 left-2", children: /* @__PURE__ */ jsx12(Badge4, { variant: "outline", className: `${className} text-[10px] backdrop-blur-sm`, children: label }) });
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/studio-react/generation-detail.tsx
|
|
955
|
+
import { Link as Link2 } from "react-router";
|
|
956
|
+
import { Badge as Badge5, Button as Button4 } from "@tangle-network/sandbox-ui/primitives";
|
|
957
|
+
import { FolderOpen } from "lucide-react";
|
|
958
|
+
import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
959
|
+
function GenerationDetail({
|
|
960
|
+
generation,
|
|
961
|
+
vaultHref,
|
|
962
|
+
onNavigate
|
|
963
|
+
}) {
|
|
964
|
+
const cfg = typeConfigFor(generation.type);
|
|
965
|
+
const Icon = cfg.icon;
|
|
966
|
+
const href = vaultHref ? vaultHref(generationVaultPath(generation)) : null;
|
|
967
|
+
return /* @__PURE__ */ jsxs12("div", { className: "space-y-4", children: [
|
|
968
|
+
/* @__PURE__ */ jsx13("div", { className: "overflow-hidden rounded-lg bg-muted/50", children: generation.type === "image" && generation.result ? /* @__PURE__ */ jsx13("img", { src: generation.result, alt: generation.prompt, className: "max-h-[420px] w-full object-contain" }) : (generation.type === "video" || generation.type === "avatar") && generation.result ? /* @__PURE__ */ jsx13("video", { src: generation.result, controls: true, className: "max-h-[420px] w-full" }) : generation.type === "speech" && generation.result ? /* @__PURE__ */ jsx13("div", { className: "p-6", children: /* @__PURE__ */ jsx13("audio", { src: generation.result, controls: true, className: "w-full" }) }) : /* @__PURE__ */ jsx13("div", { className: "flex h-32 items-center justify-center", children: /* @__PURE__ */ jsx13(Icon, { className: "h-8 w-8 text-muted-foreground/20" }) }) }),
|
|
969
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
970
|
+
/* @__PURE__ */ jsx13(Badge5, { variant: "outline", className: cfg.color, children: cfg.label }),
|
|
971
|
+
/* @__PURE__ */ jsx13(GenerationStatusBadge, { generation, inline: true }),
|
|
972
|
+
generationError(generation) && /* @__PURE__ */ jsx13("span", { className: "text-xs text-destructive", children: generationError(generation) })
|
|
973
|
+
] }),
|
|
974
|
+
/* @__PURE__ */ jsxs12("div", { children: [
|
|
975
|
+
/* @__PURE__ */ jsx13("span", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Prompt" }),
|
|
976
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sm text-foreground", children: generation.prompt })
|
|
977
|
+
] }),
|
|
978
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-4", children: [
|
|
979
|
+
generation.model && /* @__PURE__ */ jsxs12("div", { children: [
|
|
980
|
+
/* @__PURE__ */ jsx13("span", { className: "block text-[10px] font-medium text-muted-foreground", children: "Model" }),
|
|
981
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs text-foreground", children: generation.model })
|
|
982
|
+
] }),
|
|
983
|
+
generation.cost != null && /* @__PURE__ */ jsxs12("div", { children: [
|
|
984
|
+
/* @__PURE__ */ jsx13("span", { className: "block text-[10px] font-medium text-muted-foreground", children: "Cost" }),
|
|
985
|
+
/* @__PURE__ */ jsxs12("span", { className: "text-xs text-foreground", children: [
|
|
986
|
+
"$",
|
|
987
|
+
generation.cost.toFixed(4)
|
|
988
|
+
] })
|
|
989
|
+
] }),
|
|
990
|
+
generation.createdAt && /* @__PURE__ */ jsxs12("div", { children: [
|
|
991
|
+
/* @__PURE__ */ jsx13("span", { className: "block text-[10px] font-medium text-muted-foreground", children: "Created" }),
|
|
992
|
+
/* @__PURE__ */ jsx13("span", { className: "text-xs text-foreground", children: new Date(generation.createdAt).toLocaleString() })
|
|
993
|
+
] })
|
|
994
|
+
] }),
|
|
995
|
+
generation.type === "transcription" && generation.result && /* @__PURE__ */ jsxs12("div", { children: [
|
|
996
|
+
/* @__PURE__ */ jsx13("span", { className: "mb-1 block text-xs font-medium text-muted-foreground", children: "Transcription" }),
|
|
997
|
+
/* @__PURE__ */ jsx13("pre", { className: "max-h-64 overflow-y-auto whitespace-pre-wrap rounded-lg bg-muted/50 p-4 text-sm text-foreground", children: generation.result })
|
|
998
|
+
] }),
|
|
999
|
+
href && /* @__PURE__ */ jsx13("div", { className: "border-t border-border pt-2", children: /* @__PURE__ */ jsx13(Link2, { to: href, prefetch: "intent", onClick: onNavigate, children: /* @__PURE__ */ jsxs12(Button4, { size: "sm", variant: "outline", children: [
|
|
1000
|
+
/* @__PURE__ */ jsx13(FolderOpen, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1001
|
+
"Open in Vault"
|
|
1002
|
+
] }) }) })
|
|
1003
|
+
] });
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/studio-react/library-drawer.tsx
|
|
1007
|
+
import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1008
|
+
function LibraryDrawer({
|
|
1009
|
+
open,
|
|
1010
|
+
onOpenChange,
|
|
1011
|
+
generations,
|
|
1012
|
+
totalCost,
|
|
1013
|
+
typeFilter,
|
|
1014
|
+
onFilterChange,
|
|
1015
|
+
vaultHref,
|
|
1016
|
+
selected,
|
|
1017
|
+
onSelect
|
|
1018
|
+
}) {
|
|
1019
|
+
const visible = useMemo3(() => {
|
|
1020
|
+
if (!typeFilter || !isGenerationType(typeFilter)) return generations;
|
|
1021
|
+
return generations.filter((generation) => generation.type === typeFilter);
|
|
1022
|
+
}, [generations, typeFilter]);
|
|
1023
|
+
return /* @__PURE__ */ jsxs13(StudioSheet, { open, onOpenChange, title: "Asset library", children: [
|
|
1024
|
+
/* @__PURE__ */ jsxs13("header", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3", children: [
|
|
1025
|
+
selected ? /* @__PURE__ */ jsxs13(
|
|
1026
|
+
"button",
|
|
1027
|
+
{
|
|
1028
|
+
type: "button",
|
|
1029
|
+
onClick: () => onSelect(null),
|
|
1030
|
+
className: "flex items-center gap-1.5 text-sm font-medium text-foreground transition-colors hover:text-primary",
|
|
1031
|
+
children: [
|
|
1032
|
+
/* @__PURE__ */ jsx14(ArrowLeft, { className: "h-4 w-4" }),
|
|
1033
|
+
"Back to library"
|
|
1034
|
+
]
|
|
1035
|
+
}
|
|
1036
|
+
) : /* @__PURE__ */ jsx14("h2", { className: "text-sm font-semibold text-foreground", children: "Asset library" }),
|
|
1037
|
+
/* @__PURE__ */ jsx14(
|
|
1038
|
+
"button",
|
|
1039
|
+
{
|
|
1040
|
+
type: "button",
|
|
1041
|
+
"aria-label": "Close library",
|
|
1042
|
+
onClick: () => onOpenChange(false),
|
|
1043
|
+
className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
1044
|
+
children: /* @__PURE__ */ jsx14(X, { className: "h-4 w-4" })
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
] }),
|
|
1048
|
+
selected ? /* @__PURE__ */ jsx14("div", { className: "flex-1 overflow-y-auto p-4", children: /* @__PURE__ */ jsx14(
|
|
1049
|
+
GenerationDetail,
|
|
1050
|
+
{
|
|
1051
|
+
generation: selected,
|
|
1052
|
+
vaultHref,
|
|
1053
|
+
onNavigate: () => onOpenChange(false)
|
|
1054
|
+
}
|
|
1055
|
+
) }) : /* @__PURE__ */ jsxs13(Fragment5, { children: [
|
|
1056
|
+
/* @__PURE__ */ jsxs13("div", { className: "shrink-0 border-b border-border px-4 pt-3", children: [
|
|
1057
|
+
/* @__PURE__ */ jsxs13("div", { className: "mb-2 flex items-center justify-between gap-3", children: [
|
|
1058
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
|
|
1059
|
+
/* @__PURE__ */ jsxs13("span", { children: [
|
|
1060
|
+
visible.length,
|
|
1061
|
+
" generation",
|
|
1062
|
+
visible.length !== 1 ? "s" : ""
|
|
1063
|
+
] }),
|
|
1064
|
+
/* @__PURE__ */ jsxs13("span", { className: "flex items-center gap-0.5", children: [
|
|
1065
|
+
/* @__PURE__ */ jsx14(DollarSign, { className: "h-3 w-3" }),
|
|
1066
|
+
totalCost.toFixed(2),
|
|
1067
|
+
" spent"
|
|
1068
|
+
] })
|
|
1069
|
+
] }),
|
|
1070
|
+
vaultHref && /* @__PURE__ */ jsx14(Link3, { to: vaultHref(), prefetch: "intent", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsxs13(Button5, { size: "sm", variant: "outline", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx14(FolderOpen2, { className: "mr-1.5 h-3.5 w-3.5" }),
|
|
1072
|
+
"Vault"
|
|
1073
|
+
] }) })
|
|
1074
|
+
] }),
|
|
1075
|
+
/* @__PURE__ */ jsx14(Tabs, { value: typeFilter ?? "all", onValueChange: (v) => onFilterChange(v === "all" ? null : v), children: /* @__PURE__ */ jsxs13(TabsList, { className: "h-9 w-full justify-start gap-1 overflow-x-auto bg-transparent", children: [
|
|
1076
|
+
/* @__PURE__ */ jsx14(TabsTrigger, { value: "all", className: "text-xs", children: "All" }),
|
|
1077
|
+
Object.entries(TYPE_CONFIG).map(([key, cfg]) => /* @__PURE__ */ jsx14(TabsTrigger, { value: key, className: "text-xs", children: cfg.label }, key))
|
|
1078
|
+
] }) })
|
|
1079
|
+
] }),
|
|
1080
|
+
/* @__PURE__ */ jsx14("div", { className: "flex-1 overflow-y-auto p-4", children: visible.length === 0 ? /* @__PURE__ */ jsx14(
|
|
1081
|
+
EmptyState,
|
|
1082
|
+
{
|
|
1083
|
+
icon: typeFilter ? /* @__PURE__ */ jsx14(Film2, { className: "h-8 w-8 text-muted-foreground/30" }) : /* @__PURE__ */ jsx14(Sparkles3, { className: "h-10 w-10 text-muted-foreground/30" }),
|
|
1084
|
+
title: typeFilter ? `No ${TYPE_CONFIG[typeFilter]?.label ?? typeFilter} generations` : "No generations yet",
|
|
1085
|
+
description: typeFilter ? "Try a different filter or change the type in the composer." : "Everything you and the agent create lives here.",
|
|
1086
|
+
className: "py-16"
|
|
1087
|
+
}
|
|
1088
|
+
) : /* @__PURE__ */ jsx14("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2", children: visible.map((generation) => /* @__PURE__ */ jsx14(GenerationCard, { generation, onSelect }, generation.id)) }) })
|
|
1089
|
+
] })
|
|
1090
|
+
] });
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// src/studio-react/studio-workspace.tsx
|
|
1094
|
+
import { Fragment as Fragment6, jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1095
|
+
function StudioWorkspace({
|
|
1096
|
+
generations,
|
|
1097
|
+
totalCost,
|
|
1098
|
+
workspaceId,
|
|
1099
|
+
role,
|
|
1100
|
+
generationsEndpoint,
|
|
1101
|
+
vaultHref,
|
|
1102
|
+
integrationsHref
|
|
1103
|
+
}) {
|
|
1104
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
1105
|
+
const typeFilter = searchParams.get("type");
|
|
1106
|
+
const [drawerOpen, setDrawerOpen] = useState3(false);
|
|
1107
|
+
const [selected, setSelected] = useState3(null);
|
|
1108
|
+
const canvasRef = useRef3(null);
|
|
1109
|
+
const canGenerate = role !== "viewer";
|
|
1110
|
+
const canManageIntegrations = role === "owner" || role === "admin";
|
|
1111
|
+
const resolvedVaultHref = vaultHref ?? (workspaceId ? (filePath) => filePath ? `/app/${workspaceId}/vault?file=${encodeURIComponent(filePath)}` : `/app/${workspaceId}/vault` : void 0);
|
|
1112
|
+
const resolvedIntegrationsHref = integrationsHref ?? (workspaceId ? `/app/${workspaceId}/integrations` : void 0);
|
|
1113
|
+
const { mergedGenerations, latestBatch, onGenerated } = useStudioGenerations(generations, {
|
|
1114
|
+
workspaceId,
|
|
1115
|
+
generationsEndpoint
|
|
1116
|
+
});
|
|
1117
|
+
function setFilter(type) {
|
|
1118
|
+
if (type) setSearchParams({ type });
|
|
1119
|
+
else setSearchParams({});
|
|
1120
|
+
}
|
|
1121
|
+
function openLibrary() {
|
|
1122
|
+
setSelected(null);
|
|
1123
|
+
setDrawerOpen(true);
|
|
1124
|
+
}
|
|
1125
|
+
function openDetail(generation) {
|
|
1126
|
+
setSelected(generation);
|
|
1127
|
+
setDrawerOpen(true);
|
|
1128
|
+
}
|
|
1129
|
+
return /* @__PURE__ */ jsxs14("div", { className: "min-h-0 flex-1 overflow-y-auto bg-background", children: [
|
|
1130
|
+
/* @__PURE__ */ jsx15(
|
|
1131
|
+
StudioHeader,
|
|
1132
|
+
{
|
|
1133
|
+
count: mergedGenerations.length,
|
|
1134
|
+
canGenerate,
|
|
1135
|
+
onOpenLibrary: openLibrary
|
|
1136
|
+
}
|
|
1137
|
+
),
|
|
1138
|
+
/* @__PURE__ */ jsx15("div", { className: "mx-auto w-full max-w-3xl space-y-4 px-4 py-6 sm:px-6", children: canGenerate ? /* @__PURE__ */ jsxs14(Fragment6, { children: [
|
|
1139
|
+
/* @__PURE__ */ jsx15(
|
|
1140
|
+
ComposerHero,
|
|
1141
|
+
{
|
|
1142
|
+
workspaceId,
|
|
1143
|
+
integrationsHref: resolvedIntegrationsHref,
|
|
1144
|
+
canManageIntegrations,
|
|
1145
|
+
onGenerated: (generation) => {
|
|
1146
|
+
onGenerated(generation);
|
|
1147
|
+
requestAnimationFrame(() => {
|
|
1148
|
+
canvasRef.current?.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
),
|
|
1153
|
+
/* @__PURE__ */ jsx15("div", { ref: canvasRef, children: /* @__PURE__ */ jsx15(
|
|
1154
|
+
ResultCanvas,
|
|
1155
|
+
{
|
|
1156
|
+
batch: latestBatch,
|
|
1157
|
+
onOpenLibrary: openLibrary,
|
|
1158
|
+
onSelect: openDetail
|
|
1159
|
+
}
|
|
1160
|
+
) })
|
|
1161
|
+
] }) : /* @__PURE__ */ jsxs14("section", { className: "flex flex-col items-center justify-center gap-3 rounded-xl border border-border bg-card p-10 text-center shadow-sm", children: [
|
|
1162
|
+
/* @__PURE__ */ jsx15("div", { className: "flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: /* @__PURE__ */ jsx15(Images2, { className: "h-6 w-6 text-muted-foreground/40" }) }),
|
|
1163
|
+
/* @__PURE__ */ jsx15("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" }),
|
|
1164
|
+
/* @__PURE__ */ jsx15("p", { className: "max-w-sm text-xs text-muted-foreground", children: "When workspace members generate images, video, voice, avatars, or transcripts, they appear in the library." }),
|
|
1165
|
+
/* @__PURE__ */ jsxs14(Button6, { size: "sm", variant: "outline", onClick: openLibrary, children: [
|
|
1166
|
+
/* @__PURE__ */ jsx15(Images2, { className: "mr-1.5 h-4 w-4" }),
|
|
1167
|
+
"Open library"
|
|
1168
|
+
] })
|
|
1169
|
+
] }) }),
|
|
1170
|
+
/* @__PURE__ */ jsx15(
|
|
1171
|
+
LibraryDrawer,
|
|
1172
|
+
{
|
|
1173
|
+
open: drawerOpen,
|
|
1174
|
+
onOpenChange: (open) => {
|
|
1175
|
+
setDrawerOpen(open);
|
|
1176
|
+
if (!open) setSelected(null);
|
|
1177
|
+
},
|
|
1178
|
+
generations: mergedGenerations,
|
|
1179
|
+
totalCost,
|
|
1180
|
+
typeFilter,
|
|
1181
|
+
onFilterChange: setFilter,
|
|
1182
|
+
vaultHref: resolvedVaultHref,
|
|
1183
|
+
selected,
|
|
1184
|
+
onSelect: (generation) => {
|
|
1185
|
+
setSelected(generation);
|
|
1186
|
+
if (generation) setDrawerOpen(true);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
)
|
|
1190
|
+
] });
|
|
1191
|
+
}
|
|
1192
|
+
export {
|
|
1193
|
+
AvatarComposer,
|
|
1194
|
+
ComposerDisclosure,
|
|
1195
|
+
ComposerHero,
|
|
1196
|
+
Field,
|
|
1197
|
+
GenerationCard,
|
|
1198
|
+
GenerationDetail,
|
|
1199
|
+
GenerationStatusBadge,
|
|
1200
|
+
ImageComposer,
|
|
1201
|
+
LibraryDrawer,
|
|
1202
|
+
NativeSelect,
|
|
1203
|
+
PublishPackageComposer,
|
|
1204
|
+
ResultCanvas,
|
|
1205
|
+
SpeechComposer,
|
|
1206
|
+
StudioHeader,
|
|
1207
|
+
StudioSheet,
|
|
1208
|
+
StudioWorkspace,
|
|
1209
|
+
TYPE_CONFIG,
|
|
1210
|
+
TranscriptionComposer,
|
|
1211
|
+
TranscriptionOptions,
|
|
1212
|
+
VideoComposer,
|
|
1213
|
+
typeConfigFor,
|
|
1214
|
+
useStudioGenerations
|
|
1215
|
+
};
|
|
1216
|
+
//# sourceMappingURL=index.js.map
|