@agents24/chat-react 0.5.5 → 0.5.6
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/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/index.cjs +57 -27
- package/dist/scaffold/index.cjs.map +1 -1
- package/dist/scaffold/index.js +68 -32
- package/dist/scaffold/index.js.map +1 -1
- package/dist/styles.css +14 -2
- 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 +3 -1
- package/dist/ui/composer-attachments.d.ts +11 -0
- package/dist/ui/index.cjs +647 -341
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +632 -326
- 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 +8 -9
- package/scaffold/src/components/chat/chat-shell.tsx +12 -9
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
302
|
"a24-chat-message group flex w-full flex-col gap-2",
|
|
549
|
-
isUser ?
|
|
303
|
+
isUser ? "a24-chat-message--user is-user max-w-full justify-end" : "a24-chat-message--assistant is-assistant max-w-full",
|
|
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,
|
|
@@ -603,60 +357,310 @@ function ChatMessageAction({
|
|
|
603
357
|
type,
|
|
604
358
|
...props
|
|
605
359
|
},
|
|
606
|
-
children,
|
|
607
|
-
|
|
608
|
-
"span",
|
|
609
|
-
{ className: "sr-only" },
|
|
610
|
-
label || tooltipLabel
|
|
611
|
-
)
|
|
360
|
+
children,
|
|
361
|
+
React.createElement(
|
|
362
|
+
"span",
|
|
363
|
+
{ className: "sr-only" },
|
|
364
|
+
label || tooltipLabel
|
|
365
|
+
)
|
|
366
|
+
);
|
|
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
|
|
612
601
|
);
|
|
613
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
|
}
|
|
@@ -666,12 +670,11 @@ function ChatAttachments({
|
|
|
666
670
|
...props
|
|
667
671
|
}) {
|
|
668
672
|
if (!children) return null;
|
|
669
|
-
return
|
|
673
|
+
return React.createElement(
|
|
670
674
|
AttachmentGroup,
|
|
671
675
|
{
|
|
672
676
|
className: cn(
|
|
673
|
-
|
|
674
|
-
"w-fit flex-wrap overflow-visible",
|
|
677
|
+
"a24-chat-attachments w-full flex-nowrap",
|
|
675
678
|
className
|
|
676
679
|
),
|
|
677
680
|
...props
|
|
@@ -679,8 +682,305 @@ function ChatAttachments({
|
|
|
679
682
|
children
|
|
680
683
|
);
|
|
681
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",
|
|
754
|
+
className,
|
|
755
|
+
disabled = false,
|
|
756
|
+
forceExpanded = false,
|
|
757
|
+
inputToolbarContent,
|
|
758
|
+
isRunning = false,
|
|
759
|
+
onAttachmentCountChange,
|
|
760
|
+
onStop,
|
|
761
|
+
onSubmit,
|
|
762
|
+
placeholder = "Message the agent",
|
|
763
|
+
textareaRef
|
|
764
|
+
}) {
|
|
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
|
+
}
|
|
782
|
+
},
|
|
783
|
+
[textareaRef]
|
|
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
|
+
] });
|
|
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"],
|
|
@@ -2099,6 +2399,7 @@ function AgentChatMessage({
|
|
|
2099
2399
|
additionalActions,
|
|
2100
2400
|
actions,
|
|
2101
2401
|
actionsClassName,
|
|
2402
|
+
attachmentContentResolver,
|
|
2102
2403
|
className,
|
|
2103
2404
|
contentClassName,
|
|
2104
2405
|
dir,
|
|
@@ -2132,14 +2433,15 @@ function AgentChatMessage({
|
|
|
2132
2433
|
}
|
|
2133
2434
|
) : null);
|
|
2134
2435
|
return /* @__PURE__ */ jsxs6(ChatMessage, { className, dir, from: message.role, children: [
|
|
2135
|
-
showAttachments ? /* @__PURE__ */ jsx10(
|
|
2136
|
-
|
|
2436
|
+
showAttachments ? /* @__PURE__ */ jsx10(
|
|
2437
|
+
ChatAttachmentRows,
|
|
2137
2438
|
{
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2439
|
+
attachments: message.attachments || [],
|
|
2440
|
+
className: "mb-2",
|
|
2441
|
+
dir,
|
|
2442
|
+
resolveContent: attachmentContentResolver
|
|
2443
|
+
}
|
|
2444
|
+
) : null,
|
|
2143
2445
|
shouldRenderContent ? /* @__PURE__ */ jsxs6(
|
|
2144
2446
|
ChatMessageContent,
|
|
2145
2447
|
{
|
|
@@ -2594,6 +2896,7 @@ export {
|
|
|
2594
2896
|
BubbleGroup,
|
|
2595
2897
|
BubbleReactions,
|
|
2596
2898
|
ChatAttachment,
|
|
2899
|
+
ChatAttachmentRows,
|
|
2597
2900
|
ChatAttachments,
|
|
2598
2901
|
ChatMessage,
|
|
2599
2902
|
ChatMessageAction,
|
|
@@ -2607,6 +2910,7 @@ export {
|
|
|
2607
2910
|
ChatMessageAction as MessageAction,
|
|
2608
2911
|
ChatMessageActions as MessageActions,
|
|
2609
2912
|
ChatAttachment as MessageAttachment,
|
|
2913
|
+
ChatAttachmentRows as MessageAttachmentRows,
|
|
2610
2914
|
ChatAttachments as MessageAttachments,
|
|
2611
2915
|
MessageAvatar,
|
|
2612
2916
|
ChatMessageContent as MessageContent,
|
|
@@ -2622,6 +2926,7 @@ export {
|
|
|
2622
2926
|
bubbleVariants,
|
|
2623
2927
|
cn,
|
|
2624
2928
|
contextStatusMetrics,
|
|
2929
|
+
createAgentChatComposerFile,
|
|
2625
2930
|
hitlActionLabel,
|
|
2626
2931
|
hitlTitle,
|
|
2627
2932
|
isAgentChatSubagent,
|
|
@@ -2630,6 +2935,7 @@ export {
|
|
|
2630
2935
|
markerVariants,
|
|
2631
2936
|
partitionAgentChatToolTimeline,
|
|
2632
2937
|
resolvedHitlLabel,
|
|
2938
|
+
revokeAgentChatComposerFiles,
|
|
2633
2939
|
shouldCollapseUserMessage,
|
|
2634
2940
|
useAudioRecorder
|
|
2635
2941
|
};
|