@agents24/chat-react 0.5.4 → 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/README.md +8 -2
- package/compatibility.json +4 -4
- package/dist/runtime/index.cjs +127 -18
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.js +125 -18
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/use-agent-chat-runtime.d.ts +17 -2
- package/dist/scaffold/app.d.ts +1 -0
- package/dist/scaffold/components/chat/chat-composer.d.ts +10 -2
- package/dist/scaffold/components/chat/chat-message.d.ts +3 -1
- package/dist/scaffold/components/chat/chat-shell.d.ts +3 -1
- package/dist/scaffold/index.cjs +415 -253
- package/dist/scaffold/index.cjs.map +1 -1
- package/dist/scaffold/index.js +408 -240
- package/dist/scaffold/index.js.map +1 -1
- package/dist/styles.css +18 -4
- 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 +653 -343
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +638 -328
- package/dist/ui/index.js.map +1 -1
- package/package.json +4 -3
- package/scaffold/src/app.tsx +2 -1
- package/scaffold/src/chat.css +13 -1
- package/scaffold/src/components/chat/chat-composer.tsx +107 -27
- package/scaffold/src/components/chat/chat-message.tsx +8 -9
- package/scaffold/src/components/chat/chat-shell.tsx +24 -11
- package/scaffold/src/components/ui/button.tsx +1 -1
- package/scaffold/src/components/ui/dropdown-menu.tsx +1 -1
- package/scaffold/src/components/ui/sidebar.tsx +4 -4
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
|
) }),
|
|
@@ -1835,6 +2135,7 @@ function toolRowLabel(part) {
|
|
|
1835
2135
|
function ToolDisclosure({
|
|
1836
2136
|
children,
|
|
1837
2137
|
error,
|
|
2138
|
+
grouped,
|
|
1838
2139
|
label,
|
|
1839
2140
|
loading,
|
|
1840
2141
|
onExpandedChange,
|
|
@@ -1848,9 +2149,10 @@ function ToolDisclosure({
|
|
|
1848
2149
|
const [open, setOpen] = React6.useState(false);
|
|
1849
2150
|
const contentId = React6.useId();
|
|
1850
2151
|
const expandable = React6.Children.toArray(children).length > 0;
|
|
2152
|
+
const textOnlyRoot = Boolean(root && !grouped && !showOrb);
|
|
1851
2153
|
if (!expandable) {
|
|
1852
2154
|
const triggerClassName = cn("a24-tool-trigger", root && "a24-tool-trigger--root", root && !showOrb && "a24-tool-trigger--without-orb", error && "a24-tool-trigger--error", loading && "a24-tool-trigger--active");
|
|
1853
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
|
|
2155
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("a24-tool-trigger-row", textOnlyRoot && "a24-tool-trigger-row--text"), children: [
|
|
1854
2156
|
onSelect && part ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { className: triggerClassName, onClick: () => onSelect(part), type: "button", children: [
|
|
1855
2157
|
showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
|
|
1856
2158
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label })
|
|
@@ -1862,7 +2164,7 @@ function ToolDisclosure({
|
|
|
1862
2164
|
] });
|
|
1863
2165
|
}
|
|
1864
2166
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-disclosure", children: [
|
|
1865
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "a24-tool-trigger-row", children: [
|
|
2167
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("a24-tool-trigger-row", textOnlyRoot && "a24-tool-trigger-row--text"), children: [
|
|
1866
2168
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
1867
2169
|
"button",
|
|
1868
2170
|
{
|
|
@@ -1879,7 +2181,7 @@ function ToolDisclosure({
|
|
|
1879
2181
|
children: [
|
|
1880
2182
|
showOrb ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolActivityOrb, { label: statusLabel }) : null,
|
|
1881
2183
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "a24-tool-trigger__label", children: label }),
|
|
1882
|
-
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" })
|
|
1883
2185
|
]
|
|
1884
2186
|
}
|
|
1885
2187
|
),
|
|
@@ -1987,6 +2289,7 @@ function AgentChatToolSession({
|
|
|
1987
2289
|
{
|
|
1988
2290
|
accessory: slots?.renderToolAccessory?.(context),
|
|
1989
2291
|
error: failed,
|
|
2292
|
+
grouped: true,
|
|
1990
2293
|
label: labelContent,
|
|
1991
2294
|
loading: presentingActivity,
|
|
1992
2295
|
onExpandedChange: slots?.onExpandedChange,
|
|
@@ -2031,6 +2334,7 @@ function AgentChatSubagentGroup({ isLatestActivity = true, isLive = false, part,
|
|
|
2031
2334
|
{
|
|
2032
2335
|
accessory: slots?.renderToolAccessory?.(context),
|
|
2033
2336
|
error: failed,
|
|
2337
|
+
grouped: true,
|
|
2034
2338
|
label: labelContent,
|
|
2035
2339
|
loading: presentingActivity,
|
|
2036
2340
|
onExpandedChange: slots?.onExpandedChange,
|
|
@@ -2338,7 +2642,7 @@ function AgentChatUserMessageContent({
|
|
|
2338
2642
|
const [isExpanded, setIsExpanded] = React8.useState(false);
|
|
2339
2643
|
const contentId = React8.useId();
|
|
2340
2644
|
const shouldCollapse = shouldCollapseUserMessage(content, collapseThreshold);
|
|
2341
|
-
const Icon = isExpanded ?
|
|
2645
|
+
const Icon = isExpanded ? import_lucide_react6.ChevronUp : import_lucide_react6.ChevronDown;
|
|
2342
2646
|
const clampedStyle = shouldCollapse && !isExpanded ? {
|
|
2343
2647
|
WebkitBoxOrient: "vertical",
|
|
2344
2648
|
WebkitLineClamp: Math.max(1, collapsedLines),
|
|
@@ -2371,6 +2675,7 @@ function AgentChatMessage({
|
|
|
2371
2675
|
additionalActions,
|
|
2372
2676
|
actions,
|
|
2373
2677
|
actionsClassName,
|
|
2678
|
+
attachmentContentResolver,
|
|
2374
2679
|
className,
|
|
2375
2680
|
contentClassName,
|
|
2376
2681
|
dir,
|
|
@@ -2404,14 +2709,15 @@ function AgentChatMessage({
|
|
|
2404
2709
|
}
|
|
2405
2710
|
) : null);
|
|
2406
2711
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(ChatMessage, { className, dir, from: message.role, children: [
|
|
2407
|
-
showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2408
|
-
|
|
2712
|
+
showAttachments ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
2713
|
+
ChatAttachmentRows,
|
|
2409
2714
|
{
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2715
|
+
attachments: message.attachments || [],
|
|
2716
|
+
className: "mb-2",
|
|
2717
|
+
dir,
|
|
2718
|
+
resolveContent: attachmentContentResolver
|
|
2719
|
+
}
|
|
2720
|
+
) : null,
|
|
2415
2721
|
shouldRenderContent ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
2416
2722
|
ChatMessageContent,
|
|
2417
2723
|
{
|
|
@@ -2867,6 +3173,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2867
3173
|
BubbleGroup,
|
|
2868
3174
|
BubbleReactions,
|
|
2869
3175
|
ChatAttachment,
|
|
3176
|
+
ChatAttachmentRows,
|
|
2870
3177
|
ChatAttachments,
|
|
2871
3178
|
ChatMessage,
|
|
2872
3179
|
ChatMessageAction,
|
|
@@ -2880,6 +3187,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2880
3187
|
MessageAction,
|
|
2881
3188
|
MessageActions,
|
|
2882
3189
|
MessageAttachment,
|
|
3190
|
+
MessageAttachmentRows,
|
|
2883
3191
|
MessageAttachments,
|
|
2884
3192
|
MessageAvatar,
|
|
2885
3193
|
MessageContent,
|
|
@@ -2895,6 +3203,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2895
3203
|
bubbleVariants,
|
|
2896
3204
|
cn,
|
|
2897
3205
|
contextStatusMetrics,
|
|
3206
|
+
createAgentChatComposerFile,
|
|
2898
3207
|
hitlActionLabel,
|
|
2899
3208
|
hitlTitle,
|
|
2900
3209
|
isAgentChatSubagent,
|
|
@@ -2903,6 +3212,7 @@ function useAudioRecorder(onRecorded, options = {}) {
|
|
|
2903
3212
|
markerVariants,
|
|
2904
3213
|
partitionAgentChatToolTimeline,
|
|
2905
3214
|
resolvedHitlLabel,
|
|
3215
|
+
revokeAgentChatComposerFiles,
|
|
2906
3216
|
shouldCollapseUserMessage,
|
|
2907
3217
|
useAudioRecorder
|
|
2908
3218
|
});
|