Skip to content

Commit c09c6e3

Browse files
committed
Change progress to be between 0 and 1
Closes #15. Depends on whatwg/xhr#394.
1 parent da9ac67 commit c09c6e3

File tree

2 files changed

+114
-14
lines changed

2 files changed

+114
-14
lines changed

README.md

+8-4
Original file line numberDiff line numberDiff line change
@@ -213,27 +213,31 @@ if (supportsOurUseCase !== "no") {
213213

214214
### Download progress
215215

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:
217217

218218
```js
219219
const writer = await ai.writer.create({
220220
...otherOptions,
221221
monitor(m) {
222222
m.addEventListener("downloadprogress", e => {
223-
console.log(`Downloaded ${e.loaded} of ${e.total} bytes.`);
223+
console.log(`Downloaded ${e.loaded * 100}%`);
224224
});
225225
}
226226
);
227227
```
228228
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`.
230230
231231
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.
232232
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+
233237
<details>
234238
<summary>What's up with this pattern?</summary>
235239
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.
237241
238242
It is also nicely future-extensible by adding more events and properties to the `m` object.
239243

index.bs

+106-10
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ urlPrefix: https://tc39.es/ecma402/; spec: ECMA-402
2929
text: LookupMatchingLocaleByBestFit; url: sec-lookupmatchinglocalebybestfit
3030
text: IsStructurallyValidLanguageTag; url: sec-isstructurallyvalidlanguagetag
3131
text: CanonicalizeUnicodeLocaleId; url: sec-canonicalizeunicodelocaleid
32+
urlPrefix: https://tc39.es/ecma262/; spec: ECMA-262
33+
type: abstract-op
34+
text: floor; url: eqn-floor
3235
</pre>
3336

3437
<style>
@@ -191,13 +194,15 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
191194

192195
If this throws an exception |e|, catch it, and return [=a promise rejected with=] |e|.
193196

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:
195198

196199
1. [=Assert=]: this algorithm is running [=in parallel=].
197200

198201
1. [=Queue a global task=] on the [=AI task source=] given [=this=]'s [=relevant global object=] to perform the following steps:
199202

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>
201206

202207
1. Let |abortedDuringDownload| be false.
203208

@@ -234,13 +239,9 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
234239
::
235240
1. If [=initializing the summarization model=] given |promise| and |options| returns false, then abort these steps.
236241

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.
240243

241-
1. Perform |fireProgressEvent| given 0 and |totalBytes|.
242-
243-
1. Perform |fireProgressEvent| given |totalBytes| and |totalBytes|.
244+
1. Perform |fireProgressEvent| given 1.
244245

245246
1. [=Finalize summarizer creation=] given |promise| and |options|.
246247

@@ -254,7 +255,7 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
254255

255256
1. Let |lastProgressTime| be the [=monotonic clock=]'s [=monotonic clock/unsafe current time=].
256257

257-
1. Perform |fireProgressEvent| given 0 and |totalBytes|.
258+
1. Perform |fireProgressEvent| given 0.
258259

259260
1. While true:
260261

@@ -266,10 +267,105 @@ The <dfn attribute for="AI">summarizer</dfn> getter steps are to return [=this=]
266267

267268
1. [=Assert=]: |bytesSoFar| is greater than 0 and less than or equal to |totalBytes|.
268269

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| &times; 65,536) &divide; 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>&nbsp;GiB</mtext>
293+
294+
<mo>×</mo>
295+
<mfrac>
296+
<mrow>
297+
<msup>
298+
<mn>2</mn>
299+
<mn>30</mn>
300+
</msup>
301+
<mtext>&nbsp;bytes</mtext>
302+
</mrow>
303+
<mtext>GiB</mtext>
304+
</mfrac>
305+
306+
<mo>×</mo>
307+
<mfrac>
308+
<mrow>
309+
<mn>8</mn>
310+
<mtext>&nbsp;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>&nbsp;bits</mtext>
325+
</mrow>
326+
<mtext>s</mtext>
327+
</mfrac>
328+
329+
<mo>×</mo>
330+
<mfrac>
331+
<mrow>
332+
<mn>1000</mn>
333+
<mtext>&nbsp;ms</mtext>
334+
</mrow>
335+
<mtext>s</mtext>
336+
</mfrac>
337+
338+
<mo>÷</mo>
339+
<mfrac>
340+
<mrow>
341+
<mn>50</mn>
342+
<mtext>&nbsp;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>&nbsp;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>
270364

271365
1. If |bytesSoFar| equals |totalBytes|, then [=iteration/break=].
272366

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+
273369
1. Set |lastProgressTime| to the [=monotonic clock=]'s [=monotonic clock/unsafe current time=].
274370

275371
1. Otherwise, if downloading has failed and cannot continue, then:

0 commit comments

Comments
 (0)