@columenlabs/lumen-ui 0.1.276 → 0.1.278

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,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, ReceiptIcon, } from 'lucide-react';
3
+ import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon, CpuIcon, HashIcon, KeyRoundIcon, 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';
@@ -291,6 +291,13 @@ function usageRows(h) {
291
291
  credits: (s?.credits ?? 0) + Math.max(0, e.usd - key),
292
292
  plan: (s?.plan ?? 0) + plan,
293
293
  planCalls,
294
+ ...(e.turns || s?.turns
295
+ ? {
296
+ turns: (s?.turns ?? 0) + (e.turns ?? 0),
297
+ images: (s?.images ?? 0) + (e.images ?? 0),
298
+ refs: (s?.refs ?? 0) + (e.refs ?? 0),
299
+ }
300
+ : {}),
294
301
  };
295
302
  // Its type is whoever paid most; the hover splits the rest.
296
303
  const top = Math.max(row.key, row.credits, row.plan);
@@ -330,6 +337,15 @@ function usageRows(h) {
330
337
  // The app's own calls are kept (and counted above, so no Earlier row takes them) but not shown.
331
338
  return out.filter((r) => r.type !== 'internal');
332
339
  }
340
+ const plural = (n, one) => `${n} ${one}${n === 1 ? '' : 's'}`;
341
+ /** A row's attachments: their count, then each kind there was (`3 · 2 images, 1 pick`). */
342
+ const attachments = (r) => {
343
+ const kinds = [
344
+ ...(r.images ? [plural(r.images, 'image')] : []),
345
+ ...(r.refs ? [plural(r.refs, 'pick')] : []),
346
+ ];
347
+ return kinds.length ? `${(r.images ?? 0) + (r.refs ?? 0)} · ${kinds.join(', ')}` : '0';
348
+ };
333
349
  /** A row's hover: its tokens (cache reads as a share of input) and what it cost, by who paid. */
334
350
  function UsageTip({ r, children }) {
335
351
  const t = r.tokens;
@@ -341,6 +357,8 @@ function UsageTip({ r, children }) {
341
357
  ['Output', tokens(t.out)],
342
358
  ]
343
359
  : []),
360
+ ...(r.turns ? [['Turns', String(r.turns)]] : []),
361
+ ...(r.turns ? [['Attachments', attachments(r)]] : []),
344
362
  ...(r.key > 0 ? [['Cost (your key)', money(r.key)]] : []),
345
363
  ...(r.credits > 0 || (r.type === 'credits' && !r.plan)
346
364
  ? [['Credits', money(r.credits)]]
@@ -395,9 +413,8 @@ const USAGE_FIELDS = [
395
413
  label: 'Session',
396
414
  icon: MessageSquareIcon,
397
415
  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))',
416
+ // Names run long (a chat's first line): all the width the other columns leave.
417
+ maxWidth: 'fill',
401
418
  value: (r) => r.title,
402
419
  render: (r) => (_jsx(UsageTip, { r: r, children: _jsx("span", {
403
420
  // biome-ignore lint/a11y/noNoninteractiveTabindex: a keyboard reaches the row's hover
@@ -422,6 +439,17 @@ const USAGE_FIELDS = [
422
439
  value: (r) => r.at,
423
440
  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 })) })),
424
441
  },
442
+ {
443
+ key: 'turns',
444
+ label: 'Turns',
445
+ icon: MessagesSquareIcon,
446
+ format: 'integer',
447
+ hideOnPhone: true,
448
+ aggregate: 'sum',
449
+ // A run, or a chat from before turns were counted, has none: a dash, not 0.
450
+ value: (r) => r.turns ?? null,
451
+ render: (r) => (_jsx(UsageTip, { r: r, children: _jsx("span", { className: cn(!r.turns && 'text-muted-foreground'), children: r.turns ?? '—' }) })),
452
+ },
425
453
  {
426
454
  key: 'calls',
427
455
  label: 'Calls',
@@ -482,5 +510,5 @@ export function AssistantUsageTable({ client }) {
482
510
  return null;
483
511
  if (!q.data)
484
512
  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, {})) }));
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."] })] }));
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))) })] }), _jsx("div", { className: "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."] })] }));
486
514
  }
@@ -493,12 +493,18 @@ const colPropsOf = (units) => (f, ...more) => ({
493
493
  className: cn(colOf(f), ...more),
494
494
  'data-col': f?.key,
495
495
  'data-units': f && units.has(f.key) ? '' : undefined,
496
+ 'data-fill': f?.maxWidth === 'fill' ? '' : undefined,
496
497
  });
497
498
  /**
498
499
  * A column's widest cell (`maxWidth`): the first column defaults to `--lead-col-max` (18rem in
499
500
  * theme.css), so one long name never pushes the other columns off screen; `false` lifts it.
501
+ * `fill` takes the room the other columns leave (theme.css `[data-fill]`), so its cell holds to it.
500
502
  */
501
- const capOf = (f, i) => f.maxWidth === false ? undefined : (f.maxWidth ?? (i === 0 ? 'var(--lead-col-max)' : undefined));
503
+ const capOf = (f, i) => f.maxWidth === false
504
+ ? undefined
505
+ : f.maxWidth === 'fill'
506
+ ? '100%'
507
+ : (f.maxWidth ?? (i === 0 ? 'var(--lead-col-max)' : undefined));
502
508
  /** Holds a cell to its column's cap on one line: text past it ends in an ellipsis. `end` keeps a
503
509
  * right-aligned column's value on its right edge (the wrapper is a flex row, which `text-right`
504
510
  * alone does not move), so a capped or resized column keeps one edge. */
package/dist/theme.css CHANGED
@@ -537,6 +537,18 @@
537
537
  );
538
538
  }
539
539
 
540
+ /* A column that fills (`ViewField.maxWidth: 'fill'`): it takes the width the other columns leave
541
+ and gives none of its own text's width to the table (max-width: 0), so its cells end in an
542
+ ellipsis only where the table truly has no more room. Never under 12rem: narrower, the table
543
+ scrolls. A phone's cards lay the row out themselves. */
544
+ @media (min-width: 640px) {
545
+ [data-slot="table"] :is(th, td)[data-fill] {
546
+ width: 100%;
547
+ max-width: 0;
548
+ min-width: 12rem;
549
+ }
550
+ }
551
+
540
552
  /* A LumenView table on a narrow phone (`data-cards`): each row is a card, so nothing runs off
541
553
  the right edge, and every row is ONE line, whatever an app draws in it: the row's name (shortens
542
554
  in an ellipsis), then one figure (`data-card="meta"`, the sorted one, else the first number or
@@ -141,9 +141,10 @@ export interface ViewField<T> {
141
141
  /**
142
142
  * The column's widest cell, a CSS length (`14rem`): past it the cell ends in an ellipsis on one
143
143
  * line, the full text in its hover. The first column defaults to `--lead-col-max` (18rem);
144
- * `false` lets it grow.
144
+ * `false` lets it grow. `fill` gives it all the width the other columns leave (at least 12rem),
145
+ * so a long name ends in an ellipsis only where the table truly has no more room.
145
146
  */
146
- maxWidth?: string | false;
147
+ maxWidth?: 'fill' | (string & {}) | false;
147
148
  /** Left out on phones, where the table has no room for it (a returns table's longer windows). */
148
149
  hideOnPhone?: boolean;
149
150
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.276",
3
+ "version": "0.1.278",
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.276",
52
+ "@columenlabs/lumen-ui-types": "0.1.278",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",