Skip to content

Commit a91ea64

Browse files
authored
Merge pull request #231 from scribear/fix/whisper-multilang-output
Fix/whisper multilang output
2 parents 2924c72 + b82f2f9 commit a91ea64

File tree

11 files changed

+1213
-943
lines changed

11 files changed

+1213
-943
lines changed
-57 MB
Binary file not shown.
-30.7 MB
Binary file not shown.

src/components/api/returnAPI.tsx

Lines changed: 250 additions & 178 deletions
Large diffs are not rendered by default.
Lines changed: 124 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,133 @@
1-
// HACK: moving global variables from index.html to here for loadRemote
2-
3-
let dbVersion = 1
4-
let dbName = 'whisper.ggerganov.com';
5-
let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
6-
7-
// fetch a remote file from remote URL using the Fetch API
8-
async function fetchRemote(url, cbProgress, cbPrint) {
9-
cbPrint('fetchRemote: downloading with fetch()...');
10-
11-
const response = await fetch(
12-
url,
13-
{
14-
method: 'GET',
15-
}
16-
);
17-
18-
if (!response.ok) {
19-
cbPrint('fetchRemote: failed to fetch ' + url);
20-
return;
21-
}
22-
23-
const contentLength = response.headers.get('content-length');
24-
const total = parseInt(contentLength, 10);
25-
const reader = response.body.getReader();
26-
27-
var chunks = [];
28-
var receivedLength = 0;
29-
var progressLast = -1;
30-
31-
while (true) {
32-
const { done, value } = await reader.read();
33-
34-
if (done) {
35-
break;
36-
}
37-
38-
chunks.push(value);
39-
receivedLength += value.length;
40-
41-
if (contentLength) {
42-
cbProgress(receivedLength/total);
43-
44-
var progressCur = Math.round((receivedLength / total) * 10);
45-
if (progressCur != progressLast) {
46-
cbPrint('fetchRemote: fetching ' + 10*progressCur + '% ...');
47-
progressLast = progressCur;
48-
}
49-
}
50-
}
1+
// src/components/api/whisper/indexedDB.js
2+
// Silent IndexedDB caching loader with multi-URL fallbacks.
3+
4+
var dbVersion = 1;
5+
var dbName = 'whisper.ggerganov.com';
6+
var indexedDB =
7+
(window.indexedDB ||
8+
window.mozIndexedDB ||
9+
window.webkitIndexedDB ||
10+
window.msIndexedDB);
11+
12+
// fetch a URL as a Uint8Array (progress callback optional)
13+
async function fetchBinary(url, cbProgress, cbPrint) {
14+
cbPrint && cbPrint('fetchBinary: GET ' + url);
15+
16+
const res = await fetch(url, { method: 'GET', cache: 'no-cache' });
17+
if (!res.ok) throw new Error('http-' + res.status);
18+
19+
// No stream? Just read all at once.
20+
if (!res.body || !res.body.getReader) {
21+
const buf = new Uint8Array(await res.arrayBuffer());
22+
cbProgress && cbProgress(1);
23+
return buf;
24+
}
25+
26+
const total = parseInt(res.headers.get('content-length') || '0', 10) || 0;
27+
const reader = res.body.getReader();
28+
29+
let received = 0;
30+
const chunks = [];
31+
while (true) {
32+
const { done, value } = await reader.read();
33+
if (done) break;
34+
chunks.push(value);
35+
received += value.length;
36+
if (total && cbProgress) cbProgress(received / total);
37+
}
38+
39+
const out = new Uint8Array(received);
40+
let pos = 0;
41+
for (const c of chunks) { out.set(c, pos); pos += c.length; }
42+
if (!total && cbProgress) cbProgress(1);
43+
return out;
44+
}
5145

52-
var position = 0;
53-
var chunksAll = new Uint8Array(receivedLength);
46+
function openDB() {
47+
return new Promise((resolve, reject) => {
48+
const rq = indexedDB.open(dbName, dbVersion);
49+
rq.onupgradeneeded = (ev) => {
50+
const db = ev.target.result;
51+
if (!db.objectStoreNames.contains('models')) {
52+
db.createObjectStore('models', { autoIncrement: false });
53+
}
54+
};
55+
rq.onsuccess = () => resolve(rq.result);
56+
rq.onerror = () => reject(new Error('idb-open'));
57+
rq.onblocked = () => reject(new Error('idb-blocked'));
58+
rq.onabort = () => reject(new Error('idb-abort'));
59+
});
60+
}
5461

55-
for (var chunk of chunks) {
56-
chunksAll.set(chunk, position);
57-
position += chunk.length;
58-
}
62+
async function getCached(db, key) {
63+
try {
64+
return await new Promise((resolve) => {
65+
const tx = db.transaction(['models'], 'readonly');
66+
const os = tx.objectStore('models');
67+
const g = os.get(key);
68+
g.onsuccess = () => {
69+
let v = g.result;
70+
if (v && v instanceof ArrayBuffer) v = new Uint8Array(v);
71+
resolve(v || null);
72+
};
73+
g.onerror = () => resolve(null);
74+
});
75+
} catch { return null; }
76+
}
5977

60-
return chunksAll;
78+
async function putCached(db, key, data, cbPrint) {
79+
try {
80+
await new Promise((resolve) => {
81+
const tx = db.transaction(['models'], 'readwrite');
82+
const os = tx.objectStore('models');
83+
const p = os.put(data, key);
84+
p.onsuccess = () => { cbPrint && cbPrint('IDB: stored ' + key); resolve(); };
85+
p.onerror = () => resolve();
86+
});
87+
} catch (e) { cbPrint && cbPrint('IDB store error: ' + e); }
6188
}
6289

63-
// load remote data
64-
// - check if the data is already in the IndexedDB
65-
// - if not, fetch it from the remote URL and store it in the IndexedDB
66-
export function loadRemote(url, dst, size_mb, cbProgress, cbReady, cbCancel, cbPrint) {
67-
if (!navigator.storage || !navigator.storage.estimate) {
68-
cbPrint('loadRemote: navigator.storage.estimate() is not supported');
69-
} else {
70-
// query the storage quota and print it
71-
navigator.storage.estimate().then(function (estimate) {
72-
cbPrint('loadRemote: storage quota: ' + estimate.quota + ' bytes');
73-
cbPrint('loadRemote: storage usage: ' + estimate.usage + ' bytes');
74-
});
90+
// MAIN: try urls in order; cache successful one under that exact url key.
91+
export async function loadRemoteWithFallbacks(urls, dst, sizeMB, cbProgress, cbReady, cbCancel, cbPrint) {
92+
try {
93+
if (navigator.storage && navigator.storage.estimate) {
94+
const est = await navigator.storage.estimate();
95+
cbPrint && cbPrint(`IDB quota ~${est.quota}, used ~${est.usage}`);
7596
}
76-
77-
// check if the data is already in the IndexedDB
78-
var rq = indexedDB.open(dbName, dbVersion);
79-
80-
rq.onupgradeneeded = function (event) {
81-
var db = event.target.result;
82-
if (db.version == 1) {
83-
var os = db.createObjectStore('models', { autoIncrement: false });
84-
cbPrint('loadRemote: created IndexedDB ' + db.name + ' version ' + db.version);
85-
} else {
86-
// clear the database
87-
var os = event.currentTarget.transaction.objectStore('models');
88-
os.clear();
89-
cbPrint('loadRemote: cleared IndexedDB ' + db.name + ' version ' + db.version);
97+
} catch {}
98+
99+
let db = null;
100+
try { db = await openDB(); } catch { db = null; }
101+
102+
const tinyBlob = (b) => !b || (b.length || 0) < 4096;
103+
const urlList = (Array.isArray(urls) ? urls : [urls]).filter(Boolean);
104+
105+
if (urlList.length === 0) {
106+
cbCancel && cbCancel();
107+
return;
108+
}
109+
110+
for (const url of urlList) {
111+
try {
112+
// cache hit?
113+
if (db) {
114+
const cached = await getCached(db, url);
115+
if (cached && !tinyBlob(cached)) {
116+
cbReady(dst, cached);
117+
return;
90118
}
91-
};
92-
93-
rq.onsuccess = function (event) {
94-
var db = event.target.result;
95-
var tx = db.transaction(['models'], 'readonly');
96-
var os = tx.objectStore('models');
97-
var rq = os.get(url);
98-
99-
rq.onsuccess = function (event) {
100-
if (rq.result) {
101-
cbPrint('loadRemote: "' + url + '" is already in the IndexedDB');
102-
cbReady(dst, rq.result);
103-
} else {
104-
// data is not in the IndexedDB
105-
cbPrint('loadRemote: "' + url + '" is not in the IndexedDB');
106-
107-
// alert and ask the user to confirm
108-
if (!confirm(
109-
'You are about to download ' + size_mb + ' MB of data.\n' +
110-
'The model data will be cached in the browser for future use.\n\n' +
111-
'Press OK to continue.')) {
112-
cbCancel();
113-
return;
114-
}
115-
116-
fetchRemote(url, cbProgress, cbPrint).then(function (data) {
117-
if (data) {
118-
// store the data in the IndexedDB
119-
var rq = indexedDB.open(dbName, dbVersion);
120-
rq.onsuccess = function (event) {
121-
var db = event.target.result;
122-
var tx = db.transaction(['models'], 'readwrite');
123-
var os = tx.objectStore('models');
124-
125-
var rq = null;
126-
try {
127-
var rq = os.put(data, url);
128-
} catch (e) {
129-
cbPrint('loadRemote: failed to store "' + url + '" in the IndexedDB: \n' + e);
130-
cbCancel();
131-
return;
132-
}
133-
134-
rq.onsuccess = function (event) {
135-
cbPrint('loadRemote: "' + url + '" stored in the IndexedDB');
136-
cbReady(dst, data);
137-
};
138-
139-
rq.onerror = function (event) {
140-
cbPrint('loadRemote: failed to store "' + url + '" in the IndexedDB');
141-
cbCancel();
142-
};
143-
};
144-
}
145-
});
146-
}
147-
};
148-
149-
rq.onerror = function (event) {
150-
cbPrint('loadRemote: failed to get data from the IndexedDB');
151-
cbCancel();
152-
};
153-
};
154-
155-
rq.onerror = function (event) {
156-
cbPrint('loadRemote: failed to open IndexedDB');
157-
cbCancel();
158-
};
159-
160-
rq.onblocked = function (event) {
161-
cbPrint('loadRemote: failed to open IndexedDB: blocked');
162-
cbCancel();
163-
};
119+
}
120+
121+
cbPrint && cbPrint(`loadRemote: fetching ~${sizeMB} MB from ${url}`);
122+
const data = await fetchBinary(url, cbProgress, cbPrint);
123+
if (tinyBlob(data)) throw new Error('tiny-blob');
124+
if (db) await putCached(db, url, data, cbPrint);
125+
cbReady(dst, data);
126+
return;
127+
} catch (e) {
128+
cbPrint && cbPrint(`loadRemote: failed on ${url} (${e}), trying next...`);
129+
}
130+
}
164131

165-
rq.onabort = function (event) {
166-
cbPrint('loadRemote: failed to open IndexedDB: abort');
167-
cbCancel();
168-
};
132+
cbCancel();
169133
}

0 commit comments

Comments
 (0)