@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,138 @@
1
+ /**
2
+ * `useTapToTalk` — one tap goes live, the next hangs up: the TOGGLE counterpart
3
+ * of `usePushToTalk`, for an agent with automatic turn detection (the default).
4
+ * It is how a smart speaker's button works, and a page that is the speaker's
5
+ * twin wants the same thing.
6
+ *
7
+ * Tapping is the easy half. What each hand-written copy had to work out:
8
+ *
9
+ * - **Hanging up is `disconnect()`, never `end()`**, so the next tap RESUMES
10
+ * the session — the agent still knows what was said a minute ago.
11
+ * - **The mic is muted unless live.** A typed turn opens the session too, and
12
+ * must not open the microphone with it; the mute is set before the connect,
13
+ * so the call opens already in the right state.
14
+ * - **A typed turn while idle opens the session and waits for it**
15
+ * (`session.sendText(text, { connect: true })`) instead of being dropped.
16
+ * - **A call that is not live hangs itself up** after `idleHangupMs` of quiet
17
+ * (the device's follow-up window) or `thinkingHangupMs` of thinking — every
18
+ * session activity restarts the clock, and a speaking agent is never cut
19
+ * off. A LIVE call ends only when the caller taps.
20
+ * - **A connection that never comes is a failure**, after `connectTimeoutMs`.
21
+ * - **The session going down on its own** (an error, the server closing it)
22
+ * leaves live mode — otherwise the next tap would read as "hang up".
23
+ * - The talk key's auto-repeat is not a tap, it is ignored in a text field, and
24
+ * the button's own Space activation is suppressed so it does not count twice.
25
+ *
26
+ * The decisions are a statechart (`_tap-to-talk-state.ts`); this hook feeds it
27
+ * the session and runs its effects.
28
+ *
29
+ * @module
30
+ */
31
+ /**
32
+ * Options for {@link useTapToTalk}.
33
+ *
34
+ * @public
35
+ */
36
+ export type UseTapToTalkOptions = {
37
+ /**
38
+ * The keyboard key that taps, as a `KeyboardEvent.code` — `"Space"` by
39
+ * default. `false` turns the page-wide key off; the button itself still
40
+ * answers a click, Space and Enter.
41
+ */
42
+ key?: string | false | undefined;
43
+ /**
44
+ * Hang up a call that is NOT live after this long with nothing happening —
45
+ * the device's follow-up window. Default 3000 ms.
46
+ */
47
+ idleHangupMs?: number | undefined;
48
+ /**
49
+ * Hang up a call that is not live when the agent has been thinking this
50
+ * long. Default 60,000 ms — long enough for a slow tool.
51
+ */
52
+ thinkingHangupMs?: number | undefined;
53
+ /** Give up on a connection attempt after this long, and report `failed`. Default 8000 ms. */
54
+ connectTimeoutMs?: number | undefined;
55
+ };
56
+ /**
57
+ * What {@link useTapToTalk} returns.
58
+ *
59
+ * @public
60
+ */
61
+ export type UseTapToTalkResult = {
62
+ /**
63
+ * The call: `"idle"` (not meant to be running), `"connecting"`, or
64
+ * `"active"` — up, whatever the agent is doing.
65
+ */
66
+ phase: "idle" | "connecting" | "active";
67
+ /** Whether the caller is live — mic open, in a realtime conversation. The button is ON. */
68
+ live: boolean;
69
+ /**
70
+ * Whether the last attempt failed: the connection timed out or the session
71
+ * reported an error. Cleared by the next tap or typed turn.
72
+ */
73
+ failed: boolean;
74
+ /** Tap: go live (connecting first if idle), or hang up if live. */
75
+ toggle: () => void;
76
+ /**
77
+ * A typed turn. Sent at once while the call is up; while idle it opens the
78
+ * session WITHOUT the mic and is sent once connected. Blank text is ignored.
79
+ */
80
+ send: (text: string) => void;
81
+ /** Hang up now — resumably (`disconnect()`), live or not. */
82
+ hangUp: () => void;
83
+ /**
84
+ * Spread onto a `<button>`: the click, the keyboard guard against a double
85
+ * tap, and `aria-pressed`. Style it however you like.
86
+ */
87
+ buttonProps: {
88
+ onClick: () => void;
89
+ onKeyDown: (event: {
90
+ code: string;
91
+ preventDefault(): void;
92
+ }) => void;
93
+ onKeyUp: (event: {
94
+ code: string;
95
+ preventDefault(): void;
96
+ }) => void;
97
+ "aria-pressed": boolean;
98
+ };
99
+ };
100
+ /**
101
+ * Tap to go live, tap to hang up, over the session — see this module's doc
102
+ * for what it handles beyond the tap.
103
+ *
104
+ * Must be used inside the provider `mountClient()` installs. Call it ONCE per
105
+ * page: it owns the session's mute and hang-up clock.
106
+ *
107
+ * @example A talk button and a composer
108
+ * ```tsx
109
+ * import { useTapToTalk } from "@alexkroman1/aai-ui";
110
+ *
111
+ * function Speaker() {
112
+ * const { live, phase, send, buttonProps } = useTapToTalk();
113
+ * return (
114
+ * <>
115
+ * <button type="button" {...buttonProps}>
116
+ * {phase === "connecting" ? "Connecting…" : live ? "Tap to hang up" : "Tap to talk"}
117
+ * </button>
118
+ * <form
119
+ * onSubmit={(e) => {
120
+ * e.preventDefault();
121
+ * const input = e.currentTarget.elements.namedItem("say") as HTMLInputElement;
122
+ * send(input.value);
123
+ * input.value = "";
124
+ * }}
125
+ * >
126
+ * <input name="say" placeholder="…or type" />
127
+ * </form>
128
+ * </>
129
+ * );
130
+ * }
131
+ * ```
132
+ *
133
+ * @param options - The talk key and the three clocks; see {@link UseTapToTalkOptions}.
134
+ * @returns The phase, the live flag and the controls; see {@link UseTapToTalkResult}.
135
+ *
136
+ * @public
137
+ */
138
+ export declare function useTapToTalk(options?: UseTapToTalkOptions): UseTapToTalkResult;
@@ -0,0 +1,73 @@
1
+ import { useSessionSelector } from "./context.js";
2
+ import { useMemo } from "react";
3
+ //#region src/use-user-transcript.ts
4
+ /**
5
+ * `useUserTranscript` — what the caller is saying RIGHT NOW, read correctly.
6
+ *
7
+ * `SessionSnapshot.userTranscript` is `string | null`, and the two falsy values
8
+ * mean different things:
9
+ *
10
+ * - `null` — nobody is speaking. There is no partial turn.
11
+ * - `""` — speech HAS been detected and no words have come back yet. A live
12
+ * session sits here for a few hundred milliseconds at the start of every turn.
13
+ *
14
+ * Read as one falsy check, those collapse and the indicator never appears at the
15
+ * start of a turn — which is the moment it is for. So every custom chrome writes
16
+ * `transcript !== null && (transcript === "" ? "…" : transcript)`, three
17
+ * templates did exactly that, and each one re-derived a protocol distinction
18
+ * from the type rather than from anything that told them.
19
+ *
20
+ * This is the same distinction as two named booleans, so a component can say
21
+ * what it means: render on `speaking`, show `text`, and use the SDK's own
22
+ * placeholder when there is nothing to show yet.
23
+ */
24
+ /**
25
+ * Placeholder for "listening, no words yet" — the `""` case above.
26
+ *
27
+ * A one-character ellipsis rather than three dots, because it is read by a
28
+ * screen reader as an ellipsis and it does not reflow the row when the first
29
+ * real word replaces it.
30
+ *
31
+ * On `@alexkroman1/aai-ui/internal`, not the root: no public signature names
32
+ * it, and a `client.tsx` rendering its own transcript row compares against the
33
+ * character. `UseUserTranscriptResult.text` therefore spells the value out
34
+ * rather than linking here.
35
+ *
36
+ * @internal
37
+ */
38
+ const TRANSCRIBING_PLACEHOLDER = "…";
39
+ /**
40
+ * Subscribe to the caller's in-progress turn.
41
+ *
42
+ * Narrowly subscribed — a component using this re-renders at STT-partial rate,
43
+ * which is exactly what it is for and exactly what a whole-page `useSession()`
44
+ * should not do.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { useUserTranscript } from "@alexkroman1/aai-ui";
49
+ *
50
+ * function LiveTranscript() {
51
+ * const { speaking, text } = useUserTranscript();
52
+ * if (!speaking) return null;
53
+ * return <div className="italic opacity-60">{text}</div>;
54
+ * }
55
+ * ```
56
+ *
57
+ * @public
58
+ */
59
+ function useUserTranscript() {
60
+ const partial = useSessionSelector((snapshot) => snapshot.userTranscript);
61
+ return useMemo(() => ({
62
+ speaking: partial !== null,
63
+ text: displayText(partial),
64
+ partial
65
+ }), [partial]);
66
+ }
67
+ /** The three cases, spelled out: silent, detected-but-wordless, and words. */
68
+ function displayText(partial) {
69
+ if (partial === null) return "";
70
+ return partial === "" ? "…" : partial;
71
+ }
72
+ //#endregion
73
+ export { useUserTranscript as n, TRANSCRIBING_PLACEHOLDER as t };
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `useUserTranscript` — what the caller is saying RIGHT NOW, read correctly.
3
+ *
4
+ * `SessionSnapshot.userTranscript` is `string | null`, and the two falsy values
5
+ * mean different things:
6
+ *
7
+ * - `null` — nobody is speaking. There is no partial turn.
8
+ * - `""` — speech HAS been detected and no words have come back yet. A live
9
+ * session sits here for a few hundred milliseconds at the start of every turn.
10
+ *
11
+ * Read as one falsy check, those collapse and the indicator never appears at the
12
+ * start of a turn — which is the moment it is for. So every custom chrome writes
13
+ * `transcript !== null && (transcript === "" ? "…" : transcript)`, three
14
+ * templates did exactly that, and each one re-derived a protocol distinction
15
+ * from the type rather than from anything that told them.
16
+ *
17
+ * This is the same distinction as two named booleans, so a component can say
18
+ * what it means: render on `speaking`, show `text`, and use the SDK's own
19
+ * placeholder when there is nothing to show yet.
20
+ */
21
+ /**
22
+ * Placeholder for "listening, no words yet" — the `""` case above.
23
+ *
24
+ * A one-character ellipsis rather than three dots, because it is read by a
25
+ * screen reader as an ellipsis and it does not reflow the row when the first
26
+ * real word replaces it.
27
+ *
28
+ * On `@alexkroman1/aai-ui/internal`, not the root: no public signature names
29
+ * it, and a `client.tsx` rendering its own transcript row compares against the
30
+ * character. `UseUserTranscriptResult.text` therefore spells the value out
31
+ * rather than linking here.
32
+ *
33
+ * @internal
34
+ */
35
+ export declare const TRANSCRIBING_PLACEHOLDER = "\u2026";
36
+ /** What {@link useUserTranscript} returns. */
37
+ export interface UseUserTranscriptResult {
38
+ /**
39
+ * True while the caller holds the turn — from speech detection to the final
40
+ * transcript. This is the flag a live-transcript row renders on.
41
+ */
42
+ speaking: boolean;
43
+ /**
44
+ * The words so far, or a one-character ellipsis (`…`) while there are none.
45
+ * Empty string when nobody is speaking.
46
+ */
47
+ text: string;
48
+ /**
49
+ * The raw partial: the words so far, `""` while there are none, and `null`
50
+ * when nobody is speaking. For a chrome that wants to render its own
51
+ * placeholder (or none).
52
+ */
53
+ partial: string | null;
54
+ }
55
+ /**
56
+ * Subscribe to the caller's in-progress turn.
57
+ *
58
+ * Narrowly subscribed — a component using this re-renders at STT-partial rate,
59
+ * which is exactly what it is for and exactly what a whole-page `useSession()`
60
+ * should not do.
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * import { useUserTranscript } from "@alexkroman1/aai-ui";
65
+ *
66
+ * function LiveTranscript() {
67
+ * const { speaking, text } = useUserTranscript();
68
+ * if (!speaking) return null;
69
+ * return <div className="italic opacity-60">{text}</div>;
70
+ * }
71
+ * ```
72
+ *
73
+ * @public
74
+ */
75
+ export declare function useUserTranscript(): UseUserTranscriptResult;
@@ -0,0 +1,285 @@
1
+ /**
2
+ * The hook a FORM needs, as against the one a status view does.
3
+ *
4
+ * `useWorkflowRun` (`workflow-client.ts`) watches a run you already have.
5
+ * This is what comes before it: `useWorkflowSubmit` starts a run and hands the
6
+ * id straight to `useWorkflowRun`. Its sibling `useWorkflows` — the listing
7
+ * `<WorkflowFields>` renders a form from — is `use-workflows.ts`.
8
+ *
9
+ * ## `useWorkflowSubmit` — a form's two halves in one hook
10
+ *
11
+ * A page that submits a workflow always needs the same four pieces of state:
12
+ * the run id, whether a submit is in flight, whether the RUN is still going, and
13
+ * whichever of the two failed. `link-digest-workflow` writes them out by hand, which is
14
+ * the right shape for a template teaching the primitives and the wrong shape to
15
+ * write a third time — and it is easy to get subtly wrong: dropping the previous
16
+ * run id before the new `POST` returns is what stops a finished result sitting
17
+ * under a form that is already submitting again.
18
+ *
19
+ * So this is `api.start` plus {@link useWorkflowRun}, with the state between
20
+ * them. It adds no transport of its own and holds no run state of its own; the
21
+ * watching (stream first, poll as its fallback, terminal stops) is entirely
22
+ * `useWorkflowRun`'s, and `run` here IS its run.
23
+ *
24
+ * ## Why it starts ASYNCHRONOUSLY even though a synchronous call exists
25
+ *
26
+ * `api.startAndWait` would collapse this to one request, and it is the wrong
27
+ * default for a page: it holds a socket open for up to a minute, answers nothing
28
+ * until it settles, and a page has `useWorkflowRun` — which survives a reload,
29
+ * shows progress, and costs one stream. The synchronous call is for callers with
30
+ * nowhere to put a watch (a script, a cron, a form POST from a server). Pass
31
+ * `wait` here when the page really does want one request, and the run is
32
+ * followed from the same id either way.
33
+ */
34
+ import type { AnyWorkflowDef, UploadParallelOption, UploadProgress, WorkflowOutputOf } from "@alexkroman1/aai/workflow-api";
35
+ import type { FormValues } from "./components/form-types.ts";
36
+ import type { WorkflowApi, WorkflowRun } from "./workflow-client.ts";
37
+ import type { SubmitInputOf } from "./workflow-def-types.ts";
38
+ /**
39
+ * What {@link WorkflowSubmission.upload} reports while the bytes are going.
40
+ *
41
+ * The SDK's per-request {@link UploadProgress} plus WHICH file it describes,
42
+ * because a form is allowed more than one and a bar over "the upload" would
43
+ * restart at zero partway through with nothing to say why.
44
+ *
45
+ * @public
46
+ */
47
+ export type UploadStatus = UploadProgress & {
48
+ /** The file being sent, by the name the picker gave it. */
49
+ name: string;
50
+ /** Which file of the submission this is, counting from 1. */
51
+ index: number;
52
+ /** How many files this submission sends in total. */
53
+ count: number;
54
+ /**
55
+ * Whether the person has parked this upload.
56
+ *
57
+ * A paused upload is not a stopped one: the windows already stored stay stored,
58
+ * `loaded` holds where it got to, and resuming sends what is missing rather than
59
+ * the file. So a bar rendering this reads "Paused at 62%", never "62% and
60
+ * frozen" — which is what a page could otherwise only guess from a number that
61
+ * stopped moving, the same ambiguity `complete` exists to remove on the run side.
62
+ */
63
+ paused: boolean;
64
+ };
65
+ /**
66
+ * What {@link useWorkflowSubmit} returns.
67
+ *
68
+ * @see {@link WorkflowStreamSubmission} — an ALIAS of this type, returned by
69
+ * `useWorkflowStream`, which is a drop-in sibling. Exactly two fields MEAN
70
+ * something different there, and both differences follow from WHEN the run is
71
+ * created: there, `submit()` resolves when the UPLOAD finishes rather than when
72
+ * the run is accepted, and `run` is non-`undefined` from before the bytes are
73
+ * in, so a page can render `<WorkflowProgress>` beside the upload bar instead
74
+ * of after it. Here the run does not exist until the last byte lands.
75
+ */
76
+ export type WorkflowSubmission<R = unknown, I = unknown> = {
77
+ /**
78
+ * Start a run with this input. Resolves once the run EXISTS — progress
79
+ * arrives through `run` — so a `<Form>`'s handler can await it to know the
80
+ * submission was accepted.
81
+ */
82
+ submit: (input: I) => Promise<void>;
83
+ /**
84
+ * Start a run from a `<Form>`'s values, which are UNVALIDATED.
85
+ *
86
+ * The same function as {@link WorkflowSubmission.submit}, with the type the
87
+ * form path can honestly offer. `FormValues` is `Record<string, unknown>`
88
+ * scraped off the DOM at submit time — a `<TextField name="limit">`
89
+ * contributes a string whatever the schema says — so the shape is not known
90
+ * here and the SERVER is what checks it against the workflow's schema.
91
+ *
92
+ * Two doors rather than one loose one: `submit` takes the workflow's own
93
+ * input type, so a hand-built object is checked at compile time and
94
+ * `submit({ ur1: 42 })` is an error; widening it to accept `FormValues` would
95
+ * have made every object satisfy it and given the typing back. Reaching for
96
+ * this one is the author saying "these came from a form", which is a fact
97
+ * about the values and not a cast.
98
+ */
99
+ submitForm: (values: FormValues) => Promise<void>;
100
+ /** Clear the run and any error, putting the form back to its initial state. */
101
+ reset: () => void;
102
+ /**
103
+ * End the current run's `sleep()` early — "file it now" — resolving how many
104
+ * pending sleeps were interrupted.
105
+ *
106
+ * Bound to the run this submission is following, which is the point: it is
107
+ * the only reason a page holding one of these hooks needed an `api` of its
108
+ * own. `0` is an answer rather than a failure (the run had already moved past
109
+ * its wait, or there is no run yet), so nothing here has to be guarded.
110
+ */
111
+ wake: () => Promise<number>;
112
+ /**
113
+ * Stop the current run, resolving whether this call is what ended it.
114
+ *
115
+ * `false` for a run that had already finished, and for no run at all — the
116
+ * SDK's contract, because two tabs pressing Stop is ordinary. Distinct from
117
+ * `reset()`, which puts the FORM back and leaves the run running.
118
+ */
119
+ cancel: () => Promise<boolean>;
120
+ /** The run, once started, followed to completion. */
121
+ run: WorkflowRun<R> | undefined;
122
+ /**
123
+ * True from `submit()` on this mount until `reset()` — did THIS page start
124
+ * the run it is showing?
125
+ *
126
+ * A page needs it to say the right sentence and cannot derive it: a run
127
+ * ADOPTED by the mount-time lookup after a reload looks exactly like one this
128
+ * page started. Six templates kept a `useState(false)` next to this hook, set
129
+ * it in their `onSubmit` and mirrored it in their `onClear` — shadow state
130
+ * for a fact only this hook can know, since it is the thing that decides
131
+ * between `submit()` and the recovery lookup. One of the six grew a fourth
132
+ * branch and had to move the whole note into its own module with its own
133
+ * spec, which is what a seam missing one layer down looks like.
134
+ *
135
+ * The RAW fact rather than a derived "recovered", deliberately: with `run`
136
+ * these are three states, not two, and the third is the one a page most needs
137
+ * to explain. `startedHere` is "you pressed the button"; `!startedHere &&
138
+ * !run` is the mount-time lookup still going; `!startedHere && run` is a run
139
+ * this browser started earlier, now in front of somebody who did not press
140
+ * anything. A boolean meaning only the last of those cannot express the
141
+ * middle one.
142
+ *
143
+ * @example
144
+ * ```ts
145
+ * declare const submission: import("@alexkroman1/aai-ui").WorkflowSubmission;
146
+ * const note = submission.startedHere
147
+ * ? "You can close this tab or reload it — this page will find the run again."
148
+ * : submission.run === undefined
149
+ * ? "Looking for a run this tab started earlier…"
150
+ * : "Still working on the run this tab started earlier. Reloading is safe.";
151
+ * ```
152
+ */
153
+ startedHere: boolean;
154
+ /**
155
+ * True from `submit()` until the run reaches a terminal status.
156
+ *
157
+ * The WORK, not the request: a run outlives its `POST`, and a submit button
158
+ * that re-enabled on the response would invite a second submission of work
159
+ * already in flight.
160
+ */
161
+ pending: boolean;
162
+ /**
163
+ * How far the submission's files have got, while any are still going.
164
+ *
165
+ * Undefined before the first byte and again from the moment the last one
166
+ * lands, so a page can render `{upload && <UploadProgressBar upload={upload} />}`
167
+ * and the bar exists exactly for as long as there is an upload to describe. A
168
+ * form with no files never sets it at all.
169
+ *
170
+ * The wait it covers is the one `run` cannot: a run does not EXIST until its
171
+ * input is stored, so `pending` is true and there is nothing to poll — which
172
+ * for a 200 MB recording is minutes of a page that looks stuck.
173
+ */
174
+ upload: UploadStatus | undefined;
175
+ /**
176
+ * Park the upload where it is, stopping the bytes in flight.
177
+ *
178
+ * The windows already stored stay stored, so `resumeUpload()` sends what is
179
+ * missing rather than the file — which is the difference between a pause a
180
+ * person will actually use on a 200 MB recording and a cancel dressed up as one.
181
+ *
182
+ * `submit()`'s promise stays unresolved across a pause, because the submission
183
+ * genuinely has not finished: the run does not exist until the last byte lands,
184
+ * so resolving here would tell a `<Form>` the work was accepted when nothing has
185
+ * been started. A no-op when there is no upload in flight.
186
+ */
187
+ pauseUpload: () => void;
188
+ /** Continue a paused upload, sending only the windows the store does not have. */
189
+ resumeUpload: () => void;
190
+ /** The submit's own failure (a rejected input), or the watch's. */
191
+ error: string | undefined;
192
+ };
193
+ /** Options for {@link useWorkflowSubmit}. */
194
+ export type UseWorkflowSubmitOptions = {
195
+ /** The client to start runs with. Defaults to one for the page's own agent. */
196
+ api?: WorkflowApi;
197
+ /**
198
+ * Correlation key recorded with the run, for finding it again without the id.
199
+ *
200
+ * **Defaulted**, to an opaque per-page key in `sessionStorage` that the next
201
+ * load produces again — `useRunKey()`'s, minted by the hook. Pass one to
202
+ * scope runs to something the page knows better: an ACCOUNT's own id, which
203
+ * is what makes a run follow the person to a new device, or
204
+ * `useRunKey({ storage: "local" })` for a run that outlives the tab by
205
+ * design. The key is a lookup CAPABILITY (there is no per-user filtering
206
+ * behind `find`), it must fit the route's 256-character bound, and anything
207
+ * derived from a person's own input both collides and carries what they
208
+ * typed — `use-run-key.ts` argues every alternative.
209
+ */
210
+ key?: string;
211
+ /**
212
+ * On mount, adopt the newest run this `key` already has.
213
+ *
214
+ * **This is what makes a reload survivable, and it is ON.** The run id is
215
+ * this hook's own state, so a refresh loses it while the run carries on — and
216
+ * a page that cannot name a run cannot show it, cancel it or wake it. The
217
+ * hook asks `find(workflow, key)` once as it mounts and follows whatever
218
+ * comes back, so the answer, the progress and the controls are all there
219
+ * again.
220
+ *
221
+ * It used to be opt-in, on the argument that a `key` alone means only "record
222
+ * this with the run" — true of `ctx.workflows.start({ key })`, where there is
223
+ * no page to put a run back on, and not of a form: six of six page templates
224
+ * passed `useRunKey()` and `recover: true` together, which is a default in
225
+ * the wrong place. `false` is the opt-out, and what it buys is a form that
226
+ * always opens empty — no lookup on mount, and a live run reachable only by
227
+ * an id the page has already lost.
228
+ */
229
+ recover?: boolean;
230
+ /**
231
+ * Hold the `POST` open until the run settles, up to this many ms — the
232
+ * synchronous mode. Omitted (the default) returns as soon as the run exists.
233
+ */
234
+ wait?: number;
235
+ /** How often the fallback poll re-reads a live run. */
236
+ intervalMs?: number;
237
+ /**
238
+ * Send each chosen file as concurrent parts instead of in one request.
239
+ *
240
+ * **On by default.** `false` opts out, `{ partBytes, concurrency }` tunes it.
241
+ * This is the wait a form with a recording in it actually spends: the run does
242
+ * not exist until its input is stored, so until the last byte lands there is no
243
+ * run to watch and nothing for `<WorkflowProgress>` to say. Splitting the file
244
+ * across connections is what makes that stretch shorter, and it degrades to the
245
+ * single request wherever it would not help — a small file, an older agent — so
246
+ * the default costs nothing where it would not have paid. See
247
+ * `UploadOptions.parallel`.
248
+ */
249
+ parallel?: UploadParallelOption;
250
+ };
251
+ /**
252
+ * Start a workflow from a form, and follow the run it creates.
253
+ *
254
+ * @typeParam D - The workflow DEFINITION, which types both halves of the
255
+ * submission: `submit(input)` takes what the workflow's schema parses to, and
256
+ * `run.status === "completed"` narrows to a typed `run.output`.
257
+ *
258
+ * It used to be the OUTPUT type alone, and the asymmetry was the bug: a page
259
+ * already wrote `WorkflowOutputOf<typeof digest>` to get the output, while
260
+ * `submit` took `unknown`, so `submit({ ur1: 42 })` compiled and arrived as a
261
+ * 400 in the browser. Naming the def instead types the input from the same
262
+ * declaration — and `import type` is ERASED, so it costs the bundle nothing.
263
+ * Passing an output type where a def belongs is now a compile error rather
264
+ * than a silent loss of typing, which is the point.
265
+ *
266
+ * @example
267
+ * ```tsx no-check
268
+ * import { Form, SubmitButton, TextField, useWorkflowSubmit } from "@alexkroman1/aai-ui";
269
+ * import type { digest } from "./agent.ts";
270
+ *
271
+ * function DigestForm() {
272
+ * const { submit, run, pending, error } = useWorkflowSubmit<typeof digest>("digest");
273
+ * return (
274
+ * <Form onSubmit={(values) => submit(values)} error={error}>
275
+ * <TextField name="url" label="Link" type="url" required />
276
+ * <SubmitButton pending={pending}>Digest</SubmitButton>
277
+ * {run?.status === "completed" && <p>{run.output.title}</p>}
278
+ * </Form>
279
+ * );
280
+ * }
281
+ * ```
282
+ *
283
+ * @public
284
+ */
285
+ export declare function useWorkflowSubmit<D extends AnyWorkflowDef>(workflow: string, options?: UseWorkflowSubmitOptions): WorkflowSubmission<WorkflowOutputOf<D>, SubmitInputOf<D>>;