@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.
Files changed (34) hide show
  1. package/dist/api-client.d.ts +1 -1
  2. package/dist/api-client.d.ts.map +1 -1
  3. package/dist/components/agent-composer.d.ts +23 -0
  4. package/dist/components/agent-composer.d.ts.map +1 -0
  5. package/dist/components/agent-panel.d.ts +3 -1
  6. package/dist/components/agent-panel.d.ts.map +1 -1
  7. package/dist/components/agent-widget.d.ts.map +1 -1
  8. package/dist/components/document-card.d.ts +11 -0
  9. package/dist/components/document-card.d.ts.map +1 -0
  10. package/dist/components/message-item.d.ts +5 -1
  11. package/dist/components/message-item.d.ts.map +1 -1
  12. package/dist/components/task-checklist.d.ts +11 -0
  13. package/dist/components/task-checklist.d.ts.map +1 -0
  14. package/dist/components/tool-activity-group.d.ts +15 -0
  15. package/dist/components/tool-activity-group.d.ts.map +1 -0
  16. package/dist/components/tool-approval-card.d.ts +10 -5
  17. package/dist/components/tool-approval-card.d.ts.map +1 -1
  18. package/dist/file-support.d.ts +13 -0
  19. package/dist/file-support.d.ts.map +1 -0
  20. package/dist/index.d.ts +9 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +768 -111
  23. package/dist/index.js.map +1 -1
  24. package/dist/message-group.d.ts +37 -0
  25. package/dist/message-group.d.ts.map +1 -0
  26. package/dist/tool-presentation.d.ts +20 -0
  27. package/dist/tool-presentation.d.ts.map +1 -0
  28. package/dist/types.d.ts +91 -1
  29. package/dist/types.d.ts.map +1 -1
  30. package/dist/use-agent-chat.d.ts +1 -1
  31. package/dist/use-agent-chat.d.ts.map +1 -1
  32. package/dist/use-always-approve.d.ts +15 -0
  33. package/dist/use-always-approve.d.ts.map +1 -0
  34. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { ChatMessage, ChatMessageBubble, Markdown, ChatToolChip, DataTablePaged, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Button, Spinner, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, ChatTypingIndicator, cn, ChatInput, useMediaQuery, TooltipProvider, Fab } from '@cntyclub/ui-react';
2
- import { WrenchIcon, CheckIcon, ShieldAlertIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Maximize2Icon, XIcon, Trash2Icon, SparklesIcon } from 'lucide-react';
3
- import * as React2 from 'react';
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
- function MessageItem({ message }) {
222
- if (message.role === "user") {
223
- return /* @__PURE__ */ jsx(ChatMessage, { from: "user", children: /* @__PURE__ */ jsx(ChatMessageBubble, { from: "user", children: message.content }) });
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 (message.role === "assistant") {
226
- const requestedTools = message.tool_calls ?? [];
227
- if (!message.content && requestedTools.length === 0) return null;
228
- return /* @__PURE__ */ jsxs(ChatMessage, { from: "assistant", children: [
229
- message.content ? /* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }) : null,
230
- requestedTools.map((toolCall) => /* @__PURE__ */ jsxs(ChatToolChip, { icon: /* @__PURE__ */ jsx(WrenchIcon, {}), children: [
231
- "Using ",
232
- humanizeToolName(toolCall.name ?? "tool"),
233
- "\u2026"
234
- ] }, toolCall.id ?? toolCall.name))
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
- const tableBlocks = (message.blocks ?? []).filter((block) => block.type === "table");
238
- return /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: tableBlocks.length > 0 ? tableBlocks.map((block, index) => {
239
- const { columns, rows } = toDisplayTable(block);
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 block.pagination?.count === "number" ? block.pagination.count : block.row_count
689
+ totalCount: typeof table.pagination?.count === "number" ? table.pagination.count : table.row_count
247
690
  }
248
- ) }, `${message.id}-${index}`);
249
- }) : /* @__PURE__ */ jsxs(ChatToolChip, { icon: /* @__PURE__ */ jsx(CheckIcon, {}), children: [
250
- humanizeToolName(message.tool_name ?? "tool"),
251
- " finished"
252
- ] }) });
691
+ ) }, key);
692
+ }) });
253
693
  }
254
- function humanizeToolName2(toolName) {
255
- return toolName.replace(/^[a-z]+_/, "").replaceAll("_", " ");
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({ approval, onResolve, resolving }) {
258
- const argumentEntries = Object.entries(approval.arguments ?? {});
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__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
265
- "The assistant wants to run",
266
- " ",
267
- /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: humanizeToolName2(approval.tool_name) }),
268
- " ",
269
- "\u2014 this will change your data."
270
- ] }),
271
- argumentEntries.length > 0 && /* @__PURE__ */ jsxs("dl", { className: "mt-2 space-y-0.5 rounded-lg bg-muted p-2 font-mono text-xs", children: [
272
- argumentEntries.slice(0, 8).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
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
- Button,
288
- {
289
- disabled: resolving,
290
- onClick: () => onResolve(approval.id, false),
291
- size: "sm",
292
- variant: "outline",
293
- children: "Decline"
294
- }
295
- ),
296
- /* @__PURE__ */ jsxs(Button, { disabled: resolving, onClick: () => onResolve(approval.id, true), size: "sm", children: [
297
- resolving ? /* @__PURE__ */ jsx(Spinner, {}) : null,
298
- "Approve"
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] = React2.useState("");
313
- const [view, setView] = React2.useState("chat");
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 handleSubmit = React2.useCallback(
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
- void chat.send(text);
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
- [chat]
874
+ [uploadEnabled]
325
875
  );
326
- const openHistory = React2.useCallback(() => {
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((message) => /* @__PURE__ */ jsx(MessageItem, { message }, message.id)),
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("button", { className: "shrink-0 cursor-pointer font-medium", onClick: chat.clearError, type: "button", children: "Dismiss" })
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
- ChatInput,
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 = React2.useMemo(() => new AgentApiClient(config), [config]);
498
- const [agentInfo, setAgentInfo] = React2.useState(null);
499
- const [conversationId, setConversationId] = React2.useState(null);
500
- const [conversations, setConversations] = React2.useState([]);
501
- const [messages, setMessages] = React2.useState([]);
502
- const [pendingApprovals, setPendingApprovals] = React2.useState([]);
503
- const [sending, setSending] = React2.useState(false);
504
- const [resolvingApprovalId, setResolvingApprovalId] = React2.useState(null);
505
- const [error, setError] = React2.useState(null);
506
- React2.useEffect(() => {
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 = React2.useCallback((err) => {
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 = React2.useCallback(
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 = React2.useCallback(
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 = React2.useCallback(
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 = React2.useCallback(() => {
1168
+ const newChat = React5.useCallback(() => {
574
1169
  setConversationId(null);
575
1170
  setMessages([]);
576
1171
  setPendingApprovals([]);
577
1172
  setError(null);
578
1173
  }, []);
579
- const refreshConversations = React2.useCallback(async () => {
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 = React2.useCallback(
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 = React2.useCallback(
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 = React2.useCallback(() => setError(null), []);
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] = React2.useState(false);
1280
+ const [uncontrolledOpen, setUncontrolledOpen] = React5.useState(false);
634
1281
  const open = controlledOpen ?? uncontrolledOpen;
635
- const setOpen = React2.useCallback(
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] = React2.useState(false);
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 lastNavigatedMessageId = React2.useRef(null);
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
- React2.useEffect(() => {
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 = React2.useCallback(() => {
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