@invergent/agent-chat-react 1.5.2 → 1.5.3

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.d.cts CHANGED
@@ -292,6 +292,17 @@ interface AgentChatAdapter {
292
292
  sessionId: string;
293
293
  path: string;
294
294
  }): Promise<void>;
295
+ /**
296
+ * Build a same-origin URL the browser can navigate to (or anchor at via
297
+ * ``<a href download>``) to download the workspace file. The adapter
298
+ * is responsible for embedding any auth credential the server expects
299
+ * — typically as a query-string token, since cross-origin/anchor
300
+ * downloads can't carry custom headers.
301
+ */
302
+ getWorkspaceDownloadUrl(input: {
303
+ sessionId: string;
304
+ path: string;
305
+ }): string;
295
306
  openEventStream(input: {
296
307
  sessionId: string;
297
308
  after: number;
package/dist/index.d.ts CHANGED
@@ -292,6 +292,17 @@ interface AgentChatAdapter {
292
292
  sessionId: string;
293
293
  path: string;
294
294
  }): Promise<void>;
295
+ /**
296
+ * Build a same-origin URL the browser can navigate to (or anchor at via
297
+ * ``<a href download>``) to download the workspace file. The adapter
298
+ * is responsible for embedding any auth credential the server expects
299
+ * — typically as a query-string token, since cross-origin/anchor
300
+ * downloads can't carry custom headers.
301
+ */
302
+ getWorkspaceDownloadUrl(input: {
303
+ sessionId: string;
304
+ path: string;
305
+ }): string;
295
306
  openEventStream(input: {
296
307
  sessionId: string;
297
308
  after: number;
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { Collapsible as Collapsible$1, Slot, Dialog as Dialog$1, ScrollArea as S
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
7
+ import { ChevronDownIcon, AlertTriangle, ChevronDown, ChevronRight, RefreshCw, ChevronRightIcon, GitBranchIcon, XIcon, ThumbsUpIcon, ThumbsDownIcon, ActivityIcon, Loader2Icon, GlobeIcon, SearchIcon, ListTodoIcon, XCircleIcon, CheckCircle2Icon, Code, CheckIcon, CopyIcon, TerminalIcon, Trash2Icon, CircleDotIcon, CircleCheckIcon, CircleXIcon, SkullIcon, ClockIcon, CheckCircleIcon, CircleIcon, UsersIcon, MessageSquareIcon, FolderOpenIcon, UploadIcon, RefreshCwIcon, AlertCircleIcon, SquareIcon, ArrowDownIcon, DownloadIcon, TrashIcon, ImageIcon, FileTextIcon, Maximize2Icon, FolderIcon, FileIcon, ChevronLeftIcon, MinusIcon, PlusIcon, CornerDownLeftIcon } from 'lucide-react';
8
8
  import { StickToBottom, useStickToBottomContext } from 'use-stick-to-bottom';
9
9
  import { cjk } from '@streamdown/cjk';
10
10
  import { code } from '@streamdown/code';
@@ -18,6 +18,7 @@ import Ansi from 'ansi-to-react';
18
18
  import { getUsage } from 'tokenlens';
19
19
  import { Command as Command$1 } from 'cmdk';
20
20
  import { nanoid } from 'nanoid';
21
+ import 'pdfjs-dist/web/pdf_viewer.css';
21
22
  import { formatDistanceToNow } from 'date-fns';
22
23
 
23
24
  // src/agent-chat.tsx
@@ -6360,17 +6361,24 @@ function getLanguageHint(path) {
6360
6361
  return map[ext] ?? "plaintext";
6361
6362
  }
6362
6363
  var SKELETON_WIDTHS = [70, 85, 55, 90, 60, 78, 45, 82, 65, 72];
6364
+ var PDF_WORKER_SRC = new URL(
6365
+ "pdfjs-dist/legacy/build/pdf.worker.mjs",
6366
+ import.meta.url
6367
+ ).toString();
6363
6368
  function FileViewer({
6364
6369
  file,
6365
6370
  loading,
6366
6371
  error,
6372
+ downloadUrl,
6373
+ onDelete,
6367
6374
  onClose
6368
6375
  }) {
6369
6376
  const visible = loading || file !== null || error !== null;
6370
6377
  if (!visible) return null;
6371
6378
  const fileName2 = file?.path.split("/").pop() ?? "File";
6372
6379
  const lang = file ? getLanguageHint(file.path) : "";
6373
- const isImage = file?.encoding === "base64";
6380
+ const isPdf = file?.mime_type === "application/pdf" || file?.path.toLowerCase().endsWith(".pdf") || false;
6381
+ const isImage = file?.encoding === "base64" && !isPdf;
6374
6382
  const HeaderIcon = isImage ? ImageIcon : FileTextIcon;
6375
6383
  return /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col border-t border-line", children: [
6376
6384
  /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-2 border-b border-line px-3 py-2", children: [
@@ -6385,6 +6393,28 @@ function FileViewer({
6385
6393
  ] })
6386
6394
  ] })
6387
6395
  ] }),
6396
+ downloadUrl && /* @__PURE__ */ jsx(
6397
+ "a",
6398
+ {
6399
+ href: downloadUrl,
6400
+ download: fileName2,
6401
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
6402
+ "aria-label": `Download ${fileName2}`,
6403
+ title: "Download",
6404
+ children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3.5" })
6405
+ }
6406
+ ),
6407
+ onDelete && /* @__PURE__ */ jsx(
6408
+ "button",
6409
+ {
6410
+ type: "button",
6411
+ onClick: onDelete,
6412
+ className: "shrink-0 rounded p-0.5 text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive",
6413
+ "aria-label": `Delete ${fileName2}`,
6414
+ title: "Delete",
6415
+ children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3.5" })
6416
+ }
6417
+ ),
6388
6418
  /* @__PURE__ */ jsx(
6389
6419
  "button",
6390
6420
  {
@@ -6411,6 +6441,8 @@ function FileViewer({
6411
6441
  /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6412
6442
  /* @__PURE__ */ jsx("span", { children: error })
6413
6443
  ] }),
6444
+ file && isPdf && file.encoding !== "base64" && /* @__PURE__ */ jsx(FileViewerError, { message: "PDF preview requires base64 file content." }),
6445
+ file && isPdf && file.encoding === "base64" && /* @__PURE__ */ jsx(PdfPreview, { file, fileName: fileName2 }),
6414
6446
  file && isImage && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsx(
6415
6447
  "img",
6416
6448
  {
@@ -6419,66 +6451,290 @@ function FileViewer({
6419
6451
  className: "max-h-[60vh] max-w-full rounded object-contain"
6420
6452
  }
6421
6453
  ) }),
6422
- file && !isImage && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
6454
+ file && !isImage && !isPdf && /* @__PURE__ */ jsx("pre", { className: "wrap-break-word whitespace-pre-wrap p-3 text-[11px] leading-relaxed text-foreground", children: /* @__PURE__ */ jsx("code", { "data-language": lang, children: file.content }) })
6423
6455
  ] })
6424
6456
  ] });
6425
6457
  }
6426
- var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
6427
- ".py",
6428
- ".js",
6429
- ".ts",
6430
- ".tsx",
6431
- ".jsx",
6432
- ".json",
6433
- ".md",
6434
- ".yaml",
6435
- ".yml",
6436
- ".toml",
6437
- ".sql",
6438
- ".sh",
6439
- ".css",
6440
- ".html",
6441
- ".txt",
6442
- ".csv",
6443
- ".rst",
6444
- ".cfg",
6445
- ".ini",
6446
- ".env",
6447
- ".rs",
6448
- ".go",
6449
- ".java",
6450
- ".rb",
6451
- ".php",
6452
- ".c",
6453
- ".cpp",
6454
- ".h",
6455
- ".lock",
6456
- ".gitignore",
6457
- ".dockerignore",
6458
- ".editorconfig"
6459
- ]);
6460
- var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
6461
- ".png",
6462
- ".jpg",
6463
- ".jpeg",
6464
- ".gif",
6465
- ".webp",
6466
- ".svg",
6467
- ".bmp",
6468
- ".ico",
6469
- ".avif",
6470
- ".tiff",
6471
- ".tif"
6472
- ]);
6458
+ function FileViewerError({ message }) {
6459
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 p-3 text-xs text-destructive", children: [
6460
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6461
+ /* @__PURE__ */ jsx("span", { children: message })
6462
+ ] });
6463
+ }
6464
+ function PdfPreview({
6465
+ file,
6466
+ fileName: fileName2
6467
+ }) {
6468
+ const containerRef = useRef(null);
6469
+ const viewerRef = useRef(null);
6470
+ const pdfViewerRef = useRef(null);
6471
+ const eventBusRef = useRef(null);
6472
+ const findControllerRef = useRef(null);
6473
+ const [error, setError] = useState(null);
6474
+ const [pageNumber, setPageNumber] = useState(1);
6475
+ const [pagesCount, setPagesCount] = useState(0);
6476
+ const [scale, setScale] = useState(1);
6477
+ const [query, setQuery] = useState("");
6478
+ useEffect(() => {
6479
+ let cancelled = false;
6480
+ let loadingTask = null;
6481
+ let pdfDocument = null;
6482
+ const renderPdf = async () => {
6483
+ setError(null);
6484
+ const container = containerRef.current;
6485
+ const viewerElement = viewerRef.current;
6486
+ if (!container || !viewerElement) return;
6487
+ const pdfBytes = decodeBase64(file.content);
6488
+ const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
6489
+ pdfjs.GlobalWorkerOptions.workerSrc = PDF_WORKER_SRC;
6490
+ globalThis.pdfjsLib = pdfjs;
6491
+ const pdfViewerModule = await import('pdfjs-dist/web/pdf_viewer.mjs');
6492
+ loadingTask = pdfjs.getDocument({ data: pdfBytes });
6493
+ const pdf = await loadingTask.promise;
6494
+ pdfDocument = pdf;
6495
+ if (cancelled) return;
6496
+ const eventBus = new pdfViewerModule.EventBus();
6497
+ const linkService = new pdfViewerModule.PDFLinkService({ eventBus });
6498
+ const findController = new pdfViewerModule.PDFFindController({
6499
+ eventBus,
6500
+ linkService
6501
+ });
6502
+ const pdfViewer = new pdfViewerModule.PDFViewer({
6503
+ container,
6504
+ viewer: viewerElement,
6505
+ eventBus,
6506
+ linkService,
6507
+ findController,
6508
+ removePageBorders: true
6509
+ });
6510
+ linkService.setViewer(pdfViewer);
6511
+ linkService.setDocument(pdf);
6512
+ findController.setDocument(pdf);
6513
+ const onPagesInit = () => {
6514
+ pdfViewer.currentScaleValue = "page-width";
6515
+ setScale(pdfViewer.currentScale || 1);
6516
+ setPagesCount(pdfViewer.pagesCount);
6517
+ setPageNumber(pdfViewer.currentPageNumber);
6518
+ };
6519
+ const onPageChanging = (event) => {
6520
+ if (typeof event.pageNumber === "number") {
6521
+ setPageNumber(event.pageNumber);
6522
+ }
6523
+ };
6524
+ const onScaleChanging = (event) => {
6525
+ if (typeof event.scale === "number") {
6526
+ setScale(event.scale);
6527
+ }
6528
+ };
6529
+ eventBus.on("pagesinit", onPagesInit);
6530
+ eventBus.on("pagechanging", onPageChanging);
6531
+ eventBus.on("scalechanging", onScaleChanging);
6532
+ pdfViewerRef.current = pdfViewer;
6533
+ eventBusRef.current = eventBus;
6534
+ findControllerRef.current = findController;
6535
+ setPagesCount(pdf.numPages);
6536
+ pdfViewer.setDocument(pdf);
6537
+ };
6538
+ void renderPdf().catch((nextError) => {
6539
+ if (cancelled) return;
6540
+ if (nextError instanceof Error && nextError.name === "RenderingCancelledException") {
6541
+ return;
6542
+ }
6543
+ setError(formatPdfPreviewError(nextError));
6544
+ });
6545
+ return () => {
6546
+ cancelled = true;
6547
+ loadingTask?.destroy();
6548
+ pdfDocument?.destroy();
6549
+ pdfViewerRef.current?.cleanup();
6550
+ pdfViewerRef.current = null;
6551
+ eventBusRef.current = null;
6552
+ findControllerRef.current = null;
6553
+ };
6554
+ }, [file.content]);
6555
+ const setViewerPage = (nextPage) => {
6556
+ const viewer = pdfViewerRef.current;
6557
+ if (!viewer) return;
6558
+ const clampedPage = Math.min(
6559
+ Math.max(1, nextPage),
6560
+ Math.max(1, viewer.pagesCount)
6561
+ );
6562
+ viewer.currentPageNumber = clampedPage;
6563
+ setPageNumber(clampedPage);
6564
+ };
6565
+ const setViewerScale = (nextScale) => {
6566
+ const viewer = pdfViewerRef.current;
6567
+ if (!viewer) return;
6568
+ const clampedScale = Math.min(3, Math.max(0.5, nextScale));
6569
+ viewer.currentScale = clampedScale;
6570
+ setScale(clampedScale);
6571
+ };
6572
+ const runFind = (findPrevious = false) => {
6573
+ const eventBus = eventBusRef.current;
6574
+ if (!eventBus || !query.trim()) return;
6575
+ eventBus.dispatch("find", {
6576
+ source: eventBus,
6577
+ type: "again",
6578
+ query,
6579
+ phraseSearch: true,
6580
+ caseSensitive: false,
6581
+ entireWord: false,
6582
+ highlightAll: true,
6583
+ findPrevious,
6584
+ matchDiacritics: true
6585
+ });
6586
+ };
6587
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
6588
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-1.5 border-b border-line px-2 py-1.5", children: [
6589
+ /* @__PURE__ */ jsx(
6590
+ Button,
6591
+ {
6592
+ type: "button",
6593
+ variant: "ghost",
6594
+ size: "icon-xs",
6595
+ onClick: () => setViewerPage(pageNumber - 1),
6596
+ disabled: pageNumber <= 1,
6597
+ "aria-label": "Previous PDF page",
6598
+ title: "Previous page",
6599
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, { className: "size-3.5" })
6600
+ }
6601
+ ),
6602
+ /* @__PURE__ */ jsx(
6603
+ Input,
6604
+ {
6605
+ type: "number",
6606
+ min: 1,
6607
+ max: pagesCount || 1,
6608
+ value: pageNumber,
6609
+ onChange: (event) => setViewerPage(Number(event.target.value)),
6610
+ "aria-label": "PDF page number",
6611
+ className: "h-7 w-12 border-input px-1 text-center text-xs"
6612
+ }
6613
+ ),
6614
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
6615
+ "/ ",
6616
+ pagesCount || "-"
6617
+ ] }),
6618
+ /* @__PURE__ */ jsx(
6619
+ Button,
6620
+ {
6621
+ type: "button",
6622
+ variant: "ghost",
6623
+ size: "icon-xs",
6624
+ onClick: () => setViewerPage(pageNumber + 1),
6625
+ disabled: pagesCount > 0 && pageNumber >= pagesCount,
6626
+ "aria-label": "Next PDF page",
6627
+ title: "Next page",
6628
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5" })
6629
+ }
6630
+ ),
6631
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
6632
+ /* @__PURE__ */ jsx(
6633
+ Button,
6634
+ {
6635
+ type: "button",
6636
+ variant: "ghost",
6637
+ size: "icon-xs",
6638
+ onClick: () => setViewerScale(scale - 0.1),
6639
+ disabled: scale <= 0.5,
6640
+ "aria-label": "Zoom out PDF",
6641
+ title: "Zoom out",
6642
+ children: /* @__PURE__ */ jsx(MinusIcon, { className: "size-3.5" })
6643
+ }
6644
+ ),
6645
+ /* @__PURE__ */ jsxs(
6646
+ "button",
6647
+ {
6648
+ type: "button",
6649
+ className: "h-7 min-w-12 px-1 text-xs text-muted-foreground hover:text-foreground",
6650
+ onClick: () => {
6651
+ const viewer = pdfViewerRef.current;
6652
+ if (!viewer) return;
6653
+ viewer.currentScaleValue = "page-width";
6654
+ setScale(viewer.currentScale || 1);
6655
+ },
6656
+ "aria-label": "Fit PDF to width",
6657
+ title: "Fit width",
6658
+ children: [
6659
+ Math.round(scale * 100),
6660
+ "%"
6661
+ ]
6662
+ }
6663
+ ),
6664
+ /* @__PURE__ */ jsx(
6665
+ Button,
6666
+ {
6667
+ type: "button",
6668
+ variant: "ghost",
6669
+ size: "icon-xs",
6670
+ onClick: () => setViewerScale(scale + 0.1),
6671
+ disabled: scale >= 3,
6672
+ "aria-label": "Zoom in PDF",
6673
+ title: "Zoom in",
6674
+ children: /* @__PURE__ */ jsx(PlusIcon, { className: "size-3.5" })
6675
+ }
6676
+ ),
6677
+ /* @__PURE__ */ jsx("div", { className: "mx-1 h-5 w-px bg-line" }),
6678
+ /* @__PURE__ */ jsx(SearchIcon, { className: "size-3.5 text-muted-foreground" }),
6679
+ /* @__PURE__ */ jsx(
6680
+ Input,
6681
+ {
6682
+ type: "search",
6683
+ value: query,
6684
+ onChange: (event) => setQuery(event.target.value),
6685
+ onKeyDown: (event) => {
6686
+ if (event.key === "Enter") runFind(event.shiftKey);
6687
+ },
6688
+ placeholder: "Find",
6689
+ "aria-label": "Find in PDF",
6690
+ className: "h-7 w-28 border-input px-1 text-xs"
6691
+ }
6692
+ ),
6693
+ /* @__PURE__ */ jsx(
6694
+ Button,
6695
+ {
6696
+ type: "button",
6697
+ variant: "ghost",
6698
+ size: "xs",
6699
+ onClick: () => runFind(false),
6700
+ disabled: !query.trim(),
6701
+ children: "Find"
6702
+ }
6703
+ )
6704
+ ] }),
6705
+ error && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-2 p-3 text-xs text-destructive", children: [
6706
+ /* @__PURE__ */ jsx(AlertCircleIcon, { className: "mt-0.5 size-3.5 shrink-0" }),
6707
+ /* @__PURE__ */ jsx("span", { children: error })
6708
+ ] }),
6709
+ !error && /* @__PURE__ */ jsx("div", { className: "relative h-[70vh] min-h-[420px] flex-1 bg-muted/20", children: /* @__PURE__ */ jsx(
6710
+ "div",
6711
+ {
6712
+ ref: containerRef,
6713
+ "aria-label": `PDF viewer for ${fileName2}`,
6714
+ className: "absolute inset-0 overflow-auto",
6715
+ children: /* @__PURE__ */ jsx("div", { ref: viewerRef, className: "pdfViewer" })
6716
+ }
6717
+ ) })
6718
+ ] });
6719
+ }
6720
+ function decodeBase64(content) {
6721
+ const binary = globalThis.atob(content);
6722
+ const bytes = new Uint8Array(binary.length);
6723
+ for (let index = 0; index < binary.length; index += 1) {
6724
+ bytes[index] = binary.charCodeAt(index);
6725
+ }
6726
+ return bytes;
6727
+ }
6728
+ function formatPdfPreviewError(error) {
6729
+ if (error instanceof Error && (error.name === "InvalidCharacterError" || error.message === "Invalid character")) {
6730
+ return "PDF preview data is not valid base64.";
6731
+ }
6732
+ return error instanceof Error ? error.message : "Failed to render PDF preview.";
6733
+ }
6473
6734
  var SKELETON_WIDTHS2 = [75, 60, 90, 65, 80, 70, 85, 55];
6474
6735
  var DEFAULT_WIDTH = 500;
6475
6736
  var MIN_WIDTH = 300;
6476
6737
  var MAX_WIDTH = 900;
6477
- function isViewable(name) {
6478
- const dot = name.lastIndexOf(".");
6479
- const ext = dot >= 0 ? name.slice(dot).toLowerCase() : "";
6480
- return TEXT_EXTENSIONS.has(ext) || IMAGE_EXTENSIONS.has(ext);
6481
- }
6482
6738
  function collectExpandedPaths(entries, depth = 0) {
6483
6739
  const paths = [];
6484
6740
  for (const entry of entries) {
@@ -6503,8 +6759,8 @@ function findEntry(entries, path) {
6503
6759
  }
6504
6760
  function RenderEntries({
6505
6761
  entries,
6506
- onFileSelect,
6507
- onDelete
6762
+ onDelete,
6763
+ downloadUrlFor
6508
6764
  }) {
6509
6765
  return /* @__PURE__ */ jsx(Fragment, { children: entries.map((entry) => {
6510
6766
  if (entry.kind === "dir") {
@@ -6512,27 +6768,26 @@ function RenderEntries({
6512
6768
  RenderEntries,
6513
6769
  {
6514
6770
  entries: entry.children,
6515
- onFileSelect,
6516
- onDelete
6771
+ onDelete,
6772
+ downloadUrlFor
6517
6773
  }
6518
6774
  ) }, entry.path);
6519
6775
  }
6520
- const viewable = isViewable(entry.name);
6776
+ const fileName2 = entry.name;
6521
6777
  return /* @__PURE__ */ jsxs(FileTreeFile, { name: entry.name, path: entry.path, children: [
6522
6778
  /* @__PURE__ */ jsx("span", { className: "size-4 shrink-0" }),
6523
6779
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.name }),
6524
6780
  entry.size != null && /* @__PURE__ */ jsx("span", { className: "ml-1 shrink-0 text-xs text-muted-foreground/60", children: formatFileSize(entry.size) }),
6525
6781
  /* @__PURE__ */ jsxs("div", { className: "ml-1 flex shrink-0 items-center gap-0 opacity-0 transition-opacity group-hover:opacity-100", children: [
6526
- viewable && /* @__PURE__ */ jsx(
6527
- "button",
6782
+ /* @__PURE__ */ jsx(
6783
+ "a",
6528
6784
  {
6529
- type: "button",
6785
+ href: downloadUrlFor(entry.path),
6786
+ download: fileName2,
6530
6787
  className: "rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground",
6531
- onClick: (event) => {
6532
- event.stopPropagation();
6533
- onFileSelect(entry.path);
6534
- },
6535
- title: "View",
6788
+ onClick: (event) => event.stopPropagation(),
6789
+ title: "Download",
6790
+ "aria-label": `Download ${fileName2}`,
6536
6791
  children: /* @__PURE__ */ jsx(DownloadIcon, { className: "size-3" })
6537
6792
  }
6538
6793
  ),
@@ -6546,6 +6801,7 @@ function RenderEntries({
6546
6801
  onDelete(entry.path);
6547
6802
  },
6548
6803
  title: "Delete",
6804
+ "aria-label": `Delete ${fileName2}`,
6549
6805
  children: /* @__PURE__ */ jsx(TrashIcon, { className: "size-3" })
6550
6806
  }
6551
6807
  )
@@ -6863,8 +7119,8 @@ function WorkspacePanel({
6863
7119
  RenderEntries,
6864
7120
  {
6865
7121
  entries,
6866
- onFileSelect: handleSelect,
6867
- onDelete: setDeleteTarget
7122
+ onDelete: setDeleteTarget,
7123
+ downloadUrlFor: (path) => sessionId ? adapter.getWorkspaceDownloadUrl({ sessionId, path }) : "#"
6868
7124
  }
6869
7125
  )
6870
7126
  }
@@ -6876,7 +7132,13 @@ function WorkspacePanel({
6876
7132
  file,
6877
7133
  loading: fileLoading,
6878
7134
  error: fileError,
7135
+ downloadUrl: file && sessionId ? adapter.getWorkspaceDownloadUrl({
7136
+ sessionId,
7137
+ path: file.path
7138
+ }) : null,
7139
+ onDelete: file ? () => setDeleteTarget(file.path) : null,
6879
7140
  onClose: () => {
7141
+ onSelectedPathChange(null);
6880
7142
  setFile(null);
6881
7143
  setFileError(null);
6882
7144
  }