@columenlabs/lumen-ui 0.1.83 → 0.1.85

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,4 +1,4 @@
1
- import type { AssistantChat, AssistantChatRow, AssistantEvent, AssistantMessage, AssistantProvider, AssistantSettingsPatch, AssistantStatus, DraftItem, KeyState, StepItem } from '@columenlabs/lumen-ui-types/assistant';
1
+ import type { AllowanceRequest, AssistantChat, AssistantChatRow, AssistantEvent, AssistantMessage, AssistantProvider, AssistantSettingsPatch, AssistantStatus, DraftItem, KeyState, StepItem } from '@columenlabs/lumen-ui-types/assistant';
2
2
  type Test = NonNullable<KeyState['test']>;
3
3
  export interface AssistantAdapter {
4
4
  /** Names the cache its reads keep (two assistants on one page need two keys). */
@@ -36,6 +36,12 @@ export interface AssistantAdapter {
36
36
  settleDraft(chatId: string, draftId: string, action: 'save' | 'dismiss'): Promise<DraftItem>;
37
37
  /** Takes back a change the assistant made at once. */
38
38
  undoWrite(chatId: string, stepId: string): Promise<StepItem>;
39
+ /** Asks for more allowance on the shared keys (an app with `allowance`). */
40
+ requestMore?(amount: number, why: string): Promise<AssistantStatus>;
41
+ /** Approvers: every request, newest first. */
42
+ allowanceRequests?(): Promise<AllowanceRequest[]>;
43
+ /** Approvers: grants (the asked amount unless `amount` says) or declines a request. */
44
+ decideRequest?(id: string, action: 'grant' | 'decline', amount?: number): Promise<AllowanceRequest>;
39
45
  }
40
46
  /** The adapter over `assistantRoutes` mounted at `base` (`/api/assistant`). */
41
47
  export declare function assistantClient(base: string): AssistantAdapter;
@@ -63,6 +63,12 @@ export function assistantClient(base) {
63
63
  action,
64
64
  }),
65
65
  undoWrite: (chatId, stepId) => send('POST', `/chats/${encodeURIComponent(chatId)}/writes/${encodeURIComponent(stepId)}/undo`),
66
+ requestMore: (amount, why) => send('POST', '/allowance/request', { amount, why }),
67
+ allowanceRequests: () => get('/allowance/requests'),
68
+ decideRequest: (id, action, amount) => send('POST', `/allowance/requests/${encodeURIComponent(id)}`, {
69
+ action,
70
+ ...(amount !== undefined ? { amount } : {}),
71
+ }),
66
72
  async send(msg, onEvent, signal) {
67
73
  const url = `${root}/chat`;
68
74
  const res = await fetch(url, {
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, AssistantRefNote, AssistantStatus } from '@columenlabs/lumen-ui-types/assistant';
1
+ import type { AllowanceUnit, AssistantMessage, AssistantRefNote, AssistantStatus } from '@columenlabs/lumen-ui-types/assistant';
2
2
  import { type LumenOption } from '../lumen-selector.js';
3
3
  import type { AssistantAdapter } from './client.js';
4
4
  import { type AssistantSource } from './sources.js';
@@ -42,6 +42,8 @@ export interface AssistantCommand {
42
42
  }
43
43
  /** The assistant's chat: header, messages and composer. */
44
44
  export declare function LumenAssistant({ client, context, onSettings, keepDays, onClose, chat: outside, commands, sources, }: LumenAssistantProps): import("react").JSX.Element;
45
+ /** An allowance in its unit: `$4.20`, or `350 credits`. */
46
+ export declare const allowanceAmount: (u: AllowanceUnit, n: number) => string;
45
47
  /** What the composer sends with the text: the tier picked with `@` and the things pointed at with `#`. */
46
48
  export interface ComposerExtra {
47
49
  tier?: string;
@@ -9,6 +9,7 @@ import { errorText } from '../integrations.js';
9
9
  import { cn } from '../lib/utils.js';
10
10
  import { LumenSelector, LumenSelectorList, } from '../lumen-selector.js';
11
11
  import { lumenToast } from '../lumen-toast.js';
12
+ import { useOpenSettings } from '../shell.js';
12
13
  import { cardsOnScreen, kindName, pointAt, sectionsOnScreen, targetEl, textOf, } from '../spotlight.js';
13
14
  import { Alert, AlertDescription } from '../ui/alert.js';
14
15
  import { Button } from '../ui/button.js';
@@ -148,7 +149,18 @@ function Messages({ chat, status, onSettings, }) {
148
149
  useEffect(() => {
149
150
  end.current?.scrollIntoView({ block: 'end' });
150
151
  }, [count]);
151
- return (_jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 py-3", children: [status?.wait && (_jsxs(Alert, { className: "mb-3", children: [_jsx(CircleAlertIcon, {}), _jsxs(AlertDescription, { className: "flex flex-col items-start gap-2", children: [WAIT[status.wait](status.admin, status.providers.find((p) => p.id === status.wait_provider)?.label ?? 'API'), onSettings && (_jsx(Button, { size: "sm", variant: "outline", onClick: onSettings, children: "Open Settings" }))] })] })), chat.loading ? (_jsxs("p", { className: "flex items-center gap-2 text-muted-foreground text-sm", children: [_jsx(Spinner, {}), " Loading the chat\u2026"] })) : chat.items.length === 0 && !status?.wait ? (_jsxs("p", { className: "pt-8 text-center text-muted-foreground text-sm", children: ["Ask ", status?.name ?? 'the assistant', " anything."] })) : (_jsx(TierNames.Provider, { value: (id) => status?.tiers.find((t) => t.id === id)?.label ?? id, children: _jsx(UndoWrite.Provider, { value: chat.undo, children: _jsx(Items, { items: chat.items, onSettle: chat.settle, busy: chat.busy, turns: true }) }) })), chat.busy && !chat.items.some(isLive) && (_jsxs("p", { className: "flex items-center gap-2 py-1 text-muted-foreground text-sm", children: [_jsx(Spinner, {}), " Thinking\u2026"] })), _jsx("div", { ref: end })] }));
152
+ // The app's own link to Settings, else the shell's, at the section that answers the wait.
153
+ const shellSettings = useOpenSettings();
154
+ const settings = onSettings ? (_section) => onSettings() : shellSettings;
155
+ return (_jsxs("div", { className: "min-h-0 flex-1 overflow-y-auto px-3 py-3", children: [status?.wait && (_jsxs(Alert, { className: "mb-3", children: [_jsx(CircleAlertIcon, {}), _jsxs(AlertDescription, { className: "flex flex-col items-start gap-2", children: [status.wait === 'app_cap' && status.allowance
156
+ ? allowanceWait(status.allowance)
157
+ : WAIT[status.wait](status.admin, status.providers.find((p) => p.id === status.wait_provider)?.label ?? 'API'), settings && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => settings(status.wait === 'app_cap' && status.allowance
158
+ ? 'assistant-allowance'
159
+ : 'assistant-providers'), children: status.wait === 'app_cap' &&
160
+ status.allowance &&
161
+ status.allowance.request?.status !== 'open'
162
+ ? 'Request more'
163
+ : 'Open Settings' }))] })] })), chat.loading ? (_jsxs("p", { className: "flex items-center gap-2 text-muted-foreground text-sm", children: [_jsx(Spinner, {}), " Loading the chat\u2026"] })) : chat.items.length === 0 && !status?.wait ? (_jsxs("p", { className: "pt-8 text-center text-muted-foreground text-sm", children: ["Ask ", status?.name ?? 'the assistant', " anything."] })) : (_jsx(TierNames.Provider, { value: (id) => status?.tiers.find((t) => t.id === id)?.label ?? id, children: _jsx(UndoWrite.Provider, { value: chat.undo, children: _jsx(Items, { items: chat.items, onSettle: chat.settle, busy: chat.busy, turns: true }) }) })), chat.busy && !chat.items.some(isLive) && (_jsxs("p", { className: "flex items-center gap-2 py-1 text-muted-foreground text-sm", children: [_jsx(Spinner, {}), " Thinking\u2026"] })), _jsx("div", { ref: end })] }));
152
164
  }
153
165
  const isLive = (i) => !!i.live || (i.kind === 'handoff' && i.items.some(isLive));
154
166
  const WAIT = {
@@ -158,6 +170,17 @@ const WAIT = {
158
170
  app_cap: () => "You reached this month's cap on the shared keys. Add your own key, or wait for next month.",
159
171
  own_cap: () => 'Your own keys reached their monthly cap. Raise it in Settings ▸ Model providers.',
160
172
  };
173
+ /** An allowance in its unit: `$4.20`, or `350 credits`. */
174
+ export const allowanceAmount = (u, n) => u.usd === 1 && u.many === 'dollars'
175
+ ? usd(n)
176
+ : `${n.toLocaleString()} ${n === 1 ? u.one : u.many}`;
177
+ function allowanceWait(a) {
178
+ if (a.request?.status === 'open')
179
+ return `You used your allowance on the shared keys. Your request for ${allowanceAmount(a.unit, a.request.amount)} is waiting, or add your own key.`;
180
+ return a.period === 'once'
181
+ ? 'You used your allowance on the shared keys. Request more, or add your own key.'
182
+ : "You used this month's allowance on the shared keys. Request more, or add your own key.";
183
+ }
161
184
  /** The assistant's name, for a copied turn's headings. */
162
185
  const AssistantName = createContext('Assistant');
163
186
  /** A tier's name for a message's `@` chip. */
@@ -4,17 +4,21 @@ import type { AssistantAdapter } from './client.js';
4
4
  * The admin's section: the app's shared keys and each person's monthly allowance on them. Put it
5
5
  * where the app keeps its admin settings, shown only to admins.
6
6
  */
7
- export declare function assistantAdminSections({ client, }: {
7
+ export declare function assistantAdminSections({ client, allowance, }: {
8
8
  client: AssistantAdapter;
9
+ /** Add Allowance requests (the app set `allowance` on the server). */
10
+ allowance?: boolean;
9
11
  }): SettingsSectionDef[];
10
12
  /**
11
13
  * The assistant's Settings sections: the assistant, then Model providers, Models, Tools, Web and
12
14
  * Spend under it. Pass them to LumenShell's `shared` (or an app's own `settings`).
13
15
  */
14
- export declare function assistantSettingsSections({ client, name, web, }: {
16
+ export declare function assistantSettingsSections({ client, name, web, allowance, }: {
15
17
  client: AssistantAdapter;
16
18
  /** The assistant's name, the parent section's title. */
17
19
  name: string;
18
20
  /** Show the Web section (the app set up `web` on the server). */
19
21
  web?: boolean;
22
+ /** Show Allowance: what is left on the shared keys, and Request more (`allowance` on the server). */
23
+ allowance?: boolean;
20
24
  }): SettingsSectionDef[];
@@ -1,6 +1,6 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useQueryClient } from '@tanstack/react-query';
3
- import { CheckIcon, ExternalLinkIcon, GlobeIcon, KeyRoundIcon, LayersIcon, ReceiptIcon, SparklesIcon, Trash2Icon, WrenchIcon, } from 'lucide-react';
2
+ import { useQuery, useQueryClient } from '@tanstack/react-query';
3
+ import { CheckIcon, CoinsIcon, ExternalLinkIcon, GlobeIcon, InboxIcon, KeyRoundIcon, LayersIcon, ReceiptIcon, SparklesIcon, Trash2Icon, WrenchIcon, } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
5
  import { LumenChatgptSignin } from '../chatgpt-signin.js';
6
6
  import { LumenExpandList, LumenExpandRow } from '../expand-list.js';
@@ -10,12 +10,14 @@ import { InfoLabel } from '../info-tip.js';
10
10
  import { KeyTestLine, TestBadge, errorText } from '../integrations.js';
11
11
  import { LumenSelector } from '../lumen-selector.js';
12
12
  import { lumenToast } from '../lumen-toast.js';
13
+ import { ShareMeter } from '../share-meter.js';
13
14
  import { Badge } from '../ui/badge.js';
15
+ import { Button } from '../ui/button.js';
14
16
  import { Input } from '../ui/input.js';
15
17
  import { Label } from '../ui/label.js';
16
18
  import { Switch } from '../ui/switch.js';
17
19
  import { ToggleGroup, ToggleGroupItem } from '../ui/toggle-group.js';
18
- import { assistantStatusKey, usd, useAssistantStatus } from './lumen-assistant.js';
20
+ import { allowanceAmount, assistantStatusKey, usd, useAssistantStatus } from './lumen-assistant.js';
19
21
  const EFFORTS = [
20
22
  { value: 'default', label: 'Model default' },
21
23
  { value: 'low', label: 'Low' },
@@ -158,7 +160,7 @@ function SharedProviderKey({ client, p }) {
158
160
  /** Admins only: the app's shared keys and what each person may spend on them. */
159
161
  function SharedKeys({ client }) {
160
162
  const { apply } = useApply(client);
161
- return (_jsx(WithStatus, { client: client, children: (s) => s.admin ? (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: s.providers.map((p) => (_jsx(SharedProviderKey, { client: client, p: p }, p.id))) }), _jsx(Row, { label: "Each person may spend", info: "A month on the shared keys, every provider together. Past it, a person's own key pays.", children: _jsx(CapInput, { label: "Allowance per person", value: s.caps.app, onSave: (n) => void apply('cap', () => client.appCap(n)) }) })] })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "Only an admin can change these." })) }));
163
+ return (_jsx(WithStatus, { client: client, children: (s) => s.admin ? (_jsxs("div", { className: "flex flex-col", children: [_jsx(LumenExpandList, { children: s.providers.map((p) => (_jsx(SharedProviderKey, { client: client, p: p }, p.id))) }), s.allowance?.period !== 'once' && (_jsx(Row, { label: "Each person may spend", info: "A month on the shared keys, every provider together. Past it, a person's own key pays.", children: _jsx(CapInput, { label: "Allowance per person", value: s.caps.app, onSave: (n) => void apply('cap', () => client.appCap(n)) }) }))] })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "Only an admin can change these." })) }));
162
164
  }
163
165
  // ---------------------------------------------------------------- models
164
166
  function Models({ client }) {
@@ -225,11 +227,97 @@ function Spend({ client }) {
225
227
  ` · ${usd(sp.cap)} cap on ${sp.payer === 'app' ? 'the app key' : 'your key'}`] })] }), sp.rows.length ? (_jsx("div", { className: "flex flex-col text-sm", children: sp.rows.map((r) => (_jsxs("div", { className: "flex items-center gap-2 border-b py-2 last:border-b-0", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: r.label }), _jsx("span", { className: "text-muted-foreground text-xs", children: r.payer === 'app' ? 'Shared key' : 'Your key' }), _jsxs("span", { className: "w-16 text-right text-muted-foreground text-xs tabular-nums", children: [r.calls, " calls"] }), _jsx("span", { className: "w-16 text-right tabular-nums", children: usd(r.usd) })] }, `${r.label}:${r.payer}`))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: "Nothing spent this month." }))] }));
226
228
  } }));
227
229
  }
230
+ // ---------------------------------------------------------------- allowance
231
+ const day = (at) => new Date(at).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
232
+ function RequestBadge({ r }) {
233
+ if (r.status === 'open')
234
+ return _jsx(Badge, { variant: "outline", children: "Waiting" });
235
+ if (r.status === 'granted')
236
+ return _jsx(Badge, { variant: "secondary", children: "Granted" });
237
+ return _jsx(Badge, { variant: "outline", children: "Declined" });
238
+ }
239
+ /** A person's allowance on the shared keys, their newest request and Request more. */
240
+ function Allowance({ client }) {
241
+ const { apply, busy } = useApply(client);
242
+ const [amount, setAmount] = useState('');
243
+ const [why, setWhy] = useState('');
244
+ const whyId = useId();
245
+ return (_jsx(WithStatus, { client: client, children: (s) => {
246
+ const a = s.allowance;
247
+ const requestMore = client.requestMore;
248
+ if (!a || !requestMore)
249
+ return _jsx("p", { className: "text-muted-foreground text-sm", children: "This app sets no allowance." });
250
+ const n = (x) => allowanceAmount(a.unit, x);
251
+ const r = a.request;
252
+ const pick = amount || String(a.amounts[0] ?? '');
253
+ const issues = [
254
+ ...(pick ? [] : [{ where: 'Amount', message: 'Pick how much to ask for' }]),
255
+ ...(why.trim()
256
+ ? []
257
+ : [{ where: 'What for', message: 'Say what it is for', target: whyId }]),
258
+ ];
259
+ const ask = () => void apply('request', () => requestMore(Number(pick), why.trim()), 'Request sent').then((ok) => ok && setWhy(''));
260
+ return (_jsxs("div", { className: "flex flex-col", children: [_jsxs("div", { className: "flex flex-col gap-2 border-b pb-3", children: [_jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "font-semibold text-2xl tabular-nums", children: n(a.left) }), _jsxs("span", { className: "text-muted-foreground text-sm", children: ["left of ", n(a.total), a.period === 'month' ? ' this month' : ''] })] }), _jsx(ShareMeter, { value: a.total > 0 ? a.used / a.total : 1, wide: true })] }), r && (_jsx(Row, { label: `Asked for ${n(r.amount)} · ${day(r.at)}`, info: r.why || undefined, children: _jsxs("div", { className: "flex items-center gap-2 text-sm", children: [r.status === 'granted' && r.granted !== undefined && (_jsxs("span", { className: "tabular-nums", children: ["+", n(r.granted)] })), _jsx(RequestBadge, { r: r })] }) })), r?.status === 'open' ? (_jsx("p", { className: "pt-3 text-muted-foreground text-sm", children: "You can ask again once this one is decided." })) : (_jsxs("div", { className: "flex flex-col gap-2 pt-3", children: [_jsx(Input, { id: whyId, "aria-label": "What for", placeholder: "What it is for", className: "h-8 w-full", maxLength: 500, value: why, onChange: (e) => setWhy(e.target.value) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(LumenSelector, { label: "Amount", size: "sm", className: "w-40", value: pick, options: a.amounts.map((x) => ({ value: String(x), label: n(x) })), onChange: (v) => setAmount(v) }), _jsx(SaveButton, { issues: issues, label: "Request more", icon: null, busy: busy === 'request', onSave: ask })] })] }))] }));
261
+ } }));
262
+ }
263
+ /** One request, opened in place to grant (any amount) or decline. */
264
+ function RequestRow({ client, r, n, onDone, }) {
265
+ const [text, setText] = useState(String(r.amount));
266
+ const [busy, setBusy] = useState(null);
267
+ const amountId = useId();
268
+ const value = Number(text);
269
+ const decide = async (action) => {
270
+ if (!client.decideRequest)
271
+ return;
272
+ setBusy(action);
273
+ try {
274
+ await client.decideRequest(r.id, action, action === 'grant' ? value : undefined);
275
+ lumenToast.success(action === 'grant' ? `Granted ${n(value)}` : 'Declined');
276
+ onDone();
277
+ }
278
+ catch (e) {
279
+ lumenToast.error(errorText(e));
280
+ }
281
+ finally {
282
+ setBusy(null);
283
+ }
284
+ };
285
+ const issues = Number.isFinite(value) && value > 0
286
+ ? []
287
+ : [{ where: 'Grant', message: 'Grant more than 0', target: amountId }];
288
+ return (_jsx(LumenExpandRow, { id: r.id, name: r.who, meta: `${n(r.amount)} · ${day(r.at)}`, badge: _jsx(RequestBadge, { r: r }), children: _jsxs("div", { className: "flex flex-col gap-3 text-sm", children: [_jsx("p", { className: "whitespace-pre-wrap", children: r.why || 'No reason given.' }), r.status === 'open' ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { id: amountId, "aria-label": "Grant", inputMode: "decimal", className: "h-8 w-24 text-right tabular-nums", value: text, onChange: (e) => setText(e.target.value) }), _jsx(SaveButton, { issues: issues, label: "Grant", busy: busy === 'grant', onSave: () => void decide('grant') }), _jsx(Button, { size: "sm", variant: "outline", disabled: !!busy, onClick: () => void decide('decline'), children: "Decline" })] })) : (_jsxs("p", { className: "text-muted-foreground", children: [r.status === 'granted' ? `Granted ${n(r.granted ?? r.amount)}` : 'Declined', r.decided_by ? ` by ${r.decided_by}` : '', r.decided_at ? ` · ${day(r.decided_at)}` : ''] }))] }) }));
289
+ }
290
+ /** Approvers: every request for more allowance, open ones first. */
291
+ function AllowanceRequests({ client }) {
292
+ const qc = useQueryClient();
293
+ const key = [...assistantStatusKey(client), 'allowance-requests'];
294
+ const q = useQuery({
295
+ queryKey: key,
296
+ queryFn: () => client.allowanceRequests?.() ?? Promise.resolve([]),
297
+ enabled: !!client.allowanceRequests,
298
+ });
299
+ return (_jsx(WithStatus, { client: client, children: (s) => {
300
+ const a = s.allowance;
301
+ if (!a || !client.allowanceRequests)
302
+ return _jsx("p", { className: "text-muted-foreground text-sm", children: "This app sets no allowance." });
303
+ if (!a.approver)
304
+ return _jsx("p", { className: "text-muted-foreground text-sm", children: "Only an approver can see these." });
305
+ if (!q.data)
306
+ return (_jsx("p", { className: "text-muted-foreground text-sm", children: q.isError ? errorText(q.error) : 'Loading…' }));
307
+ if (!q.data.length)
308
+ return _jsx("p", { className: "text-muted-foreground text-sm", children: "No requests yet." });
309
+ const rows = [...q.data].sort((x, y) => Number(y.status === 'open') - Number(x.status === 'open') || y.at - x.at);
310
+ const done = () => {
311
+ void qc.invalidateQueries({ queryKey: assistantStatusKey(client) });
312
+ };
313
+ return (_jsx(LumenExpandList, { children: rows.map((r) => (_jsx(RequestRow, { client: client, r: r, n: (x) => allowanceAmount(a.unit, x), onDone: done }, r.id))) }));
314
+ } }));
315
+ }
228
316
  /**
229
317
  * The admin's section: the app's shared keys and each person's monthly allowance on them. Put it
230
318
  * where the app keeps its admin settings, shown only to admins.
231
319
  */
232
- export function assistantAdminSections({ client, }) {
320
+ export function assistantAdminSections({ client, allowance = false, }) {
233
321
  return [
234
322
  {
235
323
  id: 'assistant-shared-keys',
@@ -238,13 +326,24 @@ export function assistantAdminSections({ client, }) {
238
326
  description: "Keys everyone's answers run on, and the allowance",
239
327
  content: _jsx(SharedKeys, { client: client }),
240
328
  },
329
+ ...(allowance
330
+ ? [
331
+ {
332
+ id: 'assistant-allowance-requests',
333
+ title: 'Allowance requests',
334
+ icon: InboxIcon,
335
+ description: 'Grant or decline asks for more allowance',
336
+ content: _jsx(AllowanceRequests, { client: client }),
337
+ },
338
+ ]
339
+ : []),
241
340
  ];
242
341
  }
243
342
  /**
244
343
  * The assistant's Settings sections: the assistant, then Model providers, Models, Tools, Web and
245
344
  * Spend under it. Pass them to LumenShell's `shared` (or an app's own `settings`).
246
345
  */
247
- export function assistantSettingsSections({ client, name, web = true, }) {
346
+ export function assistantSettingsSections({ client, name, web = true, allowance = false, }) {
248
347
  return [
249
348
  {
250
349
  id: 'assistant',
@@ -297,5 +396,17 @@ export function assistantSettingsSections({ client, name, web = true, }) {
297
396
  description: 'What answers cost this month',
298
397
  content: _jsx(Spend, { client: client }),
299
398
  },
399
+ ...(allowance
400
+ ? [
401
+ {
402
+ id: 'assistant-allowance',
403
+ title: 'Allowance',
404
+ icon: CoinsIcon,
405
+ sub: true,
406
+ description: 'What is left on the shared keys, and Request more',
407
+ content: _jsx(Allowance, { client: client }),
408
+ },
409
+ ]
410
+ : []),
300
411
  ];
301
412
  }
package/dist/shell.d.ts CHANGED
@@ -21,6 +21,8 @@ interface ShellContext {
21
21
  headerActions: ReactNode;
22
22
  }
23
23
  export declare function useShell(): ShellContext;
24
+ /** Opens Settings at a section, or null outside a shell (a preview). */
25
+ export declare function useOpenSettings(): ShellContext['openSettings'] | null;
24
26
  /** The app this page renders in, or null outside a shell (a preview). */
25
27
  export declare function useApp(): {
26
28
  id: string;
package/dist/shell.js CHANGED
@@ -27,6 +27,10 @@ export function useShell() {
27
27
  throw new Error('useShell must be used inside <LumenShell>.');
28
28
  return c;
29
29
  }
30
+ /** Opens Settings at a section, or null outside a shell (a preview). */
31
+ export function useOpenSettings() {
32
+ return useContext(Ctx)?.openSettings ?? null;
33
+ }
30
34
  /** The app this page renders in, or null outside a shell (a preview). */
31
35
  export function useApp() {
32
36
  const c = useContext(Ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.83",
3
+ "version": "0.1.85",
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.83",
52
+ "@columenlabs/lumen-ui-types": "0.1.85",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",