@alexkroman1/aai-ui 6.3.1 → 6.5.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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Which of a submitted form's values are FILES.
3
+ *
4
+ * Its own module because both submit hooks need the identical answer and then do
5
+ * two different things with it — `useWorkflowSubmit` stores each file and passes
6
+ * its id, `useWorkflowStream` cuts it into parts and passes the group they share.
7
+ * A second copy of this predicate would be a form field that one hook treats as a
8
+ * file and the other does not, which is invisible until the run reads the wrong
9
+ * kind of string.
10
+ */
11
+ /**
12
+ * The files a submitted field carries, if that is what it carries.
13
+ *
14
+ * An array counts only when it is files ALL the way through — a mixed array is
15
+ * some other field's value that happens to contain one, and turning half of it
16
+ * into ids would corrupt it silently.
17
+ */
18
+ export declare function filesOf(value: unknown): File[];
package/dist/index.d.ts CHANGED
@@ -30,4 +30,5 @@ export { type UploadStatus, type UseWorkflowSubmitOptions, type UseWorkflowsOpti
30
30
  export { DEFAULT_PROGRESS_POLL_MS, type UseWorkflowProgressResult, useWorkflowProgress, } from "./use-workflow-progress.ts";
31
31
  export { DEFAULT_WORKFLOW_POLL_MS, MAX_MISSING_READS, type UseWorkflowRunResult, useWorkflowRun, } from "./use-workflow-run.ts";
32
32
  export { type UseWorkflowRunsOptions, type UseWorkflowRunsResult, useWorkflowRuns, } from "./use-workflow-runs.ts";
33
+ export { type UseWorkflowStreamOptions, useWorkflowStream, type WorkflowStreamSubmission, } from "./use-workflow-stream.ts";
33
34
  export { createWorkflowApi, isTerminal, type WorkflowApi, type WorkflowApiOptions, type WorkflowOutputOf, type WorkflowRun, type WorkflowSummary, } from "./workflow-client.ts";
package/dist/index.js CHANGED
@@ -625,6 +625,31 @@ function useWorkflowApiRef(api) {
625
625
  }, []);
626
626
  }
627
627
  //#endregion
628
+ //#region _workflow-files.ts
629
+ /**
630
+ * Which of a submitted form's values are FILES.
631
+ *
632
+ * Its own module because both submit hooks need the identical answer and then do
633
+ * two different things with it — `useWorkflowSubmit` stores each file and passes
634
+ * its id, `useWorkflowStream` cuts it into parts and passes the group they share.
635
+ * A second copy of this predicate would be a form field that one hook treats as a
636
+ * file and the other does not, which is invisible until the run reads the wrong
637
+ * kind of string.
638
+ */
639
+ /**
640
+ * The files a submitted field carries, if that is what it carries.
641
+ *
642
+ * An array counts only when it is files ALL the way through — a mixed array is
643
+ * some other field's value that happens to contain one, and turning half of it
644
+ * into ids would corrupt it silently.
645
+ */
646
+ function filesOf(value) {
647
+ if (value instanceof File) return [value];
648
+ if (!Array.isArray(value)) return [];
649
+ const files = value.filter((one) => one instanceof File);
650
+ return files.length > 0 && files.length === value.length ? files : [];
651
+ }
652
+ //#endregion
628
653
  //#region _repeat-until.ts
629
654
  /**
630
655
  * A bounded read, re-armed from the SETTLED read — the loop both workflow
@@ -1031,19 +1056,6 @@ function useWorkflows(opts = {}) {
1031
1056
  return state;
1032
1057
  }
1033
1058
  /**
1034
- * The files a submitted field carries, if that is what it carries.
1035
- *
1036
- * An array counts only when it is files ALL the way through — a mixed array is
1037
- * some other field's value that happens to contain one, and turning half of it
1038
- * into ids would corrupt it silently.
1039
- */
1040
- function filesOf(value) {
1041
- if (value instanceof File) return [value];
1042
- if (!Array.isArray(value)) return [];
1043
- const files = value.filter((one) => one instanceof File);
1044
- return files.length > 0 && files.length === value.length ? files : [];
1045
- }
1046
- /**
1047
1059
  * Replace every `File` in a submitted form with the id of a stored upload,
1048
1060
  * reporting how far each one has got.
1049
1061
  *
@@ -1056,7 +1068,7 @@ function filesOf(value) {
1056
1068
  * so this is invisible to every form that has none — including one whose values
1057
1069
  * are not an object at all, which `submit` accepts.
1058
1070
  */
1059
- async function uploadFiles(api, input, report) {
1071
+ async function uploadFiles(api, input, report, parallel) {
1060
1072
  if (!isRecord(input)) return input;
1061
1073
  const entries = Object.entries(input);
1062
1074
  const count = entries.reduce((total, [, value]) => total + filesOf(value).length, 0);
@@ -1068,10 +1080,13 @@ async function uploadFiles(api, input, report) {
1068
1080
  index,
1069
1081
  count
1070
1082
  };
1071
- return (await api.upload(file, { onProgress: (progress) => report({
1072
- ...position,
1073
- ...progress
1074
- }) })).id;
1083
+ return (await api.upload(file, {
1084
+ onProgress: (progress) => report({
1085
+ ...position,
1086
+ ...progress
1087
+ }),
1088
+ ...omitUndefined({ parallel })
1089
+ })).id;
1075
1090
  };
1076
1091
  const out = {};
1077
1092
  for (const [name, value] of entries) {
@@ -1116,7 +1131,7 @@ async function uploadFiles(api, input, report) {
1116
1131
  * @public
1117
1132
  */
1118
1133
  function useWorkflowSubmit(workflow, opts = {}) {
1119
- const { api, key, wait, intervalMs } = opts;
1134
+ const { api, key, wait, intervalMs, parallel } = opts;
1120
1135
  const [runId, setRunId] = useState(void 0);
1121
1136
  const [starting, setStarting] = useState(false);
1122
1137
  const [startError, setStartError] = useState(void 0);
@@ -1134,7 +1149,7 @@ function useWorkflowSubmit(workflow, opts = {}) {
1134
1149
  setRunId(void 0);
1135
1150
  try {
1136
1151
  const options = omitUndefined({ key });
1137
- const started = await uploadFiles(client, input, setUpload);
1152
+ const started = await uploadFiles(client, input, setUpload, parallel);
1138
1153
  setRunId(wait === void 0 ? await client.start(workflow, started, options) : (await client.startAndWait(workflow, started, {
1139
1154
  ...options,
1140
1155
  wait
@@ -1149,6 +1164,7 @@ function useWorkflowSubmit(workflow, opts = {}) {
1149
1164
  workflow,
1150
1165
  key,
1151
1166
  wait,
1167
+ parallel,
1152
1168
  getClient
1153
1169
  ]),
1154
1170
  reset: useCallback(() => {
@@ -1761,4 +1777,173 @@ function useWorkflowRuns(workflow, opts = {}) {
1761
1777
  };
1762
1778
  }
1763
1779
  //#endregion
1764
- export { ApiUrlChip, AutoScroll, Button, ChatView, CheckboxField, Controls, DEFAULT_PROGRESS_POLL_MS, DEFAULT_WORKFLOW_POLL_MS, Field, FileField, Form, MAX_MISSING_READS, Markdown, MessageList, NumberField, SelectField, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, SubmitButton, TRANSCRIBING_PLACEHOLDER, TextAreaField, TextField, ThemeProvider, ToolCallRow, ToolConfigContext, UiUrlChip, UploadProgressBar, VOICE_CAPTURE_CONSTRAINTS, WorkflowFields, WorkflowProgress, buildAgentUrl, client, createSessionCore, createWorkflowApi, fetchClientConfig, isTerminal, loadClientConfig, page, useAgentState, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult, useUserTranscript, useWorkflowProgress, useWorkflowRun, useWorkflowRuns, useWorkflowSubmit, useWorkflows };
1780
+ //#region use-workflow-stream.ts
1781
+ /**
1782
+ * Starting a run BEFORE its file has finished uploading.
1783
+ *
1784
+ * `useWorkflowSubmit` (`use-workflow-form.ts`) stores every file and then starts the
1785
+ * run, because a `POST /workflows/uploads` cannot answer with an id until the last
1786
+ * byte is in — the store writes an ordinary upload's record last, so "incomplete"
1787
+ * and "no such upload" are deliberately the same answer. For a long recording that
1788
+ * order is most of the wall clock.
1789
+ *
1790
+ * This hook inverts it, and it takes ONE extra idea to do so: the id is the
1791
+ * CLIENT's. It mints one, starts the run on it, and `PUT`s the whole file in a
1792
+ * single streaming request; the upload record exists from the first byte with
1793
+ * `complete: false` and its `size` grows, so the run reads whatever has arrived.
1794
+ *
1795
+ * ```text
1796
+ * start run ─┬──────────────────────────────────────────────► (still running)
1797
+ * │
1798
+ * PUT /uploads/<id> ═══════════════════════════════════► wake
1799
+ * (one request; the run polls `size` and `complete`)
1800
+ * ```
1801
+ *
1802
+ * ## What it does NOT do, which is the point
1803
+ *
1804
+ * There is no cutting, no part numbering, no terminator and no per-part request. An
1805
+ * earlier version of this hook took a `cut` callback and uploaded N parts into a
1806
+ * "group" that a separate call had to seal; it worked, and every piece of it was
1807
+ * something the caller had to get right. The whole of that is replaced by the store
1808
+ * publishing `size` as bytes land — which `readUpload` already clamped to — so the
1809
+ * run does exactly what it does over a finished file and simply waits for windows to
1810
+ * become present.
1811
+ *
1812
+ * It also means the FORMAT knowledge stays where it already was. Deciding where a
1813
+ * recording may be divided is the run's business (`planSegments` in the
1814
+ * transcription template), and nothing here needs to know it is audio at all.
1815
+ *
1816
+ * ## Three things it owns
1817
+ *
1818
+ * - **The id.** Minted here, put in the run input where the workflow's `uploads`
1819
+ * list says, and never seen by the page. It is a capability — anyone holding it
1820
+ * can read the bytes back — so it is a `crypto.randomUUID()` rather than anything
1821
+ * derived from the file, and the store refuses a second `PUT` to it.
1822
+ * - **The wake after the upload.** `POST /workflows/runs/:id/wake` ends a pending
1823
+ * `sleep`, and a run waiting on an upload is asleep between polls — so without
1824
+ * this it learns the file is complete up to a poll interval late, every time. On
1825
+ * the transcription template's 5-second interval that is most of the tail it has
1826
+ * left to pay. Best-effort, because a wake that finds nothing sleeping answers 0
1827
+ * and a missed one costs latency rather than correctness.
1828
+ * - **Reporting the bytes.** The same `UploadStatus` `useWorkflowSubmit` reports,
1829
+ * so `<UploadProgressBar>` renders either without knowing which hook it came from.
1830
+ *
1831
+ * ## A failed upload CANCELS the run
1832
+ *
1833
+ * An upload that dies stays in the store, incomplete, and `complete` never becomes
1834
+ * true — so a run left behind polls until its own abandonment bound and then fails,
1835
+ * minutes after the page has already reported the error. Cancelling is the honest
1836
+ * end to a submission that did not happen. The cost is that work already done is
1837
+ * thrown away with the run; a caller who wants to resume instead drives
1838
+ * `api.uploadStream` and `api.uploadInfo` directly.
1839
+ */
1840
+ /**
1841
+ * Start a workflow run and stream a file into it while it works.
1842
+ *
1843
+ * The workflow declares which input property carries the upload
1844
+ * (`workflow({ uploads: ["recording"] })`) — the same declaration
1845
+ * `useWorkflowSubmit` reads, because what the property carries is an upload id
1846
+ * either way. What differs is only WHEN the id becomes valid.
1847
+ *
1848
+ * @typeParam R - The workflow's output type, which is what makes
1849
+ * `run.status === "completed"` narrow to a typed `run.output`. Derive it with
1850
+ * `WorkflowOutputOf<typeof myWorkflow>`.
1851
+ *
1852
+ * @example
1853
+ * ```tsx no-check
1854
+ * const { submit, run, upload, pending, error } = useWorkflowStream("transcribe");
1855
+ *
1856
+ * <Form onSubmit={(values) => submit(values)} error={error}>
1857
+ * <WorkflowFields workflow="transcribe" />
1858
+ * <UploadProgressBar upload={upload} />
1859
+ * <SubmitButton pending={pending}>Transcribe</SubmitButton>
1860
+ * </Form>
1861
+ * ```
1862
+ *
1863
+ * @public
1864
+ */
1865
+ function useWorkflowStream(workflow, opts = {}) {
1866
+ const { api, key, intervalMs, parallel } = opts;
1867
+ const [runId, setRunId] = useState(void 0);
1868
+ const [starting, setStarting] = useState(false);
1869
+ const [startError, setStartError] = useState(void 0);
1870
+ const [upload, setUpload] = useState(void 0);
1871
+ const getClient = useWorkflowApiRef(api);
1872
+ const tracked = useWorkflowRun(runId, {
1873
+ ...api && { api },
1874
+ ...omitUndefined({ intervalMs })
1875
+ });
1876
+ return {
1877
+ submit: useCallback(async (input) => {
1878
+ const client = getClient();
1879
+ setStarting(true);
1880
+ setStartError(void 0);
1881
+ setRunId(void 0);
1882
+ let started;
1883
+ try {
1884
+ const field = await uploadField(client, workflow);
1885
+ const chosen = field === void 0 ? void 0 : fileAt(input, field);
1886
+ const id = randomUploadId();
1887
+ const payload = chosen && field ? {
1888
+ ...input,
1889
+ [field]: id
1890
+ } : input;
1891
+ started = await client.start(workflow, payload, omitUndefined({ key }));
1892
+ setRunId(started);
1893
+ if (!chosen) return;
1894
+ await client.uploadStream(id, chosen, {
1895
+ name: chosen.name,
1896
+ onProgress: (progress) => setUpload({
1897
+ ...progress,
1898
+ name: chosen.name,
1899
+ index: 1,
1900
+ count: 1
1901
+ }),
1902
+ ...omitUndefined({ parallel })
1903
+ });
1904
+ await client.wake(started).catch(() => void 0);
1905
+ } catch (err) {
1906
+ setStartError(errorMessage(err));
1907
+ if (started) await client.cancel(started).catch(() => void 0);
1908
+ } finally {
1909
+ setStarting(false);
1910
+ setUpload(void 0);
1911
+ }
1912
+ }, [
1913
+ workflow,
1914
+ key,
1915
+ parallel,
1916
+ getClient
1917
+ ]),
1918
+ reset: useCallback(() => {
1919
+ setRunId(void 0);
1920
+ setStartError(void 0);
1921
+ setUpload(void 0);
1922
+ }, []),
1923
+ run: tracked.run,
1924
+ pending: starting || tracked.polling,
1925
+ upload,
1926
+ error: startError ?? tracked.error
1927
+ };
1928
+ }
1929
+ /** A fresh upload id: a capability, so it is random rather than derived. */
1930
+ function randomUploadId() {
1931
+ return crypto.randomUUID().replaceAll("-", "");
1932
+ }
1933
+ /**
1934
+ * Which input property this workflow says carries an upload id.
1935
+ *
1936
+ * `undefined` when the workflow declares none, which is not an error: the caller
1937
+ * may be using this hook against a workflow that takes no file at all, and the run
1938
+ * then starts with the input untouched.
1939
+ */
1940
+ async function uploadField(client, workflow) {
1941
+ return (await client.list()).find((one) => one.name === workflow)?.uploads?.[0];
1942
+ }
1943
+ /** The `File` at `field`, if that is what the form put there. */
1944
+ function fileAt(input, field) {
1945
+ if (!isRecord(input)) return void 0;
1946
+ return filesOf(input[field])[0];
1947
+ }
1948
+ //#endregion
1949
+ export { ApiUrlChip, AutoScroll, Button, ChatView, CheckboxField, Controls, DEFAULT_PROGRESS_POLL_MS, DEFAULT_WORKFLOW_POLL_MS, Field, FileField, Form, MAX_MISSING_READS, Markdown, MessageList, NumberField, SelectField, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, SubmitButton, TRANSCRIBING_PLACEHOLDER, TextAreaField, TextField, ThemeProvider, ToolCallRow, ToolConfigContext, UiUrlChip, UploadProgressBar, VOICE_CAPTURE_CONSTRAINTS, WorkflowFields, WorkflowProgress, buildAgentUrl, client, createSessionCore, createWorkflowApi, fetchClientConfig, isTerminal, loadClientConfig, page, useAgentState, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult, useUserTranscript, useWorkflowProgress, useWorkflowRun, useWorkflowRuns, useWorkflowStream, useWorkflowSubmit, useWorkflows };
@@ -33,7 +33,7 @@
33
33
  * followed from the same id either way.
34
34
  */
35
35
  import { type WorkflowSummary } from "@alexkroman1/aai";
36
- import type { UploadProgress } from "@alexkroman1/aai/workflow-api";
36
+ import type { UploadParallel, UploadProgress } from "@alexkroman1/aai/workflow-api";
37
37
  import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
38
38
  /** Options for {@link useWorkflows}. */
39
39
  export type UseWorkflowsOptions = {
@@ -138,6 +138,18 @@ export type UseWorkflowSubmitOptions = {
138
138
  wait?: number;
139
139
  /** How often the fallback poll re-reads a live run. */
140
140
  intervalMs?: number;
141
+ /**
142
+ * Send each chosen file as concurrent parts instead of in one request.
143
+ *
144
+ * `true` for the defaults, or `{ partBytes, concurrency }` to tune them. This is
145
+ * the wait a form with a recording in it actually spends: the run does not exist
146
+ * until its input is stored, so until the last byte lands there is no run to
147
+ * watch and nothing for `<WorkflowProgress>` to say. Splitting the file across
148
+ * connections is what makes that stretch shorter, and it degrades to the single
149
+ * request wherever it would not help — a small file, an older agent — so turning
150
+ * it on is safe for every form. See `UploadOptions.parallel`.
151
+ */
152
+ parallel?: UploadParallel;
141
153
  };
142
154
  /**
143
155
  * Start a workflow from a form, and follow the run it creates.
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Starting a run BEFORE its file has finished uploading.
3
+ *
4
+ * `useWorkflowSubmit` (`use-workflow-form.ts`) stores every file and then starts the
5
+ * run, because a `POST /workflows/uploads` cannot answer with an id until the last
6
+ * byte is in — the store writes an ordinary upload's record last, so "incomplete"
7
+ * and "no such upload" are deliberately the same answer. For a long recording that
8
+ * order is most of the wall clock.
9
+ *
10
+ * This hook inverts it, and it takes ONE extra idea to do so: the id is the
11
+ * CLIENT's. It mints one, starts the run on it, and `PUT`s the whole file in a
12
+ * single streaming request; the upload record exists from the first byte with
13
+ * `complete: false` and its `size` grows, so the run reads whatever has arrived.
14
+ *
15
+ * ```text
16
+ * start run ─┬──────────────────────────────────────────────► (still running)
17
+ * │
18
+ * PUT /uploads/<id> ═══════════════════════════════════► wake
19
+ * (one request; the run polls `size` and `complete`)
20
+ * ```
21
+ *
22
+ * ## What it does NOT do, which is the point
23
+ *
24
+ * There is no cutting, no part numbering, no terminator and no per-part request. An
25
+ * earlier version of this hook took a `cut` callback and uploaded N parts into a
26
+ * "group" that a separate call had to seal; it worked, and every piece of it was
27
+ * something the caller had to get right. The whole of that is replaced by the store
28
+ * publishing `size` as bytes land — which `readUpload` already clamped to — so the
29
+ * run does exactly what it does over a finished file and simply waits for windows to
30
+ * become present.
31
+ *
32
+ * It also means the FORMAT knowledge stays where it already was. Deciding where a
33
+ * recording may be divided is the run's business (`planSegments` in the
34
+ * transcription template), and nothing here needs to know it is audio at all.
35
+ *
36
+ * ## Three things it owns
37
+ *
38
+ * - **The id.** Minted here, put in the run input where the workflow's `uploads`
39
+ * list says, and never seen by the page. It is a capability — anyone holding it
40
+ * can read the bytes back — so it is a `crypto.randomUUID()` rather than anything
41
+ * derived from the file, and the store refuses a second `PUT` to it.
42
+ * - **The wake after the upload.** `POST /workflows/runs/:id/wake` ends a pending
43
+ * `sleep`, and a run waiting on an upload is asleep between polls — so without
44
+ * this it learns the file is complete up to a poll interval late, every time. On
45
+ * the transcription template's 5-second interval that is most of the tail it has
46
+ * left to pay. Best-effort, because a wake that finds nothing sleeping answers 0
47
+ * and a missed one costs latency rather than correctness.
48
+ * - **Reporting the bytes.** The same `UploadStatus` `useWorkflowSubmit` reports,
49
+ * so `<UploadProgressBar>` renders either without knowing which hook it came from.
50
+ *
51
+ * ## A failed upload CANCELS the run
52
+ *
53
+ * An upload that dies stays in the store, incomplete, and `complete` never becomes
54
+ * true — so a run left behind polls until its own abandonment bound and then fails,
55
+ * minutes after the page has already reported the error. Cancelling is the honest
56
+ * end to a submission that did not happen. The cost is that work already done is
57
+ * thrown away with the run; a caller who wants to resume instead drives
58
+ * `api.uploadStream` and `api.uploadInfo` directly.
59
+ */
60
+ import type { UploadParallel } from "@alexkroman1/aai/workflow-api";
61
+ import type { UploadStatus } from "./use-workflow-form.ts";
62
+ import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
63
+ /** Options for {@link useWorkflowStream}. */
64
+ export type UseWorkflowStreamOptions = {
65
+ /** The client to start runs with. Defaults to one for the page's own agent. */
66
+ api?: WorkflowApi;
67
+ /** Correlation key recorded with the run, for finding it again without the id. */
68
+ key?: string;
69
+ /** How often the fallback poll re-reads a live run. */
70
+ intervalMs?: number;
71
+ /**
72
+ * Send the file as concurrent parts instead of in one streaming request.
73
+ *
74
+ * It COMPOSES with what this hook is for rather than competing with it: the run
75
+ * still starts before the bytes, and the store still publishes how far the file
76
+ * is readable — that number is the CONTIGUOUS prefix, so a run reading ahead of
77
+ * the uplink sees the same growing file whether one connection or four are
78
+ * filling it. What changes is only how fast it grows.
79
+ *
80
+ * `true` for the defaults, or `{ partBytes, concurrency }` to tune them. See
81
+ * `UploadOptions.parallel`.
82
+ */
83
+ parallel?: UploadParallel;
84
+ };
85
+ /** What {@link useWorkflowStream} returns. */
86
+ export type WorkflowStreamSubmission<R = unknown> = {
87
+ /**
88
+ * Start a run and stream this input's file into it.
89
+ *
90
+ * Resolves when the upload finishes, NOT when the run does — the run's own
91
+ * progress arrives through `run`. It resolves rather than rejecting on a failed
92
+ * upload; the failure is reported through `error`, the way a form expects.
93
+ */
94
+ submit: (input: unknown) => Promise<void>;
95
+ /** Clear the run and any error, putting the form back to its initial state. */
96
+ reset: () => void;
97
+ /**
98
+ * The run, from the moment it EXISTS — which here is before its bytes are in.
99
+ *
100
+ * That is the whole difference from `useWorkflowSubmit`, and what lets a page
101
+ * render `<WorkflowProgress>` beside the upload bar rather than after it.
102
+ */
103
+ run: WorkflowRun<R> | undefined;
104
+ /** True from `submit()` until the run reaches a terminal status. */
105
+ pending: boolean;
106
+ /** How far the upload has got, while it is still going. */
107
+ upload: UploadStatus | undefined;
108
+ /** The submit's own failure (a rejected input, or an upload that would not store). */
109
+ error: string | undefined;
110
+ };
111
+ /**
112
+ * Start a workflow run and stream a file into it while it works.
113
+ *
114
+ * The workflow declares which input property carries the upload
115
+ * (`workflow({ uploads: ["recording"] })`) — the same declaration
116
+ * `useWorkflowSubmit` reads, because what the property carries is an upload id
117
+ * either way. What differs is only WHEN the id becomes valid.
118
+ *
119
+ * @typeParam R - The workflow's output type, which is what makes
120
+ * `run.status === "completed"` narrow to a typed `run.output`. Derive it with
121
+ * `WorkflowOutputOf<typeof myWorkflow>`.
122
+ *
123
+ * @example
124
+ * ```tsx no-check
125
+ * const { submit, run, upload, pending, error } = useWorkflowStream("transcribe");
126
+ *
127
+ * <Form onSubmit={(values) => submit(values)} error={error}>
128
+ * <WorkflowFields workflow="transcribe" />
129
+ * <UploadProgressBar upload={upload} />
130
+ * <SubmitButton pending={pending}>Transcribe</SubmitButton>
131
+ * </Form>
132
+ * ```
133
+ *
134
+ * @public
135
+ */
136
+ export declare function useWorkflowStream<R = unknown>(workflow: string, opts?: UseWorkflowStreamOptions): WorkflowStreamSubmission<R>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "6.3.1",
3
+ "version": "6.5.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -29,7 +29,7 @@
29
29
  "remark-gfm": "^4.0.1",
30
30
  "use-stick-to-bottom": "^1.1.6",
31
31
  "use-sync-external-store": "^1.6.0",
32
- "@alexkroman1/aai": "6.3.1"
32
+ "@alexkroman1/aai": "6.5.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^19.0.0",