@columenlabs/lumen-ui 0.1.281 → 0.1.282
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,7 +1,7 @@
|
|
|
1
1
|
export { type AssistantAdapter, assistantClient } from './client.js';
|
|
2
2
|
export { ChatImage } from './chat-image.js';
|
|
3
3
|
export { type AssistantCommand, type AssistantMode, type ComposerExtra, LumenAssistant, type LumenAssistantProps, LumenComposer, assistantStatusKey, readAssistantImage, useAssistantStatus, useLumenAssistantDock, } from './lumen-assistant.js';
|
|
4
|
-
export { assistantAdminSections, assistantSettingsSections } from './settings.js';
|
|
4
|
+
export { AutoCompactField, assistantAdminSections, assistantSettingsSections } from './settings.js';
|
|
5
5
|
export { AssistantChatCost, AssistantUsageTable, UsageHistoryView, assistantUsageKey, tokens as formatTokens, usd as formatUsd, useAssistantUsage, } from './usage-history.js';
|
|
6
6
|
export { type AssistantSource, type AssistantSourceItem, type ComposeRequest, type PageSelection, composeAssistant, selectionRef, useAssistantSource, usePageSelection, useShownAssistantSources, } from './sources.js';
|
|
7
7
|
export { type AssistantChatState, type LiveItem, applyEvent, useAssistantChat, } from './use-assistant-chat.js';
|
package/dist/assistant/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export { assistantClient } from './client.js';
|
|
5
5
|
export { ChatImage } from './chat-image.js';
|
|
6
6
|
export { LumenAssistant, LumenComposer, assistantStatusKey, readAssistantImage, useAssistantStatus, useLumenAssistantDock, } from './lumen-assistant.js';
|
|
7
|
-
export { assistantAdminSections, assistantSettingsSections } from './settings.js';
|
|
7
|
+
export { AutoCompactField, assistantAdminSections, assistantSettingsSections } from './settings.js';
|
|
8
8
|
export { AssistantChatCost, AssistantUsageTable, UsageHistoryView, assistantUsageKey, tokens as formatTokens, usd as formatUsd, useAssistantUsage, } from './usage-history.js';
|
|
9
9
|
export { composeAssistant, selectionRef, useAssistantSource, usePageSelection, useShownAssistantSources, } from './sources.js';
|
|
10
10
|
export { applyEvent, useAssistantChat, } from './use-assistant-chat.js';
|
|
@@ -59,8 +59,8 @@ export interface LumenAssistantProps {
|
|
|
59
59
|
mode?: string;
|
|
60
60
|
};
|
|
61
61
|
/**
|
|
62
|
-
* Where the server compacts the chat, in tokens (the agent's `compactAt()`)
|
|
63
|
-
*
|
|
62
|
+
* Where the server compacts the chat, in tokens (the agent's `compactAt()`): the context donut turns
|
|
63
|
+
* yellow at 75% of it and red at it. The person's Auto-compact (status `compact_at`) wins; 150k by default.
|
|
64
64
|
*/
|
|
65
65
|
compactAt?: number;
|
|
66
66
|
}
|
|
@@ -4,7 +4,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
4
4
|
// (the person's messages, the answer as it streams, each tool call as a line, drafted changes as
|
|
5
5
|
// cards to Save or Dismiss, work handed to a higher tier in its own section); and the composer,
|
|
6
6
|
// with Stop, a queued message and the chat's cost and context under it.
|
|
7
|
-
import { ASSISTANT_IMAGES_MAX, ASSISTANT_IMAGE_CHARS, } from '@columenlabs/lumen-ui-types/assistant';
|
|
7
|
+
import { ASSISTANT_IMAGES_MAX, ASSISTANT_IMAGE_CHARS, COMPACT_AT, } from '@columenlabs/lumen-ui-types/assistant';
|
|
8
8
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
9
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';
|
|
@@ -36,7 +36,6 @@ import { useAssistantChat, } from './use-assistant-chat.js';
|
|
|
36
36
|
const ChatMarkdown = lazyPart(() => import('./chat-markdown.js').then((m) => m.default), null, ({ children }) => _jsx("p", { className: "whitespace-pre-wrap", children: children }));
|
|
37
37
|
/** The main model's context drawn full (tokens); the mark sits where the assistant compacts. */
|
|
38
38
|
const CONTEXT_MAX = 200_000;
|
|
39
|
-
const COMPACT_AT = 150_000;
|
|
40
39
|
export const assistantStatusKey = (client) => [
|
|
41
40
|
'lumen-assistant',
|
|
42
41
|
client.key,
|
|
@@ -191,7 +190,7 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, cha
|
|
|
191
190
|
title: c.title,
|
|
192
191
|
updated_at: new Date(c.at).toISOString(),
|
|
193
192
|
...(chatScope && allScopes && c.scope ? { where: chatScope.name?.(c.scope) } : {}),
|
|
194
|
-
})) ?? (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, meter: _jsx(ContextDonut, { usage: chat.usage, 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, meter: _jsx(ContextDonut, { usage: chat.usage, compactAt: s?.compact_at ?? compactAt, items: chat.items }) })] }));
|
|
195
194
|
}
|
|
196
195
|
function Messages({ chat, status, onSettings, onRetry, onFresh, }) {
|
|
197
196
|
const end = useRef(null);
|
|
@@ -929,30 +928,67 @@ const pointedRef = (p) => p.kind === 'card'
|
|
|
929
928
|
text: textOf(p.el),
|
|
930
929
|
}
|
|
931
930
|
: { kind: p.section.kind, label: p.section.label, id: p.section.id, text: textOf(p.el) };
|
|
932
|
-
/**
|
|
933
|
-
const CONTEXT_WATCH =
|
|
934
|
-
|
|
931
|
+
/** Share of the compact point from which the donut turns yellow; red at the compact point itself. */
|
|
932
|
+
const CONTEXT_WATCH = 0.75;
|
|
933
|
+
/** What the context pop-up counts from the chat: its turns, and the images and pointed-at things sent. */
|
|
934
|
+
function chatCounts(items) {
|
|
935
|
+
let turns = 0;
|
|
936
|
+
let attached = 0;
|
|
937
|
+
for (const i of items) {
|
|
938
|
+
if (i.kind !== 'user' || i.steered)
|
|
939
|
+
continue;
|
|
940
|
+
turns += 1;
|
|
941
|
+
attached += (i.images?.length ?? 0) + (i.refs?.length ?? 0);
|
|
942
|
+
}
|
|
943
|
+
return { turns, attached };
|
|
944
|
+
}
|
|
935
945
|
/**
|
|
936
|
-
* How full the chat's context is, as a small donut in the composer's icon row (after the paperclip and
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
946
|
+
* How full the chat's context is, as a small donut in the composer's icon row (after the paperclip and
|
|
947
|
+
* the modes). It fills against `CONTEXT_MAX`: neutral, yellow from 75% of the compact point, red at it
|
|
948
|
+
* (75k and 100k when chats compact at 100k). Hover, focus or a tap (touch has no hover) opens its pop-up:
|
|
949
|
+
* context used of the most with a bar, that the chat compacts on its own, its turns, the images and
|
|
950
|
+
* attachments sent, and how much of the last turn came from the prompt cache. No price: a person reads
|
|
951
|
+
* their use in Settings.
|
|
940
952
|
*/
|
|
941
|
-
function ContextDonut({ usage, compactAt }) {
|
|
953
|
+
function ContextDonut({ usage, compactAt, items, }) {
|
|
954
|
+
const [open, setOpen] = useState(false);
|
|
955
|
+
const touch = useRef(false);
|
|
956
|
+
const wasOpen = useRef(false);
|
|
942
957
|
const ctx = usage?.context ?? 0;
|
|
943
958
|
if (ctx <= 0)
|
|
944
959
|
return null;
|
|
945
960
|
const share = Math.min(1, ctx / CONTEXT_MAX);
|
|
946
961
|
const r = 6;
|
|
947
962
|
const round = 2 * Math.PI * r;
|
|
948
|
-
const
|
|
963
|
+
const level = ctx >= compactAt ? 'alert' : ctx >= compactAt * CONTEXT_WATCH ? 'watch' : 'ok';
|
|
964
|
+
const tone = level === 'alert'
|
|
949
965
|
? 'text-destructive'
|
|
950
|
-
:
|
|
966
|
+
: level === 'watch'
|
|
951
967
|
? 'text-warning'
|
|
952
968
|
: 'text-muted-foreground';
|
|
953
969
|
const cached = usage && usage.in > 0 ? Math.round((usage.read / usage.in) * 100) : null;
|
|
970
|
+
const { turns, attached } = chatCounts(items);
|
|
954
971
|
const said = `Context ${tokens(ctx)} of ${tokens(CONTEXT_MAX)}`;
|
|
955
|
-
|
|
972
|
+
const rows = [
|
|
973
|
+
['This chat', `${turns} ${turns === 1 ? 'turn' : 'turns'}`],
|
|
974
|
+
['Images and attachments', String(attached)],
|
|
975
|
+
...(cached !== null ? [['Cache read', `${cached}%`]] : []),
|
|
976
|
+
];
|
|
977
|
+
return (_jsxs(Tooltip, { open: open, onOpenChange: setOpen, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", "aria-label": said, "aria-expanded": open, "data-testid": "context-donut", "data-level": level,
|
|
978
|
+
// A tap opens or closes the pop-up (touch has no hover); a mouse click leaves it open.
|
|
979
|
+
// The press itself counts as outside the pop-up and closes it, so the tap reads the state before.
|
|
980
|
+
onPointerDown: (e) => {
|
|
981
|
+
touch.current = e.pointerType !== 'mouse';
|
|
982
|
+
wasOpen.current = open;
|
|
983
|
+
e.preventDefault();
|
|
984
|
+
}, onClick: (e) => {
|
|
985
|
+
e.preventDefault();
|
|
986
|
+
setOpen(touch.current ? !wasOpen.current : true);
|
|
987
|
+
}, className: cn('grid size-6 place-items-center rounded-md hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none', tone), children: _jsxs("svg", { viewBox: "0 0 16 16", className: "size-4 -rotate-90", "aria-hidden": "true", children: [_jsx("title", { children: said }), _jsx("circle", { cx: "8", cy: "8", r: r, fill: "none", strokeWidth: "2.5", className: "stroke-muted-foreground/25" }), _jsx("circle", { cx: "8", cy: "8", r: r, fill: "none", strokeWidth: "2.5", stroke: "currentColor", strokeDasharray: `${share * round} ${round}` })] }) }) }), _jsxs(TooltipContent, { "data-testid": "context-popup", className: "flex w-56 flex-col items-stretch gap-1.5 px-3 py-2.5 text-left font-sans tabular-nums", children: [_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { className: "font-medium", children: "Context" }), _jsxs("span", { className: "whitespace-nowrap", children: [tokens(ctx), " ", _jsxs("span", { className: "text-muted-foreground", children: ["/ ", tokens(CONTEXT_MAX)] })] })] }), _jsx("div", { className: "h-1 overflow-hidden rounded-full bg-muted-foreground/20", children: _jsx("div", { className: cn('h-full rounded-full', level === 'alert'
|
|
988
|
+
? 'bg-destructive'
|
|
989
|
+
: level === 'watch'
|
|
990
|
+
? 'bg-warning'
|
|
991
|
+
: 'bg-foreground/60'), style: { width: `${Math.max(2, share * 100)}%` } }) }), _jsx("span", { className: "text-muted-foreground", children: "Auto-compacts as needed" }), _jsx("div", { className: "my-0.5 border-t" }), rows.map(([k, v]) => (_jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [_jsx("span", { className: "truncate text-muted-foreground", children: k }), _jsx("span", { className: "whitespace-nowrap", children: v })] }, k)))] })] }));
|
|
956
992
|
}
|
|
957
993
|
/**
|
|
958
994
|
* The assistant docked in LumenShell: put `button` in `headerActions` and `panel` in `panel`.
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { SettingsSectionDef } from '../settings.js';
|
|
2
2
|
import type { AssistantAdapter } from './client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Settings: the context size past which a chat is summarized out of sight (Auto-compact), the
|
|
5
|
+
* person's own; the app's default until they pick. An app that shows only some of the kit's
|
|
6
|
+
* sections puts this row in its own.
|
|
7
|
+
*/
|
|
8
|
+
export declare function AutoCompactField({ client }: {
|
|
9
|
+
client: AssistantAdapter;
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
3
11
|
/**
|
|
4
12
|
* The admin's section: the app's shared keys and each person's monthly allowance on them. Put it
|
|
5
13
|
* where the app keeps its admin settings, shown only to admins.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { COMPACT_AT, COMPACT_CHOICES } from '@columenlabs/lumen-ui-types/assistant';
|
|
2
3
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
4
|
import { ChartColumnIcon, CoinsIcon, GlobeIcon, InboxIcon, KeyRoundIcon, LayersIcon, Link2Icon, LogInIcon, PlugIcon, ReceiptIcon, SendIcon, SparklesIcon, WrenchIcon, } from 'lucide-react';
|
|
4
5
|
import { useId, useState } from 'react';
|
|
@@ -18,7 +19,7 @@ import { Label } from '../ui/label.js';
|
|
|
18
19
|
import { Switch } from '../ui/switch.js';
|
|
19
20
|
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group.js';
|
|
20
21
|
import { allowanceAmount, assistantStatusKey, usd, useAssistantStatus } from './lumen-assistant.js';
|
|
21
|
-
import { AssistantUsageTable, OwnKeyUsage, UsageHistoryView } from './usage-history.js';
|
|
22
|
+
import { AssistantUsageTable, OwnKeyUsage, UsageHistoryView, tokens } from './usage-history.js';
|
|
22
23
|
const EFFORTS = [
|
|
23
24
|
{ value: 'default', label: 'Model default' },
|
|
24
25
|
{ value: 'low', label: 'Low' },
|
|
@@ -84,12 +85,30 @@ function General({ client }) {
|
|
|
84
85
|
value: t.id,
|
|
85
86
|
label: t.label,
|
|
86
87
|
})),
|
|
87
|
-
], onChange: (advice) => void set({ advice }) }) })), _jsx(SettingField, { label: "Screen", info: "Takes you to a page, a Settings row, a chart or a card when you ask. Each move has an Undo; nothing is saved.", children: _jsx(LumenSelector, { label: "Screen", size: "sm", className: "w-44", value: s.screen ?? 'move', options: [
|
|
88
|
+
], onChange: (advice) => void set({ advice }) }) })), _jsx(AutoCompactField, { client: client }), _jsx(SettingField, { label: "Screen", info: "Takes you to a page, a Settings row, a chart or a card when you ask. Each move has an Undo; nothing is saved.", children: _jsx(LumenSelector, { label: "Screen", size: "sm", className: "w-44", value: s.screen ?? 'move', options: [
|
|
88
89
|
{ value: 'move', label: 'Move' },
|
|
89
90
|
{ value: 'off', label: 'Off' },
|
|
90
91
|
], onChange: (screen) => void set({ screen: screen }) }) })] }));
|
|
91
92
|
} }));
|
|
92
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Settings: the context size past which a chat is summarized out of sight (Auto-compact), the
|
|
96
|
+
* person's own; the app's default until they pick. An app that shows only some of the kit's
|
|
97
|
+
* sections puts this row in its own.
|
|
98
|
+
*/
|
|
99
|
+
export function AutoCompactField({ client }) {
|
|
100
|
+
const { apply } = useApply(client);
|
|
101
|
+
const s = useAssistantStatus(client).data;
|
|
102
|
+
if (!s)
|
|
103
|
+
return null;
|
|
104
|
+
const now = s.compact_at ?? COMPACT_AT;
|
|
105
|
+
const choices = [...COMPACT_CHOICES];
|
|
106
|
+
if (!choices.includes(now))
|
|
107
|
+
choices.push(now);
|
|
108
|
+
return (_jsx(SettingField, { label: "Auto-compact", info: "Past this much context, the chat's older part is summarized out of sight, so long chats stay quick.", children: _jsx(LumenSelector, { label: "Auto-compact", size: "sm", className: "w-44", value: String(now), options: choices
|
|
109
|
+
.sort((a, b) => a - b)
|
|
110
|
+
.map((n) => ({ value: String(n), label: `At ${tokens(n)}` })), onChange: (v) => void apply('settings', () => client.settings({ compact_at: Number(v) })) }) }));
|
|
111
|
+
}
|
|
93
112
|
// ---------------------------------------------------------------- model providers
|
|
94
113
|
/** A dollar cap a month, saved on Enter or leaving the field. */
|
|
95
114
|
function CapInput({ label, value, onSave, }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.282",
|
|
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.282",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|