@agents24/chat-react 0.1.8 → 0.1.10

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/dist/ui/index.js CHANGED
@@ -5,15 +5,260 @@ import {
5
5
  cn
6
6
  } from "../chunk-UU7OXHL3.js";
7
7
 
8
- // src/ui/adapters.ts
8
+ // src/ui/agent-chat-composer.tsx
9
9
  import * as React from "react";
10
+ import { ArrowUp, Paperclip, Plus, Square, X } from "lucide-react";
11
+ import { jsx, jsxs } from "react/jsx-runtime";
12
+ var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
13
+ function createComposerFile(file) {
14
+ return {
15
+ id: createId(),
16
+ type: "file",
17
+ url: URL.createObjectURL(file),
18
+ filename: file.name || "attachment",
19
+ mediaType: file.type || "application/octet-stream"
20
+ };
21
+ }
22
+ function revokeComposerFiles(files) {
23
+ files.forEach((file) => {
24
+ try {
25
+ URL.revokeObjectURL(file.url);
26
+ } catch {
27
+ }
28
+ });
29
+ }
30
+ function formatFileCount(files) {
31
+ if (files.length === 1) return files[0].filename;
32
+ return `${files.length} files`;
33
+ }
34
+ 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";
35
+ 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-all 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";
36
+ function AgentChatComposer({
37
+ accept,
38
+ allowAttachments = true,
39
+ attachmentLayout = "flow",
40
+ className,
41
+ disabled = false,
42
+ inputToolbarContent,
43
+ isRunning = false,
44
+ onAttachmentCountChange,
45
+ onStop,
46
+ onSubmit,
47
+ placeholder = "Send follow-up",
48
+ textareaRef
49
+ }) {
50
+ const fileInputRef = React.useRef(null);
51
+ const filesRef = React.useRef([]);
52
+ const localTextareaRef = React.useRef(null);
53
+ const [text, setText] = React.useState("");
54
+ const [files, setFiles] = React.useState([]);
55
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
56
+ const isExpanded = text.includes("\n") || text.length > 62;
57
+ const setTextareaRef = React.useCallback(
58
+ (node) => {
59
+ localTextareaRef.current = node;
60
+ if (typeof textareaRef === "function") {
61
+ textareaRef(node);
62
+ } else if (textareaRef) {
63
+ textareaRef.current = node;
64
+ }
65
+ },
66
+ [textareaRef]
67
+ );
68
+ React.useEffect(() => {
69
+ filesRef.current = files;
70
+ onAttachmentCountChange?.(files.length);
71
+ }, [files, onAttachmentCountChange]);
72
+ React.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
73
+ React.useLayoutEffect(() => {
74
+ const textarea = localTextareaRef.current;
75
+ if (!textarea) return;
76
+ if (isExpanded) {
77
+ textarea.style.height = "auto";
78
+ const scrollHeight = textarea.scrollHeight;
79
+ textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
80
+ textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
81
+ return;
82
+ }
83
+ textarea.style.height = "";
84
+ textarea.style.overflowY = "hidden";
85
+ }, [isExpanded, text]);
86
+ const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
87
+ const handleFilesChange = (event) => {
88
+ const selected = Array.from(event.target.files || []).map(createComposerFile);
89
+ if (selected.length > 0) {
90
+ setFiles((current) => [...current, ...selected]);
91
+ }
92
+ event.currentTarget.value = "";
93
+ };
94
+ const removeFile = (fileId) => {
95
+ setFiles((current) => {
96
+ const target = current.find((file) => file.id === fileId);
97
+ if (target) revokeComposerFiles([target]);
98
+ return current.filter((file) => file.id !== fileId);
99
+ });
100
+ };
101
+ const submit = async () => {
102
+ if (!canSubmit) return;
103
+ const submittedText = text.trim();
104
+ const submittedFiles = files;
105
+ setIsSubmitting(true);
106
+ try {
107
+ await onSubmit({ text: submittedText, files: submittedFiles });
108
+ setText("");
109
+ setFiles([]);
110
+ revokeComposerFiles(submittedFiles);
111
+ } finally {
112
+ setIsSubmitting(false);
113
+ }
114
+ };
115
+ const handleKeyDown = (event) => {
116
+ if (event.key === "Enter" && !event.shiftKey) {
117
+ event.preventDefault();
118
+ void submit();
119
+ }
120
+ };
121
+ return /* @__PURE__ */ jsxs("div", { className: cn("relative mx-auto w-full max-w-3xl overflow-visible", className), children: [
122
+ files.length > 0 ? /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ "data-testid": "agent-chat-composer-attachments",
126
+ className: cn(
127
+ "flex min-w-0 gap-2 py-1",
128
+ 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"
129
+ ),
130
+ children: files.map((file) => /* @__PURE__ */ jsxs(
131
+ "div",
132
+ {
133
+ className: "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",
134
+ children: [
135
+ /* @__PURE__ */ jsx("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ jsx(Paperclip, { className: "size-3" }) }),
136
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
137
+ /* @__PURE__ */ jsx(
138
+ "button",
139
+ {
140
+ "aria-label": `Remove ${file.filename}`,
141
+ className: "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",
142
+ onClick: () => removeFile(file.id),
143
+ type: "button",
144
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
145
+ }
146
+ )
147
+ ]
148
+ },
149
+ file.id
150
+ ))
151
+ }
152
+ ) : null,
153
+ /* @__PURE__ */ jsx(
154
+ "div",
155
+ {
156
+ className: cn(
157
+ "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",
158
+ isExpanded ? "rounded-[18px] p-2 pb-1.5" : "rounded-full px-2 py-[3px]"
159
+ ),
160
+ children: /* @__PURE__ */ jsxs(
161
+ "div",
162
+ {
163
+ className: "w-full",
164
+ style: {
165
+ alignItems: "center",
166
+ display: "grid",
167
+ gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
168
+ gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
169
+ gap: isExpanded ? "3px 10px" : "8px"
170
+ },
171
+ children: [
172
+ /* @__PURE__ */ jsxs("div", { style: { gridArea: "plus" }, className: "flex shrink-0 items-center justify-center", children: [
173
+ /* @__PURE__ */ jsx(
174
+ "input",
175
+ {
176
+ ref: fileInputRef,
177
+ accept,
178
+ "aria-label": "Attach files",
179
+ className: "hidden",
180
+ multiple: true,
181
+ onChange: handleFilesChange,
182
+ type: "file"
183
+ }
184
+ ),
185
+ /* @__PURE__ */ jsx(
186
+ "button",
187
+ {
188
+ "aria-label": "Attach files",
189
+ className: pillButtonClass,
190
+ disabled: disabled || isSubmitting || isRunning || !allowAttachments,
191
+ onClick: () => fileInputRef.current?.click(),
192
+ title: "Attach files",
193
+ type: "button",
194
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
195
+ }
196
+ )
197
+ ] }),
198
+ /* @__PURE__ */ jsx("div", { style: { gridArea: "textarea" }, className: "min-w-0 w-full", children: /* @__PURE__ */ jsx(
199
+ "textarea",
200
+ {
201
+ ref: setTextareaRef,
202
+ "aria-label": "Message",
203
+ className: cn(
204
+ "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",
205
+ isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
206
+ ),
207
+ disabled: disabled || isSubmitting || isRunning,
208
+ onChange: (event) => setText(event.target.value),
209
+ onKeyDown: handleKeyDown,
210
+ placeholder,
211
+ rows: isExpanded ? 2 : 1,
212
+ value: text
213
+ }
214
+ ) }),
215
+ isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "toolbar" }, className: "flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
216
+ !isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
217
+ !isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
218
+ /* @__PURE__ */ jsx("div", { style: { gridArea: "submit" }, className: "flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ jsx(
219
+ "button",
220
+ {
221
+ "aria-label": "Stop generating",
222
+ className: submitButtonClass,
223
+ disabled: disabled || !onStop,
224
+ onClick: onStop,
225
+ title: "Stop",
226
+ type: "button",
227
+ children: /* @__PURE__ */ jsx(Square, { className: "size-3.5 fill-current" })
228
+ }
229
+ ) : /* @__PURE__ */ jsx(
230
+ "button",
231
+ {
232
+ "aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
233
+ className: cn(submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
234
+ disabled: !canSubmit,
235
+ onClick: () => void submit(),
236
+ title: "Send",
237
+ type: "button",
238
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
239
+ }
240
+ ) })
241
+ ]
242
+ }
243
+ )
244
+ }
245
+ ),
246
+ !isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx("div", { className: "mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
247
+ ] });
248
+ }
249
+
250
+ // src/ui/agent-chat-actions.tsx
251
+ import { Check, Copy, RefreshCcw, ThumbsDown, ThumbsUp } from "lucide-react";
252
+
253
+ // src/ui/adapters.ts
254
+ import * as React2 from "react";
10
255
 
11
256
  // src/ui/attachment.tsx
12
257
  import { cva } from "class-variance-authority";
13
258
  import { Slot } from "@radix-ui/react-slot";
14
- import { jsx } from "react/jsx-runtime";
259
+ import { jsx as jsx2 } from "react/jsx-runtime";
15
260
  var attachmentVariants = cva(
16
- "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:ring-1 focus-within:ring-ring/50 has-[>a,>button]:hover:bg-muted/50 data-[state=error]:border-destructive/30 data-[state=idle]:border-dashed",
261
+ "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",
17
262
  {
18
263
  variants: {
19
264
  size: {
@@ -35,7 +280,7 @@ function Attachment({
35
280
  orientation = "horizontal",
36
281
  ...props
37
282
  }) {
38
- return /* @__PURE__ */ jsx(
283
+ return /* @__PURE__ */ jsx2(
39
284
  "div",
40
285
  {
41
286
  "data-slot": "attachment",
@@ -66,7 +311,7 @@ function AttachmentMedia({
66
311
  variant = "icon",
67
312
  ...props
68
313
  }) {
69
- return /* @__PURE__ */ jsx(
314
+ return /* @__PURE__ */ jsx2(
70
315
  "div",
71
316
  {
72
317
  "data-slot": "attachment-media",
@@ -80,7 +325,7 @@ function AttachmentContent({
80
325
  className,
81
326
  ...props
82
327
  }) {
83
- return /* @__PURE__ */ jsx(
328
+ return /* @__PURE__ */ jsx2(
84
329
  "div",
85
330
  {
86
331
  "data-slot": "attachment-content",
@@ -96,7 +341,7 @@ function AttachmentTitle({
96
341
  className,
97
342
  ...props
98
343
  }) {
99
- return /* @__PURE__ */ jsx(
344
+ return /* @__PURE__ */ jsx2(
100
345
  "span",
101
346
  {
102
347
  "data-slot": "attachment-title",
@@ -112,7 +357,7 @@ function AttachmentDescription({
112
357
  className,
113
358
  ...props
114
359
  }) {
115
- return /* @__PURE__ */ jsx(
360
+ return /* @__PURE__ */ jsx2(
116
361
  "span",
117
362
  {
118
363
  "data-slot": "attachment-description",
@@ -129,7 +374,7 @@ function AttachmentActions({
129
374
  className,
130
375
  ...props
131
376
  }) {
132
- return /* @__PURE__ */ jsx(
377
+ return /* @__PURE__ */ jsx2(
133
378
  "div",
134
379
  {
135
380
  "data-slot": "attachment-actions",
@@ -146,13 +391,13 @@ function AttachmentAction({
146
391
  type = "button",
147
392
  ...props
148
393
  }) {
149
- return /* @__PURE__ */ jsx(
394
+ return /* @__PURE__ */ jsx2(
150
395
  "button",
151
396
  {
152
397
  "data-slot": "attachment-action",
153
398
  type,
154
399
  className: cn(
155
- "inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
400
+ "inline-flex size-6 shrink-0 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",
156
401
  className
157
402
  ),
158
403
  ...props
@@ -166,7 +411,7 @@ function AttachmentTrigger({
166
411
  ...props
167
412
  }) {
168
413
  const Comp = asChild ? Slot : "button";
169
- return /* @__PURE__ */ jsx(
414
+ return /* @__PURE__ */ jsx2(
170
415
  Comp,
171
416
  {
172
417
  "data-slot": "attachment-trigger",
@@ -177,7 +422,7 @@ function AttachmentTrigger({
177
422
  );
178
423
  }
179
424
  function AttachmentGroup({ className, ...props }) {
180
- return /* @__PURE__ */ jsx(
425
+ return /* @__PURE__ */ jsx2(
181
426
  "div",
182
427
  {
183
428
  "data-slot": "attachment-group",
@@ -190,111 +435,6 @@ function AttachmentGroup({ className, ...props }) {
190
435
  );
191
436
  }
192
437
 
193
- // src/ui/bubble.tsx
194
- import { cva as cva2 } from "class-variance-authority";
195
- import { Slot as Slot2 } from "@radix-ui/react-slot";
196
- import { jsx as jsx2 } from "react/jsx-runtime";
197
- function BubbleGroup({ className, ...props }) {
198
- return /* @__PURE__ */ jsx2(
199
- "div",
200
- {
201
- "data-slot": "bubble-group",
202
- className: cn("flex min-w-0 flex-col gap-2", className),
203
- ...props
204
- }
205
- );
206
- }
207
- var bubbleVariants = cva2(
208
- "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
209
- {
210
- variants: {
211
- variant: {
212
- default: "*:data-[slot=bubble-content]:bg-primary *:data-[slot=bubble-content]:text-primary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-primary/80",
213
- secondary: "*:data-[slot=bubble-content]:bg-secondary *:data-[slot=bubble-content]:text-secondary-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--secondary),var(--foreground)_5%)]",
214
- muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
215
- tinted: "*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.93_calc(c*0.4)_h)] *:data-[slot=bubble-content]:text-foreground dark:*:data-[slot=bubble-content]:bg-[oklch(from_var(--primary)_0.3_calc(c*0.4)_h)] [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.88_calc(c*0.5)_h)] dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-[oklch(from_var(--primary)_0.35_calc(c*0.5)_h)]",
216
- outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
217
- ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
218
- destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
219
- }
220
- },
221
- defaultVariants: {
222
- variant: "default"
223
- }
224
- }
225
- );
226
- function Bubble({
227
- variant = "default",
228
- align = "start",
229
- className,
230
- ...props
231
- }) {
232
- return /* @__PURE__ */ jsx2(
233
- "div",
234
- {
235
- "data-slot": "bubble",
236
- "data-variant": variant,
237
- "data-align": align,
238
- className: cn(bubbleVariants({ variant }), className),
239
- ...props
240
- }
241
- );
242
- }
243
- function BubbleContent({
244
- asChild = false,
245
- className,
246
- ...props
247
- }) {
248
- const Comp = asChild ? Slot2 : "div";
249
- return /* @__PURE__ */ jsx2(
250
- Comp,
251
- {
252
- "data-slot": "bubble-content",
253
- className: cn(
254
- "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-ring [button,a]:focus-visible:ring-3 [button,a]:focus-visible:ring-ring/50",
255
- className
256
- ),
257
- ...props
258
- }
259
- );
260
- }
261
- var bubbleReactionsVariants = cva2(
262
- "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
263
- {
264
- variants: {
265
- side: {
266
- top: "top-0 -translate-y-3/4",
267
- bottom: "bottom-0 translate-y-3/4"
268
- },
269
- align: {
270
- start: "left-3",
271
- end: "right-3"
272
- }
273
- },
274
- defaultVariants: {
275
- side: "bottom",
276
- align: "end"
277
- }
278
- }
279
- );
280
- function BubbleReactions({
281
- side = "bottom",
282
- align = "end",
283
- className,
284
- ...props
285
- }) {
286
- return /* @__PURE__ */ jsx2(
287
- "div",
288
- {
289
- "data-slot": "bubble-reactions",
290
- "data-align": align,
291
- "data-side": side,
292
- className: cn(bubbleReactionsVariants({ side, align }), className),
293
- ...props
294
- }
295
- );
296
- }
297
-
298
438
  // src/ui/message.tsx
299
439
  import { jsx as jsx3 } from "react/jsx-runtime";
300
440
  function MessageGroup({ className, ...props }) {
@@ -387,25 +527,25 @@ function ChatMessage(input) {
387
527
  const messageProps = Object.assign({}, props);
388
528
  messageProps.align = resolvedAlign;
389
529
  messageProps.className = cn(
390
- "group flex w-full flex-col gap-2 overflow-visible",
391
- isUser ? cn("is-user max-w-[80%] justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant max-w-full",
530
+ "group flex w-full max-w-[80%] flex-col gap-2",
531
+ isUser ? cn("is-user justify-end", isRtl ? "mr-auto" : "ml-auto") : "is-assistant",
392
532
  className
393
533
  );
394
- return React.createElement(Message, messageProps);
534
+ return React2.createElement(Message, messageProps);
395
535
  }
396
536
  function ChatMessageContent({
397
537
  className,
398
538
  ...props
399
539
  }) {
400
- return React.createElement(BubbleContent, {
540
+ return React2.createElement("div", {
401
541
  className: cn(
402
- "flex w-full max-w-full min-w-0 flex-col gap-2 text-sm",
403
- "group-[.is-user]:w-fit group-[.is-user]:overflow-visible 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",
404
- "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:overflow-hidden group-[.is-assistant]:text-foreground",
405
- props.dir === "rtl" ? "group-[.is-assistant]:pr-0" : "group-[.is-assistant]:pl-0",
542
+ "is-user:dark flex w-fit max-w-full min-w-0 flex-col gap-2 overflow-hidden text-sm",
543
+ "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",
544
+ "group-[.is-assistant]:w-full group-[.is-assistant]:max-w-full group-[.is-assistant]:text-foreground",
406
545
  props.dir === "rtl" ? "group-[.is-user]:mr-auto" : "group-[.is-user]:ml-auto",
407
546
  className
408
547
  ),
548
+ "data-slot": "message-content",
409
549
  ...props
410
550
  });
411
551
  }
@@ -413,7 +553,7 @@ function ChatMessageActions({
413
553
  className,
414
554
  ...props
415
555
  }) {
416
- return React.createElement("div", {
556
+ return React2.createElement("div", {
417
557
  className: cn("flex items-center gap-1", className),
418
558
  "data-slot": "message-actions",
419
559
  ...props
@@ -428,12 +568,12 @@ function ChatMessageAction({
428
568
  ...props
429
569
  }) {
430
570
  const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
431
- return React.createElement(
571
+ return React2.createElement(
432
572
  "button",
433
573
  {
434
574
  "aria-label": label || tooltipLabel,
435
575
  className: cn(
436
- "inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
576
+ "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",
437
577
  className
438
578
  ),
439
579
  title: tooltipLabel,
@@ -441,7 +581,7 @@ function ChatMessageAction({
441
581
  ...props
442
582
  },
443
583
  children,
444
- React.createElement(
584
+ React2.createElement(
445
585
  "span",
446
586
  { className: "sr-only" },
447
587
  label || tooltipLabel
@@ -457,32 +597,32 @@ function ChatAttachment({
457
597
  const mediaType = data.mediaType || data.contentType || String(data.type || "application/octet-stream");
458
598
  const filename = data.filename || data.name || "Attachment";
459
599
  const isImage = mediaType.startsWith("image/") && Boolean(data.url);
460
- return React.createElement(
600
+ return React2.createElement(
461
601
  Attachment,
462
602
  {
463
603
  orientation: orientation || (isImage ? "vertical" : "horizontal"),
464
604
  ...props
465
605
  },
466
- React.createElement(
606
+ React2.createElement(
467
607
  AttachmentMedia,
468
608
  { variant: isImage ? "image" : "icon" },
469
- isImage ? React.createElement("img", {
609
+ isImage ? React2.createElement("img", {
470
610
  alt: filename,
471
611
  className: "size-full object-cover",
472
612
  src: data.url
473
- }) : React.createElement(
613
+ }) : React2.createElement(
474
614
  "span",
475
615
  { "aria-hidden": "true", className: "text-sm" },
476
616
  filename.slice(0, 1).toUpperCase()
477
617
  )
478
618
  ),
479
- React.createElement(
619
+ React2.createElement(
480
620
  AttachmentContent,
481
621
  null,
482
- React.createElement(AttachmentTitle, null, filename),
483
- React.createElement(AttachmentDescription, null, mediaType)
622
+ React2.createElement(AttachmentTitle, null, filename),
623
+ React2.createElement(AttachmentDescription, null, mediaType)
484
624
  ),
485
- onRemove ? React.createElement(
625
+ onRemove ? React2.createElement(
486
626
  "button",
487
627
  {
488
628
  "aria-label": "Remove attachment",
@@ -493,7 +633,7 @@ function ChatAttachment({
493
633
  },
494
634
  type: "button"
495
635
  },
496
- React.createElement("span", { "aria-hidden": "true" }, "x")
636
+ React2.createElement("span", { "aria-hidden": "true" }, "x")
497
637
  ) : null
498
638
  );
499
639
  }
@@ -503,7 +643,7 @@ function ChatAttachments({
503
643
  ...props
504
644
  }) {
505
645
  if (!children) return null;
506
- return React.createElement(
646
+ return React2.createElement(
507
647
  AttachmentGroup,
508
648
  {
509
649
  className: cn(
@@ -517,71 +657,124 @@ function ChatAttachments({
517
657
  );
518
658
  }
519
659
 
520
- // src/ui/marker.tsx
521
- import { cva as cva3 } from "class-variance-authority";
522
- import { Slot as Slot3 } from "@radix-ui/react-slot";
523
- import { jsx as jsx4 } from "react/jsx-runtime";
524
- var markerVariants = cva3(
525
- "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
526
- {
527
- variants: {
528
- variant: {
529
- default: "",
530
- separator: "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
531
- border: "border-b border-border pb-2"
660
+ // src/ui/agent-chat-actions.tsx
661
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
662
+ function AgentChatDefaultActions({
663
+ handlers,
664
+ message
665
+ }) {
666
+ if (!handlers || message.role !== "assistant" || message.isFinal === false) return null;
667
+ return /* @__PURE__ */ jsxs2(ChatMessageActions, { children: [
668
+ handlers.onRetry ? /* @__PURE__ */ jsx4(
669
+ ChatMessageAction,
670
+ {
671
+ label: "Retry",
672
+ onClick: () => void handlers.onRetry?.(message),
673
+ tooltip: "Regenerate response",
674
+ children: /* @__PURE__ */ jsx4(RefreshCcw, { className: "size-4" })
532
675
  }
533
- }
534
- }
535
- );
536
- function Marker({
676
+ ) : null,
677
+ handlers.onLike ? /* @__PURE__ */ jsx4(
678
+ ChatMessageAction,
679
+ {
680
+ label: "Like",
681
+ onClick: () => void handlers.onLike?.(message),
682
+ tooltip: "Like this response",
683
+ children: /* @__PURE__ */ jsx4(ThumbsUp, { className: "size-4", fill: handlers.liked ? "currentColor" : "none" })
684
+ }
685
+ ) : null,
686
+ handlers.onDislike ? /* @__PURE__ */ jsx4(
687
+ ChatMessageAction,
688
+ {
689
+ label: "Dislike",
690
+ onClick: () => void handlers.onDislike?.(message),
691
+ tooltip: "Dislike this response",
692
+ children: /* @__PURE__ */ jsx4(ThumbsDown, { className: "size-4", fill: handlers.disliked ? "currentColor" : "none" })
693
+ }
694
+ ) : null,
695
+ handlers.onCopy ? /* @__PURE__ */ jsx4(
696
+ ChatMessageAction,
697
+ {
698
+ label: "Copy",
699
+ onClick: () => handlers.onCopy?.(message.content || "", message.id),
700
+ tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
701
+ children: handlers.copied ? /* @__PURE__ */ jsx4(Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ jsx4(Copy, { className: "size-4" })
702
+ }
703
+ ) : null
704
+ ] });
705
+ }
706
+
707
+ // src/ui/agent-chat-message.tsx
708
+ import * as React5 from "react";
709
+ import { ChevronDown as ChevronDown2, ChevronUp } from "lucide-react";
710
+
711
+ // src/ui/agent-response-timeline.tsx
712
+ import * as React4 from "react";
713
+ import { ChevronDown, ChevronRight } from "lucide-react";
714
+
715
+ // src/ui/mcp-connect-required-card.tsx
716
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
717
+ var STATUS_LABELS = {
718
+ pending: "Connect",
719
+ connecting: "Connecting",
720
+ connected: "Connected",
721
+ resuming: "Resuming",
722
+ error: "Try again"
723
+ };
724
+ function McpConnectRequiredCard({
725
+ serverName,
726
+ providerKey,
727
+ message,
728
+ status = "pending",
729
+ disabled = false,
730
+ loading = false,
731
+ error,
732
+ onConnect,
537
733
  className,
538
- variant = "default",
539
- asChild = false,
540
734
  ...props
541
735
  }) {
542
- const Comp = asChild ? Slot3 : "div";
543
- return /* @__PURE__ */ jsx4(
544
- Comp,
545
- {
546
- "data-slot": "marker",
547
- "data-variant": variant,
548
- className: cn(markerVariants({ variant, className })),
549
- ...props
550
- }
551
- );
552
- }
553
- function MarkerIcon({ className, ...props }) {
554
- return /* @__PURE__ */ jsx4(
555
- "span",
556
- {
557
- "data-slot": "marker-icon",
558
- "aria-hidden": "true",
559
- className: cn(
560
- "size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
561
- className
562
- ),
563
- ...props
564
- }
565
- );
566
- }
567
- function MarkerContent({ className, ...props }) {
568
- return /* @__PURE__ */ jsx4(
569
- "span",
736
+ const label = String(serverName || providerKey || "MCP server").trim();
737
+ const isBusy = loading || status === "connecting" || status === "resuming";
738
+ const isConnected = status === "connected";
739
+ const buttonDisabled = disabled || isBusy || isConnected || !onConnect;
740
+ return /* @__PURE__ */ jsx5(
741
+ "div",
570
742
  {
571
- "data-slot": "marker-content",
743
+ "data-agents24-mcp-connect-card": true,
744
+ "data-status": status,
572
745
  className: cn(
573
- "min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
746
+ "w-full rounded-lg border border-border/50 bg-muted/25 p-3 text-sm text-foreground",
574
747
  className
575
748
  ),
576
- ...props
749
+ ...props,
750
+ children: /* @__PURE__ */ jsxs3("div", { className: "flex min-w-0 items-start justify-between gap-3", children: [
751
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0", children: [
752
+ /* @__PURE__ */ jsx5("div", { className: "truncate text-sm font-medium", children: label }),
753
+ /* @__PURE__ */ jsx5("div", { className: "mt-1 text-sm text-muted-foreground", children: message || "Connect your account to continue." }),
754
+ status === "error" && error ? /* @__PURE__ */ jsx5("div", { className: "mt-2 text-sm text-destructive", children: error }) : null
755
+ ] }),
756
+ /* @__PURE__ */ jsx5(
757
+ "button",
758
+ {
759
+ type: "button",
760
+ className: cn(
761
+ "shrink-0 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-60",
762
+ status === "pending" || status === "error" ? "border-neutral-900 bg-neutral-900 text-white hover:bg-neutral-800" : "border-border bg-background text-foreground hover:bg-muted"
763
+ ),
764
+ disabled: buttonDisabled,
765
+ onClick: onConnect,
766
+ children: STATUS_LABELS[status]
767
+ }
768
+ )
769
+ ] })
577
770
  }
578
771
  );
579
772
  }
580
773
 
581
774
  // src/ui/message-response.tsx
582
- import * as React2 from "react";
775
+ import * as React3 from "react";
583
776
  import { defaultRehypePlugins, Streamdown } from "streamdown";
584
- import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
777
+ import { Fragment, jsx as jsx6 } from "react/jsx-runtime";
585
778
  var BLOCK_MARKDOWN_TAGS = /* @__PURE__ */ new Set([
586
779
  "blockquote",
587
780
  "div",
@@ -601,7 +794,7 @@ function isMultilineCodeNode(node) {
601
794
  return node?.tagName === "code" && node.position?.start?.line !== void 0 && node.position?.end?.line !== void 0 && node.position.start.line !== node.position.end.line;
602
795
  }
603
796
  function hasBlockMarkdownChild(child) {
604
- if (!React2.isValidElement(child)) {
797
+ if (!React3.isValidElement(child)) {
605
798
  return false;
606
799
  }
607
800
  if (typeof child.type === "string" && BLOCK_MARKDOWN_TAGS.has(child.type)) {
@@ -611,7 +804,7 @@ function hasBlockMarkdownChild(child) {
611
804
  if (BLOCK_MARKDOWN_TAGS.has(props.node?.tagName || "") || isMultilineCodeNode(props.node)) {
612
805
  return true;
613
806
  }
614
- return React2.Children.toArray(props.children).some(hasBlockMarkdownChild);
807
+ return React3.Children.toArray(props.children).some(hasBlockMarkdownChild);
615
808
  }
616
809
  function MessageMarkdownParagraph({
617
810
  children,
@@ -619,17 +812,17 @@ function MessageMarkdownParagraph({
619
812
  }) {
620
813
  const domProps = { ...props };
621
814
  delete domProps.node;
622
- const childArray = React2.Children.toArray(children).filter(
815
+ const childArray = React3.Children.toArray(children).filter(
623
816
  (child) => child !== null && child !== ""
624
817
  );
625
- const isImageOnly = childArray.length === 1 && React2.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
818
+ const isImageOnly = childArray.length === 1 && React3.isValidElement(childArray[0]) && childArray[0].props.node?.tagName === "img";
626
819
  if (isImageOnly) {
627
- return /* @__PURE__ */ jsx5(Fragment, { children });
820
+ return /* @__PURE__ */ jsx6(Fragment, { children });
628
821
  }
629
822
  if (childArray.some(hasBlockMarkdownChild)) {
630
- return /* @__PURE__ */ jsx5("div", { ...domProps, children });
823
+ return /* @__PURE__ */ jsx6("div", { ...domProps, children });
631
824
  }
632
- return /* @__PURE__ */ jsx5("p", { ...domProps, children });
825
+ return /* @__PURE__ */ jsx6("p", { ...domProps, children });
633
826
  }
634
827
  var MESSAGE_RESPONSE_COMPONENTS = {
635
828
  p: MessageMarkdownParagraph
@@ -653,7 +846,7 @@ var MESSAGE_RESPONSE_REHYPE_PLUGINS = [
653
846
  ]
654
847
  ] : []
655
848
  ];
656
- var MessageResponse = React2.memo(
849
+ var MessageResponse = React3.memo(
657
850
  ({
658
851
  children,
659
852
  className,
@@ -671,11 +864,11 @@ var MessageResponse = React2.memo(
671
864
  maxCatchupChars: 32,
672
865
  text
673
866
  });
674
- const streamdownComponents = React2.useMemo(
867
+ const streamdownComponents = React3.useMemo(
675
868
  () => ({ ...MESSAGE_RESPONSE_COMPONENTS, ...components }),
676
869
  [components]
677
870
  );
678
- return /* @__PURE__ */ jsx5(
871
+ return /* @__PURE__ */ jsx6(
679
872
  Streamdown,
680
873
  {
681
874
  className: cn("size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0", className),
@@ -689,7 +882,542 @@ var MessageResponse = React2.memo(
689
882
  (prevProps, nextProps) => prevProps.children === nextProps.children && prevProps.className === nextProps.className && prevProps.streaming === nextProps.streaming && prevProps.streamingId === nextProps.streamingId && prevProps.components === nextProps.components
690
883
  );
691
884
  MessageResponse.displayName = "MessageResponse";
885
+
886
+ // src/ui/agent-response-timeline.tsx
887
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
888
+ var CONNECT_DECISION = "connect";
889
+ var HITL_DECISIONS = ["approve", "edit", "reject", "respond", "connect"];
890
+ function asRecord(value) {
891
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
892
+ }
893
+ function optionalString(value) {
894
+ return typeof value === "string" && value.trim() ? value.trim() : null;
895
+ }
896
+ function presentationValue(part, key) {
897
+ const presentation = part.presentation || {};
898
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
899
+ return optionalString(presentation[key]) || optionalString(presentation[snakeKey]);
900
+ }
901
+ function groupKey(part) {
902
+ return presentationValue(part, "groupKey");
903
+ }
904
+ function groupLabel(part) {
905
+ return presentationValue(part, "groupLabel") || toolLabel(part);
906
+ }
907
+ function toolLabel(part) {
908
+ return presentationValue(part, "activity") || presentationValue(part, "title") || part.toolName || "Tool activity";
909
+ }
910
+ function toolDetail(part) {
911
+ return presentationValue(part, "detail") || part.errorText || null;
912
+ }
913
+ function isActiveTool(part, activeToolId) {
914
+ return part.id === activeToolId || part.toolCallId === activeToolId || part.state === "input-available" || part.state === "input-streaming";
915
+ }
916
+ function Shimmer({ children, className }) {
917
+ return /* @__PURE__ */ jsx7("span", { className: cn("inline-block animate-pulse", className), children });
918
+ }
919
+ function CompactTask({
920
+ children,
921
+ defaultOpen = false,
922
+ detail,
923
+ error,
924
+ label,
925
+ loading
926
+ }) {
927
+ const [open, setOpen] = React4.useState(defaultOpen);
928
+ const expandable = Boolean(children || detail);
929
+ const content = loading ? /* @__PURE__ */ jsx7(Shimmer, { children: label }) : /* @__PURE__ */ jsx7("span", { children: label });
930
+ if (!expandable) {
931
+ return /* @__PURE__ */ jsx7("div", { className: cn("flex min-w-0 items-center gap-1.5 text-sm", error ? "text-destructive" : "text-muted-foreground"), children: content });
932
+ }
933
+ return /* @__PURE__ */ jsxs4("div", { className: "w-full", children: [
934
+ /* @__PURE__ */ jsxs4(
935
+ "button",
936
+ {
937
+ "aria-expanded": open,
938
+ className: cn(
939
+ "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",
940
+ error ? "text-destructive" : "text-muted-foreground"
941
+ ),
942
+ onClick: () => setOpen((value) => !value),
943
+ type: "button",
944
+ children: [
945
+ content,
946
+ open ? /* @__PURE__ */ jsx7(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx7(ChevronRight, { className: "size-3 opacity-70" })
947
+ ]
948
+ }
949
+ ),
950
+ open ? /* @__PURE__ */ jsxs4("div", { className: "mt-1 pl-4 text-sm text-muted-foreground", children: [
951
+ detail ? /* @__PURE__ */ jsx7("div", { children: detail }) : null,
952
+ children
953
+ ] }) : null
954
+ ] });
955
+ }
956
+ function ToolPartRow({
957
+ activeToolId,
958
+ part
959
+ }) {
960
+ return /* @__PURE__ */ jsx7("div", { "data-agents24-tool-part": part.type, "data-state": part.state, children: /* @__PURE__ */ jsx7(
961
+ CompactTask,
962
+ {
963
+ defaultOpen: part.state === "output-error",
964
+ detail: toolDetail(part),
965
+ error: part.state === "output-error",
966
+ label: toolLabel(part),
967
+ loading: isActiveTool(part, activeToolId)
968
+ }
969
+ ) });
970
+ }
971
+ function ToolGroup({
972
+ activeToolId,
973
+ parts
974
+ }) {
975
+ const loading = parts.some((part) => isActiveTool(part, activeToolId));
976
+ return /* @__PURE__ */ jsx7(CompactTask, { label: groupLabel(parts[0]), loading, children: /* @__PURE__ */ jsx7("div", { className: "space-y-1", children: parts.map((part) => /* @__PURE__ */ jsxs4(
977
+ "div",
978
+ {
979
+ className: cn("text-sm", part.state === "output-error" ? "text-destructive" : "text-muted-foreground"),
980
+ children: [
981
+ isActiveTool(part, activeToolId) ? /* @__PURE__ */ jsx7(Shimmer, { children: toolLabel(part) }) : toolLabel(part),
982
+ toolDetail(part) ? /* @__PURE__ */ jsx7("span", { className: "ml-1 text-xs text-muted-foreground/75", children: toolDetail(part) }) : null
983
+ ]
984
+ },
985
+ part.id
986
+ )) }) });
987
+ }
988
+ function collectHitlDecisions(part) {
989
+ const configs = Array.isArray(part.hitl.review_configs) ? part.hitl.review_configs : [];
990
+ const decisions = configs.flatMap((config) => {
991
+ const record = asRecord(config);
992
+ const allowed = Array.isArray(record?.allowed_decisions) ? record?.allowed_decisions : [];
993
+ return allowed.filter((item) => HITL_DECISIONS.includes(String(item)));
994
+ });
995
+ return Array.from(new Set(decisions));
996
+ }
997
+ function isMcpConnectHitl(part) {
998
+ const kind = `${part.hitlKind || optionalString(part.hitl.kind) || ""}`.toLowerCase();
999
+ const decisions = collectHitlDecisions(part);
1000
+ return decisions.includes(CONNECT_DECISION) || kind.includes("mcp") || kind.includes("connection");
1001
+ }
1002
+ function mcpConnectLabel(part) {
1003
+ const payload = asRecord(part.hitl.client_safe_payload);
1004
+ const origin = asRecord(part.hitl.origin);
1005
+ return optionalString(part.hitl.server_name) || optionalString(part.hitl.provider_name) || optionalString(payload?.server_name) || optionalString(payload?.provider_name) || optionalString(origin?.server_name) || optionalString(origin?.provider_name) || "MCP server";
1006
+ }
1007
+ function normalizedMcpStatus(status) {
1008
+ if (status === "connecting" || status === "connected" || status === "resuming" || status === "error") return status;
1009
+ return "pending";
1010
+ }
1011
+ function HitlPartRow({
1012
+ getHitlActionState,
1013
+ isLoading,
1014
+ onHitlAction,
1015
+ part
1016
+ }) {
1017
+ const state = getHitlActionState?.(part);
1018
+ const message = optionalString(part.hitl.message) || optionalString(part.hitl.text) || "Input is required to continue.";
1019
+ if (isMcpConnectHitl(part)) {
1020
+ return /* @__PURE__ */ jsx7(
1021
+ McpConnectRequiredCard,
1022
+ {
1023
+ error: state?.error,
1024
+ message,
1025
+ onConnect: onHitlAction ? () => onHitlAction(part, CONNECT_DECISION) : void 0,
1026
+ serverName: mcpConnectLabel(part),
1027
+ status: normalizedMcpStatus(state?.status)
1028
+ }
1029
+ );
1030
+ }
1031
+ const decisions = collectHitlDecisions(part);
1032
+ return /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
1033
+ /* @__PURE__ */ jsx7(MessageResponse, { children: message }),
1034
+ state?.error ? /* @__PURE__ */ jsx7("div", { className: "text-sm text-destructive", children: state.error }) : null,
1035
+ /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap items-center gap-2", children: decisions.map((decision) => /* @__PURE__ */ jsx7(
1036
+ "button",
1037
+ {
1038
+ className: cn(
1039
+ "min-w-24 rounded-md border px-3 py-1.5 text-sm font-medium transition-colors disabled:pointer-events-none disabled:opacity-60",
1040
+ decision === "approve" || decision === "connect" ? "border-neutral-900 bg-neutral-900 text-white hover:bg-neutral-800" : "border-border bg-background text-foreground hover:bg-muted"
1041
+ ),
1042
+ disabled: !onHitlAction || isLoading || state?.status === "connecting" || state?.status === "resuming",
1043
+ onClick: () => onHitlAction?.(part, decision),
1044
+ type: "button",
1045
+ children: decision === "connect" ? "Connect" : decision
1046
+ },
1047
+ decision
1048
+ )) })
1049
+ ] });
1050
+ }
1051
+ function DefaultPartRow({
1052
+ activeToolId,
1053
+ getHitlActionState,
1054
+ isLoading,
1055
+ message,
1056
+ onHitlAction,
1057
+ part
1058
+ }) {
1059
+ if (part.kind === "text") return /* @__PURE__ */ jsx7(MessageResponse, { children: part.text });
1060
+ if (part.kind === "tool") return /* @__PURE__ */ jsx7(ToolPartRow, { activeToolId, part });
1061
+ if (part.kind === "reasoning") {
1062
+ const label = part.text || part.label || "Thinking";
1063
+ return part.status === "running" || part.status === "streaming" ? /* @__PURE__ */ jsx7(Shimmer, { className: "text-sm text-muted-foreground", children: label }) : /* @__PURE__ */ jsx7("div", { className: "text-sm text-muted-foreground", children: label });
1064
+ }
1065
+ if (part.kind === "ui-blocks") {
1066
+ if (part.state === "output-error") {
1067
+ return /* @__PURE__ */ jsx7("div", { className: "text-sm text-destructive", children: part.errorText || "Failed to render UI content." });
1068
+ }
1069
+ return /* @__PURE__ */ jsx7(CompactTask, { label: part.state === "input-streaming" ? "Rendering UI block" : "Rendered UI block", loading: part.state === "input-streaming" });
1070
+ }
1071
+ if (part.kind === "hitl") {
1072
+ return /* @__PURE__ */ jsx7(
1073
+ HitlPartRow,
1074
+ {
1075
+ getHitlActionState,
1076
+ isLoading,
1077
+ onHitlAction,
1078
+ part
1079
+ }
1080
+ );
1081
+ }
1082
+ if (part.kind === "error") return /* @__PURE__ */ jsx7("div", { className: "text-sm text-destructive", children: part.errorText });
1083
+ if (part.kind === "data") return /* @__PURE__ */ jsx7("div", { "data-agents24-data-part": part.name, className: "hidden", children: message.id });
1084
+ return null;
1085
+ }
1086
+ function AgentResponseTimeline({
1087
+ getHitlActionState,
1088
+ isLoading = false,
1089
+ message,
1090
+ onHitlAction,
1091
+ renderPart
1092
+ }) {
1093
+ const parts = message.parts || [];
1094
+ const activeToolId = [...parts].reverse().find((part) => part.kind === "tool" && (part.state === "input-available" || part.state === "input-streaming"))?.id;
1095
+ const rendered = [];
1096
+ let index = 0;
1097
+ while (index < parts.length) {
1098
+ const part = parts[index];
1099
+ if (part.kind === "tool" && groupKey(part)) {
1100
+ const key = groupKey(part);
1101
+ const group = [];
1102
+ while (index < parts.length) {
1103
+ const candidate = parts[index];
1104
+ if (candidate.kind !== "tool" || groupKey(candidate) !== key) break;
1105
+ group.push(candidate);
1106
+ index += 1;
1107
+ }
1108
+ rendered.push(group.length === 1 ? /* @__PURE__ */ jsx7(ToolPartRow, { activeToolId, part: group[0] }, group[0].id) : /* @__PURE__ */ jsx7(ToolGroup, { activeToolId, parts: group }, `tool-group-${key}-${group[0].id}`));
1109
+ continue;
1110
+ }
1111
+ rendered.push(
1112
+ /* @__PURE__ */ jsx7(React4.Fragment, { children: renderPart ? renderPart(part, message) : /* @__PURE__ */ jsx7(
1113
+ DefaultPartRow,
1114
+ {
1115
+ activeToolId,
1116
+ getHitlActionState,
1117
+ isLoading,
1118
+ message,
1119
+ onHitlAction,
1120
+ part
1121
+ }
1122
+ ) }, part.id)
1123
+ );
1124
+ index += 1;
1125
+ }
1126
+ if (rendered.length === 0 && message.content) {
1127
+ rendered.push(/* @__PURE__ */ jsx7(MessageResponse, { children: message.content }, `${message.id}-fallback`));
1128
+ }
1129
+ if (rendered.length === 0 && isLoading) {
1130
+ rendered.push(/* @__PURE__ */ jsx7("div", { className: "text-sm text-muted-foreground", children: "Thinking" }, `${message.id}-thinking`));
1131
+ }
1132
+ return /* @__PURE__ */ jsx7("div", { className: "space-y-3", children: rendered });
1133
+ }
1134
+
1135
+ // src/ui/agent-chat-message.tsx
1136
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1137
+ var DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD = 360;
1138
+ var DEFAULT_USER_MESSAGE_COLLAPSED_LINES = 4;
1139
+ function shouldCollapseUserMessage(content, threshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD) {
1140
+ return typeof threshold === "number" && threshold > 0 && content.length > threshold;
1141
+ }
1142
+ function AgentChatUserMessageContent({
1143
+ collapsedLines = DEFAULT_USER_MESSAGE_COLLAPSED_LINES,
1144
+ collapseThreshold = DEFAULT_USER_MESSAGE_COLLAPSE_THRESHOLD,
1145
+ content,
1146
+ showLessLabel = "Show less",
1147
+ showMoreLabel = "Show more"
1148
+ }) {
1149
+ const [isExpanded, setIsExpanded] = React5.useState(false);
1150
+ const contentId = React5.useId();
1151
+ const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
1152
+ const Icon = isExpanded ? ChevronUp : ChevronDown2;
1153
+ const clampedStyle = shouldCollapse && !isExpanded ? {
1154
+ WebkitBoxOrient: "vertical",
1155
+ WebkitLineClamp: Math.max(1, collapsedLines),
1156
+ display: "-webkit-box",
1157
+ overflow: "hidden"
1158
+ } : void 0;
1159
+ return /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 flex-col gap-3", children: [
1160
+ /* @__PURE__ */ jsx8("div", { id: contentId, className: "min-w-0", style: clampedStyle, children: content }),
1161
+ shouldCollapse ? /* @__PURE__ */ jsxs5(
1162
+ "button",
1163
+ {
1164
+ "aria-controls": contentId,
1165
+ "aria-expanded": isExpanded,
1166
+ className: cn(
1167
+ "inline-flex w-fit items-center gap-1 rounded-md text-left text-sm font-medium text-muted-foreground transition-colors",
1168
+ "hover:text-foreground focus-visible:border-neutral-300 focus-visible:outline-none focus-visible:ring-0"
1169
+ ),
1170
+ onClick: () => setIsExpanded((current) => !current),
1171
+ type: "button",
1172
+ children: [
1173
+ isExpanded ? showLessLabel : showMoreLabel,
1174
+ /* @__PURE__ */ jsx8(Icon, { "aria-hidden": "true", className: "size-4", strokeWidth: 2 })
1175
+ ]
1176
+ }
1177
+ ) : null
1178
+ ] });
1179
+ }
1180
+ function AgentChatMessage({
1181
+ actionHandlers,
1182
+ actions,
1183
+ actionsClassName,
1184
+ className,
1185
+ contentClassName,
1186
+ dir,
1187
+ getHitlActionState,
1188
+ message,
1189
+ onHitlAction,
1190
+ renderAssistantContent,
1191
+ renderPart,
1192
+ renderUserContent,
1193
+ showAssistantAttachments = false,
1194
+ streamingMessageId,
1195
+ userMessageCollapsedLines,
1196
+ userMessageContent,
1197
+ userMessageCollapseThreshold,
1198
+ userMessageShowLessLabel,
1199
+ userMessageShowMoreLabel
1200
+ }) {
1201
+ const isAssistant = message.role === "assistant";
1202
+ const isStreaming = isAssistant && message.id === streamingMessageId;
1203
+ const resolvedUserContent = userMessageContent ?? message.content;
1204
+ const shouldRenderContent = isAssistant || Boolean(renderUserContent || resolvedUserContent);
1205
+ const showAttachments = Boolean(message.attachments?.length) && (message.role === "user" || showAssistantAttachments);
1206
+ const renderedActions = actions ?? (actionHandlers ? /* @__PURE__ */ jsx8(AgentChatDefaultActions, { handlers: actionHandlers, message }) : null);
1207
+ return /* @__PURE__ */ jsxs5(ChatMessage, { className, dir, from: message.role, children: [
1208
+ showAttachments ? /* @__PURE__ */ jsx8(ChatAttachments, { className: "mb-2", dir, children: message.attachments?.map((attachment, index) => /* @__PURE__ */ jsx8(
1209
+ ChatAttachment,
1210
+ {
1211
+ data: attachment,
1212
+ dir: "ltr"
1213
+ },
1214
+ String(attachment.url || attachment.filename || index)
1215
+ )) }) : null,
1216
+ shouldRenderContent ? /* @__PURE__ */ jsxs5(
1217
+ ChatMessageContent,
1218
+ {
1219
+ className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
1220
+ dir,
1221
+ children: [
1222
+ isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ jsx8(
1223
+ AgentResponseTimeline,
1224
+ {
1225
+ getHitlActionState,
1226
+ isLoading: isStreaming,
1227
+ message,
1228
+ onHitlAction,
1229
+ renderPart
1230
+ }
1231
+ ) : null,
1232
+ !isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ jsx8(
1233
+ AgentChatUserMessageContent,
1234
+ {
1235
+ collapsedLines: userMessageCollapsedLines,
1236
+ collapseThreshold: userMessageCollapseThreshold,
1237
+ content: resolvedUserContent,
1238
+ showLessLabel: userMessageShowLessLabel,
1239
+ showMoreLabel: userMessageShowMoreLabel
1240
+ }
1241
+ ) : null : null
1242
+ ]
1243
+ }
1244
+ ) : null,
1245
+ renderedActions ? /* @__PURE__ */ jsx8("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
1246
+ ] });
1247
+ }
1248
+
1249
+ // src/ui/bubble.tsx
1250
+ import { cva as cva2 } from "class-variance-authority";
1251
+ import { Slot as Slot2 } from "@radix-ui/react-slot";
1252
+ import { jsx as jsx9 } from "react/jsx-runtime";
1253
+ function BubbleGroup({ className, ...props }) {
1254
+ return /* @__PURE__ */ jsx9(
1255
+ "div",
1256
+ {
1257
+ "data-slot": "bubble-group",
1258
+ className: cn("flex min-w-0 flex-col gap-2", className),
1259
+ ...props
1260
+ }
1261
+ );
1262
+ }
1263
+ var bubbleVariants = cva2(
1264
+ "group/bubble relative flex w-fit max-w-[80%] min-w-0 flex-col gap-1 group-data-[align=end]/message:self-end data-[align=end]:self-end data-[variant=ghost]:max-w-full",
1265
+ {
1266
+ variants: {
1267
+ variant: {
1268
+ default: "*:data-[slot=bubble-content]:bg-neutral-900 *:data-[slot=bubble-content]:text-white [&>[data-slot=bubble-content]:is(button,a):hover]:bg-neutral-800",
1269
+ secondary: "*:data-[slot=bubble-content]:bg-muted *:data-[slot=bubble-content]:text-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted",
1270
+ muted: "*:data-[slot=bubble-content]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:bg-[color-mix(in_oklch,var(--muted),var(--foreground)_5%)]",
1271
+ tinted: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-muted *:data-[slot=bubble-content]:text-foreground [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted",
1272
+ outline: "*:data-[slot=bubble-content]:border-border *:data-[slot=bubble-content]:bg-background [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-input/30",
1273
+ ghost: "border-none *:data-[slot=bubble-content]:rounded-none *:data-[slot=bubble-content]:bg-transparent *:data-[slot=bubble-content]:p-0 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted [&>[data-slot=bubble-content]:is(button,a):hover]:text-foreground dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-muted/50",
1274
+ destructive: "*:data-[slot=bubble-content]:bg-destructive/10 *:data-[slot=bubble-content]:text-destructive dark:*:data-[slot=bubble-content]:bg-destructive/20 [&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/20 dark:[&>[data-slot=bubble-content]:is(button,a):hover]:bg-destructive/30"
1275
+ }
1276
+ },
1277
+ defaultVariants: {
1278
+ variant: "default"
1279
+ }
1280
+ }
1281
+ );
1282
+ function Bubble({
1283
+ variant = "default",
1284
+ align = "start",
1285
+ className,
1286
+ ...props
1287
+ }) {
1288
+ return /* @__PURE__ */ jsx9(
1289
+ "div",
1290
+ {
1291
+ "data-slot": "bubble",
1292
+ "data-variant": variant,
1293
+ "data-align": align,
1294
+ className: cn(bubbleVariants({ variant }), className),
1295
+ ...props
1296
+ }
1297
+ );
1298
+ }
1299
+ function BubbleContent({
1300
+ asChild = false,
1301
+ className,
1302
+ ...props
1303
+ }) {
1304
+ const Comp = asChild ? Slot2 : "div";
1305
+ return /* @__PURE__ */ jsx9(
1306
+ Comp,
1307
+ {
1308
+ "data-slot": "bubble-content",
1309
+ className: cn(
1310
+ "w-fit max-w-full min-w-0 overflow-hidden rounded-xl border border-transparent px-3 py-2 text-sm leading-relaxed wrap-break-word group-data-[align=end]/bubble:self-end [button]:text-left [button,a]:transition-colors [button,a]:outline-none [button,a]:focus-visible:border-neutral-300 [button,a]:focus-visible:ring-0",
1311
+ className
1312
+ ),
1313
+ ...props
1314
+ }
1315
+ );
1316
+ }
1317
+ var bubbleReactionsVariants = cva2(
1318
+ "absolute z-10 flex w-fit shrink-0 items-center justify-center gap-1 rounded-full bg-muted px-1.5 py-0.5 text-sm ring-3 ring-card has-[button]:p-0",
1319
+ {
1320
+ variants: {
1321
+ side: {
1322
+ top: "top-0 -translate-y-3/4",
1323
+ bottom: "bottom-0 translate-y-3/4"
1324
+ },
1325
+ align: {
1326
+ start: "left-3",
1327
+ end: "right-3"
1328
+ }
1329
+ },
1330
+ defaultVariants: {
1331
+ side: "bottom",
1332
+ align: "end"
1333
+ }
1334
+ }
1335
+ );
1336
+ function BubbleReactions({
1337
+ side = "bottom",
1338
+ align = "end",
1339
+ className,
1340
+ ...props
1341
+ }) {
1342
+ return /* @__PURE__ */ jsx9(
1343
+ "div",
1344
+ {
1345
+ "data-slot": "bubble-reactions",
1346
+ "data-align": align,
1347
+ "data-side": side,
1348
+ className: cn(bubbleReactionsVariants({ side, align }), className),
1349
+ ...props
1350
+ }
1351
+ );
1352
+ }
1353
+
1354
+ // src/ui/marker.tsx
1355
+ import { cva as cva3 } from "class-variance-authority";
1356
+ import { Slot as Slot3 } from "@radix-ui/react-slot";
1357
+ import { jsx as jsx10 } from "react/jsx-runtime";
1358
+ var markerVariants = cva3(
1359
+ "group/marker relative flex min-h-4 w-full items-center gap-2 text-left text-sm text-muted-foreground [&_svg:not([class*='size-'])]:size-4 [a]:underline [a]:underline-offset-3 [a]:hover:text-foreground",
1360
+ {
1361
+ variants: {
1362
+ variant: {
1363
+ default: "",
1364
+ separator: "before:mr-1 before:h-px before:min-w-0 before:flex-1 before:bg-border after:ml-1 after:h-px after:min-w-0 after:flex-1 after:bg-border",
1365
+ border: "border-b border-border pb-2"
1366
+ }
1367
+ }
1368
+ }
1369
+ );
1370
+ function Marker({
1371
+ className,
1372
+ variant = "default",
1373
+ asChild = false,
1374
+ ...props
1375
+ }) {
1376
+ const Comp = asChild ? Slot3 : "div";
1377
+ return /* @__PURE__ */ jsx10(
1378
+ Comp,
1379
+ {
1380
+ "data-slot": "marker",
1381
+ "data-variant": variant,
1382
+ className: cn(markerVariants({ variant, className })),
1383
+ ...props
1384
+ }
1385
+ );
1386
+ }
1387
+ function MarkerIcon({ className, ...props }) {
1388
+ return /* @__PURE__ */ jsx10(
1389
+ "span",
1390
+ {
1391
+ "data-slot": "marker-icon",
1392
+ "aria-hidden": "true",
1393
+ className: cn(
1394
+ "size-4 shrink-0 [&_svg:not([class*='size-'])]:size-4",
1395
+ className
1396
+ ),
1397
+ ...props
1398
+ }
1399
+ );
1400
+ }
1401
+ function MarkerContent({ className, ...props }) {
1402
+ return /* @__PURE__ */ jsx10(
1403
+ "span",
1404
+ {
1405
+ "data-slot": "marker-content",
1406
+ className: cn(
1407
+ "min-w-0 wrap-break-word group-data-[variant=separator]/marker:flex-none group-data-[variant=separator]/marker:text-center *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
1408
+ className
1409
+ ),
1410
+ ...props
1411
+ }
1412
+ );
1413
+ }
692
1414
  export {
1415
+ AgentChatComposer,
1416
+ AgentChatDefaultActions,
1417
+ AgentChatMessage,
1418
+ DefaultPartRow as AgentChatPartRow,
1419
+ AgentChatUserMessageContent,
1420
+ AgentResponseTimeline,
693
1421
  Attachment,
694
1422
  AttachmentAction,
695
1423
  AttachmentActions,
@@ -712,6 +1440,7 @@ export {
712
1440
  Marker,
713
1441
  MarkerContent,
714
1442
  MarkerIcon,
1443
+ McpConnectRequiredCard,
715
1444
  ChatMessage as Message,
716
1445
  ChatMessageAction as MessageAction,
717
1446
  ChatMessageActions as MessageActions,
@@ -729,6 +1458,7 @@ export {
729
1458
  attachmentVariants,
730
1459
  bubbleVariants,
731
1460
  cn,
732
- markerVariants
1461
+ markerVariants,
1462
+ shouldCollapseUserMessage
733
1463
  };
734
1464
  //# sourceMappingURL=index.js.map