dsh-live-voice 0.5.1 → 0.5.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/lib/client.js CHANGED
@@ -34,7 +34,7 @@ __export(apply_exports, {
34
34
  inject: () => inject
35
35
  });
36
36
  module.exports = __toCommonJS(apply_exports);
37
- var import_react63 = __toESM(require("react"), 1);
37
+ var import_react64 = __toESM(require("react"), 1);
38
38
 
39
39
  // src/modules/speak/services/sessionSpeechMemory.ts
40
40
  var SessionSpeechMemory = class {
@@ -89,7 +89,7 @@ var SessionSpeechMemory = class {
89
89
  };
90
90
 
91
91
  // src/modules/recognition/vad/assets.ts
92
- var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.5.1" : "test") + "/";
92
+ var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.5.2" : "test") + "/";
93
93
  async function sileroVadAvailable(fetchImpl = globalThis.fetch) {
94
94
  try {
95
95
  const response = await fetchImpl(sileroVadAssetBase() + "availability", {
@@ -527,6 +527,9 @@ var en = {
527
527
  "dsh-live-voice.recognition.holdToTalk.help": "While a composer is open, hold Control anywhere on the page to capture speech. Release it to flush queued transcription, wait the configured send delay, queue the message, and close voice capture. Press Escape while holding to cancel.",
528
528
  "dsh-live-voice.recognition.language.automatic": "Automatic \u2014 detect language",
529
529
  "dsh-live-voice.recognition.language.label": "Recognition language",
530
+ "dsh-live-voice.recognition.language.options.chinese": "Chinese (Mandarin)",
531
+ "dsh-live-voice.recognition.language.options.french": "French",
532
+ "dsh-live-voice.recognition.language.options.spanish": "Spanish",
530
533
  "dsh-live-voice.recognition.manualSend.help": "Recognized text stays in the composer until you use the normal DSH Send control.",
531
534
  "dsh-live-voice.recognition.maxUtterance.help": "If speech never pauses, start a new transcription chunk after this duration. Default: 60 seconds.",
532
535
  "dsh-live-voice.recognition.maxUtterance.label": "Maximum continuous speech (seconds)",
@@ -621,6 +624,10 @@ var en = {
621
624
  "dsh-live-voice.settings.layout.voice": "Voice & engine",
622
625
  "dsh-live-voice.settings.persistence.loadError": "Could not load Live Voice settings from the server. Reload to try again.",
623
626
  "dsh-live-voice.settings.persistence.saveError": "Could not save Live Voice settings on the server. Please try again.",
627
+ "dsh-live-voice.settings.sourceNames.help": "When both sources capture, these names label new transcript blocks, not individual participants. Saved on blur; blank restores Me / Them. Maximum 80 characters. Existing text is unchanged.",
628
+ "dsh-live-voice.settings.sourceNames.microphone": "Microphone source name",
629
+ "dsh-live-voice.settings.sourceNames.shared": "Shared-audio source name",
630
+ "dsh-live-voice.settings.sourceNames.title": "Meeting source names",
624
631
  "dsh-live-voice.settings.tabs.conversation": "Conversation",
625
632
  "dsh-live-voice.settings.tabs.recognition": "Speech recognition",
626
633
  "dsh-live-voice.settings.tabs.speak": "Speech",
@@ -640,6 +647,7 @@ var en = {
640
647
  "dsh-live-voice.speak.autoPlayback.remainingOther": "Automatic assistant speech: {state} \u2014 {count} speech segments remaining",
641
648
  "dsh-live-voice.speak.autoPlayback.status": "Automatic assistant speech: {state}",
642
649
  "dsh-live-voice.speak.browser.automaticVoice": "Automatic local voice",
650
+ "dsh-live-voice.speak.browser.defaultVoice": "default",
643
651
  "dsh-live-voice.speak.browser.label": "Browser speech \u2014 device audio",
644
652
  "dsh-live-voice.speak.browser.name": "Browser speech",
645
653
  "dsh-live-voice.speak.browser.outputHelp": "Browser speech synthesis may ignore the selected output device; this browser API normally follows the system default.",
@@ -707,7 +715,9 @@ var en = {
707
715
  "dsh-live-voice.speak.segmentGap.help": "Wait this many milliseconds between consecutive spoken segments. 400 ms is the default.",
708
716
  "dsh-live-voice.speak.segmentGap.label": "Pause between speech segments",
709
717
  "dsh-live-voice.speak.status.paused": "Speech paused",
710
- "dsh-live-voice.speak.status.playing": "Speaking"
718
+ "dsh-live-voice.speak.status.playing": "Speaking",
719
+ "dsh-live-voice.speak.volume.help": "Speech playback only: 0% is silent; 100% is full volume (default). Microphone input is unchanged. Browser speech changes apply from the next segment.",
720
+ "dsh-live-voice.speak.volume.label": "Speech volume"
711
721
  };
712
722
  var en_default = Object.freeze(en);
713
723
 
@@ -815,6 +825,9 @@ var es = {
815
825
  "dsh-live-voice.recognition.holdToTalk.help": "Con un cuadro de mensaje abierto, mant\xE9n Control pulsado en cualquier parte de la p\xE1gina para capturar tu voz. Al soltarlo, se procesa la transcripci\xF3n pendiente, se espera el retraso de env\xEDo configurado, se a\xF1ade el mensaje a la cola y se cierra la captura de voz. Pulsa Escape mientras mantienes Control para cancelar.",
816
826
  "dsh-live-voice.recognition.language.automatic": "Autom\xE1tico \u2014 detectar idioma",
817
827
  "dsh-live-voice.recognition.language.label": "Idioma de reconocimiento",
828
+ "dsh-live-voice.recognition.language.options.chinese": "Chino (mandar\xEDn)",
829
+ "dsh-live-voice.recognition.language.options.french": "Franc\xE9s",
830
+ "dsh-live-voice.recognition.language.options.spanish": "Espa\xF1ol",
818
831
  "dsh-live-voice.recognition.manualSend.help": "El texto reconocido permanece en el cuadro de mensaje hasta que utilices el bot\xF3n de env\xEDo habitual de DSH.",
819
832
  "dsh-live-voice.recognition.maxUtterance.help": "Si hablas sin hacer pausas, se inicia un nuevo fragmento de transcripci\xF3n tras este intervalo. Valor predeterminado: 60 segundos.",
820
833
  "dsh-live-voice.recognition.maxUtterance.label": "Duraci\xF3n m\xE1xima del habla continua (segundos)",
@@ -909,6 +922,10 @@ var es = {
909
922
  "dsh-live-voice.settings.layout.voice": "Voz y motor",
910
923
  "dsh-live-voice.settings.persistence.loadError": "No se pudieron cargar los ajustes de Voz en vivo del servidor. Recarga para volver a intentarlo.",
911
924
  "dsh-live-voice.settings.persistence.saveError": "No se pudieron guardar los ajustes de Voz en vivo en el servidor. Int\xE9ntalo de nuevo.",
925
+ "dsh-live-voice.settings.sourceNames.help": "Cuando ambas fuentes capturan, estos nombres etiquetan nuevos bloques de transcripci\xF3n, no participantes individuales. Se guardan al salir del campo; vac\xEDo restaura Me / Them. M\xE1ximo 80 caracteres. El texto existente no cambia.",
926
+ "dsh-live-voice.settings.sourceNames.microphone": "Nombre de la fuente del micr\xF3fono",
927
+ "dsh-live-voice.settings.sourceNames.shared": "Nombre de la fuente del audio compartido",
928
+ "dsh-live-voice.settings.sourceNames.title": "Nombres de fuentes de la reuni\xF3n",
912
929
  "dsh-live-voice.settings.tabs.conversation": "Conversaci\xF3n",
913
930
  "dsh-live-voice.settings.tabs.recognition": "Reconocimiento de voz",
914
931
  "dsh-live-voice.settings.tabs.speak": "S\xEDntesis de voz",
@@ -928,6 +945,7 @@ var es = {
928
945
  "dsh-live-voice.speak.autoPlayback.remainingOther": "Lectura autom\xE1tica del asistente: {state} \u2014 quedan {count} segmentos de voz",
929
946
  "dsh-live-voice.speak.autoPlayback.status": "Lectura autom\xE1tica del asistente: {state}",
930
947
  "dsh-live-voice.speak.browser.automaticVoice": "Voz local autom\xE1tica",
948
+ "dsh-live-voice.speak.browser.defaultVoice": "predeterminada",
931
949
  "dsh-live-voice.speak.browser.label": "S\xEDntesis de voz del navegador \u2014 audio en este dispositivo",
932
950
  "dsh-live-voice.speak.browser.name": "S\xEDntesis de voz del navegador",
933
951
  "dsh-live-voice.speak.browser.outputHelp": "La s\xEDntesis de voz del navegador puede ignorar el dispositivo de salida seleccionado; esta API del navegador normalmente utiliza el predeterminado del sistema.",
@@ -995,7 +1013,9 @@ var es = {
995
1013
  "dsh-live-voice.speak.segmentGap.help": "Espera esta cantidad de milisegundos entre segmentos hablados consecutivos. El valor predeterminado es 400 ms.",
996
1014
  "dsh-live-voice.speak.segmentGap.label": "Pausa entre segmentos de voz",
997
1015
  "dsh-live-voice.speak.status.paused": "Lectura en pausa",
998
- "dsh-live-voice.speak.status.playing": "Hablando"
1016
+ "dsh-live-voice.speak.status.playing": "Hablando",
1017
+ "dsh-live-voice.speak.volume.help": "Solo reproducci\xF3n de voz: 0% es silencio; 100% es el volumen m\xE1ximo (predeterminado). El micr\xF3fono no cambia. En Browser, los cambios se aplican desde el siguiente segmento.",
1018
+ "dsh-live-voice.speak.volume.label": "Volumen de voz"
999
1019
  };
1000
1020
  var es_default = Object.freeze(es);
1001
1021
 
@@ -1103,6 +1123,9 @@ var fr = {
1103
1123
  "dsh-live-voice.recognition.holdToTalk.help": "Lorsqu\u2019une zone de r\xE9daction est ouverte, maintenez Ctrl n\u2019importe o\xF9 sur la page pour capturer votre voix. Rel\xE2chez la touche pour traiter la transcription en attente, attendre le d\xE9lai d\u2019envoi configur\xE9, mettre le message en file d\u2019attente et arr\xEAter la capture vocale. Appuyez sur \xC9chap tout en maintenant Ctrl pour annuler.",
1104
1124
  "dsh-live-voice.recognition.language.automatic": "Automatique \u2014 d\xE9tecter la langue",
1105
1125
  "dsh-live-voice.recognition.language.label": "Langue de reconnaissance",
1126
+ "dsh-live-voice.recognition.language.options.chinese": "Chinois (mandarin)",
1127
+ "dsh-live-voice.recognition.language.options.french": "Fran\xE7ais",
1128
+ "dsh-live-voice.recognition.language.options.spanish": "Espagnol",
1106
1129
  "dsh-live-voice.recognition.manualSend.help": "Le texte reconnu reste dans la zone de r\xE9daction jusqu\u2019\xE0 ce que vous utilisiez le bouton d\u2019envoi habituel de DSH.",
1107
1130
  "dsh-live-voice.recognition.maxUtterance.help": "Si vous parlez sans pause, un nouveau segment de transcription commence apr\xE8s cette dur\xE9e. Valeur par d\xE9faut : 60 secondes.",
1108
1131
  "dsh-live-voice.recognition.maxUtterance.label": "Dur\xE9e maximale de parole continue (secondes)",
@@ -1197,6 +1220,10 @@ var fr = {
1197
1220
  "dsh-live-voice.settings.layout.voice": "Voix et moteur",
1198
1221
  "dsh-live-voice.settings.persistence.loadError": "Impossible de charger les param\xE8tres Voix en direct depuis le serveur. Rechargez pour r\xE9essayer.",
1199
1222
  "dsh-live-voice.settings.persistence.saveError": "Impossible de sauvegarder les param\xE8tres Voix en direct sur le serveur. R\xE9essayez.",
1223
+ "dsh-live-voice.settings.sourceNames.help": "Lorsque les deux sources capturent, ces noms identifient les nouveaux blocs de transcription, pas les participants individuels. Enregistr\xE9s en quittant le champ ; vide r\xE9tablit Me / Them. Maximum 80 caract\xE8res. Le texte existant reste inchang\xE9.",
1224
+ "dsh-live-voice.settings.sourceNames.microphone": "Nom de la source microphone",
1225
+ "dsh-live-voice.settings.sourceNames.shared": "Nom de la source audio partag\xE9",
1226
+ "dsh-live-voice.settings.sourceNames.title": "Noms des sources de r\xE9union",
1200
1227
  "dsh-live-voice.settings.tabs.conversation": "Conversation",
1201
1228
  "dsh-live-voice.settings.tabs.recognition": "Reconnaissance vocale",
1202
1229
  "dsh-live-voice.settings.tabs.speak": "Synth\xE8se vocale",
@@ -1216,6 +1243,7 @@ var fr = {
1216
1243
  "dsh-live-voice.speak.autoPlayback.remainingOther": "Lecture automatique de l\u2019assistant : {state} \u2014 {count} segments vocaux restants",
1217
1244
  "dsh-live-voice.speak.autoPlayback.status": "Lecture automatique de l\u2019assistant : {state}",
1218
1245
  "dsh-live-voice.speak.browser.automaticVoice": "Voix locale automatique",
1246
+ "dsh-live-voice.speak.browser.defaultVoice": "par d\xE9faut",
1219
1247
  "dsh-live-voice.speak.browser.label": "Synth\xE8se vocale du navigateur \u2014 son sur cet appareil",
1220
1248
  "dsh-live-voice.speak.browser.name": "Synth\xE8se vocale du navigateur",
1221
1249
  "dsh-live-voice.speak.browser.outputHelp": "La synth\xE8se vocale du navigateur peut ignorer le p\xE9riph\xE9rique de sortie s\xE9lectionn\xE9 ; cette API du navigateur utilise normalement celui d\xE9fini par d\xE9faut sur le syst\xE8me.",
@@ -1283,7 +1311,9 @@ var fr = {
1283
1311
  "dsh-live-voice.speak.segmentGap.help": "Attend ce nombre de millisecondes entre des segments vocaux cons\xE9cutifs. La valeur par d\xE9faut est 400 ms.",
1284
1312
  "dsh-live-voice.speak.segmentGap.label": "Pause entre les segments vocaux",
1285
1313
  "dsh-live-voice.speak.status.paused": "Lecture en pause",
1286
- "dsh-live-voice.speak.status.playing": "Lecture en cours"
1314
+ "dsh-live-voice.speak.status.playing": "Lecture en cours",
1315
+ "dsh-live-voice.speak.volume.help": "Lecture vocale uniquement : 0% est silencieux ; 100% est le volume maximal (par d\xE9faut). Le microphone reste inchang\xE9. Dans Browser, les changements prennent effet au segment suivant.",
1316
+ "dsh-live-voice.speak.volume.label": "Volume de la voix"
1287
1317
  };
1288
1318
  var fr_default = Object.freeze(fr);
1289
1319
 
@@ -1391,6 +1421,9 @@ var hi = {
1391
1421
  "dsh-live-voice.recognition.holdToTalk.help": "\u0938\u0902\u0926\u0947\u0936 \u092C\u0949\u0915\u094D\u0938 \u0916\u0941\u0932\u093E \u0939\u094B\u0928\u0947 \u092A\u0930, \u092A\u0947\u091C \u092A\u0930 \u0915\u0939\u0940\u0902 \u092D\u0940 Control \u0926\u092C\u093E\u090F \u0930\u0916\u0915\u0930 \u0906\u0935\u093E\u091C\u093C \u0930\u093F\u0915\u0949\u0930\u094D\u0921 \u0915\u0930\u0947\u0902\u0964 \u091B\u094B\u0921\u093C\u0928\u0947 \u092A\u0930 \u0915\u0924\u093E\u0930 \u092E\u0947\u0902 \u092E\u094C\u091C\u0942\u0926 \u091F\u094D\u0930\u093E\u0902\u0938\u0915\u094D\u0930\u093F\u092A\u094D\u0936\u0928 \u092A\u0942\u0930\u093E \u0915\u093F\u092F\u093E \u091C\u093E\u0924\u093E \u0939\u0948, \u0924\u092F \u092D\u0947\u091C\u0928\u0947 \u0915\u0947 \u0935\u093F\u0932\u0902\u092C \u0924\u0915 \u092A\u094D\u0930\u0924\u0940\u0915\u094D\u0937\u093E \u0939\u094B\u0924\u0940 \u0939\u0948, \u0938\u0902\u0926\u0947\u0936 \u0915\u0924\u093E\u0930 \u092E\u0947\u0902 \u091C\u0941\u0921\u093C\u0924\u093E \u0939\u0948 \u0914\u0930 \u0906\u0935\u093E\u091C\u093C \u0930\u093F\u0915\u0949\u0930\u094D\u0921 \u0915\u0930\u0928\u093E \u092C\u0902\u0926 \u0939\u094B \u091C\u093E\u0924\u093E \u0939\u0948\u0964 \u0930\u0926\u094D\u0926 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F Control \u0926\u092C\u093E\u090F \u0930\u0916\u0924\u0947 \u0939\u0941\u090F Escape \u0926\u092C\u093E\u090F\u0901\u0964",
1392
1422
  "dsh-live-voice.recognition.language.automatic": "\u0938\u094D\u0935\u091A\u093E\u0932\u093F\u0924 \u2014 \u092D\u093E\u0937\u093E \u092A\u0939\u091A\u093E\u0928\u0947\u0902",
1393
1423
  "dsh-live-voice.recognition.language.label": "\u0935\u093E\u0915\u094D \u092A\u0939\u091A\u093E\u0928 \u0915\u0940 \u092D\u093E\u0937\u093E",
1424
+ "dsh-live-voice.recognition.language.options.chinese": "\u091A\u0940\u0928\u0940 (\u092E\u0902\u0926\u093E\u0930\u093F\u0928)",
1425
+ "dsh-live-voice.recognition.language.options.french": "\u092B\u093C\u094D\u0930\u0947\u0902\u091A",
1426
+ "dsh-live-voice.recognition.language.options.spanish": "\u0938\u094D\u092A\u0947\u0928\u093F\u0936",
1394
1427
  "dsh-live-voice.recognition.manualSend.help": "\u092A\u0939\u091A\u093E\u0928\u093E \u0917\u092F\u093E \u092A\u093E\u0920 \u0938\u0902\u0926\u0947\u0936 \u092C\u0949\u0915\u094D\u0938 \u092E\u0947\u0902 \u0924\u092C \u0924\u0915 \u0930\u0939\u0924\u093E \u0939\u0948 \u091C\u092C \u0924\u0915 \u0906\u092A DSH \u0915\u0947 \u0938\u093E\u092E\u093E\u0928\u094D\u092F \u092D\u0947\u091C\u0947\u0902 \u092C\u091F\u0928 \u0915\u093E \u0909\u092A\u092F\u094B\u0917 \u0928\u0939\u0940\u0902 \u0915\u0930\u0924\u0947\u0964",
1395
1428
  "dsh-live-voice.recognition.maxUtterance.help": "\u092F\u0926\u093F \u092C\u094B\u0932\u0928\u093E \u092C\u093F\u0928\u093E \u0930\u0941\u0915\u0947 \u091C\u093E\u0930\u0940 \u0930\u0939\u0947, \u0924\u094B \u0907\u0938 \u0905\u0935\u0927\u093F \u0915\u0947 \u092C\u093E\u0926 \u0928\u092F\u093E \u091F\u094D\u0930\u093E\u0902\u0938\u0915\u094D\u0930\u093F\u092A\u094D\u0936\u0928 \u0916\u0902\u0921 \u0936\u0941\u0930\u0942 \u0915\u0930\u0947\u0902\u0964 \u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F: 60 \u0938\u0947\u0915\u0902\u0921\u0964",
1396
1429
  "dsh-live-voice.recognition.maxUtterance.label": "\u0932\u0917\u093E\u0924\u093E\u0930 \u092C\u094B\u0932\u0928\u0947 \u0915\u0940 \u0905\u0927\u093F\u0915\u0924\u092E \u0905\u0935\u0927\u093F (\u0938\u0947\u0915\u0902\u0921)",
@@ -1485,6 +1518,10 @@ var hi = {
1485
1518
  "dsh-live-voice.settings.layout.voice": "\u0906\u0935\u093E\u091C\u093C \u0914\u0930 \u0907\u0902\u091C\u0928",
1486
1519
  "dsh-live-voice.settings.persistence.loadError": "\u0938\u0930\u094D\u0935\u0930 \u0938\u0947 \u0932\u093E\u0907\u0935 \u0935\u0949\u0907\u0938 \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",
1487
1520
  "dsh-live-voice.settings.persistence.saveError": "\u0938\u0930\u094D\u0935\u0930 \u092A\u0930 \u0932\u093E\u0907\u0935 \u0935\u0949\u0907\u0938 \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",
1521
+ "dsh-live-voice.settings.sourceNames.help": "\u091C\u092C \u0926\u094B\u0928\u094B\u0902 \u0938\u094D\u0930\u094B\u0924 \u0930\u093F\u0915\u0949\u0930\u094D\u0921 \u0915\u0930\u0924\u0947 \u0939\u0948\u0902, \u092F\u0947 \u0928\u093E\u092E \u0928\u090F \u091F\u094D\u0930\u093E\u0902\u0938\u0915\u094D\u0930\u093F\u092A\u094D\u091F \u092C\u094D\u0932\u0949\u0915 \u0915\u094B \u091A\u093F\u0939\u094D\u0928\u093F\u0924 \u0915\u0930\u0924\u0947 \u0939\u0948\u0902, \u0905\u0932\u0917-\u0905\u0932\u0917 \u092A\u094D\u0930\u0924\u093F\u092D\u093E\u0917\u093F\u092F\u094B\u0902 \u0915\u094B \u0928\u0939\u0940\u0902\u0964 \u092B\u093C\u0940\u0932\u094D\u0921 \u091B\u094B\u0921\u093C\u0928\u0947 \u092A\u0930 \u0938\u0939\u0947\u091C\u0947 \u091C\u093E\u0924\u0947 \u0939\u0948\u0902; \u0916\u093E\u0932\u0940 \u0930\u0916\u0928\u0947 \u092A\u0930 Me / Them \u0935\u093E\u092A\u0938 \u0906\u0924\u0947 \u0939\u0948\u0902\u0964 \u0905\u0927\u093F\u0915\u0924\u092E 80 \u0905\u0915\u094D\u0937\u0930\u0964 \u092E\u094C\u091C\u0942\u0926\u093E \u092A\u093E\u0920 \u0928\u0939\u0940\u0902 \u092C\u0926\u0932\u0924\u093E\u0964",
1522
+ "dsh-live-voice.settings.sourceNames.microphone": "\u092E\u093E\u0907\u0915\u094D\u0930\u094B\u092B\u093C\u094B\u0928 \u0938\u094D\u0930\u094B\u0924 \u0915\u093E \u0928\u093E\u092E",
1523
+ "dsh-live-voice.settings.sourceNames.shared": "\u0938\u093E\u091D\u093E \u0911\u0921\u093F\u092F\u094B \u0938\u094D\u0930\u094B\u0924 \u0915\u093E \u0928\u093E\u092E",
1524
+ "dsh-live-voice.settings.sourceNames.title": "\u092C\u0948\u0920\u0915 \u0938\u094D\u0930\u094B\u0924\u094B\u0902 \u0915\u0947 \u0928\u093E\u092E",
1488
1525
  "dsh-live-voice.settings.tabs.conversation": "\u092C\u093E\u0924\u091A\u0940\u0924",
1489
1526
  "dsh-live-voice.settings.tabs.recognition": "\u0935\u093E\u0915\u094D \u092A\u0939\u091A\u093E\u0928",
1490
1527
  "dsh-live-voice.settings.tabs.speak": "\u0935\u093E\u091A\u0928",
@@ -1504,6 +1541,7 @@ var hi = {
1504
1541
  "dsh-live-voice.speak.autoPlayback.remainingOther": "\u0938\u0939\u093E\u092F\u0915 \u0915\u093E \u0938\u094D\u0935\u091A\u093E\u0932\u093F\u0924 \u0935\u093E\u091A\u0928: {state} \u2014 {count} \u0935\u093E\u0915\u094D \u0916\u0902\u0921 \u0936\u0947\u0937 \u0939\u0948\u0902",
1505
1542
  "dsh-live-voice.speak.autoPlayback.status": "\u0938\u0939\u093E\u092F\u0915 \u0915\u093E \u0938\u094D\u0935\u091A\u093E\u0932\u093F\u0924 \u0935\u093E\u091A\u0928: {state}",
1506
1543
  "dsh-live-voice.speak.browser.automaticVoice": "\u0938\u094D\u0925\u093E\u0928\u0940\u092F \u0906\u0935\u093E\u091C\u093C \u0915\u093E \u0938\u094D\u0935\u091A\u093E\u0932\u093F\u0924 \u091A\u092F\u0928",
1544
+ "dsh-live-voice.speak.browser.defaultVoice": "\u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F",
1507
1545
  "dsh-live-voice.speak.browser.label": "\u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u0935\u093E\u091A\u0928 \u2014 \u0907\u0938 \u0921\u093F\u0935\u093E\u0907\u0938 \u092A\u0930 \u0911\u0921\u093F\u092F\u094B",
1508
1546
  "dsh-live-voice.speak.browser.name": "\u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u0935\u093E\u091A\u0928",
1509
1547
  "dsh-live-voice.speak.browser.outputHelp": "\u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u0915\u093E \u0935\u093E\u0915\u094D \u0938\u0902\u0936\u094D\u0932\u0947\u0937\u0923 \u091A\u0941\u0928\u0947 \u0917\u090F \u0906\u0909\u091F\u092A\u0941\u091F \u0921\u093F\u0935\u093E\u0907\u0938 \u0915\u094B \u0905\u0928\u0926\u0947\u0916\u093E \u0915\u0930 \u0938\u0915\u0924\u093E \u0939\u0948; \u092F\u0939 \u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 API \u0938\u093E\u092E\u093E\u0928\u094D\u092F\u0924\u0903 \u0938\u093F\u0938\u094D\u091F\u092E \u0915\u0947 \u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F \u0915\u093E \u0909\u092A\u092F\u094B\u0917 \u0915\u0930\u0924\u093E \u0939\u0948\u0964",
@@ -1571,7 +1609,9 @@ var hi = {
1571
1609
  "dsh-live-voice.speak.segmentGap.help": "\u0932\u0917\u093E\u0924\u093E\u0930 \u092C\u094B\u0932\u0947 \u0917\u090F \u0939\u093F\u0938\u094D\u0938\u094B\u0902 \u0915\u0947 \u092C\u0940\u091A \u0907\u0924\u0928\u0947 \u092E\u093F\u0932\u0940\u0938\u0947\u0915\u0902\u0921 \u0930\u0941\u0915\u0947\u0902\u0964 \u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F 400 ms \u0939\u0948\u0964",
1572
1610
  "dsh-live-voice.speak.segmentGap.label": "\u092C\u094B\u0932\u0947 \u0917\u090F \u0939\u093F\u0938\u094D\u0938\u094B\u0902 \u0915\u0947 \u092C\u0940\u091A \u0935\u093F\u0930\u093E\u092E",
1573
1611
  "dsh-live-voice.speak.status.paused": "\u0935\u093E\u091A\u0928 \u0920\u0939\u0930\u093E \u0939\u0941\u0906 \u0939\u0948",
1574
- "dsh-live-voice.speak.status.playing": "\u092A\u0922\u093C\u0915\u0930 \u0938\u0941\u0928\u093E\u092F\u093E \u091C\u093E \u0930\u0939\u093E \u0939\u0948"
1612
+ "dsh-live-voice.speak.status.playing": "\u092A\u0922\u093C\u0915\u0930 \u0938\u0941\u0928\u093E\u092F\u093E \u091C\u093E \u0930\u0939\u093E \u0939\u0948",
1613
+ "dsh-live-voice.speak.volume.help": "\u0915\u0947\u0935\u0932 \u0935\u093E\u0923\u0940 \u092A\u094D\u0932\u0947\u092C\u0948\u0915: 0% \u092E\u094C\u0928 \u0939\u0948; 100% \u092A\u0942\u0930\u094D\u0923 \u0906\u0935\u093E\u091C\u093C \u0939\u0948 (\u0921\u093F\u092B\u093C\u0949\u0932\u094D\u091F)\u0964 \u092E\u093E\u0907\u0915\u094D\u0930\u094B\u092B\u093C\u094B\u0928 \u0928\u0939\u0940\u0902 \u092C\u0926\u0932\u0924\u093E\u0964 Browser \u092E\u0947\u0902 \u092C\u0926\u0932\u093E\u0935 \u0905\u0917\u0932\u0947 \u0916\u0902\u0921 \u0938\u0947 \u0932\u093E\u0917\u0942 \u0939\u094B\u0924\u0947 \u0939\u0948\u0902\u0964",
1614
+ "dsh-live-voice.speak.volume.label": "\u0935\u093E\u0923\u0940 \u0915\u0940 \u0906\u0935\u093E\u091C\u093C \u0915\u093E \u0938\u094D\u0924\u0930"
1575
1615
  };
1576
1616
  var hi_default = Object.freeze(hi);
1577
1617
 
@@ -1679,6 +1719,9 @@ var ptBR = {
1679
1719
  "dsh-live-voice.recognition.holdToTalk.help": "Com o editor aberto, segure Control em qualquer lugar da p\xE1gina para capturar a fala. Solte para concluir as transcri\xE7\xF5es pendentes, aguardar o atraso configurado, enfileirar a mensagem e encerrar a captura. Pressione Escape enquanto segura para cancelar.",
1680
1720
  "dsh-live-voice.recognition.language.automatic": "Autom\xE1tico \u2014 detectar idioma",
1681
1721
  "dsh-live-voice.recognition.language.label": "Idioma de reconhecimento",
1722
+ "dsh-live-voice.recognition.language.options.chinese": "Chin\xEAs (mandarim)",
1723
+ "dsh-live-voice.recognition.language.options.french": "Franc\xEAs",
1724
+ "dsh-live-voice.recognition.language.options.spanish": "Espanhol",
1682
1725
  "dsh-live-voice.recognition.manualSend.help": "O texto reconhecido permanece no editor at\xE9 que voc\xEA use o bot\xE3o normal de envio do DSH.",
1683
1726
  "dsh-live-voice.recognition.maxUtterance.help": "Se a fala nunca pausar, inicia um novo trecho de transcri\xE7\xE3o ap\xF3s esta dura\xE7\xE3o. Padr\xE3o: 60 segundos.",
1684
1727
  "dsh-live-voice.recognition.maxUtterance.label": "Fala cont\xEDnua m\xE1xima (segundos)",
@@ -1773,6 +1816,10 @@ var ptBR = {
1773
1816
  "dsh-live-voice.settings.layout.voice": "Voz e mecanismo",
1774
1817
  "dsh-live-voice.settings.persistence.loadError": "N\xE3o foi poss\xEDvel carregar as configura\xE7\xF5es da Voz ao Vivo do servidor. Recarregue para tentar novamente.",
1775
1818
  "dsh-live-voice.settings.persistence.saveError": "N\xE3o foi poss\xEDvel salvar as configura\xE7\xF5es da Voz ao Vivo no servidor. Tente novamente.",
1819
+ "dsh-live-voice.settings.sourceNames.help": "Quando as duas fontes capturam, estes nomes identificam novos blocos da transcri\xE7\xE3o, n\xE3o participantes individuais. Salvos ao sair do campo; vazio restaura Me / Them. M\xE1ximo de 80 caracteres. O texto existente n\xE3o muda.",
1820
+ "dsh-live-voice.settings.sourceNames.microphone": "Nome da fonte do microfone",
1821
+ "dsh-live-voice.settings.sourceNames.shared": "Nome da fonte do \xE1udio compartilhado",
1822
+ "dsh-live-voice.settings.sourceNames.title": "Nomes das fontes da reuni\xE3o",
1776
1823
  "dsh-live-voice.settings.tabs.conversation": "Conversa",
1777
1824
  "dsh-live-voice.settings.tabs.recognition": "Reconhecimento de voz",
1778
1825
  "dsh-live-voice.settings.tabs.speak": "Fala",
@@ -1792,6 +1839,7 @@ var ptBR = {
1792
1839
  "dsh-live-voice.speak.autoPlayback.remainingOther": "Fala autom\xE1tica do assistente: {state} \u2014 restam {count} segmentos de fala",
1793
1840
  "dsh-live-voice.speak.autoPlayback.status": "Fala autom\xE1tica do assistente: {state}",
1794
1841
  "dsh-live-voice.speak.browser.automaticVoice": "Voz local autom\xE1tica",
1842
+ "dsh-live-voice.speak.browser.defaultVoice": "padr\xE3o",
1795
1843
  "dsh-live-voice.speak.browser.label": "Fala do navegador \u2014 \xE1udio neste dispositivo",
1796
1844
  "dsh-live-voice.speak.browser.name": "Fala do navegador",
1797
1845
  "dsh-live-voice.speak.browser.outputHelp": "A s\xEDntese de fala do navegador pode ignorar o dispositivo de sa\xEDda selecionado; esta API normalmente segue o padr\xE3o do sistema.",
@@ -1859,7 +1907,9 @@ var ptBR = {
1859
1907
  "dsh-live-voice.speak.segmentGap.help": "Aguarde esta quantidade de milissegundos entre trechos falados consecutivos. O padr\xE3o \xE9 400 ms.",
1860
1908
  "dsh-live-voice.speak.segmentGap.label": "Pausa entre trechos de fala",
1861
1909
  "dsh-live-voice.speak.status.paused": "Fala pausada",
1862
- "dsh-live-voice.speak.status.playing": "Falando"
1910
+ "dsh-live-voice.speak.status.playing": "Falando",
1911
+ "dsh-live-voice.speak.volume.help": "Somente reprodu\xE7\xE3o da fala: 0% \xE9 silencioso; 100% \xE9 o volume m\xE1ximo (padr\xE3o). O microfone n\xE3o \xE9 alterado. No Browser, mudan\xE7as valem a partir do pr\xF3ximo trecho.",
1912
+ "dsh-live-voice.speak.volume.label": "Volume da fala"
1863
1913
  };
1864
1914
  var pt_BR_default = Object.freeze(ptBR);
1865
1915
 
@@ -1967,6 +2017,9 @@ var zh = {
1967
2017
  "dsh-live-voice.recognition.holdToTalk.help": "\u6D88\u606F\u8F93\u5165\u6846\u6253\u5F00\u65F6\uFF0C\u5728\u9875\u9762\u4EFB\u610F\u4F4D\u7F6E\u6309\u4F4F Control \u952E\u5373\u53EF\u91C7\u96C6\u8BED\u97F3\u3002\u677E\u5F00\u540E\u4F1A\u5904\u7406\u5B8C\u961F\u5217\u4E2D\u7684\u8F6C\u5199\uFF0C\u7B49\u5F85\u8BBE\u5B9A\u7684\u53D1\u9001\u5EF6\u8FDF\uFF0C\u5C06\u6D88\u606F\u52A0\u5165\u961F\u5217\uFF0C\u7136\u540E\u5173\u95ED\u8BED\u97F3\u91C7\u96C6\u3002\u6309\u4F4F\u671F\u95F4\u6309 Escape \u952E\u53EF\u53D6\u6D88\u3002",
1968
2018
  "dsh-live-voice.recognition.language.automatic": "\u81EA\u52A8 \u2014 \u68C0\u6D4B\u8BED\u8A00",
1969
2019
  "dsh-live-voice.recognition.language.label": "\u8BC6\u522B\u8BED\u8A00",
2020
+ "dsh-live-voice.recognition.language.options.chinese": "\u4E2D\u6587\uFF08\u666E\u901A\u8BDD\uFF09",
2021
+ "dsh-live-voice.recognition.language.options.french": "\u6CD5\u8BED",
2022
+ "dsh-live-voice.recognition.language.options.spanish": "\u897F\u73ED\u7259\u8BED",
1970
2023
  "dsh-live-voice.recognition.manualSend.help": "\u8BC6\u522B\u51FA\u7684\u6587\u672C\u4F1A\u4FDD\u7559\u5728\u6D88\u606F\u8F93\u5165\u6846\u4E2D\uFF0C\u76F4\u5230\u4F60\u4F7F\u7528 DSH \u7684\u5E38\u89C4\u53D1\u9001\u6309\u94AE\u3002",
1971
2024
  "dsh-live-voice.recognition.maxUtterance.help": "\u5982\u679C\u8BF4\u8BDD\u4E00\u76F4\u6CA1\u6709\u505C\u987F\uFF0C\u5219\u5728\u6B64\u65F6\u957F\u540E\u5F00\u59CB\u65B0\u7684\u8F6C\u5199\u7247\u6BB5\u3002\u9ED8\u8BA4\u503C\uFF1A60 \u79D2\u3002",
1972
2025
  "dsh-live-voice.recognition.maxUtterance.label": "\u6700\u957F\u8FDE\u7EED\u8BED\u97F3\uFF08\u79D2\uFF09",
@@ -2061,6 +2114,10 @@ var zh = {
2061
2114
  "dsh-live-voice.settings.layout.voice": "\u58F0\u97F3\u4E0E\u5F15\u64CE",
2062
2115
  "dsh-live-voice.settings.persistence.loadError": "\u65E0\u6CD5\u4ECE\u670D\u52A1\u5668\u52A0\u8F7D \u5B9E\u65F6\u8BED\u97F3 \u8BBE\u7F6E\u3002\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002",
2063
2116
  "dsh-live-voice.settings.persistence.saveError": "\u65E0\u6CD5\u5728\u670D\u52A1\u5668\u4E0A\u4FDD\u5B58 \u5B9E\u65F6\u8BED\u97F3 \u8BBE\u7F6E\u3002\u8BF7\u91CD\u8BD5\u3002",
2117
+ "dsh-live-voice.settings.sourceNames.help": "\u540C\u65F6\u91C7\u96C6\u4E24\u4E2A\u97F3\u6E90\u65F6\uFF0C\u8FD9\u4E9B\u540D\u79F0\u6807\u8BB0\u65B0\u7684\u8F6C\u5F55\u6BB5\u843D\uFF0C\u800C\u975E\u5404\u4E2A\u53C2\u4F1A\u8005\u3002\u79BB\u5F00\u8F93\u5165\u6846\u65F6\u4FDD\u5B58\uFF1B\u7559\u7A7A\u6062\u590D Me / Them\u3002\u6700\u591A 80 \u4E2A\u5B57\u7B26\u3002\u73B0\u6709\u6587\u672C\u4E0D\u4F1A\u6539\u53D8\u3002",
2118
+ "dsh-live-voice.settings.sourceNames.microphone": "\u9EA6\u514B\u98CE\u97F3\u6E90\u540D\u79F0",
2119
+ "dsh-live-voice.settings.sourceNames.shared": "\u5171\u4EAB\u97F3\u9891\u97F3\u6E90\u540D\u79F0",
2120
+ "dsh-live-voice.settings.sourceNames.title": "\u4F1A\u8BAE\u97F3\u6E90\u540D\u79F0",
2064
2121
  "dsh-live-voice.settings.tabs.conversation": "\u5BF9\u8BDD",
2065
2122
  "dsh-live-voice.settings.tabs.recognition": "\u8BED\u97F3\u8BC6\u522B",
2066
2123
  "dsh-live-voice.settings.tabs.speak": "\u8BED\u97F3\u5408\u6210",
@@ -2080,6 +2137,7 @@ var zh = {
2080
2137
  "dsh-live-voice.speak.autoPlayback.remainingOther": "\u52A9\u624B\u81EA\u52A8\u6717\u8BFB\uFF1A{state} \u2014 \u5269\u4F59 {count} \u4E2A\u8BED\u97F3\u7247\u6BB5",
2081
2138
  "dsh-live-voice.speak.autoPlayback.status": "\u52A9\u624B\u81EA\u52A8\u6717\u8BFB\uFF1A{state}",
2082
2139
  "dsh-live-voice.speak.browser.automaticVoice": "\u81EA\u52A8\u9009\u62E9\u672C\u5730\u8BED\u97F3",
2140
+ "dsh-live-voice.speak.browser.defaultVoice": "\u9ED8\u8BA4",
2083
2141
  "dsh-live-voice.speak.browser.label": "\u6D4F\u89C8\u5668\u8BED\u97F3\u5408\u6210 \u2014 \u5728\u6B64\u8BBE\u5907\u64AD\u653E",
2084
2142
  "dsh-live-voice.speak.browser.name": "\u6D4F\u89C8\u5668\u8BED\u97F3\u5408\u6210",
2085
2143
  "dsh-live-voice.speak.browser.outputHelp": "\u6D4F\u89C8\u5668\u8BED\u97F3\u5408\u6210\u53EF\u80FD\u5FFD\u7565\u6240\u9009\u8F93\u51FA\u8BBE\u5907\uFF1B\u6B64\u6D4F\u89C8\u5668 API \u901A\u5E38\u4F7F\u7528\u7CFB\u7EDF\u9ED8\u8BA4\u8BBE\u5907\u3002",
@@ -2147,7 +2205,9 @@ var zh = {
2147
2205
  "dsh-live-voice.speak.segmentGap.help": "\u5728\u8FDE\u7EED\u8BED\u97F3\u7247\u6BB5\u4E4B\u95F4\u7B49\u5F85\u6B64\u6BEB\u79D2\u6570\u3002\u9ED8\u8BA4\u503C\u4E3A 400 ms\u3002",
2148
2206
  "dsh-live-voice.speak.segmentGap.label": "\u8BED\u97F3\u7247\u6BB5\u4E4B\u95F4\u7684\u505C\u987F",
2149
2207
  "dsh-live-voice.speak.status.paused": "\u8BED\u97F3\u5DF2\u6682\u505C",
2150
- "dsh-live-voice.speak.status.playing": "\u6B63\u5728\u6717\u8BFB"
2208
+ "dsh-live-voice.speak.status.playing": "\u6B63\u5728\u6717\u8BFB",
2209
+ "dsh-live-voice.speak.volume.help": "\u4EC5\u8C03\u6574\u8BED\u97F3\u64AD\u653E\uFF1A0% \u4E3A\u9759\u97F3\uFF0C100% \u4E3A\u6700\u5927\u97F3\u91CF\uFF08\u9ED8\u8BA4\uFF09\u3002\u4E0D\u6539\u53D8\u9EA6\u514B\u98CE\u8F93\u5165\u3002Browser \u7684\u66F4\u6539\u4ECE\u4E0B\u4E00\u8BED\u97F3\u7247\u6BB5\u751F\u6548\u3002",
2210
+ "dsh-live-voice.speak.volume.label": "\u8BED\u97F3\u97F3\u91CF"
2151
2211
  };
2152
2212
  var zh_default = Object.freeze(zh);
2153
2213
 
@@ -2643,7 +2703,9 @@ var import_react14 = __toESM(require("react"), 1);
2643
2703
  function SelectField({ label, options, hint, ...props }) {
2644
2704
  const generatedId = import_react14.default.useId();
2645
2705
  const id2 = props.id || generatedId;
2646
- return /* @__PURE__ */ import_react14.default.createElement("div", { className: "dlv-form-field" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "dlv-field-label" }, /* @__PURE__ */ import_react14.default.createElement("label", { htmlFor: id2 }, label), hint && /* @__PURE__ */ import_react14.default.createElement(Hint, { label, id: props["aria-describedby"] }, hint)), /* @__PURE__ */ import_react14.default.createElement("select", { ...props, id: id2 }, options.map((option) => /* @__PURE__ */ import_react14.default.createElement("option", { key: option.value, value: option.value, disabled: option.disabled }, option.label))));
2706
+ return /* @__PURE__ */ import_react14.default.createElement("div", { className: "dlv-form-field" }, /* @__PURE__ */ import_react14.default.createElement("div", { className: "dlv-field-label" }, /* @__PURE__ */ import_react14.default.createElement("label", { htmlFor: id2 }, label), hint && /* @__PURE__ */ import_react14.default.createElement(Hint, { label, id: props["aria-describedby"] }, hint)), /* @__PURE__ */ import_react14.default.createElement("select", { ...props, id: id2 }, options.map(
2707
+ (option) => "options" in option ? /* @__PURE__ */ import_react14.default.createElement("optgroup", { key: option.label, label: option.label }, option.options.map((item) => /* @__PURE__ */ import_react14.default.createElement("option", { key: item.value, value: item.value, disabled: item.disabled }, item.label))) : /* @__PURE__ */ import_react14.default.createElement("option", { key: option.value, value: option.value, disabled: option.disabled }, option.label)
2708
+ )));
2647
2709
  }
2648
2710
 
2649
2711
  // src/shared/design-system/forms/TextAreaField.tsx
@@ -3009,67 +3071,6 @@ function SpeechStatusBar({ controller }) {
3009
3071
  ));
3010
3072
  }
3011
3073
 
3012
- // src/modules/conversation/models/meetingTranscript.ts
3013
- var MeetingTranscript = class {
3014
- constructor(composer) {
3015
- this.composer = composer;
3016
- }
3017
- composer;
3018
- active = /* @__PURE__ */ new Set();
3019
- last = null;
3020
- expected = null;
3021
- timestamps = false;
3022
- sourceTimestamps = /* @__PURE__ */ new Map();
3023
- setSourceTimestamps(source, enabled) {
3024
- if (this.sourceTimestamps.get(source) === enabled) return;
3025
- this.sourceTimestamps.set(source, enabled);
3026
- this.last = null;
3027
- }
3028
- setTimestamps(enabled) {
3029
- this.timestamps = enabled;
3030
- this.last = null;
3031
- }
3032
- setActive(source, enabled) {
3033
- if (enabled) this.active.add(source);
3034
- else this.active.delete(source);
3035
- this.last = null;
3036
- }
3037
- append(source, text, startedAt) {
3038
- const chunk = text.trim();
3039
- if (!chunk || !this.active.has(source)) return;
3040
- const draft = this.composer.getDraft();
3041
- if (draft !== this.expected) this.last = null;
3042
- const labelled = this.active.size > 1;
3043
- const timestamps = this.sourceTimestamps.get(source) ?? this.timestamps;
3044
- const changed = (labelled || timestamps) && this.last !== source;
3045
- const date = new Date(Number.isFinite(startedAt) ? startedAt : Date.now());
3046
- const pad = (value) => String(value).padStart(2, "0");
3047
- const stamp = timestamps && changed ? "[" + date.getFullYear() + "/" + pad(date.getMonth() + 1) + "/" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()) + "] " : "";
3048
- const prefix = changed ? stamp + (labelled ? source === "microphone" ? "Me: " : "Them: " : "") : "";
3049
- const separator = draft ? changed ? "\n\n" : "\n" : "";
3050
- const next = draft + separator + prefix + chunk;
3051
- this.composer.setDraft(next);
3052
- this.expected = next;
3053
- this.last = labelled || timestamps ? source : null;
3054
- }
3055
- reset() {
3056
- this.active.clear();
3057
- this.last = null;
3058
- this.expected = null;
3059
- }
3060
- };
3061
-
3062
- // src/modules/core/transcript.ts
3063
- var TranscriptDraft = class {
3064
- reset() {
3065
- }
3066
- update(current, hypothesis, final = false) {
3067
- if (!final || !hypothesis) return current;
3068
- const separator = current && !/\s$/.test(current) ? " " : "";
3069
- return current + separator + hypothesis;
3070
- }
3071
- };
3072
-
3073
3074
  // src/modules/core/speechDefaults.ts
3074
3075
  var notices = {
3075
3076
  en: "You can see the code in our conversation history.",
@@ -3156,6 +3157,8 @@ var defaultSettings = Object.freeze({
3156
3157
  sharedVoiceCommandsEnabled: true,
3157
3158
  timestampsEnabled: false,
3158
3159
  sharedTimestampsEnabled: false,
3160
+ microphoneSourceName: "Me",
3161
+ sharedSourceName: "Them",
3159
3162
  voiceCommandSend: "send, send message",
3160
3163
  voiceCommandQueue: "queue, queue message",
3161
3164
  voiceCommandEnd: "end, end conversation",
@@ -3167,8 +3170,15 @@ var defaultSettings = Object.freeze({
3167
3170
  outputCodeMaxLines: 5,
3168
3171
  outputCodeNotice: resolveCodeNotice(void 0, "pt-BR"),
3169
3172
  rate: 1,
3173
+ volume: 1,
3170
3174
  segmentGapMs: 400
3171
3175
  });
3176
+ var normalizeSourceName = (value, fallback) => {
3177
+ if (typeof value !== "string" || /[\u0000-\u001f\u007f-\u009f\u2028\u2029]/u.test(value))
3178
+ return fallback;
3179
+ const name = value.trim();
3180
+ return name && name.length <= 80 ? name : fallback;
3181
+ };
3172
3182
  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;
3173
3183
  var customSilenceMinMs = 100;
3174
3184
  var customSilenceMaxMs = 1e4;
@@ -3204,6 +3214,14 @@ function normalizeSettings(value) {
3204
3214
  recognitionFilterEnabled: typeof source.recognitionFilterEnabled === "boolean" ? source.recognitionFilterEnabled : defaultSettings.recognitionFilterEnabled,
3205
3215
  recognitionMinimumWords: Number.isInteger(source.recognitionMinimumWords) && source.recognitionMinimumWords >= 1 && source.recognitionMinimumWords <= 20 ? source.recognitionMinimumWords : defaultSettings.recognitionMinimumWords,
3206
3216
  voiceCommandsEnabled: typeof source.voiceCommandsEnabled === "boolean" ? source.voiceCommandsEnabled : defaultSettings.voiceCommandsEnabled,
3217
+ microphoneSourceName: normalizeSourceName(
3218
+ source.microphoneSourceName,
3219
+ defaultSettings.microphoneSourceName
3220
+ ),
3221
+ sharedSourceName: normalizeSourceName(
3222
+ source.sharedSourceName,
3223
+ defaultSettings.sharedSourceName
3224
+ ),
3207
3225
  timestampsEnabled: typeof source.timestampsEnabled === "boolean" ? source.timestampsEnabled : false,
3208
3226
  sharedTimestampsEnabled: typeof source.sharedTimestampsEnabled === "boolean" ? source.sharedTimestampsEnabled : false,
3209
3227
  sharedVoiceCommandsEnabled: typeof source.sharedVoiceCommandsEnabled === "boolean" ? source.sharedVoiceCommandsEnabled : typeof source.voiceCommandsEnabled === "boolean" ? source.voiceCommandsEnabled : defaultSettings.sharedVoiceCommandsEnabled,
@@ -3239,10 +3257,76 @@ function normalizeSettings(value) {
3239
3257
  outputCodeMaxLines: Number.isInteger(source.outputCodeMaxLines) && source.outputCodeMaxLines >= 0 && source.outputCodeMaxLines <= 100 ? source.outputCodeMaxLines : defaultSettings.outputCodeMaxLines,
3240
3258
  outputCodeNotice: typeof source.outputCodeNotice === "string" && source.outputCodeNotice.trim() && source.outputCodeNotice.length <= 300 && !source.outputCodeNotice.includes("\0") ? resolveCodeNotice(source.outputCodeNotice, source.lang || defaultSettings.lang) : resolveCodeNotice(defaultSettings.outputCodeNotice, source.lang || defaultSettings.lang),
3241
3259
  rate: Number.isFinite(source.rate) && source.rate >= 0.1 && source.rate <= 3 ? source.rate : 1,
3260
+ volume: Number.isFinite(source.volume) && source.volume >= 0 && source.volume <= 1 ? source.volume : 1,
3242
3261
  segmentGapMs: Number.isFinite(source.segmentGapMs) && source.segmentGapMs >= 0 && source.segmentGapMs <= 2e3 ? Math.round(source.segmentGapMs) : 400
3243
3262
  };
3244
3263
  }
3245
3264
 
3265
+ // src/modules/conversation/models/meetingTranscript.ts
3266
+ var MeetingTranscript = class {
3267
+ constructor(composer, settings = () => defaultSettings) {
3268
+ this.composer = composer;
3269
+ this.settings = settings;
3270
+ }
3271
+ composer;
3272
+ settings;
3273
+ active = /* @__PURE__ */ new Set();
3274
+ last = null;
3275
+ expected = null;
3276
+ timestamps = false;
3277
+ sourceTimestamps = /* @__PURE__ */ new Map();
3278
+ setSourceTimestamps(source, enabled) {
3279
+ if (this.sourceTimestamps.get(source) === enabled) return;
3280
+ this.sourceTimestamps.set(source, enabled);
3281
+ this.last = null;
3282
+ }
3283
+ setTimestamps(enabled) {
3284
+ this.timestamps = enabled;
3285
+ this.last = null;
3286
+ }
3287
+ setActive(source, enabled) {
3288
+ if (enabled) this.active.add(source);
3289
+ else this.active.delete(source);
3290
+ this.last = null;
3291
+ }
3292
+ append(source, text, startedAt) {
3293
+ const chunk = text.trim();
3294
+ if (!chunk || !this.active.has(source)) return;
3295
+ const draft = this.composer.getDraft();
3296
+ if (draft !== this.expected) this.last = null;
3297
+ const labelled = this.active.size > 1;
3298
+ const timestamps = this.sourceTimestamps.get(source) ?? this.timestamps;
3299
+ const changed = (labelled || timestamps) && this.last !== source;
3300
+ const date = new Date(Number.isFinite(startedAt) ? startedAt : Date.now());
3301
+ const pad = (value) => String(value).padStart(2, "0");
3302
+ const stamp = timestamps && changed ? "[" + date.getFullYear() + "/" + pad(date.getMonth() + 1) + "/" + pad(date.getDate()) + " " + pad(date.getHours()) + ":" + pad(date.getMinutes()) + ":" + pad(date.getSeconds()) + "] " : "";
3303
+ const key = source === "microphone" ? "microphoneSourceName" : "sharedSourceName";
3304
+ const name = normalizeSourceName(this.settings()[key], defaultSettings[key]);
3305
+ const prefix = changed ? stamp + (labelled ? name + ": " : "") : "";
3306
+ const separator = draft ? changed ? "\n\n" : "\n" : "";
3307
+ const next = draft + separator + prefix + chunk;
3308
+ this.composer.setDraft(next);
3309
+ this.expected = next;
3310
+ this.last = labelled || timestamps ? source : null;
3311
+ }
3312
+ reset() {
3313
+ this.active.clear();
3314
+ this.last = null;
3315
+ this.expected = null;
3316
+ }
3317
+ };
3318
+
3319
+ // src/modules/core/transcript.ts
3320
+ var TranscriptDraft = class {
3321
+ reset() {
3322
+ }
3323
+ update(current, hypothesis, final = false) {
3324
+ if (!final || !hypothesis) return current;
3325
+ const separator = current && !/\s$/.test(current) ? " " : "";
3326
+ return current + separator + hypothesis;
3327
+ }
3328
+ };
3329
+
3246
3330
  // src/modules/speak/services/speechQueue.ts
3247
3331
  var SPEECH_QUEUE_PREFETCH_LIMIT = 3;
3248
3332
 
@@ -4852,6 +4936,9 @@ var VoiceCoordinator = class _VoiceCoordinator {
4852
4936
  (key) => Object.hasOwn(next, key) && settings[key] !== previousSettings[key]
4853
4937
  );
4854
4938
  if (speechPreparationChanged) this._cancelSpeechPrefetch();
4939
+ if (settings.volume !== previousSettings.volume) {
4940
+ this.engines[this.snapshot.speechEngine || settings.engine]?.setVolume?.(settings.volume);
4941
+ }
4855
4942
  this.meter.deviceId = settings.inputDeviceId;
4856
4943
  Object.assign(this.recognition, {
4857
4944
  lang: settings.recognitionLang,
@@ -5403,6 +5490,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
5403
5490
  const options = {
5404
5491
  voice: this.snapshot.settings.voice || void 0,
5405
5492
  rate: this.snapshot.settings.rate,
5493
+ volume: this.snapshot.settings.volume,
5406
5494
  lang: this.snapshot.settings.lang,
5407
5495
  outputDeviceId: this.snapshot.settings.outputDeviceId
5408
5496
  };
@@ -5738,6 +5826,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
5738
5826
  },
5739
5827
  voice: this.snapshot.settings.voice || void 0,
5740
5828
  rate: this.snapshot.settings.rate,
5829
+ volume: this.snapshot.settings.volume,
5741
5830
  outputDeviceId: this.snapshot.settings.outputDeviceId,
5742
5831
  signal: controller.signal
5743
5832
  };
@@ -5753,6 +5842,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
5753
5842
  await this._waitSpeechLaunch(controller.signal);
5754
5843
  if (epoch !== this.speechEpoch || this.disposed) return;
5755
5844
  this.speechLaunchPending = false;
5845
+ options.volume = this.snapshot.settings.volume;
5756
5846
  if (queued?.prepared && typeof engine.playPrepared === "function") {
5757
5847
  const prepared = queued.prepared;
5758
5848
  this.prefetchItems.delete(queued);
@@ -5898,7 +5988,7 @@ var MeetingController = class {
5898
5988
  this.t = translate;
5899
5989
  this.onActivity = onActivity;
5900
5990
  this.onSpeech = onSpeech;
5901
- this.transcript = new MeetingTranscript(composer);
5991
+ this.transcript = new MeetingTranscript(composer, settings);
5902
5992
  this.jobs = /* @__PURE__ */ new Map();
5903
5993
  this.generations = /* @__PURE__ */ new Map();
5904
5994
  this.listeners = /* @__PURE__ */ new Set();
@@ -7685,7 +7775,7 @@ var HostAudioSpeakingEngine = class {
7685
7775
  }
7686
7776
  }
7687
7777
  async playPrepared(prepared, options = {}) {
7688
- const { signal, outputDeviceId = "", rate = 1, retainPrepared = false } = options;
7778
+ const { signal, outputDeviceId = "", rate = 1, volume = 1, retainPrepared = false } = options;
7689
7779
  const playbackRate = this.playbackRate(rate);
7690
7780
  if (!prepared?.audio) throw new TypeError("Prepared speech audio is required.");
7691
7781
  await this.stop();
@@ -7695,6 +7785,7 @@ var HostAudioSpeakingEngine = class {
7695
7785
  const audio = operation.audio;
7696
7786
  if (retainPrepared) audio.currentTime = 0;
7697
7787
  audio.playbackRate = playbackRate;
7788
+ audio.volume = volume;
7698
7789
  try {
7699
7790
  if (outputDeviceId && typeof audio.setSinkId === "function")
7700
7791
  await audio.setSinkId(outputDeviceId);
@@ -7747,6 +7838,9 @@ var HostAudioSpeakingEngine = class {
7747
7838
  throw error;
7748
7839
  }
7749
7840
  }
7841
+ setVolume(volume) {
7842
+ if (this.current?.audio) this.current.audio.volume = volume;
7843
+ }
7750
7844
  async stop() {
7751
7845
  const operation = this.current;
7752
7846
  if (!operation) return;
@@ -7820,7 +7914,7 @@ var BrowserSpeakingEngine = class {
7820
7914
  async prepare(text, options = {}) {
7821
7915
  return this._prepare(text, options);
7822
7916
  }
7823
- _prepare(text, { voice, lang = this.lang, rate = 1, signal } = {}) {
7917
+ _prepare(text, { voice, lang = this.lang, rate = 1, volume = 1, signal } = {}) {
7824
7918
  if (signal?.aborted) throw abortError3();
7825
7919
  if (typeof text !== "string") throw new TypeError("Speech text must be a string.");
7826
7920
  if (!Number.isFinite(rate) || rate < 0.1 || rate > 10)
@@ -7844,6 +7938,7 @@ var BrowserSpeakingEngine = class {
7844
7938
  utterance2.voice = available;
7845
7939
  utterance2.lang = available.lang || lang;
7846
7940
  utterance2.rate = rate;
7941
+ utterance2.volume = volume;
7847
7942
  return utterance2;
7848
7943
  };
7849
7944
  let utterance = createUtterance();
@@ -7868,7 +7963,7 @@ var BrowserSpeakingEngine = class {
7868
7963
  this.stop();
7869
7964
  return this.playPrepared(this._prepare(text, options), options);
7870
7965
  }
7871
- async playPrepared(prepared, { signal, onPlaybackStart, retainPrepared = false } = {}) {
7966
+ async playPrepared(prepared, { signal, volume, onPlaybackStart, retainPrepared = false } = {}) {
7872
7967
  this.stop();
7873
7968
  if (signal?.aborted) {
7874
7969
  if (!retainPrepared) prepared?.dispose?.();
@@ -7881,6 +7976,7 @@ var BrowserSpeakingEngine = class {
7881
7976
  return;
7882
7977
  }
7883
7978
  const utterance = prepared.takeUtterance();
7979
+ if (volume !== void 0) utterance.volume = volume;
7884
7980
  return new Promise((resolve, reject) => {
7885
7981
  const finish = (error) => {
7886
7982
  if (this.current !== job) return;
@@ -7982,10 +8078,10 @@ var QwenHttpSpeakingEngine = class extends HostAudioSpeakingEngine {
7982
8078
  };
7983
8079
 
7984
8080
  // src/modules/settings/components/createLiveVoiceSettings.tsx
7985
- var import_react58 = __toESM(require("react"), 1);
8081
+ var import_react59 = __toESM(require("react"), 1);
7986
8082
 
7987
8083
  // src/modules/settings/components/LiveVoiceSettings.tsx
7988
- var import_react57 = __toESM(require("react"), 1);
8084
+ var import_react58 = __toESM(require("react"), 1);
7989
8085
 
7990
8086
  // src/modules/settings/hooks/useAudioDevices.ts
7991
8087
  var import_react33 = __toESM(require("react"), 1);
@@ -8024,7 +8120,7 @@ function createDeviceOptions(devices, kind, systemDefault, fallback) {
8024
8120
  var import_react34 = __toESM(require("react"), 1);
8025
8121
 
8026
8122
  // src/modules/settings/services/releases.ts
8027
- var CURRENT_VERSION = "0.5.1";
8123
+ var CURRENT_VERSION = "0.5.2";
8028
8124
  var TESTED_DSH_VERSION = "0.2.1-alpha.2";
8029
8125
  var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
8030
8126
  var RELEASES_URL = `${REPOSITORY_URL}/releases`;
@@ -8576,7 +8672,10 @@ function RecognitionEngineSettings({
8576
8672
  options: [
8577
8673
  ...engine === "browser" ? [] : [{ value: "auto", label: recognition.language.automatic() }],
8578
8674
  { value: "pt-BR", label: "Portugu\xEAs (Brasil)" },
8579
- { value: "en-US", label: "English (US)" }
8675
+ { value: "en-US", label: "English (US)" },
8676
+ { value: "zh-CN", label: recognition.language.options.chinese() },
8677
+ { value: "es-ES", label: recognition.language.options.spanish() },
8678
+ { value: "fr-FR", label: recognition.language.options.french() }
8580
8679
  ],
8581
8680
  onChange: (event) => updateSettings({ recognitionLang: event.target.value })
8582
8681
  }
@@ -8628,7 +8727,7 @@ function RecognitionFilterSettings({ settings, updateSettings }) {
8628
8727
  }
8629
8728
 
8630
8729
  // src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx
8631
- var import_react49 = __toESM(require("react"), 1);
8730
+ var import_react50 = __toESM(require("react"), 1);
8632
8731
 
8633
8732
  // src/modules/settings/sections/recognition/SilenceDetectionSettings.tsx
8634
8733
  var import_react45 = __toESM(require("react"), 1);
@@ -8862,25 +8961,50 @@ function RecognitionStatus({ capabilities = {} }) {
8862
8961
  return /* @__PURE__ */ import_react48.default.createElement(import_react48.default.Fragment, null, capabilities.capture?.supported === false ? /* @__PURE__ */ import_react48.default.createElement("p", { role: "status" }, recognition.microphone.failure({ reason: capabilities.capture.reason || "" })) : capabilities.capture?.permission === "prompt" ? /* @__PURE__ */ import_react48.default.createElement("p", { role: "status" }, recognition.microphone.permissionHelp()) : null, capabilities.recognition?.supported === false && /* @__PURE__ */ import_react48.default.createElement("p", { role: "status" }, capabilities.recognition.reason));
8863
8962
  }
8864
8963
 
8964
+ // src/modules/settings/sections/conversation/MeetingSourceSettings.tsx
8965
+ var import_react49 = __toESM(require("react"), 1);
8966
+ function MeetingSourceSettings({ settings, updateSettings }) {
8967
+ const { scoped: language } = useLanguage((ctx) => ctx.settings);
8968
+ const { scoped: meeting } = useLanguage((ctx) => ctx.meeting);
8969
+ const names = language.sourceNames;
8970
+ return /* @__PURE__ */ import_react49.default.createElement(SettingsSection, { title: meeting.title(), icon: "meeting", defaultOpen: false }, /* @__PURE__ */ import_react49.default.createElement("p", null, names.help()), /* @__PURE__ */ import_react49.default.createElement(
8971
+ DraftField,
8972
+ {
8973
+ label: names.microphone(),
8974
+ value: settings.microphoneSourceName,
8975
+ maxLength: 80,
8976
+ onCommit: (microphoneSourceName) => updateSettings({ microphoneSourceName })
8977
+ }
8978
+ ), /* @__PURE__ */ import_react49.default.createElement(
8979
+ DraftField,
8980
+ {
8981
+ label: names.shared(),
8982
+ value: settings.sharedSourceName,
8983
+ maxLength: 80,
8984
+ onCommit: (sharedSourceName) => updateSettings({ sharedSourceName })
8985
+ }
8986
+ ));
8987
+ }
8988
+
8865
8989
  // src/modules/settings/sections/recognition/RecognitionSettingsSection.tsx
8866
8990
  function RecognitionSettingsSection(props) {
8867
8991
  const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
8868
- return /* @__PURE__ */ import_react49.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react49.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react49.default.createElement(RecognitionStatus, { ...props }), /* @__PURE__ */ import_react49.default.createElement(SettingsSection, { title: settingsLanguage.layout.transcription(), icon: "mic" }, /* @__PURE__ */ import_react49.default.createElement(RecognitionEngineSettings, { ...props })), /* @__PURE__ */ import_react49.default.createElement(VoiceCommandSettings, { ...props }), /* @__PURE__ */ import_react49.default.createElement(SilenceDetectionSettings, { ...props }), /* @__PURE__ */ import_react49.default.createElement(RecognitionFilterSettings, { ...props })));
8992
+ return /* @__PURE__ */ import_react50.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react50.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react50.default.createElement(RecognitionStatus, { ...props }), /* @__PURE__ */ import_react50.default.createElement(SettingsSection, { title: settingsLanguage.layout.transcription(), icon: "mic" }, /* @__PURE__ */ import_react50.default.createElement(RecognitionEngineSettings, { ...props })), /* @__PURE__ */ import_react50.default.createElement(MeetingSourceSettings, { ...props }), /* @__PURE__ */ import_react50.default.createElement(VoiceCommandSettings, { ...props }), /* @__PURE__ */ import_react50.default.createElement(SilenceDetectionSettings, { ...props }), /* @__PURE__ */ import_react50.default.createElement(RecognitionFilterSettings, { ...props })));
8869
8993
  }
8870
8994
 
8871
8995
  // src/modules/settings/sections/speak/OutputFilterSettings.tsx
8872
- var import_react50 = __toESM(require("react"), 1);
8996
+ var import_react51 = __toESM(require("react"), 1);
8873
8997
  function OutputFilterSettings({ settings, updateSettings }) {
8874
8998
  const { scoped: speak } = useLanguage((ctx) => ctx.speak);
8875
8999
  const enabled = settings.outputCodeFilterEnabled !== false;
8876
- return /* @__PURE__ */ import_react50.default.createElement(
9000
+ return /* @__PURE__ */ import_react51.default.createElement(
8877
9001
  SettingsSection,
8878
9002
  {
8879
9003
  title: speak.filters.code.enabled(),
8880
9004
  icon: "filter",
8881
9005
  defaultOpen: false
8882
9006
  },
8883
- /* @__PURE__ */ import_react50.default.createElement(
9007
+ /* @__PURE__ */ import_react51.default.createElement(
8884
9008
  CheckboxField,
8885
9009
  {
8886
9010
  label: speak.filters.code.enabled(),
@@ -8888,7 +9012,7 @@ function OutputFilterSettings({ settings, updateSettings }) {
8888
9012
  onChange: (event) => updateSettings({ outputCodeFilterEnabled: event.target.checked })
8889
9013
  }
8890
9014
  ),
8891
- /* @__PURE__ */ import_react50.default.createElement(
9015
+ /* @__PURE__ */ import_react51.default.createElement(
8892
9016
  NumberField,
8893
9017
  {
8894
9018
  label: speak.filters.code.maxLines(),
@@ -8903,7 +9027,7 @@ function OutputFilterSettings({ settings, updateSettings }) {
8903
9027
  }
8904
9028
  }
8905
9029
  ),
8906
- /* @__PURE__ */ import_react50.default.createElement(
9030
+ /* @__PURE__ */ import_react51.default.createElement(
8907
9031
  TextField,
8908
9032
  {
8909
9033
  label: speak.filters.code.replacement(),
@@ -8916,22 +9040,69 @@ function OutputFilterSettings({ settings, updateSettings }) {
8916
9040
  }
8917
9041
 
8918
9042
  // src/modules/settings/sections/speak/SpeakSettingsSection.tsx
8919
- var import_react53 = __toESM(require("react"), 1);
9043
+ var import_react54 = __toESM(require("react"), 1);
9044
+
9045
+ // src/modules/settings/sections/speak/SpeechEngineSettings.tsx
9046
+ var import_react52 = __toESM(require("react"), 1);
9047
+
9048
+ // src/modules/settings/models/browserVoiceSelection.ts
9049
+ function baseLanguage(language) {
9050
+ return (language ?? "").trim().replace(/_/g, "-").split("-")[0].toLowerCase();
9051
+ }
9052
+ function isNaturalVoice(voice) {
9053
+ return /\bnatural\b/i.test(voice.name);
9054
+ }
9055
+ function rankBrowserVoices(voices, browserLanguage) {
9056
+ const language = baseLanguage(browserLanguage);
9057
+ const matches = (voice) => Boolean(language) && baseLanguage(voice.lang) === language;
9058
+ return [...voices].sort(
9059
+ (a, b) => Number(isNaturalVoice(b)) - Number(isNaturalVoice(a)) || Number(matches(b)) - Number(matches(a)) || a.name.localeCompare(b.name)
9060
+ );
9061
+ }
9062
+ function groupBrowserVoices(voices, browserLanguage) {
9063
+ const groups = /* @__PURE__ */ new Map();
9064
+ for (const voice of rankBrowserVoices(voices, browserLanguage)) {
9065
+ const language = baseLanguage(voice.lang);
9066
+ const group = groups.get(language) ?? [];
9067
+ group.push(voice);
9068
+ groups.set(language, group);
9069
+ }
9070
+ const preferredLanguage = baseLanguage(browserLanguage);
9071
+ return [...groups].map(([language, voices2]) => ({ language, voices: voices2 })).sort(
9072
+ (a, b) => Number(Boolean(preferredLanguage) && b.language === preferredLanguage) - Number(Boolean(preferredLanguage) && a.language === preferredLanguage) || Number(!a.language) - Number(!b.language) || a.language.localeCompare(b.language)
9073
+ );
9074
+ }
9075
+ function preferredBrowserVoice(voices, browserLanguage) {
9076
+ const voice = rankBrowserVoices(voices, browserLanguage).find(isNaturalVoice);
9077
+ return voice ? voice.voiceURI || voice.name : void 0;
9078
+ }
9079
+ function speechEngineSelectionPatch(currentEngine, nextEngine, voices, browserLanguage) {
9080
+ const voice = nextEngine === "browser" && currentEngine !== "browser" ? preferredBrowserVoice(voices, browserLanguage) : void 0;
9081
+ return { engine: nextEngine, ...voice === void 0 ? {} : { voice } };
9082
+ }
8920
9083
 
8921
9084
  // src/modules/settings/sections/speak/SpeechEngineSettings.tsx
8922
- var import_react51 = __toESM(require("react"), 1);
8923
9085
  function SpeechEngineSettings({
8924
9086
  settings,
8925
9087
  capabilities,
8926
9088
  updateSettings,
8927
9089
  hostPlatform
8928
9090
  }) {
9091
+ const locale = typeof window === "undefined" ? "" : window.navigator.language;
8929
9092
  const { scoped: commons } = useLanguage((ctx) => ctx.commons);
8930
9093
  const { scoped: speak } = useLanguage((ctx) => ctx.speak);
8931
9094
  const engine = settings.engine || "browser";
8932
9095
  const browserVoices = capabilities?.browser?.voices || [];
8933
9096
  const capability = capabilities?.[engine];
8934
- return /* @__PURE__ */ import_react51.default.createElement(import_react51.default.Fragment, null, /* @__PURE__ */ import_react51.default.createElement(
9097
+ const defaultBrowserVoice = preferredBrowserVoice(browserVoices, locale);
9098
+ const sortedBrowserOptions = groupBrowserVoices(browserVoices, locale).map((group) => ({
9099
+ label: group.language || commons.unknownLanguage(),
9100
+ options: group.voices.map((voice) => ({
9101
+ value: voice.voiceURI || voice.name,
9102
+ label: (voice.voiceURI || voice.name) === defaultBrowserVoice ? voice.name + " (" + speak.browser.defaultVoice() + ")" : voice.name
9103
+ }))
9104
+ }));
9105
+ return /* @__PURE__ */ import_react52.default.createElement(import_react52.default.Fragment, null, /* @__PURE__ */ import_react52.default.createElement(
8935
9106
  SelectField,
8936
9107
  {
8937
9108
  label: speak.engine.label(),
@@ -8944,23 +9115,24 @@ function SpeechEngineSettings({
8944
9115
  "qwen-http": speak.qwen.label()
8945
9116
  }[value]
8946
9117
  })),
8947
- onChange: (event) => updateSettings({ engine: event.target.value })
9118
+ onChange: (event) => {
9119
+ updateSettings(
9120
+ speechEngineSelectionPatch(engine, event.target.value, browserVoices, locale)
9121
+ );
9122
+ }
8948
9123
  }
8949
- ), engine === "browser" && /* @__PURE__ */ import_react51.default.createElement(
9124
+ ), engine === "browser" && /* @__PURE__ */ import_react52.default.createElement(
8950
9125
  SelectField,
8951
9126
  {
8952
9127
  label: speak.browser.voice(),
8953
9128
  value: settings.voice || "",
8954
9129
  options: [
8955
9130
  { value: "", label: commons.systemDefault() },
8956
- ...browserVoices.map((voice) => ({
8957
- value: voice.voiceURI || voice.name,
8958
- label: voice.name
8959
- }))
9131
+ ...sortedBrowserOptions
8960
9132
  ],
8961
9133
  onChange: (event) => updateSettings({ voice: event.target.value })
8962
9134
  }
8963
- ), engine === "say" && /* @__PURE__ */ import_react51.default.createElement(import_react51.default.Fragment, null, /* @__PURE__ */ import_react51.default.createElement(
9135
+ ), engine === "say" && /* @__PURE__ */ import_react52.default.createElement(import_react52.default.Fragment, null, /* @__PURE__ */ import_react52.default.createElement(
8964
9136
  TextField,
8965
9137
  {
8966
9138
  label: speak.macos.voice(),
@@ -8968,7 +9140,7 @@ function SpeechEngineSettings({
8968
9140
  placeholder: commons.systemDefault(),
8969
9141
  onCommit: (value) => updateSettings({ voice: value.trim() })
8970
9142
  }
8971
- ), /* @__PURE__ */ import_react51.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.voiceHelp())), engine === "qwen-http" && /* @__PURE__ */ import_react51.default.createElement(
9143
+ ), /* @__PURE__ */ import_react52.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.voiceHelp())), engine === "qwen-http" && /* @__PURE__ */ import_react52.default.createElement(
8972
9144
  SelectField,
8973
9145
  {
8974
9146
  label: speak.qwen.voice(),
@@ -8976,7 +9148,18 @@ function SpeechEngineSettings({
8976
9148
  options: qwenVoices,
8977
9149
  onChange: (event) => updateSettings({ voice: event.target.value })
8978
9150
  }
8979
- ), /* @__PURE__ */ import_react51.default.createElement("div", { className: "dlv-field-pair" }, /* @__PURE__ */ import_react51.default.createElement(
9151
+ ), /* @__PURE__ */ import_react52.default.createElement("label", null, speak.volume.label(), " (", Math.round((settings.volume ?? 1) * 100), "%)", /* @__PURE__ */ import_react52.default.createElement(
9152
+ "input",
9153
+ {
9154
+ type: "range",
9155
+ min: 0,
9156
+ max: 1,
9157
+ step: 0.01,
9158
+ value: settings.volume ?? 1,
9159
+ "aria-label": speak.volume.label(),
9160
+ onInput: (event) => updateSettings({ volume: Number(event.currentTarget.value) })
9161
+ }
9162
+ )), /* @__PURE__ */ import_react52.default.createElement("p", { className: "dlv-setting-description" }, speak.volume.help()), /* @__PURE__ */ import_react52.default.createElement("div", { className: "dlv-field-pair" }, /* @__PURE__ */ import_react52.default.createElement(
8980
9163
  NumberField,
8981
9164
  {
8982
9165
  label: speak.rate.label() + " (\xD7)",
@@ -8986,7 +9169,7 @@ function SpeechEngineSettings({
8986
9169
  value: settings.rate ?? 1,
8987
9170
  onCommit: (value) => updateSettings({ rate: Number(value) })
8988
9171
  }
8989
- ), /* @__PURE__ */ import_react51.default.createElement(
9172
+ ), /* @__PURE__ */ import_react52.default.createElement(
8990
9173
  NumberField,
8991
9174
  {
8992
9175
  label: speak.segmentGap.label() + " (ms)",
@@ -8996,11 +9179,11 @@ function SpeechEngineSettings({
8996
9179
  value: settings.segmentGapMs ?? 400,
8997
9180
  onCommit: (value) => updateSettings({ segmentGapMs: Number(value) })
8998
9181
  }
8999
- )), capability?.supported === false && /* @__PURE__ */ import_react51.default.createElement("p", { role: "status" }, capability.reason || commons.engine.failure()));
9182
+ )), capability?.supported === false && /* @__PURE__ */ import_react52.default.createElement("p", { role: "status" }, capability.reason || commons.engine.failure()));
9000
9183
  }
9001
9184
 
9002
9185
  // src/modules/settings/sections/speak/SpeechAdvancedSettings.tsx
9003
- var import_react52 = __toESM(require("react"), 1);
9186
+ var import_react53 = __toESM(require("react"), 1);
9004
9187
  function SpeechAdvancedSettings({
9005
9188
  controller,
9006
9189
  settings,
@@ -9018,7 +9201,7 @@ function SpeechAdvancedSettings({
9018
9201
  commons.systemDefault(),
9019
9202
  speak.output.fallbackName()
9020
9203
  );
9021
- return /* @__PURE__ */ import_react52.default.createElement(import_react52.default.Fragment, null, settings.engine !== "say" ? /* @__PURE__ */ import_react52.default.createElement(
9204
+ return /* @__PURE__ */ import_react53.default.createElement(import_react53.default.Fragment, null, settings.engine !== "say" ? /* @__PURE__ */ import_react53.default.createElement(
9022
9205
  SelectField,
9023
9206
  {
9024
9207
  label: speak.output.device(),
@@ -9026,14 +9209,14 @@ function SpeechAdvancedSettings({
9026
9209
  options: outputOptions,
9027
9210
  onChange: (event) => updateSettings({ outputDeviceId: event.target.value })
9028
9211
  }
9029
- ) : /* @__PURE__ */ import_react52.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.outputHelp()), settings.engine === "qwen-http" && /* @__PURE__ */ import_react52.default.createElement(SettingsSubcard, { title: speak.qwen.connection(), icon: "link" }, /* @__PURE__ */ import_react52.default.createElement(QwenSettings, { controller })), /* @__PURE__ */ import_react52.default.createElement(SettingsSubcard, { title: speak.agentContext.label(), icon: "settings" }, /* @__PURE__ */ import_react52.default.createElement(
9212
+ ) : /* @__PURE__ */ import_react53.default.createElement("p", { className: "dlv-setting-description" }, speak.macos.outputHelp()), settings.engine === "qwen-http" && /* @__PURE__ */ import_react53.default.createElement(SettingsSubcard, { title: speak.qwen.connection(), icon: "link" }, /* @__PURE__ */ import_react53.default.createElement(QwenSettings, { controller })), /* @__PURE__ */ import_react53.default.createElement(SettingsSubcard, { title: speak.agentContext.label(), icon: "settings" }, /* @__PURE__ */ import_react53.default.createElement(
9030
9213
  CheckboxField,
9031
9214
  {
9032
9215
  label: speak.agentContext.enabled(),
9033
9216
  checked: settings.agentVoiceContextEnabled !== false,
9034
9217
  onChange: (event) => updateSettings({ agentVoiceContextEnabled: event.target.checked })
9035
9218
  }
9036
- ), /* @__PURE__ */ import_react52.default.createElement(
9219
+ ), /* @__PURE__ */ import_react53.default.createElement(
9037
9220
  TextAreaField,
9038
9221
  {
9039
9222
  label: speak.agentContext.label(),
@@ -9041,30 +9224,30 @@ function SpeechAdvancedSettings({
9041
9224
  disabled: settings.agentVoiceContextEnabled === false,
9042
9225
  onCommit: (value) => updateSettings({ agentVoiceContext: value })
9043
9226
  }
9044
- ), /* @__PURE__ */ import_react52.default.createElement(
9227
+ ), /* @__PURE__ */ import_react53.default.createElement(
9045
9228
  "button",
9046
9229
  {
9047
9230
  type: "button",
9048
9231
  onClick: () => updateSettings({ agentVoiceContext: defaultAgentVoiceContext })
9049
9232
  },
9050
9233
  speak.agentContext.restore()
9051
- )), /* @__PURE__ */ import_react52.default.createElement("p", null, speak.engine.playbackHelp()), /* @__PURE__ */ import_react52.default.createElement("div", { className: "dlv-settings-actions" }, /* @__PURE__ */ import_react52.default.createElement("button", { type: "button", onClick: () => invoke("speak", speak.output.testPhrase()) }, speak.output.test()), speaking && /* @__PURE__ */ import_react52.default.createElement("button", { type: "button", onClick: () => invoke("stopSpeech") }, speak.output.stopTest()), /* @__PURE__ */ import_react52.default.createElement("button", { type: "button", onClick: () => invoke("refreshCapabilities", { localOnly: true }) }, settingsLanguage.engine.refresh())));
9234
+ )), /* @__PURE__ */ import_react53.default.createElement("p", null, speak.engine.playbackHelp()), /* @__PURE__ */ import_react53.default.createElement("div", { className: "dlv-settings-actions" }, /* @__PURE__ */ import_react53.default.createElement("button", { type: "button", onClick: () => invoke("speak", speak.output.testPhrase()) }, speak.output.test()), speaking && /* @__PURE__ */ import_react53.default.createElement("button", { type: "button", onClick: () => invoke("stopSpeech") }, speak.output.stopTest()), /* @__PURE__ */ import_react53.default.createElement("button", { type: "button", onClick: () => invoke("refreshCapabilities", { localOnly: true }) }, settingsLanguage.engine.refresh())));
9052
9235
  }
9053
9236
 
9054
9237
  // src/modules/settings/sections/speak/SpeakSettingsSection.tsx
9055
9238
  function SpeakSettingsSection(props) {
9056
9239
  const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
9057
- return /* @__PURE__ */ import_react53.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react53.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react53.default.createElement(SettingsSection, { title: settingsLanguage.layout.voice(), icon: "speaker" }, /* @__PURE__ */ import_react53.default.createElement(SpeechEngineSettings, { ...props })), /* @__PURE__ */ import_react53.default.createElement(SettingsSection, { title: settingsLanguage.layout.playback(), icon: "play" }, /* @__PURE__ */ import_react53.default.createElement(SpeechAdvancedSettings, { ...props })), /* @__PURE__ */ import_react53.default.createElement(OutputFilterSettings, { ...props })));
9240
+ return /* @__PURE__ */ import_react54.default.createElement(SettingsCard, null, /* @__PURE__ */ import_react54.default.createElement("div", { className: "dlv-settings-card-body" }, /* @__PURE__ */ import_react54.default.createElement(SettingsSection, { title: settingsLanguage.layout.voice(), icon: "speaker" }, /* @__PURE__ */ import_react54.default.createElement(SpeechEngineSettings, { ...props })), /* @__PURE__ */ import_react54.default.createElement(SettingsSection, { title: settingsLanguage.layout.playback(), icon: "play" }, /* @__PURE__ */ import_react54.default.createElement(SpeechAdvancedSettings, { ...props })), /* @__PURE__ */ import_react54.default.createElement(OutputFilterSettings, { ...props })));
9058
9241
  }
9059
9242
 
9060
9243
  // src/modules/settings/components/SettingsHeader.tsx
9061
- var import_react56 = __toESM(require("react"), 1);
9244
+ var import_react57 = __toESM(require("react"), 1);
9062
9245
 
9063
9246
  // src/modules/settings/components/VersionBadges.tsx
9064
- var import_react54 = __toESM(require("react"), 1);
9247
+ var import_react55 = __toESM(require("react"), 1);
9065
9248
  function VersionBadges() {
9066
9249
  const { scoped: commons } = useLanguage((ctx) => ctx.commons);
9067
- return /* @__PURE__ */ import_react54.default.createElement("div", { className: "dlv-version-badges", "aria-label": commons.version.title() }, /* @__PURE__ */ import_react54.default.createElement(
9250
+ return /* @__PURE__ */ import_react55.default.createElement("div", { className: "dlv-version-badges", "aria-label": commons.version.title() }, /* @__PURE__ */ import_react55.default.createElement(
9068
9251
  "a",
9069
9252
  {
9070
9253
  className: "dlv-shields-badge",
@@ -9074,9 +9257,9 @@ function VersionBadges() {
9074
9257
  "aria-label": commons.version.link({ version: CURRENT_VERSION }),
9075
9258
  title: commons.version.label({ version: CURRENT_VERSION })
9076
9259
  },
9077
- /* @__PURE__ */ import_react54.default.createElement("img", { src: LIVE_VOICE_BADGE_URL, alt: "" }),
9078
- /* @__PURE__ */ import_react54.default.createElement("span", null, `v${CURRENT_VERSION}`)
9079
- ), /* @__PURE__ */ import_react54.default.createElement(
9260
+ /* @__PURE__ */ import_react55.default.createElement("img", { src: LIVE_VOICE_BADGE_URL, alt: "" }),
9261
+ /* @__PURE__ */ import_react55.default.createElement("span", null, `v${CURRENT_VERSION}`)
9262
+ ), /* @__PURE__ */ import_react55.default.createElement(
9080
9263
  "a",
9081
9264
  {
9082
9265
  className: "dlv-shields-badge",
@@ -9086,32 +9269,32 @@ function VersionBadges() {
9086
9269
  "aria-label": commons.version.compatibilityLink({ version: TESTED_DSH_VERSION }),
9087
9270
  title: commons.version.compatibility({ version: TESTED_DSH_VERSION })
9088
9271
  },
9089
- /* @__PURE__ */ import_react54.default.createElement("img", { src: DSH_BADGE_URL, alt: "" }),
9090
- /* @__PURE__ */ import_react54.default.createElement("span", null, `v${TESTED_DSH_VERSION}`)
9272
+ /* @__PURE__ */ import_react55.default.createElement("img", { src: DSH_BADGE_URL, alt: "" }),
9273
+ /* @__PURE__ */ import_react55.default.createElement("span", null, `v${TESTED_DSH_VERSION}`)
9091
9274
  ));
9092
9275
  }
9093
9276
 
9094
9277
  // src/modules/settings/components/PluginUpdateAction.tsx
9095
- var import_react55 = __toESM(require("react"), 1);
9278
+ var import_react56 = __toESM(require("react"), 1);
9096
9279
  function PluginUpdateAction({ updater, tag }) {
9097
9280
  const { scoped: commons } = useLanguage((ctx) => ctx.commons);
9098
9281
  const text = commons.update;
9099
- const state = import_react55.default.useSyncExternalStore(
9282
+ const state = import_react56.default.useSyncExternalStore(
9100
9283
  updater.subscribe,
9101
9284
  updater.getSnapshot,
9102
9285
  updater.getSnapshot
9103
9286
  );
9104
- const [open, setOpen] = import_react55.default.useState(false);
9105
- const panelId = import_react55.default.useId();
9106
- const trigger = import_react55.default.useRef(null);
9107
- const confirmButton = import_react55.default.useRef(null);
9108
- import_react55.default.useEffect(() => {
9287
+ const [open, setOpen] = import_react56.default.useState(false);
9288
+ const panelId = import_react56.default.useId();
9289
+ const trigger = import_react56.default.useRef(null);
9290
+ const confirmButton = import_react56.default.useRef(null);
9291
+ import_react56.default.useEffect(() => {
9109
9292
  if (state.phase === "confirm") confirmButton.current?.focus();
9110
9293
  }, [state.phase]);
9111
9294
  const busy = state.phase === "checking" || state.phase === "installing";
9112
9295
  const settled = ["restart", "done", "uncertain"].includes(state.phase);
9113
9296
  const expanded = open || busy || settled || state.phase === "confirm";
9114
- return /* @__PURE__ */ import_react55.default.createElement("span", { className: "dlv-plugin-update" }, /* @__PURE__ */ import_react55.default.createElement(
9297
+ return /* @__PURE__ */ import_react56.default.createElement("span", { className: "dlv-plugin-update" }, /* @__PURE__ */ import_react56.default.createElement(
9115
9298
  "button",
9116
9299
  {
9117
9300
  type: "button",
@@ -9125,9 +9308,9 @@ function PluginUpdateAction({ updater, tag }) {
9125
9308
  void updater.prepare(tag);
9126
9309
  }
9127
9310
  },
9128
- /* @__PURE__ */ import_react55.default.createElement("span", { "aria-hidden": "true" }, "\u21BB "),
9311
+ /* @__PURE__ */ import_react56.default.createElement("span", { "aria-hidden": "true" }, "\u21BB "),
9129
9312
  busy ? text.installing() : text.now()
9130
- ), expanded && /* @__PURE__ */ import_react55.default.createElement("span", { id: panelId, className: "dlv-plugin-update-panel" }, state.phase === "confirm" ? /* @__PURE__ */ import_react55.default.createElement(import_react55.default.Fragment, null, /* @__PURE__ */ import_react55.default.createElement("span", { id: panelId + "-confirmation" }, text.confirm({ version: state.version })), /* @__PURE__ */ import_react55.default.createElement(
9313
+ ), expanded && /* @__PURE__ */ import_react56.default.createElement("span", { id: panelId, className: "dlv-plugin-update-panel" }, state.phase === "confirm" ? /* @__PURE__ */ import_react56.default.createElement(import_react56.default.Fragment, null, /* @__PURE__ */ import_react56.default.createElement("span", { id: panelId + "-confirmation" }, text.confirm({ version: state.version })), /* @__PURE__ */ import_react56.default.createElement(
9131
9314
  "button",
9132
9315
  {
9133
9316
  ref: confirmButton,
@@ -9136,7 +9319,7 @@ function PluginUpdateAction({ updater, tag }) {
9136
9319
  onClick: () => void updater.confirm()
9137
9320
  },
9138
9321
  text.now()
9139
- ), /* @__PURE__ */ import_react55.default.createElement(
9322
+ ), /* @__PURE__ */ import_react56.default.createElement(
9140
9323
  "button",
9141
9324
  {
9142
9325
  type: "button",
@@ -9147,13 +9330,13 @@ function PluginUpdateAction({ updater, tag }) {
9147
9330
  }
9148
9331
  },
9149
9332
  text.cancel()
9150
- )) : /* @__PURE__ */ import_react55.default.createElement(
9333
+ )) : /* @__PURE__ */ import_react56.default.createElement(
9151
9334
  "span",
9152
9335
  {
9153
9336
  role: state.phase === "failed" || state.phase === "uncertain" ? "alert" : "status"
9154
9337
  },
9155
9338
  state.phase === "restart" ? text.restart({ version: state.version }) : state.phase === "done" ? text.done({ version: state.version }) : state.problem ? text[state.problem]() : busy ? text.installing() : null
9156
- ), state.phase === "failed" && /* @__PURE__ */ import_react55.default.createElement("button", { type: "button", onClick: () => setOpen(false) }, text.cancel())));
9339
+ ), state.phase === "failed" && /* @__PURE__ */ import_react56.default.createElement("button", { type: "button", onClick: () => setOpen(false) }, text.cancel())));
9157
9340
  }
9158
9341
 
9159
9342
  // src/modules/settings/components/SettingsHeader.tsx
@@ -9164,7 +9347,7 @@ function SettingsHeader({
9164
9347
  const { scoped: commons } = useLanguage((ctx) => ctx.commons);
9165
9348
  const { scoped: settings } = useLanguage((ctx) => ctx.settings);
9166
9349
  const { latestRelease, updateAvailable } = useReleaseStatus();
9167
- return /* @__PURE__ */ import_react56.default.createElement("div", { className: "dlv-settings-heading" }, /* @__PURE__ */ import_react56.default.createElement("h3", null, commons.pluginName()), /* @__PURE__ */ import_react56.default.createElement(VersionBadges, null), /* @__PURE__ */ import_react56.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true }), updateAvailable && latestRelease ? /* @__PURE__ */ import_react56.default.createElement(import_react56.default.Fragment, null, pluginUpdate && /* @__PURE__ */ import_react56.default.createElement(PluginUpdateAction, { updater: pluginUpdate, tag: latestRelease.tag }), /* @__PURE__ */ import_react56.default.createElement(
9350
+ return /* @__PURE__ */ import_react57.default.createElement("div", { className: "dlv-settings-heading" }, /* @__PURE__ */ import_react57.default.createElement("h3", null, commons.pluginName()), /* @__PURE__ */ import_react57.default.createElement(VersionBadges, null), /* @__PURE__ */ import_react57.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true }), updateAvailable && latestRelease ? /* @__PURE__ */ import_react57.default.createElement(import_react57.default.Fragment, null, pluginUpdate && /* @__PURE__ */ import_react57.default.createElement(PluginUpdateAction, { updater: pluginUpdate, tag: latestRelease.tag }), /* @__PURE__ */ import_react57.default.createElement(
9168
9351
  "a",
9169
9352
  {
9170
9353
  className: "dlv-version-badge dlv-update-badge",
@@ -9174,9 +9357,9 @@ function SettingsHeader({
9174
9357
  "aria-label": commons.update.link({ version: latestRelease.tag }),
9175
9358
  title: commons.update.version({ version: latestRelease.tag })
9176
9359
  },
9177
- /* @__PURE__ */ import_react56.default.createElement("span", { className: "dlv-update-icon", "aria-hidden": true }, "\u2191"),
9360
+ /* @__PURE__ */ import_react57.default.createElement("span", { className: "dlv-update-icon", "aria-hidden": true }, "\u2191"),
9178
9361
  commons.update.label()
9179
- ), /* @__PURE__ */ import_react56.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true })) : null, /* @__PURE__ */ import_react56.default.createElement(
9362
+ ), /* @__PURE__ */ import_react57.default.createElement("span", { className: "dlv-heading-divider", "aria-hidden": true })) : null, /* @__PURE__ */ import_react57.default.createElement(
9180
9363
  "a",
9181
9364
  {
9182
9365
  className: "dlv-version-badge dlv-star-badge",
@@ -9188,7 +9371,7 @@ function SettingsHeader({
9188
9371
  },
9189
9372
  "\u2605 ",
9190
9373
  commons.repository.starLabel()
9191
- ), onClose ? /* @__PURE__ */ import_react56.default.createElement(IconButton, { icon: "close", label: settings.close(), onClick: onClose }) : null);
9374
+ ), onClose ? /* @__PURE__ */ import_react57.default.createElement(IconButton, { icon: "close", label: settings.close(), onClick: onClose }) : null);
9192
9375
  }
9193
9376
 
9194
9377
  // src/modules/core/developerExtension.ts
@@ -9213,10 +9396,10 @@ function LiveVoiceSettings({
9213
9396
  const { scoped: settingsLanguage } = useLanguage((ctx) => ctx.settings);
9214
9397
  const state = useConversationController(controller);
9215
9398
  const { invoke, error, clearError } = useConversationActions(controller);
9216
- const [activeTab, setActiveTab] = import_react57.default.useState("conversation");
9217
- const tabsId = import_react57.default.useId();
9218
- const container = import_react57.default.useRef(null);
9219
- import_react57.default.useLayoutEffect(() => {
9399
+ const [activeTab, setActiveTab] = import_react58.default.useState("conversation");
9400
+ const tabsId = import_react58.default.useId();
9401
+ const container = import_react58.default.useRef(null);
9402
+ import_react58.default.useLayoutEffect(() => {
9220
9403
  const element = container.current;
9221
9404
  if (!element) return;
9222
9405
  let viewport = element.parentElement;
@@ -9240,7 +9423,7 @@ function LiveVoiceSettings({
9240
9423
  window.removeEventListener("resize", resize);
9241
9424
  };
9242
9425
  }, []);
9243
- const developer = import_react57.default.useSyncExternalStore(
9426
+ const developer = import_react58.default.useSyncExternalStore(
9244
9427
  subscribeDeveloperExtension,
9245
9428
  readDeveloperExtension,
9246
9429
  () => null
@@ -9269,15 +9452,15 @@ function LiveVoiceSettings({
9269
9452
  invoke,
9270
9453
  updateSettings: (next) => invoke("updateSettings", next)
9271
9454
  };
9272
- return /* @__PURE__ */ import_react57.default.createElement(
9455
+ return /* @__PURE__ */ import_react58.default.createElement(
9273
9456
  "section",
9274
9457
  {
9275
9458
  ref: container,
9276
9459
  className: "dlv-settings",
9277
9460
  "aria-label": settingsLanguage.title()
9278
9461
  },
9279
- /* @__PURE__ */ import_react57.default.createElement(SettingsHeader, { onClose, pluginUpdate }),
9280
- /* @__PURE__ */ import_react57.default.createElement(
9462
+ /* @__PURE__ */ import_react58.default.createElement(SettingsHeader, { onClose, pluginUpdate }),
9463
+ /* @__PURE__ */ import_react58.default.createElement(
9281
9464
  SettingsTabs,
9282
9465
  {
9283
9466
  label: settingsLanguage.title(),
@@ -9287,7 +9470,7 @@ function LiveVoiceSettings({
9287
9470
  onChange: setActiveTab
9288
9471
  }
9289
9472
  ),
9290
- /* @__PURE__ */ import_react57.default.createElement("div", { className: "dlv-settings-scroll" }, tabs.map((tab) => /* @__PURE__ */ import_react57.default.createElement(
9473
+ /* @__PURE__ */ import_react58.default.createElement("div", { className: "dlv-settings-scroll" }, tabs.map((tab) => /* @__PURE__ */ import_react58.default.createElement(
9291
9474
  "div",
9292
9475
  {
9293
9476
  key: tab.id,
@@ -9297,10 +9480,10 @@ function LiveVoiceSettings({
9297
9480
  "aria-labelledby": `${tabsId}-tab-${tab.id}`,
9298
9481
  hidden: selectedTab !== tab.id
9299
9482
  },
9300
- tab.id === "speech" ? /* @__PURE__ */ import_react57.default.createElement(SpeakSettingsSection, { ...sectionProps }) : tab.id === "recognition" ? /* @__PURE__ */ import_react57.default.createElement(RecognitionSettingsSection, { ...sectionProps }) : /* @__PURE__ */ import_react57.default.createElement(ConversationSettingsSection, { ...sectionProps })
9483
+ tab.id === "speech" ? /* @__PURE__ */ import_react58.default.createElement(SpeakSettingsSection, { ...sectionProps }) : tab.id === "recognition" ? /* @__PURE__ */ import_react58.default.createElement(RecognitionSettingsSection, { ...sectionProps }) : /* @__PURE__ */ import_react58.default.createElement(ConversationSettingsSection, { ...sectionProps })
9301
9484
  ))),
9302
- Developer && /* @__PURE__ */ import_react57.default.createElement("footer", { className: "dlv-settings-footer" }, /* @__PURE__ */ import_react57.default.createElement(Developer, null)),
9303
- /* @__PURE__ */ import_react57.default.createElement(
9485
+ Developer && /* @__PURE__ */ import_react58.default.createElement("footer", { className: "dlv-settings-footer" }, /* @__PURE__ */ import_react58.default.createElement(Developer, null)),
9486
+ /* @__PURE__ */ import_react58.default.createElement(
9304
9487
  ErrorMessage,
9305
9488
  {
9306
9489
  error: error || state.error,
@@ -9322,7 +9505,7 @@ function createLiveVoiceSettings(..._legacyArguments) {
9322
9505
  onClose,
9323
9506
  pluginUpdate
9324
9507
  }) {
9325
- return /* @__PURE__ */ import_react58.default.createElement(LiveVoiceSettings, { controller, onClose, pluginUpdate });
9508
+ return /* @__PURE__ */ import_react59.default.createElement(LiveVoiceSettings, { controller, onClose, pluginUpdate });
9326
9509
  };
9327
9510
  }
9328
9511
 
@@ -9385,13 +9568,13 @@ function createSettingsClient(fetchImpl = globalThis.fetch) {
9385
9568
  }
9386
9569
 
9387
9570
  // src/modules/conversation/components/ConversationControls.tsx
9388
- var import_react61 = __toESM(require("react"), 1);
9571
+ var import_react62 = __toESM(require("react"), 1);
9389
9572
 
9390
9573
  // src/modules/conversation/components/MicrophoneButton.tsx
9391
- var import_react59 = __toESM(require("react"), 1);
9574
+ var import_react60 = __toESM(require("react"), 1);
9392
9575
  function MicrophoneButton(props) {
9393
9576
  const active = Boolean(props["aria-pressed"]);
9394
- return /* @__PURE__ */ import_react59.default.createElement(
9577
+ return /* @__PURE__ */ import_react60.default.createElement(
9395
9578
  IconButton,
9396
9579
  {
9397
9580
  ...props,
@@ -9403,7 +9586,7 @@ function MicrophoneButton(props) {
9403
9586
  }
9404
9587
 
9405
9588
  // src/modules/conversation/components/AutoPlaybackToggle.tsx
9406
- var import_react60 = __toESM(require("react"), 1);
9589
+ var import_react61 = __toESM(require("react"), 1);
9407
9590
  function AutoPlaybackToggle({ state, invoke, error }) {
9408
9591
  const { scoped: commons } = useLanguage((ctx) => ctx.commons);
9409
9592
  const { scoped: speak } = useLanguage((ctx) => ctx.speak);
@@ -9415,7 +9598,7 @@ function AutoPlaybackToggle({ state, invoke, error }) {
9415
9598
  }) : speak.autoPlayback.status({
9416
9599
  state: active ? commons.on() : commons.off()
9417
9600
  });
9418
- return /* @__PURE__ */ import_react60.default.createElement(
9601
+ return /* @__PURE__ */ import_react61.default.createElement(
9419
9602
  IconButton,
9420
9603
  {
9421
9604
  className: "dlv-composer-toggle",
@@ -9438,7 +9621,7 @@ function ConversationControls({ controller }) {
9438
9621
  const { invoke, error, clearError } = useConversationActions(controller);
9439
9622
  const busy = state.conversation || state.listening || state.starting || state.recognizing;
9440
9623
  const label = busy ? commons.conversation.end() : commons.conversation.start();
9441
- return /* @__PURE__ */ import_react61.default.createElement(import_react61.default.Fragment, null, /* @__PURE__ */ import_react61.default.createElement(AutoPlaybackToggle, { state, invoke, error }), /* @__PURE__ */ import_react61.default.createElement(
9624
+ return /* @__PURE__ */ import_react62.default.createElement(import_react62.default.Fragment, null, /* @__PURE__ */ import_react62.default.createElement(AutoPlaybackToggle, { state, invoke, error }), /* @__PURE__ */ import_react62.default.createElement(
9442
9625
  MicrophoneButton,
9443
9626
  {
9444
9627
  label,
@@ -9447,7 +9630,7 @@ function ConversationControls({ controller }) {
9447
9630
  busy ? state.conversation ? "stopConversationInput" : "cancelDictation" : "startConversation"
9448
9631
  )
9449
9632
  }
9450
- ), /* @__PURE__ */ import_react61.default.createElement(
9633
+ ), /* @__PURE__ */ import_react62.default.createElement(
9451
9634
  ErrorMessage,
9452
9635
  {
9453
9636
  error,
@@ -9459,7 +9642,7 @@ function ConversationControls({ controller }) {
9459
9642
  }
9460
9643
 
9461
9644
  // src/modules/conversation/components/SpeakButton.tsx
9462
- var import_react62 = __toESM(require("react"), 1);
9645
+ var import_react63 = __toESM(require("react"), 1);
9463
9646
  function SpeakButton({
9464
9647
  active = false,
9465
9648
  disabled = false,
@@ -9468,7 +9651,7 @@ function SpeakButton({
9468
9651
  }) {
9469
9652
  const { scoped: speak } = useLanguage((ctx) => ctx.speak);
9470
9653
  const resolvedLabel = label ?? (active ? speak.playback.stop() : speak.playback.message());
9471
- return /* @__PURE__ */ import_react62.default.createElement(
9654
+ return /* @__PURE__ */ import_react63.default.createElement(
9472
9655
  IconButton,
9473
9656
  {
9474
9657
  className: "dlv-speaker",
@@ -10514,8 +10697,8 @@ function apply(ctx) {
10514
10697
  return entry;
10515
10698
  }
10516
10699
  function useEntry(sessionId, kind) {
10517
- const [entry, setEntry] = import_react63.default.useState(null);
10518
- import_react63.default.useLayoutEffect(() => {
10700
+ const [entry, setEntry] = import_react64.default.useState(null);
10701
+ import_react64.default.useLayoutEffect(() => {
10519
10702
  if (disposed) return;
10520
10703
  const current = get(sessionId);
10521
10704
  current.refs++;
@@ -10535,8 +10718,8 @@ function apply(ctx) {
10535
10718
  function useComposer(entry, props) {
10536
10719
  const subscribedInput = props.useInput?.((value) => value);
10537
10720
  const input = subscribedInput ?? props.input;
10538
- const token = import_react63.default.useRef({});
10539
- import_react63.default.useLayoutEffect(() => {
10721
+ const token = import_react64.default.useRef({});
10722
+ import_react64.default.useLayoutEffect(() => {
10540
10723
  if (!entry || entry.closed || disposed) return;
10541
10724
  return () => {
10542
10725
  entry.composers.delete(token.current);
@@ -10548,7 +10731,7 @@ function apply(ctx) {
10548
10731
  }
10549
10732
  };
10550
10733
  }, [entry]);
10551
- import_react63.default.useLayoutEffect(() => {
10734
+ import_react64.default.useLayoutEffect(() => {
10552
10735
  if (!entry || entry.closed || disposed) return;
10553
10736
  if (!input || typeof props.inputActions?.setDraft !== "function") return;
10554
10737
  if (meetingEntry !== entry) {
@@ -10593,7 +10776,7 @@ function apply(ctx) {
10593
10776
  })
10594
10777
  );
10595
10778
  }, [entry, input, props.inputActions]);
10596
- import_react63.default.useLayoutEffect(() => {
10779
+ import_react64.default.useLayoutEffect(() => {
10597
10780
  if (!entry || entry.closed || disposed || !input) return;
10598
10781
  const published = typeof input.draft === "string" ? input.draft : "";
10599
10782
  entry.publishedDraft = published;
@@ -10612,7 +10795,7 @@ function apply(ctx) {
10612
10795
  function Buttons(props) {
10613
10796
  const entry = useEntry(props.sessionId, "buttons");
10614
10797
  useComposer(entry, props);
10615
- return entry ? /* @__PURE__ */ import_react63.default.createElement("div", { className: "dlv-composer-toggles" }, /* @__PURE__ */ import_react63.default.createElement(
10798
+ return entry ? /* @__PURE__ */ import_react64.default.createElement("div", { className: "dlv-composer-toggles" }, /* @__PURE__ */ import_react64.default.createElement(
10616
10799
  MeetingToggle,
10617
10800
  {
10618
10801
  meeting: entry.meeting,
@@ -10622,11 +10805,11 @@ function apply(ctx) {
10622
10805
  else entry.startMeetingSource("shared");
10623
10806
  }
10624
10807
  }
10625
- ), /* @__PURE__ */ import_react63.default.createElement(ConversationControls, { controller: entry.controller })) : null;
10808
+ ), /* @__PURE__ */ import_react64.default.createElement(ConversationControls, { controller: entry.controller })) : null;
10626
10809
  }
10627
10810
  function Settings() {
10628
- const [controller, setController] = import_react63.default.useState(null);
10629
- import_react63.default.useEffect(() => {
10811
+ const [controller, setController] = import_react64.default.useState(null);
10812
+ import_react64.default.useEffect(() => {
10630
10813
  const settings = preferences.getSnapshot();
10631
10814
  const browser = new BrowserSpeakingEngine({ lang: settings.lang });
10632
10815
  const qwen = new QwenHttpSpeakingEngine({ lang: settings.lang });
@@ -10697,21 +10880,21 @@ function apply(ctx) {
10697
10880
  void c.dispose();
10698
10881
  };
10699
10882
  }, []);
10700
- return controller ? /* @__PURE__ */ import_react63.default.createElement(SettingsPanel, { controller, pluginUpdate }) : null;
10883
+ return controller ? /* @__PURE__ */ import_react64.default.createElement(SettingsPanel, { controller, pluginUpdate }) : null;
10701
10884
  }
10702
10885
  registerSettingsSlot(ctx, Settings, () => t("dsh-live-voice.commons.pluginName"));
10703
10886
  function Dock(props) {
10704
10887
  const entry = useEntry(props.sessionId, "dock");
10705
10888
  useComposer(entry, props);
10706
- return entry ? /* @__PURE__ */ import_react63.default.createElement("div", { className: "dlv-bar-stack" }, /* @__PURE__ */ import_react63.default.createElement(SpeechStatusBar, { controller: entry.controller }), /* @__PURE__ */ import_react63.default.createElement(MeetingBars, { meeting: entry.meeting }), /* @__PURE__ */ import_react63.default.createElement(ConversationStatusBar, { controller: entry.controller, includeSpeech: false })) : null;
10889
+ return entry ? /* @__PURE__ */ import_react64.default.createElement("div", { className: "dlv-bar-stack" }, /* @__PURE__ */ import_react64.default.createElement(SpeechStatusBar, { controller: entry.controller }), /* @__PURE__ */ import_react64.default.createElement(MeetingBars, { meeting: entry.meeting }), /* @__PURE__ */ import_react64.default.createElement(ConversationStatusBar, { controller: entry.controller, includeSpeech: false })) : null;
10707
10890
  }
10708
10891
  function QuestionStatusView({ entry }) {
10709
- const snapshot = import_react63.default.useSyncExternalStore(
10892
+ const snapshot = import_react64.default.useSyncExternalStore(
10710
10893
  entry.controller.subscribe,
10711
10894
  entry.controller.getSnapshot
10712
10895
  );
10713
- const [overlayStyle, setOverlayStyle] = import_react63.default.useState();
10714
- import_react63.default.useLayoutEffect(() => {
10896
+ const [overlayStyle, setOverlayStyle] = import_react64.default.useState();
10897
+ import_react64.default.useLayoutEffect(() => {
10715
10898
  if (!snapshot.answeringQuestion) return;
10716
10899
  const seat = document.querySelector("[data-composer-seat]");
10717
10900
  if (!seat) return;
@@ -10733,7 +10916,7 @@ function apply(ctx) {
10733
10916
  }, [snapshot.answeringQuestion]);
10734
10917
  const target = typeof document === "undefined" ? null : document.body;
10735
10918
  return snapshot.answeringQuestion && target && overlayStyle ? (0, import_react_dom.createPortal)(
10736
- /* @__PURE__ */ import_react63.default.createElement(
10919
+ /* @__PURE__ */ import_react64.default.createElement(
10737
10920
  ConversationStatusBar,
10738
10921
  {
10739
10922
  controller: entry.controller,
@@ -10747,17 +10930,17 @@ function apply(ctx) {
10747
10930
  }
10748
10931
  function QuestionStatus(props) {
10749
10932
  const entry = useEntry(props.sessionId, "question-status");
10750
- return entry ? /* @__PURE__ */ import_react63.default.createElement(QuestionStatusView, { entry }) : null;
10933
+ return entry ? /* @__PURE__ */ import_react64.default.createElement(QuestionStatusView, { entry }) : null;
10751
10934
  }
10752
10935
  function ActionView({ entry, messageId }) {
10753
- const snapshot = import_react63.default.useSyncExternalStore(
10936
+ const snapshot = import_react64.default.useSyncExternalStore(
10754
10937
  entry.controller.subscribe,
10755
10938
  entry.controller.getSnapshot
10756
10939
  );
10757
- const chat = import_react63.default.useSyncExternalStore(entry.subscribeChat, entry.readChat);
10940
+ const chat = import_react64.default.useSyncExternalStore(entry.subscribeChat, entry.readChat);
10758
10941
  const message2 = addressedTurn(assistantMessages(chat), messageId);
10759
10942
  const active = snapshot.speaking && message2.id === snapshot.activeMessageId;
10760
- return /* @__PURE__ */ import_react63.default.createElement(
10943
+ return /* @__PURE__ */ import_react64.default.createElement(
10761
10944
  SpeakButton,
10762
10945
  {
10763
10946
  active,
@@ -10771,7 +10954,7 @@ function apply(ctx) {
10771
10954
  }
10772
10955
  function Action(props) {
10773
10956
  const entry = useEntry(props.sessionId, "action");
10774
- return entry ? /* @__PURE__ */ import_react63.default.createElement(ActionView, { entry, messageId: props.messageId }) : null;
10957
+ return entry ? /* @__PURE__ */ import_react64.default.createElement(ActionView, { entry, messageId: props.messageId }) : null;
10775
10958
  }
10776
10959
  ctx.effect(() => {
10777
10960
  const style = document.createElement("style");