ai-design-system 0.1.101 → 0.1.103
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/components/composites/ScenePlayer/ScenePlayer.stories.tsx +3 -0
- package/components/composites/ScenePlayer/ScenePlayer.tsx +175 -42
- package/dist/index.cjs +181 -53
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +12 -45
- package/dist/index.d.ts +52 -37
- package/dist/index.js +181 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import type { FormEvent } from "react";
|
|
4
5
|
import { Button } from "@/components/primitives/Button";
|
|
5
6
|
import { Icon } from "@/components/primitives/Icon";
|
|
7
|
+
import { PromptInput } from "@/components/composites/PromptInput";
|
|
8
|
+
import type { PromptInputMessage } from "@/components/ai-elements/prompt-input";
|
|
6
9
|
import { cn } from "@/lib/utils";
|
|
7
10
|
|
|
8
11
|
export interface SceneItem {
|
|
@@ -18,16 +21,31 @@ export interface SceneItem {
|
|
|
18
21
|
[key: string]: unknown;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
export interface ScenePlayerProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
24
|
+
export interface ScenePlayerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onSubmit"> {
|
|
22
25
|
scenes: SceneItem[];
|
|
23
26
|
title?: string;
|
|
24
27
|
avatarInitials?: string;
|
|
25
28
|
avatarColor?: string;
|
|
26
29
|
initialSceneIndex?: number;
|
|
27
30
|
isMuted?: boolean;
|
|
31
|
+
isPlaying?: boolean;
|
|
32
|
+
onPlayingChange?: (isPlaying: boolean) => void;
|
|
33
|
+
videoUrl?: string;
|
|
34
|
+
downloadUrl?: string;
|
|
35
|
+
onDownload?: () => void;
|
|
28
36
|
onBack?: () => void;
|
|
29
37
|
onClose?: () => void;
|
|
30
|
-
|
|
38
|
+
placeholder?: string;
|
|
39
|
+
promptValue?: string;
|
|
40
|
+
onPromptValueChange?: (value: string) => void;
|
|
41
|
+
onSubmit?: (
|
|
42
|
+
message: PromptInputMessage,
|
|
43
|
+
event: FormEvent<HTMLFormElement>
|
|
44
|
+
) => void | Promise<void>;
|
|
45
|
+
loading?: boolean;
|
|
46
|
+
onStop?: () => void;
|
|
47
|
+
enableSpeech?: boolean;
|
|
48
|
+
onPublish?: (currentScene: SceneItem, text?: string) => void;
|
|
31
49
|
publishPlaceholder?: string;
|
|
32
50
|
onSceneChange?: (sceneIndex: number, scene: SceneItem) => void;
|
|
33
51
|
}
|
|
@@ -41,10 +59,22 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
41
59
|
avatarColor = "bg-indigo-600",
|
|
42
60
|
initialSceneIndex = 0,
|
|
43
61
|
isMuted: initialMuted = false,
|
|
62
|
+
isPlaying: isPlayingProp,
|
|
63
|
+
onPlayingChange,
|
|
64
|
+
videoUrl,
|
|
65
|
+
downloadUrl,
|
|
66
|
+
onDownload,
|
|
44
67
|
onBack,
|
|
45
68
|
onClose,
|
|
69
|
+
placeholder,
|
|
70
|
+
promptValue,
|
|
71
|
+
onPromptValueChange,
|
|
72
|
+
onSubmit,
|
|
73
|
+
loading = false,
|
|
74
|
+
onStop,
|
|
75
|
+
enableSpeech = true,
|
|
46
76
|
onPublish,
|
|
47
|
-
publishPlaceholder = "
|
|
77
|
+
publishPlaceholder = "Ask a question or provide instructions...",
|
|
48
78
|
onSceneChange,
|
|
49
79
|
className,
|
|
50
80
|
...props
|
|
@@ -60,11 +90,40 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
60
90
|
return 0;
|
|
61
91
|
});
|
|
62
92
|
|
|
93
|
+
const [isPlayingState, setIsPlayingState] = React.useState<boolean>(isPlayingProp ?? true);
|
|
94
|
+
const isPlaying = isPlayingProp !== undefined ? isPlayingProp : isPlayingState;
|
|
95
|
+
|
|
96
|
+
const setIsPlaying = React.useCallback(
|
|
97
|
+
(value: boolean | ((prev: boolean) => boolean)) => {
|
|
98
|
+
const nextVal = typeof value === "function" ? value(isPlaying) : value;
|
|
99
|
+
setIsPlayingState(nextVal);
|
|
100
|
+
onPlayingChange?.(nextVal);
|
|
101
|
+
},
|
|
102
|
+
[isPlaying, onPlayingChange]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const togglePlay = React.useCallback(() => {
|
|
106
|
+
setIsPlaying((prev) => !prev);
|
|
107
|
+
}, [setIsPlaying]);
|
|
108
|
+
|
|
63
109
|
const [isMuted, setIsMuted] = React.useState<boolean>(initialMuted);
|
|
64
110
|
const videoRef = React.useRef<HTMLVideoElement | null>(null);
|
|
65
111
|
|
|
66
112
|
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
67
113
|
|
|
114
|
+
const handlePromptSubmit = React.useCallback(
|
|
115
|
+
(message: PromptInputMessage, event: FormEvent<HTMLFormElement>) => {
|
|
116
|
+
if (onSubmit) {
|
|
117
|
+
onSubmit(message, event);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (onPublish) {
|
|
121
|
+
onPublish(activeScene, message.text);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
[onSubmit, onPublish, activeScene]
|
|
125
|
+
);
|
|
126
|
+
|
|
68
127
|
const handlePrev = React.useCallback(() => {
|
|
69
128
|
if (!scenes.length) return;
|
|
70
129
|
setCurrentSceneIndex((prev) => {
|
|
@@ -84,41 +143,91 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
84
143
|
}, [scenes, onSceneChange]);
|
|
85
144
|
|
|
86
145
|
const handleSceneEnded = React.useCallback(() => {
|
|
87
|
-
if (!scenes.length) return;
|
|
146
|
+
if (!scenes.length || !isPlaying) return;
|
|
88
147
|
setCurrentSceneIndex((prev) => {
|
|
89
148
|
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
90
149
|
onSceneChange?.(nextIdx, scenes[nextIdx]);
|
|
91
150
|
return nextIdx;
|
|
92
151
|
});
|
|
93
|
-
}, [scenes, onSceneChange]);
|
|
152
|
+
}, [scenes, onSceneChange, isPlaying]);
|
|
94
153
|
|
|
95
|
-
|
|
154
|
+
const handleDownload = React.useCallback(async () => {
|
|
155
|
+
if (onDownload) {
|
|
156
|
+
onDownload();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const targetUrl = downloadUrl || videoUrl || activeScene?.scene_clip_url;
|
|
160
|
+
if (!targetUrl) return;
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const response = await fetch(targetUrl);
|
|
164
|
+
if (!response.ok) throw new Error("Fetch failed");
|
|
165
|
+
const blob = await response.blob();
|
|
166
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
167
|
+
const link = document.createElement("a");
|
|
168
|
+
link.href = blobUrl;
|
|
169
|
+
link.download = targetUrl.split("/").pop()?.split("?")[0] || "video.mp4";
|
|
170
|
+
document.body.appendChild(link);
|
|
171
|
+
link.click();
|
|
172
|
+
document.body.removeChild(link);
|
|
173
|
+
setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
|
|
174
|
+
} catch {
|
|
175
|
+
const link = document.createElement("a");
|
|
176
|
+
link.href = targetUrl;
|
|
177
|
+
link.download = targetUrl.split("/").pop()?.split("?")[0] || "video.mp4";
|
|
178
|
+
link.target = "_blank";
|
|
179
|
+
link.rel = "noopener noreferrer";
|
|
180
|
+
document.body.appendChild(link);
|
|
181
|
+
link.click();
|
|
182
|
+
document.body.removeChild(link);
|
|
183
|
+
}
|
|
184
|
+
}, [onDownload, downloadUrl, videoUrl, activeScene?.scene_clip_url]);
|
|
185
|
+
|
|
186
|
+
// Playback & mute synchronization
|
|
96
187
|
React.useEffect(() => {
|
|
97
188
|
const video = videoRef.current;
|
|
98
189
|
if (!video) return;
|
|
99
190
|
|
|
100
191
|
video.muted = isMuted;
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
playPromise.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
192
|
+
|
|
193
|
+
if (isPlaying) {
|
|
194
|
+
const playPromise = video.play();
|
|
195
|
+
if (playPromise !== undefined) {
|
|
196
|
+
playPromise.catch(() => {
|
|
197
|
+
if (!isMuted) {
|
|
198
|
+
video.muted = true;
|
|
199
|
+
setIsMuted(true);
|
|
200
|
+
video.play().catch(() => {});
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
video.pause();
|
|
110
206
|
}
|
|
111
|
-
}, [activeScene?.scene_clip_url, isMuted]);
|
|
207
|
+
}, [activeScene?.scene_clip_url, isMuted, isPlaying]);
|
|
112
208
|
|
|
113
|
-
// Keyboard navigation
|
|
209
|
+
// Keyboard navigation & playback controls
|
|
114
210
|
React.useEffect(() => {
|
|
115
211
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
212
|
+
const target = e.target as HTMLElement | null;
|
|
213
|
+
if (
|
|
214
|
+
target &&
|
|
215
|
+
(target.tagName === "INPUT" ||
|
|
216
|
+
target.tagName === "TEXTAREA" ||
|
|
217
|
+
target.isContentEditable)
|
|
218
|
+
) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
116
222
|
if (e.key === "ArrowLeft") {
|
|
117
223
|
e.preventDefault();
|
|
118
224
|
handlePrev();
|
|
119
|
-
} else if (e.key === "ArrowRight"
|
|
225
|
+
} else if (e.key === "ArrowRight") {
|
|
120
226
|
e.preventDefault();
|
|
121
227
|
handleNext();
|
|
228
|
+
} else if (e.key === " " || e.key === "k" || e.key === "K") {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
togglePlay();
|
|
122
231
|
} else if (e.key === "Escape") {
|
|
123
232
|
e.preventDefault();
|
|
124
233
|
handleBack?.();
|
|
@@ -130,7 +239,7 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
130
239
|
|
|
131
240
|
window.addEventListener("keydown", handleKeyDown);
|
|
132
241
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
133
|
-
}, [handlePrev, handleNext, handleBack]);
|
|
242
|
+
}, [handlePrev, handleNext, handleBack, togglePlay]);
|
|
134
243
|
|
|
135
244
|
if (!scenes || scenes.length === 0) {
|
|
136
245
|
return (
|
|
@@ -203,14 +312,6 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
203
312
|
</Button>
|
|
204
313
|
)}
|
|
205
314
|
|
|
206
|
-
<div
|
|
207
|
-
className={cn(
|
|
208
|
-
"w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm text-white shrink-0 ring-1.5 ring-white/80 overflow-hidden",
|
|
209
|
-
avatarColor
|
|
210
|
-
)}
|
|
211
|
-
>
|
|
212
|
-
{avatarInitials}
|
|
213
|
-
</div>
|
|
214
315
|
<div className="flex flex-col min-w-0 justify-center">
|
|
215
316
|
<span className="font-bold text-sm text-white truncate max-w-[200px]">
|
|
216
317
|
{title}
|
|
@@ -222,6 +323,18 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
222
323
|
</div>
|
|
223
324
|
|
|
224
325
|
<div className="flex items-center gap-2 shrink-0">
|
|
326
|
+
{/* Play/Pause Toggle Button */}
|
|
327
|
+
<Button
|
|
328
|
+
variant="ghost"
|
|
329
|
+
size="icon"
|
|
330
|
+
data-testid="btn-toggle-play"
|
|
331
|
+
aria-label={isPlaying ? "Pause scene" : "Play scene"}
|
|
332
|
+
onClick={togglePlay}
|
|
333
|
+
className="h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer"
|
|
334
|
+
>
|
|
335
|
+
<Icon name={isPlaying ? "pause" : "play"} className="w-4 h-4" />
|
|
336
|
+
</Button>
|
|
337
|
+
|
|
225
338
|
{/* Mute/Unmute Toggle Button */}
|
|
226
339
|
<Button
|
|
227
340
|
variant="ghost"
|
|
@@ -233,6 +346,18 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
233
346
|
>
|
|
234
347
|
<Icon name={isMuted ? "volume-x" : "volume-2"} className="w-4 h-4" />
|
|
235
348
|
</Button>
|
|
349
|
+
|
|
350
|
+
{/* Download Video Button */}
|
|
351
|
+
<Button
|
|
352
|
+
variant="ghost"
|
|
353
|
+
size="icon"
|
|
354
|
+
data-testid="btn-download"
|
|
355
|
+
aria-label="Download video"
|
|
356
|
+
onClick={handleDownload}
|
|
357
|
+
className="h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer"
|
|
358
|
+
>
|
|
359
|
+
<Icon name="download" className="w-4 h-4" />
|
|
360
|
+
</Button>
|
|
236
361
|
</div>
|
|
237
362
|
</div>
|
|
238
363
|
|
|
@@ -269,21 +394,29 @@ export const ScenePlayer = React.forwardRef<HTMLDivElement, ScenePlayerProps>(
|
|
|
269
394
|
</div>
|
|
270
395
|
</div>
|
|
271
396
|
|
|
272
|
-
{/* Bottom
|
|
273
|
-
<div
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
397
|
+
{/* Bottom PromptInput Composite from Design System */}
|
|
398
|
+
<div
|
|
399
|
+
className="p-3 bg-gradient-to-t from-black via-black/90 to-transparent pt-4 z-20 shrink-0"
|
|
400
|
+
onFocusCapture={() => {
|
|
401
|
+
setIsPlaying(false);
|
|
402
|
+
}}
|
|
403
|
+
onClickCapture={() => {
|
|
404
|
+
setIsPlaying(false);
|
|
405
|
+
}}
|
|
406
|
+
onTouchStartCapture={() => {
|
|
407
|
+
setIsPlaying(false);
|
|
408
|
+
}}
|
|
409
|
+
>
|
|
410
|
+
<PromptInput
|
|
411
|
+
placeholder={placeholder || publishPlaceholder}
|
|
412
|
+
value={promptValue}
|
|
413
|
+
onChange={onPromptValueChange}
|
|
414
|
+
onSubmit={handlePromptSubmit}
|
|
415
|
+
loading={loading}
|
|
416
|
+
onStop={onStop}
|
|
417
|
+
enableSpeech={enableSpeech}
|
|
418
|
+
className="border border-border bg-background shadow-md overflow-hidden rounded-2xl"
|
|
419
|
+
/>
|
|
287
420
|
</div>
|
|
288
421
|
</div>
|
|
289
422
|
);
|
package/dist/index.cjs
CHANGED
|
@@ -1026,6 +1026,14 @@ var defaultIcons = {
|
|
|
1026
1026
|
viewBox: "0 0 24 24",
|
|
1027
1027
|
path: "M8 5v14l11-7z"
|
|
1028
1028
|
},
|
|
1029
|
+
"pause": {
|
|
1030
|
+
name: "pause",
|
|
1031
|
+
viewBox: "0 0 24 24",
|
|
1032
|
+
elements: [
|
|
1033
|
+
{ type: "rect", attrs: { x: "14", y: "4", width: "4", height: "16", rx: "1" } },
|
|
1034
|
+
{ type: "rect", attrs: { x: "6", y: "4", width: "4", height: "16", rx: "1" } }
|
|
1035
|
+
]
|
|
1036
|
+
},
|
|
1029
1037
|
"file-text": {
|
|
1030
1038
|
name: "file-text",
|
|
1031
1039
|
viewBox: "0 0 24 24",
|
|
@@ -14575,10 +14583,22 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14575
14583
|
avatarColor = "bg-indigo-600",
|
|
14576
14584
|
initialSceneIndex = 0,
|
|
14577
14585
|
isMuted: initialMuted = false,
|
|
14586
|
+
isPlaying: isPlayingProp,
|
|
14587
|
+
onPlayingChange,
|
|
14588
|
+
videoUrl,
|
|
14589
|
+
downloadUrl,
|
|
14590
|
+
onDownload,
|
|
14578
14591
|
onBack,
|
|
14579
14592
|
onClose,
|
|
14593
|
+
placeholder,
|
|
14594
|
+
promptValue,
|
|
14595
|
+
onPromptValueChange,
|
|
14596
|
+
onSubmit,
|
|
14597
|
+
loading = false,
|
|
14598
|
+
onStop,
|
|
14599
|
+
enableSpeech = true,
|
|
14580
14600
|
onPublish,
|
|
14581
|
-
publishPlaceholder = "
|
|
14601
|
+
publishPlaceholder = "Ask a question or provide instructions...",
|
|
14582
14602
|
onSceneChange,
|
|
14583
14603
|
className
|
|
14584
14604
|
} = _b, props = __objRest(_b, [
|
|
@@ -14588,8 +14608,20 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14588
14608
|
"avatarColor",
|
|
14589
14609
|
"initialSceneIndex",
|
|
14590
14610
|
"isMuted",
|
|
14611
|
+
"isPlaying",
|
|
14612
|
+
"onPlayingChange",
|
|
14613
|
+
"videoUrl",
|
|
14614
|
+
"downloadUrl",
|
|
14615
|
+
"onDownload",
|
|
14591
14616
|
"onBack",
|
|
14592
14617
|
"onClose",
|
|
14618
|
+
"placeholder",
|
|
14619
|
+
"promptValue",
|
|
14620
|
+
"onPromptValueChange",
|
|
14621
|
+
"onSubmit",
|
|
14622
|
+
"loading",
|
|
14623
|
+
"onStop",
|
|
14624
|
+
"enableSpeech",
|
|
14593
14625
|
"onPublish",
|
|
14594
14626
|
"publishPlaceholder",
|
|
14595
14627
|
"onSceneChange",
|
|
@@ -14603,9 +14635,34 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14603
14635
|
}
|
|
14604
14636
|
return 0;
|
|
14605
14637
|
});
|
|
14638
|
+
const [isPlayingState, setIsPlayingState] = React3__namespace.useState(isPlayingProp != null ? isPlayingProp : true);
|
|
14639
|
+
const isPlaying = isPlayingProp !== void 0 ? isPlayingProp : isPlayingState;
|
|
14640
|
+
const setIsPlaying = React3__namespace.useCallback(
|
|
14641
|
+
(value) => {
|
|
14642
|
+
const nextVal = typeof value === "function" ? value(isPlaying) : value;
|
|
14643
|
+
setIsPlayingState(nextVal);
|
|
14644
|
+
onPlayingChange == null ? void 0 : onPlayingChange(nextVal);
|
|
14645
|
+
},
|
|
14646
|
+
[isPlaying, onPlayingChange]
|
|
14647
|
+
);
|
|
14648
|
+
const togglePlay = React3__namespace.useCallback(() => {
|
|
14649
|
+
setIsPlaying((prev) => !prev);
|
|
14650
|
+
}, [setIsPlaying]);
|
|
14606
14651
|
const [isMuted, setIsMuted] = React3__namespace.useState(initialMuted);
|
|
14607
14652
|
const videoRef = React3__namespace.useRef(null);
|
|
14608
14653
|
const activeScene = scenes[currentSceneIndex] || scenes[0];
|
|
14654
|
+
const handlePromptSubmit = React3__namespace.useCallback(
|
|
14655
|
+
(message, event) => {
|
|
14656
|
+
if (onSubmit) {
|
|
14657
|
+
onSubmit(message, event);
|
|
14658
|
+
return;
|
|
14659
|
+
}
|
|
14660
|
+
if (onPublish) {
|
|
14661
|
+
onPublish(activeScene, message.text);
|
|
14662
|
+
}
|
|
14663
|
+
},
|
|
14664
|
+
[onSubmit, onPublish, activeScene]
|
|
14665
|
+
);
|
|
14609
14666
|
const handlePrev = React3__namespace.useCallback(() => {
|
|
14610
14667
|
if (!scenes.length) return;
|
|
14611
14668
|
setCurrentSceneIndex((prev) => {
|
|
@@ -14623,37 +14680,79 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14623
14680
|
});
|
|
14624
14681
|
}, [scenes, onSceneChange]);
|
|
14625
14682
|
const handleSceneEnded = React3__namespace.useCallback(() => {
|
|
14626
|
-
if (!scenes.length) return;
|
|
14683
|
+
if (!scenes.length || !isPlaying) return;
|
|
14627
14684
|
setCurrentSceneIndex((prev) => {
|
|
14628
14685
|
const nextIdx = prev < scenes.length - 1 ? prev + 1 : 0;
|
|
14629
14686
|
onSceneChange == null ? void 0 : onSceneChange(nextIdx, scenes[nextIdx]);
|
|
14630
14687
|
return nextIdx;
|
|
14631
14688
|
});
|
|
14632
|
-
}, [scenes, onSceneChange]);
|
|
14689
|
+
}, [scenes, onSceneChange, isPlaying]);
|
|
14690
|
+
const handleDownload = React3__namespace.useCallback(async () => {
|
|
14691
|
+
var _a3, _b2;
|
|
14692
|
+
if (onDownload) {
|
|
14693
|
+
onDownload();
|
|
14694
|
+
return;
|
|
14695
|
+
}
|
|
14696
|
+
const targetUrl = downloadUrl || videoUrl || (activeScene == null ? void 0 : activeScene.scene_clip_url);
|
|
14697
|
+
if (!targetUrl) return;
|
|
14698
|
+
try {
|
|
14699
|
+
const response = await fetch(targetUrl);
|
|
14700
|
+
if (!response.ok) throw new Error("Fetch failed");
|
|
14701
|
+
const blob = await response.blob();
|
|
14702
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
14703
|
+
const link = document.createElement("a");
|
|
14704
|
+
link.href = blobUrl;
|
|
14705
|
+
link.download = ((_a3 = targetUrl.split("/").pop()) == null ? void 0 : _a3.split("?")[0]) || "video.mp4";
|
|
14706
|
+
document.body.appendChild(link);
|
|
14707
|
+
link.click();
|
|
14708
|
+
document.body.removeChild(link);
|
|
14709
|
+
setTimeout(() => URL.revokeObjectURL(blobUrl), 1e3);
|
|
14710
|
+
} catch (e) {
|
|
14711
|
+
const link = document.createElement("a");
|
|
14712
|
+
link.href = targetUrl;
|
|
14713
|
+
link.download = ((_b2 = targetUrl.split("/").pop()) == null ? void 0 : _b2.split("?")[0]) || "video.mp4";
|
|
14714
|
+
link.target = "_blank";
|
|
14715
|
+
link.rel = "noopener noreferrer";
|
|
14716
|
+
document.body.appendChild(link);
|
|
14717
|
+
link.click();
|
|
14718
|
+
document.body.removeChild(link);
|
|
14719
|
+
}
|
|
14720
|
+
}, [onDownload, downloadUrl, videoUrl, activeScene == null ? void 0 : activeScene.scene_clip_url]);
|
|
14633
14721
|
React3__namespace.useEffect(() => {
|
|
14634
14722
|
const video = videoRef.current;
|
|
14635
14723
|
if (!video) return;
|
|
14636
14724
|
video.muted = isMuted;
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
playPromise
|
|
14640
|
-
|
|
14641
|
-
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14725
|
+
if (isPlaying) {
|
|
14726
|
+
const playPromise = video.play();
|
|
14727
|
+
if (playPromise !== void 0) {
|
|
14728
|
+
playPromise.catch(() => {
|
|
14729
|
+
if (!isMuted) {
|
|
14730
|
+
video.muted = true;
|
|
14731
|
+
setIsMuted(true);
|
|
14732
|
+
video.play().catch(() => {
|
|
14733
|
+
});
|
|
14734
|
+
}
|
|
14735
|
+
});
|
|
14736
|
+
}
|
|
14737
|
+
} else {
|
|
14738
|
+
video.pause();
|
|
14647
14739
|
}
|
|
14648
|
-
}, [activeScene == null ? void 0 : activeScene.scene_clip_url, isMuted]);
|
|
14740
|
+
}, [activeScene == null ? void 0 : activeScene.scene_clip_url, isMuted, isPlaying]);
|
|
14649
14741
|
React3__namespace.useEffect(() => {
|
|
14650
14742
|
const handleKeyDown = (e) => {
|
|
14743
|
+
const target = e.target;
|
|
14744
|
+
if (target && (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable)) {
|
|
14745
|
+
return;
|
|
14746
|
+
}
|
|
14651
14747
|
if (e.key === "ArrowLeft") {
|
|
14652
14748
|
e.preventDefault();
|
|
14653
14749
|
handlePrev();
|
|
14654
|
-
} else if (e.key === "ArrowRight"
|
|
14750
|
+
} else if (e.key === "ArrowRight") {
|
|
14655
14751
|
e.preventDefault();
|
|
14656
14752
|
handleNext();
|
|
14753
|
+
} else if (e.key === " " || e.key === "k" || e.key === "K") {
|
|
14754
|
+
e.preventDefault();
|
|
14755
|
+
togglePlay();
|
|
14657
14756
|
} else if (e.key === "Escape") {
|
|
14658
14757
|
e.preventDefault();
|
|
14659
14758
|
handleBack == null ? void 0 : handleBack();
|
|
@@ -14664,7 +14763,7 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14664
14763
|
};
|
|
14665
14764
|
window.addEventListener("keydown", handleKeyDown);
|
|
14666
14765
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
14667
|
-
}, [handlePrev, handleNext, handleBack]);
|
|
14766
|
+
}, [handlePrev, handleNext, handleBack, togglePlay]);
|
|
14668
14767
|
if (!scenes || scenes.length === 0) {
|
|
14669
14768
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14670
14769
|
"div",
|
|
@@ -14734,16 +14833,6 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14734
14833
|
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "arrow-left", className: "w-5 h-5 text-white" })
|
|
14735
14834
|
}
|
|
14736
14835
|
),
|
|
14737
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14738
|
-
"div",
|
|
14739
|
-
{
|
|
14740
|
-
className: cn(
|
|
14741
|
-
"w-9 h-9 rounded-full flex items-center justify-center font-bold text-sm text-white shrink-0 ring-1.5 ring-white/80 overflow-hidden",
|
|
14742
|
-
avatarColor
|
|
14743
|
-
),
|
|
14744
|
-
children: avatarInitials
|
|
14745
|
-
}
|
|
14746
|
-
),
|
|
14747
14836
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col min-w-0 justify-center", children: [
|
|
14748
14837
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold text-sm text-white truncate max-w-[200px]", children: title }),
|
|
14749
14838
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/75 truncate", children: [
|
|
@@ -14754,18 +14843,44 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14754
14843
|
] })
|
|
14755
14844
|
] })
|
|
14756
14845
|
] }),
|
|
14757
|
-
/* @__PURE__ */ jsxRuntime.
|
|
14758
|
-
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
14765
|
-
|
|
14766
|
-
|
|
14767
|
-
|
|
14768
|
-
|
|
14846
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 shrink-0", children: [
|
|
14847
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14848
|
+
Button2,
|
|
14849
|
+
{
|
|
14850
|
+
variant: "ghost",
|
|
14851
|
+
size: "icon",
|
|
14852
|
+
"data-testid": "btn-toggle-play",
|
|
14853
|
+
"aria-label": isPlaying ? "Pause scene" : "Play scene",
|
|
14854
|
+
onClick: togglePlay,
|
|
14855
|
+
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14856
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: isPlaying ? "pause" : "play", className: "w-4 h-4" })
|
|
14857
|
+
}
|
|
14858
|
+
),
|
|
14859
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14860
|
+
Button2,
|
|
14861
|
+
{
|
|
14862
|
+
variant: "ghost",
|
|
14863
|
+
size: "icon",
|
|
14864
|
+
"data-testid": "btn-toggle-mute",
|
|
14865
|
+
"aria-label": isMuted ? "Unmute audio" : "Mute audio",
|
|
14866
|
+
onClick: () => setIsMuted((prev) => !prev),
|
|
14867
|
+
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14868
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: isMuted ? "volume-x" : "volume-2", className: "w-4 h-4" })
|
|
14869
|
+
}
|
|
14870
|
+
),
|
|
14871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14872
|
+
Button2,
|
|
14873
|
+
{
|
|
14874
|
+
variant: "ghost",
|
|
14875
|
+
size: "icon",
|
|
14876
|
+
"data-testid": "btn-download",
|
|
14877
|
+
"aria-label": "Download video",
|
|
14878
|
+
onClick: handleDownload,
|
|
14879
|
+
className: "h-8 w-8 rounded-full bg-black/40 hover:bg-white/20 text-white p-0 cursor-pointer",
|
|
14880
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { name: "download", className: "w-4 h-4" })
|
|
14881
|
+
}
|
|
14882
|
+
)
|
|
14883
|
+
] })
|
|
14769
14884
|
] }),
|
|
14770
14885
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 relative flex items-center justify-center bg-black overflow-hidden min-h-0", children: [
|
|
14771
14886
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -14805,21 +14920,34 @@ var ScenePlayer = React3__namespace.forwardRef(
|
|
|
14805
14920
|
)
|
|
14806
14921
|
] })
|
|
14807
14922
|
] }),
|
|
14808
|
-
/* @__PURE__ */ jsxRuntime.
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
{
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
14818
|
-
|
|
14819
|
-
|
|
14820
|
-
}
|
|
14821
|
-
|
|
14822
|
-
|
|
14923
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14924
|
+
"div",
|
|
14925
|
+
{
|
|
14926
|
+
className: "p-3 bg-gradient-to-t from-black via-black/90 to-transparent pt-4 z-20 shrink-0",
|
|
14927
|
+
onFocusCapture: () => {
|
|
14928
|
+
setIsPlaying(false);
|
|
14929
|
+
},
|
|
14930
|
+
onClickCapture: () => {
|
|
14931
|
+
setIsPlaying(false);
|
|
14932
|
+
},
|
|
14933
|
+
onTouchStartCapture: () => {
|
|
14934
|
+
setIsPlaying(false);
|
|
14935
|
+
},
|
|
14936
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14937
|
+
PromptInput2,
|
|
14938
|
+
{
|
|
14939
|
+
placeholder: placeholder || publishPlaceholder,
|
|
14940
|
+
value: promptValue,
|
|
14941
|
+
onChange: onPromptValueChange,
|
|
14942
|
+
onSubmit: handlePromptSubmit,
|
|
14943
|
+
loading,
|
|
14944
|
+
onStop,
|
|
14945
|
+
enableSpeech,
|
|
14946
|
+
className: "border border-border bg-background shadow-md overflow-hidden rounded-2xl"
|
|
14947
|
+
}
|
|
14948
|
+
)
|
|
14949
|
+
}
|
|
14950
|
+
)
|
|
14823
14951
|
]
|
|
14824
14952
|
})
|
|
14825
14953
|
);
|