@agents24/chat-react 0.5.5 → 0.5.7
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/index.cjs +11 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +11 -6
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +2 -1
- package/dist/runtime/index.cjs +17 -11
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +17 -11
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/use-agent-chat-runtime.d.ts +7 -2
- package/dist/scaffold/components/chat/chat-composer.d.ts +2 -1
- package/dist/scaffold/components/chat/chat-message.d.ts +3 -1
- package/dist/scaffold/components/chat/turn-outline.d.ts +1 -1
- package/dist/scaffold/index.cjs +195 -154
- package/dist/scaffold/index.cjs.map +1 -1
- package/dist/scaffold/index.js +215 -168
- package/dist/scaffold/index.js.map +1 -1
- package/dist/styles.css +25 -6
- package/dist/ui/adapters.d.ts +14 -2
- package/dist/ui/agent-chat-composer.d.ts +2 -9
- package/dist/ui/agent-chat-message.d.ts +7 -1
- package/dist/ui/composer-attachments.d.ts +11 -0
- package/dist/ui/index.cjs +781 -389
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +766 -375
- package/dist/ui/index.js.map +1 -1
- package/package.json +4 -3
- package/scaffold/src/components/chat/chat-composer.tsx +43 -19
- package/scaffold/src/components/chat/chat-message.tsx +19 -10
- package/scaffold/src/components/chat/chat-shell.tsx +65 -34
- package/scaffold/src/components/chat/turn-outline.tsx +17 -59
- package/scaffold/src/components/ui/message-scroller.tsx +1 -1
- package/scaffold/src/components/ui/message.tsx +1 -1
- package/scaffold/src/components/ui/sidebar.tsx +1 -1
package/dist/ui/index.js
CHANGED
|
@@ -4,8 +4,21 @@ import {
|
|
|
4
4
|
} from "../chunk-SSUO4EPD.js";
|
|
5
5
|
|
|
6
6
|
// src/ui/agent-chat-composer.tsx
|
|
7
|
+
import * as React2 from "react";
|
|
8
|
+
import { ArrowUp, Plus, Square } from "lucide-react";
|
|
9
|
+
|
|
10
|
+
// src/ui/adapters.ts
|
|
7
11
|
import * as React from "react";
|
|
8
|
-
import {
|
|
12
|
+
import { FileIcon } from "@untitledui/file-icons";
|
|
13
|
+
import {
|
|
14
|
+
ImageOff,
|
|
15
|
+
LoaderCircle,
|
|
16
|
+
X
|
|
17
|
+
} from "lucide-react";
|
|
18
|
+
|
|
19
|
+
// src/ui/attachment.tsx
|
|
20
|
+
import { cva } from "class-variance-authority";
|
|
21
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
9
22
|
|
|
10
23
|
// src/ui/utils.ts
|
|
11
24
|
import { clsx } from "clsx";
|
|
@@ -14,266 +27,8 @@ function cn(...values) {
|
|
|
14
27
|
return twMerge(clsx(values));
|
|
15
28
|
}
|
|
16
29
|
|
|
17
|
-
// src/ui/agent-chat-composer.tsx
|
|
18
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
-
var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
20
|
-
function createComposerFile(file) {
|
|
21
|
-
return {
|
|
22
|
-
id: createId(),
|
|
23
|
-
type: "file",
|
|
24
|
-
url: URL.createObjectURL(file),
|
|
25
|
-
filename: file.name || "attachment",
|
|
26
|
-
mediaType: file.type || "application/octet-stream",
|
|
27
|
-
source: file
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
function revokeComposerFiles(files) {
|
|
31
|
-
files.forEach((file) => {
|
|
32
|
-
try {
|
|
33
|
-
URL.revokeObjectURL(file.url);
|
|
34
|
-
} catch {
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
function formatFileCount(files) {
|
|
39
|
-
if (files.length === 1) return files[0].filename;
|
|
40
|
-
return `${files.length} files`;
|
|
41
|
-
}
|
|
42
|
-
var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
|
|
43
|
-
var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
|
|
44
|
-
function AgentChatComposer({
|
|
45
|
-
accept,
|
|
46
|
-
allowAttachments = true,
|
|
47
|
-
attachmentLayout = "flow",
|
|
48
|
-
className,
|
|
49
|
-
disabled = false,
|
|
50
|
-
forceExpanded = false,
|
|
51
|
-
inputToolbarContent,
|
|
52
|
-
isRunning = false,
|
|
53
|
-
onAttachmentCountChange,
|
|
54
|
-
onStop,
|
|
55
|
-
onSubmit,
|
|
56
|
-
placeholder = "Message the agent",
|
|
57
|
-
textareaRef
|
|
58
|
-
}) {
|
|
59
|
-
const fileInputRef = React.useRef(null);
|
|
60
|
-
const filesRef = React.useRef([]);
|
|
61
|
-
const localTextareaRef = React.useRef(null);
|
|
62
|
-
const [text, setText] = React.useState("");
|
|
63
|
-
const [files, setFiles] = React.useState([]);
|
|
64
|
-
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
65
|
-
const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
|
|
66
|
-
const setTextareaRef = React.useCallback(
|
|
67
|
-
(node) => {
|
|
68
|
-
localTextareaRef.current = node;
|
|
69
|
-
if (typeof textareaRef === "function") {
|
|
70
|
-
textareaRef(node);
|
|
71
|
-
} else if (textareaRef) {
|
|
72
|
-
textareaRef.current = node;
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
[textareaRef]
|
|
76
|
-
);
|
|
77
|
-
React.useEffect(() => {
|
|
78
|
-
filesRef.current = files;
|
|
79
|
-
onAttachmentCountChange?.(files.length);
|
|
80
|
-
}, [files, onAttachmentCountChange]);
|
|
81
|
-
React.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
|
|
82
|
-
React.useLayoutEffect(() => {
|
|
83
|
-
const textarea = localTextareaRef.current;
|
|
84
|
-
if (!textarea) return;
|
|
85
|
-
if (isExpanded) {
|
|
86
|
-
textarea.style.height = "auto";
|
|
87
|
-
const scrollHeight = textarea.scrollHeight;
|
|
88
|
-
textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
|
|
89
|
-
textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
textarea.style.height = "";
|
|
93
|
-
textarea.style.overflowY = "hidden";
|
|
94
|
-
}, [isExpanded, text]);
|
|
95
|
-
const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
|
|
96
|
-
const handleFilesChange = (event) => {
|
|
97
|
-
const selected = Array.from(event.target.files || []).map(createComposerFile);
|
|
98
|
-
if (selected.length > 0) {
|
|
99
|
-
setFiles((current) => [...current, ...selected]);
|
|
100
|
-
}
|
|
101
|
-
event.currentTarget.value = "";
|
|
102
|
-
};
|
|
103
|
-
const removeFile = (fileId) => {
|
|
104
|
-
setFiles((current) => {
|
|
105
|
-
const target = current.find((file) => file.id === fileId);
|
|
106
|
-
if (target) revokeComposerFiles([target]);
|
|
107
|
-
return current.filter((file) => file.id !== fileId);
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
const submit = async () => {
|
|
111
|
-
if (!canSubmit) return;
|
|
112
|
-
const submittedText = text.trim();
|
|
113
|
-
const submittedFiles = files;
|
|
114
|
-
setIsSubmitting(true);
|
|
115
|
-
try {
|
|
116
|
-
await onSubmit({ text: submittedText, files: submittedFiles });
|
|
117
|
-
setText("");
|
|
118
|
-
setFiles([]);
|
|
119
|
-
revokeComposerFiles(submittedFiles);
|
|
120
|
-
} catch {
|
|
121
|
-
} finally {
|
|
122
|
-
setIsSubmitting(false);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
const handleKeyDown = (event) => {
|
|
126
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
127
|
-
event.preventDefault();
|
|
128
|
-
void submit();
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
|
|
132
|
-
files.length > 0 ? /* @__PURE__ */ jsx(
|
|
133
|
-
"div",
|
|
134
|
-
{
|
|
135
|
-
"data-testid": "agent-chat-composer-attachments",
|
|
136
|
-
"data-layout": attachmentLayout,
|
|
137
|
-
className: cn(
|
|
138
|
-
"a24-composer__attachments flex min-w-0 gap-2 py-1",
|
|
139
|
-
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"
|
|
140
|
-
),
|
|
141
|
-
children: files.map((file) => /* @__PURE__ */ jsxs(
|
|
142
|
-
"div",
|
|
143
|
-
{
|
|
144
|
-
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-border bg-card px-1.5 text-sm font-medium text-card-foreground transition-colors hover:bg-accent",
|
|
145
|
-
children: [
|
|
146
|
-
/* @__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" }) }),
|
|
147
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
|
|
148
|
-
/* @__PURE__ */ jsx(
|
|
149
|
-
"button",
|
|
150
|
-
{
|
|
151
|
-
"aria-label": `Remove ${file.filename}`,
|
|
152
|
-
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",
|
|
153
|
-
onClick: () => removeFile(file.id),
|
|
154
|
-
type: "button",
|
|
155
|
-
children: /* @__PURE__ */ jsx(X, { className: "size-3" })
|
|
156
|
-
}
|
|
157
|
-
)
|
|
158
|
-
]
|
|
159
|
-
},
|
|
160
|
-
file.id
|
|
161
|
-
))
|
|
162
|
-
}
|
|
163
|
-
) : null,
|
|
164
|
-
/* @__PURE__ */ jsx(
|
|
165
|
-
"div",
|
|
166
|
-
{
|
|
167
|
-
"data-expanded": isExpanded ? "" : void 0,
|
|
168
|
-
className: cn(
|
|
169
|
-
"a24-composer__surface relative w-full border border-input bg-background shadow-sm",
|
|
170
|
-
isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
|
|
171
|
-
),
|
|
172
|
-
children: /* @__PURE__ */ jsxs(
|
|
173
|
-
"div",
|
|
174
|
-
{
|
|
175
|
-
className: "a24-composer__grid w-full",
|
|
176
|
-
style: {
|
|
177
|
-
alignItems: "center",
|
|
178
|
-
display: "grid",
|
|
179
|
-
gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
|
|
180
|
-
gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
|
|
181
|
-
gap: isExpanded ? "3px 10px" : "8px"
|
|
182
|
-
},
|
|
183
|
-
children: [
|
|
184
|
-
/* @__PURE__ */ jsxs("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
|
|
185
|
-
/* @__PURE__ */ jsx(
|
|
186
|
-
"input",
|
|
187
|
-
{
|
|
188
|
-
ref: fileInputRef,
|
|
189
|
-
accept,
|
|
190
|
-
"aria-label": "Attach files",
|
|
191
|
-
className: "hidden",
|
|
192
|
-
multiple: true,
|
|
193
|
-
name: "attachments",
|
|
194
|
-
onChange: handleFilesChange,
|
|
195
|
-
type: "file"
|
|
196
|
-
}
|
|
197
|
-
),
|
|
198
|
-
/* @__PURE__ */ jsx(
|
|
199
|
-
"button",
|
|
200
|
-
{
|
|
201
|
-
"aria-label": "Attach files",
|
|
202
|
-
className: cn("a24-composer__attach", pillButtonClass),
|
|
203
|
-
disabled: disabled || isSubmitting || isRunning || !allowAttachments,
|
|
204
|
-
onClick: () => fileInputRef.current?.click(),
|
|
205
|
-
title: "Attach files",
|
|
206
|
-
type: "button",
|
|
207
|
-
children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
] }),
|
|
211
|
-
/* @__PURE__ */ jsx("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx(
|
|
212
|
-
"textarea",
|
|
213
|
-
{
|
|
214
|
-
ref: setTextareaRef,
|
|
215
|
-
"aria-label": "Message",
|
|
216
|
-
autoComplete: "off",
|
|
217
|
-
className: cn(
|
|
218
|
-
"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-muted-foreground focus-visible:outline-none focus-visible:ring-0",
|
|
219
|
-
isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
|
|
220
|
-
),
|
|
221
|
-
disabled: disabled || isSubmitting || isRunning,
|
|
222
|
-
name: "message",
|
|
223
|
-
onChange: (event) => setText(event.target.value),
|
|
224
|
-
onKeyDown: handleKeyDown,
|
|
225
|
-
placeholder,
|
|
226
|
-
rows: isExpanded ? 2 : 1,
|
|
227
|
-
value: text
|
|
228
|
-
}
|
|
229
|
-
) }),
|
|
230
|
-
isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
|
|
231
|
-
!isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
|
|
232
|
-
!isExpanded ? /* @__PURE__ */ jsx("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
|
|
233
|
-
/* @__PURE__ */ jsx("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ jsx(
|
|
234
|
-
"button",
|
|
235
|
-
{
|
|
236
|
-
"aria-label": "Stop generating",
|
|
237
|
-
className: cn("a24-composer__submit", submitButtonClass),
|
|
238
|
-
disabled: disabled || !onStop,
|
|
239
|
-
onClick: onStop,
|
|
240
|
-
title: "Stop",
|
|
241
|
-
type: "button",
|
|
242
|
-
children: /* @__PURE__ */ jsx(Square, { className: "size-3.5 fill-current" })
|
|
243
|
-
}
|
|
244
|
-
) : /* @__PURE__ */ jsx(
|
|
245
|
-
"button",
|
|
246
|
-
{
|
|
247
|
-
"aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
|
|
248
|
-
className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
|
|
249
|
-
disabled: !canSubmit,
|
|
250
|
-
onClick: () => void submit(),
|
|
251
|
-
title: "Send",
|
|
252
|
-
type: "button",
|
|
253
|
-
children: /* @__PURE__ */ jsx(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
|
|
254
|
-
}
|
|
255
|
-
) })
|
|
256
|
-
]
|
|
257
|
-
}
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
),
|
|
261
|
-
!isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
|
|
262
|
-
] });
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// src/ui/agent-chat-actions.tsx
|
|
266
|
-
import * as React3 from "react";
|
|
267
|
-
import { Check, Copy, RefreshCcw, ThumbsDown, ThumbsUp } from "lucide-react";
|
|
268
|
-
import { Popover } from "radix-ui";
|
|
269
|
-
|
|
270
|
-
// src/ui/adapters.ts
|
|
271
|
-
import * as React2 from "react";
|
|
272
|
-
|
|
273
30
|
// src/ui/attachment.tsx
|
|
274
|
-
import {
|
|
275
|
-
import { Slot } from "@radix-ui/react-slot";
|
|
276
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
31
|
+
import { jsx } from "react/jsx-runtime";
|
|
277
32
|
var attachmentVariants = cva(
|
|
278
33
|
"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",
|
|
279
34
|
{
|
|
@@ -297,7 +52,7 @@ function Attachment({
|
|
|
297
52
|
orientation = "horizontal",
|
|
298
53
|
...props
|
|
299
54
|
}) {
|
|
300
|
-
return /* @__PURE__ */
|
|
55
|
+
return /* @__PURE__ */ jsx(
|
|
301
56
|
"div",
|
|
302
57
|
{
|
|
303
58
|
"data-slot": "attachment",
|
|
@@ -310,12 +65,12 @@ function Attachment({
|
|
|
310
65
|
);
|
|
311
66
|
}
|
|
312
67
|
var attachmentMediaVariants = cva(
|
|
313
|
-
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg
|
|
68
|
+
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg 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: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",
|
|
314
69
|
{
|
|
315
70
|
variants: {
|
|
316
71
|
variant: {
|
|
317
72
|
icon: "",
|
|
318
|
-
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"
|
|
73
|
+
image: "bg-muted 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"
|
|
319
74
|
}
|
|
320
75
|
},
|
|
321
76
|
defaultVariants: {
|
|
@@ -328,7 +83,7 @@ function AttachmentMedia({
|
|
|
328
83
|
variant = "icon",
|
|
329
84
|
...props
|
|
330
85
|
}) {
|
|
331
|
-
return /* @__PURE__ */
|
|
86
|
+
return /* @__PURE__ */ jsx(
|
|
332
87
|
"div",
|
|
333
88
|
{
|
|
334
89
|
"data-slot": "attachment-media",
|
|
@@ -342,7 +97,7 @@ function AttachmentContent({
|
|
|
342
97
|
className,
|
|
343
98
|
...props
|
|
344
99
|
}) {
|
|
345
|
-
return /* @__PURE__ */
|
|
100
|
+
return /* @__PURE__ */ jsx(
|
|
346
101
|
"div",
|
|
347
102
|
{
|
|
348
103
|
"data-slot": "attachment-content",
|
|
@@ -358,7 +113,7 @@ function AttachmentTitle({
|
|
|
358
113
|
className,
|
|
359
114
|
...props
|
|
360
115
|
}) {
|
|
361
|
-
return /* @__PURE__ */
|
|
116
|
+
return /* @__PURE__ */ jsx(
|
|
362
117
|
"span",
|
|
363
118
|
{
|
|
364
119
|
"data-slot": "attachment-title",
|
|
@@ -374,7 +129,7 @@ function AttachmentDescription({
|
|
|
374
129
|
className,
|
|
375
130
|
...props
|
|
376
131
|
}) {
|
|
377
|
-
return /* @__PURE__ */
|
|
132
|
+
return /* @__PURE__ */ jsx(
|
|
378
133
|
"span",
|
|
379
134
|
{
|
|
380
135
|
"data-slot": "attachment-description",
|
|
@@ -391,7 +146,7 @@ function AttachmentActions({
|
|
|
391
146
|
className,
|
|
392
147
|
...props
|
|
393
148
|
}) {
|
|
394
|
-
return /* @__PURE__ */
|
|
149
|
+
return /* @__PURE__ */ jsx(
|
|
395
150
|
"div",
|
|
396
151
|
{
|
|
397
152
|
"data-slot": "attachment-actions",
|
|
@@ -408,7 +163,7 @@ function AttachmentAction({
|
|
|
408
163
|
type = "button",
|
|
409
164
|
...props
|
|
410
165
|
}) {
|
|
411
|
-
return /* @__PURE__ */
|
|
166
|
+
return /* @__PURE__ */ jsx(
|
|
412
167
|
"button",
|
|
413
168
|
{
|
|
414
169
|
"data-slot": "attachment-action",
|
|
@@ -428,7 +183,7 @@ function AttachmentTrigger({
|
|
|
428
183
|
...props
|
|
429
184
|
}) {
|
|
430
185
|
const Comp = asChild ? Slot : "button";
|
|
431
|
-
return /* @__PURE__ */
|
|
186
|
+
return /* @__PURE__ */ jsx(
|
|
432
187
|
Comp,
|
|
433
188
|
{
|
|
434
189
|
"data-slot": "attachment-trigger",
|
|
@@ -439,7 +194,7 @@ function AttachmentTrigger({
|
|
|
439
194
|
);
|
|
440
195
|
}
|
|
441
196
|
function AttachmentGroup({ className, ...props }) {
|
|
442
|
-
return /* @__PURE__ */
|
|
197
|
+
return /* @__PURE__ */ jsx(
|
|
443
198
|
"div",
|
|
444
199
|
{
|
|
445
200
|
"data-slot": "attachment-group",
|
|
@@ -453,9 +208,9 @@ function AttachmentGroup({ className, ...props }) {
|
|
|
453
208
|
}
|
|
454
209
|
|
|
455
210
|
// src/ui/message.tsx
|
|
456
|
-
import { jsx as
|
|
211
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
457
212
|
function MessageGroup({ className, ...props }) {
|
|
458
|
-
return /* @__PURE__ */
|
|
213
|
+
return /* @__PURE__ */ jsx2(
|
|
459
214
|
"div",
|
|
460
215
|
{
|
|
461
216
|
"data-slot": "message-group",
|
|
@@ -469,7 +224,7 @@ function Message({
|
|
|
469
224
|
align = "start",
|
|
470
225
|
...props
|
|
471
226
|
}) {
|
|
472
|
-
return /* @__PURE__ */
|
|
227
|
+
return /* @__PURE__ */ jsx2(
|
|
473
228
|
"div",
|
|
474
229
|
{
|
|
475
230
|
"data-slot": "message",
|
|
@@ -483,7 +238,7 @@ function Message({
|
|
|
483
238
|
);
|
|
484
239
|
}
|
|
485
240
|
function MessageAvatar({ className, ...props }) {
|
|
486
|
-
return /* @__PURE__ */
|
|
241
|
+
return /* @__PURE__ */ jsx2(
|
|
487
242
|
"div",
|
|
488
243
|
{
|
|
489
244
|
"data-slot": "message-avatar",
|
|
@@ -496,7 +251,7 @@ function MessageAvatar({ className, ...props }) {
|
|
|
496
251
|
);
|
|
497
252
|
}
|
|
498
253
|
function MessageContent({ className, ...props }) {
|
|
499
|
-
return /* @__PURE__ */
|
|
254
|
+
return /* @__PURE__ */ jsx2(
|
|
500
255
|
"div",
|
|
501
256
|
{
|
|
502
257
|
"data-slot": "message-content",
|
|
@@ -509,7 +264,7 @@ function MessageContent({ className, ...props }) {
|
|
|
509
264
|
);
|
|
510
265
|
}
|
|
511
266
|
function MessageHeader({ className, ...props }) {
|
|
512
|
-
return /* @__PURE__ */
|
|
267
|
+
return /* @__PURE__ */ jsx2(
|
|
513
268
|
"div",
|
|
514
269
|
{
|
|
515
270
|
"data-slot": "message-header",
|
|
@@ -522,7 +277,7 @@ function MessageHeader({ className, ...props }) {
|
|
|
522
277
|
);
|
|
523
278
|
}
|
|
524
279
|
function MessageFooter({ className, ...props }) {
|
|
525
|
-
return /* @__PURE__ */
|
|
280
|
+
return /* @__PURE__ */ jsx2(
|
|
526
281
|
"div",
|
|
527
282
|
{
|
|
528
283
|
"data-slot": "message-footer",
|
|
@@ -536,23 +291,22 @@ function MessageFooter({ className, ...props }) {
|
|
|
536
291
|
}
|
|
537
292
|
|
|
538
293
|
// src/ui/adapters.ts
|
|
539
|
-
var ChatMessageRoleContext =
|
|
294
|
+
var ChatMessageRoleContext = React.createContext("assistant");
|
|
540
295
|
function ChatMessage(input) {
|
|
541
296
|
const { className, from, align, ...props } = input;
|
|
542
297
|
const isUser = from === "user";
|
|
543
|
-
const isRtl = props.dir === "rtl";
|
|
544
298
|
const resolvedAlign = align || "start";
|
|
545
299
|
const messageProps = Object.assign({}, props);
|
|
546
300
|
messageProps.align = resolvedAlign;
|
|
547
301
|
messageProps.className = cn(
|
|
548
|
-
"a24-chat-message group flex w-full flex-col
|
|
549
|
-
isUser ?
|
|
302
|
+
"a24-chat-message group flex w-full flex-col",
|
|
303
|
+
isUser ? "a24-chat-message--user is-user max-w-full justify-end gap-0" : "a24-chat-message--assistant is-assistant max-w-full gap-2",
|
|
550
304
|
className
|
|
551
305
|
);
|
|
552
|
-
return
|
|
306
|
+
return React.createElement(
|
|
553
307
|
ChatMessageRoleContext.Provider,
|
|
554
308
|
{ value: from },
|
|
555
|
-
|
|
309
|
+
React.createElement(Message, messageProps)
|
|
556
310
|
);
|
|
557
311
|
}
|
|
558
312
|
function ChatMessageContent({
|
|
@@ -560,9 +314,9 @@ function ChatMessageContent({
|
|
|
560
314
|
from,
|
|
561
315
|
...props
|
|
562
316
|
}) {
|
|
563
|
-
const inheritedRole =
|
|
317
|
+
const inheritedRole = React.useContext(ChatMessageRoleContext);
|
|
564
318
|
const isUser = (from ?? inheritedRole) === "user";
|
|
565
|
-
return
|
|
319
|
+
return React.createElement("div", {
|
|
566
320
|
className: cn(
|
|
567
321
|
"a24-chat-message-content flex max-w-full min-w-0 flex-col gap-2 text-sm",
|
|
568
322
|
isUser ? "a24-chat-message-content--user is-user:dark w-fit overflow-visible rounded-lg bg-secondary px-4 py-3 text-foreground" : "a24-chat-message-content--assistant w-full overflow-visible text-foreground",
|
|
@@ -576,7 +330,7 @@ function ChatMessageActions({
|
|
|
576
330
|
className,
|
|
577
331
|
...props
|
|
578
332
|
}) {
|
|
579
|
-
return
|
|
333
|
+
return React.createElement("div", {
|
|
580
334
|
className: cn("flex items-center gap-1", className),
|
|
581
335
|
"data-slot": "message-actions",
|
|
582
336
|
...props
|
|
@@ -591,7 +345,7 @@ function ChatMessageAction({
|
|
|
591
345
|
...props
|
|
592
346
|
}) {
|
|
593
347
|
const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
|
|
594
|
-
return
|
|
348
|
+
return React.createElement(
|
|
595
349
|
"button",
|
|
596
350
|
{
|
|
597
351
|
"aria-label": label || tooltipLabel,
|
|
@@ -604,83 +358,629 @@ function ChatMessageAction({
|
|
|
604
358
|
...props
|
|
605
359
|
},
|
|
606
360
|
children,
|
|
607
|
-
|
|
361
|
+
React.createElement(
|
|
608
362
|
"span",
|
|
609
363
|
{ className: "sr-only" },
|
|
610
364
|
label || tooltipLabel
|
|
611
365
|
)
|
|
612
366
|
);
|
|
613
367
|
}
|
|
368
|
+
var resolvedContentCache = /* @__PURE__ */ new WeakMap();
|
|
369
|
+
var CONTENT_CACHE_EXPIRY_BUFFER_MS = 15e3;
|
|
370
|
+
var CONTENT_CACHE_MAX_ENTRIES = 100;
|
|
371
|
+
function attachmentCacheKey(data) {
|
|
372
|
+
const id = typeof data.id === "string" ? data.id.trim() : "";
|
|
373
|
+
return id || null;
|
|
374
|
+
}
|
|
375
|
+
function cachedAttachmentContent(resolver, data) {
|
|
376
|
+
const key = attachmentCacheKey(data);
|
|
377
|
+
if (!key) return null;
|
|
378
|
+
const cached = resolvedContentCache.get(resolver)?.get(key);
|
|
379
|
+
if (!cached) return null;
|
|
380
|
+
if (!cached.validUntil) return cached;
|
|
381
|
+
const validUntil = Date.parse(cached.validUntil);
|
|
382
|
+
if (Number.isFinite(validUntil) && validUntil - CONTENT_CACHE_EXPIRY_BUFFER_MS > Date.now()) {
|
|
383
|
+
return cached;
|
|
384
|
+
}
|
|
385
|
+
resolvedContentCache.get(resolver)?.delete(key);
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
function cacheAttachmentContent(resolver, data, result) {
|
|
389
|
+
const key = attachmentCacheKey(data);
|
|
390
|
+
if (!key) return;
|
|
391
|
+
const cache = resolvedContentCache.get(resolver) || /* @__PURE__ */ new Map();
|
|
392
|
+
cache.delete(key);
|
|
393
|
+
cache.set(key, result);
|
|
394
|
+
while (cache.size > CONTENT_CACHE_MAX_ENTRIES) {
|
|
395
|
+
const oldestKey = cache.keys().next().value;
|
|
396
|
+
if (typeof oldestKey !== "string") break;
|
|
397
|
+
cache.delete(oldestKey);
|
|
398
|
+
}
|
|
399
|
+
resolvedContentCache.set(resolver, cache);
|
|
400
|
+
}
|
|
401
|
+
function invalidateAttachmentContent(resolver, data) {
|
|
402
|
+
const key = attachmentCacheKey(data);
|
|
403
|
+
if (key) resolvedContentCache.get(resolver)?.delete(key);
|
|
404
|
+
}
|
|
405
|
+
function attachmentMediaType(data) {
|
|
406
|
+
return data.mediaType || data.contentType || String(data.type || "application/octet-stream");
|
|
407
|
+
}
|
|
408
|
+
function attachmentFilename(data) {
|
|
409
|
+
return data.filename || data.name || "Attachment";
|
|
410
|
+
}
|
|
411
|
+
function attachmentExtension(filename) {
|
|
412
|
+
const extension = filename.split(".").pop();
|
|
413
|
+
return extension && extension !== filename ? extension.toLowerCase() : "";
|
|
414
|
+
}
|
|
415
|
+
function resolveAttachmentVisual(data) {
|
|
416
|
+
const mediaType = attachmentMediaType(data).toLowerCase();
|
|
417
|
+
const extension = attachmentExtension(attachmentFilename(data));
|
|
418
|
+
if (mediaType.startsWith("image/") || ["avif", "gif", "heic", "jpeg", "jpg", "png", "svg", "webp"].includes(extension)) {
|
|
419
|
+
return { fileIconType: extension || "image", kind: "image" };
|
|
420
|
+
}
|
|
421
|
+
if (mediaType === "application/pdf" || extension === "pdf") {
|
|
422
|
+
return { fileIconType: "pdf", kind: "pdf" };
|
|
423
|
+
}
|
|
424
|
+
if (mediaType.includes("spreadsheet") || mediaType.includes("csv") || ["csv", "numbers", "ods", "xls", "xlsm", "xlsx"].includes(extension)) {
|
|
425
|
+
if (["csv", "xls", "xlsx"].includes(extension)) {
|
|
426
|
+
return { fileIconType: extension, kind: "spreadsheet" };
|
|
427
|
+
}
|
|
428
|
+
return { fileIconType: "spreadsheets", kind: "spreadsheet" };
|
|
429
|
+
}
|
|
430
|
+
if (mediaType.includes("presentation") || ["key", "odp", "ppt", "pptx"].includes(extension)) {
|
|
431
|
+
return { fileIconType: extension === "ppt" ? "ppt" : "pptx", kind: "presentation" };
|
|
432
|
+
}
|
|
433
|
+
if (mediaType.includes("word") || mediaType.includes("rtf") || ["doc", "docx", "odt", "rtf"].includes(extension)) {
|
|
434
|
+
if (extension === "doc" || extension === "docx") {
|
|
435
|
+
return { fileIconType: extension, kind: "document" };
|
|
436
|
+
}
|
|
437
|
+
return { fileIconType: "document", kind: "document" };
|
|
438
|
+
}
|
|
439
|
+
if (mediaType.startsWith("audio/") || ["aac", "flac", "m4a", "mp3", "ogg", "wav"].includes(extension)) {
|
|
440
|
+
if (extension === "mp3" || extension === "wav") {
|
|
441
|
+
return { fileIconType: extension, kind: "audio" };
|
|
442
|
+
}
|
|
443
|
+
return { fileIconType: "audio", kind: "audio" };
|
|
444
|
+
}
|
|
445
|
+
if (mediaType.startsWith("video/") || ["avi", "m4v", "mkv", "mov", "mp4", "webm"].includes(extension)) {
|
|
446
|
+
if (["avi", "mkv", "mp4", "mpeg"].includes(extension)) {
|
|
447
|
+
return { fileIconType: extension, kind: "video" };
|
|
448
|
+
}
|
|
449
|
+
return { fileIconType: "video", kind: "video" };
|
|
450
|
+
}
|
|
451
|
+
if (mediaType.includes("zip") || mediaType.includes("compressed") || ["7z", "bz2", "gz", "rar", "tar", "tgz", "zip"].includes(extension)) {
|
|
452
|
+
return { fileIconType: extension === "rar" ? "rar" : "zip", kind: "archive" };
|
|
453
|
+
}
|
|
454
|
+
if (mediaType.includes("json") || mediaType.includes("javascript") || mediaType.includes("typescript") || mediaType.includes("xml") || ["css", "html", "js", "jsx", "json", "py", "sql", "ts", "tsx", "xml", "yaml", "yml"].includes(extension)) {
|
|
455
|
+
if (["css", "html", "java", "js", "json", "sql", "xml"].includes(extension)) {
|
|
456
|
+
return { fileIconType: extension, kind: "code" };
|
|
457
|
+
}
|
|
458
|
+
return { fileIconType: "code", kind: "code" };
|
|
459
|
+
}
|
|
460
|
+
if (mediaType.startsWith("text/") || ["md", "markdown", "txt"].includes(extension)) {
|
|
461
|
+
return { fileIconType: "txt", kind: "file" };
|
|
462
|
+
}
|
|
463
|
+
return { fileIconType: "empty", kind: "file" };
|
|
464
|
+
}
|
|
465
|
+
function solidFileIconBackground(fileIconType, kind) {
|
|
466
|
+
if (["txt", "zip", "rar"].includes(fileIconType)) return "#344054";
|
|
467
|
+
if (kind === "pdf") return "#D92D20";
|
|
468
|
+
if (kind === "spreadsheet") return "#079455";
|
|
469
|
+
if (kind === "document" || kind === "video") return "#155EEF";
|
|
470
|
+
if (kind === "presentation") return "#E62E05";
|
|
471
|
+
if (kind === "audio") return "#DD2590";
|
|
472
|
+
if (kind === "code") return "#444CE7";
|
|
473
|
+
return "#7F56D9";
|
|
474
|
+
}
|
|
475
|
+
function isImageAttachment(data) {
|
|
476
|
+
return resolveAttachmentVisual(data).kind === "image";
|
|
477
|
+
}
|
|
478
|
+
function attachmentRenderKey(data, index, rowKind) {
|
|
479
|
+
const id = typeof data.id === "string" ? data.id.trim() : "";
|
|
480
|
+
const identity = id || `${attachmentFilename(data)}:${attachmentMediaType(data)}`;
|
|
481
|
+
return `${rowKind}:${identity}:${index}`;
|
|
482
|
+
}
|
|
483
|
+
function ChatImageAttachment({
|
|
484
|
+
className,
|
|
485
|
+
data,
|
|
486
|
+
onRemove,
|
|
487
|
+
resolveContent,
|
|
488
|
+
...props
|
|
489
|
+
}) {
|
|
490
|
+
const filename = attachmentFilename(data);
|
|
491
|
+
const initialUrl = typeof data.url === "string" ? data.url.trim() : "";
|
|
492
|
+
const contentStatus = String(data.contentStatus || data.content_status || "available");
|
|
493
|
+
const attachmentKey = attachmentCacheKey(data);
|
|
494
|
+
const dataRef = React.useRef(data);
|
|
495
|
+
dataRef.current = data;
|
|
496
|
+
const rootRef = React.useRef(null);
|
|
497
|
+
const retryCountRef = React.useRef(0);
|
|
498
|
+
const [isNearViewport, setIsNearViewport] = React.useState(Boolean(initialUrl));
|
|
499
|
+
const [sourceUrl, setSourceUrl] = React.useState(initialUrl);
|
|
500
|
+
const [status, setStatus] = React.useState(
|
|
501
|
+
initialUrl ? "loading" : contentStatus === "expired" || !resolveContent ? "error" : "idle"
|
|
502
|
+
);
|
|
503
|
+
const [resolutionAttempt, setResolutionAttempt] = React.useState(0);
|
|
504
|
+
React.useEffect(() => {
|
|
505
|
+
if (initialUrl && resolutionAttempt === 0 || contentStatus === "expired" || !resolveContent) return;
|
|
506
|
+
const node = rootRef.current;
|
|
507
|
+
if (!node || typeof IntersectionObserver === "undefined") {
|
|
508
|
+
setIsNearViewport(true);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const observer = new IntersectionObserver(
|
|
512
|
+
(entries) => {
|
|
513
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
514
|
+
setIsNearViewport(true);
|
|
515
|
+
observer.disconnect();
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
{ rootMargin: "240px" }
|
|
519
|
+
);
|
|
520
|
+
observer.observe(node);
|
|
521
|
+
return () => observer.disconnect();
|
|
522
|
+
}, [contentStatus, initialUrl, resolutionAttempt, resolveContent]);
|
|
523
|
+
React.useEffect(() => {
|
|
524
|
+
if (initialUrl && resolutionAttempt === 0) {
|
|
525
|
+
setSourceUrl(initialUrl);
|
|
526
|
+
setStatus("loading");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (!isNearViewport || !resolveContent || contentStatus === "expired") return;
|
|
530
|
+
const attachment = dataRef.current;
|
|
531
|
+
const cached = cachedAttachmentContent(resolveContent, attachment);
|
|
532
|
+
if (cached) {
|
|
533
|
+
setSourceUrl(cached.url);
|
|
534
|
+
setStatus("loading");
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const controller = new AbortController();
|
|
538
|
+
setStatus("resolving");
|
|
539
|
+
void resolveContent(attachment, controller.signal).then((result) => {
|
|
540
|
+
if (controller.signal.aborted) return;
|
|
541
|
+
if (!result.url?.trim()) throw new Error("Attachment content resolver returned an empty URL");
|
|
542
|
+
cacheAttachmentContent(resolveContent, attachment, result);
|
|
543
|
+
setSourceUrl(result.url);
|
|
544
|
+
setStatus("loading");
|
|
545
|
+
}).catch(() => {
|
|
546
|
+
if (!controller.signal.aborted) setStatus("error");
|
|
547
|
+
});
|
|
548
|
+
return () => controller.abort();
|
|
549
|
+
}, [attachmentKey, contentStatus, initialUrl, isNearViewport, resolutionAttempt, resolveContent]);
|
|
550
|
+
const handleImageError = () => {
|
|
551
|
+
if (resolveContent && retryCountRef.current === 0) {
|
|
552
|
+
retryCountRef.current = 1;
|
|
553
|
+
invalidateAttachmentContent(resolveContent, dataRef.current);
|
|
554
|
+
setSourceUrl("");
|
|
555
|
+
setStatus("resolving");
|
|
556
|
+
setResolutionAttempt((current) => current + 1);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
setStatus("error");
|
|
560
|
+
};
|
|
561
|
+
return React.createElement(
|
|
562
|
+
Attachment,
|
|
563
|
+
{
|
|
564
|
+
...props,
|
|
565
|
+
className: cn("a24-chat-image-attachment", className),
|
|
566
|
+
orientation: "vertical",
|
|
567
|
+
ref: rootRef,
|
|
568
|
+
state: status === "error" ? "error" : status === "done" ? "done" : "processing",
|
|
569
|
+
title: filename
|
|
570
|
+
},
|
|
571
|
+
React.createElement(
|
|
572
|
+
AttachmentMedia,
|
|
573
|
+
{ className: "a24-chat-image-media", variant: "image" },
|
|
574
|
+
sourceUrl ? React.createElement("img", {
|
|
575
|
+
alt: filename,
|
|
576
|
+
decoding: "async",
|
|
577
|
+
loading: "lazy",
|
|
578
|
+
onError: handleImageError,
|
|
579
|
+
onLoad: () => setStatus("done"),
|
|
580
|
+
src: sourceUrl
|
|
581
|
+
}) : status === "error" ? React.createElement(ImageOff, { "aria-hidden": "true", className: "size-5" }) : React.createElement(LoaderCircle, {
|
|
582
|
+
"aria-hidden": "true",
|
|
583
|
+
className: "a24-chat-image-spinner size-5 animate-spin"
|
|
584
|
+
}),
|
|
585
|
+
status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
|
|
586
|
+
status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
|
|
587
|
+
),
|
|
588
|
+
onRemove ? React.createElement(
|
|
589
|
+
"button",
|
|
590
|
+
{
|
|
591
|
+
"aria-label": `Remove ${filename}`,
|
|
592
|
+
className: "a24-chat-image-remove",
|
|
593
|
+
onClick: (event) => {
|
|
594
|
+
event.stopPropagation();
|
|
595
|
+
onRemove();
|
|
596
|
+
},
|
|
597
|
+
type: "button"
|
|
598
|
+
},
|
|
599
|
+
React.createElement(X, { "aria-hidden": "true", className: "size-3.5" })
|
|
600
|
+
) : null
|
|
601
|
+
);
|
|
602
|
+
}
|
|
614
603
|
function ChatAttachment({
|
|
604
|
+
className,
|
|
615
605
|
data,
|
|
616
606
|
onRemove,
|
|
617
607
|
orientation,
|
|
608
|
+
resolveContent,
|
|
618
609
|
...props
|
|
619
610
|
}) {
|
|
620
|
-
const
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
611
|
+
const filename = attachmentFilename(data);
|
|
612
|
+
const { fileIconType, kind } = resolveAttachmentVisual(data);
|
|
613
|
+
if (kind === "image") {
|
|
614
|
+
return React.createElement(ChatImageAttachment, {
|
|
615
|
+
...props,
|
|
616
|
+
className,
|
|
617
|
+
data,
|
|
618
|
+
onRemove,
|
|
619
|
+
resolveContent
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return React.createElement(
|
|
624
623
|
Attachment,
|
|
625
624
|
{
|
|
626
|
-
|
|
625
|
+
className: cn("a24-chat-attachment", className),
|
|
626
|
+
orientation: orientation || "horizontal",
|
|
627
|
+
size: "sm",
|
|
627
628
|
...props
|
|
628
629
|
},
|
|
629
|
-
|
|
630
|
+
React.createElement(
|
|
630
631
|
AttachmentMedia,
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
"
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
632
|
+
Object.assign(
|
|
633
|
+
{
|
|
634
|
+
className: "a24-chat-attachment-icon",
|
|
635
|
+
style: { backgroundColor: solidFileIconBackground(fileIconType, kind) },
|
|
636
|
+
variant: "icon"
|
|
637
|
+
},
|
|
638
|
+
{ "data-file-kind": kind }
|
|
639
|
+
),
|
|
640
|
+
React.createElement(FileIcon, {
|
|
641
|
+
"aria-hidden": "true",
|
|
642
|
+
size: 28,
|
|
643
|
+
type: fileIconType,
|
|
644
|
+
variant: "solid"
|
|
645
|
+
})
|
|
641
646
|
),
|
|
642
|
-
|
|
647
|
+
React.createElement(
|
|
643
648
|
AttachmentContent,
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
React2.createElement(AttachmentDescription, null, mediaType)
|
|
649
|
+
{ className: "a24-chat-attachment-content" },
|
|
650
|
+
React.createElement(AttachmentTitle, { title: filename }, filename)
|
|
647
651
|
),
|
|
648
|
-
onRemove ?
|
|
652
|
+
onRemove ? React.createElement(
|
|
649
653
|
"button",
|
|
650
654
|
{
|
|
651
655
|
"aria-label": "Remove attachment",
|
|
652
|
-
className: "relative z-20
|
|
656
|
+
className: "relative z-20 inline-flex size-6 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
653
657
|
onClick: (event) => {
|
|
654
658
|
event.stopPropagation();
|
|
655
659
|
onRemove();
|
|
656
660
|
},
|
|
657
661
|
type: "button"
|
|
658
662
|
},
|
|
659
|
-
|
|
663
|
+
React.createElement(X, { "aria-hidden": "true", className: "size-3.5" })
|
|
660
664
|
) : null
|
|
661
665
|
);
|
|
662
666
|
}
|
|
663
|
-
function ChatAttachments({
|
|
664
|
-
children,
|
|
667
|
+
function ChatAttachments({
|
|
668
|
+
children,
|
|
669
|
+
className,
|
|
670
|
+
...props
|
|
671
|
+
}) {
|
|
672
|
+
if (!children) return null;
|
|
673
|
+
return React.createElement(
|
|
674
|
+
AttachmentGroup,
|
|
675
|
+
{
|
|
676
|
+
className: cn(
|
|
677
|
+
"a24-chat-attachments w-full flex-nowrap",
|
|
678
|
+
className
|
|
679
|
+
),
|
|
680
|
+
...props
|
|
681
|
+
},
|
|
682
|
+
children
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
function ChatAttachmentRows({
|
|
686
|
+
attachments,
|
|
687
|
+
className,
|
|
688
|
+
dir,
|
|
689
|
+
onRemove,
|
|
690
|
+
resolveContent,
|
|
691
|
+
...props
|
|
692
|
+
}) {
|
|
693
|
+
if (attachments.length === 0) return null;
|
|
694
|
+
const imageAttachments = attachments.filter(isImageAttachment);
|
|
695
|
+
const fileAttachments = attachments.filter((attachment) => !isImageAttachment(attachment));
|
|
696
|
+
const renderRow = (items, kind) => items.length > 0 ? React.createElement(
|
|
697
|
+
ChatAttachments,
|
|
698
|
+
Object.assign({ dir, key: kind }, { "data-attachment-row": kind }),
|
|
699
|
+
items.map(
|
|
700
|
+
(attachment, index) => React.createElement(ChatAttachment, {
|
|
701
|
+
data: attachment,
|
|
702
|
+
dir: "ltr",
|
|
703
|
+
key: attachmentRenderKey(attachment, index, kind),
|
|
704
|
+
onRemove: onRemove ? () => onRemove(attachment) : void 0,
|
|
705
|
+
resolveContent
|
|
706
|
+
})
|
|
707
|
+
)
|
|
708
|
+
) : null;
|
|
709
|
+
return React.createElement(
|
|
710
|
+
"div",
|
|
711
|
+
{
|
|
712
|
+
className: cn("a24-chat-attachment-rows", className),
|
|
713
|
+
dir,
|
|
714
|
+
...props
|
|
715
|
+
},
|
|
716
|
+
renderRow(imageAttachments, "images"),
|
|
717
|
+
renderRow(fileAttachments, "files")
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// src/ui/composer-attachments.ts
|
|
722
|
+
var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
723
|
+
function createAgentChatComposerFile(file) {
|
|
724
|
+
return {
|
|
725
|
+
id: createId(),
|
|
726
|
+
type: "file",
|
|
727
|
+
url: URL.createObjectURL(file),
|
|
728
|
+
filename: file.name || "attachment",
|
|
729
|
+
mediaType: file.type || "application/octet-stream",
|
|
730
|
+
source: file
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
function revokeAgentChatComposerFiles(files) {
|
|
734
|
+
files.forEach((file) => {
|
|
735
|
+
try {
|
|
736
|
+
URL.revokeObjectURL(file.url);
|
|
737
|
+
} catch {
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// src/ui/agent-chat-composer.tsx
|
|
743
|
+
import { jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
744
|
+
function formatFileCount(files) {
|
|
745
|
+
if (files.length === 1) return files[0].filename;
|
|
746
|
+
return `${files.length} files`;
|
|
747
|
+
}
|
|
748
|
+
var pillButtonClass = "inline-flex size-8 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-muted text-sm text-muted-foreground shadow-none transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
|
|
749
|
+
var submitButtonClass = "inline-flex size-9 shrink-0 items-center justify-center gap-2 rounded-lg border-0 bg-primary text-sm text-primary-foreground shadow-none transition-[background-color,color,opacity,transform] duration-200 hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-0 disabled:pointer-events-none disabled:opacity-50";
|
|
750
|
+
function AgentChatComposer({
|
|
751
|
+
accept,
|
|
752
|
+
allowAttachments = true,
|
|
753
|
+
attachmentLayout = "flow",
|
|
665
754
|
className,
|
|
666
|
-
|
|
755
|
+
disabled = false,
|
|
756
|
+
forceExpanded = false,
|
|
757
|
+
inputToolbarContent,
|
|
758
|
+
isRunning = false,
|
|
759
|
+
onAttachmentCountChange,
|
|
760
|
+
onStop,
|
|
761
|
+
onSubmit,
|
|
762
|
+
placeholder = "Message the agent",
|
|
763
|
+
textareaRef
|
|
667
764
|
}) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
765
|
+
const fileInputRef = React2.useRef(null);
|
|
766
|
+
const filesRef = React2.useRef([]);
|
|
767
|
+
const inFlightFilesRef = React2.useRef([]);
|
|
768
|
+
const localTextareaRef = React2.useRef(null);
|
|
769
|
+
const draftRevisionRef = React2.useRef(0);
|
|
770
|
+
const [text, setText] = React2.useState("");
|
|
771
|
+
const [files, setFiles] = React2.useState([]);
|
|
772
|
+
const [isSubmitting, setIsSubmitting] = React2.useState(false);
|
|
773
|
+
const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
|
|
774
|
+
const setTextareaRef = React2.useCallback(
|
|
775
|
+
(node) => {
|
|
776
|
+
localTextareaRef.current = node;
|
|
777
|
+
if (typeof textareaRef === "function") {
|
|
778
|
+
textareaRef(node);
|
|
779
|
+
} else if (textareaRef) {
|
|
780
|
+
textareaRef.current = node;
|
|
781
|
+
}
|
|
678
782
|
},
|
|
679
|
-
|
|
783
|
+
[textareaRef]
|
|
680
784
|
);
|
|
785
|
+
React2.useEffect(() => {
|
|
786
|
+
filesRef.current = files;
|
|
787
|
+
onAttachmentCountChange?.(files.length);
|
|
788
|
+
}, [files, onAttachmentCountChange]);
|
|
789
|
+
React2.useEffect(() => () => {
|
|
790
|
+
revokeAgentChatComposerFiles(filesRef.current);
|
|
791
|
+
revokeAgentChatComposerFiles(inFlightFilesRef.current);
|
|
792
|
+
}, []);
|
|
793
|
+
React2.useLayoutEffect(() => {
|
|
794
|
+
const textarea = localTextareaRef.current;
|
|
795
|
+
if (!textarea) return;
|
|
796
|
+
if (isExpanded) {
|
|
797
|
+
textarea.style.height = "auto";
|
|
798
|
+
const scrollHeight = textarea.scrollHeight;
|
|
799
|
+
textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
|
|
800
|
+
textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
textarea.style.height = "";
|
|
804
|
+
textarea.style.overflowY = "hidden";
|
|
805
|
+
}, [isExpanded, text]);
|
|
806
|
+
const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
|
|
807
|
+
const handleFilesChange = (event) => {
|
|
808
|
+
const selected = Array.from(event.target.files || []).map(createAgentChatComposerFile);
|
|
809
|
+
if (selected.length > 0) {
|
|
810
|
+
draftRevisionRef.current += 1;
|
|
811
|
+
setFiles((current) => [...current, ...selected]);
|
|
812
|
+
}
|
|
813
|
+
event.currentTarget.value = "";
|
|
814
|
+
};
|
|
815
|
+
const removeFile = (fileId) => {
|
|
816
|
+
draftRevisionRef.current += 1;
|
|
817
|
+
setFiles((current) => {
|
|
818
|
+
const target = current.find((file) => file.id === fileId);
|
|
819
|
+
if (target) revokeAgentChatComposerFiles([target]);
|
|
820
|
+
return current.filter((file) => file.id !== fileId);
|
|
821
|
+
});
|
|
822
|
+
};
|
|
823
|
+
const submit = async () => {
|
|
824
|
+
if (!canSubmit) return;
|
|
825
|
+
const submittedText = text.trim();
|
|
826
|
+
const submittedFiles = files;
|
|
827
|
+
const clearedRevision = draftRevisionRef.current + 1;
|
|
828
|
+
draftRevisionRef.current = clearedRevision;
|
|
829
|
+
inFlightFilesRef.current = submittedFiles;
|
|
830
|
+
setIsSubmitting(true);
|
|
831
|
+
setText("");
|
|
832
|
+
setFiles([]);
|
|
833
|
+
try {
|
|
834
|
+
await onSubmit({ text: submittedText, files: submittedFiles });
|
|
835
|
+
revokeAgentChatComposerFiles(submittedFiles);
|
|
836
|
+
inFlightFilesRef.current = [];
|
|
837
|
+
} catch {
|
|
838
|
+
if (draftRevisionRef.current === clearedRevision) {
|
|
839
|
+
draftRevisionRef.current += 1;
|
|
840
|
+
setText(submittedText);
|
|
841
|
+
setFiles(submittedFiles);
|
|
842
|
+
inFlightFilesRef.current = [];
|
|
843
|
+
} else {
|
|
844
|
+
revokeAgentChatComposerFiles(submittedFiles);
|
|
845
|
+
inFlightFilesRef.current = [];
|
|
846
|
+
}
|
|
847
|
+
} finally {
|
|
848
|
+
setIsSubmitting(false);
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
const handleKeyDown = (event) => {
|
|
852
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
853
|
+
event.preventDefault();
|
|
854
|
+
void submit();
|
|
855
|
+
}
|
|
856
|
+
};
|
|
857
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
|
|
858
|
+
files.length > 0 ? /* @__PURE__ */ jsx3(
|
|
859
|
+
"div",
|
|
860
|
+
{
|
|
861
|
+
"data-testid": "agent-chat-composer-attachments",
|
|
862
|
+
"data-layout": attachmentLayout,
|
|
863
|
+
className: cn(
|
|
864
|
+
"a24-composer__attachments min-w-0 py-1",
|
|
865
|
+
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"
|
|
866
|
+
),
|
|
867
|
+
children: /* @__PURE__ */ jsx3(
|
|
868
|
+
ChatAttachmentRows,
|
|
869
|
+
{
|
|
870
|
+
attachments: files,
|
|
871
|
+
onRemove: (attachment) => attachment.id && removeFile(attachment.id)
|
|
872
|
+
}
|
|
873
|
+
)
|
|
874
|
+
}
|
|
875
|
+
) : null,
|
|
876
|
+
/* @__PURE__ */ jsx3(
|
|
877
|
+
"div",
|
|
878
|
+
{
|
|
879
|
+
"data-expanded": isExpanded ? "" : void 0,
|
|
880
|
+
className: cn(
|
|
881
|
+
"a24-composer__surface relative w-full border border-input bg-background shadow-sm",
|
|
882
|
+
isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
|
|
883
|
+
),
|
|
884
|
+
children: /* @__PURE__ */ jsxs(
|
|
885
|
+
"div",
|
|
886
|
+
{
|
|
887
|
+
className: "a24-composer__grid w-full",
|
|
888
|
+
style: {
|
|
889
|
+
alignItems: "center",
|
|
890
|
+
display: "grid",
|
|
891
|
+
gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
|
|
892
|
+
gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
|
|
893
|
+
gap: isExpanded ? "3px 10px" : "8px"
|
|
894
|
+
},
|
|
895
|
+
children: [
|
|
896
|
+
/* @__PURE__ */ jsxs("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
|
|
897
|
+
/* @__PURE__ */ jsx3(
|
|
898
|
+
"input",
|
|
899
|
+
{
|
|
900
|
+
ref: fileInputRef,
|
|
901
|
+
accept,
|
|
902
|
+
"aria-label": "Attach files",
|
|
903
|
+
className: "hidden",
|
|
904
|
+
multiple: true,
|
|
905
|
+
name: "attachments",
|
|
906
|
+
onChange: handleFilesChange,
|
|
907
|
+
type: "file"
|
|
908
|
+
}
|
|
909
|
+
),
|
|
910
|
+
/* @__PURE__ */ jsx3(
|
|
911
|
+
"button",
|
|
912
|
+
{
|
|
913
|
+
"aria-label": "Attach files",
|
|
914
|
+
className: cn("a24-composer__attach", pillButtonClass),
|
|
915
|
+
disabled: disabled || isSubmitting || isRunning || !allowAttachments,
|
|
916
|
+
onClick: () => fileInputRef.current?.click(),
|
|
917
|
+
title: "Attach files",
|
|
918
|
+
type: "button",
|
|
919
|
+
children: /* @__PURE__ */ jsx3(Plus, { className: "size-4" })
|
|
920
|
+
}
|
|
921
|
+
)
|
|
922
|
+
] }),
|
|
923
|
+
/* @__PURE__ */ jsx3("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ jsx3(
|
|
924
|
+
"textarea",
|
|
925
|
+
{
|
|
926
|
+
ref: setTextareaRef,
|
|
927
|
+
"aria-label": "Message",
|
|
928
|
+
autoComplete: "off",
|
|
929
|
+
className: cn(
|
|
930
|
+
"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-muted-foreground focus-visible:outline-none focus-visible:ring-0",
|
|
931
|
+
isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
|
|
932
|
+
),
|
|
933
|
+
disabled: disabled || isSubmitting || isRunning,
|
|
934
|
+
name: "message",
|
|
935
|
+
onChange: (event) => {
|
|
936
|
+
draftRevisionRef.current += 1;
|
|
937
|
+
setText(event.target.value);
|
|
938
|
+
},
|
|
939
|
+
onKeyDown: handleKeyDown,
|
|
940
|
+
placeholder,
|
|
941
|
+
rows: isExpanded ? 2 : 1,
|
|
942
|
+
value: text
|
|
943
|
+
}
|
|
944
|
+
) }),
|
|
945
|
+
isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
|
|
946
|
+
!isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
|
|
947
|
+
!isExpanded ? /* @__PURE__ */ jsx3("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
|
|
948
|
+
/* @__PURE__ */ jsx3("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ jsx3(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
"aria-label": "Stop generating",
|
|
952
|
+
className: cn("a24-composer__submit", submitButtonClass),
|
|
953
|
+
disabled: disabled || !onStop,
|
|
954
|
+
onClick: onStop,
|
|
955
|
+
title: "Stop",
|
|
956
|
+
type: "button",
|
|
957
|
+
children: /* @__PURE__ */ jsx3(Square, { className: "size-3.5 fill-current" })
|
|
958
|
+
}
|
|
959
|
+
) : /* @__PURE__ */ jsx3(
|
|
960
|
+
"button",
|
|
961
|
+
{
|
|
962
|
+
"aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
|
|
963
|
+
className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
|
|
964
|
+
disabled: !canSubmit,
|
|
965
|
+
onClick: () => void submit(),
|
|
966
|
+
title: "Send",
|
|
967
|
+
type: "button",
|
|
968
|
+
children: /* @__PURE__ */ jsx3(ArrowUp, { className: "size-4", strokeWidth: 2.5 })
|
|
969
|
+
}
|
|
970
|
+
) })
|
|
971
|
+
]
|
|
972
|
+
}
|
|
973
|
+
)
|
|
974
|
+
}
|
|
975
|
+
),
|
|
976
|
+
!isExpanded && inputToolbarContent ? /* @__PURE__ */ jsx3("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
|
|
977
|
+
] });
|
|
681
978
|
}
|
|
682
979
|
|
|
683
980
|
// src/ui/agent-chat-actions.tsx
|
|
981
|
+
import * as React3 from "react";
|
|
982
|
+
import { Check, Copy, RefreshCcw, ThumbsDown, ThumbsUp } from "lucide-react";
|
|
983
|
+
import { Popover } from "radix-ui";
|
|
684
984
|
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
685
985
|
var FEEDBACK_REASONS = [
|
|
686
986
|
["incorrect", "Incorrect"],
|
|
@@ -690,6 +990,15 @@ var FEEDBACK_REASONS = [
|
|
|
690
990
|
["citation_issue", "Citation issue"],
|
|
691
991
|
["other", "Other"]
|
|
692
992
|
];
|
|
993
|
+
function formatSentAt(value) {
|
|
994
|
+
if (!value) return null;
|
|
995
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
996
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
997
|
+
return {
|
|
998
|
+
dateTime: date.toISOString(),
|
|
999
|
+
label: new Intl.DateTimeFormat(void 0, { timeStyle: "short" }).format(date)
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
693
1002
|
function AgentChatDefaultActions({
|
|
694
1003
|
additionalActions,
|
|
695
1004
|
dir,
|
|
@@ -700,11 +1009,43 @@ function AgentChatDefaultActions({
|
|
|
700
1009
|
const [feedbackTrigger, setFeedbackTrigger] = React3.useState(null);
|
|
701
1010
|
const [reason, setReason] = React3.useState(handlers?.feedback?.reason ?? null);
|
|
702
1011
|
const [comment, setComment] = React3.useState(handlers?.feedback?.comment ?? "");
|
|
1012
|
+
const [optimisticCopied, setOptimisticCopied] = React3.useState(false);
|
|
1013
|
+
const copyResetTimer = React3.useRef(null);
|
|
703
1014
|
React3.useEffect(() => {
|
|
704
1015
|
setReason(handlers?.feedback?.reason ?? null);
|
|
705
1016
|
setComment(handlers?.feedback?.comment ?? "");
|
|
706
1017
|
}, [handlers?.feedback?.comment, handlers?.feedback?.reason]);
|
|
707
|
-
|
|
1018
|
+
React3.useEffect(() => () => {
|
|
1019
|
+
if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
|
|
1020
|
+
}, []);
|
|
1021
|
+
const copied = handlers?.copied ?? optimisticCopied;
|
|
1022
|
+
const copyMessage = () => {
|
|
1023
|
+
handlers?.onCopy?.(message.content || "", message.id);
|
|
1024
|
+
if (handlers?.copied !== void 0) return;
|
|
1025
|
+
setOptimisticCopied(true);
|
|
1026
|
+
if (copyResetTimer.current !== null) window.clearTimeout(copyResetTimer.current);
|
|
1027
|
+
copyResetTimer.current = window.setTimeout(() => {
|
|
1028
|
+
copyResetTimer.current = null;
|
|
1029
|
+
setOptimisticCopied(false);
|
|
1030
|
+
}, 1200);
|
|
1031
|
+
};
|
|
1032
|
+
if (!handlers || message.isFinal === false) return null;
|
|
1033
|
+
if (message.role === "user") {
|
|
1034
|
+
const sentAt = formatSentAt(message.createdAt);
|
|
1035
|
+
return /* @__PURE__ */ jsxs2(ChatMessageActions, { className: "a24-user-message-actions", children: [
|
|
1036
|
+
sentAt ? /* @__PURE__ */ jsx4("time", { dateTime: sentAt.dateTime, children: sentAt.label }) : null,
|
|
1037
|
+
handlers.onCopy ? /* @__PURE__ */ jsx4(
|
|
1038
|
+
ChatMessageAction,
|
|
1039
|
+
{
|
|
1040
|
+
label: "Copy",
|
|
1041
|
+
onClick: copyMessage,
|
|
1042
|
+
tooltip: copied ? "Copied!" : "Copy to clipboard",
|
|
1043
|
+
className: "a24-user-message-copy",
|
|
1044
|
+
children: copied ? /* @__PURE__ */ jsx4(Check, { className: "a24-user-message-copy__check text-green-600" }) : /* @__PURE__ */ jsx4(Copy, { className: "a24-user-message-copy__icon" })
|
|
1045
|
+
}
|
|
1046
|
+
) : null
|
|
1047
|
+
] });
|
|
1048
|
+
}
|
|
708
1049
|
const canWriteFeedback = Boolean(message.runId && handlers.onSetFeedback);
|
|
709
1050
|
return /* @__PURE__ */ jsxs2(ChatMessageActions, { children: [
|
|
710
1051
|
handlers.onRetry ? /* @__PURE__ */ jsx4(
|
|
@@ -815,9 +1156,9 @@ function AgentChatDefaultActions({
|
|
|
815
1156
|
ChatMessageAction,
|
|
816
1157
|
{
|
|
817
1158
|
label: "Copy",
|
|
818
|
-
onClick:
|
|
819
|
-
tooltip:
|
|
820
|
-
children:
|
|
1159
|
+
onClick: copyMessage,
|
|
1160
|
+
tooltip: copied ? "Copied!" : "Copy to clipboard",
|
|
1161
|
+
children: copied ? /* @__PURE__ */ jsx4(Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ jsx4(Copy, { className: "size-4" })
|
|
821
1162
|
}
|
|
822
1163
|
) : null
|
|
823
1164
|
] });
|
|
@@ -1573,10 +1914,9 @@ function ToolDisclosure({
|
|
|
1573
1914
|
const [open, setOpen] = React6.useState(false);
|
|
1574
1915
|
const contentId = React6.useId();
|
|
1575
1916
|
const expandable = React6.Children.toArray(children).length > 0;
|
|
1576
|
-
const textOnlyRoot = Boolean(root && !grouped && !showOrb);
|
|
1577
1917
|
if (!expandable) {
|
|
1578
1918
|
const triggerClassName = cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active");
|
|
1579
|
-
return /* @__PURE__ */ jsxs4("div", { className:
|
|
1919
|
+
return /* @__PURE__ */ jsxs4("div", { className: "a24-tool-trigger-row", children: [
|
|
1580
1920
|
onSelect && part ? /* @__PURE__ */ jsxs4("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
|
|
1581
1921
|
showOrb ? /* @__PURE__ */ jsx8(ToolActivityOrb, { label: statusLabel }) : null,
|
|
1582
1922
|
/* @__PURE__ */ jsx8("span", { className: "a24-tool-trigger__label", children: label })
|
|
@@ -1588,7 +1928,7 @@ function ToolDisclosure({
|
|
|
1588
1928
|
] });
|
|
1589
1929
|
}
|
|
1590
1930
|
return /* @__PURE__ */ jsxs4("div", { className: "a24-tool-disclosure", children: [
|
|
1591
|
-
/* @__PURE__ */ jsxs4("div", { className:
|
|
1931
|
+
/* @__PURE__ */ jsxs4("div", { className: "a24-tool-trigger-row", children: [
|
|
1592
1932
|
/* @__PURE__ */ jsxs4(
|
|
1593
1933
|
"button",
|
|
1594
1934
|
{
|
|
@@ -2094,11 +2434,47 @@ function AgentChatUserMessageContent({
|
|
|
2094
2434
|
) : null
|
|
2095
2435
|
] });
|
|
2096
2436
|
}
|
|
2437
|
+
function AgentChatUserMessageActionRow({
|
|
2438
|
+
children,
|
|
2439
|
+
className
|
|
2440
|
+
}) {
|
|
2441
|
+
const rowRef = React8.useRef(null);
|
|
2442
|
+
const [visible, setVisible] = React8.useState(false);
|
|
2443
|
+
React8.useEffect(() => {
|
|
2444
|
+
const message = rowRef.current?.closest('[data-slot="message"]');
|
|
2445
|
+
if (!message) return;
|
|
2446
|
+
const show = () => setVisible(true);
|
|
2447
|
+
const hide = () => setVisible(false);
|
|
2448
|
+
const hideWhenFocusLeaves = (event) => {
|
|
2449
|
+
if (!message.contains(event.relatedTarget)) hide();
|
|
2450
|
+
};
|
|
2451
|
+
message.addEventListener("mouseenter", show);
|
|
2452
|
+
message.addEventListener("mouseleave", hide);
|
|
2453
|
+
message.addEventListener("focusin", show);
|
|
2454
|
+
message.addEventListener("focusout", hideWhenFocusLeaves);
|
|
2455
|
+
return () => {
|
|
2456
|
+
message.removeEventListener("mouseenter", show);
|
|
2457
|
+
message.removeEventListener("mouseleave", hide);
|
|
2458
|
+
message.removeEventListener("focusin", show);
|
|
2459
|
+
message.removeEventListener("focusout", hideWhenFocusLeaves);
|
|
2460
|
+
};
|
|
2461
|
+
}, []);
|
|
2462
|
+
return /* @__PURE__ */ jsx10(
|
|
2463
|
+
"div",
|
|
2464
|
+
{
|
|
2465
|
+
ref: rowRef,
|
|
2466
|
+
className: cn("a24-chat-message-action-row--user", visible && "is-visible", className),
|
|
2467
|
+
style: { opacity: visible ? 1 : 0, pointerEvents: visible ? "auto" : "none" },
|
|
2468
|
+
children
|
|
2469
|
+
}
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2097
2472
|
function AgentChatMessage({
|
|
2098
2473
|
actionHandlers,
|
|
2099
2474
|
additionalActions,
|
|
2100
2475
|
actions,
|
|
2101
2476
|
actionsClassName,
|
|
2477
|
+
attachmentContentResolver,
|
|
2102
2478
|
className,
|
|
2103
2479
|
contentClassName,
|
|
2104
2480
|
dir,
|
|
@@ -2131,48 +2507,58 @@ function AgentChatMessage({
|
|
|
2131
2507
|
message
|
|
2132
2508
|
}
|
|
2133
2509
|
) : null);
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2510
|
+
const defaultUserActions = !actions && Boolean(actionHandlers) && !isAssistant;
|
|
2511
|
+
return /* @__PURE__ */ jsxs6(
|
|
2512
|
+
ChatMessage,
|
|
2513
|
+
{
|
|
2514
|
+
className,
|
|
2515
|
+
dir,
|
|
2516
|
+
from: message.role,
|
|
2517
|
+
children: [
|
|
2518
|
+
showAttachments ? /* @__PURE__ */ jsx10(
|
|
2519
|
+
ChatAttachmentRows,
|
|
2520
|
+
{
|
|
2521
|
+
attachments: message.attachments || [],
|
|
2522
|
+
className: "mb-2",
|
|
2523
|
+
dir,
|
|
2524
|
+
resolveContent: attachmentContentResolver
|
|
2525
|
+
}
|
|
2526
|
+
) : null,
|
|
2527
|
+
shouldRenderContent ? /* @__PURE__ */ jsxs6(
|
|
2528
|
+
ChatMessageContent,
|
|
2529
|
+
{
|
|
2530
|
+
className: cn(isAssistant ? "bg-transparent p-0" : void 0, contentClassName),
|
|
2531
|
+
dir,
|
|
2532
|
+
from: message.role,
|
|
2533
|
+
children: [
|
|
2534
|
+
isAssistant ? renderAssistantContent ? renderAssistantContent({ isStreaming, message }) : /* @__PURE__ */ jsx10(
|
|
2535
|
+
Timeline,
|
|
2536
|
+
{
|
|
2537
|
+
getHitlActionState,
|
|
2538
|
+
isLoading: isStreaming,
|
|
2539
|
+
message,
|
|
2540
|
+
nodeActivityMode,
|
|
2541
|
+
onHitlAction,
|
|
2542
|
+
parts: message.parts
|
|
2543
|
+
}
|
|
2544
|
+
) : null,
|
|
2545
|
+
!isAssistant ? renderUserContent ? renderUserContent({ message }) : resolvedUserContent ? /* @__PURE__ */ jsx10(
|
|
2546
|
+
AgentChatUserMessageContent,
|
|
2547
|
+
{
|
|
2548
|
+
collapsedLines: userMessageCollapsedLines,
|
|
2549
|
+
collapseThreshold: userMessageCollapseThreshold,
|
|
2550
|
+
content: resolvedUserContent,
|
|
2551
|
+
showLessLabel: userMessageShowLessLabel,
|
|
2552
|
+
showMoreLabel: userMessageShowMoreLabel
|
|
2553
|
+
}
|
|
2554
|
+
) : null : null
|
|
2555
|
+
]
|
|
2556
|
+
}
|
|
2557
|
+
) : null,
|
|
2558
|
+
renderedActions ? defaultUserActions ? /* @__PURE__ */ jsx10(AgentChatUserMessageActionRow, { className: actionsClassName, children: renderedActions }) : /* @__PURE__ */ jsx10("div", { className: cn(dir === "rtl" ? "mt-1 h-9 w-full" : "mt-1 h-9 w-full -translate-x-2", actionsClassName), children: renderedActions }) : null
|
|
2559
|
+
]
|
|
2560
|
+
}
|
|
2561
|
+
);
|
|
2176
2562
|
}
|
|
2177
2563
|
|
|
2178
2564
|
// src/ui/agent-chat-context-status.tsx
|
|
@@ -2577,6 +2963,7 @@ export {
|
|
|
2577
2963
|
AgentChatToolPart,
|
|
2578
2964
|
AgentChatToolRow,
|
|
2579
2965
|
AgentChatToolSession,
|
|
2966
|
+
AgentChatUserMessageActionRow,
|
|
2580
2967
|
AgentChatUserMessageContent,
|
|
2581
2968
|
AgentResponseTimeline,
|
|
2582
2969
|
AskUserInteraction,
|
|
@@ -2594,6 +2981,7 @@ export {
|
|
|
2594
2981
|
BubbleGroup,
|
|
2595
2982
|
BubbleReactions,
|
|
2596
2983
|
ChatAttachment,
|
|
2984
|
+
ChatAttachmentRows,
|
|
2597
2985
|
ChatAttachments,
|
|
2598
2986
|
ChatMessage,
|
|
2599
2987
|
ChatMessageAction,
|
|
@@ -2607,6 +2995,7 @@ export {
|
|
|
2607
2995
|
ChatMessageAction as MessageAction,
|
|
2608
2996
|
ChatMessageActions as MessageActions,
|
|
2609
2997
|
ChatAttachment as MessageAttachment,
|
|
2998
|
+
ChatAttachmentRows as MessageAttachmentRows,
|
|
2610
2999
|
ChatAttachments as MessageAttachments,
|
|
2611
3000
|
MessageAvatar,
|
|
2612
3001
|
ChatMessageContent as MessageContent,
|
|
@@ -2622,6 +3011,7 @@ export {
|
|
|
2622
3011
|
bubbleVariants,
|
|
2623
3012
|
cn,
|
|
2624
3013
|
contextStatusMetrics,
|
|
3014
|
+
createAgentChatComposerFile,
|
|
2625
3015
|
hitlActionLabel,
|
|
2626
3016
|
hitlTitle,
|
|
2627
3017
|
isAgentChatSubagent,
|
|
@@ -2630,6 +3020,7 @@ export {
|
|
|
2630
3020
|
markerVariants,
|
|
2631
3021
|
partitionAgentChatToolTimeline,
|
|
2632
3022
|
resolvedHitlLabel,
|
|
3023
|
+
revokeAgentChatComposerFiles,
|
|
2633
3024
|
shouldCollapseUserMessage,
|
|
2634
3025
|
useAudioRecorder
|
|
2635
3026
|
};
|