@columenlabs/lumen-ui 0.1.82 → 0.1.83

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.
@@ -2,6 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { useQueryClient } from '@tanstack/react-query';
3
3
  import { CheckIcon, ExternalLinkIcon, GlobeIcon, KeyRoundIcon, LayersIcon, ReceiptIcon, SparklesIcon, Trash2Icon, WrenchIcon, } from 'lucide-react';
4
4
  import { useId, useState } from 'react';
5
+ import { LumenChatgptSignin } from '../chatgpt-signin.js';
5
6
  import { LumenExpandList, LumenExpandRow } from '../expand-list.js';
6
7
  import { SaveButton } from '../form-issues.js';
7
8
  import { IconButton } from '../icon-button.js';
@@ -128,50 +129,10 @@ function CapInput({ label, value, onSave, }) {
128
129
  };
129
130
  return (_jsxs("div", { className: "flex items-center gap-1 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "$" }), _jsx(Input, { "aria-label": label, inputMode: "decimal", className: "h-8 w-20 text-right tabular-nums", value: text, onChange: (e) => setText(e.target.value), onBlur: commit, onKeyDown: (e) => e.key === 'Enter' && commit() }), _jsx("span", { className: "text-muted-foreground", children: "/ month" })] }));
130
131
  }
131
- /** A person's ChatGPT plan sign-in: opens in a new tab, finished by pasting back where it ended. */
132
+ /** A person's ChatGPT plan sign-in, on the assistant's own routes. */
132
133
  function ChatgptSignin({ client, p }) {
133
134
  const { apply, refresh } = useApply(client);
134
- const id = useId();
135
- const [started, setStarted] = useState(null);
136
- const [pasted, setPasted] = useState('');
137
- const [busy, setBusy] = useState(false);
138
- const signedIn = p.own.via === 'chatgpt';
139
- const start = async () => {
140
- // Open the tab inside the click, or the browser blocks it; point it once the address is known.
141
- const tab = window.open('about:blank', '_blank');
142
- if (tab)
143
- tab.opener = null;
144
- try {
145
- const { url, redirect } = await client.startSignin(p.id);
146
- if (tab)
147
- tab.location.href = url;
148
- else
149
- window.open(url, '_blank', 'noopener');
150
- setStarted(redirect);
151
- }
152
- catch (e) {
153
- tab?.close();
154
- lumenToast.error(errorText(e));
155
- }
156
- };
157
- const finish = async () => {
158
- setBusy(true);
159
- if (await apply(`signin:${p.id}`, () => client.finishSignin(p.id, pasted.trim()), 'Signed in')) {
160
- setPasted('');
161
- setStarted(null);
162
- }
163
- setBusy(false);
164
- };
165
- const issues = [];
166
- if (pasted.trim() && started && !pasted.trim().startsWith(started.split('/auth')[0] ?? ''))
167
- issues.push({ where: 'Address', message: `It starts with ${started}`, target: id });
168
- if (signedIn)
169
- return (_jsxs("div", { className: "flex flex-col gap-3 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex-1 font-medium text-sm", children: "Your ChatGPT plan" }), _jsx("span", { className: "text-muted-foreground text-xs", children: p.own.hint }), p.own.test && _jsx(TestBadge, { test: p.own.test })] }), _jsx(KeyTestLine, { test: testOf(p.own), run: () => client.testOwnKey(p.id).finally(refresh) }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(IconButton, { icon: Trash2Icon, label: "Sign out of ChatGPT", variant: "outline", onClick: () => void apply(`own:${p.id}`, () => client.ownKey(p.id, null), 'Signed out') }), _jsx(SaveButton, { issues: [], icon: KeyRoundIcon, label: "Sign in again", onSave: start })] }), started && (_jsx(SigninPaste, { id: id, redirect: started, pasted: pasted, setPasted: setPasted, issues: issues, busy: busy, finish: finish }))] }));
170
- return (_jsxs("div", { className: "flex flex-col gap-3 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoLabel, { className: "flex-1 font-medium text-sm", info: "Run OpenAI models on your own ChatGPT plan instead of an API key.", children: "Your ChatGPT plan" }), _jsx(Badge, { variant: "outline", children: "Not signed in" })] }), _jsx("div", { className: "flex justify-end", children: _jsx(SaveButton, { issues: [], icon: ExternalLinkIcon, label: started ? 'Open the sign-in again' : 'Sign in with ChatGPT', onSave: start }) }), started && (_jsx(SigninPaste, { id: id, redirect: started, pasted: pasted, setPasted: setPasted, issues: issues, busy: busy, finish: finish }))] }));
171
- }
172
- /** Where the sign-in tab ends, and the box its address is pasted into. */
173
- function SigninPaste({ id, redirect, pasted, setPasted, issues, busy, finish, }) {
174
- return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: id, children: _jsx(InfoLabel, { info: `The sign-in ends on ${redirect}, a page that does not load. That is expected: copy the whole address from that tab's address bar.`, children: "Paste the address the sign-in ended on" }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { id: id, autoComplete: "off", placeholder: `${redirect}?code=…`, className: "font-code text-xs", value: pasted, onChange: (e) => setPasted(e.target.value) }), _jsx(SaveButton, { issues: issues, busy: busy, disabled: !pasted.trim(), label: "Finish", onSave: finish })] })] }));
135
+ return (_jsx(LumenChatgptSignin, { state: p.own, start: () => client.startSignin(p.id), finish: (url) => apply(`signin:${p.id}`, () => client.finishSignin(p.id, url), 'Signed in'), signOut: () => apply(`own:${p.id}`, () => client.ownKey(p.id, null), 'Signed out'), test: () => client.testOwnKey(p.id).finally(refresh) }));
175
136
  }
176
137
  const ready = (_jsxs(Badge, { variant: "outline", className: "text-gain", children: [_jsx(CheckIcon, {}), "Ready"] }));
177
138
  const notSet = _jsx(Badge, { variant: "outline", children: "Not set" });
@@ -0,0 +1,18 @@
1
+ import type { KeyState } from '@columenlabs/lumen-ui-types/assistant';
2
+ export interface LumenChatgptSigninProps {
3
+ /** The person's key state: signed in when `via` is `chatgpt`, `hint` the account's email. */
4
+ state: KeyState;
5
+ /** Starts a sign-in on the server: the address to open and the one it ends on. */
6
+ start: () => Promise<{
7
+ url: string;
8
+ redirect: string;
9
+ }>;
10
+ /** Finishes it from the pasted address; true once kept (the app says why when it is not). */
11
+ finish: (url: string) => Promise<boolean>;
12
+ /** Signs out (drops the sealed tokens). */
13
+ signOut: () => Promise<unknown>;
14
+ /** One free call that says whether the sign-in still works; the app refreshes its data after. */
15
+ test: () => Promise<unknown>;
16
+ }
17
+ /** Sign in with ChatGPT: opens in a new tab, finished by pasting back where it ended. */
18
+ export declare function LumenChatgptSignin({ state, start, finish, signOut, test, }: LumenChatgptSigninProps): import("react").JSX.Element;
@@ -0,0 +1,64 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ExternalLinkIcon, KeyRoundIcon, Trash2Icon } from 'lucide-react';
3
+ import { useId, useState } from 'react';
4
+ import { SaveButton } from './form-issues.js';
5
+ import { IconButton } from './icon-button.js';
6
+ import { InfoLabel } from './info-tip.js';
7
+ import { KeyTestLine, TestBadge, errorText } from './integrations.js';
8
+ import { lumenToast } from './lumen-toast.js';
9
+ import { Badge } from './ui/badge.js';
10
+ import { Input } from './ui/input.js';
11
+ import { Label } from './ui/label.js';
12
+ const testOf = (k) => k.test
13
+ ? {
14
+ at: new Date(k.test.at).toISOString(),
15
+ ok: k.test.ok,
16
+ detail: k.test.detail,
17
+ error: k.test.ok ? null : k.test.detail,
18
+ }
19
+ : null;
20
+ /** Sign in with ChatGPT: opens in a new tab, finished by pasting back where it ended. */
21
+ export function LumenChatgptSignin({ state, start, finish, signOut, test, }) {
22
+ const id = useId();
23
+ const [started, setStarted] = useState(null);
24
+ const [pasted, setPasted] = useState('');
25
+ const [busy, setBusy] = useState(false);
26
+ const signedIn = state.via === 'chatgpt';
27
+ const open = async () => {
28
+ // Open the tab inside the click, or the browser blocks it; point it once the address is known.
29
+ const tab = window.open('about:blank', '_blank');
30
+ if (tab)
31
+ tab.opener = null;
32
+ try {
33
+ const { url, redirect } = await start();
34
+ if (tab)
35
+ tab.location.href = url;
36
+ else
37
+ window.open(url, '_blank', 'noopener');
38
+ setStarted(redirect);
39
+ }
40
+ catch (e) {
41
+ tab?.close();
42
+ lumenToast.error(errorText(e));
43
+ }
44
+ };
45
+ const done = async () => {
46
+ setBusy(true);
47
+ if (await finish(pasted.trim())) {
48
+ setPasted('');
49
+ setStarted(null);
50
+ }
51
+ setBusy(false);
52
+ };
53
+ const issues = [];
54
+ if (pasted.trim() && started && !pasted.trim().startsWith(started.split('/auth')[0] ?? ''))
55
+ issues.push({ where: 'Address', message: `It starts with ${started}`, target: id });
56
+ const paste = started && (_jsx(SigninPaste, { id: id, redirect: started, pasted: pasted, setPasted: setPasted, issues: issues, busy: busy, finish: done }));
57
+ if (signedIn)
58
+ return (_jsxs("div", { className: "flex flex-col gap-3 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex-1 font-medium text-sm", children: "Your ChatGPT plan" }), _jsx("span", { className: "text-muted-foreground text-xs", children: state.hint }), state.test && _jsx(TestBadge, { test: state.test })] }), _jsx(KeyTestLine, { test: testOf(state), run: test }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(IconButton, { icon: Trash2Icon, label: "Sign out of ChatGPT", variant: "outline", onClick: () => void signOut() }), _jsx(SaveButton, { issues: [], icon: KeyRoundIcon, label: "Sign in again", onSave: open })] }), paste] }));
59
+ return (_jsxs("div", { className: "flex flex-col gap-3 border-b py-3 last:border-b-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InfoLabel, { className: "flex-1 font-medium text-sm", info: "Run OpenAI models on your own ChatGPT plan instead of an API key.", children: "Your ChatGPT plan" }), _jsx(Badge, { variant: "outline", children: "Not signed in" })] }), _jsx("div", { className: "flex justify-end", children: _jsx(SaveButton, { issues: [], icon: ExternalLinkIcon, label: started ? 'Open the sign-in again' : 'Sign in with ChatGPT', onSave: open }) }), paste] }));
60
+ }
61
+ /** Where the sign-in tab ends, and the box its address is pasted into. */
62
+ function SigninPaste({ id, redirect, pasted, setPasted, issues, busy, finish, }) {
63
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: id, children: _jsx(InfoLabel, { info: `The sign-in ends on ${redirect}, a page that does not load. That is expected: copy the whole address from that tab's address bar.`, children: "Paste the address the sign-in ended on" }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Input, { id: id, autoComplete: "off", placeholder: `${redirect}?code=…`, className: "font-code text-xs", value: pasted, onChange: (e) => setPasted(e.target.value) }), _jsx(SaveButton, { issues: issues, busy: busy, disabled: !pasted.trim(), label: "Finish", onSave: finish })] })] }));
64
+ }
package/dist/index.d.ts CHANGED
@@ -49,6 +49,7 @@ export { type Crumb, IconButton, type IconButtonProps, Page, PageBody, PageHeade
49
49
  export { DEFAULT_PREFS, deviceTimeZone, type Prefs, TIME_ZONES, type Theme, type TimeZoneId, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
50
50
  export { KIT, type KitConfig, configureLumen } from './kit-config.js';
51
51
  export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, type KeyTestResult, TestBadge, errorText, useClock, } from './integrations.js';
52
+ export { LumenChatgptSignin, type LumenChatgptSigninProps } from './chatgpt-signin.js';
52
53
  export { LumenExpandList, LumenExpandRow } from './expand-list.js';
53
54
  export { LumenPager } from './pager.js';
54
55
  export { createDeskQueryClient } from './query-client.js';
package/dist/index.js CHANGED
@@ -56,6 +56,7 @@ export { IconButton, Page, PageBody, PageHeader, } from './page.js';
56
56
  export { DEFAULT_PREFS, deviceTimeZone, TIME_ZONES, getPrefs, resolvedTheme, setPref, timeZoneLabel, usePrefs, ASSISTANT_WIDTH, } from './prefs.js';
57
57
  export { KIT, configureLumen } from './kit-config.js';
58
58
  export { ConnectionBlock, ErrorLine, IntegrationRow, KeyTestLine, TestBadge, errorText, useClock, } from './integrations.js';
59
+ export { LumenChatgptSignin } from './chatgpt-signin.js';
59
60
  export { LumenExpandList, LumenExpandRow } from './expand-list.js';
60
61
  export { LumenPager } from './pager.js';
61
62
  export { createDeskQueryClient } from './query-client.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@columenlabs/lumen-ui",
3
- "version": "0.1.82",
3
+ "version": "0.1.83",
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.82",
52
+ "@columenlabs/lumen-ui-types": "0.1.83",
53
53
  "@dnd-kit/core": "6.3.1",
54
54
  "@dnd-kit/sortable": "10.0.0",
55
55
  "@dnd-kit/utilities": "3.2.2",