@alexkroman1/aai-ui 5.13.2 → 6.1.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 +2 -1
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_sse.d.ts +56 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/audio.js +26 -26
- package/dist/{chat-view-DadZOvJO.js → chat-view-CK61bWWx.js} +4 -3
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/auto-scroll.d.ts +63 -0
- package/dist/components/chat-view.js +1 -1
- package/dist/components/controls.js +1 -1
- package/dist/components/form-types.d.ts +67 -0
- package/dist/components/form.d.ts +138 -0
- package/dist/components/message-list.js +1 -1
- package/dist/components/start-screen.js +1 -1
- package/dist/components/tool-call-block.js +1 -1
- package/dist/components/workflow-fields.d.ts +57 -0
- package/dist/components/workflow-progress.d.ts +55 -0
- package/dist/default-client/assets/audio-fO7SVU64.js +1 -0
- package/dist/default-client/assets/{capture-processor-CWRLCPS3.js → capture-processor-Dmc-KEpb.js} +4 -4
- package/dist/default-client/assets/client-audio-constants-Ck0IJO4c.js +1 -0
- package/dist/default-client/assets/index-4u908fff.js +293 -0
- package/dist/default-client/assets/index-CDugAuLK.css +2 -0
- package/dist/default-client/assets/{playback-processor-SKKE9qu2.js → playback-processor-DUsmALNH.js} +10 -3
- package/dist/default-client/index.html +3 -2
- package/dist/define-client.d.ts +40 -1
- package/dist/define-client.js +61 -19
- package/dist/hooks.d.ts +30 -0
- package/dist/hooks.js +4 -26
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1597 -7
- package/dist/{message-list-YdLocGoT.js → message-list-BwA3rdPi.js} +105 -27
- package/dist/page.d.ts +88 -0
- package/dist/{session-core-BA8H3qtF.js → session-core-ClKdVgRU.js} +245 -112
- package/dist/session-core-dial.d.ts +38 -0
- package/dist/session-core-handshake.d.ts +16 -1
- package/dist/session-core-messages.d.ts +2 -2
- package/dist/session-core-reconnect.d.ts +2 -7
- package/dist/session-core.js +1 -1
- package/dist/session-resume-store.d.ts +43 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/use-user-transcript.d.ts +70 -0
- package/dist/use-workflow-form.d.ts +136 -0
- package/dist/use-workflow-progress.d.ts +100 -0
- package/dist/use-workflow-run.d.ts +56 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/workflow-client.d.ts +97 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/worklets/playback-processor.d.ts +1 -1
- package/dist/worklets/playback-processor.js +8 -1
- package/package.json +9 -8
- package/dist/default-client/assets/audio-BqyrSHNq.js +0 -1
- package/dist/default-client/assets/index-Ctjrde3-.css +0 -2
- package/dist/default-client/assets/index-DfVI-qZ8.js +0 -293
- package/dist/{aai-logo-B8lDmsut.js → aai-logo-9xRBGVFl.js} +1 -1
- package/dist/{controls-DzQEKq9c.js → controls-Cy_YVfsa.js} +1 -1
- package/dist/{tool-call-block-CAscLGFy.js → tool-call-block-CrLN7xlI.js} +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A bounded read, re-armed from the SETTLED read — the loop both workflow
|
|
3
|
+
* watchers are built out of.
|
|
4
|
+
*
|
|
5
|
+
* `pollUntilTerminal` (`use-workflow-run.ts`) and `readProgressUntilComplete`
|
|
6
|
+
* (`use-workflow-progress.ts`) each open a request, decide from its answer
|
|
7
|
+
* whether to come back, and stop when told to. What they had in common was not
|
|
8
|
+
* the decision — one reads a snapshot, the other drains an SSE body — but the
|
|
9
|
+
* scaffold around it, and that scaffold is where the two rules live:
|
|
10
|
+
*
|
|
11
|
+
* - **Re-armed from the settled read, never on an interval.** A slow response
|
|
12
|
+
* would otherwise stack overlapping requests on an agent that is already
|
|
13
|
+
* struggling, and on the platform every one of them BROKERS.
|
|
14
|
+
* - **Cancellation is a signal, not a flag.** The teardown has to reach the
|
|
15
|
+
* in-flight request too — an abandoned progress read otherwise keeps pulling
|
|
16
|
+
* chunks out of a run for a page that has navigated away — so `step` is
|
|
17
|
+
* handed the signal rather than being trusted to check a boolean.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Call `step` until it reports it is finished, or until the returned stop
|
|
21
|
+
* function is called.
|
|
22
|
+
*
|
|
23
|
+
* `step` resolves `true` when there is nothing left to come back for. It is
|
|
24
|
+
* responsible for its own failures: a rejection would leave the loop stopped
|
|
25
|
+
* with nobody told, so each caller decides whether its failure is terminal or
|
|
26
|
+
* just another reason to try again.
|
|
27
|
+
*
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
export declare function repeatUntil(intervalMs: number, step: (signal: AbortSignal) => Promise<boolean>): () => void;
|
package/dist/_sse.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server-sent-event parser both workflow streams read through.
|
|
3
|
+
*
|
|
4
|
+
* Split out when the second stream arrived: `workflow-events.ts` watches a run's
|
|
5
|
+
* STATE and `use-workflow-progress.ts` reads what the run WROTE, and they parse
|
|
6
|
+
* the identical wire format. A second copy of a stream parser is the kind of
|
|
7
|
+
* duplication that goes wrong quietly — the two would drift on exactly the edges
|
|
8
|
+
* documented below, and the symptom is a page that silently stops updating.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
/** One parsed SSE frame. Comment frames (heartbeats) are skipped, not yielded. */
|
|
13
|
+
export type SseFrame = {
|
|
14
|
+
event: string;
|
|
15
|
+
data: unknown;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Parse an SSE byte stream into frames, with `eventsource-parser`.
|
|
19
|
+
*
|
|
20
|
+
* The parser is `aai-studio-client`'s already (`src/api-events.ts`), and it is
|
|
21
|
+
* catalogued — plus a transitive dependency of `@ai-sdk/provider-utils`, so it
|
|
22
|
+
* is in this package's tree either way. Adopting it retired a hand-rolled line
|
|
23
|
+
* splitter justified on the subset in use being "small and fixed" — true of our
|
|
24
|
+
* own server, and not of what sits between it and the page:
|
|
25
|
+
*
|
|
26
|
+
* - It split on `"\n\n"` only. The spec permits `\n`, `\r\n` and `\r`, and a
|
|
27
|
+
* CRLF stream is `\r\n\r\n` — no two adjacent `\n`, so **not one frame ever
|
|
28
|
+
* parsed** and `pump` fell through to `"fallback"` on the clean end. Silently
|
|
29
|
+
* dropping to the poll is the exact cost the run-watch stream exists to avoid,
|
|
30
|
+
* and an intermediary re-terminating lines is not our choice to make.
|
|
31
|
+
* - `line.startsWith("event: ")` required the space the spec makes optional.
|
|
32
|
+
* - It kept only the LAST `data:` line rather than joining a multi-line one.
|
|
33
|
+
*
|
|
34
|
+
* Those three are what `workflow-events.test.ts` pins, and they are the three
|
|
35
|
+
* that DISCRIMINATE — checked by running the specs against the old parser.
|
|
36
|
+
* Comment frames and a leading BOM were already fine and are not credited here:
|
|
37
|
+
* a heartbeat has no `event:` line, so the old parser dropped it anyway, and
|
|
38
|
+
* `TextDecoder` strips the BOM before either parser sees a byte.
|
|
39
|
+
*
|
|
40
|
+
* Three properties of the parser this leans on. `feed` invokes `onEvent`
|
|
41
|
+
* SYNCHRONOUSLY for every complete event in the chunk, so a batch is collected
|
|
42
|
+
* per read and yielded in arrival order — the generator shape, and therefore
|
|
43
|
+
* every caller, is unchanged. An event with no `data:` line at all is not
|
|
44
|
+
* dispatched (also per spec); every frame these routes emit carries one, since
|
|
45
|
+
* `workflow-api-events.ts` and `workflow-api-stream.ts` write `event:` and
|
|
46
|
+
* `data:` together. And a chunk ending in a lone `\r` holds that byte back,
|
|
47
|
+
* because it may yet turn out to be the first half of a `\r\n` — so a CR-ONLY
|
|
48
|
+
* stream chunked per frame dispatches one frame behind, and its last frame not
|
|
49
|
+
* at all (it would need `reset({ consume: true })`, which would also consume a
|
|
50
|
+
* genuinely truncated frame as if it were whole). Nothing emits CR-only endings,
|
|
51
|
+
* and the outcome if anything did is the safe one for both readers: a stream
|
|
52
|
+
* that ends with no final frame is read as a dropped connection, which the run
|
|
53
|
+
* watch answers by falling back to the poll and the progress reader by
|
|
54
|
+
* re-opening.
|
|
55
|
+
*/
|
|
56
|
+
export declare function sseFrames(body: ReadableStream<Uint8Array>, signal: AbortSignal): AsyncGenerator<SseFrame>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The client preamble every workflow hook needs, once.
|
|
3
|
+
*
|
|
4
|
+
* Five hooks (`useWorkflowRun`, `useWorkflowProgress`, `useWorkflowRuns`,
|
|
5
|
+
* `useWorkflows`, `useWorkflowSubmit`) opened with the same two refs and the
|
|
6
|
+
* same two paragraphs explaining them, and both halves are load-bearing rather
|
|
7
|
+
* than stylistic — which is exactly why they should not be re-derived per hook:
|
|
8
|
+
*
|
|
9
|
+
* - **The caller's client lives in a REF, never in an effect's dependency
|
|
10
|
+
* array.** The natural call site is
|
|
11
|
+
* `useWorkflowRun(id, { api: createWorkflowApi() })`, which passes a NEW
|
|
12
|
+
* object every render; as a dependency that tears the effect down and
|
|
13
|
+
* restarts it on each one, and because a restart clears state and re-renders,
|
|
14
|
+
* it schedules the next. The result is an unbounded request loop against the
|
|
15
|
+
* agent — on the platform, against the BROKER — with `error` wiped before
|
|
16
|
+
* anything can read it, presenting as "the page polls forever" rather than as
|
|
17
|
+
* a mistake at the call site.
|
|
18
|
+
* - **The no-client default is built lazily and ONCE.** As a render-time
|
|
19
|
+
* default (`api ?? createWorkflowApi()`) it is a fresh object per render,
|
|
20
|
+
* which is the same hazard one layer down; built inside an effect it is a
|
|
21
|
+
* fresh object per watch.
|
|
22
|
+
*
|
|
23
|
+
* The returned getter is stable for the life of the component and reads the ref
|
|
24
|
+
* on every call, so a caller that SWAPS clients mid-watch — a token arriving
|
|
25
|
+
* after login — is picked up by the next request without the watch restarting.
|
|
26
|
+
*/
|
|
27
|
+
import { type WorkflowApi } from "./workflow-client.ts";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve the client a workflow hook should use, now.
|
|
30
|
+
*
|
|
31
|
+
* @param api - The caller's client, or undefined for one aimed at the page's
|
|
32
|
+
* own agent.
|
|
33
|
+
* @returns A stable getter. Call it per request, never once per watch.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export declare function useWorkflowApiRef(api: WorkflowApi | undefined): () => WorkflowApi;
|
package/dist/audio.js
CHANGED
|
@@ -50,14 +50,14 @@ function createCaptureNode(ctx, onChunk, onSilent) {
|
|
|
50
50
|
node.port.postMessage({ event: "start" });
|
|
51
51
|
},
|
|
52
52
|
stop() {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
53
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
54
|
+
const cap = setTimeout(resolve, CAPTURE_STOP_ACK_TIMEOUT_MS);
|
|
55
|
+
onStopped = () => {
|
|
56
|
+
clearTimeout(cap);
|
|
57
|
+
resolve();
|
|
58
|
+
};
|
|
59
|
+
node.port.postMessage({ event: "stop" });
|
|
60
|
+
return promise;
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
}
|
|
@@ -185,24 +185,24 @@ async function createVoiceIO(opts) {
|
|
|
185
185
|
pendingStopTurn = null;
|
|
186
186
|
return Promise.resolve();
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
188
|
+
const { promise, resolve } = Promise.withResolvers();
|
|
189
|
+
onPlaybackStop?.();
|
|
190
|
+
const settle = () => {
|
|
191
|
+
clearInterval(poll);
|
|
192
|
+
clearTimeout(cap);
|
|
193
|
+
if (onPlaybackStop === settle) {
|
|
194
|
+
onPlaybackStop = null;
|
|
195
|
+
pendingStopTurn = null;
|
|
196
|
+
}
|
|
197
|
+
resolve();
|
|
198
|
+
};
|
|
199
|
+
const poll = setInterval(() => {
|
|
200
|
+
if (ctx.state !== "running") settle();
|
|
201
|
+
}, PLAYBACK_DONE_POLL_MS);
|
|
202
|
+
const cap = setTimeout(settle, PLAYBACK_DONE_MAX_WAIT_MS);
|
|
203
|
+
onPlaybackStop = settle;
|
|
204
|
+
pendingStopTurn = turn;
|
|
205
|
+
return promise;
|
|
206
206
|
},
|
|
207
207
|
flush() {
|
|
208
208
|
if (!playNode) return;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { t as MessageList } from "./message-list-BwA3rdPi.js";
|
|
1
2
|
import { useSessionSelector, useTheme } from "./context.js";
|
|
2
3
|
import { n as THINKING_COLOR, r as inkTint, t as ERROR_COLOR } from "./_colors-CcAi2FOU.js";
|
|
3
|
-
import { t as AaiLogo } from "./aai-logo-
|
|
4
|
+
import { t as AaiLogo } from "./aai-logo-9xRBGVFl.js";
|
|
4
5
|
import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
|
|
5
|
-
import { t as Controls } from "./controls-
|
|
6
|
-
import { t as MessageList } from "./message-list-YdLocGoT.js";
|
|
6
|
+
import { t as Controls } from "./controls-Cy_YVfsa.js";
|
|
7
7
|
import clsx from "clsx";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region components/console-shell.tsx
|
|
@@ -67,6 +67,7 @@ function ConsoleShell({ icon, title, state, pulsing, error, children, footer, cl
|
|
|
67
67
|
})]
|
|
68
68
|
}),
|
|
69
69
|
error && /* @__PURE__ */ jsx("div", {
|
|
70
|
+
role: "alert",
|
|
70
71
|
className: "px-3.5 py-2.5 rounded-aai border text-[13px] leading-[130%] shrink-0",
|
|
71
72
|
style: {
|
|
72
73
|
borderColor: "rgba(179,38,30,0.35)",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a `<form>`'s named controls into a plain object.
|
|
3
|
+
*
|
|
4
|
+
* Split from `form.tsx` because it is the half with no JSX in it and the half
|
|
5
|
+
* that carries the rules: what a control CONTRIBUTES is the contract — the
|
|
6
|
+
* object goes straight into a workflow's input, where a zod schema is waiting —
|
|
7
|
+
* and each branch below is a decision about that rather than about rendering.
|
|
8
|
+
*
|
|
9
|
+
* See `form.tsx`'s module doc for why the values come off the DOM at all.
|
|
10
|
+
*/
|
|
11
|
+
import type { FormValues } from "./form-types.ts";
|
|
12
|
+
/**
|
|
13
|
+
* Read one `<form>`'s named controls into a plain object.
|
|
14
|
+
*
|
|
15
|
+
* Exported for tests and for a caller doing its own submit handling.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectValues(form: HTMLFormElement): Promise<FormValues>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A scroll container that stays pinned to the bottom as its content grows,
|
|
4
|
+
* releases when the reader scrolls up, and re-engages once they return to the
|
|
5
|
+
* bottom.
|
|
6
|
+
*
|
|
7
|
+
* For clients that render their own chat chrome instead of using
|
|
8
|
+
* {@link MessageList} — a terminal, a dispatch board, a themed transcript.
|
|
9
|
+
* `MessageList` already behaves this way; this is the same mechanism with no
|
|
10
|
+
* opinion about what goes inside it.
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* The pattern this replaces is a `useEffect` that calls
|
|
14
|
+
* `ref.current?.scrollIntoView()` on every message change. That version has
|
|
15
|
+
* three faults, and they compound: it fights the reader, since scrolling up to
|
|
16
|
+
* re-read is undone by the next transcript delta; it misses growth that is not
|
|
17
|
+
* a new message, because a streamed reply, an expanding tool block or a
|
|
18
|
+
* markdown reflow changes height without changing the dependency array; and it
|
|
19
|
+
* needs a synthetic dependency (`messages.length + transcript.length`) to fire
|
|
20
|
+
* at all, which is where the dead `if (version < 0) return;` line comes from.
|
|
21
|
+
* A `ResizeObserver` on the content — what this uses — has none of those.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* import { AutoScroll, useSession } from "@alexkroman1/aai-ui";
|
|
26
|
+
*
|
|
27
|
+
* function Transcript() {
|
|
28
|
+
* const session = useSession();
|
|
29
|
+
* return (
|
|
30
|
+
* <AutoScroll className="flex-1 min-h-0" contentClassName="flex flex-col gap-2 p-4">
|
|
31
|
+
* {session.messages.map((m) => (
|
|
32
|
+
* <div key={m.id}>{m.content}</div>
|
|
33
|
+
* ))}
|
|
34
|
+
* </AutoScroll>
|
|
35
|
+
* );
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @param children - The scrollable content.
|
|
40
|
+
* @param className - Classes for the outer container. It must be given a
|
|
41
|
+
* bounded height (`flex-1 min-h-0`, `h-full`, a fixed height) — an unbounded
|
|
42
|
+
* one grows with its content and never scrolls, so nothing pins.
|
|
43
|
+
* @param contentClassName - Classes for the inner content element, where
|
|
44
|
+
* padding and the children's own layout belong.
|
|
45
|
+
* @param scrollClassName - Classes for the scrolling element itself. Defaults
|
|
46
|
+
* to hiding the scrollbar; pass `"overflow-y-auto"` to show a native one.
|
|
47
|
+
* @param style - Inline styles for the outer container.
|
|
48
|
+
* @param initial - Scroll behavior on mount. Defaults to `"instant"` (start at
|
|
49
|
+
* the latest content without animating a scroll the reader did not ask for).
|
|
50
|
+
* @param resize - Scroll behavior when pinned content grows. Defaults to
|
|
51
|
+
* `"smooth"`.
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export declare function AutoScroll({ children, className, contentClassName, scrollClassName, style, initial, resize, }: {
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
className?: string | undefined;
|
|
58
|
+
contentClassName?: string | undefined;
|
|
59
|
+
scrollClassName?: string | undefined;
|
|
60
|
+
style?: CSSProperties | undefined;
|
|
61
|
+
initial?: "instant" | "smooth" | undefined;
|
|
62
|
+
resize?: "instant" | "smooth" | undefined;
|
|
63
|
+
}): ReactNode;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a form's controls CONTRIBUTE — the three types both halves of the form
|
|
3
|
+
* code share.
|
|
4
|
+
*
|
|
5
|
+
* Their own module because `form.tsx` (the components) and `_form-values.ts`
|
|
6
|
+
* (the DOM read) both need them, and a type living in one of those would make
|
|
7
|
+
* the pair import each other.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* One submitted form, as a plain object keyed by field name.
|
|
11
|
+
*
|
|
12
|
+
* `unknown` values rather than `string`: see the module doc — a number field
|
|
13
|
+
* yields a number and a file field yields a {@link FileValue}.
|
|
14
|
+
*
|
|
15
|
+
* @public
|
|
16
|
+
*/
|
|
17
|
+
export type FormValues = Record<string, unknown>;
|
|
18
|
+
/**
|
|
19
|
+
* What a {@link FileField} contributes to {@link FormValues}.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export type FileValue = {
|
|
24
|
+
name: string;
|
|
25
|
+
/** Size in bytes. */
|
|
26
|
+
size: number;
|
|
27
|
+
/** MIME type the browser reported, or `""` when it could not tell. */
|
|
28
|
+
type: string;
|
|
29
|
+
/** Last modified, as epoch ms. */
|
|
30
|
+
lastModified: number;
|
|
31
|
+
/**
|
|
32
|
+
* The file's contents, present only when the field asked for them — see
|
|
33
|
+
* {@link FileField}'s `read` prop. A `data:` URL for `"dataUrl"`, decoded text
|
|
34
|
+
* for `"text"`.
|
|
35
|
+
*/
|
|
36
|
+
content?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* How much of a chosen file a {@link FileField} reads.
|
|
40
|
+
*
|
|
41
|
+
* `"upload"` is the odd one and the one a workflow input wants: the field
|
|
42
|
+
* contributes the `File` ITSELF rather than a description of it, and
|
|
43
|
+
* `useWorkflowSubmit` then stores it through `POST /workflows/uploads` and puts
|
|
44
|
+
* the id in the run input. Bytes cannot travel in a run input — see
|
|
45
|
+
* {@link FileField} — so this is how a form takes a file at all.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
*/
|
|
49
|
+
export type FileRead = "none" | "text" | "dataUrl" | "upload";
|
|
50
|
+
/**
|
|
51
|
+
* The props every field in `form.tsx` shares.
|
|
52
|
+
*
|
|
53
|
+
* Public because it is part of each field's own signature — a type reachable
|
|
54
|
+
* from a documented one has to be reachable from the entry point too, which the
|
|
55
|
+
* docs build enforces.
|
|
56
|
+
*
|
|
57
|
+
* @public
|
|
58
|
+
*/
|
|
59
|
+
export type FieldShell = {
|
|
60
|
+
/** Key this field contributes to {@link FormValues}. */
|
|
61
|
+
name: string;
|
|
62
|
+
/** Visible label. Omitted leaves the control unlabelled — pass `aria-label` instead. */
|
|
63
|
+
label?: string | undefined;
|
|
64
|
+
/** One line of guidance under the control. */
|
|
65
|
+
hint?: string | undefined;
|
|
66
|
+
className?: string | undefined;
|
|
67
|
+
};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { FormHTMLAttributes, InputHTMLAttributes, ReactNode, SelectHTMLAttributes, TextareaHTMLAttributes } from "react";
|
|
2
|
+
import { type ButtonSize } from "./button.tsx";
|
|
3
|
+
import type { FieldShell, FileRead, FormValues } from "./form-types.ts";
|
|
4
|
+
export type { FieldShell, FileRead, FileValue, FormValues } from "./form-types.ts";
|
|
5
|
+
/** Props of {@link Form}. */
|
|
6
|
+
export type FormProps = {
|
|
7
|
+
/**
|
|
8
|
+
* Called with the collected values. May be async — the form stays disabled
|
|
9
|
+
* for the duration, so a double-click cannot submit twice.
|
|
10
|
+
*/
|
|
11
|
+
onSubmit: (values: FormValues) => void | Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* A failure to show above the fields. The caller owns it, because the
|
|
14
|
+
* interesting failures are the server's (`useWorkflowSubmit`'s `error`) and
|
|
15
|
+
* this component never sees them.
|
|
16
|
+
*/
|
|
17
|
+
error?: string | undefined;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
className?: string | undefined;
|
|
20
|
+
} & Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit" | "className">;
|
|
21
|
+
/**
|
|
22
|
+
* A form that hands its values to `onSubmit` as one object.
|
|
23
|
+
*
|
|
24
|
+
* Native validation still applies — a `required` field blocks the submit and the
|
|
25
|
+
* browser says so, which is better than anything this could render.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* import { Form, SubmitButton, TextField } from "@alexkroman1/aai-ui";
|
|
30
|
+
*
|
|
31
|
+
* function NameForm() {
|
|
32
|
+
* return (
|
|
33
|
+
* <Form onSubmit={(values) => console.log(values.topic)}>
|
|
34
|
+
* <TextField name="topic" label="Topic" required />
|
|
35
|
+
* <SubmitButton>Start</SubmitButton>
|
|
36
|
+
* </Form>
|
|
37
|
+
* );
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
export declare function Form({ onSubmit, error, children, className, ...rest }: FormProps): import("react").JSX.Element;
|
|
44
|
+
/**
|
|
45
|
+
* Label + control + hint, in the layout every field here uses.
|
|
46
|
+
*
|
|
47
|
+
* Exported so a caller's own control gets the same shell rather than an
|
|
48
|
+
* approximation of it.
|
|
49
|
+
*
|
|
50
|
+
* @public
|
|
51
|
+
*/
|
|
52
|
+
export declare function Field({ label, hint, htmlFor, className, children, }: {
|
|
53
|
+
label?: string | undefined;
|
|
54
|
+
hint?: string | undefined;
|
|
55
|
+
/** Id of the control this labels. */
|
|
56
|
+
htmlFor?: string | undefined;
|
|
57
|
+
className?: string | undefined;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
}): import("react").JSX.Element;
|
|
60
|
+
/**
|
|
61
|
+
* A single-line text input.
|
|
62
|
+
*
|
|
63
|
+
* @public
|
|
64
|
+
*/
|
|
65
|
+
export declare function TextField({ name, label, hint, className, ...rest }: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className">): import("react").JSX.Element;
|
|
66
|
+
/**
|
|
67
|
+
* A number input. Contributes a NUMBER to {@link FormValues}, or nothing when
|
|
68
|
+
* left empty.
|
|
69
|
+
*
|
|
70
|
+
* @public
|
|
71
|
+
*/
|
|
72
|
+
export declare function NumberField({ name, label, hint, className, ...rest }: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
|
|
73
|
+
/**
|
|
74
|
+
* A multi-line text input.
|
|
75
|
+
*
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export declare function TextAreaField({ name, label, hint, className, rows, ...rest }: FieldShell & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, "name" | "className">): import("react").JSX.Element;
|
|
79
|
+
/**
|
|
80
|
+
* A dropdown. Pass `options`, or `children` for full control over the
|
|
81
|
+
* `<option>` elements.
|
|
82
|
+
*
|
|
83
|
+
* @public
|
|
84
|
+
*/
|
|
85
|
+
export declare function SelectField({ name, label, hint, className, options, children, ...rest }: FieldShell & {
|
|
86
|
+
options?: readonly (string | {
|
|
87
|
+
value: string;
|
|
88
|
+
label: string;
|
|
89
|
+
})[];
|
|
90
|
+
} & Omit<SelectHTMLAttributes<HTMLSelectElement>, "name" | "className">): import("react").JSX.Element;
|
|
91
|
+
/**
|
|
92
|
+
* A checkbox. Contributes a BOOLEAN to {@link FormValues}.
|
|
93
|
+
*
|
|
94
|
+
* @public
|
|
95
|
+
*/
|
|
96
|
+
export declare function CheckboxField({ name, label, hint, className, ...rest }: FieldShell & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
|
|
97
|
+
/**
|
|
98
|
+
* A file picker. Contributes a {@link FileValue} (or an array, with `multiple`)
|
|
99
|
+
* to {@link FormValues} — or nothing when no file was chosen.
|
|
100
|
+
*
|
|
101
|
+
* **`upload` is what a workflow input wants.** A run's input is serialized into
|
|
102
|
+
* the run record and replayed from it on every resume, so a file's BYTES cannot
|
|
103
|
+
* travel in it. With `upload` the field contributes the `File` itself,
|
|
104
|
+
* `useWorkflowSubmit` stores it through `POST /workflows/uploads` before
|
|
105
|
+
* starting the run, and the input carries the upload id — which a step reads
|
|
106
|
+
* windows of with `readUpload`. Declaring the property in the workflow's
|
|
107
|
+
* `uploads` list makes `<WorkflowFields>` render exactly this, so a declared
|
|
108
|
+
* form needs no file markup at all.
|
|
109
|
+
*
|
|
110
|
+
* **Without it the field describes the file and does not read it.** `read`
|
|
111
|
+
* exists for the cases where the bytes really are small and really are the
|
|
112
|
+
* input — a CSV of ids, a config — and the size is the author's to check.
|
|
113
|
+
*
|
|
114
|
+
* @public
|
|
115
|
+
*/
|
|
116
|
+
export declare function FileField({ name, label, hint, className, read, upload, ...rest }: FieldShell & {
|
|
117
|
+
read?: FileRead;
|
|
118
|
+
/** Shorthand for `read="upload"` — see above. */
|
|
119
|
+
upload?: boolean;
|
|
120
|
+
} & Omit<InputHTMLAttributes<HTMLInputElement>, "name" | "className" | "type">): import("react").JSX.Element;
|
|
121
|
+
/**
|
|
122
|
+
* The form's submit button, disabled and relabelled while a submit is in
|
|
123
|
+
* flight.
|
|
124
|
+
*
|
|
125
|
+
* @public
|
|
126
|
+
*/
|
|
127
|
+
export declare function SubmitButton({ children, pending, pendingLabel, size, className, }: {
|
|
128
|
+
children?: ReactNode;
|
|
129
|
+
/**
|
|
130
|
+
* Whether the WORK this form started is still going. Separate from the
|
|
131
|
+
* submit itself, which {@link Form} disables on its own: a workflow run
|
|
132
|
+
* outlives its `POST`, and the button should stay busy until the run is done.
|
|
133
|
+
*/
|
|
134
|
+
pending?: boolean;
|
|
135
|
+
pendingLabel?: string;
|
|
136
|
+
size?: ButtonSize | undefined;
|
|
137
|
+
className?: string | undefined;
|
|
138
|
+
}): import("react").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useSessionCore, useSessionSelector, useTheme } from "../context.js";
|
|
2
2
|
import { r as inkTint } from "../_colors-CcAi2FOU.js";
|
|
3
3
|
import { Button } from "./button.js";
|
|
4
|
-
import { t as AaiLogo } from "../aai-logo-
|
|
4
|
+
import { t as AaiLogo } from "../aai-logo-9xRBGVFl.js";
|
|
5
5
|
import { t as Eyebrow } from "../eyebrow-C6ZFuiz6.js";
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
/**
|
|
3
|
+
* A form built from a workflow's declared input schema.
|
|
4
|
+
*
|
|
5
|
+
* `GET workflows` reports each workflow's `inputSchema` as JSON Schema — the
|
|
6
|
+
* zod schema an author wrote in `agent.ts`, converted at listing time precisely
|
|
7
|
+
* so a browser can read it. This is what reads it: one `<WorkflowFields>` and a
|
|
8
|
+
* workflow's form matches its schema by construction, so adding a field to the
|
|
9
|
+
* schema adds it to the page and nothing can drift.
|
|
10
|
+
*
|
|
11
|
+
* ## It covers SCALARS, and says so rather than guessing
|
|
12
|
+
*
|
|
13
|
+
* A string, number, integer, boolean or enum has one obvious control each. A
|
|
14
|
+
* nested object or an array does not — every choice (a JSON textarea, a repeater,
|
|
15
|
+
* a comma-separated string) is a guess about what the author meant, and a guess
|
|
16
|
+
* that produces a value the schema then rejects is worse than no field at all.
|
|
17
|
+
* So those are SKIPPED, and the fields for them are written by hand: every field
|
|
18
|
+
* in this package is a plain named control, so a hand-written one composes with
|
|
19
|
+
* a generated one inside the same {@link Form}.
|
|
20
|
+
*/
|
|
21
|
+
import type { WorkflowSummary } from "@alexkroman1/aai";
|
|
22
|
+
/**
|
|
23
|
+
* Render one field per scalar property of a workflow's input schema.
|
|
24
|
+
*
|
|
25
|
+
* Pass the workflow's NAME and the schema is fetched here; pass a
|
|
26
|
+
* {@link WorkflowSummary} you already hold and nothing is fetched. The name form
|
|
27
|
+
* is the one a page usually wants — it is the same string the submit hook takes,
|
|
28
|
+
* and the alternative is three lines (`useWorkflows()`, a `.find()` by name, and
|
|
29
|
+
* folding that lookup's error into the form's) whose only product is this
|
|
30
|
+
* component's argument.
|
|
31
|
+
*
|
|
32
|
+
* Renders nothing when the workflow declared no schema — a workflow with no
|
|
33
|
+
* declared input takes anything, and a form for "anything" is not a form — and
|
|
34
|
+
* nothing while a named lookup is still in flight, so the hand-written fields
|
|
35
|
+
* beside it are not reordered when the schema lands.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* import { Form, SubmitButton, WorkflowFields, useWorkflowSubmit }
|
|
40
|
+
* from "@alexkroman1/aai-ui";
|
|
41
|
+
*
|
|
42
|
+
* function StartRun() {
|
|
43
|
+
* const { submit, pending, error } = useWorkflowSubmit("transcribe");
|
|
44
|
+
* return (
|
|
45
|
+
* <Form onSubmit={(values) => submit(values)} error={error}>
|
|
46
|
+
* <WorkflowFields workflow="transcribe" />
|
|
47
|
+
* <SubmitButton pending={pending}>Transcribe</SubmitButton>
|
|
48
|
+
* </Form>
|
|
49
|
+
* );
|
|
50
|
+
* }
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
export declare function WorkflowFields({ workflow }: {
|
|
56
|
+
workflow?: WorkflowSummary | string | undefined;
|
|
57
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { WorkflowApi } from "../workflow-client.ts";
|
|
3
|
+
/**
|
|
4
|
+
* What a run has said so far, rendered.
|
|
5
|
+
*
|
|
6
|
+
* The complement of a status line, and the reason both exist: a run is
|
|
7
|
+
* `running` for its whole life, so a one-round job and a ten-round one look
|
|
8
|
+
* identical while they happen. These lines come from the run itself (`report()`
|
|
9
|
+
* in a `"use step"` body), which is the only channel a workflow has before it
|
|
10
|
+
* produces an output.
|
|
11
|
+
*
|
|
12
|
+
* Three rules are baked in, and they are why this is a component rather than
|
|
13
|
+
* three lines each page writes for itself — the two templates that had written
|
|
14
|
+
* it had written all three, comments included:
|
|
15
|
+
*
|
|
16
|
+
* - **It renders nothing until there is something to render.** `supported` is
|
|
17
|
+
* what keeps this from being an empty box forever on an agent deployed before
|
|
18
|
+
* progress streams existed: "wrote nothing yet" and "serves no stream" are
|
|
19
|
+
* indistinguishable from the chunk list alone.
|
|
20
|
+
* - **The lines are TEXT, not elements.** They are append-only and two rounds
|
|
21
|
+
* legitimately produce identical text, so there is no stable per-line key to
|
|
22
|
+
* give React. Joining sidesteps the question instead of suppressing the lint
|
|
23
|
+
* rule that asks it.
|
|
24
|
+
* - **They REPLAY.** Chunks are retained with the run, so a reload mid-run —
|
|
25
|
+
* or opening a finished run tomorrow — shows how it got there rather than an
|
|
26
|
+
* empty box. That is `useWorkflowProgress`'s doing; this is what makes it
|
|
27
|
+
* visible.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* import { WorkflowProgress } from "@alexkroman1/aai-ui";
|
|
32
|
+
*
|
|
33
|
+
* function RunPanel({ runId }: { runId: string }) {
|
|
34
|
+
* return <WorkflowProgress runId={runId} />;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @param runId - The run to read. `undefined` renders nothing, so a page may
|
|
39
|
+
* pass its state straight through before a run exists.
|
|
40
|
+
* @param api - The workflow API client, when the page holds its own. Defaults
|
|
41
|
+
* to the one `page()` installs.
|
|
42
|
+
* @param className - Replaces the default classes rather than extending them,
|
|
43
|
+
* so a custom chrome is not fighting a default it did not ask for.
|
|
44
|
+
* @param placeholder - Rendered instead of nothing while the run has said
|
|
45
|
+
* nothing yet — for a page that would otherwise reflow when the first line
|
|
46
|
+
* lands.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export declare function WorkflowProgress({ runId, api, className, placeholder, }: {
|
|
51
|
+
runId?: string | undefined;
|
|
52
|
+
api?: WorkflowApi | undefined;
|
|
53
|
+
className?: string | undefined;
|
|
54
|
+
placeholder?: ReactNode | undefined;
|
|
55
|
+
}): ReactNode;
|