@effectnode/media 0.1.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/bin/effectnode-media +2 -0
- package/dist/backend/index.d.ts +8 -0
- package/dist/backend/index.js +13 -0
- package/dist/backend/movie-backend/agent/agent-backend.d.ts +16 -0
- package/dist/backend/movie-backend/agent/agent-backend.js +703 -0
- package/dist/backend/movie-backend/agent/storyprompt.d.ts +1 -0
- package/dist/backend/movie-backend/agent/storyprompt.js +90 -0
- package/dist/backend/movie-backend/agent/tools/edit-image.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/edit-image.js +192 -0
- package/dist/backend/movie-backend/agent/tools/get-time.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/get-time.js +7 -0
- package/dist/backend/movie-backend/agent/tools/grep-files.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/grep-files.js +59 -0
- package/dist/backend/movie-backend/agent/tools/image-to-video-generation.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/image-to-video-generation.js +160 -0
- package/dist/backend/movie-backend/agent/tools/index.d.ts +7 -0
- package/dist/backend/movie-backend/agent/tools/index.js +59 -0
- package/dist/backend/movie-backend/agent/tools/list-files.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/list-files.js +17 -0
- package/dist/backend/movie-backend/agent/tools/read-file.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/read-file.js +28 -0
- package/dist/backend/movie-backend/agent/tools/remove-file.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/remove-file.js +22 -0
- package/dist/backend/movie-backend/agent/tools/rename-file.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/rename-file.js +37 -0
- package/dist/backend/movie-backend/agent/tools/show-image.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/show-image.js +28 -0
- package/dist/backend/movie-backend/agent/tools/stitch-videos.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/stitch-videos.js +96 -0
- package/dist/backend/movie-backend/agent/tools/text-to-video-generation.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/text-to-video-generation.js +125 -0
- package/dist/backend/movie-backend/agent/tools/types.d.ts +15 -0
- package/dist/backend/movie-backend/agent/tools/types.js +1 -0
- package/dist/backend/movie-backend/agent/tools/update-file.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/update-file.js +27 -0
- package/dist/backend/movie-backend/agent/tools/write-file.d.ts +3 -0
- package/dist/backend/movie-backend/agent/tools/write-file.js +26 -0
- package/dist/backend/movie-backend/agent/workspace.d.ts +25 -0
- package/dist/backend/movie-backend/agent/workspace.js +124 -0
- package/dist/backend/movie-backend/core.d.ts +4 -0
- package/dist/backend/movie-backend/core.js +1167 -0
- package/dist/backend/movie-backend/generation-queue.d.ts +24 -0
- package/dist/backend/movie-backend/generation-queue.js +766 -0
- package/dist/backend/movie-backend/process.d.ts +21 -0
- package/dist/backend/movie-backend/process.js +98 -0
- package/dist/backend/movie-backend/render-media.d.ts +48 -0
- package/dist/backend/movie-backend/render-media.js +3311 -0
- package/dist/backend/routes/health.d.ts +1 -0
- package/dist/backend/routes/health.js +9 -0
- package/dist/backend/routes/index.d.ts +1 -0
- package/dist/backend/routes/index.js +11 -0
- package/dist/backend/routes/media.d.ts +1 -0
- package/dist/backend/routes/media.js +13 -0
- package/dist/backend/routes/projects.d.ts +8 -0
- package/dist/backend/routes/projects.js +68 -0
- package/dist/backend/routes/voice.d.ts +1 -0
- package/dist/backend/routes/voice.js +39 -0
- package/dist/backend/routes.d.ts +1 -0
- package/dist/backend/routes.js +20 -0
- package/dist/backend/server.d.ts +1 -0
- package/dist/backend/server.js +23 -0
- package/dist/backend/workspace.d.ts +5 -0
- package/dist/backend/workspace.js +27 -0
- package/dist/backend/ws/index.d.ts +3 -0
- package/dist/backend/ws/index.js +27 -0
- package/dist/backend/ws/messages.d.ts +8 -0
- package/dist/backend/ws/messages.js +21 -0
- package/dist/backend/ws.d.ts +3 -0
- package/dist/backend/ws.js +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +80 -0
- package/frontend/index.html +22 -0
- package/frontend/public/ffmpeg/ffmpeg-core.js +16 -0
- package/frontend/public/ffmpeg/ffmpeg-core.wasm +0 -0
- package/frontend/src/index.css +46 -0
- package/frontend/src/main.tsx +13 -0
- package/frontend/src/movie-app/AppRouter.tsx +14 -0
- package/frontend/src/movie-app/MediaStudio.tsx +9 -0
- package/frontend/src/movie-app/SetupPage.tsx +293 -0
- package/frontend/src/movie-app/components/AgentWorkspace.tsx +574 -0
- package/frontend/src/movie-app/components/ChatUI/ChatUI.tsx +655 -0
- package/frontend/src/movie-app/components/ChatUI/MarkdownMessage.tsx +267 -0
- package/frontend/src/movie-app/components/EditorTabs/AgentTab.tsx +61 -0
- package/frontend/src/movie-app/components/EditorTabs/BatchImageToVideoTab.tsx +927 -0
- package/frontend/src/movie-app/components/EditorTabs/BatchVideoTab.tsx +777 -0
- package/frontend/src/movie-app/components/EditorTabs/BatchVoiceVideoTab.tsx +913 -0
- package/frontend/src/movie-app/components/EditorTabs/CharacterSheet.tsx +234 -0
- package/frontend/src/movie-app/components/EditorTabs/CharactersTab.tsx +664 -0
- package/frontend/src/movie-app/components/EditorTabs/CropTool.tsx +251 -0
- package/frontend/src/movie-app/components/EditorTabs/ExtendVideoTab.tsx +305 -0
- package/frontend/src/movie-app/components/EditorTabs/ExtractImageTab.tsx +249 -0
- package/frontend/src/movie-app/components/EditorTabs/FastImageEditTab.tsx +555 -0
- package/frontend/src/movie-app/components/EditorTabs/GenerateVideoTab.tsx +722 -0
- package/frontend/src/movie-app/components/EditorTabs/LlmServerTab.tsx +40 -0
- package/frontend/src/movie-app/components/EditorTabs/MlxVlmServerPanel.tsx +490 -0
- package/frontend/src/movie-app/components/EditorTabs/MovieStudioTab.tsx +938 -0
- package/frontend/src/movie-app/components/EditorTabs/ReferencesToVideoTab.tsx +881 -0
- package/frontend/src/movie-app/components/EditorTabs/SceneVisualTab.tsx +267 -0
- package/frontend/src/movie-app/components/EditorTabs/StoryWriterTab.tsx +51 -0
- package/frontend/src/movie-app/components/EditorTabs/TaskQueuePanel.tsx +246 -0
- package/frontend/src/movie-app/components/EditorTabs/TerminalLogPanel.tsx +49 -0
- package/frontend/src/movie-app/components/EditorTabs/TextToImageTab.tsx +574 -0
- package/frontend/src/movie-app/components/EditorTabs/VoiceChatPanel.tsx +320 -0
- package/frontend/src/movie-app/components/ProjectEditorPage.tsx +636 -0
- package/frontend/src/movie-app/components/ProjectManager.tsx +381 -0
- package/frontend/src/movie-app/index.css +46 -0
- package/frontend/src/movie-app/index.html +16 -0
- package/frontend/src/movie-app/lambobo.png +0 -0
- package/frontend/src/movie-app/lib/batchImageToVideoStorage.ts +69 -0
- package/frontend/src/movie-app/lib/batchVideoStorage.ts +68 -0
- package/frontend/src/movie-app/lib/batchVoiceStorage.ts +75 -0
- package/frontend/src/movie-app/lib/ffmpeg.ts +42 -0
- package/frontend/src/movie-app/lib/referencesToVideoStorage.ts +64 -0
- package/frontend/src/movie-app/lib/textToImageStorage.ts +61 -0
- package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.js +16 -0
- package/frontend/src/movie-app/public/ffmpeg/ffmpeg-core.wasm +0 -0
- package/frontend/src/movie-app/stores/batchImageToVideoStore.ts +736 -0
- package/frontend/src/movie-app/stores/batchVideoStore.ts +670 -0
- package/frontend/src/movie-app/stores/batchVoiceStore.ts +990 -0
- package/frontend/src/movie-app/stores/characterStore.ts +109 -0
- package/frontend/src/movie-app/stores/chatStore.ts +459 -0
- package/frontend/src/movie-app/stores/generationStore.ts +2140 -0
- package/frontend/src/movie-app/stores/logStore.ts +25 -0
- package/frontend/src/movie-app/stores/movieStudioStore.ts +709 -0
- package/frontend/src/movie-app/stores/projectStore.ts +124 -0
- package/frontend/src/movie-app/stores/queueStore.ts +214 -0
- package/frontend/src/movie-app/stores/referencesToVideoStore.ts +467 -0
- package/frontend/src/movie-app/stores/sceneVisualStore.ts +251 -0
- package/frontend/src/movie-app/stores/voiceChatStore.ts +170 -0
- package/frontend/src/movie-app/stores/workspaceStore.ts +158 -0
- package/frontend/src/vite-env.d.ts +1 -0
- package/frontend/tsconfig.json +19 -0
- package/package.json +65 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useVoiceChatStore } from "../../stores/voiceChatStore";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
projectId: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// ========== SVG Icons ==========
|
|
9
|
+
|
|
10
|
+
const MicIcon = (
|
|
11
|
+
<svg
|
|
12
|
+
width="16"
|
|
13
|
+
height="16"
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
strokeWidth="2"
|
|
18
|
+
strokeLinecap="round"
|
|
19
|
+
strokeLinejoin="round"
|
|
20
|
+
>
|
|
21
|
+
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
|
22
|
+
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
|
23
|
+
<line x1="12" y1="19" x2="12" y2="23" />
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const UploadIcon = (
|
|
28
|
+
<svg
|
|
29
|
+
width="14"
|
|
30
|
+
height="14"
|
|
31
|
+
viewBox="0 0 24 24"
|
|
32
|
+
fill="none"
|
|
33
|
+
stroke="currentColor"
|
|
34
|
+
strokeWidth="2"
|
|
35
|
+
strokeLinecap="round"
|
|
36
|
+
strokeLinejoin="round"
|
|
37
|
+
>
|
|
38
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
39
|
+
<polyline points="17 8 12 3 7 8" />
|
|
40
|
+
<line x1="12" y1="3" x2="12" y2="15" />
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const SendIcon = (
|
|
45
|
+
<svg
|
|
46
|
+
width="16"
|
|
47
|
+
height="16"
|
|
48
|
+
viewBox="0 0 24 24"
|
|
49
|
+
fill="none"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
strokeWidth="2"
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
>
|
|
55
|
+
<line x1="22" y1="2" x2="11" y2="13" />
|
|
56
|
+
<polygon points="22 2 15 22 11 13 2 9 22 2" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const AudioIcon = (
|
|
61
|
+
<svg
|
|
62
|
+
width="16"
|
|
63
|
+
height="16"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
fill="none"
|
|
66
|
+
stroke="currentColor"
|
|
67
|
+
strokeWidth="2"
|
|
68
|
+
strokeLinecap="round"
|
|
69
|
+
strokeLinejoin="round"
|
|
70
|
+
>
|
|
71
|
+
<path d="M9 18V5l12-2v13" />
|
|
72
|
+
<circle cx="6" cy="18" r="3" />
|
|
73
|
+
<circle cx="18" cy="16" r="3" />
|
|
74
|
+
</svg>
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const SpinnerIcon = (
|
|
78
|
+
<svg
|
|
79
|
+
className="animate-spin text-tiffany-600"
|
|
80
|
+
width="16"
|
|
81
|
+
height="16"
|
|
82
|
+
viewBox="0 0 24 24"
|
|
83
|
+
fill="none"
|
|
84
|
+
stroke="currentColor"
|
|
85
|
+
strokeWidth="2"
|
|
86
|
+
>
|
|
87
|
+
<circle cx="12" cy="12" r="10" strokeOpacity="0.25" />
|
|
88
|
+
<path d="M12 2a10 10 0 0 1 10 10" strokeOpacity="0.75" />
|
|
89
|
+
</svg>
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// ========== Component ==========
|
|
93
|
+
|
|
94
|
+
export default function VoiceChatPanel({ projectId }: Props) {
|
|
95
|
+
const store = useVoiceChatStore();
|
|
96
|
+
|
|
97
|
+
const fileRef = useRef<HTMLInputElement>(null);
|
|
98
|
+
const recognitionRef = useRef<any>(null);
|
|
99
|
+
const [sttError, setSttError] = useState<string | null>(null);
|
|
100
|
+
|
|
101
|
+
// Clean up any active recognition on unmount.
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
return () => {
|
|
104
|
+
if (recognitionRef.current) {
|
|
105
|
+
try {
|
|
106
|
+
recognitionRef.current.stop();
|
|
107
|
+
} catch {
|
|
108
|
+
// ignore
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
const handleUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
115
|
+
const file = e.target.files?.[0];
|
|
116
|
+
if (!file) return;
|
|
117
|
+
|
|
118
|
+
const reader = new FileReader();
|
|
119
|
+
reader.onload = async () => {
|
|
120
|
+
const base64 = reader.result as string;
|
|
121
|
+
await store.uploadRefAudio(base64, file.name, projectId);
|
|
122
|
+
};
|
|
123
|
+
reader.readAsDataURL(file);
|
|
124
|
+
e.target.value = "";
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const stopListening = () => {
|
|
128
|
+
if (recognitionRef.current) {
|
|
129
|
+
try {
|
|
130
|
+
recognitionRef.current.stop();
|
|
131
|
+
} catch {
|
|
132
|
+
// ignore
|
|
133
|
+
}
|
|
134
|
+
recognitionRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
store.setListening(false);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const startListening = () => {
|
|
140
|
+
const SpeechRecognitionCtor =
|
|
141
|
+
(window as any).SpeechRecognition ||
|
|
142
|
+
(window as any).webkitSpeechRecognition;
|
|
143
|
+
|
|
144
|
+
if (!SpeechRecognitionCtor) {
|
|
145
|
+
setSttError("Speech recognition is not supported in this browser.");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
setSttError(null);
|
|
150
|
+
|
|
151
|
+
const recognition = new SpeechRecognitionCtor();
|
|
152
|
+
recognition.lang = "en-US";
|
|
153
|
+
recognition.interimResults = false;
|
|
154
|
+
recognition.continuous = false;
|
|
155
|
+
|
|
156
|
+
recognition.onresult = (event: any) => {
|
|
157
|
+
let transcript = "";
|
|
158
|
+
for (let i = 0; i < event.results.length; i++) {
|
|
159
|
+
transcript += event.results[i][0].transcript;
|
|
160
|
+
}
|
|
161
|
+
const trimmed = transcript.trim();
|
|
162
|
+
if (trimmed) {
|
|
163
|
+
store.setText(trimmed);
|
|
164
|
+
// Auto-send: speak → transcribe → fill the box → generate voice.
|
|
165
|
+
store.generate(projectId);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
recognition.onerror = () => {
|
|
169
|
+
store.setListening(false);
|
|
170
|
+
recognitionRef.current = null;
|
|
171
|
+
};
|
|
172
|
+
recognition.onend = () => {
|
|
173
|
+
store.setListening(false);
|
|
174
|
+
recognitionRef.current = null;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
recognitionRef.current = recognition;
|
|
178
|
+
store.setListening(true);
|
|
179
|
+
recognition.start();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const handleMic = () => {
|
|
183
|
+
if (store.listening) {
|
|
184
|
+
stopListening();
|
|
185
|
+
} else {
|
|
186
|
+
startListening();
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const handleSend = () => {
|
|
191
|
+
store.generate(projectId);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const canSend =
|
|
195
|
+
!store.generating &&
|
|
196
|
+
store.text.trim().length > 0 &&
|
|
197
|
+
store.refAudioFilename != null;
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div className="border border-ink-200 rounded-2xl p-5 flex flex-col gap-6">
|
|
201
|
+
<div className="flex items-center gap-2">
|
|
202
|
+
{AudioIcon}
|
|
203
|
+
<span className="text-sm font-semibold text-ink-900">
|
|
204
|
+
Voice Chat
|
|
205
|
+
</span>
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
{/* Reference voice upload */}
|
|
209
|
+
<div className="flex flex-col gap-2">
|
|
210
|
+
<label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider">
|
|
211
|
+
Reference Voice
|
|
212
|
+
</label>
|
|
213
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
214
|
+
<input
|
|
215
|
+
ref={fileRef}
|
|
216
|
+
type="file"
|
|
217
|
+
accept="audio/*"
|
|
218
|
+
onChange={handleUpload}
|
|
219
|
+
className="hidden"
|
|
220
|
+
/>
|
|
221
|
+
<button
|
|
222
|
+
onClick={() => fileRef.current?.click()}
|
|
223
|
+
disabled={store.generating}
|
|
224
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-xl border border-ink-200 bg-white text-ink-600 text-xs font-medium cursor-pointer hover:border-ink-300 hover:bg-ink-100 transition-colors disabled:opacity-50"
|
|
225
|
+
>
|
|
226
|
+
{UploadIcon}
|
|
227
|
+
{store.refAudioFilename ? "Replace" : "Upload"}
|
|
228
|
+
</button>
|
|
229
|
+
{store.refAudioFilename && (
|
|
230
|
+
<span className="text-[11px] text-ink-600 truncate max-w-[200px]">
|
|
231
|
+
{store.refAudioFilename}
|
|
232
|
+
</span>
|
|
233
|
+
)}
|
|
234
|
+
</div>
|
|
235
|
+
{store.refAudioUrl && (
|
|
236
|
+
<audio
|
|
237
|
+
controls
|
|
238
|
+
src={store.refAudioUrl}
|
|
239
|
+
className="w-full max-w-[320px] h-9"
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* Text input */}
|
|
245
|
+
<div className="flex items-end gap-2">
|
|
246
|
+
<textarea
|
|
247
|
+
value={store.text}
|
|
248
|
+
onChange={(e) => store.setText(e.target.value)}
|
|
249
|
+
rows={2}
|
|
250
|
+
placeholder="Type or dictate what the cloned voice should say..."
|
|
251
|
+
disabled={store.generating}
|
|
252
|
+
className="flex-1 px-3 py-2 bg-ink-50 border border-ink-200 rounded-2xl text-ink-900 text-sm placeholder-ink-500/40 focus:outline-none focus:border-tiffany-500 focus:ring-2 focus:ring-tiffany-500/30 transition-all resize-none disabled:opacity-50"
|
|
253
|
+
/>
|
|
254
|
+
<button
|
|
255
|
+
onClick={handleMic}
|
|
256
|
+
disabled={store.generating}
|
|
257
|
+
className={`flex items-center justify-center w-10 h-10 shrink-0 rounded-2xl border transition-all ${
|
|
258
|
+
store.listening
|
|
259
|
+
? "bg-red-500 border-red-500 text-white animate-pulse"
|
|
260
|
+
: "border-ink-200 text-ink-600 hover:border-ink-300 hover:bg-ink-100"
|
|
261
|
+
} disabled:opacity-50`}
|
|
262
|
+
title={store.listening ? "Stop listening" : "Speak"}
|
|
263
|
+
>
|
|
264
|
+
{MicIcon}
|
|
265
|
+
</button>
|
|
266
|
+
{store.generating ? (
|
|
267
|
+
<div className="flex items-center justify-center gap-2 px-4 py-2.5 bg-ink-100 text-ink-700 text-sm font-medium rounded-2xl">
|
|
268
|
+
{SpinnerIcon}
|
|
269
|
+
Speaking...
|
|
270
|
+
</div>
|
|
271
|
+
) : (
|
|
272
|
+
<button
|
|
273
|
+
onClick={handleSend}
|
|
274
|
+
disabled={!canSend}
|
|
275
|
+
className="flex items-center justify-center gap-2 px-4 py-2.5 bg-tiffany-500 hover:bg-tiffany-600 active:bg-tiffany-700 disabled:bg-ink-200 disabled:text-ink-500 text-ink-950 text-sm font-semibold rounded-2xl transition-all duration-150 shadow-sm"
|
|
276
|
+
>
|
|
277
|
+
{SendIcon}
|
|
278
|
+
Send
|
|
279
|
+
</button>
|
|
280
|
+
)}
|
|
281
|
+
</div>
|
|
282
|
+
|
|
283
|
+
{store.listening && (
|
|
284
|
+
<p className="text-xs text-red-600 -mt-1">Listening… speak now.</p>
|
|
285
|
+
)}
|
|
286
|
+
{sttError && <p className="text-xs text-red-600">{sttError}</p>}
|
|
287
|
+
|
|
288
|
+
{/* Result */}
|
|
289
|
+
{store.resultUrl && (
|
|
290
|
+
<div>
|
|
291
|
+
<label className="block text-xs font-semibold text-ink-700 uppercase tracking-wider mb-2">
|
|
292
|
+
Response
|
|
293
|
+
</label>
|
|
294
|
+
<audio
|
|
295
|
+
controls
|
|
296
|
+
autoPlay
|
|
297
|
+
src={store.resultUrl}
|
|
298
|
+
className="w-full max-w-[360px] h-9"
|
|
299
|
+
/>
|
|
300
|
+
</div>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
{/* Error */}
|
|
304
|
+
{store.error && (
|
|
305
|
+
<div className="p-4 bg-red-50 border border-red-200 rounded-2xl text-red-600 text-xs">
|
|
306
|
+
{store.error}
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
|
|
310
|
+
{/* Logs */}
|
|
311
|
+
{store.logs.length > 0 && (
|
|
312
|
+
<div className="p-4 bg-ink-50 border border-ink-200 rounded-2xl max-h-32 overflow-y-auto">
|
|
313
|
+
<pre className="text-xs text-ink-600 font-mono whitespace-pre-wrap">
|
|
314
|
+
{store.logs.join("")}
|
|
315
|
+
</pre>
|
|
316
|
+
</div>
|
|
317
|
+
)}
|
|
318
|
+
</div>
|
|
319
|
+
);
|
|
320
|
+
}
|