ggerganov commited on
Commit
3520198
·
unverified ·
1 Parent(s): 33a4590

wasm : refactor wasm example + reuse fetch mechanism

Browse files
examples/helpers.js ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Common Javascript functions used by the examples
2
+
3
+ function convertTypedArray(src, type) {
4
+ var buffer = new ArrayBuffer(src.byteLength);
5
+ var baseView = new src.constructor(buffer).set(src);
6
+ return new type(buffer);
7
+ }
8
+
9
+ var printTextarea = (function() {
10
+ var element = document.getElementById('output');
11
+ if (element) element.alue = ''; // clear browser cache
12
+ return function(text) {
13
+ if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
14
+ console.log(text);
15
+ if (element) {
16
+ element.value += text + "\n";
17
+ element.scrollTop = element.scrollHeight; // focus on bottom
18
+ }
19
+ };
20
+ })();
21
+
22
+ // fetch a remote file from remote URL using the Fetch API
23
+ async function fetchRemote(url, cbProgress, cbPrint) {
24
+ cbPrint('fetchRemote: downloading with fetch()...');
25
+
26
+ const response = await fetch(
27
+ url,
28
+ {
29
+ method: 'GET',
30
+ headers: {
31
+ 'Content-Type': 'application/octet-stream',
32
+ },
33
+ }
34
+ );
35
+
36
+ if (!response.ok) {
37
+ cbPrint('fetchRemote: failed to fetch ' + url);
38
+ return;
39
+ }
40
+
41
+ const contentLength = response.headers.get('content-length');
42
+ const total = parseInt(contentLength, 10);
43
+ const reader = response.body.getReader();
44
+
45
+ var chunks = [];
46
+ var receivedLength = 0;
47
+ var progressLast = -1;
48
+
49
+ while (true) {
50
+ const { done, value } = await reader.read();
51
+
52
+ if (done) {
53
+ break;
54
+ }
55
+
56
+ chunks.push(value);
57
+ receivedLength += value.length;
58
+
59
+ if (contentLength) {
60
+ cbProgress(receivedLength/total);
61
+
62
+ var progressCur = Math.round((receivedLength / total) * 10);
63
+ if (progressCur != progressLast) {
64
+ cbPrint('fetchRemote: fetching ' + 10*progressCur + '% ...');
65
+ progressLast = progressCur;
66
+ }
67
+ }
68
+ }
69
+
70
+ var position = 0;
71
+ var chunksAll = new Uint8Array(receivedLength);
72
+
73
+ for (var chunk of chunks) {
74
+ chunksAll.set(chunk, position);
75
+ position += chunk.length;
76
+ }
77
+
78
+ return chunksAll;
79
+ }
80
+
81
+ // load remote data
82
+ // - check if the data is already in the IndexedDB
83
+ // - if not, fetch it from the remote URL and store it in the IndexedDB
84
+ function loadRemote(url, dst, size_mb, cbProgress, cbReady, cbCancel, cbPrint) {
85
+ // query the storage quota and print it
86
+ navigator.storage.estimate().then(function (estimate) {
87
+ cbPrint('loadRemote: storage quota: ' + estimate.quota + ' bytes');
88
+ cbPrint('loadRemote: storage usage: ' + estimate.usage + ' bytes');
89
+ });
90
+
91
+ // check if the data is already in the IndexedDB
92
+ var rq = indexedDB.open(dbName, dbVersion);
93
+
94
+ rq.onupgradeneeded = function (event) {
95
+ var db = event.target.result;
96
+ if (db.version == 1) {
97
+ var os = db.createObjectStore('models', { autoIncrement: false });
98
+ cbPrint('loadRemote: created IndexedDB ' + db.name + ' version ' + db.version);
99
+ } else {
100
+ // clear the database
101
+ var os = event.currentTarget.transaction.objectStore('models');
102
+ os.clear();
103
+ cbPrint('loadRemote: cleared IndexedDB ' + db.name + ' version ' + db.version);
104
+ }
105
+ };
106
+
107
+ rq.onsuccess = function (event) {
108
+ var db = event.target.result;
109
+ var tx = db.transaction(['models'], 'readonly');
110
+ var os = tx.objectStore('models');
111
+ var rq = os.get(url);
112
+
113
+ rq.onsuccess = function (event) {
114
+ if (rq.result) {
115
+ cbPrint('loadRemote: "' + url + '" is already in the IndexedDB');
116
+ cbReady(dst, rq.result);
117
+ } else {
118
+ // data is not in the IndexedDB
119
+ cbPrint('loadRemote: "' + url + '" is not in the IndexedDB');
120
+
121
+ // alert and ask the user to confirm
122
+ if (!confirm(
123
+ 'You are about to download ' + size_mb + ' MB of data.\n' +
124
+ 'The model data will be cached in the browser for future use.\n\n' +
125
+ 'Press OK to continue.')) {
126
+ cbCancel();
127
+ return;
128
+ }
129
+
130
+ fetchRemote(url, cbProgress, cbPrint).then(function (data) {
131
+ if (data) {
132
+ // store the data in the IndexedDB
133
+ var rq = indexedDB.open(dbName, dbVersion);
134
+ rq.onsuccess = function (event) {
135
+ var db = event.target.result;
136
+ var tx = db.transaction(['models'], 'readwrite');
137
+ var os = tx.objectStore('models');
138
+ var rq = os.put(data, url);
139
+
140
+ rq.onsuccess = function (event) {
141
+ cbPrint('loadRemote: "' + url + '" stored in the IndexedDB');
142
+ cbReady(dst, data);
143
+ };
144
+
145
+ rq.onerror = function (event) {
146
+ cbPrint('loadRemote: failed to store "' + url + '" in the IndexedDB');
147
+ cbCancel();
148
+ };
149
+ };
150
+ }
151
+ });
152
+ }
153
+ };
154
+
155
+ rq.onerror = function (event) {
156
+ cbPrint('loadRemote: failed to get data from the IndexedDB');
157
+ cbCancel();
158
+ };
159
+ };
160
+
161
+ rq.onerror = function (event) {
162
+ cbPrint('loadRemote: failed to open IndexedDB');
163
+ cbCancel();
164
+ };
165
+
166
+ rq.onblocked = function (event) {
167
+ cbPrint('loadRemote: failed to open IndexedDB: blocked');
168
+ cbCancel();
169
+ };
170
+
171
+ rq.onabort = function (event) {
172
+ cbPrint('loadRemote: failed to open IndexedDB: abort');
173
+
174
+ };
175
+ }
176
+
examples/talk.wasm/CMakeLists.txt CHANGED
@@ -45,3 +45,4 @@ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS " \
45
  set(TARGET talk.wasm)
46
 
47
  configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
 
 
45
  set(TARGET talk.wasm)
46
 
47
  configure_file(${CMAKE_CURRENT_SOURCE_DIR}/index-tmpl.html ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/index.html @ONLY)
48
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/../helpers.js ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${TARGET}/helpers.js @ONLY)
examples/talk.wasm/README.md CHANGED
@@ -61,9 +61,8 @@ emcmake cmake ..
61
  make -j
62
 
63
  # copy the produced page to your HTTP path
64
- cp bin/talk.wasm/index.html /path/to/html/
65
- cp bin/talk.wasm/talk.js /path/to/html/
66
- cp bin/libtalk.worker.js /path/to/html/
67
  ```
68
 
69
  ## Feedback
 
61
  make -j
62
 
63
  # copy the produced page to your HTTP path
64
+ cp bin/talk.wasm/* /path/to/html/
65
+ cp bin/libtalk.worker.js /path/to/html/
 
66
  ```
67
 
68
  ## Feedback
examples/talk.wasm/emscripten.cpp CHANGED
@@ -62,7 +62,7 @@ void talk_main(size_t index) {
62
  wparams.print_special_tokens = false;
63
 
64
  wparams.max_tokens = 32;
65
- wparams.audio_ctx = 768;
66
 
67
  wparams.language = "en";
68
 
@@ -133,7 +133,7 @@ void talk_main(size_t index) {
133
  }
134
  }
135
 
136
- talk_set_status("processing ...");
137
 
138
  t_last = t_now;
139
 
@@ -192,7 +192,7 @@ void talk_main(size_t index) {
192
  text_heard = std::regex_replace(text_heard, std::regex("^\\s+"), "");
193
  text_heard = std::regex_replace(text_heard, std::regex("\\s+$"), "");
194
 
195
- talk_set_status("'" + text_heard + "' - thinking how to respond ...");
196
 
197
  const std::vector<gpt_vocab::id> tokens = gpt2_tokenize(g_gpt2, text_heard.c_str());
198
 
 
62
  wparams.print_special_tokens = false;
63
 
64
  wparams.max_tokens = 32;
65
+ wparams.audio_ctx = 768; // partial encoder context for better performance
66
 
67
  wparams.language = "en";
68
 
 
133
  }
134
  }
135
 
136
+ talk_set_status("processing audio (whisper)...");
137
 
138
  t_last = t_now;
139
 
 
192
  text_heard = std::regex_replace(text_heard, std::regex("^\\s+"), "");
193
  text_heard = std::regex_replace(text_heard, std::regex("\\s+$"), "");
194
 
195
+ talk_set_status("'" + text_heard + "' - thinking how to respond (gpt-2) ...");
196
 
197
  const std::vector<gpt_vocab::id> tokens = gpt2_tokenize(g_gpt2, text_heard.c_str());
198
 
examples/talk.wasm/index-tmpl.html CHANGED
@@ -51,7 +51,7 @@
51
  <br><br>
52
 
53
  <div id="model-whisper">
54
- <span id="model-whisper-status">Whisper model:</span>
55
  <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>
56
  <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>
57
  <span id="fetch-whisper-progress"></span>
@@ -64,7 +64,7 @@
64
  <br>
65
 
66
  <div id="model-gpt-2">
67
- <span id="model-gpt-2-status">GPT-2 model:</span>
68
  <button id="fetch-gpt-2-small" onclick="loadGPT2('small')">small 117M (240 MB)</button>
69
  <!--<button id="fetch-gpt-2-medium" onclick="loadGPT2('medium')">medium 345M (720 MB)</button>-->
70
  <span id="fetch-gpt-2-progress"></span>
@@ -158,20 +158,8 @@
158
  </div>
159
  </div>
160
 
 
161
  <script type='text/javascript'>
162
- var printTextarea = (function() {
163
- var element = document.getElementById('output');
164
- if (element) element.alue = ''; // clear browser cache
165
- return function(text) {
166
- if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
167
- console.log(text);
168
- if (element) {
169
- element.value += text + "\n";
170
- element.scrollTop = element.scrollHeight; // focus on bottom
171
- }
172
- };
173
- })();
174
-
175
  const kRestartRecording_s = 15;
176
  const kSampleRate = 16000;
177
 
@@ -218,6 +206,7 @@
218
  if (voices.length == 0) {
219
  el.innerHTML = '<option value="0">No voices available</option>';
220
  } else {
 
221
  var n = 0;
222
  voices.forEach(function(voice, i) {
223
  if (!voice.lang.startsWith('en')) return;
@@ -245,17 +234,14 @@
245
  }
246
  };
247
 
248
- // helper function
249
- function convertTypedArray(src, type) {
250
- var buffer = new ArrayBuffer(src.byteLength);
251
- var baseView = new src.constructor(buffer).set(src);
252
- return new type(buffer);
253
- }
254
-
255
  //
256
  // fetch models
257
  //
258
 
 
 
 
 
259
  function storeFS(fname, buf) {
260
  // write to WASM file using FS_createDataFile
261
  // if the file exists, delete it
@@ -267,176 +253,21 @@
267
 
268
  Module.FS_createDataFile("/", fname, buf, true, true);
269
 
270
- printTextarea('js: stored model: ' + fname + ' size: ' + buf.length);
271
 
272
  if (fname == 'whisper.bin') {
273
- document.getElementById('model-whisper').innerHTML = 'Whisper model: loaded "' + model_whisper + '"!';
274
  } else if (fname == 'gpt-2.bin') {
275
- document.getElementById('model-gpt-2').innerHTML = 'GPT-2 model: loaded "' + model_gpt_2 + '"!';
276
  }
277
 
278
  if (model_whisper != null && model_gpt_2 != null) {
279
  document.getElementById('start').disabled = false;
280
- document.getElementById('stop').disabled = false;
281
  document.getElementById('voice').disabled = false;
282
  }
283
  }
284
 
285
- let dbVersion = 1
286
- let dbName = 'whisper.ggerganov.com';
287
- let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
288
-
289
- // fetch a remote file from remote URL using the Fetch API
290
- async function fetchRemote(url, elProgress) {
291
- printTextarea('js: downloading with fetch()...');
292
-
293
- const response = await fetch(
294
- url,
295
- {
296
- method: 'GET',
297
- headers: {
298
- 'Content-Type': 'application/octet-stream',
299
- },
300
- }
301
- );
302
-
303
- if (!response.ok) {
304
- printTextarea('js: failed to fetch ' + url);
305
- return;
306
- }
307
-
308
- const contentLength = response.headers.get('content-length');
309
- const total = parseInt(contentLength, 10);
310
- const reader = response.body.getReader();
311
-
312
- var chunks = [];
313
- var receivedLength = 0;
314
- var progressLast = -1;
315
-
316
- while (true) {
317
- const { done, value } = await reader.read();
318
-
319
- if (done) {
320
- break;
321
- }
322
-
323
- chunks.push(value);
324
- receivedLength += value.length;
325
-
326
- if (contentLength) {
327
- // update progress bar element with the new percentage
328
- elProgress.innerHTML = Math.round((receivedLength / total) * 100) + '%';
329
-
330
- var progressCur = Math.round((receivedLength / total) * 10);
331
- if (progressCur != progressLast) {
332
- printTextarea('js: fetching ' + 10*progressCur + '% ...');
333
- progressLast = progressCur;
334
- }
335
- }
336
- }
337
-
338
- var chunksAll = new Uint8Array(receivedLength);
339
- var position = 0;
340
- for (var chunk of chunks) {
341
- chunksAll.set(chunk, position);
342
- position += chunk.length;
343
- }
344
-
345
- return chunksAll;
346
- }
347
-
348
- // load remote data
349
- // - check if the data is already in the IndexedDB
350
- // - if not, fetch it from the remote URL and store it in the IndexedDB
351
- // - store it in WASM memory
352
- function loadRemote(url, dst, elProgress, size_mb) {
353
- // query the storage quota and print it
354
- navigator.storage.estimate().then(function (estimate) {
355
- printTextarea('js: storage quota: ' + estimate.quota + ' bytes');
356
- printTextarea('js: storage usage: ' + estimate.usage + ' bytes');
357
- });
358
-
359
- // check if the data is already in the IndexedDB
360
- var request = indexedDB.open(dbName, dbVersion);
361
-
362
- request.onupgradeneeded = function (event) {
363
- var db = event.target.result;
364
- if (db.version == 1) {
365
- var objectStore = db.createObjectStore('models', { autoIncrement: false });
366
- printTextarea('js: created IndexedDB ' + db.name + ' version ' + db.version);
367
- } else {
368
- // clear the database
369
- var objectStore = event.currentTarget.transaction.objectStore('models');
370
- objectStore.clear();
371
- printTextarea('js: cleared IndexedDB ' + db.name + ' version ' + db.version);
372
- }
373
- };
374
-
375
- request.onsuccess = function (event) {
376
- var db = event.target.result;
377
- var transaction = db.transaction(['models'], 'readonly');
378
- var objectStore = transaction.objectStore('models');
379
- var request = objectStore.get(url);
380
-
381
- request.onsuccess = function (event) {
382
- if (request.result) {
383
- printTextarea('js: "' + url + '" is already in the IndexedDB');
384
- storeFS(dst, request.result);
385
- } else {
386
- // data is not in the IndexedDB
387
- printTextarea('js: "' + url + '" is not in the IndexedDB');
388
-
389
- // alert and ask the user to confirm
390
- if (!confirm('You are about to download ' + size_mb + ' MB of data.\nThe model data will be cached in the browser for future use.\n\nPress OK to continue.')) {
391
- var el;
392
- el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';
393
- el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';
394
- el = document.getElementById('fetch-gpt-2-small') ; if (el) el.style.display = 'inline-block';
395
- return;
396
- }
397
-
398
- fetchRemote(url, elProgress).then(function (data) {
399
- if (data) {
400
- // store the data in the IndexedDB
401
- var request = indexedDB.open(dbName, dbVersion);
402
- request.onsuccess = function (event) {
403
- var db = event.target.result;
404
- var transaction = db.transaction(['models'], 'readwrite');
405
- var objectStore = transaction.objectStore('models');
406
- var request = objectStore.put(data, url);
407
-
408
- request.onsuccess = function (event) {
409
- printTextarea('js: "' + url + '" stored in the IndexedDB');
410
- storeFS(dst, data);
411
- };
412
-
413
- request.onerror = function (event) {
414
- printTextarea('js: failed to store "' + url + '" in the IndexedDB');
415
- };
416
- };
417
- }
418
- });
419
- }
420
- };
421
-
422
- request.onerror = function (event) {
423
- printTextarea('js: failed to get data from the IndexedDB');
424
- };
425
- };
426
-
427
- request.onerror = function (event) {
428
- printTextarea('js: failed to open IndexedDB');
429
- };
430
-
431
- request.onblocked = function (event) {
432
- printTextarea('js: failed to open IndexedDB: blocked');
433
- };
434
-
435
- request.onabort = function (event) {
436
- printTextarea('js: failed to open IndexedDB: abort');
437
- };
438
- }
439
-
440
  function loadWhisper(model) {
441
  let urls = {
442
  'tiny.en': 'https://whisper.ggerganov.com/ggml-model-whisper-tiny.en.bin',
@@ -450,16 +281,27 @@
450
 
451
  let url = urls[model];
452
  let dst = 'whisper.bin';
453
- let el = document.getElementById('fetch-whisper-progress');
454
  let size_mb = sizes[model];
455
 
456
  model_whisper = model;
457
 
458
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
459
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
460
- document.getElementById('model-whisper-status').innerHTML = 'Whisper model: loading "' + model + '" ... ';
461
 
462
- loadRemote(url, dst, el, size_mb);
 
 
 
 
 
 
 
 
 
 
 
 
463
  }
464
 
465
  function loadGPT2(model) {
@@ -475,15 +317,25 @@
475
 
476
  let url = urls[model];
477
  let dst = 'gpt-2.bin';
478
- let el = document.getElementById('fetch-gpt-2-progress');
479
  let size_mb = sizes[model];
480
 
481
  model_gpt_2 = model;
482
 
483
  document.getElementById('fetch-gpt-2-small').style.display = 'none';
484
- document.getElementById('model-gpt-2-status').innerHTML = 'GPT-2 model: loading "' + model + '" ... ';
 
 
 
 
 
 
 
 
 
 
 
485
 
486
- loadRemote(url, dst, el, size_mb);
487
  }
488
 
489
  //
 
51
  <br><br>
52
 
53
  <div id="model-whisper">
54
+ Whisper model: <span id="model-whisper-status"></span>
55
  <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>
56
  <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>
57
  <span id="fetch-whisper-progress"></span>
 
64
  <br>
65
 
66
  <div id="model-gpt-2">
67
+ GPT-2 model: <span id="model-gpt-2-status"></span>
68
  <button id="fetch-gpt-2-small" onclick="loadGPT2('small')">small 117M (240 MB)</button>
69
  <!--<button id="fetch-gpt-2-medium" onclick="loadGPT2('medium')">medium 345M (720 MB)</button>-->
70
  <span id="fetch-gpt-2-progress"></span>
 
158
  </div>
159
  </div>
160
 
161
+ <script type="text/javascript" src="helpers.js"></script>
162
  <script type='text/javascript'>
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  const kRestartRecording_s = 15;
164
  const kSampleRate = 16000;
165
 
 
206
  if (voices.length == 0) {
207
  el.innerHTML = '<option value="0">No voices available</option>';
208
  } else {
209
+ // populate voice list
210
  var n = 0;
211
  voices.forEach(function(voice, i) {
212
  if (!voice.lang.startsWith('en')) return;
 
234
  }
235
  };
236
 
 
 
 
 
 
 
 
237
  //
238
  // fetch models
239
  //
240
 
241
+ let dbVersion = 1
242
+ let dbName = 'whisper.ggerganov.com';
243
+ let indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB
244
+
245
  function storeFS(fname, buf) {
246
  // write to WASM file using FS_createDataFile
247
  // if the file exists, delete it
 
253
 
254
  Module.FS_createDataFile("/", fname, buf, true, true);
255
 
256
+ printTextarea('storeFS: stored model: ' + fname + ' size: ' + buf.length);
257
 
258
  if (fname == 'whisper.bin') {
259
+ document.getElementById('model-whisper-status').innerHTML = 'loaded "' + model_whisper + '"!';
260
  } else if (fname == 'gpt-2.bin') {
261
+ document.getElementById('model-gpt-2-status').innerHTML = 'loaded "' + model_gpt_2 + '"!';
262
  }
263
 
264
  if (model_whisper != null && model_gpt_2 != null) {
265
  document.getElementById('start').disabled = false;
266
+ document.getElementById('stop' ).disabled = false;
267
  document.getElementById('voice').disabled = false;
268
  }
269
  }
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  function loadWhisper(model) {
272
  let urls = {
273
  'tiny.en': 'https://whisper.ggerganov.com/ggml-model-whisper-tiny.en.bin',
 
281
 
282
  let url = urls[model];
283
  let dst = 'whisper.bin';
 
284
  let size_mb = sizes[model];
285
 
286
  model_whisper = model;
287
 
288
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
289
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
290
+ document.getElementById('model-whisper-status').innerHTML = 'loading "' + model + '" ... ';
291
 
292
+ cbProgress = function(p) {
293
+ let el = document.getElementById('fetch-whisper-progress');
294
+ el.innerHTML = Math.round(100*p) + '%';
295
+ };
296
+
297
+ cbCancel = function() {
298
+ var el;
299
+ el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';
300
+ el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';
301
+ el = document.getElementById('model-whisper-status'); if (el) el.innerHTML = '';
302
+ };
303
+
304
+ loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea);
305
  }
306
 
307
  function loadGPT2(model) {
 
317
 
318
  let url = urls[model];
319
  let dst = 'gpt-2.bin';
 
320
  let size_mb = sizes[model];
321
 
322
  model_gpt_2 = model;
323
 
324
  document.getElementById('fetch-gpt-2-small').style.display = 'none';
325
+ document.getElementById('model-gpt-2-status').innerHTML = 'loading "' + model + '" ... ';
326
+
327
+ cbProgress = function(p) {
328
+ let el = document.getElementById('fetch-gpt-2-progress');
329
+ el.innerHTML = Math.round(100*p) + '%';
330
+ };
331
+
332
+ cbCancel = function() {
333
+ var el;
334
+ el = document.getElementById('fetch-gpt-2-small') ; if (el) el.style.display = 'inline-block';
335
+ el = document.getElementById('model-gpt-2-status'); if (el) el.innerHTML = '';
336
+ };
337
 
338
+ loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea);
339
  }
340
 
341
  //
examples/whisper.wasm/README.md CHANGED
@@ -37,7 +37,6 @@ emcmake cmake ..
37
  make -j
38
 
39
  # copy the produced page to your HTTP path
40
- cp bin/whisper.wasm/index.html /path/to/html/
41
- cp bin/whisper.wasm/whisper.js /path/to/html/
42
- cp bin/libwhisper.worker.js /path/to/html/
43
  ```
 
37
  make -j
38
 
39
  # copy the produced page to your HTTP path
40
+ cp bin/whisper.wasm/* /path/to/html/
41
+ cp bin/libwhisper.worker.js /path/to/html/
 
42
  ```
examples/whisper.wasm/index-tmpl.html CHANGED
@@ -45,13 +45,14 @@
45
  <br><br><hr>
46
 
47
  <div id="model">
48
- Model:
49
  <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>
50
  <button id="fetch-whisper-tiny" onclick="loadWhisper('tiny')">tiny (75 MB)</button>
51
  <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>
52
  <button id="fetch-whisper-base" onclick="loadWhisper('base')">base (142 MB)</button>
53
  <span id="fetch-whisper-progress"></span>
54
- <input type="file" id="file" name="file" onchange="loadFile(event, 'whisper.bin')" />
 
55
  </div>
56
 
57
  <br>
@@ -185,6 +186,7 @@
185
  </div>
186
  </div>
187
 
 
188
  <script type='text/javascript'>
189
  // TODO: convert audio buffer to WAV
190
  function setAudio(audio) {
@@ -204,28 +206,15 @@
204
  function changeInput(input) {
205
  if (input == 'file') {
206
  document.getElementById('input_file').style.display = 'block';
207
- document.getElementById('input_mic').style.display = 'none';
208
- document.getElementById('progress').style.display = 'none';
209
  } else {
210
  document.getElementById('input_file').style.display = 'none';
211
- document.getElementById('input_mic').style.display = 'block';
212
- document.getElementById('progress').style.display = 'block';
213
  }
214
  }
215
 
216
- var printTextarea = (function() {
217
- var element = document.getElementById('output');
218
- if (element) element.alue = ''; // clear browser cache
219
- return function(text) {
220
- if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
221
- console.log(text);
222
- if (element) {
223
- element.value += text + "\n";
224
- element.scrollTop = element.scrollHeight; // focus on bottom
225
- }
226
- };
227
- })();
228
-
229
  var Module = {
230
  print: printTextarea,
231
  printErr: printTextarea,
@@ -250,7 +239,7 @@
250
 
251
  // the whisper instance
252
  var instance = null;
253
- var model_fname = '';
254
 
255
  // helper function
256
  function convertTypedArray(src, type) {
@@ -278,8 +267,11 @@
278
 
279
  Module.FS_createDataFile("/", fname, buf, true, true);
280
 
281
- model_fname = fname;
282
- printTextarea('js: stored model: ' + fname + ' size: ' + buf.length);
 
 
 
283
  }
284
 
285
  function loadFile(event, fname) {
@@ -288,8 +280,8 @@
288
  return;
289
  }
290
 
291
- printTextarea("js: loading model: " + file.name + ", size: " + file.size + " bytes");
292
- printTextarea('js: please wait ...');
293
 
294
  var reader = new FileReader();
295
  reader.onload = function(event) {
@@ -300,160 +292,10 @@
300
 
301
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
302
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
303
- document.getElementById('fetch-whisper-tiny').style.display = 'none';
304
- document.getElementById('fetch-whisper-base').style.display = 'none';
305
- }
306
-
307
- // fetch a remote file from remote URL using the Fetch API
308
- async function fetchRemote(url, elProgress) {
309
- printTextarea('js: downloading with fetch()...');
310
-
311
- const response = await fetch(
312
- url,
313
- {
314
- method: 'GET',
315
- headers: {
316
- 'Content-Type': 'application/octet-stream',
317
- },
318
- }
319
- );
320
-
321
- if (!response.ok) {
322
- printTextarea('js: failed to fetch ' + url);
323
- return;
324
- }
325
-
326
- const contentLength = response.headers.get('content-length');
327
- const total = parseInt(contentLength, 10);
328
- const reader = response.body.getReader();
329
-
330
- var chunks = [];
331
- var receivedLength = 0;
332
- var progressLast = -1;
333
-
334
- while (true) {
335
- const { done, value } = await reader.read();
336
-
337
- if (done) {
338
- break;
339
- }
340
-
341
- chunks.push(value);
342
- receivedLength += value.length;
343
-
344
- if (contentLength) {
345
- // update progress bar element with the new percentage
346
- elProgress.innerHTML = Math.round((receivedLength / total) * 100) + '%';
347
-
348
- var progressCur = Math.round((receivedLength / total) * 10);
349
- if (progressCur != progressLast) {
350
- printTextarea('js: fetching ' + 10*progressCur + '% ...');
351
- progressLast = progressCur;
352
- }
353
- }
354
- }
355
-
356
- var chunksAll = new Uint8Array(receivedLength);
357
- var position = 0;
358
- for (var chunk of chunks) {
359
- chunksAll.set(chunk, position);
360
- position += chunk.length;
361
- }
362
-
363
- return chunksAll;
364
- }
365
-
366
- // load remote data
367
- // - check if the data is already in the IndexedDB
368
- // - if not, fetch it from the remote URL and store it in the IndexedDB
369
- // - store it in WASM memory
370
- function loadRemote(url, dst, elProgress, size_mb) {
371
- // query the storage quota and print it
372
- navigator.storage.estimate().then(function (estimate) {
373
- printTextarea('js: storage quota: ' + estimate.quota + ' bytes');
374
- printTextarea('js: storage usage: ' + estimate.usage + ' bytes');
375
- });
376
-
377
- // check if the data is already in the IndexedDB
378
- var request = indexedDB.open(dbName, dbVersion);
379
-
380
- request.onupgradeneeded = function (event) {
381
- var db = event.target.result;
382
- if (db.version == 1) {
383
- var objectStore = db.createObjectStore('models', { autoIncrement: false });
384
- printTextarea('js: created IndexedDB ' + db.name + ' version ' + db.version);
385
- } else {
386
- // clear the database
387
- var objectStore = event.currentTarget.transaction.objectStore('models');
388
- objectStore.clear();
389
- printTextarea('js: cleared IndexedDB ' + db.name + ' version ' + db.version);
390
- }
391
- };
392
-
393
- request.onsuccess = function (event) {
394
- var db = event.target.result;
395
- var transaction = db.transaction(['models'], 'readonly');
396
- var objectStore = transaction.objectStore('models');
397
- var request = objectStore.get(url);
398
-
399
- request.onsuccess = function (event) {
400
- if (request.result) {
401
- printTextarea('js: "' + url + '" is already in the IndexedDB');
402
- storeFS(dst, request.result);
403
- } else {
404
- // data is not in the IndexedDB
405
- printTextarea('js: "' + url + '" is not in the IndexedDB');
406
-
407
- // alert and ask the user to confirm
408
- if (!confirm('You are about to download ' + size_mb + ' MB of data.\nThe model data will be cached in the browser for future use.\n\nPress OK to continue.')) {
409
- var el;
410
- el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';
411
- el = document.getElementById('fetch-whisper-tiny'); if (el) el.style.display = 'inline-block';
412
- el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';
413
- el = document.getElementById('fetch-whisper-base'); if (el) el.style.display = 'inline-block';
414
- return;
415
- }
416
-
417
- fetchRemote(url, elProgress).then(function (data) {
418
- if (data) {
419
- // store the data in the IndexedDB
420
- var request = indexedDB.open(dbName, dbVersion);
421
- request.onsuccess = function (event) {
422
- var db = event.target.result;
423
- var transaction = db.transaction(['models'], 'readwrite');
424
- var objectStore = transaction.objectStore('models');
425
- var request = objectStore.put(data, url);
426
-
427
- request.onsuccess = function (event) {
428
- printTextarea('js: "' + url + '" stored in the IndexedDB');
429
- storeFS(dst, data);
430
- };
431
-
432
- request.onerror = function (event) {
433
- printTextarea('js: failed to store "' + url + '" in the IndexedDB');
434
- };
435
- };
436
- }
437
- });
438
- }
439
- };
440
-
441
- request.onerror = function (event) {
442
- printTextarea('js: failed to get data from the IndexedDB');
443
- };
444
- };
445
-
446
- request.onerror = function (event) {
447
- printTextarea('js: failed to open IndexedDB');
448
- };
449
-
450
- request.onblocked = function (event) {
451
- printTextarea('js: failed to open IndexedDB: blocked');
452
- };
453
-
454
- request.onabort = function (event) {
455
- printTextarea('js: failed to open IndexedDB: abort');
456
- };
457
  }
458
 
459
  function loadWhisper(model) {
@@ -473,17 +315,33 @@
473
 
474
  let url = urls[model];
475
  let dst = 'whisper.bin';
476
- let el = document.getElementById('fetch-whisper-progress');
477
  let size_mb = sizes[model];
478
 
479
  model_whisper = model;
480
 
481
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
482
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
483
- document.getElementById('fetch-whisper-tiny').style.display = 'none';
484
- document.getElementById('fetch-whisper-base').style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
486
- loadRemote(url, dst, el, size_mb);
487
  }
488
 
489
  //
@@ -651,7 +509,7 @@
651
 
652
  if (instance) {
653
  printTextarea("js: whisper initialized, instance: " + instance);
654
- document.getElementById('model').innerHTML = 'Model loaded: ' + model_fname;
655
  }
656
  }
657
 
 
45
  <br><br><hr>
46
 
47
  <div id="model">
48
+ Whisper model: <span id="model-whisper-status"></span>
49
  <button id="fetch-whisper-tiny-en" onclick="loadWhisper('tiny.en')">tiny.en (75 MB)</button>
50
  <button id="fetch-whisper-tiny" onclick="loadWhisper('tiny')">tiny (75 MB)</button>
51
  <button id="fetch-whisper-base-en" onclick="loadWhisper('base.en')">base.en (142 MB)</button>
52
  <button id="fetch-whisper-base" onclick="loadWhisper('base')">base (142 MB)</button>
53
  <span id="fetch-whisper-progress"></span>
54
+
55
+ <input type="file" id="whisper-file" name="file" onchange="loadFile(event, 'whisper.bin')" />
56
  </div>
57
 
58
  <br>
 
186
  </div>
187
  </div>
188
 
189
+ <script type="text/javascript" src="helpers.js"></script>
190
  <script type='text/javascript'>
191
  // TODO: convert audio buffer to WAV
192
  function setAudio(audio) {
 
206
  function changeInput(input) {
207
  if (input == 'file') {
208
  document.getElementById('input_file').style.display = 'block';
209
+ document.getElementById('input_mic' ).style.display = 'none';
210
+ document.getElementById('progress' ).style.display = 'none';
211
  } else {
212
  document.getElementById('input_file').style.display = 'none';
213
+ document.getElementById('input_mic' ).style.display = 'block';
214
+ document.getElementById('progress' ).style.display = 'block';
215
  }
216
  }
217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  var Module = {
219
  print: printTextarea,
220
  printErr: printTextarea,
 
239
 
240
  // the whisper instance
241
  var instance = null;
242
+ var model_whisper = '';
243
 
244
  // helper function
245
  function convertTypedArray(src, type) {
 
267
 
268
  Module.FS_createDataFile("/", fname, buf, true, true);
269
 
270
+ model_whisper = fname;
271
+
272
+ document.getElementById('model-whisper-status').innerHTML = 'loaded "' + model_whisper + '"!';
273
+
274
+ printTextarea('storeFS: stored model: ' + fname + ' size: ' + buf.length);
275
  }
276
 
277
  function loadFile(event, fname) {
 
280
  return;
281
  }
282
 
283
+ printTextarea("loadFile: loading model: " + file.name + ", size: " + file.size + " bytes");
284
+ printTextarea('loadFile: please wait ...');
285
 
286
  var reader = new FileReader();
287
  reader.onload = function(event) {
 
292
 
293
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
294
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
295
+ document.getElementById('fetch-whisper-tiny' ).style.display = 'none';
296
+ document.getElementById('fetch-whisper-base' ).style.display = 'none';
297
+ document.getElementById('whisper-file' ).style.display = 'none';
298
+ document.getElementById('model-whisper-status' ).innerHTML = 'loaded model: ' + file.name;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
  }
300
 
301
  function loadWhisper(model) {
 
315
 
316
  let url = urls[model];
317
  let dst = 'whisper.bin';
 
318
  let size_mb = sizes[model];
319
 
320
  model_whisper = model;
321
 
322
  document.getElementById('fetch-whisper-tiny-en').style.display = 'none';
323
  document.getElementById('fetch-whisper-base-en').style.display = 'none';
324
+ document.getElementById('fetch-whisper-tiny' ).style.display = 'none';
325
+ document.getElementById('fetch-whisper-base' ).style.display = 'none';
326
+ document.getElementById('whisper-file' ).style.display = 'none';
327
+ document.getElementById('model-whisper-status' ).innerHTML = 'loading model: ' + model;
328
+
329
+ cbProgress = function(p) {
330
+ let el = document.getElementById('fetch-whisper-progress');
331
+ el.innerHTML = Math.round(100*p) + '%';
332
+ };
333
+
334
+ cbCancel = function() {
335
+ var el;
336
+ el = document.getElementById('fetch-whisper-tiny-en'); if (el) el.style.display = 'inline-block';
337
+ el = document.getElementById('fetch-whisper-base-en'); if (el) el.style.display = 'inline-block';
338
+ el = document.getElementById('fetch-whisper-tiny' ); if (el) el.style.display = 'inline-block';
339
+ el = document.getElementById('fetch-whisper-base' ); if (el) el.style.display = 'inline-block';
340
+ el = document.getElementById('whisper-file' ); if (el) el.style.display = 'inline-block';
341
+ el = document.getElementById('model-whisper-status' ); if (el) el.innerHTML = '';
342
+ };
343
 
344
+ loadRemote(url, dst, size_mb, cbProgress, storeFS, cbCancel, printTextarea);
345
  }
346
 
347
  //
 
509
 
510
  if (instance) {
511
  printTextarea("js: whisper initialized, instance: " + instance);
512
+ document.getElementById('model').innerHTML = 'Model loaded: ' + model_whisper;
513
  }
514
  }
515