@columenlabs/lumen-ui 0.1.195 → 0.1.197

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.
@@ -0,0 +1,3 @@
1
+ export default function ChatMarkdown({ children }: {
2
+ children: string;
3
+ }): import("react").JSX.Element;
@@ -0,0 +1,77 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import Markdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import { cn } from '../lib/utils.js';
5
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table.js';
6
+ const ALLOWED = [
7
+ 'p',
8
+ 'strong',
9
+ 'em',
10
+ 'del',
11
+ 'code',
12
+ 'ul',
13
+ 'ol',
14
+ 'li',
15
+ 'a',
16
+ 'pre',
17
+ 'br',
18
+ 'table',
19
+ 'thead',
20
+ 'tbody',
21
+ 'tr',
22
+ 'th',
23
+ 'td',
24
+ ];
25
+ // A number, with an optional sign, currency, thousands, decimals and a short unit (12, −3.5%,
26
+ // $1,204.10, 2.1×, 4.2K). A dash or an empty cell does not decide a column either way.
27
+ const NUMBER = /^[−+\-]?[$€£]?\d[\d,]*(\.\d+)?\s?(%|×|x|[KMBkmb])?$/;
28
+ const BLANK = /^(|—|–|-|n\/a)$/i;
29
+ function text(node) {
30
+ if (node.type === 'text')
31
+ return node.value ?? '';
32
+ return (node.children ?? []).map(text).join('');
33
+ }
34
+ const cells = (row) => (row.children ?? []).filter((c) => c.tagName === 'td' || c.tagName === 'th');
35
+ const rows = (section) => (section?.children ?? []).filter((c) => c.tagName === 'tr');
36
+ /**
37
+ * Marks each cell of a column that holds only numbers `dataNum`, so the column right-aligns on
38
+ * one edge, and gives every cell its text as `dataText` for its hover.
39
+ */
40
+ function rehypeNumberColumns() {
41
+ const visit = (node) => {
42
+ if (node.tagName === 'table') {
43
+ const head = node.children?.find((c) => c.tagName === 'thead');
44
+ const body = node.children?.find((c) => c.tagName === 'tbody');
45
+ const bodyRows = rows(body);
46
+ const width = Math.max(0, ...[...rows(head), ...bodyRows].map((r) => cells(r).length));
47
+ const numeric = Array.from({ length: width }, (_, i) => {
48
+ const values = bodyRows.map((r) => text(cells(r)[i] ?? { type: 'root' }).trim());
49
+ const filled = values.filter((v) => !BLANK.test(v));
50
+ return filled.length > 0 && filled.every((v) => NUMBER.test(v));
51
+ });
52
+ for (const r of [...rows(head), ...bodyRows]) {
53
+ cells(r).forEach((c, i) => {
54
+ c.properties = { ...c.properties, dataText: text(c).trim() };
55
+ if (numeric[i])
56
+ c.properties.dataNum = '';
57
+ });
58
+ }
59
+ return;
60
+ }
61
+ node.children?.forEach(visit);
62
+ };
63
+ return (tree) => visit(tree);
64
+ }
65
+ // A header keeps the label font; only its edge follows the numbers under it.
66
+ const cellClass = (p, head = false) => cn('max-w-xs overflow-hidden text-ellipsis', p['data-num'] !== undefined && (head ? 'text-right' : 'num text-right'));
67
+ const components = {
68
+ table: ({ children }) => (_jsx("div", { className: "not-prose my-2 rounded-md border", children: _jsx(Table, { className: "text-xs", children: children }) })),
69
+ thead: ({ children }) => _jsx(TableHeader, { children: children }),
70
+ tbody: ({ children }) => _jsx(TableBody, { children: children }),
71
+ tr: ({ children }) => _jsx(TableRow, { children: children }),
72
+ th: (p) => (_jsx(TableHead, { className: cn(cellClass(p, true), 'text-muted-foreground'), title: p['data-text'], children: p.children })),
73
+ td: (p) => (_jsx(TableCell, { className: cellClass(p), title: p['data-text'], children: p.children })),
74
+ };
75
+ export default function ChatMarkdown({ children }) {
76
+ return (_jsx(Markdown, { remarkPlugins: [remarkGfm], rehypePlugins: [rehypeNumberColumns], allowedElements: ALLOWED, unwrapDisallowed: true, components: components, children: children }));
77
+ }
@@ -21,8 +21,8 @@ import { Textarea } from '../ui/textarea.js';
21
21
  import { chatMarkdown, chatTurns, copyMarkdown, turnMarkdown } from './copy.js';
22
22
  import { composeAssistant, selectionRef, useComposeRequest, usePageSelection, useShownAssistantSources, } from './sources.js';
23
23
  import { useAssistantChat } from './use-assistant-chat.js';
24
- // The Markdown renderer loads with the first answer (lazy-part.tsx).
25
- const Markdown = lazyPart(() => import('react-markdown').then((m) => m.default));
24
+ // The Markdown renderer (tables included) loads with the first answer (lazy-part.tsx).
25
+ const ChatMarkdown = lazyPart(() => import('./chat-markdown.js').then((m) => m.default));
26
26
  /** The main model's context drawn full (tokens); the mark sits where the assistant compacts. */
27
27
  const CONTEXT_MAX = 200_000;
28
28
  const COMPACT_AT = 150_000;
@@ -256,7 +256,7 @@ function Item({ item, onSettle, busy, }) {
256
256
  case 'user':
257
257
  return (_jsxs("div", { className: "ml-8 self-end rounded-lg bg-muted px-3 py-2 text-sm whitespace-pre-wrap", children: [(item.tier || item.refs?.length) && (_jsxs("span", { className: "mb-1 flex flex-wrap gap-1", children: [item.tier && _jsx(ComposerChip, { sign: "@", label: tierName(item.tier) }), item.refs?.map((r, i) => (_jsx(ComposerChip, { sign: "#", label: r.label }, `${r.kind}:${r.label}:${i}`)))] })), item.text, item.context && (_jsxs("span", { className: "mt-1 block text-muted-foreground text-xs", children: ["on ", item.context] }))] }));
258
258
  case 'text':
259
- return (_jsxs("div", { className: "prose-sm text-sm leading-relaxed [&_code]:font-code [&_code]:text-xs [&_li]:ml-4 [&_ol]:list-decimal [&_p]:my-1.5 [&_ul]:list-disc", children: [_jsx(Markdown, { allowedElements: ['p', 'strong', 'em', 'code', 'ul', 'ol', 'li', 'a', 'pre', 'br'], unwrapDisallowed: true, children: item.text }), item.live && (_jsx("span", { className: "ml-0.5 inline-block h-3.5 w-1.5 animate-pulse bg-foreground/60 align-middle" }))] }));
259
+ return (_jsxs("div", { className: "prose-sm text-sm leading-relaxed [&_code]:font-code [&_code]:text-xs [&_li]:ml-4 [&_ol]:list-decimal [&_p]:my-1.5 [&_ul]:list-disc", children: [_jsx(ChatMarkdown, { children: item.text }), item.live && (_jsx("span", { className: "ml-0.5 inline-block h-3.5 w-1.5 animate-pulse bg-foreground/60 align-middle" }))] }));
260
260
  case 'step':
261
261
  return _jsx(Step, { step: item, live: !!item.live });
262
262
  case 'draft':
@@ -138,7 +138,7 @@ const MORE = 'flex h-7 w-full items-center gap-1.5 rounded-md pr-2 pl-8 text-lef
138
138
  /** One row's inside: box or icon, label, detail, and the check when picked. */
139
139
  export function SelectorRowContent({ option, selected, multiple, trailing, }) {
140
140
  const Icon = option.icon;
141
- return (_jsxs(_Fragment, { children: [option.indent && _jsx("span", { "aria-hidden": true, className: "w-4 shrink-0" }), multiple && (_jsx("span", { "aria-hidden": true, className: cn('flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input', selected && 'border-primary bg-primary text-primary-foreground'), children: selected && _jsx(CheckIcon, { className: "size-3" }) })), option.thumb ? (_jsx("span", { "aria-hidden": true, className: "shrink-0", children: option.thumb })) : (Icon && _jsx(Icon, { className: "text-muted-foreground" })), option.description ? (_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate", children: option.content ?? option.label }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: option.description })] })) : (_jsx("span", { className: "min-w-0 flex-1 truncate", children: option.content ?? option.label })), option.detail && (_jsx("span", { className: "shrink-0 text-xs whitespace-nowrap text-muted-foreground", children: option.detail })), trailing] }));
141
+ return (_jsxs(_Fragment, { children: [option.indent && _jsx("span", { "aria-hidden": true, className: "w-4 shrink-0" }), multiple && (_jsx("span", { "aria-hidden": true, className: cn('flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input', selected && 'border-primary bg-primary text-primary-foreground'), children: selected && _jsx(CheckIcon, { className: "size-3" }) })), option.thumb ? (_jsx("span", { "aria-hidden": true, className: "shrink-0", children: option.thumb })) : (Icon && _jsx(Icon, { className: "text-muted-foreground" })), option.description ? (_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate", children: option.content ?? option.label }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: option.description })] })) : (_jsx("span", { className: cn('min-w-0 truncate', option.detail ? 'max-w-2/3 shrink-0' : 'flex-1'), children: option.content ?? option.label })), option.detail && (_jsx("span", { className: "min-w-0 flex-1 truncate text-right text-xs text-muted-foreground", title: option.detail, children: option.detail })), trailing] }));
142
142
  }
143
143
  function Rows({ shown, picked, multiple, full, expand, }) {
144
144
  return shown.map((g) => (_jsxs(Base.Group, { items: g.rows.map((o) => o.value), children: [g.label && _jsx(Base.GroupLabel, { className: GROUP_LABEL, children: g.label }), g.rows.map((o) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.195",
3
+ "version": "0.1.197",
4
4
  "description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -49,7 +49,7 @@
49
49
  "@codemirror/lang-javascript": "6.2.5",
50
50
  "@codemirror/lang-json": "6.0.2",
51
51
  "@codemirror/lang-sql": "6.10.0",
52
- "@columenlabs/lumen-ui-types": "0.1.195",
52
+ "@columenlabs/lumen-ui-types": "0.1.197",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",
@@ -80,6 +80,7 @@
80
80
  "react-markdown": "10.1.0",
81
81
  "react-resizable-panels": "^4.14.3",
82
82
  "recharts": "^3.10.1",
83
+ "remark-gfm": "4.0.1",
83
84
  "shiki": "4.5.0",
84
85
  "sonner": "^2.0.8",
85
86
  "tailwind-merge": "^3.7.0",