@mieweb/ui 0.10.0-dev.2 → 0.10.0-dev.3

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.
@@ -1,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ var chunkOGNADPM4_cjs = require('./chunk-OGNADPM4.cjs');
4
+ require('./chunk-EUAMEVVI.cjs');
5
+
3
6
  // src/components/AI/HeyOzwell/SpeakerVerify/lib/speaker-verify.js
4
7
  (function() {
5
8
  const SV_DIR = (function() {
@@ -25,7 +28,7 @@
25
28
  return "https://huggingface.co/jlocala/ozwell-voice-assets/resolve/main/sv-runtime";
26
29
  })();
27
30
  const MODEL = "./nemo_en_titanet_small.onnx";
28
- const LS_KEY = "ozwellDoctorVoiceprint";
31
+ const BASE_STORAGE_KEY = "ozwellDoctorVoiceprint";
29
32
  const DEFAULT_THRESHOLD = 0.45;
30
33
  let Module = null, handle = 0, dim = 0;
31
34
  let cohort = [];
@@ -72,74 +75,41 @@
72
75
  for (let i = 0; i < n; i++) d += a[i] * b[i];
73
76
  return d;
74
77
  }
75
- const IDB_DB = "ozwell-voice", IDB_STORE = "voiceprints";
76
- function idbOpen() {
77
- return new Promise((resolve, reject) => {
78
- const req = indexedDB.open(IDB_DB, 1);
79
- req.onupgradeneeded = () => {
80
- if (!req.result.objectStoreNames.contains(IDB_STORE)) req.result.createObjectStore(IDB_STORE);
81
- };
82
- req.onsuccess = () => resolve(req.result);
83
- req.onerror = () => reject(req.error);
84
- });
85
- }
86
- function idbReq(mode, run) {
87
- return idbOpen().then((db) => new Promise((resolve, reject) => {
88
- const tx = db.transaction(IDB_STORE, mode);
89
- const r = run(tx.objectStore(IDB_STORE));
90
- tx.oncomplete = () => {
91
- resolve(r && r.result);
92
- db.close();
93
- };
94
- tx.onerror = () => {
95
- reject(tx.error);
96
- db.close();
97
- };
98
- tx.onabort = () => {
99
- reject(tx.error);
100
- db.close();
101
- };
102
- }));
103
- }
104
- const idbGet = (key) => idbReq("readonly", (s) => s.get(key));
105
- const idbPut = (key, v) => idbReq("readwrite", (s) => s.put(v, key));
106
- const idbDel = (key) => idbReq("readwrite", (s) => s.delete(key));
107
- let _store = null;
78
+ const stores = /* @__PURE__ */ new Map();
79
+ const hydrations = /* @__PURE__ */ new Map();
108
80
  const normalizeWho = (o) => o && Array.isArray(o.centroid) ? {} : o || {};
109
- async function hydrateStore() {
81
+ async function hydrateStore(namespace) {
82
+ const storageKey = chunkOGNADPM4_cjs.voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
83
+ if (stores.has(storageKey)) return;
84
+ const pending = hydrations.get(storageKey);
85
+ if (pending) return pending;
86
+ const hydration = chunkOGNADPM4_cjs.getVoiceprints(
87
+ storageKey,
88
+ namespace === void 0 ? (raw) => normalizeWho(JSON.parse(raw || "{}")) : void 0
89
+ ).then((value) => {
90
+ stores.set(storageKey, value || {});
91
+ });
92
+ hydrations.set(storageKey, hydration);
110
93
  try {
111
- let v = await idbGet(LS_KEY);
112
- if (v === void 0) {
113
- let raw = null;
114
- try {
115
- raw = localStorage.getItem(LS_KEY);
116
- } catch (e) {
117
- }
118
- if (raw != null) {
119
- v = normalizeWho(JSON.parse(raw || "{}"));
120
- await idbPut(LS_KEY, v);
121
- try {
122
- localStorage.removeItem(LS_KEY);
123
- } catch (e) {
124
- }
125
- }
126
- }
127
- _store = v || {};
128
- } catch (e) {
129
- _store = _store || {};
94
+ await hydration;
95
+ } finally {
96
+ hydrations.delete(storageKey);
130
97
  }
131
98
  }
132
- function loadAll() {
133
- if (_store) return _store;
99
+ function loadAll(namespace) {
100
+ const storageKey = chunkOGNADPM4_cjs.voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
101
+ if (stores.has(storageKey)) return stores.get(storageKey);
102
+ if (namespace !== void 0) return {};
134
103
  try {
135
- return normalizeWho(JSON.parse(localStorage.getItem(LS_KEY) || "{}"));
104
+ return normalizeWho(JSON.parse(localStorage.getItem(BASE_STORAGE_KEY) || "{}"));
136
105
  } catch (e) {
137
106
  return {};
138
107
  }
139
108
  }
140
- function saveAll(obj) {
141
- _store = obj;
142
- idbPut(LS_KEY, obj).catch((e) => console.warn("SV save failed", e));
109
+ function saveAll(obj, namespace) {
110
+ const storageKey = chunkOGNADPM4_cjs.voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
111
+ stores.set(storageKey, obj);
112
+ void chunkOGNADPM4_cjs.setVoiceprints(storageKey, obj);
143
113
  }
144
114
  const DEFAULT_VOICE_ID = "you";
145
115
  function normPhrase(o) {
@@ -149,14 +119,15 @@
149
119
  if (Array.isArray(o.centroid)) return { voices: { [DEFAULT_VOICE_ID]: { label: "You", createdAt: 0, centroids: [o.centroid] } } };
150
120
  return { voices: {} };
151
121
  }
152
- function allCentroids(phrase) {
153
- const voices = normPhrase(loadAll()[phrase]).voices;
122
+ function allCentroids(phrase, namespace) {
123
+ const voices = normPhrase(loadAll(namespace)[phrase]).voices;
154
124
  const out = [];
155
125
  for (const id in voices) for (const c of voices[id].centroids || []) out.push(Float32Array.from(c));
156
126
  return out;
157
127
  }
158
128
  const SpeakerVerify = {
159
129
  ready: () => readyPromise,
130
+ loadNamespace: (namespace) => hydrateStore(namespace),
160
131
  isLoaded: () => handle !== 0,
161
132
  threshold: DEFAULT_THRESHOLD,
162
133
  // AS-norm (score normalization): normalize the raw cosine against a crowd of other voices, so the
@@ -172,7 +143,7 @@
172
143
  /** Enroll a VOICE for a phrase from N utterances → one condition-centroid under opts.voiceId
173
144
  * (default "you"). opts.append ADDS it as another condition for that voice; otherwise it replaces
174
145
  * that voice's conditions. opts.label names the voice. Capped to the most recent SV_CENTROID_CAP. */
175
- enroll(phrase, utterances, opts) {
146
+ enroll(phrase, utterances, opts, namespace) {
176
147
  if (!handle) throw new Error("SpeakerVerify not ready");
177
148
  const voiceId = opts && opts.voiceId || DEFAULT_VOICE_ID;
178
149
  const embs = utterances.map((u) => computeEmbedding(u.samples, u.sampleRate));
@@ -180,7 +151,7 @@
180
151
  for (const e of embs) for (let i = 0; i < dim; i++) c[i] += e[i];
181
152
  for (let i = 0; i < dim; i++) c[i] /= embs.length;
182
153
  l2normalize(c);
183
- const all = loadAll();
154
+ const all = loadAll(namespace);
184
155
  const entry = normPhrase(all[phrase]);
185
156
  const prevVoice = entry.voices[voiceId];
186
157
  const prev = opts && opts.append && prevVoice && Array.isArray(prevVoice.centroids) ? prevVoice.centroids.map((x) => Float32Array.from(x)) : [];
@@ -192,12 +163,12 @@
192
163
  centroids
193
164
  };
194
165
  all[phrase] = entry;
195
- saveAll(all);
166
+ saveAll(all, namespace);
196
167
  return { n: embs.length, conditions: centroids.length, voiceId };
197
168
  },
198
169
  /** Verify a live utterance against the BEST condition-centroid across ALL enrolled voices. */
199
- verify(phrase, samples, sampleRate) {
200
- const cents = allCentroids(phrase);
170
+ verify(phrase, samples, sampleRate, namespace) {
171
+ const cents = allCentroids(phrase, namespace);
201
172
  if (!cents.length) return { score: 0, znorm: null, pass: false, enrolled: false };
202
173
  const live = computeEmbedding(samples, sampleRate);
203
174
  let score = -1;
@@ -220,7 +191,7 @@
220
191
  return { score, znorm, pass, enrolled: true };
221
192
  },
222
193
  /** How many conditions are enrolled for a phrase, across ALL voices (0 = none). */
223
- conditionCount: (phrase) => allCentroids(phrase).length,
194
+ conditionCount: (phrase, namespace) => allCentroids(phrase, namespace).length,
224
195
  /** TitaNet speaker embedding for a raw utterance (Float32 + true sample rate). For diarization /
225
196
  * clustering. Returns the L2-normalized embedding, or null if the runtime isn't ready. */
226
197
  embed(samples, sampleRate) {
@@ -229,10 +200,10 @@
229
200
  /** Best-matching ENROLLED voice for a live utterance, across all voices + phrases (text-independent,
230
201
  * so conversational audio works). Returns { voiceId, label, score } (max cosine) or null if nothing
231
202
  * enrolled / not ready. The caller applies a threshold to decide whether to trust the name. */
232
- identify(samples, sampleRate) {
203
+ identify(samples, sampleRate, namespace) {
233
204
  if (!handle) return null;
234
205
  const live = computeEmbedding(samples, sampleRate);
235
- const all = loadAll();
206
+ const all = loadAll(namespace);
236
207
  const best = {};
237
208
  for (const phrase in all) {
238
209
  const voices = normPhrase(all[phrase]).voices;
@@ -250,8 +221,8 @@
250
221
  return top;
251
222
  },
252
223
  /** List enrolled voices aggregated across phrases: [{ id, label, createdAt, conditions }]. */
253
- listVoices() {
254
- const all = loadAll();
224
+ listVoices(namespace) {
225
+ const all = loadAll(namespace);
255
226
  const acc = {};
256
227
  for (const phrase in all) {
257
228
  const voices = normPhrase(all[phrase]).voices;
@@ -270,8 +241,8 @@
270
241
  },
271
242
  /** Remove a voice across all phrases — revokes that person's WHO match (the WHAT phrase-prints,
272
243
  * which are shared/speaker-independent, are left in voiceprintStore). */
273
- removeVoice(voiceId) {
274
- const all = loadAll();
244
+ removeVoice(voiceId, namespace) {
245
+ const all = loadAll(namespace);
275
246
  let changed = false;
276
247
  for (const phrase in all) {
277
248
  const entry = normPhrase(all[phrase]);
@@ -281,11 +252,11 @@
281
252
  changed = true;
282
253
  }
283
254
  }
284
- if (changed) saveAll(all);
255
+ if (changed) saveAll(all, namespace);
285
256
  },
286
257
  /** Rename a voice across all phrases. */
287
- renameVoice(voiceId, label) {
288
- const all = loadAll();
258
+ renameVoice(voiceId, label, namespace) {
259
+ const all = loadAll(namespace);
289
260
  let changed = false;
290
261
  for (const phrase in all) {
291
262
  const entry = normPhrase(all[phrase]);
@@ -295,19 +266,15 @@
295
266
  changed = true;
296
267
  }
297
268
  }
298
- if (changed) saveAll(all);
269
+ if (changed) saveAll(all, namespace);
299
270
  },
300
271
  // hasEnrollment(phrase) -> is that phrase enrolled; hasEnrollment() -> is anything enrolled.
301
- hasEnrollment: (phrase) => phrase ? allCentroids(phrase).length > 0 : Object.keys(loadAll()).some((p) => allCentroids(p).length > 0),
302
- enrolledPhrases: () => Object.keys(loadAll()),
303
- clearEnrollment: () => {
304
- _store = {};
305
- idbDel(LS_KEY).catch(() => {
306
- });
307
- try {
308
- localStorage.removeItem(LS_KEY);
309
- } catch (e) {
310
- }
272
+ hasEnrollment: (phrase, namespace) => phrase ? allCentroids(phrase, namespace).length > 0 : Object.keys(loadAll(namespace)).some((p) => allCentroids(p, namespace).length > 0),
273
+ enrolledPhrases: (namespace) => Object.keys(loadAll(namespace)),
274
+ clearEnrollment: (namespace) => {
275
+ const storageKey = chunkOGNADPM4_cjs.voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
276
+ stores.set(storageKey, {});
277
+ void chunkOGNADPM4_cjs.clearVoiceprints(storageKey);
311
278
  }
312
279
  };
313
280
  async function init() {
@@ -353,5 +320,5 @@
353
320
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
354
321
  else init();
355
322
  })();
356
- //# sourceMappingURL=speaker-verify-5GTWAN5Y.cjs.map
357
- //# sourceMappingURL=speaker-verify-5GTWAN5Y.cjs.map
323
+ //# sourceMappingURL=speaker-verify-4NEJAK6C.cjs.map
324
+ //# sourceMappingURL=speaker-verify-4NEJAK6C.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/AI/HeyOzwell/SpeakerVerify/lib/speaker-verify.js"],"names":["voiceprintStorageKey","getVoiceprints","setVoiceprints","clearVoiceprints"],"mappings":"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","file":"speaker-verify-4NEJAK6C.cjs","sourcesContent":["import {\n clearVoiceprints,\n getVoiceprints,\n setVoiceprints,\n voiceprintStorageKey,\n} from '../../../voiceprintStore';\n\n/**\n * On-device speaker-verification gate (the \"only the enrolled doctor can wake it\" layer).\n *\n * Loads the custom single-threaded sherpa-onnx WASM build (in /sv-wasm/) that exports the\n * SpeakerEmbeddingExtractor C-API, runs TitaNet speaker embeddings fully in-browser, and\n * verifies a live utterance against enrolled per-voice centroids stored in IndexedDB (db `ozwell-voice`,\n * migrated from the old localStorage format on first read).\n *\n * Proven in the /sv-wasm/ spike: a ~1s \"hey ozwell\" gives genuine cosine ~0.66 / impostor\n * ~0.30 (enroll-centroid: ~0.75 vs ~0.22, EER ~1.3%). This is a DIFFERENT axis from the\n * content voiceprint in hey-buddy.js: that BOOSTS the doctor's accented phrase (recall);\n * this BLOCKS a non-doctor's clean phrase (act/no-act). They compose.\n *\n * Loaded via dynamic import by the `useSpeakerVerify` hook (NOT a classic <script>). Exposes window.SpeakerVerify.\n * Audio in == Float32Array; pass its TRUE sample rate (sherpa resamples to 16k internally).\n */\n(function () {\n // Production runtime artifacts live in their OWN folder so they never clobber the\n // frozen feasibility proof in /sv-wasm (test.html + mic-test.html, kept for the\n // progression demo / YouTube short). This loads the single-threaded slim build.\n // Where the sherpa runtime (.js glue + .wasm + .data) and the AS-norm cohort are served from.\n // Default \"/sv-runtime\" (Storybook static dir). Set window.__ozwellAssets (a base URL string, or\n // { base, svRuntime }) to fetch the ~50 MB off the repo from a host — see AI/MODEL-HOSTING.md.\n const SV_DIR = (function () {\n // Storybook runs in an iframe; a console-set window.__ozwellAssets often lands on the parent frame,\n // so check current -> parent -> top (all same-origin).\n var pick = function (w) { try { return w && w.__ozwellAssets; } catch (e) { return null; } };\n var g = (typeof window !== \"undefined\") ? (pick(window) || pick(window.parent) || pick(window.top)) : null;\n var strip = function (s) { return String(s).replace(/\\/$/, \"\"); };\n if (typeof g === \"string\") return strip(g) + \"/sv-runtime\";\n if (g && g.svRuntime) return strip(g.svRuntime);\n if (g && g.base) return strip(g.base) + \"/sv-runtime\";\n // localStorage fallback — shared across Storybook frames, survives reload (the reliable path).\n try { var ls = localStorage.getItem(\"ozwellAssetBase\"); if (ls) return strip(ls) + \"/sv-runtime\"; } catch (e) { /* ignore */ }\n // Default host: the runtime was moved off the repo (see AI/MODEL-HOSTING.md) so a fresh clone\n // fetches it from here with no config. Override via window.__ozwellAssets / localStorage.\n return \"https://huggingface.co/jlocala/ozwell-voice-assets/resolve/main/sv-runtime\";\n })();\n const MODEL = \"./nemo_en_titanet_small.onnx\"; // preloaded into the WASM filesystem\n const BASE_STORAGE_KEY = \"ozwellDoctorVoiceprint\"; // { centroid: number[], n: number }\n // Threshold from the spike's enroll-centroid distributions (genuine ~0.75 / impostor ~0.22).\n // 0.45 (2026-06-22): with mic DSP on + matched enrollment the genuine doctor scores ~0.75-0.93 clean,\n // so 0.45 passes with wide margin and rejects impostors harder. Heavy background can still drag the doctor\n // toward ~0.4 (then 0.45 rejects -> \"say it again\", a fail-safe miss in an unrealistic-noise case).\n // Re-tune live: window.SpeakerVerify.threshold = 0.4 / 0.35 for heavy noise (don't go below ~0.3 — impostors).\n const DEFAULT_THRESHOLD = 0.45;\n\n let Module = null, handle = 0, dim = 0;\n let cohort = []; // AS-norm crowd: other-voice embeddings; live/centroid scored against it cancels channel\n let readyResolve, readyReject;\n const readyPromise = new Promise((res, rej) => { readyResolve = res; readyReject = rej; });\n\n function loadScript(src) {\n return new Promise((res, rej) => {\n const s = document.createElement(\"script\");\n s.src = src; s.onload = res; s.onerror = () => rej(new Error(\"failed to load \" + src));\n document.head.appendChild(s);\n });\n }\n\n // --- speaker-embedding extraction over the WASM C-API (names confirmed from the build) ---\n function computeEmbedding(samples, sampleRate) {\n const M = Module;\n const stream = M._SherpaOnnxSpeakerEmbeddingExtractorCreateStream(handle);\n const ptr = M._malloc(samples.length * 4);\n M.HEAPF32.set(samples, ptr / 4);\n M._SherpaOnnxOnlineStreamAcceptWaveform(stream, sampleRate, ptr, samples.length); // stream-only\n M._free(ptr);\n M._SherpaOnnxOnlineStreamInputFinished(stream);\n const ep = M._SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(handle, stream);\n const out = M.HEAPF32.subarray(ep / 4, ep / 4 + dim).slice();\n // free the embedding (exported in the slim rebuild); guard in case it's absent.\n if (M._SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding) {\n M._SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(ep);\n }\n M._SherpaOnnxDestroyOnlineStream(stream);\n return l2normalize(out);\n }\n\n function l2normalize(v) {\n let s = 0; for (let i = 0; i < v.length; i++) s += v[i] * v[i];\n s = Math.sqrt(s); if (s > 0) for (let i = 0; i < v.length; i++) v[i] /= s;\n return v;\n }\n function cosine(a, b) { let d = 0; const n = Math.min(a.length, b.length); for (let i = 0; i < n; i++) d += a[i] * b[i]; return d; } // both L2-normalized\n\n // --- enrollment storage: PER-PHRASE centroids of the doctor's embeddings (vectors, not\n // audio). Per-phrase because on a ~1s clip a speaker embedding still carries a little of\n // WHAT was said, so the doctor's \"hey ozwell\" centroid scores their \"ozwell i'm done\"\n // too low. We enroll + gate each phrase against its own centroid. ---\n const stores = new Map(); // in-memory WHO maps, hydrated per namespace before use\n const hydrations = new Map(); // one IndexedDB read per namespace, shared by concurrent hooks\n // Ignore the old single-centroid format ({centroid, n}) from earlier builds.\n const normalizeWho = (o) => (o && Array.isArray(o.centroid)) ? {} : (o || {});\n\n async function hydrateStore(namespace) {\n const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);\n if (stores.has(storageKey)) return;\n const pending = hydrations.get(storageKey);\n if (pending) return pending;\n const hydration = getVoiceprints(\n storageKey,\n namespace === undefined ? (raw) => normalizeWho(JSON.parse(raw || \"{}\")) : undefined\n ).then((value) => {\n stores.set(storageKey, value || {});\n });\n hydrations.set(storageKey, hydration);\n try {\n await hydration;\n } finally {\n hydrations.delete(storageKey);\n }\n }\n function loadAll(namespace) {\n const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);\n if (stores.has(storageKey)) return stores.get(storageKey);\n // Pre-hydration fallback (verify/enroll only run after ready(), which awaits hydrateStore).\n if (namespace !== undefined) return {};\n try { return normalizeWho(JSON.parse(localStorage.getItem(BASE_STORAGE_KEY) || \"{}\")); } catch (e) { return {}; }\n }\n function saveAll(obj, namespace) {\n const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);\n stores.set(storageKey, obj);\n void setVoiceprints(storageKey, obj);\n }\n // Multi-VOICE, multi-condition: each phrase stores a map of voices, each with a LIST of condition\n // centroids. Verify passes if ANY voice's ANY condition matches (so the doctor AND an enrolled\n // assistant both wake it; adding \"across the room\"/\"with background\" extends a voice's coverage).\n // Backward-compatible: legacy flat shapes ({centroids} / {centroid}) normalize to a default \"You\" voice.\n const SV_CENTROID_CAP = 6; // keep the most recent N conditions per voice\n const DEFAULT_VOICE_ID = \"you\";\n // Normalize any legacy per-phrase entry into { voices: { [id]: { label, createdAt, centroids:number[][] } } }.\n function normPhrase(o) {\n if (!o) return { voices: {} };\n if (o.voices) return o; // already voice-keyed\n if (Array.isArray(o.centroids)) return { voices: { [DEFAULT_VOICE_ID]: { label: \"You\", createdAt: 0, centroids: o.centroids } } };\n if (Array.isArray(o.centroid)) return { voices: { [DEFAULT_VOICE_ID]: { label: \"You\", createdAt: 0, centroids: [o.centroid] } } };\n return { voices: {} };\n }\n // Flat list of EVERY enrolled voice's centroids for a phrase (verify passes if any matches).\n function allCentroids(phrase, namespace) {\n const voices = normPhrase(loadAll(namespace)[phrase]).voices;\n const out = [];\n for (const id in voices) for (const c of (voices[id].centroids || [])) out.push(Float32Array.from(c));\n return out;\n }\n\n const SpeakerVerify = {\n ready: () => readyPromise,\n loadNamespace: (namespace) => hydrateStore(namespace),\n isLoaded: () => handle !== 0,\n threshold: DEFAULT_THRESHOLD,\n // AS-norm (score normalization): normalize the raw cosine against a crowd of other voices, so the\n // threshold stays stable when a room/mic drags scores down. PRIMARY gate when a cohort is loaded (znorm\n // != null); falls back to raw cosine only when there's no cohort. Turned on after live diagnostics showed\n // the genuine speaker scoring z 2–3.4 (well clear of 1.5) while raw cosine sat at 0.33–0.43 — i.e. raw\n // cosine was rejecting the real speaker in a room/mic that differed from enrollment, and AS-norm fixes it.\n useAsnorm: true,\n znormThreshold: 1.5, // \"at least N std above the crowd\"; tune from genuine vs real-impostor z-scores\n cohortSize: () => cohort.length,\n embeddingDim: () => dim,\n\n /** Enroll a VOICE for a phrase from N utterances → one condition-centroid under opts.voiceId\n * (default \"you\"). opts.append ADDS it as another condition for that voice; otherwise it replaces\n * that voice's conditions. opts.label names the voice. Capped to the most recent SV_CENTROID_CAP. */\n enroll(phrase, utterances /* [{samples, sampleRate}] */, opts, namespace) {\n if (!handle) throw new Error(\"SpeakerVerify not ready\");\n const voiceId = (opts && opts.voiceId) || DEFAULT_VOICE_ID;\n const embs = utterances.map(u => computeEmbedding(u.samples, u.sampleRate));\n const c = new Float32Array(dim);\n for (const e of embs) for (let i = 0; i < dim; i++) c[i] += e[i];\n for (let i = 0; i < dim; i++) c[i] /= embs.length;\n l2normalize(c);\n const all = loadAll(namespace);\n const entry = normPhrase(all[phrase]);\n const prevVoice = entry.voices[voiceId];\n const prev = (opts && opts.append && prevVoice && Array.isArray(prevVoice.centroids))\n ? prevVoice.centroids.map((x) => Float32Array.from(x)) : [];\n const centroids = prev.concat([c]).slice(-SV_CENTROID_CAP).map((v) => Array.from(v));\n entry.voices[voiceId] = {\n // keep the existing label when appending; otherwise use the provided one (or a sensible default)\n label: (prevVoice && opts && opts.append) ? prevVoice.label : ((opts && opts.label) || (voiceId === DEFAULT_VOICE_ID ? \"You\" : \"Voice\")),\n createdAt: (prevVoice && prevVoice.createdAt) || Date.now(),\n centroids,\n };\n all[phrase] = entry; saveAll(all, namespace);\n return { n: embs.length, conditions: centroids.length, voiceId };\n },\n\n /** Verify a live utterance against the BEST condition-centroid across ALL enrolled voices. */\n verify(phrase, samples, sampleRate, namespace) {\n const cents = allCentroids(phrase, namespace);\n if (!cents.length) return { score: 0, znorm: null, pass: false, enrolled: false };\n const live = computeEmbedding(samples, sampleRate);\n let score = -1;\n for (const c of cents) { const s = cosine(live, c); if (s > score) score = s; }\n // AS-norm: z = how many std the raw score sits above the crowd's scores against this same live clip.\n // A weird room drags the crowd scores down too, so the offset cancels and z stays stable.\n let znorm = null;\n if (cohort.length) {\n let sum = 0, sum2 = 0;\n for (const cv of cohort) { const s = cosine(live, cv); sum += s; sum2 += s * s; }\n const m = cohort.length, mean = sum / m, std = Math.sqrt(Math.max(sum2 / m - mean * mean, 1e-9));\n znorm = (score - mean) / std;\n }\n const pass = (SpeakerVerify.useAsnorm && znorm !== null)\n ? (znorm >= SpeakerVerify.znormThreshold)\n : (score >= SpeakerVerify.threshold);\n return { score, znorm, pass, enrolled: true };\n },\n\n /** How many conditions are enrolled for a phrase, across ALL voices (0 = none). */\n conditionCount: (phrase, namespace) => allCentroids(phrase, namespace).length,\n\n /** TitaNet speaker embedding for a raw utterance (Float32 + true sample rate). For diarization /\n * clustering. Returns the L2-normalized embedding, or null if the runtime isn't ready. */\n embed(samples, sampleRate) {\n return handle ? computeEmbedding(samples, sampleRate) : null;\n },\n\n /** Best-matching ENROLLED voice for a live utterance, across all voices + phrases (text-independent,\n * so conversational audio works). Returns { voiceId, label, score } (max cosine) or null if nothing\n * enrolled / not ready. The caller applies a threshold to decide whether to trust the name. */\n identify(samples, sampleRate, namespace) {\n if (!handle) return null;\n const live = computeEmbedding(samples, sampleRate);\n const all = loadAll(namespace);\n const best = {}; // voiceId -> { label, score }\n for (const phrase in all) {\n const voices = normPhrase(all[phrase]).voices;\n for (const id in voices) {\n for (const c of (voices[id].centroids || [])) {\n const s = cosine(live, Float32Array.from(c));\n if (!best[id] || s > best[id].score) best[id] = { label: voices[id].label, score: s };\n }\n }\n }\n let top = null;\n for (const id in best) {\n if (!top || best[id].score > top.score) top = { voiceId: id, label: best[id].label, score: best[id].score };\n }\n return top;\n },\n\n /** List enrolled voices aggregated across phrases: [{ id, label, createdAt, conditions }]. */\n listVoices(namespace) {\n const all = loadAll(namespace);\n const acc = {};\n for (const phrase in all) {\n const voices = normPhrase(all[phrase]).voices;\n for (const id in voices) {\n const v = voices[id];\n const conds = (v.centroids || []).length;\n if (!acc[id]) acc[id] = { id, label: v.label || id, createdAt: v.createdAt || 0, conditions: conds };\n else {\n acc[id].conditions = Math.max(acc[id].conditions, conds);\n if (v.label) acc[id].label = v.label;\n if (v.createdAt && (!acc[id].createdAt || v.createdAt < acc[id].createdAt)) acc[id].createdAt = v.createdAt;\n }\n }\n }\n return Object.keys(acc).map((k) => acc[k]).sort((a, b) => a.createdAt - b.createdAt);\n },\n\n /** Remove a voice across all phrases — revokes that person's WHO match (the WHAT phrase-prints,\n * which are shared/speaker-independent, are left in voiceprintStore). */\n removeVoice(voiceId, namespace) {\n const all = loadAll(namespace);\n let changed = false;\n for (const phrase in all) {\n const entry = normPhrase(all[phrase]);\n if (entry.voices[voiceId]) { delete entry.voices[voiceId]; all[phrase] = entry; changed = true; }\n }\n if (changed) saveAll(all, namespace);\n },\n\n /** Rename a voice across all phrases. */\n renameVoice(voiceId, label, namespace) {\n const all = loadAll(namespace);\n let changed = false;\n for (const phrase in all) {\n const entry = normPhrase(all[phrase]);\n if (entry.voices[voiceId]) { entry.voices[voiceId].label = label; all[phrase] = entry; changed = true; }\n }\n if (changed) saveAll(all, namespace);\n },\n\n // hasEnrollment(phrase) -> is that phrase enrolled; hasEnrollment() -> is anything enrolled.\n hasEnrollment: (phrase, namespace) => phrase ? allCentroids(phrase, namespace).length > 0 : Object.keys(loadAll(namespace)).some((p) => allCentroids(p, namespace).length > 0),\n enrolledPhrases: (namespace) => Object.keys(loadAll(namespace)),\n clearEnrollment: (namespace) => {\n const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);\n stores.set(storageKey, {});\n void clearVoiceprints(storageKey);\n },\n };\n\n async function init() {\n // Save the existing Emscripten global so we can restore it — don't permanently clobber window.Module\n // for any other emscripten bundle the host loads.\n const prevModule = window.Module;\n try {\n // locateFile: the page is at \"/\" but the glue + .wasm/.data live in SV_DIR, so\n // tell emscripten exactly where to fetch them (otherwise it 404s and init hangs).\n window.Module = { locateFile: (path) => SV_DIR + \"/\" + path };\n const onRuntime = new Promise((res) => { window.Module.onRuntimeInitialized = res; });\n // order matters: config-marshaling helper, then the glue (which fires onRuntimeInitialized)\n await loadScript(SV_DIR + \"/sherpa-onnx-speaker-diarization.js\");\n await loadScript(SV_DIR + \"/sherpa-onnx-wasm-main-speaker-diarization.js\");\n await onRuntime;\n Module = window.Module;\n window.Module = prevModule; // restore the global; we keep our own ref in `Module`\n const cfg = initSherpaOnnxSpeakerEmbeddingExtractorConfig(\n { model: MODEL, numThreads: 1, debug: 0, provider: \"cpu\" }, Module);\n handle = Module._SherpaOnnxCreateSpeakerEmbeddingExtractor(cfg.ptr);\n Module._free(cfg.buffer); Module._free(cfg.ptr);\n if (!handle) throw new Error(\"CreateSpeakerEmbeddingExtractor returned null\");\n dim = Module._SherpaOnnxSpeakerEmbeddingExtractorDim(handle);\n console.log(\"[SpeakerVerify] ready — embedding dim\", dim);\n // Load the AS-norm cohort (other-voice embeddings, same model). Optional — gate works without it.\n try {\n const r = await fetch(SV_DIR + \"/sv-cohort.json\");\n if (r.ok) { cohort = (await r.json()).map((v) => Float32Array.from(v)); console.log(\"[SpeakerVerify] AS-norm cohort:\", cohort.length); }\n } catch (e) { console.warn(\"[SpeakerVerify] cohort load failed (AS-norm off):\", e); }\n await hydrateStore(); // preserve the existing unscoped API by default\n readyResolve(SpeakerVerify);\n } catch (e) {\n window.Module = prevModule; // restore the global even on failure\n console.error(\"[SpeakerVerify] init failed:\", e);\n readyReject(e);\n }\n }\n\n window.SpeakerVerify = SpeakerVerify;\n if (document.readyState === \"loading\") document.addEventListener(\"DOMContentLoaded\", init);\n else init();\n})();\n"]}
@@ -1,3 +1,6 @@
1
+ import { voiceprintStorageKey, clearVoiceprints, setVoiceprints, getVoiceprints } from './chunk-74ENCTIK.js';
2
+ import './chunk-PX6F3LHL.js';
3
+
1
4
  // src/components/AI/HeyOzwell/SpeakerVerify/lib/speaker-verify.js
2
5
  (function() {
3
6
  const SV_DIR = (function() {
@@ -23,7 +26,7 @@
23
26
  return "https://huggingface.co/jlocala/ozwell-voice-assets/resolve/main/sv-runtime";
24
27
  })();
25
28
  const MODEL = "./nemo_en_titanet_small.onnx";
26
- const LS_KEY = "ozwellDoctorVoiceprint";
29
+ const BASE_STORAGE_KEY = "ozwellDoctorVoiceprint";
27
30
  const DEFAULT_THRESHOLD = 0.45;
28
31
  let Module = null, handle = 0, dim = 0;
29
32
  let cohort = [];
@@ -70,74 +73,41 @@
70
73
  for (let i = 0; i < n; i++) d += a[i] * b[i];
71
74
  return d;
72
75
  }
73
- const IDB_DB = "ozwell-voice", IDB_STORE = "voiceprints";
74
- function idbOpen() {
75
- return new Promise((resolve, reject) => {
76
- const req = indexedDB.open(IDB_DB, 1);
77
- req.onupgradeneeded = () => {
78
- if (!req.result.objectStoreNames.contains(IDB_STORE)) req.result.createObjectStore(IDB_STORE);
79
- };
80
- req.onsuccess = () => resolve(req.result);
81
- req.onerror = () => reject(req.error);
82
- });
83
- }
84
- function idbReq(mode, run) {
85
- return idbOpen().then((db) => new Promise((resolve, reject) => {
86
- const tx = db.transaction(IDB_STORE, mode);
87
- const r = run(tx.objectStore(IDB_STORE));
88
- tx.oncomplete = () => {
89
- resolve(r && r.result);
90
- db.close();
91
- };
92
- tx.onerror = () => {
93
- reject(tx.error);
94
- db.close();
95
- };
96
- tx.onabort = () => {
97
- reject(tx.error);
98
- db.close();
99
- };
100
- }));
101
- }
102
- const idbGet = (key) => idbReq("readonly", (s) => s.get(key));
103
- const idbPut = (key, v) => idbReq("readwrite", (s) => s.put(v, key));
104
- const idbDel = (key) => idbReq("readwrite", (s) => s.delete(key));
105
- let _store = null;
76
+ const stores = /* @__PURE__ */ new Map();
77
+ const hydrations = /* @__PURE__ */ new Map();
106
78
  const normalizeWho = (o) => o && Array.isArray(o.centroid) ? {} : o || {};
107
- async function hydrateStore() {
79
+ async function hydrateStore(namespace) {
80
+ const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
81
+ if (stores.has(storageKey)) return;
82
+ const pending = hydrations.get(storageKey);
83
+ if (pending) return pending;
84
+ const hydration = getVoiceprints(
85
+ storageKey,
86
+ namespace === void 0 ? (raw) => normalizeWho(JSON.parse(raw || "{}")) : void 0
87
+ ).then((value) => {
88
+ stores.set(storageKey, value || {});
89
+ });
90
+ hydrations.set(storageKey, hydration);
108
91
  try {
109
- let v = await idbGet(LS_KEY);
110
- if (v === void 0) {
111
- let raw = null;
112
- try {
113
- raw = localStorage.getItem(LS_KEY);
114
- } catch (e) {
115
- }
116
- if (raw != null) {
117
- v = normalizeWho(JSON.parse(raw || "{}"));
118
- await idbPut(LS_KEY, v);
119
- try {
120
- localStorage.removeItem(LS_KEY);
121
- } catch (e) {
122
- }
123
- }
124
- }
125
- _store = v || {};
126
- } catch (e) {
127
- _store = _store || {};
92
+ await hydration;
93
+ } finally {
94
+ hydrations.delete(storageKey);
128
95
  }
129
96
  }
130
- function loadAll() {
131
- if (_store) return _store;
97
+ function loadAll(namespace) {
98
+ const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
99
+ if (stores.has(storageKey)) return stores.get(storageKey);
100
+ if (namespace !== void 0) return {};
132
101
  try {
133
- return normalizeWho(JSON.parse(localStorage.getItem(LS_KEY) || "{}"));
102
+ return normalizeWho(JSON.parse(localStorage.getItem(BASE_STORAGE_KEY) || "{}"));
134
103
  } catch (e) {
135
104
  return {};
136
105
  }
137
106
  }
138
- function saveAll(obj) {
139
- _store = obj;
140
- idbPut(LS_KEY, obj).catch((e) => console.warn("SV save failed", e));
107
+ function saveAll(obj, namespace) {
108
+ const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
109
+ stores.set(storageKey, obj);
110
+ void setVoiceprints(storageKey, obj);
141
111
  }
142
112
  const DEFAULT_VOICE_ID = "you";
143
113
  function normPhrase(o) {
@@ -147,14 +117,15 @@
147
117
  if (Array.isArray(o.centroid)) return { voices: { [DEFAULT_VOICE_ID]: { label: "You", createdAt: 0, centroids: [o.centroid] } } };
148
118
  return { voices: {} };
149
119
  }
150
- function allCentroids(phrase) {
151
- const voices = normPhrase(loadAll()[phrase]).voices;
120
+ function allCentroids(phrase, namespace) {
121
+ const voices = normPhrase(loadAll(namespace)[phrase]).voices;
152
122
  const out = [];
153
123
  for (const id in voices) for (const c of voices[id].centroids || []) out.push(Float32Array.from(c));
154
124
  return out;
155
125
  }
156
126
  const SpeakerVerify = {
157
127
  ready: () => readyPromise,
128
+ loadNamespace: (namespace) => hydrateStore(namespace),
158
129
  isLoaded: () => handle !== 0,
159
130
  threshold: DEFAULT_THRESHOLD,
160
131
  // AS-norm (score normalization): normalize the raw cosine against a crowd of other voices, so the
@@ -170,7 +141,7 @@
170
141
  /** Enroll a VOICE for a phrase from N utterances → one condition-centroid under opts.voiceId
171
142
  * (default "you"). opts.append ADDS it as another condition for that voice; otherwise it replaces
172
143
  * that voice's conditions. opts.label names the voice. Capped to the most recent SV_CENTROID_CAP. */
173
- enroll(phrase, utterances, opts) {
144
+ enroll(phrase, utterances, opts, namespace) {
174
145
  if (!handle) throw new Error("SpeakerVerify not ready");
175
146
  const voiceId = opts && opts.voiceId || DEFAULT_VOICE_ID;
176
147
  const embs = utterances.map((u) => computeEmbedding(u.samples, u.sampleRate));
@@ -178,7 +149,7 @@
178
149
  for (const e of embs) for (let i = 0; i < dim; i++) c[i] += e[i];
179
150
  for (let i = 0; i < dim; i++) c[i] /= embs.length;
180
151
  l2normalize(c);
181
- const all = loadAll();
152
+ const all = loadAll(namespace);
182
153
  const entry = normPhrase(all[phrase]);
183
154
  const prevVoice = entry.voices[voiceId];
184
155
  const prev = opts && opts.append && prevVoice && Array.isArray(prevVoice.centroids) ? prevVoice.centroids.map((x) => Float32Array.from(x)) : [];
@@ -190,12 +161,12 @@
190
161
  centroids
191
162
  };
192
163
  all[phrase] = entry;
193
- saveAll(all);
164
+ saveAll(all, namespace);
194
165
  return { n: embs.length, conditions: centroids.length, voiceId };
195
166
  },
196
167
  /** Verify a live utterance against the BEST condition-centroid across ALL enrolled voices. */
197
- verify(phrase, samples, sampleRate) {
198
- const cents = allCentroids(phrase);
168
+ verify(phrase, samples, sampleRate, namespace) {
169
+ const cents = allCentroids(phrase, namespace);
199
170
  if (!cents.length) return { score: 0, znorm: null, pass: false, enrolled: false };
200
171
  const live = computeEmbedding(samples, sampleRate);
201
172
  let score = -1;
@@ -218,7 +189,7 @@
218
189
  return { score, znorm, pass, enrolled: true };
219
190
  },
220
191
  /** How many conditions are enrolled for a phrase, across ALL voices (0 = none). */
221
- conditionCount: (phrase) => allCentroids(phrase).length,
192
+ conditionCount: (phrase, namespace) => allCentroids(phrase, namespace).length,
222
193
  /** TitaNet speaker embedding for a raw utterance (Float32 + true sample rate). For diarization /
223
194
  * clustering. Returns the L2-normalized embedding, or null if the runtime isn't ready. */
224
195
  embed(samples, sampleRate) {
@@ -227,10 +198,10 @@
227
198
  /** Best-matching ENROLLED voice for a live utterance, across all voices + phrases (text-independent,
228
199
  * so conversational audio works). Returns { voiceId, label, score } (max cosine) or null if nothing
229
200
  * enrolled / not ready. The caller applies a threshold to decide whether to trust the name. */
230
- identify(samples, sampleRate) {
201
+ identify(samples, sampleRate, namespace) {
231
202
  if (!handle) return null;
232
203
  const live = computeEmbedding(samples, sampleRate);
233
- const all = loadAll();
204
+ const all = loadAll(namespace);
234
205
  const best = {};
235
206
  for (const phrase in all) {
236
207
  const voices = normPhrase(all[phrase]).voices;
@@ -248,8 +219,8 @@
248
219
  return top;
249
220
  },
250
221
  /** List enrolled voices aggregated across phrases: [{ id, label, createdAt, conditions }]. */
251
- listVoices() {
252
- const all = loadAll();
222
+ listVoices(namespace) {
223
+ const all = loadAll(namespace);
253
224
  const acc = {};
254
225
  for (const phrase in all) {
255
226
  const voices = normPhrase(all[phrase]).voices;
@@ -268,8 +239,8 @@
268
239
  },
269
240
  /** Remove a voice across all phrases — revokes that person's WHO match (the WHAT phrase-prints,
270
241
  * which are shared/speaker-independent, are left in voiceprintStore). */
271
- removeVoice(voiceId) {
272
- const all = loadAll();
242
+ removeVoice(voiceId, namespace) {
243
+ const all = loadAll(namespace);
273
244
  let changed = false;
274
245
  for (const phrase in all) {
275
246
  const entry = normPhrase(all[phrase]);
@@ -279,11 +250,11 @@
279
250
  changed = true;
280
251
  }
281
252
  }
282
- if (changed) saveAll(all);
253
+ if (changed) saveAll(all, namespace);
283
254
  },
284
255
  /** Rename a voice across all phrases. */
285
- renameVoice(voiceId, label) {
286
- const all = loadAll();
256
+ renameVoice(voiceId, label, namespace) {
257
+ const all = loadAll(namespace);
287
258
  let changed = false;
288
259
  for (const phrase in all) {
289
260
  const entry = normPhrase(all[phrase]);
@@ -293,19 +264,15 @@
293
264
  changed = true;
294
265
  }
295
266
  }
296
- if (changed) saveAll(all);
267
+ if (changed) saveAll(all, namespace);
297
268
  },
298
269
  // hasEnrollment(phrase) -> is that phrase enrolled; hasEnrollment() -> is anything enrolled.
299
- hasEnrollment: (phrase) => phrase ? allCentroids(phrase).length > 0 : Object.keys(loadAll()).some((p) => allCentroids(p).length > 0),
300
- enrolledPhrases: () => Object.keys(loadAll()),
301
- clearEnrollment: () => {
302
- _store = {};
303
- idbDel(LS_KEY).catch(() => {
304
- });
305
- try {
306
- localStorage.removeItem(LS_KEY);
307
- } catch (e) {
308
- }
270
+ hasEnrollment: (phrase, namespace) => phrase ? allCentroids(phrase, namespace).length > 0 : Object.keys(loadAll(namespace)).some((p) => allCentroids(p, namespace).length > 0),
271
+ enrolledPhrases: (namespace) => Object.keys(loadAll(namespace)),
272
+ clearEnrollment: (namespace) => {
273
+ const storageKey = voiceprintStorageKey(BASE_STORAGE_KEY, namespace);
274
+ stores.set(storageKey, {});
275
+ void clearVoiceprints(storageKey);
309
276
  }
310
277
  };
311
278
  async function init() {
@@ -351,5 +318,5 @@
351
318
  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
352
319
  else init();
353
320
  })();
354
- //# sourceMappingURL=speaker-verify-R67P433H.js.map
355
- //# sourceMappingURL=speaker-verify-R67P433H.js.map
321
+ //# sourceMappingURL=speaker-verify-AWSKI6DI.js.map
322
+ //# sourceMappingURL=speaker-verify-AWSKI6DI.js.map