@columenlabs/lumen-ui 0.1.262 → 0.1.264
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,
|
|
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 {
|
|
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(
|
|
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(
|
|
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]] })
|
|
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
|
-
*
|
|
938
|
-
*
|
|
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
|
|
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
|
-
|
|
979
|
-
|
|
980
|
-
|
|
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.
|
|
3
|
+
"version": "0.1.264",
|
|
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.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.264",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|