cortena-ui 1.5.0 → 1.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.
- package/CHANGELOG.md +183 -0
- package/README.md +122 -2
- package/dist/agent-chat/bridge.js +8 -4
- package/dist/agent-chat/bridge.js.map +1 -1
- package/dist/agent-chat/session.d.ts +22 -1
- package/dist/agent-chat/session.js +164 -8
- package/dist/agent-chat/session.js.map +1 -1
- package/dist/agent-chat/step-label.d.ts +99 -0
- package/dist/agent-chat/step-label.js +116 -0
- package/dist/agent-chat/step-label.js.map +1 -0
- package/dist/agent-chat/store.d.ts +90 -4
- package/dist/agent-chat/store.js +421 -28
- package/dist/agent-chat/store.js.map +1 -1
- package/dist/agent-chat/types.d.ts +101 -0
- package/dist/agent-chat/types.js.map +1 -1
- package/dist/agent-chat.d.ts +6 -5
- package/dist/agent-chat.js +5 -4
- package/dist/components/agent-chat-popup.d.ts +2 -2
- package/dist/components/agent-chat-popup.js.map +1 -1
- package/dist/components/agent-chat.d.ts +191 -15
- package/dist/components/agent-chat.js +300 -32
- package/dist/components/agent-chat.js.map +1 -1
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/toast.d.ts +1 -1
- package/package.json +3 -2
- package/src/agent-chat/bridge.ts +17 -5
- package/src/agent-chat/session.ts +193 -6
- package/src/agent-chat/step-label.ts +177 -0
- package/src/agent-chat/store.ts +692 -24
- package/src/agent-chat/types.ts +106 -0
- package/src/components/agent-chat-popup.tsx +3 -3
- package/src/components/agent-chat.tsx +737 -147
- package/src/entries/agent-chat.ts +38 -2
|
@@ -9,10 +9,11 @@ import { Textarea } from "./textarea.js";
|
|
|
9
9
|
import { Markdown } from "./markdown.js";
|
|
10
10
|
import { A2UIErrorCard } from "../a2ui/views.js";
|
|
11
11
|
import { A2UIRenderer } from "../a2ui/renderer.js";
|
|
12
|
+
import { isGenericLabel, truncate } from "../agent-chat/step-label.js";
|
|
12
13
|
import { isHiddenMessage } from "../agent-chat/session.js";
|
|
13
14
|
import { useAgentChat } from "../agent-chat/store.js";
|
|
14
15
|
import { sessionTitle } from "../agent-chat/types.js";
|
|
15
|
-
import { ChevronDown, ChevronRight, MessageSquarePlus, Send, ShieldCheck, ShieldX, Sparkles, Square } from "lucide-react";
|
|
16
|
+
import { Check, ChevronDown, ChevronRight, MessageSquarePlus, Send, ShieldCheck, ShieldX, Sparkles, Square, X } from "lucide-react";
|
|
16
17
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
17
18
|
import * as React from "react";
|
|
18
19
|
//#region src/components/agent-chat.tsx
|
|
@@ -36,6 +37,13 @@ import * as React from "react";
|
|
|
36
37
|
* and it replaces the card, return nothing and the default card is drawn.
|
|
37
38
|
*/
|
|
38
39
|
/**
|
|
40
|
+
* `useLayoutEffect` in a browser, `useEffect` on a server render, where React
|
|
41
|
+
* warns about the former. The scroll restore after a prepend has to run
|
|
42
|
+
* before the paint: an effect after it shows one frame at the old `scrollTop`,
|
|
43
|
+
* with the page that just landed pushing the reader's place off the screen.
|
|
44
|
+
*/
|
|
45
|
+
const useLayoutEffectSafe = typeof window === "undefined" ? React.useEffect : React.useLayoutEffect;
|
|
46
|
+
/**
|
|
39
47
|
* Should the list jump back to the bottom after its content grew?
|
|
40
48
|
*
|
|
41
49
|
* Judged against the height BEFORE the growth. Measuring afterwards asks "is
|
|
@@ -74,6 +82,98 @@ function AgentThinking({ text, defaultOpen, streaming }) {
|
|
|
74
82
|
});
|
|
75
83
|
}
|
|
76
84
|
/**
|
|
85
|
+
* How long a step runs before it starts saying how long it has been running.
|
|
86
|
+
*
|
|
87
|
+
* Under this, a timer is noise: every call takes a second or two and a number
|
|
88
|
+
* ticking beside each one is a fidget spinner. Over it, the user has begun to
|
|
89
|
+
* wonder whether anything is happening at all, and the number is the answer.
|
|
90
|
+
*/
|
|
91
|
+
const ELAPSED_AFTER_MS = 8e3;
|
|
92
|
+
/**
|
|
93
|
+
* `Date.now()`, re-read once a second while `active`.
|
|
94
|
+
*
|
|
95
|
+
* The interval only exists while something is running, so a finished chat sits
|
|
96
|
+
* idle rather than re-rendering the whole transcript once a second for ever.
|
|
97
|
+
*/
|
|
98
|
+
function useTickingNow(active) {
|
|
99
|
+
const [now, setNow] = React.useState(() => Date.now());
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
if (!active) return;
|
|
102
|
+
setNow(Date.now());
|
|
103
|
+
const id = setInterval(() => setNow(Date.now()), 1e3);
|
|
104
|
+
return () => clearInterval(id);
|
|
105
|
+
}, [active]);
|
|
106
|
+
return now;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* What the agent is doing, one plain sentence per tool call (SKILLS-9).
|
|
110
|
+
*
|
|
111
|
+
* This is the surface a person watches while they wait, and it is deliberately
|
|
112
|
+
* not the tool strip: the strip is the record — names, arguments, output, the
|
|
113
|
+
* MCP App seam — and reading it requires knowing what `broker_invoke` is. The
|
|
114
|
+
* strip stays at the foot for anyone who wants the detail.
|
|
115
|
+
*
|
|
116
|
+
* NOT a live region of its own. The strip is drawn inside the transcript, and
|
|
117
|
+
* the transcript is already `role="log" aria-live="polite"` — two nested live
|
|
118
|
+
* regions is one announcement per change from the inner one and another from
|
|
119
|
+
* the outer, which is how a screen reader ends up saying every step twice.
|
|
120
|
+
* `aria-label` stays: it names the group for anyone who navigates to it.
|
|
121
|
+
*/
|
|
122
|
+
function AgentSteps({ steps }) {
|
|
123
|
+
const now = useTickingNow(steps.some((step) => step.status === "running"));
|
|
124
|
+
if (steps.length === 0) return null;
|
|
125
|
+
return /* @__PURE__ */ jsx("div", {
|
|
126
|
+
"data-slot": "agent-chat-steps",
|
|
127
|
+
"aria-label": "Progress",
|
|
128
|
+
className: "mt-1 flex flex-col gap-0.5",
|
|
129
|
+
children: steps.map((step) => {
|
|
130
|
+
const elapsed = step.status === "running" ? now - step.startedAt : 0;
|
|
131
|
+
const seconds = elapsed >= ELAPSED_AFTER_MS ? Math.floor(elapsed / 1e3) : 0;
|
|
132
|
+
const held = step.status === "running" && isGenericLabel(step.toolName, step.label);
|
|
133
|
+
return /* @__PURE__ */ jsxs("p", {
|
|
134
|
+
"data-slot": "agent-chat-step",
|
|
135
|
+
"data-status": step.status,
|
|
136
|
+
"data-tool": truncate(step.toolName, 80),
|
|
137
|
+
...held ? { "aria-hidden": true } : {},
|
|
138
|
+
className: cn("flex items-start gap-2 text-[length:var(--ds-text-caption-lg)]", step.status === "error" ? "text-[color:var(--ds-destructive)]" : "text-[color:var(--ds-muted-foreground)]"),
|
|
139
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
140
|
+
"aria-hidden": true,
|
|
141
|
+
className: "mt-0.5 inline-flex size-3.5 shrink-0 items-center justify-center",
|
|
142
|
+
children: step.status === "running" ? /* @__PURE__ */ jsx(Spinner, {
|
|
143
|
+
size: 12,
|
|
144
|
+
"aria-hidden": true
|
|
145
|
+
}) : step.status === "error" ? /* @__PURE__ */ jsx(X, { className: "size-3" }) : step.status === "stopped" ? /* @__PURE__ */ jsx(Square, { className: "size-2.5" }) : step.status === "paused" ? /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-[color:var(--ds-muted-foreground)]" }) : /* @__PURE__ */ jsx(Check, { className: "size-3 text-[color:var(--ds-success)]" })
|
|
146
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
147
|
+
className: "min-w-0",
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsx("span", {
|
|
150
|
+
"data-slot": "agent-chat-step-label",
|
|
151
|
+
children: step.label
|
|
152
|
+
}, step.label),
|
|
153
|
+
seconds > 0 ? /* @__PURE__ */ jsx("span", {
|
|
154
|
+
"aria-hidden": true,
|
|
155
|
+
"data-slot": "agent-chat-step-elapsed",
|
|
156
|
+
children: ` · ${seconds}s`
|
|
157
|
+
}) : null,
|
|
158
|
+
step.status === "stopped" ? /* @__PURE__ */ jsx("span", {
|
|
159
|
+
"data-slot": "agent-chat-step-note",
|
|
160
|
+
children: " — Stopped"
|
|
161
|
+
}) : null,
|
|
162
|
+
step.status === "paused" ? /* @__PURE__ */ jsx("span", {
|
|
163
|
+
"data-slot": "agent-chat-step-note",
|
|
164
|
+
children: " — Waiting for your approval"
|
|
165
|
+
}) : null,
|
|
166
|
+
step.status === "error" && step.errorMessage ? /* @__PURE__ */ jsx("span", {
|
|
167
|
+
"data-slot": "agent-chat-step-error",
|
|
168
|
+
children: ` — ${step.errorMessage}`
|
|
169
|
+
}) : null
|
|
170
|
+
]
|
|
171
|
+
})]
|
|
172
|
+
}, step.id);
|
|
173
|
+
})
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
77
177
|
* Every block of one message, in arrival order.
|
|
78
178
|
*
|
|
79
179
|
* A block carrying a transport `error` — an unclosed fence, a body that did not
|
|
@@ -162,7 +262,7 @@ function AgentMessageBubble({ message, agentName, streaming, onAction }) {
|
|
|
162
262
|
* (`**bo`) looks worse than its raw form — so the streaming view is plain text
|
|
163
263
|
* under `whitespace-pre-wrap` and `Markdown` takes over on `final`.
|
|
164
264
|
*/
|
|
165
|
-
function AgentStreamingBubble({ agentName, text, thinkingText, a2ui }) {
|
|
265
|
+
function AgentStreamingBubble({ agentName, text, thinkingText, a2ui, steps }) {
|
|
166
266
|
return /* @__PURE__ */ jsxs("div", {
|
|
167
267
|
"data-slot": "agent-chat-streaming",
|
|
168
268
|
className: "flex w-full gap-3",
|
|
@@ -173,6 +273,7 @@ function AgentStreamingBubble({ agentName, text, thinkingText, a2ui }) {
|
|
|
173
273
|
className: cn("text-[length:var(--ds-text-caption)] font-semibold", "text-[color:var(--ds-foreground)]"),
|
|
174
274
|
children: agentName
|
|
175
275
|
}),
|
|
276
|
+
/* @__PURE__ */ jsx(AgentSteps, { steps: steps ?? [] }),
|
|
176
277
|
thinkingText ? /* @__PURE__ */ jsx(AgentThinking, {
|
|
177
278
|
text: thinkingText,
|
|
178
279
|
defaultOpen: true,
|
|
@@ -326,18 +427,68 @@ function AgentApprovalCard({ request, onResolve }) {
|
|
|
326
427
|
]
|
|
327
428
|
});
|
|
328
429
|
}
|
|
329
|
-
|
|
430
|
+
/**
|
|
431
|
+
* Two ways in, one surface. The hook is called in a component of its own so
|
|
432
|
+
* a host that hands over `chat` never calls it — and so a mount that switches
|
|
433
|
+
* from one mode to the other remounts rather than changing its hook order.
|
|
434
|
+
*/
|
|
435
|
+
function AgentChat(props) {
|
|
436
|
+
if (props.chat) {
|
|
437
|
+
const { chat, client: _client, storageScope: _scope, ephemeral: _e, thinking: _t, ...rest } = props;
|
|
438
|
+
return /* @__PURE__ */ jsx(AgentChatSurface, {
|
|
439
|
+
...rest,
|
|
440
|
+
chat
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
return /* @__PURE__ */ jsx(AgentChatOwned, { ...props });
|
|
444
|
+
}
|
|
445
|
+
function AgentChatOwned({ client, storageScope, ephemeral, thinking, chat: _chat, ...rest }) {
|
|
330
446
|
const chat = useAgentChat({
|
|
331
447
|
client,
|
|
332
448
|
storageScope,
|
|
333
449
|
...ephemeral === void 0 ? {} : { ephemeral },
|
|
334
450
|
...thinking === void 0 ? {} : { thinking }
|
|
335
451
|
});
|
|
452
|
+
return /* @__PURE__ */ jsx(AgentChatSurface, {
|
|
453
|
+
...rest,
|
|
454
|
+
chat
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/** The default at the top of the transcript: a button, when there is more. */
|
|
458
|
+
function DefaultLoadOlder({ hasMore, loading, onLoad }) {
|
|
459
|
+
if (!hasMore) return null;
|
|
460
|
+
return /* @__PURE__ */ jsx("div", {
|
|
461
|
+
className: "flex justify-center",
|
|
462
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
463
|
+
size: "sm",
|
|
464
|
+
variant: "ghost",
|
|
465
|
+
"data-slot": "agent-chat-load-older",
|
|
466
|
+
disabled: loading,
|
|
467
|
+
onClick: onLoad,
|
|
468
|
+
children: loading ? "Loading…" : "Load older messages"
|
|
469
|
+
})
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/** Distance from the bottom, in pixels, within which new content re-pins the view. */
|
|
473
|
+
const NEAR_BOTTOM_PX = 100;
|
|
474
|
+
function AgentChatSurface({ chat, agentName, attachmentsSlot, renderToolCall, headerSlot, hideHeader, components, classNames, autoLoadOlder = false, className, ...props }) {
|
|
336
475
|
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
|
337
476
|
const [value, setValue] = React.useState("");
|
|
338
477
|
const scrollRef = React.useRef(null);
|
|
339
478
|
const contentRef = React.useRef(null);
|
|
479
|
+
const sentinelRef = React.useRef(null);
|
|
340
480
|
const prevHeightRef = React.useRef(0);
|
|
481
|
+
/** Whether the reader was at the bottom at the last scroll; the smooth re-pin reads it. */
|
|
482
|
+
const nearBottomRef = React.useRef(true);
|
|
483
|
+
/** The scroll height saved before a page of older history is prepended; 0 when none is in flight. */
|
|
484
|
+
const prependHeightRef = React.useRef(0);
|
|
485
|
+
/** Set by a prepend restore; the resize observer sits out the one notification the prepend produces. */
|
|
486
|
+
const holdRef = React.useRef(false);
|
|
487
|
+
const prevMessageCountRef = React.useRef(0);
|
|
488
|
+
const visible = chat.messages.filter((message) => !isHiddenMessage(message));
|
|
489
|
+
const Empty = components?.Empty;
|
|
490
|
+
const showEmpty = visible.length === 0 && !chat.streaming && !chat.error;
|
|
491
|
+
const showTranscript = !(showEmpty && Empty);
|
|
341
492
|
/**
|
|
342
493
|
* Re-pin to the bottom whenever the content grows or the viewport shrinks.
|
|
343
494
|
* A message keeps growing after it mounts — markdown settles, the tool strip
|
|
@@ -346,12 +497,21 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
346
497
|
*
|
|
347
498
|
* Jumping rather than smooth-scrolling: this fires many times a second during
|
|
348
499
|
* streaming, and queued smooth animations fight each other and lose.
|
|
500
|
+
*
|
|
501
|
+
* Keyed on `showTranscript`: the scroller is unmounted while a host's Empty
|
|
502
|
+
* holds the region, and the observer has to attach to the one that mounts
|
|
503
|
+
* when the first message arrives.
|
|
349
504
|
*/
|
|
350
505
|
React.useEffect(() => {
|
|
351
506
|
const scrollEl = scrollRef.current;
|
|
352
507
|
const contentEl = contentRef.current;
|
|
353
508
|
if (!scrollEl || !contentEl || typeof ResizeObserver === "undefined") return;
|
|
354
509
|
const observer = new ResizeObserver(() => {
|
|
510
|
+
if (holdRef.current) {
|
|
511
|
+
holdRef.current = false;
|
|
512
|
+
prevHeightRef.current = scrollEl.scrollHeight;
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
355
515
|
const { scrollTop, scrollHeight, clientHeight } = scrollEl;
|
|
356
516
|
const stick = shouldStickToBottom({
|
|
357
517
|
prevScrollHeight: prevHeightRef.current,
|
|
@@ -363,8 +523,84 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
363
523
|
});
|
|
364
524
|
observer.observe(contentEl);
|
|
365
525
|
observer.observe(scrollEl);
|
|
526
|
+
const onScroll = () => {
|
|
527
|
+
nearBottomRef.current = scrollEl.scrollHeight - scrollEl.scrollTop - scrollEl.clientHeight < NEAR_BOTTOM_PX;
|
|
528
|
+
};
|
|
529
|
+
scrollEl.addEventListener("scroll", onScroll, { passive: true });
|
|
530
|
+
return () => {
|
|
531
|
+
observer.disconnect();
|
|
532
|
+
scrollEl.removeEventListener("scroll", onScroll);
|
|
533
|
+
};
|
|
534
|
+
}, [showTranscript]);
|
|
535
|
+
/**
|
|
536
|
+
* Page older history as the reader reaches the top, not only on a click —
|
|
537
|
+
* when the host asks for it with `autoLoadOlder`. The sentinel is the first
|
|
538
|
+
* child of the transcript; when it scrolls into the viewport and there is
|
|
539
|
+
* more, the next page is asked for.
|
|
540
|
+
*/
|
|
541
|
+
const loadOlderChat = chat.loadOlder;
|
|
542
|
+
const loadOlder = React.useCallback(() => {
|
|
543
|
+
const height = scrollRef.current?.scrollHeight ?? 0;
|
|
544
|
+
if (loadOlderChat() === true && height > 0) prependHeightRef.current = height;
|
|
545
|
+
}, [loadOlderChat]);
|
|
546
|
+
React.useEffect(() => {
|
|
547
|
+
const sentinel = sentinelRef.current;
|
|
548
|
+
const scrollEl = scrollRef.current;
|
|
549
|
+
if (!autoLoadOlder || !sentinel || !scrollEl || typeof IntersectionObserver === "undefined") return;
|
|
550
|
+
const observer = new IntersectionObserver((entries) => {
|
|
551
|
+
if (!entries[0]?.isIntersecting) return;
|
|
552
|
+
if (scrollEl.scrollHeight <= scrollEl.clientHeight) return;
|
|
553
|
+
if (chat.hasMoreHistory && !chat.loadingHistory) loadOlder();
|
|
554
|
+
}, {
|
|
555
|
+
root: scrollEl,
|
|
556
|
+
threshold: .1
|
|
557
|
+
});
|
|
558
|
+
observer.observe(sentinel);
|
|
366
559
|
return () => observer.disconnect();
|
|
367
|
-
}, [
|
|
560
|
+
}, [
|
|
561
|
+
autoLoadOlder,
|
|
562
|
+
chat.hasMoreHistory,
|
|
563
|
+
chat.loadingHistory,
|
|
564
|
+
loadOlder,
|
|
565
|
+
showTranscript
|
|
566
|
+
]);
|
|
567
|
+
/**
|
|
568
|
+
* After a prepend, put the reader back where they were; after an append,
|
|
569
|
+
* follow the newest message — smoothly, once per message rather than per
|
|
570
|
+
* resize, which is why this is separate from the observer above and why it
|
|
571
|
+
* is NOT keyed on the stream text: growth during a run is the observer's,
|
|
572
|
+
* and a smooth scroll queued per delta fought it and lost.
|
|
573
|
+
*
|
|
574
|
+
* A layout effect, so the restore lands before the paint; an effect after
|
|
575
|
+
* it drew one frame at the old `scrollTop` with the new page on top.
|
|
576
|
+
*/
|
|
577
|
+
useLayoutEffectSafe(() => {
|
|
578
|
+
const scrollEl = scrollRef.current;
|
|
579
|
+
if (!scrollEl) return;
|
|
580
|
+
const prevCount = prevMessageCountRef.current;
|
|
581
|
+
const count = chat.messages.length;
|
|
582
|
+
prevMessageCountRef.current = count;
|
|
583
|
+
const saved = prependHeightRef.current;
|
|
584
|
+
if (saved > 0 && !chat.loadingHistory) {
|
|
585
|
+
prependHeightRef.current = 0;
|
|
586
|
+
if (count > prevCount && prevCount > 0) {
|
|
587
|
+
const delta = scrollEl.scrollHeight - saved;
|
|
588
|
+
if (delta > 0) {
|
|
589
|
+
scrollEl.scrollTop += delta;
|
|
590
|
+
prevHeightRef.current = scrollEl.scrollHeight;
|
|
591
|
+
holdRef.current = true;
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
if (count !== prevCount && nearBottomRef.current) {
|
|
597
|
+
if (typeof scrollEl.scrollTo === "function") scrollEl.scrollTo({
|
|
598
|
+
top: scrollEl.scrollHeight,
|
|
599
|
+
behavior: "smooth"
|
|
600
|
+
});
|
|
601
|
+
else scrollEl.scrollTop = scrollEl.scrollHeight;
|
|
602
|
+
}
|
|
603
|
+
}, [chat.messages, chat.loadingHistory]);
|
|
368
604
|
const submit = React.useCallback(() => {
|
|
369
605
|
const trimmed = value.trim();
|
|
370
606
|
if (!trimmed || chat.streaming) return;
|
|
@@ -385,15 +621,19 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
385
621
|
}, [chat, submit]);
|
|
386
622
|
const toolEntries = chat.toolOrder.map((id) => chat.tools[id]).filter((entry) => entry !== void 0);
|
|
387
623
|
const showTools = toolEntries.length > 0 || chat.approvals.length > 0;
|
|
388
|
-
const
|
|
624
|
+
const Message = components?.Message;
|
|
625
|
+
const Streaming = components?.Streaming;
|
|
626
|
+
const LoadOlder = components?.LoadOlder ?? DefaultLoadOlder;
|
|
627
|
+
const Tools = components?.Tools;
|
|
628
|
+
const Composer = components?.Composer;
|
|
389
629
|
return /* @__PURE__ */ jsxs("div", {
|
|
390
630
|
"data-slot": "agent-chat",
|
|
391
631
|
className: cn("flex h-full min-h-0 w-full flex-col bg-[var(--ds-background)]", className),
|
|
392
632
|
...props,
|
|
393
633
|
children: [
|
|
394
|
-
/* @__PURE__ */ jsxs("div", {
|
|
634
|
+
hideHeader ? null : /* @__PURE__ */ jsxs("div", {
|
|
395
635
|
"data-slot": "agent-chat-header",
|
|
396
|
-
className: cn("flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2"),
|
|
636
|
+
className: cn("flex shrink-0 items-center gap-2 border-b border-[var(--ds-border-subtle)] px-3 py-2", classNames?.header),
|
|
397
637
|
children: [
|
|
398
638
|
/* @__PURE__ */ jsx(Button, {
|
|
399
639
|
size: "sm",
|
|
@@ -423,7 +663,7 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
423
663
|
})
|
|
424
664
|
]
|
|
425
665
|
}),
|
|
426
|
-
drawerOpen ? /* @__PURE__ */ jsx(AgentSessionDrawer, {
|
|
666
|
+
drawerOpen && !hideHeader ? /* @__PURE__ */ jsx(AgentSessionDrawer, {
|
|
427
667
|
sessions: chat.sessions,
|
|
428
668
|
current: chat.sessionKey,
|
|
429
669
|
loading: chat.loadingSessions,
|
|
@@ -432,30 +672,29 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
432
672
|
setDrawerOpen(false);
|
|
433
673
|
}
|
|
434
674
|
}) : null,
|
|
435
|
-
/* @__PURE__ */ jsx("div", {
|
|
675
|
+
showTranscript ? /* @__PURE__ */ jsx("div", {
|
|
436
676
|
ref: scrollRef,
|
|
437
677
|
"data-slot": "agent-chat-scroll",
|
|
438
|
-
className: "min-h-0 flex-1 overflow-x-hidden overflow-y-auto",
|
|
678
|
+
className: cn("min-h-0 flex-1 overflow-x-hidden overflow-y-auto [overflow-anchor:none]", classNames?.scroll),
|
|
439
679
|
children: /* @__PURE__ */ jsxs("div", {
|
|
440
680
|
ref: contentRef,
|
|
441
681
|
role: "log",
|
|
442
682
|
"aria-live": "polite",
|
|
443
683
|
"aria-label": `${agentName} chat`,
|
|
444
|
-
className: "flex min-w-0 flex-col gap-5 px-4 py-4",
|
|
684
|
+
className: cn("relative flex min-w-0 flex-col", classNames?.transcript ?? "gap-5 px-4 py-4"),
|
|
445
685
|
children: [
|
|
686
|
+
/* @__PURE__ */ jsx("div", {
|
|
687
|
+
ref: sentinelRef,
|
|
688
|
+
"data-slot": "agent-chat-sentinel",
|
|
689
|
+
className: classNames?.sentinel ?? "absolute inset-x-0 top-0 h-px"
|
|
690
|
+
}),
|
|
446
691
|
chat.error ? /* @__PURE__ */ jsx(ErrorBanner, { message: chat.error }) : null,
|
|
447
|
-
chat.hasMoreHistory ? /* @__PURE__ */ jsx(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
variant: "ghost",
|
|
452
|
-
"data-slot": "agent-chat-load-older",
|
|
453
|
-
disabled: chat.loadingHistory,
|
|
454
|
-
onClick: chat.loadOlder,
|
|
455
|
-
children: chat.loadingHistory ? "Loading…" : "Load older messages"
|
|
456
|
-
})
|
|
692
|
+
chat.hasMoreHistory || chat.loadingHistory ? /* @__PURE__ */ jsx(LoadOlder, {
|
|
693
|
+
hasMore: chat.hasMoreHistory,
|
|
694
|
+
loading: chat.loadingHistory,
|
|
695
|
+
onLoad: loadOlder
|
|
457
696
|
}) : null,
|
|
458
|
-
|
|
697
|
+
showEmpty ? /* @__PURE__ */ jsxs("p", {
|
|
459
698
|
"data-slot": "agent-chat-empty",
|
|
460
699
|
className: "text-[length:var(--ds-text-caption-lg)] text-[color:var(--ds-muted-foreground)]",
|
|
461
700
|
children: [
|
|
@@ -464,24 +703,48 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
464
703
|
" anything about this screen."
|
|
465
704
|
]
|
|
466
705
|
}) : null,
|
|
467
|
-
visible.map((message) => /* @__PURE__ */ jsx(
|
|
706
|
+
visible.map((message, index) => Message ? /* @__PURE__ */ jsx(Message, {
|
|
707
|
+
message,
|
|
708
|
+
index,
|
|
709
|
+
messages: visible,
|
|
710
|
+
agentName,
|
|
711
|
+
sessionKey: chat.sessionKey,
|
|
712
|
+
streaming: chat.streaming,
|
|
713
|
+
onAction: chat.sendAction
|
|
714
|
+
}, message.id) : /* @__PURE__ */ jsx(AgentMessageBubble, {
|
|
468
715
|
message,
|
|
469
716
|
agentName,
|
|
470
717
|
streaming: chat.streaming,
|
|
471
718
|
onAction: chat.sendAction
|
|
472
719
|
}, message.id)),
|
|
473
|
-
chat.streaming ? /* @__PURE__ */ jsx(
|
|
720
|
+
chat.streaming ? Streaming ? /* @__PURE__ */ jsx(Streaming, {
|
|
474
721
|
agentName,
|
|
475
722
|
text: chat.streamText,
|
|
476
723
|
thinkingText: chat.streamThinkingText,
|
|
477
|
-
a2ui: chat.streamA2ui
|
|
478
|
-
|
|
724
|
+
a2ui: chat.streamA2ui,
|
|
725
|
+
steps: chat.steps
|
|
726
|
+
}) : /* @__PURE__ */ jsx(AgentStreamingBubble, {
|
|
727
|
+
agentName,
|
|
728
|
+
text: chat.streamText,
|
|
729
|
+
thinkingText: chat.streamThinkingText,
|
|
730
|
+
a2ui: chat.streamA2ui,
|
|
731
|
+
steps: chat.steps
|
|
732
|
+
}) : chat.steps.length > 0 ? /* @__PURE__ */ jsx(AgentSteps, { steps: chat.steps }) : null
|
|
479
733
|
]
|
|
480
734
|
})
|
|
481
|
-
}),
|
|
482
|
-
|
|
735
|
+
}) : Empty ? /* @__PURE__ */ jsx(Empty, {
|
|
736
|
+
agentName,
|
|
737
|
+
loadingHistory: chat.loadingHistory
|
|
738
|
+
}) : null,
|
|
739
|
+
Tools ? /* @__PURE__ */ jsx(Tools, {
|
|
740
|
+
entries: toolEntries,
|
|
741
|
+
approvals: chat.approvals,
|
|
742
|
+
streaming: chat.streaming,
|
|
743
|
+
renderToolCall,
|
|
744
|
+
onResolveApproval: chat.resolveApproval
|
|
745
|
+
}) : showTools ? /* @__PURE__ */ jsxs("div", {
|
|
483
746
|
"data-slot": "agent-chat-tools",
|
|
484
|
-
className: "flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2",
|
|
747
|
+
className: cn("flex shrink-0 flex-col gap-1.5 border-t border-[var(--ds-border-subtle)] px-3 py-2", classNames?.tools),
|
|
485
748
|
children: [
|
|
486
749
|
toolEntries.length > 0 ? /* @__PURE__ */ jsx(Badge, {
|
|
487
750
|
variant: "neutral",
|
|
@@ -506,9 +769,14 @@ function AgentChat({ client, agentName, storageScope, ephemeral, thinking, attac
|
|
|
506
769
|
className: "shrink-0 px-3",
|
|
507
770
|
children: attachmentsSlot
|
|
508
771
|
}) : null,
|
|
509
|
-
/* @__PURE__ */
|
|
772
|
+
Composer ? /* @__PURE__ */ jsx(Composer, {
|
|
773
|
+
agentName,
|
|
774
|
+
streaming: chat.streaming,
|
|
775
|
+
send: chat.send,
|
|
776
|
+
abort: chat.abort
|
|
777
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
510
778
|
"data-slot": "agent-chat-composer",
|
|
511
|
-
className: "flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3",
|
|
779
|
+
className: cn("flex shrink-0 items-end gap-2 border-t border-[var(--ds-border-subtle)] p-3", classNames?.composer),
|
|
512
780
|
children: [/* @__PURE__ */ jsx(Textarea, {
|
|
513
781
|
"data-slot": "agent-chat-input",
|
|
514
782
|
"aria-label": `Message ${agentName}`,
|
|
@@ -573,6 +841,6 @@ function AgentSessionDrawer({ sessions, current, loading, onResume }) {
|
|
|
573
841
|
});
|
|
574
842
|
}
|
|
575
843
|
//#endregion
|
|
576
|
-
export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentStreamingBubble, AgentThinking, AgentToolCard, readMcpAppUri, shouldStickToBottom };
|
|
844
|
+
export { AgentA2UIBlocks, AgentApprovalCard, AgentChat, AgentMessageBubble, AgentSessionDrawer, AgentSteps, AgentStreamingBubble, AgentThinking, AgentToolCard, readMcpAppUri, shouldStickToBottom };
|
|
577
845
|
|
|
578
846
|
//# sourceMappingURL=agent-chat.js.map
|