@tangle-network/ui 11.7.0 → 11.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/chat.js +8 -7
- package/dist/chunk-4CLN43XT.js +45 -0
- package/dist/{chunk-UYTRNR7Z.js → chunk-565EATZZ.js} +1 -1
- package/dist/{chunk-4ENFLPWQ.js → chunk-BWOUSEHM.js} +1 -1
- package/dist/{chunk-XT4JMYVB.js → chunk-KNYSKNZX.js} +5 -3
- package/dist/{chunk-EIUE5GV2.js → chunk-ND3ZFXQK.js} +1 -44
- package/dist/{chunk-AAUNOHVL.js → chunk-NWECTSOH.js} +1 -1
- package/dist/{chunk-CVX53NCC.js → chunk-OXZRCXJK.js} +5 -5
- package/dist/{chunk-6RIX7M27.js → chunk-Q7ZR6LSD.js} +1 -1
- package/dist/{chunk-VDCZHOOO.js → chunk-X53RZSX6.js} +4 -4
- package/dist/{chunk-PLWRAS5Z.js → chunk-XGOWUDDW.js} +270 -106
- package/dist/{chunk-ZRVH3WCA.js → chunk-XU3HB5QL.js} +21 -1
- package/dist/{chunk-LFVF2HFB.js → chunk-YTO5V6QS.js} +1 -1
- package/dist/files.d.ts +41 -12
- package/dist/files.js +13 -5
- package/dist/hooks.js +8 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.js +30 -22
- package/dist/run.js +8 -7
- package/dist/sdk-hooks.js +8 -7
- package/dist/tool-previews.js +2 -2
- package/dist/utils.js +6 -4
- package/package.json +1 -1
- package/src/files/file-artifact-pane.stories.tsx +149 -0
- package/src/files/file-artifact-pane.test.tsx +192 -0
- package/src/files/file-artifact-pane.tsx +6 -1
- package/src/files/file-format.ts +42 -4
- package/src/files/file-preview-kind.test.ts +109 -0
- package/src/files/file-preview.tsx +315 -132
- package/src/files/index.ts +8 -1
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBytes
|
|
3
|
+
} from "./chunk-4CLN43XT.js";
|
|
1
4
|
import {
|
|
2
5
|
detectFileFormat,
|
|
3
6
|
fileExtension,
|
|
4
7
|
getCodeLanguage,
|
|
5
|
-
getFormatLabel
|
|
6
|
-
|
|
8
|
+
getFormatLabel,
|
|
9
|
+
resolveFilePreviewKind
|
|
10
|
+
} from "./chunk-XU3HB5QL.js";
|
|
11
|
+
import {
|
|
12
|
+
Button
|
|
13
|
+
} from "./chunk-MKTSMWVD.js";
|
|
7
14
|
import {
|
|
8
15
|
ArtifactPane
|
|
9
16
|
} from "./chunk-LTEKI7YJ.js";
|
|
@@ -315,19 +322,26 @@ function RichFileTree({
|
|
|
315
322
|
}
|
|
316
323
|
|
|
317
324
|
// src/files/file-preview.tsx
|
|
325
|
+
import { useState as useState2 } from "react";
|
|
318
326
|
import {
|
|
319
327
|
Download,
|
|
320
|
-
|
|
321
|
-
FileText as FileText2
|
|
328
|
+
FileSpreadsheet as FileSpreadsheet2,
|
|
329
|
+
FileText as FileText2,
|
|
330
|
+
Music,
|
|
331
|
+
X
|
|
322
332
|
} from "lucide-react";
|
|
323
333
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
334
|
+
var CSV_PREVIEW_ROW_LIMIT = 500;
|
|
335
|
+
var NEEDS_DOWNLOAD_LINK = "This file needs a download link to preview.";
|
|
336
|
+
var NO_INLINE_PREVIEW = "This file type has no inline preview.";
|
|
337
|
+
var NO_INLINE_CONTENT = "This file has no inline content to preview yet.";
|
|
324
338
|
function CodePreview({
|
|
325
339
|
content,
|
|
326
340
|
filename,
|
|
327
|
-
|
|
341
|
+
kind
|
|
328
342
|
}) {
|
|
329
343
|
const lineCount = content.split("\n").length;
|
|
330
|
-
const language = getCodeLanguage(filename,
|
|
344
|
+
const language = getCodeLanguage(filename, kind);
|
|
331
345
|
const labelToken = fileExtension(filename) || language || "txt";
|
|
332
346
|
return /* @__PURE__ */ jsx3(
|
|
333
347
|
CodeBlock,
|
|
@@ -341,112 +355,272 @@ function CodePreview({
|
|
|
341
355
|
}
|
|
342
356
|
);
|
|
343
357
|
}
|
|
344
|
-
function
|
|
345
|
-
const
|
|
346
|
-
let
|
|
358
|
+
function parseCsv(content) {
|
|
359
|
+
const rows = [];
|
|
360
|
+
let row = [];
|
|
361
|
+
let cell = "";
|
|
362
|
+
let quoted = false;
|
|
347
363
|
let inQuotes = false;
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
364
|
+
const endCell = () => {
|
|
365
|
+
row.push(quoted ? cell : cell.trim());
|
|
366
|
+
cell = "";
|
|
367
|
+
quoted = false;
|
|
368
|
+
};
|
|
369
|
+
const endRow = () => {
|
|
370
|
+
endCell();
|
|
371
|
+
if (row.length > 1 || row[0] !== "") rows.push(row);
|
|
372
|
+
row = [];
|
|
373
|
+
};
|
|
374
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
375
|
+
const char = content[index];
|
|
376
|
+
if (inQuotes) {
|
|
377
|
+
if (char === '"') {
|
|
378
|
+
if (content[index + 1] === '"') {
|
|
379
|
+
cell += '"';
|
|
380
|
+
index += 1;
|
|
381
|
+
} else {
|
|
382
|
+
inQuotes = false;
|
|
383
|
+
}
|
|
384
|
+
} else {
|
|
385
|
+
cell += char;
|
|
356
386
|
}
|
|
357
|
-
inQuotes = !inQuotes;
|
|
358
387
|
continue;
|
|
359
388
|
}
|
|
360
|
-
if (char === "
|
|
361
|
-
|
|
362
|
-
|
|
389
|
+
if (char === '"') {
|
|
390
|
+
inQuotes = true;
|
|
391
|
+
quoted = true;
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
if (char === ",") {
|
|
395
|
+
endCell();
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (char === "\r" || char === "\n") {
|
|
399
|
+
if (char === "\r" && content[index + 1] === "\n") index += 1;
|
|
400
|
+
endRow();
|
|
363
401
|
continue;
|
|
364
402
|
}
|
|
365
|
-
|
|
403
|
+
cell += char;
|
|
366
404
|
}
|
|
367
|
-
|
|
368
|
-
return
|
|
405
|
+
if (cell.length > 0 || quoted || row.length > 0) endRow();
|
|
406
|
+
return rows;
|
|
369
407
|
}
|
|
370
408
|
function CsvPreview({ content }) {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
409
|
+
const [headers = [], ...body] = parseCsv(content);
|
|
410
|
+
if (headers.length === 0) return null;
|
|
411
|
+
const visible = body.slice(0, CSV_PREVIEW_ROW_LIMIT);
|
|
412
|
+
const columnCount = Math.max(headers.length, ...visible.map((cells) => cells.length));
|
|
413
|
+
const capped = body.length > visible.length;
|
|
414
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex min-h-0 flex-1 flex-col gap-2", children: [
|
|
415
|
+
/* @__PURE__ */ jsx3("div", { className: "min-h-0 overflow-auto rounded-[var(--radius-md)] border border-border", children: /* @__PURE__ */ jsxs2("table", { className: "w-full text-sm", children: [
|
|
416
|
+
/* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsx3("tr", { children: Array.from({ length: columnCount }, (_, column) => /* @__PURE__ */ jsx3(
|
|
417
|
+
"th",
|
|
418
|
+
{
|
|
419
|
+
className: "sticky top-0 whitespace-nowrap border-b border-border bg-card px-3 py-2 text-left text-xs font-semibold text-foreground",
|
|
420
|
+
children: headers[column] ?? ""
|
|
421
|
+
},
|
|
422
|
+
column
|
|
423
|
+
)) }) }),
|
|
424
|
+
/* @__PURE__ */ jsx3("tbody", { children: visible.map((cells, rowIndex) => /* @__PURE__ */ jsx3("tr", { className: "border-b border-border hover:bg-accent", children: Array.from({ length: columnCount }, (_, column) => /* @__PURE__ */ jsx3(
|
|
425
|
+
"td",
|
|
426
|
+
{
|
|
427
|
+
className: "whitespace-nowrap px-3 py-1.5 font-mono text-xs text-foreground",
|
|
428
|
+
children: cells[column] ?? ""
|
|
429
|
+
},
|
|
430
|
+
column
|
|
431
|
+
)) }, rowIndex)) })
|
|
432
|
+
] }) }),
|
|
433
|
+
/* @__PURE__ */ jsxs2("p", { className: "shrink-0 text-xs text-muted-foreground", children: [
|
|
434
|
+
capped ? `Showing ${visible.length} of ${body.length} rows` : `${body.length} ${body.length === 1 ? "row" : "rows"}`,
|
|
435
|
+
` \xB7 ${columnCount} ${columnCount === 1 ? "column" : "columns"}`
|
|
436
|
+
] })
|
|
437
|
+
] });
|
|
395
438
|
}
|
|
439
|
+
var CHECKER_STYLE = {
|
|
440
|
+
backgroundColor: "var(--color-background)",
|
|
441
|
+
backgroundImage: "repeating-conic-gradient(var(--color-muted) 0% 25%, transparent 0% 50%)",
|
|
442
|
+
backgroundSize: "16px 16px"
|
|
443
|
+
};
|
|
396
444
|
function ImagePreview({ src, filename }) {
|
|
397
|
-
|
|
445
|
+
const [naturalSize, setNaturalSize] = useState2(false);
|
|
446
|
+
return /* @__PURE__ */ jsx3(
|
|
447
|
+
"div",
|
|
448
|
+
{
|
|
449
|
+
className: "flex min-h-[12rem] flex-1 overflow-auto rounded-[var(--radius-md)] border border-border",
|
|
450
|
+
style: CHECKER_STYLE,
|
|
451
|
+
children: /* @__PURE__ */ jsx3(
|
|
452
|
+
"button",
|
|
453
|
+
{
|
|
454
|
+
type: "button",
|
|
455
|
+
onClick: () => setNaturalSize((value) => !value),
|
|
456
|
+
"aria-pressed": naturalSize,
|
|
457
|
+
"aria-label": naturalSize ? "Fit image to pane" : "Show image at natural size",
|
|
458
|
+
className: cn(
|
|
459
|
+
"flex items-center justify-center focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary/60",
|
|
460
|
+
naturalSize ? "m-auto cursor-zoom-out" : "h-full w-full cursor-zoom-in p-4"
|
|
461
|
+
),
|
|
462
|
+
children: /* @__PURE__ */ jsx3(
|
|
463
|
+
"img",
|
|
464
|
+
{
|
|
465
|
+
src,
|
|
466
|
+
alt: filename,
|
|
467
|
+
draggable: false,
|
|
468
|
+
className: naturalSize ? "max-w-none" : "max-h-full max-w-full object-contain"
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
);
|
|
398
475
|
}
|
|
399
|
-
function PdfPreview({
|
|
400
|
-
|
|
401
|
-
|
|
476
|
+
function PdfPreview({
|
|
477
|
+
blobUrl,
|
|
478
|
+
filename,
|
|
479
|
+
size,
|
|
480
|
+
onDownload
|
|
481
|
+
}) {
|
|
482
|
+
return /* @__PURE__ */ jsx3(
|
|
483
|
+
"object",
|
|
402
484
|
{
|
|
403
|
-
|
|
485
|
+
data: blobUrl,
|
|
486
|
+
type: "application/pdf",
|
|
404
487
|
title: filename,
|
|
405
|
-
className: "w-full
|
|
488
|
+
className: "min-h-[24rem] w-full flex-1 rounded-[var(--radius-md)]",
|
|
489
|
+
children: /* @__PURE__ */ jsx3(
|
|
490
|
+
DownloadCard,
|
|
491
|
+
{
|
|
492
|
+
filename,
|
|
493
|
+
size,
|
|
494
|
+
sentence: "This browser does not show PDFs inline.",
|
|
495
|
+
onDownload,
|
|
496
|
+
className: "h-full"
|
|
497
|
+
}
|
|
498
|
+
)
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
function VideoPreview({ src, filename }) {
|
|
503
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex min-h-[12rem] flex-1 items-center justify-center overflow-hidden rounded-[var(--radius-md)] border border-border bg-background", children: /* @__PURE__ */ jsx3(
|
|
504
|
+
"video",
|
|
505
|
+
{
|
|
506
|
+
controls: true,
|
|
507
|
+
preload: "metadata",
|
|
508
|
+
src,
|
|
509
|
+
"aria-label": filename,
|
|
510
|
+
className: "max-h-full max-w-full"
|
|
406
511
|
}
|
|
407
512
|
) });
|
|
408
513
|
}
|
|
514
|
+
function AudioPreview({ src, filename }) {
|
|
515
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center gap-4 rounded-[var(--radius-md)] border border-border bg-background px-6 py-10", children: [
|
|
516
|
+
/* @__PURE__ */ jsx3(Music, { className: "h-10 w-10 text-muted-foreground opacity-60" }),
|
|
517
|
+
/* @__PURE__ */ jsx3("p", { className: "max-w-full truncate text-sm font-medium text-foreground", children: filename }),
|
|
518
|
+
/* @__PURE__ */ jsx3("audio", { controls: true, preload: "metadata", src, "aria-label": filename, className: "w-full max-w-md" })
|
|
519
|
+
] });
|
|
520
|
+
}
|
|
409
521
|
function TextPreview({ content }) {
|
|
410
522
|
return /* @__PURE__ */ jsx3("pre", { className: "bg-card rounded-[var(--radius-md)] border border-border p-4 overflow-auto max-h-[70vh] text-sm text-foreground font-mono leading-[1.55]", children: content });
|
|
411
523
|
}
|
|
412
|
-
function UnsupportedPreview({
|
|
413
|
-
filename,
|
|
414
|
-
title,
|
|
415
|
-
description
|
|
416
|
-
}) {
|
|
417
|
-
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center rounded-[var(--radius-md)] border border-dashed border-border bg-background px-6 py-16 text-center text-muted-foreground", children: [
|
|
418
|
-
/* @__PURE__ */ jsx3(FileText2, { className: "mb-3 h-12 w-12 opacity-30" }),
|
|
419
|
-
/* @__PURE__ */ jsx3("p", { className: "text-sm text-foreground", children: title }),
|
|
420
|
-
/* @__PURE__ */ jsx3("p", { className: "mt-1 max-w-md text-xs", children: description }),
|
|
421
|
-
/* @__PURE__ */ jsx3("p", { className: "mt-4 text-[11px] uppercase tracking-[0.12em]", children: filename })
|
|
422
|
-
] });
|
|
423
|
-
}
|
|
424
524
|
function MarkdownPreview({ content }) {
|
|
425
525
|
return /* @__PURE__ */ jsx3("div", { className: "rounded-[var(--radius-md)] border border-border bg-background p-5", children: /* @__PURE__ */ jsx3(Markdown, { children: content }) });
|
|
426
526
|
}
|
|
427
|
-
function
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
527
|
+
function DownloadCard({
|
|
528
|
+
filename,
|
|
529
|
+
size,
|
|
530
|
+
sentence,
|
|
531
|
+
icon: Icon = FileText2,
|
|
532
|
+
onDownload,
|
|
533
|
+
className
|
|
534
|
+
}) {
|
|
535
|
+
return /* @__PURE__ */ jsxs2(
|
|
536
|
+
"div",
|
|
537
|
+
{
|
|
538
|
+
className: cn(
|
|
539
|
+
"flex flex-col items-center justify-center rounded-[var(--radius-md)] border border-dashed border-border bg-background px-6 py-16 text-center",
|
|
540
|
+
className
|
|
541
|
+
),
|
|
542
|
+
children: [
|
|
543
|
+
/* @__PURE__ */ jsx3(Icon, { className: "mb-3 h-12 w-12 text-muted-foreground opacity-40" }),
|
|
544
|
+
/* @__PURE__ */ jsx3("p", { className: "max-w-full truncate text-sm font-medium text-foreground", children: filename }),
|
|
545
|
+
size !== void 0 && /* @__PURE__ */ jsx3("p", { className: "mt-1 text-xs text-muted-foreground", children: formatBytes(size) }),
|
|
546
|
+
/* @__PURE__ */ jsx3("p", { className: "mt-3 max-w-md text-sm text-muted-foreground", children: sentence }),
|
|
547
|
+
onDownload && /* @__PURE__ */ jsxs2(Button, { type: "button", variant: "outline", size: "sm", className: "mt-5", onClick: onDownload, children: [
|
|
548
|
+
/* @__PURE__ */ jsx3(Download, {}),
|
|
549
|
+
"Download"
|
|
550
|
+
] })
|
|
551
|
+
]
|
|
552
|
+
}
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
function PreviewBody({
|
|
556
|
+
kind,
|
|
557
|
+
filename,
|
|
558
|
+
content,
|
|
559
|
+
blobUrl,
|
|
560
|
+
size,
|
|
561
|
+
onDownload
|
|
562
|
+
}) {
|
|
563
|
+
const hasText = typeof content === "string";
|
|
564
|
+
const card = (sentence, icon) => /* @__PURE__ */ jsx3(
|
|
565
|
+
DownloadCard,
|
|
566
|
+
{
|
|
567
|
+
filename,
|
|
568
|
+
size,
|
|
569
|
+
sentence,
|
|
570
|
+
icon,
|
|
571
|
+
onDownload
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
switch (kind) {
|
|
575
|
+
case "pdf":
|
|
576
|
+
if (blobUrl) {
|
|
577
|
+
return /* @__PURE__ */ jsx3(PdfPreview, { blobUrl, filename, size, onDownload });
|
|
578
|
+
}
|
|
579
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
580
|
+
case "image":
|
|
581
|
+
if (blobUrl) return /* @__PURE__ */ jsx3(ImagePreview, { src: blobUrl, filename });
|
|
582
|
+
if (hasText) return /* @__PURE__ */ jsx3(CodePreview, { content, filename, kind: "code" });
|
|
583
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
584
|
+
case "video":
|
|
585
|
+
if (blobUrl) return /* @__PURE__ */ jsx3(VideoPreview, { src: blobUrl, filename });
|
|
586
|
+
return card(NEEDS_DOWNLOAD_LINK);
|
|
587
|
+
case "audio":
|
|
588
|
+
if (blobUrl) return /* @__PURE__ */ jsx3(AudioPreview, { src: blobUrl, filename });
|
|
589
|
+
return card(NEEDS_DOWNLOAD_LINK, Music);
|
|
590
|
+
case "csv":
|
|
591
|
+
if (hasText) return /* @__PURE__ */ jsx3(CsvPreview, { content });
|
|
592
|
+
return card(NO_INLINE_CONTENT, FileSpreadsheet2);
|
|
593
|
+
case "code":
|
|
594
|
+
case "json":
|
|
595
|
+
case "yaml":
|
|
596
|
+
if (hasText) return /* @__PURE__ */ jsx3(CodePreview, { content, filename, kind });
|
|
597
|
+
return card(NO_INLINE_CONTENT);
|
|
598
|
+
case "markdown":
|
|
599
|
+
if (hasText) return /* @__PURE__ */ jsx3(MarkdownPreview, { content });
|
|
600
|
+
return card(NO_INLINE_CONTENT);
|
|
601
|
+
case "text":
|
|
602
|
+
if (hasText) return /* @__PURE__ */ jsx3(TextPreview, { content });
|
|
603
|
+
return card(NO_INLINE_CONTENT);
|
|
604
|
+
case "spreadsheet":
|
|
605
|
+
return card("Download to open this workbook in a spreadsheet app.", FileSpreadsheet2);
|
|
606
|
+
case "binary":
|
|
607
|
+
if (hasText) return /* @__PURE__ */ jsx3(TextPreview, { content });
|
|
608
|
+
return card(blobUrl ? NO_INLINE_PREVIEW : NEEDS_DOWNLOAD_LINK);
|
|
609
|
+
}
|
|
436
610
|
}
|
|
437
611
|
function FilePreview({
|
|
438
612
|
filename,
|
|
439
613
|
content,
|
|
440
614
|
blobUrl,
|
|
441
615
|
mimeType,
|
|
616
|
+
size,
|
|
442
617
|
onClose,
|
|
443
618
|
onDownload,
|
|
444
619
|
hideHeader = false,
|
|
445
620
|
className
|
|
446
621
|
}) {
|
|
447
|
-
const
|
|
448
|
-
const previewLabel = getFormatLabel(
|
|
449
|
-
const hasRenderableSource = Boolean(content) || Boolean(blobUrl) || format === "unknown" || format === "spreadsheet";
|
|
622
|
+
const kind = resolveFilePreviewKind(filename, mimeType);
|
|
623
|
+
const previewLabel = getFormatLabel(kind);
|
|
450
624
|
return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col h-full", className), children: [
|
|
451
625
|
!hideHeader && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border shrink-0", children: [
|
|
452
626
|
/* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -474,42 +648,27 @@ function FilePreview({
|
|
|
474
648
|
}
|
|
475
649
|
)
|
|
476
650
|
] }),
|
|
477
|
-
/* @__PURE__ */
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
title: "Spreadsheet preview is not available in this surface",
|
|
489
|
-
description: "Download the workbook or convert it to CSV when you need an inline preview."
|
|
490
|
-
}
|
|
491
|
-
),
|
|
492
|
-
format === "unknown" && typeof content !== "string" && /* @__PURE__ */ jsx3(EmptyPreview, { filename }),
|
|
493
|
-
format === "unknown" && typeof content === "string" && /* @__PURE__ */ jsx3(TextPreview, { content }),
|
|
494
|
-
!hasRenderableSource && typeof content !== "string" && /* @__PURE__ */ jsx3(
|
|
495
|
-
UnsupportedPreview,
|
|
496
|
-
{
|
|
497
|
-
filename,
|
|
498
|
-
title: "Preview data is not available yet",
|
|
499
|
-
description: "This artifact can be shown once the app provides inline content or a downloadable blob."
|
|
500
|
-
}
|
|
501
|
-
)
|
|
502
|
-
] })
|
|
651
|
+
/* @__PURE__ */ jsx3("div", { className: "flex min-h-0 flex-1 flex-col overflow-auto p-3", children: /* @__PURE__ */ jsx3(
|
|
652
|
+
PreviewBody,
|
|
653
|
+
{
|
|
654
|
+
kind,
|
|
655
|
+
filename,
|
|
656
|
+
content,
|
|
657
|
+
blobUrl,
|
|
658
|
+
size,
|
|
659
|
+
onDownload
|
|
660
|
+
}
|
|
661
|
+
) })
|
|
503
662
|
] });
|
|
504
663
|
}
|
|
505
664
|
|
|
506
665
|
// src/files/file-tabs.tsx
|
|
507
|
-
import { X as X2, FileText as FileText3, FileCode as FileCode2, FileSpreadsheet as
|
|
666
|
+
import { X as X2, FileText as FileText3, FileCode as FileCode2, FileSpreadsheet as FileSpreadsheet3 } from "lucide-react";
|
|
508
667
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
509
668
|
function getTabIcon(name) {
|
|
510
669
|
const ext = name.split(".").pop()?.toLowerCase() || "";
|
|
511
670
|
if (["pdf"].includes(ext)) return FileText3;
|
|
512
|
-
if (["csv", "xlsx"].includes(ext)) return
|
|
671
|
+
if (["csv", "xlsx"].includes(ext)) return FileSpreadsheet3;
|
|
513
672
|
return FileCode2;
|
|
514
673
|
}
|
|
515
674
|
function FileTabs({ tabs, activeId, onSelect, onClose, className }) {
|
|
@@ -568,6 +727,7 @@ function FileArtifactPane({
|
|
|
568
727
|
content,
|
|
569
728
|
blobUrl,
|
|
570
729
|
mimeType,
|
|
730
|
+
size,
|
|
571
731
|
onClose,
|
|
572
732
|
onDownload,
|
|
573
733
|
path,
|
|
@@ -694,6 +854,8 @@ function FileArtifactPane({
|
|
|
694
854
|
content,
|
|
695
855
|
blobUrl,
|
|
696
856
|
mimeType,
|
|
857
|
+
size,
|
|
858
|
+
onDownload,
|
|
697
859
|
hideHeader: true
|
|
698
860
|
}
|
|
699
861
|
)
|
|
@@ -705,6 +867,8 @@ export {
|
|
|
705
867
|
filterFileTree,
|
|
706
868
|
FileTree,
|
|
707
869
|
RichFileTree,
|
|
870
|
+
CSV_PREVIEW_ROW_LIMIT,
|
|
871
|
+
parseCsv,
|
|
708
872
|
FilePreview,
|
|
709
873
|
FileTabs,
|
|
710
874
|
FileArtifactPane
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// src/files/file-format.ts
|
|
2
|
-
var IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "svg", "webp"];
|
|
2
|
+
var IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "svg", "webp", "avif"];
|
|
3
|
+
var VIDEO_EXTENSIONS = ["mp4", "webm", "mov"];
|
|
4
|
+
var AUDIO_EXTENSIONS = ["mp3", "wav", "ogg", "m4a"];
|
|
5
|
+
var SPREADSHEET_MIME_TYPES = [
|
|
6
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
7
|
+
"application/vnd.ms-excel"
|
|
8
|
+
];
|
|
3
9
|
var EXTENSION_TO_SYNTAX_LANGUAGE = {
|
|
4
10
|
ts: "typescript",
|
|
5
11
|
tsx: "tsx",
|
|
@@ -50,12 +56,17 @@ function detectFileFormat(filename, mimeType) {
|
|
|
50
56
|
const mime = mimeEssence(mimeType);
|
|
51
57
|
if (mime === "application/pdf") return "pdf";
|
|
52
58
|
if (mime.startsWith("image/")) return "image";
|
|
59
|
+
if (mime.startsWith("video/")) return "video";
|
|
60
|
+
if (mime.startsWith("audio/")) return "audio";
|
|
53
61
|
if (mime === "text/markdown") return "markdown";
|
|
54
62
|
if (mime === "application/json") return "json";
|
|
55
63
|
if (mime === "text/csv" || mime === "application/csv") return "csv";
|
|
64
|
+
if (SPREADSHEET_MIME_TYPES.includes(mime)) return "spreadsheet";
|
|
56
65
|
if (mime === "application/yaml" || mime === "application/x-yaml" || mime === "text/yaml") return "yaml";
|
|
57
66
|
if (ext === "pdf") return "pdf";
|
|
58
67
|
if (IMAGE_EXTENSIONS.includes(ext)) return "image";
|
|
68
|
+
if (VIDEO_EXTENSIONS.includes(ext)) return "video";
|
|
69
|
+
if (AUDIO_EXTENSIONS.includes(ext)) return "audio";
|
|
59
70
|
if (ext === "csv") return "csv";
|
|
60
71
|
if (ext === "xlsx" || ext === "xls") return "spreadsheet";
|
|
61
72
|
if (CODE_EXTENSIONS.has(ext)) return "code";
|
|
@@ -66,12 +77,20 @@ function detectFileFormat(filename, mimeType) {
|
|
|
66
77
|
if (mime === "text/plain") return "text";
|
|
67
78
|
return "unknown";
|
|
68
79
|
}
|
|
80
|
+
function resolveFilePreviewKind(filename, mimeType) {
|
|
81
|
+
const format = detectFileFormat(filename, mimeType);
|
|
82
|
+
return format === "unknown" ? "binary" : format;
|
|
83
|
+
}
|
|
69
84
|
function getFormatLabel(format) {
|
|
70
85
|
switch (format) {
|
|
71
86
|
case "pdf":
|
|
72
87
|
return "PDF";
|
|
73
88
|
case "image":
|
|
74
89
|
return "Image";
|
|
90
|
+
case "video":
|
|
91
|
+
return "Video";
|
|
92
|
+
case "audio":
|
|
93
|
+
return "Audio";
|
|
75
94
|
case "csv":
|
|
76
95
|
return "CSV";
|
|
77
96
|
case "spreadsheet":
|
|
@@ -101,6 +120,7 @@ function getCodeLanguage(filename, format) {
|
|
|
101
120
|
export {
|
|
102
121
|
fileExtension,
|
|
103
122
|
detectFileFormat,
|
|
123
|
+
resolveFilePreviewKind,
|
|
104
124
|
getFormatLabel,
|
|
105
125
|
getSyntaxLanguage,
|
|
106
126
|
getCodeLanguage
|
package/dist/files.d.ts
CHANGED
|
@@ -98,25 +98,39 @@ declare function RichFileTree({ root, paths, selectedPath, onSelect, search, ini
|
|
|
98
98
|
/**
|
|
99
99
|
* FilePreview — universal file renderer.
|
|
100
100
|
*
|
|
101
|
-
* Renders any file type
|
|
102
|
-
* - PDF: embedded viewer
|
|
103
|
-
* -
|
|
101
|
+
* Renders any file type:
|
|
102
|
+
* - PDF: embedded viewer at full pane height
|
|
103
|
+
* - Images: fit to the pane on a checker ground; click toggles natural size
|
|
104
|
+
* - Video / audio: native players
|
|
105
|
+
* - CSV: sticky-header table, capped at CSV_PREVIEW_ROW_LIMIT rows
|
|
106
|
+
* - XLSX / XLS: download card
|
|
104
107
|
* - Code (py/json/yaml/ts/js): syntax-highlighted, line-numbered viewer
|
|
105
108
|
* - Markdown: rendered prose
|
|
106
|
-
* - Images: inline display
|
|
107
109
|
* - Text: monospace preview
|
|
110
|
+
* - Anything else: text when content is a string, otherwise a download card
|
|
108
111
|
*/
|
|
109
112
|
interface FilePreviewProps {
|
|
110
113
|
filename: string;
|
|
111
114
|
content?: string;
|
|
115
|
+
/** Object URL or remote URL for binary content: images, PDF, video, audio. */
|
|
112
116
|
blobUrl?: string;
|
|
113
117
|
mimeType?: string;
|
|
118
|
+
/** Size in bytes; download cards show it when known. */
|
|
119
|
+
size?: number;
|
|
114
120
|
onClose?: () => void;
|
|
115
121
|
onDownload?: () => void;
|
|
116
122
|
hideHeader?: boolean;
|
|
117
123
|
className?: string;
|
|
118
124
|
}
|
|
119
|
-
|
|
125
|
+
/** Rows rendered by the CSV preview; the count line reports the total. */
|
|
126
|
+
declare const CSV_PREVIEW_ROW_LIMIT = 500;
|
|
127
|
+
/**
|
|
128
|
+
* Split CSV text into rows of cells (RFC 4180): a quoted cell may hold commas,
|
|
129
|
+
* line breaks, and doubled quotes. Unquoted cells are trimmed; blank lines and
|
|
130
|
+
* a trailing line break add no row.
|
|
131
|
+
*/
|
|
132
|
+
declare function parseCsv(content: string): string[][];
|
|
133
|
+
declare function FilePreview({ filename, content, blobUrl, mimeType, size, onClose, onDownload, hideHeader, className, }: FilePreviewProps): react_jsx_runtime.JSX.Element;
|
|
120
134
|
|
|
121
135
|
/**
|
|
122
136
|
* FileTabs — tab bar for open files in the preview panel.
|
|
@@ -170,9 +184,11 @@ interface FileArtifactPaneProps extends Omit<FilePreviewProps, "className"> {
|
|
|
170
184
|
}
|
|
171
185
|
/**
|
|
172
186
|
* FileArtifactPane — opinionated artifact frame for file previews with tabs and
|
|
173
|
-
* header actions.
|
|
187
|
+
* header actions. The body is `FilePreview`, so every kind it renders (image,
|
|
188
|
+
* PDF, video, audio, CSV, spreadsheet, markdown, code, text) renders here too;
|
|
189
|
+
* `onDownload` drives both the header button and the body's download cards.
|
|
174
190
|
*/
|
|
175
|
-
declare function FileArtifactPane({ filename, content, blobUrl, mimeType, onClose, onDownload, path, tabs, activeTabId, onTabSelect, onTabClose, eyebrow, meta, toolbar, footer, className, headerClassName, hideTitleBlock, editor, }: FileArtifactPaneProps): react_jsx_runtime.JSX.Element;
|
|
191
|
+
declare function FileArtifactPane({ filename, content, blobUrl, mimeType, size, onClose, onDownload, path, tabs, activeTabId, onTabSelect, onTabClose, eyebrow, meta, toolbar, footer, className, headerClassName, hideTitleBlock, editor, }: FileArtifactPaneProps): react_jsx_runtime.JSX.Element;
|
|
176
192
|
|
|
177
193
|
/**
|
|
178
194
|
* Shared file-format detection for every file surface — the preview pane, the
|
|
@@ -180,7 +196,13 @@ declare function FileArtifactPane({ filename, content, blobUrl, mimeType, onClos
|
|
|
180
196
|
* keeps chat and artifact views consistent and avoids the same mapping drifting
|
|
181
197
|
* across components.
|
|
182
198
|
*/
|
|
183
|
-
type FileFormat = "pdf" | "image" | "csv" | "spreadsheet" | "code" | "json" | "yaml" | "markdown" | "text" | "unknown";
|
|
199
|
+
type FileFormat = "pdf" | "image" | "video" | "audio" | "csv" | "spreadsheet" | "code" | "json" | "yaml" | "markdown" | "text" | "unknown";
|
|
200
|
+
/**
|
|
201
|
+
* How a file surface renders a file. Every format maps to its own renderer;
|
|
202
|
+
* `unknown` becomes `binary`, which renders string content as text and offers a
|
|
203
|
+
* download for anything else.
|
|
204
|
+
*/
|
|
205
|
+
type FilePreviewKind = Exclude<FileFormat, "unknown"> | "binary";
|
|
184
206
|
/**
|
|
185
207
|
* Lowercased trailing extension. Returns "" for a name with no extension
|
|
186
208
|
* ("README", "json" → ""), and the post-dot name for a dotfile (".bashrc" →
|
|
@@ -194,8 +216,15 @@ declare function fileExtension(filename: string): string;
|
|
|
194
216
|
* decides; a generic text/plain payload is the final fallback.
|
|
195
217
|
*/
|
|
196
218
|
declare function detectFileFormat(filename: string, mimeType?: string): FileFormat;
|
|
197
|
-
/**
|
|
198
|
-
|
|
219
|
+
/**
|
|
220
|
+
* The preview renderer for a file, from its name and optional MIME type only.
|
|
221
|
+
* Same precedence as `detectFileFormat` (MIME outranks extension); a file that
|
|
222
|
+
* matches nothing is `binary`. Content is not an input: the preview renders a
|
|
223
|
+
* `binary` file's string content as text and offers a download otherwise.
|
|
224
|
+
*/
|
|
225
|
+
declare function resolveFilePreviewKind(filename: string, mimeType?: string): FilePreviewKind;
|
|
226
|
+
/** Human-facing label for a detected format or preview kind. */
|
|
227
|
+
declare function getFormatLabel(format: FileFormat | FilePreviewKind): string;
|
|
199
228
|
/**
|
|
200
229
|
* Map a filename (or path) to a highlight.js language id for CodeBlock. Returns
|
|
201
230
|
* undefined when there is no confident mapping; CodeBlock then renders themed
|
|
@@ -209,6 +238,6 @@ declare function getSyntaxLanguage(filename: string): string | undefined;
|
|
|
209
238
|
* a MIME type on an extensionless file (where the extension can't reveal it);
|
|
210
239
|
* any other code format keys off the extension.
|
|
211
240
|
*/
|
|
212
|
-
declare function getCodeLanguage(filename: string, format: FileFormat): string | undefined;
|
|
241
|
+
declare function getCodeLanguage(filename: string, format: FileFormat | FilePreviewKind): string | undefined;
|
|
213
242
|
|
|
214
|
-
export { FileArtifactPane, type FileArtifactPaneProps, type FileFormat, type FileNode, FilePreview, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, detectFileFormat, fileExtension, filterFileTree, getCodeLanguage, getFormatLabel, getSyntaxLanguage };
|
|
243
|
+
export { CSV_PREVIEW_ROW_LIMIT, FileArtifactPane, type FileArtifactPaneProps, type FileFormat, type FileNode, FilePreview, type FilePreviewKind, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, detectFileFormat, fileExtension, filterFileTree, getCodeLanguage, getFormatLabel, getSyntaxLanguage, parseCsv, resolveFilePreviewKind };
|