@alexkroman1/aai-ui 6.11.0 → 7.0.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 (66) hide show
  1. package/README.md +104 -3
  2. package/dist/_run-controls.d.ts +33 -0
  3. package/dist/{chat-view-ByQFf94G.js → chat-view-BKsFfFZJ.js} +57 -11
  4. package/dist/client-config-B4nznRvH.js +134 -0
  5. package/dist/client-config.d.ts +45 -2
  6. package/dist/client-dir.d.ts +3 -1
  7. package/dist/client-dir.js +3 -1
  8. package/dist/components/auto-scroll.d.ts +24 -13
  9. package/dist/components/button.d.ts +8 -4
  10. package/dist/components/button.js +4 -4
  11. package/dist/components/chat-view.d.ts +4 -3
  12. package/dist/components/chat-view.js +1 -1
  13. package/dist/components/console-shell.d.ts +71 -10
  14. package/dist/components/controls.d.ts +15 -4
  15. package/dist/components/controls.js +48 -2
  16. package/dist/components/form-fields.d.ts +142 -0
  17. package/dist/components/form-types.d.ts +6 -0
  18. package/dist/components/form.d.ts +38 -80
  19. package/dist/components/markdown.d.ts +31 -5
  20. package/dist/components/message-list.d.ts +21 -4
  21. package/dist/components/message-list.js +1 -1
  22. package/dist/components/sidebar-layout.d.ts +11 -0
  23. package/dist/components/sidebar-layout.js +2 -0
  24. package/dist/components/start-screen.d.ts +8 -0
  25. package/dist/components/start-screen.js +2 -0
  26. package/dist/components/tool-call-block.js +1 -1
  27. package/dist/components/tool-call-row.d.ts +24 -0
  28. package/dist/components/upload-progress.d.ts +17 -7
  29. package/dist/components/workflow-fields.d.ts +10 -22
  30. package/dist/components/workflow-progress.d.ts +29 -10
  31. package/dist/context.d.ts +36 -0
  32. package/dist/context.js +105 -14
  33. package/dist/default-client/assets/index-S5fkKi6B.css +2 -0
  34. package/dist/default-client/assets/index-fEkrcZgo.js +293 -0
  35. package/dist/default-client/index.html +2 -2
  36. package/dist/define-client.d.ts +67 -62
  37. package/dist/define-client.js +56 -22
  38. package/dist/hooks.d.ts +88 -3
  39. package/dist/index.d.ts +12 -11
  40. package/dist/index.js +501 -167
  41. package/dist/internal.d.ts +40 -0
  42. package/dist/internal.js +6 -0
  43. package/dist/{message-list-CpPV7dGx.js → message-list-DHddO4QC.js} +217 -72
  44. package/dist/{session-core-CAfYmUbg.js → session-core-C2JtLArh.js} +267 -165
  45. package/dist/session-core-audio-setup.d.ts +3 -0
  46. package/dist/session-core-messages.d.ts +3 -0
  47. package/dist/session-core-state.d.ts +146 -0
  48. package/dist/session-core-types.d.ts +77 -32
  49. package/dist/session-core.d.ts +3 -2
  50. package/dist/session-core.js +1 -1
  51. package/dist/{tool-call-block-D6pTEPrT.js → tool-call-block-DoF-cSIZ.js} +27 -19
  52. package/dist/tool-config-context-DzAofqi_.js +19 -0
  53. package/dist/types.d.ts +31 -5
  54. package/dist/types.js +5 -4
  55. package/dist/{controls-CjG91QJ4.js → url-chips-DpM7Oocj.js} +3 -46
  56. package/dist/use-conversation.d.ts +122 -0
  57. package/dist/use-download-url.d.ts +83 -0
  58. package/dist/use-workflow-form.d.ts +59 -3
  59. package/dist/use-workflow-run.d.ts +35 -0
  60. package/dist/use-workflow-stream.d.ts +36 -62
  61. package/dist/workflow-client.d.ts +36 -11
  62. package/dist/workflow-status-labels.d.ts +35 -0
  63. package/package.json +10 -5
  64. package/styles.css +14 -0
  65. package/dist/default-client/assets/index-DTLrhtTF.css +0 -2
  66. package/dist/default-client/assets/index-DXODx_9r.js +0 -293
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `@alexkroman1/aai-ui/internal` — the plumbing `client()` and the default
3
+ * client install for themselves, NOT part of the public client API and not
4
+ * covered by semver. A `client.tsx` should never import from here; everything
5
+ * an author writes a page or a custom chrome against lives on the root export
6
+ * and the documented subpaths.
7
+ *
8
+ * These names used to ride on the root barrel, tagged `@internal` and nothing
9
+ * else, which meant they were in a client author's autocomplete beside
10
+ * `client()`, `<Form>`, `useAgentState` and `useWorkflowRun` — eight symbols an
11
+ * author is invited to reach for and no capability contract covers. Keeping
12
+ * them on their own subpath keeps the root importable surface the same shape as
13
+ * the promise: what is on it is contracted.
14
+ *
15
+ * A release tag cannot do this from the barrel. API Extractor reads `@internal`
16
+ * at the DECLARATION site, so a tag written on a re-export clause member is
17
+ * silently ignored and the name stays `@public` in the report — and the
18
+ * contract exemption in `contracts/internal-surface.json` is per SUBPATH, so a
19
+ * tag on the root barrel buys an entry on that ratchet rather than removing
20
+ * one. A subpath is the mechanism, the same one `@alexkroman1/aai` and
21
+ * `@alexkroman1/aai-runtime` already use, and `NON_AUTHORING_SUBPATHS` in
22
+ * `scripts/_api-contracts-tree.mjs` carries the matching entry so a name
23
+ * arriving here joins no capability contract.
24
+ *
25
+ * The corollary is the rule for adding to this file, and it runs the other way
26
+ * too: a name that WANTS to be public gets its `@internal` tag removed and
27
+ * joins a capability contract — it does not stay on the barrel wearing a tag.
28
+ * That is what happened to `fetchClientConfig`, which the SDK's own `@public`
29
+ * prose told a workflow-app author to call while the export excluded it.
30
+ *
31
+ * Named re-exports rather than `export *`: the wildcard form needs a
32
+ * `noReExportAll` suppression, and the escape-hatch ratchet only moves down.
33
+ *
34
+ * @module internal
35
+ */
36
+ export { buildAgentUrl, loadClientConfig } from "./client-config.ts";
37
+ export { ToolConfigContext } from "./components/tool-config-context.ts";
38
+ export { ApiUrlChip, SessionUrlChips, UiUrlChip } from "./components/url-chips.tsx";
39
+ export { SessionProvider, ThemeProvider } from "./context.ts";
40
+ export { VOICE_CAPTURE_CONSTRAINTS } from "./types.ts";
@@ -0,0 +1,6 @@
1
+ import { r as loadClientConfig, t as buildAgentUrl } from "./client-config-B4nznRvH.js";
2
+ import { SessionProvider, ThemeProvider } from "./context.js";
3
+ import { n as SessionUrlChips, r as UiUrlChip, t as ApiUrlChip } from "./url-chips-DpM7Oocj.js";
4
+ import { t as ToolConfigContext } from "./tool-config-context-DzAofqi_.js";
5
+ import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
6
+ export { ApiUrlChip, SessionProvider, SessionUrlChips, ThemeProvider, ToolConfigContext, UiUrlChip, VOICE_CAPTURE_CONSTRAINTS, buildAgentUrl, loadClientConfig };
@@ -1,6 +1,6 @@
1
1
  import { useSessionSelector, useTheme } from "./context.js";
2
2
  import { i as primaryTint, r as inkTint } from "./_colors-CcAi2FOU.js";
3
- import { t as ToolCallBlock } from "./tool-call-block-D6pTEPrT.js";
3
+ import { t as ToolCallBlock } from "./tool-call-block-DoF-cSIZ.js";
4
4
  import clsx from "clsx";
5
5
  import { StickToBottom } from "use-stick-to-bottom";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -46,19 +46,7 @@ import remarkGfm from "remark-gfm";
46
46
  * }
47
47
  * ```
48
48
  *
49
- * @param children - The scrollable content.
50
- * @param className - Classes for the outer container. It must be given a
51
- * bounded height (`flex-1 min-h-0`, `h-full`, a fixed height) — an unbounded
52
- * one grows with its content and never scrolls, so nothing pins.
53
- * @param contentClassName - Classes for the inner content element, where
54
- * padding and the children's own layout belong.
55
- * @param scrollClassName - Classes for the scrolling element itself. Defaults
56
- * to hiding the scrollbar; pass `"overflow-y-auto"` to show a native one.
57
- * @param style - Inline styles for the outer container.
58
- * @param initial - Scroll behavior on mount. Defaults to `"instant"` (start at
59
- * the latest content without animating a scroll the reader did not ask for).
60
- * @param resize - Scroll behavior when pinned content grows. Defaults to
61
- * `"smooth"`.
49
+ * @param props - Scroll container props.
62
50
  *
63
51
  * @public
64
52
  */
@@ -77,6 +65,196 @@ function AutoScroll({ children, className, contentClassName, scrollClassName = "
77
65
  });
78
66
  }
79
67
  //#endregion
68
+ //#region use-user-transcript.ts
69
+ /**
70
+ * `useUserTranscript` — what the caller is saying RIGHT NOW, read correctly.
71
+ *
72
+ * `SessionSnapshot.userTranscript` is `string | null`, and the two falsy values
73
+ * mean different things:
74
+ *
75
+ * - `null` — nobody is speaking. There is no partial turn.
76
+ * - `""` — speech HAS been detected and no words have come back yet. A live
77
+ * session sits here for a few hundred milliseconds at the start of every turn.
78
+ *
79
+ * Read as one falsy check, those collapse and the indicator never appears at the
80
+ * start of a turn — which is the moment it is for. So every custom chrome writes
81
+ * `transcript !== null && (transcript === "" ? "…" : transcript)`, three
82
+ * templates did exactly that, and each one re-derived a protocol distinction
83
+ * from the type rather than from anything that told them.
84
+ *
85
+ * This is the same distinction as two named booleans, so a component can say
86
+ * what it means: render on `speaking`, show `text`, and use the SDK's own
87
+ * placeholder when there is nothing to show yet.
88
+ */
89
+ /**
90
+ * Placeholder for "listening, no words yet" — the `""` case above.
91
+ *
92
+ * A one-character ellipsis rather than three dots, because it is read by a
93
+ * screen reader as an ellipsis and it does not reflow the row when the first
94
+ * real word replaces it.
95
+ *
96
+ * @public
97
+ */
98
+ const TRANSCRIBING_PLACEHOLDER = "…";
99
+ /**
100
+ * Subscribe to the caller's in-progress turn.
101
+ *
102
+ * Narrowly subscribed — a component using this re-renders at STT-partial rate,
103
+ * which is exactly what it is for and exactly what a whole-page `useSession()`
104
+ * should not do.
105
+ *
106
+ * @example
107
+ * ```tsx
108
+ * import { useUserTranscript } from "@alexkroman1/aai-ui";
109
+ *
110
+ * function LiveTranscript() {
111
+ * const { speaking, text } = useUserTranscript();
112
+ * if (!speaking) return null;
113
+ * return <div className="italic opacity-60">{text}</div>;
114
+ * }
115
+ * ```
116
+ *
117
+ * @public
118
+ */
119
+ function useUserTranscript() {
120
+ const partial = useSessionSelector((snapshot) => snapshot.userTranscript);
121
+ return {
122
+ speaking: partial !== null,
123
+ text: displayText(partial),
124
+ partial
125
+ };
126
+ }
127
+ /** The three cases, spelled out: silent, detected-but-wordless, and words. */
128
+ function displayText(partial) {
129
+ if (partial === null) return "";
130
+ return partial === "" ? "…" : partial;
131
+ }
132
+ //#endregion
133
+ //#region use-conversation.ts
134
+ /**
135
+ * `useConversation` — the exchange, already assembled, with nothing rendered.
136
+ *
137
+ * `<MessageList>` owns four decisions that are not obvious and are not
138
+ * derivable from the snapshot by eye: the message/tool-call INTERLEAVE, the
139
+ * streaming agent utterance, the live user transcript with its `null`-vs-`""`
140
+ * protocol distinction, and the thinking indicator's suppression rule. Its only
141
+ * prop is `className`, so the moment a client wants its own bubble markup it
142
+ * drops all four at once — and the three hand-rolled chromes in the template
143
+ * tree each shipped a strictly worse conversation for exactly that reason. One
144
+ * of them runs fifteen tools and its operator sees none of them, because the
145
+ * tool calls live in a second array nothing in that page ever reads.
146
+ *
147
+ * This is the headless half. `<MessageList>` is now a thin consumer of it —
148
+ * which is the only way to know the hook is complete, since a hook that the
149
+ * package's own list cannot be built from is a hook a custom chrome will find a
150
+ * hole in.
151
+ *
152
+ * ## It subscribes NARROWLY, and that is half the value
153
+ *
154
+ * The three custom chromes also call whole-page `useSession()`, which re-renders
155
+ * on *every* snapshot change — so a dispatch board re-renders at STT-partial
156
+ * rate. `<ChatView>` deliberately avoids that with per-field
157
+ * {@link useSessionSelector} calls, and so does this: a component that reads the
158
+ * conversation gets the conversation's own update rate, not the session's.
159
+ */
160
+ /**
161
+ * Interleave messages and tool calls, ordered by insertion time.
162
+ *
163
+ * Each tool call belongs immediately after its anchor message
164
+ * (`afterMessageId`); tool calls whose anchor slid out of the retained window —
165
+ * or that were inserted before any message existed — come first, since there is
166
+ * nothing left for them to follow.
167
+ */
168
+ function interleave(messages, toolCalls) {
169
+ const items = [];
170
+ let tci = 0;
171
+ const pushToolCallsThrough = (maxAfterId) => {
172
+ let tc = toolCalls[tci];
173
+ while (tc && tc.afterMessageId <= maxAfterId) {
174
+ items.push({
175
+ kind: "tool",
176
+ toolCall: tc
177
+ });
178
+ tci++;
179
+ tc = toolCalls[tci];
180
+ }
181
+ };
182
+ const firstMessage = messages[0];
183
+ if (firstMessage) pushToolCallsThrough(firstMessage.id - 1);
184
+ for (const message of messages) {
185
+ items.push({
186
+ kind: "message",
187
+ message
188
+ });
189
+ pushToolCallsThrough(message.id);
190
+ }
191
+ pushToolCallsThrough(Number.POSITIVE_INFINITY);
192
+ return items;
193
+ }
194
+ /**
195
+ * Whether the thinking indicator should show — see
196
+ * {@link UseConversationResult.thinking} for the rule and the argument.
197
+ */
198
+ function isThinking(state, messages, toolCalls) {
199
+ if (state !== "thinking") return false;
200
+ const last = toolCalls.at(-1);
201
+ if (last?.status === "pending") return false;
202
+ const lastMessage = messages.at(-1);
203
+ return !lastMessage || lastMessage.role === "user" || Boolean(last);
204
+ }
205
+ /**
206
+ * Subscribe to the conversation: the interleaved exchange, the streaming
207
+ * utterance, the live transcript and the thinking rule — with no markup.
208
+ *
209
+ * Must be used inside the provider `client()` installs.
210
+ *
211
+ * @example A custom bubble, keeping every rule `<MessageList>` knows
212
+ * ```tsx
213
+ * import { useConversation } from "@alexkroman1/aai-ui";
214
+ *
215
+ * function Transcript() {
216
+ * const { items, streaming, transcript, thinking } = useConversation();
217
+ * return (
218
+ * <div>
219
+ * {items.map((item) =>
220
+ * item.kind === "message" ? (
221
+ * <p key={item.message.id} data-role={item.message.role}>
222
+ * {item.message.content}
223
+ * </p>
224
+ * ) : (
225
+ * <code key={item.toolCall.callId}>{item.toolCall.name}</code>
226
+ * ),
227
+ * )}
228
+ * {streaming !== null && <p data-role="assistant">{streaming}</p>}
229
+ * {transcript.speaking && <p data-role="user">{transcript.text}</p>}
230
+ * {thinking && <p>…</p>}
231
+ * </div>
232
+ * );
233
+ * }
234
+ * ```
235
+ *
236
+ * @returns See {@link UseConversationResult}.
237
+ *
238
+ * @public
239
+ */
240
+ function useConversation() {
241
+ const state = useSessionSelector((s) => s.state);
242
+ const messages = useSessionSelector((s) => s.messages);
243
+ const toolCalls = useSessionSelector((s) => s.toolCalls);
244
+ const streaming = useSessionSelector((s) => s.agentTranscript);
245
+ const transcript = useUserTranscript();
246
+ return {
247
+ items: useMemo(() => interleave(messages, toolCalls), [messages, toolCalls]),
248
+ streaming,
249
+ transcript,
250
+ thinking: useMemo(() => isThinking(state, messages, toolCalls), [
251
+ state,
252
+ messages,
253
+ toolCalls
254
+ ])
255
+ };
256
+ }
257
+ //#endregion
80
258
  //#region components/markdown.tsx
81
259
  /** @jsxImportSource react */
82
260
  const BARE_ORDERED_MARKER = /^(\s*)(\d{1,9})([.)])\s*$/;
@@ -137,6 +315,18 @@ const SCALE_CLASSES = {
137
315
  * Memoized alongside `MessageBubble`: message content is referentially
138
316
  * stable across snapshots, so only the streaming row re-parses.
139
317
  *
318
+ * @example
319
+ * ```tsx
320
+ * import { Markdown, useSessionSelector } from "@alexkroman1/aai-ui";
321
+ *
322
+ * // The agent's reply as it streams, rendered rather than shown as literal
323
+ * // asterisks and backticks.
324
+ * function LiveReply() {
325
+ * const text = useSessionSelector((snapshot) => snapshot.agentTranscript);
326
+ * return text === null ? null : <Markdown text={text} variant="compact" />;
327
+ * }
328
+ * ```
329
+ *
140
330
  * @public
141
331
  */
142
332
  const Markdown = memo(function Markdown({ text, variant = "default" }) {
@@ -339,32 +529,6 @@ const MessageBubble = memo(function MessageBubble({ message, theme }) {
339
529
  });
340
530
  });
341
531
  /**
342
- * Interleave messages and tool calls into render items, ordered by insertion
343
- * time. Each tool call renders immediately after its anchor message
344
- * (`afterMessageId`); tool calls whose anchor slid out of the retained window
345
- * (or that were inserted before any message existed) render first.
346
- */
347
- function interleave(messages, toolCalls, renderMessage, renderToolCall) {
348
- const items = [];
349
- let tci = 0;
350
- const pushToolCallsThrough = (maxAfterId) => {
351
- let tc = toolCalls[tci];
352
- while (tc && tc.afterMessageId <= maxAfterId) {
353
- items.push(renderToolCall(tc));
354
- tci++;
355
- tc = toolCalls[tci];
356
- }
357
- };
358
- const firstMessage = messages[0];
359
- if (firstMessage) pushToolCallsThrough(firstMessage.id - 1);
360
- for (const msg of messages) {
361
- items.push(renderMessage(msg));
362
- pushToolCallsThrough(msg.id);
363
- }
364
- pushToolCallsThrough(Number.POSITIVE_INFINITY);
365
- return items;
366
- }
367
- /**
368
532
  * Scrollable list of all chat messages, tool-call blocks, live transcript,
369
533
  * streaming agent utterance, and a thinking indicator.
370
534
  *
@@ -382,58 +546,39 @@ function interleave(messages, toolCalls, renderMessage, renderToolCall) {
382
546
  * }
383
547
  * ```
384
548
  *
385
- * @param className - Additional CSS class names applied to the outer list container.
549
+ * @param props - Container props.
386
550
  *
387
551
  * @public
388
552
  */
389
553
  const MessageList = memo(function MessageList({ className }) {
390
- const state = useSessionSelector((s) => s.state);
391
- const messages = useSessionSelector((s) => s.messages);
392
- const toolCalls = useSessionSelector((s) => s.toolCalls);
393
- const userTranscript = useSessionSelector((s) => s.userTranscript);
394
- const agentTranscript = useSessionSelector((s) => s.agentTranscript);
554
+ const { items, streaming, transcript, thinking } = useConversation();
395
555
  const theme = useTheme();
396
- const showThinking = useMemo(() => {
397
- if (state !== "thinking") return false;
398
- const last = toolCalls.at(-1);
399
- if (last?.status === "pending") return false;
400
- const lastMsg = messages.at(-1);
401
- return !lastMsg || lastMsg.role === "user" || Boolean(last);
402
- }, [
403
- state,
404
- toolCalls,
405
- messages
406
- ]);
407
- const streamingMessage = useMemo(() => agentTranscript ? {
556
+ const streamingMessage = useMemo(() => streaming ? {
408
557
  role: "assistant",
409
- content: agentTranscript
410
- } : null, [agentTranscript]);
411
- const items = useMemo(() => interleave(messages, toolCalls, (msg) => /* @__PURE__ */ jsx(MessageBubble, {
412
- message: msg,
413
- theme
414
- }, msg.id), (tc) => /* @__PURE__ */ jsx(ToolCallBlock, { toolCall: tc }, tc.callId)), [
415
- messages,
416
- toolCalls,
558
+ content: streaming
559
+ } : null, [streaming]);
560
+ const rows = useMemo(() => items.map((item) => item.kind === "message" ? /* @__PURE__ */ jsx(MessageBubble, {
561
+ message: item.message,
417
562
  theme
418
- ]);
563
+ }, item.message.id) : /* @__PURE__ */ jsx(ToolCallBlock, { toolCall: item.toolCall }, item.toolCall.callId)), [items, theme]);
419
564
  return /* @__PURE__ */ jsxs(AutoScroll, {
420
565
  className,
421
566
  style: { background: theme.surface },
422
567
  contentClassName: "flex flex-col gap-4 p-7",
423
568
  children: [
424
- items,
569
+ rows,
425
570
  streamingMessage && /* @__PURE__ */ jsx(MessageBubble, {
426
571
  message: streamingMessage,
427
572
  theme
428
573
  }),
429
- userTranscript !== null && /* @__PURE__ */ jsx(UserBubble, {
574
+ transcript.speaking && /* @__PURE__ */ jsx(UserBubble, {
430
575
  theme,
431
576
  color: inkTint(theme.text, theme.surface, 65),
432
- children: userTranscript ? userTranscript : /* @__PURE__ */ jsx(ThinkingDots, {})
577
+ children: transcript.partial ? transcript.partial : /* @__PURE__ */ jsx(ThinkingDots, {})
433
578
  }),
434
- showThinking && /* @__PURE__ */ jsx(ThinkingDots, {})
579
+ thinking && /* @__PURE__ */ jsx(ThinkingDots, {})
435
580
  ]
436
581
  });
437
582
  });
438
583
  //#endregion
439
- export { Markdown as n, AutoScroll as r, MessageList as t };
584
+ export { useUserTranscript as a, TRANSCRIBING_PLACEHOLDER as i, Markdown as n, AutoScroll as o, useConversation as r, MessageList as t };