@alexkroman1/aai-ui 0.12.3 → 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.
Files changed (201) hide show
  1. package/README.md +257 -0
  2. package/dist/_colors-CpZO-88A.js +78 -0
  3. package/dist/_module-url-C13kAJ87.js +15 -0
  4. package/dist/_page-shell.d.ts +11 -0
  5. package/dist/_recover-run.d.ts +79 -0
  6. package/dist/_repeat-until.d.ts +30 -0
  7. package/dist/_run-controls.d.ts +33 -0
  8. package/dist/_submission-state.d.ts +92 -0
  9. package/dist/_tap-to-talk-state.d.ts +88 -0
  10. package/dist/_utils-Mi-SCFA1.js +47 -0
  11. package/dist/_utils.d.ts +26 -0
  12. package/dist/_web-storage.d.ts +43 -0
  13. package/dist/_workflow-api-ref.d.ts +37 -0
  14. package/dist/_workflow-files.d.ts +34 -0
  15. package/dist/_workflow-test-defs.d.ts +38 -0
  16. package/dist/aai-logo-D8pMVn9_.js +85 -0
  17. package/dist/agent-state-labels.d.ts +65 -0
  18. package/dist/agent-state.d.ts +108 -0
  19. package/dist/audio/capture.d.ts +44 -0
  20. package/dist/audio/index.d.ts +18 -0
  21. package/dist/audio/index.js +431 -0
  22. package/dist/audio/preconnect.d.ts +87 -0
  23. package/dist/audio/voice-io.d.ts +112 -0
  24. package/dist/build-default-client.d.ts +1 -0
  25. package/dist/chat-view-BCbI7gD4.js +396 -0
  26. package/dist/client-config-C7fQrioM.js +139 -0
  27. package/dist/client-config.d.ts +119 -0
  28. package/dist/client-dir.d.ts +44 -0
  29. package/dist/client-dir.js +58 -0
  30. package/dist/client-identity.d.ts +131 -0
  31. package/dist/client-tool.d.ts +51 -0
  32. package/dist/components/_colors.d.ts +73 -0
  33. package/dist/components/_form-readiness.d.ts +56 -0
  34. package/dist/components/_form-values.d.ts +19 -0
  35. package/dist/components/aai-logo.d.ts +12 -0
  36. package/dist/components/audio-result.d.ts +94 -0
  37. package/dist/components/auto-scroll.d.ts +74 -0
  38. package/dist/components/bullet-list.d.ts +74 -0
  39. package/dist/components/button.d.ts +29 -21
  40. package/dist/components/button.js +62 -23
  41. package/dist/components/chat-view.d.ts +24 -18
  42. package/dist/components/chat-view.js +2 -60
  43. package/dist/components/console-shell.d.ts +85 -0
  44. package/dist/components/controls.d.ts +21 -6
  45. package/dist/components/controls.js +31 -19
  46. package/dist/components/conversation-view.d.ts +130 -0
  47. package/dist/components/eyebrow.d.ts +12 -0
  48. package/dist/components/facts.d.ts +81 -0
  49. package/dist/components/form-fields.d.ts +142 -0
  50. package/dist/components/form-types.d.ts +73 -0
  51. package/dist/components/form.d.ts +96 -0
  52. package/dist/components/markdown.d.ts +57 -0
  53. package/dist/components/message-list.d.ts +34 -7
  54. package/dist/components/message-list.js +2 -75
  55. package/dist/components/session-controls.d.ts +125 -0
  56. package/dist/components/session-error-banner.d.ts +69 -0
  57. package/dist/components/session-state-dot.d.ts +111 -0
  58. package/dist/components/sidebar-layout.d.ts +31 -10
  59. package/dist/components/sidebar-layout.js +32 -12
  60. package/dist/components/start-screen.d.ts +17 -6
  61. package/dist/components/start-screen.js +34 -14
  62. package/dist/components/tool-call-block.d.ts +15 -14
  63. package/dist/components/tool-call-block.js +2 -117
  64. package/dist/components/tool-call-row.d.ts +75 -0
  65. package/dist/components/tool-config-context.d.ts +22 -0
  66. package/dist/components/upload-progress.d.ts +76 -0
  67. package/dist/components/url-chips.d.ts +29 -0
  68. package/dist/components/workflow-fields.d.ts +46 -0
  69. package/dist/components/workflow-pending-note.d.ts +81 -0
  70. package/dist/components/workflow-progress.d.ts +81 -0
  71. package/dist/components/workflow-run-error.d.ts +48 -0
  72. package/dist/components/workflow-run-panel.d.ts +79 -0
  73. package/dist/context.d.ts +288 -0
  74. package/dist/context.js +382 -0
  75. package/dist/conversation-log.d.ts +88 -0
  76. package/dist/default-client/assets/_module-url-BX0RuRU2.js +1 -0
  77. package/dist/default-client/assets/audio-cGFGID-2.js +1 -0
  78. package/dist/default-client/assets/capture-processor-BUABoo3_.js +90 -0
  79. package/dist/default-client/assets/index-8ihb1Urm.css +2 -0
  80. package/dist/default-client/assets/index-BQURVARl.js +126 -0
  81. package/dist/default-client/assets/playback-processor-ABOa11j0.js +314 -0
  82. package/dist/default-client/favicon.ico +0 -0
  83. package/dist/default-client/index.html +15 -0
  84. package/dist/default-client.d.ts +23 -0
  85. package/dist/define-client.d.ts +162 -26
  86. package/dist/define-client.js +201 -55
  87. package/dist/eyebrow-UfmSz9yy.js +27 -0
  88. package/dist/hooks-DN4gHYsj.js +182 -0
  89. package/dist/hooks.d.ts +172 -0
  90. package/dist/hooks.js +3 -0
  91. package/dist/hooks.test-d.d.ts +14 -0
  92. package/dist/inbox-protocol.d.ts +128 -0
  93. package/dist/inbox.d.ts +132 -0
  94. package/dist/index.d.ts +126 -29
  95. package/dist/index.js +5500 -15
  96. package/dist/internal.d.ts +43 -0
  97. package/dist/internal.js +8 -0
  98. package/dist/linked-client.d.ts +70 -0
  99. package/dist/message-list-DHZcLhRA.js +682 -0
  100. package/dist/notice-player.d.ts +70 -0
  101. package/dist/page.d.ts +114 -0
  102. package/dist/phone.d.ts +50 -0
  103. package/dist/route-fetch.d.ts +79 -0
  104. package/dist/session/audio-effects.d.ts +31 -0
  105. package/dist/session/audio-setup.d.ts +57 -0
  106. package/dist/session/audio-state.d.ts +157 -0
  107. package/dist/session/browser-session.d.ts +30 -0
  108. package/dist/session/close.d.ts +26 -0
  109. package/dist/session/dial.d.ts +65 -0
  110. package/dist/session/handshake.d.ts +60 -0
  111. package/dist/session/index.d.ts +17 -0
  112. package/dist/session/index.js +2 -0
  113. package/dist/session/messages.d.ts +63 -0
  114. package/dist/session/mic.d.ts +64 -0
  115. package/dist/session/preconnect.d.ts +48 -0
  116. package/dist/session/reconnect.d.ts +25 -0
  117. package/dist/session/resume-store.d.ts +57 -0
  118. package/dist/session/state.d.ts +146 -0
  119. package/dist/session/ticket.d.ts +44 -0
  120. package/dist/session/types.d.ts +476 -0
  121. package/dist/session/url.d.ts +17 -0
  122. package/dist/session/user-turn.d.ts +55 -0
  123. package/dist/session-CE0Ipsb3.js +2129 -0
  124. package/dist/stored-value.d.ts +103 -0
  125. package/dist/tool-call-block-uXtMOUWN.js +204 -0
  126. package/dist/tool-config-context-Es4YUzV2.js +19 -0
  127. package/dist/types.d.ts +272 -34
  128. package/dist/types.js +35 -4
  129. package/dist/upload/files.d.ts +83 -0
  130. package/dist/upload/index.d.ts +15 -0
  131. package/dist/upload/pause.d.ts +31 -0
  132. package/dist/upload/recall.d.ts +67 -0
  133. package/dist/upload/report.d.ts +26 -0
  134. package/dist/upload/session.d.ts +99 -0
  135. package/dist/url-chips-oyy_Eiqh.js +239 -0
  136. package/dist/use-client-runs.d.ts +87 -0
  137. package/dist/use-conversation-log.d.ts +86 -0
  138. package/dist/use-conversation.d.ts +131 -0
  139. package/dist/use-copy.d.ts +74 -0
  140. package/dist/use-download-url.d.ts +84 -0
  141. package/dist/use-flash.d.ts +61 -0
  142. package/dist/use-inbox.d.ts +101 -0
  143. package/dist/use-push-to-talk.d.ts +105 -0
  144. package/dist/use-route-mutation.d.ts +113 -0
  145. package/dist/use-route.d.ts +82 -0
  146. package/dist/use-run-key.d.ts +133 -0
  147. package/dist/use-session-controls.d.ts +69 -0
  148. package/dist/use-session-id.d.ts +57 -0
  149. package/dist/use-tap-to-talk.d.ts +138 -0
  150. package/dist/use-user-transcript-uyHhzy4d.js +73 -0
  151. package/dist/use-user-transcript.d.ts +75 -0
  152. package/dist/use-workflow-form.d.ts +285 -0
  153. package/dist/use-workflow-progress.d.ts +137 -0
  154. package/dist/use-workflow-run-DdhWmbx-.js +697 -0
  155. package/dist/use-workflow-run.d.ts +100 -0
  156. package/dist/use-workflow-runs.d.ts +71 -0
  157. package/dist/use-workflow-stream.d.ts +154 -0
  158. package/dist/use-workflows.d.ts +77 -0
  159. package/dist/workflow-client.d.ts +136 -0
  160. package/dist/workflow-def-types.d.ts +31 -0
  161. package/dist/workflow-events.d.ts +39 -0
  162. package/dist/workflow-field-kind.d.ts +95 -0
  163. package/dist/workflow-status-labels.d.ts +35 -0
  164. package/dist/worklets/_module-url.d.ts +10 -0
  165. package/dist/worklets/_playback-bench-harness.d.ts +181 -0
  166. package/dist/worklets/_playback-bench-host.d.ts +63 -0
  167. package/dist/worklets/_playback-bench-page.d.ts +65 -0
  168. package/dist/worklets/_tts-trace-harness.d.ts +142 -0
  169. package/dist/worklets/_worklet-test-utils.d.ts +27 -0
  170. package/dist/worklets/capture-processor.d.ts +4 -2
  171. package/dist/worklets/capture-processor.js +73 -33
  172. package/dist/worklets/playback-processor.d.ts +4 -2
  173. package/dist/worklets/playback-processor.js +258 -42
  174. package/package.json +57 -32
  175. package/styles.css +152 -30
  176. package/dist/audio.d.ts +0 -45
  177. package/dist/audio.js +0 -121
  178. package/dist/client-context.d.ts +0 -33
  179. package/dist/client-context.js +0 -15
  180. package/dist/client-handler.d.ts +0 -43
  181. package/dist/components/app.d.ts +0 -23
  182. package/dist/components/app.js +0 -41
  183. package/dist/components/error-banner.d.ts +0 -21
  184. package/dist/components/error-banner.js +0 -27
  185. package/dist/components/message-bubble.d.ts +0 -23
  186. package/dist/components/message-bubble.js +0 -35
  187. package/dist/components/state-indicator.d.ts +0 -29
  188. package/dist/components/state-indicator.js +0 -37
  189. package/dist/components/thinking-indicator.d.ts +0 -16
  190. package/dist/components/thinking-indicator.js +0 -34
  191. package/dist/components/tool-icons.d.ts +0 -17
  192. package/dist/components/tool-icons.js +0 -128
  193. package/dist/components/transcript.d.ts +0 -25
  194. package/dist/components/transcript.js +0 -35
  195. package/dist/session-CWB7vmqz.js +0 -429
  196. package/dist/session.d.ts +0 -69
  197. package/dist/session.js +0 -2
  198. package/dist/signals.d.ts +0 -120
  199. package/dist/signals.js +0 -161
  200. package/dist/tsdown.config.d.ts +0 -2
  201. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Filesystem location of the prebuilt default client — **Node only**.
3
+ *
4
+ * Its own subpath rather than the root barrel because it imports `node:module`
5
+ * and `node:path`: the root export is browser code, and pulling Node builtins
6
+ * into it would break every bundler that consumes this package.
7
+ *
8
+ * This exists because locating the directory is otherwise three lines of module
9
+ * archaeology plus knowledge of an internal `dist/` layout, and everybody
10
+ * serving the default UI has to write them — `aai-cli`'s dev server had its own
11
+ * copy, as did every self-hosted example. Three places that would all silently
12
+ * serve nothing if the build output moved.
13
+ *
14
+ * @module client-dir
15
+ */
16
+ /**
17
+ * Absolute path to the prebuilt browser client's static files — pass it to
18
+ * `createServerForRuntime`/`createAgentServer` as `clientDir`.
19
+ *
20
+ * A function, not a constant: resolution touches the module graph and throws
21
+ * when the package is missing, and a module-level constant would move that
22
+ * failure to import time — where it fires for callers that never wanted the
23
+ * client, and before any of their own error handling is in place.
24
+ *
25
+ * Resolved through this package's own `package.json` rather than relative to
26
+ * this module, so it lands in the same place whether the caller resolved the
27
+ * `@dev/source` TypeScript entry or the compiled one under `dist/`.
28
+ *
29
+ * @example
30
+ * ```ts
31
+ * import { agent } from "@alexkroman1/aai";
32
+ * import { createAgentServer } from "@alexkroman1/aai-runtime";
33
+ * import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
34
+ *
35
+ * const server = createAgentServer({
36
+ * agent: agent({ name: "Support" }),
37
+ * env: {},
38
+ * clientDir: defaultClientDir(),
39
+ * });
40
+ * ```
41
+ *
42
+ * @public
43
+ */
44
+ export declare function defaultClientDir(): string;
@@ -0,0 +1,58 @@
1
+ import { createRequire } from "node:module";
2
+ import path from "node:path";
3
+ //#region src/client-dir.ts
4
+ /**
5
+ * Filesystem location of the prebuilt default client — **Node only**.
6
+ *
7
+ * Its own subpath rather than the root barrel because it imports `node:module`
8
+ * and `node:path`: the root export is browser code, and pulling Node builtins
9
+ * into it would break every bundler that consumes this package.
10
+ *
11
+ * This exists because locating the directory is otherwise three lines of module
12
+ * archaeology plus knowledge of an internal `dist/` layout, and everybody
13
+ * serving the default UI has to write them — `aai-cli`'s dev server had its own
14
+ * copy, as did every self-hosted example. Three places that would all silently
15
+ * serve nothing if the build output moved.
16
+ *
17
+ * @module client-dir
18
+ */
19
+ const require = createRequire(import.meta.url);
20
+ /**
21
+ * Absolute path to the prebuilt browser client's static files — pass it to
22
+ * `createServerForRuntime`/`createAgentServer` as `clientDir`.
23
+ *
24
+ * A function, not a constant: resolution touches the module graph and throws
25
+ * when the package is missing, and a module-level constant would move that
26
+ * failure to import time — where it fires for callers that never wanted the
27
+ * client, and before any of their own error handling is in place.
28
+ *
29
+ * Resolved through this package's own `package.json` rather than relative to
30
+ * this module, so it lands in the same place whether the caller resolved the
31
+ * `@dev/source` TypeScript entry or the compiled one under `dist/`.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * import { agent } from "@alexkroman1/aai";
36
+ * import { createAgentServer } from "@alexkroman1/aai-runtime";
37
+ * import { defaultClientDir } from "@alexkroman1/aai-ui/client-dir";
38
+ *
39
+ * const server = createAgentServer({
40
+ * agent: agent({ name: "Support" }),
41
+ * env: {},
42
+ * clientDir: defaultClientDir(),
43
+ * });
44
+ * ```
45
+ *
46
+ * @public
47
+ */
48
+ function defaultClientDir() {
49
+ let pkgPath;
50
+ try {
51
+ pkgPath = require.resolve("@alexkroman1/aai-ui/package.json");
52
+ } catch (err) {
53
+ throw new Error("Could not locate the default client UI — is @alexkroman1/aai-ui installed? Try reinstalling dependencies (pnpm install).", { cause: err });
54
+ }
55
+ return path.join(path.dirname(pkgPath), "dist", "default-client");
56
+ }
57
+ //#endregion
58
+ export { defaultClientDir };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Who this browser is to an agent: the id `mountClient({ client: "auto" })`
3
+ * sends as `?client=`, the inbox HOLDER id of this tab, and the
4
+ * `session.identity` handle both are read from.
5
+ *
6
+ * ## Why the SDK mints the client id
7
+ *
8
+ * A client id is what a tool hands a workflow run (`sessionClientId(ctx)`) so
9
+ * the run's `stepNotifyClient` can reach this browser AFTER the session that
10
+ * started it has closed — a reminder due tomorrow is delivered to whoever holds
11
+ * the id tomorrow. So it must be the SAME id on the session socket and on the
12
+ * inbox socket, and it must outlive the tab. Every app that wanted a reminder in
13
+ * a browser wrote the same thirty lines (a `localStorage` read, a validity
14
+ * check, a fallback for private mode) and got the second half of this module
15
+ * wrong: see the holder below.
16
+ *
17
+ * - **`localStorage`, keyed by the agent's URL** (`_web-storage.ts`'s rule). Two
18
+ * agents served from one origin — every deployed agent, at `/:slug/` — must
19
+ * not share one: the id is the ONLY credential for a client's durable
20
+ * conversation (`VoiceSessionOptions.client`), so agent A's server, which
21
+ * receives it, could otherwise present it to agent B and read B's history.
22
+ * - **Unguessable**: 128 random bits, from `crypto.getRandomValues` — which,
23
+ * unlike `crypto.randomUUID`, exists outside a secure context, and `aai dev`
24
+ * on a LAN address is plain `http:`.
25
+ * - **Private mode** (storage throws or is absent) degrades to an id for THIS
26
+ * TAB, held in memory: the session and inbox still agree, a reminder set in
27
+ * the tab reaches the tab, and nothing outlives it.
28
+ *
29
+ * ## Why the holder is per TAB, not per browser
30
+ *
31
+ * The inbox keeps one socket per (client, holder) pair, and the same pair again
32
+ * REPLACES the old socket — right for a device back from a Wi-Fi drop, whose old
33
+ * socket is half-dead. Every tab of a browser shares `localStorage`, so an app
34
+ * that used the browser-wide id as the holder had two tabs presenting the same
35
+ * pair: each connect knocked the other tab off, the other reconnected on its
36
+ * backoff and knocked this one off, about once a second, forever. The holder is
37
+ * the browser id plus a suffix minted once per PAGE LOAD, so tabs coexist as
38
+ * holders of one client and every one of them is offered the notice.
39
+ *
40
+ * @module
41
+ */
42
+ /**
43
+ * This browser's stable client id for the agent at `platformUrl` —
44
+ * `browser-<32 hex>`, minted once and kept in `localStorage`. The id
45
+ * `mountClient({ client: "auto" })` sends; call it yourself to compose it, e.g.
46
+ * a page joined to a speaker that sends the speaker's id instead:
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * import { browserClientId, mountClient } from "@alexkroman1/aai-ui";
51
+ *
52
+ * declare function linkedSpeaker(): string | undefined;
53
+ *
54
+ * mountClient({ client: () => linkedSpeaker() ?? browserClientId() });
55
+ * ```
56
+ *
57
+ * A stored value that is not a valid client id (letters, digits, `-`, `_`, at
58
+ * most 64) is replaced. Where storage is unavailable the id lasts for this tab.
59
+ *
60
+ * @param platformUrl - The agent's base URL; the id is per agent. Defaults to
61
+ * the page's own, which is `mountClient()`'s default too.
62
+ * @returns The id — the same one on every call for the same agent.
63
+ *
64
+ * @public
65
+ */
66
+ export declare function browserClientId(platformUrl?: string): string;
67
+ /**
68
+ * This TAB's inbox holder id for the agent at `platformUrl`: the browser id
69
+ * plus a per-page-load suffix. Never the browser id alone — see the module doc
70
+ * for the two tabs that knocked each other off once a second.
71
+ *
72
+ * @internal
73
+ */
74
+ export declare function inboxHolderId(platformUrl: string): string;
75
+ /**
76
+ * Who a session is to its agent — `session.identity`. Everything here is read
77
+ * NOW (a getter `client` can change between attempts), and nothing is a
78
+ * subscription: `useClientId()` and `useSessionId()` are the reactive reads.
79
+ *
80
+ * @sealed Only `createBrowserSession` produces one.
81
+ *
82
+ * @public
83
+ */
84
+ export type SessionIdentity = {
85
+ /** The agent's base URL the session dials — `VoiceSessionOptions.platformUrl`. */
86
+ readonly platformUrl: string;
87
+ /**
88
+ * The client id the next connection attempt sends as `?client=`, trimmed, or
89
+ * `undefined` when it sends none. With `client: "auto"` it is
90
+ * {@link browserClientId}; with a getter it is the getter's answer now.
91
+ */
92
+ clientId(): string | undefined;
93
+ /**
94
+ * This tab's `?holder=` for the client's `WS /inbox` socket — per TAB, so two
95
+ * tabs of one browser coexist instead of replacing each other's socket.
96
+ */
97
+ holderId(): string;
98
+ /**
99
+ * The server's id for the current session: `undefined` until the first
100
+ * `config` frame of a session, and again after `end()`. A resume or a new
101
+ * session sets it from its own `config` frame. Sensitive — see
102
+ * `VoiceSessionOptions.onSessionId`.
103
+ */
104
+ sessionId(): string | undefined;
105
+ /**
106
+ * A session ticket for ANOTHER socket on the same server — what `useInbox()`
107
+ * presents on `WS /inbox`, which a gated server checks exactly like
108
+ * `/websocket`. The session's own `token` option when it has one; otherwise a
109
+ * fresh one from the server's `client-config` (`aai dev` with
110
+ * `AAI_SESSION_SECRET`), until a lookup shows the server issues none.
111
+ * `undefined` means "present none". Fresh on every call: tickets are short-lived.
112
+ */
113
+ ticket(): string | undefined | Promise<string | undefined>;
114
+ };
115
+ /** The client option as the dialer reads it: `"auto"` resolved, everything else as given. */
116
+ export declare function resolveClientOption(client: string | (() => string | undefined) | undefined, platformUrl: string): string | (() => string | undefined) | undefined;
117
+ /** A string-or-getter option resolved NOW, trimmed; an empty answer is none. */
118
+ export declare function resolveReported(value: string | (() => string | undefined) | undefined): string | undefined;
119
+ /** What the identity reads from the session's dialer. */
120
+ type IdentitySource = Pick<SessionIdentity, "sessionId" | "ticket">;
121
+ /**
122
+ * Build a session's {@link SessionIdentity}. `sessionId` and `ticket` are the
123
+ * dialer's (read through `source`, which may answer nothing): it owns the id
124
+ * because it sees every `config` frame, `end()` and `resume()`, and the ticket
125
+ * because it holds the `token` option and the server's `client-config` latch.
126
+ */
127
+ export declare function createSessionIdentity(options: {
128
+ platformUrl: string;
129
+ client?: string | (() => string | undefined) | undefined;
130
+ }, source: () => IdentitySource | undefined): SessionIdentity;
131
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The page's half of a `clientTool`: run the handler when the agent calls the
3
+ * tool, and send what it returned back as the call's result.
4
+ *
5
+ * Built on {@link useToolCallStart}, so it inherits that hook's dedup — one run
6
+ * per `toolCallId` per mounted hook — and its mount rule: a call still PENDING
7
+ * when the hook mounts is run (the server may be waiting on it, e.g. across a
8
+ * resume), a call that already completed is history and is not.
9
+ *
10
+ * @module
11
+ */
12
+ import type { ToolCallInfo } from "./types.ts";
13
+ /**
14
+ * Run a `clientTool` in this page and answer the agent with its result.
15
+ *
16
+ * When the model calls the tool named `toolName`, `handler` runs with the
17
+ * call's arguments. What it returns (or resolves to) is JSON-serialized and
18
+ * becomes the tool's result — the value the model reads. A handler that throws
19
+ * or rejects fails the call, and the model is told the error's message, as it
20
+ * would be for a server tool.
21
+ *
22
+ * The server waits only as long as the tool's `timeoutMs` (default 30 s); an
23
+ * answer after that, or while disconnected, is dropped. Mount the hook once per
24
+ * tool: two mounted handlers both run, and only the first answer counts.
25
+ *
26
+ * @example Let the agent ask for the caller's location
27
+ * ```tsx
28
+ * import { useClientTool } from "@alexkroman1/aai-ui";
29
+ *
30
+ * function LocationTool() {
31
+ * useClientTool("get_location", () =>
32
+ * new Promise((resolve, reject) =>
33
+ * navigator.geolocation.getCurrentPosition(
34
+ * (p) => resolve({ lat: p.coords.latitude, lon: p.coords.longitude }),
35
+ * (err) => reject(new Error(err.message)),
36
+ * ),
37
+ * ),
38
+ * );
39
+ * return null;
40
+ * }
41
+ * ```
42
+ *
43
+ * @typeParam A - The tool's argument shape. Name it, or derive it with a
44
+ * TYPE-ONLY import of the server tool: `useClientTool<InferToolInput<typeof
45
+ * getLocation>>(…)`.
46
+ * @param toolName - The name of the `clientTool` — its `tools/<name>.ts` file.
47
+ * @param handler - Runs the call; its return value is the result.
48
+ *
49
+ * @public
50
+ */
51
+ export declare function useClientTool<A = ToolCallInfo["args"]>(toolName: string, handler: (args: A, toolCall: ToolCallInfo) => unknown): void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Shared tints used by the default components (AssemblyAI design system,
3
+ * "website refresh": warm neutrals over the cream/white surfaces).
4
+ *
5
+ * These sit on top of the {@link ClientTheme} colors, which own the opaque
6
+ * palette. The neutral text steps are **derived from the theme** rather than
7
+ * hardcoded: `ClientTheme` documents `bg`/`surface`/`text` as free-form
8
+ * colors, so a client is free to hand the components a dark ground — and a
9
+ * fixed warm-grey scale tuned for cream leaves the AGENT/TOOL labels, every
10
+ * tool-call args preview, the chevron and both URL chips below WCAG AA while
11
+ * the prose and bubbles around them follow the theme correctly. Measured on a
12
+ * plausible dark theme before this was derived: 14 text nodes under 4.5:1
13
+ * (the faint step at 3.2–3.5:1, the muted step at 2.5:1), against zero on the
14
+ * default light theme.
15
+ *
16
+ * `ERROR_COLOR` and `THINKING_COLOR` stay fixed: they are *semantic* colors
17
+ * whose hue carries the meaning, so they cannot be re-derived from a theme
18
+ * that knows nothing about severity.
19
+ */
20
+ /**
21
+ * Blend `text` into `surface` by `pct`, giving a neutral step that stays on
22
+ * the theme's own ink/ground axis. On a light theme this walks *down* from
23
+ * the surface toward the ink; on a dark one it walks *up* from the ground
24
+ * toward the light ink — the same call reads correctly either way, which is
25
+ * the whole reason to derive rather than hardcode.
26
+ */
27
+ export declare function inkTint(text: string, surface: string, pct: number): string;
28
+ /**
29
+ * Muted text — subtitles, secondary labels, thinking dots (fg-muted).
30
+ * 75% lands within a couple of RGB steps of the design system's `#57534B`
31
+ * on the default theme.
32
+ */
33
+ export declare const INK_MUTED_PCT = 75;
34
+ /**
35
+ * Faint text — live transcripts, state indicator, start-screen subtitle.
36
+ * 65% reproduces the design system's `#6F6A60` on the default theme.
37
+ */
38
+ export declare const INK_FAINT_PCT = 65;
39
+ /** Subtle surface tint — inline code, tool-call chips, message bubbles. */
40
+ export declare const INK_SURFACE_PCT = 3;
41
+ /** Error red tuned for warm light surfaces. */
42
+ export declare const ERROR_COLOR = "#B3261E";
43
+ /** Amber used by the "thinking" state indicator. */
44
+ export declare const THINKING_COLOR = "#B98900";
45
+ /**
46
+ * Derive the user-bubble fill/edge from the theme primary (the mock's
47
+ * indigo-50 background with an indigo-100 border), so custom themes keep a
48
+ * coherent tinted bubble instead of a hardcoded indigo.
49
+ */
50
+ export declare function primaryTint(primary: string, surface: string, pct: number): string;
51
+ /**
52
+ * The focus ring, as the class half of a pair.
53
+ *
54
+ * The ring is the only thing a keyboard user gets, and it has regressed to
55
+ * invisible twice — `outline-none` shipped here with no replacement, on the
56
+ * button and then on the URL chips, which is WCAG 2.4.7 on every control in the
57
+ * default client. Written as a SHORTHAND so it cannot be undone by Tailwind's
58
+ * shared `--tw-outline-style` variable.
59
+ *
60
+ * It is inert on its own: an outline with no colour draws nothing, so a control
61
+ * using this must also spread {@link focusRingStyle}. Naming the two together
62
+ * is what stops the next control shipping one without the other.
63
+ */
64
+ export declare const FOCUS_RING = "outline-none focus-visible:[outline:2px_solid] focus-visible:[outline-offset:2px]";
65
+ /**
66
+ * The style half of {@link FOCUS_RING} — the colour the outline draws in.
67
+ *
68
+ * A real CSS property rather than a variant class, so the `focus-visible` rule
69
+ * only has to turn the outline on.
70
+ */
71
+ export declare function focusRingStyle(primary: string): {
72
+ outlineColor: string;
73
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Whether a form's own fields exist yet.
3
+ *
4
+ * `<Form>` leans entirely on NATIVE validation — it is a real `<form>` with no
5
+ * `noValidate`, so a `required` field is what stops an empty submit, and the
6
+ * module doc next door says so. That has one hole, and it is the whole reason
7
+ * this exists: a field set declared REMOTELY is not in the DOM while its
8
+ * declaration is in flight, so there is nothing for the browser to validate and
9
+ * an empty submit sails through.
10
+ *
11
+ * It is not theoretical. `<WorkflowFields>` renders `null` until the workflow
12
+ * listing lands, so the transcription desk's first click — before the one-request
13
+ * lookup answered — submitted a form holding only its button. The browser was
14
+ * happy, the payload was `{}`, and the run was refused by the agent with
15
+ * `Invalid input for workflow "transcribeStream": recording: Invalid input`: a
16
+ * schema complaint about a field the person had not been shown, naming a workflow
17
+ * they did not choose by name, for a file picker that appeared a moment later.
18
+ *
19
+ * ## Readiness is DECLARED by the children, because only they know
20
+ *
21
+ * `Form` cannot ask. It renders `{children}` and reads the DOM on submit, and a
22
+ * pending fetch leaves no trace in the DOM at all — which is exactly the
23
+ * difference from `data-aai-read`, the other thing a child tells the form: that
24
+ * one describes an element that EXISTS. So this is a context rather than an
25
+ * attribute, and it carries the one fact a DOM read cannot recover.
26
+ *
27
+ * A form with no such children is ready by definition — `useFormReadiness`
28
+ * outside a provider reports nothing pending, so every hand-written form is
29
+ * unaffected and `Form` keeps working outside this package.
30
+ */
31
+ /** What a child calls to say whether its own fields are ready. */
32
+ type Readiness = (key: string, pending: boolean) => void;
33
+ export declare const FormReadinessProvider: import("react").Provider<Readiness | undefined>;
34
+ /**
35
+ * Track which children are still waiting for their fields.
36
+ *
37
+ * A SET keyed by the child's own `useId`, not a counter: a child that re-renders
38
+ * while pending must not increment twice, and one that unmounts mid-flight must
39
+ * not leave the form disabled forever. Both are the ordinary lifecycle here — a
40
+ * page that switches workflows swaps one `<WorkflowFields>` for another.
41
+ */
42
+ export declare function useFormReadiness(): {
43
+ pending: boolean;
44
+ declare: Readiness;
45
+ };
46
+ /**
47
+ * Declare from a child that its fields are, or are no longer, still loading.
48
+ *
49
+ * Reports through an EFFECT rather than during render, because this writes to a
50
+ * parent's state — doing it in the render body is the "cannot update a component
51
+ * while rendering a different component" warning, and under StrictMode it is a
52
+ * double report the parent has to be idempotent about anyway. The cleanup
53
+ * releases the claim, so an unmounted field set never holds the form shut.
54
+ */
55
+ export declare function useDeclareFieldsPending(pending: boolean): void;
56
+ export {};
@@ -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,12 @@
1
+ /**
2
+ * The AssemblyAI wordmark (design-system "website refresh" asset).
3
+ *
4
+ * The mark keeps its brand blues; the letterforms inherit the theme's text
5
+ * color so the logo works on the cream default and custom themes alike.
6
+ *
7
+ * @param size - Rendered height in px. Defaults to 20.
8
+ * @internal
9
+ */
10
+ export declare const AaiLogo: import("react").MemoExoticComponent<({ size }: {
11
+ size?: number;
12
+ }) => import("react").JSX.Element>;
@@ -0,0 +1,94 @@
1
+ import type { ReactNode } from "react";
2
+ import type { UseDownloadUrlResult } from "../use-download-url.ts";
3
+ /**
4
+ * A one-cue caption track for {@link AudioResult}: the words the clip speaks,
5
+ * spanning its whole length.
6
+ *
7
+ * @public
8
+ */
9
+ export type AudioResultCaptions = {
10
+ /** The spoken text — the one cue. */
11
+ text: string;
12
+ /** The clip's length, which is where the cue ends. */
13
+ durationMs: number;
14
+ /** The track's `label`. Defaults to the player's `label`. */
15
+ label?: string | undefined;
16
+ /** The track's `srcLang`. Default `"en"`. */
17
+ srcLang?: string | undefined;
18
+ };
19
+ /**
20
+ * Props of {@link AudioResult}.
21
+ *
22
+ * @public
23
+ */
24
+ export type AudioResultProps = {
25
+ /** What {@link useDownloadUrl} returned for the run's audio upload. */
26
+ download: UseDownloadUrlResult;
27
+ /** The name the download link saves as — `"summary.wav"`, `"audit.mp3"`. */
28
+ filename: string;
29
+ /** The player's `aria-label`: what this audio IS — `"Summary read aloud"`. */
30
+ label: string;
31
+ /**
32
+ * The heading over the player — typically the duration and size, which the
33
+ * run's output carries. Omitted, there is no heading.
34
+ */
35
+ heading?: ReactNode | undefined;
36
+ /**
37
+ * The spoken text as a caption track. Omit it deliberately when the same
38
+ * words are rendered in full beside the player (see the component doc);
39
+ * pass it when they are not, or when a real track is what a page needs.
40
+ */
41
+ captions?: AudioResultCaptions | undefined;
42
+ /** Additional CSS class names for the wrapping `<section>`, appended to its own. */
43
+ className?: string | undefined;
44
+ /** Rendered under the player — the spoken text, usually. */
45
+ children?: ReactNode;
46
+ };
47
+ /**
48
+ * The player for a file a RUN produced: a heading, the fetch's pending line,
49
+ * its announced error, the `<audio>` with an optional caption track, the
50
+ * download link, and whatever the page renders beneath — the spoken text.
51
+ *
52
+ * Two templates exist because of the audio round trip, and both had written
53
+ * this block over {@link useDownloadUrl} identically: the same pending
54
+ * sentence, the same `role="alert"` paragraph, the same `<audio controls>` over
55
+ * the object URL, the same anchor with `download` set. The `download` attribute
56
+ * works on an object URL because the bytes are already in the tab — it was the
57
+ * `href` that could not carry the agent's bearer token, never the attribute —
58
+ * and that is the whole reason both pages hand this a hook result rather than a
59
+ * path.
60
+ *
61
+ * **A caption track is a judgement, not a default.** `spoken-summary-workflow` passes
62
+ * `captions`: the summary was written before it was spoken, so one cue
63
+ * spanning the clip is an honest transcript of it. `call-audit-workflow` deliberately
64
+ * does not: the spoken text is rendered in full immediately below the player,
65
+ * which is the same information a track would carry. Both are right, which is
66
+ * why the prop is optional in both directions.
67
+ *
68
+ * @example
69
+ * ```tsx
70
+ * import { AudioResult, createWorkflowApi, useDownloadUrl } from "@alexkroman1/aai-ui";
71
+ *
72
+ * const api = createWorkflowApi();
73
+ *
74
+ * function Player({ id, spoken, ms }: { id: string; spoken: string; ms: number }) {
75
+ * const audio = useDownloadUrl(id, { api });
76
+ * return (
77
+ * <AudioResult
78
+ * download={audio}
79
+ * filename="summary.wav"
80
+ * label="Summary read aloud"
81
+ * heading="Read aloud"
82
+ * captions={{ text: spoken, durationMs: ms }}
83
+ * >
84
+ * <p className="text-sm opacity-70">{spoken}</p>
85
+ * </AudioResult>
86
+ * );
87
+ * }
88
+ * ```
89
+ *
90
+ * @param props - See {@link AudioResultProps}.
91
+ *
92
+ * @public
93
+ */
94
+ export declare function AudioResult({ download, filename, label, heading, captions, className, children, }: AudioResultProps): ReactNode;
@@ -0,0 +1,74 @@
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 props - Scroll container props.
40
+ *
41
+ * @public
42
+ */
43
+ export declare function AutoScroll({ children, className, contentClassName, scrollClassName, style, initial, resize, }: {
44
+ /** The scrollable content. */
45
+ children: ReactNode;
46
+ /**
47
+ * Classes for the outer container, appended to its own.
48
+ *
49
+ * **The container must end up with a bounded height** (`flex-1 min-h-0`,
50
+ * `h-full`, a fixed height). This is the one constraint callers get wrong:
51
+ * an unbounded container grows with its content and never scrolls, so
52
+ * nothing pins and the component silently does nothing.
53
+ */
54
+ className?: string | undefined;
55
+ /**
56
+ * Classes for the inner content element, where padding and the children's
57
+ * own layout belong.
58
+ */
59
+ contentClassName?: string | undefined;
60
+ /**
61
+ * Classes for the scrolling element itself. Defaults to hiding the
62
+ * scrollbar; pass `"overflow-y-auto"` to show a native one.
63
+ */
64
+ scrollClassName?: string | undefined;
65
+ /** Inline styles for the outer container. */
66
+ style?: CSSProperties | undefined;
67
+ /**
68
+ * Scroll behavior on mount. Defaults to `"instant"` — start at the latest
69
+ * content without animating a scroll the reader did not ask for.
70
+ */
71
+ initial?: "instant" | "smooth" | undefined;
72
+ /** Scroll behavior when pinned content grows. Defaults to `"smooth"`. */
73
+ resize?: "instant" | "smooth" | undefined;
74
+ }): ReactNode;