@alexkroman1/aai-cli 6.7.1 → 6.8.0

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.
@@ -13,8 +13,8 @@
13
13
  "publish:agent": "aai publish"
14
14
  },
15
15
  "dependencies": {
16
- "@alexkroman1/aai": "^6.7.1",
17
- "@alexkroman1/aai-ui": "^6.7.1",
16
+ "@alexkroman1/aai": "^6.8.0",
17
+ "@alexkroman1/aai-ui": "^6.8.0",
18
18
  "@workflow/world-postgres": "4.3.3",
19
19
  "react": "^19.2.8",
20
20
  "react-dom": "^19.2.8",
@@ -23,7 +23,7 @@
23
23
  "zod": "^4.4.3"
24
24
  },
25
25
  "devDependencies": {
26
- "@alexkroman1/aai-cli": "^6.7.1",
26
+ "@alexkroman1/aai-cli": "^6.8.0",
27
27
  "@tailwindcss/vite": "^4.3.3",
28
28
  "@types/node": "^26.2.0",
29
29
  "@types/react": "^19.2.18",
@@ -107,6 +107,32 @@
107
107
  * the same `<Form>` — every field in `@alexkroman1/aai-ui` is a plain named
108
108
  * control, so declared and hand-written ones mix freely.
109
109
  *
110
+ * ## A long upload can be PAUSED, and survives the agent restarting
111
+ *
112
+ * The same wait is the one thing on this page a person may want to interrupt: a
113
+ * 600 MB recording is minutes of a laptop's uplink, and needing it back for a
114
+ * call should not cost the upload. So `<UploadProgressBar>` takes the hook's
115
+ * `pauseUpload`/`resumeUpload` and draws a button — every mode, because every
116
+ * mode is sending a file.
117
+ *
118
+ * **Nothing in this template implements it**, which is the part worth reading.
119
+ * Pausing is an abort plus an id: the windows already sent are stored under an
120
+ * upload id the hook minted, so resuming reads back which ranges landed and sends
121
+ * only the rest. That is the same mechanism the SDK uses on its own when a round
122
+ * fails for a reason that looks like an outage — a redeploy, a sandbox reclaimed
123
+ * on idle, `aai dev` restarting on a save — so an agent that goes away mid-upload
124
+ * is a pause nobody asked for, and the upload picks up where it stopped.
125
+ *
126
+ * The two flows differ in what a pause costs, and only in that:
127
+ *
128
+ * - **"After it uploads"** and **"the async API"** have no run yet, so a pause
129
+ * costs nothing at all. The form simply has not been submitted.
130
+ * - **"While it uploads"** has a run watching the id already, and to a run a
131
+ * paused upload is one whose `size` stopped growing — which is exactly what a
132
+ * slow uplink looks like. `workflows/stream.ts` gives that five minutes
133
+ * (`MAX_IDLE_POLLS`) before it calls the uploader gone and fails the run, so a
134
+ * pause longer than a coffee ends the run rather than the upload.
135
+ *
110
136
  * ## Two waits, ONE number
111
137
  *
112
138
  * The two bars describe the two stretches separately, and neither answers the
@@ -345,7 +371,7 @@ function TranscriptionDesk() {
345
371
  // store — so it is the SAME hook against a different workflow. Only the streaming
346
372
  // mode needs the other one, because only it needs the id before the bytes.
347
373
  const active = mode === "streaming" ? streamed : mode === "batch" ? batched : stored;
348
- const { submit, run, upload, pending, error, reset } = active;
374
+ const { submit, run, upload, pending, error, reset, pauseUpload, resumeUpload } = active;
349
375
  // History is per WORKFLOW, so the list follows the mode: two flows that produce
350
376
  // the same output are still two different things to have run, and merging them
351
377
  // would put a run under a heading that cannot explain it.
@@ -408,8 +434,10 @@ function TranscriptionDesk() {
408
434
  {/* The NAME, so the schema is fetched here rather than by this page. */}
409
435
  <WorkflowFields workflow={WORKFLOWS[mode]} />
410
436
  {/* Unguarded on purpose: it renders nothing until there are bytes in
411
- flight, and nothing again once they have landed. */}
412
- <UploadProgressBar upload={upload} />
437
+ flight, and nothing again once they have landed. The handlers are what
438
+ turn the bar into a control — see "A long upload can be PAUSED" above;
439
+ all three hooks expose the same pair, so `active` needs no branch. */}
440
+ <UploadProgressBar upload={upload} onPause={pauseUpload} onResume={resumeUpload} />
413
441
  <SubmitButton pending={pending}>Transcribe</SubmitButton>
414
442
  </Form>
415
443
 
@@ -91,9 +91,29 @@
91
91
  * realtime, so a recording long enough for the difference to matter is a recording
92
92
  * whose upload dominates either way.
93
93
  *
94
- * It grows with LENGTH rather than with size, because concurrency is capped: a
95
- * 97-minute recording is ~65 segments in ~9 rounds, and eight of those rounds happen
96
- * behind the upload instead of after it.
94
+ * Precisely: both flows are `upload + rounds x one segment`, streaming is always
95
+ * ONE round (only the last segment is left when the bytes land), and the classic
96
+ * flow is `ceil(segments / segmentConcurrency)`. So the saving is
97
+ * `(rounds - 1) x segment latency` and **it is ZERO whenever the classic fan-out
98
+ * fits in one round** — which is most files, because that width is 32:
99
+ *
100
+ * | recording | segments | classic rounds | saving |
101
+ * | --- | --- | --- | --- |
102
+ * | 12 min, 48 kHz stereo (130 MB) | 8 | 1 | none, by construction |
103
+ * | 60 min, 48 kHz stereo (660 MB) | 41 | 2 | one segment |
104
+ * | 6 h, 16 kHz mono (660 MB) | 241 | 8 | seven segments |
105
+ *
106
+ * It therefore grows with the SEGMENT COUNT, which is duration over bitrate — not
107
+ * with file size. The two 660 MB rows are the point: same bytes, same width, and
108
+ * the low-bitrate one has six times the segments and six times the benefit.
109
+ *
110
+ * This paragraph used to claim a 97-minute recording was "~65 segments in ~9
111
+ * rounds, and eight of those rounds happen behind the upload". The segment count
112
+ * was right and the rounds were not: at width 32 that is 3 rounds, so at most 2
113
+ * are hidden. It described a width of ~7, which is what `mapInBatches` and a
114
+ * smaller `BYTES_IN_FLIGHT` gave before either moved — and it overstated this
115
+ * flow's benefit about fourfold, which is exactly the expectation a reader brings
116
+ * to the mode picker and then finds unmet.
97
117
  *
98
118
  * What it always buys, at any length, is the thing a table cannot show: the page
99
119
  * shows real progress — segment timings, arriving — while the bytes are still
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-cli",
3
- "version": "6.7.1",
3
+ "version": "6.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "aai": "bin.mjs"
@@ -44,8 +44,8 @@
44
44
  "p-timeout": "^7.0.1",
45
45
  "vite": "^8.2.1",
46
46
  "zod": "^4.4.3",
47
- "@alexkroman1/aai-ui": "6.7.1",
48
- "@alexkroman1/aai": "6.7.1"
47
+ "@alexkroman1/aai-ui": "6.8.0",
48
+ "@alexkroman1/aai": "6.8.0"
49
49
  },
50
50
  "devDependencies": {
51
51
  "playwright": "^1.62.1",