dsh-live-voice 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Local-first speech-to-text (STT), text-to-speech (TTS), and meeting audio transc
4
4
 
5
5
  ## Installation
6
6
 
7
- Tested with **DeepSeek Harness v0.2.2-rc.1**. The package manifest's `dshTestedVersion` field records the tested version.
7
+ Tested with **DeepSeek Harness v0.2.2-rc.3**. The package manifest's `dshTestedVersion` field records the tested version.
8
8
 
9
9
  Install the prebuilt package from npm; no source build is required. The Git repository contains source only.
10
10
 
@@ -36682,7 +36682,7 @@ async function createQwenRuntime(options) {
36682
36682
  }
36683
36683
 
36684
36684
  // src/modules/recognition/local/assets.ts
36685
- var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.8.0" : "test") + "/";
36685
+ var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.8.1" : "test") + "/";
36686
36686
 
36687
36687
  // src/modules/recognition/local/modelCatalog.ts
36688
36688
  var browserAsrModels = [
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.8.0",
2
+ "version": "0.8.1",
3
3
  "assets": [
4
4
  "asr.worker.js",
5
5
  "manifest.json",
package/lib/client.js CHANGED
@@ -36,89 +36,6 @@ __export(apply_exports, {
36
36
  module.exports = __toCommonJS(apply_exports);
37
37
  var import_react80 = __toESM(require("react"), 1);
38
38
 
39
- // src/modules/conversation/models/inactivity.ts
40
- var VoiceInactivity = class {
41
- constructor(options) {
42
- this.options = options;
43
- }
44
- options;
45
- snapshot = { phase: "idle", remaining: 0 };
46
- listeners = /* @__PURE__ */ new Set();
47
- active = false;
48
- lastText = 0;
49
- lastTick = 0;
50
- deadline = 0;
51
- busySince = 0;
52
- warningText = "";
53
- echoUntil = 0;
54
- now = () => this.options.now?.() ?? Date.now();
55
- getSnapshot = () => this.snapshot;
56
- subscribe = (listener) => {
57
- this.listeners.add(listener);
58
- return () => {
59
- this.listeners.delete(listener);
60
- };
61
- };
62
- patch(phase, remaining = 0) {
63
- if (this.snapshot.phase === phase && this.snapshot.remaining === remaining) return;
64
- this.snapshot = { phase, remaining };
65
- for (const listener of this.listeners) listener();
66
- }
67
- keepAlive = () => {
68
- this.lastText = this.now();
69
- this.deadline = 0;
70
- this.busySince = 0;
71
- this.patch("idle");
72
- };
73
- /** Filter only matching warning echo; unrelated replies remain eligible. */
74
- recognized(text) {
75
- const normalize = (value) => value.toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
76
- if (!text.trim()) return true;
77
- const heard = normalize(text);
78
- const own = normalize(this.warningText);
79
- if (this.now() < this.echoUntil && heard.length >= 24 && heard.length >= own.length / 2 && own.includes(heard))
80
- return false;
81
- this.keepAlive();
82
- return true;
83
- }
84
- tick(active, busy, settings) {
85
- const now2 = this.now();
86
- const gap = this.lastTick && now2 - this.lastTick > 1e4;
87
- this.lastTick = now2;
88
- if (!settings.autoEndEnabled || !active) {
89
- this.active = false;
90
- if (this.snapshot.phase !== "ended" || active) this.patch("idle");
91
- return;
92
- }
93
- if (!this.active) {
94
- this.active = true;
95
- this.keepAlive();
96
- }
97
- const total = settings.autoEndMinutes * 6e4;
98
- const warning = settings.autoEndWarningMinutes * 6e4;
99
- if (gap && now2 - this.lastText >= total - warning) this.lastText = now2 - (total - warning);
100
- if (now2 - this.lastText < total - warning) return;
101
- if (!this.deadline || gap) {
102
- this.deadline = Math.max(this.lastText + total, now2 + warning);
103
- this.patch("warning", Math.ceil((this.deadline - now2) / 1e3));
104
- this.echoUntil = now2 + 3e4;
105
- this.warningText = this.options.warn(this.snapshot.remaining);
106
- }
107
- this.patch("warning", Math.max(0, Math.ceil((this.deadline - now2) / 1e3)));
108
- if (now2 < this.deadline) return;
109
- if (busy) {
110
- if (!this.busySince) this.busySince = now2;
111
- if (now2 - this.busySince < 3e4) return;
112
- }
113
- this.active = false;
114
- this.patch("ended");
115
- this.options.end();
116
- }
117
- };
118
-
119
- // src/modules/conversation/components/InactivityNotice.tsx
120
- var import_react3 = __toESM(require("react"), 1);
121
-
122
39
  // src/app/client/i18n/catalogs/en.ts
123
40
  var en = {
124
41
  "dsh-live-voice.commons.connection.contactingHost": "Contacting DSH host\u2026",
@@ -3197,6 +3114,143 @@ var appDictionaries = Object.freeze({
3197
3114
  zh: zh_default
3198
3115
  });
3199
3116
 
3117
+ // src/app/client/i18n/registerDshLocales.ts
3118
+ var LIVE_VOICE_LOCALE_NAMESPACE = "dsh-live-voice";
3119
+ var LIVE_VOICE_LANGUAGES = liveVoiceLanguageDefinitions;
3120
+ var liveVoiceDictionaries = appDictionaries;
3121
+ function createFallbackTranslator(locale = "en") {
3122
+ return (key, params = {}) => {
3123
+ const template = liveVoiceDictionaries[locale]?.[key] ?? liveVoiceDictionaries.en[key] ?? key;
3124
+ return String(template).replace(
3125
+ /\{([A-Za-z0-9_]+)\}/g,
3126
+ (_2, name) => String(params[name] ?? "{" + name + "}")
3127
+ );
3128
+ };
3129
+ }
3130
+ function registerDshLocales(ctx) {
3131
+ if (!ctx.locale) return createFallbackTranslator();
3132
+ const registeredLanguages = new Set(
3133
+ (ctx.locale.getSnapshot?.().locales || []).map(
3134
+ (language) => language.id.toLowerCase()
3135
+ )
3136
+ );
3137
+ for (const language of LIVE_VOICE_LANGUAGES) {
3138
+ if (registeredLanguages.has(language.id.toLowerCase())) continue;
3139
+ ctx.effect(
3140
+ () => ctx.locale.addLanguage(language),
3141
+ "dsh-live-voice: " + language.id + " language"
3142
+ );
3143
+ registeredLanguages.add(language.id.toLowerCase());
3144
+ }
3145
+ for (const [locale, dictionary] of Object.entries(liveVoiceDictionaries))
3146
+ ctx.effect(
3147
+ () => ctx.locale.register(LIVE_VOICE_LOCALE_NAMESPACE, locale, dictionary),
3148
+ "dsh-live-voice: " + locale + " dictionary"
3149
+ );
3150
+ return ctx.locale.bind(LIVE_VOICE_LOCALE_NAMESPACE);
3151
+ }
3152
+ var registerLiveVoiceLocales = registerDshLocales;
3153
+
3154
+ // src/app/client/i18n/spokenWarning.ts
3155
+ function warningLanguage(language) {
3156
+ const value = language?.trim().replaceAll("_", "-").toLowerCase();
3157
+ if (!value) return void 0;
3158
+ if (value === "pt" || value.startsWith("pt-")) return "pt-BR";
3159
+ const base = value.split("-")[0];
3160
+ return base && base in appDictionaries ? base : void 0;
3161
+ }
3162
+ function spokenInactivityWarning(ttsLanguage, uiLanguage, seconds) {
3163
+ const spoken = warningLanguage(ttsLanguage);
3164
+ const locale = spoken ?? warningLanguage(uiLanguage) ?? "en";
3165
+ return {
3166
+ text: createFallbackTranslator(locale)("dsh-live-voice.settings.autoEnd.warning", { seconds }),
3167
+ lang: spoken ? ttsLanguage.replaceAll("_", "-") : locale
3168
+ };
3169
+ }
3170
+
3171
+ // src/modules/conversation/models/inactivity.ts
3172
+ var VoiceInactivity = class {
3173
+ constructor(options) {
3174
+ this.options = options;
3175
+ }
3176
+ options;
3177
+ snapshot = { phase: "idle", remaining: 0 };
3178
+ listeners = /* @__PURE__ */ new Set();
3179
+ active = false;
3180
+ lastText = 0;
3181
+ lastTick = 0;
3182
+ deadline = 0;
3183
+ busySince = 0;
3184
+ warningText = "";
3185
+ echoUntil = 0;
3186
+ now = () => this.options.now?.() ?? Date.now();
3187
+ getSnapshot = () => this.snapshot;
3188
+ subscribe = (listener) => {
3189
+ this.listeners.add(listener);
3190
+ return () => {
3191
+ this.listeners.delete(listener);
3192
+ };
3193
+ };
3194
+ patch(phase, remaining = 0) {
3195
+ if (this.snapshot.phase === phase && this.snapshot.remaining === remaining) return;
3196
+ this.snapshot = { phase, remaining };
3197
+ for (const listener of this.listeners) listener();
3198
+ }
3199
+ keepAlive = () => {
3200
+ this.lastText = this.now();
3201
+ this.deadline = 0;
3202
+ this.busySince = 0;
3203
+ this.patch("idle");
3204
+ };
3205
+ /** Filter only matching warning echo; unrelated replies remain eligible. */
3206
+ recognized(text) {
3207
+ const normalize = (value) => value.toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim();
3208
+ if (!text.trim()) return true;
3209
+ const heard = normalize(text);
3210
+ const own = normalize(this.warningText);
3211
+ if (this.now() < this.echoUntil && heard.length >= 24 && heard.length >= own.length / 2 && own.includes(heard))
3212
+ return false;
3213
+ this.keepAlive();
3214
+ return true;
3215
+ }
3216
+ tick(active, busy, settings) {
3217
+ const now2 = this.now();
3218
+ const gap = this.lastTick && now2 - this.lastTick > 1e4;
3219
+ this.lastTick = now2;
3220
+ if (!settings.autoEndEnabled || !active) {
3221
+ this.active = false;
3222
+ if (this.snapshot.phase !== "ended" || active) this.patch("idle");
3223
+ return;
3224
+ }
3225
+ if (!this.active) {
3226
+ this.active = true;
3227
+ this.keepAlive();
3228
+ }
3229
+ const total = settings.autoEndMinutes * 6e4;
3230
+ const warning = settings.autoEndWarningMinutes * 6e4;
3231
+ if (gap && now2 - this.lastText >= total - warning) this.lastText = now2 - (total - warning);
3232
+ if (now2 - this.lastText < total - warning) return;
3233
+ if (!this.deadline || gap) {
3234
+ this.deadline = Math.max(this.lastText + total, now2 + warning);
3235
+ this.patch("warning", Math.ceil((this.deadline - now2) / 1e3));
3236
+ this.echoUntil = now2 + 3e4;
3237
+ this.warningText = this.options.warn(this.snapshot.remaining);
3238
+ }
3239
+ this.patch("warning", Math.max(0, Math.ceil((this.deadline - now2) / 1e3)));
3240
+ if (now2 < this.deadline) return;
3241
+ if (busy) {
3242
+ if (!this.busySince) this.busySince = now2;
3243
+ if (now2 - this.busySince < 3e4) return;
3244
+ }
3245
+ this.active = false;
3246
+ this.patch("ended");
3247
+ this.options.end();
3248
+ }
3249
+ };
3250
+
3251
+ // src/modules/conversation/components/InactivityNotice.tsx
3252
+ var import_react3 = __toESM(require("react"), 1);
3253
+
3200
3254
  // src/app/client/i18n/DshLanguageBoundary.tsx
3201
3255
  var import_react2 = __toESM(require("react"), 1);
3202
3256
 
@@ -3261,43 +3315,6 @@ function createTranslationRuntime({ availableLangs: availableLangs2, defaultLang
3261
3315
  return { TranslationProvider, useTranslation };
3262
3316
  }
3263
3317
 
3264
- // src/app/client/i18n/registerDshLocales.ts
3265
- var LIVE_VOICE_LOCALE_NAMESPACE = "dsh-live-voice";
3266
- var LIVE_VOICE_LANGUAGES = liveVoiceLanguageDefinitions;
3267
- var liveVoiceDictionaries = appDictionaries;
3268
- function createFallbackTranslator(locale = "en") {
3269
- return (key, params = {}) => {
3270
- const template = liveVoiceDictionaries[locale]?.[key] ?? liveVoiceDictionaries.en[key] ?? key;
3271
- return String(template).replace(
3272
- /\{([A-Za-z0-9_]+)\}/g,
3273
- (_2, name) => String(params[name] ?? "{" + name + "}")
3274
- );
3275
- };
3276
- }
3277
- function registerDshLocales(ctx) {
3278
- if (!ctx.locale) return createFallbackTranslator();
3279
- const registeredLanguages = new Set(
3280
- (ctx.locale.getSnapshot?.().locales || []).map(
3281
- (language) => language.id.toLowerCase()
3282
- )
3283
- );
3284
- for (const language of LIVE_VOICE_LANGUAGES) {
3285
- if (registeredLanguages.has(language.id.toLowerCase())) continue;
3286
- ctx.effect(
3287
- () => ctx.locale.addLanguage(language),
3288
- "dsh-live-voice: " + language.id + " language"
3289
- );
3290
- registeredLanguages.add(language.id.toLowerCase());
3291
- }
3292
- for (const [locale, dictionary] of Object.entries(liveVoiceDictionaries))
3293
- ctx.effect(
3294
- () => ctx.locale.register(LIVE_VOICE_LOCALE_NAMESPACE, locale, dictionary),
3295
- "dsh-live-voice: " + locale + " dictionary"
3296
- );
3297
- return ctx.locale.bind(LIVE_VOICE_LOCALE_NAMESPACE);
3298
- }
3299
- var registerLiveVoiceLocales = registerDshLocales;
3300
-
3301
3318
  // src/app/client/i18n/runtime.tsx
3302
3319
  function interpolate(template, params = {}) {
3303
3320
  return template.replace(
@@ -3497,7 +3514,7 @@ var SessionSpeechMemory = class {
3497
3514
  };
3498
3515
 
3499
3516
  // src/modules/recognition/vad/assets.ts
3500
- var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.8.0" : "test") + "/";
3517
+ var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.8.1" : "test") + "/";
3501
3518
  async function sileroVadAvailable(fetchImpl = globalThis.fetch) {
3502
3519
  try {
3503
3520
  const response = await fetchImpl(sileroVadAssetBase() + "availability", {
@@ -6907,7 +6924,7 @@ async function removeBrowserModel(key, storage = caches) {
6907
6924
  }
6908
6925
 
6909
6926
  // src/modules/recognition/local/assets.ts
6910
- var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.8.0" : "test") + "/";
6927
+ var browserAsrAssetBase = () => "/api/dsh-live-voice/asr/" + encodeURIComponent(true ? "0.8.1" : "test") + "/";
6911
6928
 
6912
6929
  // src/modules/recognition/local/runtimeIdentity.ts
6913
6930
  var workerProtocol = "asr-worker-v3-ort-1.31.0-dev.20260914-8d85527a0";
@@ -11551,9 +11568,9 @@ var VoiceCoordinator = class _VoiceCoordinator {
11551
11568
  }
11552
11569
  this.prefetchItems.clear();
11553
11570
  }
11554
- _speechPreparationKey() {
11571
+ _speechPreparationKey(item = {}) {
11555
11572
  const { engine, voice, lang, rate, outputDeviceId } = this.snapshot.settings;
11556
- return JSON.stringify([engine, voice, lang, rate, outputDeviceId]);
11573
+ return JSON.stringify([engine, voice, item.lang ?? lang, rate, outputDeviceId]);
11557
11574
  }
11558
11575
  _resetStaleSpeechItem(item, key) {
11559
11576
  if (item.preparationKey === key) return;
@@ -11574,13 +11591,13 @@ var VoiceCoordinator = class _VoiceCoordinator {
11574
11591
  lang: this.snapshot.settings.lang,
11575
11592
  outputDeviceId: this.snapshot.settings.outputDeviceId
11576
11593
  };
11577
- const key = this._speechPreparationKey();
11578
11594
  for (const item of this.queue.slice(0, SPEECH_QUEUE_PREFETCH_LIMIT)) {
11595
+ const key = this._speechPreparationKey(item);
11579
11596
  this._resetStaleSpeechItem(item, key);
11580
11597
  if (item.prepared || item.preparing) continue;
11581
11598
  this.prefetchItems.add(item);
11582
11599
  const signal = this.prefetchController.signal;
11583
- item.preparing = engine.prepare(item.text, { ...options, signal }).then((prepared) => {
11600
+ item.preparing = engine.prepare(item.text, { ...options, lang: item.lang ?? options.lang, signal }).then((prepared) => {
11584
11601
  if (item.preparationKey === key) item.preparing = null;
11585
11602
  if (signal.aborted || item.preparationKey !== key || !this.queue.includes(item) && !this.speechHistory.includes(item)) {
11586
11603
  prepared.dispose?.();
@@ -11957,15 +11974,15 @@ var VoiceCoordinator = class _VoiceCoordinator {
11957
11974
  this._syncSpeechSegments();
11958
11975
  return this.play(first, messageId, true);
11959
11976
  }
11960
- async play(text, messageId, manual = false) {
11977
+ async play(text, messageId, manual = false, speechOptions = {}) {
11961
11978
  if (this.disposed || !text.trim()) return;
11962
11979
  if (this.snapshot.speaking) {
11963
- this.queue.push({ text, id: messageId, manual });
11980
+ this.queue.push({ text, id: messageId, manual, lang: speechOptions.lang });
11964
11981
  this._prefetchSpeech();
11965
11982
  this._syncSpeechSegments();
11966
11983
  return;
11967
11984
  }
11968
- const item = this.activeSpeechItem?.text === text ? this.activeSpeechItem : { text, id: messageId, manual };
11985
+ const item = this.activeSpeechItem?.text === text ? this.activeSpeechItem : { text, id: messageId, manual, lang: speechOptions.lang };
11969
11986
  if (!this.speechHistory.includes(item)) this.speechHistory.push(item);
11970
11987
  this.speechCursor = this.speechHistory.indexOf(item);
11971
11988
  const epoch = ++this.speechEpoch;
@@ -11984,7 +12001,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
11984
12001
  let playbackStarted = false;
11985
12002
  let playbackFinished = false;
11986
12003
  const engine = this.engines[this.snapshot.settings.engine];
11987
- this._resetStaleSpeechItem(item, this._speechPreparationKey());
12004
+ this._resetStaleSpeechItem(item, this._speechPreparationKey(item));
11988
12005
  item.engine = engine;
11989
12006
  if (!engine) {
11990
12007
  this.queue.unshift(item);
@@ -12040,6 +12057,7 @@ var VoiceCoordinator = class _VoiceCoordinator {
12040
12057
  this.patch({ speechLoading: false });
12041
12058
  }
12042
12059
  },
12060
+ lang: item.lang ?? this.snapshot.settings.lang,
12043
12061
  voice: this.snapshot.settings.voice || void 0,
12044
12062
  rate: this.snapshot.settings.rate,
12045
12063
  volume: this.snapshot.settings.volume,
@@ -15246,8 +15264,8 @@ function createDeviceOptions(devices, kind, systemDefault, fallback) {
15246
15264
  var import_react42 = __toESM(require("react"), 1);
15247
15265
 
15248
15266
  // src/modules/settings/services/releases.ts
15249
- var CURRENT_VERSION = "0.8.0";
15250
- var TESTED_DSH_VERSION = "0.2.2-rc.1";
15267
+ var CURRENT_VERSION = "0.8.1";
15268
+ var TESTED_DSH_VERSION = "0.2.2-rc.3";
15251
15269
  var REPOSITORY_URL = "https://github.com/victorwads/dsh-live-voice";
15252
15270
  var RELEASES_URL = `${REPOSITORY_URL}/releases`;
15253
15271
  var LATEST_RELEASE_API_URL = "https://api.github.com/repos/victorwads/dsh-live-voice/releases/latest";
@@ -18156,11 +18174,15 @@ function apply(ctx) {
18156
18174
  let inactivityEnding = false;
18157
18175
  const inactivity = new VoiceInactivity({
18158
18176
  warn: (seconds) => {
18159
- const text = t("dsh-live-voice.settings.autoEnd.warning", { seconds });
18160
18177
  const c = meetingEntry?.controller;
18178
+ const { text, lang } = spokenInactivityWarning(
18179
+ c?.getSnapshot().settings.lang ?? preferences.getSnapshot().lang,
18180
+ ctx.locale?.getSnapshot?.().active,
18181
+ seconds
18182
+ );
18161
18183
  const s = c?.getSnapshot();
18162
18184
  if (c && !s.speaking && !s.paused && !s.speechRunActive && !c.queue.length)
18163
- run(c, c.play(text, null, true));
18185
+ run(c, c.play(text, null, true, { lang }));
18164
18186
  return text;
18165
18187
  },
18166
18188
  end: () => {
package/lib/server.js CHANGED
@@ -2066,7 +2066,7 @@ var VAD_ASSET_TYPES = {
2066
2066
  };
2067
2067
  var MAX_VAD_ASSET_BYTES = 32 * 1024 * 1024;
2068
2068
  function registerVadAssetRoutes(ctx, options = {}) {
2069
- const version = options.version ?? (true ? "0.8.0" : "test");
2069
+ const version = options.version ?? (true ? "0.8.1" : "test");
2070
2070
  if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]{0,127}$/.test(version))
2071
2071
  throw new Error("Invalid VAD asset version");
2072
2072
  const directory = options.directory ?? fileURLToPath(new URL("./vad/", import.meta.url));
@@ -2160,7 +2160,7 @@ import { lstat as lstat2, readFile as readFile5 } from "node:fs/promises";
2160
2160
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2161
2161
  import { join as join8 } from "node:path";
2162
2162
  function registerBrowserAsrAssets(ctx, options = {}) {
2163
- const version = options.version ?? (true ? "0.8.0" : "test");
2163
+ const version = options.version ?? (true ? "0.8.1" : "test");
2164
2164
  if (!/^[a-zA-Z0-9][a-zA-Z0-9.+-]{0,127}$/.test(version)) throw new Error("invalid-version");
2165
2165
  const base = "/api/dsh-live-voice/asr/" + encodeURIComponent(version) + "/";
2166
2166
  const directory = options.directory ?? fileURLToPath2(new URL("./asr/", import.meta.url));
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "pluginVersion": "0.8.0",
3
+ "pluginVersion": "0.8.1",
4
4
  "dependencies": {
5
5
  "@ricky0123/vad-web": "0.0.31",
6
6
  "onnxruntime-web": "1.22.0"
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "vad.worker.js": {
43
43
  "bytes": 79337,
44
- "sha256": "543b52073b17a67ecaa1ff72350ecd6f1f4e67399420ef961f1be5a809ab022a"
44
+ "sha256": "7bc4a97041cb5740bb0254387de63f69d5e869c020d9dd82096f5d8b4fbfdf06"
45
45
  }
46
46
  }
47
47
  }
@@ -1632,7 +1632,7 @@ var vo = Ke;
1632
1632
  Object.defineProperty(I.versions, "web", { value: $t, enumerable: true });
1633
1633
 
1634
1634
  // src/modules/recognition/vad/assets.ts
1635
- var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.8.0" : "test") + "/";
1635
+ var sileroVadAssetBase = () => "/api/dsh-live-voice/vad/" + encodeURIComponent(true ? "0.8.1" : "test") + "/";
1636
1636
 
1637
1637
  // src/modules/recognition/vad/StreamingResampler.ts
1638
1638
  var StreamingResampler = class {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-live-voice",
3
- "version": "0.8.0",
4
- "dshTestedVersion": "0.2.2-rc.1",
3
+ "version": "0.8.1",
4
+ "dshTestedVersion": "0.2.2-rc.3",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "node --experimental-strip-types ../../scripts/build.ts --package live-voice",
@@ -1,5 +1,6 @@
1
1
  // @ts-nocheck
2
2
  import React from 'react';
3
+ import { spokenInactivityWarning } from './i18n/spokenWarning.js';
3
4
  import { VoiceInactivity } from '../../modules/conversation/models/inactivity.js';
4
5
  import { InactivityNotice } from '../../modules/conversation/components/InactivityNotice.js';
5
6
  import {
@@ -97,12 +98,16 @@ export function apply(ctx) {
97
98
  let inactivityEnding = false;
98
99
  const inactivity = new VoiceInactivity({
99
100
  warn: (seconds) => {
100
- const text = t('dsh-live-voice.settings.autoEnd.warning', { seconds });
101
101
  const c = meetingEntry?.controller;
102
+ const { text, lang } = spokenInactivityWarning(
103
+ c?.getSnapshot().settings.lang ?? preferences.getSnapshot().lang,
104
+ ctx.locale?.getSnapshot?.().active,
105
+ seconds,
106
+ );
102
107
  // Do not replace assistant playback or a manually paused speech queue.
103
108
  const s = c?.getSnapshot();
104
109
  if (c && !s.speaking && !s.paused && !s.speechRunActive && !c.queue.length)
105
- run(c, c.play(text, null, true));
110
+ run(c, c.play(text, null, true, { lang }));
106
111
  return text;
107
112
  },
108
113
  end: () => {
@@ -0,0 +1,21 @@
1
+ import { appDictionaries, type LiveVoiceLocale } from './catalogs/index.js';
2
+ import { createFallbackTranslator } from './registerDshLocales.js';
3
+ export function warningLanguage(language: string | undefined): LiveVoiceLocale | undefined {
4
+ const value = language?.trim().replaceAll('_', '-').toLowerCase();
5
+ if (!value) return undefined;
6
+ if (value === 'pt' || value.startsWith('pt-')) return 'pt-BR';
7
+ const base = value.split('-')[0];
8
+ return base && base in appDictionaries ? (base as LiveVoiceLocale) : undefined;
9
+ }
10
+ export function spokenInactivityWarning(
11
+ ttsLanguage: string | undefined,
12
+ uiLanguage: string | undefined,
13
+ seconds: number,
14
+ ) {
15
+ const spoken = warningLanguage(ttsLanguage);
16
+ const locale = spoken ?? warningLanguage(uiLanguage) ?? 'en';
17
+ return {
18
+ text: createFallbackTranslator(locale)('dsh-live-voice.settings.autoEnd.warning', { seconds }),
19
+ lang: spoken ? ttsLanguage!.replaceAll('_', '-') : locale,
20
+ };
21
+ }
@@ -1077,9 +1077,9 @@ export class VoiceCoordinator {
1077
1077
  this.prefetchItems.clear();
1078
1078
  }
1079
1079
 
1080
- _speechPreparationKey() {
1080
+ _speechPreparationKey(item = {}) {
1081
1081
  const { engine, voice, lang, rate, outputDeviceId } = this.snapshot.settings;
1082
- return JSON.stringify([engine, voice, lang, rate, outputDeviceId]);
1082
+ return JSON.stringify([engine, voice, item.lang ?? lang, rate, outputDeviceId]);
1083
1083
  }
1084
1084
  _resetStaleSpeechItem(item, key) {
1085
1085
  if (item.preparationKey === key) return;
@@ -1100,14 +1100,14 @@ export class VoiceCoordinator {
1100
1100
  lang: this.snapshot.settings.lang,
1101
1101
  outputDeviceId: this.snapshot.settings.outputDeviceId,
1102
1102
  };
1103
- const key = this._speechPreparationKey();
1104
1103
  for (const item of this.queue.slice(0, SPEECH_QUEUE_PREFETCH_LIMIT)) {
1104
+ const key = this._speechPreparationKey(item);
1105
1105
  this._resetStaleSpeechItem(item, key);
1106
1106
  if (item.prepared || item.preparing) continue;
1107
1107
  this.prefetchItems.add(item);
1108
1108
  const signal = this.prefetchController.signal;
1109
1109
  item.preparing = engine
1110
- .prepare(item.text, { ...options, signal })
1110
+ .prepare(item.text, { ...options, lang: item.lang ?? options.lang, signal })
1111
1111
  .then((prepared) => {
1112
1112
  if (item.preparationKey === key) item.preparing = null;
1113
1113
  if (
@@ -1542,10 +1542,10 @@ export class VoiceCoordinator {
1542
1542
  this._syncSpeechSegments();
1543
1543
  return this.play(first, messageId, true);
1544
1544
  }
1545
- async play(text, messageId, manual = false) {
1545
+ async play(text, messageId, manual = false, speechOptions = {}) {
1546
1546
  if (this.disposed || !text.trim()) return;
1547
1547
  if (this.snapshot.speaking) {
1548
- this.queue.push({ text, id: messageId, manual });
1548
+ this.queue.push({ text, id: messageId, manual, lang: speechOptions.lang });
1549
1549
  this._prefetchSpeech();
1550
1550
  this._syncSpeechSegments();
1551
1551
  return;
@@ -1553,7 +1553,7 @@ export class VoiceCoordinator {
1553
1553
  const item =
1554
1554
  this.activeSpeechItem?.text === text
1555
1555
  ? this.activeSpeechItem
1556
- : { text, id: messageId, manual };
1556
+ : { text, id: messageId, manual, lang: speechOptions.lang };
1557
1557
  if (!this.speechHistory.includes(item)) this.speechHistory.push(item);
1558
1558
  this.speechCursor = this.speechHistory.indexOf(item);
1559
1559
  const epoch = ++this.speechEpoch;
@@ -1572,7 +1572,7 @@ export class VoiceCoordinator {
1572
1572
  let playbackStarted = false;
1573
1573
  let playbackFinished = false;
1574
1574
  const engine = this.engines[this.snapshot.settings.engine];
1575
- this._resetStaleSpeechItem(item, this._speechPreparationKey());
1575
+ this._resetStaleSpeechItem(item, this._speechPreparationKey(item));
1576
1576
  item.engine = engine;
1577
1577
  if (!engine) {
1578
1578
  this.queue.unshift(item);
@@ -1635,6 +1635,7 @@ export class VoiceCoordinator {
1635
1635
  this.patch({ speechLoading: false });
1636
1636
  }
1637
1637
  },
1638
+ lang: item.lang ?? this.snapshot.settings.lang,
1638
1639
  voice: this.snapshot.settings.voice || undefined,
1639
1640
  rate: this.snapshot.settings.rate,
1640
1641
  volume: this.snapshot.settings.volume,