@unctad-ai/voice-agent-ui 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unctad-ai/voice-agent-ui",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "clsx": "^2.1.0",
21
21
  "tailwind-merge": "^2.0.0",
22
- "@unctad-ai/voice-agent-core": "1.0.0",
23
- "@unctad-ai/voice-agent-registries": "1.0.0"
22
+ "@unctad-ai/voice-agent-core": "1.0.1",
23
+ "@unctad-ai/voice-agent-registries": "1.0.1"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "lucide-react": "*",
@@ -1,7 +0,0 @@
1
- import {
2
- VoiceSettingsView
3
- } from "./chunk-IYKRYBBO.js";
4
- export {
5
- VoiceSettingsView as default
6
- };
7
- //# sourceMappingURL=VoiceSettingsView-J4OIRJ23.js.map
@@ -1,629 +0,0 @@
1
- // src/components/VoiceSettingsView.tsx
2
- import { motion } from "motion/react";
3
- import {
4
- ArrowLeft,
5
- RotateCcw,
6
- Volume2,
7
- Gauge,
8
- AudioLines,
9
- Sparkles,
10
- Mic,
11
- Timer,
12
- MessageSquare,
13
- Activity,
14
- EyeOff,
15
- Info,
16
- Ear,
17
- Clock,
18
- Zap,
19
- Minimize2,
20
- Signal
21
- } from "lucide-react";
22
-
23
- // src/contexts/VoiceSettingsContext.tsx
24
- import { createContext, useContext, useState, useRef, useCallback } from "react";
25
- import {
26
- DEFAULT_VOLUME,
27
- DEFAULT_PLAYBACK_SPEED,
28
- DEFAULT_TTS_ENABLED,
29
- DEFAULT_AUTO_LISTEN,
30
- DEFAULT_IDLE_TIMEOUT_MS,
31
- DEFAULT_EXPRESSIVENESS,
32
- DEFAULT_RESPONSE_LENGTH,
33
- DEFAULT_SHOW_PIPELINE_METRICS,
34
- DEFAULT_PIPELINE_METRICS_AUTO_HIDE_MS,
35
- DEFAULT_SPEECH_THRESHOLD,
36
- DEFAULT_PAUSE_TOLERANCE_MS,
37
- DEFAULT_BARGE_IN_THRESHOLD,
38
- DEFAULT_PANEL_COLLAPSE_TIMEOUT_MS,
39
- DEFAULT_STT_TIMEOUT_MS,
40
- DEFAULT_TTS_TIMEOUT_MS,
41
- DEFAULT_LLM_TIMEOUT_MS,
42
- DEFAULT_MIN_AUDIO_RMS
43
- } from "@unctad-ai/voice-agent-core";
44
- import { jsx } from "react/jsx-runtime";
45
- var DEFAULTS = {
46
- volume: DEFAULT_VOLUME,
47
- playbackSpeed: DEFAULT_PLAYBACK_SPEED,
48
- ttsEnabled: DEFAULT_TTS_ENABLED,
49
- autoListen: DEFAULT_AUTO_LISTEN,
50
- idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
51
- expressiveness: DEFAULT_EXPRESSIVENESS,
52
- responseLength: DEFAULT_RESPONSE_LENGTH,
53
- showPipelineMetrics: DEFAULT_SHOW_PIPELINE_METRICS,
54
- pipelineMetricsAutoHideMs: DEFAULT_PIPELINE_METRICS_AUTO_HIDE_MS,
55
- speechThreshold: DEFAULT_SPEECH_THRESHOLD,
56
- pauseToleranceMs: DEFAULT_PAUSE_TOLERANCE_MS,
57
- bargeInThreshold: DEFAULT_BARGE_IN_THRESHOLD,
58
- panelCollapseTimeoutMs: DEFAULT_PANEL_COLLAPSE_TIMEOUT_MS,
59
- sttTimeoutMs: DEFAULT_STT_TIMEOUT_MS,
60
- ttsTimeoutMs: DEFAULT_TTS_TIMEOUT_MS,
61
- llmTimeoutMs: DEFAULT_LLM_TIMEOUT_MS,
62
- minAudioRms: DEFAULT_MIN_AUDIO_RMS
63
- };
64
- var STORAGE_KEY = "voice-settings";
65
- function loadSettings() {
66
- try {
67
- const raw = localStorage.getItem(STORAGE_KEY);
68
- if (!raw) return { ...DEFAULTS };
69
- const parsed = JSON.parse(raw);
70
- return { ...DEFAULTS, ...parsed };
71
- } catch {
72
- return { ...DEFAULTS };
73
- }
74
- }
75
- function persistSettings(settings) {
76
- try {
77
- localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
78
- } catch {
79
- }
80
- }
81
- var VoiceSettingsContext = createContext(void 0);
82
- function VoiceSettingsProvider({ children }) {
83
- const [settings, setSettings] = useState(loadSettings);
84
- const volumeRef = useRef(settings.volume);
85
- const speedRef = useRef(settings.playbackSpeed);
86
- const updateSetting = useCallback(
87
- (key, value) => {
88
- setSettings((prev) => {
89
- const next = { ...prev, [key]: value };
90
- persistSettings(next);
91
- if (key === "volume") volumeRef.current = value;
92
- if (key === "playbackSpeed") speedRef.current = value;
93
- return next;
94
- });
95
- },
96
- []
97
- );
98
- const resetSettings = useCallback(() => {
99
- const defaults = { ...DEFAULTS };
100
- setSettings(defaults);
101
- persistSettings(defaults);
102
- volumeRef.current = defaults.volume;
103
- speedRef.current = defaults.playbackSpeed;
104
- }, []);
105
- return /* @__PURE__ */ jsx(
106
- VoiceSettingsContext.Provider,
107
- {
108
- value: { settings, volumeRef, speedRef, updateSetting, resetSettings },
109
- children
110
- }
111
- );
112
- }
113
- function useVoiceSettings() {
114
- const context = useContext(VoiceSettingsContext);
115
- if (context) return context;
116
- return {
117
- settings: DEFAULTS,
118
- volumeRef: { current: DEFAULTS.volume },
119
- speedRef: { current: DEFAULTS.playbackSpeed },
120
- updateSetting: () => {
121
- },
122
- resetSettings: () => {
123
- }
124
- };
125
- }
126
-
127
- // src/components/VoiceSettingsView.tsx
128
- import { VAD, useSiteConfig } from "@unctad-ai/voice-agent-core";
129
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
130
- function expressivenessLabel(v) {
131
- if (v <= 0.15) return "Low";
132
- if (v <= 0.4) return "Medium";
133
- return "High";
134
- }
135
- function speechThresholdLabel(v) {
136
- if (v >= 0.75) return "Strict";
137
- if (v >= 0.5) return "Balanced";
138
- return "Sensitive";
139
- }
140
- function bargeInLabel(v) {
141
- if (v >= 0.8) return "Hard";
142
- if (v >= 0.6) return "Normal";
143
- return "Easy";
144
- }
145
- function SliderSetting({
146
- icon,
147
- label,
148
- value,
149
- displayValue,
150
- min,
151
- max,
152
- step,
153
- onChange
154
- }) {
155
- return /* @__PURE__ */ jsxs("div", { className: "py-3 space-y-2.5", children: [
156
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
157
- icon,
158
- /* @__PURE__ */ jsx2("span", { className: "flex-1 text-sm font-medium text-neutral-900", children: label }),
159
- /* @__PURE__ */ jsx2("span", { className: "text-xs font-medium tabular-nums", style: { color: "var(--voice-settings-accent, #DB2129)" }, children: displayValue })
160
- ] }),
161
- /* @__PURE__ */ jsx2(
162
- "input",
163
- {
164
- type: "range",
165
- value,
166
- min,
167
- max,
168
- step,
169
- onChange: (e) => onChange(Number(e.target.value)),
170
- className: "w-full",
171
- style: { accentColor: "var(--voice-settings-accent, #DB2129)" }
172
- }
173
- )
174
- ] });
175
- }
176
- function ToggleSetting({
177
- icon,
178
- label,
179
- description,
180
- checked,
181
- onChange
182
- }) {
183
- return /* @__PURE__ */ jsxs("label", { className: "py-3 flex items-center gap-3 cursor-pointer", children: [
184
- icon,
185
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
186
- /* @__PURE__ */ jsx2("div", { className: "text-sm font-medium text-neutral-900", children: label }),
187
- /* @__PURE__ */ jsx2("div", { className: "text-xs text-neutral-500", children: description })
188
- ] }),
189
- /* @__PURE__ */ jsx2(
190
- "button",
191
- {
192
- role: "switch",
193
- "aria-checked": checked,
194
- onClick: () => onChange(!checked),
195
- style: {
196
- position: "relative",
197
- display: "inline-flex",
198
- alignItems: "center",
199
- width: 44,
200
- height: 24,
201
- borderRadius: 9999,
202
- backgroundColor: checked ? "var(--voice-settings-accent, #DB2129)" : "#d1d5db",
203
- border: "none",
204
- padding: 0,
205
- cursor: "pointer",
206
- transition: "background-color 0.2s",
207
- flexShrink: 0
208
- },
209
- children: /* @__PURE__ */ jsx2(
210
- "span",
211
- {
212
- style: {
213
- display: "inline-block",
214
- width: 20,
215
- height: 20,
216
- borderRadius: 9999,
217
- backgroundColor: "#fff",
218
- boxShadow: "0 1px 3px rgba(0,0,0,0.2)",
219
- transition: "transform 0.2s",
220
- transform: checked ? "translateX(22px)" : "translateX(2px)"
221
- }
222
- }
223
- )
224
- }
225
- )
226
- ] });
227
- }
228
- function SelectSetting({
229
- icon,
230
- label,
231
- value,
232
- onChange,
233
- options
234
- }) {
235
- return /* @__PURE__ */ jsxs("div", { className: "py-3 flex items-center gap-3", children: [
236
- icon,
237
- /* @__PURE__ */ jsx2("span", { className: "flex-1 text-sm font-medium text-neutral-900", children: label }),
238
- /* @__PURE__ */ jsx2(
239
- "select",
240
- {
241
- value,
242
- onChange: (e) => onChange(e.target.value),
243
- onMouseEnter: (e) => {
244
- e.currentTarget.style.borderColor = "#d1d5db";
245
- e.currentTarget.style.backgroundColor = "#f3f4f6";
246
- },
247
- onMouseLeave: (e) => {
248
- e.currentTarget.style.borderColor = "#e5e7eb";
249
- e.currentTarget.style.backgroundColor = "#f9fafb";
250
- },
251
- onFocus: (e) => {
252
- e.currentTarget.style.borderColor = "#9ca3af";
253
- },
254
- onBlur: (e) => {
255
- e.currentTarget.style.borderColor = "#e5e7eb";
256
- },
257
- style: {
258
- height: 32,
259
- fontSize: 12,
260
- fontWeight: 500,
261
- color: "#374151",
262
- borderRadius: 9999,
263
- border: "1px solid #e5e7eb",
264
- backgroundColor: "#f9fafb",
265
- paddingLeft: 12,
266
- paddingRight: 28,
267
- outline: "none",
268
- WebkitAppearance: "none",
269
- appearance: "none",
270
- cursor: "pointer",
271
- transition: "border-color 0.15s, background-color 0.15s",
272
- backgroundImage: `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23999' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E")`,
273
- backgroundRepeat: "no-repeat",
274
- backgroundPosition: "right 8px center"
275
- },
276
- children: options.map((opt) => /* @__PURE__ */ jsx2("option", { value: opt.value, children: opt.label }, opt.value))
277
- }
278
- )
279
- ] });
280
- }
281
- function VoiceSettingsView({ onBack, onVolumeChange }) {
282
- const { settings, updateSetting, resetSettings } = useVoiceSettings();
283
- const { colors } = useSiteConfig();
284
- const iconClass = "w-4 h-4 shrink-0";
285
- const iconStyle = { color: colors.primary };
286
- return /* @__PURE__ */ jsxs(
287
- motion.div,
288
- {
289
- initial: { opacity: 0, x: 20 },
290
- animate: { opacity: 1, x: 0 },
291
- exit: { opacity: 0, x: 20 },
292
- transition: { duration: 0.2, ease: [0.22, 1, 0.36, 1] },
293
- className: "voice-settings absolute inset-0 flex flex-col z-10",
294
- style: { borderRadius: "inherit", backgroundColor: "#f9fafb", "--voice-settings-accent": colors.primary },
295
- children: [
296
- /* @__PURE__ */ jsxs(
297
- "div",
298
- {
299
- className: "flex items-center gap-2.5 shrink-0 px-4",
300
- style: { height: 56, borderBottom: "1px solid #e5e7eb" },
301
- children: [
302
- /* @__PURE__ */ jsx2(
303
- "button",
304
- {
305
- onClick: onBack,
306
- className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors",
307
- style: { color: "#6b7280" },
308
- onMouseEnter: (e) => {
309
- e.currentTarget.style.color = "#111827";
310
- },
311
- onMouseLeave: (e) => {
312
- e.currentTarget.style.color = "#6b7280";
313
- },
314
- "aria-label": "Back to conversation",
315
- children: /* @__PURE__ */ jsx2(ArrowLeft, { className: "w-4 h-4" })
316
- }
317
- ),
318
- /* @__PURE__ */ jsx2("span", { className: "flex-1 text-sm font-semibold", style: { color: "#111827" }, children: "Settings" }),
319
- /* @__PURE__ */ jsx2(
320
- "button",
321
- {
322
- onClick: resetSettings,
323
- className: "w-8 h-8 rounded-full flex items-center justify-center cursor-pointer transition-colors",
324
- style: { color: "#9ca3af" },
325
- onMouseEnter: (e) => {
326
- e.currentTarget.style.color = colors.primary;
327
- },
328
- onMouseLeave: (e) => {
329
- e.currentTarget.style.color = "#9ca3af";
330
- },
331
- "aria-label": "Reset all settings",
332
- title: "Reset to defaults",
333
- children: /* @__PURE__ */ jsx2(RotateCcw, { className: "w-3.5 h-3.5" })
334
- }
335
- )
336
- ]
337
- }
338
- ),
339
- /* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto", children: [
340
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Voice Input", children: [
341
- /* @__PURE__ */ jsx2(
342
- ToggleSetting,
343
- {
344
- icon: /* @__PURE__ */ jsx2(Mic, { className: iconClass, style: iconStyle }),
345
- label: "Auto-listen",
346
- description: "Start mic when panel opens",
347
- checked: settings.autoListen,
348
- onChange: (v) => updateSetting("autoListen", v)
349
- }
350
- ),
351
- /* @__PURE__ */ jsx2(Divider, {}),
352
- /* @__PURE__ */ jsx2(
353
- SelectSetting,
354
- {
355
- icon: /* @__PURE__ */ jsx2(Timer, { className: iconClass, style: iconStyle }),
356
- label: "Idle timeout",
357
- value: String(settings.idleTimeoutMs),
358
- onChange: (v) => updateSetting("idleTimeoutMs", Number(v)),
359
- options: [
360
- { value: "30000", label: "30s" },
361
- { value: "60000", label: "1 min" },
362
- { value: "120000", label: "2 min" },
363
- { value: "300000", label: "5 min" }
364
- ]
365
- }
366
- ),
367
- /* @__PURE__ */ jsx2(Divider, {}),
368
- /* @__PURE__ */ jsx2(
369
- SliderSetting,
370
- {
371
- icon: /* @__PURE__ */ jsx2(Ear, { className: iconClass, style: iconStyle }),
372
- label: "Speech threshold",
373
- value: settings.speechThreshold * 100,
374
- displayValue: speechThresholdLabel(settings.speechThreshold),
375
- min: 30,
376
- max: 90,
377
- step: 5,
378
- onChange: (v) => updateSetting("speechThreshold", v / 100)
379
- }
380
- ),
381
- /* @__PURE__ */ jsx2(Divider, {}),
382
- /* @__PURE__ */ jsx2(
383
- SelectSetting,
384
- {
385
- icon: /* @__PURE__ */ jsx2(Clock, { className: iconClass, style: iconStyle }),
386
- label: "Pause tolerance",
387
- value: String(settings.pauseToleranceMs),
388
- onChange: (v) => updateSetting("pauseToleranceMs", Number(v)),
389
- options: [
390
- { value: "400", label: "Fast" },
391
- { value: "600", label: "Default" },
392
- { value: "800", label: "Relaxed" },
393
- { value: "1000", label: "Patient" }
394
- ]
395
- }
396
- ),
397
- /* @__PURE__ */ jsx2(Divider, {}),
398
- /* @__PURE__ */ jsx2(
399
- SliderSetting,
400
- {
401
- icon: /* @__PURE__ */ jsx2(Zap, { className: iconClass, style: iconStyle }),
402
- label: "Barge-in threshold",
403
- value: settings.bargeInThreshold * 100,
404
- displayValue: bargeInLabel(settings.bargeInThreshold),
405
- min: 40,
406
- max: 90,
407
- step: 5,
408
- onChange: (v) => updateSetting("bargeInThreshold", v / 100)
409
- }
410
- ),
411
- /* @__PURE__ */ jsx2(Divider, {}),
412
- /* @__PURE__ */ jsx2(
413
- SelectSetting,
414
- {
415
- icon: /* @__PURE__ */ jsx2(Minimize2, { className: iconClass, style: iconStyle }),
416
- label: "Auto-collapse",
417
- value: String(settings.panelCollapseTimeoutMs),
418
- onChange: (v) => updateSetting("panelCollapseTimeoutMs", Number(v)),
419
- options: [
420
- { value: "120000", label: "2 min" },
421
- { value: "300000", label: "5 min" },
422
- { value: "600000", label: "10 min" },
423
- { value: "0", label: "Never" }
424
- ]
425
- }
426
- )
427
- ] }),
428
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Voice Output", children: [
429
- /* @__PURE__ */ jsx2(
430
- ToggleSetting,
431
- {
432
- icon: /* @__PURE__ */ jsx2(AudioLines, { className: iconClass, style: iconStyle }),
433
- label: "Text-to-speech",
434
- description: "Speak responses aloud",
435
- checked: settings.ttsEnabled,
436
- onChange: (v) => updateSetting("ttsEnabled", v)
437
- }
438
- ),
439
- /* @__PURE__ */ jsx2(Divider, {}),
440
- /* @__PURE__ */ jsx2(
441
- SliderSetting,
442
- {
443
- icon: /* @__PURE__ */ jsx2(Volume2, { className: iconClass, style: iconStyle }),
444
- label: "Volume",
445
- value: settings.volume * 100,
446
- displayValue: `${Math.round(settings.volume * 100)}%`,
447
- min: 0,
448
- max: 100,
449
- step: 1,
450
- onChange: (v) => {
451
- updateSetting("volume", v / 100);
452
- onVolumeChange?.(v / 100);
453
- }
454
- }
455
- ),
456
- /* @__PURE__ */ jsx2(Divider, {}),
457
- /* @__PURE__ */ jsx2(
458
- SliderSetting,
459
- {
460
- icon: /* @__PURE__ */ jsx2(Gauge, { className: iconClass, style: iconStyle }),
461
- label: "Speed",
462
- value: settings.playbackSpeed * 100,
463
- displayValue: `${settings.playbackSpeed.toFixed(2)}x`,
464
- min: 75,
465
- max: 150,
466
- step: 5,
467
- onChange: (v) => updateSetting("playbackSpeed", v / 100)
468
- }
469
- ),
470
- /* @__PURE__ */ jsx2(Divider, {}),
471
- /* @__PURE__ */ jsx2(
472
- SliderSetting,
473
- {
474
- icon: /* @__PURE__ */ jsx2(Sparkles, { className: iconClass, style: iconStyle }),
475
- label: "Expressiveness",
476
- value: settings.expressiveness * 100,
477
- displayValue: expressivenessLabel(settings.expressiveness),
478
- min: 10,
479
- max: 60,
480
- step: 5,
481
- onChange: (v) => updateSetting("expressiveness", v / 100)
482
- }
483
- ),
484
- /* @__PURE__ */ jsx2(Divider, {}),
485
- /* @__PURE__ */ jsx2(
486
- SelectSetting,
487
- {
488
- icon: /* @__PURE__ */ jsx2(MessageSquare, { className: iconClass, style: iconStyle }),
489
- label: "Response length",
490
- value: String(settings.responseLength),
491
- onChange: (v) => updateSetting("responseLength", Number(v)),
492
- options: [
493
- { value: "30", label: "Brief" },
494
- { value: "60", label: "Normal" },
495
- { value: "100", label: "Detailed" }
496
- ]
497
- }
498
- )
499
- ] }),
500
- /* @__PURE__ */ jsxs(SettingsSection, { title: "Developer", children: [
501
- /* @__PURE__ */ jsx2(
502
- ToggleSetting,
503
- {
504
- icon: /* @__PURE__ */ jsx2(Activity, { className: iconClass, style: iconStyle }),
505
- label: "Pipeline metrics",
506
- description: "Show STT / LLM / TTS timings",
507
- checked: settings.showPipelineMetrics,
508
- onChange: (v) => updateSetting("showPipelineMetrics", v)
509
- }
510
- ),
511
- /* @__PURE__ */ jsx2(Divider, {}),
512
- /* @__PURE__ */ jsx2(
513
- SelectSetting,
514
- {
515
- icon: /* @__PURE__ */ jsx2(EyeOff, { className: iconClass, style: iconStyle }),
516
- label: "Auto-hide metrics",
517
- value: String(settings.pipelineMetricsAutoHideMs),
518
- onChange: (v) => updateSetting("pipelineMetricsAutoHideMs", Number(v)),
519
- options: [
520
- { value: "5000", label: "5s" },
521
- { value: "8000", label: "8s" },
522
- { value: "15000", label: "15s" },
523
- { value: "0", label: "Never" }
524
- ]
525
- }
526
- ),
527
- /* @__PURE__ */ jsx2(Divider, {}),
528
- /* @__PURE__ */ jsx2(
529
- SelectSetting,
530
- {
531
- icon: /* @__PURE__ */ jsx2(Mic, { className: iconClass, style: iconStyle }),
532
- label: "STT timeout",
533
- value: String(settings.sttTimeoutMs),
534
- onChange: (v) => updateSetting("sttTimeoutMs", Number(v)),
535
- options: [
536
- { value: "10000", label: "10s" },
537
- { value: "15000", label: "15s" },
538
- { value: "30000", label: "30s" }
539
- ]
540
- }
541
- ),
542
- /* @__PURE__ */ jsx2(Divider, {}),
543
- /* @__PURE__ */ jsx2(
544
- SelectSetting,
545
- {
546
- icon: /* @__PURE__ */ jsx2(AudioLines, { className: iconClass, style: iconStyle }),
547
- label: "TTS timeout",
548
- value: String(settings.ttsTimeoutMs),
549
- onChange: (v) => updateSetting("ttsTimeoutMs", Number(v)),
550
- options: [
551
- { value: "30000", label: "30s" },
552
- { value: "55000", label: "55s" },
553
- { value: "90000", label: "90s" }
554
- ]
555
- }
556
- ),
557
- /* @__PURE__ */ jsx2(Divider, {}),
558
- /* @__PURE__ */ jsx2(
559
- SelectSetting,
560
- {
561
- icon: /* @__PURE__ */ jsx2(Sparkles, { className: iconClass, style: iconStyle }),
562
- label: "LLM timeout",
563
- value: String(settings.llmTimeoutMs),
564
- onChange: (v) => updateSetting("llmTimeoutMs", Number(v)),
565
- options: [
566
- { value: "10000", label: "10s" },
567
- { value: "20000", label: "20s" },
568
- { value: "30000", label: "30s" },
569
- { value: "60000", label: "60s" }
570
- ]
571
- }
572
- ),
573
- /* @__PURE__ */ jsx2(Divider, {}),
574
- /* @__PURE__ */ jsx2(
575
- SelectSetting,
576
- {
577
- icon: /* @__PURE__ */ jsx2(Signal, { className: iconClass, style: iconStyle }),
578
- label: "Min audio level",
579
- value: String(settings.minAudioRms),
580
- onChange: (v) => updateSetting("minAudioRms", Number(v)),
581
- options: [
582
- { value: "0.01", label: "Sensitive" },
583
- { value: "0.02", label: "Default" },
584
- { value: "0.035", label: "Moderate" },
585
- { value: "0.05", label: "Strict" }
586
- ]
587
- }
588
- )
589
- ] }),
590
- /* @__PURE__ */ jsx2(SettingsSection, { title: "Info", last: true, children: /* @__PURE__ */ jsxs("div", { className: "py-3 flex items-start gap-3", children: [
591
- /* @__PURE__ */ jsx2(Info, { className: iconClass, style: { ...iconStyle, marginTop: 2 } }),
592
- /* @__PURE__ */ jsx2("div", { className: "space-y-1 text-xs text-neutral-500", children: /* @__PURE__ */ jsxs("div", { children: [
593
- "VAD Threshold:",
594
- " ",
595
- /* @__PURE__ */ jsx2("span", { className: "font-medium text-neutral-700", children: VAD.positiveSpeechThreshold })
596
- ] }) })
597
- ] }) })
598
- ] })
599
- ]
600
- }
601
- );
602
- }
603
- function SettingsSection({
604
- title,
605
- children,
606
- last
607
- }) {
608
- return /* @__PURE__ */ jsxs("div", { style: { borderBottom: last ? "none" : "1px solid #e5e7eb" }, children: [
609
- /* @__PURE__ */ jsx2(
610
- "div",
611
- {
612
- className: "px-4 pt-4 pb-1 text-[11px] font-semibold uppercase tracking-wider",
613
- style: { color: "#9ca3af" },
614
- children: title
615
- }
616
- ),
617
- /* @__PURE__ */ jsx2("div", { className: "px-4", children })
618
- ] });
619
- }
620
- function Divider() {
621
- return /* @__PURE__ */ jsx2("div", { style: { height: 1, backgroundColor: "#f3f4f6" } });
622
- }
623
-
624
- export {
625
- VoiceSettingsProvider,
626
- useVoiceSettings,
627
- VoiceSettingsView
628
- };
629
- //# sourceMappingURL=chunk-IYKRYBBO.js.map