@columenlabs/lumen-ui 0.1.260 → 0.1.262
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,7 +36,7 @@ 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 chat or run: its Type (the person's API key, their plan sign-in, or
|
|
39
|
+
* This month's use, one row a session (a chat, or every run of one task on one thing): its Type (the person's API key, their plan sign-in, or
|
|
40
40
|
* the app's credits), Date, Calls and Amount, grouped by Type, Date or not at all, filtered by
|
|
41
41
|
* type or name. A row's hover gives its tokens and cost. A sign-in's amount is an estimate: the
|
|
42
42
|
* same tokens at the model's API rates (the plan charges $0).
|
|
@@ -254,30 +254,59 @@ const TYPE_LABEL = {
|
|
|
254
254
|
key: 'API key',
|
|
255
255
|
plan: 'Sign-in',
|
|
256
256
|
credits: 'Credits',
|
|
257
|
+
internal: 'Internal',
|
|
258
|
+
};
|
|
259
|
+
const TYPE_TONE = {
|
|
260
|
+
key: 'sky',
|
|
261
|
+
plan: 'amber',
|
|
262
|
+
credits: 'muted',
|
|
263
|
+
internal: 'muted',
|
|
257
264
|
};
|
|
258
|
-
const TYPE_TONE = { key: 'sky', plan: 'amber', credits: 'muted' };
|
|
259
265
|
/** Dollars with no `<` or `≈` (the table's note says which amounts are estimates). */
|
|
260
266
|
const money = (n) => (n === 0 ? '$0' : `$${n.toFixed(n < 10 ? 2 : 0)}`);
|
|
261
267
|
const amountOf = (r) => r.key + r.credits + r.plan;
|
|
262
268
|
const EARLIER = 'Earlier';
|
|
263
269
|
/** This month's rows: every chat and run, then the spend no chat or run carries, by who paid. */
|
|
264
270
|
function usageRows(h) {
|
|
265
|
-
|
|
271
|
+
// One row a session: a chat, or every run of one task on one thing (each Rebuild of a part).
|
|
272
|
+
const sessions = new Map();
|
|
273
|
+
for (const e of h.entries) {
|
|
266
274
|
const key = e.own_usd ?? 0;
|
|
267
275
|
const plan = e.own_plan_usd ?? 0;
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
const id = e.kind === 'chat' ? e.id : `task:${e.task ?? ''}:${e.title}`;
|
|
277
|
+
const s = sessions.get(id);
|
|
278
|
+
const planCalls = (s?.planCalls ?? 0) + (e.own_plan_calls ?? 0);
|
|
279
|
+
const row = {
|
|
280
|
+
id,
|
|
271
281
|
title: e.title,
|
|
272
|
-
type,
|
|
273
|
-
at: e.at,
|
|
274
|
-
calls: e.calls,
|
|
275
|
-
tokens: {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
282
|
+
type: 'credits',
|
|
283
|
+
at: Math.max(s?.at ?? 0, e.at),
|
|
284
|
+
calls: (s?.calls ?? 0) + e.calls,
|
|
285
|
+
tokens: {
|
|
286
|
+
in: (s?.tokens?.in ?? 0) + e.in,
|
|
287
|
+
read: (s?.tokens?.read ?? 0) + e.read,
|
|
288
|
+
out: (s?.tokens?.out ?? 0) + e.out,
|
|
289
|
+
},
|
|
290
|
+
key: (s?.key ?? 0) + key,
|
|
291
|
+
credits: (s?.credits ?? 0) + Math.max(0, e.usd - key),
|
|
292
|
+
plan: (s?.plan ?? 0) + plan,
|
|
293
|
+
planCalls,
|
|
279
294
|
};
|
|
280
|
-
|
|
295
|
+
// Its type is whoever paid most; the hover splits the rest.
|
|
296
|
+
const top = Math.max(row.key, row.credits, row.plan);
|
|
297
|
+
row.type =
|
|
298
|
+
row.plan > 0 && row.plan === top
|
|
299
|
+
? 'plan'
|
|
300
|
+
: row.key > 0 && row.key === top
|
|
301
|
+
? 'key'
|
|
302
|
+
: row.credits > 0 || !planCalls
|
|
303
|
+
? 'credits'
|
|
304
|
+
: 'plan';
|
|
305
|
+
if (e.internal)
|
|
306
|
+
row.type = 'internal';
|
|
307
|
+
sessions.set(id, row);
|
|
308
|
+
}
|
|
309
|
+
const out = [...sessions.values()];
|
|
281
310
|
const sum = (xs) => xs.reduce((a, n) => a + n, 0);
|
|
282
311
|
const earlier = (type, title, calls, amount) => amount >= 0.01 &&
|
|
283
312
|
out.push({
|
|
@@ -298,7 +327,8 @@ function usageRows(h) {
|
|
|
298
327
|
earlier('plan', 'Earlier this month, not per chat', Math.max(0, sum(plans.map((r) => r.calls)) - sum(h.entries.map((e) => e.own_plan_calls ?? 0))), sum(plans.map((r) => r.list_usd ?? 0)) - sum(h.entries.map((e) => e.own_plan_usd ?? 0)));
|
|
299
328
|
earlier('credits', 'Earlier this month, not per chat', Math.max(0, sum(app.map((r) => r.calls)) -
|
|
300
329
|
sum(h.entries.filter((e) => e.usd > (e.own_usd ?? 0)).map((e) => e.calls))), sum(app.map((r) => r.usd)) - sum(h.entries.map((e) => Math.max(0, e.usd - (e.own_usd ?? 0)))));
|
|
301
|
-
|
|
330
|
+
// The app's own calls are kept (and counted above, so no Earlier row takes them) but not shown.
|
|
331
|
+
return out.filter((r) => r.type !== 'internal');
|
|
302
332
|
}
|
|
303
333
|
/** A row's hover: its tokens (cache reads as a share of input) and what it cost, by who paid. */
|
|
304
334
|
function UsageTip({ r, children }) {
|
|
@@ -319,15 +349,55 @@ function UsageTip({ r, children }) {
|
|
|
319
349
|
? [['Estimate (API rates)', money(r.plan)]]
|
|
320
350
|
: []),
|
|
321
351
|
];
|
|
322
|
-
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: children }), _jsx(TooltipContent, { className: "max-w-72", children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "
|
|
352
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: children }), _jsx(TooltipContent, { className: "max-w-72", children: _jsxs("div", { className: "flex min-w-0 flex-col gap-1", children: [_jsx("span", { className: "whitespace-normal break-words font-medium", children: r.title }), _jsx("dl", { className: "grid grid-cols-[auto_auto] gap-x-4 gap-y-0.5", children: lines.map(([k, v]) => (_jsxs(Fragment, { children: [_jsx("dt", { className: "opacity-75", children: k }), _jsx("dd", { className: "num text-right", children: v })] }, k))) }), !t && _jsx("span", { className: "opacity-75", children: "Spent before each chat kept its own count." })] }) })] }));
|
|
323
353
|
}
|
|
324
|
-
|
|
354
|
+
/** Midnight of `d`'s day, local time. */
|
|
355
|
+
const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
356
|
+
/** Days from `at`'s day back to today (0 today, 1 yesterday). */
|
|
357
|
+
const daysAgo = (at) => Math.round((startOf(new Date()) - startOf(new Date(at))) / 86_400_000);
|
|
358
|
+
/** Days since Monday, today included (0 on a Monday). */
|
|
359
|
+
const sinceMonday = () => (new Date().getDay() + 6) % 7;
|
|
360
|
+
const sameMonth = (at) => {
|
|
361
|
+
const d = new Date(at);
|
|
362
|
+
const n = new Date();
|
|
363
|
+
return d.getFullYear() === n.getFullYear() && d.getMonth() === n.getMonth();
|
|
364
|
+
};
|
|
365
|
+
/** Grouping by date: Today, Yesterday, This week, This month, then each month by name. Spend
|
|
366
|
+
* from before chats kept their own count is this month's. */
|
|
367
|
+
const dayOf = (at) => {
|
|
368
|
+
if (!at)
|
|
369
|
+
return 'This month';
|
|
370
|
+
const n = daysAgo(at);
|
|
371
|
+
if (n <= 0)
|
|
372
|
+
return 'Today';
|
|
373
|
+
if (n === 1)
|
|
374
|
+
return 'Yesterday';
|
|
375
|
+
if (n <= sinceMonday())
|
|
376
|
+
return 'This week';
|
|
377
|
+
if (sameMonth(at))
|
|
378
|
+
return 'This month';
|
|
379
|
+
return new Date(at).toLocaleDateString(undefined, { month: 'long', year: 'numeric' });
|
|
380
|
+
};
|
|
381
|
+
/** A row's date: today's time, Yesterday, the weekday this week, else the day. */
|
|
382
|
+
const relWhen = (at) => {
|
|
383
|
+
const n = daysAgo(at);
|
|
384
|
+
if (n <= 0)
|
|
385
|
+
return new Date(at).toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
|
|
386
|
+
if (n === 1)
|
|
387
|
+
return 'Yesterday';
|
|
388
|
+
if (n <= sinceMonday())
|
|
389
|
+
return new Date(at).toLocaleDateString(undefined, { weekday: 'short' });
|
|
390
|
+
return new Date(at).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
391
|
+
};
|
|
325
392
|
const USAGE_FIELDS = [
|
|
326
393
|
{
|
|
327
394
|
key: 'title',
|
|
328
395
|
label: 'Session',
|
|
329
396
|
icon: MessageSquareIcon,
|
|
330
397
|
format: 'text',
|
|
398
|
+
// Names run long (a chat's first line): as wide as the table leaves after its other columns
|
|
399
|
+
// (the table's box is a size container), never under the usual name column.
|
|
400
|
+
maxWidth: 'max(12rem, calc(100cqw - 22rem))',
|
|
331
401
|
value: (r) => r.title,
|
|
332
402
|
render: (r) => (_jsx(UsageTip, { r: r, children: _jsx("span", {
|
|
333
403
|
// biome-ignore lint/a11y/noNoninteractiveTabindex: a keyboard reaches the row's hover
|
|
@@ -350,7 +420,7 @@ const USAGE_FIELDS = [
|
|
|
350
420
|
format: 'integer',
|
|
351
421
|
hideOnPhone: true,
|
|
352
422
|
value: (r) => r.at,
|
|
353
|
-
render: (r) => (_jsx(UsageTip, { r: r, children: r.at ? (_jsx("time", { dateTime: new Date(r.at).toISOString(), className: "text-muted-foreground", children:
|
|
423
|
+
render: (r) => (_jsx(UsageTip, { r: r, children: r.at ? (_jsx("time", { dateTime: new Date(r.at).toISOString(), className: "text-muted-foreground", children: relWhen(r.at) })) : (_jsx("span", { className: "text-muted-foreground", children: EARLIER })) })),
|
|
354
424
|
},
|
|
355
425
|
{
|
|
356
426
|
key: 'calls',
|
|
@@ -398,7 +468,7 @@ const GROUPS = [
|
|
|
398
468
|
{ id: 'date', label: 'Date', field: DAY_FIELD },
|
|
399
469
|
];
|
|
400
470
|
/**
|
|
401
|
-
* This month's use, one row a chat or run: its Type (the person's API key, their plan sign-in, or
|
|
471
|
+
* This month's use, one row a session (a chat, or every run of one task on one thing): its Type (the person's API key, their plan sign-in, or
|
|
402
472
|
* the app's credits), Date, Calls and Amount, grouped by Type, Date or not at all, filtered by
|
|
403
473
|
* type or name. A row's hover gives its tokens and cost. A sign-in's amount is an estimate: the
|
|
404
474
|
* same tokens at the model's API rates (the plan charges $0).
|
|
@@ -412,5 +482,5 @@ export function AssistantUsageTable({ client }) {
|
|
|
412
482
|
return null;
|
|
413
483
|
if (!q.data)
|
|
414
484
|
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, {})) }));
|
|
415
|
-
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))) })] }), _jsx(LumenTable, { domain: USAGE_DOMAIN, rows: filter.results, groupBy: GROUPS.find((g) => g.id === group)?.field ?? null, 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 }), _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."] })] }));
|
|
485
|
+
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))) })] }), _jsx("div", { className: "@container min-w-0", children: _jsx(LumenTable, { domain: USAGE_DOMAIN, rows: filter.results, groupBy: GROUPS.find((g) => g.id === group)?.field ?? null, 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 }) }), _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."] })] }));
|
|
416
486
|
}
|
package/dist/lumen-view.js
CHANGED
|
@@ -507,6 +507,12 @@ function CappedCell({ max, end, title, children, }) {
|
|
|
507
507
|
return children;
|
|
508
508
|
return (_jsx("div", { className: cn('flex min-w-0 *:min-w-0 *:max-w-full *:truncate', end && 'justify-end'), style: { maxWidth: max }, title: title, children: children }));
|
|
509
509
|
}
|
|
510
|
+
/** A group heading's row count, as a chip beside its name. */
|
|
511
|
+
function GroupCount({ children }) {
|
|
512
|
+
if (children === '' || children == null)
|
|
513
|
+
return null;
|
|
514
|
+
return (_jsx(Badge, { variant: "secondary", className: "num h-4.5 shrink-0 px-1.5 font-normal text-xs", children: children }));
|
|
515
|
+
}
|
|
510
516
|
/** The flat or grouped table: sortable headers, a Total row, group rows that collapse. */
|
|
511
517
|
// Sorting, and paging of flat tables (the page is LumenView's, set right of the row count).
|
|
512
518
|
const SORTING = tableFeatures({
|
|
@@ -874,7 +880,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
874
880
|
const n = new Set(o);
|
|
875
881
|
n.has(k) ? n.delete(k) : n.add(k);
|
|
876
882
|
return n;
|
|
877
|
-
}), children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, groupBy && i === 0 && 'pl-8'), children: i === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1 whitespace-nowrap", children: [chevron(k, `${key} ${openLabel()}`), subgroup?.mark?.(key, ms), _jsx("span", { className: "truncate font-medium", title: key, children: titleOf(key, ms) }), _jsx(
|
|
883
|
+
}), children: [selection && _jsx(TableCell, {}), shown.map((f, i) => (_jsx(TableCell, { ...col(f, groupBy && i === 0 && 'pl-8'), children: i === 0 ? (_jsxs("span", { className: "flex min-w-0 items-center gap-1 whitespace-nowrap", children: [chevron(k, `${key} ${openLabel()}`), subgroup?.mark?.(key, ms), _jsx("span", { className: "truncate font-medium", title: key, children: titleOf(key, ms) }), _jsx(GroupCount, { children: ms.length })] })) : (rollCell(f, ms, 'fold')) }, f.key))), add && _jsx(TableCell, {})] }), openParts.has(k) && ms.map((p) => partRow(p, subgroup?.name))] }, k));
|
|
878
884
|
};
|
|
879
885
|
const rowsOf = (list, scope) => {
|
|
880
886
|
const under = folds ? subgroup?.under : undefined;
|
|
@@ -957,7 +963,7 @@ function LumenTableRows({ domain, columns = domain.fields, rows, groupBy = null,
|
|
|
957
963
|
const n = new Set(c);
|
|
958
964
|
n.has(key) ? n.delete(key) : n.add(key);
|
|
959
965
|
return n;
|
|
960
|
-
}), children: [_jsx(ChevronRightIcon, { className: cn('transition-transform', open && 'rotate-90') }), _jsx(GroupMark, { field: groupBy, value: key }), _jsx("span", { className: "truncate", children: key }), _jsx(
|
|
966
|
+
}), 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
|
|
961
967
|
? (served.get(key)?.n?.toLocaleString() ?? '')
|
|
962
968
|
: members.length })] }), groupActions?.(key, members.map((m) => m.original))] }) }, f.key)) : (_jsx(TableCell, { ...col(f, PINNED_GROUP), children: !serverGroups &&
|
|
963
969
|
rollCell(f, members.map((m) => m.original), 'group') }, f.key))), add && _jsx(TableCell, { className: PINNED_GROUP })] }), open && (serverGroups ? servedRows(members, key) : drawn(members, key))] }, key));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.262",
|
|
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.262",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|