@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.
Files changed (215) 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 +58 -0
  40. package/dist/components/button.js +85 -0
  41. package/dist/components/chat-view.d.ts +35 -0
  42. package/dist/components/chat-view.js +3 -0
  43. package/dist/components/console-shell.d.ts +85 -0
  44. package/dist/components/controls.d.ts +33 -0
  45. package/dist/components/controls.js +49 -0
  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 +48 -0
  54. package/dist/components/message-list.js +3 -0
  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 +42 -0
  59. package/dist/components/sidebar-layout.js +56 -0
  60. package/dist/components/start-screen.d.ts +37 -0
  61. package/dist/components/start-screen.js +71 -0
  62. package/dist/components/tool-call-block.d.ts +28 -0
  63. package/dist/components/tool-call-block.js +3 -0
  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 +129 -32
  95. package/dist/index.js +5503 -18
  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 +58 -31
  175. package/styles.css +152 -30
  176. package/dist/_components/app.d.ts +0 -23
  177. package/dist/_components/app.js +0 -41
  178. package/dist/_components/button.d.ts +0 -50
  179. package/dist/_components/button.js +0 -46
  180. package/dist/_components/chat-view.d.ts +0 -29
  181. package/dist/_components/chat-view.js +0 -61
  182. package/dist/_components/controls.d.ts +0 -18
  183. package/dist/_components/controls.js +0 -37
  184. package/dist/_components/error-banner.d.ts +0 -21
  185. package/dist/_components/error-banner.js +0 -27
  186. package/dist/_components/message-bubble.d.ts +0 -23
  187. package/dist/_components/message-bubble.js +0 -35
  188. package/dist/_components/message-list.d.ts +0 -21
  189. package/dist/_components/message-list.js +0 -76
  190. package/dist/_components/sidebar-layout.d.ts +0 -21
  191. package/dist/_components/sidebar-layout.js +0 -36
  192. package/dist/_components/start-screen.d.ts +0 -26
  193. package/dist/_components/start-screen.js +0 -51
  194. package/dist/_components/state-indicator.d.ts +0 -29
  195. package/dist/_components/state-indicator.js +0 -37
  196. package/dist/_components/thinking-indicator.d.ts +0 -16
  197. package/dist/_components/thinking-indicator.js +0 -34
  198. package/dist/_components/tool-call-block.d.ts +0 -27
  199. package/dist/_components/tool-call-block.js +0 -118
  200. package/dist/_components/tool-icons.d.ts +0 -17
  201. package/dist/_components/tool-icons.js +0 -128
  202. package/dist/_components/transcript.d.ts +0 -25
  203. package/dist/_components/transcript.js +0 -35
  204. package/dist/audio.d.ts +0 -45
  205. package/dist/audio.js +0 -121
  206. package/dist/client-context.d.ts +0 -33
  207. package/dist/client-context.js +0 -15
  208. package/dist/client-handler.d.ts +0 -43
  209. package/dist/session-CWB7vmqz.js +0 -429
  210. package/dist/session.d.ts +0 -69
  211. package/dist/session.js +0 -2
  212. package/dist/signals.d.ts +0 -120
  213. package/dist/signals.js +0 -161
  214. package/dist/tsdown.config.d.ts +0 -2
  215. package/dist/types.test-d.d.ts +0 -7
@@ -0,0 +1,87 @@
1
+ /**
2
+ * `useClientRuns` — the page half of `clientRunsRoutes()` on
3
+ * `@alexkroman1/aai`: the runs going on for this client, polled, with a
4
+ * cancel.
5
+ *
6
+ * `useRoute` over the list route plus `useRouteMutation` over the cancel one,
7
+ * so it inherits both halves' rules — the stale-read drop, data kept through a
8
+ * failed poll, the session's client as `?client=`, no state after unmount —
9
+ * and adds only what every "Running" panel re-wrote: the row type, the cancel
10
+ * URL (`DELETE <path>/<runId>`, the id encoded) and the re-read after it.
11
+ *
12
+ * @module
13
+ */
14
+ import type { ClientRun } from "@alexkroman1/aai";
15
+ /**
16
+ * Options for {@link useClientRuns}.
17
+ *
18
+ * @public
19
+ */
20
+ export type UseClientRunsOptions = {
21
+ /** Read the list again every this many ms, while mounted. Default 5000; `0` never. */
22
+ pollMs?: number | undefined;
23
+ /**
24
+ * The `?client=` to send. Default: the session's client inside
25
+ * `mountClient()`, none on a page with no session.
26
+ */
27
+ client?: string | undefined;
28
+ };
29
+ /**
30
+ * What {@link useClientRuns} returns.
31
+ *
32
+ * @public
33
+ */
34
+ export type UseClientRunsResult = {
35
+ /** The client's runs, oldest first — `undefined` until the first read lands. */
36
+ runs: ClientRun[] | undefined;
37
+ /** Why the last read or cancel failed — the route's `{ error }` when it said — or `undefined`. */
38
+ error: string | undefined;
39
+ /** Read the list again now. */
40
+ reload: () => void;
41
+ /**
42
+ * Cancel one run, then re-read the list. Resolves `true` when this call
43
+ * ended it, `false` when it had already finished or the cancel failed (the
44
+ * reason is in `error`). Never rejects.
45
+ */
46
+ cancel: (runId: string) => Promise<boolean>;
47
+ /** The run id whose cancel is in flight, or `undefined` — to disable its button. */
48
+ cancelling: string | undefined;
49
+ };
50
+ /**
51
+ * The runs going on for this client, from a `clientRunsRoutes()` pair — see
52
+ * this module's doc.
53
+ *
54
+ * @example A "Running" panel with a Cancel on each live reminder
55
+ * ```tsx
56
+ * import { useClientRuns } from "@alexkroman1/aai-ui";
57
+ *
58
+ * function Running() {
59
+ * const { runs, error, cancel, cancelling } = useClientRuns();
60
+ * if (!runs) return error ? <p role="alert">{error}</p> : null;
61
+ * return (
62
+ * <ul>
63
+ * {runs.map((r) => (
64
+ * <li key={r.runId}>
65
+ * {r.title} — {r.status}
66
+ * {r.detail && <small>{r.detail}</small>}
67
+ * {r.status === "running" && (
68
+ * <button type="button" disabled={cancelling === r.runId} onClick={() => void cancel(r.runId)}>
69
+ * Cancel
70
+ * </button>
71
+ * )}
72
+ * </li>
73
+ * ))}
74
+ * </ul>
75
+ * );
76
+ * }
77
+ * ```
78
+ *
79
+ * @param path - The `path` the routes were declared with, without `/api`.
80
+ * Default `"/tasks"`, `clientRunsRoutes()`' own default.
81
+ * @param options - `pollMs` and `client`; see {@link UseClientRunsOptions}.
82
+ * @returns The rows, the last error, `reload`, `cancel` and the id being
83
+ * cancelled; see {@link UseClientRunsResult}.
84
+ *
85
+ * @public
86
+ */
87
+ export declare function useClientRuns(path?: string, options?: UseClientRunsOptions): UseClientRunsResult;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * `useConversationLog` — everything this page has said and heard, across
3
+ * sessions, kept in `localStorage`.
4
+ *
5
+ * `useConversation()` is the CURRENT session; a page beside a device holds a
6
+ * new resumable session every few turns, and wants the whole history on screen
7
+ * after a reload. This hook writes the live conversation into a persisted log
8
+ * as it happens — handling the replay a resumed session brings back, so no turn
9
+ * is logged twice (the rules are `conversation-log.ts`'s module doc) — plus the
10
+ * page's own notes, what the device said on its own, and, through `mirror`, the
11
+ * live conversation of the client's OTHER sessions (a linked speaker's).
12
+ *
13
+ * **It opens no inbox socket.** The inbox keeps one socket per (client, holder)
14
+ * and a second from the same tab REPLACES the first, so `mirror` is a handler
15
+ * to hand the page's one `useInbox({ onEvent })`.
16
+ *
17
+ * `<ConversationView log={entries}>` renders it with the kit's own rows.
18
+ *
19
+ * @module
20
+ */
21
+ import { type ConversationLogEntry } from "./conversation-log.ts";
22
+ import type { InboxEvent } from "./inbox-protocol.ts";
23
+ /**
24
+ * Options for {@link useConversationLog}.
25
+ *
26
+ * @public
27
+ */
28
+ export type UseConversationLogOptions = {
29
+ /**
30
+ * The `localStorage` key, read once on mount. Default: one per agent URL, so
31
+ * two agents on one origin keep separate logs.
32
+ */
33
+ storageKey?: string | undefined;
34
+ /** Entries kept; the oldest go first. Default 300 — `localStorage` holds a few MB per origin. */
35
+ max?: number | undefined;
36
+ };
37
+ /**
38
+ * What {@link useConversationLog} returns.
39
+ *
40
+ * @public
41
+ */
42
+ export type UseConversationLogResult = {
43
+ /** The log, oldest first; the live session is its newest session entry. */
44
+ readonly entries: readonly ConversationLogEntry[];
45
+ /** Log a line of the page's own: "New session", "Continuing an earlier conversation". */
46
+ addNote(text: string): void;
47
+ /** Log what the agent said outside a session — a reminder read aloud — as an assistant turn. */
48
+ addSpoken(text: string): void;
49
+ /**
50
+ * Mirror a frame of the client's live conversation into the log — pass it as
51
+ * `useInbox({ onEvent: log.mirror })`. The page's own session is skipped (it
52
+ * logs itself); every other session of the client is logged as it happens.
53
+ */
54
+ mirror(event: InboxEvent): void;
55
+ /** Forget the whole log, stored copy included. */
56
+ clear(): void;
57
+ };
58
+ /**
59
+ * A transcript that outlives the session — see this module's doc.
60
+ *
61
+ * Must be used inside the provider `mountClient()` installs; call it once per
62
+ * page, since two logs over one key would overwrite each other.
63
+ *
64
+ * @example The whole history, with a note per new session and a linked speaker mirrored
65
+ * ```tsx
66
+ * import { ConversationView, useConversationLog, useInbox } from "@alexkroman1/aai-ui";
67
+ *
68
+ * function History() {
69
+ * const log = useConversationLog();
70
+ * useInbox({ onEvent: log.mirror, onNotice: (n) => log.addNote(n.event) });
71
+ * return (
72
+ * <ConversationView
73
+ * log={log.entries}
74
+ * className="flex-1 min-h-0"
75
+ * renderMessage={(m) => <p data-role={m.role}>{m.content}</p>}
76
+ * />
77
+ * );
78
+ * }
79
+ * ```
80
+ *
81
+ * @param options - Where it is stored and how much; see {@link UseConversationLogOptions}.
82
+ * @returns The entries and the ways to add to them; see {@link UseConversationLogResult}.
83
+ *
84
+ * @public
85
+ */
86
+ export declare function useConversationLog(options?: UseConversationLogOptions): UseConversationLogResult;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * `useConversation` — the exchange, already assembled, with nothing rendered.
3
+ *
4
+ * `<MessageList>` owns four decisions that are not obvious and are not
5
+ * derivable from the snapshot by eye: the message/tool-call INTERLEAVE, the
6
+ * streaming agent utterance, the live user transcript with its `null`-vs-`""`
7
+ * protocol distinction, and the thinking indicator's suppression rule. Its only
8
+ * prop is `className`, so the moment a client wants its own bubble markup it
9
+ * drops all four at once — and the three hand-rolled chromes in the template
10
+ * tree each shipped a strictly worse conversation for exactly that reason. One
11
+ * of them runs fifteen tools and its operator sees none of them, because the
12
+ * tool calls live in a second array nothing in that page ever reads.
13
+ *
14
+ * This is the headless half. `<MessageList>` is now a thin consumer of it —
15
+ * which is the only way to know the hook is complete, since a hook that the
16
+ * package's own list cannot be built from is a hook a custom chrome will find a
17
+ * hole in.
18
+ *
19
+ * ## It subscribes NARROWLY, and that is half the value
20
+ *
21
+ * The three custom chromes also call whole-page `useSession()`, which re-renders
22
+ * on *every* snapshot change — so a dispatch board re-renders at STT-partial
23
+ * rate. `<ChatView>` deliberately avoids that with per-field
24
+ * {@link useSessionSelector} calls, and so does this: a component that reads the
25
+ * conversation gets the conversation's own update rate, not the session's.
26
+ */
27
+ import type { ChatMessage, ToolCallInfo } from "./types.ts";
28
+ import { type UseUserTranscriptResult } from "./use-user-transcript.ts";
29
+ /**
30
+ * One row of the conversation: a finalized message, or a tool invocation.
31
+ *
32
+ * A discriminated union rather than two arrays, because the ORDER between them
33
+ * is the thing this hook computes — handing back two lists would hand back the
34
+ * problem. `kind` is what a `switch` in a custom renderer narrows on.
35
+ *
36
+ * @public
37
+ */
38
+ export type ConversationItem = {
39
+ kind: "message";
40
+ message: ChatMessage;
41
+ } | {
42
+ kind: "tool";
43
+ toolCall: ToolCallInfo;
44
+ };
45
+ /**
46
+ * What {@link useConversation} returns.
47
+ *
48
+ * @public
49
+ */
50
+ export type UseConversationResult = {
51
+ /**
52
+ * Messages and tool calls in one list, in the order they happened.
53
+ *
54
+ * Referentially stable while neither array changes, so a consumer may map it
55
+ * inside a `useMemo` keyed on it, or hand rows to `memo()`ed components,
56
+ * without rebuilding the list on unrelated snapshot updates.
57
+ */
58
+ items: readonly ConversationItem[];
59
+ /**
60
+ * The agent's utterance as it arrives, or `null` between turns.
61
+ *
62
+ * Not yet a member of `items`: it has no id and it is replaced wholesale on
63
+ * every delta, so it is rendered as its own trailing row and disappears when
64
+ * the finalized message takes its place.
65
+ */
66
+ streaming: string | null;
67
+ /**
68
+ * The caller's in-progress turn — {@link useUserTranscript}'s result,
69
+ * forwarded rather than re-derived, so the `null`-vs-`""` distinction is made
70
+ * in exactly one place.
71
+ */
72
+ transcript: UseUserTranscriptResult;
73
+ /**
74
+ * Whether to show a thinking indicator.
75
+ *
76
+ * The suppression rule, and it is why this is a field rather than
77
+ * `state === "thinking"`: the agent is `thinking` for a stretch during which
78
+ * something ELSE is already saying so. A pending tool call draws its own
79
+ * spinner, and a trailing agent message means the reply has begun landing —
80
+ * in both cases a second indicator underneath reads as a second thing
81
+ * happening. So it is on only while `thinking` with no pending tool call, and
82
+ * either no messages yet, a trailing USER message, or a settled tool call
83
+ * after it.
84
+ */
85
+ thinking: boolean;
86
+ };
87
+ /**
88
+ * Interleave messages and tool calls, ordered by insertion time.
89
+ *
90
+ * Each tool call belongs immediately after its anchor message
91
+ * (`afterMessageId`); tool calls whose anchor slid out of the retained window —
92
+ * or that were inserted before any message existed — come first, since there is
93
+ * nothing left for them to follow.
94
+ */
95
+ export declare function interleave(messages: readonly ChatMessage[], toolCalls: readonly ToolCallInfo[]): ConversationItem[];
96
+ /**
97
+ * Subscribe to the conversation: the interleaved exchange, the streaming
98
+ * utterance, the live transcript and the thinking rule — with no markup.
99
+ *
100
+ * Must be used inside the provider `mountClient()` installs.
101
+ *
102
+ * @example A custom bubble, keeping every rule `<MessageList>` knows
103
+ * ```tsx
104
+ * import { useConversation } from "@alexkroman1/aai-ui";
105
+ *
106
+ * function Transcript() {
107
+ * const { items, streaming, transcript, thinking } = useConversation();
108
+ * return (
109
+ * <div>
110
+ * {items.map((item) =>
111
+ * item.kind === "message" ? (
112
+ * <p key={item.message.id} data-role={item.message.role}>
113
+ * {item.message.content}
114
+ * </p>
115
+ * ) : (
116
+ * <code key={item.toolCall.callId}>{item.toolCall.name}</code>
117
+ * ),
118
+ * )}
119
+ * {streaming !== null && <p data-role="assistant">{streaming}</p>}
120
+ * {transcript.speaking && <p data-role="user">{transcript.text}</p>}
121
+ * {thinking && <p>…</p>}
122
+ * </div>
123
+ * );
124
+ * }
125
+ * ```
126
+ *
127
+ * @returns See {@link UseConversationResult}.
128
+ *
129
+ * @public
130
+ */
131
+ export declare function useConversation(): UseConversationResult;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Copy-to-clipboard with a per-item flash on the button that triggered it.
3
+ *
4
+ * Anywhere a UI hands the reader a string to paste somewhere else — a session
5
+ * URL, a webhook, a `curl` line — the button wants the same three things, and
6
+ * two of them are easy to get wrong:
7
+ *
8
+ * - The flash is keyed by the copied TEXT, so one row's "Copied" does not light
9
+ * up every button beside it.
10
+ * - A FAILED write is reported rather than swallowed. There is no clipboard in
11
+ * an insecure context, and a browser may refuse the permission; a `catch`
12
+ * that does nothing leaves a button that visibly does nothing when clicked,
13
+ * which reads as a broken page rather than as a blocked capability.
14
+ * - The single-timer / clear-on-unmount half is {@link useFlash}'s.
15
+ */
16
+ /**
17
+ * What {@link useCopy} hands back — the click handler and the two readings a
18
+ * button needs off one shared flash.
19
+ *
20
+ * @public
21
+ */
22
+ export type UseCopyResult = {
23
+ /** Copy `text`, flashing the button that owns it. */
24
+ copy: (text: string) => void;
25
+ /**
26
+ * The button label for `text` — `idle` until it is clicked, then `"Copied"`
27
+ * or `"Failed"` for the length of the flash.
28
+ *
29
+ * Only the idle word is the caller's: it is the button's NAME (`"Copy"`,
30
+ * `"UI"`, `"Webhook URL"`), where the other two are the OUTCOME and are the
31
+ * one bit of state a reader can see. A caller wanting other words for those
32
+ * reads {@link UseCopyResult.didCopy} and writes its own.
33
+ */
34
+ label: (text: string, idle?: string) => string;
35
+ /** True when `text` was the last thing copied, successfully. */
36
+ didCopy: (text: string) => boolean;
37
+ };
38
+ /**
39
+ * One copier for a group of copy buttons.
40
+ *
41
+ * @remarks
42
+ * Call it ONCE per group and pass the {@link UseCopyResult} down, rather than once per
43
+ * button: the flash is shared, so clicking a second row clears the first row's
44
+ * "Copied" — which is what makes a list of URLs readable, since two rows
45
+ * claiming to be on the clipboard is a lie about one of them.
46
+ *
47
+ * A chip whose idle text is its own name rather than the word "Copy" passes
48
+ * that name to {@link UseCopyResult.label}; the two outcome words are fixed, for the
49
+ * reason that member's own doc gives.
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * import { useCopy } from "@alexkroman1/aai-ui";
54
+ *
55
+ * function UrlList({ urls }: { urls: readonly string[] }) {
56
+ * const copier = useCopy();
57
+ * return (
58
+ * <ul>
59
+ * {urls.map((url) => (
60
+ * <li key={url}>
61
+ * <code>{url}</code>
62
+ * <button type="button" onClick={() => copier.copy(url)}>
63
+ * {copier.label(url)}
64
+ * </button>
65
+ * </li>
66
+ * ))}
67
+ * </ul>
68
+ * );
69
+ * }
70
+ * ```
71
+ *
72
+ * @public
73
+ */
74
+ export declare function useCopy(): UseCopyResult;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * `useDownloadUrl` — an upload id a run produced, as something `<audio>`,
3
+ * `<img>` or `<a download>` will accept.
4
+ *
5
+ * `api.download(id)` resolves a `Blob`, and it has to: the byte route takes the
6
+ * same bearer every workflow route does, and neither `<audio src>` nor
7
+ * `<a href>` can send one. So every page that plays back what a run WROTE ends
8
+ * up at the same four lines — `download` → `createObjectURL` → state — and the
9
+ * two that are really the point are the two the four lines are wrapped in:
10
+ *
11
+ * - **`URL.revokeObjectURL` on cleanup.** An object URL pins its blob for the
12
+ * life of the DOCUMENT. Miss it and every completed run's audio stays resident
13
+ * until the tab closes, which on a page people run all day is a leak measured
14
+ * in the size of the files.
15
+ * - **A `cancelled` flag.** A second run settling while the first download is
16
+ * still in flight otherwise sets state from the stale one, and the page plays
17
+ * the previous run's audio under the current run's transcript — a wrong answer
18
+ * that looks like a right one.
19
+ *
20
+ * Two templates had written this hook, identically, doc paragraph included, and
21
+ * `aai-ui` exported no download helper at all. Both also faked `pending` by
22
+ * checking `url === undefined && error === undefined`, which reads "idle" and
23
+ * "downloading" as the same thing — so this reports it.
24
+ */
25
+ import type { WorkflowApi } from "./workflow-client.ts";
26
+ /** Options for {@link useDownloadUrl}. */
27
+ export type UseDownloadUrlOptions = {
28
+ /** The client to read the bytes with. Defaults to one for the page's own agent. */
29
+ api?: WorkflowApi;
30
+ };
31
+ /**
32
+ * What {@link useDownloadUrl} reports.
33
+ *
34
+ * @public
35
+ */
36
+ export type UseDownloadUrlResult = {
37
+ /**
38
+ * An object URL for the stored bytes, once they are here. Valid until the id
39
+ * changes or the component unmounts — do not stash it anywhere that outlives
40
+ * the render that read it.
41
+ */
42
+ url?: string;
43
+ /** The read's failure, as the agent's own sentence where it gave one. */
44
+ error?: string;
45
+ /**
46
+ * True while the bytes are on their way.
47
+ *
48
+ * Its own field rather than "neither `url` nor `error`", which cannot tell a
49
+ * download in flight from no id to download — the two states a page most
50
+ * wants to render differently (a spinner, and nothing at all).
51
+ */
52
+ pending: boolean;
53
+ };
54
+ /**
55
+ * Read an upload's bytes and hand back a URL a DOM element can use.
56
+ *
57
+ * @param uploadId - The id a completed run reported, or `undefined` before one
58
+ * exists — which is what a page passes straight through while it waits, and
59
+ * reports as idle rather than pending.
60
+ * @param options - See {@link UseDownloadUrlOptions}.
61
+ * @returns See {@link UseDownloadUrlResult}.
62
+ *
63
+ * @example
64
+ * ```tsx no-check
65
+ * import { useDownloadUrl, useWorkflowSubmit } from "@alexkroman1/aai-ui";
66
+ * import type { spokenSummary } from "./agent.ts";
67
+ *
68
+ * function Playback() {
69
+ * const { run } = useWorkflowSubmit<typeof spokenSummary>("spokenSummary");
70
+ * const output = run?.status === "completed" ? run.output : undefined;
71
+ * const audio = useDownloadUrl(output?.audio);
72
+ * if (audio.pending) return <p>Fetching audio…</p>;
73
+ * if (audio.error !== undefined) return <p role="alert">{audio.error}</p>;
74
+ * return audio.url === undefined ? null : (
75
+ * <a href={audio.url} download="summary.mp3">
76
+ * Download
77
+ * </a>
78
+ * );
79
+ * }
80
+ * ```
81
+ *
82
+ * @public
83
+ */
84
+ export declare function useDownloadUrl(uploadId: string | undefined, options?: UseDownloadUrlOptions): UseDownloadUrlResult;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A value that shows itself for a moment and then goes away.
3
+ *
4
+ * The "Copied" on a copy button, the "Saved" under an editor, the "Sent" on a
5
+ * submit. There were three hand-rolled copies of this — two in the studio
6
+ * front-end and one inside this package's own URL chips — each with the same
7
+ * 1500ms and the same comment explaining the same two things the naive version
8
+ * gets wrong. It is one primitive now, and it is PUBLIC because a custom chrome
9
+ * is exactly the caller that needs it: the moment a client renders its own copy
10
+ * button or its own save note, it is writing the timer again.
11
+ */
12
+ /**
13
+ * What {@link useFlash} hands back.
14
+ *
15
+ * @typeParam T - What is being flashed. A `string` for a label; a record for a
16
+ * flash that has to say WHICH thing it belongs to, as `useCopy` does.
17
+ *
18
+ * @public
19
+ */
20
+ export type UseFlashResult<T> = {
21
+ /** What is being shown right now, or `null` between flashes. */
22
+ readonly value: T | null;
23
+ /** Show `value` for the hook's duration, replacing any flash already up. */
24
+ readonly flash: (value: T) => void;
25
+ };
26
+ /**
27
+ * A transient value: set it, and it clears itself after `ms`.
28
+ *
29
+ * @remarks
30
+ * The two things this holds that a `useState` plus a `setTimeout` at the call
31
+ * site reliably gets wrong:
32
+ *
33
+ * - **At most ONE live timer.** A second flash while the first is still up
34
+ * re-arms rather than stacking, so the new value gets its full window
35
+ * instead of being cleared early by the previous click's timeout.
36
+ * - **Nothing fires after unmount.** The timer is cleared on teardown, so a
37
+ * chip clicked and then scrolled out of the tree does not `setState` on a
38
+ * component React has already thrown away.
39
+ *
40
+ * @example
41
+ * ```tsx
42
+ * import { useFlash } from "@alexkroman1/aai-ui";
43
+ *
44
+ * function SaveNote({ onSave }: { onSave: () => Promise<void> }) {
45
+ * const { value: note, flash } = useFlash<string>();
46
+ * return (
47
+ * <button type="button" onClick={() => void onSave().then(() => flash("Saved"))}>
48
+ * {note ?? "Save"}
49
+ * </button>
50
+ * );
51
+ * }
52
+ * ```
53
+ *
54
+ * @typeParam T - What is being flashed.
55
+ * @param ms - How long the value stays up. Defaults to 1500ms, which is what
56
+ * every caller wanted: long enough to read a word, short enough that the
57
+ * control is back to its real label before the reader looks again.
58
+ *
59
+ * @public
60
+ */
61
+ export declare function useFlash<T>(ms?: number): UseFlashResult<T>;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * `useInbox()` — the session's client, reachable after the session has ended.
3
+ *
4
+ * A tool that starts a durable run (a reminder, a research job) hands the run
5
+ * `sessionClientId(ctx)`, and the run's `stepNotifyClient` delivers to whoever
6
+ * holds that client's `WS /inbox` socket when it is done — usually long after
7
+ * the voice session closed. This hook holds that socket for the page, under the
8
+ * SESSION's own client id (`mountClient({ client: "auto" })` is the one-word
9
+ * way to have one) and this tab's holder id, and plays each notice aloud.
10
+ *
11
+ * Three decisions a hand-written copy got wrong or had to rediscover:
12
+ *
13
+ * - **Busy by default while the session is running**, so a reminder never talks
14
+ * over a reply: the page answers `busy`, the step's retry brings the notice
15
+ * back, and it plays once the call is over. Override with `busy`.
16
+ * - **Callbacks are read at call time**, not captured at mount: a new `onNotice`
17
+ * every render does not reconnect the socket. Only `events` does, because it
18
+ * is part of the socket's URL.
19
+ * - **Playback is unlocked by the page's first gesture** (`notice-player.ts`):
20
+ * the autoplay policy silences a context created without one, and a notice
21
+ * arrives hours after any click.
22
+ * - **One tab plays: the one last touched.** Every tab is a holder of its own,
23
+ * and the inbox sends a notice to each, so nine open tabs played it nine
24
+ * times a few milliseconds apart — heard as one choppy, phasing voice. A
25
+ * gesture claims the client's player in `localStorage`, which every tab of
26
+ * the origin shares synchronously; the others still ack and still call
27
+ * `onNotice`, they just stay quiet. A claimant that closes gives the claim
28
+ * back, and with no claim every unlocked tab plays, as before.
29
+ *
30
+ * @module
31
+ */
32
+ import type { InboxEvent, InboxNotice } from "./inbox-protocol.ts";
33
+ /**
34
+ * Options for {@link useInbox}.
35
+ *
36
+ * @public
37
+ */
38
+ export type UseInboxOptions = {
39
+ /**
40
+ * A notice arrived whole — once per delivery, after playback has started
41
+ * (when `play` is on). Log it, show it, or play it yourself with `play: false`.
42
+ */
43
+ onNotice?: ((notice: InboxNotice) => void) | undefined;
44
+ /**
45
+ * A frame of the client's live conversation — every session of this client,
46
+ * this page's own included (compare `sessionId` with `useSessionId()` to skip
47
+ * it). Giving one asks for the feed, unless `events` says otherwise.
48
+ */
49
+ onEvent?: ((event: InboxEvent) => void) | undefined;
50
+ /**
51
+ * Whether to refuse a notice for now (`busy`; it comes back later). Default:
52
+ * the session is running — connecting, listening, thinking or speaking.
53
+ */
54
+ busy?: (() => boolean) | undefined;
55
+ /**
56
+ * Play each notice's audio (PCM16LE mono, 16 kHz) through built-in playback.
57
+ * Default `true`.
58
+ */
59
+ play?: boolean | undefined;
60
+ /** Ask for the client's live conversation (`?events=1`). Default: whether `onEvent` is set. */
61
+ events?: boolean | undefined;
62
+ };
63
+ /**
64
+ * What {@link useInbox} returns.
65
+ *
66
+ * @public
67
+ */
68
+ export type UseInboxResult = {
69
+ /** Whether the inbox socket is open now. */
70
+ readonly connected: boolean;
71
+ /** Silence a notice that is playing — e.g. when the user starts talking. */
72
+ stopPlayback(): void;
73
+ };
74
+ /**
75
+ * Hold this session's `WS /inbox` socket open for the life of the component,
76
+ * and play what arrives — see the module doc.
77
+ *
78
+ * The session needs a client id: `mountClient({ client: "auto" })`, or a
79
+ * `client` of your own. With none, no socket is opened (`connected` stays
80
+ * `false`) until the session's `client` getter answers one.
81
+ *
82
+ * @example
83
+ * ```tsx
84
+ * import { mountClient, useInbox } from "@alexkroman1/aai-ui";
85
+ *
86
+ * function App() {
87
+ * const { connected } = useInbox({
88
+ * onNotice: (notice) => console.log(notice.event, notice.data),
89
+ * });
90
+ * return <p>{connected ? "Reminders on" : "Reminders offline"}</p>;
91
+ * }
92
+ *
93
+ * mountClient({ client: "auto", component: App });
94
+ * ```
95
+ *
96
+ * @param options - What to do with a notice or a live event; see {@link UseInboxOptions}.
97
+ * @returns Whether the socket is open, and a way to stop playback.
98
+ *
99
+ * @public
100
+ */
101
+ export declare function useInbox(options?: UseInboxOptions): UseInboxResult;