@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.cjs
CHANGED
|
@@ -61,6 +61,7 @@ __export(ui_exports, {
|
|
|
61
61
|
BubbleGroup: () => BubbleGroup,
|
|
62
62
|
BubbleReactions: () => BubbleReactions,
|
|
63
63
|
ChatAttachment: () => ChatAttachment,
|
|
64
|
+
ChatAttachmentRows: () => ChatAttachmentRows,
|
|
64
65
|
ChatAttachments: () => ChatAttachments,
|
|
65
66
|
ChatMessage: () => ChatMessage,
|
|
66
67
|
ChatMessageAction: () => ChatMessageAction,
|
|
@@ -74,6 +75,7 @@ __export(ui_exports, {
|
|
|
74
75
|
MessageAction: () => ChatMessageAction,
|
|
75
76
|
MessageActions: () => ChatMessageActions,
|
|
76
77
|
MessageAttachment: () => ChatAttachment,
|
|
78
|
+
MessageAttachmentRows: () => ChatAttachmentRows,
|
|
77
79
|
MessageAttachments: () => ChatAttachments,
|
|
78
80
|
MessageAvatar: () => MessageAvatar,
|
|
79
81
|
MessageContent: () => ChatMessageContent,
|
|
@@ -89,6 +91,7 @@ __export(ui_exports, {
|
|
|
89
91
|
bubbleVariants: () => bubbleVariants,
|
|
90
92
|
cn: () => cn,
|
|
91
93
|
contextStatusMetrics: () => contextStatusMetrics,
|
|
94
|
+
createAgentChatComposerFile: () => createAgentChatComposerFile,
|
|
92
95
|
hitlActionLabel: () => hitlActionLabel,
|
|
93
96
|
hitlTitle: () => hitlTitle,
|
|
94
97
|
isAgentChatSubagent: () => isAgentChatSubagent,
|
|
@@ -97,15 +100,25 @@ __export(ui_exports, {
|
|
|
97
100
|
markerVariants: () => markerVariants,
|
|
98
101
|
partitionAgentChatToolTimeline: () => partitionAgentChatToolTimeline,
|
|
99
102
|
resolvedHitlLabel: () => resolvedHitlLabel,
|
|
103
|
+
revokeAgentChatComposerFiles: () => revokeAgentChatComposerFiles,
|
|
100
104
|
shouldCollapseUserMessage: () => shouldCollapseUserMessage,
|
|
101
105
|
useAudioRecorder: () => useAudioRecorder
|
|
102
106
|
});
|
|
103
107
|
module.exports = __toCommonJS(ui_exports);
|
|
104
108
|
|
|
105
109
|
// src/ui/agent-chat-composer.tsx
|
|
110
|
+
var React2 = __toESM(require("react"), 1);
|
|
111
|
+
var import_lucide_react2 = require("lucide-react");
|
|
112
|
+
|
|
113
|
+
// src/ui/adapters.ts
|
|
106
114
|
var React = __toESM(require("react"), 1);
|
|
115
|
+
var import_file_icons = require("@untitledui/file-icons");
|
|
107
116
|
var import_lucide_react = require("lucide-react");
|
|
108
117
|
|
|
118
|
+
// src/ui/attachment.tsx
|
|
119
|
+
var import_class_variance_authority = require("class-variance-authority");
|
|
120
|
+
var import_react_slot = require("@radix-ui/react-slot");
|
|
121
|
+
|
|
109
122
|
// src/ui/utils.ts
|
|
110
123
|
var import_clsx = require("clsx");
|
|
111
124
|
var import_tailwind_merge = require("tailwind-merge");
|
|
@@ -113,266 +126,8 @@ function cn(...values) {
|
|
|
113
126
|
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(values));
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
// src/ui/agent-chat-composer.tsx
|
|
117
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
118
|
-
var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
119
|
-
function createComposerFile(file) {
|
|
120
|
-
return {
|
|
121
|
-
id: createId(),
|
|
122
|
-
type: "file",
|
|
123
|
-
url: URL.createObjectURL(file),
|
|
124
|
-
filename: file.name || "attachment",
|
|
125
|
-
mediaType: file.type || "application/octet-stream",
|
|
126
|
-
source: file
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
function revokeComposerFiles(files) {
|
|
130
|
-
files.forEach((file) => {
|
|
131
|
-
try {
|
|
132
|
-
URL.revokeObjectURL(file.url);
|
|
133
|
-
} catch {
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
function formatFileCount(files) {
|
|
138
|
-
if (files.length === 1) return files[0].filename;
|
|
139
|
-
return `${files.length} files`;
|
|
140
|
-
}
|
|
141
|
-
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";
|
|
142
|
-
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";
|
|
143
|
-
function AgentChatComposer({
|
|
144
|
-
accept,
|
|
145
|
-
allowAttachments = true,
|
|
146
|
-
attachmentLayout = "flow",
|
|
147
|
-
className,
|
|
148
|
-
disabled = false,
|
|
149
|
-
forceExpanded = false,
|
|
150
|
-
inputToolbarContent,
|
|
151
|
-
isRunning = false,
|
|
152
|
-
onAttachmentCountChange,
|
|
153
|
-
onStop,
|
|
154
|
-
onSubmit,
|
|
155
|
-
placeholder = "Message the agent",
|
|
156
|
-
textareaRef
|
|
157
|
-
}) {
|
|
158
|
-
const fileInputRef = React.useRef(null);
|
|
159
|
-
const filesRef = React.useRef([]);
|
|
160
|
-
const localTextareaRef = React.useRef(null);
|
|
161
|
-
const [text, setText] = React.useState("");
|
|
162
|
-
const [files, setFiles] = React.useState([]);
|
|
163
|
-
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
164
|
-
const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
|
|
165
|
-
const setTextareaRef = React.useCallback(
|
|
166
|
-
(node) => {
|
|
167
|
-
localTextareaRef.current = node;
|
|
168
|
-
if (typeof textareaRef === "function") {
|
|
169
|
-
textareaRef(node);
|
|
170
|
-
} else if (textareaRef) {
|
|
171
|
-
textareaRef.current = node;
|
|
172
|
-
}
|
|
173
|
-
},
|
|
174
|
-
[textareaRef]
|
|
175
|
-
);
|
|
176
|
-
React.useEffect(() => {
|
|
177
|
-
filesRef.current = files;
|
|
178
|
-
onAttachmentCountChange?.(files.length);
|
|
179
|
-
}, [files, onAttachmentCountChange]);
|
|
180
|
-
React.useEffect(() => () => revokeComposerFiles(filesRef.current), []);
|
|
181
|
-
React.useLayoutEffect(() => {
|
|
182
|
-
const textarea = localTextareaRef.current;
|
|
183
|
-
if (!textarea) return;
|
|
184
|
-
if (isExpanded) {
|
|
185
|
-
textarea.style.height = "auto";
|
|
186
|
-
const scrollHeight = textarea.scrollHeight;
|
|
187
|
-
textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
|
|
188
|
-
textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
textarea.style.height = "";
|
|
192
|
-
textarea.style.overflowY = "hidden";
|
|
193
|
-
}, [isExpanded, text]);
|
|
194
|
-
const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
|
|
195
|
-
const handleFilesChange = (event) => {
|
|
196
|
-
const selected = Array.from(event.target.files || []).map(createComposerFile);
|
|
197
|
-
if (selected.length > 0) {
|
|
198
|
-
setFiles((current) => [...current, ...selected]);
|
|
199
|
-
}
|
|
200
|
-
event.currentTarget.value = "";
|
|
201
|
-
};
|
|
202
|
-
const removeFile = (fileId) => {
|
|
203
|
-
setFiles((current) => {
|
|
204
|
-
const target = current.find((file) => file.id === fileId);
|
|
205
|
-
if (target) revokeComposerFiles([target]);
|
|
206
|
-
return current.filter((file) => file.id !== fileId);
|
|
207
|
-
});
|
|
208
|
-
};
|
|
209
|
-
const submit = async () => {
|
|
210
|
-
if (!canSubmit) return;
|
|
211
|
-
const submittedText = text.trim();
|
|
212
|
-
const submittedFiles = files;
|
|
213
|
-
setIsSubmitting(true);
|
|
214
|
-
try {
|
|
215
|
-
await onSubmit({ text: submittedText, files: submittedFiles });
|
|
216
|
-
setText("");
|
|
217
|
-
setFiles([]);
|
|
218
|
-
revokeComposerFiles(submittedFiles);
|
|
219
|
-
} catch {
|
|
220
|
-
} finally {
|
|
221
|
-
setIsSubmitting(false);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
const handleKeyDown = (event) => {
|
|
225
|
-
if (event.key === "Enter" && !event.shiftKey) {
|
|
226
|
-
event.preventDefault();
|
|
227
|
-
void submit();
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
|
|
231
|
-
files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
232
|
-
"div",
|
|
233
|
-
{
|
|
234
|
-
"data-testid": "agent-chat-composer-attachments",
|
|
235
|
-
"data-layout": attachmentLayout,
|
|
236
|
-
className: cn(
|
|
237
|
-
"a24-composer__attachments flex min-w-0 gap-2 py-1",
|
|
238
|
-
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"
|
|
239
|
-
),
|
|
240
|
-
children: files.map((file) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
241
|
-
"div",
|
|
242
|
-
{
|
|
243
|
-
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",
|
|
244
|
-
children: [
|
|
245
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex size-5 shrink-0 items-center justify-center rounded bg-background text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Paperclip, { className: "size-3" }) }),
|
|
246
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "min-w-0 flex-1 truncate", children: file.filename }),
|
|
247
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
248
|
-
"button",
|
|
249
|
-
{
|
|
250
|
-
"aria-label": `Remove ${file.filename}`,
|
|
251
|
-
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",
|
|
252
|
-
onClick: () => removeFile(file.id),
|
|
253
|
-
type: "button",
|
|
254
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "size-3" })
|
|
255
|
-
}
|
|
256
|
-
)
|
|
257
|
-
]
|
|
258
|
-
},
|
|
259
|
-
file.id
|
|
260
|
-
))
|
|
261
|
-
}
|
|
262
|
-
) : null,
|
|
263
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
264
|
-
"div",
|
|
265
|
-
{
|
|
266
|
-
"data-expanded": isExpanded ? "" : void 0,
|
|
267
|
-
className: cn(
|
|
268
|
-
"a24-composer__surface relative w-full border border-input bg-background shadow-sm",
|
|
269
|
-
isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
|
|
270
|
-
),
|
|
271
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
272
|
-
"div",
|
|
273
|
-
{
|
|
274
|
-
className: "a24-composer__grid w-full",
|
|
275
|
-
style: {
|
|
276
|
-
alignItems: "center",
|
|
277
|
-
display: "grid",
|
|
278
|
-
gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
|
|
279
|
-
gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
|
|
280
|
-
gap: isExpanded ? "3px 10px" : "8px"
|
|
281
|
-
},
|
|
282
|
-
children: [
|
|
283
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
|
|
284
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
285
|
-
"input",
|
|
286
|
-
{
|
|
287
|
-
ref: fileInputRef,
|
|
288
|
-
accept,
|
|
289
|
-
"aria-label": "Attach files",
|
|
290
|
-
className: "hidden",
|
|
291
|
-
multiple: true,
|
|
292
|
-
name: "attachments",
|
|
293
|
-
onChange: handleFilesChange,
|
|
294
|
-
type: "file"
|
|
295
|
-
}
|
|
296
|
-
),
|
|
297
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
298
|
-
"button",
|
|
299
|
-
{
|
|
300
|
-
"aria-label": "Attach files",
|
|
301
|
-
className: cn("a24-composer__attach", pillButtonClass),
|
|
302
|
-
disabled: disabled || isSubmitting || isRunning || !allowAttachments,
|
|
303
|
-
onClick: () => fileInputRef.current?.click(),
|
|
304
|
-
title: "Attach files",
|
|
305
|
-
type: "button",
|
|
306
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Plus, { className: "size-4" })
|
|
307
|
-
}
|
|
308
|
-
)
|
|
309
|
-
] }),
|
|
310
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
311
|
-
"textarea",
|
|
312
|
-
{
|
|
313
|
-
ref: setTextareaRef,
|
|
314
|
-
"aria-label": "Message",
|
|
315
|
-
autoComplete: "off",
|
|
316
|
-
className: cn(
|
|
317
|
-
"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",
|
|
318
|
-
isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
|
|
319
|
-
),
|
|
320
|
-
disabled: disabled || isSubmitting || isRunning,
|
|
321
|
-
name: "message",
|
|
322
|
-
onChange: (event) => setText(event.target.value),
|
|
323
|
-
onKeyDown: handleKeyDown,
|
|
324
|
-
placeholder,
|
|
325
|
-
rows: isExpanded ? 2 : 1,
|
|
326
|
-
value: text
|
|
327
|
-
}
|
|
328
|
-
) }),
|
|
329
|
-
isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
|
|
330
|
-
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
|
|
331
|
-
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
|
|
332
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
333
|
-
"button",
|
|
334
|
-
{
|
|
335
|
-
"aria-label": "Stop generating",
|
|
336
|
-
className: cn("a24-composer__submit", submitButtonClass),
|
|
337
|
-
disabled: disabled || !onStop,
|
|
338
|
-
onClick: onStop,
|
|
339
|
-
title: "Stop",
|
|
340
|
-
type: "button",
|
|
341
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Square, { className: "size-3.5 fill-current" })
|
|
342
|
-
}
|
|
343
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
344
|
-
"button",
|
|
345
|
-
{
|
|
346
|
-
"aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
|
|
347
|
-
className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
|
|
348
|
-
disabled: !canSubmit,
|
|
349
|
-
onClick: () => void submit(),
|
|
350
|
-
title: "Send",
|
|
351
|
-
type: "button",
|
|
352
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
|
|
353
|
-
}
|
|
354
|
-
) })
|
|
355
|
-
]
|
|
356
|
-
}
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
),
|
|
360
|
-
!isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
|
|
361
|
-
] });
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// src/ui/agent-chat-actions.tsx
|
|
365
|
-
var React3 = __toESM(require("react"), 1);
|
|
366
|
-
var import_lucide_react2 = require("lucide-react");
|
|
367
|
-
var import_radix_ui = require("radix-ui");
|
|
368
|
-
|
|
369
|
-
// src/ui/adapters.ts
|
|
370
|
-
var React2 = __toESM(require("react"), 1);
|
|
371
|
-
|
|
372
129
|
// src/ui/attachment.tsx
|
|
373
|
-
var
|
|
374
|
-
var import_react_slot = require("@radix-ui/react-slot");
|
|
375
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
130
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
376
131
|
var attachmentVariants = (0, import_class_variance_authority.cva)(
|
|
377
132
|
"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",
|
|
378
133
|
{
|
|
@@ -396,7 +151,7 @@ function Attachment({
|
|
|
396
151
|
orientation = "horizontal",
|
|
397
152
|
...props
|
|
398
153
|
}) {
|
|
399
|
-
return /* @__PURE__ */ (0,
|
|
154
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
400
155
|
"div",
|
|
401
156
|
{
|
|
402
157
|
"data-slot": "attachment",
|
|
@@ -409,12 +164,12 @@ function Attachment({
|
|
|
409
164
|
);
|
|
410
165
|
}
|
|
411
166
|
var attachmentMediaVariants = (0, import_class_variance_authority.cva)(
|
|
412
|
-
"relative flex aspect-square w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg
|
|
167
|
+
"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",
|
|
413
168
|
{
|
|
414
169
|
variants: {
|
|
415
170
|
variant: {
|
|
416
171
|
icon: "",
|
|
417
|
-
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"
|
|
172
|
+
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"
|
|
418
173
|
}
|
|
419
174
|
},
|
|
420
175
|
defaultVariants: {
|
|
@@ -427,7 +182,7 @@ function AttachmentMedia({
|
|
|
427
182
|
variant = "icon",
|
|
428
183
|
...props
|
|
429
184
|
}) {
|
|
430
|
-
return /* @__PURE__ */ (0,
|
|
185
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
431
186
|
"div",
|
|
432
187
|
{
|
|
433
188
|
"data-slot": "attachment-media",
|
|
@@ -441,7 +196,7 @@ function AttachmentContent({
|
|
|
441
196
|
className,
|
|
442
197
|
...props
|
|
443
198
|
}) {
|
|
444
|
-
return /* @__PURE__ */ (0,
|
|
199
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
445
200
|
"div",
|
|
446
201
|
{
|
|
447
202
|
"data-slot": "attachment-content",
|
|
@@ -457,7 +212,7 @@ function AttachmentTitle({
|
|
|
457
212
|
className,
|
|
458
213
|
...props
|
|
459
214
|
}) {
|
|
460
|
-
return /* @__PURE__ */ (0,
|
|
215
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
461
216
|
"span",
|
|
462
217
|
{
|
|
463
218
|
"data-slot": "attachment-title",
|
|
@@ -473,7 +228,7 @@ function AttachmentDescription({
|
|
|
473
228
|
className,
|
|
474
229
|
...props
|
|
475
230
|
}) {
|
|
476
|
-
return /* @__PURE__ */ (0,
|
|
231
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
477
232
|
"span",
|
|
478
233
|
{
|
|
479
234
|
"data-slot": "attachment-description",
|
|
@@ -490,7 +245,7 @@ function AttachmentActions({
|
|
|
490
245
|
className,
|
|
491
246
|
...props
|
|
492
247
|
}) {
|
|
493
|
-
return /* @__PURE__ */ (0,
|
|
248
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
494
249
|
"div",
|
|
495
250
|
{
|
|
496
251
|
"data-slot": "attachment-actions",
|
|
@@ -507,7 +262,7 @@ function AttachmentAction({
|
|
|
507
262
|
type = "button",
|
|
508
263
|
...props
|
|
509
264
|
}) {
|
|
510
|
-
return /* @__PURE__ */ (0,
|
|
265
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
511
266
|
"button",
|
|
512
267
|
{
|
|
513
268
|
"data-slot": "attachment-action",
|
|
@@ -527,7 +282,7 @@ function AttachmentTrigger({
|
|
|
527
282
|
...props
|
|
528
283
|
}) {
|
|
529
284
|
const Comp = asChild ? import_react_slot.Slot : "button";
|
|
530
|
-
return /* @__PURE__ */ (0,
|
|
285
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
531
286
|
Comp,
|
|
532
287
|
{
|
|
533
288
|
"data-slot": "attachment-trigger",
|
|
@@ -538,7 +293,7 @@ function AttachmentTrigger({
|
|
|
538
293
|
);
|
|
539
294
|
}
|
|
540
295
|
function AttachmentGroup({ className, ...props }) {
|
|
541
|
-
return /* @__PURE__ */ (0,
|
|
296
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
542
297
|
"div",
|
|
543
298
|
{
|
|
544
299
|
"data-slot": "attachment-group",
|
|
@@ -552,9 +307,9 @@ function AttachmentGroup({ className, ...props }) {
|
|
|
552
307
|
}
|
|
553
308
|
|
|
554
309
|
// src/ui/message.tsx
|
|
555
|
-
var
|
|
310
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
556
311
|
function MessageGroup({ className, ...props }) {
|
|
557
|
-
return /* @__PURE__ */ (0,
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
558
313
|
"div",
|
|
559
314
|
{
|
|
560
315
|
"data-slot": "message-group",
|
|
@@ -568,7 +323,7 @@ function Message({
|
|
|
568
323
|
align = "start",
|
|
569
324
|
...props
|
|
570
325
|
}) {
|
|
571
|
-
return /* @__PURE__ */ (0,
|
|
326
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
572
327
|
"div",
|
|
573
328
|
{
|
|
574
329
|
"data-slot": "message",
|
|
@@ -582,7 +337,7 @@ function Message({
|
|
|
582
337
|
);
|
|
583
338
|
}
|
|
584
339
|
function MessageAvatar({ className, ...props }) {
|
|
585
|
-
return /* @__PURE__ */ (0,
|
|
340
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
586
341
|
"div",
|
|
587
342
|
{
|
|
588
343
|
"data-slot": "message-avatar",
|
|
@@ -595,7 +350,7 @@ function MessageAvatar({ className, ...props }) {
|
|
|
595
350
|
);
|
|
596
351
|
}
|
|
597
352
|
function MessageContent({ className, ...props }) {
|
|
598
|
-
return /* @__PURE__ */ (0,
|
|
353
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
599
354
|
"div",
|
|
600
355
|
{
|
|
601
356
|
"data-slot": "message-content",
|
|
@@ -608,7 +363,7 @@ function MessageContent({ className, ...props }) {
|
|
|
608
363
|
);
|
|
609
364
|
}
|
|
610
365
|
function MessageHeader({ className, ...props }) {
|
|
611
|
-
return /* @__PURE__ */ (0,
|
|
366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
612
367
|
"div",
|
|
613
368
|
{
|
|
614
369
|
"data-slot": "message-header",
|
|
@@ -621,7 +376,7 @@ function MessageHeader({ className, ...props }) {
|
|
|
621
376
|
);
|
|
622
377
|
}
|
|
623
378
|
function MessageFooter({ className, ...props }) {
|
|
624
|
-
return /* @__PURE__ */ (0,
|
|
379
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
625
380
|
"div",
|
|
626
381
|
{
|
|
627
382
|
"data-slot": "message-footer",
|
|
@@ -635,23 +390,22 @@ function MessageFooter({ className, ...props }) {
|
|
|
635
390
|
}
|
|
636
391
|
|
|
637
392
|
// src/ui/adapters.ts
|
|
638
|
-
var ChatMessageRoleContext =
|
|
393
|
+
var ChatMessageRoleContext = React.createContext("assistant");
|
|
639
394
|
function ChatMessage(input) {
|
|
640
395
|
const { className, from, align, ...props } = input;
|
|
641
396
|
const isUser = from === "user";
|
|
642
|
-
const isRtl = props.dir === "rtl";
|
|
643
397
|
const resolvedAlign = align || "start";
|
|
644
398
|
const messageProps = Object.assign({}, props);
|
|
645
399
|
messageProps.align = resolvedAlign;
|
|
646
400
|
messageProps.className = cn(
|
|
647
401
|
"a24-chat-message group flex w-full flex-col gap-2",
|
|
648
|
-
isUser ?
|
|
402
|
+
isUser ? "a24-chat-message--user is-user max-w-full justify-end" : "a24-chat-message--assistant is-assistant max-w-full",
|
|
649
403
|
className
|
|
650
404
|
);
|
|
651
|
-
return
|
|
405
|
+
return React.createElement(
|
|
652
406
|
ChatMessageRoleContext.Provider,
|
|
653
407
|
{ value: from },
|
|
654
|
-
|
|
408
|
+
React.createElement(Message, messageProps)
|
|
655
409
|
);
|
|
656
410
|
}
|
|
657
411
|
function ChatMessageContent({
|
|
@@ -659,9 +413,9 @@ function ChatMessageContent({
|
|
|
659
413
|
from,
|
|
660
414
|
...props
|
|
661
415
|
}) {
|
|
662
|
-
const inheritedRole =
|
|
416
|
+
const inheritedRole = React.useContext(ChatMessageRoleContext);
|
|
663
417
|
const isUser = (from ?? inheritedRole) === "user";
|
|
664
|
-
return
|
|
418
|
+
return React.createElement("div", {
|
|
665
419
|
className: cn(
|
|
666
420
|
"a24-chat-message-content flex max-w-full min-w-0 flex-col gap-2 text-sm",
|
|
667
421
|
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",
|
|
@@ -675,7 +429,7 @@ function ChatMessageActions({
|
|
|
675
429
|
className,
|
|
676
430
|
...props
|
|
677
431
|
}) {
|
|
678
|
-
return
|
|
432
|
+
return React.createElement("div", {
|
|
679
433
|
className: cn("flex items-center gap-1", className),
|
|
680
434
|
"data-slot": "message-actions",
|
|
681
435
|
...props
|
|
@@ -690,7 +444,7 @@ function ChatMessageAction({
|
|
|
690
444
|
...props
|
|
691
445
|
}) {
|
|
692
446
|
const tooltipLabel = typeof tooltip === "string" ? tooltip : void 0;
|
|
693
|
-
return
|
|
447
|
+
return React.createElement(
|
|
694
448
|
"button",
|
|
695
449
|
{
|
|
696
450
|
"aria-label": label || tooltipLabel,
|
|
@@ -702,60 +456,310 @@ function ChatMessageAction({
|
|
|
702
456
|
type,
|
|
703
457
|
...props
|
|
704
458
|
},
|
|
705
|
-
children,
|
|
706
|
-
|
|
707
|
-
"span",
|
|
708
|
-
{ className: "sr-only" },
|
|
709
|
-
label || tooltipLabel
|
|
710
|
-
)
|
|
459
|
+
children,
|
|
460
|
+
React.createElement(
|
|
461
|
+
"span",
|
|
462
|
+
{ className: "sr-only" },
|
|
463
|
+
label || tooltipLabel
|
|
464
|
+
)
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
var resolvedContentCache = /* @__PURE__ */ new WeakMap();
|
|
468
|
+
var CONTENT_CACHE_EXPIRY_BUFFER_MS = 15e3;
|
|
469
|
+
var CONTENT_CACHE_MAX_ENTRIES = 100;
|
|
470
|
+
function attachmentCacheKey(data) {
|
|
471
|
+
const id = typeof data.id === "string" ? data.id.trim() : "";
|
|
472
|
+
return id || null;
|
|
473
|
+
}
|
|
474
|
+
function cachedAttachmentContent(resolver, data) {
|
|
475
|
+
const key = attachmentCacheKey(data);
|
|
476
|
+
if (!key) return null;
|
|
477
|
+
const cached = resolvedContentCache.get(resolver)?.get(key);
|
|
478
|
+
if (!cached) return null;
|
|
479
|
+
if (!cached.validUntil) return cached;
|
|
480
|
+
const validUntil = Date.parse(cached.validUntil);
|
|
481
|
+
if (Number.isFinite(validUntil) && validUntil - CONTENT_CACHE_EXPIRY_BUFFER_MS > Date.now()) {
|
|
482
|
+
return cached;
|
|
483
|
+
}
|
|
484
|
+
resolvedContentCache.get(resolver)?.delete(key);
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
function cacheAttachmentContent(resolver, data, result) {
|
|
488
|
+
const key = attachmentCacheKey(data);
|
|
489
|
+
if (!key) return;
|
|
490
|
+
const cache = resolvedContentCache.get(resolver) || /* @__PURE__ */ new Map();
|
|
491
|
+
cache.delete(key);
|
|
492
|
+
cache.set(key, result);
|
|
493
|
+
while (cache.size > CONTENT_CACHE_MAX_ENTRIES) {
|
|
494
|
+
const oldestKey = cache.keys().next().value;
|
|
495
|
+
if (typeof oldestKey !== "string") break;
|
|
496
|
+
cache.delete(oldestKey);
|
|
497
|
+
}
|
|
498
|
+
resolvedContentCache.set(resolver, cache);
|
|
499
|
+
}
|
|
500
|
+
function invalidateAttachmentContent(resolver, data) {
|
|
501
|
+
const key = attachmentCacheKey(data);
|
|
502
|
+
if (key) resolvedContentCache.get(resolver)?.delete(key);
|
|
503
|
+
}
|
|
504
|
+
function attachmentMediaType(data) {
|
|
505
|
+
return data.mediaType || data.contentType || String(data.type || "application/octet-stream");
|
|
506
|
+
}
|
|
507
|
+
function attachmentFilename(data) {
|
|
508
|
+
return data.filename || data.name || "Attachment";
|
|
509
|
+
}
|
|
510
|
+
function attachmentExtension(filename) {
|
|
511
|
+
const extension = filename.split(".").pop();
|
|
512
|
+
return extension && extension !== filename ? extension.toLowerCase() : "";
|
|
513
|
+
}
|
|
514
|
+
function resolveAttachmentVisual(data) {
|
|
515
|
+
const mediaType = attachmentMediaType(data).toLowerCase();
|
|
516
|
+
const extension = attachmentExtension(attachmentFilename(data));
|
|
517
|
+
if (mediaType.startsWith("image/") || ["avif", "gif", "heic", "jpeg", "jpg", "png", "svg", "webp"].includes(extension)) {
|
|
518
|
+
return { fileIconType: extension || "image", kind: "image" };
|
|
519
|
+
}
|
|
520
|
+
if (mediaType === "application/pdf" || extension === "pdf") {
|
|
521
|
+
return { fileIconType: "pdf", kind: "pdf" };
|
|
522
|
+
}
|
|
523
|
+
if (mediaType.includes("spreadsheet") || mediaType.includes("csv") || ["csv", "numbers", "ods", "xls", "xlsm", "xlsx"].includes(extension)) {
|
|
524
|
+
if (["csv", "xls", "xlsx"].includes(extension)) {
|
|
525
|
+
return { fileIconType: extension, kind: "spreadsheet" };
|
|
526
|
+
}
|
|
527
|
+
return { fileIconType: "spreadsheets", kind: "spreadsheet" };
|
|
528
|
+
}
|
|
529
|
+
if (mediaType.includes("presentation") || ["key", "odp", "ppt", "pptx"].includes(extension)) {
|
|
530
|
+
return { fileIconType: extension === "ppt" ? "ppt" : "pptx", kind: "presentation" };
|
|
531
|
+
}
|
|
532
|
+
if (mediaType.includes("word") || mediaType.includes("rtf") || ["doc", "docx", "odt", "rtf"].includes(extension)) {
|
|
533
|
+
if (extension === "doc" || extension === "docx") {
|
|
534
|
+
return { fileIconType: extension, kind: "document" };
|
|
535
|
+
}
|
|
536
|
+
return { fileIconType: "document", kind: "document" };
|
|
537
|
+
}
|
|
538
|
+
if (mediaType.startsWith("audio/") || ["aac", "flac", "m4a", "mp3", "ogg", "wav"].includes(extension)) {
|
|
539
|
+
if (extension === "mp3" || extension === "wav") {
|
|
540
|
+
return { fileIconType: extension, kind: "audio" };
|
|
541
|
+
}
|
|
542
|
+
return { fileIconType: "audio", kind: "audio" };
|
|
543
|
+
}
|
|
544
|
+
if (mediaType.startsWith("video/") || ["avi", "m4v", "mkv", "mov", "mp4", "webm"].includes(extension)) {
|
|
545
|
+
if (["avi", "mkv", "mp4", "mpeg"].includes(extension)) {
|
|
546
|
+
return { fileIconType: extension, kind: "video" };
|
|
547
|
+
}
|
|
548
|
+
return { fileIconType: "video", kind: "video" };
|
|
549
|
+
}
|
|
550
|
+
if (mediaType.includes("zip") || mediaType.includes("compressed") || ["7z", "bz2", "gz", "rar", "tar", "tgz", "zip"].includes(extension)) {
|
|
551
|
+
return { fileIconType: extension === "rar" ? "rar" : "zip", kind: "archive" };
|
|
552
|
+
}
|
|
553
|
+
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)) {
|
|
554
|
+
if (["css", "html", "java", "js", "json", "sql", "xml"].includes(extension)) {
|
|
555
|
+
return { fileIconType: extension, kind: "code" };
|
|
556
|
+
}
|
|
557
|
+
return { fileIconType: "code", kind: "code" };
|
|
558
|
+
}
|
|
559
|
+
if (mediaType.startsWith("text/") || ["md", "markdown", "txt"].includes(extension)) {
|
|
560
|
+
return { fileIconType: "txt", kind: "file" };
|
|
561
|
+
}
|
|
562
|
+
return { fileIconType: "empty", kind: "file" };
|
|
563
|
+
}
|
|
564
|
+
function solidFileIconBackground(fileIconType, kind) {
|
|
565
|
+
if (["txt", "zip", "rar"].includes(fileIconType)) return "#344054";
|
|
566
|
+
if (kind === "pdf") return "#D92D20";
|
|
567
|
+
if (kind === "spreadsheet") return "#079455";
|
|
568
|
+
if (kind === "document" || kind === "video") return "#155EEF";
|
|
569
|
+
if (kind === "presentation") return "#E62E05";
|
|
570
|
+
if (kind === "audio") return "#DD2590";
|
|
571
|
+
if (kind === "code") return "#444CE7";
|
|
572
|
+
return "#7F56D9";
|
|
573
|
+
}
|
|
574
|
+
function isImageAttachment(data) {
|
|
575
|
+
return resolveAttachmentVisual(data).kind === "image";
|
|
576
|
+
}
|
|
577
|
+
function attachmentRenderKey(data, index, rowKind) {
|
|
578
|
+
const id = typeof data.id === "string" ? data.id.trim() : "";
|
|
579
|
+
const identity = id || `${attachmentFilename(data)}:${attachmentMediaType(data)}`;
|
|
580
|
+
return `${rowKind}:${identity}:${index}`;
|
|
581
|
+
}
|
|
582
|
+
function ChatImageAttachment({
|
|
583
|
+
className,
|
|
584
|
+
data,
|
|
585
|
+
onRemove,
|
|
586
|
+
resolveContent,
|
|
587
|
+
...props
|
|
588
|
+
}) {
|
|
589
|
+
const filename = attachmentFilename(data);
|
|
590
|
+
const initialUrl = typeof data.url === "string" ? data.url.trim() : "";
|
|
591
|
+
const contentStatus = String(data.contentStatus || data.content_status || "available");
|
|
592
|
+
const attachmentKey = attachmentCacheKey(data);
|
|
593
|
+
const dataRef = React.useRef(data);
|
|
594
|
+
dataRef.current = data;
|
|
595
|
+
const rootRef = React.useRef(null);
|
|
596
|
+
const retryCountRef = React.useRef(0);
|
|
597
|
+
const [isNearViewport, setIsNearViewport] = React.useState(Boolean(initialUrl));
|
|
598
|
+
const [sourceUrl, setSourceUrl] = React.useState(initialUrl);
|
|
599
|
+
const [status, setStatus] = React.useState(
|
|
600
|
+
initialUrl ? "loading" : contentStatus === "expired" || !resolveContent ? "error" : "idle"
|
|
601
|
+
);
|
|
602
|
+
const [resolutionAttempt, setResolutionAttempt] = React.useState(0);
|
|
603
|
+
React.useEffect(() => {
|
|
604
|
+
if (initialUrl && resolutionAttempt === 0 || contentStatus === "expired" || !resolveContent) return;
|
|
605
|
+
const node = rootRef.current;
|
|
606
|
+
if (!node || typeof IntersectionObserver === "undefined") {
|
|
607
|
+
setIsNearViewport(true);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
const observer = new IntersectionObserver(
|
|
611
|
+
(entries) => {
|
|
612
|
+
if (entries.some((entry) => entry.isIntersecting)) {
|
|
613
|
+
setIsNearViewport(true);
|
|
614
|
+
observer.disconnect();
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
{ rootMargin: "240px" }
|
|
618
|
+
);
|
|
619
|
+
observer.observe(node);
|
|
620
|
+
return () => observer.disconnect();
|
|
621
|
+
}, [contentStatus, initialUrl, resolutionAttempt, resolveContent]);
|
|
622
|
+
React.useEffect(() => {
|
|
623
|
+
if (initialUrl && resolutionAttempt === 0) {
|
|
624
|
+
setSourceUrl(initialUrl);
|
|
625
|
+
setStatus("loading");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
if (!isNearViewport || !resolveContent || contentStatus === "expired") return;
|
|
629
|
+
const attachment = dataRef.current;
|
|
630
|
+
const cached = cachedAttachmentContent(resolveContent, attachment);
|
|
631
|
+
if (cached) {
|
|
632
|
+
setSourceUrl(cached.url);
|
|
633
|
+
setStatus("loading");
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const controller = new AbortController();
|
|
637
|
+
setStatus("resolving");
|
|
638
|
+
void resolveContent(attachment, controller.signal).then((result) => {
|
|
639
|
+
if (controller.signal.aborted) return;
|
|
640
|
+
if (!result.url?.trim()) throw new Error("Attachment content resolver returned an empty URL");
|
|
641
|
+
cacheAttachmentContent(resolveContent, attachment, result);
|
|
642
|
+
setSourceUrl(result.url);
|
|
643
|
+
setStatus("loading");
|
|
644
|
+
}).catch(() => {
|
|
645
|
+
if (!controller.signal.aborted) setStatus("error");
|
|
646
|
+
});
|
|
647
|
+
return () => controller.abort();
|
|
648
|
+
}, [attachmentKey, contentStatus, initialUrl, isNearViewport, resolutionAttempt, resolveContent]);
|
|
649
|
+
const handleImageError = () => {
|
|
650
|
+
if (resolveContent && retryCountRef.current === 0) {
|
|
651
|
+
retryCountRef.current = 1;
|
|
652
|
+
invalidateAttachmentContent(resolveContent, dataRef.current);
|
|
653
|
+
setSourceUrl("");
|
|
654
|
+
setStatus("resolving");
|
|
655
|
+
setResolutionAttempt((current) => current + 1);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
setStatus("error");
|
|
659
|
+
};
|
|
660
|
+
return React.createElement(
|
|
661
|
+
Attachment,
|
|
662
|
+
{
|
|
663
|
+
...props,
|
|
664
|
+
className: cn("a24-chat-image-attachment", className),
|
|
665
|
+
orientation: "vertical",
|
|
666
|
+
ref: rootRef,
|
|
667
|
+
state: status === "error" ? "error" : status === "done" ? "done" : "processing",
|
|
668
|
+
title: filename
|
|
669
|
+
},
|
|
670
|
+
React.createElement(
|
|
671
|
+
AttachmentMedia,
|
|
672
|
+
{ className: "a24-chat-image-media", variant: "image" },
|
|
673
|
+
sourceUrl ? React.createElement("img", {
|
|
674
|
+
alt: filename,
|
|
675
|
+
decoding: "async",
|
|
676
|
+
loading: "lazy",
|
|
677
|
+
onError: handleImageError,
|
|
678
|
+
onLoad: () => setStatus("done"),
|
|
679
|
+
src: sourceUrl
|
|
680
|
+
}) : status === "error" ? React.createElement(import_lucide_react.ImageOff, { "aria-hidden": "true", className: "size-5" }) : React.createElement(import_lucide_react.LoaderCircle, {
|
|
681
|
+
"aria-hidden": "true",
|
|
682
|
+
className: "a24-chat-image-spinner size-5 animate-spin"
|
|
683
|
+
}),
|
|
684
|
+
status !== "done" && status !== "error" ? React.createElement("span", { className: "sr-only", role: "status" }, `Loading ${filename}`) : null,
|
|
685
|
+
status === "error" ? React.createElement("span", { className: "sr-only" }, `${filename} preview unavailable`) : null
|
|
686
|
+
),
|
|
687
|
+
onRemove ? React.createElement(
|
|
688
|
+
"button",
|
|
689
|
+
{
|
|
690
|
+
"aria-label": `Remove ${filename}`,
|
|
691
|
+
className: "a24-chat-image-remove",
|
|
692
|
+
onClick: (event) => {
|
|
693
|
+
event.stopPropagation();
|
|
694
|
+
onRemove();
|
|
695
|
+
},
|
|
696
|
+
type: "button"
|
|
697
|
+
},
|
|
698
|
+
React.createElement(import_lucide_react.X, { "aria-hidden": "true", className: "size-3.5" })
|
|
699
|
+
) : null
|
|
711
700
|
);
|
|
712
701
|
}
|
|
713
702
|
function ChatAttachment({
|
|
703
|
+
className,
|
|
714
704
|
data,
|
|
715
705
|
onRemove,
|
|
716
706
|
orientation,
|
|
707
|
+
resolveContent,
|
|
717
708
|
...props
|
|
718
709
|
}) {
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
710
|
+
const filename = attachmentFilename(data);
|
|
711
|
+
const { fileIconType, kind } = resolveAttachmentVisual(data);
|
|
712
|
+
if (kind === "image") {
|
|
713
|
+
return React.createElement(ChatImageAttachment, {
|
|
714
|
+
...props,
|
|
715
|
+
className,
|
|
716
|
+
data,
|
|
717
|
+
onRemove,
|
|
718
|
+
resolveContent
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return React.createElement(
|
|
723
722
|
Attachment,
|
|
724
723
|
{
|
|
725
|
-
|
|
724
|
+
className: cn("a24-chat-attachment", className),
|
|
725
|
+
orientation: orientation || "horizontal",
|
|
726
|
+
size: "sm",
|
|
726
727
|
...props
|
|
727
728
|
},
|
|
728
|
-
|
|
729
|
+
React.createElement(
|
|
729
730
|
AttachmentMedia,
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
"
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
731
|
+
Object.assign(
|
|
732
|
+
{
|
|
733
|
+
className: "a24-chat-attachment-icon",
|
|
734
|
+
style: { backgroundColor: solidFileIconBackground(fileIconType, kind) },
|
|
735
|
+
variant: "icon"
|
|
736
|
+
},
|
|
737
|
+
{ "data-file-kind": kind }
|
|
738
|
+
),
|
|
739
|
+
React.createElement(import_file_icons.FileIcon, {
|
|
740
|
+
"aria-hidden": "true",
|
|
741
|
+
size: 28,
|
|
742
|
+
type: fileIconType,
|
|
743
|
+
variant: "solid"
|
|
744
|
+
})
|
|
740
745
|
),
|
|
741
|
-
|
|
746
|
+
React.createElement(
|
|
742
747
|
AttachmentContent,
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
React2.createElement(AttachmentDescription, null, mediaType)
|
|
748
|
+
{ className: "a24-chat-attachment-content" },
|
|
749
|
+
React.createElement(AttachmentTitle, { title: filename }, filename)
|
|
746
750
|
),
|
|
747
|
-
onRemove ?
|
|
751
|
+
onRemove ? React.createElement(
|
|
748
752
|
"button",
|
|
749
753
|
{
|
|
750
754
|
"aria-label": "Remove attachment",
|
|
751
|
-
className: "relative z-20
|
|
755
|
+
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",
|
|
752
756
|
onClick: (event) => {
|
|
753
757
|
event.stopPropagation();
|
|
754
758
|
onRemove();
|
|
755
759
|
},
|
|
756
760
|
type: "button"
|
|
757
761
|
},
|
|
758
|
-
|
|
762
|
+
React.createElement(import_lucide_react.X, { "aria-hidden": "true", className: "size-3.5" })
|
|
759
763
|
) : null
|
|
760
764
|
);
|
|
761
765
|
}
|
|
@@ -765,12 +769,11 @@ function ChatAttachments({
|
|
|
765
769
|
...props
|
|
766
770
|
}) {
|
|
767
771
|
if (!children) return null;
|
|
768
|
-
return
|
|
772
|
+
return React.createElement(
|
|
769
773
|
AttachmentGroup,
|
|
770
774
|
{
|
|
771
775
|
className: cn(
|
|
772
|
-
|
|
773
|
-
"w-fit flex-wrap overflow-visible",
|
|
776
|
+
"a24-chat-attachments w-full flex-nowrap",
|
|
774
777
|
className
|
|
775
778
|
),
|
|
776
779
|
...props
|
|
@@ -778,8 +781,305 @@ function ChatAttachments({
|
|
|
778
781
|
children
|
|
779
782
|
);
|
|
780
783
|
}
|
|
784
|
+
function ChatAttachmentRows({
|
|
785
|
+
attachments,
|
|
786
|
+
className,
|
|
787
|
+
dir,
|
|
788
|
+
onRemove,
|
|
789
|
+
resolveContent,
|
|
790
|
+
...props
|
|
791
|
+
}) {
|
|
792
|
+
if (attachments.length === 0) return null;
|
|
793
|
+
const imageAttachments = attachments.filter(isImageAttachment);
|
|
794
|
+
const fileAttachments = attachments.filter((attachment) => !isImageAttachment(attachment));
|
|
795
|
+
const renderRow = (items, kind) => items.length > 0 ? React.createElement(
|
|
796
|
+
ChatAttachments,
|
|
797
|
+
Object.assign({ dir, key: kind }, { "data-attachment-row": kind }),
|
|
798
|
+
items.map(
|
|
799
|
+
(attachment, index) => React.createElement(ChatAttachment, {
|
|
800
|
+
data: attachment,
|
|
801
|
+
dir: "ltr",
|
|
802
|
+
key: attachmentRenderKey(attachment, index, kind),
|
|
803
|
+
onRemove: onRemove ? () => onRemove(attachment) : void 0,
|
|
804
|
+
resolveContent
|
|
805
|
+
})
|
|
806
|
+
)
|
|
807
|
+
) : null;
|
|
808
|
+
return React.createElement(
|
|
809
|
+
"div",
|
|
810
|
+
{
|
|
811
|
+
className: cn("a24-chat-attachment-rows", className),
|
|
812
|
+
dir,
|
|
813
|
+
...props
|
|
814
|
+
},
|
|
815
|
+
renderRow(imageAttachments, "images"),
|
|
816
|
+
renderRow(fileAttachments, "files")
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/ui/composer-attachments.ts
|
|
821
|
+
var createId = () => typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `file-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
822
|
+
function createAgentChatComposerFile(file) {
|
|
823
|
+
return {
|
|
824
|
+
id: createId(),
|
|
825
|
+
type: "file",
|
|
826
|
+
url: URL.createObjectURL(file),
|
|
827
|
+
filename: file.name || "attachment",
|
|
828
|
+
mediaType: file.type || "application/octet-stream",
|
|
829
|
+
source: file
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function revokeAgentChatComposerFiles(files) {
|
|
833
|
+
files.forEach((file) => {
|
|
834
|
+
try {
|
|
835
|
+
URL.revokeObjectURL(file.url);
|
|
836
|
+
} catch {
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/ui/agent-chat-composer.tsx
|
|
842
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
843
|
+
function formatFileCount(files) {
|
|
844
|
+
if (files.length === 1) return files[0].filename;
|
|
845
|
+
return `${files.length} files`;
|
|
846
|
+
}
|
|
847
|
+
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";
|
|
848
|
+
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";
|
|
849
|
+
function AgentChatComposer({
|
|
850
|
+
accept,
|
|
851
|
+
allowAttachments = true,
|
|
852
|
+
attachmentLayout = "flow",
|
|
853
|
+
className,
|
|
854
|
+
disabled = false,
|
|
855
|
+
forceExpanded = false,
|
|
856
|
+
inputToolbarContent,
|
|
857
|
+
isRunning = false,
|
|
858
|
+
onAttachmentCountChange,
|
|
859
|
+
onStop,
|
|
860
|
+
onSubmit,
|
|
861
|
+
placeholder = "Message the agent",
|
|
862
|
+
textareaRef
|
|
863
|
+
}) {
|
|
864
|
+
const fileInputRef = React2.useRef(null);
|
|
865
|
+
const filesRef = React2.useRef([]);
|
|
866
|
+
const inFlightFilesRef = React2.useRef([]);
|
|
867
|
+
const localTextareaRef = React2.useRef(null);
|
|
868
|
+
const draftRevisionRef = React2.useRef(0);
|
|
869
|
+
const [text, setText] = React2.useState("");
|
|
870
|
+
const [files, setFiles] = React2.useState([]);
|
|
871
|
+
const [isSubmitting, setIsSubmitting] = React2.useState(false);
|
|
872
|
+
const isExpanded = forceExpanded || text.includes("\n") || text.length > 62;
|
|
873
|
+
const setTextareaRef = React2.useCallback(
|
|
874
|
+
(node) => {
|
|
875
|
+
localTextareaRef.current = node;
|
|
876
|
+
if (typeof textareaRef === "function") {
|
|
877
|
+
textareaRef(node);
|
|
878
|
+
} else if (textareaRef) {
|
|
879
|
+
textareaRef.current = node;
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
[textareaRef]
|
|
883
|
+
);
|
|
884
|
+
React2.useEffect(() => {
|
|
885
|
+
filesRef.current = files;
|
|
886
|
+
onAttachmentCountChange?.(files.length);
|
|
887
|
+
}, [files, onAttachmentCountChange]);
|
|
888
|
+
React2.useEffect(() => () => {
|
|
889
|
+
revokeAgentChatComposerFiles(filesRef.current);
|
|
890
|
+
revokeAgentChatComposerFiles(inFlightFilesRef.current);
|
|
891
|
+
}, []);
|
|
892
|
+
React2.useLayoutEffect(() => {
|
|
893
|
+
const textarea = localTextareaRef.current;
|
|
894
|
+
if (!textarea) return;
|
|
895
|
+
if (isExpanded) {
|
|
896
|
+
textarea.style.height = "auto";
|
|
897
|
+
const scrollHeight = textarea.scrollHeight;
|
|
898
|
+
textarea.style.height = `${Math.min(scrollHeight, 224)}px`;
|
|
899
|
+
textarea.style.overflowY = scrollHeight >= 224 ? "auto" : "hidden";
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
textarea.style.height = "";
|
|
903
|
+
textarea.style.overflowY = "hidden";
|
|
904
|
+
}, [isExpanded, text]);
|
|
905
|
+
const canSubmit = !disabled && !isSubmitting && !isRunning && (text.trim().length > 0 || files.length > 0);
|
|
906
|
+
const handleFilesChange = (event) => {
|
|
907
|
+
const selected = Array.from(event.target.files || []).map(createAgentChatComposerFile);
|
|
908
|
+
if (selected.length > 0) {
|
|
909
|
+
draftRevisionRef.current += 1;
|
|
910
|
+
setFiles((current) => [...current, ...selected]);
|
|
911
|
+
}
|
|
912
|
+
event.currentTarget.value = "";
|
|
913
|
+
};
|
|
914
|
+
const removeFile = (fileId) => {
|
|
915
|
+
draftRevisionRef.current += 1;
|
|
916
|
+
setFiles((current) => {
|
|
917
|
+
const target = current.find((file) => file.id === fileId);
|
|
918
|
+
if (target) revokeAgentChatComposerFiles([target]);
|
|
919
|
+
return current.filter((file) => file.id !== fileId);
|
|
920
|
+
});
|
|
921
|
+
};
|
|
922
|
+
const submit = async () => {
|
|
923
|
+
if (!canSubmit) return;
|
|
924
|
+
const submittedText = text.trim();
|
|
925
|
+
const submittedFiles = files;
|
|
926
|
+
const clearedRevision = draftRevisionRef.current + 1;
|
|
927
|
+
draftRevisionRef.current = clearedRevision;
|
|
928
|
+
inFlightFilesRef.current = submittedFiles;
|
|
929
|
+
setIsSubmitting(true);
|
|
930
|
+
setText("");
|
|
931
|
+
setFiles([]);
|
|
932
|
+
try {
|
|
933
|
+
await onSubmit({ text: submittedText, files: submittedFiles });
|
|
934
|
+
revokeAgentChatComposerFiles(submittedFiles);
|
|
935
|
+
inFlightFilesRef.current = [];
|
|
936
|
+
} catch {
|
|
937
|
+
if (draftRevisionRef.current === clearedRevision) {
|
|
938
|
+
draftRevisionRef.current += 1;
|
|
939
|
+
setText(submittedText);
|
|
940
|
+
setFiles(submittedFiles);
|
|
941
|
+
inFlightFilesRef.current = [];
|
|
942
|
+
} else {
|
|
943
|
+
revokeAgentChatComposerFiles(submittedFiles);
|
|
944
|
+
inFlightFilesRef.current = [];
|
|
945
|
+
}
|
|
946
|
+
} finally {
|
|
947
|
+
setIsSubmitting(false);
|
|
948
|
+
}
|
|
949
|
+
};
|
|
950
|
+
const handleKeyDown = (event) => {
|
|
951
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
952
|
+
event.preventDefault();
|
|
953
|
+
void submit();
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("a24-composer relative mx-auto w-full max-w-3xl overflow-visible", className), "data-agents24-chat-composer": "", children: [
|
|
957
|
+
files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
958
|
+
"div",
|
|
959
|
+
{
|
|
960
|
+
"data-testid": "agent-chat-composer-attachments",
|
|
961
|
+
"data-layout": attachmentLayout,
|
|
962
|
+
className: cn(
|
|
963
|
+
"a24-composer__attachments min-w-0 py-1",
|
|
964
|
+
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"
|
|
965
|
+
),
|
|
966
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
967
|
+
ChatAttachmentRows,
|
|
968
|
+
{
|
|
969
|
+
attachments: files,
|
|
970
|
+
onRemove: (attachment) => attachment.id && removeFile(attachment.id)
|
|
971
|
+
}
|
|
972
|
+
)
|
|
973
|
+
}
|
|
974
|
+
) : null,
|
|
975
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
976
|
+
"div",
|
|
977
|
+
{
|
|
978
|
+
"data-expanded": isExpanded ? "" : void 0,
|
|
979
|
+
className: cn(
|
|
980
|
+
"a24-composer__surface relative w-full border border-input bg-background shadow-sm",
|
|
981
|
+
isExpanded ? "rounded-xl p-2 pb-1.5" : "rounded-xl px-2 py-[3px]"
|
|
982
|
+
),
|
|
983
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
984
|
+
"div",
|
|
985
|
+
{
|
|
986
|
+
className: "a24-composer__grid w-full",
|
|
987
|
+
style: {
|
|
988
|
+
alignItems: "center",
|
|
989
|
+
display: "grid",
|
|
990
|
+
gridTemplateAreas: isExpanded ? `"textarea textarea textarea" "plus toolbar submit"` : `"plus textarea composer mic submit"`,
|
|
991
|
+
gridTemplateColumns: isExpanded ? "auto 1fr auto" : "auto 1fr auto auto auto",
|
|
992
|
+
gap: isExpanded ? "3px 10px" : "8px"
|
|
993
|
+
},
|
|
994
|
+
children: [
|
|
995
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { gridArea: "plus" }, className: "a24-composer__attach-slot flex shrink-0 items-center justify-center", children: [
|
|
996
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
997
|
+
"input",
|
|
998
|
+
{
|
|
999
|
+
ref: fileInputRef,
|
|
1000
|
+
accept,
|
|
1001
|
+
"aria-label": "Attach files",
|
|
1002
|
+
className: "hidden",
|
|
1003
|
+
multiple: true,
|
|
1004
|
+
name: "attachments",
|
|
1005
|
+
onChange: handleFilesChange,
|
|
1006
|
+
type: "file"
|
|
1007
|
+
}
|
|
1008
|
+
),
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1010
|
+
"button",
|
|
1011
|
+
{
|
|
1012
|
+
"aria-label": "Attach files",
|
|
1013
|
+
className: cn("a24-composer__attach", pillButtonClass),
|
|
1014
|
+
disabled: disabled || isSubmitting || isRunning || !allowAttachments,
|
|
1015
|
+
onClick: () => fileInputRef.current?.click(),
|
|
1016
|
+
title: "Attach files",
|
|
1017
|
+
type: "button",
|
|
1018
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Plus, { className: "size-4" })
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
] }),
|
|
1022
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "textarea" }, className: "a24-composer__textarea-slot min-w-0 w-full", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1023
|
+
"textarea",
|
|
1024
|
+
{
|
|
1025
|
+
ref: setTextareaRef,
|
|
1026
|
+
"aria-label": "Message",
|
|
1027
|
+
autoComplete: "off",
|
|
1028
|
+
className: cn(
|
|
1029
|
+
"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",
|
|
1030
|
+
isExpanded ? "min-h-9 max-h-[224px] py-1" : "min-h-9 h-9 max-h-9 py-2"
|
|
1031
|
+
),
|
|
1032
|
+
disabled: disabled || isSubmitting || isRunning,
|
|
1033
|
+
name: "message",
|
|
1034
|
+
onChange: (event) => {
|
|
1035
|
+
draftRevisionRef.current += 1;
|
|
1036
|
+
setText(event.target.value);
|
|
1037
|
+
},
|
|
1038
|
+
onKeyDown: handleKeyDown,
|
|
1039
|
+
placeholder,
|
|
1040
|
+
rows: isExpanded ? 2 : 1,
|
|
1041
|
+
value: text
|
|
1042
|
+
}
|
|
1043
|
+
) }),
|
|
1044
|
+
isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "toolbar" }, className: "a24-composer__toolbar flex min-w-0 items-center gap-1.5", children: inputToolbarContent }) : null,
|
|
1045
|
+
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "composer" }, className: "flex min-w-0 items-center gap-1.5" }) : null,
|
|
1046
|
+
!isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "mic" }, className: "flex shrink-0 items-center justify-end" }) : null,
|
|
1047
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { gridArea: "submit" }, className: "a24-composer__submit-slot flex shrink-0 items-center justify-end", children: isRunning ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1048
|
+
"button",
|
|
1049
|
+
{
|
|
1050
|
+
"aria-label": "Stop generating",
|
|
1051
|
+
className: cn("a24-composer__submit", submitButtonClass),
|
|
1052
|
+
disabled: disabled || !onStop,
|
|
1053
|
+
onClick: onStop,
|
|
1054
|
+
title: "Stop",
|
|
1055
|
+
type: "button",
|
|
1056
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Square, { className: "size-3.5 fill-current" })
|
|
1057
|
+
}
|
|
1058
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1059
|
+
"button",
|
|
1060
|
+
{
|
|
1061
|
+
"aria-label": files.length > 0 ? `Send ${formatFileCount(files)}` : "Send message",
|
|
1062
|
+
className: cn("a24-composer__submit", submitButtonClass, text.trim().length > 0 && "hover:scale-105"),
|
|
1063
|
+
disabled: !canSubmit,
|
|
1064
|
+
onClick: () => void submit(),
|
|
1065
|
+
title: "Send",
|
|
1066
|
+
type: "button",
|
|
1067
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.ArrowUp, { className: "size-4", strokeWidth: 2.5 })
|
|
1068
|
+
}
|
|
1069
|
+
) })
|
|
1070
|
+
]
|
|
1071
|
+
}
|
|
1072
|
+
)
|
|
1073
|
+
}
|
|
1074
|
+
),
|
|
1075
|
+
!isExpanded && inputToolbarContent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "a24-composer__toolbar a24-composer__toolbar--external mt-2 flex items-center gap-2 px-1", children: inputToolbarContent }) : null
|
|
1076
|
+
] });
|
|
1077
|
+
}
|
|
781
1078
|
|
|
782
1079
|
// src/ui/agent-chat-actions.tsx
|
|
1080
|
+
var React3 = __toESM(require("react"), 1);
|
|
1081
|
+
var import_lucide_react3 = require("lucide-react");
|
|
1082
|
+
var import_radix_ui = require("radix-ui");
|
|
783
1083
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
784
1084
|
var FEEDBACK_REASONS = [
|
|
785
1085
|
["incorrect", "Incorrect"],
|
|
@@ -812,7 +1112,7 @@ function AgentChatDefaultActions({
|
|
|
812
1112
|
label: "Retry",
|
|
813
1113
|
onClick: () => void handlers.onRetry?.(message),
|
|
814
1114
|
tooltip: "Regenerate response",
|
|
815
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1115
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.RefreshCcw, { className: "size-4" })
|
|
816
1116
|
}
|
|
817
1117
|
) : null,
|
|
818
1118
|
canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -823,7 +1123,7 @@ function AgentChatDefaultActions({
|
|
|
823
1123
|
rating: handlers.feedback?.rating === "like" ? null : "like"
|
|
824
1124
|
}),
|
|
825
1125
|
tooltip: "Like this response",
|
|
826
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1126
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ThumbsUp, { className: "size-4", fill: handlers.feedback?.rating === "like" ? "currentColor" : "none" })
|
|
827
1127
|
}
|
|
828
1128
|
) : null,
|
|
829
1129
|
canWriteFeedback ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_radix_ui.Popover.Root, { onOpenChange: setFeedbackOpen, open: feedbackOpen, children: [
|
|
@@ -839,7 +1139,7 @@ function AgentChatDefaultActions({
|
|
|
839
1139
|
},
|
|
840
1140
|
ref: setFeedbackTrigger,
|
|
841
1141
|
tooltip: "Dislike this response",
|
|
842
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1142
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.ThumbsDown, { className: "size-4", fill: handlers.feedback?.rating === "dislike" ? "currentColor" : "none" })
|
|
843
1143
|
}
|
|
844
1144
|
) }),
|
|
845
1145
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -916,7 +1216,7 @@ function AgentChatDefaultActions({
|
|
|
916
1216
|
label: "Copy",
|
|
917
1217
|
onClick: () => handlers.onCopy?.(message.content || "", message.id),
|
|
918
1218
|
tooltip: handlers.copied ? "Copied!" : "Copy to clipboard",
|
|
919
|
-
children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1219
|
+
children: handlers.copied ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Check, { className: "size-4 text-green-600" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Copy, { className: "size-4" })
|
|
920
1220
|
}
|
|
921
1221
|
) : null
|
|
922
1222
|
] });
|
|
@@ -924,7 +1224,7 @@ function AgentChatDefaultActions({
|
|
|
924
1224
|
|
|
925
1225
|
// src/ui/agent-chat-message.tsx
|
|
926
1226
|
var React8 = __toESM(require("react"), 1);
|
|
927
|
-
var
|
|
1227
|
+
var import_lucide_react6 = require("lucide-react");
|
|
928
1228
|
|
|
929
1229
|
// src/ui/agent-response-timeline.tsx
|
|
930
1230
|
var React7 = __toESM(require("react"), 1);
|
|
@@ -1113,7 +1413,7 @@ function useStreamingText({
|
|
|
1113
1413
|
|
|
1114
1414
|
// src/ui/ask-user-interaction.tsx
|
|
1115
1415
|
var React4 = __toESM(require("react"), 1);
|
|
1116
|
-
var
|
|
1416
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1117
1417
|
|
|
1118
1418
|
// src/ui/ask-user-interaction-state.ts
|
|
1119
1419
|
function emptyAskUserDrafts(questions) {
|
|
@@ -1207,8 +1507,8 @@ function ResolvedQuestions({
|
|
|
1207
1507
|
" question",
|
|
1208
1508
|
count === 1 ? "" : "s"
|
|
1209
1509
|
] }),
|
|
1210
|
-
open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1211
|
-
|
|
1510
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.ChevronDown, { "aria-hidden": "true", className: "size-3.5" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1511
|
+
import_lucide_react4.ChevronRight,
|
|
1212
1512
|
{
|
|
1213
1513
|
"aria-hidden": "true",
|
|
1214
1514
|
className: "size-3.5 opacity-70 rtl:rotate-180"
|
|
@@ -1323,7 +1623,7 @@ function AskUserInteraction({
|
|
|
1323
1623
|
onClick: () => setStep((current) => Math.max(0, current - 1)),
|
|
1324
1624
|
type: "button",
|
|
1325
1625
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1326
|
-
|
|
1626
|
+
import_lucide_react4.ChevronLeft,
|
|
1327
1627
|
{
|
|
1328
1628
|
"aria-hidden": "true",
|
|
1329
1629
|
className: "size-4 rtl:rotate-180"
|
|
@@ -1352,7 +1652,7 @@ function AskUserInteraction({
|
|
|
1352
1652
|
onClick: () => completeStep(drafts),
|
|
1353
1653
|
type: "button",
|
|
1354
1654
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1355
|
-
|
|
1655
|
+
import_lucide_react4.ChevronRight,
|
|
1356
1656
|
{
|
|
1357
1657
|
"aria-hidden": "true",
|
|
1358
1658
|
className: "size-4 rtl:rotate-180"
|
|
@@ -1389,7 +1689,7 @@ function AskUserInteraction({
|
|
|
1389
1689
|
),
|
|
1390
1690
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "min-w-0 flex-1 font-medium text-foreground", children: option.label }),
|
|
1391
1691
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1392
|
-
|
|
1692
|
+
import_lucide_react4.ChevronRight,
|
|
1393
1693
|
{
|
|
1394
1694
|
"aria-hidden": "true",
|
|
1395
1695
|
className: "size-4 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100 rtl:rotate-180"
|
|
@@ -1401,7 +1701,7 @@ function AskUserInteraction({
|
|
|
1401
1701
|
);
|
|
1402
1702
|
}),
|
|
1403
1703
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-h-11 items-center gap-2 rounded-xl bg-muted px-2.5 py-1.5 focus-within:ring-1 focus-within:ring-ring", children: [
|
|
1404
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1704
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "flex size-8 shrink-0 items-center justify-center rounded-full border border-border/70 bg-background/70 text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react4.Pencil, { "aria-hidden": "true", className: "size-4" }) }),
|
|
1405
1705
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1406
1706
|
"input",
|
|
1407
1707
|
{
|
|
@@ -1653,7 +1953,7 @@ function resolvedHitlLabel(part) {
|
|
|
1653
1953
|
|
|
1654
1954
|
// src/ui/tool-sessions.tsx
|
|
1655
1955
|
var React6 = __toESM(require("react"), 1);
|
|
1656
|
-
var
|
|
1956
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1657
1957
|
var import_thinking_orbs = require("thinking-orbs");
|
|
1658
1958
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1659
1959
|
function agentChatToolLabel(part) {
|
|
@@ -1821,7 +2121,7 @@ function AgentChatNodeActivityRow({
|
|
|
1821
2121
|
type: "button",
|
|
1822
2122
|
children: [
|
|
1823
2123
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
|
|
1824
|
-
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2124
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
|
|
1825
2125
|
]
|
|
1826
2126
|
}
|
|
1827
2127
|
) }),
|
|
@@ -1881,7 +2181,7 @@ function ToolDisclosure({
|
|
|
1881
2181
|
children: [
|
|
1882
2182
|
showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
|
|
1883
2183
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
|
|
1884
|
-
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
2184
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronDown, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react5.ChevronRight, { "aria-hidden": "true", className: "a24-tool-trigger__chevron" })
|
|
1885
2185
|
]
|
|
1886
2186
|
}
|
|
1887
2187
|
),
|
|
@@ -2342,7 +2642,7 @@ function AgentChatUserMessageContent({
|
|
|
2342
2642
|
const [isExpanded, setIsExpanded] = React8.useState(false);
|
|
2343
2643
|
const contentId = React8.useId();
|
|
2344
2644
|
const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
|
|
2345
|
-
const Icon = isExpanded ?
|
|
2645
|
+
const Icon = isExpanded ? import_lucide_react6.ChevronUp : import_lucide_react6.ChevronDown;
|
|
2346
2646
|
const clampedStyle = shouldCollapse && !isExpanded ? {
|
|
2347
2647
|
WebkitBoxOrient: "vertical",
|
|
2348
2648
|
WebkitLineClamp: Math.max(1, collapsedLines),
|
|
@@ -2375,6 +2675,7 @@ function AgentChatMessage({
|
|
|
2375
2675
|
additionalActions,
|
|
2376
2676
|
actions,
|
|
2377
2677
|
actionsClassName,
|
|
2678
|
+
attachmentContentResolver,
|
|
2378
2679
|
className,
|
|
2379
2680
|
contentClassName,
|
|
2380
2681
|
dir,
|
|
@@ -2408,14 +2709,15 @@ function AgentChatMessage({
|
|
|
2408
2709
|
}
|
|
2409
2710
|
) : null);
|
|
2410
2711
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
|
|
2411
|
-
showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2412
|
-
|
|
2712
|
+
showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2713
|
+
ChatAttachmentRows,
|
|
2413
2714
|
{
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2715
|
+
attachments: message.attachments || [],
|
|
2716
|
+
className: "mb-2",
|
|
2717
|
+
dir,
|
|
2718
|
+
resolveContent: attachmentContentResolver
|
|
2719
|
+
}
|
|
2720
|
+
) : null,
|
|
2419
2721
|
shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2420
2722
|
ChatMessageContent,
|
|
2421
2723
|
{
|
|
@@ -2871,6 +3173,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2871
3173
|
BubbleGroup,
|
|
2872
3174
|
BubbleReactions,
|
|
2873
3175
|
ChatAttachment,
|
|
3176
|
+
ChatAttachmentRows,
|
|
2874
3177
|
ChatAttachments,
|
|
2875
3178
|
ChatMessage,
|
|
2876
3179
|
ChatMessageAction,
|
|
@@ -2884,6 +3187,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2884
3187
|
MessageAction,
|
|
2885
3188
|
MessageActions,
|
|
2886
3189
|
MessageAttachment,
|
|
3190
|
+
MessageAttachmentRows,
|
|
2887
3191
|
MessageAttachments,
|
|
2888
3192
|
MessageAvatar,
|
|
2889
3193
|
MessageContent,
|
|
@@ -2899,6 +3203,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2899
3203
|
bubbleVariants,
|
|
2900
3204
|
cn,
|
|
2901
3205
|
contextStatusMetrics,
|
|
3206
|
+
createAgentChatComposerFile,
|
|
2902
3207
|
hitlActionLabel,
|
|
2903
3208
|
hitlTitle,
|
|
2904
3209
|
isAgentChatSubagent,
|
|
@@ -2907,6 +3212,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2907
3212
|
markerVariants,
|
|
2908
3213
|
partitionAgentChatToolTimeline,
|
|
2909
3214
|
resolvedHitlLabel,
|
|
3215
|
+
revokeAgentChatComposerFiles,
|
|
2910
3216
|
shouldCollapseUserMessage,
|
|
2911
3217
|
useAudioRecorder
|
|
2912
3218
|
});
|