@cntyclub/agent-react 0.4.0 → 0.6.0
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/api-client.d.ts +1 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/components/agent-composer.d.ts +23 -0
- package/dist/components/agent-composer.d.ts.map +1 -0
- package/dist/components/agent-panel.d.ts +3 -1
- package/dist/components/agent-panel.d.ts.map +1 -1
- package/dist/components/agent-widget.d.ts.map +1 -1
- package/dist/components/document-card.d.ts +11 -0
- package/dist/components/document-card.d.ts.map +1 -0
- package/dist/components/message-item.d.ts +5 -1
- package/dist/components/message-item.d.ts.map +1 -1
- package/dist/components/task-checklist.d.ts +11 -0
- package/dist/components/task-checklist.d.ts.map +1 -0
- package/dist/components/tool-activity-group.d.ts +15 -0
- package/dist/components/tool-activity-group.d.ts.map +1 -0
- package/dist/components/tool-approval-card.d.ts +10 -5
- package/dist/components/tool-approval-card.d.ts.map +1 -1
- package/dist/file-support.d.ts +13 -0
- package/dist/file-support.d.ts.map +1 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +768 -111
- package/dist/index.js.map +1 -1
- package/dist/message-group.d.ts +37 -0
- package/dist/message-group.d.ts.map +1 -0
- package/dist/tool-presentation.d.ts +20 -0
- package/dist/tool-presentation.d.ts.map +1 -0
- package/dist/types.d.ts +91 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent-chat.d.ts +1 -1
- package/dist/use-agent-chat.d.ts.map +1 -1
- package/dist/use-always-approve.d.ts +15 -0
- package/dist/use-always-approve.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ChatMessage, ChatMessageBubble, Markdown, ChatToolChip,
|
|
2
|
-
import {
|
|
3
|
-
import * as
|
|
1
|
+
import { Button, cn, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, ChatTypingIndicator, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
|
|
2
|
+
import { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, CheckIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Trash2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
|
|
3
|
+
import * as React5 from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
// src/api-client.ts
|
|
@@ -25,13 +25,15 @@ var AgentApiClient = class {
|
|
|
25
25
|
const base = this.config.apiBaseUrl.replace(/\/$/, "");
|
|
26
26
|
const separator = path.includes("?") ? "&" : "?";
|
|
27
27
|
const url = `${base}${path}${separator}client_id=${encodeURIComponent(this.config.clientId)}`;
|
|
28
|
+
const isFormData = typeof FormData !== "undefined" && options.body instanceof FormData;
|
|
29
|
+
const headers = {
|
|
30
|
+
Authorization: `Bearer ${token}`,
|
|
31
|
+
"X-Agent-Client-Id": this.config.clientId
|
|
32
|
+
};
|
|
33
|
+
if (!isFormData) headers["Content-Type"] = "application/json";
|
|
28
34
|
const response = await fetch(url, {
|
|
29
|
-
body: options.body === void 0 ? void 0 : JSON.stringify(options.body),
|
|
30
|
-
headers
|
|
31
|
-
Authorization: `Bearer ${token}`,
|
|
32
|
-
"Content-Type": "application/json",
|
|
33
|
-
"X-Agent-Client-Id": this.config.clientId
|
|
34
|
-
},
|
|
35
|
+
body: options.body === void 0 ? void 0 : isFormData ? options.body : JSON.stringify(options.body),
|
|
36
|
+
headers,
|
|
35
37
|
method: options.method ?? "GET"
|
|
36
38
|
});
|
|
37
39
|
if (response.status === 204) return void 0;
|
|
@@ -49,7 +51,15 @@ var AgentApiClient = class {
|
|
|
49
51
|
getConfig() {
|
|
50
52
|
return this.request("/agent-mode/config/");
|
|
51
53
|
}
|
|
52
|
-
sendMessage(message, conversationId) {
|
|
54
|
+
sendMessage(message, conversationId, files) {
|
|
55
|
+
if (files && files.length > 0) {
|
|
56
|
+
const form = new FormData();
|
|
57
|
+
form.append("client_id", this.config.clientId);
|
|
58
|
+
form.append("message", message);
|
|
59
|
+
if (conversationId) form.append("conversation_id", conversationId);
|
|
60
|
+
for (const file of files) form.append("files", file, file.name);
|
|
61
|
+
return this.request("/agent-mode/chat/", { body: form, method: "POST" });
|
|
62
|
+
}
|
|
53
63
|
return this.request("/agent-mode/chat/", {
|
|
54
64
|
body: {
|
|
55
65
|
client_id: this.config.clientId,
|
|
@@ -77,6 +87,268 @@ var AgentApiClient = class {
|
|
|
77
87
|
});
|
|
78
88
|
}
|
|
79
89
|
};
|
|
90
|
+
|
|
91
|
+
// src/file-support.ts
|
|
92
|
+
var DEFAULT_ACCEPT = [".txt", ".md", ".markdown", ".csv", ".tsv", ".log", ".docx"];
|
|
93
|
+
var DEFAULT_MAX_SIZE_MB = 5;
|
|
94
|
+
function fileUploadEnabled(config) {
|
|
95
|
+
return config.fileUpload?.enabled !== false;
|
|
96
|
+
}
|
|
97
|
+
function acceptedExtensions(config) {
|
|
98
|
+
const accept = config.fileUpload?.accept;
|
|
99
|
+
return accept && accept.length > 0 ? accept.map((ext) => ext.toLowerCase()) : DEFAULT_ACCEPT;
|
|
100
|
+
}
|
|
101
|
+
function maxSizeMb(config) {
|
|
102
|
+
return config.fileUpload?.maxSizeMb ?? DEFAULT_MAX_SIZE_MB;
|
|
103
|
+
}
|
|
104
|
+
function acceptAttribute(config) {
|
|
105
|
+
return acceptedExtensions(config).join(",");
|
|
106
|
+
}
|
|
107
|
+
function extensionOf(filename) {
|
|
108
|
+
const dot = filename.lastIndexOf(".");
|
|
109
|
+
return dot >= 0 ? filename.slice(dot).toLowerCase() : "";
|
|
110
|
+
}
|
|
111
|
+
function validateFile(config, file) {
|
|
112
|
+
const extensions = acceptedExtensions(config);
|
|
113
|
+
if (!extensions.includes(extensionOf(file.name))) {
|
|
114
|
+
return `\u201C${file.name}\u201D isn\u2019t a supported text file. Allowed: ${extensions.join(", ")}.`;
|
|
115
|
+
}
|
|
116
|
+
const limit = maxSizeMb(config);
|
|
117
|
+
if (file.size > limit * 1024 * 1024) {
|
|
118
|
+
return `\u201C${file.name}\u201D is too large (max ${limit} MB).`;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function formatBytes(bytes) {
|
|
123
|
+
if (!bytes && bytes !== 0) return "";
|
|
124
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
125
|
+
if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
126
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
127
|
+
}
|
|
128
|
+
function AgentComposer({
|
|
129
|
+
acceptAttribute: acceptAttribute2,
|
|
130
|
+
className,
|
|
131
|
+
disabled,
|
|
132
|
+
files,
|
|
133
|
+
onPickFiles,
|
|
134
|
+
onRemoveFile,
|
|
135
|
+
onSubmit,
|
|
136
|
+
onValueChange,
|
|
137
|
+
placeholder = "Ask anything\u2026",
|
|
138
|
+
value
|
|
139
|
+
}) {
|
|
140
|
+
const textareaRef = React5.useRef(null);
|
|
141
|
+
const inputRef = React5.useRef(null);
|
|
142
|
+
const canSubmit = !disabled && (value.trim().length > 0 || files.length > 0);
|
|
143
|
+
const submit = React5.useCallback(() => {
|
|
144
|
+
if (!canSubmit) return;
|
|
145
|
+
onSubmit(value.trim());
|
|
146
|
+
}, [canSubmit, onSubmit, value]);
|
|
147
|
+
React5.useEffect(() => {
|
|
148
|
+
const textarea = textareaRef.current;
|
|
149
|
+
if (!textarea) return;
|
|
150
|
+
textarea.style.height = "auto";
|
|
151
|
+
textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
|
|
152
|
+
}, [value]);
|
|
153
|
+
const handlePick = (event) => {
|
|
154
|
+
const picked = Array.from(event.target.files ?? []);
|
|
155
|
+
if (picked.length) onPickFiles?.(picked);
|
|
156
|
+
event.target.value = "";
|
|
157
|
+
};
|
|
158
|
+
return /* @__PURE__ */ jsx(
|
|
159
|
+
"form",
|
|
160
|
+
{
|
|
161
|
+
className: cn("shrink-0 bg-background p-3 pt-1.5", className),
|
|
162
|
+
"data-slot": "agent-composer",
|
|
163
|
+
onSubmit: (event) => {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
submit();
|
|
166
|
+
},
|
|
167
|
+
children: /* @__PURE__ */ jsxs(
|
|
168
|
+
"div",
|
|
169
|
+
{
|
|
170
|
+
className: cn(
|
|
171
|
+
"flex flex-col gap-1.5 rounded-2xl border border-input bg-popover p-1.5 shadow-xs transition-[border-color,box-shadow] duration-150 focus-within:border-ring/64 focus-within:ring-2 focus-within:ring-ring/24 dark:bg-input/32",
|
|
172
|
+
disabled && "opacity-64"
|
|
173
|
+
),
|
|
174
|
+
children: [
|
|
175
|
+
files.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 px-1 pt-1", children: files.map((file, index) => /* @__PURE__ */ jsxs(
|
|
176
|
+
"span",
|
|
177
|
+
{
|
|
178
|
+
className: "flex max-w-52 items-center gap-1.5 rounded-lg border border-border/72 bg-muted/48 py-1 pr-1 pl-2 text-xs",
|
|
179
|
+
children: [
|
|
180
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
|
|
181
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: file.name }),
|
|
182
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-muted-foreground", children: formatBytes(file.size) }),
|
|
183
|
+
onRemoveFile ? /* @__PURE__ */ jsx(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
"aria-label": `Remove ${file.name}`,
|
|
187
|
+
className: "flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground hover:bg-background hover:text-foreground [&_svg]:size-3",
|
|
188
|
+
onClick: () => onRemoveFile(index),
|
|
189
|
+
type: "button",
|
|
190
|
+
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
191
|
+
}
|
|
192
|
+
) : null
|
|
193
|
+
]
|
|
194
|
+
},
|
|
195
|
+
`${file.name}-${index}`
|
|
196
|
+
)) }) : null,
|
|
197
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-1.5", children: [
|
|
198
|
+
onPickFiles ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
199
|
+
/* @__PURE__ */ jsx(
|
|
200
|
+
"input",
|
|
201
|
+
{
|
|
202
|
+
accept: acceptAttribute2,
|
|
203
|
+
className: "hidden",
|
|
204
|
+
multiple: true,
|
|
205
|
+
onChange: handlePick,
|
|
206
|
+
ref: inputRef,
|
|
207
|
+
type: "file"
|
|
208
|
+
}
|
|
209
|
+
),
|
|
210
|
+
/* @__PURE__ */ jsx(
|
|
211
|
+
Button,
|
|
212
|
+
{
|
|
213
|
+
"aria-label": "Attach files",
|
|
214
|
+
className: "shrink-0",
|
|
215
|
+
disabled,
|
|
216
|
+
onClick: () => inputRef.current?.click(),
|
|
217
|
+
size: "icon-sm",
|
|
218
|
+
type: "button",
|
|
219
|
+
variant: "ghost",
|
|
220
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, {})
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
] }) : null,
|
|
224
|
+
/* @__PURE__ */ jsx(
|
|
225
|
+
"textarea",
|
|
226
|
+
{
|
|
227
|
+
className: "max-h-36 min-h-8 flex-1 resize-none self-center bg-transparent py-1.5 pl-1.5 text-sm outline-none placeholder:text-muted-foreground",
|
|
228
|
+
disabled,
|
|
229
|
+
onChange: (event) => onValueChange(event.target.value),
|
|
230
|
+
onKeyDown: (event) => {
|
|
231
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
232
|
+
event.preventDefault();
|
|
233
|
+
submit();
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
placeholder,
|
|
237
|
+
ref: textareaRef,
|
|
238
|
+
rows: 1,
|
|
239
|
+
value
|
|
240
|
+
}
|
|
241
|
+
),
|
|
242
|
+
/* @__PURE__ */ jsx(
|
|
243
|
+
Button,
|
|
244
|
+
{
|
|
245
|
+
"aria-label": "Send message",
|
|
246
|
+
className: "shrink-0 rounded-full before:rounded-full",
|
|
247
|
+
disabled: !canSubmit,
|
|
248
|
+
size: "icon-sm",
|
|
249
|
+
type: "submit",
|
|
250
|
+
children: /* @__PURE__ */ jsx(ArrowUpIcon, {})
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
] })
|
|
254
|
+
]
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/message-group.ts
|
|
262
|
+
function groupMessages(messages) {
|
|
263
|
+
const items = [];
|
|
264
|
+
let group = null;
|
|
265
|
+
let groupKey = "";
|
|
266
|
+
const stepIndexByCallId = /* @__PURE__ */ new Map();
|
|
267
|
+
const flush = () => {
|
|
268
|
+
if (group && group.length > 0) {
|
|
269
|
+
items.push({ key: `tools-${groupKey}`, kind: "tool-activity", steps: group });
|
|
270
|
+
}
|
|
271
|
+
group = null;
|
|
272
|
+
groupKey = "";
|
|
273
|
+
stepIndexByCallId.clear();
|
|
274
|
+
};
|
|
275
|
+
for (const message of messages) {
|
|
276
|
+
if (message.role === "user") {
|
|
277
|
+
flush();
|
|
278
|
+
items.push({ key: message.id, kind: "user", message });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (message.role === "assistant") {
|
|
282
|
+
const hasText = Boolean(message.content && message.content.trim());
|
|
283
|
+
if (hasText) {
|
|
284
|
+
flush();
|
|
285
|
+
items.push({ key: message.id, kind: "assistant-text", message });
|
|
286
|
+
}
|
|
287
|
+
for (const toolCall of message.tool_calls ?? []) {
|
|
288
|
+
const callId2 = toolCall.id ?? `${message.id}-${toolCall.name}`;
|
|
289
|
+
if (!group) {
|
|
290
|
+
group = [];
|
|
291
|
+
groupKey = message.id;
|
|
292
|
+
}
|
|
293
|
+
stepIndexByCallId.set(callId2, group.length);
|
|
294
|
+
group.push({
|
|
295
|
+
args: toolCall.arguments ?? {},
|
|
296
|
+
callId: callId2,
|
|
297
|
+
name: toolCall.name ?? "tool",
|
|
298
|
+
status: "running"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const callId = message.tool_call_id ?? "";
|
|
304
|
+
if (!group) {
|
|
305
|
+
group = [];
|
|
306
|
+
groupKey = message.id;
|
|
307
|
+
}
|
|
308
|
+
const existingIndex = callId ? stepIndexByCallId.get(callId) : void 0;
|
|
309
|
+
if (existingIndex !== void 0) {
|
|
310
|
+
const step = group[existingIndex];
|
|
311
|
+
step.status = "done";
|
|
312
|
+
step.result = message.result || step.result;
|
|
313
|
+
step.blocks = message.blocks ?? step.blocks;
|
|
314
|
+
} else {
|
|
315
|
+
group.push({
|
|
316
|
+
args: {},
|
|
317
|
+
blocks: message.blocks,
|
|
318
|
+
callId: callId || message.id,
|
|
319
|
+
name: message.tool_name ?? "tool",
|
|
320
|
+
result: message.result,
|
|
321
|
+
status: "done"
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
flush();
|
|
326
|
+
return items;
|
|
327
|
+
}
|
|
328
|
+
function MessageItem({ message }) {
|
|
329
|
+
if (message.role === "user") {
|
|
330
|
+
const attachments = message.attachments ?? [];
|
|
331
|
+
return /* @__PURE__ */ jsxs(ChatMessage, { from: "user", children: [
|
|
332
|
+
attachments.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap justify-end gap-1.5", children: attachments.map((attachment) => /* @__PURE__ */ jsxs(
|
|
333
|
+
"span",
|
|
334
|
+
{
|
|
335
|
+
className: "flex max-w-52 items-center gap-1.5 rounded-lg border border-border/72 bg-muted/48 px-2 py-1 text-muted-foreground text-xs",
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-3.5 shrink-0" }),
|
|
338
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: attachment.filename })
|
|
339
|
+
]
|
|
340
|
+
},
|
|
341
|
+
attachment.id
|
|
342
|
+
)) }) : null,
|
|
343
|
+
message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) : null
|
|
344
|
+
] });
|
|
345
|
+
}
|
|
346
|
+
if (message.role === "assistant") {
|
|
347
|
+
if (!message.content || !message.content.trim()) return null;
|
|
348
|
+
return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) });
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
80
352
|
var NOISE_KEYS = /* @__PURE__ */ new Set([
|
|
81
353
|
"id",
|
|
82
354
|
"pk",
|
|
@@ -215,87 +487,328 @@ function toDisplayTable(block) {
|
|
|
215
487
|
});
|
|
216
488
|
return { columns, rows: displayRows };
|
|
217
489
|
}
|
|
490
|
+
|
|
491
|
+
// src/tool-presentation.ts
|
|
218
492
|
function humanizeToolName(toolName) {
|
|
219
493
|
return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
|
|
220
494
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
495
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
496
|
+
function isTechnicalKey(key) {
|
|
497
|
+
const lower = key.toLowerCase();
|
|
498
|
+
return lower === "id" || lower === "client_id" || lower === "slug" || lower.endsWith("_id") || lower.endsWith("_ids") || lower.endsWith("_key") || lower.endsWith("_slug") || lower.endsWith("_uuid") || lower === "uuid" || lower.endsWith("_s3_key") || lower === "embedding";
|
|
499
|
+
}
|
|
500
|
+
function isTechnicalValue(value) {
|
|
501
|
+
return typeof value === "string" && UUID_RE2.test(value.trim());
|
|
502
|
+
}
|
|
503
|
+
function humanizeKey(key) {
|
|
504
|
+
const spaced = key.replaceAll("_", " ").trim();
|
|
505
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
506
|
+
}
|
|
507
|
+
function formatValue(value) {
|
|
508
|
+
if (value === null || value === void 0) return "\u2014";
|
|
509
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
510
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
511
|
+
if (Array.isArray(value)) {
|
|
512
|
+
const items = value.filter((item) => !isTechnicalValue(item)).map((item) => formatValue(item));
|
|
513
|
+
return items.length ? items.join(", ") : `${value.length} item${value.length === 1 ? "" : "s"}`;
|
|
224
514
|
}
|
|
225
|
-
if (
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
515
|
+
if (typeof value === "object") {
|
|
516
|
+
const record = value;
|
|
517
|
+
for (const key of ["name", "title", "label", "email"]) {
|
|
518
|
+
if (typeof record[key] === "string") return record[key];
|
|
519
|
+
}
|
|
520
|
+
return JSON.stringify(value);
|
|
521
|
+
}
|
|
522
|
+
return String(value);
|
|
523
|
+
}
|
|
524
|
+
function deriveDetails(args) {
|
|
525
|
+
const details = [];
|
|
526
|
+
for (const [key, value] of Object.entries(args ?? {})) {
|
|
527
|
+
if (isTechnicalKey(key)) continue;
|
|
528
|
+
if (isTechnicalValue(value)) continue;
|
|
529
|
+
if (value === "" || value === null || value === void 0) continue;
|
|
530
|
+
const formatted = formatValue(value);
|
|
531
|
+
if (!formatted || formatted === "\u2014") continue;
|
|
532
|
+
details.push({ label: humanizeKey(key), value: formatted.length > 140 ? `${formatted.slice(0, 140)}\u2026` : formatted });
|
|
533
|
+
}
|
|
534
|
+
return details;
|
|
535
|
+
}
|
|
536
|
+
function resolveToolPresentation(describe, toolName, args) {
|
|
537
|
+
let description;
|
|
538
|
+
try {
|
|
539
|
+
description = describe?.(toolName, args ?? {});
|
|
540
|
+
} catch {
|
|
541
|
+
description = null;
|
|
542
|
+
}
|
|
543
|
+
const title = description?.title?.trim() || humanizeToolName(toolName);
|
|
544
|
+
const summary = description?.summary?.trim() || null;
|
|
545
|
+
let details = description?.details ?? [];
|
|
546
|
+
const wantsDerived = description?.showDerivedDetails ?? description?.details === void 0;
|
|
547
|
+
if (details.length === 0 && wantsDerived) {
|
|
548
|
+
details = deriveDetails(args ?? {});
|
|
236
549
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
550
|
+
return { details, summary, title };
|
|
551
|
+
}
|
|
552
|
+
function DownloadButton({ block, size = "sm" }) {
|
|
553
|
+
if (!block.download_url) {
|
|
554
|
+
return /* @__PURE__ */ jsx(Button, { disabled: true, size, variant: "outline", children: size === "icon-sm" ? /* @__PURE__ */ jsx(DownloadIcon, {}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
555
|
+
/* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
556
|
+
" Download"
|
|
557
|
+
] }) });
|
|
558
|
+
}
|
|
559
|
+
return /* @__PURE__ */ jsxs(
|
|
560
|
+
Button,
|
|
561
|
+
{
|
|
562
|
+
render: (
|
|
563
|
+
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
|
564
|
+
/* @__PURE__ */ jsx("a", { download: block.filename, href: block.download_url, rel: "noopener noreferrer", target: "_blank" })
|
|
565
|
+
),
|
|
566
|
+
size,
|
|
567
|
+
variant: size === "icon-sm" ? "outline" : "default",
|
|
568
|
+
children: [
|
|
569
|
+
/* @__PURE__ */ jsx(DownloadIcon, {}),
|
|
570
|
+
size === "icon-sm" ? null : "Download"
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
function DocumentViewer({ block, onClose }) {
|
|
576
|
+
React5.useEffect(() => {
|
|
577
|
+
const onKey = (event) => event.key === "Escape" && onClose();
|
|
578
|
+
window.addEventListener("keydown", onKey);
|
|
579
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
580
|
+
}, [onClose]);
|
|
581
|
+
return /* @__PURE__ */ jsxs("div", { "aria-modal": "true", className: "fixed inset-0 z-[60] flex flex-col bg-background", role: "dialog", children: [
|
|
582
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-border/72 border-b px-4 py-3", children: [
|
|
583
|
+
/* @__PURE__ */ jsx(FileTextIcon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
584
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
585
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-medium text-sm", children: block.title || block.filename }),
|
|
586
|
+
/* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-xs uppercase", children: [
|
|
587
|
+
block.format,
|
|
588
|
+
block.size_bytes ? ` \xB7 ${formatBytes(block.size_bytes)}` : ""
|
|
589
|
+
] })
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsx(DownloadButton, { block }),
|
|
592
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Close preview", onClick: onClose, size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ jsx(XIcon, {}) })
|
|
593
|
+
] }),
|
|
594
|
+
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-auto p-4", children: /* @__PURE__ */ jsxs("pre", { className: "mx-auto max-w-3xl whitespace-pre-wrap break-words font-mono text-xs leading-relaxed text-foreground", children: [
|
|
595
|
+
block.preview || "(no preview available \u2014 download to view)",
|
|
596
|
+
block.preview_truncated ? "\n\n\u2026 preview truncated. Download for the full file." : ""
|
|
597
|
+
] }) })
|
|
598
|
+
] });
|
|
599
|
+
}
|
|
600
|
+
function DocumentCard({ block }) {
|
|
601
|
+
const [open, setOpen] = React5.useState(false);
|
|
602
|
+
const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
|
|
603
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
|
|
604
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
|
|
605
|
+
/* @__PURE__ */ jsx("span", { className: "flex size-7 shrink-0 items-center justify-center rounded-lg bg-primary/8 text-primary [&_svg]:size-4", children: /* @__PURE__ */ jsx(FileTextIcon, {}) }),
|
|
606
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
607
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-medium text-sm", children: block.title || block.filename }),
|
|
608
|
+
/* @__PURE__ */ jsxs("div", { className: "text-muted-foreground text-xs uppercase", children: [
|
|
609
|
+
block.format,
|
|
610
|
+
block.size_bytes ? ` \xB7 ${formatBytes(block.size_bytes)}` : ""
|
|
611
|
+
] })
|
|
612
|
+
] }),
|
|
613
|
+
/* @__PURE__ */ jsx(Button, { "aria-label": "Preview", onClick: () => setOpen(true), size: "icon-sm", variant: "ghost", children: /* @__PURE__ */ jsx(Maximize2Icon, {}) }),
|
|
614
|
+
/* @__PURE__ */ jsx(DownloadButton, { block, size: "icon-sm" })
|
|
615
|
+
] }),
|
|
616
|
+
previewLines ? /* @__PURE__ */ jsx(
|
|
617
|
+
"button",
|
|
618
|
+
{
|
|
619
|
+
className: cn(
|
|
620
|
+
"block w-full cursor-pointer px-3 py-2 text-left transition-colors hover:bg-accent/40"
|
|
621
|
+
),
|
|
622
|
+
onClick: () => setOpen(true),
|
|
623
|
+
type: "button",
|
|
624
|
+
children: /* @__PURE__ */ jsx("pre", { className: "max-h-28 overflow-hidden whitespace-pre-wrap break-words font-mono text-[11px] leading-snug text-muted-foreground", children: previewLines })
|
|
625
|
+
}
|
|
626
|
+
) : null,
|
|
627
|
+
open ? /* @__PURE__ */ jsx(DocumentViewer, { block, onClose: () => setOpen(false) }) : null
|
|
628
|
+
] });
|
|
629
|
+
}
|
|
630
|
+
function TaskChecklist({ tasks }) {
|
|
631
|
+
if (!tasks || tasks.length === 0) return null;
|
|
632
|
+
const done = tasks.filter((task) => task.status === "done").length;
|
|
633
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full rounded-xl border border-border bg-card p-3 shadow-xs", children: [
|
|
634
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center gap-1.5 font-medium text-sm", children: [
|
|
635
|
+
/* @__PURE__ */ jsx(ListChecksIcon, { className: "size-4 text-muted-foreground" }),
|
|
636
|
+
"Tasks",
|
|
637
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto text-muted-foreground text-xs", children: [
|
|
638
|
+
done,
|
|
639
|
+
"/",
|
|
640
|
+
tasks.length
|
|
641
|
+
] })
|
|
642
|
+
] }),
|
|
643
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1.5", children: tasks.map((task, index) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-sm", children: [
|
|
644
|
+
task.status === "in_progress" ? /* @__PURE__ */ jsx("span", { className: "flex size-4 shrink-0 items-center justify-center [&_svg]:size-3.5", children: /* @__PURE__ */ jsx(Spinner, {}) }) : /* @__PURE__ */ jsx(
|
|
645
|
+
Checkbox,
|
|
646
|
+
{
|
|
647
|
+
"aria-hidden": true,
|
|
648
|
+
checked: task.status === "done",
|
|
649
|
+
className: "pointer-events-none shrink-0",
|
|
650
|
+
readOnly: true,
|
|
651
|
+
tabIndex: -1
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
/* @__PURE__ */ jsx(
|
|
655
|
+
"span",
|
|
656
|
+
{
|
|
657
|
+
className: cn(
|
|
658
|
+
"min-w-0",
|
|
659
|
+
task.status === "done" && "text-muted-foreground line-through",
|
|
660
|
+
task.status === "in_progress" && "font-medium"
|
|
661
|
+
),
|
|
662
|
+
children: task.title
|
|
663
|
+
}
|
|
664
|
+
)
|
|
665
|
+
] }, `${index}-${task.title}`)) })
|
|
666
|
+
] });
|
|
667
|
+
}
|
|
668
|
+
function StepBlocks({ blocks, stepKey }) {
|
|
669
|
+
const visible = (blocks ?? []).filter(
|
|
670
|
+
(block) => block.type === "table" || block.type === "document" || block.type === "tasks"
|
|
671
|
+
);
|
|
672
|
+
if (visible.length === 0) return null;
|
|
673
|
+
return /* @__PURE__ */ jsx(Fragment, { children: visible.map((block, index) => {
|
|
674
|
+
const key = `${stepKey}-block-${index}`;
|
|
675
|
+
if (block.type === "document") {
|
|
676
|
+
return /* @__PURE__ */ jsx(DocumentCard, { block }, key);
|
|
677
|
+
}
|
|
678
|
+
if (block.type === "tasks") {
|
|
679
|
+
return /* @__PURE__ */ jsx(TaskChecklist, { tasks: block.tasks }, key);
|
|
680
|
+
}
|
|
681
|
+
const table = block;
|
|
682
|
+
const { columns, rows } = toDisplayTable(table);
|
|
240
683
|
return /* @__PURE__ */ jsx("div", { className: "w-full max-w-full", children: /* @__PURE__ */ jsx(
|
|
241
684
|
DataTablePaged,
|
|
242
685
|
{
|
|
243
686
|
columns,
|
|
244
687
|
pageSize: 8,
|
|
245
688
|
rows,
|
|
246
|
-
totalCount: typeof
|
|
689
|
+
totalCount: typeof table.pagination?.count === "number" ? table.pagination.count : table.row_count
|
|
247
690
|
}
|
|
248
|
-
) },
|
|
249
|
-
})
|
|
250
|
-
humanizeToolName(message.tool_name ?? "tool"),
|
|
251
|
-
" finished"
|
|
252
|
-
] }) });
|
|
691
|
+
) }, key);
|
|
692
|
+
}) });
|
|
253
693
|
}
|
|
254
|
-
function
|
|
255
|
-
return
|
|
694
|
+
function RawJsonDisclosure({ result }) {
|
|
695
|
+
return /* @__PURE__ */ jsxs(Collapsible, { className: "mt-1.5", children: [
|
|
696
|
+
/* @__PURE__ */ jsxs(CollapsibleTrigger, { className: "group flex items-center gap-1 text-muted-foreground text-xs hover:text-foreground", children: [
|
|
697
|
+
/* @__PURE__ */ jsx(CodeIcon, { className: "size-3" }),
|
|
698
|
+
/* @__PURE__ */ jsx("span", { children: "Raw JSON" }),
|
|
699
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 transition-transform group-data-[panel-open]:rotate-180" })
|
|
700
|
+
] }),
|
|
701
|
+
/* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("pre", { className: "mt-1 max-h-64 overflow-auto rounded-lg bg-muted p-2 font-mono text-[11px] leading-snug text-muted-foreground", children: result }) })
|
|
702
|
+
] });
|
|
703
|
+
}
|
|
704
|
+
function StepDetail({
|
|
705
|
+
describeToolCall,
|
|
706
|
+
step
|
|
707
|
+
}) {
|
|
708
|
+
const { details, summary, title } = resolveToolPresentation(describeToolCall, step.name, step.args);
|
|
709
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border/72 bg-card p-2.5", children: [
|
|
710
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 font-medium text-sm", children: [
|
|
711
|
+
step.status === "running" ? /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-warning" }) : /* @__PURE__ */ jsx(CheckIcon, { className: "size-3.5 text-success" }),
|
|
712
|
+
title
|
|
713
|
+
] }),
|
|
714
|
+
summary ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-muted-foreground text-xs", children: summary }) : null,
|
|
715
|
+
details.length > 0 ? /* @__PURE__ */ jsx("dl", { className: "mt-1.5 space-y-0.5 text-xs", children: details.map((detail) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
716
|
+
/* @__PURE__ */ jsx("dt", { className: "shrink-0 text-muted-foreground", children: detail.label }),
|
|
717
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words", children: detail.value })
|
|
718
|
+
] }, detail.label)) }) : null,
|
|
719
|
+
step.result ? /* @__PURE__ */ jsx(RawJsonDisclosure, { result: step.result }) : null
|
|
720
|
+
] });
|
|
721
|
+
}
|
|
722
|
+
function ToolActivityGroup({ describeToolCall, steps }) {
|
|
723
|
+
const [open, setOpen] = React5.useState(false);
|
|
724
|
+
if (steps.length === 0) return null;
|
|
725
|
+
const last = steps[steps.length - 1];
|
|
726
|
+
const anyRunning = steps.some((step) => step.status === "running");
|
|
727
|
+
const lastTitle = resolveToolPresentation(describeToolCall, last.name, last.args).title || humanizeToolName(last.name);
|
|
728
|
+
const chipLabel = anyRunning ? `${lastTitle}\u2026` : steps.length > 1 ? lastTitle : `${lastTitle} finished`;
|
|
729
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2", children: [
|
|
730
|
+
/* @__PURE__ */ jsxs(Collapsible, { onOpenChange: setOpen, open, children: [
|
|
731
|
+
/* @__PURE__ */ jsx(
|
|
732
|
+
CollapsibleTrigger,
|
|
733
|
+
{
|
|
734
|
+
render: /* @__PURE__ */ jsx(
|
|
735
|
+
ChatToolChip,
|
|
736
|
+
{
|
|
737
|
+
className: "cursor-pointer pr-2 hover:bg-muted",
|
|
738
|
+
icon: anyRunning ? /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-pulse rounded-full bg-warning" }) : /* @__PURE__ */ jsx(CheckIcon, {}),
|
|
739
|
+
children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
740
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: chipLabel }),
|
|
741
|
+
steps.length > 1 ? /* @__PURE__ */ jsxs("span", { className: "rounded-full bg-background px-1.5 text-[10px] text-foreground/72", children: [
|
|
742
|
+
steps.length,
|
|
743
|
+
" steps"
|
|
744
|
+
] }) : null,
|
|
745
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, { className: cn("size-3 transition-transform", open && "rotate-180") })
|
|
746
|
+
] })
|
|
747
|
+
}
|
|
748
|
+
)
|
|
749
|
+
}
|
|
750
|
+
),
|
|
751
|
+
/* @__PURE__ */ jsx(CollapsiblePanel, { children: /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-1.5", children: steps.map((step) => /* @__PURE__ */ jsx(StepDetail, { describeToolCall, step }, step.callId)) }) })
|
|
752
|
+
] }),
|
|
753
|
+
steps.map((step) => /* @__PURE__ */ jsx(StepBlocks, { blocks: step.blocks, stepKey: step.callId }, `${step.callId}-blocks`))
|
|
754
|
+
] });
|
|
256
755
|
}
|
|
257
|
-
function ToolApprovalCard({
|
|
258
|
-
|
|
756
|
+
function ToolApprovalCard({
|
|
757
|
+
approval,
|
|
758
|
+
describeToolCall,
|
|
759
|
+
onAlwaysApprove,
|
|
760
|
+
onResolve,
|
|
761
|
+
resolving
|
|
762
|
+
}) {
|
|
763
|
+
const { details, summary, title } = resolveToolPresentation(
|
|
764
|
+
describeToolCall,
|
|
765
|
+
approval.tool_name,
|
|
766
|
+
approval.arguments ?? {}
|
|
767
|
+
);
|
|
259
768
|
return /* @__PURE__ */ jsxs(ChatToolCard, { className: "border-warning/40", children: [
|
|
260
769
|
/* @__PURE__ */ jsxs(ChatToolCardHeader, { children: [
|
|
261
770
|
/* @__PURE__ */ jsx(ShieldAlertIcon, { className: "size-4 text-warning" }),
|
|
262
771
|
"Approval needed"
|
|
263
772
|
] }),
|
|
264
|
-
/* @__PURE__ */
|
|
265
|
-
"The assistant wants to
|
|
266
|
-
" ",
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
/* @__PURE__ */ jsxs("dt", { className: "shrink-0 text-muted-foreground", children: [
|
|
274
|
-
key,
|
|
275
|
-
":"
|
|
276
|
-
] }),
|
|
277
|
-
/* @__PURE__ */ jsx("dd", { className: "truncate", children: typeof value === "object" ? JSON.stringify(value) : String(value) })
|
|
278
|
-
] }, key)),
|
|
279
|
-
argumentEntries.length > 8 && /* @__PURE__ */ jsxs("div", { className: "text-muted-foreground", children: [
|
|
280
|
-
"\u2026and ",
|
|
281
|
-
argumentEntries.length - 8,
|
|
282
|
-
" more"
|
|
283
|
-
] })
|
|
284
|
-
] }),
|
|
773
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-muted-foreground", children: summary ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
774
|
+
"The assistant wants to ",
|
|
775
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: title.toLowerCase() }),
|
|
776
|
+
" \u2014 this will change your data."
|
|
777
|
+
] }) }),
|
|
778
|
+
details.length > 0 && /* @__PURE__ */ jsx("dl", { className: "mt-2 space-y-1 rounded-lg bg-muted p-2 text-xs", children: details.map((detail) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
779
|
+
/* @__PURE__ */ jsx("dt", { className: "shrink-0 text-muted-foreground", children: detail.label }),
|
|
780
|
+
/* @__PURE__ */ jsx("dd", { className: "min-w-0 break-words font-medium text-foreground", children: detail.value })
|
|
781
|
+
] }, detail.label)) }),
|
|
285
782
|
/* @__PURE__ */ jsxs(ChatToolCardActions, { children: [
|
|
286
|
-
/* @__PURE__ */ jsx(
|
|
287
|
-
|
|
288
|
-
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
783
|
+
/* @__PURE__ */ jsx(Button, { disabled: resolving, onClick: () => onResolve(approval.id, false), size: "sm", variant: "outline", children: "Decline" }),
|
|
784
|
+
/* @__PURE__ */ jsxs(Group, { children: [
|
|
785
|
+
/* @__PURE__ */ jsxs(Button, { disabled: resolving, onClick: () => onResolve(approval.id, true), size: "sm", children: [
|
|
786
|
+
resolving ? /* @__PURE__ */ jsx(Spinner, {}) : null,
|
|
787
|
+
"Approve"
|
|
788
|
+
] }),
|
|
789
|
+
/* @__PURE__ */ jsxs(Menu, { children: [
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
MenuTrigger,
|
|
792
|
+
{
|
|
793
|
+
render: /* @__PURE__ */ jsx(Button, { "aria-label": "More approve options", disabled: resolving, size: "icon-sm", variant: "default", children: /* @__PURE__ */ jsx(ChevronDownIcon, {}) })
|
|
794
|
+
}
|
|
795
|
+
),
|
|
796
|
+
/* @__PURE__ */ jsx(MenuPopup, { align: "end", children: /* @__PURE__ */ jsxs(
|
|
797
|
+
MenuItem,
|
|
798
|
+
{
|
|
799
|
+
onClick: () => {
|
|
800
|
+
onAlwaysApprove(approval.tool_name);
|
|
801
|
+
onResolve(approval.id, true);
|
|
802
|
+
},
|
|
803
|
+
children: [
|
|
804
|
+
/* @__PURE__ */ jsx(CheckCheckIcon, {}),
|
|
805
|
+
"Always approve \u201C",
|
|
806
|
+
title,
|
|
807
|
+
"\u201D"
|
|
808
|
+
]
|
|
809
|
+
}
|
|
810
|
+
) })
|
|
811
|
+
] })
|
|
299
812
|
] })
|
|
300
813
|
] })
|
|
301
814
|
] });
|
|
@@ -306,24 +819,77 @@ function AgentPanel({
|
|
|
306
819
|
chat,
|
|
307
820
|
className,
|
|
308
821
|
config,
|
|
822
|
+
onAlwaysApprove,
|
|
309
823
|
onClose,
|
|
310
824
|
onToggleAgentMode
|
|
311
825
|
}) {
|
|
312
|
-
const [draft, setDraft] =
|
|
313
|
-
const [view, setView] =
|
|
826
|
+
const [draft, setDraft] = React5.useState("");
|
|
827
|
+
const [view, setView] = React5.useState("chat");
|
|
828
|
+
const [files, setFiles] = React5.useState([]);
|
|
829
|
+
const [uploadError, setUploadError] = React5.useState(null);
|
|
830
|
+
const [dragging, setDragging] = React5.useState(false);
|
|
831
|
+
const dragDepth = React5.useRef(0);
|
|
314
832
|
const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
|
|
833
|
+
const uploadEnabled = fileUploadEnabled(config);
|
|
315
834
|
const suggestions = config.suggestions ?? [
|
|
316
835
|
"What can you help me with?",
|
|
317
836
|
"Give me a quick summary of my data."
|
|
318
837
|
];
|
|
319
|
-
const
|
|
838
|
+
const addFiles = React5.useCallback(
|
|
839
|
+
(incoming) => {
|
|
840
|
+
const accepted = [];
|
|
841
|
+
let rejection = null;
|
|
842
|
+
for (const file of incoming) {
|
|
843
|
+
const error = validateFile(config, file);
|
|
844
|
+
if (error) {
|
|
845
|
+
rejection = error;
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
accepted.push(file);
|
|
849
|
+
}
|
|
850
|
+
setUploadError(rejection);
|
|
851
|
+
if (accepted.length) setFiles((current) => [...current, ...accepted].slice(0, 10));
|
|
852
|
+
},
|
|
853
|
+
[config]
|
|
854
|
+
);
|
|
855
|
+
const removeFile = React5.useCallback((index) => {
|
|
856
|
+
setFiles((current) => current.filter((_, i) => i !== index));
|
|
857
|
+
}, []);
|
|
858
|
+
const handleSubmit = React5.useCallback(
|
|
320
859
|
(text) => {
|
|
860
|
+
const staged = files;
|
|
321
861
|
setDraft("");
|
|
322
|
-
|
|
862
|
+
setFiles([]);
|
|
863
|
+
setUploadError(null);
|
|
864
|
+
void chat.send(text, staged);
|
|
865
|
+
},
|
|
866
|
+
[chat, files]
|
|
867
|
+
);
|
|
868
|
+
const onDragEnter = React5.useCallback(
|
|
869
|
+
(event) => {
|
|
870
|
+
if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
|
|
871
|
+
dragDepth.current += 1;
|
|
872
|
+
setDragging(true);
|
|
323
873
|
},
|
|
324
|
-
[
|
|
874
|
+
[uploadEnabled]
|
|
325
875
|
);
|
|
326
|
-
const
|
|
876
|
+
const onDragLeave = React5.useCallback((event) => {
|
|
877
|
+
if (!event.dataTransfer?.types?.includes("Files")) return;
|
|
878
|
+
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
879
|
+
if (dragDepth.current === 0) setDragging(false);
|
|
880
|
+
}, []);
|
|
881
|
+
const onDrop = React5.useCallback(
|
|
882
|
+
(event) => {
|
|
883
|
+
if (!uploadEnabled) return;
|
|
884
|
+
event.preventDefault();
|
|
885
|
+
dragDepth.current = 0;
|
|
886
|
+
setDragging(false);
|
|
887
|
+
const dropped = Array.from(event.dataTransfer?.files ?? []);
|
|
888
|
+
if (dropped.length) addFiles(dropped);
|
|
889
|
+
},
|
|
890
|
+
[addFiles, uploadEnabled]
|
|
891
|
+
);
|
|
892
|
+
const openHistory = React5.useCallback(() => {
|
|
327
893
|
setView("history");
|
|
328
894
|
void chat.refreshConversations();
|
|
329
895
|
}, [chat]);
|
|
@@ -332,11 +898,20 @@ function AgentPanel({
|
|
|
332
898
|
"div",
|
|
333
899
|
{
|
|
334
900
|
className: cn(
|
|
335
|
-
"flex min-h-0 flex-col overflow-hidden bg-background text-foreground",
|
|
901
|
+
"relative flex min-h-0 flex-col overflow-hidden bg-background text-foreground",
|
|
336
902
|
className
|
|
337
903
|
),
|
|
338
904
|
"data-slot": "agent-panel",
|
|
905
|
+
onDragEnter,
|
|
906
|
+
onDragLeave,
|
|
907
|
+
onDragOver: uploadEnabled ? (event) => event.preventDefault() : void 0,
|
|
908
|
+
onDrop,
|
|
339
909
|
children: [
|
|
910
|
+
dragging && view === "chat" ? /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-40 flex flex-col items-center justify-center gap-2 bg-background/85 backdrop-blur-sm", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 rounded-2xl border-2 border-primary/40 border-dashed px-8 py-6 text-center", children: [
|
|
911
|
+
/* @__PURE__ */ jsx(UploadCloudIcon, { className: "size-8 text-primary" }),
|
|
912
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-sm", children: "Drop text files to attach" }),
|
|
913
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs", children: acceptAttribute(config).replaceAll(",", " \xB7 ") })
|
|
914
|
+
] }) }) : null,
|
|
340
915
|
/* @__PURE__ */ jsxs(ChatHeader, { children: [
|
|
341
916
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2.5", children: [
|
|
342
917
|
/* @__PURE__ */ jsx(ChatHeaderAvatar, {}),
|
|
@@ -461,11 +1036,15 @@ function AgentPanel({
|
|
|
461
1036
|
suggestion
|
|
462
1037
|
)) })
|
|
463
1038
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
464
|
-
chat.messages.map(
|
|
1039
|
+
groupMessages(chat.messages).map(
|
|
1040
|
+
(item) => item.kind === "tool-activity" ? /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(ToolActivityGroup, { describeToolCall: config.describeToolCall, steps: item.steps }) }, item.key) : /* @__PURE__ */ jsx(MessageItem, { message: item.message }, item.key)
|
|
1041
|
+
),
|
|
465
1042
|
chat.pendingApprovals.map((approval) => /* @__PURE__ */ jsx(
|
|
466
1043
|
ToolApprovalCard,
|
|
467
1044
|
{
|
|
468
1045
|
approval,
|
|
1046
|
+
describeToolCall: config.describeToolCall,
|
|
1047
|
+
onAlwaysApprove,
|
|
469
1048
|
onResolve: (callId, approve) => void chat.resolveApproval(callId, approve),
|
|
470
1049
|
resolving: chat.resolvingApprovalId === approval.id
|
|
471
1050
|
},
|
|
@@ -473,15 +1052,30 @@ function AgentPanel({
|
|
|
473
1052
|
)),
|
|
474
1053
|
busy ? /* @__PURE__ */ jsx(ChatTypingIndicator, {}) : null
|
|
475
1054
|
] }) }),
|
|
476
|
-
chat.error ? /* @__PURE__ */ jsxs("div", { className: "mx-3 mb-1 flex items-center justify-between gap-2 rounded-lg bg-destructive/10 px-3 py-2 text-destructive text-xs", children: [
|
|
477
|
-
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: chat.error }),
|
|
478
|
-
/* @__PURE__ */ jsx(
|
|
1055
|
+
chat.error || uploadError ? /* @__PURE__ */ jsxs("div", { className: "mx-3 mb-1 flex items-center justify-between gap-2 rounded-lg bg-destructive/10 px-3 py-2 text-destructive text-xs", children: [
|
|
1056
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: chat.error ?? uploadError }),
|
|
1057
|
+
/* @__PURE__ */ jsx(
|
|
1058
|
+
"button",
|
|
1059
|
+
{
|
|
1060
|
+
className: "shrink-0 cursor-pointer font-medium",
|
|
1061
|
+
onClick: () => {
|
|
1062
|
+
chat.clearError();
|
|
1063
|
+
setUploadError(null);
|
|
1064
|
+
},
|
|
1065
|
+
type: "button",
|
|
1066
|
+
children: "Dismiss"
|
|
1067
|
+
}
|
|
1068
|
+
)
|
|
479
1069
|
] }) : null,
|
|
480
1070
|
/* @__PURE__ */ jsx(
|
|
481
|
-
|
|
1071
|
+
AgentComposer,
|
|
482
1072
|
{
|
|
1073
|
+
acceptAttribute: acceptAttribute(config),
|
|
483
1074
|
className: cn(agentMode && "mx-auto w-full max-w-3xl"),
|
|
484
1075
|
disabled: busy || chat.pendingApprovals.length > 0,
|
|
1076
|
+
files,
|
|
1077
|
+
onPickFiles: uploadEnabled ? addFiles : void 0,
|
|
1078
|
+
onRemoveFile: removeFile,
|
|
485
1079
|
onSubmit: handleSubmit,
|
|
486
1080
|
onValueChange: setDraft,
|
|
487
1081
|
placeholder: chat.pendingApprovals.length > 0 ? "Approve or decline the pending action first\u2026" : `Ask ${agentName}\u2026`,
|
|
@@ -494,16 +1088,16 @@ function AgentPanel({
|
|
|
494
1088
|
);
|
|
495
1089
|
}
|
|
496
1090
|
function useAgentChat(config) {
|
|
497
|
-
const client =
|
|
498
|
-
const [agentInfo, setAgentInfo] =
|
|
499
|
-
const [conversationId, setConversationId] =
|
|
500
|
-
const [conversations, setConversations] =
|
|
501
|
-
const [messages, setMessages] =
|
|
502
|
-
const [pendingApprovals, setPendingApprovals] =
|
|
503
|
-
const [sending, setSending] =
|
|
504
|
-
const [resolvingApprovalId, setResolvingApprovalId] =
|
|
505
|
-
const [error, setError] =
|
|
506
|
-
|
|
1091
|
+
const client = React5.useMemo(() => new AgentApiClient(config), [config]);
|
|
1092
|
+
const [agentInfo, setAgentInfo] = React5.useState(null);
|
|
1093
|
+
const [conversationId, setConversationId] = React5.useState(null);
|
|
1094
|
+
const [conversations, setConversations] = React5.useState([]);
|
|
1095
|
+
const [messages, setMessages] = React5.useState([]);
|
|
1096
|
+
const [pendingApprovals, setPendingApprovals] = React5.useState([]);
|
|
1097
|
+
const [sending, setSending] = React5.useState(false);
|
|
1098
|
+
const [resolvingApprovalId, setResolvingApprovalId] = React5.useState(null);
|
|
1099
|
+
const [error, setError] = React5.useState(null);
|
|
1100
|
+
React5.useEffect(() => {
|
|
507
1101
|
let cancelled = false;
|
|
508
1102
|
client.getConfig().then((info) => {
|
|
509
1103
|
if (!cancelled) setAgentInfo(info);
|
|
@@ -513,11 +1107,11 @@ function useAgentChat(config) {
|
|
|
513
1107
|
cancelled = true;
|
|
514
1108
|
};
|
|
515
1109
|
}, [client]);
|
|
516
|
-
const handleFailure =
|
|
1110
|
+
const handleFailure = React5.useCallback((err) => {
|
|
517
1111
|
const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
|
|
518
1112
|
setError(message);
|
|
519
1113
|
}, []);
|
|
520
|
-
const applyResponse =
|
|
1114
|
+
const applyResponse = React5.useCallback(
|
|
521
1115
|
(response) => {
|
|
522
1116
|
if (response.status === "error" && !response.messages?.length) {
|
|
523
1117
|
setError(response.message ?? "The assistant could not answer.");
|
|
@@ -538,13 +1132,14 @@ function useAgentChat(config) {
|
|
|
538
1132
|
},
|
|
539
1133
|
[]
|
|
540
1134
|
);
|
|
541
|
-
const send =
|
|
542
|
-
async (text) => {
|
|
1135
|
+
const send = React5.useCallback(
|
|
1136
|
+
async (text, files) => {
|
|
543
1137
|
if (sending) return;
|
|
1138
|
+
if (!text.trim() && !(files && files.length)) return;
|
|
544
1139
|
setSending(true);
|
|
545
1140
|
setError(null);
|
|
546
1141
|
try {
|
|
547
|
-
const response = await client.sendMessage(text, conversationId);
|
|
1142
|
+
const response = await client.sendMessage(text, conversationId, files);
|
|
548
1143
|
applyResponse(response);
|
|
549
1144
|
} catch (err) {
|
|
550
1145
|
handleFailure(err);
|
|
@@ -554,7 +1149,7 @@ function useAgentChat(config) {
|
|
|
554
1149
|
},
|
|
555
1150
|
[applyResponse, client, conversationId, handleFailure, sending]
|
|
556
1151
|
);
|
|
557
|
-
const resolveApproval =
|
|
1152
|
+
const resolveApproval = React5.useCallback(
|
|
558
1153
|
async (callId, approve) => {
|
|
559
1154
|
if (resolvingApprovalId) return;
|
|
560
1155
|
setResolvingApprovalId(callId);
|
|
@@ -570,13 +1165,13 @@ function useAgentChat(config) {
|
|
|
570
1165
|
},
|
|
571
1166
|
[applyResponse, client, handleFailure, resolvingApprovalId]
|
|
572
1167
|
);
|
|
573
|
-
const newChat =
|
|
1168
|
+
const newChat = React5.useCallback(() => {
|
|
574
1169
|
setConversationId(null);
|
|
575
1170
|
setMessages([]);
|
|
576
1171
|
setPendingApprovals([]);
|
|
577
1172
|
setError(null);
|
|
578
1173
|
}, []);
|
|
579
|
-
const refreshConversations =
|
|
1174
|
+
const refreshConversations = React5.useCallback(async () => {
|
|
580
1175
|
try {
|
|
581
1176
|
const { conversations: list } = await client.listConversations();
|
|
582
1177
|
setConversations(list);
|
|
@@ -584,7 +1179,7 @@ function useAgentChat(config) {
|
|
|
584
1179
|
handleFailure(err);
|
|
585
1180
|
}
|
|
586
1181
|
}, [client, handleFailure]);
|
|
587
|
-
const openConversation =
|
|
1182
|
+
const openConversation = React5.useCallback(
|
|
588
1183
|
async (id) => {
|
|
589
1184
|
setError(null);
|
|
590
1185
|
try {
|
|
@@ -598,7 +1193,7 @@ function useAgentChat(config) {
|
|
|
598
1193
|
},
|
|
599
1194
|
[client, handleFailure]
|
|
600
1195
|
);
|
|
601
|
-
const deleteConversation =
|
|
1196
|
+
const deleteConversation = React5.useCallback(
|
|
602
1197
|
async (id) => {
|
|
603
1198
|
try {
|
|
604
1199
|
await client.deleteConversation(id);
|
|
@@ -610,7 +1205,7 @@ function useAgentChat(config) {
|
|
|
610
1205
|
},
|
|
611
1206
|
[client, conversationId, handleFailure, newChat]
|
|
612
1207
|
);
|
|
613
|
-
const clearError =
|
|
1208
|
+
const clearError = React5.useCallback(() => setError(null), []);
|
|
614
1209
|
return {
|
|
615
1210
|
agentInfo,
|
|
616
1211
|
clearError,
|
|
@@ -629,26 +1224,86 @@ function useAgentChat(config) {
|
|
|
629
1224
|
sending
|
|
630
1225
|
};
|
|
631
1226
|
}
|
|
1227
|
+
var STORAGE_PREFIX = "cc-agent-always-approve:";
|
|
1228
|
+
function storageKey(clientId) {
|
|
1229
|
+
return `${STORAGE_PREFIX}${clientId || "default"}`;
|
|
1230
|
+
}
|
|
1231
|
+
function readStored(clientId) {
|
|
1232
|
+
if (typeof window === "undefined") return /* @__PURE__ */ new Set();
|
|
1233
|
+
try {
|
|
1234
|
+
const raw = window.localStorage.getItem(storageKey(clientId));
|
|
1235
|
+
if (!raw) return /* @__PURE__ */ new Set();
|
|
1236
|
+
const parsed = JSON.parse(raw);
|
|
1237
|
+
return Array.isArray(parsed) ? new Set(parsed.filter((v) => typeof v === "string")) : /* @__PURE__ */ new Set();
|
|
1238
|
+
} catch {
|
|
1239
|
+
return /* @__PURE__ */ new Set();
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
function useAlwaysApprove(clientId) {
|
|
1243
|
+
const [approved, setApproved] = React5.useState(() => /* @__PURE__ */ new Set());
|
|
1244
|
+
React5.useEffect(() => {
|
|
1245
|
+
setApproved(readStored(clientId));
|
|
1246
|
+
}, [clientId]);
|
|
1247
|
+
const persist = React5.useCallback(
|
|
1248
|
+
(next) => {
|
|
1249
|
+
setApproved(next);
|
|
1250
|
+
if (typeof window === "undefined") return;
|
|
1251
|
+
try {
|
|
1252
|
+
window.localStorage.setItem(storageKey(clientId), JSON.stringify([...next]));
|
|
1253
|
+
} catch {
|
|
1254
|
+
}
|
|
1255
|
+
},
|
|
1256
|
+
[clientId]
|
|
1257
|
+
);
|
|
1258
|
+
const isAlwaysApproved = React5.useCallback((toolName) => approved.has(toolName), [approved]);
|
|
1259
|
+
const setAlwaysApprove = React5.useCallback(
|
|
1260
|
+
(toolName) => {
|
|
1261
|
+
if (approved.has(toolName)) return;
|
|
1262
|
+
const next = new Set(approved);
|
|
1263
|
+
next.add(toolName);
|
|
1264
|
+
persist(next);
|
|
1265
|
+
},
|
|
1266
|
+
[approved, persist]
|
|
1267
|
+
);
|
|
1268
|
+
const clearAlwaysApprove = React5.useCallback(
|
|
1269
|
+
(toolName) => {
|
|
1270
|
+
if (!approved.has(toolName)) return;
|
|
1271
|
+
const next = new Set(approved);
|
|
1272
|
+
next.delete(toolName);
|
|
1273
|
+
persist(next);
|
|
1274
|
+
},
|
|
1275
|
+
[approved, persist]
|
|
1276
|
+
);
|
|
1277
|
+
return { clearAlwaysApprove, isAlwaysApproved, setAlwaysApprove };
|
|
1278
|
+
}
|
|
632
1279
|
function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
633
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
1280
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React5.useState(false);
|
|
634
1281
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
635
|
-
const setOpen =
|
|
1282
|
+
const setOpen = React5.useCallback(
|
|
636
1283
|
(next) => {
|
|
637
1284
|
setUncontrolledOpen(next);
|
|
638
1285
|
onOpenChange?.(next);
|
|
639
1286
|
},
|
|
640
1287
|
[onOpenChange]
|
|
641
1288
|
);
|
|
642
|
-
const [agentMode, setAgentMode] =
|
|
1289
|
+
const [agentMode, setAgentMode] = React5.useState(false);
|
|
643
1290
|
const isDesktop = useMediaQuery("(min-width: 640px)", {
|
|
644
1291
|
defaultSSRValue: true,
|
|
645
1292
|
ssr: true
|
|
646
1293
|
});
|
|
647
1294
|
const fullscreen = !isDesktop || agentMode;
|
|
648
1295
|
const chat = useAgentChat(config);
|
|
649
|
-
const
|
|
1296
|
+
const alwaysApprove = useAlwaysApprove(config.clientId);
|
|
1297
|
+
const { pendingApprovals, resolveApproval, resolvingApprovalId } = chat;
|
|
1298
|
+
const { isAlwaysApproved } = alwaysApprove;
|
|
1299
|
+
React5.useEffect(() => {
|
|
1300
|
+
if (!open || resolvingApprovalId) return;
|
|
1301
|
+
const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
|
|
1302
|
+
if (target) void resolveApproval(target.id, true);
|
|
1303
|
+
}, [open, pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
|
|
1304
|
+
const lastNavigatedMessageId = React5.useRef(null);
|
|
650
1305
|
const { messages } = chat;
|
|
651
|
-
|
|
1306
|
+
React5.useEffect(() => {
|
|
652
1307
|
if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
|
|
653
1308
|
const toolMessages = messages.filter((message) => message.role === "tool");
|
|
654
1309
|
const latest = toolMessages[toolMessages.length - 1];
|
|
@@ -660,7 +1315,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
660
1315
|
const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
|
|
661
1316
|
if (path) config.navigate(path);
|
|
662
1317
|
}, [config, fullscreen, messages, open]);
|
|
663
|
-
const closePanel =
|
|
1318
|
+
const closePanel = React5.useCallback(() => {
|
|
664
1319
|
setOpen(false);
|
|
665
1320
|
setAgentMode(false);
|
|
666
1321
|
}, [setOpen]);
|
|
@@ -688,6 +1343,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
688
1343
|
chat,
|
|
689
1344
|
className: "flex-1",
|
|
690
1345
|
config,
|
|
1346
|
+
onAlwaysApprove: alwaysApprove.setAlwaysApprove,
|
|
691
1347
|
onClose: closePanel,
|
|
692
1348
|
onToggleAgentMode: () => setAgentMode((value) => !value)
|
|
693
1349
|
}
|
|
@@ -713,6 +1369,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
713
1369
|
chat,
|
|
714
1370
|
className: "flex-1",
|
|
715
1371
|
config,
|
|
1372
|
+
onAlwaysApprove: alwaysApprove.setAlwaysApprove,
|
|
716
1373
|
onClose: closePanel,
|
|
717
1374
|
onToggleAgentMode: () => setAgentMode(true)
|
|
718
1375
|
}
|
|
@@ -737,6 +1394,6 @@ function defineAgentConfig(config) {
|
|
|
737
1394
|
return config;
|
|
738
1395
|
}
|
|
739
1396
|
|
|
740
|
-
export { AgentApiClient, AgentApiError, AgentPanel, AgentWidget, MessageItem, ToolApprovalCard, defineAgentConfig, useAgentChat };
|
|
1397
|
+
export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
|
|
741
1398
|
//# sourceMappingURL=index.js.map
|
|
742
1399
|
//# sourceMappingURL=index.js.map
|