@alexkroman1/aai-ui 0.12.2 → 0.13.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 +257 -0
- package/dist/_colors-CpZO-88A.js +78 -0
- package/dist/_module-url-C13kAJ87.js +15 -0
- package/dist/_page-shell.d.ts +11 -0
- package/dist/_recover-run.d.ts +79 -0
- package/dist/_repeat-until.d.ts +30 -0
- package/dist/_run-controls.d.ts +33 -0
- package/dist/_submission-state.d.ts +92 -0
- package/dist/_tap-to-talk-state.d.ts +88 -0
- package/dist/_utils-Mi-SCFA1.js +47 -0
- package/dist/_utils.d.ts +26 -0
- package/dist/_web-storage.d.ts +43 -0
- package/dist/_workflow-api-ref.d.ts +37 -0
- package/dist/_workflow-files.d.ts +34 -0
- package/dist/_workflow-test-defs.d.ts +38 -0
- package/dist/aai-logo-D8pMVn9_.js +85 -0
- package/dist/agent-state-labels.d.ts +65 -0
- package/dist/agent-state.d.ts +108 -0
- package/dist/audio/capture.d.ts +44 -0
- package/dist/audio/index.d.ts +18 -0
- package/dist/audio/index.js +431 -0
- package/dist/audio/preconnect.d.ts +87 -0
- package/dist/audio/voice-io.d.ts +112 -0
- package/dist/build-default-client.d.ts +1 -0
- package/dist/chat-view-BCbI7gD4.js +396 -0
- package/dist/client-config-C7fQrioM.js +139 -0
- package/dist/client-config.d.ts +119 -0
- package/dist/client-dir.d.ts +44 -0
- package/dist/client-dir.js +58 -0
- package/dist/client-identity.d.ts +131 -0
- package/dist/client-tool.d.ts +51 -0
- package/dist/components/_colors.d.ts +73 -0
- package/dist/components/_form-readiness.d.ts +56 -0
- package/dist/components/_form-values.d.ts +19 -0
- package/dist/components/aai-logo.d.ts +12 -0
- package/dist/components/audio-result.d.ts +94 -0
- package/dist/components/auto-scroll.d.ts +74 -0
- package/dist/components/bullet-list.d.ts +74 -0
- package/dist/components/button.d.ts +58 -0
- package/dist/components/button.js +85 -0
- package/dist/components/chat-view.d.ts +35 -0
- package/dist/components/chat-view.js +3 -0
- package/dist/components/console-shell.d.ts +85 -0
- package/dist/components/controls.d.ts +33 -0
- package/dist/components/controls.js +49 -0
- package/dist/components/conversation-view.d.ts +130 -0
- package/dist/components/eyebrow.d.ts +12 -0
- package/dist/components/facts.d.ts +81 -0
- package/dist/components/form-fields.d.ts +142 -0
- package/dist/components/form-types.d.ts +73 -0
- package/dist/components/form.d.ts +96 -0
- package/dist/components/markdown.d.ts +57 -0
- package/dist/components/message-list.d.ts +48 -0
- package/dist/components/message-list.js +3 -0
- package/dist/components/session-controls.d.ts +125 -0
- package/dist/components/session-error-banner.d.ts +69 -0
- package/dist/components/session-state-dot.d.ts +111 -0
- package/dist/components/sidebar-layout.d.ts +42 -0
- package/dist/components/sidebar-layout.js +56 -0
- package/dist/components/start-screen.d.ts +37 -0
- package/dist/components/start-screen.js +71 -0
- package/dist/components/tool-call-block.d.ts +28 -0
- package/dist/components/tool-call-block.js +3 -0
- package/dist/components/tool-call-row.d.ts +75 -0
- package/dist/components/tool-config-context.d.ts +22 -0
- package/dist/components/upload-progress.d.ts +76 -0
- package/dist/components/url-chips.d.ts +29 -0
- package/dist/components/workflow-fields.d.ts +46 -0
- package/dist/components/workflow-pending-note.d.ts +81 -0
- package/dist/components/workflow-progress.d.ts +81 -0
- package/dist/components/workflow-run-error.d.ts +48 -0
- package/dist/components/workflow-run-panel.d.ts +79 -0
- package/dist/context.d.ts +288 -0
- package/dist/context.js +382 -0
- package/dist/conversation-log.d.ts +88 -0
- package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
- package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
- package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
- package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
- package/dist/default-client/assets/index-BQURVARl.js +126 -0
- package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
- package/dist/default-client/favicon.ico +0 -0
- package/dist/default-client/index.html +15 -0
- package/dist/default-client.d.ts +23 -0
- package/dist/define-client.d.ts +162 -26
- package/dist/define-client.js +201 -55
- package/dist/eyebrow-UfmSz9yy.js +27 -0
- package/dist/hooks-DN4gHYsj.js +182 -0
- package/dist/hooks.d.ts +172 -0
- package/dist/hooks.js +3 -0
- package/dist/hooks.test-d.d.ts +14 -0
- package/dist/inbox-protocol.d.ts +128 -0
- package/dist/inbox.d.ts +132 -0
- package/dist/index.d.ts +129 -32
- package/dist/index.js +5503 -18
- package/dist/internal.d.ts +43 -0
- package/dist/internal.js +8 -0
- package/dist/linked-client.d.ts +70 -0
- package/dist/message-list-DHZcLhRA.js +682 -0
- package/dist/notice-player.d.ts +70 -0
- package/dist/page.d.ts +114 -0
- package/dist/phone.d.ts +50 -0
- package/dist/route-fetch.d.ts +79 -0
- package/dist/session/audio-effects.d.ts +31 -0
- package/dist/session/audio-setup.d.ts +57 -0
- package/dist/session/audio-state.d.ts +157 -0
- package/dist/session/browser-session.d.ts +30 -0
- package/dist/session/close.d.ts +26 -0
- package/dist/session/dial.d.ts +65 -0
- package/dist/session/handshake.d.ts +60 -0
- package/dist/session/index.d.ts +17 -0
- package/dist/session/index.js +2 -0
- package/dist/session/messages.d.ts +63 -0
- package/dist/session/mic.d.ts +64 -0
- package/dist/session/preconnect.d.ts +48 -0
- package/dist/session/reconnect.d.ts +25 -0
- package/dist/session/resume-store.d.ts +57 -0
- package/dist/session/state.d.ts +146 -0
- package/dist/session/ticket.d.ts +44 -0
- package/dist/session/types.d.ts +476 -0
- package/dist/session/url.d.ts +17 -0
- package/dist/session/user-turn.d.ts +55 -0
- package/dist/session-CE0Ipsb3.js +2129 -0
- package/dist/stored-value.d.ts +103 -0
- package/dist/tool-call-block-uXtMOUWN.js +204 -0
- package/dist/tool-config-context-Es4YUzV2.js +19 -0
- package/dist/types.d.ts +272 -34
- package/dist/types.js +35 -4
- package/dist/upload/files.d.ts +83 -0
- package/dist/upload/index.d.ts +15 -0
- package/dist/upload/pause.d.ts +31 -0
- package/dist/upload/recall.d.ts +67 -0
- package/dist/upload/report.d.ts +26 -0
- package/dist/upload/session.d.ts +99 -0
- package/dist/url-chips-oyy_Eiqh.js +239 -0
- package/dist/use-client-runs.d.ts +87 -0
- package/dist/use-conversation-log.d.ts +86 -0
- package/dist/use-conversation.d.ts +131 -0
- package/dist/use-copy.d.ts +74 -0
- package/dist/use-download-url.d.ts +84 -0
- package/dist/use-flash.d.ts +61 -0
- package/dist/use-inbox.d.ts +101 -0
- package/dist/use-push-to-talk.d.ts +105 -0
- package/dist/use-route-mutation.d.ts +113 -0
- package/dist/use-route.d.ts +82 -0
- package/dist/use-run-key.d.ts +133 -0
- package/dist/use-session-controls.d.ts +69 -0
- package/dist/use-session-id.d.ts +57 -0
- package/dist/use-tap-to-talk.d.ts +138 -0
- package/dist/use-user-transcript-uyHhzy4d.js +73 -0
- package/dist/use-user-transcript.d.ts +75 -0
- package/dist/use-workflow-form.d.ts +285 -0
- package/dist/use-workflow-progress.d.ts +137 -0
- package/dist/use-workflow-run-DdhWmbx-.js +697 -0
- package/dist/use-workflow-run.d.ts +100 -0
- package/dist/use-workflow-runs.d.ts +71 -0
- package/dist/use-workflow-stream.d.ts +154 -0
- package/dist/use-workflows.d.ts +77 -0
- package/dist/workflow-client.d.ts +136 -0
- package/dist/workflow-def-types.d.ts +31 -0
- package/dist/workflow-events.d.ts +39 -0
- package/dist/workflow-field-kind.d.ts +95 -0
- package/dist/workflow-status-labels.d.ts +35 -0
- package/dist/worklets/_module-url.d.ts +10 -0
- package/dist/worklets/_playback-bench-harness.d.ts +181 -0
- package/dist/worklets/_playback-bench-host.d.ts +63 -0
- package/dist/worklets/_playback-bench-page.d.ts +65 -0
- package/dist/worklets/_tts-trace-harness.d.ts +142 -0
- package/dist/worklets/_worklet-test-utils.d.ts +27 -0
- package/dist/worklets/capture-processor.d.ts +4 -2
- package/dist/worklets/capture-processor.js +73 -33
- package/dist/worklets/playback-processor.d.ts +4 -2
- package/dist/worklets/playback-processor.js +258 -42
- package/package.json +58 -31
- package/styles.css +152 -30
- package/dist/_components/app.d.ts +0 -23
- package/dist/_components/app.js +0 -41
- package/dist/_components/button.d.ts +0 -50
- package/dist/_components/button.js +0 -46
- package/dist/_components/chat-view.d.ts +0 -29
- package/dist/_components/chat-view.js +0 -61
- package/dist/_components/controls.d.ts +0 -18
- package/dist/_components/controls.js +0 -37
- package/dist/_components/error-banner.d.ts +0 -21
- package/dist/_components/error-banner.js +0 -27
- package/dist/_components/message-bubble.d.ts +0 -23
- package/dist/_components/message-bubble.js +0 -35
- package/dist/_components/message-list.d.ts +0 -21
- package/dist/_components/message-list.js +0 -76
- package/dist/_components/sidebar-layout.d.ts +0 -21
- package/dist/_components/sidebar-layout.js +0 -36
- package/dist/_components/start-screen.d.ts +0 -26
- package/dist/_components/start-screen.js +0 -51
- package/dist/_components/state-indicator.d.ts +0 -29
- package/dist/_components/state-indicator.js +0 -37
- package/dist/_components/thinking-indicator.d.ts +0 -16
- package/dist/_components/thinking-indicator.js +0 -34
- package/dist/_components/tool-call-block.d.ts +0 -27
- package/dist/_components/tool-call-block.js +0 -118
- package/dist/_components/tool-icons.d.ts +0 -17
- package/dist/_components/tool-icons.js +0 -128
- package/dist/_components/transcript.d.ts +0 -25
- package/dist/_components/transcript.js +0 -35
- package/dist/audio.d.ts +0 -45
- package/dist/audio.js +0 -121
- package/dist/client-context.d.ts +0 -33
- package/dist/client-context.js +0 -15
- package/dist/client-handler.d.ts +0 -43
- package/dist/session-CWB7vmqz.js +0 -429
- package/dist/session.d.ts +0 -69
- package/dist/session.js +0 -2
- package/dist/signals.d.ts +0 -120
- package/dist/signals.js +0 -161
- package/dist/tsdown.config.d.ts +0 -2
- package/dist/types.test-d.d.ts +0 -7
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `usePushToTalk` — a hold-to-speak button, for an agent that declares
|
|
3
|
+
* `turnTaking: { detection: "manual" }`.
|
|
4
|
+
*
|
|
5
|
+
* The three session edges underneath (`session.userTurn`'s `start`, `commit`
|
|
6
|
+
* and `clear`) are one line each; what a button needs on top of them is
|
|
7
|
+
* where hand-written versions go wrong, and every one of the four is a turn
|
|
8
|
+
* that is left OPEN — the microphone live and nothing ever answered:
|
|
9
|
+
*
|
|
10
|
+
* - **The pointer leaves.** A `mouseup` outside the button never reaches it.
|
|
11
|
+
* The handlers here capture the pointer on the way down, so the release
|
|
12
|
+
* arrives wherever it happens; an `onPointerCancel` (a scroll gesture, a
|
|
13
|
+
* system dialog) DISCARDS the turn rather than answering half a sentence.
|
|
14
|
+
* - **The key repeats.** Holding a key fires `keydown` over and over, and each
|
|
15
|
+
* one would re-open the turn. Only the first counts.
|
|
16
|
+
* - **The window loses focus mid-hold.** The `keyup` goes to another window
|
|
17
|
+
* and is never delivered, so the turn is committed on `blur` — what the
|
|
18
|
+
* caller would have done had the release been seen.
|
|
19
|
+
* - **The component unmounts mid-hold.** The turn is discarded on the way out.
|
|
20
|
+
*
|
|
21
|
+
* And one that is a nuisance rather than a stuck turn: the hold key is ignored
|
|
22
|
+
* while focus is in a text field, where Space is a character.
|
|
23
|
+
*/
|
|
24
|
+
/** Options for {@link usePushToTalk}. */
|
|
25
|
+
export type UsePushToTalkOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* The keyboard key that holds the turn open, as a `KeyboardEvent.code` —
|
|
28
|
+
* `"Space"` by default, so the whole page is a walkie-talkie. `false` turns
|
|
29
|
+
* the global key off; the button itself still answers Space and Enter while
|
|
30
|
+
* it has focus.
|
|
31
|
+
*/
|
|
32
|
+
holdKey?: string | false;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* What {@link usePushToTalk} returns.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export type UsePushToTalkResult = {
|
|
40
|
+
/** Whether a turn is being held open right now — the button is DOWN. */
|
|
41
|
+
talking: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Whether pressing would do anything: the call is live. False before Start
|
|
44
|
+
* and while paused, which is when a button should render disabled.
|
|
45
|
+
*/
|
|
46
|
+
ready: boolean;
|
|
47
|
+
/** Open a turn. Interrupts the agent if it is speaking. Ignored while held. */
|
|
48
|
+
press: () => void;
|
|
49
|
+
/** Close the turn and have the agent answer it. Ignored unless held. */
|
|
50
|
+
release: () => void;
|
|
51
|
+
/** Close the turn and discard it — nothing is answered. Ignored unless held. */
|
|
52
|
+
cancel: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Spread onto a `<button>`: pointer capture, the keyboard pair, and
|
|
55
|
+
* `aria-pressed`. The handlers are the whole contract — style it however you
|
|
56
|
+
* like.
|
|
57
|
+
*/
|
|
58
|
+
buttonProps: {
|
|
59
|
+
onPointerDown: (event: {
|
|
60
|
+
currentTarget: Element;
|
|
61
|
+
pointerId: number;
|
|
62
|
+
}) => void;
|
|
63
|
+
onPointerUp: () => void;
|
|
64
|
+
onPointerCancel: () => void;
|
|
65
|
+
onKeyDown: (event: {
|
|
66
|
+
key: string;
|
|
67
|
+
repeat: boolean;
|
|
68
|
+
preventDefault(): void;
|
|
69
|
+
}) => void;
|
|
70
|
+
onKeyUp: (event: {
|
|
71
|
+
key: string;
|
|
72
|
+
preventDefault(): void;
|
|
73
|
+
}) => void;
|
|
74
|
+
onContextMenu: (event: {
|
|
75
|
+
preventDefault(): void;
|
|
76
|
+
}) => void;
|
|
77
|
+
disabled: boolean;
|
|
78
|
+
"aria-pressed": boolean;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Hold-to-speak over the session's push-to-talk methods, with the four ways a
|
|
83
|
+
* turn gets stuck open handled — see this module's doc.
|
|
84
|
+
*
|
|
85
|
+
* Must be used inside the provider `mountClient()` installs, against an agent
|
|
86
|
+
* declaring `turnTaking: { detection: "manual" }`; any other agent ignores the commands and
|
|
87
|
+
* its server says so once.
|
|
88
|
+
*
|
|
89
|
+
* @example A hold-to-talk button
|
|
90
|
+
* ```tsx
|
|
91
|
+
* import { usePushToTalk } from "@alexkroman1/aai-ui";
|
|
92
|
+
*
|
|
93
|
+
* function TalkButton() {
|
|
94
|
+
* const { talking, buttonProps } = usePushToTalk();
|
|
95
|
+
* return (
|
|
96
|
+
* <button type="button" {...buttonProps}>
|
|
97
|
+
* {talking ? "Listening… release to send" : "Hold to talk (or hold Space)"}
|
|
98
|
+
* </button>
|
|
99
|
+
* );
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @public
|
|
104
|
+
*/
|
|
105
|
+
export declare function usePushToTalk(options?: UsePushToTalkOptions): UsePushToTalkResult;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useRouteMutation` — WRITE to one of the agent's own `/api` routes from a
|
|
3
|
+
* component: the writing half of `useRoute`, over `routeFetch`.
|
|
4
|
+
*
|
|
5
|
+
* Every save, delete and connect button on a device-twin page wrote the same
|
|
6
|
+
* nine lines: set a busy flag, call the route, clear the error on success or
|
|
7
|
+
* set it from the thrown message on failure, clear the flag, and re-read the
|
|
8
|
+
* list the write changed. This is those lines once, plus the three things the
|
|
9
|
+
* copies got wrong:
|
|
10
|
+
*
|
|
11
|
+
* - **A write settling after unmount sets no state and calls nothing** — the
|
|
12
|
+
* page navigated away, and its `reload` would read into a dead component.
|
|
13
|
+
* - **An OLDER write settling after a newer one does not overwrite its
|
|
14
|
+
* outcome** — each write is numbered, and the error shown is that of the
|
|
15
|
+
* highest-numbered write to have settled (`useRoute`'s stale-read rule, for
|
|
16
|
+
* writes, which cannot be dropped the way a read can).
|
|
17
|
+
* - **`busy` names WHICH write is in flight**, not just that one is — a row of
|
|
18
|
+
* buttons disables the one that was pressed, where a boolean greyed them all.
|
|
19
|
+
*
|
|
20
|
+
* `run` never rejects: a failure is `error` (the route's own `{ error }`
|
|
21
|
+
* sentence when it answered one) and `run` resolves `undefined`.
|
|
22
|
+
*
|
|
23
|
+
* @module
|
|
24
|
+
*/
|
|
25
|
+
import { type RouteMethod } from "./route-fetch.ts";
|
|
26
|
+
/**
|
|
27
|
+
* Options for {@link useRouteMutation}.
|
|
28
|
+
*
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
export type UseRouteMutationOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* The `?client=` to send. Default: the session's client
|
|
34
|
+
* (`session.identity.clientId()`, read per write) inside `mountClient()`,
|
|
35
|
+
* none on a page with no session.
|
|
36
|
+
*/
|
|
37
|
+
client?: string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Called after every write settles, success or failure — a `useRoute`'s
|
|
40
|
+
* `reload`, so the list shows what the write did. Not called after unmount.
|
|
41
|
+
* Read at settle time, so an inline arrow is fine.
|
|
42
|
+
*/
|
|
43
|
+
onSettled?: (() => void) | undefined;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Options for one {@link UseRouteMutationResult.run} call.
|
|
47
|
+
*
|
|
48
|
+
* @public
|
|
49
|
+
*/
|
|
50
|
+
export type RouteMutationRunOptions = {
|
|
51
|
+
/**
|
|
52
|
+
* What {@link UseRouteMutationResult.busy} names while this write is in
|
|
53
|
+
* flight — the field being saved, the row being deleted. Default:
|
|
54
|
+
* `"<METHOD> <path>"`.
|
|
55
|
+
*/
|
|
56
|
+
key?: string | undefined;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* What {@link useRouteMutation} returns.
|
|
60
|
+
*
|
|
61
|
+
* @public
|
|
62
|
+
*/
|
|
63
|
+
export type UseRouteMutationResult = {
|
|
64
|
+
/**
|
|
65
|
+
* Send one write: `routeFetch(method, path, body, { client })`. Resolves the
|
|
66
|
+
* route's answer, or `undefined` when it failed (the reason is in `error`) —
|
|
67
|
+
* never rejects.
|
|
68
|
+
*/
|
|
69
|
+
run: <T = unknown>(method: RouteMethod, path: string, body?: unknown, options?: RouteMutationRunOptions) => Promise<T | undefined>;
|
|
70
|
+
/**
|
|
71
|
+
* The key of the newest write still in flight (see
|
|
72
|
+
* {@link RouteMutationRunOptions.key}), or `undefined` when none is —
|
|
73
|
+
* `busy === "email"` for one field, `busy !== undefined` for any.
|
|
74
|
+
*/
|
|
75
|
+
busy: string | undefined;
|
|
76
|
+
/** Why the newest settled write failed — the route's `{ error }` when it said — or `undefined`. */
|
|
77
|
+
error: string | undefined;
|
|
78
|
+
/** Forget `error` — when the reader dismissed it, or edited the field again. */
|
|
79
|
+
clearError: () => void;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Write to the agent's own routes, with a busy key, the last error and a
|
|
83
|
+
* re-read after — see this module's doc.
|
|
84
|
+
*
|
|
85
|
+
* @example A profile field that saves, then re-reads
|
|
86
|
+
* ```tsx
|
|
87
|
+
* import { useRoute, useRouteMutation } from "@alexkroman1/aai-ui";
|
|
88
|
+
*
|
|
89
|
+
* function Name() {
|
|
90
|
+
* const { data, reload } = useRoute<{ name: string }>("/profile");
|
|
91
|
+
* const { run, busy, error } = useRouteMutation({ onSettled: reload });
|
|
92
|
+
* return (
|
|
93
|
+
* <form
|
|
94
|
+
* onSubmit={(e) => {
|
|
95
|
+
* e.preventDefault();
|
|
96
|
+
* const name = new FormData(e.currentTarget).get("name");
|
|
97
|
+
* void run("PUT", "/profile", { name }, { key: "name" });
|
|
98
|
+
* }}
|
|
99
|
+
* >
|
|
100
|
+
* <input name="name" defaultValue={data?.name} disabled={busy === "name"} />
|
|
101
|
+
* {error && <p role="alert">{error}</p>}
|
|
102
|
+
* </form>
|
|
103
|
+
* );
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @param options - `client` and `onSettled`; see {@link UseRouteMutationOptions}.
|
|
108
|
+
* @returns `run`, the busy key, the last error and `clearError`; see
|
|
109
|
+
* {@link UseRouteMutationResult}.
|
|
110
|
+
*
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
export declare function useRouteMutation(options?: UseRouteMutationOptions): UseRouteMutationResult;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useRoute` — read one of the agent's own `/api` routes into a component, and
|
|
3
|
+
* again on demand or on a poll. The reading half of `routeFetch`.
|
|
4
|
+
*
|
|
5
|
+
* What a hand-written `useEffect` + `fetch` got wrong here: a slow earlier read
|
|
6
|
+
* overwriting a newer one (each read carries an epoch, and a stale one is
|
|
7
|
+
* dropped — `useWorkflowRuns`' rule), a read landing after unmount, and a
|
|
8
|
+
* failure rendered as an empty answer. `data` is KEPT through a failed re-read,
|
|
9
|
+
* so a poll that hiccups does not blank the panel; `error` says it is stale.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Options for {@link useRoute}.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
*/
|
|
18
|
+
export type UseRouteOptions = {
|
|
19
|
+
/** Read again every this many ms, while mounted. Default: never. */
|
|
20
|
+
pollMs?: number | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* The `?client=` to send. Default: the session's client
|
|
23
|
+
* (`session.identity.clientId()`, read per request) inside `mountClient()`,
|
|
24
|
+
* none on a page with no session.
|
|
25
|
+
*/
|
|
26
|
+
client?: string | undefined;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* What {@link useRoute} returns.
|
|
30
|
+
*
|
|
31
|
+
* @public
|
|
32
|
+
*/
|
|
33
|
+
export type UseRouteResult<T> = {
|
|
34
|
+
/** The last answer, kept through a failed re-read. `undefined` until the first. */
|
|
35
|
+
data: T | undefined;
|
|
36
|
+
/** Why the LAST read failed — the route's `{ error }` when it said — or `undefined`. */
|
|
37
|
+
error: string | undefined;
|
|
38
|
+
/** Whether a read is in flight. */
|
|
39
|
+
loading: boolean;
|
|
40
|
+
/** Read again now — after a write, say. */
|
|
41
|
+
reload: () => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* `GET` one of the agent's own routes, on mount, on `reload()` and every
|
|
45
|
+
* `pollMs` — see this module's doc.
|
|
46
|
+
*
|
|
47
|
+
* @example A list that re-reads after each delete
|
|
48
|
+
* ```tsx
|
|
49
|
+
* import { routeFetch, useClientId, useRoute } from "@alexkroman1/aai-ui";
|
|
50
|
+
*
|
|
51
|
+
* type Memory = { id: string; text: string };
|
|
52
|
+
*
|
|
53
|
+
* function Memories() {
|
|
54
|
+
* const client = useClientId();
|
|
55
|
+
* const { data, error, reload } = useRoute<{ memories: Memory[] }>("/memories");
|
|
56
|
+
* if (error) return <p role="alert">{error}</p>;
|
|
57
|
+
* return (
|
|
58
|
+
* <ul>
|
|
59
|
+
* {data?.memories.map((m) => (
|
|
60
|
+
* <li key={m.id}>
|
|
61
|
+
* {m.text}
|
|
62
|
+
* <button
|
|
63
|
+
* type="button"
|
|
64
|
+
* onClick={() => void routeFetch("DELETE", `/memories/${m.id}`, undefined, { client }).then(reload)}
|
|
65
|
+
* >
|
|
66
|
+
* Forget
|
|
67
|
+
* </button>
|
|
68
|
+
* </li>
|
|
69
|
+
* ))}
|
|
70
|
+
* </ul>
|
|
71
|
+
* );
|
|
72
|
+
* }
|
|
73
|
+
* ```
|
|
74
|
+
*
|
|
75
|
+
* @typeParam T - The shape the route answers. Not checked.
|
|
76
|
+
* @param path - The route's path as declared, without `/api`. `null` reads nothing.
|
|
77
|
+
* @param options - `pollMs` and `client`; see {@link UseRouteOptions}.
|
|
78
|
+
* @returns The answer, the last error, and `reload`; see {@link UseRouteResult}.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
export declare function useRoute<T = unknown>(path: string | null, options?: UseRouteOptions): UseRouteResult<T>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The handle a page keeps on the runs it started, across a reload.
|
|
3
|
+
*
|
|
4
|
+
* `useWorkflowSubmit` is what makes a run survivable — the run id is that
|
|
5
|
+
* hook's own state, so a refresh loses it while the run carries on — and the
|
|
6
|
+
* `key` is what it looks the run up BY, because it is a lookup CAPABILITY:
|
|
7
|
+
* there is no per-user filtering behind `find`, so the key IS the scoping
|
|
8
|
+
* mechanism. Choosing one is easy to get wrong in three separate ways, and six
|
|
9
|
+
* shipped templates had each written the same twenty lines to get it right.
|
|
10
|
+
* This is those lines.
|
|
11
|
+
*
|
|
12
|
+
* **`useWorkflowSubmit` now mints one for itself** ({@link useDefaultRunKey}),
|
|
13
|
+
* so a page resumes its own run across a reload with nothing written at the
|
|
14
|
+
* call site — six of six page templates passed `useRunKey()` and
|
|
15
|
+
* `recover: true`, which is a default in the wrong place. The hook stays
|
|
16
|
+
* PUBLIC for the page that wants to choose: an app with accounts passes the
|
|
17
|
+
* ACCOUNT's own id instead, and a run then follows the person to a new device,
|
|
18
|
+
* which is a promise only a login can keep; a page whose run outlives the tab
|
|
19
|
+
* passes `useRunKey({ storage: "local" })`.
|
|
20
|
+
*
|
|
21
|
+
* ## Three properties, and the rejected alternatives are why each one matters
|
|
22
|
+
*
|
|
23
|
+
* - **Opaque** — `crypto.randomUUID()`, never derived from what was submitted.
|
|
24
|
+
* A key derived from the input collides the moment two people submit the same
|
|
25
|
+
* thing, and they then recover each other's runs; it also carries what they
|
|
26
|
+
* typed into a lookup token the platform deliberately stopped logging.
|
|
27
|
+
* - **Short.** A `randomUUID` is 36 characters, well inside the 256 that
|
|
28
|
+
* `POST /workflows/runs` allows a key.
|
|
29
|
+
* - **Minted once per load and written back for the next one**, which is the
|
|
30
|
+
* whole mechanism: the load that presses the button records the key with the
|
|
31
|
+
* run, and the load after it finds the run by producing the same key.
|
|
32
|
+
*
|
|
33
|
+
* Storage rather than the page's own URL, for all of them. A `?key=` parameter
|
|
34
|
+
* survives more (a new tab, a bookmark, a shared link) and that is the problem:
|
|
35
|
+
* a URL is pasted into chats, copied into referrers and kept in history, and
|
|
36
|
+
* what a leaked one buys is somebody else's work — reading it, and `cancel()` on
|
|
37
|
+
* it. An app with accounts should pass the ACCOUNT's own id here instead, and
|
|
38
|
+
* then a run follows the person to a new device, which is a promise only a login
|
|
39
|
+
* can keep.
|
|
40
|
+
*
|
|
41
|
+
* ## The storage is the caller's decision, and it is not a detail
|
|
42
|
+
*
|
|
43
|
+
* `"session"` (the default) dies with the tab, which covers exactly the
|
|
44
|
+
* interruption most pages have — a reload, a same-tab navigation, a crashed tab
|
|
45
|
+
* — and is the same lifetime as this package's other two stores, the session
|
|
46
|
+
* resume id (`session/resume-store.ts`) and the upload recall
|
|
47
|
+
* (`upload/recall.ts`), so both halves of a reload make the same promise.
|
|
48
|
+
*
|
|
49
|
+
* `"local"` is for a run that outlives all of that BY DESIGN — one that sleeps
|
|
50
|
+
* between digests and may live a month, where closing the browser on Tuesday and
|
|
51
|
+
* coming back on Friday to press Stop is the ordinary case rather than an edge
|
|
52
|
+
* one, and a tab-scoped key would answer that with an empty form beside a run
|
|
53
|
+
* still posting somewhere. It is as far as a key can go without a login, and no
|
|
54
|
+
* further. `podcast-digest-workflow` is that template, and the reason this hook is still
|
|
55
|
+
* called by name anywhere; the other five take the tab-scoped default the
|
|
56
|
+
* submit hook mints for them.
|
|
57
|
+
*
|
|
58
|
+
* ## Anything ELSE a page stores back must be VALIDATED on read
|
|
59
|
+
*
|
|
60
|
+
* This key needs no validation, and it is worth saying why, because it is the
|
|
61
|
+
* exception: any string is a legal key, so a value from storage can only fail to
|
|
62
|
+
* match a run. A page that remembers something more — which MODE submitted, say
|
|
63
|
+
* — is remembering a value it will turn into a name, and storage hands back a
|
|
64
|
+
* string some earlier version of that page wrote: a renamed mode, a hand-edited
|
|
65
|
+
* value, a slot another app on the origin happens to share. Unchecked, that
|
|
66
|
+
* starts a run called `undefined` and answers a 400 nobody typed. Check it
|
|
67
|
+
* against the page's own list on the way out (`recalledMode` in
|
|
68
|
+
* `transcription-workflow/recover.ts` is the worked example) — the recall is the
|
|
69
|
+
* page's, the validation is not optional.
|
|
70
|
+
*
|
|
71
|
+
* ## The slot is keyed by the page's own URL
|
|
72
|
+
*
|
|
73
|
+
* Every deployed agent is served from one origin at `/:slug/`, so a fixed name
|
|
74
|
+
* would have two agents scaffolded from the same template recover each other's
|
|
75
|
+
* runs. The key is the page's own directory — resolved through `"./"`, which
|
|
76
|
+
* drops the query and the hash, since a reload carrying `?foo` or `#bar` has to
|
|
77
|
+
* find the same key. Same call `session/resume-store.ts` makes, for the same
|
|
78
|
+
* reason.
|
|
79
|
+
*
|
|
80
|
+
* One key per PAGE is right even for a page driving several workflows: `find` is
|
|
81
|
+
* scoped by workflow as well as by key, so three hooks sharing one key recover
|
|
82
|
+
* three separate runs. `transcription-workflow` is that page.
|
|
83
|
+
*
|
|
84
|
+
* Every access is guarded. Storage THROWS outright in some contexts (Safari
|
|
85
|
+
* private mode, an iframe blocked by policy) and is ABSENT in others (any
|
|
86
|
+
* server-side render), and a page that cannot remember its key must degrade to
|
|
87
|
+
* the behaviour it would have had anyway — one run per load — rather than
|
|
88
|
+
* failing to render.
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* The key `useWorkflowSubmit` uses when the page named none.
|
|
92
|
+
*
|
|
93
|
+
* Two things it does that a plain `useRunKey()` at the call site cannot, and
|
|
94
|
+
* both are about a page that DID name one:
|
|
95
|
+
*
|
|
96
|
+
* - **It mints nothing when the caller has a key.** Minting writes to storage,
|
|
97
|
+
* so an unconditional `useRunKey()` inside the hook would leave a slot behind
|
|
98
|
+
* on every page that passes an account id and never reads it back.
|
|
99
|
+
* - **It stays reactive to the caller's key.** A key that arrives late — an
|
|
100
|
+
* account id resolved after a login — must reach the lookup, which re-asks on
|
|
101
|
+
* a changed key by design; freezing it into `useState` would pin the page to
|
|
102
|
+
* whatever it held on its first render.
|
|
103
|
+
*
|
|
104
|
+
* The minted half is still frozen for the component's life, which is what
|
|
105
|
+
* `useRunKey` freezes it for: a fresh key per render would record every run
|
|
106
|
+
* under a name the next load cannot produce.
|
|
107
|
+
*
|
|
108
|
+
* @param explicit - The caller's own key, or undefined for a page with none.
|
|
109
|
+
* @returns The key to record runs under and look them up by.
|
|
110
|
+
*
|
|
111
|
+
* @internal
|
|
112
|
+
*/
|
|
113
|
+
export declare function useDefaultRunKey(explicit: string | undefined): string;
|
|
114
|
+
/**
|
|
115
|
+
* A lookup key for `useWorkflowSubmit({ key })`, stable across reloads.
|
|
116
|
+
*
|
|
117
|
+
* @param options - See the module doc for the whole argument. The storage kind
|
|
118
|
+
* is read once, when the key is minted: a value that changed afterwards would
|
|
119
|
+
* be asking to move a key that has already been recorded with a run.
|
|
120
|
+
* @returns The key to record runs under and to look them up by — the same one
|
|
121
|
+
* for the life of the component, and for the next load in the same tab (or the
|
|
122
|
+
* same browser, under `"local"`).
|
|
123
|
+
*/
|
|
124
|
+
export declare function useRunKey(options?: {
|
|
125
|
+
/**
|
|
126
|
+
* Which store keeps the key between loads.
|
|
127
|
+
*
|
|
128
|
+
* `"session"` (the default) dies with the tab; `"local"` survives the
|
|
129
|
+
* browser closing, which is what a run that sleeps for days needs. See "The
|
|
130
|
+
* storage is the caller's decision".
|
|
131
|
+
*/
|
|
132
|
+
storage?: "session" | "local";
|
|
133
|
+
}): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSessionControls` — the two flags and four methods a control row is made
|
|
3
|
+
* of, and nothing else.
|
|
4
|
+
*
|
|
5
|
+
* Every custom chrome's control row opened with the same three lines:
|
|
6
|
+
* `useSessionActions()` for the methods, then two one-field
|
|
7
|
+
* `useSessionSelector` calls for `started` and `running`. That is the right
|
|
8
|
+
* shape — the whole-snapshot `useSession()` it replaced re-rendered four
|
|
9
|
+
* buttons at STT-partial rate to read two booleans that flip once a call — and
|
|
10
|
+
* it is also three lines every row writes identically, which is what a hook is
|
|
11
|
+
* for. `<SessionControls>` is built on it; a chrome whose buttons are too
|
|
12
|
+
* unusual for even that component's render slot takes the hook alone.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What {@link useSessionControls} returns.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
export type UseSessionControlsResult = {
|
|
20
|
+
/** Whether a call has been started and not yet ended — the Start/End hinge. */
|
|
21
|
+
started: boolean;
|
|
22
|
+
/** Whether the started call is live rather than paused — the Pause/Resume hinge. */
|
|
23
|
+
running: boolean;
|
|
24
|
+
/** Dial. What the button before `started` presses. */
|
|
25
|
+
start: () => void;
|
|
26
|
+
/** Pause a running call, or resume a paused one. */
|
|
27
|
+
toggle: () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Hang up AND dial again — a brand-new session with fresh session-scoped
|
|
30
|
+
* state, a fresh greeting, and the chrome kept on the call. `end()` then
|
|
31
|
+
* `start()`, never `reset()`: `reset()` clears the conversation and reconnects
|
|
32
|
+
* carrying the same session id, so every `sessionSlot` on the agent survives
|
|
33
|
+
* and the next tool call repopulates the board, cart or game that was just
|
|
34
|
+
* abandoned. Three chromes each found this and wrote the pair by hand.
|
|
35
|
+
*/
|
|
36
|
+
restart: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Hang up. Flips `started` back, so a chrome returns to its Start button and
|
|
39
|
+
* the next `start()` is a new session. `reset()` would keep the call live.
|
|
40
|
+
*/
|
|
41
|
+
end: () => void;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The state and the actions a Start / Pause–Resume / New conversation / End row
|
|
45
|
+
* renders from, on two one-field subscriptions.
|
|
46
|
+
*
|
|
47
|
+
* Must be used inside the provider `mountClient()` installs.
|
|
48
|
+
*
|
|
49
|
+
* @example A footer that renders its own buttons
|
|
50
|
+
* ```tsx
|
|
51
|
+
* import { useSessionControls } from "@alexkroman1/aai-ui";
|
|
52
|
+
*
|
|
53
|
+
* function Footer() {
|
|
54
|
+
* const { started, running, start, toggle, end } = useSessionControls();
|
|
55
|
+
* if (!started) return <button type="button" onClick={start}>Begin</button>;
|
|
56
|
+
* return (
|
|
57
|
+
* <>
|
|
58
|
+
* <button type="button" onClick={toggle}>{running ? "Hold" : "Resume"}</button>
|
|
59
|
+
* <button type="button" onClick={end}>Hang up</button>
|
|
60
|
+
* </>
|
|
61
|
+
* );
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @returns See {@link UseSessionControlsResult}.
|
|
66
|
+
*
|
|
67
|
+
* @public
|
|
68
|
+
*/
|
|
69
|
+
export declare function useSessionControls(): UseSessionControlsResult;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSessionId()` and `useClientId()` — the two ids a page keys things by, as
|
|
3
|
+
* React state.
|
|
4
|
+
*
|
|
5
|
+
* Both read `session.identity` through the session's own subscription, so they
|
|
6
|
+
* are current without any wiring. The alternative every app wrote was an
|
|
7
|
+
* external store fed by `mountClient({ onSessionId })` — which is set up
|
|
8
|
+
* OUTSIDE the component tree, fires only on a `config` frame (so it never says
|
|
9
|
+
* "no session" after `end()`), and has to be threaded to wherever the id is
|
|
10
|
+
* read. The session already knows both ids; these hooks just ask it.
|
|
11
|
+
*
|
|
12
|
+
* `useSyncExternalStore` over the snapshot subscription rather than a store of
|
|
13
|
+
* their own: each returns a string (or `undefined`), which compares by value,
|
|
14
|
+
* so a component re-renders only when ITS id changes, not on every frame the
|
|
15
|
+
* session notifies for. The dialer notifies when the confirmed session id
|
|
16
|
+
* changes even if no snapshot field moved with it.
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* The server's id for the current session: `undefined` before the session's
|
|
22
|
+
* first `config` frame and after `end()`; a resume or a new session updates it
|
|
23
|
+
* from its own `config` frame. Key a page's history by it.
|
|
24
|
+
*
|
|
25
|
+
* Treat it as sensitive — whoever holds it can resume the session and read its
|
|
26
|
+
* history (`VoiceSessionOptions.onSessionId` explains why).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* import { useSessionId } from "@alexkroman1/aai-ui";
|
|
31
|
+
*
|
|
32
|
+
* function SessionTag() {
|
|
33
|
+
* const id = useSessionId();
|
|
34
|
+
* return <small>{id ?? "no session yet"}</small>;
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @returns The current session id, or `undefined`.
|
|
39
|
+
*
|
|
40
|
+
* @public
|
|
41
|
+
*/
|
|
42
|
+
export declare function useSessionId(): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* The client id this session sends as `?client=` — the id a tool reads with
|
|
45
|
+
* `sessionClientId(ctx)` and the inbox socket is held under. With
|
|
46
|
+
* `mountClient({ client: "auto" })` it is this browser's `browserClientId()`;
|
|
47
|
+
* `undefined` when the session sends none.
|
|
48
|
+
*
|
|
49
|
+
* A `client` GETTER is read when the session notifies (each snapshot change),
|
|
50
|
+
* so a page that changes what its getter answers sees the new id on the next
|
|
51
|
+
* render the session causes.
|
|
52
|
+
*
|
|
53
|
+
* @returns The client id, or `undefined`.
|
|
54
|
+
*
|
|
55
|
+
* @public
|
|
56
|
+
*/
|
|
57
|
+
export declare function useClientId(): string | undefined;
|