@alexkroman1/aai-ui 13.3.0 → 15.0.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.
- package/README.md +159 -69
- package/dist/{_colors-CZ6OlPbL.js → _colors-CpZO-88A.js} +24 -1
- package/dist/_recover-run.d.ts +2 -2
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_upload-files.d.ts +2 -2
- package/dist/_upload-report.d.ts +26 -0
- package/dist/{_utils-CyzjK0gW.js → _utils-DnQDM9Uy.js} +3 -3
- package/dist/_utils.d.ts +3 -3
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-files.d.ts +1 -1
- package/dist/agent-state-labels.d.ts +60 -0
- package/dist/audio.js +20 -14
- package/dist/{chat-view-Bv5VFJIE.js → chat-view-C_3T7Ln8.js} +96 -29
- package/dist/{client-config-DD820zHn.js → client-config-DJQHnYjm.js} +4 -4
- package/dist/client-config.d.ts +4 -4
- package/dist/client-dir.d.ts +1 -1
- package/dist/client-dir.js +1 -1
- package/dist/components/_colors.d.ts +23 -0
- package/dist/components/_form-readiness.d.ts +1 -1
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.js +3 -3
- package/dist/components/chat-view.js +1 -1
- package/dist/components/console-shell.d.ts +16 -20
- package/dist/components/controls.js +3 -3
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +6 -6
- package/dist/components/form-types.d.ts +1 -1
- package/dist/components/form.d.ts +1 -1
- package/dist/components/message-list.js +1 -1
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/start-screen.js +1 -1
- package/dist/components/tool-call-block.js +1 -1
- package/dist/components/tool-config-context.d.ts +1 -1
- package/dist/components/workflow-progress.d.ts +11 -4
- package/dist/context.d.ts +142 -19
- package/dist/context.js +155 -17
- package/dist/default-client/assets/{audio-BuDICbPf.js → audio-9zQsNc1w.js} +1 -1
- package/dist/default-client/assets/index-BTv30Z4F.css +2 -0
- package/dist/default-client/assets/index-BowRD-xq.js +284 -0
- package/dist/default-client/index.html +2 -2
- package/dist/define-client.d.ts +19 -19
- package/dist/define-client.js +19 -19
- package/dist/hooks.d.ts +44 -8
- package/dist/hooks.js +19 -13
- package/dist/index.d.ts +14 -7
- package/dist/index.js +551 -231
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +5 -5
- package/dist/{message-list-C0pL7x41.js → message-list-CdOnSh5m.js} +19 -12
- package/dist/page.d.ts +11 -11
- package/dist/session-core-audio-setup.d.ts +1 -1
- package/dist/session-core-dial.d.ts +0 -2
- package/dist/{session-core-C9elBIdu.js → session-core-gwePM95B.js} +125 -52
- package/dist/session-core-messages.d.ts +2 -2
- package/dist/session-core-types.d.ts +58 -1
- package/dist/session-core.d.ts +6 -6
- package/dist/session-core.js +2 -2
- package/dist/session-resume-store.d.ts +3 -3
- package/dist/{tool-call-block-Bunc6rCw.js → tool-call-block-C2t_5fpp.js} +27 -9
- package/dist/{tool-config-context-Bh8p3DtG.js → tool-config-context-Es4YUzV2.js} +1 -1
- package/dist/types.d.ts +19 -4
- package/dist/types.js +2 -2
- package/dist/url-chips-D62RTz7v.js +239 -0
- package/dist/use-conversation.d.ts +1 -1
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +2 -2
- package/dist/use-flash.d.ts +61 -0
- package/dist/{use-user-transcript-DFTSEuZN.js → use-user-transcript-uyHhzy4d.js} +3 -2
- package/dist/use-workflow-form.d.ts +35 -3
- package/dist/use-workflow-progress.d.ts +1 -1
- package/dist/{use-workflow-run-CXGEcM0l.js → use-workflow-run-CNahvlgL.js} +12 -15
- package/dist/use-workflow-run.d.ts +2 -2
- package/dist/use-workflow-runs.d.ts +1 -1
- package/dist/use-workflow-stream.d.ts +2 -2
- package/dist/use-workflows.d.ts +2 -2
- package/dist/workflow-client.d.ts +3 -3
- package/dist/workflow-field-kind.d.ts +95 -0
- package/package.json +2 -2
- package/styles.css +78 -0
- package/dist/default-client/assets/index-B1_ROnTJ.js +0 -284
- package/dist/default-client/assets/index-S5fkKi6B.css +0 -2
- package/dist/url-chips-C2u7QPv8.js +0 -100
package/dist/index.js
CHANGED
|
@@ -1,25 +1,190 @@
|
|
|
1
|
-
import { n as fetchClientConfig } from "./client-config-
|
|
2
|
-
import { i as AutoScroll, n as Markdown, r as useConversation, t as MessageList } from "./message-list-
|
|
3
|
-
import { ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
|
|
4
|
-
import {
|
|
1
|
+
import { n as fetchClientConfig } from "./client-config-DJQHnYjm.js";
|
|
2
|
+
import { i as AutoScroll, n as Markdown, r as useConversation, t as MessageList } from "./message-list-CdOnSh5m.js";
|
|
3
|
+
import { ThemeProvider, useSession, useSessionActions, useSessionError, useSessionSelector, useSessionStatus, useTheme } from "./context.js";
|
|
4
|
+
import { a as inkTint, i as focusRingStyle, n as FOCUS_RING } from "./_colors-CpZO-88A.js";
|
|
5
5
|
import { Button } from "./components/button.js";
|
|
6
|
-
import { n as ConsoleShell, t as ChatView } from "./chat-view-
|
|
7
|
-
import { n as setPageTitle } from "./_utils-
|
|
6
|
+
import { n as ConsoleShell, r as SessionErrorBanner, t as ChatView } from "./chat-view-C_3T7Ln8.js";
|
|
7
|
+
import { n as setPageTitle } from "./_utils-DnQDM9Uy.js";
|
|
8
|
+
import { a as useFlash, i as useCopy } from "./url-chips-D62RTz7v.js";
|
|
8
9
|
import { Controls } from "./components/controls.js";
|
|
9
|
-
import { n as useUserTranscript } from "./use-user-transcript-
|
|
10
|
-
import { n as ToolCallRow } from "./tool-call-block-
|
|
10
|
+
import { n as useUserTranscript } from "./use-user-transcript-uyHhzy4d.js";
|
|
11
|
+
import { n as ToolCallRow } from "./tool-call-block-C2t_5fpp.js";
|
|
11
12
|
import { SidebarLayout } from "./components/sidebar-layout.js";
|
|
12
13
|
import { StartScreen } from "./components/start-screen.js";
|
|
13
|
-
import { a as useWorkflowProgress, c as isTerminal, o as useWorkflowApiRef, r as useWorkflowRun, s as createWorkflowApi } from "./use-workflow-run-
|
|
14
|
-
import { t as
|
|
15
|
-
import {
|
|
14
|
+
import { a as useWorkflowProgress, c as isTerminal, o as useWorkflowApiRef, r as useWorkflowRun, s as createWorkflowApi } from "./use-workflow-run-CNahvlgL.js";
|
|
15
|
+
import { i as urlSlot, n as storageGet, r as storageSet, t as createBrowserSession } from "./session-core-gwePM95B.js";
|
|
16
|
+
import { mountClient, mountRoot, resolveContainer } from "./define-client.js";
|
|
16
17
|
import { useAgentState, useEvent, useToolCallStart, useToolResult } from "./hooks.js";
|
|
17
18
|
import clsx from "clsx";
|
|
18
19
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
-
import { createContext, createElement, useCallback, useContext, useEffect, useId, useRef, useState } from "react";
|
|
20
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
20
21
|
import { errorMessage } from "@alexkroman1/aai";
|
|
21
22
|
import { formatBytes, isRecord, omitUndefined } from "@alexkroman1/aai/utils";
|
|
22
23
|
import { createEpoch } from "@alexkroman1/aai/internal";
|
|
24
|
+
//#region src/agent-state-labels.ts
|
|
25
|
+
/**
|
|
26
|
+
* The default label per {@link AgentState}.
|
|
27
|
+
*
|
|
28
|
+
* Override the ones your page has a better word for and keep the rest:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* import type { AgentState } from "@alexkroman1/aai-ui";
|
|
32
|
+
* import { AGENT_STATE_LABELS } from "@alexkroman1/aai-ui";
|
|
33
|
+
*
|
|
34
|
+
* // A dispatch board that shouts, and renames one state.
|
|
35
|
+
* const STATE_LABEL = { ...AGENT_STATE_LABELS, thinking: "Processing" };
|
|
36
|
+
* const shout = (s: AgentState) => STATE_LABEL[s].toUpperCase();
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* **Sentence case, deliberately.** A template that wants caps applies its own
|
|
40
|
+
* `.toUpperCase()`, and a template that wants Title Case is already there;
|
|
41
|
+
* shipping the shouted form instead would leave the two chromes that do not
|
|
42
|
+
* shout with a string they have to un-shout, which no case transform does
|
|
43
|
+
* correctly.
|
|
44
|
+
*
|
|
45
|
+
* Two wordings are decisions rather than transliterations of the member name:
|
|
46
|
+
*
|
|
47
|
+
* - `disconnected` is **"Idle"**. It is the state a session is in BEFORE it has
|
|
48
|
+
* ever started as well as after it ends, so it is the first word most callers
|
|
49
|
+
* see; "Disconnected" reads as a fault on a page where nothing has gone
|
|
50
|
+
* wrong yet. Both chromes that mapped this state by hand chose "Idle" too.
|
|
51
|
+
* - `connecting` and `thinking` carry an ellipsis, `listening` and `speaking`
|
|
52
|
+
* do not. The first two are waits with nothing for the caller to do; the
|
|
53
|
+
* other two describe someone actually talking. Same distinction
|
|
54
|
+
* `WORKFLOW_STATUS_LABELS` draws with its one "Working…".
|
|
55
|
+
*
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
const AGENT_STATE_LABELS = {
|
|
59
|
+
disconnected: "Idle",
|
|
60
|
+
connecting: "Connecting…",
|
|
61
|
+
ready: "Ready",
|
|
62
|
+
listening: "Listening",
|
|
63
|
+
thinking: "Thinking…",
|
|
64
|
+
speaking: "Speaking",
|
|
65
|
+
error: "Error"
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/components/bullet-list.tsx
|
|
69
|
+
/** @jsxImportSource react */
|
|
70
|
+
/**
|
|
71
|
+
* A disc-bulleted list of short strings — a run's key points, findings, risks.
|
|
72
|
+
*
|
|
73
|
+
* Five pages had written this, byte-identical apart from a `text-sm` suffix on
|
|
74
|
+
* two of them, and all five had the same two defects. Both are the reason this
|
|
75
|
+
* is a component rather than four lines a page repeats:
|
|
76
|
+
*
|
|
77
|
+
* - **All five keyed by the string itself, and these lists are MODEL OUTPUT.**
|
|
78
|
+
* Two identical bullets are entirely plausible — a summariser that repeats
|
|
79
|
+
* itself is a bad summary, not a bad program — and a repeated string is then
|
|
80
|
+
* a duplicate `key`: React warns, and the two `<li>`s contend for one slot in
|
|
81
|
+
* the reconciliation. What is keyed here instead is the content PLUS how many
|
|
82
|
+
* times that content has already appeared in this list, which is unique by
|
|
83
|
+
* construction and unchanged by a re-render that did not change the text.
|
|
84
|
+
* (Position alone would also be sound — these lists are replaced wholesale by
|
|
85
|
+
* each new output and never reordered — but it is what `noArrayIndexKey`
|
|
86
|
+
* exists to talk you out of, and a unique key is one `Map` away, so there is
|
|
87
|
+
* no reason to spend a lint suppression on it.)
|
|
88
|
+
* - **Three of the five rendered an empty `<ul>` under a heading.** Two had
|
|
89
|
+
* hand-rolled `if (items.length === 0) return null` and three had not, so the
|
|
90
|
+
* same absent field was "nothing" on two pages and a stray heading with a
|
|
91
|
+
* void under it on three. Emptiness renders NOTHING here, `title` included:
|
|
92
|
+
* a heading over no bullets is a claim the run did not make.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```tsx
|
|
96
|
+
* import { BulletList } from "@alexkroman1/aai-ui";
|
|
97
|
+
*
|
|
98
|
+
* function Findings({ risks }: { risks: string[] }) {
|
|
99
|
+
* return <BulletList title="Risks" items={risks} size="sm" />;
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @param props - Bullet-list props.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
106
|
+
*/
|
|
107
|
+
function BulletList({ items, title, size = "base", className }) {
|
|
108
|
+
if (items.length === 0) return null;
|
|
109
|
+
const seen = /* @__PURE__ */ new Map();
|
|
110
|
+
const list = /* @__PURE__ */ jsx("ul", {
|
|
111
|
+
className: clsx("flex list-disc flex-col gap-1 pl-5", size === "sm" && "text-sm", className),
|
|
112
|
+
children: items.map((item) => {
|
|
113
|
+
const nth = seen.get(item) ?? 0;
|
|
114
|
+
seen.set(item, nth + 1);
|
|
115
|
+
return /* @__PURE__ */ jsx("li", { children: item }, `${nth}:${item}`);
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
const heading = title === void 0 || title === null || title === false ? void 0 : title;
|
|
119
|
+
if (heading === void 0) return list;
|
|
120
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
121
|
+
className: "flex flex-col gap-1",
|
|
122
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
123
|
+
className: "text-sm font-medium opacity-70",
|
|
124
|
+
children: heading
|
|
125
|
+
}), list]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/components/facts.tsx
|
|
130
|
+
/** @jsxImportSource react */
|
|
131
|
+
/** What goes between two facts. A separator, not a bullet — U+00B7, not U+2022. */
|
|
132
|
+
const FACT_SEPARATOR = " · ";
|
|
133
|
+
/**
|
|
134
|
+
* A muted line of run facts, joined by `·` — "6 segments · 12:04 of audio ·
|
|
135
|
+
* 1,840 words".
|
|
136
|
+
*
|
|
137
|
+
* Nine pages had written this by hand under four different typographies for
|
|
138
|
+
* one role, two of them (`call-audit` and `spoken-summary`) byte-identical down
|
|
139
|
+
* to the payload. Three things it takes off the caller:
|
|
140
|
+
*
|
|
141
|
+
* - **The separator cannot be forgotten, and neither can the space around it.**
|
|
142
|
+
* Four of the nine carried a literal `{" "}` at the end of a line, because
|
|
143
|
+
* Prettier's wrap ate the space that made `· ` read as a separator rather
|
|
144
|
+
* than as punctuation glued to the next word. A line that is correct only
|
|
145
|
+
* because somebody remembered an invisible JSX expression is exactly the
|
|
146
|
+
* thing a component should own.
|
|
147
|
+
* - **A fact worth omitting is omitted, and by the caller's own condition.**
|
|
148
|
+
* The hand-written shape for a conditional fact was to splice the separator
|
|
149
|
+
* into the string — `{x ? \` · budget exhausted\` : ""}` — which puts the
|
|
150
|
+
* punctuation in two places and gets the leading separator wrong the moment
|
|
151
|
+
* the fact before it also disappears. Passing the condition and letting this
|
|
152
|
+
* drop it keeps the separator in one place.
|
|
153
|
+
* - **A line with nothing left to say renders NOTHING.** With every fact
|
|
154
|
+
* conditional, the alternative is a muted empty row, or a bare `·`.
|
|
155
|
+
*
|
|
156
|
+
* The facts are JOINED into one string rather than interleaved as elements, and
|
|
157
|
+
* that is why the prop is text: joined, there is no per-fact `key` to invent —
|
|
158
|
+
* the same reasoning `WorkflowProgress` gives for its log lines. A line that
|
|
159
|
+
* genuinely needs an element in it (a link) wants its own markup.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```tsx
|
|
163
|
+
* import { Facts } from "@alexkroman1/aai-ui";
|
|
164
|
+
*
|
|
165
|
+
* function RunFacts({ words, cut }: { words: number; cut: number }) {
|
|
166
|
+
* return <Facts size="xs" items={[`${words} words`, cut > 0 && `${cut} blind cuts`]} />;
|
|
167
|
+
* }
|
|
168
|
+
* ```
|
|
169
|
+
*
|
|
170
|
+
* @param props - Facts-line props.
|
|
171
|
+
*
|
|
172
|
+
* @public
|
|
173
|
+
*/
|
|
174
|
+
function Facts({ items, size = "sm", as = "p", className }) {
|
|
175
|
+
const shown = items.filter((item) => item !== false && item !== null && item !== void 0 && item !== "");
|
|
176
|
+
if (shown.length === 0) return null;
|
|
177
|
+
const text = shown.join(FACT_SEPARATOR);
|
|
178
|
+
const classes = clsx(size === "xs" ? "text-xs opacity-60" : "text-sm opacity-70", className);
|
|
179
|
+
return as === "span" ? /* @__PURE__ */ jsx("span", {
|
|
180
|
+
className: classes,
|
|
181
|
+
children: text
|
|
182
|
+
}) : /* @__PURE__ */ jsx("p", {
|
|
183
|
+
className: classes,
|
|
184
|
+
children: text
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
//#endregion
|
|
23
188
|
//#region src/components/_form-readiness.ts
|
|
24
189
|
/**
|
|
25
190
|
* Whether a form's own fields exist yet.
|
|
@@ -47,7 +212,7 @@ import { createEpoch } from "@alexkroman1/aai/internal";
|
|
|
47
212
|
* one describes an element that EXISTS. So this is a context rather than an
|
|
48
213
|
* attribute, and it carries the one fact a DOM read cannot recover.
|
|
49
214
|
*
|
|
50
|
-
* A form with no such children is ready by definition — `
|
|
215
|
+
* A form with no such children is ready by definition — `useFormReadiness`
|
|
51
216
|
* outside a provider reports nothing pending, so every hand-written form is
|
|
52
217
|
* unaffected and `Form` keeps working outside this package.
|
|
53
218
|
*/
|
|
@@ -173,7 +338,7 @@ function readFiles(files, read) {
|
|
|
173
338
|
if (read === "upload") return Promise.resolve([...files]);
|
|
174
339
|
return Promise.all(files.map((file) => describeFile(file, read)));
|
|
175
340
|
}
|
|
176
|
-
/** The `data-aai-read` attribute as a {@link
|
|
341
|
+
/** The `data-aai-read` attribute as a {@link FileReadMode}, defaulting to `"none"`. */
|
|
177
342
|
function readMode(raw) {
|
|
178
343
|
return raw === "text" || raw === "dataUrl" || raw === "upload" ? raw : "none";
|
|
179
344
|
}
|
|
@@ -274,12 +439,12 @@ function Field({ label, hint, htmlFor, className, children }) {
|
|
|
274
439
|
function useControlProps() {
|
|
275
440
|
const theme = useTheme();
|
|
276
441
|
return {
|
|
277
|
-
className: clsx("w-full rounded-aai border px-3 py-2 text-sm font-aai",
|
|
442
|
+
className: clsx("w-full rounded-aai border px-3 py-2 text-sm font-aai", FOCUS_RING, "disabled:cursor-not-allowed disabled:opacity-50"),
|
|
278
443
|
style: {
|
|
279
444
|
background: theme.surface,
|
|
280
445
|
color: theme.text,
|
|
281
446
|
borderColor: theme.border,
|
|
282
|
-
|
|
447
|
+
...focusRingStyle(theme.primary)
|
|
283
448
|
}
|
|
284
449
|
};
|
|
285
450
|
}
|
|
@@ -311,16 +476,14 @@ function useFileControlProps() {
|
|
|
311
476
|
};
|
|
312
477
|
}
|
|
313
478
|
/**
|
|
314
|
-
*
|
|
479
|
+
* `TextField` and `NumberField`, which differ only in the `type` they set.
|
|
315
480
|
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* @public
|
|
481
|
+
* Internal, because the two stay separate PUBLIC exports: each is named in the
|
|
482
|
+
* `components` capability contract and in the API report, and a caller writes
|
|
483
|
+
* the one whose value shape it wants — `NumberField` is the one that
|
|
484
|
+
* contributes a number to {@link FormValues}.
|
|
322
485
|
*/
|
|
323
|
-
function
|
|
486
|
+
function InputField({ defaultType, name, label, hint, className, ...rest }) {
|
|
324
487
|
const id = useId();
|
|
325
488
|
const control = useControlProps();
|
|
326
489
|
return /* @__PURE__ */ jsx(Field, {
|
|
@@ -331,13 +494,29 @@ function TextField({ name, label, hint, className, ...rest }) {
|
|
|
331
494
|
children: /* @__PURE__ */ jsx("input", {
|
|
332
495
|
id,
|
|
333
496
|
name,
|
|
334
|
-
type:
|
|
497
|
+
type: defaultType,
|
|
335
498
|
...control,
|
|
336
499
|
...rest
|
|
337
500
|
})
|
|
338
501
|
});
|
|
339
502
|
}
|
|
340
503
|
/**
|
|
504
|
+
* A single-line text input.
|
|
505
|
+
*
|
|
506
|
+
* Accepts every `<input>` attribute except `name` and `className`, which this
|
|
507
|
+
* component owns, plus the shared {@link FieldShell} props.
|
|
508
|
+
*
|
|
509
|
+
* @param props - {@link FieldShell} props plus `<input>` attributes.
|
|
510
|
+
*
|
|
511
|
+
* @public
|
|
512
|
+
*/
|
|
513
|
+
function TextField(props) {
|
|
514
|
+
return /* @__PURE__ */ jsx(InputField, {
|
|
515
|
+
defaultType: "text",
|
|
516
|
+
...props
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
341
520
|
* A number input. Contributes a NUMBER to {@link FormValues}, or nothing when
|
|
342
521
|
* left empty.
|
|
343
522
|
*
|
|
@@ -349,21 +528,10 @@ function TextField({ name, label, hint, className, ...rest }) {
|
|
|
349
528
|
*
|
|
350
529
|
* @public
|
|
351
530
|
*/
|
|
352
|
-
function NumberField(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
label,
|
|
357
|
-
hint,
|
|
358
|
-
htmlFor: id,
|
|
359
|
-
className,
|
|
360
|
-
children: /* @__PURE__ */ jsx("input", {
|
|
361
|
-
id,
|
|
362
|
-
name,
|
|
363
|
-
type: "number",
|
|
364
|
-
...control,
|
|
365
|
-
...rest
|
|
366
|
-
})
|
|
531
|
+
function NumberField(props) {
|
|
532
|
+
return /* @__PURE__ */ jsx(InputField, {
|
|
533
|
+
defaultType: "number",
|
|
534
|
+
...props
|
|
367
535
|
});
|
|
368
536
|
}
|
|
369
537
|
/**
|
|
@@ -474,14 +642,14 @@ function CheckboxField({ name, label, hint, className, ...rest }) {
|
|
|
474
642
|
* travel in it. With `upload` the field contributes the `File` itself,
|
|
475
643
|
* `useWorkflowSubmit` stores it through `POST /workflows/uploads` before
|
|
476
644
|
* starting the run, and the input carries the upload id — which a step reads
|
|
477
|
-
* windows of with `
|
|
645
|
+
* windows of with `stepReadUpload`. Declaring the property in the workflow's
|
|
478
646
|
* `uploads` list makes `<WorkflowFields>` render exactly this, so a declared
|
|
479
647
|
* form needs no file markup at all.
|
|
480
648
|
*
|
|
481
649
|
* **Without it the field describes the file and does not read it.** `read`
|
|
482
650
|
* exists for the cases where the bytes really are small and really are the
|
|
483
651
|
* input — a CSV of ids, a config — and the size is the author's to check. See
|
|
484
|
-
* {@link
|
|
652
|
+
* {@link FileReadMode} for the four values; `upload` is shorthand for
|
|
485
653
|
* `read="upload"`.
|
|
486
654
|
*
|
|
487
655
|
* Otherwise accepts every `<input>` attribute except `name`, `className` and
|
|
@@ -812,14 +980,14 @@ function UploadProgressBar({ upload, onPause, onResume, className }) {
|
|
|
812
980
|
* }
|
|
813
981
|
* ```
|
|
814
982
|
*
|
|
815
|
-
* @param
|
|
983
|
+
* @param options - See {@link UseWorkflowsOptions}.
|
|
816
984
|
* @returns The listing, its loading flag and its failure — see
|
|
817
985
|
* {@link UseWorkflowsResult}.
|
|
818
986
|
*
|
|
819
987
|
* @public
|
|
820
988
|
*/
|
|
821
|
-
function useWorkflows(
|
|
822
|
-
const { api, skip = false } =
|
|
989
|
+
function useWorkflows(options = {}) {
|
|
990
|
+
const { api, skip = false } = options;
|
|
823
991
|
const [state, setState] = useState({
|
|
824
992
|
workflows: [],
|
|
825
993
|
loading: !skip,
|
|
@@ -850,6 +1018,100 @@ function useWorkflows(opts = {}) {
|
|
|
850
1018
|
return state;
|
|
851
1019
|
}
|
|
852
1020
|
//#endregion
|
|
1021
|
+
//#region src/workflow-field-kind.ts
|
|
1022
|
+
/**
|
|
1023
|
+
* Which control one property of a workflow's input schema renders as.
|
|
1024
|
+
*
|
|
1025
|
+
* `<WorkflowFields>` walks a workflow's `inputSchema` and picks a control per
|
|
1026
|
+
* property. This is that decision with nothing rendered — ONE function, called
|
|
1027
|
+
* by `SchemaField` itself, so a reader outside the component can ask the same
|
|
1028
|
+
* question and get the same answer.
|
|
1029
|
+
*
|
|
1030
|
+
* **Why it is published.** The rule is not derivable from the schema by
|
|
1031
|
+
* inspection: an upload property IS a plain `string` there, and the ORDER
|
|
1032
|
+
* matters in a way no reader can see. The studio's API pane documents the
|
|
1033
|
+
* form-to-JSON correspondence for every workflow app, and it was carrying a
|
|
1034
|
+
* hand-kept mirror of the switch below — "a declared upload beats everything,
|
|
1035
|
+
* then `enum`, then …" — with a comment admitting as much and a test pinning
|
|
1036
|
+
* the order to catch the drift. A copy that drifted would name a control the
|
|
1037
|
+
* reader cannot find on their own page. Nothing else could be done about it
|
|
1038
|
+
* while the decision was private, and the same reasoning already settled the
|
|
1039
|
+
* studio's SDK-subpath list: mirroring "would just move the drift somewhere
|
|
1040
|
+
* new".
|
|
1041
|
+
*
|
|
1042
|
+
* So a NEW control added to `<WorkflowFields>` is a new member of
|
|
1043
|
+
* {@link WorkflowFieldKind}, and every reader of the rule finds out from the
|
|
1044
|
+
* type rather than from a table somebody remembered to edit.
|
|
1045
|
+
*/
|
|
1046
|
+
/**
|
|
1047
|
+
* A property's declared type, taking the first non-null member of a union.
|
|
1048
|
+
*
|
|
1049
|
+
* `["string", "null"]` is how an optional-and-nullable field converts; the
|
|
1050
|
+
* control is the one the non-null half wants. Not exported from the package
|
|
1051
|
+
* barrel — the published question is the KIND, and this is the one detail
|
|
1052
|
+
* `<WorkflowFields>` needs past it (an integer's `step`).
|
|
1053
|
+
*/
|
|
1054
|
+
function schemaTypeOf(schema) {
|
|
1055
|
+
if (!isRecord(schema)) return void 0;
|
|
1056
|
+
const { type } = schema;
|
|
1057
|
+
if (typeof type === "string") return type;
|
|
1058
|
+
return Array.isArray(type) ? type.find((member) => member !== "null") : void 0;
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Which control `<WorkflowFields>` renders for one property of an input schema.
|
|
1062
|
+
*
|
|
1063
|
+
* @remarks
|
|
1064
|
+
* The ORDER is the contract, and it is the half a reader cannot infer:
|
|
1065
|
+
*
|
|
1066
|
+
* 1. **A declared upload wins outright.** It is a plain `string` in the schema
|
|
1067
|
+
* — the id — so testing the type first would render a text box asking a
|
|
1068
|
+
* person to type an id no person has. The declaration is the workflow's
|
|
1069
|
+
* (`workflow({ uploads: [...] })`), not the schema's, precisely because a
|
|
1070
|
+
* marker inside the schema would only work for the library that carried it.
|
|
1071
|
+
* 2. **Then a non-empty `enum`**, before the type switch: an enum of strings
|
|
1072
|
+
* is a `string` too, and a select is the narrower, better control.
|
|
1073
|
+
* 3. **Then the type** — `boolean`, `number`/`integer`, `string`.
|
|
1074
|
+
* 4. **Anything else is `"none"`** rather than a guess. See
|
|
1075
|
+
* {@link WorkflowFieldKind}.
|
|
1076
|
+
*
|
|
1077
|
+
* @example
|
|
1078
|
+
* ```ts
|
|
1079
|
+
* import { fieldKindFor, type WorkflowFieldKind } from "@alexkroman1/aai-ui";
|
|
1080
|
+
* import type { WorkflowSummary } from "@alexkroman1/aai/workflow-api";
|
|
1081
|
+
*
|
|
1082
|
+
* function kindsOf(summary: WorkflowSummary): Record<string, WorkflowFieldKind> {
|
|
1083
|
+
* const uploads = new Set(summary.uploads ?? []);
|
|
1084
|
+
* const schema = summary.inputSchema as { properties?: Record<string, unknown> };
|
|
1085
|
+
* return Object.fromEntries(
|
|
1086
|
+
* Object.entries(schema.properties ?? {}).map(([name, property]) => [
|
|
1087
|
+
* name,
|
|
1088
|
+
* fieldKindFor(property, { upload: uploads.has(name) }),
|
|
1089
|
+
* ]),
|
|
1090
|
+
* );
|
|
1091
|
+
* }
|
|
1092
|
+
* ```
|
|
1093
|
+
*
|
|
1094
|
+
* @param schema - The property's JSON Schema, as `GET workflows` reports it.
|
|
1095
|
+
* Anything that is not an object reads as `"none"`.
|
|
1096
|
+
* @param options - `upload` says the property is named in the workflow's own
|
|
1097
|
+
* `uploads` declaration, which is what step 1 above tests.
|
|
1098
|
+
*
|
|
1099
|
+
* @public
|
|
1100
|
+
*/
|
|
1101
|
+
function fieldKindFor(schema, options) {
|
|
1102
|
+
if (options?.upload === true) return "file";
|
|
1103
|
+
if (!isRecord(schema)) return "none";
|
|
1104
|
+
const { enum: choices } = schema;
|
|
1105
|
+
if (Array.isArray(choices) && choices.length > 0) return "select";
|
|
1106
|
+
switch (schemaTypeOf(schema)) {
|
|
1107
|
+
case "boolean": return "checkbox";
|
|
1108
|
+
case "number":
|
|
1109
|
+
case "integer": return "number";
|
|
1110
|
+
case "string": return "text";
|
|
1111
|
+
default: return "none";
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
//#endregion
|
|
853
1115
|
//#region src/components/workflow-fields.tsx
|
|
854
1116
|
/** @jsxImportSource react */
|
|
855
1117
|
/**
|
|
@@ -927,39 +1189,37 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
927
1189
|
const label = humanize(name);
|
|
928
1190
|
const hint = property.description === void 0 ? {} : { hint: property.description };
|
|
929
1191
|
const defaults = property.default === void 0 ? {} : { defaultValue: String(property.default) };
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
case "boolean": return /* @__PURE__ */ jsx(CheckboxField, {
|
|
1192
|
+
switch (fieldKindFor(property, { upload })) {
|
|
1193
|
+
case "file": return /* @__PURE__ */ jsx(FileField, {
|
|
1194
|
+
name,
|
|
1195
|
+
label,
|
|
1196
|
+
required,
|
|
1197
|
+
upload: true,
|
|
1198
|
+
...hint
|
|
1199
|
+
});
|
|
1200
|
+
case "select": return /* @__PURE__ */ jsx(SelectField, {
|
|
1201
|
+
name,
|
|
1202
|
+
label,
|
|
1203
|
+
required,
|
|
1204
|
+
options: (property.enum ?? []).map((value) => String(value)),
|
|
1205
|
+
...hint,
|
|
1206
|
+
...defaults
|
|
1207
|
+
});
|
|
1208
|
+
case "checkbox": return /* @__PURE__ */ jsx(CheckboxField, {
|
|
948
1209
|
name,
|
|
949
1210
|
label,
|
|
950
1211
|
defaultChecked: property.default === true,
|
|
951
1212
|
...hint
|
|
952
1213
|
});
|
|
953
|
-
case "number":
|
|
954
|
-
case "integer": return /* @__PURE__ */ jsx(NumberField, {
|
|
1214
|
+
case "number": return /* @__PURE__ */ jsx(NumberField, {
|
|
955
1215
|
name,
|
|
956
1216
|
label,
|
|
957
1217
|
required,
|
|
958
|
-
step:
|
|
1218
|
+
step: schemaTypeOf(property) === "integer" ? 1 : "any",
|
|
959
1219
|
...hint,
|
|
960
1220
|
...defaults
|
|
961
1221
|
});
|
|
962
|
-
case "
|
|
1222
|
+
case "text": return /* @__PURE__ */ jsx(TextField, {
|
|
963
1223
|
name,
|
|
964
1224
|
label,
|
|
965
1225
|
required,
|
|
@@ -969,12 +1229,6 @@ function SchemaField({ name, property, required, upload = false }) {
|
|
|
969
1229
|
default: return null;
|
|
970
1230
|
}
|
|
971
1231
|
}
|
|
972
|
-
/** A property's type, taking the first non-null member of a union. */
|
|
973
|
-
function typeOf(property) {
|
|
974
|
-
const { type } = property;
|
|
975
|
-
if (typeof type === "string") return type;
|
|
976
|
-
return Array.isArray(type) ? type.find((member) => member !== "null") : void 0;
|
|
977
|
-
}
|
|
978
1232
|
/** The listing's `unknown` schema as the object shape this reads, when it is one. */
|
|
979
1233
|
function asObjectSchema(schema) {
|
|
980
1234
|
return isRecord(schema) ? schema : void 0;
|
|
@@ -997,13 +1251,13 @@ function humanize(name) {
|
|
|
997
1251
|
*
|
|
998
1252
|
* The complement of a status line, and the reason both exist: a run is
|
|
999
1253
|
* `running` for its whole life, so a one-round job and a ten-round one look
|
|
1000
|
-
* identical while they happen. These lines come from the run itself (`
|
|
1254
|
+
* identical while they happen. These lines come from the run itself (`stepReport()`
|
|
1001
1255
|
* in a `"use step"` body), which is the only channel a workflow has before it
|
|
1002
1256
|
* produces an output.
|
|
1003
1257
|
*
|
|
1004
|
-
*
|
|
1258
|
+
* Four rules are baked in, and they are why this is a component rather than
|
|
1005
1259
|
* three lines each page writes for itself — the two templates that had written
|
|
1006
|
-
* it had written
|
|
1260
|
+
* it had written three of them, comments included:
|
|
1007
1261
|
*
|
|
1008
1262
|
* - **It renders nothing until there is something to render.** `supported` is
|
|
1009
1263
|
* what keeps this from being an empty box forever on an agent deployed before
|
|
@@ -1017,6 +1271,13 @@ function humanize(name) {
|
|
|
1017
1271
|
* or opening a finished run tomorrow — shows how it got there rather than an
|
|
1018
1272
|
* empty box. That is `useWorkflowProgress`'s doing; this is what makes it
|
|
1019
1273
|
* visible.
|
|
1274
|
+
* - **They are ANNOUNCED**, for the reason the first paragraph gives: this is
|
|
1275
|
+
* the only channel a run has before it produces an output, and a `<pre>` that
|
|
1276
|
+
* grows is a silent one. A screen-reader user pressing "Digest" got nothing
|
|
1277
|
+
* between the click and a terminal state minutes later — no "fetching", no
|
|
1278
|
+
* "summarising", no evidence the button did anything. See `role="log"` below.
|
|
1279
|
+
* The six pages that render this pass only `className`, so no template could
|
|
1280
|
+
* have fixed it locally; that is what makes it this component's job.
|
|
1020
1281
|
*
|
|
1021
1282
|
* @example
|
|
1022
1283
|
* ```tsx
|
|
@@ -1033,27 +1294,33 @@ function humanize(name) {
|
|
|
1033
1294
|
*/
|
|
1034
1295
|
function WorkflowProgress({ runId, api, className, placeholder, lines }) {
|
|
1035
1296
|
const { progress, streaming, supported } = useWorkflowProgress(runId, omitUndefined({ api }));
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1297
|
+
const text = useMemo(() => {
|
|
1298
|
+
const shown = lines === void 0 ? progress : progress.slice(Math.max(progress.length - lines, 0));
|
|
1299
|
+
return shown.length === 0 ? null : shown.join("\n");
|
|
1300
|
+
}, [progress, lines]);
|
|
1301
|
+
if (!supported || text === null) return placeholder ?? null;
|
|
1038
1302
|
return /* @__PURE__ */ jsxs("pre", {
|
|
1303
|
+
role: "log",
|
|
1304
|
+
"aria-live": "polite",
|
|
1305
|
+
"aria-atomic": "false",
|
|
1039
1306
|
className: clsx(className ?? "whitespace-pre-wrap border-l pl-4 text-xs opacity-70"),
|
|
1040
|
-
children: [
|
|
1307
|
+
children: [text, streaming && "\n…"]
|
|
1041
1308
|
});
|
|
1042
1309
|
}
|
|
1043
1310
|
//#endregion
|
|
1044
1311
|
//#region src/page.tsx
|
|
1045
1312
|
/** @jsxImportSource react */
|
|
1046
1313
|
/**
|
|
1047
|
-
* `
|
|
1314
|
+
* `mountPage()` — mount a WORKFLOW APP's UI: React, theme, no session.
|
|
1048
1315
|
*
|
|
1049
|
-
* The twin of `
|
|
1316
|
+
* The twin of `mountClient()` for an agent whose front door is a form rather than a
|
|
1050
1317
|
* microphone (`workflowApp()`). It is a separate entry rather than
|
|
1051
|
-
* an option on `
|
|
1052
|
-
* constructs a `
|
|
1318
|
+
* an option on `mountClient()` because of what `mountClient()` unavoidably does: it
|
|
1319
|
+
* constructs a `BrowserSession`, which owns a WebSocket URL provider, an audio
|
|
1053
1320
|
* graph, and a microphone request. A flag would have to make all of that
|
|
1054
1321
|
* conditional, and every session hook would then have to answer "what does this
|
|
1055
1322
|
* mean with no session?" — so the honest split is two mounts. A page that wants
|
|
1056
|
-
* voice uses `
|
|
1323
|
+
* voice uses `mountClient()`; a page that wants neither audio nor a socket uses this.
|
|
1057
1324
|
*
|
|
1058
1325
|
* Authoring is otherwise identical — the file is still `client.tsx`, still
|
|
1059
1326
|
* React, still Tailwind, still the same theme tokens — so a workflow app reads
|
|
@@ -1069,7 +1336,7 @@ function WorkflowProgress({ runId, api, className, placeholder, lines }) {
|
|
|
1069
1336
|
*
|
|
1070
1337
|
* @example
|
|
1071
1338
|
* ```tsx
|
|
1072
|
-
* import { createWorkflowApi,
|
|
1339
|
+
* import { createWorkflowApi, mountPage, useWorkflowRun } from "@alexkroman1/aai-ui";
|
|
1073
1340
|
* import { useState } from "react";
|
|
1074
1341
|
*
|
|
1075
1342
|
* // Hoisted: a client built in render is a new object every render.
|
|
@@ -1088,14 +1355,14 @@ function WorkflowProgress({ runId, api, className, placeholder, lines }) {
|
|
|
1088
1355
|
* );
|
|
1089
1356
|
* }
|
|
1090
1357
|
*
|
|
1091
|
-
*
|
|
1358
|
+
* mountPage({ name: "Digest", component: App });
|
|
1092
1359
|
* ```
|
|
1093
1360
|
*
|
|
1094
1361
|
* @throws If the target element is not found in the DOM.
|
|
1095
1362
|
*
|
|
1096
1363
|
* @public
|
|
1097
1364
|
*/
|
|
1098
|
-
function
|
|
1365
|
+
function mountPage(config) {
|
|
1099
1366
|
const container = resolveContainer(config.target);
|
|
1100
1367
|
setPageTitle(config.name);
|
|
1101
1368
|
return mountRoot(container, createElement(ThemeProvider, { value: config.theme }, createElement(config.component)));
|
|
@@ -1136,7 +1403,7 @@ const PENDING = { pending: true };
|
|
|
1136
1403
|
* @param uploadId - The id a completed run reported, or `undefined` before one
|
|
1137
1404
|
* exists — which is what a page passes straight through while it waits, and
|
|
1138
1405
|
* reports as idle rather than pending.
|
|
1139
|
-
* @param
|
|
1406
|
+
* @param options - See {@link UseDownloadUrlOptions}.
|
|
1140
1407
|
* @returns See {@link UseDownloadUrlResult}.
|
|
1141
1408
|
*
|
|
1142
1409
|
* @example
|
|
@@ -1160,9 +1427,9 @@ const PENDING = { pending: true };
|
|
|
1160
1427
|
*
|
|
1161
1428
|
* @public
|
|
1162
1429
|
*/
|
|
1163
|
-
function useDownloadUrl(uploadId,
|
|
1430
|
+
function useDownloadUrl(uploadId, options = {}) {
|
|
1164
1431
|
const [state, setState] = useState(IDLE);
|
|
1165
|
-
const getClient = useWorkflowApiRef(
|
|
1432
|
+
const getClient = useWorkflowApiRef(options.api);
|
|
1166
1433
|
useEffect(() => {
|
|
1167
1434
|
if (uploadId === void 0) {
|
|
1168
1435
|
setState(IDLE);
|
|
@@ -1286,13 +1553,7 @@ function useDownloadUrl(uploadId, opts = {}) {
|
|
|
1286
1553
|
const PREFIX$1 = "aai:run-key:";
|
|
1287
1554
|
/** This page's own slot — see "The slot is keyed by the page's own URL". */
|
|
1288
1555
|
function slotFor() {
|
|
1289
|
-
|
|
1290
|
-
if (href === void 0) return PREFIX$1;
|
|
1291
|
-
try {
|
|
1292
|
-
return `${PREFIX$1}${new URL("./", href).href}`;
|
|
1293
|
-
} catch {
|
|
1294
|
-
return `${PREFIX$1}${href}`;
|
|
1295
|
-
}
|
|
1556
|
+
return urlSlot(PREFIX$1, "./");
|
|
1296
1557
|
}
|
|
1297
1558
|
/**
|
|
1298
1559
|
* Read the key this page already has, or mint and remember one.
|
|
@@ -1302,17 +1563,12 @@ function slotFor() {
|
|
|
1302
1563
|
* hand `recover` one nothing was ever started under.
|
|
1303
1564
|
*/
|
|
1304
1565
|
function mintRunKey(storage) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
store?.setItem(slot, minted);
|
|
1312
|
-
return minted;
|
|
1313
|
-
} catch {
|
|
1314
|
-
return crypto.randomUUID();
|
|
1315
|
-
}
|
|
1566
|
+
const slot = slotFor();
|
|
1567
|
+
const stored = storageGet(storage, slot);
|
|
1568
|
+
if (stored !== void 0) return stored;
|
|
1569
|
+
const minted = crypto.randomUUID();
|
|
1570
|
+
storageSet(storage, slot, minted);
|
|
1571
|
+
return minted;
|
|
1316
1572
|
}
|
|
1317
1573
|
/**
|
|
1318
1574
|
* The key `useWorkflowSubmit` uses when the page named none.
|
|
@@ -1338,7 +1594,7 @@ function mintRunKey(storage) {
|
|
|
1338
1594
|
* @internal
|
|
1339
1595
|
*/
|
|
1340
1596
|
function useDefaultRunKey(explicit) {
|
|
1341
|
-
const [minted] = useState(() => explicit === void 0 ? mintRunKey("session") :
|
|
1597
|
+
const [minted] = useState(() => explicit === void 0 ? mintRunKey("session") : "");
|
|
1342
1598
|
return explicit ?? minted;
|
|
1343
1599
|
}
|
|
1344
1600
|
/**
|
|
@@ -1422,11 +1678,11 @@ function useRunKey(options = {}) {
|
|
|
1422
1678
|
*/
|
|
1423
1679
|
function useRecoveredRun(opts) {
|
|
1424
1680
|
const { workflow, key, enabled, getClient } = opts;
|
|
1425
|
-
const [recovering, setRecovering] = useState(enabled
|
|
1681
|
+
const [recovering, setRecovering] = useState(enabled);
|
|
1426
1682
|
const handlers = useRef(opts);
|
|
1427
1683
|
handlers.current = opts;
|
|
1428
1684
|
useEffect(() => {
|
|
1429
|
-
if (!enabled
|
|
1685
|
+
if (!enabled) return;
|
|
1430
1686
|
let cancelled = false;
|
|
1431
1687
|
setRecovering(true);
|
|
1432
1688
|
getClient().find(workflow, key, { limit: 1 }).then((found) => {
|
|
@@ -1489,6 +1745,99 @@ function useRunControls(runId, getClient) {
|
|
|
1489
1745
|
};
|
|
1490
1746
|
}
|
|
1491
1747
|
//#endregion
|
|
1748
|
+
//#region src/_upload-pause.ts
|
|
1749
|
+
/**
|
|
1750
|
+
* The two buttons a page puts over a live upload, bound to whichever gate the
|
|
1751
|
+
* submission is currently holding.
|
|
1752
|
+
*
|
|
1753
|
+
* `useWorkflowSubmit` and `useWorkflowStream` both own an {@link UploadGate} and
|
|
1754
|
+
* both reported the pause the same way: park the gate, then fold `paused` into
|
|
1755
|
+
* the status the bar is already drawing. Folded rather than replaced, because
|
|
1756
|
+
* everything else about that status — which file, how far, of how many — is
|
|
1757
|
+
* still true. Two copies of that rule are two copies that can stop agreeing
|
|
1758
|
+
* about what a paused bar says.
|
|
1759
|
+
*/
|
|
1760
|
+
/**
|
|
1761
|
+
* Bind pause/resume to the live submission's gate.
|
|
1762
|
+
*
|
|
1763
|
+
* @param getGate - Reads the gate out of the caller's ref, so the callbacks stay
|
|
1764
|
+
* stable across submissions rather than being re-created per gate.
|
|
1765
|
+
* @param setUpload - The status setter the progress bar renders from.
|
|
1766
|
+
*
|
|
1767
|
+
* @internal
|
|
1768
|
+
*/
|
|
1769
|
+
function useUploadPause(getGate, setUpload) {
|
|
1770
|
+
const setPaused = useCallback((paused) => {
|
|
1771
|
+
setUpload((current) => current ? {
|
|
1772
|
+
...current,
|
|
1773
|
+
paused
|
|
1774
|
+
} : current);
|
|
1775
|
+
}, [setUpload]);
|
|
1776
|
+
return {
|
|
1777
|
+
pauseUpload: useCallback(() => {
|
|
1778
|
+
getGate()?.pause();
|
|
1779
|
+
setPaused(true);
|
|
1780
|
+
}, [getGate, setPaused]),
|
|
1781
|
+
resumeUpload: useCallback(() => {
|
|
1782
|
+
getGate()?.resume();
|
|
1783
|
+
setPaused(false);
|
|
1784
|
+
}, [getGate, setPaused])
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
//#endregion
|
|
1788
|
+
//#region src/_submission-state.ts
|
|
1789
|
+
/** The shared submission scaffold. See the module doc. */
|
|
1790
|
+
function useSubmissionState() {
|
|
1791
|
+
const [runId, setRunId] = useState(void 0);
|
|
1792
|
+
const [starting, setStarting] = useState(false);
|
|
1793
|
+
const [startError, setStartError] = useState(void 0);
|
|
1794
|
+
const [upload, setUpload] = useState(void 0);
|
|
1795
|
+
const current = useRef(void 0);
|
|
1796
|
+
const begin = useCallback((token) => {
|
|
1797
|
+
setStarting(true);
|
|
1798
|
+
setStartError(void 0);
|
|
1799
|
+
setRunId(void 0);
|
|
1800
|
+
current.current?.gate.cancel();
|
|
1801
|
+
current.current = token;
|
|
1802
|
+
}, []);
|
|
1803
|
+
const end = useCallback((token) => {
|
|
1804
|
+
if (current.current !== token) return;
|
|
1805
|
+
current.current = void 0;
|
|
1806
|
+
setStarting(false);
|
|
1807
|
+
setUpload(void 0);
|
|
1808
|
+
}, []);
|
|
1809
|
+
const reset = useCallback(() => {
|
|
1810
|
+
current.current?.gate.cancel();
|
|
1811
|
+
current.current = void 0;
|
|
1812
|
+
setRunId(void 0);
|
|
1813
|
+
setStartError(void 0);
|
|
1814
|
+
setUpload(void 0);
|
|
1815
|
+
}, []);
|
|
1816
|
+
const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => current.current?.gate, []), setUpload);
|
|
1817
|
+
return {
|
|
1818
|
+
runId,
|
|
1819
|
+
starting,
|
|
1820
|
+
startError,
|
|
1821
|
+
upload,
|
|
1822
|
+
actions: useMemo(() => ({
|
|
1823
|
+
setRunId,
|
|
1824
|
+
setStartError,
|
|
1825
|
+
setUpload,
|
|
1826
|
+
begin,
|
|
1827
|
+
end,
|
|
1828
|
+
reset,
|
|
1829
|
+
pauseUpload,
|
|
1830
|
+
resumeUpload
|
|
1831
|
+
}), [
|
|
1832
|
+
begin,
|
|
1833
|
+
end,
|
|
1834
|
+
reset,
|
|
1835
|
+
pauseUpload,
|
|
1836
|
+
resumeUpload
|
|
1837
|
+
])
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
//#endregion
|
|
1492
1841
|
//#region src/_upload-recall.ts
|
|
1493
1842
|
/**
|
|
1494
1843
|
* Where an upload's ID survives a page RELOAD.
|
|
@@ -1554,9 +1903,9 @@ function fingerprint(file) {
|
|
|
1554
1903
|
}
|
|
1555
1904
|
/** This scope's remembered ids, or nothing at all — a parse failure is nothing. */
|
|
1556
1905
|
function read(scope) {
|
|
1906
|
+
const raw = storageGet("session", keyFor(scope));
|
|
1907
|
+
if (raw === void 0) return {};
|
|
1557
1908
|
try {
|
|
1558
|
-
const raw = globalThis.sessionStorage?.getItem(keyFor(scope));
|
|
1559
|
-
if (raw === null || raw === void 0) return {};
|
|
1560
1909
|
const parsed = JSON.parse(raw);
|
|
1561
1910
|
return isRecord(parsed) ? parsed : {};
|
|
1562
1911
|
} catch {
|
|
@@ -1564,9 +1913,7 @@ function read(scope) {
|
|
|
1564
1913
|
}
|
|
1565
1914
|
}
|
|
1566
1915
|
function write(scope, entries) {
|
|
1567
|
-
|
|
1568
|
-
globalThis.sessionStorage?.setItem(keyFor(scope), JSON.stringify(entries));
|
|
1569
|
-
} catch {}
|
|
1916
|
+
storageSet("session", keyFor(scope), JSON.stringify(entries));
|
|
1570
1917
|
}
|
|
1571
1918
|
/**
|
|
1572
1919
|
* The id this file was last being stored under in this tab, if any.
|
|
@@ -1710,7 +2057,7 @@ function createUploadGate() {
|
|
|
1710
2057
|
*
|
|
1711
2058
|
* Its own module because both submit hooks need the identical answer and then do
|
|
1712
2059
|
* two different things with it — `useWorkflowSubmit` stores each file and passes
|
|
1713
|
-
* its id, `useWorkflowStream`
|
|
2060
|
+
* its id, `useWorkflowStream` mints the id first so the run can start on it.
|
|
1714
2061
|
* A second copy of this predicate would be a form field that one hook treats as a
|
|
1715
2062
|
* file and the other does not, which is invisible until the run reads the wrong
|
|
1716
2063
|
* kind of string.
|
|
@@ -1914,43 +2261,39 @@ async function uploadFiles(api, input, report, parallel, session) {
|
|
|
1914
2261
|
return out;
|
|
1915
2262
|
}
|
|
1916
2263
|
//#endregion
|
|
1917
|
-
//#region src/_upload-
|
|
2264
|
+
//#region src/_upload-report.ts
|
|
1918
2265
|
/**
|
|
1919
|
-
*
|
|
1920
|
-
* submission is currently holding.
|
|
2266
|
+
* What the bar draws, as a comparable string.
|
|
1921
2267
|
*
|
|
1922
|
-
*
|
|
1923
|
-
*
|
|
1924
|
-
*
|
|
1925
|
-
*
|
|
1926
|
-
* still true. Two copies of that rule are two copies that can stop agreeing
|
|
1927
|
-
* about what a paused bar says.
|
|
2268
|
+
* The fraction is quantized to a tenth of a percent — finer than anything the
|
|
2269
|
+
* bar or the byte readout resolves. Everything else is compared exactly:
|
|
2270
|
+
* `paused` is a state change a person is waiting to see, and the name/index
|
|
2271
|
+
* pair moving means a different FILE, which must never be coalesced away.
|
|
1928
2272
|
*/
|
|
2273
|
+
function renderKey(status) {
|
|
2274
|
+
const { name, index, count, loaded, total, fraction, paused } = status;
|
|
2275
|
+
const shown = fraction === void 0 ? loaded : Math.round(fraction * 1e3);
|
|
2276
|
+
return `${paused} ${index} ${count} ${total ?? -1} ${shown} ${name}`;
|
|
2277
|
+
}
|
|
1929
2278
|
/**
|
|
1930
|
-
*
|
|
2279
|
+
* Wrap an `UploadStatus` setter so redundant reports never reach React.
|
|
1931
2280
|
*
|
|
1932
|
-
*
|
|
1933
|
-
*
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
1936
|
-
* @internal
|
|
2281
|
+
* Clearing the status (`undefined`) always passes and resets the comparison —
|
|
2282
|
+
* it ends one file's bar, and the next file must not be coalesced against the
|
|
2283
|
+
* previous one's last frame.
|
|
1937
2284
|
*/
|
|
1938
|
-
function
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
resumeUpload: useCallback(() => {
|
|
1951
|
-
getGate()?.resume();
|
|
1952
|
-
setPaused(false);
|
|
1953
|
-
}, [getGate, setPaused])
|
|
2285
|
+
function coalesceUploadReports(set) {
|
|
2286
|
+
let last;
|
|
2287
|
+
return (status) => {
|
|
2288
|
+
if (status === void 0) {
|
|
2289
|
+
last = void 0;
|
|
2290
|
+
set(void 0);
|
|
2291
|
+
return;
|
|
2292
|
+
}
|
|
2293
|
+
const key = renderKey(status);
|
|
2294
|
+
if (key === last) return;
|
|
2295
|
+
last = key;
|
|
2296
|
+
set(status);
|
|
1954
2297
|
};
|
|
1955
2298
|
}
|
|
1956
2299
|
//#endregion
|
|
@@ -2022,81 +2365,69 @@ function useUploadPause(getGate, setUpload) {
|
|
|
2022
2365
|
*
|
|
2023
2366
|
* @public
|
|
2024
2367
|
*/
|
|
2025
|
-
function useWorkflowSubmit(workflow,
|
|
2026
|
-
const { api, recover = true, wait, intervalMs, parallel } =
|
|
2027
|
-
const key = useDefaultRunKey(
|
|
2028
|
-
const
|
|
2029
|
-
const
|
|
2030
|
-
const [startError, setStartError] = useState(void 0);
|
|
2031
|
-
const [upload, setUpload] = useState(void 0);
|
|
2032
|
-
const session = useRef(void 0);
|
|
2368
|
+
function useWorkflowSubmit(workflow, options = {}) {
|
|
2369
|
+
const { api, recover = true, wait, intervalMs, parallel } = options;
|
|
2370
|
+
const key = useDefaultRunKey(options.key);
|
|
2371
|
+
const state = useSubmissionState();
|
|
2372
|
+
const { runId, actions } = state;
|
|
2033
2373
|
const getClient = useWorkflowApiRef(api);
|
|
2034
2374
|
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
2035
2375
|
api,
|
|
2036
2376
|
intervalMs
|
|
2037
2377
|
}));
|
|
2038
2378
|
const { wake, cancel } = useRunControls(runId, getClient);
|
|
2379
|
+
const [startedHere, setStartedHere] = useState(false);
|
|
2039
2380
|
const recovering = useRecoveredRun({
|
|
2040
2381
|
workflow,
|
|
2041
2382
|
key,
|
|
2042
2383
|
enabled: recover,
|
|
2043
2384
|
getClient,
|
|
2044
2385
|
onFound: (found) => {
|
|
2045
|
-
setRunId((current) => current ?? found);
|
|
2386
|
+
actions.setRunId((current) => current ?? found);
|
|
2046
2387
|
},
|
|
2047
|
-
onError: setStartError
|
|
2388
|
+
onError: actions.setStartError
|
|
2048
2389
|
});
|
|
2049
2390
|
const submit = useCallback(async (input) => {
|
|
2050
2391
|
const client = getClient();
|
|
2051
|
-
setStarting(true);
|
|
2052
|
-
setStartError(void 0);
|
|
2053
|
-
setRunId(void 0);
|
|
2054
|
-
session.current?.gate.cancel();
|
|
2055
2392
|
const current = createUploadSession(workflow);
|
|
2056
|
-
|
|
2393
|
+
setStartedHere(true);
|
|
2394
|
+
actions.begin(current);
|
|
2057
2395
|
try {
|
|
2058
2396
|
const options = omitUndefined({ key });
|
|
2059
|
-
const started = await uploadFiles(client, input, setUpload, parallel, current);
|
|
2060
|
-
setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
|
|
2397
|
+
const started = await uploadFiles(client, input, coalesceUploadReports(actions.setUpload), parallel, current);
|
|
2398
|
+
actions.setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
|
|
2061
2399
|
...options,
|
|
2062
2400
|
wait
|
|
2063
2401
|
})).runId);
|
|
2064
2402
|
} catch (err) {
|
|
2065
|
-
if (!current.gate.cancelled) setStartError(errorMessage(err));
|
|
2403
|
+
if (!current.gate.cancelled) actions.setStartError(errorMessage(err));
|
|
2066
2404
|
} finally {
|
|
2067
|
-
|
|
2068
|
-
session.current = void 0;
|
|
2069
|
-
setStarting(false);
|
|
2070
|
-
setUpload(void 0);
|
|
2071
|
-
}
|
|
2405
|
+
actions.end(current);
|
|
2072
2406
|
}
|
|
2073
2407
|
}, [
|
|
2074
2408
|
workflow,
|
|
2075
2409
|
key,
|
|
2076
2410
|
wait,
|
|
2077
2411
|
parallel,
|
|
2078
|
-
getClient
|
|
2412
|
+
getClient,
|
|
2413
|
+
actions
|
|
2079
2414
|
]);
|
|
2080
|
-
const reset = useCallback(() => {
|
|
2081
|
-
session.current?.gate.cancel();
|
|
2082
|
-
session.current = void 0;
|
|
2083
|
-
setRunId(void 0);
|
|
2084
|
-
setStartError(void 0);
|
|
2085
|
-
setUpload(void 0);
|
|
2086
|
-
}, []);
|
|
2087
|
-
const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => session.current?.gate, []), setUpload);
|
|
2088
2415
|
return {
|
|
2089
2416
|
submit,
|
|
2090
2417
|
submitForm: submit,
|
|
2091
|
-
reset
|
|
2418
|
+
reset: useCallback(() => {
|
|
2419
|
+
setStartedHere(false);
|
|
2420
|
+
actions.reset();
|
|
2421
|
+
}, [actions]),
|
|
2092
2422
|
wake,
|
|
2093
2423
|
cancel,
|
|
2094
|
-
pauseUpload,
|
|
2095
|
-
resumeUpload,
|
|
2424
|
+
pauseUpload: actions.pauseUpload,
|
|
2425
|
+
resumeUpload: actions.resumeUpload,
|
|
2096
2426
|
run: tracked.run,
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2427
|
+
startedHere,
|
|
2428
|
+
pending: recovering || state.starting || tracked.polling,
|
|
2429
|
+
upload: state.upload,
|
|
2430
|
+
error: state.startError ?? tracked.error
|
|
2100
2431
|
};
|
|
2101
2432
|
}
|
|
2102
2433
|
//#endregion
|
|
@@ -2141,8 +2472,8 @@ function useWorkflowSubmit(workflow, opts = {}) {
|
|
|
2141
2472
|
*
|
|
2142
2473
|
* @public
|
|
2143
2474
|
*/
|
|
2144
|
-
function useWorkflowRuns(workflow,
|
|
2145
|
-
const { api, limit, key, skip = false } =
|
|
2475
|
+
function useWorkflowRuns(workflow, options = {}) {
|
|
2476
|
+
const { api, limit, key, skip = false } = options;
|
|
2146
2477
|
const [runs, setRuns] = useState([]);
|
|
2147
2478
|
const [loading, setLoading] = useState(!skip && workflow !== void 0);
|
|
2148
2479
|
const [error, setError] = useState(void 0);
|
|
@@ -2226,7 +2557,7 @@ function useWorkflowRuns(workflow, opts = {}) {
|
|
|
2226
2557
|
* earlier version of this hook took a `cut` callback and uploaded N parts into a
|
|
2227
2558
|
* "group" that a separate call had to seal; it worked, and every piece of it was
|
|
2228
2559
|
* something the caller had to get right. The whole of that is replaced by the store
|
|
2229
|
-
* publishing `size` as bytes land — which `
|
|
2560
|
+
* publishing `size` as bytes land — which `stepReadUpload` already clamped to — so the
|
|
2230
2561
|
* run does exactly what it does over a finished file and simply waits for windows to
|
|
2231
2562
|
* become present.
|
|
2232
2563
|
*
|
|
@@ -2302,27 +2633,23 @@ function useWorkflowRuns(workflow, opts = {}) {
|
|
|
2302
2633
|
*
|
|
2303
2634
|
* @public
|
|
2304
2635
|
*/
|
|
2305
|
-
function useWorkflowStream(workflow,
|
|
2306
|
-
const { api, key, intervalMs, parallel } =
|
|
2307
|
-
const
|
|
2308
|
-
const
|
|
2309
|
-
const [startError, setStartError] = useState(void 0);
|
|
2310
|
-
const [upload, setUpload] = useState(void 0);
|
|
2311
|
-
const gateRef = useRef(void 0);
|
|
2636
|
+
function useWorkflowStream(workflow, options = {}) {
|
|
2637
|
+
const { api, key, intervalMs, parallel } = options;
|
|
2638
|
+
const state = useSubmissionState();
|
|
2639
|
+
const { runId, actions } = state;
|
|
2312
2640
|
const getClient = useWorkflowApiRef(api);
|
|
2313
2641
|
const tracked = useWorkflowRun(runId, omitUndefined({
|
|
2314
2642
|
api,
|
|
2315
2643
|
intervalMs
|
|
2316
2644
|
}));
|
|
2317
2645
|
const { wake, cancel } = useRunControls(runId, getClient);
|
|
2646
|
+
const [startedHere, setStartedHere] = useState(false);
|
|
2318
2647
|
const submit = useCallback(async (input) => {
|
|
2319
2648
|
const client = getClient();
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
const gate = createUploadGate();
|
|
2325
|
-
gateRef.current = gate;
|
|
2649
|
+
const current = { gate: createUploadGate() };
|
|
2650
|
+
const { gate } = current;
|
|
2651
|
+
setStartedHere(true);
|
|
2652
|
+
actions.begin(current);
|
|
2326
2653
|
let started;
|
|
2327
2654
|
try {
|
|
2328
2655
|
const id = randomUploadId();
|
|
@@ -2335,7 +2662,7 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
2335
2662
|
});
|
|
2336
2663
|
started = begun.runId;
|
|
2337
2664
|
const chosen = begun.file;
|
|
2338
|
-
setRunId(started);
|
|
2665
|
+
actions.setRunId(started);
|
|
2339
2666
|
if (!chosen) return;
|
|
2340
2667
|
await streamFile({
|
|
2341
2668
|
client,
|
|
@@ -2343,45 +2670,38 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
2343
2670
|
id,
|
|
2344
2671
|
file: chosen,
|
|
2345
2672
|
parallel,
|
|
2346
|
-
report: setUpload
|
|
2673
|
+
report: coalesceUploadReports(actions.setUpload)
|
|
2347
2674
|
});
|
|
2348
2675
|
await client.wake(started).catch(() => void 0);
|
|
2349
2676
|
} catch (err) {
|
|
2350
|
-
if (!gate.cancelled) setStartError(errorMessage(err));
|
|
2677
|
+
if (!gate.cancelled) actions.setStartError(errorMessage(err));
|
|
2351
2678
|
if (started) await client.cancel(started).catch(() => void 0);
|
|
2352
2679
|
} finally {
|
|
2353
|
-
|
|
2354
|
-
gateRef.current = void 0;
|
|
2355
|
-
setStarting(false);
|
|
2356
|
-
setUpload(void 0);
|
|
2357
|
-
}
|
|
2680
|
+
actions.end(current);
|
|
2358
2681
|
}
|
|
2359
2682
|
}, [
|
|
2360
2683
|
workflow,
|
|
2361
2684
|
key,
|
|
2362
2685
|
parallel,
|
|
2363
|
-
getClient
|
|
2686
|
+
getClient,
|
|
2687
|
+
actions
|
|
2364
2688
|
]);
|
|
2365
|
-
const reset = useCallback(() => {
|
|
2366
|
-
gateRef.current?.cancel();
|
|
2367
|
-
gateRef.current = void 0;
|
|
2368
|
-
setRunId(void 0);
|
|
2369
|
-
setStartError(void 0);
|
|
2370
|
-
setUpload(void 0);
|
|
2371
|
-
}, []);
|
|
2372
|
-
const { pauseUpload, resumeUpload } = useUploadPause(useCallback(() => gateRef.current, []), setUpload);
|
|
2373
2689
|
return {
|
|
2374
2690
|
submit,
|
|
2375
2691
|
submitForm: submit,
|
|
2376
|
-
reset
|
|
2692
|
+
reset: useCallback(() => {
|
|
2693
|
+
setStartedHere(false);
|
|
2694
|
+
actions.reset();
|
|
2695
|
+
}, [actions]),
|
|
2377
2696
|
wake,
|
|
2378
2697
|
cancel,
|
|
2379
|
-
pauseUpload,
|
|
2380
|
-
resumeUpload,
|
|
2698
|
+
pauseUpload: actions.pauseUpload,
|
|
2699
|
+
resumeUpload: actions.resumeUpload,
|
|
2381
2700
|
run: tracked.run,
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2701
|
+
startedHere,
|
|
2702
|
+
pending: state.starting || tracked.polling,
|
|
2703
|
+
upload: state.upload,
|
|
2704
|
+
error: state.startError ?? tracked.error
|
|
2385
2705
|
};
|
|
2386
2706
|
}
|
|
2387
2707
|
/**
|
|
@@ -2392,8 +2712,8 @@ function useWorkflowStream(workflow, opts = {}) {
|
|
|
2392
2712
|
* listing in state would make a submit before it landed a race, and the failure
|
|
2393
2713
|
* mode of that race is a `File` reaching a run input.
|
|
2394
2714
|
*/
|
|
2395
|
-
async function beginRun(
|
|
2396
|
-
const { client, workflow, input, id } =
|
|
2715
|
+
async function beginRun(options) {
|
|
2716
|
+
const { client, workflow, input, id } = options;
|
|
2397
2717
|
const field = await uploadField(client, workflow);
|
|
2398
2718
|
const chosen = field === void 0 ? void 0 : fileAt(input, field);
|
|
2399
2719
|
const payload = chosen && field ? {
|
|
@@ -2402,7 +2722,7 @@ async function beginRun(opts) {
|
|
|
2402
2722
|
} : input;
|
|
2403
2723
|
assertSendable(workflow, payload, field);
|
|
2404
2724
|
return {
|
|
2405
|
-
runId: await client.start(workflow, payload, omitUndefined({ key:
|
|
2725
|
+
runId: await client.start(workflow, payload, omitUndefined({ key: options.key })),
|
|
2406
2726
|
file: chosen
|
|
2407
2727
|
};
|
|
2408
2728
|
}
|
|
@@ -2414,8 +2734,8 @@ async function beginRun(opts) {
|
|
|
2414
2734
|
* the id, start the run, then the bytes, then the wake — and the sending is the
|
|
2415
2735
|
* one step of that list with a loop in it.
|
|
2416
2736
|
*/
|
|
2417
|
-
async function streamFile(
|
|
2418
|
-
const { client, gate, id, file, parallel, report } =
|
|
2737
|
+
async function streamFile(options) {
|
|
2738
|
+
const { client, gate, id, file, parallel, report } = options;
|
|
2419
2739
|
await sendThroughGate(gate, async (resume) => {
|
|
2420
2740
|
await client.uploadStream(id, file, {
|
|
2421
2741
|
name: file.name,
|
|
@@ -2503,4 +2823,4 @@ const WORKFLOW_STATUS_LABELS = {
|
|
|
2503
2823
|
cancelled: "Cancelled"
|
|
2504
2824
|
};
|
|
2505
2825
|
//#endregion
|
|
2506
|
-
export { AutoScroll, Button, ChatView, CheckboxField, ConsoleShell, Controls, Field, FileField, Form, Markdown, MessageList, NumberField, SelectField, SidebarLayout, StartScreen, SubmitButton, TextAreaField, TextField, ToolCallRow, UploadProgressBar, WORKFLOW_STATUS_LABELS, WorkflowFields, WorkflowProgress,
|
|
2826
|
+
export { AGENT_STATE_LABELS, AutoScroll, BulletList, Button, ChatView, CheckboxField, ConsoleShell, Controls, Facts, Field, FileField, Form, Markdown, MessageList, NumberField, SelectField, SessionErrorBanner, SidebarLayout, StartScreen, SubmitButton, TextAreaField, TextField, ToolCallRow, UploadProgressBar, WORKFLOW_STATUS_LABELS, WorkflowFields, WorkflowProgress, createBrowserSession, createWorkflowApi, fetchClientConfig, fieldKindFor, isTerminal, mountClient, mountPage, useAgentState, useConversation, useCopy, useDownloadUrl, useEvent, useFlash, useRunKey, useSession, useSessionActions, useSessionError, useSessionSelector, useSessionStatus, useTheme, useToolCallStart, useToolResult, useUserTranscript, useWorkflowProgress, useWorkflowRun, useWorkflowRuns, useWorkflowStream, useWorkflowSubmit, useWorkflows };
|