@alexkroman1/aai-ui 5.5.1 → 5.7.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.
@@ -1,4 +1,4 @@
1
- import{i as e}from"./index-C99SRlSn.js";import{t}from"./_module-url-BX0RuRU2.js";var n=t(`
1
+ import{i as e}from"./index-fLjOiK97.js";import{t}from"./_module-url-BX0RuRU2.js";var n=t(`
2
2
  class PlaybackProcessor extends AudioWorkletProcessor {
3
3
  constructor(options) {
4
4
  super();
@@ -6,8 +6,8 @@
6
6
  <title>aai</title>
7
7
  <link rel="icon" href="./favicon.ico" />
8
8
  <style>html, body { background: #FBF8F2; margin: 0; }</style>
9
- <script type="module" crossorigin src="./assets/index-C99SRlSn.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-CRfdZPH1.css">
9
+ <script type="module" crossorigin src="./assets/index-fLjOiK97.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-CuPmIAbL.css">
11
11
  </head>
12
12
  <body>
13
13
  <main id="app"></main>
@@ -1,8 +1,8 @@
1
- import { r as fetchClientConfig, t as createSessionCore } from "./session-core-KY9B5nqn.js";
1
+ import { r as fetchClientConfig, t as createSessionCore } from "./session-core-DqibBzJz.js";
2
2
  import { SessionProvider, ThemeProvider } from "./context.js";
3
- import { t as ChatView } from "./chat-view-CqRGHImR.js";
3
+ import { t as ChatView } from "./chat-view-DFBmX5eX.js";
4
4
  import { t as pageBaseUrl } from "./_utils-CsqbIVGI.js";
5
- import { n as ToolConfigContext } from "./tool-call-block-C--Bj04_.js";
5
+ import { n as ToolConfigContext } from "./tool-call-block-BjemDFB9.js";
6
6
  import { SidebarLayout } from "./components/sidebar-layout.js";
7
7
  import { StartScreen } from "./components/start-screen.js";
8
8
  import { createElement, useEffect, useState } from "react";
package/dist/index.d.ts CHANGED
@@ -2,10 +2,11 @@ export { buildAgentUrl, type ClientConfigResponse, fetchClientConfig, loadClient
2
2
  export { Button, type ButtonSize, type ButtonVariant } from "./components/button.tsx";
3
3
  export { ChatView } from "./components/chat-view.tsx";
4
4
  export { Controls } from "./components/controls.tsx";
5
- export { Markdown } from "./components/markdown.tsx";
5
+ export { Markdown, type MarkdownVariant } from "./components/markdown.tsx";
6
6
  export { MessageList } from "./components/message-list.tsx";
7
7
  export { SidebarLayout } from "./components/sidebar-layout.tsx";
8
8
  export { StartScreen } from "./components/start-screen.tsx";
9
+ export { ToolCallRow, type ToolCallRowProps, type ToolCallRowVariant, } from "./components/tool-call-row.tsx";
9
10
  export type { ToolDisplayConfig } from "./components/tool-config-context.ts";
10
11
  export { ToolConfigContext } from "./components/tool-config-context.ts";
11
12
  export { ApiUrlChip, SessionUrlChips, UiUrlChip } from "./components/url-chips.tsx";
package/dist/index.js CHANGED
@@ -1,13 +1,13 @@
1
- import { i as loadClientConfig, n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-KY9B5nqn.js";
1
+ import { i as loadClientConfig, n as buildAgentUrl, r as fetchClientConfig, t as createSessionCore } from "./session-core-DqibBzJz.js";
2
2
  import { SessionProvider, ThemeProvider, useSession, useSessionSelector, useTheme } from "./context.js";
3
3
  import { Button } from "./components/button.js";
4
- import { t as ChatView } from "./chat-view-CqRGHImR.js";
4
+ import { t as ChatView } from "./chat-view-DFBmX5eX.js";
5
5
  import { i as UiUrlChip, n as ApiUrlChip, r as SessionUrlChips, t as Controls } from "./controls-CoBO6sId.js";
6
- import { n as Markdown, t as MessageList } from "./message-list-CdtrNi4w.js";
7
- import { n as ToolConfigContext } from "./tool-call-block-C--Bj04_.js";
6
+ import { n as Markdown, t as MessageList } from "./message-list-DkAHoB3A.js";
7
+ import { n as ToolConfigContext, r as ToolCallRow } from "./tool-call-block-BjemDFB9.js";
8
8
  import { SidebarLayout } from "./components/sidebar-layout.js";
9
9
  import { StartScreen } from "./components/start-screen.js";
10
10
  import { VOICE_CAPTURE_CONSTRAINTS } from "./types.js";
11
11
  import { client } from "./define-client.js";
12
12
  import { useAgentState, useEvent, useToolCallStart, useToolResult } from "./hooks.js";
13
- export { ApiUrlChip, Button, ChatView, Controls, Markdown, MessageList, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, ThemeProvider, ToolConfigContext, UiUrlChip, VOICE_CAPTURE_CONSTRAINTS, buildAgentUrl, client, createSessionCore, fetchClientConfig, loadClientConfig, useAgentState, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult };
13
+ export { ApiUrlChip, Button, ChatView, Controls, Markdown, MessageList, SessionProvider, SessionUrlChips, SidebarLayout, StartScreen, ThemeProvider, ToolCallRow, ToolConfigContext, UiUrlChip, VOICE_CAPTURE_CONSTRAINTS, buildAgentUrl, client, createSessionCore, fetchClientConfig, loadClientConfig, useAgentState, useEvent, useSession, useSessionSelector, useTheme, useToolCallStart, useToolResult };
@@ -1,9 +1,10 @@
1
1
  import { useSessionSelector, useTheme } from "./context.js";
2
2
  import { i as TEXT_MUTED, n as SURFACE_TINT, o as primaryTint, r as TEXT_FAINT } from "./_colors-Bfh3-BVE.js";
3
- import { t as ToolCallBlock } from "./tool-call-block-C--Bj04_.js";
3
+ import { t as ToolCallBlock } from "./tool-call-block-BjemDFB9.js";
4
4
  import clsx from "clsx";
5
- import { memo, useCallback, useEffect, useMemo, useRef } from "react";
5
+ import { memo, useMemo } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { StickToBottom } from "use-stick-to-bottom";
7
8
  import ReactMarkdown from "react-markdown";
8
9
  import remarkGfm from "remark-gfm";
9
10
  //#region components/markdown.tsx
@@ -34,6 +35,22 @@ function escapeBareListMarkers(text) {
34
35
  return line;
35
36
  }).join("\n");
36
37
  }
38
+ const SCALE_CLASSES = {
39
+ default: {
40
+ h1: "mt-3 mb-1.5 text-[17px] font-semibold",
41
+ h2: "mt-3 mb-1.5 text-[16px] font-semibold",
42
+ h3: "mt-3 mb-1.5 text-[15px] font-semibold",
43
+ code: "text-[12.5px]",
44
+ table: "my-1.5 w-full border-collapse text-sm"
45
+ },
46
+ compact: {
47
+ h1: "mt-3 mb-1.5 text-[15px] font-medium",
48
+ h2: "mt-3 mb-1.5 text-[14px] font-medium",
49
+ h3: "mt-3 mb-1.5 text-[13px] font-medium",
50
+ code: "text-[11px]",
51
+ table: "my-1.5 w-full border-collapse text-[12px]"
52
+ }
53
+ };
37
54
  /**
38
55
  * Agent prose, rendered as Markdown.
39
56
  *
@@ -41,6 +58,8 @@ function escapeBareListMarkers(text) {
41
58
  * this they arrived as literal asterisks and backticks. Styling is
42
59
  * per-element (theme colors via inline styles, spacing via Tailwind) so it
43
60
  * stays on the default client's type scale and follows custom themes.
61
+ * The optional `variant` selects the type scale (see
62
+ * {@link MarkdownVariant}).
44
63
  * GFM is on for tables and strikethrough. react-markdown does not render
45
64
  * raw HTML unless rehype-raw is added — keep it that way, this text comes
46
65
  * from a model.
@@ -50,8 +69,9 @@ function escapeBareListMarkers(text) {
50
69
  *
51
70
  * @public
52
71
  */
53
- const Markdown = memo(function Markdown({ text }) {
72
+ const Markdown = memo(function Markdown({ text, variant = "default" }) {
54
73
  const theme = useTheme();
74
+ const scale = SCALE_CLASSES[variant];
55
75
  const components = useMemo(() => ({
56
76
  p: (props) => /* @__PURE__ */ jsx("p", {
57
77
  className: "my-1.5 first:mt-0 last:mb-0",
@@ -70,15 +90,15 @@ const Markdown = memo(function Markdown({ text }) {
70
90
  ...props
71
91
  }),
72
92
  h1: (props) => /* @__PURE__ */ jsx("h1", {
73
- className: "mt-3 mb-1.5 text-[17px] font-semibold",
93
+ className: scale.h1,
74
94
  ...props
75
95
  }),
76
96
  h2: (props) => /* @__PURE__ */ jsx("h2", {
77
- className: "mt-3 mb-1.5 text-[16px] font-semibold",
97
+ className: scale.h2,
78
98
  ...props
79
99
  }),
80
100
  h3: (props) => /* @__PURE__ */ jsx("h3", {
81
- className: "mt-3 mb-1.5 text-[15px] font-semibold",
101
+ className: scale.h3,
82
102
  ...props
83
103
  }),
84
104
  a: ({ style, ...props }) => /* @__PURE__ */ jsx("a", {
@@ -93,11 +113,11 @@ const Markdown = memo(function Markdown({ text }) {
93
113
  }),
94
114
  code: ({ className, style, children, ...rest }) => {
95
115
  return /language-/.test(className ?? "") ? /* @__PURE__ */ jsx("code", {
96
- className: "font-aai-mono text-[12.5px]",
116
+ className: clsx("font-aai-mono", scale.code),
97
117
  ...rest,
98
118
  children
99
119
  }) : /* @__PURE__ */ jsx("code", {
100
- className: "rounded-sm border px-1 py-0.5 font-aai-mono text-[12.5px]",
120
+ className: clsx("rounded-sm border px-1 py-0.5 font-aai-mono", scale.code),
101
121
  style: {
102
122
  borderColor: theme.border,
103
123
  background: SURFACE_TINT,
@@ -126,7 +146,7 @@ const Markdown = memo(function Markdown({ text }) {
126
146
  ...props
127
147
  }),
128
148
  table: (props) => /* @__PURE__ */ jsx("table", {
129
- className: "my-1.5 w-full border-collapse text-sm",
149
+ className: scale.table,
130
150
  ...props
131
151
  }),
132
152
  th: ({ style, ...props }) => /* @__PURE__ */ jsx("th", {
@@ -153,7 +173,7 @@ const Markdown = memo(function Markdown({ text }) {
153
173
  },
154
174
  ...props
155
175
  })
156
- }), [theme]);
176
+ }), [theme, scale]);
157
177
  return /* @__PURE__ */ jsx(ReactMarkdown, {
158
178
  remarkPlugins: [remarkGfm],
159
179
  components,
@@ -233,53 +253,6 @@ const MessageBubble = memo(function MessageBubble({ message, theme }) {
233
253
  });
234
254
  });
235
255
  /**
236
- * How close to the bottom (px) the container must be for auto-scroll to stay
237
- * engaged. Generous enough that an in-flight smooth scroll doesn't unpin.
238
- */
239
- const NEAR_BOTTOM_PX = 96;
240
- /**
241
- * Smooth-scroll to the anchor whenever the content version advances — but
242
- * only while the container is pinned near the bottom. A user who scrolled up
243
- * to read history isn't yanked back down by streaming updates; scrolling back
244
- * to the bottom re-engages the auto-scroll.
245
- *
246
- * The scroll runs inside `requestAnimationFrame` and is deduped per frame:
247
- * several snapshot updates in one frame (transcript + message + tool call)
248
- * trigger a single scroll after layout instead of one forced layout each.
249
- *
250
- * While a partial transcript is streaming, updates arrive faster than a
251
- * smooth scroll finishes — each restart leaves the animation perpetually
252
- * mid-flight — so `streaming` switches to an instant jump; committed-content
253
- * updates keep the smooth animation.
254
- */
255
- function useAutoScroll(contentVersion, streaming) {
256
- const anchorRef = useRef(null);
257
- const pinnedRef = useRef(true);
258
- const scheduledRef = useRef(false);
259
- const streamingRef = useRef(streaming);
260
- streamingRef.current = streaming;
261
- const onScroll = useCallback((event) => {
262
- const el = event.currentTarget;
263
- pinnedRef.current = el.scrollTop + el.clientHeight >= el.scrollHeight - NEAR_BOTTOM_PX;
264
- }, []);
265
- useEffect(() => {
266
- if (contentVersion === 0 || scheduledRef.current || !pinnedRef.current) return;
267
- scheduledRef.current = true;
268
- requestAnimationFrame(() => {
269
- scheduledRef.current = false;
270
- if (!pinnedRef.current) return;
271
- anchorRef.current?.scrollIntoView({
272
- behavior: streamingRef.current ? "instant" : "smooth",
273
- block: "end"
274
- });
275
- });
276
- }, [contentVersion]);
277
- return {
278
- anchorRef,
279
- onScroll
280
- };
281
- }
282
- /**
283
256
  * Interleave messages and tool calls into render items, ordered by insertion
284
257
  * time. Each tool call renders immediately after its anchor message
285
258
  * (`afterMessageId`); tool calls whose anchor slid out of the retained window
@@ -323,7 +296,7 @@ function interleave(messages, toolCalls, renderMessage, renderToolCall) {
323
296
  * }
324
297
  * ```
325
298
  *
326
- * @param className - Additional CSS class names applied to the scroll container.
299
+ * @param className - Additional CSS class names applied to the outer list container.
327
300
  *
328
301
  * @public
329
302
  */
@@ -333,7 +306,6 @@ const MessageList = memo(function MessageList({ className }) {
333
306
  const toolCalls = useSessionSelector((s) => s.toolCalls);
334
307
  const userTranscript = useSessionSelector((s) => s.userTranscript);
335
308
  const agentTranscript = useSessionSelector((s) => s.agentTranscript);
336
- const contentVersion = useSessionSelector((s) => s.contentVersion);
337
309
  const theme = useTheme();
338
310
  const showThinking = useMemo(() => {
339
311
  if (state !== "thinking") return false;
@@ -346,7 +318,6 @@ const MessageList = memo(function MessageList({ className }) {
346
318
  toolCalls,
347
319
  messages
348
320
  ]);
349
- const { anchorRef, onScroll } = useAutoScroll(contentVersion, userTranscript !== null);
350
321
  const streamingMessage = useMemo(() => agentTranscript ? {
351
322
  role: "assistant",
352
323
  content: agentTranscript
@@ -359,12 +330,14 @@ const MessageList = memo(function MessageList({ className }) {
359
330
  toolCalls,
360
331
  theme
361
332
  ]);
362
- return /* @__PURE__ */ jsx("div", {
333
+ return /* @__PURE__ */ jsx(StickToBottom, {
363
334
  role: "log",
364
- className: clsx("flex-1 overflow-y-auto [scrollbar-width:none]", className),
335
+ className: clsx("flex-1 min-h-0", className),
365
336
  style: { background: theme.surface },
366
- onScroll,
367
- children: /* @__PURE__ */ jsxs("div", {
337
+ initial: "instant",
338
+ resize: "smooth",
339
+ children: /* @__PURE__ */ jsxs(StickToBottom.Content, {
340
+ scrollClassName: "overflow-y-auto [scrollbar-width:none]",
368
341
  className: "flex flex-col gap-4 p-7",
369
342
  children: [
370
343
  items,
@@ -377,8 +350,7 @@ const MessageList = memo(function MessageList({ className }) {
377
350
  color: "#6F6A60",
378
351
  children: userTranscript ? userTranscript : /* @__PURE__ */ jsx(ThinkingDots, {})
379
352
  }),
380
- showThinking && /* @__PURE__ */ jsx(ThinkingDots, {}),
381
- /* @__PURE__ */ jsx("div", { ref: anchorRef })
353
+ showThinking && /* @__PURE__ */ jsx(ThinkingDots, {})
382
354
  ]
383
355
  })
384
356
  });
@@ -375,6 +375,9 @@ function createMessageHandlers(deps) {
375
375
  case "error":
376
376
  handleErrorEvent(e);
377
377
  break;
378
+ case "idle_timeout":
379
+ deps.conn.retiredByServer = true;
380
+ break;
378
381
  default: break;
379
382
  }
380
383
  }
@@ -591,6 +594,7 @@ function createSessionCore(options) {
591
594
  }
592
595
  const conn = {
593
596
  ws: null,
597
+ retiredByServer: false,
594
598
  voiceIO: null,
595
599
  audioSetupInFlight: false,
596
600
  generation: createEpoch(),
@@ -714,6 +718,7 @@ function createSessionCore(options) {
714
718
  loadAudioModules().catch(() => {});
715
719
  teardownConnection();
716
720
  conn.generation.bump();
721
+ conn.retiredByServer = false;
717
722
  const controller = new AbortController();
718
723
  connectionController = controller;
719
724
  const { signal: sig } = controller;
@@ -735,7 +740,7 @@ function createSessionCore(options) {
735
740
  socket.addEventListener("close", () => {
736
741
  if (sig.aborted) return;
737
742
  cleanupAudio();
738
- if (reconnectPending(socket)) {
743
+ if (!conn.retiredByServer && reconnectPending(socket)) {
739
744
  conn.generation.bump();
740
745
  socketErrored = false;
741
746
  updateState({
@@ -140,6 +140,17 @@ export type SessionCoreOptions = VoiceSessionOptions;
140
140
  */
141
141
  export type ConnState = {
142
142
  ws: InstanceType<WebSocketConstructor> | null;
143
+ /**
144
+ * The server retired this session for idleness (`idle_timeout`), so the
145
+ * close that follows is EXPECTED and must not be retried.
146
+ *
147
+ * Without this the automatic reconnect would immediately re-open the
148
+ * session the server just reclaimed — a tab left open would cycle forever
149
+ * and the guest would never see zero sessions, which is the whole point of
150
+ * the timeout. Reconnecting is the user's call from here (the controls
151
+ * reconnect on demand).
152
+ */
153
+ retiredByServer: boolean;
143
154
  voiceIO: VoiceIO | null;
144
155
  audioSetupInFlight: boolean;
145
156
  /** Connection epoch, bumped on each connect()/retry (see `createEpoch`).
@@ -1,2 +1,2 @@
1
- import { t as createSessionCore } from "./session-core-KY9B5nqn.js";
1
+ import { t as createSessionCore } from "./session-core-DqibBzJz.js";
2
2
  export { createSessionCore };
@@ -4,7 +4,92 @@ import { t as Eyebrow } from "./eyebrow-C6ZFuiz6.js";
4
4
  import { n as truncate, r as tryParseJSON } from "./_utils-CsqbIVGI.js";
5
5
  import clsx from "clsx";
6
6
  import { createContext, memo, useContext, useMemo, useState } from "react";
7
- import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region components/tool-call-row.tsx
9
+ /** @jsxImportSource react */
10
+ const VARIANT_CLASSES = {
11
+ default: {
12
+ button: "gap-2.5 px-3.5 py-2.5",
13
+ title: "text-[13px]",
14
+ chevron: "text-[10px]"
15
+ },
16
+ compact: {
17
+ button: "gap-2 px-3 py-2",
18
+ title: "text-[11px]",
19
+ chevron: "text-[9px]"
20
+ }
21
+ };
22
+ /**
23
+ * The design system's console row for one tool invocation: a small outlined
24
+ * "TOOL" chip (or a custom `icon`), the tool title in mono, a truncated
25
+ * detail preview, and a rotating chevron that expands to the panel content.
26
+ *
27
+ * Purely presentational — callers own the mapping from their tool-call data
28
+ * to `title`/`detail`/`pending` and the expanded panel. The deployed agent
29
+ * UI's message list renders it via its tool-call block, and the studio's
30
+ * chat transcript renders it with `variant="compact"`, so the two surfaces
31
+ * read as one component.
32
+ *
33
+ * Colors come from the nearest theme context (see {@link useTheme}); without
34
+ * a provider the default AssemblyAI theme applies.
35
+ *
36
+ * @public
37
+ */
38
+ function ToolCallRow({ title, detail, pending = false, icon, variant = "default", className, children }) {
39
+ const [isOpen, setIsOpen] = useState(false);
40
+ const theme = useTheme();
41
+ const sizes = VARIANT_CLASSES[variant];
42
+ const canExpand = children != null;
43
+ return /* @__PURE__ */ jsxs("div", {
44
+ className: clsx("flex flex-col rounded-md border overflow-hidden", className),
45
+ style: {
46
+ borderColor: theme.border,
47
+ background: theme.bg
48
+ },
49
+ children: [/* @__PURE__ */ jsxs("button", {
50
+ type: "button",
51
+ "aria-expanded": canExpand ? isOpen : void 0,
52
+ disabled: !canExpand,
53
+ className: clsx("flex items-center select-none text-left w-full appearance-none border-none bg-transparent", sizes.button, canExpand && "cursor-pointer"),
54
+ onClick: () => {
55
+ if (canExpand) setIsOpen(!isOpen);
56
+ },
57
+ children: [
58
+ icon ? /* @__PURE__ */ jsx("span", {
59
+ className: "w-4 h-4 shrink-0 text-center leading-4",
60
+ children: icon
61
+ }) : /* @__PURE__ */ jsx(Eyebrow, {
62
+ className: "shrink-0",
63
+ style: { color: TEXT_FAINT },
64
+ children: "Tool"
65
+ }),
66
+ /* @__PURE__ */ jsx("span", {
67
+ className: clsx("shrink-0 font-aai-mono font-medium", sizes.title, pending && "tool-shimmer"),
68
+ style: { color: theme.text },
69
+ children: title
70
+ }),
71
+ /* @__PURE__ */ jsx("span", {
72
+ className: clsx("font-aai-mono truncate flex-1 min-w-0", sizes.title),
73
+ style: { color: TEXT_FAINT },
74
+ children: detail
75
+ }),
76
+ canExpand && /* @__PURE__ */ jsx("span", {
77
+ className: clsx("shrink-0 transition-transform duration-150", sizes.chevron, isOpen && "rotate-90"),
78
+ style: { color: "#6F6A60" },
79
+ children: "▶"
80
+ })
81
+ ]
82
+ }), isOpen && canExpand && /* @__PURE__ */ jsx("div", {
83
+ className: "border-t max-h-64 overflow-auto",
84
+ style: {
85
+ borderColor: theme.border,
86
+ background: theme.surface
87
+ },
88
+ children
89
+ })]
90
+ });
91
+ }
92
+ //#endregion
8
93
  //#region components/tool-config-context.ts
9
94
  /**
10
95
  * Context for tool display configuration. Installed by `client()` from
@@ -29,10 +114,10 @@ function formatResult(result) {
29
114
  return parsed === result ? result : JSON.stringify(parsed, null, 2);
30
115
  }
31
116
  /**
32
- * Renders a tool invocation as the design system's console row: a small
33
- * outlined "TOOL" chip (or the tool's configured icon), the tool name in
34
- * mono, a truncated args preview, and a rotating chevron that expands the
35
- * formatted result.
117
+ * Renders a tool invocation as the design system's console row (see
118
+ * `ToolCallRow`): a small outlined "TOOL" chip (or the tool's configured
119
+ * icon), the tool name in mono, a truncated args preview, and a rotating
120
+ * chevron that expands the formatted result.
36
121
  *
37
122
  * Tool display is configured via `ToolConfigContext`. If no config is found
38
123
  * for a tool name, the raw tool name is shown as the title.
@@ -50,7 +135,6 @@ function formatResult(result) {
50
135
  * @internal Not exported from the package — rendered by `MessageList`.
51
136
  */
52
137
  const ToolCallBlock = memo(function ToolCallBlock({ toolCall, className }) {
53
- const [isOpen, setIsOpen] = useState(false);
54
138
  const theme = useTheme();
55
139
  const toolConfig = useToolConfig();
56
140
  const config = Object.hasOwn(toolConfig, toolCall.name) ? toolConfig[toolCall.name] : void 0;
@@ -59,75 +143,34 @@ const ToolCallBlock = memo(function ToolCallBlock({ toolCall, className }) {
59
143
  const icon = config?.icon;
60
144
  const canExpand = !isPending && Boolean(toolCall.result);
61
145
  const formatted = useMemo(() => toolCall.result ? formatResult(toolCall.result) : "", [toolCall.result]);
62
- const subtitle = useMemo(() => {
63
- const args = toolCall.args;
64
- if (toolCall.name === "run_code" && args.code) return truncate(String(args.code).split("\n")[0] ?? "");
65
- for (const key of [
66
- "query",
67
- "url",
68
- "question"
69
- ]) if (args[key]) return String(args[key]);
70
- return truncate(JSON.stringify(args));
71
- }, [toolCall.name, toolCall.args]);
72
- return /* @__PURE__ */ jsxs("div", {
73
- className: clsx("flex flex-col rounded-md border overflow-hidden", className),
74
- style: {
75
- borderColor: theme.border,
76
- background: theme.bg
77
- },
78
- children: [/* @__PURE__ */ jsxs("button", {
79
- type: "button",
80
- "aria-expanded": canExpand ? isOpen : void 0,
81
- disabled: isPending,
82
- className: clsx("flex items-center gap-2.5 px-3.5 py-2.5 select-none text-left w-full appearance-none border-none bg-transparent", canExpand && "cursor-pointer"),
83
- onClick: () => {
84
- if (canExpand) setIsOpen(!isOpen);
85
- },
86
- children: [
87
- icon ? /* @__PURE__ */ jsx("span", {
88
- className: "w-4 h-4 shrink-0 text-center leading-4",
89
- children: icon
90
- }) : /* @__PURE__ */ jsx(Eyebrow, {
91
- className: "shrink-0",
92
- style: { color: TEXT_FAINT },
93
- children: "Tool"
94
- }),
95
- /* @__PURE__ */ jsx("span", {
96
- className: clsx("font-aai-mono text-[13px] font-medium", isPending && "tool-shimmer"),
97
- style: { color: theme.text },
98
- children: title
99
- }),
100
- /* @__PURE__ */ jsx("span", {
101
- className: "font-aai-mono text-[13px] truncate flex-1 min-w-0",
102
- style: { color: TEXT_FAINT },
103
- children: subtitle
104
- }),
105
- canExpand && /* @__PURE__ */ jsx("span", {
106
- className: clsx("text-[10px] shrink-0 transition-transform duration-150", isOpen && "rotate-90"),
107
- style: { color: "#6F6A60" },
108
- children: "▶"
109
- })
110
- ]
111
- }), isOpen && /* @__PURE__ */ jsxs("div", {
112
- className: "border-t max-h-64 overflow-auto",
146
+ return /* @__PURE__ */ jsx(ToolCallRow, {
147
+ title,
148
+ detail: useMemo(() => {
149
+ const args = toolCall.args;
150
+ if (toolCall.name === "run_code" && args.code) return truncate(String(args.code).split("\n")[0] ?? "");
151
+ for (const key of [
152
+ "query",
153
+ "url",
154
+ "question"
155
+ ]) if (args[key]) return String(args[key]);
156
+ return truncate(JSON.stringify(args));
157
+ }, [toolCall.name, toolCall.args]),
158
+ pending: isPending,
159
+ icon,
160
+ className,
161
+ children: canExpand ? /* @__PURE__ */ jsxs(Fragment, { children: [toolCall.name === "run_code" && Boolean(toolCall.args.code) && /* @__PURE__ */ jsx("pre", {
162
+ className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word border-b",
113
163
  style: {
114
- borderColor: theme.border,
115
- background: theme.surface
164
+ color: theme.text,
165
+ borderColor: theme.border
116
166
  },
117
- children: [toolCall.name === "run_code" && Boolean(toolCall.args.code) && /* @__PURE__ */ jsx("pre", {
118
- className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word border-b",
119
- style: {
120
- color: theme.text,
121
- borderColor: theme.border
122
- },
123
- children: String(toolCall.args.code)
124
- }), formatted && /* @__PURE__ */ jsx("pre", {
125
- className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word",
126
- style: { color: "#57534B" },
127
- children: formatted
128
- })]
129
- })]
167
+ children: String(toolCall.args.code)
168
+ }), formatted && /* @__PURE__ */ jsx("pre", {
169
+ className: "font-aai-mono text-xs px-3.5 py-3 m-0 whitespace-pre-wrap wrap-break-word",
170
+ style: { color: "#57534B" },
171
+ children: formatted
172
+ })] }) : void 0
130
173
  });
131
174
  });
132
175
  //#endregion
133
- export { ToolConfigContext as n, ToolCallBlock as t };
176
+ export { ToolConfigContext as n, ToolCallRow as r, ToolCallBlock as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alexkroman1/aai-ui",
3
- "version": "5.5.1",
3
+ "version": "5.7.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -21,8 +21,9 @@
21
21
  "partysocket": "^1.3.0",
22
22
  "react-markdown": "^10.1.0",
23
23
  "remark-gfm": "^4.0.1",
24
+ "use-stick-to-bottom": "^1.1.6",
24
25
  "use-sync-external-store": "^1.6.0",
25
- "@alexkroman1/aai": "5.5.1"
26
+ "@alexkroman1/aai": "5.7.0"
26
27
  },
27
28
  "peerDependencies": {
28
29
  "react": "^19.0.0",