@invergent/agent-chat-react 1.4.3 → 1.4.4

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/index.cjs CHANGED
@@ -1711,6 +1711,28 @@ function DialogHeader({ className, ...props }) {
1711
1711
  }
1712
1712
  );
1713
1713
  }
1714
+ function DialogFooter({
1715
+ className,
1716
+ showCloseButton = false,
1717
+ children,
1718
+ ...props
1719
+ }) {
1720
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1721
+ "div",
1722
+ {
1723
+ "data-slot": "dialog-footer",
1724
+ className: cn(
1725
+ "flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
1726
+ className
1727
+ ),
1728
+ ...props,
1729
+ children: [
1730
+ children,
1731
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(radixUi.Dialog.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", children: "Close" }) })
1732
+ ]
1733
+ }
1734
+ );
1735
+ }
1714
1736
  function DialogTitle({
1715
1737
  className,
1716
1738
  ...props
@@ -6075,6 +6097,777 @@ function ChatThread({
6075
6097
  ] })
6076
6098
  ] });
6077
6099
  }
6100
+ var noop = () => {
6101
+ };
6102
+ var FileTreeContext = react.createContext({
6103
+ expandedPaths: /* @__PURE__ */ new Set(),
6104
+ togglePath: noop
6105
+ });
6106
+ function FileTree({
6107
+ expanded: controlledExpanded,
6108
+ defaultExpanded = /* @__PURE__ */ new Set(),
6109
+ selectedPath,
6110
+ onSelect,
6111
+ onExpandedChange,
6112
+ className,
6113
+ children,
6114
+ ...props
6115
+ }) {
6116
+ const [internalExpanded, setInternalExpanded] = react.useState(defaultExpanded);
6117
+ const expandedPaths = controlledExpanded ?? internalExpanded;
6118
+ const togglePath = react.useCallback(
6119
+ (path) => {
6120
+ const next = new Set(expandedPaths);
6121
+ if (next.has(path)) {
6122
+ next.delete(path);
6123
+ } else {
6124
+ next.add(path);
6125
+ }
6126
+ setInternalExpanded(next);
6127
+ onExpandedChange?.(next);
6128
+ },
6129
+ [expandedPaths, onExpandedChange]
6130
+ );
6131
+ const contextValue = react.useMemo(
6132
+ () => ({
6133
+ expandedPaths,
6134
+ onSelect,
6135
+ selectedPath: selectedPath ?? void 0,
6136
+ togglePath
6137
+ }),
6138
+ [expandedPaths, onSelect, selectedPath, togglePath]
6139
+ );
6140
+ return /* @__PURE__ */ jsxRuntime.jsx(FileTreeContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxRuntime.jsx(
6141
+ "div",
6142
+ {
6143
+ className: cn("rounded-lg border bg-background text-sm", className),
6144
+ role: "tree",
6145
+ ...props,
6146
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2", children })
6147
+ }
6148
+ ) });
6149
+ }
6150
+ function FileTreeIcon({
6151
+ className,
6152
+ children,
6153
+ ...props
6154
+ }) {
6155
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("shrink-0", className), ...props, children });
6156
+ }
6157
+ function FileTreeName({
6158
+ className,
6159
+ children,
6160
+ ...props
6161
+ }) {
6162
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("truncate", className), ...props, children });
6163
+ }
6164
+ function FileTreeFolder({
6165
+ path,
6166
+ name,
6167
+ className,
6168
+ children,
6169
+ ...props
6170
+ }) {
6171
+ const { expandedPaths, togglePath, selectedPath, onSelect } = react.useContext(FileTreeContext);
6172
+ const isExpanded = expandedPaths.has(path);
6173
+ const isSelected = selectedPath === path;
6174
+ const handleOpenChange = react.useCallback(() => {
6175
+ togglePath(path);
6176
+ }, [togglePath, path]);
6177
+ const handleSelect = react.useCallback(() => {
6178
+ onSelect?.(path);
6179
+ }, [onSelect, path]);
6180
+ return /* @__PURE__ */ jsxRuntime.jsx(Collapsible, { onOpenChange: handleOpenChange, open: isExpanded, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("", className), role: "treeitem", tabIndex: 0, ...props, children: [
6181
+ /* @__PURE__ */ jsxRuntime.jsxs(
6182
+ "div",
6183
+ {
6184
+ className: cn(
6185
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
6186
+ isSelected && "bg-muted"
6187
+ ),
6188
+ children: [
6189
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6190
+ "button",
6191
+ {
6192
+ className: "flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0",
6193
+ type: "button",
6194
+ "aria-label": isExpanded ? `Collapse ${name}` : `Expand ${name}`,
6195
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6196
+ lucideReact.ChevronRightIcon,
6197
+ {
6198
+ className: cn(
6199
+ "size-4 shrink-0 text-muted-foreground transition-transform",
6200
+ isExpanded && "rotate-90"
6201
+ )
6202
+ }
6203
+ )
6204
+ }
6205
+ ) }),
6206
+ /* @__PURE__ */ jsxRuntime.jsxs(
6207
+ "button",
6208
+ {
6209
+ className: "flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left",
6210
+ onClick: handleSelect,
6211
+ type: "button",
6212
+ children: [
6213
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 text-blue-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderIcon, { className: "size-4 text-blue-500" }) }),
6214
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
6215
+ ]
6216
+ }
6217
+ )
6218
+ ]
6219
+ }
6220
+ ),
6221
+ /* @__PURE__ */ jsxRuntime.jsx(CollapsibleContent, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-4 border-l border-muted-foreground/50 pl-2", children }) })
6222
+ ] }) });
6223
+ }
6224
+ function FileTreeFile({
6225
+ path,
6226
+ name,
6227
+ icon,
6228
+ className,
6229
+ children,
6230
+ ...props
6231
+ }) {
6232
+ const { selectedPath, onSelect } = react.useContext(FileTreeContext);
6233
+ const isSelected = selectedPath === path;
6234
+ const handleClick = react.useCallback(() => {
6235
+ onSelect?.(path);
6236
+ }, [onSelect, path]);
6237
+ const handleKeyDown = react.useCallback(
6238
+ (event) => {
6239
+ if (event.key === "Enter" || event.key === " ") {
6240
+ onSelect?.(path);
6241
+ }
6242
+ },
6243
+ [onSelect, path]
6244
+ );
6245
+ return /* @__PURE__ */ jsxRuntime.jsx(
6246
+ "div",
6247
+ {
6248
+ className: cn(
6249
+ "group flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
6250
+ isSelected && "bg-muted",
6251
+ className
6252
+ ),
6253
+ onClick: handleClick,
6254
+ onKeyDown: handleKeyDown,
6255
+ role: "treeitem",
6256
+ tabIndex: 0,
6257
+ ...props,
6258
+ children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6259
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-4 shrink-0" }),
6260
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeIcon, { children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileIcon, { className: "size-4 text-muted-foreground" }) }),
6261
+ /* @__PURE__ */ jsxRuntime.jsx(FileTreeName, { children: name })
6262
+ ] })
6263
+ }
6264
+ );
6265
+ }
6266
+ function Skeleton({ className, ...props }) {
6267
+ return /* @__PURE__ */ jsxRuntime.jsx(
6268
+ "div",
6269
+ {
6270
+ className: cn("animate-pulse rounded-md bg-muted", className),
6271
+ ...props
6272
+ }
6273
+ );
6274
+ }
6275
+
6276
+ // src/lib/format.ts
6277
+ function formatFileSize(bytes) {
6278
+ if (bytes < 1024) return `${bytes} B`;
6279
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
6280
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
6281
+ }
6282
+ function getLanguageHint(path) {
6283
+ const name = path.split("/").pop() ?? path;
6284
+ const dot = name.lastIndexOf(".");
6285
+ if (dot < 0) return "plaintext";
6286
+ const ext = name.slice(dot).toLowerCase();
6287
+ const map = {
6288
+ ".py": "python",
6289
+ ".js": "javascript",
6290
+ ".ts": "typescript",
6291
+ ".tsx": "tsx",
6292
+ ".jsx": "jsx",
6293
+ ".json": "json",
6294
+ ".yaml": "yaml",
6295
+ ".yml": "yaml",
6296
+ ".toml": "toml",
6297
+ ".sql": "sql",
6298
+ ".sh": "bash",
6299
+ ".css": "css",
6300
+ ".html": "html",
6301
+ ".md": "markdown",
6302
+ ".rs": "rust",
6303
+ ".go": "go",
6304
+ ".java": "java",
6305
+ ".rb": "ruby",
6306
+ ".php": "php",
6307
+ ".c": "c",
6308
+ ".cpp": "cpp",
6309
+ ".h": "c",
6310
+ ".hpp": "cpp"
6311
+ };
6312
+ return map[ext] ?? "plaintext";
6313
+ }
6314
+ var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
6315
+ function FileViewer({
6316
+ file,
6317
+ loading,
6318
+ error,
6319
+ onClose
6320
+ }) {
6321
+ const visible = loading || file !== null || error !== null;
6322
+ if (!visible) return null;
6323
+ const fileName2 = file?.path.split("/").pop() ?? "File";
6324
+ const lang = file ? getLanguageHint(file.path) : "";
6325
+ const isImage = file?.encoding === "base64";
6326
+ const HeaderIcon = isImage ? lucideReact.ImageIcon : lucideReact.FileTextIcon;
6327
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
6328
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
6329
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }),
6330
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6331
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs font-medium", children: fileName2 }),
6332
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "truncate text-[10px] text-muted-foreground", children: [
6333
+ file?.path ?? "Loading...",
6334
+ file && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ml-1.5", children: [
6335
+ formatFileSize(file.size),
6336
+ file.truncated && " (truncated)"
6337
+ ] })
6338
+ ] })
6339
+ ] }),
6340
+ /* @__PURE__ */ jsxRuntime.jsx(
6341
+ "button",
6342
+ {
6343
+ type: "button",
6344
+ onClick: onClose,
6345
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6346
+ "aria-label": "Close file",
6347
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-3.5" })
6348
+ }
6349
+ )
6350
+ ] }),
6351
+ /* @__PURE__ */ jsxRuntime.jsxs(ScrollArea, { className: "min-h-0 flex-1", children: [
6352
+ loading && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5 p-3", children: Array.from({ length: 10 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
6353
+ Skeleton,
6354
+ {
6355
+ className: "h-3.5 rounded",
6356
+ style: {
6357
+ width: `${SKELETON_WIDTHS[index % SKELETON_WIDTHS.length]}%`
6358
+ }
6359
+ },
6360
+ index
6361
+ )) }),
6362
+ error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
6363
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6364
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: error })
6365
+ ] }),
6366
+ file && isImage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
6367
+ "img",
6368
+ {
6369
+ src: `data:${file.mime_type ?? "image/png"};base64,${file.content}`,
6370
+ alt: fileName2,
6371
+ className: "max-h-[60vh] max-w-full rounded object-contain"
6372
+ }
6373
+ ) }),
6374
+ file && !isImage && /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx("code", { "data-language": lang, children: file.content }) })
6375
+ ] })
6376
+ ] });
6377
+ }
6378
+ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
6379
+ ".py",
6380
+ ".js",
6381
+ ".ts",
6382
+ ".tsx",
6383
+ ".jsx",
6384
+ ".json",
6385
+ ".md",
6386
+ ".yaml",
6387
+ ".yml",
6388
+ ".toml",
6389
+ ".sql",
6390
+ ".sh",
6391
+ ".css",
6392
+ ".html",
6393
+ ".txt",
6394
+ ".csv",
6395
+ ".rst",
6396
+ ".cfg",
6397
+ ".ini",
6398
+ ".env",
6399
+ ".rs",
6400
+ ".go",
6401
+ ".java",
6402
+ ".rb",
6403
+ ".php",
6404
+ ".c",
6405
+ ".cpp",
6406
+ ".h",
6407
+ ".lock",
6408
+ ".gitignore",
6409
+ ".dockerignore",
6410
+ ".editorconfig"
6411
+ ]);
6412
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
6413
+ ".png",
6414
+ ".jpg",
6415
+ ".jpeg",
6416
+ ".gif",
6417
+ ".webp",
6418
+ ".svg",
6419
+ ".bmp",
6420
+ ".ico",
6421
+ ".avif",
6422
+ ".tiff",
6423
+ ".tif"
6424
+ ]);
6425
+ var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
6426
+ var DEFAULT_WIDTH = 500;
6427
+ var MIN_WIDTH = 300;
6428
+ var MAX_WIDTH = 900;
6429
+ function isViewable(name) {
6430
+ const dot = name.lastIndexOf(".");
6431
+ const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
6432
+ return TEXT_EXTENSIONS.has(ext) || IMAGE_EXTENSIONS.has(ext);
6433
+ }
6434
+ function collectExpandedPaths(entries, depth = 0) {
6435
+ const paths = [];
6436
+ for (const entry of entries) {
6437
+ if (entry.kind === "dir" && depth < 1) {
6438
+ paths.push(entry.path);
6439
+ if (entry.children) {
6440
+ paths.push(...collectExpandedPaths(entry.children, depth + 1));
6441
+ }
6442
+ }
6443
+ }
6444
+ return paths;
6445
+ }
6446
+ function findEntry(entries, path) {
6447
+ for (const entry of entries) {
6448
+ if (entry.path === path) return entry;
6449
+ if (entry.kind === "dir" && entry.children) {
6450
+ const found = findEntry(entry.children, path);
6451
+ if (found) return found;
6452
+ }
6453
+ }
6454
+ return null;
6455
+ }
6456
+ function RenderEntries({
6457
+ entries,
6458
+ onFileSelect,
6459
+ onDelete
6460
+ }) {
6461
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: entries.map((entry) => {
6462
+ if (entry.kind === "dir") {
6463
+ return /* @__PURE__ */ jsxRuntime.jsx(FileTreeFolder, { name: entry.name, path: entry.path, children: entry.children && entry.children.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
6464
+ RenderEntries,
6465
+ {
6466
+ entries: entry.children,
6467
+ onFileSelect,
6468
+ onDelete
6469
+ }
6470
+ ) }, entry.path);
6471
+ }
6472
+ const viewable = isViewable(entry.name);
6473
+ return /* @__PURE__ */ jsxRuntime.jsxs(FileTreeFile, { name: entry.name, path: entry.path, children: [
6474
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "size-4 shrink-0" }),
6475
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.name }),
6476
+ entry.size != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 shrink-0 text-xs text-muted-foreground/60", children: formatFileSize(entry.size) }),
6477
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-1 flex shrink-0 items-center gap-0 opacity-0 transition-opacity group-hover:opacity-100", children: [
6478
+ viewable && /* @__PURE__ */ jsxRuntime.jsx(
6479
+ "button",
6480
+ {
6481
+ type: "button",
6482
+ className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
6483
+ onClick: (event) => {
6484
+ event.stopPropagation();
6485
+ onFileSelect(entry.path);
6486
+ },
6487
+ title: "View",
6488
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.DownloadIcon, { className: "size-3" })
6489
+ }
6490
+ ),
6491
+ /* @__PURE__ */ jsxRuntime.jsx(
6492
+ "button",
6493
+ {
6494
+ type: "button",
6495
+ className: "rounded p-0.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive",
6496
+ onClick: (event) => {
6497
+ event.stopPropagation();
6498
+ onDelete(entry.path);
6499
+ },
6500
+ title: "Delete",
6501
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrashIcon, { className: "size-3" })
6502
+ }
6503
+ )
6504
+ ] })
6505
+ ] }, entry.path);
6506
+ }) });
6507
+ }
6508
+ function WorkspacePanel({
6509
+ adapter,
6510
+ sessionId,
6511
+ selectedPath,
6512
+ onSelectedPathChange
6513
+ }) {
6514
+ const fileInputRef = react.useRef(null);
6515
+ const [entries, setEntries] = react.useState([]);
6516
+ const [rootName, setRootName] = react.useState("workspace");
6517
+ const [treeLoading, setTreeLoading] = react.useState(false);
6518
+ const [treeError, setTreeError] = react.useState(null);
6519
+ const [file, setFile] = react.useState(null);
6520
+ const [fileLoading, setFileLoading] = react.useState(false);
6521
+ const [fileError, setFileError] = react.useState(null);
6522
+ const [notice, setNotice] = react.useState(null);
6523
+ const [uploading, setUploading] = react.useState(false);
6524
+ const [deleteTarget, setDeleteTarget] = react.useState(null);
6525
+ const [expandedPaths, setExpandedPaths] = react.useState(/* @__PURE__ */ new Set());
6526
+ const [width, setWidth] = react.useState(DEFAULT_WIDTH);
6527
+ const isResizing = react.useRef(false);
6528
+ const startX = react.useRef(0);
6529
+ const startWidth = react.useRef(DEFAULT_WIDTH);
6530
+ const fetchTree = react.useCallback(async () => {
6531
+ if (!sessionId) {
6532
+ setEntries([]);
6533
+ setRootName("workspace");
6534
+ setTreeError(null);
6535
+ return;
6536
+ }
6537
+ setTreeLoading(true);
6538
+ setTreeError(null);
6539
+ try {
6540
+ const tree = await adapter.getWorkspaceTree({ sessionId });
6541
+ setEntries(tree.entries);
6542
+ setRootName(tree.root || "workspace");
6543
+ setExpandedPaths(new Set(collectExpandedPaths(tree.entries)));
6544
+ } catch (error) {
6545
+ setEntries([]);
6546
+ setRootName("workspace");
6547
+ setTreeError(error.message);
6548
+ } finally {
6549
+ setTreeLoading(false);
6550
+ }
6551
+ }, [adapter, sessionId]);
6552
+ const fetchFile = react.useCallback(
6553
+ async (path) => {
6554
+ if (!sessionId) return;
6555
+ setFileLoading(true);
6556
+ setFileError(null);
6557
+ try {
6558
+ const nextFile = await adapter.getWorkspaceFile({ sessionId, path });
6559
+ setFile(nextFile);
6560
+ } catch (error) {
6561
+ setFile(null);
6562
+ setFileError(error.message);
6563
+ } finally {
6564
+ setFileLoading(false);
6565
+ }
6566
+ },
6567
+ [adapter, sessionId]
6568
+ );
6569
+ react.useEffect(() => {
6570
+ void fetchTree();
6571
+ }, [fetchTree]);
6572
+ react.useEffect(() => {
6573
+ if (!sessionId) {
6574
+ setFile(null);
6575
+ setFileError(null);
6576
+ onSelectedPathChange(null);
6577
+ }
6578
+ }, [onSelectedPathChange, sessionId]);
6579
+ react.useEffect(() => {
6580
+ if (!selectedPath) return;
6581
+ const parts = selectedPath.split("/");
6582
+ if (parts.length <= 1) return;
6583
+ setExpandedPaths((prev) => {
6584
+ const next = new Set(prev);
6585
+ let path = "";
6586
+ for (let index = 0; index < parts.length - 1; index += 1) {
6587
+ path = path ? `${path}/${parts[index]}` : parts[index];
6588
+ next.add(path);
6589
+ }
6590
+ return next;
6591
+ });
6592
+ }, [selectedPath]);
6593
+ react.useEffect(() => {
6594
+ if (!selectedPath || !sessionId || entries.length === 0) return;
6595
+ const entry = findEntry(entries, selectedPath);
6596
+ if (entry?.kind === "file" && file?.path !== selectedPath) {
6597
+ void fetchFile(selectedPath);
6598
+ }
6599
+ }, [entries, fetchFile, file?.path, selectedPath, sessionId]);
6600
+ const handleSelect = react.useCallback(
6601
+ (path) => {
6602
+ onSelectedPathChange(path);
6603
+ const entry = findEntry(entries, path);
6604
+ if (entry?.kind === "file") {
6605
+ void fetchFile(path);
6606
+ }
6607
+ },
6608
+ [entries, fetchFile, onSelectedPathChange]
6609
+ );
6610
+ const handleUpload = react.useCallback(
6611
+ async (files) => {
6612
+ if (!sessionId || files.length === 0) return;
6613
+ setUploading(true);
6614
+ setNotice(null);
6615
+ try {
6616
+ for (const uploadedFile of Array.from(files)) {
6617
+ await adapter.uploadWorkspaceFile({
6618
+ sessionId,
6619
+ file: uploadedFile
6620
+ });
6621
+ }
6622
+ setNotice(
6623
+ files.length === 1 ? `Uploaded ${files[0]?.name ?? "file"}` : `Uploaded ${files.length} files`
6624
+ );
6625
+ await fetchTree();
6626
+ } catch (error) {
6627
+ setNotice(error.message);
6628
+ } finally {
6629
+ setUploading(false);
6630
+ if (fileInputRef.current) fileInputRef.current.value = "";
6631
+ }
6632
+ },
6633
+ [adapter, fetchTree, sessionId]
6634
+ );
6635
+ const handleDelete = react.useCallback(
6636
+ async (path) => {
6637
+ if (!sessionId) return;
6638
+ try {
6639
+ await adapter.deleteWorkspaceFile({ sessionId, path });
6640
+ if (selectedPath === path) {
6641
+ onSelectedPathChange(null);
6642
+ setFile(null);
6643
+ setFileError(null);
6644
+ }
6645
+ setNotice(`Deleted ${path.split("/").pop() ?? path}`);
6646
+ await fetchTree();
6647
+ } catch (error) {
6648
+ setNotice(error.message);
6649
+ } finally {
6650
+ setDeleteTarget(null);
6651
+ }
6652
+ },
6653
+ [adapter, fetchTree, onSelectedPathChange, selectedPath, sessionId]
6654
+ );
6655
+ const onResizeStart = react.useCallback(
6656
+ (event) => {
6657
+ event.preventDefault();
6658
+ isResizing.current = true;
6659
+ startX.current = event.clientX;
6660
+ startWidth.current = width;
6661
+ document.body.style.cursor = "col-resize";
6662
+ document.body.style.userSelect = "none";
6663
+ },
6664
+ [width]
6665
+ );
6666
+ react.useEffect(() => {
6667
+ const onMouseMove = (event) => {
6668
+ if (!isResizing.current) return;
6669
+ const delta = startX.current - event.clientX;
6670
+ setWidth(
6671
+ Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth.current + delta))
6672
+ );
6673
+ };
6674
+ const onMouseUp = () => {
6675
+ if (!isResizing.current) return;
6676
+ isResizing.current = false;
6677
+ document.body.style.cursor = "";
6678
+ document.body.style.userSelect = "";
6679
+ };
6680
+ window.addEventListener("mousemove", onMouseMove);
6681
+ window.addEventListener("mouseup", onMouseUp);
6682
+ return () => {
6683
+ window.removeEventListener("mousemove", onMouseMove);
6684
+ window.removeEventListener("mouseup", onMouseUp);
6685
+ };
6686
+ }, []);
6687
+ return /* @__PURE__ */ jsxRuntime.jsxs(
6688
+ "aside",
6689
+ {
6690
+ className: "relative z-10 flex min-h-0 flex-col overflow-hidden border-l border-muted-foreground/20 bg-card",
6691
+ style: { width, minWidth: MIN_WIDTH, maxWidth: MAX_WIDTH },
6692
+ children: [
6693
+ /* @__PURE__ */ jsxRuntime.jsx(
6694
+ "div",
6695
+ {
6696
+ className: "absolute inset-y-0 left-0 z-20 w-1.5 cursor-col-resize transition-colors hover:bg-primary/20 active:bg-primary/30",
6697
+ onMouseDown: onResizeStart
6698
+ }
6699
+ ),
6700
+ /* @__PURE__ */ jsxRuntime.jsx(
6701
+ "input",
6702
+ {
6703
+ ref: fileInputRef,
6704
+ type: "file",
6705
+ multiple: true,
6706
+ className: "hidden",
6707
+ onChange: (event) => {
6708
+ if (event.target.files) void handleUpload(event.target.files);
6709
+ }
6710
+ }
6711
+ ),
6712
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-14 items-center gap-2 border-b border-line px-3 py-3", children: [
6713
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpenIcon, { className: "size-4 shrink-0 text-amber-500" }),
6714
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
6715
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-sm font-medium text-foreground", children: rootName }),
6716
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "truncate text-xs text-faint", children: "Workspace" })
6717
+ ] }),
6718
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6719
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6720
+ Button,
6721
+ {
6722
+ variant: "ghost",
6723
+ size: "icon-sm",
6724
+ onClick: () => fileInputRef.current?.click(),
6725
+ disabled: !sessionId || uploading,
6726
+ "aria-label": "Upload files",
6727
+ children: uploading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-4" })
6728
+ }
6729
+ ) }),
6730
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Upload files" })
6731
+ ] }),
6732
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
6733
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
6734
+ Button,
6735
+ {
6736
+ variant: "ghost",
6737
+ size: "icon-sm",
6738
+ onClick: () => void fetchTree(),
6739
+ disabled: !sessionId || treeLoading,
6740
+ "aria-label": "Refresh workspace",
6741
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6742
+ lucideReact.RefreshCwIcon,
6743
+ {
6744
+ className: cn("size-4", treeLoading && "animate-spin")
6745
+ }
6746
+ )
6747
+ }
6748
+ ) }),
6749
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: "Refresh" })
6750
+ ] })
6751
+ ] }),
6752
+ notice && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-line px-3 py-2 text-xs text-muted-foreground", children: notice }),
6753
+ /* @__PURE__ */ jsxRuntime.jsx(ScrollArea, { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-1 py-1", children: [
6754
+ treeLoading && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1 p-2", children: Array.from({ length: 8 }).map((_, index) => /* @__PURE__ */ jsxRuntime.jsx(
6755
+ Skeleton,
6756
+ {
6757
+ className: "h-5 rounded",
6758
+ style: {
6759
+ width: `${SKELETON_WIDTHS2[index % SKELETON_WIDTHS2.length]}%`
6760
+ }
6761
+ },
6762
+ index
6763
+ )) }),
6764
+ treeError && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2 p-3 text-sm text-destructive", children: [
6765
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),
6766
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: treeError })
6767
+ ] }),
6768
+ !treeLoading && !treeError && entries.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-8 text-center text-sm text-faint", children: [
6769
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No workspace files" }),
6770
+ /* @__PURE__ */ jsxRuntime.jsxs(
6771
+ Button,
6772
+ {
6773
+ variant: "outline",
6774
+ size: "sm",
6775
+ className: "mt-3 gap-1.5",
6776
+ onClick: () => fileInputRef.current?.click(),
6777
+ disabled: !sessionId,
6778
+ children: [
6779
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadIcon, { className: "size-3.5" }),
6780
+ "Upload files"
6781
+ ]
6782
+ }
6783
+ )
6784
+ ] }),
6785
+ entries.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
6786
+ FileTree,
6787
+ {
6788
+ expanded: expandedPaths,
6789
+ onExpandedChange: setExpandedPaths,
6790
+ selectedPath,
6791
+ onSelect: handleSelect,
6792
+ className: "rounded-none border-0",
6793
+ children: /* @__PURE__ */ jsxRuntime.jsx(
6794
+ RenderEntries,
6795
+ {
6796
+ entries,
6797
+ onFileSelect: handleSelect,
6798
+ onDelete: setDeleteTarget
6799
+ }
6800
+ )
6801
+ }
6802
+ )
6803
+ ] }) }),
6804
+ /* @__PURE__ */ jsxRuntime.jsx(
6805
+ FileViewer,
6806
+ {
6807
+ file,
6808
+ loading: fileLoading,
6809
+ error: fileError,
6810
+ onClose: () => {
6811
+ setFile(null);
6812
+ setFileError(null);
6813
+ }
6814
+ }
6815
+ ),
6816
+ /* @__PURE__ */ jsxRuntime.jsx(
6817
+ ConfirmDialog,
6818
+ {
6819
+ open: deleteTarget !== null,
6820
+ title: "Delete file?",
6821
+ description: `This will permanently delete ${deleteTarget?.split("/").pop() ?? "this file"} from the workspace.`,
6822
+ confirmLabel: "Delete",
6823
+ onConfirm: () => deleteTarget ? handleDelete(deleteTarget) : Promise.resolve(),
6824
+ onCancel: () => setDeleteTarget(null)
6825
+ }
6826
+ )
6827
+ ]
6828
+ }
6829
+ );
6830
+ }
6831
+ function ConfirmDialog({
6832
+ open,
6833
+ title,
6834
+ description,
6835
+ confirmLabel,
6836
+ onConfirm,
6837
+ onCancel
6838
+ }) {
6839
+ const [loading, setLoading] = react.useState(false);
6840
+ const handleConfirm = async () => {
6841
+ setLoading(true);
6842
+ try {
6843
+ await onConfirm();
6844
+ } finally {
6845
+ setLoading(false);
6846
+ }
6847
+ };
6848
+ return /* @__PURE__ */ jsxRuntime.jsx(
6849
+ Dialog,
6850
+ {
6851
+ open,
6852
+ onOpenChange: (nextOpen) => {
6853
+ if (!nextOpen && !loading) onCancel();
6854
+ },
6855
+ children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "sm:max-w-md", children: [
6856
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
6857
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: title }),
6858
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: description })
6859
+ ] }),
6860
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
6861
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { variant: "outline", disabled: loading, onClick: onCancel, children: "Cancel" }),
6862
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { variant: "destructive", disabled: loading, onClick: handleConfirm, children: [
6863
+ loading && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2Icon, { className: "mr-1.5 size-3.5 animate-spin" }),
6864
+ confirmLabel
6865
+ ] })
6866
+ ] })
6867
+ ] })
6868
+ }
6869
+ );
6870
+ }
6078
6871
 
6079
6872
  // src/runtime/events.ts
6080
6873
  var AGENT_CHAT_LISTENED_EVENTS = [
@@ -6885,6 +7678,7 @@ function AgentChat({
6885
7678
  onMessagesChange,
6886
7679
  disabled
6887
7680
  }) {
7681
+ const [workspacePath, setWorkspacePath] = react.useState(null);
6888
7682
  const runtime = useAgentChatRuntime({
6889
7683
  adapter,
6890
7684
  agentId,
@@ -6894,29 +7688,47 @@ function AgentChat({
6894
7688
  react.useEffect(() => {
6895
7689
  onMessagesChange?.(runtime.messages);
6896
7690
  }, [onMessagesChange, runtime.messages]);
7691
+ const handleFileSelect = react.useCallback(
7692
+ (path) => {
7693
+ setWorkspacePath(path);
7694
+ onFileSelect?.(path);
7695
+ },
7696
+ [onFileSelect]
7697
+ );
6897
7698
  return /* @__PURE__ */ jsxRuntime.jsx(
6898
7699
  AgentChatAdapterProvider,
6899
7700
  {
6900
7701
  value: {
6901
7702
  adapter,
6902
7703
  sessionId,
6903
- onFileSelect
7704
+ onFileSelect: handleFileSelect
6904
7705
  },
6905
- children: /* @__PURE__ */ jsxRuntime.jsx("section", { className: "flex h-full min-h-0 flex-col bg-card text-sm text-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(
6906
- ChatThread,
6907
- {
6908
- sessionId,
6909
- messages: runtime.messages,
6910
- isRunning: runtime.isRunning,
6911
- onSend: (content) => void runtime.send(content),
6912
- onStop: () => void runtime.stop(),
6913
- onRetry: runtime.retry,
6914
- onFileSelect,
6915
- disabled,
6916
- tokenUsage: runtime.tokenUsage,
6917
- retryIndicator: runtime.retryIndicator
6918
- }
6919
- ) })
7706
+ children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { children: /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex h-full min-h-0 bg-card text-sm text-foreground", children: [
7707
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: /* @__PURE__ */ jsxRuntime.jsx(
7708
+ ChatThread,
7709
+ {
7710
+ sessionId,
7711
+ messages: runtime.messages,
7712
+ isRunning: runtime.isRunning,
7713
+ onSend: (content) => void runtime.send(content),
7714
+ onStop: () => void runtime.stop(),
7715
+ onRetry: runtime.retry,
7716
+ onFileSelect: handleFileSelect,
7717
+ disabled,
7718
+ tokenUsage: runtime.tokenUsage,
7719
+ retryIndicator: runtime.retryIndicator
7720
+ }
7721
+ ) }),
7722
+ /* @__PURE__ */ jsxRuntime.jsx(
7723
+ WorkspacePanel,
7724
+ {
7725
+ adapter,
7726
+ sessionId,
7727
+ selectedPath: workspacePath,
7728
+ onSelectedPathChange: setWorkspacePath
7729
+ }
7730
+ )
7731
+ ] }) })
6920
7732
  }
6921
7733
  );
6922
7734
  }