@alexkroman1/aai-ui 0.14.1 → 0.14.3
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.
- package/dist/_actor-store.d.ts +16 -0
- package/dist/_recover-run.d.ts +13 -17
- package/dist/_submission-state.d.ts +33 -69
- package/dist/_tap-to-talk-state.d.ts +2 -6
- package/dist/_workflow-form-state.d.ts +117 -0
- package/dist/default-client/assets/{audio-CABEDpag.js → audio-BvhkVqBK.js} +1 -1
- package/dist/default-client/assets/{capture-processor-X_vJUkv4.js → capture-processor-BbngzdtV.js} +1 -1
- package/dist/default-client/assets/{index-DdRGAZxZ.js → index-Bov53GC9.js} +12 -12
- package/dist/default-client/assets/{playback-processor-DdO-2G6m.js → playback-processor-zTrexiPF.js} +1 -1
- package/dist/default-client/index.html +1 -1
- package/dist/define-client.js +1 -1
- package/dist/inbox.d.ts +2 -1
- package/dist/index.js +477 -316
- package/dist/session/connection.d.ts +121 -0
- package/dist/session/handshake.d.ts +20 -33
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +1 -1
- package/dist/session/messages.d.ts +6 -0
- package/dist/session/reconnect.d.ts +5 -0
- package/dist/session/types.d.ts +3 -13
- package/dist/{session-6tIQSoak.js → session-CYdjRvbl.js} +277 -147
- package/dist/upload/index.d.ts +2 -2
- package/package.json +2 -2
- package/dist/upload/pause.d.ts +0 -31
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as AGENT_STATE_LABELS, i as SessionErrorBanner, n as ConsoleShell, r as SessionStateDot, t as ChatView } from "./chat-view-BCbI7gD4.js";
|
|
2
2
|
import { n as pageBaseUrl, r as setPageTitle, t as isTypingTarget } from "./_utils-Mi-SCFA1.js";
|
|
3
3
|
import { n as fetchClientConfig, t as buildAgentUrl } from "./client-config-C7fQrioM.js";
|
|
4
|
-
import { a as ticketCarriage, c as resolveReported, d as storageSet, f as urlSlot, i as resolveSessionTokenSync, l as storageGet, r as resolveSessionToken, s as browserClientId, t as createBrowserSession, u as storageRemove } from "./session-
|
|
4
|
+
import { a as ticketCarriage, c as resolveReported, d as storageSet, f as urlSlot, i as resolveSessionTokenSync, l as storageGet, r as resolveSessionToken, s as browserClientId, t as createBrowserSession, u as storageRemove } from "./session-CYdjRvbl.js";
|
|
5
5
|
import { ThemeProvider, useOptionalSessionCore, useSession, useSessionActions, useSessionCore, useSessionError, useSessionSelector, useSessionStatus, useTheme } from "./context.js";
|
|
6
6
|
import { a as useAgentState, i as selectAgentState, n as useToolCallStart, r as useToolResult, t as useEvent } from "./hooks-DN4gHYsj.js";
|
|
7
7
|
import { a as useConversation, i as interleave, n as Markdown, o as AutoScroll, r as ConversationView, t as MessageList } from "./message-list-DHZcLhRA.js";
|
|
@@ -17,12 +17,12 @@ import { a as useWorkflowProgress, c as isTerminal, o as useWorkflowApiRef, r as
|
|
|
17
17
|
import { mountClient, mountRoot, resolveContainer } from "./define-client.js";
|
|
18
18
|
import { errorMessage, errorMessage as errorMessage$1 } from "@alexkroman1/aai";
|
|
19
19
|
import { InboxServerFrameSchema } from "@alexkroman1/aai/protocol";
|
|
20
|
-
import { CLIENT_ID_RE,
|
|
20
|
+
import { CLIENT_ID_RE, createBackoffLoop, createEpoch } from "@alexkroman1/aai/internal";
|
|
21
21
|
import { Fragment, createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
22
22
|
import clsx from "clsx";
|
|
23
23
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
24
24
|
import { formatBytes, isRecord, omitUndefined, safeJsonParse as safeJsonParse$1 } from "@alexkroman1/aai/utils";
|
|
25
|
-
import { assign, createActor, raise, setup, stateIn } from "xstate";
|
|
25
|
+
import { assign, createActor, fromPromise, raise, setup, stateIn } from "xstate";
|
|
26
26
|
//#region src/client-tool.ts
|
|
27
27
|
/**
|
|
28
28
|
* The page's half of a `clientTool`: run the handler when the agent calls the
|
|
@@ -2160,7 +2160,8 @@ function createNoticeAssembler(busy) {
|
|
|
2160
2160
|
* half is `aai-runtime`'s `aai-runtime/src/inbox/inbox.ts`.
|
|
2161
2161
|
*
|
|
2162
2162
|
* - **Held from creation, reconnected until `close()`**, on a jittered backoff
|
|
2163
|
-
* from 1 s doubling to 30 s (`
|
|
2163
|
+
* from 1 s doubling to 30 s (`createBackoffLoop` with `jitter`), reset by
|
|
2164
|
+
* every open. The
|
|
2164
2165
|
* jitter matters here more than for most loops: an agent restart drops every
|
|
2165
2166
|
* open inbox at once, and every tab of every user comes back together.
|
|
2166
2167
|
* - **Its own socket, not partysocket.** The session's reconnecting socket
|
|
@@ -2227,23 +2228,12 @@ function createInbox(options) {
|
|
|
2227
2228
|
const subscribers = /* @__PURE__ */ new Set();
|
|
2228
2229
|
let socket;
|
|
2229
2230
|
let open = false;
|
|
2230
|
-
let closed = false;
|
|
2231
|
-
let failures = 0;
|
|
2232
|
-
let retry;
|
|
2233
2231
|
const assembler = createNoticeAssembler(() => options.busy?.() ?? false);
|
|
2234
2232
|
function setOpen(next) {
|
|
2235
2233
|
if (open === next) return;
|
|
2236
2234
|
open = next;
|
|
2237
2235
|
for (const callback of subscribers) callback();
|
|
2238
2236
|
}
|
|
2239
|
-
function scheduleRetry() {
|
|
2240
|
-
if (closed) return;
|
|
2241
|
-
failures++;
|
|
2242
|
-
retry = setTimeout(connect, jitteredBackoff(failures, {
|
|
2243
|
-
baseMs: INBOX_RECONNECT_BASE_MS,
|
|
2244
|
-
maxMs: INBOX_RECONNECT_MAX_MS
|
|
2245
|
-
}));
|
|
2246
|
-
}
|
|
2247
2237
|
function url(client, queryToken) {
|
|
2248
2238
|
const target = buildAgentUrl(options.platformUrl, "inbox");
|
|
2249
2239
|
target.protocol = target.protocol === "https:" ? "wss:" : "ws:";
|
|
@@ -2272,11 +2262,9 @@ function createInbox(options) {
|
|
|
2272
2262
|
return typeof answer === "string" || answer === void 0 ? resolveSessionTokenSync(answer, attempt) : resolveSessionToken(() => answer, attempt);
|
|
2273
2263
|
}
|
|
2274
2264
|
function connect() {
|
|
2275
|
-
retry = void 0;
|
|
2276
|
-
if (closed) return;
|
|
2277
2265
|
const client = resolveReported(options.client);
|
|
2278
2266
|
if (!(client && CLIENT_ID_RE.test(client))) {
|
|
2279
|
-
|
|
2267
|
+
loop.retry();
|
|
2280
2268
|
return;
|
|
2281
2269
|
}
|
|
2282
2270
|
const token = askToken();
|
|
@@ -2285,7 +2273,7 @@ function createInbox(options) {
|
|
|
2285
2273
|
return;
|
|
2286
2274
|
}
|
|
2287
2275
|
token.then((answer) => {
|
|
2288
|
-
if (!
|
|
2276
|
+
if (!loop.stopped()) dial(client, answer);
|
|
2289
2277
|
});
|
|
2290
2278
|
}
|
|
2291
2279
|
function dial(client, token) {
|
|
@@ -2300,7 +2288,7 @@ function createInbox(options) {
|
|
|
2300
2288
|
ws.send(JSON.stringify(out.reply));
|
|
2301
2289
|
};
|
|
2302
2290
|
ws.addEventListener("open", () => {
|
|
2303
|
-
|
|
2291
|
+
loop.reset();
|
|
2304
2292
|
setOpen(true);
|
|
2305
2293
|
});
|
|
2306
2294
|
ws.addEventListener("message", (e) => {
|
|
@@ -2317,10 +2305,15 @@ function createInbox(options) {
|
|
|
2317
2305
|
socket = void 0;
|
|
2318
2306
|
setOpen(false);
|
|
2319
2307
|
assembler.reset();
|
|
2320
|
-
|
|
2308
|
+
loop.retry();
|
|
2321
2309
|
});
|
|
2322
2310
|
}
|
|
2323
|
-
connect
|
|
2311
|
+
const loop = createBackoffLoop(connect, {
|
|
2312
|
+
baseMs: INBOX_RECONNECT_BASE_MS,
|
|
2313
|
+
maxMs: INBOX_RECONNECT_MAX_MS,
|
|
2314
|
+
jitter: true
|
|
2315
|
+
});
|
|
2316
|
+
loop.start();
|
|
2324
2317
|
return {
|
|
2325
2318
|
connected: () => open,
|
|
2326
2319
|
subscribe(callback) {
|
|
@@ -2330,8 +2323,7 @@ function createInbox(options) {
|
|
|
2330
2323
|
};
|
|
2331
2324
|
},
|
|
2332
2325
|
close() {
|
|
2333
|
-
|
|
2334
|
-
clearTimeout(retry);
|
|
2326
|
+
loop.stop();
|
|
2335
2327
|
const ws = socket;
|
|
2336
2328
|
socket = void 0;
|
|
2337
2329
|
ws?.close();
|
|
@@ -2395,100 +2387,6 @@ function createLinkedClient(options) {
|
|
|
2395
2387
|
});
|
|
2396
2388
|
}
|
|
2397
2389
|
//#endregion
|
|
2398
|
-
//#region src/_recover-run.ts
|
|
2399
|
-
/**
|
|
2400
|
-
* Finding a run again when the page has lost its id.
|
|
2401
|
-
*
|
|
2402
|
-
* A run is durable and a page is not — which `useWorkflowRun`'s doc says, and
|
|
2403
|
-
* which was only half true of the hooks above it: the run id lived in plain
|
|
2404
|
-
* `useState`, so a refresh (or a same-tab navigation, or a crashed tab) left a
|
|
2405
|
-
* live run with nothing anywhere able to name it. The run really did continue;
|
|
2406
|
-
* the person really could not get back to it.
|
|
2407
|
-
*
|
|
2408
|
-
* `StartOptions.key` is the handle that survives that, and it always was — a
|
|
2409
|
-
* caller's own name for a run, indexed by the agent, read back with
|
|
2410
|
-
* `find(workflow, key)`. What was missing is the two lines that ASK. This is
|
|
2411
|
-
* them, plus the four decisions they turn out to carry.
|
|
2412
|
-
*
|
|
2413
|
-
* ## It is a MOUNT-time act, not "whenever there is no run"
|
|
2414
|
-
*
|
|
2415
|
-
* The tempting spelling is "if we hold no run id, look one up", and it breaks
|
|
2416
|
-
* `reset()`: a form put back to its initial state holds no run id, so the next
|
|
2417
|
-
* pass would re-adopt the very run the person had just dismissed — a Clear
|
|
2418
|
-
* button that clears nothing. So the lookup runs once per mount (and again only
|
|
2419
|
-
* if the KEY changes, which is a different person's run), and every later
|
|
2420
|
-
* absence of a run id is taken at face value.
|
|
2421
|
-
*
|
|
2422
|
-
* ## The lookup NEVER wins a race against a submit
|
|
2423
|
-
*
|
|
2424
|
-
* A person who reloads and immediately submits has started the run they want,
|
|
2425
|
-
* and an answer that was already in flight names an older one. The caller
|
|
2426
|
-
* therefore adopts through `current ?? found`: the recovered id fills an empty
|
|
2427
|
-
* slot and never replaces a full one.
|
|
2428
|
-
*
|
|
2429
|
-
* ## A failed lookup is REPORTED
|
|
2430
|
-
*
|
|
2431
|
-
* The alternative is a page that quietly shows an empty form to somebody whose
|
|
2432
|
-
* run is live, who then starts a second one — the duplicated work the key
|
|
2433
|
-
* exists to prevent, and on a workflow app that is real money. A person who has
|
|
2434
|
-
* never run anything pays a banner they can ignore. Same trade as
|
|
2435
|
-
* `useWorkflows`, for the same reason: an empty answer here is a confident
|
|
2436
|
-
* false statement.
|
|
2437
|
-
*
|
|
2438
|
-
* ## It is ON by default, and it used to be opt-in
|
|
2439
|
-
*
|
|
2440
|
-
* The argument for opt-in was that a `key` on its own means only "record this
|
|
2441
|
-
* with the run" — which is what a voice agent's `ctx.workflows.start({ key })`
|
|
2442
|
-
* means, there being no page to put a run back on. A FORM is the other case: it
|
|
2443
|
-
* is the page, and losing the run is the thing it cannot recover from. Six of
|
|
2444
|
-
* six page templates wrote `useRunKey()` and `recover: true` together, which is
|
|
2445
|
-
* the same shape `session/resume-store.ts` names on the voice side — a default
|
|
2446
|
-
* in the wrong place — so `useWorkflowSubmit` now mints the key and asks.
|
|
2447
|
-
*
|
|
2448
|
-
* `enabled` remains, because `recover: false` remains: a page whose form must
|
|
2449
|
-
* always open empty says so, and then nothing here runs.
|
|
2450
|
-
*/
|
|
2451
|
-
/**
|
|
2452
|
-
* Look up the newest run for a key, once, as the component mounts.
|
|
2453
|
-
*
|
|
2454
|
-
* @param opts - See {@link RecoverRunOptions}.
|
|
2455
|
-
* @returns Whether the lookup is still out. A caller folds it into its own
|
|
2456
|
-
* `pending`, because a form offering Submit while a live run is arriving is a
|
|
2457
|
-
* form inviting a second one.
|
|
2458
|
-
*
|
|
2459
|
-
* @internal
|
|
2460
|
-
*/
|
|
2461
|
-
function useRecoveredRun(opts) {
|
|
2462
|
-
const { workflow, key, enabled, getClient } = opts;
|
|
2463
|
-
const [recovering, setRecovering] = useState(enabled);
|
|
2464
|
-
const handlers = useRef(opts);
|
|
2465
|
-
handlers.current = opts;
|
|
2466
|
-
useEffect(() => {
|
|
2467
|
-
if (!enabled) return;
|
|
2468
|
-
let cancelled = false;
|
|
2469
|
-
setRecovering(true);
|
|
2470
|
-
getClient().find(workflow, key, { limit: 1 }).then((found) => {
|
|
2471
|
-
if (cancelled) return;
|
|
2472
|
-
const newest = found[0];
|
|
2473
|
-
if (newest !== void 0) handlers.current.onFound(newest.runId);
|
|
2474
|
-
setRecovering(false);
|
|
2475
|
-
}).catch((err) => {
|
|
2476
|
-
if (cancelled) return;
|
|
2477
|
-
handlers.current.onError(errorMessage$1(err));
|
|
2478
|
-
setRecovering(false);
|
|
2479
|
-
});
|
|
2480
|
-
return () => {
|
|
2481
|
-
cancelled = true;
|
|
2482
|
-
};
|
|
2483
|
-
}, [
|
|
2484
|
-
enabled,
|
|
2485
|
-
key,
|
|
2486
|
-
workflow,
|
|
2487
|
-
getClient
|
|
2488
|
-
]);
|
|
2489
|
-
return recovering;
|
|
2490
|
-
}
|
|
2491
|
-
//#endregion
|
|
2492
2390
|
//#region src/_run-controls.ts
|
|
2493
2391
|
/**
|
|
2494
2392
|
* The two things a page does TO a run it started, bound to the run it has.
|
|
@@ -2527,6 +2425,392 @@ function useRunControls(runId, getClient) {
|
|
|
2527
2425
|
};
|
|
2528
2426
|
}
|
|
2529
2427
|
//#endregion
|
|
2428
|
+
//#region src/_recover-run.ts
|
|
2429
|
+
/**
|
|
2430
|
+
* Look up the newest run for a key.
|
|
2431
|
+
*
|
|
2432
|
+
* @returns The run's id, or `undefined` for a key with no runs. A failure
|
|
2433
|
+
* REJECTS, so the machine can report it — see "A failed lookup is REPORTED".
|
|
2434
|
+
*/
|
|
2435
|
+
async function findRecoveredRun(client, workflow, key) {
|
|
2436
|
+
return (await client.find(workflow, key, { limit: 1 }))[0]?.runId;
|
|
2437
|
+
}
|
|
2438
|
+
//#endregion
|
|
2439
|
+
//#region src/_actor-store.ts
|
|
2440
|
+
/** Start `actor` and expose it through `read`, its memoized projection. */
|
|
2441
|
+
function actorStore(actor, read) {
|
|
2442
|
+
actor.start();
|
|
2443
|
+
return {
|
|
2444
|
+
send: (event) => actor.send(event),
|
|
2445
|
+
getView: read,
|
|
2446
|
+
subscribe(listener) {
|
|
2447
|
+
const sub = actor.subscribe(() => listener());
|
|
2448
|
+
return () => sub.unsubscribe();
|
|
2449
|
+
},
|
|
2450
|
+
stop: () => actor.stop()
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
//#endregion
|
|
2454
|
+
//#region src/_workflow-form-state.ts
|
|
2455
|
+
/**
|
|
2456
|
+
* A workflow form's submission as a statechart, with no React: the mount-time
|
|
2457
|
+
* lookup, the submission in flight, the person's pause, and what the page is
|
|
2458
|
+
* left holding afterwards. `useWorkflowSubmit` and `useWorkflowStream` feed it
|
|
2459
|
+
* through `_submission-state.ts`; `_workflow-form-state.test.ts` specs it
|
|
2460
|
+
* without a renderer. The pattern is `_tap-to-talk-state.ts`'s and
|
|
2461
|
+
* `session/audio-state.ts`'s: a `setup()` machine, effects in `input`,
|
|
2462
|
+
* cancellable work as a `fromPromise` invoke.
|
|
2463
|
+
*
|
|
2464
|
+
* ```text
|
|
2465
|
+
* idle ──RECOVER──► recovering ──found──► following
|
|
2466
|
+
* ▲ │ └──none──► idle
|
|
2467
|
+
* │ └──failed──► failed
|
|
2468
|
+
* RESET (anywhere)
|
|
2469
|
+
* SUBMIT (anywhere) ──► submitting { active ⇄ paused } ──done──► following
|
|
2470
|
+
* └─failed─► failed
|
|
2471
|
+
* ```
|
|
2472
|
+
*
|
|
2473
|
+
* It replaces four `useState`s, a `startedHere` flag, a `recovering` flag and
|
|
2474
|
+
* a ref holding the live submission, and three rules that were each a patch
|
|
2475
|
+
* over a race the position now answers:
|
|
2476
|
+
*
|
|
2477
|
+
* - **A superseded submission owns nothing.** It used to be dropped by a token
|
|
2478
|
+
* compare in `end()` (`current !== token`). A `SUBMIT` now RE-ENTERS
|
|
2479
|
+
* `submitting`, which stops the old invoke: its signal aborts, which cancels
|
|
2480
|
+
* its gate, and XState drops its result. Reports from it are refused at the
|
|
2481
|
+
* producer, on the same signal — the one "is this still wanted?" test, in the
|
|
2482
|
+
* one place that knows the answer.
|
|
2483
|
+
* - **The lookup never wins a race against a submit.** It used to be
|
|
2484
|
+
* `setRunId(current => current ?? found)`. The lookup is an invoke of
|
|
2485
|
+
* `recovering` now, so a `SUBMIT` (or a `RESET`) that leaves it stops it,
|
|
2486
|
+
* and an answer that lands afterwards is an answer nobody asked for.
|
|
2487
|
+
* - **`pending` was three hooks' flags OR-ed together.** Two of the three were
|
|
2488
|
+
* positions in this machine; {@link WorkflowFormView.busy} is both. The
|
|
2489
|
+
* third, the run's own `polling`, stays outside on purpose: it is
|
|
2490
|
+
* `useWorkflowRun`'s and cannot be derived from the snapshot (see that hook).
|
|
2491
|
+
*
|
|
2492
|
+
* ## What `submitting` does not split
|
|
2493
|
+
*
|
|
2494
|
+
* Uploading and starting are not child states. The two hooks run them in
|
|
2495
|
+
* OPPOSITE orders — `useWorkflowSubmit` stores the bytes and then starts the
|
|
2496
|
+
* run, `useWorkflowStream` starts the run and then streams the bytes — so a
|
|
2497
|
+
* fixed `uploading → starting` order would be wrong for one of them, and
|
|
2498
|
+
* nothing this machine decides depends on which step is current. `active` /
|
|
2499
|
+
* `paused` is the split that does matter: it is the gate, and the bar's
|
|
2500
|
+
* `paused` flag, moving together.
|
|
2501
|
+
*/
|
|
2502
|
+
const machine$1 = setup({
|
|
2503
|
+
types: {},
|
|
2504
|
+
actors: {
|
|
2505
|
+
lookup: fromPromise(({ input }) => input.find()),
|
|
2506
|
+
/**
|
|
2507
|
+
* One submission. An invoke rather than a fire-and-forget call so that
|
|
2508
|
+
* leaving `submitting` — a superseding `SUBMIT`, a `RESET`, an unmount —
|
|
2509
|
+
* stops it, and stopping it is what abandons it.
|
|
2510
|
+
*/
|
|
2511
|
+
submission: fromPromise(async ({ input, signal }) => {
|
|
2512
|
+
const { gate, work, settle, send } = input;
|
|
2513
|
+
signal.addEventListener("abort", () => gate?.cancel(), { once: true });
|
|
2514
|
+
const live = (event) => {
|
|
2515
|
+
if (!signal.aborted) send(event);
|
|
2516
|
+
};
|
|
2517
|
+
try {
|
|
2518
|
+
await work?.({
|
|
2519
|
+
progress: (upload) => live({
|
|
2520
|
+
type: "PROGRESS",
|
|
2521
|
+
upload
|
|
2522
|
+
}),
|
|
2523
|
+
started: (runId) => live({
|
|
2524
|
+
type: "STARTED",
|
|
2525
|
+
runId
|
|
2526
|
+
})
|
|
2527
|
+
});
|
|
2528
|
+
} finally {
|
|
2529
|
+
if (signal.aborted) settle?.();
|
|
2530
|
+
}
|
|
2531
|
+
})
|
|
2532
|
+
},
|
|
2533
|
+
guards: {
|
|
2534
|
+
found: ({ event }) => "output" in event && typeof event.output === "string",
|
|
2535
|
+
hasRun: ({ context }) => context.runId !== void 0,
|
|
2536
|
+
noRun: ({ context }) => context.runId === void 0
|
|
2537
|
+
},
|
|
2538
|
+
actions: {
|
|
2539
|
+
open: assign(({ event }) => event.type === "SUBMIT" ? {
|
|
2540
|
+
gate: event.gate,
|
|
2541
|
+
work: event.work,
|
|
2542
|
+
settle: event.settle,
|
|
2543
|
+
runId: void 0,
|
|
2544
|
+
startError: void 0,
|
|
2545
|
+
upload: void 0,
|
|
2546
|
+
startedHere: true
|
|
2547
|
+
} : {}),
|
|
2548
|
+
/**
|
|
2549
|
+
* Leaving `submitting`, by whichever edge. The bar goes too: from here the
|
|
2550
|
+
* wait belongs to the RUN, and a bar left at 100% under a running workflow
|
|
2551
|
+
* reads as the thing taking the time.
|
|
2552
|
+
*/
|
|
2553
|
+
close: assign({
|
|
2554
|
+
gate: void 0,
|
|
2555
|
+
work: void 0,
|
|
2556
|
+
settle: void 0,
|
|
2557
|
+
upload: void 0
|
|
2558
|
+
}),
|
|
2559
|
+
/**
|
|
2560
|
+
* `submit()`'s promise. Before `close`, which forgets it; and before the
|
|
2561
|
+
* edge's own actions only in program order — a resolution runs its
|
|
2562
|
+
* continuation as a microtask, after the whole transition has landed, so
|
|
2563
|
+
* an awaiting caller already reads the error or the run.
|
|
2564
|
+
*/
|
|
2565
|
+
settle: ({ context }) => context.settle?.(),
|
|
2566
|
+
clear: assign({
|
|
2567
|
+
runId: void 0,
|
|
2568
|
+
startError: void 0,
|
|
2569
|
+
startedHere: false
|
|
2570
|
+
}),
|
|
2571
|
+
pauseGate: ({ context }) => context.gate?.pause(),
|
|
2572
|
+
resumeGate: ({ context }) => context.gate?.resume(),
|
|
2573
|
+
/** Folded rather than replaced: which file and how far are still true. */
|
|
2574
|
+
markPaused: assign({ upload: ({ context }) => context.upload ? {
|
|
2575
|
+
...context.upload,
|
|
2576
|
+
paused: true
|
|
2577
|
+
} : void 0 }),
|
|
2578
|
+
markResumed: assign({ upload: ({ context }) => context.upload ? {
|
|
2579
|
+
...context.upload,
|
|
2580
|
+
paused: false
|
|
2581
|
+
} : void 0 })
|
|
2582
|
+
}
|
|
2583
|
+
}).createMachine({
|
|
2584
|
+
id: "workflowForm",
|
|
2585
|
+
context: ({ input }) => ({
|
|
2586
|
+
effects: input,
|
|
2587
|
+
runId: void 0,
|
|
2588
|
+
startError: void 0,
|
|
2589
|
+
upload: void 0,
|
|
2590
|
+
startedHere: false,
|
|
2591
|
+
gate: void 0,
|
|
2592
|
+
work: void 0,
|
|
2593
|
+
settle: void 0
|
|
2594
|
+
}),
|
|
2595
|
+
initial: "idle",
|
|
2596
|
+
on: {
|
|
2597
|
+
SUBMIT: {
|
|
2598
|
+
target: ".submitting",
|
|
2599
|
+
reenter: true,
|
|
2600
|
+
actions: "open"
|
|
2601
|
+
},
|
|
2602
|
+
RESET: {
|
|
2603
|
+
target: ".idle",
|
|
2604
|
+
actions: "clear"
|
|
2605
|
+
}
|
|
2606
|
+
},
|
|
2607
|
+
states: {
|
|
2608
|
+
idle: { on: { RECOVER: "recovering" } },
|
|
2609
|
+
/**
|
|
2610
|
+
* The mount-time lookup (`_recover-run.ts`). Leaving by any other edge —
|
|
2611
|
+
* a submit, a reset — stops it, so a late answer adopts nothing.
|
|
2612
|
+
*/
|
|
2613
|
+
recovering: {
|
|
2614
|
+
entry: assign({ startError: void 0 }),
|
|
2615
|
+
invoke: {
|
|
2616
|
+
src: "lookup",
|
|
2617
|
+
input: ({ context }) => context.effects,
|
|
2618
|
+
onDone: [{
|
|
2619
|
+
guard: "found",
|
|
2620
|
+
target: "following",
|
|
2621
|
+
actions: assign({ runId: ({ event }) => event.output })
|
|
2622
|
+
}, { target: "idle" }],
|
|
2623
|
+
onError: {
|
|
2624
|
+
target: "failed",
|
|
2625
|
+
actions: assign({ startError: ({ event }) => errorMessage$1(event.error) })
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2629
|
+
submitting: {
|
|
2630
|
+
initial: "active",
|
|
2631
|
+
exit: ["settle", "close"],
|
|
2632
|
+
invoke: {
|
|
2633
|
+
src: "submission",
|
|
2634
|
+
input: ({ context, self }) => ({
|
|
2635
|
+
gate: context.gate,
|
|
2636
|
+
work: context.work,
|
|
2637
|
+
settle: context.settle,
|
|
2638
|
+
send: (event) => {
|
|
2639
|
+
self.send(event);
|
|
2640
|
+
}
|
|
2641
|
+
}),
|
|
2642
|
+
onDone: [{
|
|
2643
|
+
guard: "hasRun",
|
|
2644
|
+
target: "following"
|
|
2645
|
+
}, { target: "idle" }],
|
|
2646
|
+
onError: {
|
|
2647
|
+
target: "failed",
|
|
2648
|
+
actions: assign({ startError: ({ event }) => errorMessage$1(event.error) })
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
on: {
|
|
2652
|
+
PROGRESS: { actions: assign({ upload: ({ event }) => event.upload }) },
|
|
2653
|
+
STARTED: { actions: assign({ runId: ({ event }) => event.runId }) }
|
|
2654
|
+
},
|
|
2655
|
+
states: {
|
|
2656
|
+
active: { on: { PAUSE: {
|
|
2657
|
+
target: "paused",
|
|
2658
|
+
actions: ["pauseGate", "markPaused"]
|
|
2659
|
+
} } },
|
|
2660
|
+
paused: { on: { RESUME: {
|
|
2661
|
+
target: "active",
|
|
2662
|
+
actions: ["resumeGate", "markResumed"]
|
|
2663
|
+
} } }
|
|
2664
|
+
}
|
|
2665
|
+
},
|
|
2666
|
+
/** A run on the page — started here, or adopted by the lookup. */
|
|
2667
|
+
following: {},
|
|
2668
|
+
/**
|
|
2669
|
+
* The submission's or the lookup's own failure. A stream submission can
|
|
2670
|
+
* land here WITH a run (started, then its upload failed and it was
|
|
2671
|
+
* cancelled), which is why `RECOVER` is guarded rather than plain.
|
|
2672
|
+
*/
|
|
2673
|
+
failed: { on: { RECOVER: {
|
|
2674
|
+
guard: "noRun",
|
|
2675
|
+
target: "recovering"
|
|
2676
|
+
} } }
|
|
2677
|
+
}
|
|
2678
|
+
});
|
|
2679
|
+
/** Start a machine over `effects`. */
|
|
2680
|
+
function createWorkflowForm(effects) {
|
|
2681
|
+
const actor = createActor(machine$1, { input: effects });
|
|
2682
|
+
let view = {
|
|
2683
|
+
runId: void 0,
|
|
2684
|
+
startError: void 0,
|
|
2685
|
+
upload: void 0,
|
|
2686
|
+
startedHere: false,
|
|
2687
|
+
busy: false
|
|
2688
|
+
};
|
|
2689
|
+
const read = () => {
|
|
2690
|
+
const at = actor.getSnapshot();
|
|
2691
|
+
const { runId, startError, upload, startedHere } = at.context;
|
|
2692
|
+
const busy = at.matches("recovering") || at.matches("submitting");
|
|
2693
|
+
if (runId !== view.runId || startError !== view.startError || upload !== view.upload || startedHere !== view.startedHere || busy !== view.busy) view = {
|
|
2694
|
+
runId,
|
|
2695
|
+
startError,
|
|
2696
|
+
upload,
|
|
2697
|
+
startedHere,
|
|
2698
|
+
busy
|
|
2699
|
+
};
|
|
2700
|
+
return view;
|
|
2701
|
+
};
|
|
2702
|
+
return actorStore(actor, read);
|
|
2703
|
+
}
|
|
2704
|
+
//#endregion
|
|
2705
|
+
//#region src/_submission-state.ts
|
|
2706
|
+
/**
|
|
2707
|
+
* The state a form submission carries, for both hooks that make one: the
|
|
2708
|
+
* form's statechart (`_workflow-form-state.ts`) bridged into React with
|
|
2709
|
+
* `useSyncExternalStore`, the way `use-tap-to-talk.ts` bridges its own.
|
|
2710
|
+
*
|
|
2711
|
+
* `useWorkflowSubmit` and `useWorkflowStream` stay separate hooks — they differ
|
|
2712
|
+
* in WHEN the run is created relative to the bytes, which is the whole reason
|
|
2713
|
+
* the streaming one exists — but everything around that ordering is shared:
|
|
2714
|
+
* the run id, the submission's own failure, the bar, `startedHere`, the
|
|
2715
|
+
* supersede rule, `reset` and the pause pair. Each hook still owns its own
|
|
2716
|
+
* submission BODY, handed to {@link SubmissionActions.submit}.
|
|
2717
|
+
*
|
|
2718
|
+
* Nothing here decides anything. The decisions are positions of the machine,
|
|
2719
|
+
* which is why this module holds no token, no ref to a live submission, and no
|
|
2720
|
+
* "is this still the current one?" check.
|
|
2721
|
+
*/
|
|
2722
|
+
const IDLE_VIEW$1 = {
|
|
2723
|
+
runId: void 0,
|
|
2724
|
+
startError: void 0,
|
|
2725
|
+
upload: void 0,
|
|
2726
|
+
startedHere: false,
|
|
2727
|
+
busy: false
|
|
2728
|
+
};
|
|
2729
|
+
/**
|
|
2730
|
+
* The frames before the machine exists, for a page that will look its run up.
|
|
2731
|
+
* Busy from the FIRST render rather than from the effect, so there is no frame
|
|
2732
|
+
* in which a page about to adopt a run reads as idle.
|
|
2733
|
+
*/
|
|
2734
|
+
const RECOVERING_VIEW = {
|
|
2735
|
+
...IDLE_VIEW$1,
|
|
2736
|
+
busy: true
|
|
2737
|
+
};
|
|
2738
|
+
const NO_SUBSCRIPTION$1 = () => () => void 0;
|
|
2739
|
+
const idleView = () => IDLE_VIEW$1;
|
|
2740
|
+
const recoveringView = () => RECOVERING_VIEW;
|
|
2741
|
+
/** What a lookup is FOR, so a re-render asking the same question asks nothing. */
|
|
2742
|
+
function questionOf(recover) {
|
|
2743
|
+
return recover?.enabled ? `${recover.workflow}\n${recover.key}` : void 0;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* The shared submission scaffold. See the module doc.
|
|
2747
|
+
*
|
|
2748
|
+
* @param recover - The mount-time lookup (`_recover-run.ts`), or nothing for a
|
|
2749
|
+
* hook that refuses it (`useWorkflowStream`).
|
|
2750
|
+
*/
|
|
2751
|
+
function useSubmissionState(recover) {
|
|
2752
|
+
const lookup = useRef(recover);
|
|
2753
|
+
lookup.current = recover;
|
|
2754
|
+
const asked = useRef(void 0);
|
|
2755
|
+
const live = useRef(null);
|
|
2756
|
+
const [store, setStore] = useState(null);
|
|
2757
|
+
useEffect(() => {
|
|
2758
|
+
const next = createWorkflowForm({ find: async () => {
|
|
2759
|
+
const at = lookup.current;
|
|
2760
|
+
if (!at) return;
|
|
2761
|
+
return await findRecoveredRun(at.getClient(), at.workflow, at.key);
|
|
2762
|
+
} });
|
|
2763
|
+
asked.current = questionOf(lookup.current);
|
|
2764
|
+
if (asked.current !== void 0) next.send({ type: "RECOVER" });
|
|
2765
|
+
live.current = next;
|
|
2766
|
+
setStore(next);
|
|
2767
|
+
return () => {
|
|
2768
|
+
next.stop();
|
|
2769
|
+
live.current = null;
|
|
2770
|
+
asked.current = void 0;
|
|
2771
|
+
setStore(null);
|
|
2772
|
+
};
|
|
2773
|
+
}, []);
|
|
2774
|
+
const question = questionOf(recover);
|
|
2775
|
+
useEffect(() => {
|
|
2776
|
+
if (!store || question === asked.current) return;
|
|
2777
|
+
asked.current = question;
|
|
2778
|
+
if (question !== void 0) store.send({ type: "RECOVER" });
|
|
2779
|
+
}, [store, question]);
|
|
2780
|
+
const beforeStore = question === void 0 ? idleView : recoveringView;
|
|
2781
|
+
const view = useSyncExternalStore(store?.subscribe ?? NO_SUBSCRIPTION$1, store ? store.getView : beforeStore);
|
|
2782
|
+
const submit = useCallback((gate, work) => {
|
|
2783
|
+
const machine = live.current;
|
|
2784
|
+
if (!machine) return Promise.resolve();
|
|
2785
|
+
const done = Promise.withResolvers();
|
|
2786
|
+
machine.send({
|
|
2787
|
+
type: "SUBMIT",
|
|
2788
|
+
gate,
|
|
2789
|
+
work,
|
|
2790
|
+
settle: () => done.resolve()
|
|
2791
|
+
});
|
|
2792
|
+
return done.promise;
|
|
2793
|
+
}, []);
|
|
2794
|
+
const reset = useCallback(() => live.current?.send({ type: "RESET" }), []);
|
|
2795
|
+
const pauseUpload = useCallback(() => live.current?.send({ type: "PAUSE" }), []);
|
|
2796
|
+
const resumeUpload = useCallback(() => live.current?.send({ type: "RESUME" }), []);
|
|
2797
|
+
const actions = useMemo(() => ({
|
|
2798
|
+
submit,
|
|
2799
|
+
reset,
|
|
2800
|
+
pauseUpload,
|
|
2801
|
+
resumeUpload
|
|
2802
|
+
}), [
|
|
2803
|
+
submit,
|
|
2804
|
+
reset,
|
|
2805
|
+
pauseUpload,
|
|
2806
|
+
resumeUpload
|
|
2807
|
+
]);
|
|
2808
|
+
return {
|
|
2809
|
+
...view,
|
|
2810
|
+
actions
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
//#endregion
|
|
2530
2814
|
//#region src/_workflow-files.ts
|
|
2531
2815
|
/**
|
|
2532
2816
|
* Which of a submitted form's values are FILES.
|
|
@@ -2739,44 +3023,44 @@ async function sendThroughGate(gate, send) {
|
|
|
2739
3023
|
*/
|
|
2740
3024
|
function createUploadGate() {
|
|
2741
3025
|
let controller = new AbortController();
|
|
2742
|
-
let
|
|
2743
|
-
let cancelled = false;
|
|
3026
|
+
let phase = "running";
|
|
2744
3027
|
let open;
|
|
2745
3028
|
let closed;
|
|
3029
|
+
/** Wake an uploader parked on `settle()`, whichever way the gate is opening. */
|
|
3030
|
+
const release = () => {
|
|
3031
|
+
open?.();
|
|
3032
|
+
open = void 0;
|
|
3033
|
+
closed = void 0;
|
|
3034
|
+
};
|
|
2746
3035
|
return {
|
|
2747
3036
|
get paused() {
|
|
2748
|
-
return paused;
|
|
3037
|
+
return phase === "paused";
|
|
2749
3038
|
},
|
|
2750
3039
|
get cancelled() {
|
|
2751
|
-
return cancelled;
|
|
3040
|
+
return phase === "cancelled";
|
|
2752
3041
|
},
|
|
2753
3042
|
get signal() {
|
|
2754
3043
|
return controller.signal;
|
|
2755
3044
|
},
|
|
2756
3045
|
pause() {
|
|
2757
|
-
if (
|
|
2758
|
-
|
|
3046
|
+
if (phase !== "running") return;
|
|
3047
|
+
phase = "paused";
|
|
2759
3048
|
const gate = Promise.withResolvers();
|
|
2760
3049
|
closed = gate.promise;
|
|
2761
3050
|
open = gate.resolve;
|
|
2762
3051
|
controller.abort();
|
|
2763
3052
|
},
|
|
2764
3053
|
resume() {
|
|
2765
|
-
if (
|
|
2766
|
-
|
|
3054
|
+
if (phase !== "paused") return;
|
|
3055
|
+
phase = "running";
|
|
2767
3056
|
controller = new AbortController();
|
|
2768
|
-
|
|
2769
|
-
open = void 0;
|
|
2770
|
-
closed = void 0;
|
|
3057
|
+
release();
|
|
2771
3058
|
},
|
|
2772
3059
|
cancel() {
|
|
2773
|
-
if (cancelled) return;
|
|
2774
|
-
|
|
2775
|
-
paused = false;
|
|
3060
|
+
if (phase === "cancelled") return;
|
|
3061
|
+
phase = "cancelled";
|
|
2776
3062
|
controller.abort();
|
|
2777
|
-
|
|
2778
|
-
open = void 0;
|
|
2779
|
-
closed = void 0;
|
|
3063
|
+
release();
|
|
2780
3064
|
},
|
|
2781
3065
|
async settle() {
|
|
2782
3066
|
if (closed) await closed;
|
|
@@ -2950,46 +3234,6 @@ async function uploadFiles(api, input, report, parallel, session) {
|
|
|
2950
3234
|
return out;
|
|
2951
3235
|
}
|
|
2952
3236
|
//#endregion
|
|
2953
|
-
//#region src/upload/pause.ts
|
|
2954
|
-
/**
|
|
2955
|
-
* The two buttons a page puts over a live upload, bound to whichever gate the
|
|
2956
|
-
* submission is currently holding.
|
|
2957
|
-
*
|
|
2958
|
-
* `useWorkflowSubmit` and `useWorkflowStream` both own an {@link UploadGate} and
|
|
2959
|
-
* both reported the pause the same way: park the gate, then fold `paused` into
|
|
2960
|
-
* the status the bar is already drawing. Folded rather than replaced, because
|
|
2961
|
-
* everything else about that status — which file, how far, of how many — is
|
|
2962
|
-
* still true. Two copies of that rule are two copies that can stop agreeing
|
|
2963
|
-
* about what a paused bar says.
|
|
2964
|
-
*/
|
|
2965
|
-
/**
|
|
2966
|
-
* Bind pause/resume to the live submission's gate.
|
|
2967
|
-
*
|
|
2968
|
-
* @param getGate - Reads the gate out of the caller's ref, so the callbacks stay
|
|
2969
|
-
* stable across submissions rather than being re-created per gate.
|
|
2970
|
-
* @param setUpload - The status setter the progress bar renders from.
|
|
2971
|
-
*
|
|
2972
|
-
* @internal
|
|
2973
|
-
*/
|
|
2974
|
-
function useUploadPause(getGate, setUpload) {
|
|
2975
|
-
const setPaused = useCallback((paused) => {
|
|
2976
|
-
setUpload((current) => current ? {
|
|
2977
|
-
...current,
|
|
2978
|
-
paused
|
|
2979
|
-
} : current);
|
|
2980
|
-
}, [setUpload]);
|
|
2981
|
-
return {
|
|
2982
|
-
pauseUpload: useCallback(() => {
|
|
2983
|
-
getGate()?.pause();
|
|
2984
|
-
setPaused(true);
|
|
2985
|
-
}, [getGate, setPaused]),
|
|
2986
|
-
resumeUpload: useCallback(() => {
|
|
2987
|
-
getGate()?.resume();
|
|
2988
|
-
setPaused(false);
|
|
2989
|
-
}, [getGate, setPaused])
|
|
2990
|
-
};
|
|
2991
|
-
}
|
|
2992
|
-
//#endregion
|
|
2993
3237
|
//#region src/upload/report.ts
|
|
2994
3238
|
/**
|
|
2995
3239
|
* What the bar draws, as a comparable string.
|
|
@@ -3026,59 +3270,6 @@ function coalesceUploadReports(set) {
|
|
|
3026
3270
|
};
|
|
3027
3271
|
}
|
|
3028
3272
|
//#endregion
|
|
3029
|
-
//#region src/_submission-state.ts
|
|
3030
|
-
/** The shared submission scaffold. See the module doc. */
|
|
3031
|
-
function useSubmissionState() {
|
|
3032
|
-
const [runId, setRunId] = useState(void 0);
|
|
3033
|
-
const [starting, setStarting] = useState(false);
|
|
3034
|
-
const [startError, setStartError] = useState(void 0);
|
|
3035
|
-
const [upload, setUpload] = useState(void 0);
|
|
3036
|
-
const current = useRef(void 0);
|
|
3037
|
-
const begin = useCallback((token) => {
|
|
3038
|
-
setStarting(true);
|
|
3039
|
-
setStartError(void 0);
|
|
3040
|
-
setRunId(void 0);
|
|
3041
|
-
current.current?.gate.cancel();
|
|
3042
|
-
current.current = token;
|
|
3043
|
-
}, []);
|
|
3044
|
-
const end = useCallback((token) => {
|
|
3045
|
-
if (current.current !== token) return;
|
|
3046
|
-
current.current = void 0;
|
|
3047
|
-
setStarting(false);
|
|
3048
|
-
setUpload(void 0);
|
|
3049
|
-
}, []);
|
|
3050
|
-
const reset = useCallback(() => {
|
|
3051
|
-
current.current?.gate.cancel();
|
|
3052
|
-
current.current = void 0;
|
|
3053
|
-
setRunId(void 0);
|
|
3054
|
-
setStartError(void 0);
|
|
3055
|
-
setUpload(void 0);
|
|
3056
|
-
}, []);
|
|
3057
|
-
const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => current.current?.gate, []), setUpload);
|
|
3058
|
-
return {
|
|
3059
|
-
runId,
|
|
3060
|
-
starting,
|
|
3061
|
-
startError,
|
|
3062
|
-
upload,
|
|
3063
|
-
actions: useMemo(() => ({
|
|
3064
|
-
setRunId,
|
|
3065
|
-
setStartError,
|
|
3066
|
-
setUpload,
|
|
3067
|
-
begin,
|
|
3068
|
-
end,
|
|
3069
|
-
reset,
|
|
3070
|
-
pauseUpload,
|
|
3071
|
-
resumeUpload
|
|
3072
|
-
}), [
|
|
3073
|
-
begin,
|
|
3074
|
-
end,
|
|
3075
|
-
reset,
|
|
3076
|
-
pauseUpload,
|
|
3077
|
-
resumeUpload
|
|
3078
|
-
])
|
|
3079
|
-
};
|
|
3080
|
-
}
|
|
3081
|
-
//#endregion
|
|
3082
3273
|
//#region src/use-run-key.ts
|
|
3083
3274
|
/**
|
|
3084
3275
|
* The handle a page keeps on the runs it started, across a reload.
|
|
@@ -3304,42 +3495,31 @@ function useRunKey(options = {}) {
|
|
|
3304
3495
|
function useWorkflowSubmit(workflow, options = {}) {
|
|
3305
3496
|
const { api, recover = true, wait, intervalMs, parallel } = options;
|
|
3306
3497
|
const key = useDefaultRunKey(options.key);
|
|
3307
|
-
const state = useSubmissionState();
|
|
3308
|
-
const { runId, actions } = state;
|
|
3309
3498
|
const getClient = useWorkflowApiRef(api);
|
|
3499
|
+
const form = useSubmissionState({
|
|
3500
|
+
workflow,
|
|
3501
|
+
key,
|
|
3502
|
+
enabled: recover,
|
|
3503
|
+
getClient
|
|
3504
|
+
});
|
|
3505
|
+
const { runId, actions } = form;
|
|
3310
3506
|
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
3311
3507
|
api,
|
|
3312
3508
|
intervalMs
|
|
3313
3509
|
}));
|
|
3314
3510
|
const { wake, cancel } = useRunControls(runId, getClient);
|
|
3315
|
-
const
|
|
3316
|
-
const recovering = useRecoveredRun({
|
|
3317
|
-
workflow,
|
|
3318
|
-
key,
|
|
3319
|
-
enabled: recover,
|
|
3320
|
-
getClient,
|
|
3321
|
-
onFound: (found) => {
|
|
3322
|
-
actions.setRunId((current) => current ?? found);
|
|
3323
|
-
},
|
|
3324
|
-
onError: actions.setStartError
|
|
3325
|
-
});
|
|
3326
|
-
const submit = useCallback(async (input) => {
|
|
3511
|
+
const submit = useCallback((input) => {
|
|
3327
3512
|
const client = getClient();
|
|
3328
|
-
const
|
|
3329
|
-
|
|
3330
|
-
actions.begin(current);
|
|
3331
|
-
try {
|
|
3513
|
+
const session = createUploadSession(workflow);
|
|
3514
|
+
return actions.submit(session.gate, async ({ progress, started }) => {
|
|
3332
3515
|
const options = omitUndefined({ key });
|
|
3333
|
-
const
|
|
3334
|
-
|
|
3516
|
+
const report = coalesceUploadReports(progress);
|
|
3517
|
+
const stored = await uploadFiles(client, input, report, parallel, session);
|
|
3518
|
+
started(wait === void 0 ? await client.start(workflow, stored, options) : (await client.startAndWait(workflow, stored, {
|
|
3335
3519
|
...options,
|
|
3336
3520
|
wait
|
|
3337
3521
|
})).runId);
|
|
3338
|
-
}
|
|
3339
|
-
if (!current.gate.cancelled) actions.setStartError(errorMessage$1(err));
|
|
3340
|
-
} finally {
|
|
3341
|
-
actions.end(current);
|
|
3342
|
-
}
|
|
3522
|
+
});
|
|
3343
3523
|
}, [
|
|
3344
3524
|
workflow,
|
|
3345
3525
|
key,
|
|
@@ -3351,19 +3531,16 @@ function useWorkflowSubmit(workflow, options = {}) {
|
|
|
3351
3531
|
return {
|
|
3352
3532
|
submit,
|
|
3353
3533
|
submitForm: submit,
|
|
3354
|
-
reset:
|
|
3355
|
-
setStartedHere(false);
|
|
3356
|
-
actions.reset();
|
|
3357
|
-
}, [actions]),
|
|
3534
|
+
reset: actions.reset,
|
|
3358
3535
|
wake,
|
|
3359
3536
|
cancel,
|
|
3360
3537
|
pauseUpload: actions.pauseUpload,
|
|
3361
3538
|
resumeUpload: actions.resumeUpload,
|
|
3362
3539
|
run: tracked.run,
|
|
3363
|
-
startedHere,
|
|
3364
|
-
pending:
|
|
3365
|
-
upload:
|
|
3366
|
-
error:
|
|
3540
|
+
startedHere: form.startedHere,
|
|
3541
|
+
pending: form.busy || tracked.polling,
|
|
3542
|
+
upload: form.upload,
|
|
3543
|
+
error: form.startError ?? tracked.error
|
|
3367
3544
|
};
|
|
3368
3545
|
}
|
|
3369
3546
|
//#endregion
|
|
@@ -4926,16 +5103,7 @@ function createTapToTalk(effects, timing) {
|
|
|
4926
5103
|
};
|
|
4927
5104
|
return view;
|
|
4928
5105
|
};
|
|
4929
|
-
actor
|
|
4930
|
-
return {
|
|
4931
|
-
send: (event) => actor.send(event),
|
|
4932
|
-
getView: read,
|
|
4933
|
-
subscribe(listener) {
|
|
4934
|
-
const sub = actor.subscribe(() => listener());
|
|
4935
|
-
return () => sub.unsubscribe();
|
|
4936
|
-
},
|
|
4937
|
-
stop: () => actor.stop()
|
|
4938
|
-
};
|
|
5106
|
+
return actorStore(actor, read);
|
|
4939
5107
|
}
|
|
4940
5108
|
//#endregion
|
|
4941
5109
|
//#region src/use-tap-to-talk.ts
|
|
@@ -5338,50 +5506,46 @@ function useWorkflowRuns(workflow, options = {}) {
|
|
|
5338
5506
|
*/
|
|
5339
5507
|
function useWorkflowStream(workflow, options = {}) {
|
|
5340
5508
|
const { api, key, intervalMs, parallel } = options;
|
|
5341
|
-
const state = useSubmissionState();
|
|
5342
|
-
const { runId, actions } = state;
|
|
5343
5509
|
const getClient = useWorkflowApiRef(api);
|
|
5510
|
+
const form = useSubmissionState();
|
|
5511
|
+
const { runId, actions } = form;
|
|
5344
5512
|
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
5345
5513
|
api,
|
|
5346
5514
|
intervalMs
|
|
5347
5515
|
}));
|
|
5348
5516
|
const { wake, cancel } = useRunControls(runId, getClient);
|
|
5349
|
-
const
|
|
5350
|
-
const submit = useCallback(async (input) => {
|
|
5517
|
+
const submit = useCallback((input) => {
|
|
5351
5518
|
const client = getClient();
|
|
5352
|
-
const
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
})
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
} finally {
|
|
5383
|
-
actions.end(current);
|
|
5384
|
-
}
|
|
5519
|
+
const gate = createUploadGate();
|
|
5520
|
+
return actions.submit(gate, async ({ progress, started: adopt }) => {
|
|
5521
|
+
let started;
|
|
5522
|
+
try {
|
|
5523
|
+
const id = randomUploadId();
|
|
5524
|
+
const begun = await beginRun({
|
|
5525
|
+
client,
|
|
5526
|
+
workflow,
|
|
5527
|
+
input,
|
|
5528
|
+
id,
|
|
5529
|
+
...omitUndefined({ key })
|
|
5530
|
+
});
|
|
5531
|
+
started = begun.runId;
|
|
5532
|
+
const chosen = begun.file;
|
|
5533
|
+
adopt(started);
|
|
5534
|
+
if (!chosen) return;
|
|
5535
|
+
await streamFile({
|
|
5536
|
+
client,
|
|
5537
|
+
gate,
|
|
5538
|
+
id,
|
|
5539
|
+
file: chosen,
|
|
5540
|
+
parallel,
|
|
5541
|
+
report: coalesceUploadReports(progress)
|
|
5542
|
+
});
|
|
5543
|
+
await client.wake(started).catch(() => void 0);
|
|
5544
|
+
} catch (err) {
|
|
5545
|
+
if (started) await client.cancel(started).catch(() => void 0);
|
|
5546
|
+
throw err;
|
|
5547
|
+
}
|
|
5548
|
+
});
|
|
5385
5549
|
}, [
|
|
5386
5550
|
workflow,
|
|
5387
5551
|
key,
|
|
@@ -5392,19 +5556,16 @@ function useWorkflowStream(workflow, options = {}) {
|
|
|
5392
5556
|
return {
|
|
5393
5557
|
submit,
|
|
5394
5558
|
submitForm: submit,
|
|
5395
|
-
reset:
|
|
5396
|
-
setStartedHere(false);
|
|
5397
|
-
actions.reset();
|
|
5398
|
-
}, [actions]),
|
|
5559
|
+
reset: actions.reset,
|
|
5399
5560
|
wake,
|
|
5400
5561
|
cancel,
|
|
5401
5562
|
pauseUpload: actions.pauseUpload,
|
|
5402
5563
|
resumeUpload: actions.resumeUpload,
|
|
5403
5564
|
run: tracked.run,
|
|
5404
|
-
startedHere,
|
|
5405
|
-
pending:
|
|
5406
|
-
upload:
|
|
5407
|
-
error:
|
|
5565
|
+
startedHere: form.startedHere,
|
|
5566
|
+
pending: form.busy || tracked.polling,
|
|
5567
|
+
upload: form.upload,
|
|
5568
|
+
error: form.startError ?? tracked.error
|
|
5408
5569
|
};
|
|
5409
5570
|
}
|
|
5410
5571
|
/**
|