You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardexpand all lines: README.md
+8-4
Original file line number
Diff line number
Diff line change
@@ -213,27 +213,31 @@ if (supportsOurUseCase !== "no") {
213
213
214
214
### Download progress
215
215
216
-
In cases where using the API is only possible after a download, you can monitor the download progress (e.g. in order to show your users a progress bar) using code such as the following:
216
+
For cases where using the API is only possible after a download, you can monitor the download progress (e.g. in order to show your users a progress bar) using code such as the following:
217
217
218
218
```js
219
219
constwriter=awaitai.writer.create({
220
220
...otherOptions,
221
221
monitor(m) {
222
222
m.addEventListener("downloadprogress", e=> {
223
-
console.log(`Downloaded ${e.loaded} of ${e.total} bytes.`);
223
+
console.log(`Downloaded ${e.loaded*100}%`);
224
224
});
225
225
}
226
226
);
227
227
```
228
228
229
-
If the download fails, then `downloadprogress` events will stop being emitted, and the promise returned by `create()` will be rejected with a `"NetworkError"``DOMException`.
229
+
If the download fails, then `downloadprogress` events will stop being fired, and the promise returned by `create()` will be rejected with a `"NetworkError"``DOMException`.
230
230
231
231
Note that in the case that multiple entities are downloaded (e.g., a base model plus a [LoRA fine-tuning](https://arxiv.org/abs/2106.09685) for writing, or for the particular style requested) web developers do not get the ability to monitor the individual downloads. All of them are bundled into the overall `downloadprogress` events, and the `create()` promise is not fulfilled until all downloads and loads are successful.
232
232
233
+
The event is a [`ProgressEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent) whose `loaded` property is between 0 and 1, and whose `total` property is always 1. (The exact number of total or downloaded bytes are not exposed; see the discussion in [issue #15](https://github.com/webmachinelearning/writing-assistance-apis/issues/15).)
234
+
235
+
At least two events, with `e.loaded===0` and `e.loaded===1`, will always be fired. This is true even if creating the model doesn't require any downloading.
236
+
233
237
<details>
234
238
<summary>What's up with this pattern?</summary>
235
239
236
-
This pattern is a little involved. Several alternatives have been considered. However, asking around the web standards community it seemed like this one was best, as it allows using standard event handlers and `ProgressEvent`s, and also ensures that once the promise is settled, the translator or language detector object is completely ready to use.
240
+
This pattern is a little involved. Several alternatives have been considered. However, asking around the web standards community it seemed like this one was best, as it allows using standard event handlers and `ProgressEvent`s, and also ensures that once the promise is settled, the returned object is completely ready to use.
237
241
238
242
It is also nicely future-extensible by adding more events and properties to the `m` object.
@@ -191,13 +194,15 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
191
194
192
195
If this throws an exception |e|, catch it, and return [=a promise rejected with=] |e|.
193
196
194
-
1. Set |fireProgressEvent| to an algorithm taking arguments |loaded| and |total|, which performs the following steps:
197
+
1. Set |fireProgressEvent| to an algorithm taking argument |loaded|, which performs the following steps:
195
198
196
199
1. [=Assert=]: this algorithm is running [=in parallel=].
197
200
198
201
1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to perform the following steps:
199
202
200
-
1. [=Fire an event=] named {{AICreateMonitor/downloadprogress}} at |monitor|, using {{ProgressEvent}}, with the {{ProgressEvent/loaded}} attribute initialized to |loaded|, the {{ProgressEvent/total}} attribute initialized to |total|, and the {{ProgressEvent/lengthComputable}} attribute initialized to true.
203
+
1. [=Fire an event=] named {{AICreateMonitor/downloadprogress}} at |monitor|, using {{ProgressEvent}}, with the {{ProgressEvent/loaded}} attribute initialized to |loaded|, the {{ProgressEvent/total}} attribute initialized to 1, and the {{ProgressEvent/lengthComputable}} attribute initialized to true.
204
+
205
+
<p class="advisement">This assumes <a href="https://github.com/whatwg/xhr/pull/394">whatwg/xhr#394</a> is merged so that passing non-integer values for {{ProgressEvent/loaded}} works as expected.</p>
201
206
202
207
1. Let |abortedDuringDownload| be false.
203
208
@@ -234,13 +239,9 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
234
239
::
235
240
1. If [=initializing the summarization model=] given |promise| and |options| returns false, then abort these steps.
236
241
237
-
1. Let |totalBytes| be the total size of the previously-downloaded summarization capabilities, in bytes.
238
-
239
-
1. [=Assert=]: |totalBytes| is greater than 0.
242
+
1. Perform |fireProgressEvent| given 0.
240
243
241
-
1. Perform |fireProgressEvent| given 0 and |totalBytes|.
242
-
243
-
1. Perform |fireProgressEvent| given |totalBytes| and |totalBytes|.
244
+
1. Perform |fireProgressEvent| given 1.
244
245
245
246
1. [=Finalize summarizer creation=] given |promise| and |options|.
246
247
@@ -254,7 +255,7 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
254
255
255
256
1. Let |lastProgressTime| be the [=monotonic clock=]'s [=monotonic clock/unsafe current time=].
256
257
257
-
1. Perform |fireProgressEvent| given 0 and |totalBytes|.
258
+
1. Perform |fireProgressEvent| given 0.
258
259
259
260
1. While true:
260
261
@@ -266,10 +267,105 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
266
267
267
268
1. [=Assert=]: |bytesSoFar| is greater than 0 and less than or equal to |totalBytes|.
268
269
269
-
1. Perform |fireProgressEvent| given |bytesSoFar| and |totalBytes|.
270
+
1. Let |rawProgressFraction| be |bytesSoFar| divided by |totalBytes|.
271
+
272
+
1. Let |progressFraction| be [$floor$](|rawProgressFraction| × 65,536) ÷ 65,536.
273
+
274
+
1. Perform |fireProgressEvent| given |progressFraction|.
275
+
276
+
<div class="note">
277
+
<p>We use a fraction, instead of firing a progress event with the number of bytes downloaded, to avoid giving precise information about the size of the model or other material being downloaded.</p>
278
+
279
+
<p>|progressFraction| is calculated from |rawProgressFraction| to give a precision of one part in 2<sup>16</sup>. This ensures that over most internet speeds and with most model sizes, the {{ProgressEvent/loaded}} value will be different from the previous one that was fired ~50 milliseconds ago.</p>
280
+
281
+
<details>
282
+
<summary>Full calculation</summary>
283
+
284
+
<p>Assume a 5 GiB download size, and a 20 Mbps download speed (chosen as a number on the lower range from [this source](https://worldpopulationreview.com/country-rankings/internet-speeds-by-country)). Then, downloading 5 GiB will take:</p>
285
+
286
+
<math style="display:block math">
287
+
<mtable>
288
+
<mtr>
289
+
<mtd></mtd>
290
+
<mtd style="text-align: left">
291
+
<mn>5</mn>
292
+
<mtext> GiB</mtext>
293
+
294
+
<mo>×</mo>
295
+
<mfrac>
296
+
<mrow>
297
+
<msup>
298
+
<mn>2</mn>
299
+
<mn>30</mn>
300
+
</msup>
301
+
<mtext> bytes</mtext>
302
+
</mrow>
303
+
<mtext>GiB</mtext>
304
+
</mfrac>
305
+
306
+
<mo>×</mo>
307
+
<mfrac>
308
+
<mrow>
309
+
<mn>8</mn>
310
+
<mtext> bits</mtext>
311
+
</mrow>
312
+
<mtext>bytes</mtext>
313
+
</mfrac>
314
+
315
+
<mo>÷</mo>
316
+
<mfrac>
317
+
<mrow>
318
+
<mn>20</mn>
319
+
<mo>×</mo>
320
+
<msup>
321
+
<mn>10</mn>
322
+
<mn>6</mn>
323
+
</msup>
324
+
<mtext> bits</mtext>
325
+
</mrow>
326
+
<mtext>s</mtext>
327
+
</mfrac>
328
+
329
+
<mo>×</mo>
330
+
<mfrac>
331
+
<mrow>
332
+
<mn>1000</mn>
333
+
<mtext> ms</mtext>
334
+
</mrow>
335
+
<mtext>s</mtext>
336
+
</mfrac>
337
+
338
+
<mo>÷</mo>
339
+
<mfrac>
340
+
<mrow>
341
+
<mn>50</mn>
342
+
<mtext> ms</mtext>
343
+
</mrow>
344
+
<mtext>interval</mtext>
345
+
</mfrac>
346
+
</mtd>
347
+
</mtr>
348
+
349
+
<mtr>
350
+
<mtd>
351
+
<mo>=</mo>
352
+
</mtd>
353
+
<mtd style="text-align: left">
354
+
<mn>49,950</mn>
355
+
<mtext> intervals</mtext>
356
+
</mtd>
357
+
</mtr>
358
+
</mtable>
359
+
</math>
360
+
361
+
Rounding up to the nearest power of two gives a conservative estimate of 65,536 fifty millisecond intervals, so we want to give progress to 1 part in 2<sup>16</sup>.
362
+
</details>
363
+
</div>
270
364
271
365
1. If |bytesSoFar| equals |totalBytes|, then [=iteration/break=].
272
366
367
+
<p class="note">Since this is the only exit condition for the loop, we are guaranteed to fire a {{AICreateMonitor/downloadprogress}} event for the 100% mark.</p>
368
+
273
369
1. Set |lastProgressTime| to the [=monotonic clock=]'s [=monotonic clock/unsafe current time=].
274
370
275
371
1. Otherwise, if downloading has failed and cannot continue, then:
0 commit comments