@tangle-network/ui 3.0.0 → 5.0.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 (41) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/chat.js +8 -7
  3. package/dist/{chunk-TMFOPHHN.js → chunk-52Y3FMFI.js} +2 -2
  4. package/dist/{chunk-7UO2ZMRQ.js → chunk-5VPTNXX7.js} +2 -2
  5. package/dist/{chunk-XIHMJ7ZQ.js → chunk-AAUNOHVL.js} +5 -30
  6. package/dist/{chunk-YJ2G3XO5.js → chunk-CMX2I43A.js} +1 -1
  7. package/dist/{chunk-2VH6PUXD.js → chunk-DGW77LD7.js} +1 -1
  8. package/dist/{chunk-CD53GZOM.js → chunk-FJBTCTZM.js} +1 -1
  9. package/dist/{chunk-YNN4O57I.js → chunk-JBPWIYTQ.js} +4 -4
  10. package/dist/{chunk-2NFQRQOD.js → chunk-KT5RNO7N.js} +4 -4
  11. package/dist/{chunk-HJKCSXCH.js → chunk-LELGOLFV.js} +44 -78
  12. package/dist/{chunk-EEE55AVS.js → chunk-SZ44QDA6.js} +1 -1
  13. package/dist/{chunk-66BNMOVT.js → chunk-WUQDUBJG.js} +5 -4
  14. package/dist/chunk-ZRVH3WCA.js +107 -0
  15. package/dist/{code-block-DjXf8eOG.d.ts → code-block-0kSpWMnf.d.ts} +7 -1
  16. package/dist/{document-editor-pane-A5LT5H4N.js → document-editor-pane-WCTA3ZOE.js} +3 -3
  17. package/dist/editor.js +3 -3
  18. package/dist/files.d.ts +39 -2
  19. package/dist/files.js +16 -4
  20. package/dist/hooks.js +5 -5
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +22 -10
  23. package/dist/markdown.d.ts +1 -1
  24. package/dist/markdown.js +2 -2
  25. package/dist/openui.js +3 -3
  26. package/dist/primitives.d.ts +1 -1
  27. package/dist/primitives.js +1 -1
  28. package/dist/run.js +8 -7
  29. package/dist/sdk-hooks.js +5 -5
  30. package/dist/tool-previews.js +3 -2
  31. package/package.json +2 -2
  32. package/src/files/file-artifact-pane.tsx +3 -3
  33. package/src/files/file-format.test.ts +176 -0
  34. package/src/files/file-format.ts +167 -0
  35. package/src/files/file-preview.stories.tsx +87 -0
  36. package/src/files/file-preview.test.tsx +52 -0
  37. package/src/files/file-preview.tsx +48 -94
  38. package/src/files/index.ts +8 -0
  39. package/src/markdown/code-block.test.tsx +62 -0
  40. package/src/markdown/code-block.tsx +11 -4
  41. package/src/tool-previews/write-file-preview.tsx +2 -30
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 5.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [c56ea6c]
8
+ - @tangle-network/brand@0.6.0
9
+
10
+ ## 4.1.0
11
+
12
+ ### Minor Changes
13
+
14
+ - d7a442d: Share theme-aware file-format rendering across the preview and artifact surfaces. `FilePreview` now routes `code`/`json`/`yaml` through the same theme-aware `CodeBlock` the chat markdown renderer uses, so code is syntax-highlighted and theme-consistent in the artifact pane instead of monochrome. A new `files/file-format` module (`detectFileFormat`, `getFormatLabel`, `getSyntaxLanguage`, `fileExtension`) is the single source of truth for extension/MIME detection, consumed by `FilePreview`, `FileArtifactPane`, and `WriteFilePreview`. `CodeBlock` gains an optional `label` prop to display a header name independent of the highlight language.
15
+
16
+ ## 4.0.0
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [184c8bb]
21
+ - @tangle-network/brand@0.5.0
22
+
3
23
  ## 3.0.0
4
24
 
5
25
  ### Patch Changes
package/dist/chat.js CHANGED
@@ -6,18 +6,19 @@ import {
6
6
  MessageList,
7
7
  ThinkingIndicator,
8
8
  UserMessage
9
- } from "./chunk-2NFQRQOD.js";
9
+ } from "./chunk-KT5RNO7N.js";
10
10
  import "./chunk-54SQQMMM.js";
11
- import "./chunk-YNN4O57I.js";
12
- import "./chunk-2VH6PUXD.js";
11
+ import "./chunk-JBPWIYTQ.js";
12
+ import "./chunk-DGW77LD7.js";
13
13
  import "./chunk-4CLN43XT.js";
14
14
  import "./chunk-BX6AQMUS.js";
15
- import "./chunk-XIHMJ7ZQ.js";
16
- import "./chunk-TMFOPHHN.js";
15
+ import "./chunk-AAUNOHVL.js";
16
+ import "./chunk-52Y3FMFI.js";
17
17
  import "./chunk-GYPQXTJU.js";
18
18
  import "./chunk-MKTSMWVD.js";
19
- import "./chunk-CD53GZOM.js";
20
- import "./chunk-66BNMOVT.js";
19
+ import "./chunk-ZRVH3WCA.js";
20
+ import "./chunk-FJBTCTZM.js";
21
+ import "./chunk-WUQDUBJG.js";
21
22
  import "./chunk-RQHJBTEU.js";
22
23
  export {
23
24
  AgentTimeline,
@@ -17,10 +17,10 @@ import {
17
17
  } from "./chunk-MKTSMWVD.js";
18
18
  import {
19
19
  Markdown
20
- } from "./chunk-CD53GZOM.js";
20
+ } from "./chunk-FJBTCTZM.js";
21
21
  import {
22
22
  CodeBlock
23
- } from "./chunk-66BNMOVT.js";
23
+ } from "./chunk-WUQDUBJG.js";
24
24
  import {
25
25
  cn
26
26
  } from "./chunk-RQHJBTEU.js";
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ToolCallGroup,
3
3
  ToolCallStep
4
- } from "./chunk-2VH6PUXD.js";
4
+ } from "./chunk-DGW77LD7.js";
5
5
  import {
6
6
  Markdown
7
- } from "./chunk-CD53GZOM.js";
7
+ } from "./chunk-FJBTCTZM.js";
8
8
  import {
9
9
  cn
10
10
  } from "./chunk-RQHJBTEU.js";
@@ -1,7 +1,10 @@
1
+ import {
2
+ getSyntaxLanguage
3
+ } from "./chunk-ZRVH3WCA.js";
1
4
  import {
2
5
  CodeBlock,
3
6
  CopyButton
4
- } from "./chunk-66BNMOVT.js";
7
+ } from "./chunk-WUQDUBJG.js";
5
8
  import {
6
9
  cn
7
10
  } from "./chunk-RQHJBTEU.js";
@@ -153,39 +156,11 @@ function extractWriteContent(input) {
153
156
  const content = String(obj.content ?? obj.contents ?? obj.data ?? "");
154
157
  return { path, content };
155
158
  }
156
- function getLanguageFromPath(path) {
157
- const ext = path.split(".").pop()?.toLowerCase();
158
- const map = {
159
- ts: "typescript",
160
- tsx: "tsx",
161
- js: "javascript",
162
- jsx: "jsx",
163
- rs: "rust",
164
- py: "python",
165
- go: "go",
166
- rb: "ruby",
167
- json: "json",
168
- yaml: "yaml",
169
- yml: "yaml",
170
- toml: "toml",
171
- md: "markdown",
172
- css: "css",
173
- scss: "scss",
174
- html: "html",
175
- sh: "bash",
176
- bash: "bash",
177
- zsh: "bash",
178
- sql: "sql",
179
- sol: "solidity",
180
- proto: "protobuf"
181
- };
182
- return ext ? map[ext] : void 0;
183
- }
184
159
  var WriteFilePreview = memo2(({ part }) => {
185
160
  const write = extractWriteContent(part.state.input);
186
161
  if (!write) return null;
187
162
  const lineCount = write.content.split("\n").length;
188
- const language = getLanguageFromPath(write.path);
163
+ const language = getSyntaxLanguage(write.path);
189
164
  return /* @__PURE__ */ jsxs3(
190
165
  PreviewCard,
191
166
  {
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-OEX7NZE3.js";
12
12
  import {
13
13
  parseToolEvent
14
- } from "./chunk-7UO2ZMRQ.js";
14
+ } from "./chunk-5VPTNXX7.js";
15
15
 
16
16
  // src/hooks/use-dropdown-menu.ts
17
17
  import { useEffect, useRef, useState } from "react";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CodeBlock
3
- } from "./chunk-66BNMOVT.js";
3
+ } from "./chunk-WUQDUBJG.js";
4
4
  import {
5
5
  cn
6
6
  } from "./chunk-RQHJBTEU.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CodeBlock,
3
3
  CopyButton
4
- } from "./chunk-66BNMOVT.js";
4
+ } from "./chunk-WUQDUBJG.js";
5
5
  import {
6
6
  cn
7
7
  } from "./chunk-RQHJBTEU.js";
@@ -15,16 +15,16 @@ import {
15
15
  QuestionPreview,
16
16
  WebSearchPreview,
17
17
  WriteFilePreview
18
- } from "./chunk-XIHMJ7ZQ.js";
18
+ } from "./chunk-AAUNOHVL.js";
19
19
  import {
20
20
  OpenUIArtifactRenderer
21
- } from "./chunk-TMFOPHHN.js";
21
+ } from "./chunk-52Y3FMFI.js";
22
22
  import {
23
23
  Markdown
24
- } from "./chunk-CD53GZOM.js";
24
+ } from "./chunk-FJBTCTZM.js";
25
25
  import {
26
26
  CodeBlock
27
- } from "./chunk-66BNMOVT.js";
27
+ } from "./chunk-WUQDUBJG.js";
28
28
  import {
29
29
  cn
30
30
  } from "./chunk-RQHJBTEU.js";
@@ -6,20 +6,20 @@ import {
6
6
  import {
7
7
  InlineThinkingItem,
8
8
  RunGroup
9
- } from "./chunk-YNN4O57I.js";
9
+ } from "./chunk-JBPWIYTQ.js";
10
10
  import {
11
11
  ToolCallGroup,
12
12
  ToolCallStep
13
- } from "./chunk-2VH6PUXD.js";
13
+ } from "./chunk-DGW77LD7.js";
14
14
  import {
15
15
  getToolDisplayMetadata
16
16
  } from "./chunk-BX6AQMUS.js";
17
17
  import {
18
18
  OpenUIArtifactRenderer
19
- } from "./chunk-TMFOPHHN.js";
19
+ } from "./chunk-52Y3FMFI.js";
20
20
  import {
21
21
  Markdown
22
- } from "./chunk-CD53GZOM.js";
22
+ } from "./chunk-FJBTCTZM.js";
23
23
  import {
24
24
  cn
25
25
  } from "./chunk-RQHJBTEU.js";
@@ -1,9 +1,19 @@
1
+ import {
2
+ detectFileFormat,
3
+ fileExtension,
4
+ getCodeLanguage,
5
+ getFormatLabel
6
+ } from "./chunk-ZRVH3WCA.js";
1
7
  import {
2
8
  ArtifactPane
3
9
  } from "./chunk-CSAIKY36.js";
4
10
  import {
5
11
  Markdown
6
- } from "./chunk-CD53GZOM.js";
12
+ } from "./chunk-FJBTCTZM.js";
13
+ import {
14
+ CodeBlock,
15
+ CopyButton
16
+ } from "./chunk-WUQDUBJG.js";
7
17
  import {
8
18
  cn
9
19
  } from "./chunk-RQHJBTEU.js";
@@ -311,69 +321,25 @@ import {
311
321
  FileText as FileText2
312
322
  } from "lucide-react";
313
323
  import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
314
- function getPreviewType(filename, mimeType) {
315
- const ext = filename.split(".").pop()?.toLowerCase() || "";
316
- if (mimeType?.startsWith("application/pdf") || ext === "pdf") return "pdf";
317
- if (mimeType?.startsWith("image/") || ["png", "jpg", "jpeg", "gif", "svg", "webp"].includes(ext)) return "image";
318
- if (["csv"].includes(ext)) return "csv";
319
- if (["xlsx", "xls"].includes(ext)) return "spreadsheet";
320
- if (["py", "ts", "js", "tsx", "jsx", "sh", "bash"].includes(ext) || ["profile", "bashrc", "bash_logout", "env", "gitignore"].includes(ext)) return "code";
321
- if (["json"].includes(ext)) return "json";
322
- if (["yaml", "yml"].includes(ext)) return "yaml";
323
- if (["md", "markdown"].includes(ext)) return "markdown";
324
- if (["txt", "log", "text"].includes(ext)) return "text";
325
- if (mimeType?.startsWith("text/plain")) return "text";
326
- return "unknown";
327
- }
328
- function getPreviewLabel(previewType) {
329
- switch (previewType) {
330
- case "pdf":
331
- return "PDF";
332
- case "image":
333
- return "Image";
334
- case "csv":
335
- return "CSV";
336
- case "spreadsheet":
337
- return "Spreadsheet";
338
- case "code":
339
- return "Code";
340
- case "json":
341
- return "JSON";
342
- case "yaml":
343
- return "YAML";
344
- case "markdown":
345
- return "Markdown";
346
- case "text":
347
- return "Text";
348
- default:
349
- return "File";
350
- }
351
- }
352
- function CodePreview({ content, filename }) {
353
- const lines = content.split("\n");
354
- const language = filename.split(".").pop()?.toUpperCase() || "TXT";
355
- return /* @__PURE__ */ jsxs2("div", { className: "relative bg-background rounded-[var(--radius-md)] border border-border overflow-hidden", children: [
356
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-2.5", children: [
357
- /* @__PURE__ */ jsxs2("div", { className: "flex gap-1.5", children: [
358
- /* @__PURE__ */ jsx3("div", { className: "w-3 h-3 rounded-full bg-[#FF5F57]" }),
359
- /* @__PURE__ */ jsx3("div", { className: "w-3 h-3 rounded-full bg-[#FEBC2E]" }),
360
- /* @__PURE__ */ jsx3("div", { className: "w-3 h-3 rounded-full bg-[#8E59FF]" })
361
- ] }),
362
- /* @__PURE__ */ jsx3("div", { className: "ml-2 min-w-0 flex-1 truncate text-xs font-mono text-muted-foreground", children: filename }),
363
- /* @__PURE__ */ jsxs2("div", { className: "inline-flex items-center gap-2 rounded-[var(--radius-full)] border border-border bg-card px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.12em] text-muted-foreground", children: [
364
- /* @__PURE__ */ jsx3("span", { children: language }),
365
- /* @__PURE__ */ jsx3("span", { className: "h-1 w-1 rounded-full bg-[var(--border-hover)]" }),
366
- /* @__PURE__ */ jsxs2("span", { children: [
367
- lines.length,
368
- " lines"
369
- ] })
370
- ] })
371
- ] }),
372
- /* @__PURE__ */ jsx3("div", { className: "overflow-auto max-h-[70vh]", children: /* @__PURE__ */ jsx3("table", { className: "w-full", children: /* @__PURE__ */ jsx3("tbody", { children: lines.map((line, i) => /* @__PURE__ */ jsxs2("tr", { className: "hover:bg-accent", children: [
373
- /* @__PURE__ */ jsx3("td", { className: "text-right pr-4 pl-4 py-0 select-none text-muted-foreground text-xs font-mono w-10 align-top leading-[1.55]", children: i + 1 }),
374
- /* @__PURE__ */ jsx3("td", { className: "pr-4 py-0 font-mono text-[13px] text-foreground leading-[1.55] whitespace-pre", children: line || " " })
375
- ] }, i)) }) }) })
376
- ] });
324
+ function CodePreview({
325
+ content,
326
+ filename,
327
+ format
328
+ }) {
329
+ const lineCount = content.split("\n").length;
330
+ const language = getCodeLanguage(filename, format);
331
+ const labelToken = fileExtension(filename) || language || "txt";
332
+ return /* @__PURE__ */ jsx3(
333
+ CodeBlock,
334
+ {
335
+ code: content,
336
+ language,
337
+ label: `${labelToken} \xB7 ${lineCount} lines`,
338
+ showLineNumbers: true,
339
+ className: "max-h-[70vh] overflow-auto",
340
+ children: /* @__PURE__ */ jsx3(CopyButton, { text: content })
341
+ }
342
+ );
377
343
  }
378
344
  function parseCsvRow(line) {
379
345
  const cells = [];
@@ -478,9 +444,9 @@ function FilePreview({
478
444
  hideHeader = false,
479
445
  className
480
446
  }) {
481
- const previewType = getPreviewType(filename, mimeType);
482
- const previewLabel = getPreviewLabel(previewType);
483
- const hasRenderableSource = Boolean(content) || Boolean(blobUrl) || previewType === "unknown" || previewType === "spreadsheet";
447
+ const format = detectFileFormat(filename, mimeType);
448
+ const previewLabel = getFormatLabel(format);
449
+ const hasRenderableSource = Boolean(content) || Boolean(blobUrl) || format === "unknown" || format === "spreadsheet";
484
450
  return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col h-full", className), children: [
485
451
  !hideHeader && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-border shrink-0", children: [
486
452
  /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
@@ -509,13 +475,13 @@ function FilePreview({
509
475
  )
510
476
  ] }),
511
477
  /* @__PURE__ */ jsxs2("div", { className: "flex-1 overflow-auto p-3", children: [
512
- previewType === "pdf" && blobUrl && /* @__PURE__ */ jsx3(PdfPreview, { blobUrl, filename }),
513
- previewType === "image" && blobUrl && /* @__PURE__ */ jsx3(ImagePreview, { src: blobUrl, filename }),
514
- previewType === "csv" && typeof content === "string" && /* @__PURE__ */ jsx3(CsvPreview, { content }),
515
- (previewType === "code" || previewType === "json" || previewType === "yaml") && typeof content === "string" && /* @__PURE__ */ jsx3(CodePreview, { content, filename }),
516
- previewType === "text" && typeof content === "string" && /* @__PURE__ */ jsx3(TextPreview, { content }),
517
- previewType === "markdown" && typeof content === "string" && /* @__PURE__ */ jsx3(MarkdownPreview, { content }),
518
- previewType === "spreadsheet" && /* @__PURE__ */ jsx3(
478
+ format === "pdf" && blobUrl && /* @__PURE__ */ jsx3(PdfPreview, { blobUrl, filename }),
479
+ format === "image" && blobUrl && /* @__PURE__ */ jsx3(ImagePreview, { src: blobUrl, filename }),
480
+ format === "csv" && typeof content === "string" && /* @__PURE__ */ jsx3(CsvPreview, { content }),
481
+ (format === "code" || format === "json" || format === "yaml") && typeof content === "string" && /* @__PURE__ */ jsx3(CodePreview, { content, filename, format }),
482
+ format === "text" && typeof content === "string" && /* @__PURE__ */ jsx3(TextPreview, { content }),
483
+ format === "markdown" && typeof content === "string" && /* @__PURE__ */ jsx3(MarkdownPreview, { content }),
484
+ format === "spreadsheet" && /* @__PURE__ */ jsx3(
519
485
  UnsupportedPreview,
520
486
  {
521
487
  filename,
@@ -523,8 +489,8 @@ function FilePreview({
523
489
  description: "Download the workbook or convert it to CSV when you need an inline preview."
524
490
  }
525
491
  ),
526
- previewType === "unknown" && typeof content !== "string" && /* @__PURE__ */ jsx3(EmptyPreview, { filename }),
527
- previewType === "unknown" && typeof content === "string" && /* @__PURE__ */ jsx3(TextPreview, { content }),
492
+ format === "unknown" && typeof content !== "string" && /* @__PURE__ */ jsx3(EmptyPreview, { filename }),
493
+ format === "unknown" && typeof content === "string" && /* @__PURE__ */ jsx3(TextPreview, { content }),
528
494
  !hasRenderableSource && typeof content !== "string" && /* @__PURE__ */ jsx3(
529
495
  UnsupportedPreview,
530
496
  {
@@ -594,7 +560,7 @@ import { lazy, Suspense } from "react";
594
560
  import { Download as Download2, X as X3 } from "lucide-react";
595
561
  import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
596
562
  var LazyDocumentEditorPane = lazy(async () => {
597
- const module = await import("./document-editor-pane-A5LT5H4N.js");
563
+ const module = await import("./document-editor-pane-WCTA3ZOE.js");
598
564
  return { default: module.DocumentEditorPane };
599
565
  });
600
566
  function FileArtifactPane({
@@ -626,7 +592,7 @@ function FileArtifactPane({
626
592
  onClose: onTabClose
627
593
  }
628
594
  ) : void 0;
629
- const isMarkdown = mimeType === "text/markdown" || filename.toLowerCase().endsWith(".md") || path?.toLowerCase().endsWith(".md");
595
+ const isMarkdown = detectFileFormat(filename, mimeType) === "markdown" || (path ? detectFileFormat(path, mimeType) === "markdown" : false);
630
596
  const isEditableMarkdown = isMarkdown && editor?.enabled;
631
597
  const headerActions = /* @__PURE__ */ jsxs4(Fragment, { children: [
632
598
  onDownload && /* @__PURE__ */ jsx5(
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-CSAIKY36.js";
10
10
  import {
11
11
  Markdown
12
- } from "./chunk-CD53GZOM.js";
12
+ } from "./chunk-FJBTCTZM.js";
13
13
  import {
14
14
  cn
15
15
  } from "./chunk-RQHJBTEU.js";
@@ -83,24 +83,25 @@ function useIsLightTheme() {
83
83
  return isLight;
84
84
  }
85
85
  var CodeBlock = memo(
86
- ({ code, language, showLineNumbers = false, light: lightProp, className, children, ...props }) => {
86
+ ({ code, language, label, showLineNumbers = false, light: lightProp, className, children, ...props }) => {
87
87
  const isLight = useIsLightTheme();
88
88
  const light = lightProp ?? isLight;
89
89
  const theme = getSyntaxTheme();
90
90
  const bg = "bg-card border-border";
91
91
  const headerBg = light ? "bg-muted/50 border-border" : "bg-background border-border";
92
92
  const langColor = "text-muted-foreground";
93
+ const headerLabel = label ?? language;
93
94
  return /* @__PURE__ */ jsxs(
94
95
  "div",
95
96
  {
96
97
  className: cn("group relative overflow-hidden rounded-lg border font-mono", bg, className),
97
98
  ...props,
98
99
  children: [
99
- language && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between border-b px-3 py-1", headerBg), children: [
100
- /* @__PURE__ */ jsx("span", { className: cn("text-[calc(var(--font-size-xs)-1px)] font-mono font-medium uppercase tracking-widest", langColor), children: language }),
100
+ headerLabel && /* @__PURE__ */ jsxs("div", { className: cn("flex items-center justify-between border-b px-3 py-1", headerBg), children: [
101
+ /* @__PURE__ */ jsx("span", { className: cn("text-[calc(var(--font-size-xs)-1px)] font-mono font-medium uppercase tracking-widest", langColor), children: headerLabel }),
101
102
  children
102
103
  ] }),
103
- !language && children && /* @__PURE__ */ jsx("div", { className: "absolute right-2 top-2 z-10 flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100", children }),
104
+ !headerLabel && children && /* @__PURE__ */ jsx("div", { className: "absolute right-2 top-2 z-10 flex items-center gap-2 opacity-0 transition-opacity group-hover:opacity-100", children }),
104
105
  /* @__PURE__ */ jsx(
105
106
  SyntaxHighlighter,
106
107
  {
@@ -0,0 +1,107 @@
1
+ // src/files/file-format.ts
2
+ var IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "svg", "webp"];
3
+ var EXTENSION_TO_SYNTAX_LANGUAGE = {
4
+ ts: "typescript",
5
+ tsx: "tsx",
6
+ js: "javascript",
7
+ jsx: "jsx",
8
+ mjs: "javascript",
9
+ cjs: "javascript",
10
+ rs: "rust",
11
+ py: "python",
12
+ go: "go",
13
+ rb: "ruby",
14
+ json: "json",
15
+ yaml: "yaml",
16
+ yml: "yaml",
17
+ toml: "toml",
18
+ md: "markdown",
19
+ markdown: "markdown",
20
+ css: "css",
21
+ scss: "scss",
22
+ html: "html",
23
+ sh: "bash",
24
+ bash: "bash",
25
+ zsh: "bash",
26
+ bashrc: "bash",
27
+ bash_logout: "bash",
28
+ profile: "bash",
29
+ sql: "sql",
30
+ sol: "solidity",
31
+ proto: "protobuf"
32
+ };
33
+ var NON_CODE_SYNTAX_EXTENSIONS = /* @__PURE__ */ new Set(["json", "yaml", "yml", "md", "markdown"]);
34
+ var PLAIN_CODE_EXTENSIONS = ["env", "gitignore"];
35
+ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
36
+ ...Object.keys(EXTENSION_TO_SYNTAX_LANGUAGE).filter((ext) => !NON_CODE_SYNTAX_EXTENSIONS.has(ext)),
37
+ ...PLAIN_CODE_EXTENSIONS
38
+ ]);
39
+ function fileExtension(filename) {
40
+ const base = filename.slice(filename.lastIndexOf("/") + 1);
41
+ const dot = base.lastIndexOf(".");
42
+ if (dot < 0) return "";
43
+ return base.slice(dot + 1).toLowerCase();
44
+ }
45
+ function mimeEssence(mimeType) {
46
+ return mimeType?.split(";")[0]?.trim().toLowerCase() ?? "";
47
+ }
48
+ function detectFileFormat(filename, mimeType) {
49
+ const ext = fileExtension(filename);
50
+ const mime = mimeEssence(mimeType);
51
+ if (mime === "application/pdf") return "pdf";
52
+ if (mime.startsWith("image/")) return "image";
53
+ if (mime === "text/markdown") return "markdown";
54
+ if (mime === "application/json") return "json";
55
+ if (mime === "text/csv" || mime === "application/csv") return "csv";
56
+ if (mime === "application/yaml" || mime === "application/x-yaml" || mime === "text/yaml") return "yaml";
57
+ if (ext === "pdf") return "pdf";
58
+ if (IMAGE_EXTENSIONS.includes(ext)) return "image";
59
+ if (ext === "csv") return "csv";
60
+ if (ext === "xlsx" || ext === "xls") return "spreadsheet";
61
+ if (CODE_EXTENSIONS.has(ext)) return "code";
62
+ if (ext === "json") return "json";
63
+ if (ext === "yaml" || ext === "yml") return "yaml";
64
+ if (ext === "md" || ext === "markdown") return "markdown";
65
+ if (["txt", "log", "text"].includes(ext)) return "text";
66
+ if (mime === "text/plain") return "text";
67
+ return "unknown";
68
+ }
69
+ function getFormatLabel(format) {
70
+ switch (format) {
71
+ case "pdf":
72
+ return "PDF";
73
+ case "image":
74
+ return "Image";
75
+ case "csv":
76
+ return "CSV";
77
+ case "spreadsheet":
78
+ return "Spreadsheet";
79
+ case "code":
80
+ return "Code";
81
+ case "json":
82
+ return "JSON";
83
+ case "yaml":
84
+ return "YAML";
85
+ case "markdown":
86
+ return "Markdown";
87
+ case "text":
88
+ return "Text";
89
+ default:
90
+ return "File";
91
+ }
92
+ }
93
+ function getSyntaxLanguage(filename) {
94
+ return EXTENSION_TO_SYNTAX_LANGUAGE[fileExtension(filename)];
95
+ }
96
+ function getCodeLanguage(filename, format) {
97
+ if (format === "json" || format === "yaml") return format;
98
+ return getSyntaxLanguage(filename);
99
+ }
100
+
101
+ export {
102
+ fileExtension,
103
+ detectFileFormat,
104
+ getFormatLabel,
105
+ getSyntaxLanguage,
106
+ getCodeLanguage
107
+ };
@@ -5,12 +5,18 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
5
5
  interface CodeBlockProps extends HTMLAttributes<HTMLDivElement> {
6
6
  code: string;
7
7
  language?: string;
8
+ /**
9
+ * Header text. Defaults to `language`. Set this when the display name differs
10
+ * from the highlight.js language id — e.g. a file extension ("BASHRC") whose
11
+ * content highlights as a known language, or none.
12
+ */
13
+ label?: string;
8
14
  showLineNumbers?: boolean;
9
15
  /** Force light theme; defaults to dark */
10
16
  light?: boolean;
11
17
  children?: ReactNode;
12
18
  }
13
- declare const CodeBlock: React.MemoExoticComponent<({ code, language, showLineNumbers, light: lightProp, className, children, ...props }: CodeBlockProps) => react_jsx_runtime.JSX.Element>;
19
+ declare const CodeBlock: React.MemoExoticComponent<({ code, language, label, showLineNumbers, light: lightProp, className, children, ...props }: CodeBlockProps) => react_jsx_runtime.JSX.Element>;
14
20
  /** Copy-to-clipboard button for use inside CodeBlock. */
15
21
  declare const CopyButton: React.MemoExoticComponent<({ text }: {
16
22
  text: string;
@@ -1,11 +1,11 @@
1
1
  "use client";
2
2
  import {
3
3
  DocumentEditorPane
4
- } from "./chunk-EEE55AVS.js";
4
+ } from "./chunk-SZ44QDA6.js";
5
5
  import "./chunk-Q56BYXQF.js";
6
6
  import "./chunk-CSAIKY36.js";
7
- import "./chunk-CD53GZOM.js";
8
- import "./chunk-66BNMOVT.js";
7
+ import "./chunk-FJBTCTZM.js";
8
+ import "./chunk-WUQDUBJG.js";
9
9
  import "./chunk-RQHJBTEU.js";
10
10
  export {
11
11
  DocumentEditorPane
package/dist/editor.js CHANGED
@@ -11,11 +11,11 @@ import {
11
11
  useEditorConnection,
12
12
  useEditorContext,
13
13
  useYjsState
14
- } from "./chunk-EEE55AVS.js";
14
+ } from "./chunk-SZ44QDA6.js";
15
15
  import "./chunk-Q56BYXQF.js";
16
16
  import "./chunk-CSAIKY36.js";
17
- import "./chunk-CD53GZOM.js";
18
- import "./chunk-66BNMOVT.js";
17
+ import "./chunk-FJBTCTZM.js";
18
+ import "./chunk-WUQDUBJG.js";
19
19
  import "./chunk-RQHJBTEU.js";
20
20
  export {
21
21
  CollaboratorsList,
package/dist/files.d.ts CHANGED
@@ -101,7 +101,7 @@ declare function RichFileTree({ root, paths, selectedPath, onSelect, search, ini
101
101
  * Renders any file type beautifully:
102
102
  * - PDF: embedded viewer
103
103
  * - CSV/XLSX: tabular preview
104
- * - Code (py/json/yaml/ts/js): line-numbered viewer
104
+ * - Code (py/json/yaml/ts/js): syntax-highlighted, line-numbered viewer
105
105
  * - Markdown: rendered prose
106
106
  * - Images: inline display
107
107
  * - Text: monospace preview
@@ -172,4 +172,41 @@ interface FileArtifactPaneProps extends Omit<FilePreviewProps, "className"> {
172
172
  */
173
173
  declare function FileArtifactPane({ filename, content, blobUrl, mimeType, onClose, onDownload, path, tabs, activeTabId, onTabSelect, onTabClose, eyebrow, meta, toolbar, footer, className, editor, }: FileArtifactPaneProps): react_jsx_runtime.JSX.Element;
174
174
 
175
- export { FileArtifactPane, type FileArtifactPaneProps, type FileNode, FilePreview, type FilePreviewProps, type FileTabData, FileTabs, type FileTabsProps, FileTree, type FileTreeProps, type FileTreeVisibilityOptions, RichFileTree, type RichFileTreeGitEntry, type RichFileTreeGitStatus, type RichFileTreeProps, type RichFileTreeThemeVars, filterFileTree };
175
+ /**
176
+ * Shared file-format detection for every file surface — the preview pane, the
177
+ * artifact pane, and code rendering. Centralising extension/MIME → format logic
178
+ * keeps chat and artifact views consistent and avoids the same mapping drifting
179
+ * across components.
180
+ */
181
+ type FileFormat = "pdf" | "image" | "csv" | "spreadsheet" | "code" | "json" | "yaml" | "markdown" | "text" | "unknown";
182
+ /**
183
+ * Lowercased trailing extension. Returns "" for a name with no extension
184
+ * ("README", "json" → ""), and the post-dot name for a dotfile (".bashrc" →
185
+ * "bashrc"). Directory components are ignored so dots in a directory name don't
186
+ * leak in ("my.config/file" → "").
187
+ */
188
+ declare function fileExtension(filename: string): string;
189
+ /**
190
+ * Resolve a filename + optional MIME type to the renderer format. A specific,
191
+ * authoritative MIME type wins over the extension; otherwise the extension
192
+ * decides; a generic text/plain payload is the final fallback.
193
+ */
194
+ declare function detectFileFormat(filename: string, mimeType?: string): FileFormat;
195
+ /** Human-facing label for a detected format. */
196
+ declare function getFormatLabel(format: FileFormat): string;
197
+ /**
198
+ * Map a filename (or path) to a highlight.js language id for CodeBlock. Returns
199
+ * undefined when there is no confident mapping; CodeBlock then renders themed
200
+ * monospace and lets the highlighter auto-detect, so callers never need a
201
+ * bespoke language table.
202
+ */
203
+ declare function getSyntaxLanguage(filename: string): string | undefined;
204
+ /**
205
+ * Highlight language for a file already classified as a code-like format.
206
+ * `json`/`yaml` are their own highlight language even when detected purely from
207
+ * a MIME type on an extensionless file (where the extension can't reveal it);
208
+ * any other code format keys off the extension.
209
+ */
210
+ declare function getCodeLanguage(filename: string, format: FileFormat): string | undefined;
211
+
212
+ 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 };