@columenlabs/lumen-ui 0.1.263 → 0.1.265

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.
@@ -6,11 +6,10 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
6
6
  // with Stop, a queued message and the chat's cost and context under it.
7
7
  import { ASSISTANT_IMAGES_MAX, ASSISTANT_IMAGE_CHARS, } from '@columenlabs/lumen-ui-types/assistant';
8
8
  import { useQuery, useQueryClient } from '@tanstack/react-query';
9
- import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, GlobeIcon, LightbulbIcon, ListPlusIcon, MonitorIcon, PaperclipIcon, PencilIcon, PlusIcon, ReceiptIcon, RefreshCwIcon, ReplyIcon, SearchIcon, SparklesIcon, SquareIcon, TriangleAlertIcon, Undo2Icon, WrenchIcon, XIcon, ZapIcon, } from 'lucide-react';
9
+ import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, GlobeIcon, LightbulbIcon, ListPlusIcon, MonitorIcon, PaperclipIcon, PencilIcon, PlusIcon, RefreshCwIcon, ReplyIcon, SearchIcon, SparklesIcon, SquareIcon, TriangleAlertIcon, Undo2Icon, WrenchIcon, XIcon, } from 'lucide-react';
10
10
  import { Fragment, createContext, useContext, useEffect, useRef, useState, } from 'react';
11
11
  import { LumenAssistantButton, LumenAssistantChats, LumenAssistantPanel, assistantOpen, useAssistantHotkey, } from '../assistant-panel.js';
12
12
  import { screenMapFor, useAssistantScreen } from '../assistant-screen.js';
13
- import { useIsMobile } from '../hooks/use-mobile.js';
14
13
  import { IconButton } from '../icon-button.js';
15
14
  import { errorText } from '../integrations.js';
16
15
  import { lazyPart } from '../lazy-part.js';
@@ -23,15 +22,13 @@ import { Alert, AlertDescription } from '../ui/alert.js';
23
22
  import { Button } from '../ui/button.js';
24
23
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible.js';
25
24
  import { InputGroup, InputGroupAddon, InputGroupTextarea } from '../ui/input-group.js';
26
- import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover.js';
27
- import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../ui/sheet.js';
28
25
  import { Spinner } from '../ui/spinner.js';
29
26
  import { ChatImage } from './chat-image.js';
30
27
  import { chatMarkdown, chatTurns, copyMarkdown, turnMarkdown } from './copy.js';
31
28
  import { ImageCard, ImagePending, toolIs } from './image-card.js';
32
29
  import { ReplyToSelection } from './reply-selection.js';
33
30
  import { composeAssistant, selectionRef, useComposeRequest, usePageSelection, useShownAssistantSources, } from './sources.js';
34
- import { AssistantChatCost, UsageHistoryView, tokens, usd } from './usage-history.js';
31
+ import { tokens, usd } from './usage-history.js';
35
32
  import { useAssistantChat, } from './use-assistant-chat.js';
36
33
  // The Markdown renderer (tables included) loads with the first answer (lazy-part.tsx).
37
34
  // A tab whose build a deploy replaced shows the answer as plain text instead of reloading mid chat.
@@ -186,14 +183,14 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, cha
186
183
  allCommands.push(c);
187
184
  const shown = useShownAssistantSources();
188
185
  const allSources = [...shown, ...sources.filter((x) => !shown.some((y) => y.id === x.id))];
189
- return (_jsxs("div", { "data-assistant": "", className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "flex h-12 shrink-0 items-center gap-1 border-b px-3", children: [_jsx(SparklesIcon, { className: "size-4 text-edge" }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: chat.title || s?.name || 'Assistant' }), s && tiers.length > 1 && (_jsx(LumenSelector, { trigger: "chip", size: "sm", label: "Tier", value: s.tier, options: tiers.map((t) => ({ value: t.id, label: t.label, detail: t.model_label })), onChange: (t) => void setTier(t) })), _jsx(AssistantChatCost, { client: client, chatId: chat.chatId, busy: chat.busy }), _jsx(LumenAssistantChats, { chats: chats.data
186
+ return (_jsxs("div", { "data-assistant": "", className: "flex h-full min-h-0 flex-col", children: [_jsxs("div", { className: "flex h-12 shrink-0 items-center gap-1 border-b px-3", children: [_jsx(SparklesIcon, { className: "size-4 text-edge" }), _jsx("span", { className: "min-w-0 flex-1 truncate font-medium", children: chat.title || s?.name || 'Assistant' }), s && tiers.length > 1 && (_jsx(LumenSelector, { trigger: "chip", size: "sm", label: "Tier", value: s.tier, options: tiers.map((t) => ({ value: t.id, label: t.label, detail: t.model_label })), onChange: (t) => void setTier(t) })), _jsx(LumenAssistantChats, { chats: chats.data
190
187
  ?.filter((c) => !chatScope || allScopes || c.scope === chatScope.current)
191
188
  .map((c) => ({
192
189
  id: c.id,
193
190
  title: c.title,
194
191
  updated_at: new Date(c.at).toISOString(),
195
192
  ...(chatScope && allScopes && c.scope ? { where: chatScope.name?.(c.scope) } : {}),
196
- })) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, toggle: chatScope && { label: chatScope.label, on: allScopes, onChange: setAllScopes }, note: `Chats are kept ${keepDays} days after their last message.`, onRefresh: () => void chats.refetch(), onOpen: (c) => void chat.open(c.id) }), _jsx(IconButton, { icon: CopyIcon, label: "Copy chat", disabled: !chat.items.length, onClick: () => copyMarkdown(chatMarkdown(chat.title, chat.items, name), 'Chat') }), _jsx(IconButton, { icon: PlusIcon, label: "New chat", disabled: !chat.items.length, onClick: chat.reset }), onClose && (_jsx(IconButton, { icon: XIcon, label: `Close ${s?.name ?? 'assistant'}`, onClick: onClose }))] }), _jsx(AssistantName.Provider, { value: name, children: _jsx(Screen.Provider, { value: controls, children: _jsx(ImageTool.Provider, { value: imageTool ?? null, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings, onRetry: (at, user) => chat.retry(at, user, extra()), onFresh: (choice) => chat.fresh(choice, extra()) }) }) }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, queue: chat.queue, canSteer: chat.canSteer, onSteerQueued: chat.steerQueued, onUnqueue: chat.unqueue, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, images: images, modes: modes, onSend: (text, more, how) => chat.send(text, { ...extra(), ...more }, how), onStop: chat.stop }), _jsx(UsageLine, { usage: chat.usage, client: client, spent: s?.spend.usd, compactAt: compactAt })] }));
193
+ })) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, toggle: chatScope && { label: chatScope.label, on: allScopes, onChange: setAllScopes }, note: `Chats are kept ${keepDays} days after their last message.`, onRefresh: () => void chats.refetch(), onOpen: (c) => void chat.open(c.id) }), _jsx(IconButton, { icon: CopyIcon, label: "Copy chat", disabled: !chat.items.length, onClick: () => copyMarkdown(chatMarkdown(chat.title, chat.items, name), 'Chat') }), _jsx(IconButton, { icon: PlusIcon, label: "New chat", disabled: !chat.items.length, onClick: chat.reset }), onClose && (_jsx(IconButton, { icon: XIcon, label: `Close ${s?.name ?? 'assistant'}`, onClick: onClose }))] }), _jsx(AssistantName.Provider, { value: name, children: _jsx(Screen.Provider, { value: controls, children: _jsx(ImageTool.Provider, { value: imageTool ?? null, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings, onRetry: (at, user) => chat.retry(at, user, extra()), onFresh: (choice) => chat.fresh(choice, extra()) }) }) }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, queue: chat.queue, canSteer: chat.canSteer, onSteerQueued: chat.steerQueued, onUnqueue: chat.unqueue, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, images: images, modes: modes, onSend: (text, more, how) => chat.send(text, { ...extra(), ...more }, how), onStop: chat.stop }), _jsx(ContextLine, { usage: chat.usage, compactAt: compactAt })] }));
197
194
  }
198
195
  function Messages({ chat, status, onSettings, onRetry, onFresh, }) {
199
196
  const end = useRef(null);
@@ -528,7 +525,7 @@ const WHY = {
528
525
  check_failed: 'a check failed',
529
526
  };
530
527
  function HandOff({ item, onSettle, busy, }) {
531
- return (_jsxs("section", { className: "flex flex-col gap-2 border-l-2 border-primary/40 pl-3", children: [_jsxs("p", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(CornerDownRightIcon, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: item.label }), _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", WHY[item.why]] }), item.usage && (_jsx("span", { className: "ml-auto text-muted-foreground tabular-nums", children: usd(item.usage.usd) }))] }), item.task && _jsx("p", { className: "text-muted-foreground text-xs", children: item.task }), _jsx(Items, { items: item.items, onSettle: onSettle, busy: busy })] }));
528
+ return (_jsxs("section", { className: "flex flex-col gap-2 border-l-2 border-primary/40 pl-3", children: [_jsxs("p", { className: "flex items-center gap-1.5 text-xs", children: [_jsx(CornerDownRightIcon, { className: "size-3.5 text-muted-foreground" }), _jsx("span", { className: "font-medium", children: item.label }), _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", WHY[item.why]] })] }), item.task && _jsx("p", { className: "text-muted-foreground text-xs", children: item.task }), _jsx(Items, { items: item.items, onSettle: onSettle, busy: busy })] }));
532
529
  }
533
530
  /** The composer's triggers: `@` points at a thing (the `sources`), `#` picks the tier for one message. */
534
531
  const POINT = '@';
@@ -931,55 +928,15 @@ const pointedRef = (p) => p.kind === 'card'
931
928
  text: textOf(p.el),
932
929
  }
933
930
  : { kind: p.section.kind, label: p.section.label, id: p.section.id, text: textOf(p.el) };
934
- /** Share of `in` read from the prompt cache, as a whole percent. */
935
- const hitPct = (p) => (p.in ? Math.round((p.read / p.in) * 100) : 0);
936
931
  /**
937
- * A part's row name: its tier (the label's first piece; the whole label is the hover) and what it
938
- * was: the main model, `×N` hand-offs to a higher tier, advice or a compaction.
932
+ * How full the chat's context is (a thin bar with a tick where it compacts). What a chat cost is
933
+ * not shown here: a person reads their use in Settings, not on every answer.
939
934
  */
940
- function partRow(p, i) {
941
- const bits = p.label.split(' · ');
942
- const last = bits[bits.length - 1] ?? '';
943
- const kind = p.runs
944
- ? `×${p.runs}`
945
- : last === 'advice' || last === 'compact'
946
- ? last
947
- : i === 0
948
- ? 'main'
949
- : '';
950
- return { tier: bits[0] ?? p.label, kind };
951
- }
952
- /** The chat's use per part: cost, tokens in with the cache %, out, calls; then the total. */
953
- function UsageTable({ usage }) {
954
- const total = 'mt-1 border-t pt-1';
955
- 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) => {
956
- const r = partRow(p, i);
957
- 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));
958
- }), _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 })] }));
959
- }
960
- /**
961
- * This month's total of every chat and run; it opens the month's history (each model, chat and
962
- * run), a sheet on a phone.
963
- */
964
- function MonthTotal({ client, spent, phone, className, }) {
965
- const trigger = (_jsxs("button", { type: "button", className: cn('flex shrink-0 items-center gap-1 tabular-nums hover:text-foreground', className), "aria-label": "Usage this month", title: "Usage this month: every chat and run", children: [_jsx(ReceiptIcon, { className: "size-3", "aria-hidden": true }), _jsxs("span", { children: ["This month ", _jsx("span", { className: "num", children: usd(spent) })] })] }));
966
- return phone ? (_jsxs(Sheet, { children: [_jsx(SheetTrigger, { asChild: true, children: trigger }), _jsxs(SheetContent, { side: "bottom", children: [_jsx(SheetHeader, { children: _jsx(SheetTitle, { children: "Usage" }) }), _jsx(UsageHistoryView, { client: client, className: "px-4 pb-6" })] })] })) : (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { asChild: true, children: trigger }), _jsx(PopoverContent, { align: "end", className: "w-md max-w-[92vw] p-3", children: _jsx(UsageHistoryView, { client: client }) })] }));
967
- }
968
- /**
969
- * The chat's cost, tokens in and out and cache hit (a pop-up splits them per part, a sheet on a
970
- * phone), how full the context is, and this month's total (`MonthTotal`).
971
- */
972
- function UsageLine({ usage, client, spent, compactAt, }) {
973
- const phone = useIsMobile();
974
- const month = !!client.usage && spent !== undefined && spent > 0;
975
- if ((!usage || !usage.calls) && !month)
976
- return null;
935
+ function ContextLine({ usage, compactAt }) {
977
936
  const ctx = usage?.context ?? 0;
978
- const total = month ? (_jsx(MonthTotal, { client: client, spent: spent ?? 0, phone: phone, className: ctx > 0 ? '' : 'ml-auto' })) : null;
979
- if (!usage || !usage.calls)
980
- return (_jsx("div", { className: "flex shrink-0 items-center gap-3 px-3 pb-2 text-muted-foreground text-xs", children: total }));
981
- 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), "%"] })] }));
982
- 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 > compactAt ? '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: `${(compactAt / CONTEXT_MAX) * 100}%` } })] }), _jsx("span", { className: "tabular-nums", children: tokens(ctx) })] })), total] }));
937
+ if (ctx <= 0)
938
+ return null;
939
+ return (_jsx("div", { className: "flex shrink-0 items-center gap-3 px-3 pb-2 text-muted-foreground text-xs", children: _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 > compactAt ? '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: `${(compactAt / CONTEXT_MAX) * 100}%` } })] }), _jsx("span", { className: "tabular-nums", children: tokens(ctx) })] }) }));
983
940
  }
984
941
  /**
985
942
  * 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.263",
3
+ "version": "0.1.265",
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.263",
52
+ "@columenlabs/lumen-ui-types": "0.1.265",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",