dsh-live-voice 0.3.1 → 0.3.2
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/CHANGELOG.md +75 -3
- package/PLAN.md +19 -1
- package/README.md +1 -1
- package/docs/CONFIGURATION.md +17 -3
- package/docs/VOICE-LIFECYCLE.md +5 -1
- package/lib/client.js +512 -318
- package/lib/server.js +157 -4
- package/package.json +1 -1
- package/src/app/client/apply.tsx +96 -40
- package/src/app/client/i18n/catalogs/base.ts +6 -0
- package/src/app/client/i18n/catalogs/en.ts +9 -0
- package/src/app/client/i18n/catalogs/es.ts +9 -0
- package/src/app/client/i18n/catalogs/fr.ts +9 -0
- package/src/app/client/i18n/catalogs/hi.ts +9 -0
- package/src/app/client/i18n/catalogs/pt-BR.ts +9 -0
- package/src/app/client/i18n/catalogs/zh.ts +8 -0
- package/src/app/server/apply.ts +4 -0
- package/src/app/server/registerRoutes.ts +40 -0
- package/src/modules/conversation/ARCHITECTURE.md +2 -0
- package/src/modules/core/ARCHITECTURE.md +1 -1
- package/src/modules/core/coordinator.ts +8 -6
- package/src/modules/core/qwen/QwenSettings.tsx +15 -14
- package/src/modules/core/settings.ts +26 -8
- package/src/modules/core/transcript.ts +5 -43
- package/src/modules/recognition/engines/whisper/WhisperRecognitionEngine.ts +5 -3
- package/src/modules/recognition/engines/whisper/WhisperSettings.tsx +19 -20
- package/src/modules/settings/ARCHITECTURE.md +1 -1
- package/src/modules/settings/models/settingsHost.ts +45 -0
- package/src/modules/settings/models/settingsStorage.ts +45 -10
- package/src/modules/settings/sections/recognition/RecognitionFilterSettings.tsx +2 -2
- package/src/modules/settings/sections/recognition/SilenceDetectionSettings.tsx +37 -7
- package/src/modules/settings/sections/recognition/VoiceCommandSettings.tsx +2 -2
- package/src/modules/settings/sections/speak/OutputFilterSettings.tsx +4 -4
- package/src/modules/settings/sections/speak/SpeechAdvancedSettings.tsx +2 -2
- package/src/modules/settings/sections/speak/SpeechEngineSettings.tsx +2 -2
- package/src/modules/settings/services/releases.ts +9 -1
- package/src/shared/ARCHITECTURE.md +1 -1
- package/src/shared/design-system/forms/DraftField.tsx +79 -0
- package/src/shared/design-system/forms/NumberField.tsx +4 -10
- package/src/shared/design-system/forms/TextAreaField.tsx +4 -10
- package/src/shared/design-system/forms/TextField.tsx +4 -10
- package/src/styles/index.ts +1 -1
package/lib/client.js
CHANGED
|
@@ -34,63 +34,34 @@ __export(apply_exports, {
|
|
|
34
34
|
inject: () => inject
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(apply_exports);
|
|
37
|
-
var
|
|
37
|
+
var import_react54 = __toESM(require("react"), 1);
|
|
38
38
|
var import_react_dom = require("react-dom");
|
|
39
39
|
|
|
40
40
|
// src/modules/core/transcript.ts
|
|
41
41
|
var TranscriptDraft = class {
|
|
42
|
-
constructor() {
|
|
43
|
-
this.reset();
|
|
44
|
-
}
|
|
45
42
|
reset() {
|
|
46
|
-
this.owned = null;
|
|
47
|
-
this.edited = false;
|
|
48
43
|
}
|
|
49
44
|
update(current, hypothesis, final = false) {
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
let base = current;
|
|
55
|
-
let at = current.length;
|
|
56
|
-
if (this.owned) {
|
|
57
|
-
const { start, text: text2, before: before2, after: after2 } = this.owned;
|
|
58
|
-
if (current === before2 + text2 + after2) {
|
|
59
|
-
base = before2 + after2;
|
|
60
|
-
at = start;
|
|
61
|
-
} else if (text2 && current.indexOf(text2) >= 0 && current.indexOf(text2) === current.lastIndexOf(text2)) {
|
|
62
|
-
at = current.indexOf(text2);
|
|
63
|
-
base = current.slice(0, at) + current.slice(at + text2.length);
|
|
64
|
-
} else {
|
|
65
|
-
this.owned = null;
|
|
66
|
-
this.edited = !final;
|
|
67
|
-
return current;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const before = base.slice(0, at);
|
|
71
|
-
const after = base.slice(at);
|
|
72
|
-
const separator = hypothesis && before && !/\s$/.test(before) ? " " : "";
|
|
73
|
-
const text = separator + hypothesis;
|
|
74
|
-
const result = before + text + after;
|
|
75
|
-
this.owned = final ? null : { start: at, text, before, after };
|
|
76
|
-
return result;
|
|
45
|
+
if (!final || !hypothesis) return current;
|
|
46
|
+
const separator = current && !/\s$/.test(current) ? " " : "";
|
|
47
|
+
return current + separator + hypothesis;
|
|
77
48
|
}
|
|
78
49
|
};
|
|
79
50
|
|
|
80
51
|
// src/modules/core/settings.ts
|
|
81
52
|
var voiceDetectionPresets = Object.freeze({
|
|
82
53
|
short: Object.freeze({
|
|
83
|
-
silenceMs:
|
|
54
|
+
silenceMs: 500,
|
|
84
55
|
label: "Short",
|
|
85
56
|
description: "Send quickly after a short pause."
|
|
86
57
|
}),
|
|
87
58
|
natural: Object.freeze({
|
|
88
|
-
silenceMs:
|
|
59
|
+
silenceMs: 1e3,
|
|
89
60
|
label: "Natural",
|
|
90
61
|
description: "Allow normal pauses between phrases."
|
|
91
62
|
}),
|
|
92
63
|
long: Object.freeze({
|
|
93
|
-
silenceMs:
|
|
64
|
+
silenceMs: 2e3,
|
|
94
65
|
label: "Long",
|
|
95
66
|
description: "Wait through longer thinking pauses."
|
|
96
67
|
})
|
|
@@ -116,7 +87,8 @@ var defaultSettings = Object.freeze({
|
|
|
116
87
|
recognitionEngine: "browser",
|
|
117
88
|
recognitionProcessLocally: true,
|
|
118
89
|
recognitionAutoInstall: true,
|
|
119
|
-
voiceDetectionPreset: "
|
|
90
|
+
voiceDetectionPreset: "short",
|
|
91
|
+
voiceDetectionCustomSilenceMs: 1e3,
|
|
120
92
|
recognitionMaxUtteranceSeconds: 60,
|
|
121
93
|
microphoneEnabled: true,
|
|
122
94
|
holdToTalkEnabled: true,
|
|
@@ -150,6 +122,10 @@ var defaultSettings = Object.freeze({
|
|
|
150
122
|
segmentGapMs: 400
|
|
151
123
|
});
|
|
152
124
|
var normalizeCommandPhrases = (value, fallback) => typeof value === "string" && value.length <= 1e3 && !value.includes("\0") ? value.split(/[,\r\n]+/).map((phrase) => phrase.trim()).filter(Boolean).slice(0, 20).join(", ") : fallback;
|
|
125
|
+
var customSilenceMinMs = 100;
|
|
126
|
+
var customSilenceMaxMs = 1e4;
|
|
127
|
+
var normalizeCustomSilenceMs = (value) => Number.isInteger(value) && value >= customSilenceMinMs && value <= customSilenceMaxMs ? value : defaultSettings.voiceDetectionCustomSilenceMs;
|
|
128
|
+
var voiceDetectionSilenceMs = (settings) => settings.voiceDetectionPreset === "custom" ? normalizeCustomSilenceMs(settings.voiceDetectionCustomSilenceMs) : (voiceDetectionPresets[settings.voiceDetectionPreset] || voiceDetectionPresets[defaultSettings.voiceDetectionPreset]).silenceMs;
|
|
153
129
|
function normalizeSettings(value) {
|
|
154
130
|
const source = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
155
131
|
return {
|
|
@@ -157,7 +133,8 @@ function normalizeSettings(value) {
|
|
|
157
133
|
recognitionEngine: ["browser", "whisper-http", "qwen-http"].includes(source.recognitionEngine) ? source.recognitionEngine : defaultSettings.recognitionEngine,
|
|
158
134
|
recognitionProcessLocally: typeof source.recognitionProcessLocally === "boolean" ? source.recognitionProcessLocally : defaultSettings.recognitionProcessLocally,
|
|
159
135
|
recognitionAutoInstall: typeof source.recognitionAutoInstall === "boolean" ? source.recognitionAutoInstall : defaultSettings.recognitionAutoInstall,
|
|
160
|
-
voiceDetectionPreset: Object.hasOwn(voiceDetectionPresets, source.voiceDetectionPreset) ? source.voiceDetectionPreset : defaultSettings.voiceDetectionPreset,
|
|
136
|
+
voiceDetectionPreset: source.voiceDetectionPreset === "custom" || Object.hasOwn(voiceDetectionPresets, source.voiceDetectionPreset) ? source.voiceDetectionPreset : defaultSettings.voiceDetectionPreset,
|
|
137
|
+
voiceDetectionCustomSilenceMs: normalizeCustomSilenceMs(source.voiceDetectionCustomSilenceMs),
|
|
161
138
|
recognitionMaxUtteranceSeconds: Number.isInteger(source.recognitionMaxUtteranceSeconds) && source.recognitionMaxUtteranceSeconds >= 10 && source.recognitionMaxUtteranceSeconds <= 300 ? source.recognitionMaxUtteranceSeconds : defaultSettings.recognitionMaxUtteranceSeconds,
|
|
162
139
|
microphoneEnabled: typeof source.microphoneEnabled === "boolean" ? source.microphoneEnabled : defaultSettings.microphoneEnabled,
|
|
163
140
|
holdToTalkEnabled: typeof source.holdToTalkEnabled === "boolean" ? source.holdToTalkEnabled : defaultSettings.holdToTalkEnabled,
|
|
@@ -339,7 +316,8 @@ var VoiceCoordinator = class {
|
|
|
339
316
|
lang: this.snapshot.settings.recognitionLang,
|
|
340
317
|
processLocally: this.snapshot.settings.recognitionProcessLocally,
|
|
341
318
|
autoInstallLocalPack: this.snapshot.settings.recognitionAutoInstall,
|
|
342
|
-
voiceDetectionPreset: this.snapshot.settings.voiceDetectionPreset
|
|
319
|
+
voiceDetectionPreset: this.snapshot.settings.voiceDetectionPreset,
|
|
320
|
+
voiceDetectionCustomSilenceMs: this.snapshot.settings.voiceDetectionCustomSilenceMs
|
|
343
321
|
});
|
|
344
322
|
this.patch({ capabilities: { ...this.snapshot.capabilities, recognition: void 0 } });
|
|
345
323
|
}
|
|
@@ -361,7 +339,8 @@ var VoiceCoordinator = class {
|
|
|
361
339
|
lang: settings.recognitionLang,
|
|
362
340
|
processLocally: settings.recognitionProcessLocally,
|
|
363
341
|
autoInstallLocalPack: settings.recognitionAutoInstall,
|
|
364
|
-
voiceDetectionPreset: settings.voiceDetectionPreset
|
|
342
|
+
voiceDetectionPreset: settings.voiceDetectionPreset,
|
|
343
|
+
voiceDetectionCustomSilenceMs: settings.voiceDetectionCustomSilenceMs
|
|
365
344
|
});
|
|
366
345
|
this.patch({
|
|
367
346
|
settings,
|
|
@@ -454,7 +433,6 @@ var VoiceCoordinator = class {
|
|
|
454
433
|
}
|
|
455
434
|
muteListening() {
|
|
456
435
|
this.cancelAutoSend();
|
|
457
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), "", true));
|
|
458
436
|
this.transcript.reset();
|
|
459
437
|
this.updateSettings({ microphoneEnabled: false });
|
|
460
438
|
if (!this.snapshot.settings.voiceCommandsEnabled) return this.stopListening();
|
|
@@ -636,7 +614,6 @@ var VoiceCoordinator = class {
|
|
|
636
614
|
clear: this.snapshot.settings.voiceCommandClear
|
|
637
615
|
});
|
|
638
616
|
if (command) {
|
|
639
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), "", true));
|
|
640
617
|
this.transcript.reset();
|
|
641
618
|
this.cancelAutoSend();
|
|
642
619
|
this.patch({ recognizing: false });
|
|
@@ -660,13 +637,11 @@ var VoiceCoordinator = class {
|
|
|
660
637
|
return;
|
|
661
638
|
}
|
|
662
639
|
if (this.snapshot.muted) {
|
|
663
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), "", true));
|
|
664
640
|
this.transcript.reset();
|
|
665
641
|
this.patch({ recognizing: false });
|
|
666
642
|
return;
|
|
667
643
|
}
|
|
668
644
|
if (this.snapshot.settings.recognitionFilterEnabled && !hasMinimumWords(final, this.snapshot.settings.recognitionMinimumWords)) {
|
|
669
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), "", true));
|
|
670
645
|
this.transcript.reset();
|
|
671
646
|
this.patch({ recognizing: false });
|
|
672
647
|
return;
|
|
@@ -677,7 +652,6 @@ var VoiceCoordinator = class {
|
|
|
677
652
|
}
|
|
678
653
|
if (interim) {
|
|
679
654
|
this.cancelAutoSend();
|
|
680
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), interim));
|
|
681
655
|
}
|
|
682
656
|
if (!interim && this.snapshot.recognizing)
|
|
683
657
|
this.assistantSpeechNotBefore = Date.now() + this.snapshot.settings.assistantSpeechDelaySeconds * 1e3;
|
|
@@ -745,7 +719,6 @@ var VoiceCoordinator = class {
|
|
|
745
719
|
});
|
|
746
720
|
}
|
|
747
721
|
async cancelDictation() {
|
|
748
|
-
this.composer.setDraft(this.transcript.update(this.composer.getDraft(), "", true));
|
|
749
722
|
await this.stopListening();
|
|
750
723
|
}
|
|
751
724
|
async endConversation() {
|
|
@@ -1233,7 +1206,7 @@ var VoiceOwnership = class {
|
|
|
1233
1206
|
};
|
|
1234
1207
|
|
|
1235
1208
|
// src/modules/recognition/components/RecognitionCapabilityStatus.tsx
|
|
1236
|
-
var
|
|
1209
|
+
var import_react20 = __toESM(require("react"), 1);
|
|
1237
1210
|
|
|
1238
1211
|
// src/app/client/i18n/catalogs/en.ts
|
|
1239
1212
|
var en = {
|
|
@@ -1324,6 +1297,8 @@ var en = {
|
|
|
1324
1297
|
"dsh-live-voice.recognition.planned.vote": "Coming Soon \u2014 vote on repo issues",
|
|
1325
1298
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 Soon",
|
|
1326
1299
|
"dsh-live-voice.recognition.planned.webGpu": "Browser WebGPU Inference \u2014 Soon",
|
|
1300
|
+
"dsh-live-voice.recognition.presets.custom.description": "Choose your own silence duration.",
|
|
1301
|
+
"dsh-live-voice.recognition.presets.custom.label": "Custom",
|
|
1327
1302
|
"dsh-live-voice.recognition.presets.long.description": "Wait through longer thinking pauses.",
|
|
1328
1303
|
"dsh-live-voice.recognition.presets.long.label": "Long",
|
|
1329
1304
|
"dsh-live-voice.recognition.presets.natural.description": "Allow normal pauses between phrases.",
|
|
@@ -1336,6 +1311,8 @@ var en = {
|
|
|
1336
1311
|
"dsh-live-voice.recognition.qwen.endpointHelp": "HTTP API at the configured DSH host URL (default: http://127.0.0.1:8080/inference). Audio uses the authenticated DSH host transcription route.",
|
|
1337
1312
|
"dsh-live-voice.recognition.qwen.hostHelp": "Host-wide settings for the Qwen3 ASR + TTS server. Enter any HTTP or HTTPS base URL reachable from the DSH host. The browser accesses it through authenticated DSH routes.",
|
|
1338
1313
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 HTTP API",
|
|
1314
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "Enter a whole number from 100 to 10,000 ms. Saved when you leave the field. Short pauses may split speech; recognition adds its own latency.",
|
|
1315
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "Custom pause (milliseconds)",
|
|
1339
1316
|
"dsh-live-voice.recognition.silenceDetection.duration": "Pause before sending: {milliseconds} ms",
|
|
1340
1317
|
"dsh-live-voice.recognition.silenceDetection.help": "Controls how long a pause must last before captured speech is sent for recognition.",
|
|
1341
1318
|
"dsh-live-voice.recognition.silenceDetection.label": "Silence detection",
|
|
@@ -1373,6 +1350,8 @@ var en = {
|
|
|
1373
1350
|
"dsh-live-voice.settings.engine.refresh": "Refresh available engines",
|
|
1374
1351
|
"dsh-live-voice.settings.filters.title": "Filtering",
|
|
1375
1352
|
"dsh-live-voice.settings.general.title": "General",
|
|
1353
|
+
"dsh-live-voice.settings.persistence.loadError": "Could not load Live Voice settings from the server. Reload to try again.",
|
|
1354
|
+
"dsh-live-voice.settings.persistence.saveError": "Could not save Live Voice settings on the server. Please try again.",
|
|
1376
1355
|
"dsh-live-voice.settings.tabs.conversation": "Conversation",
|
|
1377
1356
|
"dsh-live-voice.settings.tabs.recognition": "Speech recognition",
|
|
1378
1357
|
"dsh-live-voice.settings.tabs.speak": "Speech",
|
|
@@ -1541,6 +1520,8 @@ var es = {
|
|
|
1541
1520
|
"dsh-live-voice.recognition.planned.vote": "Pr\xF3ximamente \u2014 vota en las incidencias del repositorio",
|
|
1542
1521
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 Pr\xF3ximamente",
|
|
1543
1522
|
"dsh-live-voice.recognition.planned.webGpu": "Inferencia WebGPU en el navegador \u2014 Pr\xF3ximamente",
|
|
1523
|
+
"dsh-live-voice.recognition.presets.custom.description": "Elige la duraci\xF3n del silencio.",
|
|
1524
|
+
"dsh-live-voice.recognition.presets.custom.label": "Personalizada",
|
|
1544
1525
|
"dsh-live-voice.recognition.presets.long.description": "Espera durante pausas de reflexi\xF3n m\xE1s largas.",
|
|
1545
1526
|
"dsh-live-voice.recognition.presets.long.label": "Larga",
|
|
1546
1527
|
"dsh-live-voice.recognition.presets.natural.description": "Permite pausas normales entre frases.",
|
|
@@ -1553,6 +1534,8 @@ var es = {
|
|
|
1553
1534
|
"dsh-live-voice.recognition.qwen.endpointHelp": "API HTTP en la URL configurada en el host de DSH (predeterminada: http://127.0.0.1:8080/inference). El audio utiliza la ruta de transcripci\xF3n autenticada del host de DSH.",
|
|
1554
1535
|
"dsh-live-voice.recognition.qwen.hostHelp": "Configuraci\xF3n para todo el host del servidor Qwen3 ASR + TTS. Introduce una URL base HTTP o HTTPS accesible desde el host de DSH. El navegador accede a ella mediante las rutas autenticadas de DSH.",
|
|
1555
1536
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 API HTTP",
|
|
1537
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "Introduce un n\xFAmero entero de 100 a 10.000 ms. Se guarda al salir del campo. Las pausas cortas pueden dividir el habla; el reconocimiento a\xF1ade su propia latencia.",
|
|
1538
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "Pausa personalizada (milisegundos)",
|
|
1556
1539
|
"dsh-live-voice.recognition.silenceDetection.duration": "Pausa antes de enviar: {milliseconds} ms",
|
|
1557
1540
|
"dsh-live-voice.recognition.silenceDetection.help": "Determina cu\xE1nto debe durar una pausa antes de enviar la voz capturada para su reconocimiento.",
|
|
1558
1541
|
"dsh-live-voice.recognition.silenceDetection.label": "Detecci\xF3n de silencio",
|
|
@@ -1590,6 +1573,8 @@ var es = {
|
|
|
1590
1573
|
"dsh-live-voice.settings.engine.refresh": "Actualizar los motores disponibles",
|
|
1591
1574
|
"dsh-live-voice.settings.filters.title": "Filtrado",
|
|
1592
1575
|
"dsh-live-voice.settings.general.title": "General",
|
|
1576
|
+
"dsh-live-voice.settings.persistence.loadError": "No se pudieron cargar los ajustes de Live Voice del servidor. Recarga para volver a intentarlo.",
|
|
1577
|
+
"dsh-live-voice.settings.persistence.saveError": "No se pudieron guardar los ajustes de Live Voice en el servidor. Int\xE9ntalo de nuevo.",
|
|
1593
1578
|
"dsh-live-voice.settings.tabs.conversation": "Conversaci\xF3n",
|
|
1594
1579
|
"dsh-live-voice.settings.tabs.recognition": "Reconocimiento de voz",
|
|
1595
1580
|
"dsh-live-voice.settings.tabs.speak": "S\xEDntesis de voz",
|
|
@@ -1758,6 +1743,8 @@ var fr = {
|
|
|
1758
1743
|
"dsh-live-voice.recognition.planned.vote": "Bient\xF4t disponible \u2014 votez dans les tickets du d\xE9p\xF4t",
|
|
1759
1744
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 Bient\xF4t disponible",
|
|
1760
1745
|
"dsh-live-voice.recognition.planned.webGpu": "Inf\xE9rence WebGPU dans le navigateur \u2014 Bient\xF4t disponible",
|
|
1746
|
+
"dsh-live-voice.recognition.presets.custom.description": "Choisissez la dur\xE9e du silence.",
|
|
1747
|
+
"dsh-live-voice.recognition.presets.custom.label": "Personnalis\xE9e",
|
|
1761
1748
|
"dsh-live-voice.recognition.presets.long.description": "Attend pendant les pauses de r\xE9flexion plus longues.",
|
|
1762
1749
|
"dsh-live-voice.recognition.presets.long.label": "Longue",
|
|
1763
1750
|
"dsh-live-voice.recognition.presets.natural.description": "Autorise des pauses normales entre les phrases.",
|
|
@@ -1770,6 +1757,8 @@ var fr = {
|
|
|
1770
1757
|
"dsh-live-voice.recognition.qwen.endpointHelp": "API HTTP \xE0 l\u2019URL configur\xE9e sur l\u2019h\xF4te DSH (par d\xE9faut : http://127.0.0.1:8080/inference). Le son passe par la route de transcription authentifi\xE9e de l\u2019h\xF4te DSH.",
|
|
1771
1758
|
"dsh-live-voice.recognition.qwen.hostHelp": "Param\xE8tres communs \xE0 tout l\u2019h\xF4te pour le serveur Qwen3 ASR + TTS. Saisissez une URL de base HTTP ou HTTPS accessible depuis l\u2019h\xF4te DSH. Le navigateur y acc\xE8de via les routes authentifi\xE9es de DSH.",
|
|
1772
1759
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 API HTTP",
|
|
1760
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "Saisissez un entier de 100 \xE0 10 000 ms. Enregistr\xE9 en quittant le champ. Les pauses courtes peuvent couper la parole ; la reconnaissance ajoute sa propre latence.",
|
|
1761
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "Pause personnalis\xE9e (millisecondes)",
|
|
1773
1762
|
"dsh-live-voice.recognition.silenceDetection.duration": "Pause avant l\u2019envoi : {milliseconds} ms",
|
|
1774
1763
|
"dsh-live-voice.recognition.silenceDetection.help": "D\xE9termine la dur\xE9e de pause n\xE9cessaire avant que la parole captur\xE9e soit envoy\xE9e pour reconnaissance.",
|
|
1775
1764
|
"dsh-live-voice.recognition.silenceDetection.label": "D\xE9tection du silence",
|
|
@@ -1807,6 +1796,8 @@ var fr = {
|
|
|
1807
1796
|
"dsh-live-voice.settings.engine.refresh": "Actualiser les moteurs disponibles",
|
|
1808
1797
|
"dsh-live-voice.settings.filters.title": "Filtrage",
|
|
1809
1798
|
"dsh-live-voice.settings.general.title": "G\xE9n\xE9ral",
|
|
1799
|
+
"dsh-live-voice.settings.persistence.loadError": "Impossible de charger les param\xE8tres Live Voice depuis le serveur. Rechargez pour r\xE9essayer.",
|
|
1800
|
+
"dsh-live-voice.settings.persistence.saveError": "Impossible de sauvegarder les param\xE8tres Live Voice sur le serveur. R\xE9essayez.",
|
|
1810
1801
|
"dsh-live-voice.settings.tabs.conversation": "Conversation",
|
|
1811
1802
|
"dsh-live-voice.settings.tabs.recognition": "Reconnaissance vocale",
|
|
1812
1803
|
"dsh-live-voice.settings.tabs.speak": "Synth\xE8se vocale",
|
|
@@ -1975,6 +1966,8 @@ var hi = {
|
|
|
1975
1966
|
"dsh-live-voice.recognition.planned.vote": "\u091C\u0932\u094D\u0926 \u0906 \u0930\u0939\u093E \u0939\u0948 \u2014 \u0930\u093F\u092A\u0949\u091C\u093C\u093F\u091F\u0930\u0940 \u0915\u0947 \u0907\u0936\u094D\u092F\u0942 \u092E\u0947\u0902 \u0935\u094B\u091F \u0926\u0947\u0902",
|
|
1976
1967
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 \u091C\u0932\u094D\u0926 \u0906 \u0930\u0939\u093E \u0939\u0948",
|
|
1977
1968
|
"dsh-live-voice.recognition.planned.webGpu": "\u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u092E\u0947\u0902 WebGPU \u0907\u0928\u094D\u092B\u093C\u0930\u0947\u0902\u0938 \u2014 \u091C\u0932\u094D\u0926 \u0906 \u0930\u0939\u093E \u0939\u0948",
|
|
1969
|
+
"dsh-live-voice.recognition.presets.custom.description": "\u092E\u094C\u0928 \u0915\u0940 \u0905\u0935\u0927\u093F \u0938\u094D\u0935\u092F\u0902 \u091A\u0941\u0928\u0947\u0902\u0964",
|
|
1970
|
+
"dsh-live-voice.recognition.presets.custom.label": "\u0915\u0938\u094D\u091F\u092E",
|
|
1978
1971
|
"dsh-live-voice.recognition.presets.long.description": "\u0932\u0902\u092C\u0947 \u0938\u094B\u091A-\u0935\u093F\u091A\u093E\u0930 \u0935\u093E\u0932\u0947 \u0935\u093F\u0930\u093E\u092E\u094B\u0902 \u0924\u0915 \u092A\u094D\u0930\u0924\u0940\u0915\u094D\u0937\u093E \u0915\u0930\u0947\u0902\u0964",
|
|
1979
1972
|
"dsh-live-voice.recognition.presets.long.label": "\u0932\u0902\u092C\u093E",
|
|
1980
1973
|
"dsh-live-voice.recognition.presets.natural.description": "\u0935\u093E\u0915\u094D\u092F\u093E\u0902\u0936\u094B\u0902 \u0915\u0947 \u092C\u0940\u091A \u0938\u093E\u092E\u093E\u0928\u094D\u092F \u0935\u093F\u0930\u093E\u092E \u0915\u0940 \u0905\u0928\u0941\u092E\u0924\u093F \u0926\u0947\u0902\u0964",
|
|
@@ -1987,6 +1980,8 @@ var hi = {
|
|
|
1987
1980
|
"dsh-live-voice.recognition.qwen.endpointHelp": "HTTP API, \u0915\u0949\u0928\u094D\u092B\u093C\u093F\u0917\u0930 \u0915\u093F\u090F \u0917\u090F DSH \u0939\u094B\u0938\u094D\u091F URL \u092A\u0930 \u0939\u0948 (\u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F: http://127.0.0.1:8080/inference)\u0964 \u0911\u0921\u093F\u092F\u094B \u0915\u0947 \u0932\u093F\u090F \u092A\u094D\u0930\u092E\u093E\u0923\u0940\u0915\u0930\u0923 \u0935\u093E\u0932\u0947 DSH \u0939\u094B\u0938\u094D\u091F \u091F\u094D\u0930\u093E\u0902\u0938\u0915\u094D\u0930\u093F\u092A\u094D\u0936\u0928 \u0930\u0942\u091F \u0915\u093E \u0909\u092A\u092F\u094B\u0917 \u0939\u094B\u0924\u093E \u0939\u0948\u0964",
|
|
1988
1981
|
"dsh-live-voice.recognition.qwen.hostHelp": "Qwen3 ASR + TTS \u0938\u0930\u094D\u0935\u0930 \u0915\u0940 \u092A\u0942\u0930\u0947 \u0939\u094B\u0938\u094D\u091F \u092A\u0930 \u0932\u093E\u0917\u0942 \u0939\u094B\u0928\u0947 \u0935\u093E\u0932\u0940 \u0938\u0947\u091F\u093F\u0902\u0917\u094D\u0938\u0964 \u0915\u094B\u0908 \u092D\u0940 HTTP \u092F\u093E HTTPS \u092C\u0947\u0938 URL \u0926\u0930\u094D\u091C \u0915\u0930\u0947\u0902 \u091C\u093F\u0938 \u0924\u0915 DSH \u0939\u094B\u0938\u094D\u091F \u092A\u0939\u0941\u0901\u091A \u0938\u0915\u0947\u0964 \u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u092A\u094D\u0930\u092E\u093E\u0923\u0940\u0915\u0930\u0923 \u0935\u093E\u0932\u0947 DSH \u0930\u0942\u091F\u094B\u0902 \u0915\u0947 \u091C\u093C\u0930\u093F\u090F \u0907\u0938\u0947 \u090F\u0915\u094D\u0938\u0947\u0938 \u0915\u0930\u0924\u093E \u0939\u0948\u0964",
|
|
1989
1982
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 HTTP API",
|
|
1983
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "100 \u0938\u0947 10,000 ms \u0924\u0915 \u092A\u0942\u0930\u094D\u0923\u093E\u0902\u0915 \u0926\u0930\u094D\u091C \u0915\u0930\u0947\u0902\u0964 \u092B\u093C\u0940\u0932\u094D\u0921 \u091B\u094B\u0921\u093C\u0928\u0947 \u092A\u0930 \u0938\u0939\u0947\u091C\u093E \u091C\u093E\u0924\u093E \u0939\u0948\u0964 \u091B\u094B\u091F\u0947 \u0935\u093F\u0930\u093E\u092E \u092C\u094B\u0932\u0928\u0947 \u0915\u094B \u0916\u0902\u0921\u094B\u0902 \u092E\u0947\u0902 \u092C\u093E\u0901\u091F \u0938\u0915\u0924\u0947 \u0939\u0948\u0902; \u092A\u0939\u091A\u093E\u0928 \u092E\u0947\u0902 \u0905\u0924\u093F\u0930\u093F\u0915\u094D\u0924 \u0938\u092E\u092F \u0932\u0917\u0924\u093E \u0939\u0948\u0964",
|
|
1984
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "\u0915\u0938\u094D\u091F\u092E \u0935\u093F\u0930\u093E\u092E (\u092E\u093F\u0932\u0940\u0938\u0947\u0915\u0902\u0921)",
|
|
1990
1985
|
"dsh-live-voice.recognition.silenceDetection.duration": "\u092D\u0947\u091C\u0928\u0947 \u0938\u0947 \u092A\u0939\u0932\u0947 \u0915\u093E \u0935\u093F\u0930\u093E\u092E: {milliseconds} \u092E\u093F\u0932\u0940\u0938\u0947\u0915\u0902\u0921",
|
|
1991
1986
|
"dsh-live-voice.recognition.silenceDetection.help": "\u0924\u092F \u0915\u0930\u0924\u093E \u0939\u0948 \u0915\u093F \u0930\u093F\u0915\u0949\u0930\u094D\u0921 \u0915\u0940 \u0917\u0908 \u0906\u0935\u093E\u091C\u093C \u0915\u094B \u092A\u0939\u091A\u093E\u0928 \u0915\u0947 \u0932\u093F\u090F \u092D\u0947\u091C\u0928\u0947 \u0938\u0947 \u092A\u0939\u0932\u0947 \u0935\u093F\u0930\u093E\u092E \u0915\u093F\u0924\u0928\u0940 \u0926\u0947\u0930 \u0930\u0939\u0928\u093E \u091A\u093E\u0939\u093F\u090F\u0964",
|
|
1992
1987
|
"dsh-live-voice.recognition.silenceDetection.label": "\u092E\u094C\u0928 \u0915\u093E \u092A\u0924\u093E \u0932\u0917\u093E\u0928\u093E",
|
|
@@ -2024,6 +2019,8 @@ var hi = {
|
|
|
2024
2019
|
"dsh-live-voice.settings.engine.refresh": "\u0909\u092A\u0932\u092C\u094D\u0927 \u0907\u0902\u091C\u0928 \u0930\u0940\u092B\u093C\u094D\u0930\u0947\u0936 \u0915\u0930\u0947\u0902",
|
|
2025
2020
|
"dsh-live-voice.settings.filters.title": "\u092B\u093C\u093F\u0932\u094D\u091F\u0930\u093F\u0902\u0917",
|
|
2026
2021
|
"dsh-live-voice.settings.general.title": "\u0938\u093E\u092E\u093E\u0928\u094D\u092F",
|
|
2022
|
+
"dsh-live-voice.settings.persistence.loadError": "\u0938\u0930\u094D\u0935\u0930 \u0938\u0947 Live Voice \u0938\u0947\u091F\u093F\u0902\u0917 \u0932\u094B\u0921 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u0940\u0902\u0964 \u092B\u093F\u0930 \u092A\u094D\u0930\u092F\u093E\u0938 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u092A\u0947\u091C \u0930\u0940\u0932\u094B\u0921 \u0915\u0930\u0947\u0902\u0964",
|
|
2023
|
+
"dsh-live-voice.settings.persistence.saveError": "\u0938\u0930\u094D\u0935\u0930 \u092A\u0930 Live Voice \u0938\u0947\u091F\u093F\u0902\u0917 \u0938\u0947\u0935 \u0928\u0939\u0940\u0902 \u0939\u094B \u0938\u0915\u0940\u0902\u0964 \u092B\u093F\u0930 \u092A\u094D\u0930\u092F\u093E\u0938 \u0915\u0930\u0947\u0902\u0964",
|
|
2027
2024
|
"dsh-live-voice.settings.tabs.conversation": "\u092C\u093E\u0924\u091A\u0940\u0924",
|
|
2028
2025
|
"dsh-live-voice.settings.tabs.recognition": "\u0935\u093E\u0915\u094D \u092A\u0939\u091A\u093E\u0928",
|
|
2029
2026
|
"dsh-live-voice.settings.tabs.speak": "\u0935\u093E\u091A\u0928",
|
|
@@ -2192,6 +2189,8 @@ var ptBR = {
|
|
|
2192
2189
|
"dsh-live-voice.recognition.planned.vote": "Em breve \u2014 vote nas issues do reposit\xF3rio",
|
|
2193
2190
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 Em breve",
|
|
2194
2191
|
"dsh-live-voice.recognition.planned.webGpu": "Infer\xEAncia WebGPU no navegador \u2014 Em breve",
|
|
2192
|
+
"dsh-live-voice.recognition.presets.custom.description": "Escolha a dura\xE7\xE3o do sil\xEAncio.",
|
|
2193
|
+
"dsh-live-voice.recognition.presets.custom.label": "Personalizada",
|
|
2195
2194
|
"dsh-live-voice.recognition.presets.long.description": "Aguarda durante pausas de pensamento mais longas.",
|
|
2196
2195
|
"dsh-live-voice.recognition.presets.long.label": "Longa",
|
|
2197
2196
|
"dsh-live-voice.recognition.presets.natural.description": "Permite pausas normais entre frases.",
|
|
@@ -2204,6 +2203,8 @@ var ptBR = {
|
|
|
2204
2203
|
"dsh-live-voice.recognition.qwen.endpointHelp": "API HTTP na URL configurada do host DSH (padr\xE3o: http://127.0.0.1:8080/inference). O \xE1udio usa a rota de transcri\xE7\xE3o autenticada do host DSH.",
|
|
2205
2204
|
"dsh-live-voice.recognition.qwen.hostHelp": "Configura\xE7\xF5es de todo o host para o servidor Qwen3 ASR + TTS. Insira qualquer URL base HTTP ou HTTPS acess\xEDvel a partir do host DSH. O navegador acessa atrav\xE9s de rotas autenticadas do DSH.",
|
|
2206
2205
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 API HTTP",
|
|
2206
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "Insira um n\xFAmero inteiro de 100 a 10.000 ms. Salvo ao sair do campo. Pausas curtas podem dividir a fala; o reconhecimento acrescenta sua pr\xF3pria lat\xEAncia.",
|
|
2207
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "Pausa personalizada (milissegundos)",
|
|
2207
2208
|
"dsh-live-voice.recognition.silenceDetection.duration": "Pausa antes de enviar: {milliseconds} ms",
|
|
2208
2209
|
"dsh-live-voice.recognition.silenceDetection.help": "Controla a dura\xE7\xE3o m\xEDnima da pausa antes de enviar a fala capturada para reconhecimento.",
|
|
2209
2210
|
"dsh-live-voice.recognition.silenceDetection.label": "Detec\xE7\xE3o de sil\xEAncio",
|
|
@@ -2241,6 +2242,8 @@ var ptBR = {
|
|
|
2241
2242
|
"dsh-live-voice.settings.engine.refresh": "Atualizar mecanismos dispon\xEDveis",
|
|
2242
2243
|
"dsh-live-voice.settings.filters.title": "Filtragem",
|
|
2243
2244
|
"dsh-live-voice.settings.general.title": "Gerais",
|
|
2245
|
+
"dsh-live-voice.settings.persistence.loadError": "N\xE3o foi poss\xEDvel carregar as configura\xE7\xF5es do Live Voice do servidor. Recarregue para tentar novamente.",
|
|
2246
|
+
"dsh-live-voice.settings.persistence.saveError": "N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es do Live Voice no servidor. Tente novamente.",
|
|
2244
2247
|
"dsh-live-voice.settings.tabs.conversation": "Conversa",
|
|
2245
2248
|
"dsh-live-voice.settings.tabs.recognition": "Reconhecimento de voz",
|
|
2246
2249
|
"dsh-live-voice.settings.tabs.speak": "Fala",
|
|
@@ -2409,6 +2412,8 @@ var zh = {
|
|
|
2409
2412
|
"dsh-live-voice.recognition.planned.vote": "\u5373\u5C06\u63A8\u51FA \u2014 \u8BF7\u5728\u4ED3\u5E93\u8BAE\u9898\u4E2D\u6295\u7968",
|
|
2410
2413
|
"dsh-live-voice.recognition.planned.voxtral": "Voxtral Realtime \u2014 \u5373\u5C06\u63A8\u51FA",
|
|
2411
2414
|
"dsh-live-voice.recognition.planned.webGpu": "\u6D4F\u89C8\u5668 WebGPU \u63A8\u7406 \u2014 \u5373\u5C06\u63A8\u51FA",
|
|
2415
|
+
"dsh-live-voice.recognition.presets.custom.description": "\u81EA\u884C\u9009\u62E9\u9759\u97F3\u65F6\u957F\u3002",
|
|
2416
|
+
"dsh-live-voice.recognition.presets.custom.label": "\u81EA\u5B9A\u4E49",
|
|
2412
2417
|
"dsh-live-voice.recognition.presets.long.description": "\u7B49\u5F85\u8F83\u957F\u7684\u601D\u8003\u505C\u987F\u3002",
|
|
2413
2418
|
"dsh-live-voice.recognition.presets.long.label": "\u957F",
|
|
2414
2419
|
"dsh-live-voice.recognition.presets.natural.description": "\u5141\u8BB8\u77ED\u8BED\u4E4B\u95F4\u51FA\u73B0\u81EA\u7136\u505C\u987F\u3002",
|
|
@@ -2421,6 +2426,8 @@ var zh = {
|
|
|
2421
2426
|
"dsh-live-voice.recognition.qwen.endpointHelp": "HTTP API \u4F4D\u4E8E\u914D\u7F6E\u7684 DSH \u4E3B\u673A URL\uFF08\u9ED8\u8BA4\uFF1Ahttp://127.0.0.1:8080/inference\uFF09\u3002\u97F3\u9891\u901A\u8FC7\u9700\u8981\u8EAB\u4EFD\u9A8C\u8BC1\u7684 DSH \u4E3B\u673A\u8F6C\u5199\u8DEF\u7531\u53D1\u9001\u3002",
|
|
2422
2427
|
"dsh-live-voice.recognition.qwen.hostHelp": "Qwen3 ASR + TTS \u670D\u52A1\u5668\u7684\u4E3B\u673A\u7EA7\u8BBE\u7F6E\u3002\u8BF7\u8F93\u5165 DSH \u4E3B\u673A\u53EF\u8BBF\u95EE\u7684\u4EFB\u610F HTTP \u6216 HTTPS \u57FA\u7840 URL\u3002\u6D4F\u89C8\u5668\u901A\u8FC7\u9700\u8981\u8EAB\u4EFD\u9A8C\u8BC1\u7684 DSH \u8DEF\u7531\u8BBF\u95EE\u8BE5\u670D\u52A1\u5668\u3002",
|
|
2423
2428
|
"dsh-live-voice.recognition.qwen.label": "Qwen3 ASR \u2014 HTTP API",
|
|
2429
|
+
"dsh-live-voice.recognition.silenceDetection.customHelp": "\u8BF7\u8F93\u5165 100 \u81F3 10,000 ms \u7684\u6574\u6570\u3002\u79BB\u5F00\u8F93\u5165\u6846\u65F6\u4FDD\u5B58\u3002\u77ED\u505C\u987F\u53EF\u80FD\u5207\u5206\u8BED\u97F3\uFF1B\u8BC6\u522B\u8FD8\u9700\u8981\u989D\u5916\u65F6\u95F4\u3002",
|
|
2430
|
+
"dsh-live-voice.recognition.silenceDetection.customLabel": "\u81EA\u5B9A\u4E49\u505C\u987F\uFF08\u6BEB\u79D2\uFF09",
|
|
2424
2431
|
"dsh-live-voice.recognition.silenceDetection.duration": "\u53D1\u9001\u524D\u505C\u987F\uFF1A{milliseconds} \u6BEB\u79D2",
|
|
2425
2432
|
"dsh-live-voice.recognition.silenceDetection.help": "\u8BBE\u7F6E\u505C\u987F\u6301\u7EED\u591A\u4E45\u540E\uFF0C\u5C06\u91C7\u96C6\u7684\u8BED\u97F3\u53D1\u9001\u8FDB\u884C\u8BC6\u522B\u3002",
|
|
2426
2433
|
"dsh-live-voice.recognition.silenceDetection.label": "\u9759\u97F3\u68C0\u6D4B",
|
|
@@ -2458,6 +2465,8 @@ var zh = {
|
|
|
2458
2465
|
"dsh-live-voice.settings.engine.refresh": "\u5237\u65B0\u53EF\u7528\u5F15\u64CE",
|
|
2459
2466
|
"dsh-live-voice.settings.filters.title": "\u8FC7\u6EE4",
|
|
2460
2467
|
"dsh-live-voice.settings.general.title": "\u5E38\u89C4",
|
|
2468
|
+
"dsh-live-voice.settings.persistence.loadError": "\u65E0\u6CD5\u4ECE\u670D\u52A1\u5668\u52A0\u8F7D Live Voice \u8BBE\u7F6E\u3002\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002",
|
|
2469
|
+
"dsh-live-voice.settings.persistence.saveError": "\u65E0\u6CD5\u5728\u670D\u52A1\u5668\u4E0A\u4FDD\u5B58 Live Voice \u8BBE\u7F6E\u3002\u8BF7\u91CD\u8BD5\u3002",
|
|
2461
2470
|
"dsh-live-voice.settings.tabs.conversation": "\u5BF9\u8BDD",
|
|
2462
2471
|
"dsh-live-voice.settings.tabs.recognition": "\u8BED\u97F3\u8BC6\u522B",
|
|
2463
2472
|
"dsh-live-voice.settings.tabs.speak": "\u8BED\u97F3\u5408\u6210",
|
|
@@ -2831,43 +2840,99 @@ function CheckboxField({
|
|
|
2831
2840
|
}
|
|
2832
2841
|
|
|
2833
2842
|
// src/shared/design-system/forms/NumberField.tsx
|
|
2843
|
+
var import_react12 = __toESM(require("react"), 1);
|
|
2844
|
+
|
|
2845
|
+
// src/shared/design-system/forms/DraftField.tsx
|
|
2834
2846
|
var import_react11 = __toESM(require("react"), 1);
|
|
2835
|
-
function
|
|
2836
|
-
|
|
2847
|
+
function DraftField({
|
|
2848
|
+
label,
|
|
2849
|
+
value,
|
|
2850
|
+
defaultValue,
|
|
2851
|
+
onCommit,
|
|
2852
|
+
multiline,
|
|
2853
|
+
onInput,
|
|
2854
|
+
onChange,
|
|
2855
|
+
onFocus,
|
|
2856
|
+
onBlur,
|
|
2857
|
+
...props
|
|
2858
|
+
}) {
|
|
2859
|
+
const saved = String(value ?? defaultValue ?? "");
|
|
2860
|
+
const [draft, setDraft] = import_react11.default.useState(saved);
|
|
2861
|
+
const editing = import_react11.default.useRef(false);
|
|
2862
|
+
const dirty = import_react11.default.useRef(false);
|
|
2863
|
+
import_react11.default.useEffect(() => {
|
|
2864
|
+
if (!editing.current) setDraft(saved);
|
|
2865
|
+
}, [saved]);
|
|
2866
|
+
const inputProps = {
|
|
2867
|
+
...props,
|
|
2868
|
+
value: draft,
|
|
2869
|
+
onFocus: (event) => {
|
|
2870
|
+
editing.current = true;
|
|
2871
|
+
onFocus?.(event);
|
|
2872
|
+
},
|
|
2873
|
+
onInput: (event) => {
|
|
2874
|
+
editing.current = true;
|
|
2875
|
+
dirty.current = true;
|
|
2876
|
+
setDraft(event.currentTarget.value);
|
|
2877
|
+
onInput?.(event);
|
|
2878
|
+
},
|
|
2879
|
+
onChange: (event) => {
|
|
2880
|
+
editing.current = true;
|
|
2881
|
+
dirty.current = true;
|
|
2882
|
+
setDraft(event.currentTarget.value);
|
|
2883
|
+
onChange?.(event);
|
|
2884
|
+
},
|
|
2885
|
+
onBlur: (event) => {
|
|
2886
|
+
editing.current = false;
|
|
2887
|
+
const raw = event.currentTarget.value;
|
|
2888
|
+
const changed = dirty.current;
|
|
2889
|
+
dirty.current = false;
|
|
2890
|
+
setDraft(saved);
|
|
2891
|
+
const valid = multiline || props.type !== "number" || raw.trim() !== "" && event.currentTarget.validity.valid;
|
|
2892
|
+
if (changed && valid) onCommit?.(raw);
|
|
2893
|
+
onBlur?.(event);
|
|
2894
|
+
}
|
|
2895
|
+
};
|
|
2896
|
+
return /* @__PURE__ */ import_react11.default.createElement("label", null, label, multiline ? /* @__PURE__ */ import_react11.default.createElement("textarea", { ...inputProps }) : /* @__PURE__ */ import_react11.default.createElement("input", { ...inputProps }));
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/shared/design-system/forms/NumberField.tsx
|
|
2900
|
+
function NumberField(props) {
|
|
2901
|
+
return /* @__PURE__ */ import_react12.default.createElement(DraftField, { ...props, type: "number" });
|
|
2837
2902
|
}
|
|
2838
2903
|
|
|
2839
2904
|
// src/shared/design-system/forms/SelectField.tsx
|
|
2840
|
-
var
|
|
2905
|
+
var import_react13 = __toESM(require("react"), 1);
|
|
2841
2906
|
function SelectField({ label, options, ...props }) {
|
|
2842
|
-
return /* @__PURE__ */
|
|
2907
|
+
return /* @__PURE__ */ import_react13.default.createElement("label", null, label, /* @__PURE__ */ import_react13.default.createElement("select", { ...props }, options.map((option) => /* @__PURE__ */ import_react13.default.createElement("option", { key: option.value, value: option.value, disabled: option.disabled }, option.label))));
|
|
2843
2908
|
}
|
|
2844
2909
|
|
|
2845
2910
|
// src/shared/design-system/forms/TextAreaField.tsx
|
|
2846
|
-
var
|
|
2847
|
-
function TextAreaField(
|
|
2848
|
-
return /* @__PURE__ */
|
|
2911
|
+
var import_react14 = __toESM(require("react"), 1);
|
|
2912
|
+
function TextAreaField(props) {
|
|
2913
|
+
return /* @__PURE__ */ import_react14.default.createElement(DraftField, { ...props, multiline: true });
|
|
2849
2914
|
}
|
|
2850
2915
|
|
|
2851
2916
|
// src/shared/design-system/forms/TextField.tsx
|
|
2852
|
-
var
|
|
2853
|
-
function TextField(
|
|
2854
|
-
return /* @__PURE__ */
|
|
2917
|
+
var import_react15 = __toESM(require("react"), 1);
|
|
2918
|
+
function TextField(props) {
|
|
2919
|
+
return /* @__PURE__ */ import_react15.default.createElement(DraftField, { ...props, type: "text" });
|
|
2855
2920
|
}
|
|
2856
2921
|
|
|
2857
2922
|
// src/shared/design-system/layout/SettingsCard.tsx
|
|
2858
|
-
var
|
|
2923
|
+
var import_react16 = __toESM(require("react"), 1);
|
|
2859
2924
|
function SettingsCard({
|
|
2860
2925
|
children,
|
|
2861
2926
|
className = ""
|
|
2862
2927
|
}) {
|
|
2863
|
-
return /* @__PURE__ */
|
|
2928
|
+
return /* @__PURE__ */ import_react16.default.createElement("div", { className: ["dlv-settings-card", className].filter(Boolean).join(" ") }, children);
|
|
2864
2929
|
}
|
|
2865
2930
|
|
|
2866
2931
|
// src/shared/design-system/layout/SettingsSection.tsx
|
|
2867
|
-
var
|
|
2932
|
+
var import_react17 = __toESM(require("react"), 1);
|
|
2868
2933
|
|
|
2869
2934
|
// src/shared/design-system/layout/SettingsSubcard.tsx
|
|
2870
|
-
var
|
|
2935
|
+
var import_react18 = __toESM(require("react"), 1);
|
|
2871
2936
|
function SettingsSubcard({
|
|
2872
2937
|
title,
|
|
2873
2938
|
children,
|
|
@@ -2875,13 +2940,13 @@ function SettingsSubcard({
|
|
|
2875
2940
|
ariaLabel,
|
|
2876
2941
|
icon = "settings"
|
|
2877
2942
|
}) {
|
|
2878
|
-
return /* @__PURE__ */
|
|
2943
|
+
return /* @__PURE__ */ import_react18.default.createElement("details", { className: "dlv-settings-subcard", open }, /* @__PURE__ */ import_react18.default.createElement("summary", { "aria-label": ariaLabel }, /* @__PURE__ */ import_react18.default.createElement(Icon, { name: icon, className: "dlv-settings-subcard-icon" }), /* @__PURE__ */ import_react18.default.createElement("span", null, title), /* @__PURE__ */ import_react18.default.createElement(Icon, { name: "chevron", className: "dlv-settings-subcard-chevron" })), /* @__PURE__ */ import_react18.default.createElement("div", { className: "dlv-settings-subcard-body" }, children));
|
|
2879
2944
|
}
|
|
2880
2945
|
|
|
2881
2946
|
// src/shared/design-system/layout/SettingsTabs.tsx
|
|
2882
|
-
var
|
|
2947
|
+
var import_react19 = __toESM(require("react"), 1);
|
|
2883
2948
|
function SettingsTabs({ label, tabs, active, idPrefix, onChange }) {
|
|
2884
|
-
const refs =
|
|
2949
|
+
const refs = import_react19.default.useRef([]);
|
|
2885
2950
|
const activate = (index) => {
|
|
2886
2951
|
const tab = tabs[index];
|
|
2887
2952
|
if (!tab) return;
|
|
@@ -2898,7 +2963,7 @@ function SettingsTabs({ label, tabs, active, idPrefix, onChange }) {
|
|
|
2898
2963
|
event.preventDefault();
|
|
2899
2964
|
activate(next);
|
|
2900
2965
|
};
|
|
2901
|
-
return /* @__PURE__ */
|
|
2966
|
+
return /* @__PURE__ */ import_react19.default.createElement("div", { className: "dlv-settings-tabs", role: "tablist", "aria-label": label }, tabs.map((tab, index) => /* @__PURE__ */ import_react19.default.createElement(
|
|
2902
2967
|
"button",
|
|
2903
2968
|
{
|
|
2904
2969
|
key: tab.id,
|
|
@@ -2916,8 +2981,8 @@ function SettingsTabs({ label, tabs, active, idPrefix, onChange }) {
|
|
|
2916
2981
|
onClick: () => onChange(tab.id),
|
|
2917
2982
|
onKeyDown: (event) => onKeyDown(event, index)
|
|
2918
2983
|
},
|
|
2919
|
-
tab.icon ? /* @__PURE__ */
|
|
2920
|
-
/* @__PURE__ */
|
|
2984
|
+
tab.icon ? /* @__PURE__ */ import_react19.default.createElement(Icon, { name: tab.icon, className: "dlv-settings-tab-icon" }) : null,
|
|
2985
|
+
/* @__PURE__ */ import_react19.default.createElement("span", null, tab.label)
|
|
2921
2986
|
)));
|
|
2922
2987
|
}
|
|
2923
2988
|
|
|
@@ -3277,7 +3342,8 @@ var WhisperHttpRecognitionEngine = class {
|
|
|
3277
3342
|
constructor({
|
|
3278
3343
|
globals = globalThis,
|
|
3279
3344
|
meter,
|
|
3280
|
-
voiceDetectionPreset =
|
|
3345
|
+
voiceDetectionPreset = defaultSettings.voiceDetectionPreset,
|
|
3346
|
+
voiceDetectionCustomSilenceMs = defaultSettings.voiceDetectionCustomSilenceMs,
|
|
3281
3347
|
maxUtteranceSeconds = 60
|
|
3282
3348
|
} = {}) {
|
|
3283
3349
|
this.g = globals;
|
|
@@ -3285,11 +3351,12 @@ var WhisperHttpRecognitionEngine = class {
|
|
|
3285
3351
|
this.session = null;
|
|
3286
3352
|
this.lang = "pt-BR";
|
|
3287
3353
|
this.voiceDetectionPreset = voiceDetectionPreset;
|
|
3354
|
+
this.voiceDetectionCustomSilenceMs = voiceDetectionCustomSilenceMs;
|
|
3288
3355
|
this.maxUtteranceSeconds = maxUtteranceSeconds;
|
|
3289
3356
|
this.route = ROUTE;
|
|
3290
3357
|
}
|
|
3291
3358
|
get segmentation() {
|
|
3292
|
-
return
|
|
3359
|
+
return { silenceMs: voiceDetectionSilenceMs(this) };
|
|
3293
3360
|
}
|
|
3294
3361
|
async capability() {
|
|
3295
3362
|
try {
|
|
@@ -3787,16 +3854,16 @@ var QwenHttpSpeakingEngine = class extends HostAudioSpeakingEngine {
|
|
|
3787
3854
|
};
|
|
3788
3855
|
|
|
3789
3856
|
// src/modules/settings/components/createLiveVoiceSettings.tsx
|
|
3790
|
-
var
|
|
3857
|
+
var import_react46 = __toESM(require("react"), 1);
|
|
3791
3858
|
|
|
3792
3859
|
// src/modules/settings/components/LiveVoiceSettings.tsx
|
|
3793
|
-
var
|
|
3860
|
+
var import_react45 = __toESM(require("react"), 1);
|
|
3794
3861
|
|
|
3795
3862
|
// src/modules/settings/hooks/useAudioDevices.ts
|
|
3796
|
-
var
|
|
3863
|
+
var import_react21 = __toESM(require("react"), 1);
|
|
3797
3864
|
function useAudioDevices() {
|
|
3798
|
-
const [audioDevices, setAudioDevices] =
|
|
3799
|
-
|
|
3865
|
+
const [audioDevices, setAudioDevices] = import_react21.default.useState([]);
|
|
3866
|
+
import_react21.default.useEffect(() => {
|
|
3800
3867
|
let active = true;
|
|
3801
3868
|
const refresh = async () => {
|
|
3802
3869
|
try {
|
|
@@ -3826,17 +3893,17 @@ function createDeviceOptions(devices, kind, systemDefault, fallback) {
|
|
|
3826
3893
|
}
|
|
3827
3894
|
|
|
3828
3895
|
// src/modules/conversation/hooks/useConversationActions.ts
|
|
3829
|
-
var
|
|
3896
|
+
var import_react22 = __toESM(require("react"), 1);
|
|
3830
3897
|
function useConversationActions(controller) {
|
|
3831
|
-
const [error, setError] =
|
|
3832
|
-
const alive =
|
|
3833
|
-
|
|
3898
|
+
const [error, setError] = import_react22.default.useState("");
|
|
3899
|
+
const alive = import_react22.default.useRef(true);
|
|
3900
|
+
import_react22.default.useEffect(
|
|
3834
3901
|
() => () => {
|
|
3835
3902
|
alive.current = false;
|
|
3836
3903
|
},
|
|
3837
3904
|
[]
|
|
3838
3905
|
);
|
|
3839
|
-
const invoke =
|
|
3906
|
+
const invoke = import_react22.default.useCallback(
|
|
3840
3907
|
(name, ...args) => {
|
|
3841
3908
|
setError("");
|
|
3842
3909
|
try {
|
|
@@ -3849,26 +3916,26 @@ function useConversationActions(controller) {
|
|
|
3849
3916
|
},
|
|
3850
3917
|
[controller]
|
|
3851
3918
|
);
|
|
3852
|
-
const clearError =
|
|
3919
|
+
const clearError = import_react22.default.useCallback(() => setError(""), []);
|
|
3853
3920
|
return { invoke, error, clearError };
|
|
3854
3921
|
}
|
|
3855
3922
|
|
|
3856
3923
|
// src/modules/conversation/hooks/useConversationController.ts
|
|
3857
|
-
var
|
|
3924
|
+
var import_react23 = __toESM(require("react"), 1);
|
|
3858
3925
|
function useConversationController(controller) {
|
|
3859
|
-
const subscribe =
|
|
3926
|
+
const subscribe = import_react23.default.useCallback(
|
|
3860
3927
|
(listener) => controller.subscribe(listener),
|
|
3861
3928
|
[controller]
|
|
3862
3929
|
);
|
|
3863
|
-
const read =
|
|
3864
|
-
return
|
|
3930
|
+
const read = import_react23.default.useCallback(() => controller.getSnapshot(), [controller]);
|
|
3931
|
+
return import_react23.default.useSyncExternalStore(subscribe, read, read);
|
|
3865
3932
|
}
|
|
3866
3933
|
|
|
3867
3934
|
// src/modules/settings/hooks/useReleaseStatus.ts
|
|
3868
|
-
var
|
|
3935
|
+
var import_react24 = __toESM(require("react"), 1);
|
|
3869
3936
|
|
|
3870
3937
|
// src/modules/settings/services/releases.ts
|
|
3871
|
-
var CURRENT_VERSION = "0.3.
|
|
3938
|
+
var CURRENT_VERSION = "0.3.2";
|
|
3872
3939
|
var TESTED_DSH_VERSION = "0.2.0-rc.2";
|
|
3873
3940
|
var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
|
|
3874
3941
|
var RELEASES_URL = `${REPOSITORY_URL}/releases`;
|
|
@@ -3929,9 +3996,16 @@ function writeCached(storage, value) {
|
|
|
3929
3996
|
} catch {
|
|
3930
3997
|
}
|
|
3931
3998
|
}
|
|
3999
|
+
var releaseMemory = /* @__PURE__ */ new Map();
|
|
4000
|
+
var memoryStorage = {
|
|
4001
|
+
getItem: (key) => releaseMemory.get(key) ?? null,
|
|
4002
|
+
setItem: (key, value) => {
|
|
4003
|
+
releaseMemory.set(key, value);
|
|
4004
|
+
}
|
|
4005
|
+
};
|
|
3932
4006
|
async function checkLatestRelease({
|
|
3933
4007
|
fetchImpl = globalThis.window?.fetch?.bind(globalThis.window),
|
|
3934
|
-
storage =
|
|
4008
|
+
storage = memoryStorage,
|
|
3935
4009
|
now = Date.now()
|
|
3936
4010
|
} = {}) {
|
|
3937
4011
|
const cached = readCached(storage, now);
|
|
@@ -3963,10 +4037,10 @@ function hasNewerRelease(release, currentVersion = CURRENT_VERSION) {
|
|
|
3963
4037
|
|
|
3964
4038
|
// src/modules/settings/hooks/useReleaseStatus.ts
|
|
3965
4039
|
function useReleaseStatus() {
|
|
3966
|
-
const [latestRelease, setLatestRelease] =
|
|
4040
|
+
const [latestRelease, setLatestRelease] = import_react24.default.useState(
|
|
3967
4041
|
null
|
|
3968
4042
|
);
|
|
3969
|
-
|
|
4043
|
+
import_react24.default.useEffect(() => {
|
|
3970
4044
|
let active = true;
|
|
3971
4045
|
void checkLatestRelease().then((result) => {
|
|
3972
4046
|
if (active) setLatestRelease(result.release);
|
|
@@ -3979,10 +4053,10 @@ function useReleaseStatus() {
|
|
|
3979
4053
|
}
|
|
3980
4054
|
|
|
3981
4055
|
// src/modules/settings/sections/conversation/ConversationSettingsSection.tsx
|
|
3982
|
-
var
|
|
4056
|
+
var import_react30 = __toESM(require("react"), 1);
|
|
3983
4057
|
|
|
3984
4058
|
// src/modules/settings/sections/conversation/DeliverySettings.tsx
|
|
3985
|
-
var
|
|
4059
|
+
var import_react25 = __toESM(require("react"), 1);
|
|
3986
4060
|
function DeliverySettings({
|
|
3987
4061
|
settings,
|
|
3988
4062
|
updateSettings
|
|
@@ -3990,7 +4064,7 @@ function DeliverySettings({
|
|
|
3990
4064
|
const { scoped } = useLanguage((ctx) => ctx.settings);
|
|
3991
4065
|
const mode = settings.sendingMode || "manual";
|
|
3992
4066
|
const seconds = [2, 3, 4, 5, 6, 8, 10];
|
|
3993
|
-
return /* @__PURE__ */
|
|
4067
|
+
return /* @__PURE__ */ import_react25.default.createElement(import_react25.default.Fragment, null, /* @__PURE__ */ import_react25.default.createElement(
|
|
3994
4068
|
SelectField,
|
|
3995
4069
|
{
|
|
3996
4070
|
label: scoped.delivery.label(),
|
|
@@ -4002,7 +4076,7 @@ function DeliverySettings({
|
|
|
4002
4076
|
],
|
|
4003
4077
|
onChange: (event) => updateSettings({ sendingMode: event.target.value })
|
|
4004
4078
|
}
|
|
4005
|
-
), mode !== "manual" ? /* @__PURE__ */
|
|
4079
|
+
), mode !== "manual" ? /* @__PURE__ */ import_react25.default.createElement(
|
|
4006
4080
|
SelectField,
|
|
4007
4081
|
{
|
|
4008
4082
|
label: scoped.autoSend.delay(),
|
|
@@ -4010,17 +4084,17 @@ function DeliverySettings({
|
|
|
4010
4084
|
options: seconds.map((value) => ({ value: String(value), label: String(value) })),
|
|
4011
4085
|
onChange: (event) => updateSettings({ autoSendDelaySeconds: Number(event.target.value) })
|
|
4012
4086
|
}
|
|
4013
|
-
) : /* @__PURE__ */
|
|
4087
|
+
) : /* @__PURE__ */ import_react25.default.createElement("p", { className: "dlv-setting-description" }, scoped.delivery.manualLabel()));
|
|
4014
4088
|
}
|
|
4015
4089
|
|
|
4016
4090
|
// src/modules/settings/sections/conversation/HoldToTalkSettings.tsx
|
|
4017
|
-
var
|
|
4091
|
+
var import_react26 = __toESM(require("react"), 1);
|
|
4018
4092
|
function HoldToTalkSettings({
|
|
4019
4093
|
settings,
|
|
4020
4094
|
updateSettings
|
|
4021
4095
|
}) {
|
|
4022
4096
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4023
|
-
return /* @__PURE__ */
|
|
4097
|
+
return /* @__PURE__ */ import_react26.default.createElement(
|
|
4024
4098
|
CheckboxField,
|
|
4025
4099
|
{
|
|
4026
4100
|
label: recognition.holdToTalk.enabled(),
|
|
@@ -4032,14 +4106,14 @@ function HoldToTalkSettings({
|
|
|
4032
4106
|
}
|
|
4033
4107
|
|
|
4034
4108
|
// src/modules/settings/sections/conversation/VoiceModeSettings.tsx
|
|
4035
|
-
var
|
|
4109
|
+
var import_react27 = __toESM(require("react"), 1);
|
|
4036
4110
|
function VoiceModeSettings({
|
|
4037
4111
|
settings,
|
|
4038
4112
|
updateSettings
|
|
4039
4113
|
}) {
|
|
4040
4114
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4041
4115
|
const mode = settings.mode || "speaker";
|
|
4042
|
-
return /* @__PURE__ */
|
|
4116
|
+
return /* @__PURE__ */ import_react27.default.createElement(import_react27.default.Fragment, null, /* @__PURE__ */ import_react27.default.createElement(
|
|
4043
4117
|
SelectField,
|
|
4044
4118
|
{
|
|
4045
4119
|
label: recognition.mode.label(),
|
|
@@ -4050,16 +4124,16 @@ function VoiceModeSettings({
|
|
|
4050
4124
|
],
|
|
4051
4125
|
onChange: (event) => updateSettings({ mode: event.target.value })
|
|
4052
4126
|
}
|
|
4053
|
-
), /* @__PURE__ */
|
|
4127
|
+
), /* @__PURE__ */ import_react27.default.createElement("p", { className: "dlv-setting-description" }, mode === "headphones" ? recognition.headphoneMode.help() : recognition.speakerMode.help()));
|
|
4054
4128
|
}
|
|
4055
4129
|
|
|
4056
4130
|
// src/modules/settings/sections/conversation/ConversationDelaySettings.tsx
|
|
4057
|
-
var
|
|
4131
|
+
var import_react28 = __toESM(require("react"), 1);
|
|
4058
4132
|
var DELAYS = [1, 2, 3, 4, 5, 6, 8, 10];
|
|
4059
4133
|
function ConversationDelaySettings({ settings, updateSettings }) {
|
|
4060
4134
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4061
4135
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4062
|
-
return /* @__PURE__ */
|
|
4136
|
+
return /* @__PURE__ */ import_react28.default.createElement(import_react28.default.Fragment, null, /* @__PURE__ */ import_react28.default.createElement(
|
|
4063
4137
|
SelectField,
|
|
4064
4138
|
{
|
|
4065
4139
|
label: speak.responseDelay.label(),
|
|
@@ -4070,41 +4144,41 @@ function ConversationDelaySettings({ settings, updateSettings }) {
|
|
|
4070
4144
|
})),
|
|
4071
4145
|
onChange: (event) => updateSettings({ assistantSpeechDelaySeconds: Number(event.target.value) })
|
|
4072
4146
|
}
|
|
4073
|
-
), /* @__PURE__ */
|
|
4147
|
+
), /* @__PURE__ */ import_react28.default.createElement("p", { className: "dlv-setting-description" }, speak.responseDelay.help()));
|
|
4074
4148
|
}
|
|
4075
4149
|
|
|
4076
4150
|
// src/modules/settings/sections/speak/PlaybackPolicySettings.tsx
|
|
4077
|
-
var
|
|
4151
|
+
var import_react29 = __toESM(require("react"), 1);
|
|
4078
4152
|
function PlaybackPolicySettings({ settings, updateSettings }) {
|
|
4079
4153
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4080
|
-
return /* @__PURE__ */
|
|
4154
|
+
return /* @__PURE__ */ import_react29.default.createElement(import_react29.default.Fragment, null, /* @__PURE__ */ import_react29.default.createElement(
|
|
4081
4155
|
CheckboxField,
|
|
4082
4156
|
{
|
|
4083
4157
|
label: speak.autoPlayback.label(),
|
|
4084
4158
|
checked: settings.announceAssistantMessages !== false,
|
|
4085
4159
|
onChange: (event) => updateSettings({ announceAssistantMessages: event.target.checked })
|
|
4086
4160
|
}
|
|
4087
|
-
), /* @__PURE__ */
|
|
4161
|
+
), /* @__PURE__ */ import_react29.default.createElement("p", null, speak.autoPlayback.help()), /* @__PURE__ */ import_react29.default.createElement(
|
|
4088
4162
|
CheckboxField,
|
|
4089
4163
|
{
|
|
4090
4164
|
label: speak.interruption.enabled(),
|
|
4091
4165
|
checked: Boolean(settings.interruptSpeechOnUserMessage),
|
|
4092
4166
|
onChange: (event) => updateSettings({ interruptSpeechOnUserMessage: event.target.checked })
|
|
4093
4167
|
}
|
|
4094
|
-
), /* @__PURE__ */
|
|
4168
|
+
), /* @__PURE__ */ import_react29.default.createElement("p", null, settings.interruptSpeechOnUserMessage ? speak.interruption.enabledHelp() : speak.interruption.disabledHelp()));
|
|
4095
4169
|
}
|
|
4096
4170
|
|
|
4097
4171
|
// src/modules/settings/sections/conversation/ConversationSettingsSection.tsx
|
|
4098
4172
|
function ConversationSettingsSection(props) {
|
|
4099
4173
|
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4100
|
-
return /* @__PURE__ */
|
|
4174
|
+
return /* @__PURE__ */ import_react30.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react30.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react30.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "send" }, /* @__PURE__ */ import_react30.default.createElement("div", { className: "dlv-conversation-fields" }, /* @__PURE__ */ import_react30.default.createElement(DeliverySettings, { ...props }), /* @__PURE__ */ import_react30.default.createElement(HoldToTalkSettings, { ...props }), /* @__PURE__ */ import_react30.default.createElement(VoiceModeSettings, { ...props }), /* @__PURE__ */ import_react30.default.createElement(PlaybackPolicySettings, { ...props }), /* @__PURE__ */ import_react30.default.createElement(ConversationDelaySettings, { ...props })))));
|
|
4101
4175
|
}
|
|
4102
4176
|
|
|
4103
4177
|
// src/modules/settings/sections/recognition/RecognitionEngineSettings.tsx
|
|
4104
|
-
var
|
|
4178
|
+
var import_react33 = __toESM(require("react"), 1);
|
|
4105
4179
|
|
|
4106
4180
|
// src/modules/core/qwen/QwenSettings.tsx
|
|
4107
|
-
var
|
|
4181
|
+
var import_react31 = __toESM(require("react"), 1);
|
|
4108
4182
|
var BASE = "/api/dsh-live-voice/qwen";
|
|
4109
4183
|
var UNLOADED = "dsh-live-voice.speak.qwen.restartRequired";
|
|
4110
4184
|
async function qwenSettingsRequest(path, { method = "GET", config, signal } = {}, fetchImpl = globalThis.fetch) {
|
|
@@ -4133,15 +4207,15 @@ function QwenSettings({ controller }) {
|
|
|
4133
4207
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4134
4208
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4135
4209
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4136
|
-
const [draft, setDraft] =
|
|
4210
|
+
const [draft, setDraft] = import_react31.default.useState({
|
|
4137
4211
|
baseUrl: "http://127.0.0.1:8080/",
|
|
4138
4212
|
timeoutMs: 3e5
|
|
4139
4213
|
});
|
|
4140
|
-
const [busy, setBusy] =
|
|
4141
|
-
const [loaded, setLoaded] =
|
|
4142
|
-
const [error, setError] =
|
|
4143
|
-
const [message2, setMessage] =
|
|
4144
|
-
const active =
|
|
4214
|
+
const [busy, setBusy] = import_react31.default.useState(true);
|
|
4215
|
+
const [loaded, setLoaded] = import_react31.default.useState(false);
|
|
4216
|
+
const [error, setError] = import_react31.default.useState("");
|
|
4217
|
+
const [message2, setMessage] = import_react31.default.useState("");
|
|
4218
|
+
const active = import_react31.default.useRef(null);
|
|
4145
4219
|
const translateStatus = (value) => ({
|
|
4146
4220
|
"dsh-live-voice.commons.connection.unsaved": commons.connection.unsaved(),
|
|
4147
4221
|
"dsh-live-voice.speak.qwen.saved": speak.qwen.saved(),
|
|
@@ -4151,7 +4225,7 @@ function QwenSettings({ controller }) {
|
|
|
4151
4225
|
"dsh-live-voice.speak.qwen.requestFailed": speak.qwen.requestFailed(),
|
|
4152
4226
|
"dsh-live-voice.speak.qwen.healthFailed": speak.qwen.healthFailed()
|
|
4153
4227
|
})[value] ?? value;
|
|
4154
|
-
async function run(action) {
|
|
4228
|
+
async function run(action, config = draft) {
|
|
4155
4229
|
active.current?.abort();
|
|
4156
4230
|
const abort = new AbortController();
|
|
4157
4231
|
active.current = abort;
|
|
@@ -4169,7 +4243,7 @@ function QwenSettings({ controller }) {
|
|
|
4169
4243
|
await controller.endConversation?.();
|
|
4170
4244
|
const value = await qwenSettingsRequest("/config", {
|
|
4171
4245
|
method: "PUT",
|
|
4172
|
-
config: { ...
|
|
4246
|
+
config: { ...config, timeoutMs: Number(config.timeoutMs) },
|
|
4173
4247
|
signal: abort.signal
|
|
4174
4248
|
});
|
|
4175
4249
|
if (!abort.signal.aborted) {
|
|
@@ -4180,7 +4254,7 @@ function QwenSettings({ controller }) {
|
|
|
4180
4254
|
} else {
|
|
4181
4255
|
const value = await qwenSettingsRequest("/test", {
|
|
4182
4256
|
method: "POST",
|
|
4183
|
-
config: { ...
|
|
4257
|
+
config: { ...config, timeoutMs: Number(config.timeoutMs) },
|
|
4184
4258
|
signal: abort.signal
|
|
4185
4259
|
});
|
|
4186
4260
|
if (!abort.signal.aborted) {
|
|
@@ -4195,30 +4269,33 @@ function QwenSettings({ controller }) {
|
|
|
4195
4269
|
if (!abort.signal.aborted) setBusy(false);
|
|
4196
4270
|
}
|
|
4197
4271
|
}
|
|
4198
|
-
|
|
4272
|
+
import_react31.default.useEffect(() => {
|
|
4199
4273
|
void run("load");
|
|
4200
4274
|
return () => active.current?.abort();
|
|
4201
4275
|
}, []);
|
|
4202
|
-
const field = (label, key, type = "text") =>
|
|
4203
|
-
"
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4276
|
+
const field = (label, key, type = "text") => {
|
|
4277
|
+
const Field = type === "number" ? NumberField : TextField;
|
|
4278
|
+
return /* @__PURE__ */ import_react31.default.createElement(
|
|
4279
|
+
Field,
|
|
4280
|
+
{
|
|
4281
|
+
label,
|
|
4282
|
+
value: draft[key],
|
|
4283
|
+
disabled: busy || !loaded,
|
|
4284
|
+
autoComplete: "off",
|
|
4285
|
+
...type === "number" ? { min: 1e3, max: 6e5, step: 1 } : {},
|
|
4286
|
+
onCommit: (value) => {
|
|
4287
|
+
const config = { ...draft, [key]: value };
|
|
4288
|
+
setDraft(config);
|
|
4289
|
+
void run("save", config);
|
|
4290
|
+
}
|
|
4214
4291
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
return /* @__PURE__ */
|
|
4292
|
+
);
|
|
4293
|
+
};
|
|
4294
|
+
return /* @__PURE__ */ import_react31.default.createElement(import_react31.default.Fragment, null, /* @__PURE__ */ import_react31.default.createElement("p", null, recognition.qwen.hostHelp()), field(speak.qwen.endpoint(), "baseUrl"), field(commons.connection.timeout(), "timeoutMs", "number"), /* @__PURE__ */ import_react31.default.createElement("div", { className: "dlv-settings-actions" }, /* @__PURE__ */ import_react31.default.createElement("button", { type: "button", disabled: busy || !loaded, onClick: () => run("save") }, speak.qwen.save()), /* @__PURE__ */ import_react31.default.createElement("button", { type: "button", disabled: busy || !loaded, onClick: () => run("test") }, speak.qwen.test()), /* @__PURE__ */ import_react31.default.createElement("button", { type: "button", disabled: busy, onClick: () => run("load") }, commons.connection.reload())), busy && /* @__PURE__ */ import_react31.default.createElement("p", { role: "status" }, commons.connection.contactingHost()), message2 && /* @__PURE__ */ import_react31.default.createElement("p", { role: "status" }, translateStatus(message2)), error && /* @__PURE__ */ import_react31.default.createElement("p", { role: "alert" }, translateStatus(error)));
|
|
4218
4295
|
}
|
|
4219
4296
|
|
|
4220
4297
|
// src/modules/recognition/engines/whisper/WhisperSettings.tsx
|
|
4221
|
-
var
|
|
4298
|
+
var import_react32 = __toESM(require("react"), 1);
|
|
4222
4299
|
var BASE2 = "/api/dsh-live-voice/whisper";
|
|
4223
4300
|
var UNLOADED2 = "dsh-live-voice.recognition.whisper.restartRequired";
|
|
4224
4301
|
async function whisperSettingsRequest(path, { method = "GET", config, signal } = {}, fetchImpl = globalThis.fetch) {
|
|
@@ -4247,16 +4324,16 @@ function WhisperSettings({ controller }) {
|
|
|
4247
4324
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4248
4325
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4249
4326
|
const { scoped: settings } = useLanguage((ctx) => ctx.settings);
|
|
4250
|
-
const [draft, setDraft] =
|
|
4327
|
+
const [draft, setDraft] = import_react32.default.useState({
|
|
4251
4328
|
url: "http://127.0.0.1:8080/inference",
|
|
4252
4329
|
healthUrl: "/health",
|
|
4253
4330
|
timeoutMs: 3e4
|
|
4254
4331
|
});
|
|
4255
|
-
const [busy, setBusy] =
|
|
4256
|
-
const [loaded, setLoaded] =
|
|
4257
|
-
const [error, setError] =
|
|
4258
|
-
const [message2, setMessage] =
|
|
4259
|
-
const active =
|
|
4332
|
+
const [busy, setBusy] = import_react32.default.useState(true);
|
|
4333
|
+
const [loaded, setLoaded] = import_react32.default.useState(false);
|
|
4334
|
+
const [error, setError] = import_react32.default.useState("");
|
|
4335
|
+
const [message2, setMessage] = import_react32.default.useState("");
|
|
4336
|
+
const active = import_react32.default.useRef(null);
|
|
4260
4337
|
const translateStatus = (value) => ({
|
|
4261
4338
|
"dsh-live-voice.commons.connection.unsaved": commons.connection.unsaved(),
|
|
4262
4339
|
"dsh-live-voice.recognition.whisper.saved": recognition.whisper.saved(),
|
|
@@ -4266,7 +4343,7 @@ function WhisperSettings({ controller }) {
|
|
|
4266
4343
|
"dsh-live-voice.recognition.whisper.requestFailed": recognition.whisper.requestFailed(),
|
|
4267
4344
|
"dsh-live-voice.recognition.whisper.healthFailed": recognition.whisper.healthFailed()
|
|
4268
4345
|
})[value] ?? value;
|
|
4269
|
-
async function run(action) {
|
|
4346
|
+
async function run(action, config = draft) {
|
|
4270
4347
|
active.current?.abort();
|
|
4271
4348
|
const abort = new AbortController();
|
|
4272
4349
|
active.current = abort;
|
|
@@ -4284,7 +4361,7 @@ function WhisperSettings({ controller }) {
|
|
|
4284
4361
|
await controller.endConversation?.();
|
|
4285
4362
|
const value = await whisperSettingsRequest("/config", {
|
|
4286
4363
|
method: "PUT",
|
|
4287
|
-
config: { ...
|
|
4364
|
+
config: { ...config, timeoutMs: Number(config.timeoutMs) },
|
|
4288
4365
|
signal: abort.signal
|
|
4289
4366
|
});
|
|
4290
4367
|
if (!abort.signal.aborted) {
|
|
@@ -4295,7 +4372,7 @@ function WhisperSettings({ controller }) {
|
|
|
4295
4372
|
} else {
|
|
4296
4373
|
const value = await whisperSettingsRequest("/test", {
|
|
4297
4374
|
method: "POST",
|
|
4298
|
-
config: { ...
|
|
4375
|
+
config: { ...config, timeoutMs: Number(config.timeoutMs) },
|
|
4299
4376
|
signal: abort.signal
|
|
4300
4377
|
});
|
|
4301
4378
|
if (!abort.signal.aborted) {
|
|
@@ -4310,28 +4387,29 @@ function WhisperSettings({ controller }) {
|
|
|
4310
4387
|
if (!abort.signal.aborted) setBusy(false);
|
|
4311
4388
|
}
|
|
4312
4389
|
}
|
|
4313
|
-
|
|
4390
|
+
import_react32.default.useEffect(() => {
|
|
4314
4391
|
void run("load");
|
|
4315
4392
|
return () => active.current?.abort();
|
|
4316
4393
|
}, []);
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4394
|
+
const field = (label, key, type = "text") => {
|
|
4395
|
+
const Field = type === "number" ? NumberField : TextField;
|
|
4396
|
+
return /* @__PURE__ */ import_react32.default.createElement(
|
|
4397
|
+
Field,
|
|
4320
4398
|
{
|
|
4321
|
-
|
|
4399
|
+
label,
|
|
4322
4400
|
value: draft[key],
|
|
4323
4401
|
disabled: busy || !loaded,
|
|
4324
4402
|
autoComplete: "off",
|
|
4325
4403
|
...type === "number" ? { min: 100, max: 3e5, step: 1 } : {},
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4404
|
+
onCommit: (value) => {
|
|
4405
|
+
const config = { ...draft, [key]: value };
|
|
4406
|
+
setDraft(config);
|
|
4407
|
+
void run("save", config);
|
|
4330
4408
|
}
|
|
4331
4409
|
}
|
|
4332
|
-
)
|
|
4333
|
-
}
|
|
4334
|
-
return /* @__PURE__ */
|
|
4410
|
+
);
|
|
4411
|
+
};
|
|
4412
|
+
return /* @__PURE__ */ import_react32.default.createElement(import_react32.default.Fragment, null, /* @__PURE__ */ import_react32.default.createElement("p", null, settings.whisper.hostHelp()), field(commons.connection.endpoint(), "url"), field(commons.connection.healthEndpoint(), "healthUrl"), field(commons.connection.timeout(), "timeoutMs", "number"), /* @__PURE__ */ import_react32.default.createElement("div", { className: "dlv-settings-actions" }, /* @__PURE__ */ import_react32.default.createElement("button", { type: "button", disabled: busy || !loaded, onClick: () => run("save") }, recognition.whisper.save()), /* @__PURE__ */ import_react32.default.createElement("button", { type: "button", disabled: busy || !loaded, onClick: () => run("test") }, commons.connection.test()), /* @__PURE__ */ import_react32.default.createElement("button", { type: "button", disabled: busy, onClick: () => run("load") }, commons.connection.reload())), busy && /* @__PURE__ */ import_react32.default.createElement("p", { role: "status" }, commons.connection.contactingHost()), message2 && /* @__PURE__ */ import_react32.default.createElement("p", { role: "status" }, translateStatus(message2)), error && /* @__PURE__ */ import_react32.default.createElement("p", { role: "alert" }, translateStatus(error)));
|
|
4335
4413
|
}
|
|
4336
4414
|
|
|
4337
4415
|
// src/modules/settings/sections/recognition/RecognitionEngineSettings.tsx
|
|
@@ -4348,7 +4426,7 @@ function RecognitionEngineSettings({
|
|
|
4348
4426
|
const setEngine = (next) => updateSettings(
|
|
4349
4427
|
next === "browser" && settings.recognitionLang === "auto" ? { recognitionEngine: next, recognitionLang: "pt-BR" } : { recognitionEngine: next }
|
|
4350
4428
|
);
|
|
4351
|
-
return /* @__PURE__ */
|
|
4429
|
+
return /* @__PURE__ */ import_react33.default.createElement(import_react33.default.Fragment, null, /* @__PURE__ */ import_react33.default.createElement(
|
|
4352
4430
|
SelectField,
|
|
4353
4431
|
{
|
|
4354
4432
|
label: recognition.engine.label(),
|
|
@@ -4372,7 +4450,7 @@ function RecognitionEngineSettings({
|
|
|
4372
4450
|
],
|
|
4373
4451
|
onChange: (event) => setEngine(event.target.value)
|
|
4374
4452
|
}
|
|
4375
|
-
), /* @__PURE__ */
|
|
4453
|
+
), /* @__PURE__ */ import_react33.default.createElement(
|
|
4376
4454
|
SelectField,
|
|
4377
4455
|
{
|
|
4378
4456
|
label: recognition.microphone.device(),
|
|
@@ -4385,7 +4463,7 @@ function RecognitionEngineSettings({
|
|
|
4385
4463
|
),
|
|
4386
4464
|
onChange: (event) => updateSettings({ inputDeviceId: event.target.value })
|
|
4387
4465
|
}
|
|
4388
|
-
), /* @__PURE__ */
|
|
4466
|
+
), /* @__PURE__ */ import_react33.default.createElement(
|
|
4389
4467
|
SelectField,
|
|
4390
4468
|
{
|
|
4391
4469
|
label: recognition.language.label(),
|
|
@@ -4397,29 +4475,29 @@ function RecognitionEngineSettings({
|
|
|
4397
4475
|
],
|
|
4398
4476
|
onChange: (event) => updateSettings({ recognitionLang: event.target.value })
|
|
4399
4477
|
}
|
|
4400
|
-
), engine === "browser" && /* @__PURE__ */
|
|
4478
|
+
), engine === "browser" && /* @__PURE__ */ import_react33.default.createElement(import_react33.default.Fragment, null, /* @__PURE__ */ import_react33.default.createElement("p", null, recognition.browser.help()), /* @__PURE__ */ import_react33.default.createElement(
|
|
4401
4479
|
CheckboxField,
|
|
4402
4480
|
{
|
|
4403
4481
|
label: recognition.browser.localProcessing(),
|
|
4404
4482
|
checked: settings.recognitionProcessLocally !== false,
|
|
4405
4483
|
onChange: (event) => updateSettings({ recognitionProcessLocally: event.target.checked })
|
|
4406
4484
|
}
|
|
4407
|
-
), settings.recognitionProcessLocally !== false ? /* @__PURE__ */
|
|
4485
|
+
), settings.recognitionProcessLocally !== false ? /* @__PURE__ */ import_react33.default.createElement(
|
|
4408
4486
|
CheckboxField,
|
|
4409
4487
|
{
|
|
4410
4488
|
label: recognition.browser.autoInstallPack(),
|
|
4411
4489
|
checked: settings.recognitionAutoInstall !== false,
|
|
4412
4490
|
onChange: (event) => updateSettings({ recognitionAutoInstall: event.target.checked })
|
|
4413
4491
|
}
|
|
4414
|
-
) : /* @__PURE__ */
|
|
4492
|
+
) : /* @__PURE__ */ import_react33.default.createElement("p", { role: "status" }, recognition.browser.remoteServiceWarning())), engine === "whisper-http" && /* @__PURE__ */ import_react33.default.createElement(SettingsSubcard, { title: commons.connection.title(), icon: "link" }, /* @__PURE__ */ import_react33.default.createElement("p", null, recognition.whisper.captureHelp()), /* @__PURE__ */ import_react33.default.createElement(WhisperSettings, { controller })), engine === "qwen-http" && /* @__PURE__ */ import_react33.default.createElement(SettingsSubcard, { title: commons.connection.title(), icon: "link" }, /* @__PURE__ */ import_react33.default.createElement("p", null, recognition.qwen.captureHelp()), settings.engine !== "qwen-http" && /* @__PURE__ */ import_react33.default.createElement(QwenSettings, { controller })));
|
|
4415
4493
|
}
|
|
4416
4494
|
|
|
4417
4495
|
// src/modules/settings/sections/recognition/RecognitionFilterSettings.tsx
|
|
4418
|
-
var
|
|
4496
|
+
var import_react34 = __toESM(require("react"), 1);
|
|
4419
4497
|
function RecognitionFilterSettings({ settings, updateSettings }) {
|
|
4420
4498
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4421
4499
|
const enabled = settings.recognitionFilterEnabled !== false;
|
|
4422
|
-
return /* @__PURE__ */
|
|
4500
|
+
return /* @__PURE__ */ import_react34.default.createElement(SettingsSubcard, { title: recognition.minimumWords.label(), icon: "filter" }, /* @__PURE__ */ import_react34.default.createElement(
|
|
4423
4501
|
CheckboxField,
|
|
4424
4502
|
{
|
|
4425
4503
|
label: recognition.minimumWords.enabled(),
|
|
@@ -4427,7 +4505,7 @@ function RecognitionFilterSettings({ settings, updateSettings }) {
|
|
|
4427
4505
|
checked: enabled,
|
|
4428
4506
|
onChange: (event) => updateSettings({ recognitionFilterEnabled: event.target.checked })
|
|
4429
4507
|
}
|
|
4430
|
-
), /* @__PURE__ */
|
|
4508
|
+
), /* @__PURE__ */ import_react34.default.createElement(
|
|
4431
4509
|
NumberField,
|
|
4432
4510
|
{
|
|
4433
4511
|
label: recognition.minimumWords.label(),
|
|
@@ -4435,8 +4513,8 @@ function RecognitionFilterSettings({ settings, updateSettings }) {
|
|
|
4435
4513
|
max: 20,
|
|
4436
4514
|
value: settings.recognitionMinimumWords ?? 2,
|
|
4437
4515
|
disabled: !enabled,
|
|
4438
|
-
|
|
4439
|
-
const value = Number(
|
|
4516
|
+
onCommit: (raw) => {
|
|
4517
|
+
const value = Number(raw);
|
|
4440
4518
|
if (Number.isInteger(value) && value >= 1 && value <= 20)
|
|
4441
4519
|
updateSettings({ recognitionMinimumWords: value });
|
|
4442
4520
|
}
|
|
@@ -4445,23 +4523,24 @@ function RecognitionFilterSettings({ settings, updateSettings }) {
|
|
|
4445
4523
|
}
|
|
4446
4524
|
|
|
4447
4525
|
// src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx
|
|
4448
|
-
var
|
|
4526
|
+
var import_react38 = __toESM(require("react"), 1);
|
|
4449
4527
|
|
|
4450
4528
|
// src/modules/settings/sections/recognition/SilenceDetectionSettings.tsx
|
|
4451
|
-
var
|
|
4529
|
+
var import_react35 = __toESM(require("react"), 1);
|
|
4452
4530
|
function SilenceDetectionSettings({ settings, updateSettings }) {
|
|
4453
4531
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4532
|
+
const savedCustomMs = normalizeCustomSilenceMs(settings.voiceDetectionCustomSilenceMs);
|
|
4454
4533
|
if (!usesPluginVoiceDetection(settings.recognitionEngine)) return null;
|
|
4455
|
-
const selected = settings.voiceDetectionPreset ||
|
|
4456
|
-
return /* @__PURE__ */
|
|
4534
|
+
const selected = settings.voiceDetectionPreset || defaultSettings.voiceDetectionPreset;
|
|
4535
|
+
return /* @__PURE__ */ import_react35.default.createElement(
|
|
4457
4536
|
SettingsSubcard,
|
|
4458
4537
|
{
|
|
4459
4538
|
title: recognition.silenceDetection.label(),
|
|
4460
4539
|
ariaLabel: recognition.silenceDetection.title(),
|
|
4461
4540
|
icon: "pause"
|
|
4462
4541
|
},
|
|
4463
|
-
/* @__PURE__ */
|
|
4464
|
-
/* @__PURE__ */
|
|
4542
|
+
/* @__PURE__ */ import_react35.default.createElement("p", null, recognition.silenceDetection.help()),
|
|
4543
|
+
/* @__PURE__ */ import_react35.default.createElement(
|
|
4465
4544
|
NumberField,
|
|
4466
4545
|
{
|
|
4467
4546
|
label: recognition.maxUtterance.label(),
|
|
@@ -4469,22 +4548,22 @@ function SilenceDetectionSettings({ settings, updateSettings }) {
|
|
|
4469
4548
|
max: 300,
|
|
4470
4549
|
step: 1,
|
|
4471
4550
|
value: settings.recognitionMaxUtteranceSeconds ?? 60,
|
|
4472
|
-
|
|
4473
|
-
const value = Number(
|
|
4551
|
+
onCommit: (raw) => {
|
|
4552
|
+
const value = Number(raw);
|
|
4474
4553
|
if (Number.isInteger(value) && value >= 10 && value <= 300)
|
|
4475
4554
|
updateSettings({ recognitionMaxUtteranceSeconds: value });
|
|
4476
4555
|
}
|
|
4477
4556
|
}
|
|
4478
4557
|
),
|
|
4479
|
-
/* @__PURE__ */
|
|
4480
|
-
/* @__PURE__ */
|
|
4558
|
+
/* @__PURE__ */ import_react35.default.createElement("small", null, recognition.maxUtterance.help()),
|
|
4559
|
+
/* @__PURE__ */ import_react35.default.createElement(
|
|
4481
4560
|
"div",
|
|
4482
4561
|
{
|
|
4483
4562
|
className: "dlv-preset-group",
|
|
4484
4563
|
role: "radiogroup",
|
|
4485
4564
|
"aria-label": recognition.silenceDetection.pauseLabel()
|
|
4486
4565
|
},
|
|
4487
|
-
Object.
|
|
4566
|
+
[...Object.keys(voiceDetectionPresets), "custom"].map((value) => /* @__PURE__ */ import_react35.default.createElement("label", { key: value, className: "dlv-preset" }, /* @__PURE__ */ import_react35.default.createElement(
|
|
4488
4567
|
"input",
|
|
4489
4568
|
{
|
|
4490
4569
|
type: "radio",
|
|
@@ -4493,16 +4572,31 @@ function SilenceDetectionSettings({ settings, updateSettings }) {
|
|
|
4493
4572
|
checked: selected === value,
|
|
4494
4573
|
onChange: () => updateSettings({ voiceDetectionPreset: value })
|
|
4495
4574
|
}
|
|
4496
|
-
), /* @__PURE__ */
|
|
4575
|
+
), /* @__PURE__ */ import_react35.default.createElement("span", null, /* @__PURE__ */ import_react35.default.createElement("strong", null, recognition.presets[value].label()), /* @__PURE__ */ import_react35.default.createElement("small", null, recognition.presets[value].description()))))
|
|
4497
4576
|
),
|
|
4498
|
-
/* @__PURE__ */
|
|
4499
|
-
|
|
4577
|
+
selected === "custom" && /* @__PURE__ */ import_react35.default.createElement(import_react35.default.Fragment, null, /* @__PURE__ */ import_react35.default.createElement(
|
|
4578
|
+
NumberField,
|
|
4579
|
+
{
|
|
4580
|
+
label: recognition.silenceDetection.customLabel(),
|
|
4581
|
+
min: customSilenceMinMs,
|
|
4582
|
+
max: customSilenceMaxMs,
|
|
4583
|
+
step: 1,
|
|
4584
|
+
value: savedCustomMs,
|
|
4585
|
+
onCommit: (draft) => {
|
|
4586
|
+
const value = Number(draft);
|
|
4587
|
+
if (draft.trim() && Number.isInteger(value) && value >= customSilenceMinMs && value <= customSilenceMaxMs)
|
|
4588
|
+
updateSettings({ voiceDetectionCustomSilenceMs: value });
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
), /* @__PURE__ */ import_react35.default.createElement("small", null, recognition.silenceDetection.customHelp())),
|
|
4592
|
+
/* @__PURE__ */ import_react35.default.createElement("p", { className: "dlv-vad-summary" }, recognition.silenceDetection.duration({
|
|
4593
|
+
milliseconds: voiceDetectionSilenceMs(settings)
|
|
4500
4594
|
}))
|
|
4501
4595
|
);
|
|
4502
4596
|
}
|
|
4503
4597
|
|
|
4504
4598
|
// src/modules/settings/sections/recognition/VoiceCommandSettings.tsx
|
|
4505
|
-
var
|
|
4599
|
+
var import_react36 = __toESM(require("react"), 1);
|
|
4506
4600
|
var COMMANDS = [
|
|
4507
4601
|
["send", "voiceCommandSend", "send message, send it"],
|
|
4508
4602
|
["queue", "voiceCommandQueue", "queue message, queue it"],
|
|
@@ -4515,53 +4609,53 @@ var COMMANDS = [
|
|
|
4515
4609
|
function VoiceCommandSettings({ settings, updateSettings }) {
|
|
4516
4610
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4517
4611
|
const enabled = settings.voiceCommandsEnabled !== false;
|
|
4518
|
-
return /* @__PURE__ */
|
|
4612
|
+
return /* @__PURE__ */ import_react36.default.createElement(SettingsSubcard, { title: recognition.commands.title(), icon: "send" }, /* @__PURE__ */ import_react36.default.createElement(
|
|
4519
4613
|
CheckboxField,
|
|
4520
4614
|
{
|
|
4521
4615
|
label: recognition.commands.enabled(),
|
|
4522
4616
|
checked: enabled,
|
|
4523
4617
|
onChange: (event) => updateSettings({ voiceCommandsEnabled: event.target.checked })
|
|
4524
4618
|
}
|
|
4525
|
-
), /* @__PURE__ */
|
|
4619
|
+
), /* @__PURE__ */ import_react36.default.createElement("p", { className: "dlv-setting-description" }, recognition.voiceCommands.help()), COMMANDS.map(([label, key, fallback]) => /* @__PURE__ */ import_react36.default.createElement(
|
|
4526
4620
|
TextAreaField,
|
|
4527
4621
|
{
|
|
4528
4622
|
key,
|
|
4529
4623
|
rows: 2,
|
|
4530
4624
|
maxLength: 1e3,
|
|
4531
4625
|
label: recognition.commands[label](),
|
|
4532
|
-
value: settings[key]
|
|
4626
|
+
value: settings[key] ?? fallback,
|
|
4533
4627
|
disabled: !enabled,
|
|
4534
|
-
|
|
4628
|
+
onCommit: (value) => updateSettings({ [key]: value })
|
|
4535
4629
|
}
|
|
4536
4630
|
)));
|
|
4537
4631
|
}
|
|
4538
4632
|
|
|
4539
4633
|
// src/modules/settings/sections/recognition/RecognitionStatus.tsx
|
|
4540
|
-
var
|
|
4634
|
+
var import_react37 = __toESM(require("react"), 1);
|
|
4541
4635
|
function RecognitionStatus({ capabilities = {} }) {
|
|
4542
4636
|
const { scoped: recognition } = useLanguage((ctx) => ctx.recognition);
|
|
4543
|
-
return /* @__PURE__ */
|
|
4637
|
+
return /* @__PURE__ */ import_react37.default.createElement(import_react37.default.Fragment, null, capabilities.capture?.supported === false ? /* @__PURE__ */ import_react37.default.createElement("p", { role: "status" }, recognition.microphone.failure({ reason: capabilities.capture.reason || "" })) : capabilities.capture?.permission === "prompt" ? /* @__PURE__ */ import_react37.default.createElement("p", { role: "status" }, recognition.microphone.permissionHelp()) : null, capabilities.recognition?.supported === false && /* @__PURE__ */ import_react37.default.createElement("p", { role: "status" }, capabilities.recognition.reason));
|
|
4544
4638
|
}
|
|
4545
4639
|
|
|
4546
4640
|
// src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx
|
|
4547
4641
|
function RecognitionSettingsSection(props) {
|
|
4548
4642
|
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4549
|
-
return /* @__PURE__ */
|
|
4643
|
+
return /* @__PURE__ */ import_react38.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react38.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react38.default.createElement(RecognitionStatus, { ...props }), /* @__PURE__ */ import_react38.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "mic" }, /* @__PURE__ */ import_react38.default.createElement(RecognitionEngineSettings, { ...props })), /* @__PURE__ */ import_react38.default.createElement(VoiceCommandSettings, { ...props }), /* @__PURE__ */ import_react38.default.createElement(RecognitionFilterSettings, { ...props }), /* @__PURE__ */ import_react38.default.createElement(SilenceDetectionSettings, { ...props })));
|
|
4550
4644
|
}
|
|
4551
4645
|
|
|
4552
4646
|
// src/modules/settings/sections/speak/OutputFilterSettings.tsx
|
|
4553
|
-
var
|
|
4647
|
+
var import_react39 = __toESM(require("react"), 1);
|
|
4554
4648
|
function OutputFilterSettings({ settings, updateSettings }) {
|
|
4555
4649
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4556
4650
|
const enabled = settings.outputCodeFilterEnabled !== false;
|
|
4557
|
-
return /* @__PURE__ */
|
|
4651
|
+
return /* @__PURE__ */ import_react39.default.createElement(SettingsSubcard, { title: speak.filters.code.enabled(), icon: "filter" }, /* @__PURE__ */ import_react39.default.createElement(
|
|
4558
4652
|
CheckboxField,
|
|
4559
4653
|
{
|
|
4560
4654
|
label: speak.filters.code.enabled(),
|
|
4561
4655
|
checked: enabled,
|
|
4562
4656
|
onChange: (event) => updateSettings({ outputCodeFilterEnabled: event.target.checked })
|
|
4563
4657
|
}
|
|
4564
|
-
), /* @__PURE__ */
|
|
4658
|
+
), /* @__PURE__ */ import_react39.default.createElement(
|
|
4565
4659
|
NumberField,
|
|
4566
4660
|
{
|
|
4567
4661
|
label: speak.filters.code.maxLines(),
|
|
@@ -4569,34 +4663,34 @@ function OutputFilterSettings({ settings, updateSettings }) {
|
|
|
4569
4663
|
max: 100,
|
|
4570
4664
|
value: settings.outputCodeMaxLines ?? 5,
|
|
4571
4665
|
disabled: !enabled,
|
|
4572
|
-
|
|
4573
|
-
const value = Number(
|
|
4666
|
+
onCommit: (raw) => {
|
|
4667
|
+
const value = Number(raw);
|
|
4574
4668
|
if (Number.isInteger(value) && value >= 0 && value <= 100)
|
|
4575
4669
|
updateSettings({ outputCodeMaxLines: value });
|
|
4576
4670
|
}
|
|
4577
4671
|
}
|
|
4578
|
-
), /* @__PURE__ */
|
|
4672
|
+
), /* @__PURE__ */ import_react39.default.createElement(
|
|
4579
4673
|
TextField,
|
|
4580
4674
|
{
|
|
4581
4675
|
label: speak.filters.code.replacement(),
|
|
4582
|
-
|
|
4676
|
+
value: settings.outputCodeNotice ?? speak.filters.code.notice(),
|
|
4583
4677
|
disabled: !enabled,
|
|
4584
|
-
|
|
4678
|
+
onCommit: (value) => updateSettings({ outputCodeNotice: value })
|
|
4585
4679
|
}
|
|
4586
4680
|
));
|
|
4587
4681
|
}
|
|
4588
4682
|
|
|
4589
4683
|
// src/modules/settings/sections/speak/SpeakSettingsSection.tsx
|
|
4590
|
-
var
|
|
4684
|
+
var import_react42 = __toESM(require("react"), 1);
|
|
4591
4685
|
|
|
4592
4686
|
// src/modules/settings/sections/speak/SpeechEngineSettings.tsx
|
|
4593
|
-
var
|
|
4687
|
+
var import_react40 = __toESM(require("react"), 1);
|
|
4594
4688
|
function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
4595
4689
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4596
4690
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4597
4691
|
const engine = settings.engine || "browser";
|
|
4598
4692
|
const browserVoices = capabilities?.browser?.voices || [];
|
|
4599
|
-
return /* @__PURE__ */
|
|
4693
|
+
return /* @__PURE__ */ import_react40.default.createElement(import_react40.default.Fragment, null, /* @__PURE__ */ import_react40.default.createElement(
|
|
4600
4694
|
SelectField,
|
|
4601
4695
|
{
|
|
4602
4696
|
label: speak.engine.label(),
|
|
@@ -4620,7 +4714,7 @@ function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
|
4620
4714
|
],
|
|
4621
4715
|
onChange: (event) => updateSettings({ engine: event.target.value })
|
|
4622
4716
|
}
|
|
4623
|
-
), engine === "browser" && /* @__PURE__ */
|
|
4717
|
+
), engine === "browser" && /* @__PURE__ */ import_react40.default.createElement(
|
|
4624
4718
|
SelectField,
|
|
4625
4719
|
{
|
|
4626
4720
|
label: speak.browser.voice(),
|
|
@@ -4634,7 +4728,7 @@ function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
|
4634
4728
|
],
|
|
4635
4729
|
onChange: (event) => updateSettings({ voice: event.target.value })
|
|
4636
4730
|
}
|
|
4637
|
-
), engine === "qwen-http" && /* @__PURE__ */
|
|
4731
|
+
), engine === "qwen-http" && /* @__PURE__ */ import_react40.default.createElement(
|
|
4638
4732
|
SelectField,
|
|
4639
4733
|
{
|
|
4640
4734
|
label: speak.qwen.voice(),
|
|
@@ -4642,7 +4736,7 @@ function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
|
4642
4736
|
options: qwenVoices,
|
|
4643
4737
|
onChange: (event) => updateSettings({ voice: event.target.value })
|
|
4644
4738
|
}
|
|
4645
|
-
), /* @__PURE__ */
|
|
4739
|
+
), /* @__PURE__ */ import_react40.default.createElement(
|
|
4646
4740
|
NumberField,
|
|
4647
4741
|
{
|
|
4648
4742
|
label: speak.rate.label(),
|
|
@@ -4650,9 +4744,9 @@ function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
|
4650
4744
|
max: 3,
|
|
4651
4745
|
step: 0.1,
|
|
4652
4746
|
value: settings.rate ?? 1,
|
|
4653
|
-
|
|
4747
|
+
onCommit: (value) => updateSettings({ rate: Number(value) })
|
|
4654
4748
|
}
|
|
4655
|
-
), /* @__PURE__ */
|
|
4749
|
+
), /* @__PURE__ */ import_react40.default.createElement(
|
|
4656
4750
|
NumberField,
|
|
4657
4751
|
{
|
|
4658
4752
|
label: speak.segmentGap.label(),
|
|
@@ -4660,13 +4754,13 @@ function SpeechEngineSettings({ settings, capabilities, updateSettings }) {
|
|
|
4660
4754
|
max: 2e3,
|
|
4661
4755
|
step: 50,
|
|
4662
4756
|
value: settings.segmentGapMs ?? 400,
|
|
4663
|
-
|
|
4757
|
+
onCommit: (value) => updateSettings({ segmentGapMs: Number(value) })
|
|
4664
4758
|
}
|
|
4665
|
-
), capabilities?.[engine === "qwen-http" ? "qwen" : engine]?.supported === false && /* @__PURE__ */
|
|
4759
|
+
), capabilities?.[engine === "qwen-http" ? "qwen" : engine]?.supported === false && /* @__PURE__ */ import_react40.default.createElement("p", { role: "status" }, capabilities?.[engine === "qwen-http" ? "qwen" : engine]?.reason || commons.engine.failure()));
|
|
4666
4760
|
}
|
|
4667
4761
|
|
|
4668
4762
|
// src/modules/settings/sections/speak/SpeechAdvancedSettings.tsx
|
|
4669
|
-
var
|
|
4763
|
+
var import_react41 = __toESM(require("react"), 1);
|
|
4670
4764
|
function SpeechAdvancedSettings({
|
|
4671
4765
|
controller,
|
|
4672
4766
|
settings,
|
|
@@ -4684,7 +4778,7 @@ function SpeechAdvancedSettings({
|
|
|
4684
4778
|
commons.systemDefault(),
|
|
4685
4779
|
speak.output.fallbackName()
|
|
4686
4780
|
);
|
|
4687
|
-
return /* @__PURE__ */
|
|
4781
|
+
return /* @__PURE__ */ import_react41.default.createElement(import_react41.default.Fragment, null, settings.engine !== "say" ? /* @__PURE__ */ import_react41.default.createElement(
|
|
4688
4782
|
SelectField,
|
|
4689
4783
|
{
|
|
4690
4784
|
label: speak.output.device(),
|
|
@@ -4692,45 +4786,45 @@ function SpeechAdvancedSettings({
|
|
|
4692
4786
|
options: outputOptions,
|
|
4693
4787
|
onChange: (event) => updateSettings({ outputDeviceId: event.target.value })
|
|
4694
4788
|
}
|
|
4695
|
-
) : /* @__PURE__ */
|
|
4789
|
+
) : /* @__PURE__ */ import_react41.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.outputHelp()), settings.engine === "qwen-http" && /* @__PURE__ */ import_react41.default.createElement(SettingsSubcard, { title: speak.qwen.connection(), icon: "link" }, /* @__PURE__ */ import_react41.default.createElement(QwenSettings, { controller })), /* @__PURE__ */ import_react41.default.createElement(SettingsSubcard, { title: speak.agentContext.label(), icon: "settings" }, /* @__PURE__ */ import_react41.default.createElement(
|
|
4696
4790
|
CheckboxField,
|
|
4697
4791
|
{
|
|
4698
4792
|
label: speak.agentContext.enabled(),
|
|
4699
4793
|
checked: settings.agentVoiceContextEnabled !== false,
|
|
4700
4794
|
onChange: (event) => updateSettings({ agentVoiceContextEnabled: event.target.checked })
|
|
4701
4795
|
}
|
|
4702
|
-
), /* @__PURE__ */
|
|
4796
|
+
), /* @__PURE__ */ import_react41.default.createElement(
|
|
4703
4797
|
TextAreaField,
|
|
4704
4798
|
{
|
|
4705
4799
|
label: speak.agentContext.label(),
|
|
4706
|
-
value: settings.agentVoiceContext
|
|
4800
|
+
value: settings.agentVoiceContext ?? defaultAgentVoiceContext,
|
|
4707
4801
|
disabled: settings.agentVoiceContextEnabled === false,
|
|
4708
|
-
|
|
4802
|
+
onCommit: (value) => updateSettings({ agentVoiceContext: value })
|
|
4709
4803
|
}
|
|
4710
|
-
), /* @__PURE__ */
|
|
4804
|
+
), /* @__PURE__ */ import_react41.default.createElement(
|
|
4711
4805
|
"button",
|
|
4712
4806
|
{
|
|
4713
4807
|
type: "button",
|
|
4714
4808
|
onClick: () => updateSettings({ agentVoiceContext: defaultAgentVoiceContext })
|
|
4715
4809
|
},
|
|
4716
4810
|
speak.agentContext.restore()
|
|
4717
|
-
)), /* @__PURE__ */
|
|
4811
|
+
)), /* @__PURE__ */ import_react41.default.createElement("p", null, speak.engine.playbackHelp()), /* @__PURE__ */ import_react41.default.createElement("div", { className: "dlv-settings-actions" }, /* @__PURE__ */ import_react41.default.createElement("button", { type: "button", onClick: () => invoke("speak", speak.output.testPhrase()) }, speak.output.test()), speaking && /* @__PURE__ */ import_react41.default.createElement("button", { type: "button", onClick: () => invoke("stopSpeech") }, speak.output.stopTest()), /* @__PURE__ */ import_react41.default.createElement("button", { type: "button", onClick: () => invoke("refreshCapabilities") }, settingsLanguage.engine.refresh())));
|
|
4718
4812
|
}
|
|
4719
4813
|
|
|
4720
4814
|
// src/modules/settings/sections/speak/SpeakSettingsSection.tsx
|
|
4721
4815
|
function SpeakSettingsSection(props) {
|
|
4722
4816
|
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4723
|
-
return /* @__PURE__ */
|
|
4817
|
+
return /* @__PURE__ */ import_react42.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react42.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react42.default.createElement(SettingsSubcard, { title: settingsLanguage.general.title(), icon: "speaker" }, /* @__PURE__ */ import_react42.default.createElement(SpeechEngineSettings, { ...props }), /* @__PURE__ */ import_react42.default.createElement(SpeechAdvancedSettings, { ...props })), /* @__PURE__ */ import_react42.default.createElement(OutputFilterSettings, { ...props })));
|
|
4724
4818
|
}
|
|
4725
4819
|
|
|
4726
4820
|
// src/modules/settings/components/SettingsHeader.tsx
|
|
4727
|
-
var
|
|
4821
|
+
var import_react44 = __toESM(require("react"), 1);
|
|
4728
4822
|
|
|
4729
4823
|
// src/modules/settings/components/VersionBadges.tsx
|
|
4730
|
-
var
|
|
4824
|
+
var import_react43 = __toESM(require("react"), 1);
|
|
4731
4825
|
function VersionBadges() {
|
|
4732
4826
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4733
|
-
return /* @__PURE__ */
|
|
4827
|
+
return /* @__PURE__ */ import_react43.default.createElement("div", { className: "dlv-version-badges", "aria-label": commons.version.title() }, /* @__PURE__ */ import_react43.default.createElement(
|
|
4734
4828
|
"a",
|
|
4735
4829
|
{
|
|
4736
4830
|
className: "dlv-shields-badge",
|
|
@@ -4740,9 +4834,9 @@ function VersionBadges() {
|
|
|
4740
4834
|
"aria-label": commons.version.link({ version: CURRENT_VERSION }),
|
|
4741
4835
|
title: commons.version.label({ version: CURRENT_VERSION })
|
|
4742
4836
|
},
|
|
4743
|
-
/* @__PURE__ */
|
|
4744
|
-
/* @__PURE__ */
|
|
4745
|
-
), /* @__PURE__ */
|
|
4837
|
+
/* @__PURE__ */ import_react43.default.createElement("img", { src: LIVE_VOICE_BADGE_URL, alt: "" }),
|
|
4838
|
+
/* @__PURE__ */ import_react43.default.createElement("span", null, `v${CURRENT_VERSION}`)
|
|
4839
|
+
), /* @__PURE__ */ import_react43.default.createElement(
|
|
4746
4840
|
"a",
|
|
4747
4841
|
{
|
|
4748
4842
|
className: "dlv-shields-badge",
|
|
@@ -4752,8 +4846,8 @@ function VersionBadges() {
|
|
|
4752
4846
|
"aria-label": commons.version.compatibilityLink({ version: TESTED_DSH_VERSION }),
|
|
4753
4847
|
title: commons.version.compatibility({ version: TESTED_DSH_VERSION })
|
|
4754
4848
|
},
|
|
4755
|
-
/* @__PURE__ */
|
|
4756
|
-
/* @__PURE__ */
|
|
4849
|
+
/* @__PURE__ */ import_react43.default.createElement("img", { src: DSH_BADGE_URL, alt: "" }),
|
|
4850
|
+
/* @__PURE__ */ import_react43.default.createElement("span", null, `v${TESTED_DSH_VERSION}`)
|
|
4757
4851
|
));
|
|
4758
4852
|
}
|
|
4759
4853
|
|
|
@@ -4762,7 +4856,7 @@ function SettingsHeader({ onClose }) {
|
|
|
4762
4856
|
const { scoped: commons } = useLanguage((ctx) => ctx.commons);
|
|
4763
4857
|
const { scoped: settings } = useLanguage((ctx) => ctx.settings);
|
|
4764
4858
|
const { latestRelease, updateAvailable } = useReleaseStatus();
|
|
4765
|
-
return /* @__PURE__ */
|
|
4859
|
+
return /* @__PURE__ */ import_react44.default.createElement("div", { className: "dlv-settings-heading" }, /* @__PURE__ */ import_react44.default.createElement("h3", null, commons.pluginName()), /* @__PURE__ */ import_react44.default.createElement(VersionBadges, null), /* @__PURE__ */ import_react44.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true }), updateAvailable && latestRelease ? /* @__PURE__ */ import_react44.default.createElement(import_react44.default.Fragment, null, /* @__PURE__ */ import_react44.default.createElement(
|
|
4766
4860
|
"a",
|
|
4767
4861
|
{
|
|
4768
4862
|
className: "dlv-version-badge dlv-update-badge",
|
|
@@ -4772,9 +4866,9 @@ function SettingsHeader({ onClose }) {
|
|
|
4772
4866
|
"aria-label": commons.update.link({ version: latestRelease.tag }),
|
|
4773
4867
|
title: commons.update.version({ version: latestRelease.tag })
|
|
4774
4868
|
},
|
|
4775
|
-
/* @__PURE__ */
|
|
4869
|
+
/* @__PURE__ */ import_react44.default.createElement("span", { className: "dlv-update-icon", "aria-hidden": true }, "\u2191"),
|
|
4776
4870
|
commons.update.label()
|
|
4777
|
-
), /* @__PURE__ */
|
|
4871
|
+
), /* @__PURE__ */ import_react44.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true })) : null, /* @__PURE__ */ import_react44.default.createElement(
|
|
4778
4872
|
"a",
|
|
4779
4873
|
{
|
|
4780
4874
|
className: "dlv-version-badge dlv-star-badge",
|
|
@@ -4786,7 +4880,7 @@ function SettingsHeader({ onClose }) {
|
|
|
4786
4880
|
},
|
|
4787
4881
|
"\u2605 ",
|
|
4788
4882
|
commons.repository.starLabel()
|
|
4789
|
-
), onClose ? /* @__PURE__ */
|
|
4883
|
+
), onClose ? /* @__PURE__ */ import_react44.default.createElement(IconButton, { icon: "close", label: settings.close(), onClick: onClose }) : null);
|
|
4790
4884
|
}
|
|
4791
4885
|
|
|
4792
4886
|
// src/modules/settings/components/LiveVoiceSettings.tsx
|
|
@@ -4795,8 +4889,8 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4795
4889
|
const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
|
|
4796
4890
|
const state = useConversationController(controller);
|
|
4797
4891
|
const { invoke, error, clearError } = useConversationActions(controller);
|
|
4798
|
-
const [activeTab, setActiveTab] =
|
|
4799
|
-
const tabsId =
|
|
4892
|
+
const [activeTab, setActiveTab] = import_react45.default.useState("conversation");
|
|
4893
|
+
const tabsId = import_react45.default.useId();
|
|
4800
4894
|
const tabs = [
|
|
4801
4895
|
{ id: "speech", label: settingsLanguage.tabs.speak(), icon: "speaker" },
|
|
4802
4896
|
{
|
|
@@ -4818,7 +4912,7 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4818
4912
|
invoke,
|
|
4819
4913
|
updateSettings: (next) => invoke("updateSettings", next)
|
|
4820
4914
|
};
|
|
4821
|
-
return /* @__PURE__ */
|
|
4915
|
+
return /* @__PURE__ */ import_react45.default.createElement("section", { className: "dlv-settings", "aria-label": settingsLanguage.title() }, /* @__PURE__ */ import_react45.default.createElement(SettingsHeader, { onClose }), /* @__PURE__ */ import_react45.default.createElement(
|
|
4822
4916
|
SettingsTabs,
|
|
4823
4917
|
{
|
|
4824
4918
|
label: settingsLanguage.title(),
|
|
@@ -4827,7 +4921,7 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4827
4921
|
idPrefix: tabsId,
|
|
4828
4922
|
onChange: setActiveTab
|
|
4829
4923
|
}
|
|
4830
|
-
), tabs.map((tab) => /* @__PURE__ */
|
|
4924
|
+
), tabs.map((tab) => /* @__PURE__ */ import_react45.default.createElement(
|
|
4831
4925
|
"div",
|
|
4832
4926
|
{
|
|
4833
4927
|
key: tab.id,
|
|
@@ -4837,8 +4931,8 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4837
4931
|
"aria-labelledby": `${tabsId}-tab-${tab.id}`,
|
|
4838
4932
|
hidden: activeTab !== tab.id
|
|
4839
4933
|
},
|
|
4840
|
-
tab.id === "speech" ? /* @__PURE__ */
|
|
4841
|
-
)), /* @__PURE__ */
|
|
4934
|
+
tab.id === "speech" ? /* @__PURE__ */ import_react45.default.createElement(SpeakSettingsSection, { ...sectionProps }) : tab.id === "recognition" ? /* @__PURE__ */ import_react45.default.createElement(RecognitionSettingsSection, { ...sectionProps }) : /* @__PURE__ */ import_react45.default.createElement(ConversationSettingsSection, { ...sectionProps })
|
|
4935
|
+
)), /* @__PURE__ */ import_react45.default.createElement(
|
|
4842
4936
|
ErrorMessage,
|
|
4843
4937
|
{
|
|
4844
4938
|
error: error || state.error,
|
|
@@ -4855,17 +4949,64 @@ function LiveVoiceSettings({ controller, onClose }) {
|
|
|
4855
4949
|
// src/modules/settings/components/createLiveVoiceSettings.tsx
|
|
4856
4950
|
function createLiveVoiceSettings(..._legacyArguments) {
|
|
4857
4951
|
return function SettingsPanel({ controller, onClose }) {
|
|
4858
|
-
return /* @__PURE__ */
|
|
4952
|
+
return /* @__PURE__ */ import_react46.default.createElement(LiveVoiceSettings, { controller, onClose });
|
|
4953
|
+
};
|
|
4954
|
+
}
|
|
4955
|
+
|
|
4956
|
+
// src/modules/settings/models/settingsStorage.ts
|
|
4957
|
+
var SETTINGS_PATH = "/api/dsh-live-voice/settings";
|
|
4958
|
+
function createSettingsClient(fetchImpl = globalThis.fetch) {
|
|
4959
|
+
let settings = normalizeSettings({});
|
|
4960
|
+
let queue = Promise.resolve();
|
|
4961
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
4962
|
+
async function request(method, patch) {
|
|
4963
|
+
const response = await fetchImpl(SETTINGS_PATH, {
|
|
4964
|
+
method,
|
|
4965
|
+
credentials: "same-origin",
|
|
4966
|
+
cache: "no-store",
|
|
4967
|
+
...patch === void 0 ? {} : { headers: { "content-type": "application/json" }, body: JSON.stringify(patch) }
|
|
4968
|
+
});
|
|
4969
|
+
const body = await response.json();
|
|
4970
|
+
if (!response.ok || body?.ok !== true) throw new Error("settings-request-failed");
|
|
4971
|
+
settings = normalizeSettings(body.value);
|
|
4972
|
+
for (const listener of listeners) listener(settings);
|
|
4973
|
+
return settings;
|
|
4974
|
+
}
|
|
4975
|
+
const ready = request("GET");
|
|
4976
|
+
void ready.catch(() => {
|
|
4977
|
+
});
|
|
4978
|
+
return {
|
|
4979
|
+
ready,
|
|
4980
|
+
getSnapshot: () => settings,
|
|
4981
|
+
subscribe(listener) {
|
|
4982
|
+
listeners.add(listener);
|
|
4983
|
+
return () => {
|
|
4984
|
+
listeners.delete(listener);
|
|
4985
|
+
};
|
|
4986
|
+
},
|
|
4987
|
+
save(patch) {
|
|
4988
|
+
const operation = queue.then(async () => {
|
|
4989
|
+
await ready;
|
|
4990
|
+
return request("PUT", patch);
|
|
4991
|
+
});
|
|
4992
|
+
queue = operation.then(
|
|
4993
|
+
() => {
|
|
4994
|
+
},
|
|
4995
|
+
() => {
|
|
4996
|
+
}
|
|
4997
|
+
);
|
|
4998
|
+
return operation;
|
|
4999
|
+
}
|
|
4859
5000
|
};
|
|
4860
5001
|
}
|
|
4861
5002
|
|
|
4862
5003
|
// src/modules/conversation/components/ConversationControls.tsx
|
|
4863
|
-
var
|
|
5004
|
+
var import_react48 = __toESM(require("react"), 1);
|
|
4864
5005
|
|
|
4865
5006
|
// src/modules/conversation/components/MicrophoneButton.tsx
|
|
4866
|
-
var
|
|
5007
|
+
var import_react47 = __toESM(require("react"), 1);
|
|
4867
5008
|
function MicrophoneButton(props) {
|
|
4868
|
-
return /* @__PURE__ */
|
|
5009
|
+
return /* @__PURE__ */ import_react47.default.createElement(IconButton, { ...props, className: "dlv-mic", icon: "mic" });
|
|
4869
5010
|
}
|
|
4870
5011
|
|
|
4871
5012
|
// src/modules/conversation/components/ConversationControls.tsx
|
|
@@ -4882,14 +5023,14 @@ function ConversationControls({ controller }) {
|
|
|
4882
5023
|
const unavailable = capability?.supported === false || capture?.supported === false;
|
|
4883
5024
|
const reason = capture?.supported === false ? capture.reason : capability?.reason;
|
|
4884
5025
|
const label = pending ? recognition.microphone.checking() : unavailable ? reason || recognition.status.unavailable() : commons.conversation.start();
|
|
4885
|
-
return /* @__PURE__ */
|
|
5026
|
+
return /* @__PURE__ */ import_react48.default.createElement(import_react48.default.Fragment, null, /* @__PURE__ */ import_react48.default.createElement(
|
|
4886
5027
|
MicrophoneButton,
|
|
4887
5028
|
{
|
|
4888
5029
|
label,
|
|
4889
5030
|
disabled: pending,
|
|
4890
5031
|
onClick: () => invoke(unavailable ? "explainRecognition" : "startConversation")
|
|
4891
5032
|
}
|
|
4892
|
-
), /* @__PURE__ */
|
|
5033
|
+
), /* @__PURE__ */ import_react48.default.createElement(
|
|
4893
5034
|
ErrorMessage,
|
|
4894
5035
|
{
|
|
4895
5036
|
error,
|
|
@@ -4901,10 +5042,10 @@ function ConversationControls({ controller }) {
|
|
|
4901
5042
|
}
|
|
4902
5043
|
|
|
4903
5044
|
// src/modules/conversation/components/ConversationStatusBar.tsx
|
|
4904
|
-
var
|
|
5045
|
+
var import_react52 = __toESM(require("react"), 1);
|
|
4905
5046
|
|
|
4906
5047
|
// src/modules/conversation/components/DeliveryModeButton.tsx
|
|
4907
|
-
var
|
|
5048
|
+
var import_react49 = __toESM(require("react"), 1);
|
|
4908
5049
|
function nextDeliveryMode(mode) {
|
|
4909
5050
|
return mode === "queue" ? "steer" : mode === "steer" ? "manual" : "queue";
|
|
4910
5051
|
}
|
|
@@ -4917,7 +5058,7 @@ function DeliveryModeButton({
|
|
|
4917
5058
|
const description = mode === "steer" ? settings.delivery.steerDescription() : mode === "queue" ? commons.queue() : commons.manual();
|
|
4918
5059
|
const status = settings.delivery.status({ mode: description });
|
|
4919
5060
|
const visible = mode === "steer" ? commons.send() : mode === "queue" ? commons.delivery.queueBadge() : commons.toggle.offBadge();
|
|
4920
|
-
return /* @__PURE__ */
|
|
5061
|
+
return /* @__PURE__ */ import_react49.default.createElement(
|
|
4921
5062
|
IconButton,
|
|
4922
5063
|
{
|
|
4923
5064
|
className: "dlv-live-toggle",
|
|
@@ -4933,18 +5074,18 @@ function DeliveryModeButton({
|
|
|
4933
5074
|
}
|
|
4934
5075
|
|
|
4935
5076
|
// src/modules/conversation/components/PlaybackControls.tsx
|
|
4936
|
-
var
|
|
5077
|
+
var import_react50 = __toESM(require("react"), 1);
|
|
4937
5078
|
function PlaybackControls({ state, invoke }) {
|
|
4938
5079
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
4939
5080
|
const capabilities = state.capabilities?.[state.settings?.engine] ?? {};
|
|
4940
|
-
return /* @__PURE__ */
|
|
5081
|
+
return /* @__PURE__ */ import_react50.default.createElement(import_react50.default.Fragment, null, state.speechSegmentsRemaining > 1 ? /* @__PURE__ */ import_react50.default.createElement(
|
|
4941
5082
|
IconButton,
|
|
4942
5083
|
{
|
|
4943
5084
|
label: speak.playback.next(),
|
|
4944
5085
|
icon: "skipNext",
|
|
4945
5086
|
onClick: () => invoke("skipSpeechSegment")
|
|
4946
5087
|
}
|
|
4947
|
-
) : null, state.speechSegmentsRemaining > 0 && capabilities.pause ? /* @__PURE__ */
|
|
5088
|
+
) : null, state.speechSegmentsRemaining > 0 && capabilities.pause ? /* @__PURE__ */ import_react50.default.createElement(
|
|
4948
5089
|
IconButton,
|
|
4949
5090
|
{
|
|
4950
5091
|
label: speak.playback.pause(),
|
|
@@ -4952,14 +5093,14 @@ function PlaybackControls({ state, invoke }) {
|
|
|
4952
5093
|
disabled: !state.speaking || state.paused,
|
|
4953
5094
|
onClick: () => invoke("pauseSpeech")
|
|
4954
5095
|
}
|
|
4955
|
-
) : null, state.paused && capabilities.resume ? /* @__PURE__ */
|
|
5096
|
+
) : null, state.paused && capabilities.resume ? /* @__PURE__ */ import_react50.default.createElement(
|
|
4956
5097
|
IconButton,
|
|
4957
5098
|
{
|
|
4958
5099
|
label: speak.playback.resume(),
|
|
4959
5100
|
icon: "play",
|
|
4960
5101
|
onClick: () => invoke("resumeSpeech")
|
|
4961
5102
|
}
|
|
4962
|
-
) : null, state.speechSegmentsRemaining > 0 ? /* @__PURE__ */
|
|
5103
|
+
) : null, state.speechSegmentsRemaining > 0 ? /* @__PURE__ */ import_react50.default.createElement(
|
|
4963
5104
|
IconButton,
|
|
4964
5105
|
{
|
|
4965
5106
|
label: speak.playback.stopAll(),
|
|
@@ -4970,10 +5111,10 @@ function PlaybackControls({ state, invoke }) {
|
|
|
4970
5111
|
}
|
|
4971
5112
|
|
|
4972
5113
|
// src/modules/conversation/components/Waveform.tsx
|
|
4973
|
-
var
|
|
5114
|
+
var import_react51 = __toESM(require("react"), 1);
|
|
4974
5115
|
function Waveform({ controller, enabled }) {
|
|
4975
|
-
const ref =
|
|
4976
|
-
|
|
5116
|
+
const ref = import_react51.default.useRef(null);
|
|
5117
|
+
import_react51.default.useEffect(() => {
|
|
4977
5118
|
const canvas = ref.current;
|
|
4978
5119
|
const context = canvas?.getContext("2d");
|
|
4979
5120
|
if (!canvas || !context) return;
|
|
@@ -5025,7 +5166,7 @@ function Waveform({ controller, enabled }) {
|
|
|
5025
5166
|
window.removeEventListener("resize", resize);
|
|
5026
5167
|
};
|
|
5027
5168
|
}, [controller, enabled]);
|
|
5028
|
-
return /* @__PURE__ */
|
|
5169
|
+
return /* @__PURE__ */ import_react51.default.createElement("canvas", { ref, className: "dlv-wave", "aria-hidden": true });
|
|
5029
5170
|
}
|
|
5030
5171
|
|
|
5031
5172
|
// src/modules/conversation/components/conversationStatus.ts
|
|
@@ -5056,8 +5197,8 @@ function ConversationStatusBar({
|
|
|
5056
5197
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
5057
5198
|
const state = useConversationController(controller);
|
|
5058
5199
|
const { invoke, error, clearError } = useConversationActions(controller);
|
|
5059
|
-
const [now, setNow] =
|
|
5060
|
-
|
|
5200
|
+
const [now, setNow] = import_react52.default.useState(Date.now());
|
|
5201
|
+
import_react52.default.useEffect(() => {
|
|
5061
5202
|
if (!state.autoSendAt) return;
|
|
5062
5203
|
setNow(Date.now());
|
|
5063
5204
|
const timer = setInterval(() => setNow(Date.now()), 200);
|
|
@@ -5081,33 +5222,33 @@ function ConversationStatusBar({
|
|
|
5081
5222
|
}) : speak.autoPlayback.status({
|
|
5082
5223
|
state: autoPlayback ? commons.on() : commons.off()
|
|
5083
5224
|
});
|
|
5084
|
-
return /* @__PURE__ */
|
|
5225
|
+
return /* @__PURE__ */ import_react52.default.createElement(
|
|
5085
5226
|
"div",
|
|
5086
5227
|
{
|
|
5087
5228
|
className: overlay ? "dlv-bar-wrap dlv-question-overlay" : "dlv-bar-wrap",
|
|
5088
5229
|
style: overlay ? overlayStyle : void 0
|
|
5089
5230
|
},
|
|
5090
|
-
/* @__PURE__ */
|
|
5231
|
+
/* @__PURE__ */ import_react52.default.createElement("div", { className: "dlv-pill", role: "group", "aria-label": commons.controls.title() }, capture && !state.conversation ? /* @__PURE__ */ import_react52.default.createElement(
|
|
5091
5232
|
IconButton,
|
|
5092
5233
|
{
|
|
5093
5234
|
label: recognition.dictation.cancel(),
|
|
5094
5235
|
icon: "close",
|
|
5095
5236
|
onClick: () => invoke("cancelDictation")
|
|
5096
5237
|
}
|
|
5097
|
-
) : null, state.conversation ? /* @__PURE__ */
|
|
5238
|
+
) : null, state.conversation ? /* @__PURE__ */ import_react52.default.createElement(
|
|
5098
5239
|
IconButton,
|
|
5099
5240
|
{
|
|
5100
5241
|
label: commons.conversation.end(),
|
|
5101
5242
|
icon: "close",
|
|
5102
5243
|
onClick: () => invoke("endConversation")
|
|
5103
5244
|
}
|
|
5104
|
-
) : null, /* @__PURE__ */
|
|
5245
|
+
) : null, /* @__PURE__ */ import_react52.default.createElement(Waveform, { controller, enabled: Boolean(state.listening) }), /* @__PURE__ */ import_react52.default.createElement("span", { className: "dlv-status", role: "status", "aria-live": "polite" }, status), /* @__PURE__ */ import_react52.default.createElement(
|
|
5105
5246
|
DeliveryModeButton,
|
|
5106
5247
|
{
|
|
5107
5248
|
mode: state.settings.sendingMode || "manual",
|
|
5108
5249
|
onChange: (sendingMode) => invoke("updateSettings", { sendingMode })
|
|
5109
5250
|
}
|
|
5110
|
-
), /* @__PURE__ */
|
|
5251
|
+
), /* @__PURE__ */ import_react52.default.createElement(
|
|
5111
5252
|
IconButton,
|
|
5112
5253
|
{
|
|
5113
5254
|
className: `dlv-live-toggle${state.speechSegmentsRemaining > 0 ? " dlv-live-toggle-expanded" : ""}`,
|
|
@@ -5119,21 +5260,21 @@ function ConversationStatusBar({
|
|
|
5119
5260
|
"aria-checked": autoPlayback,
|
|
5120
5261
|
onClick: () => invoke("updateSettings", { announceAssistantMessages: !autoPlayback })
|
|
5121
5262
|
}
|
|
5122
|
-
), remaining ? /* @__PURE__ */
|
|
5263
|
+
), remaining ? /* @__PURE__ */ import_react52.default.createElement(
|
|
5123
5264
|
IconButton,
|
|
5124
5265
|
{
|
|
5125
5266
|
label: settings.autoSend.cancel(),
|
|
5126
5267
|
icon: "close",
|
|
5127
5268
|
onClick: () => invoke("cancelAutoSend")
|
|
5128
5269
|
}
|
|
5129
|
-
) : null, state.conversation && !capture ? /* @__PURE__ */
|
|
5270
|
+
) : null, state.conversation && !capture ? /* @__PURE__ */ import_react52.default.createElement(
|
|
5130
5271
|
IconButton,
|
|
5131
5272
|
{
|
|
5132
5273
|
label: recognition.microphone.takeControl(),
|
|
5133
5274
|
icon: "mic",
|
|
5134
5275
|
onClick: () => invoke("startConversation")
|
|
5135
5276
|
}
|
|
5136
|
-
) : null, capture ? /* @__PURE__ */
|
|
5277
|
+
) : null, capture ? /* @__PURE__ */ import_react52.default.createElement(
|
|
5137
5278
|
IconButton,
|
|
5138
5279
|
{
|
|
5139
5280
|
className: "dlv-live-toggle dlv-mic-state",
|
|
@@ -5147,8 +5288,8 @@ function ConversationStatusBar({
|
|
|
5147
5288
|
"data-muted": state.muted ? "true" : "false",
|
|
5148
5289
|
onClick: () => invoke(state.muted ? "resumeListeningInput" : "muteListening")
|
|
5149
5290
|
}
|
|
5150
|
-
) : null, /* @__PURE__ */
|
|
5151
|
-
/* @__PURE__ */
|
|
5291
|
+
) : null, /* @__PURE__ */ import_react52.default.createElement(PlaybackControls, { state, invoke })),
|
|
5292
|
+
/* @__PURE__ */ import_react52.default.createElement(
|
|
5152
5293
|
ErrorMessage,
|
|
5153
5294
|
{
|
|
5154
5295
|
error: error || state.error,
|
|
@@ -5164,7 +5305,7 @@ function ConversationStatusBar({
|
|
|
5164
5305
|
}
|
|
5165
5306
|
|
|
5166
5307
|
// src/modules/conversation/components/SpeakButton.tsx
|
|
5167
|
-
var
|
|
5308
|
+
var import_react53 = __toESM(require("react"), 1);
|
|
5168
5309
|
function SpeakButton({
|
|
5169
5310
|
active = false,
|
|
5170
5311
|
disabled = false,
|
|
@@ -5173,7 +5314,7 @@ function SpeakButton({
|
|
|
5173
5314
|
}) {
|
|
5174
5315
|
const { scoped: speak } = useLanguage((ctx) => ctx.speak);
|
|
5175
5316
|
const resolvedLabel = label ?? (active ? speak.playback.stop() : speak.playback.message());
|
|
5176
|
-
return /* @__PURE__ */
|
|
5317
|
+
return /* @__PURE__ */ import_react53.default.createElement(
|
|
5177
5318
|
IconButton,
|
|
5178
5319
|
{
|
|
5179
5320
|
className: "dlv-speaker",
|
|
@@ -5312,7 +5453,7 @@ var styles = `
|
|
|
5312
5453
|
.dlv-settings label{display:grid;gap:6px;font-size:14px}.dlv-settings label.dlv-check{display:flex;align-items:center;gap:8px}.dlv-settings label.dlv-check input{width:auto}
|
|
5313
5454
|
.dlv-settings :is(input,select,textarea){box-sizing:border-box;width:100%;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;font:inherit}
|
|
5314
5455
|
.dlv-settings small,.dlv-settings p{font-size:13px;color:var(--dsw-alias-label-secondary);line-height:1.5}.dlv-settings .dlv-setting-description{line-height:1.65;margin:3px 0 5px}.dlv-settings .dlv-conversation-fields{display:grid;gap:20px;padding:4px 0}
|
|
5315
|
-
.dlv-preset-group{display:grid;grid-template-columns:repeat(
|
|
5456
|
+
.dlv-preset-group{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.dlv-settings label.dlv-preset{display:flex;align-items:flex-start;gap:8px;padding:10px;border:1px solid var(--dsw-alias-border-l1);border-radius:8px;cursor:pointer}.dlv-settings label.dlv-preset:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-1)}.dlv-settings label.dlv-preset input{width:auto;margin-top:3px}.dlv-preset span{display:grid;gap:3px}.dlv-vad-summary{font-weight:500}
|
|
5316
5457
|
.dlv-settings-actions{display:flex;flex-wrap:wrap;gap:8px}.dlv-settings-actions button{padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:inherit;cursor:pointer}.dlv-settings-actions button:disabled{opacity:.45;cursor:default}
|
|
5317
5458
|
@media(max-width:480px){.dlv-settings-heading{gap:6px}.dlv-heading-divider{min-width:4px}.dlv-settings-heading .dlv-star-badge{padding:3px 6px;font-size:10px}.dlv-preset-group{grid-template-columns:1fr}.dlv-pill{flex-wrap:wrap}.dlv-wave{flex-basis:90px}.dlv-status{flex-basis:100px}}
|
|
5318
5459
|
`;
|
|
@@ -5324,6 +5465,8 @@ function apply(ctx) {
|
|
|
5324
5465
|
const SettingsPanel = createLiveVoiceSettings(t, ctx.locale);
|
|
5325
5466
|
const controllers = /* @__PURE__ */ new Map();
|
|
5326
5467
|
const retiring = /* @__PURE__ */ new Set();
|
|
5468
|
+
const preferences = createSettingsClient();
|
|
5469
|
+
const settingsControllers = /* @__PURE__ */ new Set();
|
|
5327
5470
|
let disposed = false;
|
|
5328
5471
|
let voiceModeActive = false;
|
|
5329
5472
|
const publishVoiceContext = (entry, active = entry?.controller?.getSnapshot().conversation === true) => {
|
|
@@ -5350,16 +5493,19 @@ function apply(ctx) {
|
|
|
5350
5493
|
"recognitionProcessLocally",
|
|
5351
5494
|
"recognitionAutoInstall",
|
|
5352
5495
|
"voiceDetectionPreset",
|
|
5496
|
+
"voiceDetectionCustomSilenceMs",
|
|
5353
5497
|
"recognitionMaxUtteranceSeconds"
|
|
5354
5498
|
]);
|
|
5355
5499
|
const changesRecognition = (next) => Object.keys(next).some((key) => recognitionSettingKeys.has(key));
|
|
5356
5500
|
const recognitionFor = (settings, meter) => settings.recognitionEngine === "qwen-http" ? new QwenHttpRecognitionEngine({
|
|
5357
5501
|
meter,
|
|
5358
5502
|
voiceDetectionPreset: settings.voiceDetectionPreset,
|
|
5503
|
+
voiceDetectionCustomSilenceMs: settings.voiceDetectionCustomSilenceMs,
|
|
5359
5504
|
maxUtteranceSeconds: settings.recognitionMaxUtteranceSeconds
|
|
5360
5505
|
}) : settings.recognitionEngine === "whisper-http" ? new WhisperHttpRecognitionEngine({
|
|
5361
5506
|
meter,
|
|
5362
5507
|
voiceDetectionPreset: settings.voiceDetectionPreset,
|
|
5508
|
+
voiceDetectionCustomSilenceMs: settings.voiceDetectionCustomSilenceMs,
|
|
5363
5509
|
maxUtteranceSeconds: settings.recognitionMaxUtteranceSeconds
|
|
5364
5510
|
}) : new BrowserRecognitionEngine({
|
|
5365
5511
|
processLocally: settings.recognitionProcessLocally,
|
|
@@ -5369,6 +5515,38 @@ function apply(ctx) {
|
|
|
5369
5515
|
if (!disposed && !controller.disposed)
|
|
5370
5516
|
controller.patch({ error: error?.message || String(error) });
|
|
5371
5517
|
});
|
|
5518
|
+
async function savePreferences(controller, next) {
|
|
5519
|
+
try {
|
|
5520
|
+
return await preferences.save(next);
|
|
5521
|
+
} catch {
|
|
5522
|
+
const error = new Error(t("dsh-live-voice.settings.persistence.saveError"));
|
|
5523
|
+
if (!disposed && !controller.disposed) controller.patch({ error: error.message });
|
|
5524
|
+
throw error;
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
const unsubscribePreferences = preferences.subscribe((settings) => {
|
|
5528
|
+
if (disposed) return;
|
|
5529
|
+
for (const entry of controllers.values()) {
|
|
5530
|
+
if (entry.closed) continue;
|
|
5531
|
+
const previous = entry.controller.getSnapshot().settings;
|
|
5532
|
+
if ([...recognitionSettingKeys].some((key) => previous[key] !== settings[key]))
|
|
5533
|
+
entry.controller.replaceRecognition(recognitionFor(settings, entry.controller.meter));
|
|
5534
|
+
entry.applySettings(settings);
|
|
5535
|
+
}
|
|
5536
|
+
for (const c of settingsControllers) {
|
|
5537
|
+
if (c.disposed) continue;
|
|
5538
|
+
const previous = c.getSnapshot().settings;
|
|
5539
|
+
if ([...recognitionSettingKeys].some((key) => previous[key] !== settings[key]))
|
|
5540
|
+
c.replaceRecognition(recognitionFor(settings, c.meter));
|
|
5541
|
+
c.applySavedSettings(settings);
|
|
5542
|
+
for (const engine of Object.values(c.engines)) engine.lang = settings.lang;
|
|
5543
|
+
run(c, c.refreshCapabilities());
|
|
5544
|
+
}
|
|
5545
|
+
});
|
|
5546
|
+
ctx.effect(
|
|
5547
|
+
() => () => unsubscribePreferences(),
|
|
5548
|
+
"dsh-live-voice: remove preferences subscription"
|
|
5549
|
+
);
|
|
5372
5550
|
function retire(entry) {
|
|
5373
5551
|
if (entry.closed) return;
|
|
5374
5552
|
entry.closed = true;
|
|
@@ -5397,6 +5575,10 @@ function apply(ctx) {
|
|
|
5397
5575
|
key,
|
|
5398
5576
|
draft: "",
|
|
5399
5577
|
pendingDraft: void 0,
|
|
5578
|
+
publishedDraft: void 0,
|
|
5579
|
+
publishedRevision: void 0,
|
|
5580
|
+
pendingRevision: void 0,
|
|
5581
|
+
staleDrafts: /* @__PURE__ */ new Set(),
|
|
5400
5582
|
composers: /* @__PURE__ */ new Map(),
|
|
5401
5583
|
refs: 0,
|
|
5402
5584
|
buttons: 0,
|
|
@@ -5404,14 +5586,7 @@ function apply(ctx) {
|
|
|
5404
5586
|
closed: false,
|
|
5405
5587
|
lastVoiceContextBody: null
|
|
5406
5588
|
};
|
|
5407
|
-
|
|
5408
|
-
try {
|
|
5409
|
-
settings = normalizeSettings(
|
|
5410
|
-
JSON.parse(localStorage.getItem("dsh-live-voice.settings") || "{}")
|
|
5411
|
-
);
|
|
5412
|
-
} catch {
|
|
5413
|
-
settings = normalizeSettings(null);
|
|
5414
|
-
}
|
|
5589
|
+
const settings = preferences.getSnapshot();
|
|
5415
5590
|
const engineBrowser = new BrowserSpeakingEngine({ lang: settings.lang || "pt-BR" });
|
|
5416
5591
|
const engineSay = new HostAudioSpeakingEngine({
|
|
5417
5592
|
endpoint: "/api/dsh-live-voice/say/speech",
|
|
@@ -5466,8 +5641,12 @@ function apply(ctx) {
|
|
|
5466
5641
|
if (disposed || entry.closed) return;
|
|
5467
5642
|
const owner = [...entry.composers.values()].at(-1);
|
|
5468
5643
|
if (!owner) return;
|
|
5644
|
+
if (entry.pendingDraft === void 0) entry.staleDrafts.clear();
|
|
5645
|
+
entry.staleDrafts.add(entry.draft);
|
|
5646
|
+
if (entry.publishedDraft !== void 0) entry.staleDrafts.add(entry.publishedDraft);
|
|
5469
5647
|
entry.draft = text;
|
|
5470
5648
|
entry.pendingDraft = text;
|
|
5649
|
+
entry.pendingRevision = entry.publishedRevision;
|
|
5471
5650
|
owner.actions.setDraft(text);
|
|
5472
5651
|
}
|
|
5473
5652
|
},
|
|
@@ -5540,14 +5719,7 @@ function apply(ctx) {
|
|
|
5540
5719
|
controller.updateSettings = (next) => {
|
|
5541
5720
|
if (disposed || entry.closed) return;
|
|
5542
5721
|
entry.applySettings(next);
|
|
5543
|
-
|
|
5544
|
-
try {
|
|
5545
|
-
localStorage.setItem("dsh-live-voice.settings", JSON.stringify(settings2));
|
|
5546
|
-
} catch {
|
|
5547
|
-
}
|
|
5548
|
-
for (const other of controllers.values()) {
|
|
5549
|
-
if (other !== entry && !other.closed) other.applySettings(settings2);
|
|
5550
|
-
}
|
|
5722
|
+
return savePreferences(controller, next);
|
|
5551
5723
|
};
|
|
5552
5724
|
for (const method of ["stopListening", "cancelDictation", "endConversation", "stopSpeech"]) {
|
|
5553
5725
|
const original = controller[method].bind(controller);
|
|
@@ -5572,7 +5744,12 @@ function apply(ctx) {
|
|
|
5572
5744
|
return ownership.run(
|
|
5573
5745
|
controller,
|
|
5574
5746
|
[...controllers.values()].map((other) => other.controller).concat([...retiring]),
|
|
5575
|
-
() => {
|
|
5747
|
+
async () => {
|
|
5748
|
+
try {
|
|
5749
|
+
await preferences.ready;
|
|
5750
|
+
} catch {
|
|
5751
|
+
throw new Error(t("dsh-live-voice.settings.persistence.loadError"));
|
|
5752
|
+
}
|
|
5576
5753
|
if (disposed || entry.closed || !entry.refs || request !== entry.request) return;
|
|
5577
5754
|
if (method !== "speak" && !entry.composers.size) return;
|
|
5578
5755
|
const result = original(...args);
|
|
@@ -5583,12 +5760,16 @@ function apply(ctx) {
|
|
|
5583
5760
|
};
|
|
5584
5761
|
}
|
|
5585
5762
|
controllers.set(key, entry);
|
|
5763
|
+
void preferences.ready.catch(() => {
|
|
5764
|
+
if (!entry.closed)
|
|
5765
|
+
controller.patch({ error: t("dsh-live-voice.settings.persistence.loadError") });
|
|
5766
|
+
});
|
|
5586
5767
|
run(controller, controller.refreshCapabilities());
|
|
5587
5768
|
return entry;
|
|
5588
5769
|
}
|
|
5589
5770
|
function useEntry(sessionId, kind) {
|
|
5590
|
-
const [entry, setEntry] =
|
|
5591
|
-
|
|
5771
|
+
const [entry, setEntry] = import_react54.default.useState(null);
|
|
5772
|
+
import_react54.default.useLayoutEffect(() => {
|
|
5592
5773
|
if (disposed) return;
|
|
5593
5774
|
const current = get(sessionId);
|
|
5594
5775
|
current.refs++;
|
|
@@ -5608,14 +5789,14 @@ function apply(ctx) {
|
|
|
5608
5789
|
function useComposer(entry, props) {
|
|
5609
5790
|
const subscribedInput = props.useInput?.((value) => value);
|
|
5610
5791
|
const input = subscribedInput ?? props.input;
|
|
5611
|
-
const token =
|
|
5612
|
-
|
|
5792
|
+
const token = import_react54.default.useRef({});
|
|
5793
|
+
import_react54.default.useLayoutEffect(() => {
|
|
5613
5794
|
if (!entry || entry.closed || disposed) return;
|
|
5614
5795
|
return () => {
|
|
5615
5796
|
entry.composers.delete(token.current);
|
|
5616
5797
|
};
|
|
5617
5798
|
}, [entry]);
|
|
5618
|
-
|
|
5799
|
+
import_react54.default.useLayoutEffect(() => {
|
|
5619
5800
|
if (!entry || entry.closed || disposed) return;
|
|
5620
5801
|
if (!input || typeof props.inputActions?.setDraft !== "function") return;
|
|
5621
5802
|
entry.composers.set(token.current, {
|
|
@@ -5641,13 +5822,17 @@ function apply(ctx) {
|
|
|
5641
5822
|
if (voiceModeActive && !entry.controller.getSnapshot().conversation)
|
|
5642
5823
|
run(entry.controller, entry.controller.startConversation());
|
|
5643
5824
|
}, [entry, input, props.inputActions]);
|
|
5644
|
-
|
|
5825
|
+
import_react54.default.useLayoutEffect(() => {
|
|
5645
5826
|
if (!entry || entry.closed || disposed || !input) return;
|
|
5646
5827
|
const published = typeof input.draft === "string" ? input.draft : "";
|
|
5828
|
+
entry.publishedDraft = published;
|
|
5829
|
+
entry.publishedRevision = input.draftRev;
|
|
5830
|
+
const newerRevision = Number.isInteger(input.draftRev) && Number.isInteger(entry.pendingRevision) && input.draftRev > entry.pendingRevision;
|
|
5647
5831
|
if (entry.pendingDraft === void 0) entry.draft = published;
|
|
5648
|
-
else if (published === entry.pendingDraft) {
|
|
5832
|
+
else if (published === entry.pendingDraft || newerRevision || !entry.staleDrafts.has(published)) {
|
|
5649
5833
|
entry.draft = published;
|
|
5650
5834
|
entry.pendingDraft = void 0;
|
|
5835
|
+
entry.staleDrafts.clear();
|
|
5651
5836
|
}
|
|
5652
5837
|
entry.controller.composerChanged(entry.draft);
|
|
5653
5838
|
});
|
|
@@ -5655,19 +5840,12 @@ function apply(ctx) {
|
|
|
5655
5840
|
function Buttons(props) {
|
|
5656
5841
|
const entry = useEntry(props.sessionId, "buttons");
|
|
5657
5842
|
useComposer(entry, props);
|
|
5658
|
-
return entry ? /* @__PURE__ */
|
|
5843
|
+
return entry ? /* @__PURE__ */ import_react54.default.createElement(ConversationControls, { controller: entry.controller }) : null;
|
|
5659
5844
|
}
|
|
5660
5845
|
function Settings() {
|
|
5661
|
-
const [controller, setController] =
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
try {
|
|
5665
|
-
settings = normalizeSettings(
|
|
5666
|
-
JSON.parse(localStorage.getItem("dsh-live-voice.settings") || "{}")
|
|
5667
|
-
);
|
|
5668
|
-
} catch {
|
|
5669
|
-
settings = normalizeSettings(null);
|
|
5670
|
-
}
|
|
5846
|
+
const [controller, setController] = import_react54.default.useState(null);
|
|
5847
|
+
import_react54.default.useEffect(() => {
|
|
5848
|
+
const settings = preferences.getSnapshot();
|
|
5671
5849
|
const browser = new BrowserSpeakingEngine({ lang: settings.lang });
|
|
5672
5850
|
const qwen = new QwenHttpSpeakingEngine({ lang: settings.lang });
|
|
5673
5851
|
const meter = new MicrophoneMeter();
|
|
@@ -5693,9 +5871,21 @@ function apply(ctx) {
|
|
|
5693
5871
|
c.speak = (...args) => ownership.run(
|
|
5694
5872
|
c,
|
|
5695
5873
|
[...controllers.values()].map((entry) => entry.controller).concat([...retiring]),
|
|
5696
|
-
() =>
|
|
5874
|
+
async () => {
|
|
5875
|
+
try {
|
|
5876
|
+
await preferences.ready;
|
|
5877
|
+
} catch {
|
|
5878
|
+
throw new Error(t("dsh-live-voice.settings.persistence.loadError"));
|
|
5879
|
+
}
|
|
5880
|
+
if (!disposed && !c.disposed) return speak(...args);
|
|
5881
|
+
}
|
|
5697
5882
|
);
|
|
5698
5883
|
const update = c.updateSettings.bind(c);
|
|
5884
|
+
c.applySavedSettings = update;
|
|
5885
|
+
settingsControllers.add(c);
|
|
5886
|
+
void preferences.ready.catch(
|
|
5887
|
+
() => c.patch({ error: t("dsh-live-voice.settings.persistence.loadError") })
|
|
5888
|
+
);
|
|
5699
5889
|
let settingsRevision = 0;
|
|
5700
5890
|
c.updateSettings = async (next) => {
|
|
5701
5891
|
const revision = ++settingsRevision;
|
|
@@ -5704,10 +5894,11 @@ function apply(ctx) {
|
|
|
5704
5894
|
qwen.lang = c.getSnapshot().settings.lang;
|
|
5705
5895
|
const settings2 = c.getSnapshot().settings;
|
|
5706
5896
|
if (changesRecognition(next)) c.replaceRecognition(recognitionFor(settings2, c.meter));
|
|
5707
|
-
|
|
5897
|
+
const saved = savePreferences(c, next);
|
|
5708
5898
|
run(c, c.refreshCapabilities());
|
|
5709
5899
|
const active = [...controllers.values()];
|
|
5710
|
-
await Promise.
|
|
5900
|
+
const [savedSettings] = await Promise.all([
|
|
5901
|
+
saved,
|
|
5711
5902
|
c.endConversation(),
|
|
5712
5903
|
...active.map((entry) => entry.controller.endConversation())
|
|
5713
5904
|
]);
|
|
@@ -5715,8 +5906,10 @@ function apply(ctx) {
|
|
|
5715
5906
|
for (const entry of controllers.values()) {
|
|
5716
5907
|
if (entry.closed) continue;
|
|
5717
5908
|
if (changesRecognition(next))
|
|
5718
|
-
entry.controller.replaceRecognition(
|
|
5719
|
-
|
|
5909
|
+
entry.controller.replaceRecognition(
|
|
5910
|
+
recognitionFor(savedSettings, entry.controller.meter)
|
|
5911
|
+
);
|
|
5912
|
+
entry.applySettings(savedSettings);
|
|
5720
5913
|
run(entry.controller, entry.controller.refreshCapabilities());
|
|
5721
5914
|
}
|
|
5722
5915
|
};
|
|
@@ -5726,24 +5919,25 @@ function apply(ctx) {
|
|
|
5726
5919
|
refresh();
|
|
5727
5920
|
return () => {
|
|
5728
5921
|
document.removeEventListener("dsh-live-voice:capabilitieschanged", refresh);
|
|
5922
|
+
settingsControllers.delete(c);
|
|
5729
5923
|
void c.dispose();
|
|
5730
5924
|
};
|
|
5731
5925
|
}, []);
|
|
5732
|
-
return controller ? /* @__PURE__ */
|
|
5926
|
+
return controller ? /* @__PURE__ */ import_react54.default.createElement(SettingsPanel, { controller }) : null;
|
|
5733
5927
|
}
|
|
5734
5928
|
registerSettingsSlot(ctx, Settings, () => t("dsh-live-voice.commons.pluginName"));
|
|
5735
5929
|
function Dock(props) {
|
|
5736
5930
|
const entry = useEntry(props.sessionId, "dock");
|
|
5737
5931
|
useComposer(entry, props);
|
|
5738
|
-
return entry ? /* @__PURE__ */
|
|
5932
|
+
return entry ? /* @__PURE__ */ import_react54.default.createElement(ConversationStatusBar, { controller: entry.controller }) : null;
|
|
5739
5933
|
}
|
|
5740
5934
|
function QuestionStatusView({ entry }) {
|
|
5741
|
-
const snapshot =
|
|
5935
|
+
const snapshot = import_react54.default.useSyncExternalStore(
|
|
5742
5936
|
entry.controller.subscribe,
|
|
5743
5937
|
entry.controller.getSnapshot
|
|
5744
5938
|
);
|
|
5745
|
-
const [overlayStyle, setOverlayStyle] =
|
|
5746
|
-
|
|
5939
|
+
const [overlayStyle, setOverlayStyle] = import_react54.default.useState();
|
|
5940
|
+
import_react54.default.useLayoutEffect(() => {
|
|
5747
5941
|
if (!snapshot.answeringQuestion) return;
|
|
5748
5942
|
const seat = document.querySelector("[data-composer-seat]");
|
|
5749
5943
|
if (!seat) return;
|
|
@@ -5765,7 +5959,7 @@ function apply(ctx) {
|
|
|
5765
5959
|
}, [snapshot.answeringQuestion]);
|
|
5766
5960
|
const target = typeof document === "undefined" ? null : document.body;
|
|
5767
5961
|
return snapshot.answeringQuestion && target && overlayStyle ? (0, import_react_dom.createPortal)(
|
|
5768
|
-
/* @__PURE__ */
|
|
5962
|
+
/* @__PURE__ */ import_react54.default.createElement(
|
|
5769
5963
|
ConversationStatusBar,
|
|
5770
5964
|
{
|
|
5771
5965
|
controller: entry.controller,
|
|
@@ -5779,19 +5973,19 @@ function apply(ctx) {
|
|
|
5779
5973
|
}
|
|
5780
5974
|
function QuestionStatus(props) {
|
|
5781
5975
|
const entry = useEntry(props.sessionId, "question-status");
|
|
5782
|
-
return entry ? /* @__PURE__ */
|
|
5976
|
+
return entry ? /* @__PURE__ */ import_react54.default.createElement(QuestionStatusView, { entry }) : null;
|
|
5783
5977
|
}
|
|
5784
5978
|
function ActionView({ entry, messageId }) {
|
|
5785
|
-
const snapshot =
|
|
5979
|
+
const snapshot = import_react54.default.useSyncExternalStore(
|
|
5786
5980
|
entry.controller.subscribe,
|
|
5787
5981
|
entry.controller.getSnapshot
|
|
5788
5982
|
);
|
|
5789
|
-
const chat =
|
|
5983
|
+
const chat = import_react54.default.useSyncExternalStore(entry.subscribeChat, entry.readChat);
|
|
5790
5984
|
const message2 = addressedTurn(assistantMessages(chat), messageId);
|
|
5791
5985
|
const capability = snapshot.capabilities[snapshot.settings.engine];
|
|
5792
5986
|
const active = snapshot.speaking && message2.id === snapshot.activeMessageId;
|
|
5793
5987
|
const unavailable = capability?.supported !== true;
|
|
5794
|
-
return /* @__PURE__ */
|
|
5988
|
+
return /* @__PURE__ */ import_react54.default.createElement(
|
|
5795
5989
|
SpeakButton,
|
|
5796
5990
|
{
|
|
5797
5991
|
active,
|
|
@@ -5806,7 +6000,7 @@ function apply(ctx) {
|
|
|
5806
6000
|
}
|
|
5807
6001
|
function Action(props) {
|
|
5808
6002
|
const entry = useEntry(props.sessionId, "action");
|
|
5809
|
-
return entry ? /* @__PURE__ */
|
|
6003
|
+
return entry ? /* @__PURE__ */ import_react54.default.createElement(ActionView, { entry, messageId: props.messageId }) : null;
|
|
5810
6004
|
}
|
|
5811
6005
|
ctx.effect(() => {
|
|
5812
6006
|
const style = document.createElement("style");
|