dsh-live-voice 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +48 -14
  2. package/lib/client.js +6474 -3396
  3. package/lib/server.js +29 -6
  4. package/package.json +9 -30
  5. package/src/app/AGENTS.md +1 -1
  6. package/src/app/ARCHITECTURE.md +1 -1
  7. package/src/app/client/apply.tsx +242 -35
  8. package/src/app/client/composerSelection.ts +92 -0
  9. package/src/app/client/i18n/catalogs/base.ts +35 -0
  10. package/src/app/client/i18n/catalogs/en.ts +43 -3
  11. package/src/app/client/i18n/catalogs/es.ts +51 -11
  12. package/src/app/client/i18n/catalogs/fr.ts +52 -11
  13. package/src/app/client/i18n/catalogs/hi.ts +50 -11
  14. package/src/app/client/i18n/catalogs/pt-BR.ts +52 -11
  15. package/src/app/client/i18n/catalogs/zh.ts +50 -11
  16. package/src/modules/AGENTS.md +1 -1
  17. package/src/modules/conversation/ARCHITECTURE.md +6 -2
  18. package/src/modules/conversation/components/AutoPlaybackToggle.tsx +31 -0
  19. package/src/modules/conversation/components/ConversationControls.tsx +23 -8
  20. package/src/modules/conversation/components/ConversationStatusBar.tsx +91 -93
  21. package/src/modules/conversation/components/DeliveryModeButton.tsx +3 -3
  22. package/src/modules/conversation/components/MeetingControls.tsx +40 -0
  23. package/src/modules/conversation/components/MicrophoneButton.tsx +15 -2
  24. package/src/modules/conversation/components/PlaybackControls.tsx +20 -5
  25. package/src/modules/conversation/components/RecognitionBar.tsx +14 -0
  26. package/src/modules/conversation/components/ScrollingSpeechCaption.tsx +100 -0
  27. package/src/modules/conversation/components/SpeechStatusBar.tsx +99 -0
  28. package/src/modules/conversation/components/Waveform.tsx +8 -3
  29. package/src/modules/conversation/components/conversationStatus.ts +1 -3
  30. package/src/modules/conversation/components/createConversationComponents.tsx +1 -1
  31. package/src/modules/conversation/components/index.ts +1 -0
  32. package/src/modules/conversation/components/speechCaption.ts +27 -0
  33. package/src/modules/conversation/models/chat.ts +15 -2
  34. package/src/modules/conversation/models/meeting.ts +256 -0
  35. package/src/modules/conversation/models/meetingTranscript.ts +63 -0
  36. package/src/modules/core/ARCHITECTURE.md +1 -1
  37. package/src/modules/core/coordinator.ts +353 -81
  38. package/src/modules/core/developerExtension.ts +26 -0
  39. package/src/modules/core/diagnostics.ts +153 -0
  40. package/src/modules/core/filters.ts +20 -15
  41. package/src/modules/core/markdownSpeech.ts +154 -0
  42. package/src/modules/core/settings.ts +17 -13
  43. package/src/modules/core/sharedAudio.ts +88 -0
  44. package/src/modules/core/speechDefaults.ts +21 -0
  45. package/src/modules/recognition/engines/browser/BrowserRecognitionEngine.ts +8 -1
  46. package/src/modules/recognition/engines/whisper/WhisperRecognitionEngine.ts +10 -3
  47. package/src/modules/settings/components/LiveVoiceSettings.tsx +17 -3
  48. package/src/modules/settings/components/SettingsHeader.tsx +1 -1
  49. package/src/modules/settings/models/settingsStorage.ts +11 -3
  50. package/src/modules/settings/sections/conversation/ConversationDelaySettings.tsx +8 -3
  51. package/src/modules/settings/sections/conversation/DeliverySettings.tsx +25 -2
  52. package/src/modules/settings/sections/recognition/RecognitionEngineSettings.tsx +2 -1
  53. package/src/modules/settings/sections/speak/SpeechEngineSettings.tsx +12 -1
  54. package/src/modules/speak/engines/audio/HostAudioEngine.ts +19 -2
  55. package/src/modules/speak/engines/browser/BrowserSpeakingEngine.ts +33 -2
  56. package/src/shared/design-system/buttons/ToggleButton.tsx +8 -1
  57. package/src/shared/design-system/icons/icons.ts +6 -0
  58. package/src/styles/index.ts +13 -3
  59. package/CHANGELOG.md +0 -240
  60. package/DEVELOPMENT.md +0 -83
  61. package/PLAN.md +0 -296
  62. package/docs/ARCHITECTURE.md +0 -33
  63. package/docs/CHOOSING-AN-ENGINE.md +0 -120
  64. package/docs/CONFIGURATION.md +0 -163
  65. package/docs/REVIEW.md +0 -35
  66. package/docs/VOICE-LIFECYCLE.md +0 -63
  67. package/scripts/build.ts +0 -62
  68. package/scripts/check-dist.mjs +0 -38
  69. package/scripts/preview-ui.ts +0 -31
  70. package/scripts/probe-browser.ts +0 -46
@@ -0,0 +1,92 @@
1
+ /** Preserve browser selection around DSH's synchronous, whole-draft editor write.
2
+ * Selection changes are published back through the editor's normal selectionchange bridge.
3
+ * Capture offsets, not live Ranges: setDraft replaces the underlying text nodes.
4
+ */
5
+ export function preserveComposerSelection(write: () => void, doc: Document = document) {
6
+ const active = doc.activeElement as HTMLElement | null;
7
+ const selection = doc.getSelection();
8
+ const editable = active?.closest<HTMLElement>('[contenteditable="true"]');
9
+ const ownsSelection =
10
+ editable &&
11
+ selection?.anchorNode &&
12
+ selection.focusNode &&
13
+ editable.contains(selection.anchorNode) &&
14
+ editable.contains(selection.focusNode);
15
+ const offset = (node: Node, at: number) => {
16
+ const range = doc.createRange();
17
+ range.selectNodeContents(editable!);
18
+ range.setEnd(node, at);
19
+ return range.toString().length;
20
+ };
21
+ const anchor = ownsSelection ? offset(selection!.anchorNode!, selection!.anchorOffset) : null;
22
+ const focus = ownsSelection ? offset(selection!.focusNode!, selection!.focusOffset) : null;
23
+ const outside =
24
+ !ownsSelection && selection?.rangeCount ? selection.getRangeAt(0).cloneRange() : null;
25
+ const input = active as HTMLInputElement | HTMLTextAreaElement | null;
26
+ const inputSelection =
27
+ input && typeof input.selectionStart === 'number'
28
+ ? {
29
+ start: input.selectionStart,
30
+ end: input.selectionEnd!,
31
+ direction: input.selectionDirection!,
32
+ }
33
+ : null;
34
+ const scrollTop = active?.scrollTop,
35
+ scrollLeft = active?.scrollLeft;
36
+ const bookmark = (node: Node, at: number) => {
37
+ const path: number[] = [];
38
+ let child = node;
39
+ while (child !== editable && child.parentNode) {
40
+ path.unshift(Array.prototype.indexOf.call(child.parentNode.childNodes, child));
41
+ child = child.parentNode;
42
+ }
43
+ return { path, at, type: node.nodeType, text: node.textContent };
44
+ };
45
+ const anchorMark = ownsSelection
46
+ ? bookmark(selection!.anchorNode!, selection!.anchorOffset)
47
+ : null;
48
+ const focusMark = ownsSelection ? bookmark(selection!.focusNode!, selection!.focusOffset) : null;
49
+ write();
50
+ if (active?.isConnected && doc.activeElement !== active) active.focus({ preventScroll: true });
51
+ if (ownsSelection && editable!.isConnected) {
52
+ const point = (at: number): [Node, number] => {
53
+ const walker = doc.createTreeWalker(editable!, 4);
54
+ let node: Node | null;
55
+ let last: Node = editable!;
56
+ while ((node = walker.nextNode())) {
57
+ last = node;
58
+ const length = node.textContent?.length ?? 0;
59
+ if (at <= length) return [node, at];
60
+ at -= length;
61
+ }
62
+ return [last, last === editable ? last.childNodes.length : (last.textContent?.length ?? 0)];
63
+ };
64
+ const restorePoint = (mark: NonNullable<typeof anchorMark>, at: number): [Node, number] => {
65
+ let node: Node | undefined = editable!;
66
+ for (const index of mark.path) node = node?.childNodes[index];
67
+ // Preserve paragraph/empty-line boundaries when the rebuilt structure matches.
68
+ if (
69
+ node &&
70
+ node.nodeType === mark.type &&
71
+ (node.nodeType !== 3 || node.textContent?.startsWith(mark.text ?? '')) &&
72
+ mark.at <= (node.nodeType === 3 ? node.textContent!.length : node.childNodes.length)
73
+ )
74
+ return [node, mark.at];
75
+ return point(at);
76
+ };
77
+ const [a, ao] = restorePoint(anchorMark!, anchor!);
78
+ const [f, fo] = restorePoint(focusMark!, focus!);
79
+ selection!.setBaseAndExtent(a, ao, f, fo);
80
+ // Let Lexical adopt the restored DOM selection instead of retaining selectEnd().
81
+ doc.dispatchEvent(new doc.defaultView!.Event('selectionchange'));
82
+ } else if (inputSelection && active?.isConnected) {
83
+ input!.setSelectionRange(inputSelection.start, inputSelection.end, inputSelection.direction);
84
+ } else if (outside?.startContainer.isConnected && outside.endContainer.isConnected) {
85
+ selection?.removeAllRanges();
86
+ selection?.addRange(outside);
87
+ }
88
+ if (active?.isConnected) {
89
+ active.scrollTop = scrollTop!;
90
+ active.scrollLeft = scrollLeft!;
91
+ }
92
+ }
@@ -23,12 +23,14 @@ export interface LiveVoiceTranslation extends Translation {
23
23
  'dsh-live-voice.commons.input.listening': string;
24
24
  'dsh-live-voice.commons.input.listeningBadge': string;
25
25
  'dsh-live-voice.commons.manual': string;
26
+ 'dsh-live-voice.commons.milliseconds': string;
26
27
  'dsh-live-voice.commons.off': string;
27
28
  'dsh-live-voice.commons.on': string;
28
29
  'dsh-live-voice.commons.pluginName': string;
29
30
  'dsh-live-voice.commons.queue': string;
30
31
  'dsh-live-voice.commons.repository.starLabel': string;
31
32
  'dsh-live-voice.commons.repository.starLink': string;
33
+ 'dsh-live-voice.commons.second': string;
32
34
  'dsh-live-voice.commons.seconds': string;
33
35
  'dsh-live-voice.commons.send': string;
34
36
  'dsh-live-voice.commons.status.ready': string;
@@ -43,6 +45,24 @@ export interface LiveVoiceTranslation extends Translation {
43
45
  'dsh-live-voice.commons.version.label': string;
44
46
  'dsh-live-voice.commons.version.link': string;
45
47
  'dsh-live-voice.commons.version.title': string;
48
+ 'dsh-live-voice.meeting.failed': string;
49
+ 'dsh-live-voice.meeting.help': string;
50
+ 'dsh-live-voice.meeting.httpRequired': string;
51
+ 'dsh-live-voice.meeting.inactive': string;
52
+ 'dsh-live-voice.meeting.listening': string;
53
+ 'dsh-live-voice.meeting.microphone': string;
54
+ 'dsh-live-voice.meeting.noAudio': string;
55
+ 'dsh-live-voice.meeting.processing': string;
56
+ 'dsh-live-voice.meeting.shared': string;
57
+ 'dsh-live-voice.meeting.start': string;
58
+ 'dsh-live-voice.meeting.startShared': string;
59
+ 'dsh-live-voice.meeting.starting': string;
60
+ 'dsh-live-voice.meeting.stop': string;
61
+ 'dsh-live-voice.meeting.stopShared': string;
62
+ 'dsh-live-voice.meeting.timestamps': string;
63
+ 'dsh-live-voice.meeting.timestampsBadge': string;
64
+ 'dsh-live-voice.meeting.title': string;
65
+ 'dsh-live-voice.meeting.unavailable': string;
46
66
  'dsh-live-voice.recognition.autoSend.countdownHelp': string;
47
67
  'dsh-live-voice.recognition.browser.autoInstallPack': string;
48
68
  'dsh-live-voice.recognition.browser.help': string;
@@ -131,6 +151,10 @@ export interface LiveVoiceTranslation extends Translation {
131
151
  'dsh-live-voice.settings.autoSend.countdown': string;
132
152
  'dsh-live-voice.settings.autoSend.delay': string;
133
153
  'dsh-live-voice.settings.close': string;
154
+ 'dsh-live-voice.settings.delivery.dshEnterHelp': string;
155
+ 'dsh-live-voice.settings.delivery.dshEnterQuestion': string;
156
+ 'dsh-live-voice.settings.delivery.dshEnterQueue': string;
157
+ 'dsh-live-voice.settings.delivery.dshEnterSteer': string;
134
158
  'dsh-live-voice.settings.delivery.label': string;
135
159
  'dsh-live-voice.settings.delivery.manualLabel': string;
136
160
  'dsh-live-voice.settings.delivery.queueLabel': string;
@@ -153,6 +177,8 @@ export interface LiveVoiceTranslation extends Translation {
153
177
  'dsh-live-voice.speak.agentContext.help': string;
154
178
  'dsh-live-voice.speak.agentContext.label': string;
155
179
  'dsh-live-voice.speak.agentContext.restore': string;
180
+ 'dsh-live-voice.speak.autoPlayback.disable': string;
181
+ 'dsh-live-voice.speak.autoPlayback.enable': string;
156
182
  'dsh-live-voice.speak.autoPlayback.enabled': string;
157
183
  'dsh-live-voice.speak.autoPlayback.help': string;
158
184
  'dsh-live-voice.speak.autoPlayback.label': string;
@@ -164,6 +190,11 @@ export interface LiveVoiceTranslation extends Translation {
164
190
  'dsh-live-voice.speak.browser.name': string;
165
191
  'dsh-live-voice.speak.browser.outputHelp': string;
166
192
  'dsh-live-voice.speak.browser.voice': string;
193
+ 'dsh-live-voice.speak.captions.approximate': string;
194
+ 'dsh-live-voice.speak.captions.loading': string;
195
+ 'dsh-live-voice.speak.captions.position': string;
196
+ 'dsh-live-voice.speak.captions.progress': string;
197
+ 'dsh-live-voice.speak.captions.title': string;
167
198
  'dsh-live-voice.speak.engine.label': string;
168
199
  'dsh-live-voice.speak.engine.playbackHelp': string;
169
200
  'dsh-live-voice.speak.filters.code.enabled': string;
@@ -176,6 +207,8 @@ export interface LiveVoiceTranslation extends Translation {
176
207
  'dsh-live-voice.speak.macos.label': string;
177
208
  'dsh-live-voice.speak.macos.name': string;
178
209
  'dsh-live-voice.speak.macos.outputHelp': string;
210
+ 'dsh-live-voice.speak.macos.voice': string;
211
+ 'dsh-live-voice.speak.macos.voiceHelp': string;
179
212
  'dsh-live-voice.speak.output.checking': string;
180
213
  'dsh-live-voice.speak.output.device': string;
181
214
  'dsh-live-voice.speak.output.fallbackName': string;
@@ -186,6 +219,7 @@ export interface LiveVoiceTranslation extends Translation {
186
219
  'dsh-live-voice.speak.playback.message': string;
187
220
  'dsh-live-voice.speak.playback.next': string;
188
221
  'dsh-live-voice.speak.playback.pause': string;
222
+ 'dsh-live-voice.speak.playback.previous': string;
189
223
  'dsh-live-voice.speak.playback.resume': string;
190
224
  'dsh-live-voice.speak.playback.stop': string;
191
225
  'dsh-live-voice.speak.playback.stopAll': string;
@@ -215,6 +249,7 @@ export interface LiveVoiceTranslation extends Translation {
215
249
  'dsh-live-voice.speak.rate.label': string;
216
250
  'dsh-live-voice.speak.responseDelay.help': string;
217
251
  'dsh-live-voice.speak.responseDelay.label': string;
252
+ 'dsh-live-voice.speak.responseDelay.none': string;
218
253
  'dsh-live-voice.speak.segmentGap.help': string;
219
254
  'dsh-live-voice.speak.segmentGap.label': string;
220
255
  'dsh-live-voice.speak.status.paused': string;
@@ -23,12 +23,14 @@ const en: LiveVoiceTranslation = {
23
23
  'dsh-live-voice.commons.input.listening': 'listening',
24
24
  'dsh-live-voice.commons.input.listeningBadge': 'LISTENING',
25
25
  'dsh-live-voice.commons.manual': 'manual',
26
+ 'dsh-live-voice.commons.milliseconds': '{milliseconds} ms',
26
27
  'dsh-live-voice.commons.off': 'off',
27
28
  'dsh-live-voice.commons.on': 'on',
28
29
  'dsh-live-voice.commons.pluginName': 'Live Voice',
29
30
  'dsh-live-voice.commons.queue': 'queue',
30
31
  'dsh-live-voice.commons.repository.starLabel': 'Star Us on GitHub',
31
32
  'dsh-live-voice.commons.repository.starLink': 'Star DSH Live Voice on GitHub',
33
+ 'dsh-live-voice.commons.second': '1 second',
32
34
  'dsh-live-voice.commons.seconds': '{seconds} seconds',
33
35
  'dsh-live-voice.commons.send': 'SEND',
34
36
  'dsh-live-voice.commons.status.ready': 'Voice ready',
@@ -44,6 +46,25 @@ const en: LiveVoiceTranslation = {
44
46
  'dsh-live-voice.commons.version.label': 'DSH Live Voice v{version}',
45
47
  'dsh-live-voice.commons.version.link': 'DSH Live Voice v{version}. Open releases',
46
48
  'dsh-live-voice.commons.version.title': 'Version information',
49
+ 'dsh-live-voice.meeting.failed': 'Recognition failed.',
50
+ 'dsh-live-voice.meeting.help':
51
+ 'Independent sources; transcripts stay in the composer until you send them manually.',
52
+ 'dsh-live-voice.meeting.httpRequired': 'Use Qwen or Whisper HTTP for meeting mode.',
53
+ 'dsh-live-voice.meeting.inactive': 'Inactive',
54
+ 'dsh-live-voice.meeting.listening': 'Listening',
55
+ 'dsh-live-voice.meeting.microphone': 'Microphone',
56
+ 'dsh-live-voice.meeting.noAudio': 'No audio was shared. Enable audio in the sharing dialog.',
57
+ 'dsh-live-voice.meeting.processing': 'Transcribing…',
58
+ 'dsh-live-voice.meeting.shared': 'Shared audio',
59
+ 'dsh-live-voice.meeting.start': 'Start source',
60
+ 'dsh-live-voice.meeting.startShared': 'Start audio sharing',
61
+ 'dsh-live-voice.meeting.starting': 'Starting…',
62
+ 'dsh-live-voice.meeting.stop': 'Stop source',
63
+ 'dsh-live-voice.meeting.stopShared': 'Stop audio sharing',
64
+ 'dsh-live-voice.meeting.timestamps': 'Transcript timestamps',
65
+ 'dsh-live-voice.meeting.timestampsBadge': 'Timestamp',
66
+ 'dsh-live-voice.meeting.title': 'Meeting mode',
67
+ 'dsh-live-voice.meeting.unavailable': 'Recognition is unavailable.',
47
68
  'dsh-live-voice.recognition.autoSend.countdownHelp':
48
69
  'Countdown starts after a final recognized phrase. New speech or edits cancel it.',
49
70
  'dsh-live-voice.recognition.browser.autoInstallPack':
@@ -68,7 +89,7 @@ const en: LiveVoiceTranslation = {
68
89
  'dsh-live-voice.recognition.dictation.cancel': 'Cancel dictation',
69
90
  'dsh-live-voice.recognition.engine.label': 'Recognition engine',
70
91
  'dsh-live-voice.recognition.headphoneMode.help':
71
- 'Open microphone keeps listening while responses play. When your speech is detected, playback pauses and resumes only when you choose.',
92
+ 'The microphone stays open during playback. Recognized non-echo speech can pause playback after the configured delay; it resumes after microphone and shared meeting audio stay silent for that delay. Manual Pause requires Resume.',
72
93
  'dsh-live-voice.recognition.headphoneMode.label': 'Headphones — open microphone',
73
94
  'dsh-live-voice.recognition.holdToTalk.enabled': 'Hold Control to talk',
74
95
  'dsh-live-voice.recognition.holdToTalk.help':
@@ -84,7 +105,8 @@ const en: LiveVoiceTranslation = {
84
105
  'dsh-live-voice.recognition.microphone.device': 'Input device',
85
106
  'dsh-live-voice.recognition.microphone.failure': 'Microphone: {reason}',
86
107
  'dsh-live-voice.recognition.microphone.ignore': 'Ignore composer input',
87
- 'dsh-live-voice.recognition.microphone.inputStatus': 'Microphone input: {state}',
108
+ 'dsh-live-voice.recognition.microphone.inputStatus':
109
+ 'Audio input: {state}. Ignoring discards dictation, not capture: speech interruption stays active; configured voice commands still work when enabled. Stop capture with the source button in the composer.',
88
110
  'dsh-live-voice.recognition.microphone.label': 'Microphone',
89
111
  'dsh-live-voice.recognition.microphone.permissionHelp':
90
112
  'Microphone permission will be requested only when you start dictation or a voice conversation.',
@@ -130,7 +152,7 @@ const en: LiveVoiceTranslation = {
130
152
  'dsh-live-voice.recognition.silenceDetection.pauseLabel': 'Pause before sending',
131
153
  'dsh-live-voice.recognition.silenceDetection.title': 'Silence detection settings',
132
154
  'dsh-live-voice.recognition.speakerMode.help':
133
- 'Gated listening releases the microphone while responses play, preventing speaker audio from being recognized. Use Take microphone to interrupt.',
155
+ 'Playback releases the microphone to reduce speaker feedback. Take microphone interrupts manually; recognized non-echo participants in shared meeting audio can pause playback and it resumes after stable silence.',
134
156
  'dsh-live-voice.recognition.speakerMode.label': 'Speakers — gated listening',
135
157
  'dsh-live-voice.recognition.status.answer': 'Recognizing answer…',
136
158
  'dsh-live-voice.recognition.status.awaitingAnswer': 'Listening for your answer…',
@@ -158,6 +180,12 @@ const en: LiveVoiceTranslation = {
158
180
  'dsh-live-voice.settings.autoSend.countdown': 'Sending in {remaining}…',
159
181
  'dsh-live-voice.settings.autoSend.delay': 'Send after silence',
160
182
  'dsh-live-voice.settings.close': 'Close voice settings',
183
+ 'dsh-live-voice.settings.delivery.dshEnterHelp':
184
+ 'Live Voice uses your answer to choose the correct action for Steer and Queue. This does not change DSH settings; match your current DSH configuration and update this answer if it changes.',
185
+ 'dsh-live-voice.settings.delivery.dshEnterQuestion':
186
+ 'In your DSH, what does Enter do when the agent is busy?',
187
+ 'dsh-live-voice.settings.delivery.dshEnterQueue': 'Puts the message in the queue',
188
+ 'dsh-live-voice.settings.delivery.dshEnterSteer': 'Sends the message as steer',
161
189
  'dsh-live-voice.settings.delivery.label': 'Sending mode',
162
190
  'dsh-live-voice.settings.delivery.manualLabel': 'Off — review and send manually',
163
191
  'dsh-live-voice.settings.delivery.queueLabel': 'Queue — automatically add after silence',
@@ -185,6 +213,8 @@ const en: LiveVoiceTranslation = {
185
213
  'This English instruction is sent to the agent only during an active voice conversation with automatic assistant speech enabled.',
186
214
  'dsh-live-voice.speak.agentContext.label': 'Agent voice context',
187
215
  'dsh-live-voice.speak.agentContext.restore': 'Restore default',
216
+ 'dsh-live-voice.speak.autoPlayback.disable': 'Disable automatic assistant speech',
217
+ 'dsh-live-voice.speak.autoPlayback.enable': 'Enable automatic assistant speech',
188
218
  'dsh-live-voice.speak.autoPlayback.enabled': 'Automatically speak new assistant messages',
189
219
  'dsh-live-voice.speak.autoPlayback.help':
190
220
  'During a voice conversation, assistant phrases are announced automatically. Playback waits while you are speaking.',
@@ -200,6 +230,11 @@ const en: LiveVoiceTranslation = {
200
230
  'dsh-live-voice.speak.browser.outputHelp':
201
231
  'Browser speech synthesis may ignore the selected output device; this browser API normally follows the system default.',
202
232
  'dsh-live-voice.speak.browser.voice': 'Local browser voice',
233
+ 'dsh-live-voice.speak.captions.approximate': 'Approximate speech captions',
234
+ 'dsh-live-voice.speak.captions.loading': 'Preparing speech audio',
235
+ 'dsh-live-voice.speak.captions.position': 'Speech segment {index} of {total}',
236
+ 'dsh-live-voice.speak.captions.progress': 'Approximate speech progress',
237
+ 'dsh-live-voice.speak.captions.title': 'Speech and live captions',
203
238
  'dsh-live-voice.speak.engine.label': 'Speech engine',
204
239
  'dsh-live-voice.speak.engine.playbackHelp':
205
240
  'Qwen and macOS say synthesize on the DSH host; compact AAC/M4A audio plays in this browser. Browser speech synthesizes and plays on this device.',
@@ -215,6 +250,9 @@ const en: LiveVoiceTranslation = {
215
250
  'dsh-live-voice.speak.macos.label': 'macOS say — host audio',
216
251
  'dsh-live-voice.speak.macos.name': 'macOS say',
217
252
  'dsh-live-voice.speak.macos.outputHelp': 'macOS say uses the output selected on the DSH host.',
253
+ 'dsh-live-voice.speak.macos.voice': 'macOS say voice',
254
+ 'dsh-live-voice.speak.macos.voiceHelp':
255
+ 'Enter the exact voice name used by say -v. Leave empty to use the macOS default voice. List host voices in Terminal with say -v "?".',
218
256
  'dsh-live-voice.speak.output.checking': 'Checking speech output…',
219
257
  'dsh-live-voice.speak.output.device': 'Output device',
220
258
  'dsh-live-voice.speak.output.fallbackName': 'Audio output',
@@ -226,6 +264,7 @@ const en: LiveVoiceTranslation = {
226
264
  'dsh-live-voice.speak.playback.message': 'Speak message',
227
265
  'dsh-live-voice.speak.playback.next': 'Skip to next speech segment',
228
266
  'dsh-live-voice.speak.playback.pause': 'Pause speech',
267
+ 'dsh-live-voice.speak.playback.previous': 'Previous speech segment',
229
268
  'dsh-live-voice.speak.playback.resume': 'Resume speech',
230
269
  'dsh-live-voice.speak.playback.stop': 'Stop speaking',
231
270
  'dsh-live-voice.speak.playback.stopAll': 'Stop all speech',
@@ -258,6 +297,7 @@ const en: LiveVoiceTranslation = {
258
297
  'dsh-live-voice.speak.responseDelay.help':
259
298
  'After you stop speaking, automatic assistant playback waits for this much continuous silence. Speaking again restarts the wait.',
260
299
  'dsh-live-voice.speak.responseDelay.label': 'Assistant response delay',
300
+ 'dsh-live-voice.speak.responseDelay.none': 'No delay',
261
301
  'dsh-live-voice.speak.segmentGap.help':
262
302
  'Wait this many milliseconds between consecutive spoken segments. 400 ms is the default.',
263
303
  'dsh-live-voice.speak.segmentGap.label': 'Pause between speech segments',
@@ -23,12 +23,14 @@ const es: LiveVoiceTranslation = {
23
23
  'dsh-live-voice.commons.input.listening': 'escuchando',
24
24
  'dsh-live-voice.commons.input.listeningBadge': 'ESCUCHANDO',
25
25
  'dsh-live-voice.commons.manual': 'manual',
26
+ 'dsh-live-voice.commons.milliseconds': '{milliseconds} ms',
26
27
  'dsh-live-voice.commons.off': 'desactivado',
27
28
  'dsh-live-voice.commons.on': 'activado',
28
- 'dsh-live-voice.commons.pluginName': 'Live Voice',
29
+ 'dsh-live-voice.commons.pluginName': 'Voz en vivo',
29
30
  'dsh-live-voice.commons.queue': 'cola',
30
31
  'dsh-live-voice.commons.repository.starLabel': 'Danos una estrella en GitHub',
31
- 'dsh-live-voice.commons.repository.starLink': 'Dar una estrella a DSH Live Voice en GitHub',
32
+ 'dsh-live-voice.commons.repository.starLink': 'Dar una estrella a DSH Voz en vivo en GitHub',
33
+ 'dsh-live-voice.commons.second': '1 segundo',
32
34
  'dsh-live-voice.commons.seconds': '{seconds} segundos',
33
35
  'dsh-live-voice.commons.send': 'ENVIAR',
34
36
  'dsh-live-voice.commons.status.ready': 'Funciones de voz listas',
@@ -42,9 +44,28 @@ const es: LiveVoiceTranslation = {
42
44
  'dsh-live-voice.commons.version.compatibility': 'Compatible con DSH v{version}',
43
45
  'dsh-live-voice.commons.version.compatibilityLink':
44
46
  'Compatible con DSH v{version}. Abrir la página de esta versión',
45
- 'dsh-live-voice.commons.version.label': 'DSH Live Voice v{version}',
46
- 'dsh-live-voice.commons.version.link': 'DSH Live Voice v{version}. Abrir la lista de versiones',
47
+ 'dsh-live-voice.commons.version.label': 'DSH Voz en vivo v{version}',
48
+ 'dsh-live-voice.commons.version.link': 'DSH Voz en vivo v{version}. Abrir la lista de versiones',
47
49
  'dsh-live-voice.commons.version.title': 'Información de versión',
50
+ 'dsh-live-voice.meeting.failed': 'Error de reconocimiento.',
51
+ 'dsh-live-voice.meeting.help':
52
+ 'Fuentes independientes; envía las transcripciones manualmente desde el editor.',
53
+ 'dsh-live-voice.meeting.httpRequired': 'Usa Qwen o Whisper HTTP para el modo reunión.',
54
+ 'dsh-live-voice.meeting.inactive': 'Inactivo',
55
+ 'dsh-live-voice.meeting.listening': 'Escuchando',
56
+ 'dsh-live-voice.meeting.microphone': 'Micrófono',
57
+ 'dsh-live-voice.meeting.noAudio': 'No se compartió audio. Activa el audio en el diálogo.',
58
+ 'dsh-live-voice.meeting.processing': 'Transcribiendo…',
59
+ 'dsh-live-voice.meeting.shared': 'Audio compartido',
60
+ 'dsh-live-voice.meeting.start': 'Iniciar fuente',
61
+ 'dsh-live-voice.meeting.startShared': 'Iniciar audio compartido',
62
+ 'dsh-live-voice.meeting.starting': 'Iniciando…',
63
+ 'dsh-live-voice.meeting.stop': 'Detener fuente',
64
+ 'dsh-live-voice.meeting.stopShared': 'Detener audio compartido',
65
+ 'dsh-live-voice.meeting.timestamps': 'Marcas de tiempo de transcripción',
66
+ 'dsh-live-voice.meeting.timestampsBadge': 'Tiempo',
67
+ 'dsh-live-voice.meeting.title': 'Modo reunión',
68
+ 'dsh-live-voice.meeting.unavailable': 'Reconocimiento no disponible.',
48
69
  'dsh-live-voice.recognition.autoSend.countdownHelp':
49
70
  'La cuenta atrás comienza tras el reconocimiento definitivo de una frase. Hablar de nuevo o editar el texto la cancela.',
50
71
  'dsh-live-voice.recognition.browser.autoInstallPack':
@@ -71,7 +92,7 @@ const es: LiveVoiceTranslation = {
71
92
  'dsh-live-voice.recognition.dictation.cancel': 'Cancelar dictado',
72
93
  'dsh-live-voice.recognition.engine.label': 'Motor de reconocimiento',
73
94
  'dsh-live-voice.recognition.headphoneMode.help':
74
- 'El micrófono abierto sigue escuchando mientras se reproducen las respuestas. Al detectar tu voz, la reproducción se pausa y solo se reanuda cuando tú lo decides.',
95
+ 'El micrófono sigue abierto durante la reproducción. El habla reconocida sin eco puede pausarla tras el retraso configurado; se reanuda cuando el micrófono y el audio compartido guardan silencio durante ese intervalo. La pausa manual requiere Reanudar.',
75
96
  'dsh-live-voice.recognition.headphoneMode.label': 'Auriculares — micrófono abierto',
76
97
  'dsh-live-voice.recognition.holdToTalk.enabled': 'Mantener Control pulsado para hablar',
77
98
  'dsh-live-voice.recognition.holdToTalk.help':
@@ -88,7 +109,8 @@ const es: LiveVoiceTranslation = {
88
109
  'dsh-live-voice.recognition.microphone.failure': 'Micrófono: {reason}',
89
110
  'dsh-live-voice.recognition.microphone.ignore':
90
111
  'Ignorar la entrada de voz en el cuadro de mensaje',
91
- 'dsh-live-voice.recognition.microphone.inputStatus': 'Entrada del micrófono: {state}',
112
+ 'dsh-live-voice.recognition.microphone.inputStatus':
113
+ 'Entrada de audio: {state}. Ignorar descarta el dictado, no la captura: la interrupción por voz sigue activa; los comandos configurados funcionan si están habilitados. Detén la captura con el botón de la fuente en el campo de mensaje.',
92
114
  'dsh-live-voice.recognition.microphone.label': 'Micrófono',
93
115
  'dsh-live-voice.recognition.microphone.permissionHelp':
94
116
  'Solo se solicitará permiso para usar el micrófono cuando inicies un dictado o una conversación por voz.',
@@ -139,7 +161,7 @@ const es: LiveVoiceTranslation = {
139
161
  'dsh-live-voice.recognition.silenceDetection.pauseLabel': 'Pausa antes de enviar',
140
162
  'dsh-live-voice.recognition.silenceDetection.title': 'Configuración de detección de silencio',
141
163
  'dsh-live-voice.recognition.speakerMode.help':
142
- 'La escucha controlada libera el micrófono mientras se reproducen las respuestas para evitar que se reconozca el audio de los altavoces. Usa «Tomar el control del micrófono» para interrumpir la reproducción.',
164
+ 'La reproducción libera el micrófono para reducir el retorno de los altavoces. Tomar micrófono interrumpe manualmente; participantes reconocidos sin eco en el audio compartido pueden pausar la reproducción, que se reanuda tras silencio estable.',
143
165
  'dsh-live-voice.recognition.speakerMode.label': 'Altavoces — escucha controlada',
144
166
  'dsh-live-voice.recognition.status.answer': 'Reconociendo respuesta…',
145
167
  'dsh-live-voice.recognition.status.awaitingAnswer': 'Escuchando tu respuesta…',
@@ -170,6 +192,12 @@ const es: LiveVoiceTranslation = {
170
192
  'dsh-live-voice.settings.autoSend.countdown': 'Enviando en {remaining}…',
171
193
  'dsh-live-voice.settings.autoSend.delay': 'Enviar después del silencio',
172
194
  'dsh-live-voice.settings.close': 'Cerrar configuración de voz',
195
+ 'dsh-live-voice.settings.delivery.dshEnterHelp':
196
+ 'Voz en vivo usa tu respuesta para elegir la acción correcta para Redirigir y Cola. Esto no cambia la configuración de DSH; indica el comportamiento que ya usas y actualiza esta respuesta si cambia.',
197
+ 'dsh-live-voice.settings.delivery.dshEnterQuestion':
198
+ 'En tu DSH, ¿qué hace Enter cuando el agente está ocupado?',
199
+ 'dsh-live-voice.settings.delivery.dshEnterQueue': 'Añade el mensaje a la cola',
200
+ 'dsh-live-voice.settings.delivery.dshEnterSteer': 'Envía el mensaje para redirigir al agente',
173
201
  'dsh-live-voice.settings.delivery.label': 'Modo de envío',
174
202
  'dsh-live-voice.settings.delivery.manualLabel': 'Desactivado — revisar y enviar manualmente',
175
203
  'dsh-live-voice.settings.delivery.queueLabel': 'Cola — añadir automáticamente tras un silencio',
@@ -182,13 +210,13 @@ const es: LiveVoiceTranslation = {
182
210
  'dsh-live-voice.settings.filters.title': 'Filtrado',
183
211
  'dsh-live-voice.settings.general.title': 'General',
184
212
  'dsh-live-voice.settings.persistence.loadError':
185
- 'No se pudieron cargar los ajustes de Live Voice del servidor. Recarga para volver a intentarlo.',
213
+ 'No se pudieron cargar los ajustes de Voz en vivo del servidor. Recarga para volver a intentarlo.',
186
214
  'dsh-live-voice.settings.persistence.saveError':
187
- 'No se pudieron guardar los ajustes de Live Voice en el servidor. Inténtalo de nuevo.',
215
+ 'No se pudieron guardar los ajustes de Voz en vivo en el servidor. Inténtalo de nuevo.',
188
216
  'dsh-live-voice.settings.tabs.conversation': 'Conversación',
189
217
  'dsh-live-voice.settings.tabs.recognition': 'Reconocimiento de voz',
190
218
  'dsh-live-voice.settings.tabs.speak': 'Síntesis de voz',
191
- 'dsh-live-voice.settings.title': 'Configuración de Live Voice',
219
+ 'dsh-live-voice.settings.title': 'Configuración de Voz en vivo',
192
220
  'dsh-live-voice.settings.whisper.hostHelp':
193
221
  'Configuración para todo el host. Solo se permiten URL HTTP sin autenticación en la interfaz de bucle local (localhost, 127.0.0.1, [::1]). El bucle local se refiere al host de DSH, no a este navegador. Todas las comprobaciones de estado y solicitudes de audio pasan por el servidor con autenticación.',
194
222
  'dsh-live-voice.speak.agentContext.enabled': 'Activar el contexto de voz del agente',
@@ -198,6 +226,8 @@ const es: LiveVoiceTranslation = {
198
226
  'Esta instrucción en inglés se envía al agente solo durante una conversación de voz activa con habla automática del asistente activada.',
199
227
  'dsh-live-voice.speak.agentContext.label': 'Contexto de voz del agente',
200
228
  'dsh-live-voice.speak.agentContext.restore': 'Restaurar predeterminado',
229
+ 'dsh-live-voice.speak.autoPlayback.disable': 'Desactivar voz automática del asistente',
230
+ 'dsh-live-voice.speak.autoPlayback.enable': 'Activar voz automática del asistente',
201
231
  'dsh-live-voice.speak.autoPlayback.enabled':
202
232
  'Leer automáticamente los nuevos mensajes del asistente en voz alta',
203
233
  'dsh-live-voice.speak.autoPlayback.help':
@@ -214,6 +244,11 @@ const es: LiveVoiceTranslation = {
214
244
  'dsh-live-voice.speak.browser.outputHelp':
215
245
  'La síntesis de voz del navegador puede ignorar el dispositivo de salida seleccionado; esta API del navegador normalmente utiliza el predeterminado del sistema.',
216
246
  'dsh-live-voice.speak.browser.voice': 'Voz local del navegador',
247
+ 'dsh-live-voice.speak.captions.approximate': 'Subtítulos de voz aproximados',
248
+ 'dsh-live-voice.speak.captions.loading': 'Preparando audio de voz',
249
+ 'dsh-live-voice.speak.captions.position': 'Fragmento de voz {index} de {total}',
250
+ 'dsh-live-voice.speak.captions.progress': 'Progreso aproximado de voz',
251
+ 'dsh-live-voice.speak.captions.title': 'Voz y subtítulos en vivo',
217
252
  'dsh-live-voice.speak.engine.label': 'Motor de síntesis de voz',
218
253
  'dsh-live-voice.speak.engine.playbackHelp':
219
254
  'Qwen y macOS say sintetizan en el host de DSH; el audio AAC/M4A compacto se reproduce en este navegador. La voz del navegador se sintetiza y reproduce en este dispositivo.',
@@ -234,17 +269,21 @@ const es: LiveVoiceTranslation = {
234
269
  'dsh-live-voice.speak.macos.name': 'macOS say',
235
270
  'dsh-live-voice.speak.macos.outputHelp':
236
271
  'macOS say utiliza la salida seleccionada en el host de DSH.',
272
+ 'dsh-live-voice.speak.macos.voice': 'Voz de macOS say',
273
+ 'dsh-live-voice.speak.macos.voiceHelp':
274
+ 'Introduce el nombre exacto de la voz usado por say -v. Déjalo vacío para usar la voz predeterminada de macOS. Lista las voces del host en Terminal con say -v "?".',
237
275
  'dsh-live-voice.speak.output.checking': 'Comprobando la salida de voz…',
238
276
  'dsh-live-voice.speak.output.device': 'Dispositivo de salida',
239
277
  'dsh-live-voice.speak.output.fallbackName': 'Salida de audio',
240
278
  'dsh-live-voice.speak.output.stopTest': 'Detener la prueba de voz',
241
279
  'dsh-live-voice.speak.output.test': 'Probar la salida de voz seleccionada',
242
280
  'dsh-live-voice.speak.output.testPhrase':
243
- 'DSH Live Voice. La salida de voz seleccionada funciona.',
281
+ 'DSH Voz en vivo. La salida de voz seleccionada funciona.',
244
282
  'dsh-live-voice.speak.output.testing': 'Probando la voz…',
245
283
  'dsh-live-voice.speak.playback.message': 'Leer el mensaje en voz alta',
246
284
  'dsh-live-voice.speak.playback.next': 'Saltar al siguiente segmento de voz',
247
285
  'dsh-live-voice.speak.playback.pause': 'Pausar la lectura en voz alta',
286
+ 'dsh-live-voice.speak.playback.previous': 'Fragmento de voz anterior',
248
287
  'dsh-live-voice.speak.playback.resume': 'Reanudar la lectura en voz alta',
249
288
  'dsh-live-voice.speak.playback.stop': 'Detener la lectura en voz alta',
250
289
  'dsh-live-voice.speak.playback.stopAll': 'Detener toda la lectura en voz alta',
@@ -279,6 +318,7 @@ const es: LiveVoiceTranslation = {
279
318
  'dsh-live-voice.speak.responseDelay.help':
280
319
  'Cuando dejas de hablar, la reproducción automática del asistente espera este intervalo de silencio continuo. Si vuelves a hablar, la espera se reinicia.',
281
320
  'dsh-live-voice.speak.responseDelay.label': 'Demora de respuesta del asistente',
321
+ 'dsh-live-voice.speak.responseDelay.none': 'Sin retraso',
282
322
  'dsh-live-voice.speak.segmentGap.help':
283
323
  'Espera esta cantidad de milisegundos entre segmentos hablados consecutivos. El valor predeterminado es 400 ms.',
284
324
  'dsh-live-voice.speak.segmentGap.label': 'Pausa entre segmentos de voz',