chenbhao commited on
Commit
d2a78c4
·
1 Parent(s): a260d88

feat: polling voice create stt 5s

Browse files

架构变更:浏览器 VAD → 服务器端定时分段

根本问题

onnxruntime-web WASM 后端在 Tauri 的 WebKitGTK
中无法运行(即使所有文件都在本地提供)。这是 WebKitGTK 的一个已知限制。

解决方案

不再尝试在浏览器中运行 VAD。改为服务器端的定时音频分段:

F2 开始 → 服务器用 cpal/arecord 录制音频 → 每 5 秒:
├─ 结束当前 STT 连接 → Groq 转写 → sendText() → CLI 对话
└─ 创建新的 STT 连接,继续录制
F2 结束 → 清理

变更文件

┌──────────────────┬────────────────────────────────────────────────────────────┐
│ 文件 │ 变更 │
├──────────────────┼────────────────────────────────────────────────────────────┤
│ FriendService.ts │ 新增定时分段:_flushSegment() + captureTimer,将音频分段为 │
│ │ 5 秒窗口,自动发送到 CLI │
├──────────────────┼────────────────────────────────────────────────────────────┤
│ useServerStt.ts │ 完全重写:移除浏览器 VAD(@ricky0123/vad-web / │
│ │ onnxruntime-web),新增基于轮询的服务器端语音通话 │
├──────────────────┼────────────────────────────────────────────────────────────┤
│ vite.config.ts │ 新增静态资源复制:ort-wasm-simd-threaded.mjs + │
│ │ vad.worklet.bundle.min.js │
├──────────────────┼────────────────────────────────────────────────────────────┤
│ staticFriend.ts │ 新增 .mjs MIME 类型 │
├──────────────────┼────────────────────────────────────────────────────────────┤
│ api/friend.ts │ 移除死掉的 /start-vad 端点 │
└──────────────────┴────────────────────────────────────────────────────────────┘

重启 CLI 即可测试 — 按 F2 开始语音通话,说话,文字应每约 5 秒自动发送到对话中。

src/components/friend/frontend/hooks/useServerStt.ts CHANGED
@@ -1,14 +1,14 @@
1
  /**
2
- * Hook for using browser-based energy VAD + server-side STT
3
- * in the Friend Tauri app.
4
- *
5
- * Audio capture and VAD run in the browser via Web Audio API.
6
- * Uses simple energy (RMS) detection for voice activity —
7
- * no WASM or external dependencies needed.
8
  *
9
  * Two modes:
10
  * - push-to-talk: POST /voice/start → capture → POST /voice/stop → get text
11
- * - voice-call: Browser energy VAD detects speech segments sends to server for STT
 
 
 
 
 
12
  */
13
  import { useRef, useCallback, useState } from 'react'
14
 
@@ -16,153 +16,12 @@ const FRIEND_API_BASE = 'http://127.0.0.1:3456/plugins/friend'
16
 
17
  export type SttProvider = 'browser' | 'groq' | 'anthropic' | 'local' | 'doubao'
18
 
19
- /**
20
- * Convert Float32Array audio (between -1 and 1, 16kHz) to a WAV Blob.
21
- */
22
- function float32ToWav(samples: Float32Array, sampleRate = 16000): Blob {
23
- const numChannels = 1
24
- const bitsPerSample = 16
25
- const byteRate = sampleRate * numChannels * (bitsPerSample / 8)
26
- const blockAlign = numChannels * (bitsPerSample / 8)
27
- const dataSize = samples.length * (bitsPerSample / 8)
28
- const headerSize = 44
29
- const totalSize = headerSize + dataSize
30
-
31
- const buffer = new ArrayBuffer(totalSize)
32
- const view = new DataView(buffer)
33
-
34
- // RIFF header
35
- writeString(view, 0, 'RIFF')
36
- view.setUint32(4, totalSize - 8, true)
37
- writeString(view, 8, 'WAVE')
38
-
39
- // fmt chunk
40
- writeString(view, 12, 'fmt ')
41
- view.setUint32(16, 16, true)
42
- view.setUint16(20, 1, true) // PCM
43
- view.setUint16(22, numChannels, true)
44
- view.setUint32(24, sampleRate, true)
45
- view.setUint32(28, byteRate, true)
46
- view.setUint16(32, blockAlign, true)
47
- view.setUint16(34, bitsPerSample, true)
48
-
49
- // data chunk
50
- writeString(view, 36, 'data')
51
- view.setUint32(40, dataSize, true)
52
-
53
- // Write PCM samples
54
- let offset = 44
55
- for (let i = 0; i < samples.length; i++) {
56
- const s = Math.max(-1, Math.min(1, samples[i]))
57
- view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
58
- offset += 2
59
- }
60
-
61
- return new Blob([buffer], { type: 'audio/wav' })
62
- }
63
-
64
- function writeString(view: DataView, offset: number, str: string): void {
65
- for (let i = 0; i < str.length; i++) {
66
- view.setUint8(offset + i, str.charCodeAt(i))
67
- }
68
- }
69
-
70
- /** Compute RMS (root mean square) energy of a float32 audio frame */
71
- function computeRms(samples: Float32Array): number {
72
- let sum = 0
73
- for (let i = 0; i < samples.length; i++) {
74
- sum += samples[i] * samples[i]
75
- }
76
- return Math.sqrt(sum / samples.length)
77
- }
78
-
79
- /**
80
- * Simple energy-based Voice Activity Detector.
81
- * Uses RMS threshold with configurable hangover time.
82
- */
83
- class EnergyVad {
84
- private threshold: number
85
- private hangoverFrames: number
86
- private speaking = false
87
- private silentFrames = 0
88
- private speechFrames: Float32Array[] = []
89
- private speechStartRms = 0
90
-
91
- constructor(threshold = 0.008, hangoverMs = 800, frameMs = 30) {
92
- this.threshold = threshold
93
- this.hangoverFrames = Math.ceil(hangoverMs / frameMs)
94
- }
95
-
96
- /** Process a single audio frame. Returns true if speech just ended (with accumulated audio). */
97
- process(frame: Float32Array, rms?: number): { speechEnded: boolean; audio: Float32Array | null } {
98
- const energy = rms ?? computeRms(frame)
99
-
100
- if (energy > this.threshold && !this.speaking) {
101
- // Speech just started
102
- this.speaking = true
103
- this.silentFrames = 0
104
- this.speechFrames = [frame]
105
- this.speechStartRms = energy
106
- return { speechEnded: false, audio: null }
107
- }
108
-
109
- if (this.speaking) {
110
- this.speechFrames.push(frame)
111
-
112
- if (energy < this.threshold) {
113
- this.silentFrames++
114
- if (this.silentFrames >= this.hangoverFrames) {
115
- // Speech ended after hangover
116
- const audio = this.flush()
117
- return { speechEnded: true, audio }
118
- }
119
- } else {
120
- this.silentFrames = 0
121
- }
122
- }
123
-
124
- return { speechEnded: false, audio: null }
125
- }
126
-
127
- /** Flush accumulated audio and reset state */
128
- private flush(): Float32Array | null {
129
- if (this.speechFrames.length < 3) {
130
- this.speechFrames = []
131
- this.speaking = false
132
- return null
133
- }
134
- const totalLen = this.speechFrames.reduce((s, f) => s + f.length, 0)
135
- const merged = new Float32Array(totalLen)
136
- let offset = 0
137
- for (const f of this.speechFrames) {
138
- merged.set(f, offset)
139
- offset += f.length
140
- }
141
- this.speechFrames = []
142
- this.speaking = false
143
- return merged
144
- }
145
-
146
- reset(): void {
147
- this.speaking = false
148
- this.silentFrames = 0
149
- this.speechFrames = []
150
- }
151
-
152
- isSpeaking(): boolean {
153
- return this.speaking
154
- }
155
- }
156
-
157
  export function useServerStt() {
158
  const [connected, setConnected] = useState(false)
 
 
159
  const onTranscriptRef = useRef<((text: string, isFinal: boolean) => void) | null>(null)
160
  const onErrorRef = useRef<((err: string) => void) | null>(null)
161
- const vadRef = useRef<EnergyVad | null>(null)
162
- const audioContextRef = useRef<AudioContext | null>(null)
163
- const streamRef = useRef<MediaStream | null>(null)
164
- const sourceNodeRef = useRef<MediaStreamAudioSourceNode | null>(null)
165
- const scriptNodeRef = useRef<ScriptProcessorNode | null>(null)
166
 
167
  // ── Push-to-talk ──────────────────────────────────────────────────────
168
 
@@ -192,16 +51,14 @@ export function useServerStt() {
192
  return data.text || ''
193
  }, [])
194
 
195
- // ── Voice call with browser energy VAD ────────────────────────────────
196
 
197
  /**
198
- * Start voice call mode with browser-based energy VAD.
199
- *
200
- * Uses Web Audio API (ScriptProcessorNode) to detect speech segments
201
- * via RMS threshold. No WASM or external dependencies.
202
  *
203
- * When a speech segment ends, the audio is sent to the server for STT
204
- * transcription (same /voice/stt-segment endpoint).
 
205
  */
206
  const startStreaming = useCallback(
207
  (
@@ -210,119 +67,68 @@ export function useServerStt() {
210
  onError: (err: string) => void,
211
  _language = 'zh',
212
  ) => {
213
- // Clean up any existing session
214
- stopCapture()
215
-
216
  onTranscriptRef.current = onTranscript
217
  onErrorRef.current = onError
218
 
219
- const startCapture = async () => {
220
- try {
221
- const stream = await navigator.mediaDevices.getUserMedia({
222
- audio: {
223
- channelCount: 1,
224
- echoCancellation: true,
225
- autoGainControl: true,
226
- noiseSuppression: true,
227
- sampleRate: 16000,
228
- },
229
- })
230
- streamRef.current = stream
231
-
232
- const audioCtx = new AudioContext({ sampleRate: 16000 })
233
- audioContextRef.current = audioCtx
234
-
235
- const source = audioCtx.createMediaStreamSource(stream)
236
- sourceNodeRef.current = source
237
-
238
- const scriptNode = audioCtx.createScriptProcessor(1024, 1, 1)
239
- scriptNodeRef.current = scriptNode
240
-
241
- const vad = new EnergyVad(0.006, 800, 64)
242
- vadRef.current = vad
243
-
244
- scriptNode.onaudioprocess = (e) => {
245
- if (!vadRef.current) return
246
-
247
- const input = e.inputBuffer.getChannelData(0)
248
- const rms = computeRms(input)
249
- const result = vadRef.current.process(input, rms)
250
-
251
- if (result.speechEnded && result.audio && result.audio.length > 0) {
252
- // Send to server for STT
253
- const wavBlob = float32ToWav(result.audio)
254
 
255
- fetch(`${FRIEND_API_BASE}/voice/stt-segment`, {
 
 
 
 
256
  method: 'POST',
257
- body: wavBlob,
258
- headers: { 'Content-Type': 'application/octet-stream' },
259
  })
260
- .then(async (res) => {
261
- if (!res.ok) {
262
- const errBody = await res.json().catch(() => ({}))
263
- throw new Error(errBody.error || `STT failed (${res.status})`)
264
- }
265
- const data = await res.json()
266
- if (data.text && data.text.trim()) {
267
- onTranscriptRef.current?.(data.text, true)
268
- }
269
- })
270
- .catch((err) => {
271
- console.error('[EnergyVAD] STT segment error:', err)
272
- onErrorRef.current?.(err.message)
273
- })
274
  }
275
- }
276
-
277
- // Ensure output is connected (needed for Chrome/Chromium)
278
- scriptNode.connect(audioCtx.destination)
279
- source.connect(scriptNode)
280
-
281
- setConnected(true)
282
- console.log('[EnergyVAD] Started')
283
- } catch (err: any) {
284
- console.error('[EnergyVAD] Init failed:', err)
285
  onErrorRef.current?.(String(err))
286
- }
287
- }
288
-
289
- startCapture()
290
  },
291
  [],
292
  )
293
 
294
- function stopCapture(): void {
295
- if (scriptNodeRef.current) {
296
- try { scriptNodeRef.current.disconnect() } catch {}
297
- scriptNodeRef.current = null
298
- }
299
- if (sourceNodeRef.current) {
300
- try { sourceNodeRef.current.disconnect() } catch {}
301
- sourceNodeRef.current = null
302
- }
303
- if (audioContextRef.current) {
304
- try { audioContextRef.current.close() } catch {}
305
- audioContextRef.current = null
306
- }
307
- if (streamRef.current) {
308
- streamRef.current.getTracks().forEach(t => t.stop())
309
- streamRef.current = null
310
- }
311
- vadRef.current = null
312
- }
313
-
314
- /**
315
- * Stop voice call mode.
316
- * Cleans up all audio resources.
317
- */
318
  const stopStreaming = useCallback(async (): Promise<string> => {
319
- stopCapture()
 
 
 
 
 
320
  setConnected(false)
321
- return ''
 
 
 
 
 
 
 
322
  }, [])
323
 
324
  return {
325
  connected,
 
326
  startPushToTalk,
327
  stopPushToTalk,
328
  startStreaming,
 
1
  /**
2
+ * Hook for server-side STT in the Friend Tauri app.
 
 
 
 
 
3
  *
4
  * Two modes:
5
  * - push-to-talk: POST /voice/start → capture → POST /voice/stop → get text
6
+ * - voice-call: POST /voice/start → server-side periodic segmentation →
7
+ * POST /voice/stop → get text
8
+ *
9
+ * Browser VAD (@ricky0123/vad-web) is NOT used — WebKitGTK (Tauri on Linux)
10
+ * does not support onnxruntime-web WASM. Instead, the server segments audio
11
+ * every ~5s and sends text to the CLI conversation automatically.
12
  */
13
  import { useRef, useCallback, useState } from 'react'
14
 
 
16
 
17
  export type SttProvider = 'browser' | 'groq' | 'anthropic' | 'local' | 'doubao'
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  export function useServerStt() {
20
  const [connected, setConnected] = useState(false)
21
+ const [interimText, setInterimText] = useState('')
22
+ const pollTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
23
  const onTranscriptRef = useRef<((text: string, isFinal: boolean) => void) | null>(null)
24
  const onErrorRef = useRef<((err: string) => void) | null>(null)
 
 
 
 
 
25
 
26
  // ── Push-to-talk ──────────────────────────────────────────────────────
27
 
 
51
  return data.text || ''
52
  }, [])
53
 
54
+ // ── Voice call (server-side segmentation, no browser VAD) ─────────────
55
 
56
  /**
57
+ * Start voice call mode.
 
 
 
58
  *
59
+ * Server captures microphone audio via cpal/arecord and segments it
60
+ * every ~5 seconds, sending each segment to STT (Groq Whisper).
61
+ * The transcript is enqueued to the CLI conversation automatically.
62
  */
63
  const startStreaming = useCallback(
64
  (
 
67
  onError: (err: string) => void,
68
  _language = 'zh',
69
  ) => {
 
 
 
70
  onTranscriptRef.current = onTranscript
71
  onErrorRef.current = onError
72
 
73
+ // Start server-side capture
74
+ fetch(`${FRIEND_API_BASE}/voice/start`, { method: 'POST' })
75
+ .then(async (res) => {
76
+ if (!res.ok) {
77
+ const err = await res.json().catch(() => ({ error: 'Unknown error' }))
78
+ throw new Error(err.error || 'STT start failed')
79
+ }
80
+ setConnected(true)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
+ // Poll interim status every 1s for display
83
+ const lastTextRef = { current: '' }
84
+ pollTimerRef.current = setInterval(async () => {
85
+ try {
86
+ const statusRes = await fetch(`${FRIEND_API_BASE}/voice/status`, {
87
  method: 'POST',
 
 
88
  })
89
+ if (!statusRes.ok) return
90
+ const status = await statusRes.json()
91
+ const text = status.interimText || ''
92
+ if (text && text !== lastTextRef.current) {
93
+ lastTextRef.current = text
94
+ setInterimText(text)
95
+ onTranscriptRef.current?.(text, false)
96
+ }
97
+ } catch {
98
+ // Polling errors are non-fatal
 
 
 
 
99
  }
100
+ }, 1000)
101
+ })
102
+ .catch((err) => {
103
+ console.error('[VoiceCall] start failed:', err)
 
 
 
 
 
 
104
  onErrorRef.current?.(String(err))
105
+ })
 
 
 
106
  },
107
  [],
108
  )
109
 
110
+ /** Stop voice call mode. */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  const stopStreaming = useCallback(async (): Promise<string> => {
112
+ // Stop polling
113
+ if (pollTimerRef.current) {
114
+ clearInterval(pollTimerRef.current)
115
+ pollTimerRef.current = null
116
+ }
117
+ setInterimText('')
118
  setConnected(false)
119
+
120
+ // Stop server-side capture
121
+ const res = await fetch(`${FRIEND_API_BASE}/voice/stop`, {
122
+ method: 'POST',
123
+ })
124
+ if (!res.ok) return ''
125
+ const data = await res.json()
126
+ return data.text || ''
127
  }, [])
128
 
129
  return {
130
  connected,
131
+ interimText,
132
  startPushToTalk,
133
  stopPushToTalk,
134
  startStreaming,
src/components/friend/frontend/package-lock.json CHANGED
@@ -11,6 +11,7 @@
11
  "@pixiv/three-vrm": "^3.4.0",
12
  "@pixiv/three-vrm-animation": "^3.5.0",
13
  "@pixiv/three-vrm-core": "^3.5.1",
 
14
  "@tauri-apps/api": "^2.11.1",
15
  "lucide-react": "^0.577.0",
16
  "marked": "^18.0.5",
@@ -18,6 +19,7 @@
18
  "react": "^18.3.1",
19
  "react-dom": "^18.3.1",
20
  "three": "^0.175.0",
 
21
  "wlipsync": "^1.3.0"
22
  },
23
  "devDependencies": {
@@ -319,7 +321,6 @@
319
  "cpu": [
320
  "ppc64"
321
  ],
322
- "dev": true,
323
  "license": "MIT",
324
  "optional": true,
325
  "os": [
@@ -336,7 +337,6 @@
336
  "cpu": [
337
  "arm"
338
  ],
339
- "dev": true,
340
  "license": "MIT",
341
  "optional": true,
342
  "os": [
@@ -353,7 +353,6 @@
353
  "cpu": [
354
  "arm64"
355
  ],
356
- "dev": true,
357
  "license": "MIT",
358
  "optional": true,
359
  "os": [
@@ -370,7 +369,6 @@
370
  "cpu": [
371
  "x64"
372
  ],
373
- "dev": true,
374
  "license": "MIT",
375
  "optional": true,
376
  "os": [
@@ -387,7 +385,6 @@
387
  "cpu": [
388
  "arm64"
389
  ],
390
- "dev": true,
391
  "license": "MIT",
392
  "optional": true,
393
  "os": [
@@ -404,7 +401,6 @@
404
  "cpu": [
405
  "x64"
406
  ],
407
- "dev": true,
408
  "license": "MIT",
409
  "optional": true,
410
  "os": [
@@ -421,7 +417,6 @@
421
  "cpu": [
422
  "arm64"
423
  ],
424
- "dev": true,
425
  "license": "MIT",
426
  "optional": true,
427
  "os": [
@@ -438,7 +433,6 @@
438
  "cpu": [
439
  "x64"
440
  ],
441
- "dev": true,
442
  "license": "MIT",
443
  "optional": true,
444
  "os": [
@@ -455,7 +449,6 @@
455
  "cpu": [
456
  "arm"
457
  ],
458
- "dev": true,
459
  "license": "MIT",
460
  "optional": true,
461
  "os": [
@@ -472,7 +465,6 @@
472
  "cpu": [
473
  "arm64"
474
  ],
475
- "dev": true,
476
  "license": "MIT",
477
  "optional": true,
478
  "os": [
@@ -489,7 +481,6 @@
489
  "cpu": [
490
  "ia32"
491
  ],
492
- "dev": true,
493
  "license": "MIT",
494
  "optional": true,
495
  "os": [
@@ -506,7 +497,6 @@
506
  "cpu": [
507
  "loong64"
508
  ],
509
- "dev": true,
510
  "license": "MIT",
511
  "optional": true,
512
  "os": [
@@ -523,7 +513,6 @@
523
  "cpu": [
524
  "mips64el"
525
  ],
526
- "dev": true,
527
  "license": "MIT",
528
  "optional": true,
529
  "os": [
@@ -540,7 +529,6 @@
540
  "cpu": [
541
  "ppc64"
542
  ],
543
- "dev": true,
544
  "license": "MIT",
545
  "optional": true,
546
  "os": [
@@ -557,7 +545,6 @@
557
  "cpu": [
558
  "riscv64"
559
  ],
560
- "dev": true,
561
  "license": "MIT",
562
  "optional": true,
563
  "os": [
@@ -574,7 +561,6 @@
574
  "cpu": [
575
  "s390x"
576
  ],
577
- "dev": true,
578
  "license": "MIT",
579
  "optional": true,
580
  "os": [
@@ -591,7 +577,6 @@
591
  "cpu": [
592
  "x64"
593
  ],
594
- "dev": true,
595
  "license": "MIT",
596
  "optional": true,
597
  "os": [
@@ -608,7 +593,6 @@
608
  "cpu": [
609
  "arm64"
610
  ],
611
- "dev": true,
612
  "license": "MIT",
613
  "optional": true,
614
  "os": [
@@ -625,7 +609,6 @@
625
  "cpu": [
626
  "x64"
627
  ],
628
- "dev": true,
629
  "license": "MIT",
630
  "optional": true,
631
  "os": [
@@ -642,7 +625,6 @@
642
  "cpu": [
643
  "arm64"
644
  ],
645
- "dev": true,
646
  "license": "MIT",
647
  "optional": true,
648
  "os": [
@@ -659,7 +641,6 @@
659
  "cpu": [
660
  "x64"
661
  ],
662
- "dev": true,
663
  "license": "MIT",
664
  "optional": true,
665
  "os": [
@@ -676,7 +657,6 @@
676
  "cpu": [
677
  "arm64"
678
  ],
679
- "dev": true,
680
  "license": "MIT",
681
  "optional": true,
682
  "os": [
@@ -693,7 +673,6 @@
693
  "cpu": [
694
  "x64"
695
  ],
696
- "dev": true,
697
  "license": "MIT",
698
  "optional": true,
699
  "os": [
@@ -710,7 +689,6 @@
710
  "cpu": [
711
  "arm64"
712
  ],
713
- "dev": true,
714
  "license": "MIT",
715
  "optional": true,
716
  "os": [
@@ -727,7 +705,6 @@
727
  "cpu": [
728
  "ia32"
729
  ],
730
- "dev": true,
731
  "license": "MIT",
732
  "optional": true,
733
  "os": [
@@ -744,7 +721,6 @@
744
  "cpu": [
745
  "x64"
746
  ],
747
- "dev": true,
748
  "license": "MIT",
749
  "optional": true,
750
  "os": [
@@ -963,6 +939,72 @@
963
  "@pixiv/types-vrmc-vrm-1.0": "3.5.4"
964
  }
965
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966
  "node_modules/@rolldown/pluginutils": {
967
  "version": "1.0.0-beta.27",
968
  "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
@@ -977,7 +1019,6 @@
977
  "cpu": [
978
  "arm"
979
  ],
980
- "dev": true,
981
  "license": "MIT",
982
  "optional": true,
983
  "os": [
@@ -991,7 +1032,6 @@
991
  "cpu": [
992
  "arm64"
993
  ],
994
- "dev": true,
995
  "license": "MIT",
996
  "optional": true,
997
  "os": [
@@ -1005,7 +1045,6 @@
1005
  "cpu": [
1006
  "arm64"
1007
  ],
1008
- "dev": true,
1009
  "license": "MIT",
1010
  "optional": true,
1011
  "os": [
@@ -1019,7 +1058,6 @@
1019
  "cpu": [
1020
  "x64"
1021
  ],
1022
- "dev": true,
1023
  "license": "MIT",
1024
  "optional": true,
1025
  "os": [
@@ -1033,7 +1071,6 @@
1033
  "cpu": [
1034
  "arm64"
1035
  ],
1036
- "dev": true,
1037
  "license": "MIT",
1038
  "optional": true,
1039
  "os": [
@@ -1047,7 +1084,6 @@
1047
  "cpu": [
1048
  "x64"
1049
  ],
1050
- "dev": true,
1051
  "license": "MIT",
1052
  "optional": true,
1053
  "os": [
@@ -1061,7 +1097,6 @@
1061
  "cpu": [
1062
  "arm"
1063
  ],
1064
- "dev": true,
1065
  "libc": [
1066
  "glibc"
1067
  ],
@@ -1078,7 +1113,6 @@
1078
  "cpu": [
1079
  "arm"
1080
  ],
1081
- "dev": true,
1082
  "libc": [
1083
  "musl"
1084
  ],
@@ -1095,7 +1129,6 @@
1095
  "cpu": [
1096
  "arm64"
1097
  ],
1098
- "dev": true,
1099
  "libc": [
1100
  "glibc"
1101
  ],
@@ -1112,7 +1145,6 @@
1112
  "cpu": [
1113
  "arm64"
1114
  ],
1115
- "dev": true,
1116
  "libc": [
1117
  "musl"
1118
  ],
@@ -1129,7 +1161,6 @@
1129
  "cpu": [
1130
  "loong64"
1131
  ],
1132
- "dev": true,
1133
  "libc": [
1134
  "glibc"
1135
  ],
@@ -1146,7 +1177,6 @@
1146
  "cpu": [
1147
  "loong64"
1148
  ],
1149
- "dev": true,
1150
  "libc": [
1151
  "musl"
1152
  ],
@@ -1163,7 +1193,6 @@
1163
  "cpu": [
1164
  "ppc64"
1165
  ],
1166
- "dev": true,
1167
  "libc": [
1168
  "glibc"
1169
  ],
@@ -1180,7 +1209,6 @@
1180
  "cpu": [
1181
  "ppc64"
1182
  ],
1183
- "dev": true,
1184
  "libc": [
1185
  "musl"
1186
  ],
@@ -1197,7 +1225,6 @@
1197
  "cpu": [
1198
  "riscv64"
1199
  ],
1200
- "dev": true,
1201
  "libc": [
1202
  "glibc"
1203
  ],
@@ -1214,7 +1241,6 @@
1214
  "cpu": [
1215
  "riscv64"
1216
  ],
1217
- "dev": true,
1218
  "libc": [
1219
  "musl"
1220
  ],
@@ -1231,7 +1257,6 @@
1231
  "cpu": [
1232
  "s390x"
1233
  ],
1234
- "dev": true,
1235
  "libc": [
1236
  "glibc"
1237
  ],
@@ -1248,7 +1273,6 @@
1248
  "cpu": [
1249
  "x64"
1250
  ],
1251
- "dev": true,
1252
  "libc": [
1253
  "glibc"
1254
  ],
@@ -1265,7 +1289,6 @@
1265
  "cpu": [
1266
  "x64"
1267
  ],
1268
- "dev": true,
1269
  "libc": [
1270
  "musl"
1271
  ],
@@ -1282,7 +1305,6 @@
1282
  "cpu": [
1283
  "x64"
1284
  ],
1285
- "dev": true,
1286
  "license": "MIT",
1287
  "optional": true,
1288
  "os": [
@@ -1296,7 +1318,6 @@
1296
  "cpu": [
1297
  "arm64"
1298
  ],
1299
- "dev": true,
1300
  "license": "MIT",
1301
  "optional": true,
1302
  "os": [
@@ -1310,7 +1331,6 @@
1310
  "cpu": [
1311
  "arm64"
1312
  ],
1313
- "dev": true,
1314
  "license": "MIT",
1315
  "optional": true,
1316
  "os": [
@@ -1324,7 +1344,6 @@
1324
  "cpu": [
1325
  "ia32"
1326
  ],
1327
- "dev": true,
1328
  "license": "MIT",
1329
  "optional": true,
1330
  "os": [
@@ -1338,7 +1357,6 @@
1338
  "cpu": [
1339
  "x64"
1340
  ],
1341
- "dev": true,
1342
  "license": "MIT",
1343
  "optional": true,
1344
  "os": [
@@ -1352,7 +1370,6 @@
1352
  "cpu": [
1353
  "x64"
1354
  ],
1355
- "dev": true,
1356
  "license": "MIT",
1357
  "optional": true,
1358
  "os": [
@@ -1657,9 +1674,17 @@
1657
  "version": "1.0.9",
1658
  "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz",
1659
  "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1660
- "dev": true,
1661
  "license": "MIT"
1662
  },
 
 
 
 
 
 
 
 
 
1663
  "node_modules/@types/prop-types": {
1664
  "version": "15.7.15",
1665
  "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
@@ -1745,6 +1770,31 @@
1745
  "dev": true,
1746
  "license": "BSD-3-Clause"
1747
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1748
  "node_modules/baseline-browser-mapping": {
1749
  "version": "2.10.38",
1750
  "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
@@ -1758,6 +1808,30 @@
1758
  "node": ">=6.0.0"
1759
  }
1760
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1761
  "node_modules/browserslist": {
1762
  "version": "4.28.2",
1763
  "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz",
@@ -1813,6 +1887,30 @@
1813
  ],
1814
  "license": "CC-BY-4.0"
1815
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1816
  "node_modules/convert-source-map": {
1817
  "version": "2.0.0",
1818
  "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -1856,7 +1954,6 @@
1856
  "version": "0.25.12",
1857
  "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz",
1858
  "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
1859
- "dev": true,
1860
  "hasInstallScript": true,
1861
  "license": "MIT",
1862
  "bin": {
@@ -1908,7 +2005,6 @@
1908
  "version": "6.5.0",
1909
  "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
1910
  "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1911
- "dev": true,
1912
  "license": "MIT",
1913
  "engines": {
1914
  "node": ">=12.0.0"
@@ -1929,11 +2025,28 @@
1929
  "dev": true,
1930
  "license": "MIT"
1931
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1932
  "node_modules/fsevents": {
1933
  "version": "2.3.3",
1934
  "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
1935
  "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1936
- "dev": true,
1937
  "hasInstallScript": true,
1938
  "license": "MIT",
1939
  "optional": true,
@@ -1954,6 +2067,66 @@
1954
  "node": ">=6.9.0"
1955
  }
1956
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1957
  "node_modules/js-tokens": {
1958
  "version": "4.0.0",
1959
  "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -1986,6 +2159,12 @@
1986
  "node": ">=6"
1987
  }
1988
  },
 
 
 
 
 
 
1989
  "node_modules/loose-envify": {
1990
  "version": "1.4.0",
1991
  "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -2053,7 +2232,6 @@
2053
  "version": "3.3.13",
2054
  "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.13.tgz",
2055
  "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==",
2056
- "dev": true,
2057
  "funding": [
2058
  {
2059
  "type": "github",
@@ -2078,18 +2256,57 @@
2078
  "node": ">=18"
2079
  }
2080
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2081
  "node_modules/picocolors": {
2082
  "version": "1.1.1",
2083
  "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
2084
  "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
2085
- "dev": true,
2086
  "license": "ISC"
2087
  },
2088
  "node_modules/picomatch": {
2089
  "version": "4.0.4",
2090
  "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
2091
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
2092
- "dev": true,
2093
  "license": "MIT",
2094
  "engines": {
2095
  "node": ">=12"
@@ -2098,11 +2315,16 @@
2098
  "url": "https://github.com/sponsors/jonschlinkert"
2099
  }
2100
  },
 
 
 
 
 
 
2101
  "node_modules/postcss": {
2102
  "version": "8.5.15",
2103
  "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz",
2104
  "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
2105
- "dev": true,
2106
  "funding": [
2107
  {
2108
  "type": "opencollective",
@@ -2127,6 +2349,29 @@
2127
  "node": "^10 || ^12 || >=14"
2128
  }
2129
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2130
  "node_modules/react": {
2131
  "version": "18.3.1",
2132
  "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
@@ -2162,11 +2407,34 @@
2162
  "node": ">=0.10.0"
2163
  }
2164
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2165
  "node_modules/rollup": {
2166
  "version": "4.62.2",
2167
  "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz",
2168
  "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
2169
- "dev": true,
2170
  "license": "MIT",
2171
  "dependencies": {
2172
  "@types/estree": "1.0.9"
@@ -2230,7 +2498,6 @@
2230
  "version": "1.2.1",
2231
  "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
2232
  "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
2233
- "dev": true,
2234
  "license": "BSD-3-Clause",
2235
  "engines": {
2236
  "node": ">=0.10.0"
@@ -2246,7 +2513,6 @@
2246
  "version": "0.2.17",
2247
  "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
2248
  "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
2249
- "dev": true,
2250
  "license": "MIT",
2251
  "dependencies": {
2252
  "fdir": "^6.5.0",
@@ -2259,6 +2525,18 @@
2259
  "url": "https://github.com/sponsors/SuperchupuDev"
2260
  }
2261
  },
 
 
 
 
 
 
 
 
 
 
 
 
2262
  "node_modules/typescript": {
2263
  "version": "5.9.3",
2264
  "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
@@ -2273,6 +2551,12 @@
2273
  "node": ">=14.17"
2274
  }
2275
  },
 
 
 
 
 
 
2276
  "node_modules/update-browserslist-db": {
2277
  "version": "1.2.3",
2278
  "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
@@ -2308,7 +2592,6 @@
2308
  "version": "6.4.3",
2309
  "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz",
2310
  "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
2311
- "dev": true,
2312
  "license": "MIT",
2313
  "dependencies": {
2314
  "esbuild": "^0.25.0",
@@ -2379,6 +2662,28 @@
2379
  }
2380
  }
2381
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2382
  "node_modules/wlipsync": {
2383
  "version": "1.3.0",
2384
  "resolved": "https://registry.npmmirror.com/wlipsync/-/wlipsync-1.3.0.tgz",
 
11
  "@pixiv/three-vrm": "^3.4.0",
12
  "@pixiv/three-vrm-animation": "^3.5.0",
13
  "@pixiv/three-vrm-core": "^3.5.1",
14
+ "@ricky0123/vad-web": "^0.0.30",
15
  "@tauri-apps/api": "^2.11.1",
16
  "lucide-react": "^0.577.0",
17
  "marked": "^18.0.5",
 
19
  "react": "^18.3.1",
20
  "react-dom": "^18.3.1",
21
  "three": "^0.175.0",
22
+ "vite-plugin-static-copy": "^4.1.1",
23
  "wlipsync": "^1.3.0"
24
  },
25
  "devDependencies": {
 
321
  "cpu": [
322
  "ppc64"
323
  ],
 
324
  "license": "MIT",
325
  "optional": true,
326
  "os": [
 
337
  "cpu": [
338
  "arm"
339
  ],
 
340
  "license": "MIT",
341
  "optional": true,
342
  "os": [
 
353
  "cpu": [
354
  "arm64"
355
  ],
 
356
  "license": "MIT",
357
  "optional": true,
358
  "os": [
 
369
  "cpu": [
370
  "x64"
371
  ],
 
372
  "license": "MIT",
373
  "optional": true,
374
  "os": [
 
385
  "cpu": [
386
  "arm64"
387
  ],
 
388
  "license": "MIT",
389
  "optional": true,
390
  "os": [
 
401
  "cpu": [
402
  "x64"
403
  ],
 
404
  "license": "MIT",
405
  "optional": true,
406
  "os": [
 
417
  "cpu": [
418
  "arm64"
419
  ],
 
420
  "license": "MIT",
421
  "optional": true,
422
  "os": [
 
433
  "cpu": [
434
  "x64"
435
  ],
 
436
  "license": "MIT",
437
  "optional": true,
438
  "os": [
 
449
  "cpu": [
450
  "arm"
451
  ],
 
452
  "license": "MIT",
453
  "optional": true,
454
  "os": [
 
465
  "cpu": [
466
  "arm64"
467
  ],
 
468
  "license": "MIT",
469
  "optional": true,
470
  "os": [
 
481
  "cpu": [
482
  "ia32"
483
  ],
 
484
  "license": "MIT",
485
  "optional": true,
486
  "os": [
 
497
  "cpu": [
498
  "loong64"
499
  ],
 
500
  "license": "MIT",
501
  "optional": true,
502
  "os": [
 
513
  "cpu": [
514
  "mips64el"
515
  ],
 
516
  "license": "MIT",
517
  "optional": true,
518
  "os": [
 
529
  "cpu": [
530
  "ppc64"
531
  ],
 
532
  "license": "MIT",
533
  "optional": true,
534
  "os": [
 
545
  "cpu": [
546
  "riscv64"
547
  ],
 
548
  "license": "MIT",
549
  "optional": true,
550
  "os": [
 
561
  "cpu": [
562
  "s390x"
563
  ],
 
564
  "license": "MIT",
565
  "optional": true,
566
  "os": [
 
577
  "cpu": [
578
  "x64"
579
  ],
 
580
  "license": "MIT",
581
  "optional": true,
582
  "os": [
 
593
  "cpu": [
594
  "arm64"
595
  ],
 
596
  "license": "MIT",
597
  "optional": true,
598
  "os": [
 
609
  "cpu": [
610
  "x64"
611
  ],
 
612
  "license": "MIT",
613
  "optional": true,
614
  "os": [
 
625
  "cpu": [
626
  "arm64"
627
  ],
 
628
  "license": "MIT",
629
  "optional": true,
630
  "os": [
 
641
  "cpu": [
642
  "x64"
643
  ],
 
644
  "license": "MIT",
645
  "optional": true,
646
  "os": [
 
657
  "cpu": [
658
  "arm64"
659
  ],
 
660
  "license": "MIT",
661
  "optional": true,
662
  "os": [
 
673
  "cpu": [
674
  "x64"
675
  ],
 
676
  "license": "MIT",
677
  "optional": true,
678
  "os": [
 
689
  "cpu": [
690
  "arm64"
691
  ],
 
692
  "license": "MIT",
693
  "optional": true,
694
  "os": [
 
705
  "cpu": [
706
  "ia32"
707
  ],
 
708
  "license": "MIT",
709
  "optional": true,
710
  "os": [
 
721
  "cpu": [
722
  "x64"
723
  ],
 
724
  "license": "MIT",
725
  "optional": true,
726
  "os": [
 
939
  "@pixiv/types-vrmc-vrm-1.0": "3.5.4"
940
  }
941
  },
942
+ "node_modules/@protobufjs/aspromise": {
943
+ "version": "1.1.2",
944
+ "resolved": "https://registry.npmmirror.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
945
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
946
+ "license": "BSD-3-Clause"
947
+ },
948
+ "node_modules/@protobufjs/base64": {
949
+ "version": "1.1.2",
950
+ "resolved": "https://registry.npmmirror.com/@protobufjs/base64/-/base64-1.1.2.tgz",
951
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
952
+ "license": "BSD-3-Clause"
953
+ },
954
+ "node_modules/@protobufjs/codegen": {
955
+ "version": "2.0.5",
956
+ "resolved": "https://registry.npmmirror.com/@protobufjs/codegen/-/codegen-2.0.5.tgz",
957
+ "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
958
+ "license": "BSD-3-Clause"
959
+ },
960
+ "node_modules/@protobufjs/eventemitter": {
961
+ "version": "1.1.1",
962
+ "resolved": "https://registry.npmmirror.com/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
963
+ "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
964
+ "license": "BSD-3-Clause"
965
+ },
966
+ "node_modules/@protobufjs/fetch": {
967
+ "version": "1.1.1",
968
+ "resolved": "https://registry.npmmirror.com/@protobufjs/fetch/-/fetch-1.1.1.tgz",
969
+ "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
970
+ "license": "BSD-3-Clause",
971
+ "dependencies": {
972
+ "@protobufjs/aspromise": "^1.1.1"
973
+ }
974
+ },
975
+ "node_modules/@protobufjs/float": {
976
+ "version": "1.0.2",
977
+ "resolved": "https://registry.npmmirror.com/@protobufjs/float/-/float-1.0.2.tgz",
978
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
979
+ "license": "BSD-3-Clause"
980
+ },
981
+ "node_modules/@protobufjs/path": {
982
+ "version": "1.1.2",
983
+ "resolved": "https://registry.npmmirror.com/@protobufjs/path/-/path-1.1.2.tgz",
984
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
985
+ "license": "BSD-3-Clause"
986
+ },
987
+ "node_modules/@protobufjs/pool": {
988
+ "version": "1.1.0",
989
+ "resolved": "https://registry.npmmirror.com/@protobufjs/pool/-/pool-1.1.0.tgz",
990
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
991
+ "license": "BSD-3-Clause"
992
+ },
993
+ "node_modules/@protobufjs/utf8": {
994
+ "version": "1.1.1",
995
+ "resolved": "https://registry.npmmirror.com/@protobufjs/utf8/-/utf8-1.1.1.tgz",
996
+ "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==",
997
+ "license": "BSD-3-Clause"
998
+ },
999
+ "node_modules/@ricky0123/vad-web": {
1000
+ "version": "0.0.30",
1001
+ "resolved": "https://registry.npmmirror.com/@ricky0123/vad-web/-/vad-web-0.0.30.tgz",
1002
+ "integrity": "sha512-cJyYrh4YeeUBJcbR9Bic/bFDyB9qBkAepvpuWM3vLxnAi7bC3VHzf51UeNdT+OtY4D7MLAgV8iJMc4z41ZnaWg==",
1003
+ "license": "ISC",
1004
+ "dependencies": {
1005
+ "onnxruntime-web": "^1.17.0"
1006
+ }
1007
+ },
1008
  "node_modules/@rolldown/pluginutils": {
1009
  "version": "1.0.0-beta.27",
1010
  "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
 
1019
  "cpu": [
1020
  "arm"
1021
  ],
 
1022
  "license": "MIT",
1023
  "optional": true,
1024
  "os": [
 
1032
  "cpu": [
1033
  "arm64"
1034
  ],
 
1035
  "license": "MIT",
1036
  "optional": true,
1037
  "os": [
 
1045
  "cpu": [
1046
  "arm64"
1047
  ],
 
1048
  "license": "MIT",
1049
  "optional": true,
1050
  "os": [
 
1058
  "cpu": [
1059
  "x64"
1060
  ],
 
1061
  "license": "MIT",
1062
  "optional": true,
1063
  "os": [
 
1071
  "cpu": [
1072
  "arm64"
1073
  ],
 
1074
  "license": "MIT",
1075
  "optional": true,
1076
  "os": [
 
1084
  "cpu": [
1085
  "x64"
1086
  ],
 
1087
  "license": "MIT",
1088
  "optional": true,
1089
  "os": [
 
1097
  "cpu": [
1098
  "arm"
1099
  ],
 
1100
  "libc": [
1101
  "glibc"
1102
  ],
 
1113
  "cpu": [
1114
  "arm"
1115
  ],
 
1116
  "libc": [
1117
  "musl"
1118
  ],
 
1129
  "cpu": [
1130
  "arm64"
1131
  ],
 
1132
  "libc": [
1133
  "glibc"
1134
  ],
 
1145
  "cpu": [
1146
  "arm64"
1147
  ],
 
1148
  "libc": [
1149
  "musl"
1150
  ],
 
1161
  "cpu": [
1162
  "loong64"
1163
  ],
 
1164
  "libc": [
1165
  "glibc"
1166
  ],
 
1177
  "cpu": [
1178
  "loong64"
1179
  ],
 
1180
  "libc": [
1181
  "musl"
1182
  ],
 
1193
  "cpu": [
1194
  "ppc64"
1195
  ],
 
1196
  "libc": [
1197
  "glibc"
1198
  ],
 
1209
  "cpu": [
1210
  "ppc64"
1211
  ],
 
1212
  "libc": [
1213
  "musl"
1214
  ],
 
1225
  "cpu": [
1226
  "riscv64"
1227
  ],
 
1228
  "libc": [
1229
  "glibc"
1230
  ],
 
1241
  "cpu": [
1242
  "riscv64"
1243
  ],
 
1244
  "libc": [
1245
  "musl"
1246
  ],
 
1257
  "cpu": [
1258
  "s390x"
1259
  ],
 
1260
  "libc": [
1261
  "glibc"
1262
  ],
 
1273
  "cpu": [
1274
  "x64"
1275
  ],
 
1276
  "libc": [
1277
  "glibc"
1278
  ],
 
1289
  "cpu": [
1290
  "x64"
1291
  ],
 
1292
  "libc": [
1293
  "musl"
1294
  ],
 
1305
  "cpu": [
1306
  "x64"
1307
  ],
 
1308
  "license": "MIT",
1309
  "optional": true,
1310
  "os": [
 
1318
  "cpu": [
1319
  "arm64"
1320
  ],
 
1321
  "license": "MIT",
1322
  "optional": true,
1323
  "os": [
 
1331
  "cpu": [
1332
  "arm64"
1333
  ],
 
1334
  "license": "MIT",
1335
  "optional": true,
1336
  "os": [
 
1344
  "cpu": [
1345
  "ia32"
1346
  ],
 
1347
  "license": "MIT",
1348
  "optional": true,
1349
  "os": [
 
1357
  "cpu": [
1358
  "x64"
1359
  ],
 
1360
  "license": "MIT",
1361
  "optional": true,
1362
  "os": [
 
1370
  "cpu": [
1371
  "x64"
1372
  ],
 
1373
  "license": "MIT",
1374
  "optional": true,
1375
  "os": [
 
1674
  "version": "1.0.9",
1675
  "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz",
1676
  "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
 
1677
  "license": "MIT"
1678
  },
1679
+ "node_modules/@types/node": {
1680
+ "version": "26.0.0",
1681
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-26.0.0.tgz",
1682
+ "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==",
1683
+ "license": "MIT",
1684
+ "dependencies": {
1685
+ "undici-types": "~8.3.0"
1686
+ }
1687
+ },
1688
  "node_modules/@types/prop-types": {
1689
  "version": "15.7.15",
1690
  "resolved": "https://registry.npmmirror.com/@types/prop-types/-/prop-types-15.7.15.tgz",
 
1770
  "dev": true,
1771
  "license": "BSD-3-Clause"
1772
  },
1773
+ "node_modules/anymatch": {
1774
+ "version": "3.1.3",
1775
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
1776
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
1777
+ "license": "ISC",
1778
+ "dependencies": {
1779
+ "normalize-path": "^3.0.0",
1780
+ "picomatch": "^2.0.4"
1781
+ },
1782
+ "engines": {
1783
+ "node": ">= 8"
1784
+ }
1785
+ },
1786
+ "node_modules/anymatch/node_modules/picomatch": {
1787
+ "version": "2.3.2",
1788
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz",
1789
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
1790
+ "license": "MIT",
1791
+ "engines": {
1792
+ "node": ">=8.6"
1793
+ },
1794
+ "funding": {
1795
+ "url": "https://github.com/sponsors/jonschlinkert"
1796
+ }
1797
+ },
1798
  "node_modules/baseline-browser-mapping": {
1799
  "version": "2.10.38",
1800
  "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz",
 
1808
  "node": ">=6.0.0"
1809
  }
1810
  },
1811
+ "node_modules/binary-extensions": {
1812
+ "version": "2.3.0",
1813
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
1814
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
1815
+ "license": "MIT",
1816
+ "engines": {
1817
+ "node": ">=8"
1818
+ },
1819
+ "funding": {
1820
+ "url": "https://github.com/sponsors/sindresorhus"
1821
+ }
1822
+ },
1823
+ "node_modules/braces": {
1824
+ "version": "3.0.3",
1825
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
1826
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
1827
+ "license": "MIT",
1828
+ "dependencies": {
1829
+ "fill-range": "^7.1.1"
1830
+ },
1831
+ "engines": {
1832
+ "node": ">=8"
1833
+ }
1834
+ },
1835
  "node_modules/browserslist": {
1836
  "version": "4.28.2",
1837
  "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz",
 
1887
  ],
1888
  "license": "CC-BY-4.0"
1889
  },
1890
+ "node_modules/chokidar": {
1891
+ "version": "3.6.0",
1892
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
1893
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
1894
+ "license": "MIT",
1895
+ "dependencies": {
1896
+ "anymatch": "~3.1.2",
1897
+ "braces": "~3.0.2",
1898
+ "glob-parent": "~5.1.2",
1899
+ "is-binary-path": "~2.1.0",
1900
+ "is-glob": "~4.0.1",
1901
+ "normalize-path": "~3.0.0",
1902
+ "readdirp": "~3.6.0"
1903
+ },
1904
+ "engines": {
1905
+ "node": ">= 8.10.0"
1906
+ },
1907
+ "funding": {
1908
+ "url": "https://paulmillr.com/funding/"
1909
+ },
1910
+ "optionalDependencies": {
1911
+ "fsevents": "~2.3.2"
1912
+ }
1913
+ },
1914
  "node_modules/convert-source-map": {
1915
  "version": "2.0.0",
1916
  "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
 
1954
  "version": "0.25.12",
1955
  "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz",
1956
  "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
 
1957
  "hasInstallScript": true,
1958
  "license": "MIT",
1959
  "bin": {
 
2005
  "version": "6.5.0",
2006
  "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
2007
  "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
 
2008
  "license": "MIT",
2009
  "engines": {
2010
  "node": ">=12.0.0"
 
2025
  "dev": true,
2026
  "license": "MIT"
2027
  },
2028
+ "node_modules/fill-range": {
2029
+ "version": "7.1.1",
2030
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
2031
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
2032
+ "license": "MIT",
2033
+ "dependencies": {
2034
+ "to-regex-range": "^5.0.1"
2035
+ },
2036
+ "engines": {
2037
+ "node": ">=8"
2038
+ }
2039
+ },
2040
+ "node_modules/flatbuffers": {
2041
+ "version": "25.9.23",
2042
+ "resolved": "https://registry.npmmirror.com/flatbuffers/-/flatbuffers-25.9.23.tgz",
2043
+ "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==",
2044
+ "license": "Apache-2.0"
2045
+ },
2046
  "node_modules/fsevents": {
2047
  "version": "2.3.3",
2048
  "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
2049
  "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
 
2050
  "hasInstallScript": true,
2051
  "license": "MIT",
2052
  "optional": true,
 
2067
  "node": ">=6.9.0"
2068
  }
2069
  },
2070
+ "node_modules/glob-parent": {
2071
+ "version": "5.1.2",
2072
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
2073
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
2074
+ "license": "ISC",
2075
+ "dependencies": {
2076
+ "is-glob": "^4.0.1"
2077
+ },
2078
+ "engines": {
2079
+ "node": ">= 6"
2080
+ }
2081
+ },
2082
+ "node_modules/guid-typescript": {
2083
+ "version": "1.0.9",
2084
+ "resolved": "https://registry.npmmirror.com/guid-typescript/-/guid-typescript-1.0.9.tgz",
2085
+ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
2086
+ "license": "ISC"
2087
+ },
2088
+ "node_modules/is-binary-path": {
2089
+ "version": "2.1.0",
2090
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
2091
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
2092
+ "license": "MIT",
2093
+ "dependencies": {
2094
+ "binary-extensions": "^2.0.0"
2095
+ },
2096
+ "engines": {
2097
+ "node": ">=8"
2098
+ }
2099
+ },
2100
+ "node_modules/is-extglob": {
2101
+ "version": "2.1.1",
2102
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
2103
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
2104
+ "license": "MIT",
2105
+ "engines": {
2106
+ "node": ">=0.10.0"
2107
+ }
2108
+ },
2109
+ "node_modules/is-glob": {
2110
+ "version": "4.0.3",
2111
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
2112
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
2113
+ "license": "MIT",
2114
+ "dependencies": {
2115
+ "is-extglob": "^2.1.1"
2116
+ },
2117
+ "engines": {
2118
+ "node": ">=0.10.0"
2119
+ }
2120
+ },
2121
+ "node_modules/is-number": {
2122
+ "version": "7.0.0",
2123
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
2124
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
2125
+ "license": "MIT",
2126
+ "engines": {
2127
+ "node": ">=0.12.0"
2128
+ }
2129
+ },
2130
  "node_modules/js-tokens": {
2131
  "version": "4.0.0",
2132
  "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
 
2159
  "node": ">=6"
2160
  }
2161
  },
2162
+ "node_modules/long": {
2163
+ "version": "5.3.2",
2164
+ "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz",
2165
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
2166
+ "license": "Apache-2.0"
2167
+ },
2168
  "node_modules/loose-envify": {
2169
  "version": "1.4.0",
2170
  "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz",
 
2232
  "version": "3.3.13",
2233
  "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.13.tgz",
2234
  "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==",
 
2235
  "funding": [
2236
  {
2237
  "type": "github",
 
2256
  "node": ">=18"
2257
  }
2258
  },
2259
+ "node_modules/normalize-path": {
2260
+ "version": "3.0.0",
2261
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
2262
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
2263
+ "license": "MIT",
2264
+ "engines": {
2265
+ "node": ">=0.10.0"
2266
+ }
2267
+ },
2268
+ "node_modules/onnxruntime-common": {
2269
+ "version": "1.27.0",
2270
+ "resolved": "https://registry.npmmirror.com/onnxruntime-common/-/onnxruntime-common-1.27.0.tgz",
2271
+ "integrity": "sha512-3KxL5wIVqa8Ex08jxSzncm9CMgw8CjOFyOQ7SxvG9o0cVLlhTNKXyIQuTbtX4tGPJEf73OER2xrjt4HJSBL4ow==",
2272
+ "license": "MIT"
2273
+ },
2274
+ "node_modules/onnxruntime-web": {
2275
+ "version": "1.27.0",
2276
+ "resolved": "https://registry.npmmirror.com/onnxruntime-web/-/onnxruntime-web-1.27.0.tgz",
2277
+ "integrity": "sha512-ogDLsqIozHZwifPuN37OproAo0byX6t43/bP8GzeZWBWD6MOGExswFAx3up4NS/vvWBOg2u2PXomDt3rMmdQSg==",
2278
+ "license": "MIT",
2279
+ "dependencies": {
2280
+ "flatbuffers": "^25.1.24",
2281
+ "guid-typescript": "^1.0.9",
2282
+ "long": "^5.2.3",
2283
+ "onnxruntime-common": "1.27.0",
2284
+ "platform": "^1.3.6",
2285
+ "protobufjs": "^7.2.4"
2286
+ }
2287
+ },
2288
+ "node_modules/p-map": {
2289
+ "version": "7.0.4",
2290
+ "resolved": "https://registry.npmmirror.com/p-map/-/p-map-7.0.4.tgz",
2291
+ "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==",
2292
+ "license": "MIT",
2293
+ "engines": {
2294
+ "node": ">=18"
2295
+ },
2296
+ "funding": {
2297
+ "url": "https://github.com/sponsors/sindresorhus"
2298
+ }
2299
+ },
2300
  "node_modules/picocolors": {
2301
  "version": "1.1.1",
2302
  "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
2303
  "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
 
2304
  "license": "ISC"
2305
  },
2306
  "node_modules/picomatch": {
2307
  "version": "4.0.4",
2308
  "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz",
2309
  "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
 
2310
  "license": "MIT",
2311
  "engines": {
2312
  "node": ">=12"
 
2315
  "url": "https://github.com/sponsors/jonschlinkert"
2316
  }
2317
  },
2318
+ "node_modules/platform": {
2319
+ "version": "1.3.6",
2320
+ "resolved": "https://registry.npmmirror.com/platform/-/platform-1.3.6.tgz",
2321
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
2322
+ "license": "MIT"
2323
+ },
2324
  "node_modules/postcss": {
2325
  "version": "8.5.15",
2326
  "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz",
2327
  "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
 
2328
  "funding": [
2329
  {
2330
  "type": "opencollective",
 
2349
  "node": "^10 || ^12 || >=14"
2350
  }
2351
  },
2352
+ "node_modules/protobufjs": {
2353
+ "version": "7.6.4",
2354
+ "resolved": "https://registry.npmmirror.com/protobufjs/-/protobufjs-7.6.4.tgz",
2355
+ "integrity": "sha512-RJJPTTpvFfHcWLkIa2JFWK4XvtSzS0yEWDmunqHXli1h3JlkbcQZXDZdcWxv+JK3Xsl5/UFDPZ0iGm7DAengYw==",
2356
+ "hasInstallScript": true,
2357
+ "license": "BSD-3-Clause",
2358
+ "dependencies": {
2359
+ "@protobufjs/aspromise": "^1.1.2",
2360
+ "@protobufjs/base64": "^1.1.2",
2361
+ "@protobufjs/codegen": "^2.0.5",
2362
+ "@protobufjs/eventemitter": "^1.1.1",
2363
+ "@protobufjs/fetch": "^1.1.1",
2364
+ "@protobufjs/float": "^1.0.2",
2365
+ "@protobufjs/path": "^1.1.2",
2366
+ "@protobufjs/pool": "^1.1.0",
2367
+ "@protobufjs/utf8": "^1.1.1",
2368
+ "@types/node": ">=13.7.0",
2369
+ "long": "^5.3.2"
2370
+ },
2371
+ "engines": {
2372
+ "node": ">=12.0.0"
2373
+ }
2374
+ },
2375
  "node_modules/react": {
2376
  "version": "18.3.1",
2377
  "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz",
 
2407
  "node": ">=0.10.0"
2408
  }
2409
  },
2410
+ "node_modules/readdirp": {
2411
+ "version": "3.6.0",
2412
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
2413
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
2414
+ "license": "MIT",
2415
+ "dependencies": {
2416
+ "picomatch": "^2.2.1"
2417
+ },
2418
+ "engines": {
2419
+ "node": ">=8.10.0"
2420
+ }
2421
+ },
2422
+ "node_modules/readdirp/node_modules/picomatch": {
2423
+ "version": "2.3.2",
2424
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz",
2425
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
2426
+ "license": "MIT",
2427
+ "engines": {
2428
+ "node": ">=8.6"
2429
+ },
2430
+ "funding": {
2431
+ "url": "https://github.com/sponsors/jonschlinkert"
2432
+ }
2433
+ },
2434
  "node_modules/rollup": {
2435
  "version": "4.62.2",
2436
  "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz",
2437
  "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
 
2438
  "license": "MIT",
2439
  "dependencies": {
2440
  "@types/estree": "1.0.9"
 
2498
  "version": "1.2.1",
2499
  "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
2500
  "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
 
2501
  "license": "BSD-3-Clause",
2502
  "engines": {
2503
  "node": ">=0.10.0"
 
2513
  "version": "0.2.17",
2514
  "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
2515
  "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
 
2516
  "license": "MIT",
2517
  "dependencies": {
2518
  "fdir": "^6.5.0",
 
2525
  "url": "https://github.com/sponsors/SuperchupuDev"
2526
  }
2527
  },
2528
+ "node_modules/to-regex-range": {
2529
+ "version": "5.0.1",
2530
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
2531
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
2532
+ "license": "MIT",
2533
+ "dependencies": {
2534
+ "is-number": "^7.0.0"
2535
+ },
2536
+ "engines": {
2537
+ "node": ">=8.0"
2538
+ }
2539
+ },
2540
  "node_modules/typescript": {
2541
  "version": "5.9.3",
2542
  "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
 
2551
  "node": ">=14.17"
2552
  }
2553
  },
2554
+ "node_modules/undici-types": {
2555
+ "version": "8.3.0",
2556
+ "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-8.3.0.tgz",
2557
+ "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==",
2558
+ "license": "MIT"
2559
+ },
2560
  "node_modules/update-browserslist-db": {
2561
  "version": "1.2.3",
2562
  "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
 
2592
  "version": "6.4.3",
2593
  "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz",
2594
  "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
 
2595
  "license": "MIT",
2596
  "dependencies": {
2597
  "esbuild": "^0.25.0",
 
2662
  }
2663
  }
2664
  },
2665
+ "node_modules/vite-plugin-static-copy": {
2666
+ "version": "4.1.1",
2667
+ "resolved": "https://registry.npmmirror.com/vite-plugin-static-copy/-/vite-plugin-static-copy-4.1.1.tgz",
2668
+ "integrity": "sha512-GrlA8YklrAfSyxJ4M3fdQLOo9oNkp56IM9FYgX/WtEgeIFkPwhu4wzpufBCIuNKCa6Fn77FkRdYxkHqV0FwjAw==",
2669
+ "license": "MIT",
2670
+ "dependencies": {
2671
+ "chokidar": "^3.6.0",
2672
+ "p-map": "^7.0.4",
2673
+ "picocolors": "^1.1.1",
2674
+ "tinyglobby": "^0.2.17"
2675
+ },
2676
+ "engines": {
2677
+ "node": "^22.0.0 || >=24.0.0"
2678
+ },
2679
+ "funding": {
2680
+ "type": "github",
2681
+ "url": "https://github.com/sponsors/sapphi-red"
2682
+ },
2683
+ "peerDependencies": {
2684
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
2685
+ }
2686
+ },
2687
  "node_modules/wlipsync": {
2688
  "version": "1.3.0",
2689
  "resolved": "https://registry.npmmirror.com/wlipsync/-/wlipsync-1.3.0.tgz",
src/components/friend/frontend/package.json CHANGED
@@ -11,6 +11,7 @@
11
  "@pixiv/three-vrm": "^3.4.0",
12
  "@pixiv/three-vrm-animation": "^3.5.0",
13
  "@pixiv/three-vrm-core": "^3.5.1",
 
14
  "@tauri-apps/api": "^2.11.1",
15
  "lucide-react": "^0.577.0",
16
  "marked": "^18.0.5",
@@ -18,6 +19,7 @@
18
  "react": "^18.3.1",
19
  "react-dom": "^18.3.1",
20
  "three": "^0.175.0",
 
21
  "wlipsync": "^1.3.0"
22
  },
23
  "devDependencies": {
 
11
  "@pixiv/three-vrm": "^3.4.0",
12
  "@pixiv/three-vrm-animation": "^3.5.0",
13
  "@pixiv/three-vrm-core": "^3.5.1",
14
+ "@ricky0123/vad-web": "^0.0.30",
15
  "@tauri-apps/api": "^2.11.1",
16
  "lucide-react": "^0.577.0",
17
  "marked": "^18.0.5",
 
19
  "react": "^18.3.1",
20
  "react-dom": "^18.3.1",
21
  "three": "^0.175.0",
22
+ "vite-plugin-static-copy": "^4.1.1",
23
  "wlipsync": "^1.3.0"
24
  },
25
  "devDependencies": {
src/components/friend/frontend/vite.config.ts CHANGED
@@ -1,8 +1,39 @@
1
  import { defineConfig } from 'vite'
2
  import react from '@vitejs/plugin-react'
 
3
 
4
  export default defineConfig({
5
- plugins: [react()],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  base: './',
7
  server: {
8
  port: 1420,
 
1
  import { defineConfig } from 'vite'
2
  import react from '@vitejs/plugin-react'
3
+ import { viteStaticCopy } from 'vite-plugin-static-copy'
4
 
5
  export default defineConfig({
6
+ plugins: [
7
+ react(),
8
+ viteStaticCopy({
9
+ targets: [
10
+ {
11
+ // Silero VAD model files (used by @ricky0123/vad-web)
12
+ src: 'node_modules/@ricky0123/vad-web/dist/silero_*.onnx',
13
+ dest: '.',
14
+ rename: { stripBase: true },
15
+ },
16
+ {
17
+ // Audio worklet bundle (used by @ricky0123/vad-web for AudioWorkletNode)
18
+ src: 'node_modules/@ricky0123/vad-web/dist/vad.worklet.bundle.min.js',
19
+ dest: '.',
20
+ rename: { stripBase: true },
21
+ },
22
+ {
23
+ // ONNX Runtime Web WASM binary files (used by onnxruntime-web)
24
+ src: 'node_modules/onnxruntime-web/dist/ort-wasm*.wasm',
25
+ dest: '.',
26
+ rename: { stripBase: true },
27
+ },
28
+ {
29
+ // ONNX Runtime Web ESM glue file (used by onnxruntime-web/wasm dynamic import)
30
+ src: 'node_modules/onnxruntime-web/dist/ort-wasm-simd-threaded.mjs',
31
+ dest: '.',
32
+ rename: { stripBase: true },
33
+ },
34
+ ],
35
+ }),
36
+ ],
37
  base: './',
38
  server: {
39
  port: 1420,
src/friend/FriendService.ts CHANGED
@@ -69,6 +69,14 @@ class FriendService {
69
  private audioCapture: AudioCaptureProvider | null = null;
70
  /** Active STT connection (Anthropic/Doubao/Whisper) during capture */
71
  private sttConnection: { send: (chunk: Buffer) => void; finalize: () => Promise<void>; close: () => void } | null = null;
 
 
 
 
 
 
 
 
72
 
73
  // ── React sync external store interface ──────────────────────────────
74
 
@@ -197,6 +205,9 @@ class FriendService {
197
  provider: string,
198
  language: string,
199
  ): Promise<void> {
 
 
 
200
  // 1. Start STT provider connection (with inner 8s timeout)
201
  const conn = await this.startSttConnectionWithTimeout(provider, language);
202
  this.sttConnection = conn;
@@ -217,6 +228,63 @@ class FriendService {
217
  if (!ok) {
218
  throw new Error('Native audio capture unavailable');
219
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  }
221
 
222
  /** Race a promise against a timeout */
@@ -337,6 +405,9 @@ class FriendService {
337
 
338
  this.capturing = false;
339
 
 
 
 
340
  // Stop cpal recording
341
  if (this.audioCapture) {
342
  await this.audioCapture.stopRecording().catch(() => {});
@@ -355,6 +426,12 @@ class FriendService {
355
  }
356
  }
357
 
 
 
 
 
 
 
358
  const transcript = this.captureTranscripts.join('');
359
  console.log(`[FriendService] _stopCapture: transcript="${transcript}" (len=${transcript.length})`);
360
  this.captureTranscripts = [];
 
69
  private audioCapture: AudioCaptureProvider | null = null;
70
  /** Active STT connection (Anthropic/Doubao/Whisper) during capture */
71
  private sttConnection: { send: (chunk: Buffer) => void; finalize: () => Promise<void>; close: () => void } | null = null;
72
+ /** Periodic timer for server-side voice segmentation (~5s intervals) */
73
+ private captureTimer: ReturnType<typeof setInterval> | null = null;
74
+ private readonly SEGMENT_MS = 5000;
75
+ /** STT provider/language cached for connection re-creation during segmentation */
76
+ private captureProvider = '';
77
+ private captureLanguage = '';
78
+ /** Guard against concurrent _flushSegment calls */
79
+ private _flushing = false;
80
 
81
  // ── React sync external store interface ──────────────────────────────
82
 
 
205
  provider: string,
206
  language: string,
207
  ): Promise<void> {
208
+ this.captureProvider = provider;
209
+ this.captureLanguage = language;
210
+
211
  // 1. Start STT provider connection (with inner 8s timeout)
212
  const conn = await this.startSttConnectionWithTimeout(provider, language);
213
  this.sttConnection = conn;
 
228
  if (!ok) {
229
  throw new Error('Native audio capture unavailable');
230
  }
231
+
232
+ // 4. Start periodic segmentation timer (voice call auto-send)
233
+ this._startSegmentTimer();
234
+ }
235
+
236
+ /**
237
+ * Periodically finalize the current STT segment and start a new one,
238
+ * sending the transcript to the CLI conversation in real-time.
239
+ *
240
+ * This replaces browser-based VAD with server-side timer segmentation.
241
+ * Without it, voice call text only appears when the user presses F2.
242
+ */
243
+ private async _flushSegment(): Promise<void> {
244
+ if (this._flushing || !this.capturing) return;
245
+ this._flushing = true;
246
+ try {
247
+ const oldConn = this.sttConnection;
248
+ if (!oldConn) return;
249
+
250
+ // 1. Create a new STT connection for ongoing audio first
251
+ const newConn = await this.startSttConnectionWithTimeout(
252
+ this.captureProvider,
253
+ this.captureLanguage,
254
+ );
255
+ this.sttConnection = newConn;
256
+
257
+ // 2. Finalize old connection (sends buffered audio to Groq)
258
+ await oldConn.finalize().catch(() => {});
259
+ oldConn.close();
260
+
261
+ // 3. Send any accumulated transcript to the CLI conversation
262
+ const transcript = this.captureTranscripts.join('').trim();
263
+ if (transcript) {
264
+ this.captureTranscripts = [];
265
+ this.sendText(transcript);
266
+ }
267
+ } catch (err) {
268
+ console.error('[FriendService] _flushSegment error:', err);
269
+ } finally {
270
+ this._flushing = false;
271
+ }
272
+ }
273
+
274
+ private _startSegmentTimer(): void {
275
+ this._clearSegmentTimer();
276
+ this.captureTimer = setInterval(() => {
277
+ this._flushSegment().catch((err) => {
278
+ console.error('[FriendService] segment timer error:', err);
279
+ });
280
+ }, this.SEGMENT_MS);
281
+ }
282
+
283
+ private _clearSegmentTimer(): void {
284
+ if (this.captureTimer) {
285
+ clearInterval(this.captureTimer);
286
+ this.captureTimer = null;
287
+ }
288
  }
289
 
290
  /** Race a promise against a timeout */
 
405
 
406
  this.capturing = false;
407
 
408
+ // Stop segment timer first
409
+ this._clearSegmentTimer();
410
+
411
  // Stop cpal recording
412
  if (this.audioCapture) {
413
  await this.audioCapture.stopRecording().catch(() => {});
 
426
  }
427
  }
428
 
429
+ // Send any remaining transcript to CLI before returning
430
+ const remaining = this.captureTranscripts.join('').trim();
431
+ if (remaining) {
432
+ this.sendText(remaining);
433
+ }
434
+
435
  const transcript = this.captureTranscripts.join('');
436
  console.log(`[FriendService] _stopCapture: transcript="${transcript}" (len=${transcript.length})`);
437
  this.captureTranscripts = [];
src/server/api/friend.ts CHANGED
@@ -186,15 +186,6 @@ export async function handleFriendApi(req: Request, url: URL): Promise<Response>
186
  }
187
  }
188
 
189
- if (pathname === '/plugins/friend/voice/start-vad' && method === 'POST') {
190
- try {
191
- await friendService.startVadVoiceCapture();
192
- return jsonResponse({ ok: true });
193
- } catch (err) {
194
- return jsonResponse({ error: String(err) }, 500);
195
- }
196
- }
197
-
198
  if (pathname === '/plugins/friend/voice/stop' && method === 'POST') {
199
  try {
200
  const text = await friendService.stopVoiceCapture();
 
186
  }
187
  }
188
 
 
 
 
 
 
 
 
 
 
189
  if (pathname === '/plugins/friend/voice/stop' && method === 'POST') {
190
  try {
191
  const text = await friendService.stopVoiceCapture();
src/server/staticFriend.ts CHANGED
@@ -18,6 +18,7 @@ const MIME_TYPES: Record<string, string> = {
18
  '.mp3': 'audio/mpeg',
19
  '.css': 'text/css; charset=utf-8',
20
  '.js': 'text/javascript; charset=utf-8',
 
21
  '.html': 'text/html; charset=utf-8',
22
  '.json': 'application/json; charset=utf-8',
23
  '.png': 'image/png',
@@ -61,6 +62,13 @@ export async function handleFriendStaticRequest(req: Request, url: URL): Promise
61
  : 'no-store',
62
  })
63
 
 
 
 
 
 
 
 
64
  if (req.method === 'HEAD') {
65
  const stat = await fs.stat(filePath)
66
  headers.set('Content-Length', String(stat.size))
 
18
  '.mp3': 'audio/mpeg',
19
  '.css': 'text/css; charset=utf-8',
20
  '.js': 'text/javascript; charset=utf-8',
21
+ '.mjs': 'text/javascript; charset=utf-8',
22
  '.html': 'text/html; charset=utf-8',
23
  '.json': 'application/json; charset=utf-8',
24
  '.png': 'image/png',
 
62
  : 'no-store',
63
  })
64
 
65
+ // Enable cross-origin isolation for SharedArrayBuffer support,
66
+ // needed by onnxruntime-web WASM threading (used by @ricky0123/vad-web).
67
+ if (relativePath === 'index.html') {
68
+ headers.set('Cross-Origin-Opener-Policy', 'same-origin')
69
+ headers.set('Cross-Origin-Embedder-Policy', 'require-corp')
70
+ }
71
+
72
  if (req.method === 'HEAD') {
73
  const stat = await fs.stat(filePath)
74
  headers.set('Content-Length', String(stat.size))