@agents24/chat-react 0.3.1 → 0.4.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/LICENSE +19 -0
- package/README.md +21 -66
- package/compatibility.json +54 -0
- package/dist/active-content.d.ts +10 -0
- package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
- package/dist/chunk-CHAU7FNW.js.map +1 -0
- package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
- package/dist/chunk-LI67M74F.js.map +1 -0
- package/dist/chunk-NLEOWTZG.js +1327 -0
- package/dist/chunk-NLEOWTZG.js.map +1 -0
- package/dist/index.cjs +66 -1852
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.js +6 -1841
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +7 -7
- package/dist/mcp-oauth.d.ts +3 -1
- package/dist/renderers.d.ts +3 -3
- package/dist/styles.css +407 -0
- package/dist/templates/agent-chat-app.d.ts +18 -0
- package/dist/templates/agent-chat-layout.d.ts +69 -0
- package/dist/templates/agent-chat-shell.d.ts +1 -1
- package/dist/templates/index.cjs +1904 -18
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.js +529 -11
- package/dist/templates/index.js.map +1 -1
- package/dist/types.d.ts +12 -380
- package/dist/ui/agent-chat-actions.d.ts +1 -2
- package/dist/ui/agent-chat-composer.d.ts +2 -1
- package/dist/ui/agent-chat-message.d.ts +3 -3
- package/dist/ui/agent-response-timeline.d.ts +2 -2
- package/dist/ui/attachment.d.ts +9 -9
- package/dist/ui/bubble.d.ts +4 -4
- package/dist/ui/index.cjs +137 -43
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +62 -1220
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/marker.d.ts +3 -3
- package/dist/ui/mcp-connect-required-card.d.ts +1 -1
- package/dist/ui/message-response.d.ts +1 -1
- package/dist/ui/message.d.ts +6 -6
- package/package.json +27 -8
- package/dist/chunk-EWZO4QJI.js.map +0 -1
- package/dist/chunk-FFO6KEB4.js.map +0 -1
- package/dist/chunk-UU7OXHL3.js +0 -11
- package/dist/chunk-UU7OXHL3.js.map +0 -1
- package/dist/context-window.d.ts +0 -38
- package/dist/controller-actions.d.ts +0 -45
- package/dist/controller-helpers.d.ts +0 -27
- package/dist/controller-hitl.d.ts +0 -22
- package/dist/controller-options.d.ts +0 -15
- package/dist/controller-thread-events.d.ts +0 -7
- package/dist/controller.d.ts +0 -5
- package/dist/message-lifecycle.d.ts +0 -15
- package/dist/model.d.ts +0 -30
- package/dist/sse.d.ts +0 -5
- package/dist/transport.d.ts +0 -43
package/dist/templates/index.cjs
CHANGED
|
@@ -30,7 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/templates/index.ts
|
|
31
31
|
var templates_exports = {};
|
|
32
32
|
__export(templates_exports, {
|
|
33
|
+
AgentChatApp: () => AgentChatApp,
|
|
34
|
+
AgentChatLayout: () => AgentChatLayout,
|
|
33
35
|
AgentChatShell: () => AgentChatShell,
|
|
36
|
+
AgentChatStatePanel: () => AgentChatStatePanel,
|
|
34
37
|
buildAgentChatOutlineItems: () => buildAgentChatOutlineItems,
|
|
35
38
|
getAgentChatShellScrollState: () => getAgentChatShellScrollState
|
|
36
39
|
});
|
|
@@ -94,7 +97,7 @@ function LatestThreadScrollerRoot({
|
|
|
94
97
|
{
|
|
95
98
|
ref: setRef,
|
|
96
99
|
className: cx(
|
|
97
|
-
"relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
|
|
100
|
+
"a24-scroller relative flex size-full min-h-0 flex-1 flex-col overflow-hidden",
|
|
98
101
|
className
|
|
99
102
|
),
|
|
100
103
|
...props
|
|
@@ -124,7 +127,7 @@ function LatestThreadScrollerViewport({
|
|
|
124
127
|
{
|
|
125
128
|
ref: setRef,
|
|
126
129
|
className: cx(
|
|
127
|
-
"size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
|
|
130
|
+
"a24-scroller__viewport size-full min-h-0 min-w-0 flex-1 overflow-y-auto overscroll-contain",
|
|
128
131
|
className
|
|
129
132
|
),
|
|
130
133
|
preserveScrollOnPrepend,
|
|
@@ -154,7 +157,7 @@ function LatestThreadScrollerContent({
|
|
|
154
157
|
{
|
|
155
158
|
ref: setRef,
|
|
156
159
|
"data-agents24-latest-thread-content": "",
|
|
157
|
-
className: cx("flex h-max min-h-full flex-col gap-8 p-4", className),
|
|
160
|
+
className: cx("a24-scroller__content flex h-max min-h-full flex-col gap-8 p-4", className),
|
|
158
161
|
...props
|
|
159
162
|
}
|
|
160
163
|
);
|
|
@@ -168,7 +171,7 @@ function LatestThreadScrollerItem({
|
|
|
168
171
|
import_message_scroller.MessageScroller.Item,
|
|
169
172
|
{
|
|
170
173
|
className: cx(
|
|
171
|
-
"min-w-0 shrink-0 overflow-visible",
|
|
174
|
+
"a24-scroller__item min-w-0 shrink-0 overflow-visible",
|
|
172
175
|
className
|
|
173
176
|
),
|
|
174
177
|
messageId,
|
|
@@ -203,7 +206,7 @@ function LatestThreadScrollerButton({
|
|
|
203
206
|
...buttonProps,
|
|
204
207
|
"aria-label": direction === "end" ? "Scroll to latest message" : "Scroll to first message",
|
|
205
208
|
className: cx(
|
|
206
|
-
"absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-
|
|
209
|
+
"a24-scroller__button absolute bottom-4 left-1/2 z-50 flex size-9 -translate-x-1/2 items-center justify-center rounded-full border border-border bg-background text-foreground shadow-md transition-[background-color,border-color,color,opacity,transform] duration-200 data-[active=false]:pointer-events-none data-[active=false]:translate-y-2 data-[active=false]:opacity-0 data-[active=true]:translate-y-0 data-[active=true]:opacity-100",
|
|
207
210
|
className
|
|
208
211
|
),
|
|
209
212
|
type: "button",
|
|
@@ -277,14 +280,16 @@ function LatestThreadScrollerOutline({
|
|
|
277
280
|
{
|
|
278
281
|
ref: navRef,
|
|
279
282
|
"aria-label": "Transcript outline",
|
|
283
|
+
"data-side": railSide,
|
|
284
|
+
"data-visible": hasSideRoom ? "" : void 0,
|
|
280
285
|
className: cx(
|
|
281
|
-
"pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
|
|
286
|
+
"a24-outline pointer-events-none absolute top-[46%] z-10 max-h-[min(18rem,calc(100%-10rem))] w-16 -translate-y-1/2 overflow-visible",
|
|
282
287
|
hasSideRoom ? "hidden md:block" : "hidden",
|
|
283
288
|
railSide === "left" ? "left-1" : "right-1",
|
|
284
289
|
className
|
|
285
290
|
),
|
|
286
291
|
dir,
|
|
287
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
|
|
292
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-outline__rail pointer-events-auto flex flex-col gap-0 py-1", children: items.map((item, index) => {
|
|
288
293
|
const itemMessageIds = item.messageIds && item.messageIds.length > 0 ? item.messageIds : [item.id];
|
|
289
294
|
const active = itemMessageIds.some(
|
|
290
295
|
(messageId) => visibleMessageIdSet.has(messageId) || messageId === currentAnchorId
|
|
@@ -303,9 +308,10 @@ function LatestThreadScrollerOutline({
|
|
|
303
308
|
{
|
|
304
309
|
type: "button",
|
|
305
310
|
"data-active": active,
|
|
311
|
+
"data-side": railSide,
|
|
306
312
|
dir: "ltr",
|
|
307
313
|
className: cx(
|
|
308
|
-
"group relative flex h-2 w-16 items-center rounded-sm outline-none",
|
|
314
|
+
"a24-outline__button group relative flex h-2 w-16 items-center rounded-sm outline-none",
|
|
309
315
|
railSide === "left" ? "justify-start" : "justify-end"
|
|
310
316
|
),
|
|
311
317
|
"aria-label": `Jump to ${item.label || fallbackLabel}`,
|
|
@@ -321,7 +327,7 @@ function LatestThreadScrollerOutline({
|
|
|
321
327
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
322
328
|
"span",
|
|
323
329
|
{
|
|
324
|
-
className: "h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
|
|
330
|
+
className: "a24-outline__bar h-0.5 rounded-full bg-muted-foreground/55 transition-[width,background-color,opacity] duration-150 ease-out group-hover:bg-foreground group-focus-visible:bg-foreground group-data-[active=true]:bg-foreground",
|
|
325
331
|
style: {
|
|
326
332
|
opacity: barOpacity,
|
|
327
333
|
width: barWidth
|
|
@@ -333,7 +339,7 @@ function LatestThreadScrollerOutline({
|
|
|
333
339
|
{
|
|
334
340
|
dir,
|
|
335
341
|
className: cx(
|
|
336
|
-
"pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-
|
|
342
|
+
"a24-outline__preview pointer-events-none absolute top-1/2 z-20 max-h-32 w-64 -translate-y-1/2 overflow-hidden rounded-lg border border-border/80 bg-background p-3 text-start opacity-0 shadow-lg transition-[opacity,transform] duration-200 group-hover:pointer-events-auto group-hover:opacity-100 group-focus-visible:pointer-events-auto group-focus-visible:opacity-100",
|
|
337
343
|
railSide === "left" ? "left-full ml-2 -translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0" : "right-full mr-2 translate-x-1 group-hover:translate-x-0 group-focus-visible:translate-x-0",
|
|
338
344
|
cardClassName
|
|
339
345
|
),
|
|
@@ -504,21 +510,21 @@ function AgentChatShell({
|
|
|
504
510
|
const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
|
|
505
511
|
const isEmptyState = messages.length === 0;
|
|
506
512
|
if (isEmptyState) {
|
|
507
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
|
|
513
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("a24-shell a24-shell--empty relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__empty relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "a24-shell__empty-content flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
|
|
508
514
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
509
515
|
"p",
|
|
510
516
|
{
|
|
511
517
|
className: cn(
|
|
512
|
-
"pb-6 text-3xl font-semibold transition-transform duration-200",
|
|
518
|
+
"a24-shell__empty-title pb-6 text-3xl font-semibold transition-transform duration-200",
|
|
513
519
|
emptyInputAttachmentCount > 0 && "-translate-y-12"
|
|
514
520
|
),
|
|
515
521
|
children: emptyTitle
|
|
516
522
|
}
|
|
517
523
|
),
|
|
518
|
-
!hideComposer && composer ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
|
|
524
|
+
!hideComposer && composer ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__empty-composer mx-auto w-full max-w-3xl px-4", children: composer }) : null
|
|
519
525
|
] }) }) });
|
|
520
526
|
}
|
|
521
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
|
|
527
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: cn("a24-shell relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Provider, { children: [
|
|
522
528
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(LatestThreadScroller.Root, { children: [
|
|
523
529
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
524
530
|
LatestThreadScroller.Viewport,
|
|
@@ -533,7 +539,7 @@ function AgentChatShell({
|
|
|
533
539
|
{
|
|
534
540
|
ref: contentRef,
|
|
535
541
|
className: cn(
|
|
536
|
-
"mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
|
|
542
|
+
"a24-shell__content mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
|
|
537
543
|
contentClassName
|
|
538
544
|
),
|
|
539
545
|
style: { paddingBottom: bottomPadding },
|
|
@@ -567,23 +573,1903 @@ function AgentChatShell({
|
|
|
567
573
|
{
|
|
568
574
|
ref: overlayRef,
|
|
569
575
|
className: cn(
|
|
570
|
-
"pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
|
|
576
|
+
"a24-shell__composer-overlay pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
|
|
571
577
|
composerOverlayClassName
|
|
572
578
|
),
|
|
573
579
|
style: {
|
|
574
580
|
background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--chat-background, var(--background)) 85%, transparent) 45%, var(--chat-background, var(--background)) 65%, var(--chat-background, var(--background)) 100%)"
|
|
575
581
|
},
|
|
576
582
|
children: [
|
|
577
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LatestThreadScroller.Button, {}) }),
|
|
578
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__scroll-button pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LatestThreadScroller.Button, {}) }),
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "a24-shell__composer-inner pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
|
|
579
585
|
]
|
|
580
586
|
}
|
|
581
587
|
) : null
|
|
582
588
|
] }) });
|
|
583
589
|
}
|
|
590
|
+
|
|
591
|
+
// src/templates/agent-chat-layout.tsx
|
|
592
|
+
var React8 = __toESM(require("react"), 1);
|
|
593
|
+
var import_lucide_react5 = require("lucide-react");
|
|
594
|
+
|
|
595
|
+
// src/ui/agent-chat-composer.tsx
|
|
596
|
+
var React3 = __toESM(require("react"), 1);
|
|
597
|
+
var import_lucide_react = require("lucide-react");
|
|
598
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
599
|
+
var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
600
|
+
function createComposerFile(file) {
|
|
601
|
+
return {
|
|
602
|
+
id: createId(),
|
|
603
|
+
type: "file",
|
|
604
|
+
url: URL.createObjectURL(file),
|
|
605
|
+
filename: file.name || "attachment",
|
|
606
|
+
mediaType: file.type || "application/octet-stream",
|
|
607
|
+
source: file
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
function revokeComposerFiles(files) {
|
|
611
|
+
files.forEach((file) => {
|
|
612
|
+
try {
|
|
613
|
+
URL.revokeObjectURL(file.url);
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
function formatFileCount(files) {
|
|
619
|
+
if (files.length === 1) return files[0].filename;
|
|
620
|
+
return `${files.length} files`;
|
|
621
|
+
}
|
|
622
|
+
var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-100 text-sm text-neutral-600 shadow-none transition-colors hover:bg-neutral-200 hover:text-neutral-800 focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-800 dark:text-neutral-300 dark:hover:bg-neutral-700";
|
|
623
|
+
var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-full border-0 bg-neutral-900 text-sm text-white shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-neutral-800 hover:text-white focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white";
|
|
624
|
+
function AgentChatComposer({
|
|
625
|
+
accept,
|
|
626
|
+
allowAttachments = true,
|
|
627
|
+
attachmentLayout = "flow",
|
|
628
|
+
className,
|
|
629
|
+
disabled = false,
|
|
630
|
+
inputToolbarContent,
|
|
631
|
+
isRunning = false,
|
|
632
|
+
onAttachmentCountChange,
|
|
633
|
+
onStop,
|
|
634
|
+
onSubmit,
|
|
635
|
+
placeholder = "Send follow-up\u2026",
|
|
636
|
+
textareaRef
|
|
637
|
+
}) {
|
|
638
|
+
const fileInputRef = React3.useRef(null);
|
|
639
|
+
const filesRef = React3.useRef([]);
|
|
640
|
+
const localTextareaRef = React3.useRef(null);
|
|
641
|
+
const [text, setText] = React3.useState("");
|
|
642
|
+
const [files, setFiles] = React3.useState([]);
|
|
643
|
+
const [isSubmitting, setIsSubmitting] = React3.useState(false);
|
|
644
|
+
const isExpanded = text.includes("\n") || text.length > 62;
|
|
645
|
+
const setTextareaRef = React3.useCallback(
|
|
646
|
+
(node) => {
|
|
647
|
+
localTextareaRef.current = node;
|
|
648
|
+
if (typeof textareaRef === "function") {
|
|
649
|
+
textareaRef(node);
|
|
650
|
+
} else if (textareaRef) {
|
|
651
|
+
textareaRef.current = node;
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
[textareaRef]
|
|
655
|
+
);
|
|
656
|
+
React3.useEffect(() => {
|
|
657
|
+
filesRef.current = files;
|
|
658
|
+
onAttachmentCountChange?.(files.length);
|
|
659
|
+
}, [files, onAttachmentCountChange]);
|
|
660
|
+
React3.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
|
|
661
|
+
React3.useLayoutEffect(() => {
|
|
662
|
+
const textarea = localTextareaRef.current;
|
|
663
|
+
if (!textarea) return;
|
|
664
|
+
if (isExpanded) {
|
|
665
|
+
textarea.style.height = "auto";
|
|
666
|
+
const scrollHeight = textarea.scrollHeight;
|
|
667
|
+
textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
|
|
668
|
+
textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
textarea.style.height = "";
|
|
672
|
+
textarea.style.overflowY = "hidden";
|
|
673
|
+
}, [isExpanded, text]);
|
|
674
|
+
const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
|
|
675
|
+
const handleFilesChange = (event) => {
|
|
676
|
+
const selected = Array.from(event.target.files || []).map(createComposerFile);
|
|
677
|
+
if (selected.length > 0) {
|
|
678
|
+
setFiles((current) => [...current, ...selected]);
|
|
679
|
+
}
|
|
680
|
+
event.currentTarget.value = "";
|
|
681
|
+
};
|
|
682
|
+
const removeFile = (fileId) => {
|
|
683
|
+
setFiles((current) => {
|
|
684
|
+
const target = current.find((file) => file.id === fileId);
|
|
685
|
+
if (target) revokeComposerFiles([target]);
|
|
686
|
+
return current.filter((file) => file.id !== fileId);
|
|
687
|
+
});
|
|
688
|
+
};
|
|
689
|
+
const submit = async () => {
|
|
690
|
+
if (!canSubmit) return;
|
|
691
|
+
const submittedText = text.trim();
|
|
692
|
+
const submittedFiles = files;
|
|
693
|
+
setIsSubmitting(true);
|
|
694
|
+
try {
|
|
695
|
+
await onSubmit({ text: submittedText, files: submittedFiles });
|
|
696
|
+
setText("");
|
|
697
|
+
setFiles([]);
|
|
698
|
+
revokeComposerFiles(submittedFiles);
|
|
699
|
+
} finally {
|
|
700
|
+
setIsSubmitting(false);
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
const handleKeyDown = (event) => {
|
|
704
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
705
|
+
event.preventDefault();
|
|
706
|
+
void submit();
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
|
|
710
|
+
files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
711
|
+
"div",
|
|
712
|
+
{
|
|
713
|
+
"data-testid": "agent-chat-composer-attachments",
|
|
714
|
+
"data-layout": attachmentLayout,
|
|
715
|
+
className: cn(
|
|
716
|
+
"a24-composer__attachments flex min-w-0 gap-2 py-1",
|
|
717
|
+
attachmentLayout === "overlay" ? "absolute bottom-full left-0 right-0 mb-1.5 flex-nowrap overflow-x-auto overflow-y-hidden overscroll-x-contain px-2" : "mb-2 flex-wrap px-1"
|
|
718
|
+
),
|
|
719
|
+
children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
720
|
+
"div",
|
|
721
|
+
{
|
|
722
|
+
className: "a24-composer__attachment group relative flex h-8 max-w-[min(20rem,calc(100vw-2rem))] cursor-default select-none items-center gap-1.5 rounded-md border border-neutral-200 bg-white px-1.5 text-sm font-medium text-foreground transition-colors hover:bg-neutral-50",
|
|
723
|
+
children: [
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
|
|
726
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
727
|
+
"button",
|
|
728
|
+
{
|
|
729
|
+
"aria-label": `Remove ${file.filename}`,
|
|
730
|
+
className: "a24-composer__attachment-remove inline-flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground opacity-70 transition-colors hover:bg-muted hover:text-foreground hover:opacity-100 focus-visible:outline-none",
|
|
731
|
+
onClick: () => removeFile(file.id),
|
|
732
|
+
type: "button",
|
|
733
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.X, { className: "size-3" })
|
|
734
|
+
}
|
|
735
|
+
)
|
|
736
|
+
]
|
|
737
|
+
},
|
|
738
|
+
file.id
|
|
739
|
+
))
|
|
740
|
+
}
|
|
741
|
+
) : null,
|
|
742
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
743
|
+
"div",
|
|
744
|
+
{
|
|
745
|
+
"data-expanded": isExpanded ? "" : void 0,
|
|
746
|
+
className: cn(
|
|
747
|
+
"a24-composer__surface relative w-full border border-neutral-200 bg-white shadow-sm transition-colors duration-200 focus-within:border-neutral-300 dark:border-neutral-700 dark:bg-card dark:focus-within:border-neutral-600",
|
|
748
|
+
isExpanded ? "rounded-[18px] p-2 pb-1.5" : "rounded-full px-2 py-[3px]"
|
|
749
|
+
),
|
|
750
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
751
|
+
"div",
|
|
752
|
+
{
|
|
753
|
+
className: "a24-composer__grid w-full",
|
|
754
|
+
style: {
|
|
755
|
+
alignItems: "center",
|
|
756
|
+
display: "grid",
|
|
757
|
+
gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
|
|
758
|
+
gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
|
|
759
|
+
gap: isExpanded ? "3px 10px" : "8px"
|
|
760
|
+
},
|
|
761
|
+
children: [
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
764
|
+
"input",
|
|
765
|
+
{
|
|
766
|
+
ref: fileInputRef,
|
|
767
|
+
accept,
|
|
768
|
+
"aria-label": "Attach files",
|
|
769
|
+
className: "hidden",
|
|
770
|
+
multiple: true,
|
|
771
|
+
name: "attachments",
|
|
772
|
+
onChange: handleFilesChange,
|
|
773
|
+
type: "file"
|
|
774
|
+
}
|
|
775
|
+
),
|
|
776
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
777
|
+
"button",
|
|
778
|
+
{
|
|
779
|
+
"aria-label": "Attach files",
|
|
780
|
+
className: cn("a24-composer__attach", pillButtonClass),
|
|
781
|
+
disabled: disabled || isSubmitting || isRunning || !allowAttachments,
|
|
782
|
+
onClick: () => fileInputRef.current?.click(),
|
|
783
|
+
title: "Attach files",
|
|
784
|
+
type: "button",
|
|
785
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Plus, { className: "size-4" })
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
] }),
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
790
|
+
"textarea",
|
|
791
|
+
{
|
|
792
|
+
ref: setTextareaRef,
|
|
793
|
+
"aria-label": "Message",
|
|
794
|
+
autoComplete: "off",
|
|
795
|
+
className: cn(
|
|
796
|
+
"a24-composer__textarea w-full resize-none border-0 bg-transparent px-1 text-[15px] leading-relaxed text-foreground shadow-none outline-none placeholder:text-neutral-400 focus-visible:ring-0 scrollbar-thin",
|
|
797
|
+
isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
|
|
798
|
+
),
|
|
799
|
+
disabled: disabled || isSubmitting || isRunning,
|
|
800
|
+
name: "message",
|
|
801
|
+
onChange: (event) => setText(event.target.value),
|
|
802
|
+
onKeyDown: handleKeyDown,
|
|
803
|
+
placeholder,
|
|
804
|
+
rows: isExpanded ? 2 : 1,
|
|
805
|
+
value: text
|
|
806
|
+
}
|
|
807
|
+
) }),
|
|
808
|
+
isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
|
|
809
|
+
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
|
|
810
|
+
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
|
|
811
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
812
|
+
"button",
|
|
813
|
+
{
|
|
814
|
+
"aria-label": "Stop generating",
|
|
815
|
+
className: cn("a24-composer__submit", submitButtonClass),
|
|
816
|
+
disabled: disabled || !onStop,
|
|
817
|
+
onClick: onStop,
|
|
818
|
+
title: "Stop",
|
|
819
|
+
type: "button",
|
|
820
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.Square, { className: "size-3.5 fill-current" })
|
|
821
|
+
}
|
|
822
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
823
|
+
"button",
|
|
824
|
+
{
|
|
825
|
+
"aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
|
|
826
|
+
className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
|
|
827
|
+
disabled: !canSubmit,
|
|
828
|
+
onClick: () => void submit(),
|
|
829
|
+
title: "Send",
|
|
830
|
+
type: "button",
|
|
831
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
|
|
832
|
+
}
|
|
833
|
+
) })
|
|
834
|
+
]
|
|
835
|
+
}
|
|
836
|
+
)
|
|
837
|
+
}
|
|
838
|
+
),
|
|
839
|
+
!isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
|
|
840
|
+
] });
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/ui/agent-chat-message.tsx
|
|
844
|
+
var React7 = __toESM(require("react"), 1);
|
|
845
|
+
var import_lucide_react4 = require("lucide-react");
|
|
846
|
+
|
|
847
|
+
// src/ui/adapters.ts
|
|
848
|
+
var React4 = __toESM(require("react"), 1);
|
|
849
|
+
|
|
850
|
+
// src/ui/attachment.tsx
|
|
851
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
852
|
+
var import_react_slot = require("@radix-ui/react-slot");
|
|
853
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
854
|
+
var attachmentVariants = (0, import_class_variance_authority.cva)(
|
|
855
|
+
"group/attachment relative flex w-fit max-w-full min-w-0 shrink-0 flex-wrap rounded-xl border bg-card text-card-foreground transition-colors focus-within:border-neutral-300 focus-within:ring-0 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
|
|
856
|
+
{
|
|
857
|
+
variants: {
|
|
858
|
+
size: {
|
|
859
|
+
default: "gap-2 text-sm has-data-[slot=attachment-content]:px-2.5 has-data-[slot=attachment-content]:py-2 has-data-[slot=attachment-media]:p-2",
|
|
860
|
+
sm: "gap-2.5 text-xs has-data-[slot=attachment-content]:px-2 has-data-[slot=attachment-content]:py-1.5 has-data-[slot=attachment-media]:p-1.5",
|
|
861
|
+
xs: "gap-1.5 rounded-lg text-xs has-data-[slot=attachment-content]:px-1.5 has-data-[slot=attachment-content]:py-1 has-data-[slot=attachment-media]:p-1"
|
|
862
|
+
},
|
|
863
|
+
orientation: {
|
|
864
|
+
horizontal: "min-w-40 items-center",
|
|
865
|
+
vertical: "w-24 flex-col has-data-[slot=attachment-content]:w-30"
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
);
|
|
870
|
+
function Attachment({
|
|
871
|
+
className,
|
|
872
|
+
state = "done",
|
|
873
|
+
size = "default",
|
|
874
|
+
orientation = "horizontal",
|
|
875
|
+
...props
|
|
876
|
+
}) {
|
|
877
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
878
|
+
"div",
|
|
879
|
+
{
|
|
880
|
+
"data-slot": "attachment",
|
|
881
|
+
"data-state": state,
|
|
882
|
+
"data-size": size,
|
|
883
|
+
"data-orientation": orientation,
|
|
884
|
+
className: cn(attachmentVariants({ size, orientation }), className),
|
|
885
|
+
...props
|
|
886
|
+
}
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
var attachmentMediaVariants = (0, import_class_variance_authority.cva)(
|
|
890
|
+
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground group-data-[orientation=vertical]/attachment:w-full group-data-[size=sm]/attachment:w-8 group-data-[size=xs]/attachment:w-7 group-data-[size=xs]/attachment:rounded-md group-data-[state=error]/attachment:bg-destructive/10 group-data-[state=error]/attachment:text-destructive group-data-[orientation=vertical]/attachment:*:data-[slot=spinner]:size-6! [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 group-data-[orientation=vertical]/attachment:[&_svg:not([class*='size-'])]:size-6 group-data-[size=xs]/attachment:[&_svg:not([class*='size-'])]:size-3.5",
|
|
891
|
+
{
|
|
892
|
+
variants: {
|
|
893
|
+
variant: {
|
|
894
|
+
icon: "",
|
|
895
|
+
image: "opacity-60 group-data-[state=done]/attachment:opacity-100 group-data-[state=idle]/attachment:opacity-100 *:[img]:aspect-square *:[img]:w-full *:[img]:object-cover"
|
|
896
|
+
}
|
|
897
|
+
},
|
|
898
|
+
defaultVariants: {
|
|
899
|
+
variant: "icon"
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
);
|
|
903
|
+
function AttachmentMedia({
|
|
904
|
+
className,
|
|
905
|
+
variant = "icon",
|
|
906
|
+
...props
|
|
907
|
+
}) {
|
|
908
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
909
|
+
"div",
|
|
910
|
+
{
|
|
911
|
+
"data-slot": "attachment-media",
|
|
912
|
+
"data-variant": variant,
|
|
913
|
+
className: cn(attachmentMediaVariants({ variant }), className),
|
|
914
|
+
...props
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
}
|
|
918
|
+
function AttachmentContent({
|
|
919
|
+
className,
|
|
920
|
+
...props
|
|
921
|
+
}) {
|
|
922
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
923
|
+
"div",
|
|
924
|
+
{
|
|
925
|
+
"data-slot": "attachment-content",
|
|
926
|
+
className: cn(
|
|
927
|
+
"max-w-full min-w-0 flex-1 leading-tight group-data-[orientation=vertical]/attachment:px-1",
|
|
928
|
+
className
|
|
929
|
+
),
|
|
930
|
+
...props
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
function AttachmentTitle({
|
|
935
|
+
className,
|
|
936
|
+
...props
|
|
937
|
+
}) {
|
|
938
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
939
|
+
"span",
|
|
940
|
+
{
|
|
941
|
+
"data-slot": "attachment-title",
|
|
942
|
+
className: cn(
|
|
943
|
+
"block max-w-full min-w-0 truncate font-medium group-data-[state=processing]/attachment:shimmer group-data-[state=uploading]/attachment:shimmer",
|
|
944
|
+
className
|
|
945
|
+
),
|
|
946
|
+
...props
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
function AttachmentDescription({
|
|
951
|
+
className,
|
|
952
|
+
...props
|
|
953
|
+
}) {
|
|
954
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
955
|
+
"span",
|
|
956
|
+
{
|
|
957
|
+
"data-slot": "attachment-description",
|
|
958
|
+
className: cn(
|
|
959
|
+
"mt-0.5 block min-w-0 truncate text-xs text-muted-foreground group-data-[state=error]/attachment:text-destructive/80",
|
|
960
|
+
"max-w-full",
|
|
961
|
+
className
|
|
962
|
+
),
|
|
963
|
+
...props
|
|
964
|
+
}
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
function AttachmentGroup({ className, ...props }) {
|
|
968
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
969
|
+
"div",
|
|
970
|
+
{
|
|
971
|
+
"data-slot": "attachment-group",
|
|
972
|
+
className: cn(
|
|
973
|
+
"flex min-w-0 scroll-fade-x snap-x snap-mandatory scroll-px-1 scrollbar-none gap-3 overflow-x-auto overscroll-x-contain py-1 *:data-[slot=attachment]:flex-none *:data-[slot=attachment]:snap-start",
|
|
974
|
+
className
|
|
975
|
+
),
|
|
976
|
+
...props
|
|
977
|
+
}
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/ui/message.tsx
|
|
982
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
983
|
+
function Message({
|
|
984
|
+
className,
|
|
985
|
+
align = "start",
|
|
986
|
+
...props
|
|
987
|
+
}) {
|
|
988
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
989
|
+
"div",
|
|
990
|
+
{
|
|
991
|
+
"data-slot": "message",
|
|
992
|
+
"data-align": align,
|
|
993
|
+
className: cn(
|
|
994
|
+
"group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
|
|
995
|
+
className
|
|
996
|
+
),
|
|
997
|
+
...props
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/ui/adapters.ts
|
|
1003
|
+
function ChatMessage(input) {
|
|
1004
|
+
const { className, from, align, ...props } = input;
|
|
1005
|
+
const isUser = from === "user";
|
|
1006
|
+
const isRtl = props.dir === "rtl";
|
|
1007
|
+
const resolvedAlign = align || "start";
|
|
1008
|
+
const messageProps = Object.assign({}, props);
|
|
1009
|
+
messageProps.align = resolvedAlign;
|
|
1010
|
+
messageProps.className = cn(
|
|
1011
|
+
"group flex w-full flex-col gap-2",
|
|
1012
|
+
isUser ? cn("is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant max-w-full",
|
|
1013
|
+
className
|
|
1014
|
+
);
|
|
1015
|
+
return React4.createElement(Message, messageProps);
|
|
1016
|
+
}
|
|
1017
|
+
function ChatMessageContent({
|
|
1018
|
+
className,
|
|
1019
|
+
...props
|
|
1020
|
+
}) {
|
|
1021
|
+
return React4.createElement("div", {
|
|
1022
|
+
className: cn(
|
|
1023
|
+
"is-user:dark flex w-full max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm group-[.is-user]:w-fit group-[.is-user]:overflow-visible",
|
|
1024
|
+
"group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
|
|
1025
|
+
"group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:text-foreground",
|
|
1026
|
+
props.dir === "rtl" ? "group-[.is-user]:mr-auto" : "group-[.is-user]:ml-auto",
|
|
1027
|
+
className
|
|
1028
|
+
),
|
|
1029
|
+
"data-slot": "bubble-content",
|
|
1030
|
+
...props
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
function ChatMessageActions({
|
|
1034
|
+
className,
|
|
1035
|
+
...props
|
|
1036
|
+
}) {
|
|
1037
|
+
return React4.createElement("div", {
|
|
1038
|
+
className: cn("flex items-center gap-1", className),
|
|
1039
|
+
"data-slot": "message-actions",
|
|
1040
|
+
...props
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
function ChatMessageAction({
|
|
1044
|
+
children,
|
|
1045
|
+
className,
|
|
1046
|
+
label,
|
|
1047
|
+
tooltip,
|
|
1048
|
+
type = "button",
|
|
1049
|
+
...props
|
|
1050
|
+
}) {
|
|
1051
|
+
const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
|
|
1052
|
+
return React4.createElement(
|
|
1053
|
+
"button",
|
|
1054
|
+
{
|
|
1055
|
+
"aria-label": label || tooltipLabel,
|
|
1056
|
+
className: cn(
|
|
1057
|
+
"inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50",
|
|
1058
|
+
className
|
|
1059
|
+
),
|
|
1060
|
+
title: tooltipLabel,
|
|
1061
|
+
type,
|
|
1062
|
+
...props
|
|
1063
|
+
},
|
|
1064
|
+
children,
|
|
1065
|
+
React4.createElement(
|
|
1066
|
+
"span",
|
|
1067
|
+
{ className: "sr-only" },
|
|
1068
|
+
label || tooltipLabel
|
|
1069
|
+
)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
function ChatAttachment({
|
|
1073
|
+
data,
|
|
1074
|
+
onRemove,
|
|
1075
|
+
orientation,
|
|
1076
|
+
...props
|
|
1077
|
+
}) {
|
|
1078
|
+
const mediaType = data.mediaType || data.contentType || String(data.type || "application/octet-stream");
|
|
1079
|
+
const filename = data.filename || data.name || "Attachment";
|
|
1080
|
+
const isImage = mediaType.startsWith("image/") && Boolean(data.url);
|
|
1081
|
+
return React4.createElement(
|
|
1082
|
+
Attachment,
|
|
1083
|
+
{
|
|
1084
|
+
orientation: orientation || (isImage ? "vertical" : "horizontal"),
|
|
1085
|
+
...props
|
|
1086
|
+
},
|
|
1087
|
+
React4.createElement(
|
|
1088
|
+
AttachmentMedia,
|
|
1089
|
+
{ variant: isImage ? "image" : "icon" },
|
|
1090
|
+
isImage ? React4.createElement("img", {
|
|
1091
|
+
alt: filename,
|
|
1092
|
+
className: "size-full object-cover",
|
|
1093
|
+
src: data.url
|
|
1094
|
+
}) : React4.createElement(
|
|
1095
|
+
"span",
|
|
1096
|
+
{ "aria-hidden": "true", className: "text-sm" },
|
|
1097
|
+
filename.slice(0, 1).toUpperCase()
|
|
1098
|
+
)
|
|
1099
|
+
),
|
|
1100
|
+
React4.createElement(
|
|
1101
|
+
AttachmentContent,
|
|
1102
|
+
null,
|
|
1103
|
+
React4.createElement(AttachmentTitle, null, filename),
|
|
1104
|
+
React4.createElement(AttachmentDescription, null, mediaType)
|
|
1105
|
+
),
|
|
1106
|
+
onRemove ? React4.createElement(
|
|
1107
|
+
"button",
|
|
1108
|
+
{
|
|
1109
|
+
"aria-label": "Remove attachment",
|
|
1110
|
+
className: "relative z-20 mr-1 inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
1111
|
+
onClick: (event) => {
|
|
1112
|
+
event.stopPropagation();
|
|
1113
|
+
onRemove();
|
|
1114
|
+
},
|
|
1115
|
+
type: "button"
|
|
1116
|
+
},
|
|
1117
|
+
React4.createElement("span", { "aria-hidden": "true" }, "x")
|
|
1118
|
+
) : null
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
function ChatAttachments({
|
|
1122
|
+
children,
|
|
1123
|
+
className,
|
|
1124
|
+
...props
|
|
1125
|
+
}) {
|
|
1126
|
+
if (!children) return null;
|
|
1127
|
+
return React4.createElement(
|
|
1128
|
+
AttachmentGroup,
|
|
1129
|
+
{
|
|
1130
|
+
className: cn(
|
|
1131
|
+
props.dir === "rtl" ? "mr-auto" : "ml-auto",
|
|
1132
|
+
"w-fit flex-wrap overflow-visible",
|
|
1133
|
+
className
|
|
1134
|
+
),
|
|
1135
|
+
...props
|
|
1136
|
+
},
|
|
1137
|
+
children
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/ui/agent-chat-actions.tsx
|
|
1142
|
+
var import_lucide_react2 = require("lucide-react");
|
|
1143
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
1144
|
+
function AgentChatDefaultActions({
|
|
1145
|
+
handlers,
|
|
1146
|
+
message
|
|
1147
|
+
}) {
|
|
1148
|
+
if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
|
|
1149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(ChatMessageActions, { children: [
|
|
1150
|
+
handlers.onRetry ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1151
|
+
ChatMessageAction,
|
|
1152
|
+
{
|
|
1153
|
+
label: "Retry",
|
|
1154
|
+
onClick: () => void handlers.onRetry?.(message),
|
|
1155
|
+
tooltip: "Regenerate response",
|
|
1156
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.RefreshCcw, { className: "size-4" })
|
|
1157
|
+
}
|
|
1158
|
+
) : null,
|
|
1159
|
+
handlers.onLike ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1160
|
+
ChatMessageAction,
|
|
1161
|
+
{
|
|
1162
|
+
label: "Like",
|
|
1163
|
+
onClick: () => void handlers.onLike?.(message),
|
|
1164
|
+
tooltip: "Like this response",
|
|
1165
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ThumbsUp, { className: "size-4", fill: handlers.liked ? "currentColor" : "none" })
|
|
1166
|
+
}
|
|
1167
|
+
) : null,
|
|
1168
|
+
handlers.onDislike ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1169
|
+
ChatMessageAction,
|
|
1170
|
+
{
|
|
1171
|
+
label: "Dislike",
|
|
1172
|
+
onClick: () => void handlers.onDislike?.(message),
|
|
1173
|
+
tooltip: "Dislike this response",
|
|
1174
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.ThumbsDown, { className: "size-4", fill: handlers.disliked ? "currentColor" : "none" })
|
|
1175
|
+
}
|
|
1176
|
+
) : null,
|
|
1177
|
+
handlers.onCopy ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1178
|
+
ChatMessageAction,
|
|
1179
|
+
{
|
|
1180
|
+
label: "Copy",
|
|
1181
|
+
onClick: () => handlers.onCopy?.(message.content || "", message.id),
|
|
1182
|
+
tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
|
|
1183
|
+
children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react2.Copy, { className: "size-4" })
|
|
1184
|
+
}
|
|
1185
|
+
) : null
|
|
1186
|
+
] });
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// src/ui/agent-response-timeline.tsx
|
|
1190
|
+
var React6 = __toESM(require("react"), 1);
|
|
1191
|
+
var import_lucide_react3 = require("lucide-react");
|
|
1192
|
+
|
|
1193
|
+
// src/ui/message-response.tsx
|
|
1194
|
+
var React5 = __toESM(require("react"), 1);
|
|
1195
|
+
var import_streamdown = require("streamdown");
|
|
1196
|
+
|
|
1197
|
+
// src/active-content.ts
|
|
1198
|
+
var CHAT_MARKDOWN_HARDEN_OPTIONS = Object.freeze({
|
|
1199
|
+
allowDataImages: false,
|
|
1200
|
+
allowedImagePrefixes: [],
|
|
1201
|
+
allowedLinkPrefixes: ["*"],
|
|
1202
|
+
allowedProtocols: [],
|
|
1203
|
+
defaultOrigin: void 0,
|
|
1204
|
+
imageBlockPolicy: "text-only",
|
|
1205
|
+
linkBlockPolicy: "text-only"
|
|
1206
|
+
});
|
|
1207
|
+
function safeChatMarkdownHref(value) {
|
|
1208
|
+
if (!value) return void 0;
|
|
1209
|
+
if (value.startsWith("#")) return value;
|
|
1210
|
+
try {
|
|
1211
|
+
const url = new URL(value);
|
|
1212
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : void 0;
|
|
1213
|
+
} catch {
|
|
1214
|
+
return void 0;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
// src/streaming-text.ts
|
|
1219
|
+
var import_react = require("react");
|
|
1220
|
+
var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
|
|
1221
|
+
var defaultCompletedTextCache = /* @__PURE__ */ new Map();
|
|
1222
|
+
var defaultStreamingTextCache = {
|
|
1223
|
+
get: (id) => defaultCompletedTextCache.get(id),
|
|
1224
|
+
set: (id, text) => {
|
|
1225
|
+
defaultCompletedTextCache.set(id, text);
|
|
1226
|
+
if (defaultCompletedTextCache.size > DEFAULT_COMPLETED_TEXT_CACHE_SIZE) {
|
|
1227
|
+
const firstKey = defaultCompletedTextCache.keys().next().value;
|
|
1228
|
+
if (firstKey) defaultCompletedTextCache.delete(firstKey);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
function useStreamingText({
|
|
1233
|
+
id,
|
|
1234
|
+
isStreaming,
|
|
1235
|
+
text,
|
|
1236
|
+
cache = defaultStreamingTextCache,
|
|
1237
|
+
charsPerSecond = 72,
|
|
1238
|
+
catchupThreshold = 40,
|
|
1239
|
+
maxCatchupChars = 20
|
|
1240
|
+
}) {
|
|
1241
|
+
const cacheAdapter = cache === false ? null : cache;
|
|
1242
|
+
const [displayedText, setDisplayedText] = (0, import_react.useState)(() => {
|
|
1243
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1244
|
+
return cachedText && text.startsWith(cachedText) ? cachedText : text;
|
|
1245
|
+
});
|
|
1246
|
+
const targetRef = (0, import_react.useRef)(text);
|
|
1247
|
+
const displayedRef = (0, import_react.useRef)(displayedText);
|
|
1248
|
+
const rafRef = (0, import_react.useRef)(null);
|
|
1249
|
+
const lastFrameAtRef = (0, import_react.useRef)(null);
|
|
1250
|
+
const idRef = (0, import_react.useRef)(id);
|
|
1251
|
+
const shouldAnimateRef = (0, import_react.useRef)(isStreaming);
|
|
1252
|
+
(0, import_react.useEffect)(() => {
|
|
1253
|
+
if (isStreaming || !text) return;
|
|
1254
|
+
cacheAdapter?.set(id, text);
|
|
1255
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
1256
|
+
(0, import_react.useEffect)(() => {
|
|
1257
|
+
targetRef.current = text;
|
|
1258
|
+
}, [text]);
|
|
1259
|
+
(0, import_react.useEffect)(() => {
|
|
1260
|
+
if (idRef.current === id) return;
|
|
1261
|
+
idRef.current = id;
|
|
1262
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1263
|
+
const initial = cachedText && text.startsWith(cachedText) ? cachedText : text;
|
|
1264
|
+
shouldAnimateRef.current = isStreaming && initial !== text;
|
|
1265
|
+
displayedRef.current = initial;
|
|
1266
|
+
setDisplayedText(initial);
|
|
1267
|
+
if (rafRef.current !== null) {
|
|
1268
|
+
cancelAnimationFrame(rafRef.current);
|
|
1269
|
+
rafRef.current = null;
|
|
1270
|
+
}
|
|
1271
|
+
lastFrameAtRef.current = null;
|
|
1272
|
+
}, [cacheAdapter, id, isStreaming, text]);
|
|
1273
|
+
(0, import_react.useEffect)(() => {
|
|
1274
|
+
if (typeof window === "undefined") {
|
|
1275
|
+
displayedRef.current = text;
|
|
1276
|
+
setDisplayedText(text);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
const reduceMotion = typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
1280
|
+
if (reduceMotion) {
|
|
1281
|
+
displayedRef.current = text;
|
|
1282
|
+
setDisplayedText(text);
|
|
1283
|
+
shouldAnimateRef.current = false;
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const cachedText = cacheAdapter?.get(id);
|
|
1287
|
+
if (cachedText === text) {
|
|
1288
|
+
displayedRef.current = text;
|
|
1289
|
+
setDisplayedText(text);
|
|
1290
|
+
shouldAnimateRef.current = false;
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
if (isStreaming) {
|
|
1294
|
+
shouldAnimateRef.current = true;
|
|
1295
|
+
}
|
|
1296
|
+
if (!shouldAnimateRef.current) {
|
|
1297
|
+
displayedRef.current = text;
|
|
1298
|
+
setDisplayedText(text);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
if (!text.startsWith(displayedRef.current)) {
|
|
1302
|
+
displayedRef.current = "";
|
|
1303
|
+
setDisplayedText("");
|
|
1304
|
+
}
|
|
1305
|
+
const tick = (timestamp) => {
|
|
1306
|
+
const previousTimestamp = lastFrameAtRef.current ?? timestamp;
|
|
1307
|
+
lastFrameAtRef.current = timestamp;
|
|
1308
|
+
const target = targetRef.current;
|
|
1309
|
+
const current = displayedRef.current;
|
|
1310
|
+
if (current.length >= target.length) {
|
|
1311
|
+
shouldAnimateRef.current = false;
|
|
1312
|
+
rafRef.current = null;
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const elapsedMs = Math.max(8, timestamp - previousTimestamp);
|
|
1316
|
+
const charsFromTime = Math.max(1, Math.floor(elapsedMs / 1e3 * charsPerSecond));
|
|
1317
|
+
const gap = target.length - current.length;
|
|
1318
|
+
const catchupStep = gap > catchupThreshold ? Math.min(maxCatchupChars, Math.ceil(gap / 10)) : charsFromTime;
|
|
1319
|
+
const nextLength = Math.min(
|
|
1320
|
+
target.length,
|
|
1321
|
+
current.length + Math.max(charsFromTime, catchupStep)
|
|
1322
|
+
);
|
|
1323
|
+
const next = target.slice(0, nextLength);
|
|
1324
|
+
displayedRef.current = next;
|
|
1325
|
+
setDisplayedText(next);
|
|
1326
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
1327
|
+
};
|
|
1328
|
+
if (rafRef.current === null && displayedRef.current.length < text.length) {
|
|
1329
|
+
rafRef.current = window.requestAnimationFrame(tick);
|
|
1330
|
+
}
|
|
1331
|
+
return () => {
|
|
1332
|
+
if (rafRef.current !== null) {
|
|
1333
|
+
cancelAnimationFrame(rafRef.current);
|
|
1334
|
+
rafRef.current = null;
|
|
1335
|
+
}
|
|
1336
|
+
lastFrameAtRef.current = null;
|
|
1337
|
+
};
|
|
1338
|
+
}, [cacheAdapter, catchupThreshold, charsPerSecond, id, isStreaming, maxCatchupChars, text]);
|
|
1339
|
+
return {
|
|
1340
|
+
displayedText,
|
|
1341
|
+
isAnimating: isStreaming,
|
|
1342
|
+
mode: isStreaming ? "streaming" : "static",
|
|
1343
|
+
parseIncompleteMarkdown: isStreaming
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// src/ui/message-response.tsx
|
|
1348
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1349
|
+
function SafeMarkdownLink({ children, href, ...props }) {
|
|
1350
|
+
const domProps = { ...props };
|
|
1351
|
+
delete domProps.node;
|
|
1352
|
+
const safeHref = safeChatMarkdownHref(href);
|
|
1353
|
+
if (!safeHref) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children });
|
|
1354
|
+
if (safeHref.startsWith("#")) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, children });
|
|
1355
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { ...domProps, href: safeHref, rel: "noopener noreferrer", target: "_blank", children });
|
|
1356
|
+
}
|
|
1357
|
+
function BlockedMarkdownImage({ alt }) {
|
|
1358
|
+
return alt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { "aria-label": `Image blocked: ${alt}`, role: "img", children: alt }) : null;
|
|
1359
|
+
}
|
|
1360
|
+
var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
|
|
1361
|
+
"blockquote",
|
|
1362
|
+
"div",
|
|
1363
|
+
"h1",
|
|
1364
|
+
"h2",
|
|
1365
|
+
"h3",
|
|
1366
|
+
"h4",
|
|
1367
|
+
"h5",
|
|
1368
|
+
"h6",
|
|
1369
|
+
"hr",
|
|
1370
|
+
"ol",
|
|
1371
|
+
"pre",
|
|
1372
|
+
"table",
|
|
1373
|
+
"ul"
|
|
1374
|
+
]);
|
|
1375
|
+
function isMultilineCodeNode(node) {
|
|
1376
|
+
return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
|
|
1377
|
+
}
|
|
1378
|
+
function hasBlockMarkdownChild(child) {
|
|
1379
|
+
if (!React5.isValidElement(child)) {
|
|
1380
|
+
return false;
|
|
1381
|
+
}
|
|
1382
|
+
if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
const props = child.props;
|
|
1386
|
+
if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1389
|
+
return React5.Children.toArray(props.children).some(hasBlockMarkdownChild);
|
|
1390
|
+
}
|
|
1391
|
+
function MessageMarkdownParagraph({
|
|
1392
|
+
children,
|
|
1393
|
+
...props
|
|
1394
|
+
}) {
|
|
1395
|
+
const domProps = { ...props };
|
|
1396
|
+
delete domProps.node;
|
|
1397
|
+
const childArray = React5.Children.toArray(children).filter(
|
|
1398
|
+
(child) => child !== null && child !== ""
|
|
1399
|
+
);
|
|
1400
|
+
const isImageOnly = childArray.length === 1 && React5.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
|
|
1401
|
+
if (isImageOnly) {
|
|
1402
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children });
|
|
1403
|
+
}
|
|
1404
|
+
if (childArray.some(hasBlockMarkdownChild)) {
|
|
1405
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { ...domProps, children });
|
|
1406
|
+
}
|
|
1407
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { ...domProps, children });
|
|
1408
|
+
}
|
|
1409
|
+
var MESSAGE_RESPONSE_COMPONENTS = {
|
|
1410
|
+
a: SafeMarkdownLink,
|
|
1411
|
+
img: BlockedMarkdownImage,
|
|
1412
|
+
p: MessageMarkdownParagraph
|
|
1413
|
+
};
|
|
1414
|
+
var streamdownDefaultRehypePlugins = import_streamdown.defaultRehypePlugins || {};
|
|
1415
|
+
var streamdownHardenPlugin = Array.isArray(
|
|
1416
|
+
streamdownDefaultRehypePlugins.harden
|
|
1417
|
+
) ? streamdownDefaultRehypePlugins.harden[0] : streamdownDefaultRehypePlugins.harden;
|
|
1418
|
+
var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
|
|
1419
|
+
...Object.entries(streamdownDefaultRehypePlugins).filter(([key]) => key !== "harden").map(([, plugin]) => plugin),
|
|
1420
|
+
...streamdownHardenPlugin ? [
|
|
1421
|
+
[
|
|
1422
|
+
streamdownHardenPlugin,
|
|
1423
|
+
CHAT_MARKDOWN_HARDEN_OPTIONS
|
|
1424
|
+
]
|
|
1425
|
+
] : []
|
|
1426
|
+
];
|
|
1427
|
+
var MessageResponse = React5.memo(
|
|
1428
|
+
({
|
|
1429
|
+
children,
|
|
1430
|
+
className,
|
|
1431
|
+
components,
|
|
1432
|
+
rehypePlugins,
|
|
1433
|
+
streaming = false,
|
|
1434
|
+
streamingId,
|
|
1435
|
+
...props
|
|
1436
|
+
}) => {
|
|
1437
|
+
const text = typeof children === "string" || typeof children === "number" ? String(children) : "";
|
|
1438
|
+
const streamingText = useStreamingText({
|
|
1439
|
+
charsPerSecond: 120,
|
|
1440
|
+
id: streamingId || text,
|
|
1441
|
+
isStreaming: streaming && text.length > 0,
|
|
1442
|
+
maxCatchupChars: 32,
|
|
1443
|
+
text
|
|
1444
|
+
});
|
|
1445
|
+
const streamdownComponents = React5.useMemo(
|
|
1446
|
+
() => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
|
|
1447
|
+
[components]
|
|
1448
|
+
);
|
|
1449
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1450
|
+
import_streamdown.Streamdown,
|
|
1451
|
+
{
|
|
1452
|
+
className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
|
|
1453
|
+
components: streamdownComponents,
|
|
1454
|
+
rehypePlugins: rehypePlugins || MESSAGE_RESPONSE_REHYPE_PLUGINS,
|
|
1455
|
+
...props,
|
|
1456
|
+
children: text ? streamingText.displayedText : children
|
|
1457
|
+
}
|
|
1458
|
+
);
|
|
1459
|
+
},
|
|
1460
|
+
(prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.streaming === nextProps.streaming && prevProps.streamingId === nextProps.streamingId && prevProps.components === nextProps.components
|
|
1461
|
+
);
|
|
1462
|
+
MessageResponse.displayName = "MessageResponse";
|
|
1463
|
+
|
|
1464
|
+
// src/ui/hitl-presentation.ts
|
|
1465
|
+
function optionalString(value) {
|
|
1466
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1467
|
+
}
|
|
1468
|
+
function hitlActionLabel(part, action) {
|
|
1469
|
+
if (action === "approve") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Allow once" : "Approve";
|
|
1470
|
+
if (action === "reject") return ["tool_review", "app_data_permission"].includes(part.hitlKind) ? "Don\u2019t allow" : "Reject";
|
|
1471
|
+
if (action === "connect") return "Connect";
|
|
1472
|
+
return "Skip";
|
|
1473
|
+
}
|
|
1474
|
+
function hitlTitle(part) {
|
|
1475
|
+
const presentation = part.presentation || {};
|
|
1476
|
+
if (part.hitlKind === "tool_review") return `Run ${optionalString(presentation.tool_name) || "this tool"}?`;
|
|
1477
|
+
if (part.hitlKind === "mcp_auth") {
|
|
1478
|
+
const serverName = optionalString(presentation.server_name);
|
|
1479
|
+
return serverName ? `Connect ${serverName} to continue` : part.message;
|
|
1480
|
+
}
|
|
1481
|
+
if (part.hitlKind === "app_data_permission") return "Allow this data change?";
|
|
1482
|
+
return part.message;
|
|
1483
|
+
}
|
|
1484
|
+
function resolvedHitlLabel(part) {
|
|
1485
|
+
const outcome = String(part.resolution?.outcome || part.status).trim().toLowerCase();
|
|
1486
|
+
return {
|
|
1487
|
+
approved: part.hitlKind === "user_approval" ? "Approved" : "Allowed",
|
|
1488
|
+
rejected: part.hitlKind === "user_approval" ? "Rejected" : "Not allowed",
|
|
1489
|
+
connected: "connected",
|
|
1490
|
+
skipped: "Skipped",
|
|
1491
|
+
timeout: "Timed out",
|
|
1492
|
+
expired: "Timed out",
|
|
1493
|
+
cancelled: "Cancelled",
|
|
1494
|
+
invalidated: "No longer available",
|
|
1495
|
+
policy_denied: "Not allowed"
|
|
1496
|
+
}[outcome] || outcome.replace(/_/g, " ");
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
// src/ui/agent-response-timeline.tsx
|
|
1500
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1501
|
+
function optionalString2(value) {
|
|
1502
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1503
|
+
}
|
|
1504
|
+
function presentationValue(part, key) {
|
|
1505
|
+
const presentation = part.presentation || {};
|
|
1506
|
+
const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
1507
|
+
return optionalString2(presentation[key]) || optionalString2(presentation[snakeKey]);
|
|
1508
|
+
}
|
|
1509
|
+
function groupKey(part) {
|
|
1510
|
+
return presentationValue(part, "groupKey");
|
|
1511
|
+
}
|
|
1512
|
+
function groupLabel(part) {
|
|
1513
|
+
return presentationValue(part, "groupLabel") || toolLabel(part);
|
|
1514
|
+
}
|
|
1515
|
+
function toolLabel(part) {
|
|
1516
|
+
return presentationValue(part, "activity") || presentationValue(part, "title") || part.toolName || "Tool activity";
|
|
1517
|
+
}
|
|
1518
|
+
function toolDetail(part) {
|
|
1519
|
+
return presentationValue(part, "detail") || part.errorText || null;
|
|
1520
|
+
}
|
|
1521
|
+
function isActiveTool(part, activeToolId) {
|
|
1522
|
+
return part.id === activeToolId || part.toolCallId === activeToolId || part.state === "input-available" || part.state === "input-streaming";
|
|
1523
|
+
}
|
|
1524
|
+
function Shimmer({ children, className }) {
|
|
1525
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: cn("inline-block animate-pulse", className), children });
|
|
1526
|
+
}
|
|
1527
|
+
function CompactTask({
|
|
1528
|
+
children,
|
|
1529
|
+
defaultOpen = false,
|
|
1530
|
+
detail,
|
|
1531
|
+
error,
|
|
1532
|
+
label,
|
|
1533
|
+
loading
|
|
1534
|
+
}) {
|
|
1535
|
+
const [open, setOpen] = React6.useState(defaultOpen);
|
|
1536
|
+
const expandable = Boolean(children || detail);
|
|
1537
|
+
const content = loading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { children: label });
|
|
1538
|
+
if (!expandable) {
|
|
1539
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("flex min-w-0 items-center gap-1.5 text-sm", error ? "text-destructive" : "text-muted-foreground"), children: content });
|
|
1540
|
+
}
|
|
1541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "w-full", children: [
|
|
1542
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1543
|
+
"button",
|
|
1544
|
+
{
|
|
1545
|
+
"aria-expanded": open,
|
|
1546
|
+
className: cn(
|
|
1547
|
+
"group flex min-w-0 items-center gap-1.5 rounded-md px-0 py-0.5 text-left text-sm transition-colors hover:text-foreground",
|
|
1548
|
+
error ? "text-destructive" : "text-muted-foreground"
|
|
1549
|
+
),
|
|
1550
|
+
onClick: () => setOpen((value) => !value),
|
|
1551
|
+
type: "button",
|
|
1552
|
+
children: [
|
|
1553
|
+
content,
|
|
1554
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronDown, { className: "size-3" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react3.ChevronRight, { className: "size-3 opacity-70" })
|
|
1555
|
+
]
|
|
1556
|
+
}
|
|
1557
|
+
),
|
|
1558
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "mt-1 pl-4 text-sm text-muted-foreground", children: [
|
|
1559
|
+
detail ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { children: detail }) : null,
|
|
1560
|
+
children
|
|
1561
|
+
] }) : null
|
|
1562
|
+
] });
|
|
1563
|
+
}
|
|
1564
|
+
function ToolPartRow({
|
|
1565
|
+
activeToolId,
|
|
1566
|
+
part
|
|
1567
|
+
}) {
|
|
1568
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1569
|
+
CompactTask,
|
|
1570
|
+
{
|
|
1571
|
+
defaultOpen: part.state === "output-error",
|
|
1572
|
+
detail: toolDetail(part),
|
|
1573
|
+
error: part.state === "output-error",
|
|
1574
|
+
label: toolLabel(part),
|
|
1575
|
+
loading: isActiveTool(part, activeToolId)
|
|
1576
|
+
}
|
|
1577
|
+
) });
|
|
1578
|
+
}
|
|
1579
|
+
function ToolGroup({
|
|
1580
|
+
activeToolId,
|
|
1581
|
+
parts
|
|
1582
|
+
}) {
|
|
1583
|
+
const loading = parts.some((part) => isActiveTool(part, activeToolId));
|
|
1584
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CompactTask, { label: groupLabel(parts[0]), loading, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-1", children: parts.map((part) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1585
|
+
"div",
|
|
1586
|
+
{
|
|
1587
|
+
className: cn("text-sm", part.state === "output-error" ? "text-destructive" : "text-muted-foreground"),
|
|
1588
|
+
children: [
|
|
1589
|
+
isActiveTool(part, activeToolId) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { children: toolLabel(part) }) : toolLabel(part),
|
|
1590
|
+
toolDetail(part) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "ml-1 text-xs text-muted-foreground/75", children: toolDetail(part) }) : null
|
|
1591
|
+
]
|
|
1592
|
+
},
|
|
1593
|
+
part.id
|
|
1594
|
+
)) }) });
|
|
1595
|
+
}
|
|
1596
|
+
function HitlPartRow({
|
|
1597
|
+
getHitlActionState,
|
|
1598
|
+
isLoading,
|
|
1599
|
+
onHitlAction,
|
|
1600
|
+
part
|
|
1601
|
+
}) {
|
|
1602
|
+
const state = getHitlActionState?.(part);
|
|
1603
|
+
const [comment, setComment] = React6.useState("");
|
|
1604
|
+
const presentation = part.presentation || {};
|
|
1605
|
+
const requireComment = presentation.require_comment === true;
|
|
1606
|
+
const isTerminal = part.status !== "pending";
|
|
1607
|
+
const resolvedRef = React6.useRef(null);
|
|
1608
|
+
const wasTerminal = React6.useRef(isTerminal);
|
|
1609
|
+
React6.useEffect(() => {
|
|
1610
|
+
if (isTerminal && !wasTerminal.current) resolvedRef.current?.focus();
|
|
1611
|
+
wasTerminal.current = isTerminal;
|
|
1612
|
+
}, [isTerminal]);
|
|
1613
|
+
if (isTerminal) {
|
|
1614
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl a24-hitl--resolved px-1 py-1 text-sm text-muted-foreground", ref: resolvedRef, role: "status", tabIndex: -1, children: resolvedHitlLabel(part) });
|
|
1615
|
+
}
|
|
1616
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-hitl space-y-2.5 px-1 py-2", children: [
|
|
1617
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__title text-sm font-medium text-foreground text-pretty", children: hitlTitle(part) }),
|
|
1618
|
+
requireComment ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1619
|
+
"textarea",
|
|
1620
|
+
{
|
|
1621
|
+
"aria-label": "Approval comment",
|
|
1622
|
+
autoComplete: "off",
|
|
1623
|
+
className: "a24-hitl__comment min-h-20 w-full resize-y rounded-md border border-border/60 bg-background px-3 py-2 text-sm outline-none focus-visible:border-neutral-300 focus-visible:ring-0 dark:focus-visible:border-neutral-600",
|
|
1624
|
+
name: "hitl-comment",
|
|
1625
|
+
onChange: (event) => setComment(event.target.value),
|
|
1626
|
+
placeholder: "Comment required\u2026",
|
|
1627
|
+
value: comment
|
|
1628
|
+
}
|
|
1629
|
+
) : null,
|
|
1630
|
+
state?.error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__error text-sm text-destructive", children: state.error }) : null,
|
|
1631
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-hitl__actions flex flex-wrap items-center gap-1.5", children: part.allowedActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1632
|
+
"button",
|
|
1633
|
+
{
|
|
1634
|
+
"aria-label": hitlActionLabel(part, action),
|
|
1635
|
+
className: cn(
|
|
1636
|
+
"a24-hitl__action min-h-10 rounded-md px-3 text-sm font-medium transition-[background-color,color,opacity,transform] duration-150 ease-out active:scale-[0.96] disabled:pointer-events-none disabled:opacity-60",
|
|
1637
|
+
action === "approve" || action === "connect" ? "bg-neutral-900 text-white hover:bg-neutral-800 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-white" : "bg-transparent text-muted-foreground hover:bg-background/80 hover:text-foreground"
|
|
1638
|
+
),
|
|
1639
|
+
"data-primary": action === "approve" || action === "connect" ? "" : void 0,
|
|
1640
|
+
disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming" || requireComment && !comment.trim(),
|
|
1641
|
+
onClick: () => onHitlAction?.(part, action, comment.trim() || void 0),
|
|
1642
|
+
type: "button",
|
|
1643
|
+
children: hitlActionLabel(part, action)
|
|
1644
|
+
},
|
|
1645
|
+
action
|
|
1646
|
+
)) })
|
|
1647
|
+
] });
|
|
1648
|
+
}
|
|
1649
|
+
function DefaultPartRow({
|
|
1650
|
+
activeToolId,
|
|
1651
|
+
getHitlActionState,
|
|
1652
|
+
isLoading,
|
|
1653
|
+
message,
|
|
1654
|
+
onHitlAction,
|
|
1655
|
+
part
|
|
1656
|
+
}) {
|
|
1657
|
+
if (part.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageResponse, { children: part.text });
|
|
1658
|
+
if (part.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolPartRow, { activeToolId, part });
|
|
1659
|
+
if (part.kind === "reasoning") {
|
|
1660
|
+
const label = part.text || part.label || "Thinking";
|
|
1661
|
+
return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-muted-foreground", children: label });
|
|
1662
|
+
}
|
|
1663
|
+
if (part.kind === "ui-blocks") {
|
|
1664
|
+
if (part.state === "output-error") {
|
|
1665
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
|
|
1666
|
+
}
|
|
1667
|
+
return part.bundle ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SafeUiBlocks, { bundle: part.bundle }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
|
|
1668
|
+
}
|
|
1669
|
+
if (part.kind === "hitl") {
|
|
1670
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1671
|
+
HitlPartRow,
|
|
1672
|
+
{
|
|
1673
|
+
getHitlActionState,
|
|
1674
|
+
isLoading,
|
|
1675
|
+
onHitlAction,
|
|
1676
|
+
part
|
|
1677
|
+
}
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
if (part.kind === "error") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-destructive", role: "alert", children: part.errorText });
|
|
1681
|
+
if (part.kind === "source") {
|
|
1682
|
+
const href = safeExternalUrl(part.url);
|
|
1683
|
+
return href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { className: "a24-source-link", href, rel: "noopener noreferrer", target: "_blank", children: part.title }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-source-link", children: part.title });
|
|
1684
|
+
}
|
|
1685
|
+
if (part.kind === "citation") {
|
|
1686
|
+
const href = safeExternalUrl(part.url);
|
|
1687
|
+
const label = part.label || "Citation";
|
|
1688
|
+
return href ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("a", { className: "a24-citation", href, rel: "noopener noreferrer", target: "_blank", children: label }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-citation", children: label });
|
|
1689
|
+
}
|
|
1690
|
+
if (part.kind === "attachment") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-output-attachment", children: part.attachment.filename || "Attachment" });
|
|
1691
|
+
if (part.kind === "data") return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
|
|
1692
|
+
return null;
|
|
1693
|
+
}
|
|
1694
|
+
function safeExternalUrl(value) {
|
|
1695
|
+
if (!value) return null;
|
|
1696
|
+
try {
|
|
1697
|
+
const url = new URL(value);
|
|
1698
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
|
|
1699
|
+
} catch {
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
function SafeUiBlocks({ bundle }) {
|
|
1704
|
+
const rows = Array.isArray(bundle.rows) ? bundle.rows : [];
|
|
1705
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { "aria-label": typeof bundle.title === "string" ? bundle.title : "Agent result", className: "a24-ui-blocks", children: [
|
|
1706
|
+
typeof bundle.title === "string" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h3", { children: bundle.title }) : null,
|
|
1707
|
+
rows.map((rawRow, rowIndex) => {
|
|
1708
|
+
const row = rawRow && typeof rawRow === "object" && !Array.isArray(rawRow) ? rawRow : {};
|
|
1709
|
+
const blocks = Array.isArray(row.blocks) ? row.blocks : [];
|
|
1710
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-ui-blocks__row", children: blocks.map((rawBlock, blockIndex) => {
|
|
1711
|
+
const block = rawBlock && typeof rawBlock === "object" && !Array.isArray(rawBlock) ? rawBlock : {};
|
|
1712
|
+
const title = typeof block.title === "string" ? block.title : "Result";
|
|
1713
|
+
const kind = String(block.kind || "note");
|
|
1714
|
+
if (kind === "table" && Array.isArray(block.columns) && Array.isArray(block.rows)) {
|
|
1715
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-ui-block", children: [
|
|
1716
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: title }),
|
|
1717
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "a24-ui-table-scroll", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { children: [
|
|
1718
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: block.columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { children: String(column) }, index)) }) }),
|
|
1719
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: block.rows.map((rawCells, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tr", { children: (Array.isArray(rawCells) ? rawCells : []).map((cell, cellIndex) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { children: String(cell) }, cellIndex)) }, index)) })
|
|
1720
|
+
] }) })
|
|
1721
|
+
] }, String(block.id || blockIndex));
|
|
1722
|
+
}
|
|
1723
|
+
const value = block.value ?? block.text ?? "";
|
|
1724
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-ui-block", children: [
|
|
1725
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h4", { children: title }),
|
|
1726
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { children: String(value) })
|
|
1727
|
+
] }, String(block.id || blockIndex));
|
|
1728
|
+
}) }, `row-${rowIndex}`);
|
|
1729
|
+
})
|
|
1730
|
+
] });
|
|
1731
|
+
}
|
|
1732
|
+
function AgentResponseTimeline({
|
|
1733
|
+
getHitlActionState,
|
|
1734
|
+
isLoading = false,
|
|
1735
|
+
message,
|
|
1736
|
+
onHitlAction,
|
|
1737
|
+
renderPart
|
|
1738
|
+
}) {
|
|
1739
|
+
const parts = message.parts || [];
|
|
1740
|
+
const activeToolId = [...parts].reverse().find((part) => part.kind === "tool" && (part.state === "input-available" || part.state === "input-streaming"))?.id;
|
|
1741
|
+
const rendered = [];
|
|
1742
|
+
let index = 0;
|
|
1743
|
+
while (index < parts.length) {
|
|
1744
|
+
const part = parts[index];
|
|
1745
|
+
if (part.kind === "tool" && groupKey(part)) {
|
|
1746
|
+
const key = groupKey(part);
|
|
1747
|
+
const group = [];
|
|
1748
|
+
while (index < parts.length) {
|
|
1749
|
+
const candidate = parts[index];
|
|
1750
|
+
if (candidate.kind !== "tool" || groupKey(candidate) !== key) break;
|
|
1751
|
+
group.push(candidate);
|
|
1752
|
+
index += 1;
|
|
1753
|
+
}
|
|
1754
|
+
rendered.push(group.length === 1 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolPartRow, { activeToolId, part: group[0] }, group[0].id) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolGroup, { activeToolId, parts: group }, `tool-group-${key}-${group[0].id}`));
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
rendered.push(
|
|
1758
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(React6.Fragment, { children: renderPart ? renderPart(part, message) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1759
|
+
DefaultPartRow,
|
|
1760
|
+
{
|
|
1761
|
+
activeToolId,
|
|
1762
|
+
getHitlActionState,
|
|
1763
|
+
isLoading,
|
|
1764
|
+
message,
|
|
1765
|
+
onHitlAction,
|
|
1766
|
+
part
|
|
1767
|
+
}
|
|
1768
|
+
) }, part.id)
|
|
1769
|
+
);
|
|
1770
|
+
index += 1;
|
|
1771
|
+
}
|
|
1772
|
+
if (rendered.length === 0 && message.content) {
|
|
1773
|
+
rendered.push(/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageResponse, { children: message.content }, `${message.id}-fallback`));
|
|
1774
|
+
}
|
|
1775
|
+
if (rendered.length === 0 && isLoading) {
|
|
1776
|
+
rendered.push(/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "text-sm text-muted-foreground", children: "Thinking" }, `${message.id}-thinking`));
|
|
1777
|
+
}
|
|
1778
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "space-y-3", children: rendered });
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// src/ui/agent-chat-message.tsx
|
|
1782
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1783
|
+
var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
|
|
1784
|
+
var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
|
|
1785
|
+
function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
|
|
1786
|
+
return typeof threshold === "number" && threshold > 0 && content.length > threshold;
|
|
1787
|
+
}
|
|
1788
|
+
function AgentChatUserMessageContent({
|
|
1789
|
+
collapsedLines = DEFAULT_USER_MESSAGE_COLLAPSED_LINES,
|
|
1790
|
+
collapseThreshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD,
|
|
1791
|
+
content,
|
|
1792
|
+
showLessLabel = "Show less",
|
|
1793
|
+
showMoreLabel = "Show more"
|
|
1794
|
+
}) {
|
|
1795
|
+
const [isExpanded, setIsExpanded] = React7.useState(false);
|
|
1796
|
+
const contentId = React7.useId();
|
|
1797
|
+
const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
|
|
1798
|
+
const Icon = isExpanded ? import_lucide_react4.ChevronUp : import_lucide_react4.ChevronDown;
|
|
1799
|
+
const clampedStyle = shouldCollapse && !isExpanded ? {
|
|
1800
|
+
WebkitBoxOrient: "vertical",
|
|
1801
|
+
WebkitLineClamp: Math.max(1, collapsedLines),
|
|
1802
|
+
display: "-webkit-box",
|
|
1803
|
+
overflow: "hidden"
|
|
1804
|
+
} : void 0;
|
|
1805
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex min-w-0 flex-col gap-3", children: [
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
|
|
1807
|
+
shouldCollapse ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1808
|
+
"button",
|
|
1809
|
+
{
|
|
1810
|
+
"aria-controls": contentId,
|
|
1811
|
+
"aria-expanded": isExpanded,
|
|
1812
|
+
className: cn(
|
|
1813
|
+
"inline-flex w-fit items-center gap-1 rounded-md text-left text-sm font-medium text-muted-foreground transition-colors",
|
|
1814
|
+
"hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0"
|
|
1815
|
+
),
|
|
1816
|
+
onClick: () => setIsExpanded((current) => !current),
|
|
1817
|
+
type: "button",
|
|
1818
|
+
children: [
|
|
1819
|
+
isExpanded ? showLessLabel : showMoreLabel,
|
|
1820
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
|
|
1821
|
+
]
|
|
1822
|
+
}
|
|
1823
|
+
) : null
|
|
1824
|
+
] });
|
|
1825
|
+
}
|
|
1826
|
+
function AgentChatMessage({
|
|
1827
|
+
actionHandlers,
|
|
1828
|
+
actions,
|
|
1829
|
+
actionsClassName,
|
|
1830
|
+
className,
|
|
1831
|
+
contentClassName,
|
|
1832
|
+
dir,
|
|
1833
|
+
getHitlActionState,
|
|
1834
|
+
message,
|
|
1835
|
+
onHitlAction,
|
|
1836
|
+
renderAssistantContent,
|
|
1837
|
+
renderPart,
|
|
1838
|
+
renderUserContent,
|
|
1839
|
+
showAssistantAttachments = false,
|
|
1840
|
+
streamingMessageId,
|
|
1841
|
+
userMessageCollapsedLines,
|
|
1842
|
+
userMessageContent,
|
|
1843
|
+
userMessageCollapseThreshold,
|
|
1844
|
+
userMessageShowLessLabel,
|
|
1845
|
+
userMessageShowMoreLabel
|
|
1846
|
+
}) {
|
|
1847
|
+
const isAssistant = message.role === "assistant";
|
|
1848
|
+
const isStreaming = isAssistant && message.id === streamingMessageId;
|
|
1849
|
+
const resolvedUserContent = userMessageContent ?? message.content;
|
|
1850
|
+
const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
|
|
1851
|
+
const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
|
|
1852
|
+
const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AgentChatDefaultActions, { handlers: actionHandlers, message }) : null);
|
|
1853
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
|
|
1854
|
+
showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1855
|
+
ChatAttachment,
|
|
1856
|
+
{
|
|
1857
|
+
data: attachment,
|
|
1858
|
+
dir: "ltr"
|
|
1859
|
+
},
|
|
1860
|
+
String(attachment.url || attachment.filename || index)
|
|
1861
|
+
)) }) : null,
|
|
1862
|
+
shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
|
|
1863
|
+
ChatMessageContent,
|
|
1864
|
+
{
|
|
1865
|
+
className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
|
|
1866
|
+
dir,
|
|
1867
|
+
children: [
|
|
1868
|
+
isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1869
|
+
AgentResponseTimeline,
|
|
1870
|
+
{
|
|
1871
|
+
getHitlActionState,
|
|
1872
|
+
isLoading: isStreaming,
|
|
1873
|
+
message,
|
|
1874
|
+
onHitlAction,
|
|
1875
|
+
renderPart
|
|
1876
|
+
}
|
|
1877
|
+
) : null,
|
|
1878
|
+
!isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1879
|
+
AgentChatUserMessageContent,
|
|
1880
|
+
{
|
|
1881
|
+
collapsedLines: userMessageCollapsedLines,
|
|
1882
|
+
collapseThreshold: userMessageCollapseThreshold,
|
|
1883
|
+
content: resolvedUserContent,
|
|
1884
|
+
showLessLabel: userMessageShowLessLabel,
|
|
1885
|
+
showMoreLabel: userMessageShowMoreLabel
|
|
1886
|
+
}
|
|
1887
|
+
) : null : null
|
|
1888
|
+
]
|
|
1889
|
+
}
|
|
1890
|
+
) : null,
|
|
1891
|
+
renderedActions ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full translate-x-2" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
|
|
1892
|
+
] });
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
// src/templates/agent-chat-layout.tsx
|
|
1896
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1897
|
+
var stateCopy = {
|
|
1898
|
+
loading: { title: "Loading chat", detail: "Your conversation is being prepared." },
|
|
1899
|
+
reconnecting: { title: "Reconnecting", detail: "Messages are safe. Trying the connection again." },
|
|
1900
|
+
offline: { title: "You\u2019re offline", detail: "Reconnect to continue this conversation." },
|
|
1901
|
+
denied: { title: "Access denied", detail: "This deployment does not allow this request." },
|
|
1902
|
+
quota: { title: "Usage limit reached", detail: "Try again after the deployment limit resets." },
|
|
1903
|
+
failed: { title: "Something went wrong", detail: "The request could not be completed safely." },
|
|
1904
|
+
expired: { title: "Session expired", detail: "Reconnect to start a new secure session." }
|
|
1905
|
+
};
|
|
1906
|
+
function AgentChatStatePanel({
|
|
1907
|
+
errorMessage,
|
|
1908
|
+
onRetry,
|
|
1909
|
+
state
|
|
1910
|
+
}) {
|
|
1911
|
+
const copy = stateCopy[state];
|
|
1912
|
+
const retryable = state === "offline" || state === "failed" || state === "expired";
|
|
1913
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-state", "data-state": state, role: state === "failed" ? "alert" : "status", children: [
|
|
1914
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-state__icon", "aria-hidden": "true", children: state === "offline" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.WifiOff, {}) : state === "reconnecting" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.RefreshCw, {}) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.MessageSquare, {}) }),
|
|
1915
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { children: copy.title }),
|
|
1916
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { children: errorMessage || copy.detail }),
|
|
1917
|
+
retryable && onRetry ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "a24-button a24-button--primary", onClick: () => void onRetry(), type: "button", children: "Try again" }) : null
|
|
1918
|
+
] });
|
|
1919
|
+
}
|
|
1920
|
+
function connectionLabel(state) {
|
|
1921
|
+
if (state === "connected") return "Connected";
|
|
1922
|
+
if (state === "connecting") return "Connecting";
|
|
1923
|
+
if (state === "reconnecting") return "Reconnecting";
|
|
1924
|
+
if (state === "expired") return "Session expired";
|
|
1925
|
+
return "Offline";
|
|
1926
|
+
}
|
|
1927
|
+
function AgentAvatar({ agent }) {
|
|
1928
|
+
return agent.avatarUrl ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { alt: "", className: "a24-avatar", height: 36, src: agent.avatarUrl, width: 36 }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": "true", className: "a24-avatar a24-avatar--fallback", children: agent.name.trim().slice(0, 1).toUpperCase() || "A" });
|
|
1929
|
+
}
|
|
1930
|
+
function ThreadNavigation({
|
|
1931
|
+
activeThreadId,
|
|
1932
|
+
close,
|
|
1933
|
+
hasMoreThreads,
|
|
1934
|
+
isDeletingThread,
|
|
1935
|
+
isLoadingThreads,
|
|
1936
|
+
onDeleteThread,
|
|
1937
|
+
onLoadMoreThreads,
|
|
1938
|
+
onNewThread,
|
|
1939
|
+
onOpenThread,
|
|
1940
|
+
slots,
|
|
1941
|
+
threads
|
|
1942
|
+
}) {
|
|
1943
|
+
const [query, setQuery] = React8.useState("");
|
|
1944
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
1945
|
+
const visibleThreads = normalizedQuery ? threads.filter((thread) => String(thread.title || "New chat").toLocaleLowerCase().includes(normalizedQuery)) : threads;
|
|
1946
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-sidebar__inner", children: [
|
|
1947
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-sidebar__top", children: [
|
|
1948
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1949
|
+
"button",
|
|
1950
|
+
{
|
|
1951
|
+
"aria-label": "New chat",
|
|
1952
|
+
className: "a24-button a24-button--primary a24-sidebar__new",
|
|
1953
|
+
onClick: () => {
|
|
1954
|
+
onNewThread();
|
|
1955
|
+
close();
|
|
1956
|
+
},
|
|
1957
|
+
type: "button",
|
|
1958
|
+
children: [
|
|
1959
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Plus, { "aria-hidden": "true" }),
|
|
1960
|
+
"New chat"
|
|
1961
|
+
]
|
|
1962
|
+
}
|
|
1963
|
+
),
|
|
1964
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Close conversations", className: "a24-icon-button a24-sidebar__mobile-close", onClick: close, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.X, { "aria-hidden": "true" }) })
|
|
1965
|
+
] }),
|
|
1966
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "a24-search", children: [
|
|
1967
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Search, { "aria-hidden": "true" }),
|
|
1968
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-sr-only", children: "Search conversations" }),
|
|
1969
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1970
|
+
"input",
|
|
1971
|
+
{
|
|
1972
|
+
autoComplete: "off",
|
|
1973
|
+
name: "conversation-search",
|
|
1974
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1975
|
+
placeholder: "Search conversations\u2026",
|
|
1976
|
+
type: "search",
|
|
1977
|
+
value: query
|
|
1978
|
+
}
|
|
1979
|
+
)
|
|
1980
|
+
] }),
|
|
1981
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("nav", { "aria-label": "Conversations", className: "a24-thread-list", children: [
|
|
1982
|
+
isLoadingThreads && threads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "a24-sidebar__hint", role: "status", children: "Loading conversations\u2026" }) : null,
|
|
1983
|
+
!isLoadingThreads && visibleThreads.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "a24-sidebar__hint", children: "No conversations yet." }) : null,
|
|
1984
|
+
visibleThreads.map((thread) => {
|
|
1985
|
+
const selected = thread.id === activeThreadId;
|
|
1986
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-thread", "data-selected": selected || void 0, children: [
|
|
1987
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1988
|
+
"button",
|
|
1989
|
+
{
|
|
1990
|
+
"aria-current": selected ? "page" : void 0,
|
|
1991
|
+
className: "a24-thread__open",
|
|
1992
|
+
onClick: () => {
|
|
1993
|
+
void onOpenThread(thread.id);
|
|
1994
|
+
close();
|
|
1995
|
+
},
|
|
1996
|
+
type: "button",
|
|
1997
|
+
children: [
|
|
1998
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-thread__title", children: thread.title || "New chat" }),
|
|
1999
|
+
thread.status ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-thread__status", children: thread.status }) : null
|
|
2000
|
+
]
|
|
2001
|
+
}
|
|
2002
|
+
),
|
|
2003
|
+
slots?.threadActions ? slots.threadActions(thread) : onDeleteThread ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2004
|
+
"button",
|
|
2005
|
+
{
|
|
2006
|
+
"aria-label": `Delete ${thread.title || "conversation"}`,
|
|
2007
|
+
className: "a24-icon-button a24-thread__delete",
|
|
2008
|
+
disabled: isDeletingThread,
|
|
2009
|
+
onClick: () => void onDeleteThread(thread.id),
|
|
2010
|
+
type: "button",
|
|
2011
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Trash2, { "aria-hidden": "true" })
|
|
2012
|
+
}
|
|
2013
|
+
) : null
|
|
2014
|
+
] }, thread.id);
|
|
2015
|
+
})
|
|
2016
|
+
] }),
|
|
2017
|
+
hasMoreThreads && onLoadMoreThreads ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { className: "a24-button a24-button--quiet a24-sidebar__more", onClick: () => void onLoadMoreThreads(), type: "button", children: [
|
|
2018
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.MoreHorizontal, { "aria-hidden": "true" }),
|
|
2019
|
+
"Load more"
|
|
2020
|
+
] }) : null
|
|
2021
|
+
] });
|
|
2022
|
+
}
|
|
2023
|
+
function AgentChatLayout({
|
|
2024
|
+
activeThreadId,
|
|
2025
|
+
agent,
|
|
2026
|
+
allowAttachments = true,
|
|
2027
|
+
className,
|
|
2028
|
+
connectionState = "connected",
|
|
2029
|
+
dir = "ltr",
|
|
2030
|
+
errorMessage,
|
|
2031
|
+
hasMoreThreads,
|
|
2032
|
+
isDeletingThread,
|
|
2033
|
+
isLoadingOlder,
|
|
2034
|
+
isLoadingThreads,
|
|
2035
|
+
isRunning = false,
|
|
2036
|
+
messages,
|
|
2037
|
+
modelId,
|
|
2038
|
+
models = [],
|
|
2039
|
+
onDeleteThread,
|
|
2040
|
+
onHitlAction,
|
|
2041
|
+
onLoadMoreThreads,
|
|
2042
|
+
onLoadOlder,
|
|
2043
|
+
onModelChange,
|
|
2044
|
+
onNewThread,
|
|
2045
|
+
onOpenThread,
|
|
2046
|
+
onRetryConnection,
|
|
2047
|
+
onStop,
|
|
2048
|
+
onSubmit,
|
|
2049
|
+
placeholder,
|
|
2050
|
+
slots,
|
|
2051
|
+
state = "ready",
|
|
2052
|
+
streamingMessageId,
|
|
2053
|
+
threads,
|
|
2054
|
+
title
|
|
2055
|
+
}) {
|
|
2056
|
+
const [sidebarOpen, setSidebarOpen] = React8.useState(false);
|
|
2057
|
+
const menuButtonRef = React8.useRef(null);
|
|
2058
|
+
const closeSidebar = React8.useCallback(() => {
|
|
2059
|
+
setSidebarOpen(false);
|
|
2060
|
+
menuButtonRef.current?.focus();
|
|
2061
|
+
}, []);
|
|
2062
|
+
React8.useEffect(() => {
|
|
2063
|
+
if (!sidebarOpen) return void 0;
|
|
2064
|
+
const onKeyDown = (event) => {
|
|
2065
|
+
if (event.key !== "Escape") return;
|
|
2066
|
+
event.preventDefault();
|
|
2067
|
+
closeSidebar();
|
|
2068
|
+
};
|
|
2069
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2070
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2071
|
+
}, [closeSidebar, sidebarOpen]);
|
|
2072
|
+
const shellState = state === "streaming" || state === "paused" ? "ready" : state;
|
|
2073
|
+
const showState = shellState !== "ready";
|
|
2074
|
+
const liveLabel = state === "streaming" ? "Agent is responding" : state === "paused" ? "Waiting for your response" : connectionLabel(connectionState);
|
|
2075
|
+
const composer = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2076
|
+
AgentChatComposer,
|
|
2077
|
+
{
|
|
2078
|
+
allowAttachments,
|
|
2079
|
+
disabled: state === "denied" || state === "quota" || state === "expired" || state === "offline",
|
|
2080
|
+
inputToolbarContent: models.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("label", { className: "a24-model-select", children: [
|
|
2081
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "a24-sr-only", children: "Model" }),
|
|
2082
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("select", { "aria-label": "Model", onChange: (event) => onModelChange?.(event.target.value), value: modelId || models[0]?.id, children: models.map((model) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: model.id, children: model.label }, model.id)) }),
|
|
2083
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true" })
|
|
2084
|
+
] }) : void 0,
|
|
2085
|
+
isRunning,
|
|
2086
|
+
onStop,
|
|
2087
|
+
onSubmit,
|
|
2088
|
+
placeholder
|
|
2089
|
+
}
|
|
2090
|
+
);
|
|
2091
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: cn("a24-chat", className), "data-agents24-chat-layout": "", dir, children: [
|
|
2092
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("a", { className: "a24-skip-link", href: "#agents24-chat-main", children: "Skip to messages" }),
|
|
2093
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("nav", { "aria-label": "Chat navigation", className: "a24-sidebar", "data-open": sidebarOpen || void 0, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2094
|
+
ThreadNavigation,
|
|
2095
|
+
{
|
|
2096
|
+
activeThreadId,
|
|
2097
|
+
close: closeSidebar,
|
|
2098
|
+
hasMoreThreads,
|
|
2099
|
+
isDeletingThread,
|
|
2100
|
+
isLoadingThreads,
|
|
2101
|
+
onDeleteThread,
|
|
2102
|
+
onLoadMoreThreads,
|
|
2103
|
+
onNewThread,
|
|
2104
|
+
onOpenThread,
|
|
2105
|
+
slots,
|
|
2106
|
+
threads
|
|
2107
|
+
}
|
|
2108
|
+
) }),
|
|
2109
|
+
sidebarOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Close conversations", className: "a24-sidebar-backdrop", onClick: closeSidebar, type: "button" }) : null,
|
|
2110
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-chat__body", children: [
|
|
2111
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("header", { className: "a24-header", role: "banner", children: [
|
|
2112
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { "aria-label": "Open conversations", className: "a24-icon-button a24-header__menu", onClick: () => setSidebarOpen(true), ref: menuButtonRef, type: "button", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Menu, { "aria-hidden": "true" }) }),
|
|
2113
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentAvatar, { agent }),
|
|
2114
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-header__identity", children: [
|
|
2115
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { children: title || agent.name }),
|
|
2116
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "a24-connection", "data-state": connectionState, children: [
|
|
2117
|
+
connectionState === "offline" || connectionState === "expired" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.WifiOff, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Wifi, { "aria-hidden": "true" }),
|
|
2118
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: connectionLabel(connectionState) })
|
|
2119
|
+
] })
|
|
2120
|
+
] }),
|
|
2121
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "a24-header__actions", children: slots?.headerActions })
|
|
2122
|
+
] }),
|
|
2123
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("main", { "aria-busy": state === "loading" || state === "reconnecting", className: "a24-main", id: "agents24-chat-main", children: showState ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(AgentChatStatePanel, { errorMessage, onRetry: onRetryConnection, state: shellState }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2124
|
+
AgentChatShell,
|
|
2125
|
+
{
|
|
2126
|
+
composer,
|
|
2127
|
+
dir,
|
|
2128
|
+
isLoadingOlder,
|
|
2129
|
+
messages,
|
|
2130
|
+
onLoadOlder,
|
|
2131
|
+
renderEmptyState: slots?.empty ? () => slots.empty : void 0,
|
|
2132
|
+
renderMessage: (message) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2133
|
+
AgentChatMessage,
|
|
2134
|
+
{
|
|
2135
|
+
actions: slots?.messageActions?.(message),
|
|
2136
|
+
message,
|
|
2137
|
+
onHitlAction: (part, action, comment) => void onHitlAction?.(part, action, comment),
|
|
2138
|
+
renderAssistantContent: slots?.assistantBody,
|
|
2139
|
+
streamingMessageId
|
|
2140
|
+
}
|
|
2141
|
+
)
|
|
2142
|
+
}
|
|
2143
|
+
) }),
|
|
2144
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { "aria-live": "polite", "aria-atomic": "true", className: "a24-sr-only", children: liveLabel })
|
|
2145
|
+
] })
|
|
2146
|
+
] });
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
// src/templates/agent-chat-app.tsx
|
|
2150
|
+
var import_react2 = require("@agents24/react");
|
|
2151
|
+
var React9 = __toESM(require("react"), 1);
|
|
2152
|
+
|
|
2153
|
+
// src/mcp-oauth.ts
|
|
2154
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
2155
|
+
function isLoopback(hostname) {
|
|
2156
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
|
|
2157
|
+
}
|
|
2158
|
+
function normalizedOrigin(value) {
|
|
2159
|
+
try {
|
|
2160
|
+
const url = new URL(value);
|
|
2161
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && isLoopback(url.hostname))) {
|
|
2162
|
+
throw new Error("unsafe callback origin");
|
|
2163
|
+
}
|
|
2164
|
+
if (url.origin === "null") throw new Error("opaque callback origin");
|
|
2165
|
+
return url.origin;
|
|
2166
|
+
} catch {
|
|
2167
|
+
throw new Error("MCP authorization returned an invalid callback origin.");
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
function validateMcpCallbackResult(result, expectedServerId) {
|
|
2171
|
+
const serverId = String(result.server_id || "").trim();
|
|
2172
|
+
const connectionId = String(result.connection_id || "").trim();
|
|
2173
|
+
if (!serverId || serverId !== expectedServerId) {
|
|
2174
|
+
throw new Error("MCP authorization completed for an unexpected server.");
|
|
2175
|
+
}
|
|
2176
|
+
if (!UUID_PATTERN.test(connectionId)) {
|
|
2177
|
+
throw new Error("MCP authorization returned an invalid connection identity.");
|
|
2178
|
+
}
|
|
2179
|
+
return result;
|
|
2180
|
+
}
|
|
2181
|
+
function waitForMcpOauthComplete({
|
|
2182
|
+
popup,
|
|
2183
|
+
callbackOrigin,
|
|
2184
|
+
popupNonce,
|
|
2185
|
+
requireConnectionId = false,
|
|
2186
|
+
serverId,
|
|
2187
|
+
timeoutMs = 12e4
|
|
2188
|
+
}) {
|
|
2189
|
+
if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
|
|
2190
|
+
const expectedOrigin = normalizedOrigin(callbackOrigin);
|
|
2191
|
+
const expectedServerId = String(serverId || "").trim();
|
|
2192
|
+
const expectedPopupNonce = String(popupNonce || "").trim();
|
|
2193
|
+
if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
|
|
2194
|
+
if (!expectedPopupNonce) return Promise.reject(new Error("MCP authorization is missing its popup binding."));
|
|
2195
|
+
const returnFocusTo = typeof document !== "undefined" ? document.activeElement : null;
|
|
2196
|
+
return new Promise((resolve, reject) => {
|
|
2197
|
+
let settled = false;
|
|
2198
|
+
const finish = (result) => {
|
|
2199
|
+
if (settled) return;
|
|
2200
|
+
settled = true;
|
|
2201
|
+
window.clearTimeout(timeout);
|
|
2202
|
+
window.clearInterval(closeTimer);
|
|
2203
|
+
window.removeEventListener("message", onMessage);
|
|
2204
|
+
returnFocusTo?.focus?.();
|
|
2205
|
+
if (result instanceof Error) reject(result);
|
|
2206
|
+
else resolve(result);
|
|
2207
|
+
};
|
|
2208
|
+
const onMessage = (event) => {
|
|
2209
|
+
if (event.source !== popup || event.origin !== expectedOrigin) return;
|
|
2210
|
+
const data = event.data;
|
|
2211
|
+
if (data?.type !== "agents24.mcp.callback" || data.server_id !== expectedServerId || data.popup_nonce !== expectedPopupNonce) return;
|
|
2212
|
+
const code = String(data.code || "").trim();
|
|
2213
|
+
if (!code || code.length > 2048) return;
|
|
2214
|
+
const connectionId = String(data.connection_id || "").trim();
|
|
2215
|
+
if ((requireConnectionId || connectionId) && !UUID_PATTERN.test(connectionId)) return;
|
|
2216
|
+
finish({
|
|
2217
|
+
code,
|
|
2218
|
+
...connectionId ? { connectionId } : {},
|
|
2219
|
+
popupNonce: expectedPopupNonce,
|
|
2220
|
+
serverId: expectedServerId
|
|
2221
|
+
});
|
|
2222
|
+
};
|
|
2223
|
+
const timeout = window.setTimeout(
|
|
2224
|
+
() => finish(new Error("MCP authorization timed out.")),
|
|
2225
|
+
Math.max(1, timeoutMs)
|
|
2226
|
+
);
|
|
2227
|
+
const closeTimer = window.setInterval(() => {
|
|
2228
|
+
if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
|
|
2229
|
+
}, 500);
|
|
2230
|
+
window.addEventListener("message", onMessage);
|
|
2231
|
+
});
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// src/templates/agent-chat-app.tsx
|
|
2235
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
2236
|
+
function browserCrypto() {
|
|
2237
|
+
const value = globalThis.crypto;
|
|
2238
|
+
if (!value?.subtle || !value.randomUUID) throw new Error("Secure browser crypto is required for MCP authorization.");
|
|
2239
|
+
return value;
|
|
2240
|
+
}
|
|
2241
|
+
function base64Url(bytes) {
|
|
2242
|
+
let binary = "";
|
|
2243
|
+
bytes.forEach((byte) => {
|
|
2244
|
+
binary += String.fromCharCode(byte);
|
|
2245
|
+
});
|
|
2246
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
2247
|
+
}
|
|
2248
|
+
async function createPkceProof() {
|
|
2249
|
+
const crypto2 = browserCrypto();
|
|
2250
|
+
const verifier = base64Url(crypto2.getRandomValues(new Uint8Array(32)));
|
|
2251
|
+
const bytes = new TextEncoder().encode(verifier);
|
|
2252
|
+
const digest = await crypto2.subtle.digest("SHA-256", bytes);
|
|
2253
|
+
return { verifier, challenge: base64Url(new Uint8Array(digest)) };
|
|
2254
|
+
}
|
|
2255
|
+
function stateFromError(error) {
|
|
2256
|
+
const typed = error;
|
|
2257
|
+
if (typed?.kind === "authentication") return "expired";
|
|
2258
|
+
if (typed?.kind === "authorization") return "denied";
|
|
2259
|
+
if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
|
|
2260
|
+
if (typed?.kind === "network") return "offline";
|
|
2261
|
+
return "failed";
|
|
2262
|
+
}
|
|
2263
|
+
function BoundAgentChatApp({
|
|
2264
|
+
agent: agentOverride,
|
|
2265
|
+
client,
|
|
2266
|
+
completeMcpAuthorization,
|
|
2267
|
+
mcpRedirectUri,
|
|
2268
|
+
modelId,
|
|
2269
|
+
models: modelOverrides,
|
|
2270
|
+
onModelChange,
|
|
2271
|
+
slots,
|
|
2272
|
+
...layoutProps
|
|
2273
|
+
}) {
|
|
2274
|
+
const controller = (0, import_react2.useAgentChat)();
|
|
2275
|
+
const attachments = (0, import_react2.useAgentAttachments)();
|
|
2276
|
+
const mcp = (0, import_react2.useAgentMcp)();
|
|
2277
|
+
const [agent, setAgent] = React9.useState(agentOverride || { name: "Agent" });
|
|
2278
|
+
const [capabilities, setCapabilities] = React9.useState([]);
|
|
2279
|
+
const [bootstrapModels, setBootstrapModels] = React9.useState([]);
|
|
2280
|
+
const [selectedModelId, setSelectedModelId] = React9.useState(modelId || null);
|
|
2281
|
+
const [bootstrapLoading, setBootstrapLoading] = React9.useState(true);
|
|
2282
|
+
const [localError, setLocalError] = React9.useState(null);
|
|
2283
|
+
const [liked, setLiked] = React9.useState({});
|
|
2284
|
+
const [disliked, setDisliked] = React9.useState({});
|
|
2285
|
+
const loadBootstrap = React9.useCallback(async () => {
|
|
2286
|
+
setBootstrapLoading(true);
|
|
2287
|
+
setLocalError(null);
|
|
2288
|
+
try {
|
|
2289
|
+
const bootstrap = await client.bootstrap();
|
|
2290
|
+
if (!agentOverride) setAgent({ name: bootstrap.deployment.name || "Agent" });
|
|
2291
|
+
setCapabilities(bootstrap.capabilities);
|
|
2292
|
+
const modelFeatures = bootstrap.features.models;
|
|
2293
|
+
const allowedModelIds = Array.isArray(modelFeatures?.allowed_ids) ? modelFeatures.allowed_ids.filter((value) => typeof value === "string") : [];
|
|
2294
|
+
if (bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable) {
|
|
2295
|
+
setBootstrapModels(allowedModelIds.map((id) => ({ id, label: id })));
|
|
2296
|
+
if (!modelId) {
|
|
2297
|
+
const defaultId = typeof modelFeatures.default_id === "string" ? modelFeatures.default_id : allowedModelIds[0];
|
|
2298
|
+
setSelectedModelId(defaultId || null);
|
|
2299
|
+
}
|
|
2300
|
+
} else {
|
|
2301
|
+
setBootstrapModels([]);
|
|
2302
|
+
}
|
|
2303
|
+
await controller.refreshThreads();
|
|
2304
|
+
} catch (error2) {
|
|
2305
|
+
setLocalError(error2);
|
|
2306
|
+
} finally {
|
|
2307
|
+
setBootstrapLoading(false);
|
|
2308
|
+
}
|
|
2309
|
+
}, [agentOverride, client, controller.refreshThreads, modelId]);
|
|
2310
|
+
React9.useEffect(() => {
|
|
2311
|
+
if (modelId !== void 0) setSelectedModelId(modelId);
|
|
2312
|
+
}, [modelId]);
|
|
2313
|
+
React9.useEffect(() => {
|
|
2314
|
+
void loadBootstrap();
|
|
2315
|
+
}, [loadBootstrap]);
|
|
2316
|
+
const submit = React9.useCallback(async (message) => {
|
|
2317
|
+
setLocalError(null);
|
|
2318
|
+
try {
|
|
2319
|
+
const uploaded = await Promise.all(message.files.map((file) => attachments.upload({
|
|
2320
|
+
data: file.source,
|
|
2321
|
+
name: file.filename,
|
|
2322
|
+
mediaType: file.mediaType,
|
|
2323
|
+
threadId: controller.activeThreadId || void 0
|
|
2324
|
+
})));
|
|
2325
|
+
await controller.submit({
|
|
2326
|
+
text: message.text,
|
|
2327
|
+
attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
|
|
2328
|
+
attachments: uploaded,
|
|
2329
|
+
requestedModelId: selectedModelId || void 0
|
|
2330
|
+
});
|
|
2331
|
+
} catch (error2) {
|
|
2332
|
+
setLocalError(error2);
|
|
2333
|
+
}
|
|
2334
|
+
}, [attachments, controller, selectedModelId]);
|
|
2335
|
+
const deleteThread = React9.useCallback(async (threadId) => {
|
|
2336
|
+
if (!window.confirm("Delete this conversation? This cannot be undone.")) return;
|
|
2337
|
+
await controller.deleteThread(threadId);
|
|
2338
|
+
}, [controller.deleteThread]);
|
|
2339
|
+
const changeModel = React9.useCallback((nextModelId) => {
|
|
2340
|
+
if (modelId === void 0) setSelectedModelId(nextModelId);
|
|
2341
|
+
onModelChange?.(nextModelId);
|
|
2342
|
+
}, [modelId, onModelChange]);
|
|
2343
|
+
const handleMcp = React9.useCallback(async (part) => {
|
|
2344
|
+
if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
|
|
2345
|
+
const serverId = String(part.presentation.server_id || "").trim();
|
|
2346
|
+
if (!serverId) throw new Error("MCP authorization is missing its server identity.");
|
|
2347
|
+
const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
|
|
2348
|
+
if (!runId) throw new Error("MCP authorization is missing its paused run.");
|
|
2349
|
+
const popupNonce = browserCrypto().randomUUID();
|
|
2350
|
+
const proof = await createPkceProof();
|
|
2351
|
+
const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
|
|
2352
|
+
try {
|
|
2353
|
+
const start = await mcp.startAuthorization({
|
|
2354
|
+
runId,
|
|
2355
|
+
serverId,
|
|
2356
|
+
interruptId: part.interruptId,
|
|
2357
|
+
redirectUri: mcpRedirectUri,
|
|
2358
|
+
popupNonce,
|
|
2359
|
+
codeChallenge: proof.challenge
|
|
2360
|
+
});
|
|
2361
|
+
if (!start.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
|
|
2362
|
+
if (popup) popup.location.replace(start.authorization_url);
|
|
2363
|
+
const completion = await waitForMcpOauthComplete({
|
|
2364
|
+
popup,
|
|
2365
|
+
callbackOrigin: start.callback_origin,
|
|
2366
|
+
popupNonce,
|
|
2367
|
+
requireConnectionId: Boolean(completeMcpAuthorization),
|
|
2368
|
+
serverId
|
|
2369
|
+
});
|
|
2370
|
+
if (completeMcpAuthorization) {
|
|
2371
|
+
await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
|
|
2372
|
+
} else {
|
|
2373
|
+
const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
|
|
2374
|
+
validateMcpCallbackResult(redeemed, serverId);
|
|
2375
|
+
}
|
|
2376
|
+
popup?.close();
|
|
2377
|
+
await controller.resumeHitl(part, "connect");
|
|
2378
|
+
} catch (error2) {
|
|
2379
|
+
popup?.close();
|
|
2380
|
+
throw error2;
|
|
2381
|
+
}
|
|
2382
|
+
}, [completeMcpAuthorization, controller, mcp, mcpRedirectUri]);
|
|
2383
|
+
const onHitlAction = React9.useCallback(async (part, action, comment) => {
|
|
2384
|
+
setLocalError(null);
|
|
2385
|
+
try {
|
|
2386
|
+
if (part.hitlKind === "mcp_auth" && action === "connect") await handleMcp(part);
|
|
2387
|
+
else await controller.resumeHitl(part, action, comment);
|
|
2388
|
+
} catch (error2) {
|
|
2389
|
+
setLocalError(error2);
|
|
2390
|
+
}
|
|
2391
|
+
}, [controller, handleMcp]);
|
|
2392
|
+
const error = localError || controller.error;
|
|
2393
|
+
const state = bootstrapLoading ? "loading" : error ? stateFromError(error) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : "ready";
|
|
2394
|
+
const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : bootstrapLoading ? "connecting" : "connected";
|
|
2395
|
+
const defaultMessageActions = (message) => message.role === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2396
|
+
AgentChatDefaultActions,
|
|
2397
|
+
{
|
|
2398
|
+
handlers: {
|
|
2399
|
+
copied: false,
|
|
2400
|
+
disliked: Boolean(disliked[message.id]),
|
|
2401
|
+
liked: Boolean(liked[message.id]),
|
|
2402
|
+
onCopy: async (content) => navigator.clipboard.writeText(content),
|
|
2403
|
+
onDislike: () => {
|
|
2404
|
+
setDisliked((current) => ({ ...current, [message.id]: !current[message.id] }));
|
|
2405
|
+
setLiked((current) => ({ ...current, [message.id]: false }));
|
|
2406
|
+
},
|
|
2407
|
+
onLike: () => {
|
|
2408
|
+
setLiked((current) => ({ ...current, [message.id]: !current[message.id] }));
|
|
2409
|
+
setDisliked((current) => ({ ...current, [message.id]: false }));
|
|
2410
|
+
},
|
|
2411
|
+
onRetry: () => controller.regenerate(message)
|
|
2412
|
+
},
|
|
2413
|
+
message
|
|
2414
|
+
}
|
|
2415
|
+
) : null;
|
|
2416
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2417
|
+
AgentChatLayout,
|
|
2418
|
+
{
|
|
2419
|
+
...layoutProps,
|
|
2420
|
+
activeThreadId: controller.activeThreadId,
|
|
2421
|
+
agent,
|
|
2422
|
+
allowAttachments: layoutProps.allowAttachments ?? capabilities.includes("attachments.write"),
|
|
2423
|
+
connectionState,
|
|
2424
|
+
errorMessage: error instanceof Error ? error.message : null,
|
|
2425
|
+
isLoadingOlder: controller.isLoadingOlder,
|
|
2426
|
+
isLoadingThreads: controller.isLoadingThreads,
|
|
2427
|
+
isRunning: controller.runState === "streaming" || controller.isSubmitting,
|
|
2428
|
+
messages: controller.messages,
|
|
2429
|
+
hasMoreThreads: controller.hasMoreThreads,
|
|
2430
|
+
modelId: selectedModelId,
|
|
2431
|
+
models: modelOverrides ?? bootstrapModels,
|
|
2432
|
+
onDeleteThread: deleteThread,
|
|
2433
|
+
onHitlAction,
|
|
2434
|
+
onLoadOlder: controller.hasOlderTurns ? controller.loadOlder : void 0,
|
|
2435
|
+
onLoadMoreThreads: controller.hasMoreThreads ? controller.loadMoreThreads : void 0,
|
|
2436
|
+
onModelChange: changeModel,
|
|
2437
|
+
onNewThread: controller.startNewThread,
|
|
2438
|
+
onOpenThread: controller.openThread,
|
|
2439
|
+
onRetryConnection: loadBootstrap,
|
|
2440
|
+
onStop: () => void controller.cancelRun(),
|
|
2441
|
+
onSubmit: submit,
|
|
2442
|
+
slots: { ...slots, messageActions: slots?.messageActions || defaultMessageActions },
|
|
2443
|
+
state,
|
|
2444
|
+
streamingMessageId: controller.streamingMessageId,
|
|
2445
|
+
threads: controller.threads.map((thread) => ({ id: thread.id, title: thread.title, status: thread.last_run_status || thread.status, updatedAt: thread.updated_at }))
|
|
2446
|
+
}
|
|
2447
|
+
);
|
|
2448
|
+
}
|
|
2449
|
+
function AgentChatApp({
|
|
2450
|
+
client,
|
|
2451
|
+
createAbortController,
|
|
2452
|
+
createId: createId2,
|
|
2453
|
+
storage,
|
|
2454
|
+
...props
|
|
2455
|
+
}) {
|
|
2456
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2457
|
+
import_react2.Agents24Provider,
|
|
2458
|
+
{
|
|
2459
|
+
client,
|
|
2460
|
+
createAbortController: createAbortController || (() => new AbortController()),
|
|
2461
|
+
createId: createId2,
|
|
2462
|
+
storage,
|
|
2463
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BoundAgentChatApp, { client, ...props })
|
|
2464
|
+
}
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
584
2467
|
// Annotate the CommonJS export names for ESM import in node:
|
|
585
2468
|
0 && (module.exports = {
|
|
2469
|
+
AgentChatApp,
|
|
2470
|
+
AgentChatLayout,
|
|
586
2471
|
AgentChatShell,
|
|
2472
|
+
AgentChatStatePanel,
|
|
587
2473
|
buildAgentChatOutlineItems,
|
|
588
2474
|
getAgentChatShellScrollState
|
|
589
2475
|
});
|