@mieweb/ui 0.6.1-dev.164 → 0.6.1-dev.166
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/dist/brands/index.cjs +7 -7
- package/dist/brands/index.js +2 -2
- package/dist/{chunk-NBD236EH.js → chunk-5O23GUS3.js} +15 -2
- package/dist/chunk-5O23GUS3.js.map +1 -0
- package/dist/chunk-6I7IDZ4A.js +51 -0
- package/dist/chunk-6I7IDZ4A.js.map +1 -0
- package/dist/{chunk-Z6NRP4Z5.cjs → chunk-JWTCEWQ4.cjs} +2 -2
- package/dist/{chunk-Z6NRP4Z5.cjs.map → chunk-JWTCEWQ4.cjs.map} +1 -1
- package/dist/{chunk-Y65SK5Y2.cjs → chunk-MJ7YITLN.cjs} +2 -2
- package/dist/{chunk-Y65SK5Y2.cjs.map → chunk-MJ7YITLN.cjs.map} +1 -1
- package/dist/{chunk-R6PBBPU3.js → chunk-TXRQQMG5.js} +2 -2
- package/dist/{chunk-R6PBBPU3.js.map → chunk-TXRQQMG5.js.map} +1 -1
- package/dist/chunk-UVSODK6V.cjs +53 -0
- package/dist/chunk-UVSODK6V.cjs.map +1 -0
- package/dist/{chunk-YYDW3ZZS.cjs → chunk-W5B3VQUQ.cjs} +15 -2
- package/dist/chunk-W5B3VQUQ.cjs.map +1 -0
- package/dist/{chunk-NSLR3B7K.js → chunk-XVF472GT.js} +2 -2
- package/dist/{chunk-NSLR3B7K.js.map → chunk-XVF472GT.js.map} +1 -1
- package/dist/components/Markdown/index.cjs +10 -10
- package/dist/components/Markdown/index.js +2 -2
- package/dist/components/Skeleton/index.d.cts +1 -1
- package/dist/components/Skeleton/index.d.ts +1 -1
- package/dist/hey-buddy-CLUVAY2X.cjs +1133 -0
- package/dist/hey-buddy-CLUVAY2X.cjs.map +1 -0
- package/dist/hey-buddy-NMSWZ4TN.js +1131 -0
- package/dist/hey-buddy-NMSWZ4TN.js.map +1 -0
- package/dist/index.cjs +4345 -542
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +786 -1
- package/dist/index.d.ts +786 -1
- package/dist/index.js +4321 -569
- package/dist/index.js.map +1 -1
- package/dist/speaker-verify-5GTWAN5Y.cjs +357 -0
- package/dist/speaker-verify-5GTWAN5Y.cjs.map +1 -0
- package/dist/speaker-verify-R67P433H.js +355 -0
- package/dist/speaker-verify-R67P433H.js.map +1 -0
- package/dist/styles/init.css +5 -0
- package/dist/styles.css +1 -1
- package/dist/tailwind-preset.cjs +4 -4
- package/dist/tailwind-preset.js +1 -1
- package/package.json +4 -3
- package/dist/chunk-NBD236EH.js.map +0 -1
- package/dist/chunk-YYDW3ZZS.cjs.map +0 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/components/AI/HeyOzwell/SpeakerVerify/lib/speaker-verify.js
|
|
4
|
+
(function() {
|
|
5
|
+
const SV_DIR = (function() {
|
|
6
|
+
var pick = function(w) {
|
|
7
|
+
try {
|
|
8
|
+
return w && w.__ozwellAssets;
|
|
9
|
+
} catch (e) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var g = typeof window !== "undefined" ? pick(window) || pick(window.parent) || pick(window.top) : null;
|
|
14
|
+
var strip = function(s) {
|
|
15
|
+
return String(s).replace(/\/$/, "");
|
|
16
|
+
};
|
|
17
|
+
if (typeof g === "string") return strip(g) + "/sv-runtime";
|
|
18
|
+
if (g && g.svRuntime) return strip(g.svRuntime);
|
|
19
|
+
if (g && g.base) return strip(g.base) + "/sv-runtime";
|
|
20
|
+
try {
|
|
21
|
+
var ls = localStorage.getItem("ozwellAssetBase");
|
|
22
|
+
if (ls) return strip(ls) + "/sv-runtime";
|
|
23
|
+
} catch (e) {
|
|
24
|
+
}
|
|
25
|
+
return "https://huggingface.co/jlocala/ozwell-voice-assets/resolve/main/sv-runtime";
|
|
26
|
+
})();
|
|
27
|
+
const MODEL = "./nemo_en_titanet_small.onnx";
|
|
28
|
+
const LS_KEY = "ozwellDoctorVoiceprint";
|
|
29
|
+
const DEFAULT_THRESHOLD = 0.45;
|
|
30
|
+
let Module = null, handle = 0, dim = 0;
|
|
31
|
+
let cohort = [];
|
|
32
|
+
let readyResolve, readyReject;
|
|
33
|
+
const readyPromise = new Promise((res, rej) => {
|
|
34
|
+
readyResolve = res;
|
|
35
|
+
readyReject = rej;
|
|
36
|
+
});
|
|
37
|
+
function loadScript(src) {
|
|
38
|
+
return new Promise((res, rej) => {
|
|
39
|
+
const s = document.createElement("script");
|
|
40
|
+
s.src = src;
|
|
41
|
+
s.onload = res;
|
|
42
|
+
s.onerror = () => rej(new Error("failed to load " + src));
|
|
43
|
+
document.head.appendChild(s);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function computeEmbedding(samples, sampleRate) {
|
|
47
|
+
const M = Module;
|
|
48
|
+
const stream = M._SherpaOnnxSpeakerEmbeddingExtractorCreateStream(handle);
|
|
49
|
+
const ptr = M._malloc(samples.length * 4);
|
|
50
|
+
M.HEAPF32.set(samples, ptr / 4);
|
|
51
|
+
M._SherpaOnnxOnlineStreamAcceptWaveform(stream, sampleRate, ptr, samples.length);
|
|
52
|
+
M._free(ptr);
|
|
53
|
+
M._SherpaOnnxOnlineStreamInputFinished(stream);
|
|
54
|
+
const ep = M._SherpaOnnxSpeakerEmbeddingExtractorComputeEmbedding(handle, stream);
|
|
55
|
+
const out = M.HEAPF32.subarray(ep / 4, ep / 4 + dim).slice();
|
|
56
|
+
if (M._SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding) {
|
|
57
|
+
M._SherpaOnnxSpeakerEmbeddingExtractorDestroyEmbedding(ep);
|
|
58
|
+
}
|
|
59
|
+
M._SherpaOnnxDestroyOnlineStream(stream);
|
|
60
|
+
return l2normalize(out);
|
|
61
|
+
}
|
|
62
|
+
function l2normalize(v) {
|
|
63
|
+
let s = 0;
|
|
64
|
+
for (let i = 0; i < v.length; i++) s += v[i] * v[i];
|
|
65
|
+
s = Math.sqrt(s);
|
|
66
|
+
if (s > 0) for (let i = 0; i < v.length; i++) v[i] /= s;
|
|
67
|
+
return v;
|
|
68
|
+
}
|
|
69
|
+
function cosine(a, b) {
|
|
70
|
+
let d = 0;
|
|
71
|
+
const n = Math.min(a.length, b.length);
|
|
72
|
+
for (let i = 0; i < n; i++) d += a[i] * b[i];
|
|
73
|
+
return d;
|
|
74
|
+
}
|
|
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;
|
|
108
|
+
const normalizeWho = (o) => o && Array.isArray(o.centroid) ? {} : o || {};
|
|
109
|
+
async function hydrateStore() {
|
|
110
|
+
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 || {};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function loadAll() {
|
|
133
|
+
if (_store) return _store;
|
|
134
|
+
try {
|
|
135
|
+
return normalizeWho(JSON.parse(localStorage.getItem(LS_KEY) || "{}"));
|
|
136
|
+
} catch (e) {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function saveAll(obj) {
|
|
141
|
+
_store = obj;
|
|
142
|
+
idbPut(LS_KEY, obj).catch((e) => console.warn("SV save failed", e));
|
|
143
|
+
}
|
|
144
|
+
const DEFAULT_VOICE_ID = "you";
|
|
145
|
+
function normPhrase(o) {
|
|
146
|
+
if (!o) return { voices: {} };
|
|
147
|
+
if (o.voices) return o;
|
|
148
|
+
if (Array.isArray(o.centroids)) return { voices: { [DEFAULT_VOICE_ID]: { label: "You", createdAt: 0, centroids: o.centroids } } };
|
|
149
|
+
if (Array.isArray(o.centroid)) return { voices: { [DEFAULT_VOICE_ID]: { label: "You", createdAt: 0, centroids: [o.centroid] } } };
|
|
150
|
+
return { voices: {} };
|
|
151
|
+
}
|
|
152
|
+
function allCentroids(phrase) {
|
|
153
|
+
const voices = normPhrase(loadAll()[phrase]).voices;
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const id in voices) for (const c of voices[id].centroids || []) out.push(Float32Array.from(c));
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
const SpeakerVerify = {
|
|
159
|
+
ready: () => readyPromise,
|
|
160
|
+
isLoaded: () => handle !== 0,
|
|
161
|
+
threshold: DEFAULT_THRESHOLD,
|
|
162
|
+
// AS-norm (score normalization): normalize the raw cosine against a crowd of other voices, so the
|
|
163
|
+
// threshold stays stable when a room/mic drags scores down. PRIMARY gate when a cohort is loaded (znorm
|
|
164
|
+
// != null); falls back to raw cosine only when there's no cohort. Turned on after live diagnostics showed
|
|
165
|
+
// 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
|
|
166
|
+
// cosine was rejecting the real speaker in a room/mic that differed from enrollment, and AS-norm fixes it.
|
|
167
|
+
useAsnorm: true,
|
|
168
|
+
znormThreshold: 1.5,
|
|
169
|
+
// "at least N std above the crowd"; tune from genuine vs real-impostor z-scores
|
|
170
|
+
cohortSize: () => cohort.length,
|
|
171
|
+
embeddingDim: () => dim,
|
|
172
|
+
/** Enroll a VOICE for a phrase from N utterances → one condition-centroid under opts.voiceId
|
|
173
|
+
* (default "you"). opts.append ADDS it as another condition for that voice; otherwise it replaces
|
|
174
|
+
* that voice's conditions. opts.label names the voice. Capped to the most recent SV_CENTROID_CAP. */
|
|
175
|
+
enroll(phrase, utterances, opts) {
|
|
176
|
+
if (!handle) throw new Error("SpeakerVerify not ready");
|
|
177
|
+
const voiceId = opts && opts.voiceId || DEFAULT_VOICE_ID;
|
|
178
|
+
const embs = utterances.map((u) => computeEmbedding(u.samples, u.sampleRate));
|
|
179
|
+
const c = new Float32Array(dim);
|
|
180
|
+
for (const e of embs) for (let i = 0; i < dim; i++) c[i] += e[i];
|
|
181
|
+
for (let i = 0; i < dim; i++) c[i] /= embs.length;
|
|
182
|
+
l2normalize(c);
|
|
183
|
+
const all = loadAll();
|
|
184
|
+
const entry = normPhrase(all[phrase]);
|
|
185
|
+
const prevVoice = entry.voices[voiceId];
|
|
186
|
+
const prev = opts && opts.append && prevVoice && Array.isArray(prevVoice.centroids) ? prevVoice.centroids.map((x) => Float32Array.from(x)) : [];
|
|
187
|
+
const centroids = prev.concat([c]).slice(-6).map((v) => Array.from(v));
|
|
188
|
+
entry.voices[voiceId] = {
|
|
189
|
+
// keep the existing label when appending; otherwise use the provided one (or a sensible default)
|
|
190
|
+
label: prevVoice && opts && opts.append ? prevVoice.label : opts && opts.label || (voiceId === DEFAULT_VOICE_ID ? "You" : "Voice"),
|
|
191
|
+
createdAt: prevVoice && prevVoice.createdAt || Date.now(),
|
|
192
|
+
centroids
|
|
193
|
+
};
|
|
194
|
+
all[phrase] = entry;
|
|
195
|
+
saveAll(all);
|
|
196
|
+
return { n: embs.length, conditions: centroids.length, voiceId };
|
|
197
|
+
},
|
|
198
|
+
/** Verify a live utterance against the BEST condition-centroid across ALL enrolled voices. */
|
|
199
|
+
verify(phrase, samples, sampleRate) {
|
|
200
|
+
const cents = allCentroids(phrase);
|
|
201
|
+
if (!cents.length) return { score: 0, znorm: null, pass: false, enrolled: false };
|
|
202
|
+
const live = computeEmbedding(samples, sampleRate);
|
|
203
|
+
let score = -1;
|
|
204
|
+
for (const c of cents) {
|
|
205
|
+
const s = cosine(live, c);
|
|
206
|
+
if (s > score) score = s;
|
|
207
|
+
}
|
|
208
|
+
let znorm = null;
|
|
209
|
+
if (cohort.length) {
|
|
210
|
+
let sum = 0, sum2 = 0;
|
|
211
|
+
for (const cv of cohort) {
|
|
212
|
+
const s = cosine(live, cv);
|
|
213
|
+
sum += s;
|
|
214
|
+
sum2 += s * s;
|
|
215
|
+
}
|
|
216
|
+
const m = cohort.length, mean = sum / m, std = Math.sqrt(Math.max(sum2 / m - mean * mean, 1e-9));
|
|
217
|
+
znorm = (score - mean) / std;
|
|
218
|
+
}
|
|
219
|
+
const pass = SpeakerVerify.useAsnorm && znorm !== null ? znorm >= SpeakerVerify.znormThreshold : score >= SpeakerVerify.threshold;
|
|
220
|
+
return { score, znorm, pass, enrolled: true };
|
|
221
|
+
},
|
|
222
|
+
/** How many conditions are enrolled for a phrase, across ALL voices (0 = none). */
|
|
223
|
+
conditionCount: (phrase) => allCentroids(phrase).length,
|
|
224
|
+
/** TitaNet speaker embedding for a raw utterance (Float32 + true sample rate). For diarization /
|
|
225
|
+
* clustering. Returns the L2-normalized embedding, or null if the runtime isn't ready. */
|
|
226
|
+
embed(samples, sampleRate) {
|
|
227
|
+
return handle ? computeEmbedding(samples, sampleRate) : null;
|
|
228
|
+
},
|
|
229
|
+
/** Best-matching ENROLLED voice for a live utterance, across all voices + phrases (text-independent,
|
|
230
|
+
* so conversational audio works). Returns { voiceId, label, score } (max cosine) or null if nothing
|
|
231
|
+
* enrolled / not ready. The caller applies a threshold to decide whether to trust the name. */
|
|
232
|
+
identify(samples, sampleRate) {
|
|
233
|
+
if (!handle) return null;
|
|
234
|
+
const live = computeEmbedding(samples, sampleRate);
|
|
235
|
+
const all = loadAll();
|
|
236
|
+
const best = {};
|
|
237
|
+
for (const phrase in all) {
|
|
238
|
+
const voices = normPhrase(all[phrase]).voices;
|
|
239
|
+
for (const id in voices) {
|
|
240
|
+
for (const c of voices[id].centroids || []) {
|
|
241
|
+
const s = cosine(live, Float32Array.from(c));
|
|
242
|
+
if (!best[id] || s > best[id].score) best[id] = { label: voices[id].label, score: s };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
let top = null;
|
|
247
|
+
for (const id in best) {
|
|
248
|
+
if (!top || best[id].score > top.score) top = { voiceId: id, label: best[id].label, score: best[id].score };
|
|
249
|
+
}
|
|
250
|
+
return top;
|
|
251
|
+
},
|
|
252
|
+
/** List enrolled voices aggregated across phrases: [{ id, label, createdAt, conditions }]. */
|
|
253
|
+
listVoices() {
|
|
254
|
+
const all = loadAll();
|
|
255
|
+
const acc = {};
|
|
256
|
+
for (const phrase in all) {
|
|
257
|
+
const voices = normPhrase(all[phrase]).voices;
|
|
258
|
+
for (const id in voices) {
|
|
259
|
+
const v = voices[id];
|
|
260
|
+
const conds = (v.centroids || []).length;
|
|
261
|
+
if (!acc[id]) acc[id] = { id, label: v.label || id, createdAt: v.createdAt || 0, conditions: conds };
|
|
262
|
+
else {
|
|
263
|
+
acc[id].conditions = Math.max(acc[id].conditions, conds);
|
|
264
|
+
if (v.label) acc[id].label = v.label;
|
|
265
|
+
if (v.createdAt && (!acc[id].createdAt || v.createdAt < acc[id].createdAt)) acc[id].createdAt = v.createdAt;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return Object.keys(acc).map((k) => acc[k]).sort((a, b) => a.createdAt - b.createdAt);
|
|
270
|
+
},
|
|
271
|
+
/** Remove a voice across all phrases — revokes that person's WHO match (the WHAT phrase-prints,
|
|
272
|
+
* which are shared/speaker-independent, are left in voiceprintStore). */
|
|
273
|
+
removeVoice(voiceId) {
|
|
274
|
+
const all = loadAll();
|
|
275
|
+
let changed = false;
|
|
276
|
+
for (const phrase in all) {
|
|
277
|
+
const entry = normPhrase(all[phrase]);
|
|
278
|
+
if (entry.voices[voiceId]) {
|
|
279
|
+
delete entry.voices[voiceId];
|
|
280
|
+
all[phrase] = entry;
|
|
281
|
+
changed = true;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (changed) saveAll(all);
|
|
285
|
+
},
|
|
286
|
+
/** Rename a voice across all phrases. */
|
|
287
|
+
renameVoice(voiceId, label) {
|
|
288
|
+
const all = loadAll();
|
|
289
|
+
let changed = false;
|
|
290
|
+
for (const phrase in all) {
|
|
291
|
+
const entry = normPhrase(all[phrase]);
|
|
292
|
+
if (entry.voices[voiceId]) {
|
|
293
|
+
entry.voices[voiceId].label = label;
|
|
294
|
+
all[phrase] = entry;
|
|
295
|
+
changed = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (changed) saveAll(all);
|
|
299
|
+
},
|
|
300
|
+
// 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
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
async function init() {
|
|
314
|
+
const prevModule = window.Module;
|
|
315
|
+
try {
|
|
316
|
+
window.Module = { locateFile: (path) => SV_DIR + "/" + path };
|
|
317
|
+
const onRuntime = new Promise((res) => {
|
|
318
|
+
window.Module.onRuntimeInitialized = res;
|
|
319
|
+
});
|
|
320
|
+
await loadScript(SV_DIR + "/sherpa-onnx-speaker-diarization.js");
|
|
321
|
+
await loadScript(SV_DIR + "/sherpa-onnx-wasm-main-speaker-diarization.js");
|
|
322
|
+
await onRuntime;
|
|
323
|
+
Module = window.Module;
|
|
324
|
+
window.Module = prevModule;
|
|
325
|
+
const cfg = initSherpaOnnxSpeakerEmbeddingExtractorConfig(
|
|
326
|
+
{ model: MODEL, numThreads: 1, debug: 0, provider: "cpu" },
|
|
327
|
+
Module
|
|
328
|
+
);
|
|
329
|
+
handle = Module._SherpaOnnxCreateSpeakerEmbeddingExtractor(cfg.ptr);
|
|
330
|
+
Module._free(cfg.buffer);
|
|
331
|
+
Module._free(cfg.ptr);
|
|
332
|
+
if (!handle) throw new Error("CreateSpeakerEmbeddingExtractor returned null");
|
|
333
|
+
dim = Module._SherpaOnnxSpeakerEmbeddingExtractorDim(handle);
|
|
334
|
+
console.log("[SpeakerVerify] ready \u2014 embedding dim", dim);
|
|
335
|
+
try {
|
|
336
|
+
const r = await fetch(SV_DIR + "/sv-cohort.json");
|
|
337
|
+
if (r.ok) {
|
|
338
|
+
cohort = (await r.json()).map((v) => Float32Array.from(v));
|
|
339
|
+
console.log("[SpeakerVerify] AS-norm cohort:", cohort.length);
|
|
340
|
+
}
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.warn("[SpeakerVerify] cohort load failed (AS-norm off):", e);
|
|
343
|
+
}
|
|
344
|
+
await hydrateStore();
|
|
345
|
+
readyResolve(SpeakerVerify);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
window.Module = prevModule;
|
|
348
|
+
console.error("[SpeakerVerify] init failed:", e);
|
|
349
|
+
readyReject(e);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
window.SpeakerVerify = SpeakerVerify;
|
|
353
|
+
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
|
|
354
|
+
else init();
|
|
355
|
+
})();
|
|
356
|
+
//# sourceMappingURL=speaker-verify-5GTWAN5Y.cjs.map
|
|
357
|
+
//# sourceMappingURL=speaker-verify-5GTWAN5Y.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AI/HeyOzwell/SpeakerVerify/lib/speaker-verify.js"],"names":[],"mappings":"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","file":"speaker-verify-5GTWAN5Y.cjs","sourcesContent":["/**\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 LS_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 // --- WHO-centroid persistence on IndexedDB (replaces localStorage; same db/store as voiceprintStore.ts).\n // verify()/enroll() read these synchronously, so we keep an in-memory `_store` hydrated from IndexedDB on\n // init (before ready() resolves) and write through on save. ---\n const IDB_DB = \"ozwell-voice\", IDB_STORE = \"voiceprints\";\n function idbOpen() {\n return new Promise((resolve, reject) => {\n const req = indexedDB.open(IDB_DB, 1);\n req.onupgradeneeded = () => { if (!req.result.objectStoreNames.contains(IDB_STORE)) req.result.createObjectStore(IDB_STORE); };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n }\n function idbReq(mode, run) {\n return idbOpen().then((db) => new Promise((resolve, reject) => {\n const tx = db.transaction(IDB_STORE, mode);\n const r = run(tx.objectStore(IDB_STORE));\n tx.oncomplete = () => { resolve(r && r.result); db.close(); };\n tx.onerror = () => { reject(tx.error); db.close(); };\n tx.onabort = () => { reject(tx.error); db.close(); }; // else an aborted tx leaves the promise pending\n }));\n }\n const idbGet = (key) => idbReq(\"readonly\", (s) => s.get(key));\n const idbPut = (key, v) => idbReq(\"readwrite\", (s) => s.put(v, key));\n const idbDel = (key) => idbReq(\"readwrite\", (s) => s.delete(key));\n\n let _store = null; // in-memory WHO map; hydrated from IndexedDB before ready() resolves\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() {\n try {\n let v = await idbGet(LS_KEY);\n if (v === undefined) { // nothing in IndexedDB — migrate a legacy localStorage value once, then drop it\n let raw = null; try { raw = localStorage.getItem(LS_KEY); } catch (e) { /* ignore */ }\n if (raw != null) {\n v = normalizeWho(JSON.parse(raw || \"{}\"));\n await idbPut(LS_KEY, v);\n try { localStorage.removeItem(LS_KEY); } catch (e) { /* ignore */ }\n }\n }\n _store = v || {};\n } catch (e) { _store = _store || {}; }\n }\n function loadAll() {\n if (_store) return _store;\n // Pre-hydration fallback (verify/enroll only run after ready(), which awaits hydrateStore).\n try { return normalizeWho(JSON.parse(localStorage.getItem(LS_KEY) || \"{}\")); } catch (e) { return {}; }\n }\n function saveAll(obj) {\n _store = obj;\n idbPut(LS_KEY, obj).catch((e) => console.warn(\"SV save failed\", e));\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) {\n const voices = normPhrase(loadAll()[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 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) {\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();\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);\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) {\n const cents = allCentroids(phrase);\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) => allCentroids(phrase).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) {\n if (!handle) return null;\n const live = computeEmbedding(samples, sampleRate);\n const all = loadAll();\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() {\n const all = loadAll();\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) {\n const all = loadAll();\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);\n },\n\n /** Rename a voice across all phrases. */\n renameVoice(voiceId, label) {\n const all = loadAll();\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);\n },\n\n // hasEnrollment(phrase) -> is that phrase enrolled; hasEnrollment() -> is anything enrolled.\n hasEnrollment: (phrase) => phrase ? allCentroids(phrase).length > 0 : Object.keys(loadAll()).some((p) => allCentroids(p).length > 0),\n enrolledPhrases: () => Object.keys(loadAll()),\n clearEnrollment: () => { _store = {}; idbDel(LS_KEY).catch(() => {}); try { localStorage.removeItem(LS_KEY); } catch (e) { /* ignore */ } },\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(); // load enrolled WHO centroids from IndexedDB before reporting ready\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"]}
|