@columenlabs/lumen-ui 0.1.301 → 0.1.302
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.
|
@@ -36,10 +36,12 @@ export declare function AssistantChatCost({ client, chatId, busy, }: {
|
|
|
36
36
|
busy?: boolean;
|
|
37
37
|
}): import("react").JSX.Element | null;
|
|
38
38
|
/**
|
|
39
|
-
* This month's use, one row a session (a chat, or every run of one task on one thing): its
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* This month's use, one row a session (a chat, or every run of one task on one thing): its
|
|
40
|
+
* Category (Chat, Edit, Rebuild: `UsageEntry.category`), Type (the person's API key, their plan
|
|
41
|
+
* sign-in, or the app's credits), Date, Calls and Amount, grouped by Date (first), Category, Type or
|
|
42
|
+
* not at all, filtered by category, type or name, PAGE_SIZE rows a page (a group's totals count all
|
|
43
|
+
* its rows). A row's hover gives its tokens and cost. A sign-in's amount is an estimate: the same
|
|
44
|
+
* tokens at the model's API rates (the plan charges $0).
|
|
43
45
|
*/
|
|
44
46
|
export declare function AssistantUsageTable({ client }: {
|
|
45
47
|
client: AssistantAdapter;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useQuery } from '@tanstack/react-query';
|
|
3
|
-
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon, CpuIcon, HashIcon, KeyRoundIcon, MessageSquareIcon, MessagesSquareIcon, ReceiptIcon, } from 'lucide-react';
|
|
3
|
+
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon, CpuIcon, HashIcon, KeyRoundIcon, LayersIcon, MessageSquareIcon, MessagesSquareIcon, ReceiptIcon, } from 'lucide-react';
|
|
4
4
|
import { Fragment, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import { DetailsTable } from '../details-table.js';
|
|
6
6
|
import { IconButton } from '../icon-button.js';
|
|
@@ -8,6 +8,7 @@ import { InfoLabel } from '../info-tip.js';
|
|
|
8
8
|
import { cn } from '../lib/utils.js';
|
|
9
9
|
import { LumenTokenizedFilter, useLumenFilter } from '../lumen-filter.js';
|
|
10
10
|
import { LumenTable } from '../lumen-view.js';
|
|
11
|
+
import { LumenPageFooter } from '../pager.js';
|
|
11
12
|
import { SettingField } from '../setting-parts.js';
|
|
12
13
|
import { Badge } from '../ui/badge.js';
|
|
13
14
|
import { Label } from '../ui/label.js';
|
|
@@ -280,6 +281,7 @@ function usageRows(h) {
|
|
|
280
281
|
id,
|
|
281
282
|
title: e.title,
|
|
282
283
|
type: 'credits',
|
|
284
|
+
category: s?.category ?? (e.category || (e.kind === 'chat' ? 'Chat' : (e.task ?? 'Run'))),
|
|
283
285
|
at: Math.max(s?.at ?? 0, e.at),
|
|
284
286
|
calls: (s?.calls ?? 0) + e.calls,
|
|
285
287
|
tokens: {
|
|
@@ -320,6 +322,7 @@ function usageRows(h) {
|
|
|
320
322
|
id: `earlier:${type}:${title}`,
|
|
321
323
|
title,
|
|
322
324
|
type,
|
|
325
|
+
category: EARLIER,
|
|
323
326
|
at: 0,
|
|
324
327
|
calls,
|
|
325
328
|
key: type === 'key' ? amount : 0,
|
|
@@ -430,6 +433,17 @@ const USAGE_FIELDS = [
|
|
|
430
433
|
value: (r) => TYPE_LABEL[r.type],
|
|
431
434
|
render: (r) => (_jsx(UsageTip, { r: r, children: _jsx(Badge, { variant: "outline", className: cn('whitespace-nowrap', CHIP_TONE[TYPE_TONE[r.type]]), children: TYPE_LABEL[r.type] }) })),
|
|
432
435
|
},
|
|
436
|
+
{
|
|
437
|
+
key: 'category',
|
|
438
|
+
label: 'Category',
|
|
439
|
+
icon: LayersIcon,
|
|
440
|
+
format: 'text',
|
|
441
|
+
groupable: true,
|
|
442
|
+
filterable: true,
|
|
443
|
+
hideOnPhone: true,
|
|
444
|
+
value: (r) => r.category,
|
|
445
|
+
render: (r) => (_jsx(UsageTip, { r: r, children: _jsx(Badge, { variant: "outline", className: cn('whitespace-nowrap', CHIP_TONE.muted), children: r.category }) })),
|
|
446
|
+
},
|
|
433
447
|
{
|
|
434
448
|
key: 'at',
|
|
435
449
|
label: 'Date',
|
|
@@ -488,27 +502,42 @@ const USAGE_DOMAIN = {
|
|
|
488
502
|
formulas: false,
|
|
489
503
|
};
|
|
490
504
|
const USAGE_FILTER = [
|
|
505
|
+
{ key: 'category', label: 'Category', icon: LayersIcon, values: (r) => [r.category] },
|
|
491
506
|
{ key: 'type', label: 'Type', icon: KeyRoundIcon, values: (r) => [TYPE_LABEL[r.type]] },
|
|
492
507
|
];
|
|
508
|
+
const fieldOf = (key) => USAGE_FIELDS.find((f) => f.key === key) ?? null;
|
|
493
509
|
const GROUPS = [
|
|
494
|
-
{ id: 'type', label: 'Type', field: USAGE_FIELDS[1] },
|
|
495
|
-
{ id: 'none', label: 'None', field: null },
|
|
496
510
|
{ id: 'date', label: 'Date', field: DAY_FIELD },
|
|
511
|
+
{ id: 'category', label: 'Category', field: fieldOf('category') },
|
|
512
|
+
{ id: 'type', label: 'Type', field: fieldOf('type') },
|
|
513
|
+
{ id: 'none', label: 'None', field: null },
|
|
497
514
|
];
|
|
515
|
+
/** Rows a page of the table shows; a group's totals still count every row of it. */
|
|
516
|
+
const PAGE_SIZE = 50;
|
|
498
517
|
/**
|
|
499
|
-
* This month's use, one row a session (a chat, or every run of one task on one thing): its
|
|
500
|
-
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
518
|
+
* This month's use, one row a session (a chat, or every run of one task on one thing): its
|
|
519
|
+
* Category (Chat, Edit, Rebuild: `UsageEntry.category`), Type (the person's API key, their plan
|
|
520
|
+
* sign-in, or the app's credits), Date, Calls and Amount, grouped by Date (first), Category, Type or
|
|
521
|
+
* not at all, filtered by category, type or name, PAGE_SIZE rows a page (a group's totals count all
|
|
522
|
+
* its rows). A row's hover gives its tokens and cost. A sign-in's amount is an estimate: the same
|
|
523
|
+
* tokens at the model's API rates (the plan charges $0).
|
|
503
524
|
*/
|
|
504
525
|
export function AssistantUsageTable({ client }) {
|
|
505
|
-
const [group, setGroup] = useState('
|
|
526
|
+
const [group, setGroup] = useState('date');
|
|
527
|
+
const [page, setPage] = useState(0);
|
|
506
528
|
const q = useAssistantUsage(client);
|
|
507
529
|
const rows = useMemo(() => (q.data ? usageRows(q.data) : []), [q.data]);
|
|
508
530
|
const filter = useLumenFilter(rows, USAGE_FILTER, { text: (r) => [r.title] });
|
|
531
|
+
const shown = filter.results;
|
|
532
|
+
// A new filter or grouping starts from the first page; a page past the end shows the last.
|
|
533
|
+
const rowSet = shown.map((r) => r.id).join('\u0000');
|
|
534
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: resets on these changes only
|
|
535
|
+
useEffect(() => setPage(0), [rowSet, group]);
|
|
536
|
+
const pages = Math.max(1, Math.ceil(shown.length / PAGE_SIZE));
|
|
537
|
+
const at = Math.min(page, pages - 1);
|
|
509
538
|
if (!client.usage)
|
|
510
539
|
return null;
|
|
511
540
|
if (!q.data)
|
|
512
541
|
return (_jsx("div", { className: "flex justify-center p-4", children: q.isError ? (_jsx("p", { className: "text-muted-foreground text-sm", children: "Usage didn't load. Try again later." })) : (_jsx(Spinner, {})) }));
|
|
513
|
-
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenTokenizedFilter, { filter: filter, label: "Filter usage", placeholder: "Filter: type, or a session name", className: "min-w-48 flex-1", link: null }), _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, value: group, "aria-label": "Group by", onValueChange: (v) => v && setGroup(v), children: GROUPS.map((g) => (_jsx(ToggleGroupItem, { value: g.id, "aria-label": `Group by ${g.label}`, children: g.label }, g.id))) })] }),
|
|
542
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenTokenizedFilter, { filter: filter, label: "Filter usage", placeholder: "Filter: category, type, or a session name", className: "min-w-48 flex-1", link: null }), _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, value: group, "aria-label": "Group by", onValueChange: (v) => v && setGroup(v), children: GROUPS.map((g) => (_jsx(ToggleGroupItem, { value: g.id, "aria-label": `Group by ${g.label}`, children: g.label }, g.id))) })] }), _jsxs("div", { className: "min-w-0", children: [_jsx(LumenTable, { domain: USAGE_DOMAIN, rows: shown, groupBy: GROUPS.find((g) => g.id === group)?.field ?? null, page: { index: at, size: PAGE_SIZE }, sort: { key: 'at', desc: true }, emptyState: rows.length ? undefined : (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nothing used this month." })), onClearFilter: filter.clear }), pages > 1 && (_jsx(LumenPageFooter, { total: shown.length, page: at, size: PAGE_SIZE, onPage: setPage }))] }), _jsxs("p", { className: "text-muted-foreground text-xs", children: ["Sign-in amounts are estimates: your ", PLAN_NAME, " charges $0, so the amount is what the same tokens would cost at the model's API rates. Key and credit amounts are what was charged."] })] }));
|
|
514
543
|
}
|
package/dist/lumen-view.d.ts
CHANGED
|
@@ -213,7 +213,8 @@ export interface LumenTableProps<T> {
|
|
|
213
213
|
rows: T[];
|
|
214
214
|
groupBy?: ViewField<T> | null;
|
|
215
215
|
chips?: ViewChip[];
|
|
216
|
-
/** The page a
|
|
216
|
+
/** The page a table shows; null shows every row. A grouped table pages its rows in group order:
|
|
217
|
+
* a group with rows on the page shows its row there, its count and totals over all its rows. */
|
|
217
218
|
page?: {
|
|
218
219
|
index: number;
|
|
219
220
|
size: number;
|
package/dist/lumen-view.js
CHANGED
|
@@ -761,6 +761,13 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
761
761
|
}
|
|
762
762
|
return [...m];
|
|
763
763
|
}, [groupBy, sorted, serverGroups]);
|
|
764
|
+
// A grouped table's page (`page`): the rows on it, in group order; every group keeps its totals.
|
|
765
|
+
const paged = useMemo(() => {
|
|
766
|
+
if (!groups || !page || serverGroups)
|
|
767
|
+
return null;
|
|
768
|
+
const start = page.index * page.size;
|
|
769
|
+
return new Set(groups.flatMap(([, ms]) => ms).slice(start, start + page.size));
|
|
770
|
+
}, [groups, page, serverGroups]);
|
|
764
771
|
const span = shown.length + (selection ? 1 : 0) + (add ? 1 : 0);
|
|
765
772
|
const served = new Map((serverGroups?.list ?? []).map((g) => [g.key, g]));
|
|
766
773
|
const startsOpen = (i) => rows.length <= OPEN_ALL || i === 0;
|
|
@@ -959,6 +966,9 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
959
966
|
(!serverGroups || serverGroups.onToggleAll) && (_jsx(IconButton, { icon: anyClosed ? ChevronsUpDownIcon : ChevronsDownUpIcon, size: "icon-xs", className: "-ml-2 mr-1 align-middle", label: anyClosed ? 'Expand all groups' : 'Collapse all groups', onClick: () => setAll(anyClosed) })), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "xs", className: cn('font-medium', !(i === 0 && groups) && '-mx-2'), style: { color: colorOf(domain, shown[i]) ?? undefined }, onClick: h.column.getCanSort() ? h.column.getToggleSortingHandler() : undefined, children: [shown[i] ? _jsx(FreshnessMark, { domain: domain, field: shown[i] }) : null, flexRender(h.column.columnDef.header, h.getContext()), _jsx(SortIcon, { dir: h.column.getIsSorted() })] }) }), shown[i] && (domain.groups?.length || freshnessOf(domain, shown[i])) ? (_jsxs(TooltipContent, { children: [domain.groups?.length ? _jsx("div", { children: pathOf(domain, shown[i]) }) : null, freshnessLine(domain, shown[i]) ? (_jsx("div", { children: freshnessLine(domain, shown[i]) })) : null] })) : null] }), resizing && shown[i] && (_jsx(ColumnResizer, { label: shown[i].label, width: widthOf(shown[i]), onWidth: (w) => colWidths.set(h.id, w), onDone: (w) => colWidths.save(h.id, w), onReset: () => colWidths.reset(h.id) }))] }, h.id))), add && (_jsx(TableHead, { className: cn('w-8 pl-1', PINNED_HEAD), children: _jsx(AddColumnMenu, { add: add, fieldOpen: fieldOpen, onFieldOpen: setFieldOpen }) }))] }, hg.id))) }), _jsxs(TableBody, { children: [!rows.length && !serverGroups?.list?.length && (_jsx(TableRow, { "data-slot": "table-empty", className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: span, className: "py-6 text-center whitespace-normal text-muted-foreground", children: _jsxs("div", { className: "flex flex-col items-center gap-3", children: [emptyState ?? 'No rows match.', onClearFilter && (_jsxs(Button, { variant: "outline", size: "sm", onClick: onClearFilter, children: [_jsx(FilterXIcon, {}), "Clear filters"] }))] }) }) })), groups && groupBy
|
|
960
967
|
? groups.map(([key, members], gi) => {
|
|
961
968
|
const open = isOpen(key, gi);
|
|
969
|
+
const here = paged ? members.filter((m) => paged.has(m)) : members;
|
|
970
|
+
if (!here.length)
|
|
971
|
+
return null;
|
|
962
972
|
return (_jsxs(Fragment, { children: [_jsxs(TableRow, { className: ROLLUP, "data-group": key, "data-rollup": "", children: [selection && (_jsx(TableCell, { "data-card": "select", className: cn('w-8 pr-0', PINNED_GROUP), children: _jsx(Checkbox, { checked: members.every((m) => selection.keys.has(keyOf(m.original)))
|
|
963
973
|
? true
|
|
964
974
|
: members.some((m) => selection.keys.has(keyOf(m.original)))
|
|
@@ -972,7 +982,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
972
982
|
}), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(GroupMark, { field: groupBy, value: key }), _jsx("span", { className: "truncate", children: key }), _jsx(GroupCount, { children: serverGroups
|
|
973
983
|
? (served.get(key)?.n?.toLocaleString() ?? '')
|
|
974
984
|
: members.length })] }), groupActions?.(key, members.map((m) => m.original))] }) }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
|
|
975
|
-
rollCell(f, members.map((m) => m.original), 'group') }, f.key))), add && _jsx(TableCell, { className: PINNED_GROUP })] }), open && (serverGroups ? servedRows(members, key) : drawn(
|
|
985
|
+
rollCell(f, members.map((m) => m.original), 'group') }, f.key))), add && _jsx(TableCell, { className: PINNED_GROUP })] }), open && (serverGroups ? servedRows(members, key) : drawn(here, key))] }, key));
|
|
976
986
|
})
|
|
977
987
|
: drawn(sorted, ''), serverGroups?.more && !serverGroups.pages && (_jsx(TableRow, { "data-more": true, children: _jsx(TableCell, { colSpan: span, className: "py-1", children: _jsxs(Button, { variant: "ghost", size: "xs", onClick: serverGroups.more, children: [_jsx(ChevronsDownIcon, {}), "More groups"] }) }) })), hasTotal && rows.length > 0 && (_jsxs(TableRow, { className: cn(ROLLUP, 'border-t-2'), "data-total": true, "data-rollup": "", children: [selection && _jsx(TableCell, {}), rollCells(rows).map((cell, i) => i === 0 && !(shown[0] && rollsUp(shown[0])) ? (_jsx(TableCell, { "data-card": "lead", children: "Total" }, "total")) : (cell)), add && _jsx(TableCell, {})] }))] })] }) })] }));
|
|
978
988
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.302",
|
|
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.302",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|