@timur00kh/whisper.wasm 0.0.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/LICENSE +20 -0
- package/README.md +251 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +576 -0
- package/dist/index.umd.js +13 -0
- package/dist/libmain-D50HCaHR.js +13 -0
- package/dist/libmain-DyRJqz-4.mjs +2198 -0
- package/dist/utils/Logger.d.ts +19 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/sleep.d.ts +2 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/whisper/ModelConfig.d.ts +18 -0
- package/dist/whisper/ModelConfig.d.ts.map +1 -0
- package/dist/whisper/ModelManager.d.ts +76 -0
- package/dist/whisper/ModelManager.d.ts.map +1 -0
- package/dist/whisper/TranscriptionSession.d.ts +17 -0
- package/dist/whisper/TranscriptionSession.d.ts.map +1 -0
- package/dist/whisper/WhisperWasmService.d.ts +34 -0
- package/dist/whisper/WhisperWasmService.d.ts.map +1 -0
- package/dist/whisper/parseCueLine.d.ts +13 -0
- package/dist/whisper/parseCueLine.d.ts.map +1 -0
- package/dist/whisper/types.d.ts +31 -0
- package/dist/whisper/types.d.ts.map +1 -0
- package/package.json +99 -0
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
const p = class p {
|
|
2
|
+
constructor(e = p.levels.INFO, t = "") {
|
|
3
|
+
this.level = e, this.prefix = t;
|
|
4
|
+
}
|
|
5
|
+
debug(...e) {
|
|
6
|
+
this.level <= p.levels.DEBUG && console.debug(`[${this.prefix}] [DEBUG]`, ...e);
|
|
7
|
+
}
|
|
8
|
+
info(...e) {
|
|
9
|
+
this.level <= p.levels.INFO && console.info(`[${this.prefix}] [INFO]`, ...e);
|
|
10
|
+
}
|
|
11
|
+
warn(...e) {
|
|
12
|
+
this.level <= p.levels.WARN && console.warn(`[${this.prefix}] [WARN]`, ...e);
|
|
13
|
+
}
|
|
14
|
+
error(...e) {
|
|
15
|
+
this.level <= p.levels.ERROR && console.error(`[${this.prefix}] [ERROR]`, ...e);
|
|
16
|
+
}
|
|
17
|
+
setLevel(e) {
|
|
18
|
+
this.level = e;
|
|
19
|
+
}
|
|
20
|
+
getLevel() {
|
|
21
|
+
return this.level;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
p.levels = {
|
|
25
|
+
DEBUG: 0,
|
|
26
|
+
INFO: 1,
|
|
27
|
+
WARN: 2,
|
|
28
|
+
ERROR: 3
|
|
29
|
+
};
|
|
30
|
+
let b = p;
|
|
31
|
+
const _ = async () => WebAssembly.validate(new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10, 1, 8, 0, 65, 0, 253, 15, 253, 98, 11])), B = {
|
|
32
|
+
language: "auto",
|
|
33
|
+
threads: 4,
|
|
34
|
+
translate: !1
|
|
35
|
+
};
|
|
36
|
+
function M(l) {
|
|
37
|
+
const e = String(l).trim().replace(",", "."), t = e.split(":").map(Number);
|
|
38
|
+
if (t.some(Number.isNaN)) throw new Error(`Bad time: "${l}"`);
|
|
39
|
+
let r = 0, a = 0, i = 0;
|
|
40
|
+
if (t.length === 3)
|
|
41
|
+
[r, a] = t, i = parseFloat(e.split(":").pop() || "0");
|
|
42
|
+
else if (t.length === 2)
|
|
43
|
+
[a] = t, i = parseFloat(e.split(":").pop() || "0");
|
|
44
|
+
else
|
|
45
|
+
throw new Error(`Bad time format: "${l}"`);
|
|
46
|
+
return Math.floor(((r * 60 + a) * 60 + i) * 1e3);
|
|
47
|
+
}
|
|
48
|
+
function T(l) {
|
|
49
|
+
const t = /^\s*\[?\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*-->\s*([0-9]{1,2}:[0-9]{2}:(?:[0-9]{2}[.,][0-9]{1,3})|[0-9]{1,2}:[0-9]{2}[.,][0-9]{1,3})\s*\]?\s*(.*)\s*$/.exec(l);
|
|
50
|
+
if (!t)
|
|
51
|
+
throw new Error("Line does not match VTT-like pattern: " + l);
|
|
52
|
+
const r = t[1], a = t[2], i = t[3] || "", s = M(r), n = M(a);
|
|
53
|
+
if (n < s)
|
|
54
|
+
throw new Error("End time is before start time");
|
|
55
|
+
return {
|
|
56
|
+
startMs: s,
|
|
57
|
+
endMs: n,
|
|
58
|
+
start: r,
|
|
59
|
+
end: a,
|
|
60
|
+
text: i
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function q(l) {
|
|
64
|
+
return new Promise((e) => setTimeout(e, l));
|
|
65
|
+
}
|
|
66
|
+
function R(l, e = 16e3 * 100) {
|
|
67
|
+
const t = [];
|
|
68
|
+
for (let r = 0; r < l.length; r += e)
|
|
69
|
+
t.push(l.subarray(r, r + e));
|
|
70
|
+
return t;
|
|
71
|
+
}
|
|
72
|
+
class x {
|
|
73
|
+
constructor(e, t) {
|
|
74
|
+
this.whisperService = e, this.logger = new b((t == null ? void 0 : t.logLevel) || b.levels.ERROR, "TranscriptionSession");
|
|
75
|
+
}
|
|
76
|
+
async *streamimg(e, t = {}) {
|
|
77
|
+
const r = R(e);
|
|
78
|
+
let a = 0;
|
|
79
|
+
for await (const i of r) {
|
|
80
|
+
const s = [];
|
|
81
|
+
let n = null, o = !1, h, d = 0;
|
|
82
|
+
for (this.whisperService.transcribe(
|
|
83
|
+
i,
|
|
84
|
+
(c) => {
|
|
85
|
+
d = c.timeEnd, c.timeStart += a, c.timeEnd += a, n ? (n(c), n = null) : s.push(c);
|
|
86
|
+
},
|
|
87
|
+
t
|
|
88
|
+
).then(() => {
|
|
89
|
+
o = !0, a += d, n == null || n(void 0);
|
|
90
|
+
}).catch((c) => {
|
|
91
|
+
h = c;
|
|
92
|
+
}); ; ) {
|
|
93
|
+
if (h) throw h;
|
|
94
|
+
if (o) break;
|
|
95
|
+
if (s.length)
|
|
96
|
+
yield s.shift();
|
|
97
|
+
else {
|
|
98
|
+
const c = await new Promise((f) => n = f);
|
|
99
|
+
c && (yield c);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
t.sleepMsBetweenChunks && await q(t.sleepMsBetweenChunks);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
class L extends EventTarget {
|
|
107
|
+
on(e, t) {
|
|
108
|
+
return this.addEventListener(e, t), () => this.removeEventListener(e, t);
|
|
109
|
+
}
|
|
110
|
+
emit(e, t) {
|
|
111
|
+
this.dispatchEvent(new CustomEvent(e, { detail: t }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
class A {
|
|
115
|
+
constructor(e) {
|
|
116
|
+
this.wasmModule = null, this.instance = null, this.modelFileName = "whisper.bin", this.isTranscribing = !1, this.bus = new L(), this.logger = new b((e == null ? void 0 : e.logLevel) ?? b.levels.ERROR, "WhisperWasmService"), e != null && e.init && this.loadWasmScript();
|
|
117
|
+
}
|
|
118
|
+
async checkWasmSupport() {
|
|
119
|
+
return await _();
|
|
120
|
+
}
|
|
121
|
+
async loadWasmScript() {
|
|
122
|
+
this.wasmModule = await (await import("./libmain-DyRJqz-4.mjs")).default({
|
|
123
|
+
print: (e, ...t) => {
|
|
124
|
+
this.logger.debug(t), e.startsWith("[") ? (this.logger.info(e), this.bus.emit("transcribe", e)) : (this.logger.debug(e), this.bus.emit("system_info", e));
|
|
125
|
+
},
|
|
126
|
+
printErr: (e, ...t) => {
|
|
127
|
+
this.logger.debug(t), this.logger.warn(e), this.bus.emit("transcribeError", e);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
async loadWasmModule(e) {
|
|
132
|
+
if (!await this.checkWasmSupport())
|
|
133
|
+
throw new Error("WASM is not supported");
|
|
134
|
+
return this.wasmModule && (this.wasmModule.FS_unlink(this.modelFileName), this.wasmModule.free()), await this.loadWasmScript(), await q(100), this.storeFS(this.modelFileName, e), this.instance = this.wasmModule.init(this.modelFileName), Promise.resolve();
|
|
135
|
+
}
|
|
136
|
+
storeFS(e, t) {
|
|
137
|
+
if (!this.wasmModule)
|
|
138
|
+
throw new Error("WASM module not loaded");
|
|
139
|
+
try {
|
|
140
|
+
this.wasmModule.FS_unlink(e);
|
|
141
|
+
} catch {
|
|
142
|
+
}
|
|
143
|
+
this.wasmModule.FS_createDataFile("/", e, t, !0, !0, !0);
|
|
144
|
+
}
|
|
145
|
+
async transcribe(e, t, r = {}) {
|
|
146
|
+
if (this.isTranscribing)
|
|
147
|
+
throw new Error("Already transcribing");
|
|
148
|
+
if (!this.wasmModule)
|
|
149
|
+
throw new Error("WASM module not loaded");
|
|
150
|
+
if (!this.instance)
|
|
151
|
+
throw new Error("WASM instance not loaded");
|
|
152
|
+
const a = 120;
|
|
153
|
+
e.length > 16e3 * a && this.logger.warn(
|
|
154
|
+
"It's not recommended to transcribe audio data that is longer than 120 seconds"
|
|
155
|
+
), this.isTranscribing = !0;
|
|
156
|
+
const { language: i, threads: s, translate: n } = {
|
|
157
|
+
...B,
|
|
158
|
+
...r
|
|
159
|
+
}, o = [], h = Date.now();
|
|
160
|
+
return this.wasmModule.full_default(this.instance, e, i, s, n), await new Promise((d, c) => {
|
|
161
|
+
const f = this.bus.on("transcribe", (g) => {
|
|
162
|
+
const { startMs: w, endMs: y, text: z } = T(g.detail), v = {
|
|
163
|
+
timeStart: w,
|
|
164
|
+
timeEnd: y,
|
|
165
|
+
text: z,
|
|
166
|
+
raw: g.detail
|
|
167
|
+
};
|
|
168
|
+
o.push(v), t == null || t(v);
|
|
169
|
+
}), u = setTimeout(
|
|
170
|
+
() => {
|
|
171
|
+
this.isTranscribing = !1, f(), m(), this.logger.error("Transcribe timeout"), c(new Error("Transcribe timeout"));
|
|
172
|
+
},
|
|
173
|
+
a * 2 * 1e3
|
|
174
|
+
), m = this.bus.on("transcribeError", (g) => {
|
|
175
|
+
this.isTranscribing = !1, f(), m(), clearTimeout(u), this.logger.debug("Transcribe error", g.detail), d({ segments: o, transcribeDurationMs: Date.now() - h });
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
createSession() {
|
|
180
|
+
return new x(this, { logLevel: this.logger.getLevel() });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const S = {
|
|
184
|
+
"tiny.en": {
|
|
185
|
+
id: "tiny.en",
|
|
186
|
+
name: "Tiny English",
|
|
187
|
+
size: 75,
|
|
188
|
+
language: "en",
|
|
189
|
+
quantized: !1,
|
|
190
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin"
|
|
191
|
+
},
|
|
192
|
+
tiny: {
|
|
193
|
+
id: "tiny",
|
|
194
|
+
name: "Tiny Multilingual",
|
|
195
|
+
size: 75,
|
|
196
|
+
language: "multilingual",
|
|
197
|
+
quantized: !1,
|
|
198
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin"
|
|
199
|
+
},
|
|
200
|
+
"base.en": {
|
|
201
|
+
id: "base.en",
|
|
202
|
+
name: "Base English",
|
|
203
|
+
size: 142,
|
|
204
|
+
language: "en",
|
|
205
|
+
quantized: !1,
|
|
206
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin"
|
|
207
|
+
},
|
|
208
|
+
base: {
|
|
209
|
+
id: "base",
|
|
210
|
+
name: "Base Multilingual",
|
|
211
|
+
size: 142,
|
|
212
|
+
language: "multilingual",
|
|
213
|
+
quantized: !1,
|
|
214
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin"
|
|
215
|
+
},
|
|
216
|
+
"small.en": {
|
|
217
|
+
id: "small.en",
|
|
218
|
+
name: "Small English",
|
|
219
|
+
size: 466,
|
|
220
|
+
language: "en",
|
|
221
|
+
quantized: !1,
|
|
222
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin"
|
|
223
|
+
},
|
|
224
|
+
small: {
|
|
225
|
+
id: "small",
|
|
226
|
+
name: "Small Multilingual",
|
|
227
|
+
size: 466,
|
|
228
|
+
language: "multilingual",
|
|
229
|
+
quantized: !1,
|
|
230
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin"
|
|
231
|
+
},
|
|
232
|
+
"tiny.en-q5_1": {
|
|
233
|
+
id: "tiny.en-q5_1",
|
|
234
|
+
name: "Tiny English (Q5_1)",
|
|
235
|
+
size: 31,
|
|
236
|
+
language: "en",
|
|
237
|
+
quantized: !0,
|
|
238
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q5_1.bin"
|
|
239
|
+
},
|
|
240
|
+
"tiny-q5_1": {
|
|
241
|
+
id: "tiny-q5_1",
|
|
242
|
+
name: "Tiny Multilingual (Q5_1)",
|
|
243
|
+
size: 31,
|
|
244
|
+
language: "multilingual",
|
|
245
|
+
quantized: !0,
|
|
246
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin"
|
|
247
|
+
},
|
|
248
|
+
"base.en-q5_1": {
|
|
249
|
+
id: "base.en-q5_1",
|
|
250
|
+
name: "Base English (Q5_1)",
|
|
251
|
+
size: 57,
|
|
252
|
+
language: "en",
|
|
253
|
+
quantized: !0,
|
|
254
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q5_1.bin"
|
|
255
|
+
},
|
|
256
|
+
"base-q5_1": {
|
|
257
|
+
id: "base-q5_1",
|
|
258
|
+
name: "Base Multilingual (Q5_1)",
|
|
259
|
+
size: 57,
|
|
260
|
+
language: "multilingual",
|
|
261
|
+
quantized: !0,
|
|
262
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin"
|
|
263
|
+
},
|
|
264
|
+
"small.en-q5_1": {
|
|
265
|
+
id: "small.en-q5_1",
|
|
266
|
+
name: "Small English (Q5_1)",
|
|
267
|
+
size: 182,
|
|
268
|
+
language: "en",
|
|
269
|
+
quantized: !0,
|
|
270
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en-q5_1.bin"
|
|
271
|
+
},
|
|
272
|
+
"small-q5_1": {
|
|
273
|
+
id: "small-q5_1",
|
|
274
|
+
name: "Small Multilingual (Q5_1)",
|
|
275
|
+
size: 182,
|
|
276
|
+
language: "multilingual",
|
|
277
|
+
quantized: !0,
|
|
278
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q5_1.bin"
|
|
279
|
+
},
|
|
280
|
+
"medium.en-q5_0": {
|
|
281
|
+
id: "medium.en-q5_0",
|
|
282
|
+
name: "Medium English (Q5_0)",
|
|
283
|
+
size: 515,
|
|
284
|
+
language: "en",
|
|
285
|
+
quantized: !0,
|
|
286
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en-q5_0.bin"
|
|
287
|
+
},
|
|
288
|
+
"medium-q5_0": {
|
|
289
|
+
id: "medium-q5_0",
|
|
290
|
+
name: "Medium Multilingual (Q5_0)",
|
|
291
|
+
size: 515,
|
|
292
|
+
language: "multilingual",
|
|
293
|
+
quantized: !0,
|
|
294
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q5_0.bin"
|
|
295
|
+
},
|
|
296
|
+
"large-q5_0": {
|
|
297
|
+
id: "large-q5_0",
|
|
298
|
+
name: "Large Multilingual (Q5_0)",
|
|
299
|
+
size: 1030,
|
|
300
|
+
language: "multilingual",
|
|
301
|
+
quantized: !0,
|
|
302
|
+
url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-q5_0.bin"
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
function W() {
|
|
306
|
+
return Object.values(S).map(({ url: l, ...e }) => e);
|
|
307
|
+
}
|
|
308
|
+
function E(l) {
|
|
309
|
+
return S[l];
|
|
310
|
+
}
|
|
311
|
+
class F {
|
|
312
|
+
constructor(e = { logLevel: b.levels.ERROR }) {
|
|
313
|
+
this.cacheEnabled = !0, this.models = W(), this.logger = new b(e.logLevel, "ModelManager");
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Loads model by name
|
|
317
|
+
*/
|
|
318
|
+
async loadModel(e, t = !0, r) {
|
|
319
|
+
var m;
|
|
320
|
+
const a = E(e);
|
|
321
|
+
if (!a)
|
|
322
|
+
throw new Error(`Model ${e} not found in config`);
|
|
323
|
+
if (this.cacheEnabled && t) {
|
|
324
|
+
const g = await this.getCachedModel(e);
|
|
325
|
+
if (g)
|
|
326
|
+
return this.logger.info(`Model ${e} loaded from cache`), r && r(100), g;
|
|
327
|
+
}
|
|
328
|
+
this.logger.info(`Loading model ${e} from ${a.url}`);
|
|
329
|
+
const i = await fetch(a.url);
|
|
330
|
+
if (!i.ok)
|
|
331
|
+
throw new Error(`Failed to load model: ${i.statusText}`);
|
|
332
|
+
const s = i.headers.get("content-length"), n = s ? parseInt(s, 10) : 0;
|
|
333
|
+
let o = 0;
|
|
334
|
+
const h = (m = i.body) == null ? void 0 : m.getReader();
|
|
335
|
+
if (!h)
|
|
336
|
+
throw new Error("Response body is not readable");
|
|
337
|
+
const d = [];
|
|
338
|
+
try {
|
|
339
|
+
let g = !1;
|
|
340
|
+
for (; !g; ) {
|
|
341
|
+
const w = await h.read();
|
|
342
|
+
if (g = w.done, !g && w.value && (d.push(w.value), o += w.value.length, r && n > 0)) {
|
|
343
|
+
const y = Math.round(o / n * 100);
|
|
344
|
+
r(y);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
} finally {
|
|
348
|
+
h.releaseLock();
|
|
349
|
+
}
|
|
350
|
+
const c = d.reduce((g, w) => g + w.length, 0), f = new Uint8Array(c);
|
|
351
|
+
let u = 0;
|
|
352
|
+
for (const g of d)
|
|
353
|
+
f.set(g, u), u += g.length;
|
|
354
|
+
return this.cacheEnabled && t && await this.saveModelToCache(e, f), r && r(100), f;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Loads WASM model by URL and saves it to IndexedDB using the URL itself as key.
|
|
358
|
+
*/
|
|
359
|
+
async loadModelByUrl(e, t) {
|
|
360
|
+
var r;
|
|
361
|
+
try {
|
|
362
|
+
if (this.cacheEnabled) {
|
|
363
|
+
const u = await this.getCachedModelByUrl(e);
|
|
364
|
+
if (u)
|
|
365
|
+
return this.logger.info(`WASM module loaded from cache by URL: ${e}`), t && t(100), u;
|
|
366
|
+
}
|
|
367
|
+
this.logger.info(`Loading WASM module from URL: ${e}`);
|
|
368
|
+
const a = await fetch(e);
|
|
369
|
+
if (!a.ok)
|
|
370
|
+
throw new Error(`Failed to load WASM module: ${a.statusText}`);
|
|
371
|
+
const i = a.headers.get("content-length"), s = i ? parseInt(i, 10) : 0;
|
|
372
|
+
let n = 0;
|
|
373
|
+
const o = (r = a.body) == null ? void 0 : r.getReader();
|
|
374
|
+
if (!o)
|
|
375
|
+
throw new Error("Response body is not readable");
|
|
376
|
+
const h = [];
|
|
377
|
+
try {
|
|
378
|
+
let u = !1;
|
|
379
|
+
for (; !u; ) {
|
|
380
|
+
const m = await o.read();
|
|
381
|
+
if (u = m.done, !u && m.value && (h.push(m.value), n += m.value.length, t && s > 0)) {
|
|
382
|
+
const g = Math.round(n / s * 100);
|
|
383
|
+
t(g);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} finally {
|
|
387
|
+
o.releaseLock();
|
|
388
|
+
}
|
|
389
|
+
const d = h.reduce((u, m) => u + m.length, 0), c = new Uint8Array(d);
|
|
390
|
+
let f = 0;
|
|
391
|
+
for (const u of h)
|
|
392
|
+
c.set(u, f), f += u.length;
|
|
393
|
+
return this.cacheEnabled && await this.saveModelToCacheByUrl(e, c), t && t(100), c;
|
|
394
|
+
} catch (a) {
|
|
395
|
+
throw this.logger.error(a), new Error("Failed to load WASM module");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get model from IndexedDB by URL (key is the URL itself)
|
|
400
|
+
*/
|
|
401
|
+
async getCachedModelByUrl(e) {
|
|
402
|
+
try {
|
|
403
|
+
const a = (await this.openIndexedDB()).transaction(["modelsByUrl"], "readonly").objectStore("modelsByUrl");
|
|
404
|
+
return new Promise((i, s) => {
|
|
405
|
+
const n = a.get(e);
|
|
406
|
+
n.onsuccess = () => {
|
|
407
|
+
const o = n.result;
|
|
408
|
+
o && o.data ? i(o.data) : i(null);
|
|
409
|
+
}, n.onerror = () => s(n.error);
|
|
410
|
+
});
|
|
411
|
+
} catch (t) {
|
|
412
|
+
return this.logger.error("Error reading model from cache by URL:", t), null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Saves model to IndexedDB by URL (key is the URL itself)
|
|
417
|
+
*/
|
|
418
|
+
async saveModelToCacheByUrl(e, t) {
|
|
419
|
+
try {
|
|
420
|
+
const i = (await this.openIndexedDB()).transaction(["modelsByUrl"], "readwrite").objectStore("modelsByUrl");
|
|
421
|
+
await new Promise((s, n) => {
|
|
422
|
+
const o = i.put({
|
|
423
|
+
url: e,
|
|
424
|
+
data: t,
|
|
425
|
+
timestamp: Date.now(),
|
|
426
|
+
size: t.length
|
|
427
|
+
});
|
|
428
|
+
o.onsuccess = () => s(), o.onerror = () => n(o.error);
|
|
429
|
+
}), this.logger.info(`Model saved to cache by URL: ${e}`);
|
|
430
|
+
} catch (r) {
|
|
431
|
+
this.logger.error("Error saving model to cache by URL:", r);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Gets list of available models with cache information
|
|
436
|
+
*/
|
|
437
|
+
async getAvailableModels() {
|
|
438
|
+
const e = [...this.models];
|
|
439
|
+
if (!this.cacheEnabled)
|
|
440
|
+
return e;
|
|
441
|
+
try {
|
|
442
|
+
const t = await this.getCachedModelNames();
|
|
443
|
+
return e.map((r) => ({
|
|
444
|
+
...r,
|
|
445
|
+
cached: t.includes(r.id)
|
|
446
|
+
}));
|
|
447
|
+
} catch (t) {
|
|
448
|
+
return this.logger.error("Error checking cache status:", t), e;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Gets list of available models without cache check (synchronously)
|
|
453
|
+
*/
|
|
454
|
+
getAvailableModelsSync() {
|
|
455
|
+
return [...this.models];
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Gets model by name from config
|
|
459
|
+
*/
|
|
460
|
+
getModelConfig(e) {
|
|
461
|
+
return E(e);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Saves model to IndexedDB
|
|
465
|
+
*/
|
|
466
|
+
async saveModelToCache(e, t) {
|
|
467
|
+
try {
|
|
468
|
+
const i = (await this.openIndexedDB()).transaction(["models"], "readwrite").objectStore("models");
|
|
469
|
+
await new Promise((s, n) => {
|
|
470
|
+
const o = i.put({
|
|
471
|
+
name: e,
|
|
472
|
+
data: t,
|
|
473
|
+
timestamp: Date.now(),
|
|
474
|
+
size: t.length
|
|
475
|
+
});
|
|
476
|
+
o.onsuccess = () => s(), o.onerror = () => n(o.error);
|
|
477
|
+
}), this.logger.info(`Model ${e} saved to cache`);
|
|
478
|
+
} catch (r) {
|
|
479
|
+
this.logger.error("Error saving model to cache:", r);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Gets model from IndexedDB cache
|
|
484
|
+
*/
|
|
485
|
+
async getCachedModel(e) {
|
|
486
|
+
try {
|
|
487
|
+
const a = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
488
|
+
return new Promise((i, s) => {
|
|
489
|
+
const n = a.get(e);
|
|
490
|
+
n.onsuccess = () => {
|
|
491
|
+
const o = n.result;
|
|
492
|
+
o && o.data ? i(o.data) : i(null);
|
|
493
|
+
}, n.onerror = () => s(n.error);
|
|
494
|
+
});
|
|
495
|
+
} catch (t) {
|
|
496
|
+
return this.logger.error("Error getting cached model:", t), null;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Gets list of model names loaded in cache
|
|
501
|
+
*/
|
|
502
|
+
async getCachedModelNames() {
|
|
503
|
+
try {
|
|
504
|
+
const r = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
505
|
+
return new Promise((a, i) => {
|
|
506
|
+
const s = r.getAllKeys();
|
|
507
|
+
s.onsuccess = () => {
|
|
508
|
+
const n = s.result;
|
|
509
|
+
a(n);
|
|
510
|
+
}, s.onerror = () => i(s.error);
|
|
511
|
+
});
|
|
512
|
+
} catch (e) {
|
|
513
|
+
return this.logger.error("Error getting cached model names:", e), [];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Opens IndexedDB for model caching
|
|
518
|
+
*/
|
|
519
|
+
async openIndexedDB() {
|
|
520
|
+
return new Promise((e, t) => {
|
|
521
|
+
const r = indexedDB.open("WhisperModels", 2);
|
|
522
|
+
r.onerror = () => t(r.error), r.onsuccess = () => e(r.result), r.onupgradeneeded = (a) => {
|
|
523
|
+
const i = a.target.result;
|
|
524
|
+
if (!i.objectStoreNames.contains("models")) {
|
|
525
|
+
const s = i.createObjectStore("models", { keyPath: "name" });
|
|
526
|
+
s.createIndex("timestamp", "timestamp", { unique: !1 }), s.createIndex("size", "size", { unique: !1 });
|
|
527
|
+
}
|
|
528
|
+
if (!i.objectStoreNames.contains("modelsByUrl")) {
|
|
529
|
+
const s = i.createObjectStore("modelsByUrl", { keyPath: "url" });
|
|
530
|
+
s.createIndex("timestamp", "timestamp", { unique: !1 }), s.createIndex("size", "size", { unique: !1 });
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Clears model cache
|
|
537
|
+
*/
|
|
538
|
+
async clearCache() {
|
|
539
|
+
try {
|
|
540
|
+
const t = (await this.openIndexedDB()).transaction(["models", "modelsByUrl"], "readwrite"), r = t.objectStore("models");
|
|
541
|
+
await new Promise((i, s) => {
|
|
542
|
+
const n = r.clear();
|
|
543
|
+
n.onsuccess = () => i(), n.onerror = () => s(n.error);
|
|
544
|
+
});
|
|
545
|
+
const a = t.objectStore("modelsByUrl");
|
|
546
|
+
await new Promise((i, s) => {
|
|
547
|
+
const n = a.clear();
|
|
548
|
+
n.onsuccess = () => i(), n.onerror = () => s(n.error);
|
|
549
|
+
}), this.logger.info("Model cache cleared");
|
|
550
|
+
} catch (e) {
|
|
551
|
+
this.logger.error("Error clearing cache:", e);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Gets cache information
|
|
556
|
+
*/
|
|
557
|
+
async getCacheInfo() {
|
|
558
|
+
try {
|
|
559
|
+
const r = (await this.openIndexedDB()).transaction(["models"], "readonly").objectStore("models");
|
|
560
|
+
return new Promise((a, i) => {
|
|
561
|
+
const s = r.getAll();
|
|
562
|
+
s.onsuccess = () => {
|
|
563
|
+
const n = s.result, o = n.reduce((h, d) => h + (d.size || 0), 0);
|
|
564
|
+
a({ count: n.length, totalSize: o });
|
|
565
|
+
}, s.onerror = () => i(s.error);
|
|
566
|
+
});
|
|
567
|
+
} catch (e) {
|
|
568
|
+
return this.logger.error("Error getting cache info:", e), { count: 0, totalSize: 0 };
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
export {
|
|
573
|
+
F as ModelManager,
|
|
574
|
+
A as WhisperWasmService,
|
|
575
|
+
W as getAllModels
|
|
576
|
+
};
|