@columenlabs/lumen-ui 0.1.260 → 0.1.261

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
- const out = h.entries.map((e) => {
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 type = plan > 0 || e.own_plan_calls ? 'plan' : key > 0 ? 'key' : 'credits';
269
- return {
270
- id: e.id,
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: { in: e.in, read: e.read, out: e.out },
276
- key,
277
- credits: Math.max(0, e.usd - key),
278
- plan,
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
- return out;
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,7 +349,7 @@ 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: "truncate 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." })] }) })] }));
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
  const dayOf = (at) => at ? new Date(at).toLocaleDateString(undefined, { month: 'short', day: 'numeric' }) : EARLIER;
325
355
  const USAGE_FIELDS = [
@@ -328,6 +358,9 @@ const USAGE_FIELDS = [
328
358
  label: 'Session',
329
359
  icon: MessageSquareIcon,
330
360
  format: 'text',
361
+ // Names run long (a chat's first line): as wide as the table leaves after its other columns
362
+ // (the table's box is a size container), never under the usual name column.
363
+ maxWidth: 'max(12rem, calc(100cqw - 22rem))',
331
364
  value: (r) => r.title,
332
365
  render: (r) => (_jsx(UsageTip, { r: r, children: _jsx("span", {
333
366
  // biome-ignore lint/a11y/noNoninteractiveTabindex: a keyboard reaches the row's hover
@@ -398,7 +431,7 @@ const GROUPS = [
398
431
  { id: 'date', label: 'Date', field: DAY_FIELD },
399
432
  ];
400
433
  /**
401
- * This month's use, one row a chat or run: its Type (the person's API key, their plan sign-in, or
434
+ * 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
435
  * the app's credits), Date, Calls and Amount, grouped by Type, Date or not at all, filtered by
403
436
  * type or name. A row's hover gives its tokens and cost. A sign-in's amount is an estimate: the
404
437
  * same tokens at the model's API rates (the plan charges $0).
@@ -412,5 +445,5 @@ export function AssistantUsageTable({ client }) {
412
445
  return null;
413
446
  if (!q.data)
414
447
  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."] })] }));
448
+ 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
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.260",
3
+ "version": "0.1.261",
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.260",
52
+ "@columenlabs/lumen-ui-types": "0.1.261",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",