@columenlabs/lumen-ui 0.1.234 → 0.1.236
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.
|
@@ -17,7 +17,7 @@ import { Label } from '../ui/label.js';
|
|
|
17
17
|
import { Switch } from '../ui/switch.js';
|
|
18
18
|
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group.js';
|
|
19
19
|
import { allowanceAmount, assistantStatusKey, usd, useAssistantStatus } from './lumen-assistant.js';
|
|
20
|
-
import { UsageHistoryView } from './usage-history.js';
|
|
20
|
+
import { OwnKeyUsage, UsageHistoryView } from './usage-history.js';
|
|
21
21
|
const EFFORTS = [
|
|
22
22
|
{ value: 'default', label: 'Model default' },
|
|
23
23
|
{ value: 'low', label: 'Low' },
|
|
@@ -117,12 +117,20 @@ const wayTest = (k, plan = false) => k?.test
|
|
|
117
117
|
: null;
|
|
118
118
|
/** A provider's mark: its initial on a tile. */
|
|
119
119
|
const mark = (label) => _jsx(ConnectionLogo, { text: label.charAt(0) });
|
|
120
|
+
/**
|
|
121
|
+
* Whether the app lets this person use `w` of their own on `p` (the app's `ownWays`, the same
|
|
122
|
+
* rule the server pays by). A way it does not allow never shows, even when one is still stored:
|
|
123
|
+
* the server would not run on it, so Settings never says it works.
|
|
124
|
+
*/
|
|
125
|
+
const mayOwn = (p, w) => !p.own_ways || p.own_ways.includes(w);
|
|
126
|
+
/** Whether the person has a way of their own on `p` that the app lets them use. */
|
|
127
|
+
const ownUsable = (p) => (mayOwn(p, 'key') && p.own.set) || (mayOwn(p, 'plan') && !!p.plan?.set);
|
|
120
128
|
/** One provider, collapsed to a row: the person's own key and, where it has one, their plan sign-in. */
|
|
121
129
|
function OwnProviderKey({ client, p }) {
|
|
122
130
|
const { apply, refresh } = useApply(client);
|
|
123
131
|
const ways = [];
|
|
124
|
-
const may = (w) =>
|
|
125
|
-
if (p.signin === 'chatgpt' &&
|
|
132
|
+
const may = (w) => mayOwn(p, w);
|
|
133
|
+
if (p.signin === 'chatgpt' && may('plan'))
|
|
126
134
|
ways.push({
|
|
127
135
|
id: 'plan',
|
|
128
136
|
label: 'ChatGPT plan',
|
|
@@ -139,7 +147,7 @@ function OwnProviderKey({ client, p }) {
|
|
|
139
147
|
removeText: 'Your API key, if you set one, runs from then on.',
|
|
140
148
|
removeLabel: 'Sign out',
|
|
141
149
|
});
|
|
142
|
-
if (may('key')
|
|
150
|
+
if (may('key'))
|
|
143
151
|
ways.push({
|
|
144
152
|
id: 'key',
|
|
145
153
|
label: 'API key',
|
|
@@ -161,14 +169,14 @@ function OwnProviderKey({ client, p }) {
|
|
|
161
169
|
function Providers({ client }) {
|
|
162
170
|
const { apply } = useApply(client);
|
|
163
171
|
return (_jsx(WithStatus, { client: client, children: (s) => {
|
|
164
|
-
// A provider the app gives this person no way of their own on shows no row
|
|
165
|
-
//
|
|
166
|
-
const rows = s.providers.filter((p) => !p.own_ways || p.own_ways.length > 0
|
|
167
|
-
const ownSet = rows.some(
|
|
172
|
+
// A provider the app gives this person no way of their own on shows no row: what shows here
|
|
173
|
+
// is exactly what the server may pay with.
|
|
174
|
+
const rows = s.providers.filter((p) => !p.own_ways || p.own_ways.length > 0);
|
|
175
|
+
const ownSet = rows.some(ownUsable);
|
|
168
176
|
return (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: rows.map((p) => (_jsx(OwnProviderKey, { client: client, p: p }, p.id))) }), ownSet && (_jsx(Row, { label: "Answers run on", info: "Your own key or plan first, with the shared credits behind it; or the shared credits first.", children: _jsx(LumenSelector, { label: "Answers run on", size: "sm", className: "w-44", value: s.pay, options: [
|
|
169
177
|
{ value: 'own', label: 'Your own first' },
|
|
170
178
|
{ value: 'app', label: 'Shared credits first' },
|
|
171
|
-
], onChange: (pay) => void apply('pay', () => client.settings({ pay: pay })) }) })), ownSet && (_jsx(Row, { label: "Your keys may spend", info: "On your keys, every provider together.", children: _jsx(CapInput, { label: "Your monthly cap", value: s.caps.own, onSave: (n) => void apply('own', () => client.ownCap(n)) }) }))] }));
|
|
179
|
+
], onChange: (pay) => void apply('pay', () => client.settings({ pay: pay })) }) })), ownSet && (_jsx(Row, { label: "Your keys may spend", info: "On your keys, every provider together.", children: _jsx(CapInput, { label: "Your monthly cap", value: s.caps.own, onSave: (n) => void apply('own', () => client.ownCap(n)) }) })), _jsx(OwnKeyUsage, { client: client, always: ownSet })] }));
|
|
172
180
|
} }));
|
|
173
181
|
}
|
|
174
182
|
/** One provider's shared key, which an admin sets for everyone. */
|
|
@@ -13,3 +13,12 @@ export declare function UsageHistoryView({ client, className, }: {
|
|
|
13
13
|
client: AssistantAdapter;
|
|
14
14
|
className?: string;
|
|
15
15
|
}): import("react").JSX.Element | null;
|
|
16
|
+
/**
|
|
17
|
+
* Spend on the person's own keys and plans over the last 1, 7 or 30 days: one row a chat or run
|
|
18
|
+
* (Session, Date, Amount), newest first, and a Total of the rows. A session counts in the range its
|
|
19
|
+
* last use falls in. Shown when `always` (an own key or plan is set) or any session was paid on one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function OwnKeyUsage({ client, always }: {
|
|
22
|
+
client: AssistantAdapter;
|
|
23
|
+
always?: boolean;
|
|
24
|
+
}): import("react").JSX.Element | null;
|
|
@@ -2,9 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useQuery } from '@tanstack/react-query';
|
|
3
3
|
import { ChevronLeftIcon, ChevronRightIcon, CpuIcon, MessageSquareIcon } from 'lucide-react';
|
|
4
4
|
import { Fragment, useState } from 'react';
|
|
5
|
+
import { DetailsTable } from '../details-table.js';
|
|
5
6
|
import { IconButton } from '../icon-button.js';
|
|
7
|
+
import { InfoLabel } from '../info-tip.js';
|
|
6
8
|
import { cn } from '../lib/utils.js';
|
|
9
|
+
import { Label } from '../ui/label.js';
|
|
7
10
|
import { Spinner } from '../ui/spinner.js';
|
|
11
|
+
import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group.js';
|
|
8
12
|
export const usd = (n) => n === 0 ? '$0' : n < 0.01 ? '<$0.01' : `$${n.toFixed(n < 10 ? 2 : 0)}`;
|
|
9
13
|
export const tokens = (n) => n < 1000
|
|
10
14
|
? String(n)
|
|
@@ -86,3 +90,45 @@ export function UsageHistoryView({ client, className, }) {
|
|
|
86
90
|
const models = perModel(h.rows);
|
|
87
91
|
return (_jsxs("div", { className: cn('flex min-w-0 flex-col gap-3', className), children: [_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(IconButton, { icon: ChevronLeftIcon, label: "Earlier month", disabled: !older, onClick: () => setMonth(older) }), _jsx("span", { className: "min-w-0 flex-1 truncate text-center font-medium text-sm", children: monthName(h.month) }), _jsx(IconButton, { icon: ChevronRightIcon, label: "Later month", disabled: !newer, onClick: () => setMonth(newer) })] }), _jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "num font-semibold text-2xl", children: usd(h.usd) }), _jsx("span", { className: "text-muted-foreground text-xs", children: h.entries.length === 1 ? '1 chat or run' : `${h.entries.length} chats and runs` })] }), models.length > 0 && (_jsxs("div", { className: "grid grid-cols-[minmax(0,1fr)_auto_auto_auto_auto] gap-x-3 gap-y-0.5 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: "Model" }), _jsx("span", { className: "text-right text-muted-foreground", children: "Cost" }), _jsx("span", { className: "text-right text-muted-foreground", children: "In" }), _jsx("span", { className: "text-right text-muted-foreground", children: "Out" }), _jsx("span", { className: "text-right text-muted-foreground", children: "Calls" }), models.map((m) => (_jsxs(Fragment, { children: [_jsx("span", { className: "truncate font-medium", title: m.label, children: m.label }), _jsx("span", { className: "num text-right", children: usd(m.usd) }), _jsx("span", { className: "num text-right", children: tokens(m.in) }), _jsx("span", { className: "num text-right", children: tokens(m.out) }), _jsx("span", { className: "num text-right", children: m.calls })] }, m.label)))] })), h.entries.length ? (_jsx("ul", { className: "-my-1.5 max-h-72 divide-y overflow-y-auto", children: h.entries.map((e) => (_jsx(Entry, { e: e }, e.id))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nothing used this month." }))] }));
|
|
88
92
|
}
|
|
93
|
+
/** The ranges the own-key table offers, in days. */
|
|
94
|
+
const OWN_RANGES = [
|
|
95
|
+
{ id: '1d', days: 1 },
|
|
96
|
+
{ id: '7d', days: 7 },
|
|
97
|
+
{ id: '30d', days: 30 },
|
|
98
|
+
];
|
|
99
|
+
/**
|
|
100
|
+
* Spend on the person's own keys and plans over the last 1, 7 or 30 days: one row a chat or run
|
|
101
|
+
* (Session, Date, Amount), newest first, and a Total of the rows. A session counts in the range its
|
|
102
|
+
* last use falls in. Shown when `always` (an own key or plan is set) or any session was paid on one.
|
|
103
|
+
*/
|
|
104
|
+
export function OwnKeyUsage({ client, always }) {
|
|
105
|
+
const [range, setRange] = useState('7d');
|
|
106
|
+
const now = useAssistantUsage(client).data;
|
|
107
|
+
// The month before, for a range that reaches back past this month's start.
|
|
108
|
+
const before = now?.months[now.months.indexOf(now.month) + 1];
|
|
109
|
+
const prev = useAssistantUsage(client, before, !!before && range === '30d').data;
|
|
110
|
+
if (!now)
|
|
111
|
+
return null;
|
|
112
|
+
const all = [...now.entries, ...(range === '30d' ? (prev?.entries ?? []) : [])].filter((e) => (e.own_usd ?? 0) > 0);
|
|
113
|
+
if (!always && !all.length)
|
|
114
|
+
return null;
|
|
115
|
+
const days = OWN_RANGES.find((r) => r.id === range)?.days ?? 7;
|
|
116
|
+
const since = Date.now() - days * 86_400_000;
|
|
117
|
+
const seen = new Set();
|
|
118
|
+
const sessions = all
|
|
119
|
+
.filter((e) => e.at >= since && !seen.has(e.id) && seen.add(e.id))
|
|
120
|
+
.sort((a, b) => b.at - a.at);
|
|
121
|
+
const total = sessions.reduce((a, e) => a + (e.own_usd ?? 0), 0);
|
|
122
|
+
return (_jsxs("div", { "data-slot": "setting-row", className: "flex flex-col gap-2 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-4", children: [_jsx(Label, { className: "min-w-0 flex-1", children: _jsx(InfoLabel, { info: "Each chat and run your own key or plan paid for, by its last use.", children: "Spent on your keys" }) }), _jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, value: range, "aria-label": "Range", onValueChange: (v) => v && setRange(v), children: OWN_RANGES.map((r) => (_jsx(ToggleGroupItem, { value: r.id, "aria-label": `Last ${r.id}`, children: r.id }, r.id))) })] }), sessions.length ? (_jsx(DetailsTable, { head: [
|
|
123
|
+
{ label: 'Session', grow: true },
|
|
124
|
+
{ label: 'Date', hideOnPhone: true },
|
|
125
|
+
{ label: 'Amount', right: true },
|
|
126
|
+
], rows: sessions.map((e) => ({
|
|
127
|
+
key: e.id,
|
|
128
|
+
cells: [
|
|
129
|
+
_jsx("span", { className: "block truncate", title: e.title, children: e.title }, "n"),
|
|
130
|
+
_jsx("time", { dateTime: new Date(e.at).toISOString(), title: new Date(e.at).toLocaleString(), children: when(e.at) }, "t"),
|
|
131
|
+
usd(e.own_usd ?? 0),
|
|
132
|
+
],
|
|
133
|
+
})), foot: ['Total', _jsx("span", { className: "max-md:hidden" }, "d"), usd(total)] })) : (_jsxs("p", { className: "text-muted-foreground text-sm", children: ["Nothing spent on your keys in the last ", range, "."] }))] }));
|
|
134
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.236",
|
|
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.236",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|