@columenlabs/lumen-ui 0.1.77 → 0.1.79

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.
@@ -0,0 +1,42 @@
1
+ import type { AssistantChat, AssistantChatRow, AssistantEvent, AssistantMessage, AssistantProvider, AssistantSettingsPatch, AssistantStatus, DraftItem, KeyState, StepItem } from '@columenlabs/lumen-ui-types/assistant';
2
+ type Test = NonNullable<KeyState['test']>;
3
+ export interface AssistantAdapter {
4
+ /** Names the cache its reads keep (two assistants on one page need two keys). */
5
+ key: string;
6
+ status(): Promise<AssistantStatus>;
7
+ settings(patch: AssistantSettingsPatch): Promise<AssistantStatus>;
8
+ /** Admin: what each person may spend a month on the app's keys. */
9
+ appCap(usd: number): Promise<AssistantStatus>;
10
+ /** Admin: the app's key of a provider (tested before it is kept); null removes it. */
11
+ appKey(provider: AssistantProvider, key: string | null): Promise<AssistantStatus>;
12
+ testAppKey(provider: AssistantProvider): Promise<Test>;
13
+ /** The person's own key of a provider; null removes it. */
14
+ ownKey(provider: AssistantProvider, key: string | null): Promise<AssistantStatus>;
15
+ /** What the person may spend a month on their own keys. */
16
+ ownCap(usd: number): Promise<AssistantStatus>;
17
+ testOwnKey(provider: AssistantProvider): Promise<Test>;
18
+ /** Starts a plan sign-in (ChatGPT): the address to open and the one it ends on. */
19
+ startSignin(provider: AssistantProvider): Promise<{
20
+ url: string;
21
+ redirect: string;
22
+ }>;
23
+ /** Finishes it from the address the sign-in tab ended on. */
24
+ finishSignin(provider: AssistantProvider, url: string): Promise<AssistantStatus>;
25
+ /** Admin: ScrapeQL's Access service token; null removes it. */
26
+ scrapeql(token: {
27
+ client_id: string;
28
+ client_secret: string;
29
+ } | null): Promise<AssistantStatus>;
30
+ testScrapeql(): Promise<Test>;
31
+ chats(): Promise<AssistantChatRow[]>;
32
+ chat(id: string): Promise<AssistantChat>;
33
+ removeChat(id: string): Promise<void>;
34
+ /** One answer: `onEvent` hears each event as it streams; abort `signal` to stop it. */
35
+ send(msg: AssistantMessage, onEvent: (e: AssistantEvent) => void, signal?: AbortSignal): Promise<void>;
36
+ settleDraft(chatId: string, draftId: string, action: 'save' | 'dismiss'): Promise<DraftItem>;
37
+ /** Takes back a change the assistant made at once. */
38
+ undoWrite(chatId: string, stepId: string): Promise<StepItem>;
39
+ }
40
+ /** The adapter over `assistantRoutes` mounted at `base` (`/api/assistant`). */
41
+ export declare function assistantClient(base: string): AssistantAdapter;
42
+ export {};
@@ -0,0 +1,81 @@
1
+ import { ApiError, apiJson } from '../api-fetch.js';
2
+ /** Reads `event:` / `data:` blocks off a server-sent event stream. */
3
+ async function readEvents(body, on) {
4
+ const reader = body.getReader();
5
+ const dec = new TextDecoder();
6
+ let buf = '';
7
+ for (;;) {
8
+ const { done, value } = await reader.read();
9
+ if (done)
10
+ break;
11
+ buf += dec.decode(value, { stream: true });
12
+ let at = buf.indexOf('\n\n');
13
+ while (at >= 0) {
14
+ const block = buf.slice(0, at);
15
+ buf = buf.slice(at + 2);
16
+ let event = 'message';
17
+ let data = '';
18
+ for (const line of block.split('\n')) {
19
+ if (line.startsWith('event:'))
20
+ event = line.slice(6).trim();
21
+ else if (line.startsWith('data:'))
22
+ data += line.slice(5).trim();
23
+ }
24
+ if (data) {
25
+ try {
26
+ on({ event, data: JSON.parse(data) });
27
+ }
28
+ catch { }
29
+ }
30
+ at = buf.indexOf('\n\n');
31
+ }
32
+ }
33
+ }
34
+ /** The adapter over `assistantRoutes` mounted at `base` (`/api/assistant`). */
35
+ export function assistantClient(base) {
36
+ const root = base.replace(/\/+$/, '');
37
+ const get = (path) => apiJson(`${root}${path}`, {}, 'assistant');
38
+ const send = (method, path, body = {}) => apiJson(`${root}${path}`, { method, body: JSON.stringify(body) }, 'assistant');
39
+ return {
40
+ key: `assistant:${root}`,
41
+ status: () => get('/status'),
42
+ settings: (patch) => send('PUT', '/settings', patch),
43
+ appCap: (usd) => send('PUT', '/app', { cap_usd: usd }),
44
+ appKey: (p, key) => key
45
+ ? send('PUT', `/key?provider=${p}`, { api_key: key })
46
+ : send('DELETE', `/key?provider=${p}`),
47
+ testAppKey: (p) => send('POST', `/key/test?provider=${p}`),
48
+ ownKey: (p, key) => key
49
+ ? send('PUT', `/my-key?provider=${p}`, { api_key: key })
50
+ : send('DELETE', `/my-key?provider=${p}`),
51
+ ownCap: (usd) => send('PUT', '/my-key/cap', { cap_usd: usd }),
52
+ testOwnKey: (p) => send('POST', `/my-key/test?provider=${p}`),
53
+ startSignin: (p) => send('POST', `/my-signin?provider=${p}`),
54
+ finishSignin: (p, url) => send('POST', `/my-signin/finish?provider=${p}`, { url }),
55
+ scrapeql: (token) => token ? send('PUT', '/web/scrapeql', token) : send('DELETE', '/web/scrapeql'),
56
+ testScrapeql: () => send('POST', '/web/test'),
57
+ chats: () => get('/chats'),
58
+ chat: (id) => get(`/chats/${encodeURIComponent(id)}`),
59
+ removeChat: async (id) => {
60
+ await send('DELETE', `/chats/${encodeURIComponent(id)}`);
61
+ },
62
+ settleDraft: (chatId, draftId, action) => send('POST', `/chats/${encodeURIComponent(chatId)}/drafts/${encodeURIComponent(draftId)}`, {
63
+ action,
64
+ }),
65
+ undoWrite: (chatId, stepId) => send('POST', `/chats/${encodeURIComponent(chatId)}/writes/${encodeURIComponent(stepId)}/undo`),
66
+ async send(msg, onEvent, signal) {
67
+ const url = `${root}/chat`;
68
+ const res = await fetch(url, {
69
+ method: 'POST',
70
+ headers: { 'content-type': 'application/json', accept: 'text/event-stream' },
71
+ body: JSON.stringify(msg),
72
+ ...(signal ? { signal } : {}),
73
+ });
74
+ if (!res.ok || !res.body) {
75
+ const b = (await res.json().catch(() => ({})));
76
+ throw new ApiError(b.error ?? `The answer failed (${res.status})`, 'POST', url, res.status, b.code);
77
+ }
78
+ await readEvents(res.body, onEvent);
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,5 @@
1
+ export { type AssistantAdapter, assistantClient } from './client.js';
2
+ export { type AssistantCommand, type ComposerExtra, LumenAssistant, type LumenAssistantProps, LumenComposer, assistantStatusKey, useAssistantStatus, useLumenAssistantDock, } from './lumen-assistant.js';
3
+ export { assistantSettingsSections } from './settings.js';
4
+ export { type AssistantSource, type AssistantSourceItem, type ComposeRequest, type PageSelection, composeAssistant, selectionRef, useAssistantSource, usePageSelection, useShownAssistantSources, } from './sources.js';
5
+ export { type AssistantChatState, type LiveItem, applyEvent, useAssistantChat, } from './use-assistant-chat.js';
@@ -0,0 +1,8 @@
1
+ // `@columenlabs/lumen-ui/assistant`: an assistant's chat panel and Settings sections, over the
2
+ // routes of `@columenlabs/lumen-ai/assistant`; the shapes both share are
3
+ // `@columenlabs/lumen-ui-types/assistant`.
4
+ export { assistantClient } from './client.js';
5
+ export { LumenAssistant, LumenComposer, assistantStatusKey, useAssistantStatus, useLumenAssistantDock, } from './lumen-assistant.js';
6
+ export { assistantSettingsSections } from './settings.js';
7
+ export { composeAssistant, selectionRef, useAssistantSource, usePageSelection, useShownAssistantSources, } from './sources.js';
8
+ export { applyEvent, useAssistantChat, } from './use-assistant-chat.js';
@@ -0,0 +1,84 @@
1
+ import type { AssistantMessage, AssistantRefNote, AssistantStatus } from '@columenlabs/lumen-ui-types/assistant';
2
+ import { type LumenOption } from '../lumen-selector.js';
3
+ import type { AssistantAdapter } from './client.js';
4
+ import { type AssistantSource } from './sources.js';
5
+ import { type AssistantChatState } from './use-assistant-chat.js';
6
+ export declare const assistantStatusKey: (client: AssistantAdapter) => string[];
7
+ /** The person's assistant status, shared by the panel and Settings. */
8
+ export declare function useAssistantStatus(client: AssistantAdapter): import("@tanstack/react-query").UseQueryResult<AssistantStatus, Error>;
9
+ export declare const usd: (n: number) => string;
10
+ export interface LumenAssistantProps {
11
+ client: AssistantAdapter;
12
+ /** The page the person is on, sent with each message. */
13
+ context?: AssistantMessage['context'];
14
+ /** Opens the app's Settings at the assistant (from a "not connected" line). */
15
+ onSettings?: () => void;
16
+ /** Days chats are kept, for the Chats menu's line. */
17
+ keepDays?: number;
18
+ /** Closes the panel (its ✕). */
19
+ onClose?: () => void;
20
+ /** An outside chat state (the dock shares one between the button and the panel). */
21
+ chat?: AssistantChatState;
22
+ /**
23
+ * The app's own slash commands, listed before the server's (`createAssistant` `commands`) and the
24
+ * kit's: /compact, /clear, /model and /escalate.
25
+ */
26
+ commands?: AssistantCommand[];
27
+ /** What `#` lists everywhere; a page adds its own with `useAssistantSource`. */
28
+ sources?: readonly AssistantSource[];
29
+ }
30
+ /** A slash command: typing `/` in the composer lists them. */
31
+ export interface AssistantCommand {
32
+ /** Typed after the slash: `story`. */
33
+ name: string;
34
+ /** One line in the list. */
35
+ about: string;
36
+ /** What follows the name, shown in the list (`<words>`). Without it, picking it runs it. */
37
+ args?: string;
38
+ /** Choices for the words after it, listed as they are typed (a tier, a saved search). */
39
+ options?: () => LumenOption[];
40
+ /** Runs it: a string is sent as the message; nothing when the command acted itself. */
41
+ run: (args: string) => string | undefined | Promise<string | undefined>;
42
+ }
43
+ /** The assistant's chat: header, messages and composer. */
44
+ export declare function LumenAssistant({ client, context, onSettings, keepDays, onClose, chat: outside, commands, sources, }: LumenAssistantProps): import("react").JSX.Element;
45
+ /** What the composer sends with the text: the tier picked with `@` and the things pointed at with `#`. */
46
+ export interface ComposerExtra {
47
+ tier?: string;
48
+ refs?: AssistantRefNote[];
49
+ }
50
+ /**
51
+ * The message box: Enter sends, Shift+Enter breaks a line; Stop while an answer runs. Three
52
+ * triggers list what can follow them as it is typed: `/` at the start a command, `@` a tier for
53
+ * this message, `#` a thing to point at (the `sources`); a picked tier or thing shows as a chip.
54
+ */
55
+ export declare function LumenComposer({ name, busy, queued, disabled, commands, tiers, sources, onSend, onStop, }: {
56
+ name: string;
57
+ busy: boolean;
58
+ queued: string | null;
59
+ disabled?: boolean;
60
+ commands?: AssistantCommand[];
61
+ /** What `@` lists. */
62
+ tiers?: {
63
+ id: string;
64
+ label: string;
65
+ model_label?: string;
66
+ }[];
67
+ /** What `#` lists, a group each. */
68
+ sources?: readonly AssistantSource[];
69
+ onSend: (text: string, extra: ComposerExtra) => void;
70
+ onStop: () => void;
71
+ }): import("react").JSX.Element;
72
+ /**
73
+ * The assistant docked in LumenShell: put `button` in `headerActions` and `panel` in `panel`.
74
+ * Open or closed is this browser tab's own, and ⌘J toggles it.
75
+ */
76
+ export declare function useLumenAssistantDock(client: AssistantAdapter, o?: Omit<LumenAssistantProps, 'client' | 'chat' | 'onClose'> & {
77
+ name: string;
78
+ }): {
79
+ open: boolean;
80
+ setOpen: (v: boolean) => void;
81
+ chat: AssistantChatState;
82
+ button: import("react").JSX.Element;
83
+ panel: import("react").JSX.Element;
84
+ };