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