@viji-dev/sdk 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +155 -60
  2. package/bin/viji.js +9 -29
  3. package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
  4. package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
  5. package/dist/assets/core-CiQx3w0t.js +12 -0
  6. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  7. package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
  8. package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
  9. package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
  10. package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
  11. package/dist/assets/glsl-DMyvO4G4.js +1 -0
  12. package/dist/assets/index-BhFxsauQ.js +215 -0
  13. package/dist/assets/index-BqhVeA7U.css +1 -0
  14. package/dist/assets/index-T4TOjvD0.js +1 -0
  15. package/dist/assets/index-Wz9WqGqz.js +52 -0
  16. package/dist/assets/index-t24aGwla.js +1 -0
  17. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  18. package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
  19. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  20. package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
  21. package/{index.html → dist/index.html} +2 -1
  22. package/package.json +31 -35
  23. package/src/cli/commands/build.js +50 -99
  24. package/src/cli/commands/create.js +32 -47
  25. package/src/cli/commands/dev.js +30 -97
  26. package/src/cli/server/dev-server.js +233 -0
  27. package/src/cli/server/scene-scanner.js +93 -0
  28. package/src/cli/server/vite-scene-plugin.d.ts +2 -0
  29. package/src/cli/server/vite-scene-plugin.js +134 -0
  30. package/src/cli/utils/cli-utils.js +29 -139
  31. package/src/cli/utils/scene-compiler.js +10 -17
  32. package/src/templates/scene-templates.js +85 -0
  33. package/.gitignore +0 -29
  34. package/eslint.config.js +0 -37
  35. package/postcss.config.js +0 -6
  36. package/scenes/audio-visualizer/main.js +0 -287
  37. package/scenes/core-demo/main.js +0 -532
  38. package/scenes/demo-scene/main.js +0 -619
  39. package/scenes/global.d.ts +0 -15
  40. package/scenes/particle-system/main.js +0 -349
  41. package/scenes/tsconfig.json +0 -12
  42. package/scenes/video-mirror/main.ts +0 -436
  43. package/src/App.css +0 -42
  44. package/src/App.tsx +0 -279
  45. package/src/cli/commands/init.js +0 -262
  46. package/src/components/SDKPage.tsx +0 -337
  47. package/src/components/core/CoreContainer.tsx +0 -126
  48. package/src/components/ui/DeviceSelectionList.tsx +0 -137
  49. package/src/components/ui/FPSCounter.tsx +0 -78
  50. package/src/components/ui/FileDropzonePanel.tsx +0 -120
  51. package/src/components/ui/FileListPanel.tsx +0 -285
  52. package/src/components/ui/InputExpansionPanel.tsx +0 -31
  53. package/src/components/ui/MediaPlayerControls.tsx +0 -191
  54. package/src/components/ui/MenuContainer.tsx +0 -71
  55. package/src/components/ui/ParametersMenu.tsx +0 -797
  56. package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
  57. package/src/components/ui/QuickInputControls.tsx +0 -542
  58. package/src/components/ui/SDKMenuSystem.tsx +0 -96
  59. package/src/components/ui/SettingsMenu.tsx +0 -346
  60. package/src/components/ui/SimpleInputControls.tsx +0 -137
  61. package/src/index.css +0 -68
  62. package/src/main.tsx +0 -10
  63. package/src/scenes-hmr.ts +0 -158
  64. package/src/services/project-filesystem.ts +0 -436
  65. package/src/stores/scene-player/index.ts +0 -3
  66. package/src/stores/scene-player/input-manager.store.ts +0 -1045
  67. package/src/stores/scene-player/scene-session.store.ts +0 -659
  68. package/src/styles/globals.css +0 -111
  69. package/src/templates/minimal-template.js +0 -11
  70. package/src/utils/debounce.js +0 -34
  71. package/src/vite-env.d.ts +0 -1
  72. package/tailwind.config.js +0 -18
  73. package/tsconfig.app.json +0 -27
  74. package/tsconfig.json +0 -27
  75. package/tsconfig.node.json +0 -27
  76. package/vite.config.ts +0 -54
  77. /package/{public → dist}/favicon.png +0 -0
@@ -1,542 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Button, Popover, PopoverTrigger, PopoverContent, Card, CardBody } from '@heroui/react';
3
- import {
4
- VideoCameraIcon,
5
- MicrophoneIcon,
6
- CursorArrowRaysIcon,
7
- VideoCameraSlashIcon,
8
- NoSymbolIcon
9
- } from '@heroicons/react/24/outline';
10
- import {
11
- ComputerDesktopIcon,
12
- DocumentIcon
13
- } from '@heroicons/react/24/solid';
14
- import { useInputManagerStore, AudioInputType, VideoInputType } from '../../stores/scene-player/input-manager.store';
15
- import { useSceneSessionStore } from '../../stores/scene-player/scene-session.store';
16
- import InputExpansionPanel from './InputExpansionPanel';
17
- import DeviceSelectionList from './DeviceSelectionList';
18
- import FileDropzonePanel from './FileDropzonePanel';
19
- import FileListPanel from './FileListPanel';
20
-
21
- interface QuickInputControlsProps {
22
- className?: string;
23
- onSetControlsPinned?: (pinned: boolean) => void;
24
- }
25
-
26
- type PanelType = 'devices' | 'files' | null;
27
-
28
- const QuickInputControls: React.FC<QuickInputControlsProps> = ({ className = "", onSetControlsPinned }) => {
29
- // Debug refs
30
- const videoContentRef = useRef<HTMLDivElement | null>(null);
31
- const audioContentRef = useRef<HTMLDivElement | null>(null);
32
-
33
- const [showVideoMenu, setShowVideoMenu] = useState(false);
34
- const [showAudioMenu, setShowAudioMenu] = useState(false);
35
- const videoOpenedAtRef = useRef<number>(0);
36
- const audioOpenedAtRef = useRef<number>(0);
37
- const clickInsideVideoRef = useRef<boolean>(false);
38
- const clickInsideAudioRef = useRef<boolean>(false);
39
- const [activeVideoPanel, setActiveVideoPanel] = useState<PanelType>(null);
40
- const [activeAudioPanel, setActiveAudioPanel] = useState<PanelType>(null);
41
- const [showFileList, setShowFileList] = useState(false);
42
-
43
- // Toggle helpers to mirror frontend behavior (only one menu open at a time)
44
- const openVideoMenu = () => {
45
- setShowAudioMenu(false);
46
- videoOpenedAtRef.current = Date.now();
47
- setShowVideoMenu(true);
48
- };
49
- const openAudioMenu = () => {
50
- setShowVideoMenu(false);
51
- audioOpenedAtRef.current = Date.now();
52
- setShowAudioMenu(true);
53
- };
54
- const closeVideoMenu = () => setShowVideoMenu(false);
55
- const closeAudioMenu = () => setShowAudioMenu(false);
56
-
57
- const {
58
- inputConfiguration,
59
- streamError,
60
- setInteractionEnabled,
61
- setAudioInputType,
62
- setVideoInputType,
63
- updateStreamsForCore,
64
- setStreamError
65
- } = useInputManagerStore();
66
-
67
- const { updateCoreConfig } = useSceneSessionStore();
68
-
69
- // Get current input state
70
- const hasVideoInput = inputConfiguration.video.type !== VideoInputType.NONE;
71
- const hasAudioInput = inputConfiguration.audio.type !== AudioInputType.NONE;
72
- const interactionEnabled = inputConfiguration.interactionEnabled;
73
-
74
- // Focus video popover on open (minimal)
75
- useEffect(() => {
76
- if (showVideoMenu) {
77
- requestAnimationFrame(() => {
78
- try { videoContentRef.current?.focus?.(); } catch {}
79
- });
80
- }
81
- }, [showVideoMenu]);
82
-
83
- useEffect(() => {
84
- if (showAudioMenu) {
85
- requestAnimationFrame(() => {
86
- try { audioContentRef.current?.focus?.(); } catch {}
87
- });
88
- }
89
- }, [showAudioMenu]);
90
-
91
- // Minimal initial outside-click guard (like visualizer)
92
- useEffect(() => {
93
- if (!showVideoMenu && !showAudioMenu) return;
94
- const handler = (e: Event) => {
95
- const now = Date.now();
96
- const withinGuard = (showVideoMenu && now - (videoOpenedAtRef.current || 0) < 200) || (showAudioMenu && now - (audioOpenedAtRef.current || 0) < 200);
97
- if ((e.type === 'pointerdown' || e.type === 'click') && withinGuard) {
98
- e.stopPropagation();
99
- e.preventDefault();
100
- }
101
- };
102
- document.addEventListener('pointerdown', handler, true);
103
- document.addEventListener('click', handler, true);
104
- return () => {
105
- document.removeEventListener('pointerdown', handler, true);
106
- document.removeEventListener('click', handler, true);
107
- };
108
- }, [showVideoMenu, showAudioMenu]);
109
-
110
- // Ensure panels close when input types change to non-panel types
111
- useEffect(() => {
112
- if (inputConfiguration.video.type !== VideoInputType.CAMERA &&
113
- inputConfiguration.video.type !== VideoInputType.FILES) {
114
- setActiveVideoPanel(null);
115
- }
116
- }, [inputConfiguration.video.type]);
117
-
118
- useEffect(() => {
119
- if (inputConfiguration.audio.type !== AudioInputType.MICROPHONE &&
120
- inputConfiguration.audio.type !== AudioInputType.FILES) {
121
- setActiveAudioPanel(null);
122
- }
123
- }, [inputConfiguration.audio.type]);
124
-
125
- // Pin/unpin controls while file panels are active (both dropzone and list views)
126
- useEffect(() => {
127
- const filesPanelActive = activeAudioPanel === 'files' || activeVideoPanel === 'files';
128
- const isAnyFilesSubpanelOpen = filesPanelActive; // dropzone or list both count
129
- onSetControlsPinned?.(isAnyFilesSubpanelOpen);
130
- }, [activeAudioPanel, activeVideoPanel, showFileList, onSetControlsPinned]);
131
-
132
- const handleInteractionToggle = async () => {
133
- const newState = !interactionEnabled;
134
- setInteractionEnabled(newState);
135
-
136
- // Update VijiCore interaction state (now works at runtime!)
137
- try {
138
- await updateCoreConfig({
139
- allowUserInteraction: newState
140
- });
141
- } catch (error) {
142
- console.error('Failed to update interaction state:', error);
143
- // Revert local state if core update failed
144
- setInteractionEnabled(!newState);
145
- }
146
- };
147
-
148
- const handleVideoInputChange = async (type: VideoInputType) => {
149
- setShowVideoMenu(false);
150
-
151
- // Close audio panel when opening video panel
152
- setActiveAudioPanel(null);
153
-
154
- // Manage panel state based on input type FIRST (before async operations)
155
- if (type === VideoInputType.CAMERA) {
156
- setActiveVideoPanel('devices');
157
- } else if (type === VideoInputType.FILES) {
158
- const hasFiles = inputConfiguration.video.files.length > 0;
159
- setActiveVideoPanel('files');
160
- setShowFileList(hasFiles);
161
- } else {
162
- // Close panel for NONE, SCREEN_VIDEO, or any other type
163
- setActiveVideoPanel(null);
164
- }
165
-
166
- // Then update the input type only if changed
167
- if (inputConfiguration.video.type !== type) {
168
- await setVideoInputType(type);
169
- }
170
-
171
- // Update core only for video when type changed (avoid touching audio)
172
- if (inputConfiguration.video.type !== type) {
173
- try {
174
- const streams = await updateStreamsForCore();
175
- await updateCoreConfig({
176
- videoStream: streams.videoStream
177
- });
178
- } catch (error) {
179
- console.error('Failed to update video input:', error);
180
- }
181
- }
182
- };
183
-
184
- const handleAudioInputChange = async (type: AudioInputType) => {
185
- setShowAudioMenu(false);
186
-
187
- // Close video panel when opening audio panel
188
- setActiveVideoPanel(null);
189
-
190
- // Manage panel state based on input type FIRST (before async operations)
191
- if (type === AudioInputType.MICROPHONE) {
192
- setActiveAudioPanel('devices');
193
- } else if (type === AudioInputType.FILES) {
194
- const hasFiles = inputConfiguration.audio.files.length > 0;
195
- setActiveAudioPanel('files');
196
- setShowFileList(hasFiles);
197
- } else {
198
- // Close panel for NONE, SCREEN_AUDIO, or any other type
199
- setActiveAudioPanel(null);
200
- }
201
-
202
- // Then update the input type only if changed
203
- if (inputConfiguration.audio.type !== type) {
204
- await setAudioInputType(type);
205
- }
206
-
207
- // Update core only for audio when type changed (avoid touching video)
208
- if (inputConfiguration.audio.type !== type) {
209
- try {
210
- const streams = await updateStreamsForCore();
211
- await updateCoreConfig({
212
- audioStream: streams.audioStream
213
- });
214
- } catch (error) {
215
- console.error('Failed to update audio input:', error);
216
- }
217
- }
218
- };
219
-
220
- const getVideoIcon = () => {
221
- switch (inputConfiguration.video.type) {
222
- case VideoInputType.CAMERA:
223
- return <VideoCameraIcon className="w-5 h-5" />;
224
- case VideoInputType.SCREEN_VIDEO:
225
- return <ComputerDesktopIcon className="w-5 h-5" />;
226
- case VideoInputType.FILES:
227
- return <DocumentIcon className="w-5 h-5" />;
228
- default:
229
- return <VideoCameraSlashIcon className="w-5 h-5" />;
230
- }
231
- };
232
-
233
- const getAudioIcon = () => {
234
- switch (inputConfiguration.audio.type) {
235
- case AudioInputType.MICROPHONE:
236
- return <MicrophoneIcon className="w-5 h-5" />;
237
- case AudioInputType.SCREEN_AUDIO:
238
- return <ComputerDesktopIcon className="w-5 h-5" />;
239
- case AudioInputType.FILES:
240
- return <DocumentIcon className="w-5 h-5" />;
241
- default:
242
- return <NoSymbolIcon className="w-5 h-5" />;
243
- }
244
- };
245
-
246
- // Panel management handlers
247
- const handleFilesAdded = () => {
248
- setShowFileList(true);
249
- };
250
-
251
- const handleBackToDropzone = () => {
252
- setShowFileList(false);
253
- };
254
-
255
- // const closePanels = () => {
256
- // setActiveVideoPanel(null);
257
- // setActiveAudioPanel(null);
258
- // setShowFileList(false);
259
- // };
260
-
261
- // Close panels when clicking outside or when menus close
262
- // Note: menu close now controlled by toggles
263
-
264
- // Panel content rendering
265
- const renderVideoPanelContent = () => {
266
- if (activeVideoPanel === 'devices') {
267
- const handleDeviceSelect = async () => {
268
- try {
269
- const streams = await updateStreamsForCore();
270
- await updateCoreConfig({ videoStream: streams.videoStream });
271
- } catch (error) {
272
- console.error('Failed to apply video device stream to core:', error);
273
- }
274
- };
275
- return <DeviceSelectionList deviceType="video" onDeviceSelect={handleDeviceSelect} />;
276
- } else if (activeVideoPanel === 'files') {
277
- return showFileList ? (
278
- <FileListPanel fileType="video" onBackToDropzone={handleBackToDropzone} />
279
- ) : (
280
- <FileDropzonePanel fileType="video" onFilesAdded={handleFilesAdded} />
281
- );
282
- }
283
- return null;
284
- };
285
-
286
- const renderAudioPanelContent = () => {
287
- if (activeAudioPanel === 'devices') {
288
- const handleDeviceSelect = async () => {
289
- try {
290
- const streams = await updateStreamsForCore();
291
- await updateCoreConfig({ audioStream: streams.audioStream });
292
- } catch (error) {
293
- console.error('Failed to apply audio device stream to core:', error);
294
- }
295
- };
296
- return <DeviceSelectionList deviceType="audio" onDeviceSelect={handleDeviceSelect} />;
297
- } else if (activeAudioPanel === 'files') {
298
- return showFileList ? (
299
- <FileListPanel fileType="audio" onBackToDropzone={handleBackToDropzone} />
300
- ) : (
301
- <FileDropzonePanel fileType="audio" onFilesAdded={handleFilesAdded} />
302
- );
303
- }
304
- return null;
305
- };
306
-
307
- return (
308
- <div className={`absolute ${className}`}>
309
- {/* Video Input Expansion Panel */}
310
- <InputExpansionPanel
311
- isOpen={activeVideoPanel !== null}
312
- title={activeVideoPanel === 'devices' ? 'Camera' : 'Files'}
313
- className="bottom-0"
314
- >
315
- {renderVideoPanelContent()}
316
- </InputExpansionPanel>
317
-
318
- {/* Audio Input Expansion Panel */}
319
- <InputExpansionPanel
320
- isOpen={activeAudioPanel !== null}
321
- title={activeAudioPanel === 'devices' ? 'Microphone' : 'Files'}
322
- className="bottom-0"
323
- >
324
- {renderAudioPanelContent()}
325
- </InputExpansionPanel>
326
-
327
- <div className="flex flex-col space-y-2 pointer-events-auto">
328
- {/* Video Control */}
329
- <Popover
330
- isOpen={showVideoMenu}
331
- onOpenChange={(isOpen) => {
332
- if (isOpen) {
333
- openVideoMenu();
334
- } else {
335
- // Ignore auto-closes; we close via explicit actions
336
- setTimeout(() => { /* swallow */ }, 0);
337
- }
338
- }}
339
- placement="left"
340
- shouldCloseOnBlur={false}
341
- isDismissable={false}
342
- shouldCloseOnInteractOutside={() => false}
343
- isKeyboardDismissDisabled
344
- portalContainer={typeof document !== 'undefined' ? document.getElementById('root') ?? undefined : undefined}
345
- disableAnimation
346
- >
347
- <PopoverTrigger>
348
- <Button
349
- isIconOnly
350
- variant="flat"
351
- className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
352
- hasVideoInput ? 'ring-2 ring-primary-400' : ''
353
- }`}
354
- aria-label={hasVideoInput ? "Video input active" : "No video input"}
355
- data-role="video-trigger"
356
- onPointerDownCapture={(e) => { e.stopPropagation(); clickInsideVideoRef.current = true; setTimeout(() => { clickInsideVideoRef.current = false; }, 0); }}
357
- onPress={(e: any) => { e?.preventDefault?.(); e?.stopPropagation?.();
358
- if (showVideoMenu) { closeVideoMenu(); } else { openVideoMenu(); }
359
- }}
360
- >
361
- {getVideoIcon()}
362
- </Button>
363
- </PopoverTrigger>
364
- <PopoverContent
365
- className="bg-black/90 backdrop-blur-sm border-white/20"
366
- tabIndex={-1}
367
- onPointerDownCapture={() => { clickInsideVideoRef.current = true; setTimeout(() => { clickInsideVideoRef.current = false; }, 0); }}
368
- >
369
- <Card className="bg-transparent border-0 shadow-none">
370
- <CardBody className="p-3">
371
- <div className="flex flex-col space-y-2">
372
- <Button
373
- size="sm"
374
- variant="flat"
375
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
376
- startContent={<ComputerDesktopIcon className="w-4 h-4" />}
377
- onPress={() => handleVideoInputChange(VideoInputType.SCREEN_VIDEO)}
378
- >
379
- Screen Share
380
- </Button>
381
- <Button
382
- size="sm"
383
- variant="flat"
384
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
385
- startContent={<DocumentIcon className="w-4 h-4" />}
386
- onPress={() => handleVideoInputChange(VideoInputType.FILES)}
387
- >
388
- Video File
389
- </Button>
390
- <Button
391
- size="sm"
392
- variant="flat"
393
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
394
- startContent={<VideoCameraIcon className="w-4 h-4" />}
395
- onPress={() => handleVideoInputChange(VideoInputType.CAMERA)}
396
- >
397
- Camera
398
- </Button>
399
- <Button
400
- size="sm"
401
- variant="flat"
402
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
403
- startContent={<NoSymbolIcon className="w-4 h-4" />}
404
- onPress={() => handleVideoInputChange(VideoInputType.NONE)}
405
- >
406
- No Input
407
- </Button>
408
- </div>
409
- </CardBody>
410
- </Card>
411
- </PopoverContent>
412
- </Popover>
413
-
414
- {/* Audio Control */}
415
- <Popover
416
- isOpen={showAudioMenu}
417
- onOpenChange={(isOpen) => {
418
- if (isOpen) {
419
- openAudioMenu();
420
- } else {
421
- setTimeout(() => { /* swallow */ }, 0);
422
- }
423
- }}
424
- placement="left"
425
- shouldCloseOnBlur={false}
426
- isDismissable={false}
427
- shouldCloseOnInteractOutside={() => false}
428
- isKeyboardDismissDisabled
429
- portalContainer={typeof document !== 'undefined' ? document.getElementById('root') ?? undefined : undefined}
430
- disableAnimation
431
- >
432
- <PopoverTrigger>
433
- <Button
434
- isIconOnly
435
- variant="flat"
436
- className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
437
- hasAudioInput ? 'ring-2 ring-primary-400' : ''
438
- }`}
439
- aria-label={hasAudioInput ? "Audio input active" : "No audio input"}
440
- data-role="audio-trigger"
441
- onPointerDownCapture={(e) => { e.stopPropagation(); clickInsideAudioRef.current = true; setTimeout(() => { clickInsideAudioRef.current = false; }, 0); }}
442
- onPress={(e: any) => { e?.preventDefault?.(); e?.stopPropagation?.();
443
- if (showAudioMenu) { closeAudioMenu(); } else { openAudioMenu(); }
444
- }}
445
- >
446
- {getAudioIcon()}
447
- </Button>
448
- </PopoverTrigger>
449
- <PopoverContent
450
- className="bg-black/90 backdrop-blur-sm border-white/20"
451
- tabIndex={-1}
452
- onPointerDownCapture={() => { clickInsideAudioRef.current = true; setTimeout(() => { clickInsideAudioRef.current = false; }, 0); }}
453
- >
454
- <Card className="bg-transparent border-0 shadow-none">
455
- <CardBody className="p-3">
456
- <div className="flex flex-col space-y-2">
457
- <Button
458
- size="sm"
459
- variant="flat"
460
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
461
- startContent={<ComputerDesktopIcon className="w-4 h-4" />}
462
- onPress={() => handleAudioInputChange(AudioInputType.SCREEN_AUDIO)}
463
- >
464
- Screen Audio
465
- </Button>
466
- <Button
467
- size="sm"
468
- variant="flat"
469
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
470
- startContent={<DocumentIcon className="w-4 h-4" />}
471
- onPress={() => handleAudioInputChange(AudioInputType.FILES)}
472
- >
473
- Audio File
474
- </Button>
475
- <Button
476
- size="sm"
477
- variant="flat"
478
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
479
- startContent={<MicrophoneIcon className="w-4 h-4" />}
480
- onPress={() => handleAudioInputChange(AudioInputType.MICROPHONE)}
481
- >
482
- Microphone
483
- </Button>
484
- <Button
485
- size="sm"
486
- variant="flat"
487
- className="bg-white/10 text-white hover:bg-white/20 justify-start"
488
- startContent={<NoSymbolIcon className="w-4 h-4" />}
489
- onPress={() => handleAudioInputChange(AudioInputType.NONE)}
490
- >
491
- No Input
492
- </Button>
493
- </div>
494
- </CardBody>
495
- </Card>
496
- </PopoverContent>
497
- </Popover>
498
-
499
- {/* Interaction Control */}
500
- <Button
501
- isIconOnly
502
- variant="flat"
503
- className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
504
- interactionEnabled ? 'ring-2 ring-primary-400' : ''
505
- }`}
506
- onPress={handleInteractionToggle}
507
- aria-label={interactionEnabled ? "Disable interactions" : "Enable interactions"}
508
- >
509
- <CursorArrowRaysIcon className="w-5 h-5" />
510
- </Button>
511
- </div>
512
-
513
- {/* Error Display */}
514
- {streamError && (
515
- <div className="absolute top-16 right-0 w-80 z-50">
516
- <Card className="bg-red-900/90 backdrop-blur-sm border border-red-500/50 rounded-xl">
517
- <CardBody className="p-4">
518
- <div className="flex items-start justify-between">
519
- <div className="flex-1">
520
- <h4 className="text-red-100 font-medium text-sm mb-1">Device Error</h4>
521
- <p className="text-red-200 text-xs">{streamError}</p>
522
- </div>
523
- <Button
524
- isIconOnly
525
- size="sm"
526
- variant="light"
527
- className="text-red-200 hover:text-red-100 min-w-6 w-6 h-6"
528
- onPress={() => setStreamError('')}
529
- aria-label="Dismiss error"
530
- >
531
- <NoSymbolIcon className="w-4 h-4" />
532
- </Button>
533
- </div>
534
- </CardBody>
535
- </Card>
536
- </div>
537
- )}
538
- </div>
539
- );
540
- };
541
-
542
- export default QuickInputControls;
@@ -1,96 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { Button, Tooltip } from '@heroui/react';
3
- import {
4
- FolderOpenIcon,
5
- AdjustmentsHorizontalIcon,
6
- Cog6ToothIcon
7
- } from '@heroicons/react/24/outline';
8
- import MenuContainer from './MenuContainer';
9
- import ProjectSwitcherMenu from './ProjectSwitcherMenu';
10
- import ParametersMenu from './ParametersMenu';
11
- import SettingsMenu from './SettingsMenu';
12
-
13
- type MenuType = 'projects' | 'parameters' | 'settings' | null;
14
-
15
- interface SDKMenuSystemProps {
16
- currentProject?: any;
17
- onProjectSwitch?: (project: any) => void;
18
- // Note: UI CRUD removed from ProjectSwitcherMenu - onProjectCreate, onProjectDelete, onProjectRename no longer used
19
- }
20
-
21
- const SDKMenuSystem: React.FC<SDKMenuSystemProps> = ({
22
- currentProject,
23
- onProjectSwitch,
24
- }) => {
25
- // Projects menu should be open by default as specified by user
26
- const [active, setActive] = useState<MenuType>('projects');
27
-
28
- const toggle = (type: MenuType) => setActive((prev) => (prev === type ? null : type));
29
-
30
- return (
31
- <>
32
- <div className="absolute top-4 left-4 z-60 flex space-x-2 pointer-events-auto">
33
- {/* Project Switcher Button - First and opened by default */}
34
- <Tooltip content="Projects" placement="bottom" className="bg-black/80 text-white">
35
- <Button
36
- isIconOnly
37
- variant={active === 'projects' ? 'solid' : 'flat'}
38
- color={active === 'projects' ? 'primary' : 'default'}
39
- className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
40
- onPress={() => toggle('projects')}
41
- aria-label="Toggle projects"
42
- >
43
- <FolderOpenIcon className="w-5 h-5" />
44
- </Button>
45
- </Tooltip>
46
-
47
- {/* Parameters Button */}
48
- <Tooltip content="Parameters" placement="bottom" className="bg-black/80 text-white">
49
- <Button
50
- isIconOnly
51
- variant={active === 'parameters' ? 'solid' : 'flat'}
52
- color={active === 'parameters' ? 'primary' : 'default'}
53
- className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
54
- onPress={() => toggle('parameters')}
55
- aria-label="Toggle parameters"
56
- >
57
- <AdjustmentsHorizontalIcon className="w-5 h-5" />
58
- </Button>
59
- </Tooltip>
60
-
61
- {/* Settings Button */}
62
- <Tooltip content="Settings" placement="bottom" className="bg-black/80 text-white">
63
- <Button
64
- isIconOnly
65
- variant={active === 'settings' ? 'solid' : 'flat'}
66
- color={active === 'settings' ? 'primary' : 'default'}
67
- className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
68
- onPress={() => toggle('settings')}
69
- aria-label="Toggle settings"
70
- >
71
- <Cog6ToothIcon className="w-5 h-5" />
72
- </Button>
73
- </Tooltip>
74
- </div>
75
-
76
- {active && (
77
- <MenuContainer isOpen={!!active} activeMenu={active as any} onClose={() => setActive(null)}>
78
- {active === 'projects' && (
79
- <ProjectSwitcherMenu
80
- currentProject={currentProject}
81
- onProjectSwitch={onProjectSwitch}
82
- />
83
- )}
84
- {active === 'parameters' && (
85
- <ParametersMenu hidePresetsControls />
86
- )}
87
- {active === 'settings' && (
88
- <SettingsMenu />
89
- )}
90
- </MenuContainer>
91
- )}
92
- </>
93
- );
94
- };
95
-
96
- export default SDKMenuSystem;