@columenlabs/lumen-ui 0.1.200 → 0.1.202

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.
@@ -1,9 +1,10 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
- import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, LightbulbIcon, MonitorIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, TriangleAlertIcon, Undo2Icon, WrenchIcon, XIcon, } from 'lucide-react';
4
- import { createContext, useContext, useEffect, useRef, useState } from 'react';
3
+ import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, LightbulbIcon, MonitorIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, TriangleAlertIcon, Undo2Icon, WrenchIcon, XIcon, ZapIcon, } from 'lucide-react';
4
+ import { Fragment, createContext, useContext, useEffect, useRef, useState, } from 'react';
5
5
  import { LumenAssistantButton, LumenAssistantChats, LumenAssistantPanel, assistantOpen, useAssistantHotkey, } from '../assistant-panel.js';
6
6
  import { screenMapFor, useAssistantScreen } from '../assistant-screen.js';
7
+ import { useIsMobile } from '../hooks/use-mobile.js';
7
8
  import { IconButton } from '../icon-button.js';
8
9
  import { errorText } from '../integrations.js';
9
10
  import { lazyPart } from '../lazy-part.js';
@@ -16,6 +17,7 @@ import { Alert, AlertDescription } from '../ui/alert.js';
16
17
  import { Button } from '../ui/button.js';
17
18
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible.js';
18
19
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover.js';
20
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../ui/sheet.js';
19
21
  import { Spinner } from '../ui/spinner.js';
20
22
  import { Textarea } from '../ui/textarea.js';
21
23
  import { chatMarkdown, chatTurns, copyMarkdown, turnMarkdown } from './copy.js';
@@ -682,12 +684,43 @@ const pointedRef = (p) => p.kind === 'card'
682
684
  text: textOf(p.el),
683
685
  }
684
686
  : { kind: p.section.kind, label: p.section.label, id: p.section.id, text: textOf(p.el) };
685
- /** The chat's cost and tokens (a popup splits them per part), and how full the context is. */
687
+ /** Share of `in` read from the prompt cache, as a whole percent. */
688
+ const hitPct = (p) => (p.in ? Math.round((p.read / p.in) * 100) : 0);
689
+ /**
690
+ * A part's row name: its tier (the label's first piece; the whole label is the hover) and what it
691
+ * was: the main model, `×N` hand-offs to a higher tier, advice or a compaction.
692
+ */
693
+ function partRow(p, i) {
694
+ const bits = p.label.split(' · ');
695
+ const last = bits[bits.length - 1] ?? '';
696
+ const kind = p.runs
697
+ ? `×${p.runs}`
698
+ : last === 'advice' || last === 'compact'
699
+ ? last
700
+ : i === 0
701
+ ? 'main'
702
+ : '';
703
+ return { tier: bits[0] ?? p.label, kind };
704
+ }
705
+ /** The chat's use per part: cost, tokens in with the cache %, out, calls; then the total. */
706
+ function UsageTable({ usage }) {
707
+ const total = 'mt-1 border-t pt-1';
708
+ return (_jsxs("div", { className: "grid grid-cols-[auto_auto_auto_auto_auto_auto] gap-x-3 gap-y-0.5 whitespace-nowrap text-xs", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", { className: "text-right text-muted-foreground", children: "Cost" }), _jsx("span", { className: "text-right text-muted-foreground", children: "In (cached)" }), _jsx("span", { className: "text-right text-muted-foreground", children: "Out" }), _jsx("span", { className: "text-right text-muted-foreground", children: "Calls" }), usage.parts.map((p, i) => {
709
+ const r = partRow(p, i);
710
+ return (_jsxs(Fragment, { children: [_jsx("span", { className: "truncate font-medium", title: p.label, children: r.tier }), _jsx("span", { className: "text-muted-foreground", children: r.kind }), _jsx("span", { className: "num text-right", children: usd(p.usd) }), _jsxs("span", { className: "num text-right", children: [tokens(p.in), " (", hitPct(p), "%)"] }), _jsx("span", { className: "num text-right", children: tokens(p.out) }), _jsx("span", { className: "num text-right", children: p.calls })] }, p.label));
711
+ }), _jsx("span", { className: cn(total, 'font-semibold'), children: "Total" }), _jsx("span", { className: total }), _jsx("span", { className: cn(total, 'num text-right font-semibold'), children: usd(usage.usd) }), _jsxs("span", { className: cn(total, 'num text-right'), children: [tokens(usage.in), " (", hitPct(usage), "%)"] }), _jsx("span", { className: cn(total, 'num text-right'), children: tokens(usage.out) }), _jsx("span", { className: cn(total, 'num text-right'), children: usage.calls })] }));
712
+ }
713
+ /**
714
+ * The chat's cost, tokens in and out and cache hit (a pop-up splits them per part, a sheet on a
715
+ * phone), and how full the context is.
716
+ */
686
717
  function UsageLine({ usage }) {
718
+ const phone = useIsMobile();
687
719
  if (!usage || !usage.calls)
688
720
  return null;
689
721
  const ctx = usage.context ?? 0;
690
- return (_jsxs("div", { className: "flex shrink-0 items-center gap-3 px-3 pb-2 text-muted-foreground text-xs", children: [_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "tabular-nums hover:text-foreground", children: [usd(usage.usd), " \u00B7 ", tokens(usage.in + usage.out), " tokens"] }) }), _jsxs(PopoverContent, { align: "start", className: "w-72 gap-1 p-2 text-xs", children: [usage.parts.map((p) => (_jsxs("div", { className: "flex justify-between gap-2 tabular-nums", children: [_jsx("span", { className: "truncate", children: p.label }), _jsxs("span", { children: [tokens(p.in), " in \u00B7 ", tokens(p.out), " out \u00B7 ", usd(p.usd)] })] }, p.label))), _jsxs("p", { className: "pt-1 text-muted-foreground", children: [tokens(usage.read), " of ", tokens(usage.in), " in read from cache"] })] })] }), ctx > 0 && (_jsxs("span", { className: "ml-auto flex items-center gap-1.5", title: `Context ${tokens(ctx)} of ${tokens(CONTEXT_MAX)}`, children: [_jsxs("span", { className: "relative h-1 w-16 overflow-hidden rounded-full bg-muted", children: [_jsx("span", { className: cn('absolute inset-y-0 left-0', ctx > COMPACT_AT ? 'bg-warning' : 'bg-primary/60'), style: { width: `${Math.min(100, (ctx / CONTEXT_MAX) * 100)}%` } }), _jsx("span", { className: "absolute inset-y-0 w-px bg-foreground/40", style: { left: `${(COMPACT_AT / CONTEXT_MAX) * 100}%` } })] }), _jsx("span", { className: "tabular-nums", children: tokens(ctx) })] }))] }));
722
+ const trigger = (_jsxs("button", { type: "button", className: "flex min-w-0 items-center gap-2 tabular-nums hover:text-foreground", "aria-label": "This chat's usage", children: [_jsxs("span", { className: "num truncate", children: [usd(usage.usd), " \u00B7 \u2191", tokens(usage.in), " \u2193", tokens(usage.out)] }), _jsxs("span", { className: "num flex shrink-0 items-center gap-0.5", title: "Read from cache", children: [_jsx(ZapIcon, { className: "size-3", "aria-hidden": true }), hitPct(usage), "%"] })] }));
723
+ return (_jsxs("div", { className: "flex shrink-0 items-center gap-3 px-3 pb-2 text-muted-foreground text-xs", children: [phone ? (_jsxs(Sheet, { children: [_jsx(SheetTrigger, { asChild: true, children: trigger }), _jsxs(SheetContent, { side: "bottom", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: "This chat's usage" }) }), _jsx("div", { className: "overflow-x-auto px-4 pb-6", children: _jsx(UsageTable, { usage: usage }) })] })] })) : (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: trigger }), _jsx(PopoverContent, { align: "start", className: "w-auto p-2", children: _jsx(UsageTable, { usage: usage }) })] })), ctx > 0 && (_jsxs("span", { className: "ml-auto flex items-center gap-1.5", title: `Context ${tokens(ctx)} of ${tokens(CONTEXT_MAX)}`, children: [_jsxs("span", { className: "relative h-1 w-16 overflow-hidden rounded-full bg-muted", children: [_jsx("span", { className: cn('absolute inset-y-0 left-0', ctx > COMPACT_AT ? 'bg-warning' : 'bg-primary/60'), style: { width: `${Math.min(100, (ctx / CONTEXT_MAX) * 100)}%` } }), _jsx("span", { className: "absolute inset-y-0 w-px bg-foreground/40", style: { left: `${(COMPACT_AT / CONTEXT_MAX) * 100}%` } })] }), _jsx("span", { className: "tabular-nums", children: tokens(ctx) })] }))] }));
691
724
  }
692
725
  /**
693
726
  * The assistant docked in LumenShell: put `button` in `headerActions` and `panel` in `panel`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
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.200",
52
+ "@columenlabs/lumen-ui-types": "0.1.202",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",