sparkecoder 0.1.20 → 0.1.22
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/agent/index.d.ts +2 -2
- package/dist/agent/index.js +53 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +397 -46
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -1
- package/dist/db/index.js.map +1 -1
- package/dist/{index-BzedNBK-.d.ts → index-CNwLFGiZ.d.ts} +24 -3
- package/dist/index.d.ts +4 -4
- package/dist/index.js +392 -41
- package/dist/index.js.map +1 -1
- package/dist/{schema-CkrIadxa.d.ts → schema-Df7MU3nM.d.ts} +26 -3
- package/dist/server/index.js +392 -41
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/build-manifest.json +2 -2
- package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1d78db71._.js → 2374f_387a1437._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_378282b1._.js → 2374f_5f58fd73._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_30f9df13._.js → 2374f_65fcfd95._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8825dcc9._.js → 2374f_741f6b67._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9bf3c7f3._.js → 2374f_814be2c9._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5de336d2._.js → 2374f_84859a94._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bbc99511._.js → 2374f_cfd0137a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d94c2b70._.js → 2374f_f1038f7c._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__a984d933._.js → [root-of-the-server]__3ec22171._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c7618534._.js +8 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +1 -1
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
- package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +5 -0
- package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/src/app/(main)/layout.tsx +2 -2
- package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +89 -36
- package/web/.next/standalone/web/src/components/chat-interface.tsx +354 -38
- package/web/.next/standalone/web/src/components/ui/sidebar.tsx +2 -2
- package/web/.next/standalone/web/src/lib/api.ts +133 -2
- package/web/.next/static/chunks/{55705f91c6cfe307.js → 3bb454ca848ec78e.js} +3 -3
- package/web/.next/static/chunks/{5ec82ce8f3aabaf0.js → 5e5b485d77ac0d8f.js} +1 -1
- package/web/.next/static/chunks/{c9d8a4b9a763e232.js → beb9625c4a470042.js} +1 -1
- package/web/.next/static/chunks/c81c1aec4369c77f.js +5 -0
- package/web/.next/static/chunks/cb355fac10c6ad11.css +1 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_19b6934c._.js +0 -8
- package/web/.next/standalone/web/.next/static/chunks/61d61c75ce7cd4ba.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/61d61c75ce7cd4ba.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/d0a69c59b1c0d99c.css +0 -1
- package/web/.next/static/chunks/61d61c75ce7cd4ba.js +0 -5
- package/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
- /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
- /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_buildManifest.js +0 -0
- /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{ogmHZngrFt0TlARPSetHj → n86r6x1RoUipFp6nLIk-R}/_ssgManifest.js +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { Button } from "@/components/ui/button";
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
|
-
import { LoaderIcon, MicIcon,
|
|
5
|
+
import { LoaderIcon, MicIcon, MicOffIcon } from "lucide-react";
|
|
6
6
|
import {
|
|
7
7
|
type ComponentProps,
|
|
8
8
|
useCallback,
|
|
@@ -17,6 +17,7 @@ interface SpeechRecognition extends EventTarget {
|
|
|
17
17
|
lang: string;
|
|
18
18
|
start(): void;
|
|
19
19
|
stop(): void;
|
|
20
|
+
abort(): void;
|
|
20
21
|
onstart: ((this: SpeechRecognition, ev: Event) => void) | null;
|
|
21
22
|
onend: ((this: SpeechRecognition, ev: Event) => void) | null;
|
|
22
23
|
onresult:
|
|
@@ -67,8 +68,11 @@ declare global {
|
|
|
67
68
|
|
|
68
69
|
type SpeechInputMode = "speech-recognition" | "media-recorder" | "none";
|
|
69
70
|
|
|
70
|
-
export type SpeechInputProps = ComponentProps<typeof Button> & {
|
|
71
|
+
export type SpeechInputProps = Omit<ComponentProps<typeof Button>, 'onClick'> & {
|
|
72
|
+
/** Called with finalized transcript text (confirmed words) */
|
|
71
73
|
onTranscriptionChange?: (text: string) => void;
|
|
74
|
+
/** Called with interim transcript text as user speaks (live preview) */
|
|
75
|
+
onInterimTranscription?: (text: string) => void;
|
|
72
76
|
/**
|
|
73
77
|
* Callback for when audio is recorded using MediaRecorder fallback.
|
|
74
78
|
* This is called in browsers that don't support the Web Speech API (Firefox, Safari).
|
|
@@ -98,19 +102,20 @@ const detectSpeechInputMode = (): SpeechInputMode => {
|
|
|
98
102
|
export const SpeechInput = ({
|
|
99
103
|
className,
|
|
100
104
|
onTranscriptionChange,
|
|
105
|
+
onInterimTranscription,
|
|
101
106
|
onAudioRecorded,
|
|
102
107
|
lang = "en-US",
|
|
108
|
+
disabled,
|
|
103
109
|
...props
|
|
104
110
|
}: SpeechInputProps) => {
|
|
105
111
|
const [isListening, setIsListening] = useState(false);
|
|
106
112
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
107
113
|
const [mode, setMode] = useState<SpeechInputMode>("none");
|
|
108
|
-
const [recognition, setRecognition] = useState<SpeechRecognition | null>(
|
|
109
|
-
null
|
|
110
|
-
);
|
|
111
114
|
const recognitionRef = useRef<SpeechRecognition | null>(null);
|
|
112
115
|
const mediaRecorderRef = useRef<MediaRecorder | null>(null);
|
|
113
116
|
const audioChunksRef = useRef<Blob[]>([]);
|
|
117
|
+
// Track the accumulated final transcript for the current session
|
|
118
|
+
const sessionTranscriptRef = useRef<string>("");
|
|
114
119
|
|
|
115
120
|
// Detect mode on mount
|
|
116
121
|
useEffect(() => {
|
|
@@ -123,9 +128,9 @@ export const SpeechInput = ({
|
|
|
123
128
|
return;
|
|
124
129
|
}
|
|
125
130
|
|
|
126
|
-
const
|
|
131
|
+
const SpeechRecognitionAPI =
|
|
127
132
|
window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
128
|
-
const speechRecognition = new
|
|
133
|
+
const speechRecognition = new SpeechRecognitionAPI();
|
|
129
134
|
|
|
130
135
|
speechRecognition.continuous = true;
|
|
131
136
|
speechRecognition.interimResults = true;
|
|
@@ -133,41 +138,58 @@ export const SpeechInput = ({
|
|
|
133
138
|
|
|
134
139
|
speechRecognition.onstart = () => {
|
|
135
140
|
setIsListening(true);
|
|
141
|
+
sessionTranscriptRef.current = "";
|
|
136
142
|
};
|
|
137
143
|
|
|
138
144
|
speechRecognition.onend = () => {
|
|
139
145
|
setIsListening(false);
|
|
146
|
+
// Clear any interim display when stopped
|
|
147
|
+
onInterimTranscription?.("");
|
|
140
148
|
};
|
|
141
149
|
|
|
142
150
|
speechRecognition.onresult = (event) => {
|
|
143
|
-
let
|
|
151
|
+
let interimTranscript = "";
|
|
152
|
+
let newFinalTranscript = "";
|
|
144
153
|
|
|
145
154
|
for (let i = event.resultIndex; i < event.results.length; i++) {
|
|
146
155
|
const result = event.results[i];
|
|
156
|
+
const transcript = result[0]?.transcript ?? "";
|
|
157
|
+
|
|
147
158
|
if (result.isFinal) {
|
|
148
|
-
|
|
159
|
+
newFinalTranscript += transcript;
|
|
160
|
+
} else {
|
|
161
|
+
interimTranscript += transcript;
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
// If we have new finalized text, add it to the input
|
|
166
|
+
if (newFinalTranscript) {
|
|
167
|
+
sessionTranscriptRef.current += newFinalTranscript;
|
|
168
|
+
onTranscriptionChange?.(newFinalTranscript);
|
|
169
|
+
// Clear interim since it's now final
|
|
170
|
+
onInterimTranscription?.("");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Show interim results as live preview
|
|
174
|
+
if (interimTranscript) {
|
|
175
|
+
onInterimTranscription?.(interimTranscript);
|
|
154
176
|
}
|
|
155
177
|
};
|
|
156
178
|
|
|
157
179
|
speechRecognition.onerror = (event) => {
|
|
158
180
|
console.error("Speech recognition error:", event.error);
|
|
159
181
|
setIsListening(false);
|
|
182
|
+
onInterimTranscription?.("");
|
|
160
183
|
};
|
|
161
184
|
|
|
162
185
|
recognitionRef.current = speechRecognition;
|
|
163
|
-
setRecognition(speechRecognition);
|
|
164
186
|
|
|
165
187
|
return () => {
|
|
166
188
|
if (recognitionRef.current) {
|
|
167
|
-
recognitionRef.current.
|
|
189
|
+
recognitionRef.current.abort();
|
|
168
190
|
}
|
|
169
191
|
};
|
|
170
|
-
}, [mode, onTranscriptionChange,
|
|
192
|
+
}, [mode, lang, onTranscriptionChange, onInterimTranscription]);
|
|
171
193
|
|
|
172
194
|
// Start MediaRecorder recording
|
|
173
195
|
const startMediaRecorder = useCallback(async () => {
|
|
@@ -241,11 +263,16 @@ export const SpeechInput = ({
|
|
|
241
263
|
}, []);
|
|
242
264
|
|
|
243
265
|
const toggleListening = useCallback(() => {
|
|
244
|
-
if (mode === "speech-recognition" &&
|
|
266
|
+
if (mode === "speech-recognition" && recognitionRef.current) {
|
|
245
267
|
if (isListening) {
|
|
246
|
-
|
|
268
|
+
recognitionRef.current.stop();
|
|
247
269
|
} else {
|
|
248
|
-
|
|
270
|
+
try {
|
|
271
|
+
recognitionRef.current.start();
|
|
272
|
+
} catch (e) {
|
|
273
|
+
// Recognition might already be started
|
|
274
|
+
console.warn("Speech recognition start error:", e);
|
|
275
|
+
}
|
|
249
276
|
}
|
|
250
277
|
} else if (mode === "media-recorder") {
|
|
251
278
|
if (isListening) {
|
|
@@ -254,45 +281,71 @@ export const SpeechInput = ({
|
|
|
254
281
|
startMediaRecorder();
|
|
255
282
|
}
|
|
256
283
|
}
|
|
257
|
-
}, [mode,
|
|
284
|
+
}, [mode, isListening, startMediaRecorder, stopMediaRecorder]);
|
|
258
285
|
|
|
259
286
|
// Determine if button should be disabled
|
|
260
|
-
const
|
|
287
|
+
const isButtonDisabled =
|
|
288
|
+
disabled ||
|
|
261
289
|
mode === "none" ||
|
|
262
|
-
(mode === "speech-recognition" && !
|
|
290
|
+
(mode === "speech-recognition" && !recognitionRef.current) ||
|
|
263
291
|
(mode === "media-recorder" && !onAudioRecorded) ||
|
|
264
292
|
isProcessing;
|
|
265
293
|
|
|
294
|
+
// Not supported - show disabled mic with tooltip hint
|
|
295
|
+
if (mode === "none") {
|
|
296
|
+
return (
|
|
297
|
+
<Button
|
|
298
|
+
type="button"
|
|
299
|
+
className={cn(
|
|
300
|
+
"relative rounded-full opacity-50 cursor-not-allowed",
|
|
301
|
+
className
|
|
302
|
+
)}
|
|
303
|
+
disabled
|
|
304
|
+
title="Speech recognition not supported in this browser"
|
|
305
|
+
{...props}
|
|
306
|
+
>
|
|
307
|
+
<MicOffIcon className="size-4" />
|
|
308
|
+
</Button>
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
266
312
|
return (
|
|
267
313
|
<div className="relative inline-flex items-center justify-center">
|
|
268
|
-
{/* Animated pulse rings */}
|
|
269
|
-
{isListening &&
|
|
270
|
-
|
|
271
|
-
<
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
style={{
|
|
275
|
-
animationDelay: `${index * 0.3}s`,
|
|
276
|
-
animationDuration: "2s",
|
|
277
|
-
}}
|
|
314
|
+
{/* Animated pulse rings when listening */}
|
|
315
|
+
{isListening && (
|
|
316
|
+
<>
|
|
317
|
+
<span className="absolute inset-0 rounded-full bg-red-500/20 animate-ping" />
|
|
318
|
+
<span
|
|
319
|
+
className="absolute inset-0 rounded-full bg-red-500/10 animate-ping"
|
|
320
|
+
style={{ animationDelay: "0.2s" }}
|
|
278
321
|
/>
|
|
279
|
-
|
|
322
|
+
</>
|
|
323
|
+
)}
|
|
280
324
|
|
|
281
325
|
{/* Main record button */}
|
|
282
326
|
<Button
|
|
327
|
+
type="button"
|
|
283
328
|
className={cn(
|
|
284
|
-
"relative z-10 rounded-full transition-all duration-
|
|
329
|
+
"relative z-10 rounded-full transition-all duration-200",
|
|
285
330
|
isListening
|
|
286
|
-
? "bg-
|
|
287
|
-
: "
|
|
331
|
+
? "bg-red-500 text-white hover:bg-red-600 shadow-lg shadow-red-500/25"
|
|
332
|
+
: "hover:bg-muted",
|
|
288
333
|
className
|
|
289
334
|
)}
|
|
290
|
-
disabled={
|
|
335
|
+
disabled={isButtonDisabled}
|
|
291
336
|
onClick={toggleListening}
|
|
337
|
+
title={isListening ? "Stop recording" : "Start voice input"}
|
|
338
|
+
variant={isListening ? "default" : "ghost"}
|
|
292
339
|
{...props}
|
|
293
340
|
>
|
|
294
341
|
{isProcessing && <LoaderIcon className="size-4 animate-spin" />}
|
|
295
|
-
{!isProcessing && isListening &&
|
|
342
|
+
{!isProcessing && isListening && (
|
|
343
|
+
<div className="relative">
|
|
344
|
+
<MicIcon className="size-4" />
|
|
345
|
+
{/* Recording indicator dot */}
|
|
346
|
+
<span className="absolute -top-0.5 -right-0.5 size-2 bg-white rounded-full animate-pulse" />
|
|
347
|
+
</div>
|
|
348
|
+
)}
|
|
296
349
|
{!(isProcessing || isListening) && <MicIcon className="size-4" />}
|
|
297
350
|
</Button>
|
|
298
351
|
</div>
|