@columenlabs/lumen-ui 0.1.189 → 0.1.191
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.
- package/dist/assistant/copy.js +2 -0
- package/dist/assistant/lumen-assistant.js +77 -3
- package/dist/assistant/settings.js +4 -1
- package/dist/assistant/use-assistant-chat.d.ts +3 -1
- package/dist/assistant/use-assistant-chat.js +65 -3
- package/dist/assistant-screen.d.ts +9 -16
- package/dist/assistant-screen.js +26 -1
- package/dist/frame-script.d.ts +4 -1
- package/dist/frame-script.js +11 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/shell.js +13 -12
- package/dist/workspaces.d.ts +15 -0
- package/dist/workspaces.js +47 -5
- package/package.json +2 -2
package/dist/assistant/copy.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
3
|
-
import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, LightbulbIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, Undo2Icon, WrenchIcon, XIcon, } from 'lucide-react';
|
|
3
|
+
import { ArrowUpIcon, CheckIcon, ChevronRightIcon, CircleAlertIcon, CopyIcon, CornerDownRightIcon, CrosshairIcon, FileTextIcon, LightbulbIcon, MonitorIcon, PencilIcon, PlusIcon, SparklesIcon, SquareIcon, TriangleAlertIcon, Undo2Icon, WrenchIcon, XIcon, } from 'lucide-react';
|
|
4
4
|
import { createContext, useContext, useEffect, useRef, useState } from 'react';
|
|
5
5
|
import { LumenAssistantButton, LumenAssistantChats, LumenAssistantPanel, assistantOpen, useAssistantHotkey, } from '../assistant-panel.js';
|
|
6
|
+
import { screenMapFor, useAssistantScreen } from '../assistant-screen.js';
|
|
6
7
|
import { IconButton } from '../icon-button.js';
|
|
7
8
|
import { errorText } from '../integrations.js';
|
|
8
9
|
import { lazyPart } from '../lazy-part.js';
|
|
@@ -68,6 +69,43 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, onC
|
|
|
68
69
|
lumenToast.error(errorText(e));
|
|
69
70
|
}
|
|
70
71
|
};
|
|
72
|
+
// The assistant moves the screen (Settings ▸ Screen): the page map goes with each message, and
|
|
73
|
+
// each new screen call runs here once; how it ended goes with the next message.
|
|
74
|
+
const screen = useAssistantScreen();
|
|
75
|
+
const screenOn = !!screen && (s?.screen ?? 'move') === 'move';
|
|
76
|
+
const extra = () => ({
|
|
77
|
+
...(context ? { context } : {}),
|
|
78
|
+
...(screenOn && screen ? { screen: screenMapFor(screen) } : {}),
|
|
79
|
+
});
|
|
80
|
+
const ran = useRef(new Set());
|
|
81
|
+
const ended = chat.screenEnded;
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
for (const i of screenCalls(chat.items)) {
|
|
84
|
+
if (!i.live || ran.current.has(i.id))
|
|
85
|
+
continue;
|
|
86
|
+
ran.current.add(i.id);
|
|
87
|
+
if (!screen) {
|
|
88
|
+
ended({ id: i.id, status: 'failed', text: 'this page cannot be moved' });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
void screen.run(i.id, i.moves).then((o) => {
|
|
92
|
+
ended({ id: i.id, ...o });
|
|
93
|
+
// On a phone the panel covers the page: it steps aside so the move shows.
|
|
94
|
+
if (o.status === 'done' && onClose && window.matchMedia('(max-width: 767px)').matches)
|
|
95
|
+
onClose();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}, [chat.items, screen, ended, onClose]);
|
|
99
|
+
const controls = screen
|
|
100
|
+
? {
|
|
101
|
+
canUndo: (id) => screen.canUndo(id),
|
|
102
|
+
undo: (id) => void screen.undo(id).then((o) => ended({ id, ...o })),
|
|
103
|
+
go: (item) => {
|
|
104
|
+
ran.current.add(item.id);
|
|
105
|
+
void screen.run(item.id, item.moves, true).then((o) => ended({ id: item.id, ...o }));
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
: null;
|
|
71
109
|
const tiers = s?.tiers ?? [];
|
|
72
110
|
const above = tiers.slice(tiers.findIndex((t) => t.id === s?.tier) + 1);
|
|
73
111
|
// The kit's commands; the app's own (`commands`, then the server's) come first and win a name.
|
|
@@ -121,7 +159,7 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, onC
|
|
|
121
159
|
args: '<task>',
|
|
122
160
|
run: (task) => {
|
|
123
161
|
if (task.trim())
|
|
124
|
-
chat.send(task, { ...(
|
|
162
|
+
chat.send(task, { ...extra(), tier: above[0]?.id });
|
|
125
163
|
return undefined;
|
|
126
164
|
},
|
|
127
165
|
},
|
|
@@ -142,7 +180,7 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, onC
|
|
|
142
180
|
id: c.id,
|
|
143
181
|
title: c.title,
|
|
144
182
|
updated_at: new Date(c.at).toISOString(),
|
|
145
|
-
})) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, note: `Chats are kept ${keepDays} days after their last message.`, onRefresh: () => void chats.refetch(), onOpen: (c) => void chat.open(c.id) }), _jsx(IconButton, { icon: CopyIcon, label: "Copy chat", disabled: !chat.items.length, onClick: () => copyMarkdown(chatMarkdown(chat.title, chat.items, name), 'Chat') }), _jsx(IconButton, { icon: PlusIcon, label: "New chat", disabled: !chat.items.length, onClick: chat.reset }), onClose && (_jsx(IconButton, { icon: XIcon, label: `Close ${s?.name ?? 'assistant'}`, onClick: onClose }))] }), _jsx(AssistantName.Provider, { value: name, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, onSend: (text,
|
|
183
|
+
})) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, note: `Chats are kept ${keepDays} days after their last message.`, onRefresh: () => void chats.refetch(), onOpen: (c) => void chat.open(c.id) }), _jsx(IconButton, { icon: CopyIcon, label: "Copy chat", disabled: !chat.items.length, onClick: () => copyMarkdown(chatMarkdown(chat.title, chat.items, name), 'Chat') }), _jsx(IconButton, { icon: PlusIcon, label: "New chat", disabled: !chat.items.length, onClick: chat.reset }), onClose && (_jsx(IconButton, { icon: XIcon, label: `Close ${s?.name ?? 'assistant'}`, onClick: onClose }))] }), _jsx(AssistantName.Provider, { value: name, children: _jsx(Screen.Provider, { value: controls, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings }) }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, onSend: (text, more) => chat.send(text, { ...extra(), ...more }), onStop: chat.stop }), _jsx(UsageLine, { usage: chat.usage })] }));
|
|
146
184
|
}
|
|
147
185
|
function Messages({ chat, status, onSettings, }) {
|
|
148
186
|
const end = useRef(null);
|
|
@@ -223,6 +261,8 @@ function Item({ item, onSettle, busy, }) {
|
|
|
223
261
|
return _jsx(Step, { step: item, live: !!item.live });
|
|
224
262
|
case 'draft':
|
|
225
263
|
return _jsx(Draft, { draft: item, onSettle: onSettle, busy: busy });
|
|
264
|
+
case 'screen':
|
|
265
|
+
return _jsx(ScreenChip, { item: item });
|
|
226
266
|
case 'handoff':
|
|
227
267
|
return _jsx(HandOff, { item: item, onSettle: onSettle, busy: busy });
|
|
228
268
|
case 'advice':
|
|
@@ -239,6 +279,40 @@ function Item({ item, onSettle, busy, }) {
|
|
|
239
279
|
return (_jsxs("p", { role: "alert", className: "flex items-start gap-1.5 text-loss text-sm", children: [_jsx(CircleAlertIcon, { className: "mt-0.5 size-4 shrink-0" }), item.text] }));
|
|
240
280
|
}
|
|
241
281
|
}
|
|
282
|
+
/** Every screen call in the chat, hand-off sections included. */
|
|
283
|
+
function screenCalls(items) {
|
|
284
|
+
return items.flatMap((i) => i.kind === 'screen' ? [i] : i.kind === 'handoff' ? screenCalls(i.items) : []);
|
|
285
|
+
}
|
|
286
|
+
const Screen = createContext(null);
|
|
287
|
+
/** A screen call's moves in words, for the chip's hover. */
|
|
288
|
+
const movesText = (moves) => moves
|
|
289
|
+
.map((m) => 'go' in m
|
|
290
|
+
? `Go to ${m.go}`
|
|
291
|
+
: 'settings' in m
|
|
292
|
+
? `Settings ▸ ${m.settings}${m.row ? ` ▸ ${m.row}` : ''}`
|
|
293
|
+
: 'chart' in m
|
|
294
|
+
? `Chart ${[m.chart].flat().join(', ')}`
|
|
295
|
+
: 'open' in m
|
|
296
|
+
? `Open ${m.open}`
|
|
297
|
+
: `Point at ${m.point.replace(/^card:/, '')}`)
|
|
298
|
+
.join(' · ');
|
|
299
|
+
/**
|
|
300
|
+
* A screen call in the chat: ✓ and what is on screen, with Undo; why it failed; or, while the
|
|
301
|
+
* person was mid-edit, Go now.
|
|
302
|
+
*/
|
|
303
|
+
function ScreenChip({ item }) {
|
|
304
|
+
const c = useContext(Screen);
|
|
305
|
+
// No outcome and not running: it ran in another tab or before a reload, which kept no result.
|
|
306
|
+
const status = item.outcome?.status ?? (item.live ? 'running' : 'sent');
|
|
307
|
+
const said = status === 'failed'
|
|
308
|
+
? `Couldn't move · ${item.outcome?.text ?? ''}`
|
|
309
|
+
: status === 'waiting'
|
|
310
|
+
? 'Waits while you edit'
|
|
311
|
+
: status === 'undone'
|
|
312
|
+
? 'Undone'
|
|
313
|
+
: null;
|
|
314
|
+
return (_jsxs("div", { "data-slot": "assistant-screen", "data-status": status, className: "flex min-w-0 items-center gap-2 rounded-lg border bg-muted/30 px-3 py-1.5 text-muted-foreground text-xs", children: [status === 'running' ? (_jsx(Spinner, { className: "size-3.5" })) : status === 'done' ? (_jsx(CheckIcon, { className: "size-3.5 shrink-0 text-edge" })) : status === 'failed' ? (_jsx(TriangleAlertIcon, { className: "size-3.5 shrink-0 text-loss" })) : status === 'undone' ? (_jsx(Undo2Icon, { className: "size-3.5 shrink-0" })) : (_jsx(MonitorIcon, { className: "size-3.5 shrink-0" })), _jsxs("span", { className: "min-w-0 flex-1 truncate", title: `${item.label} · ${movesText(item.moves)}`, children: [_jsx("span", { className: cn(status === 'undone' ? 'line-through' : 'text-foreground'), children: item.label }), said && _jsxs("span", { children: [" \u00B7 ", said] })] }), status === 'done' && c?.canUndo(item.id) && (_jsx(IconButton, { icon: Undo2Icon, label: "Undo", size: "icon-xs", onClick: () => c.undo(item.id) })), status === 'waiting' && c && (_jsx(IconButton, { icon: MonitorIcon, label: "Go now", variant: "outline", size: "icon-xs", onClick: () => c.go(item) }))] }));
|
|
315
|
+
}
|
|
242
316
|
const Rule = ({ children }) => (_jsx("p", { className: "flex items-center gap-2 text-muted-foreground text-xs before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border", children: children }));
|
|
243
317
|
function Step({ step, live }) {
|
|
244
318
|
const undo = useContext(UndoWrite);
|
|
@@ -85,7 +85,10 @@ function General({ client }) {
|
|
|
85
85
|
value: t.id,
|
|
86
86
|
label: t.label,
|
|
87
87
|
})),
|
|
88
|
-
], onChange: (advice) => void set({ advice }) }) }))
|
|
88
|
+
], onChange: (advice) => void set({ advice }) }) })), _jsx(Row, { label: "Screen", info: "Takes you to a page, a Settings row, a chart or a card when you ask. Each move has an Undo; nothing is saved.", children: _jsx(LumenSelector, { label: "Screen", size: "sm", className: "w-44", value: s.screen ?? 'move', options: [
|
|
89
|
+
{ value: 'move', label: 'Move' },
|
|
90
|
+
{ value: 'off', label: 'Off' },
|
|
91
|
+
], onChange: (screen) => void set({ screen: screen }) }) })] }));
|
|
89
92
|
} }));
|
|
90
93
|
}
|
|
91
94
|
// ---------------------------------------------------------------- model providers
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssistantEvent, AssistantMessage, ChatItem, ChatUsage, DraftItem, StepItem } from '@columenlabs/lumen-ui-types/assistant';
|
|
1
|
+
import type { AssistantEvent, AssistantMessage, AssistantScreenOutcome, ChatItem, ChatUsage, DraftItem, StepItem } from '@columenlabs/lumen-ui-types/assistant';
|
|
2
2
|
import type { AssistantAdapter } from './client.js';
|
|
3
3
|
/** An item as the panel draws it: a text still streaming, a step still running. */
|
|
4
4
|
export type LiveItem = ChatItem & {
|
|
@@ -22,6 +22,8 @@ export interface AssistantChatState {
|
|
|
22
22
|
settle: (draft: DraftItem, action: 'save' | 'dismiss') => Promise<void>;
|
|
23
23
|
/** Takes back a change the assistant made at once (Undo on its line). */
|
|
24
24
|
undo: (step: StepItem) => Promise<void>;
|
|
25
|
+
/** A screen call ended in the browser: shown on its chip, and sent with the next message. */
|
|
26
|
+
screenEnded: (outcome: AssistantScreenOutcome) => void;
|
|
25
27
|
}
|
|
26
28
|
/** A copy of the items with one event applied (hand-off sections copied too). */
|
|
27
29
|
export declare function applyEvent(prev: LiveItem[], e: AssistantEvent): LiveItem[];
|
|
@@ -9,6 +9,27 @@ function browserZone() {
|
|
|
9
9
|
return undefined;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
// Screen calls' outcomes not yet sent (they go with the next message), kept for this tab so a
|
|
13
|
+
// reload neither loses them nor leaves their chips without a result.
|
|
14
|
+
const SCREEN_KEY = 'lumen-assistant:screen';
|
|
15
|
+
function readOutcomes() {
|
|
16
|
+
try {
|
|
17
|
+
const m = new Map(JSON.parse(sessionStorage.getItem(SCREEN_KEY) ?? '[]'));
|
|
18
|
+
return m;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return new Map();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function keepOutcomes(m) {
|
|
25
|
+
try {
|
|
26
|
+
if (m.size)
|
|
27
|
+
sessionStorage.setItem(SCREEN_KEY, JSON.stringify([...m].slice(-50)));
|
|
28
|
+
else
|
|
29
|
+
sessionStorage.removeItem(SCREEN_KEY);
|
|
30
|
+
}
|
|
31
|
+
catch { }
|
|
32
|
+
}
|
|
12
33
|
/** Where an event lands: the chat, or the last hand-off section when it came from inside one. */
|
|
13
34
|
function sinkOf(items, handoff) {
|
|
14
35
|
if (!handoff)
|
|
@@ -62,6 +83,12 @@ export function applyEvent(prev, e) {
|
|
|
62
83
|
sink.push(draft);
|
|
63
84
|
break;
|
|
64
85
|
}
|
|
86
|
+
case 'screen': {
|
|
87
|
+
// Live until the browser runs it: the panel runs a screen call once, while it is new.
|
|
88
|
+
const { handoff: _h, ...call } = e.data;
|
|
89
|
+
sink.push({ ...call, live: true });
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
65
92
|
case 'handoff':
|
|
66
93
|
items.push({ ...e.data, items: [] });
|
|
67
94
|
break;
|
|
@@ -102,11 +129,14 @@ export function applyEvent(prev, e) {
|
|
|
102
129
|
}
|
|
103
130
|
return items;
|
|
104
131
|
}
|
|
105
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Nothing streams any more: a text or step cut by Stop ends where it was. A screen call stays live
|
|
134
|
+
* until the browser has run it (`screenEnded`).
|
|
135
|
+
*/
|
|
106
136
|
const settled = (items) => items.map((i) => {
|
|
107
137
|
if (i.kind === 'handoff')
|
|
108
138
|
return { ...i, items: settled(i.items) };
|
|
109
|
-
if (!i.live)
|
|
139
|
+
if (!i.live || i.kind === 'screen')
|
|
110
140
|
return i;
|
|
111
141
|
const { live: _l, ...rest } = i;
|
|
112
142
|
return rest;
|
|
@@ -120,6 +150,8 @@ export function useAssistantChat(client) {
|
|
|
120
150
|
const [loading, setLoading] = useState(false);
|
|
121
151
|
const [queued, setQueued] = useState(null);
|
|
122
152
|
const abort = useRef(null);
|
|
153
|
+
const outcomes = useRef(null);
|
|
154
|
+
outcomes.current ??= readOutcomes();
|
|
123
155
|
const idRef = useRef(null);
|
|
124
156
|
idRef.current = chatId;
|
|
125
157
|
const run = useCallback(async (message, extra) => {
|
|
@@ -138,12 +170,17 @@ export function useAssistantChat(client) {
|
|
|
138
170
|
...(extra?.tier ? { tier: extra.tier } : {}),
|
|
139
171
|
},
|
|
140
172
|
]);
|
|
173
|
+
// Every screen outcome not yet sent goes with this message; kept again if it fails.
|
|
174
|
+
const sent = [...(outcomes.current?.values() ?? [])];
|
|
175
|
+
outcomes.current?.clear();
|
|
176
|
+
keepOutcomes(new Map());
|
|
141
177
|
try {
|
|
142
178
|
await client.send({
|
|
143
179
|
message,
|
|
144
180
|
...(idRef.current ? { chat_id: idRef.current } : {}),
|
|
145
181
|
...(browserZone() ? { time_zone: browserZone() } : {}),
|
|
146
182
|
...extra,
|
|
183
|
+
...(sent.length ? { outcomes: sent } : {}),
|
|
147
184
|
}, (e) => {
|
|
148
185
|
if (e.event === 'chat') {
|
|
149
186
|
idRef.current = e.data.id;
|
|
@@ -159,6 +196,11 @@ export function useAssistantChat(client) {
|
|
|
159
196
|
}, ctl.signal);
|
|
160
197
|
}
|
|
161
198
|
catch (err) {
|
|
199
|
+
for (const o of sent)
|
|
200
|
+
if (!outcomes.current?.has(o.id))
|
|
201
|
+
outcomes.current?.set(o.id, o);
|
|
202
|
+
if (outcomes.current)
|
|
203
|
+
keepOutcomes(outcomes.current);
|
|
162
204
|
if (ctl.signal.aborted)
|
|
163
205
|
setItems((p) => [...p, { kind: 'stopped' }]);
|
|
164
206
|
else
|
|
@@ -205,7 +247,14 @@ export function useAssistantChat(client) {
|
|
|
205
247
|
idRef.current = c.id;
|
|
206
248
|
setChatId(c.id);
|
|
207
249
|
setTitle(c.title);
|
|
208
|
-
|
|
250
|
+
// A call that ended here but whose outcome is not sent yet shows it already.
|
|
251
|
+
const kept = outcomes.current ?? new Map();
|
|
252
|
+
setItems(c.items.map((i) => {
|
|
253
|
+
const o = i.kind === 'screen' && !i.outcome ? kept.get(i.id) : undefined;
|
|
254
|
+
return o && i.kind === 'screen'
|
|
255
|
+
? { ...i, outcome: { status: o.status, ...(o.text ? { text: o.text } : {}) } }
|
|
256
|
+
: i;
|
|
257
|
+
}));
|
|
209
258
|
setUsage(c.usage ?? null);
|
|
210
259
|
}
|
|
211
260
|
catch (err) {
|
|
@@ -236,6 +285,18 @@ export function useAssistantChat(client) {
|
|
|
236
285
|
return;
|
|
237
286
|
replace(await client.undoWrite(id, step.id));
|
|
238
287
|
}, [client, replace]);
|
|
288
|
+
const screenEnded = useCallback((o) => {
|
|
289
|
+
outcomes.current?.set(o.id, o);
|
|
290
|
+
if (outcomes.current)
|
|
291
|
+
keepOutcomes(outcomes.current);
|
|
292
|
+
const outcome = { status: o.status, ...(o.text ? { text: o.text } : {}) };
|
|
293
|
+
const mark = (list) => list.map((i) => i.kind === 'screen' && i.id === o.id
|
|
294
|
+
? { kind: 'screen', id: i.id, label: i.label, moves: i.moves, outcome }
|
|
295
|
+
: i.kind === 'handoff'
|
|
296
|
+
? { ...i, items: mark(i.items) }
|
|
297
|
+
: i);
|
|
298
|
+
setItems(mark);
|
|
299
|
+
}, []);
|
|
239
300
|
return {
|
|
240
301
|
chatId,
|
|
241
302
|
title,
|
|
@@ -250,5 +311,6 @@ export function useAssistantChat(client) {
|
|
|
250
311
|
reset,
|
|
251
312
|
settle,
|
|
252
313
|
undo,
|
|
314
|
+
screenEnded,
|
|
253
315
|
};
|
|
254
316
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AssistantScreenMap, AssistantScreenMove, AssistantScreenStatus } from '@columenlabs/lumen-ui-types/assistant';
|
|
1
2
|
import type { IndicatorSpec } from '@columenlabs/lumen-ui-types/chart-formula';
|
|
2
3
|
import { type ReactNode } from 'react';
|
|
3
4
|
import { type CardOnScreen } from './spotlight.js';
|
|
@@ -28,20 +29,8 @@ export interface AssistantMoveHandlers {
|
|
|
28
29
|
restore?: (snap: unknown) => void;
|
|
29
30
|
}
|
|
30
31
|
/** One move, as the server checked it. */
|
|
31
|
-
export type AssistantMove =
|
|
32
|
-
|
|
33
|
-
} | {
|
|
34
|
-
settings: string;
|
|
35
|
-
row?: string;
|
|
36
|
-
} | {
|
|
37
|
-
chart: string | string[];
|
|
38
|
-
indicators?: IndicatorSpec[];
|
|
39
|
-
} | {
|
|
40
|
-
open: string;
|
|
41
|
-
} | {
|
|
42
|
-
point: string;
|
|
43
|
-
};
|
|
44
|
-
export type ScreenStatus = 'done' | 'failed' | 'waiting' | 'undone';
|
|
32
|
+
export type AssistantMove = AssistantScreenMove;
|
|
33
|
+
export type ScreenStatus = AssistantScreenStatus;
|
|
45
34
|
export interface ScreenOutcome {
|
|
46
35
|
status: ScreenStatus;
|
|
47
36
|
text?: string;
|
|
@@ -86,7 +75,7 @@ export interface ScreenMap {
|
|
|
86
75
|
cards: CardOnScreen[];
|
|
87
76
|
workspace?: WorkspaceOnScreen;
|
|
88
77
|
}
|
|
89
|
-
interface ScreenApi {
|
|
78
|
+
export interface ScreenApi {
|
|
90
79
|
register: (slot: string, h: AssistantMoveHandlers | null) => void;
|
|
91
80
|
pages: (key: string, items: AssistantPage[] | null) => void;
|
|
92
81
|
/** The workspace page says which workspace and tabs are on screen while it shows. */
|
|
@@ -121,4 +110,8 @@ export declare function useAssistantPages(items: {
|
|
|
121
110
|
export declare function AssistantScreenProvider({ children }: {
|
|
122
111
|
children: ReactNode;
|
|
123
112
|
}): import("react").JSX.Element;
|
|
124
|
-
|
|
113
|
+
/**
|
|
114
|
+
* The page map as the assistant's server reads it (`AssistantMessage.screen`): routes, named pages,
|
|
115
|
+
* Settings sections, the cards on screen and the rows the view can open.
|
|
116
|
+
*/
|
|
117
|
+
export declare function screenMapFor(api: ScreenApi): AssistantScreenMap;
|
package/dist/assistant-screen.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useRouter } from '@tanstack/react-router';
|
|
3
3
|
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, } from 'react';
|
|
4
4
|
import { cardsOnScreen } from './spotlight.js';
|
|
@@ -88,6 +88,31 @@ const here = () => `${window.location.pathname}${window.location.search}${window
|
|
|
88
88
|
const cardOf = (id) => document.querySelector(`[data-slot="lumen-card"][data-card="${CSS.escape(id)}"]`);
|
|
89
89
|
/** Holds what each part of the screen takes, and runs the assistant's moves over them. */
|
|
90
90
|
export function AssistantScreenProvider({ children }) {
|
|
91
|
+
// One per app: a shell inside an app's own provider uses that one.
|
|
92
|
+
if (useContext(ScreenCtx))
|
|
93
|
+
return _jsx(_Fragment, { children: children });
|
|
94
|
+
return _jsx(ScreenProvider, { children: children });
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The page map as the assistant's server reads it (`AssistantMessage.screen`): routes, named pages,
|
|
98
|
+
* Settings sections, the cards on screen and the rows the view can open.
|
|
99
|
+
*/
|
|
100
|
+
export function screenMapFor(api) {
|
|
101
|
+
const m = api.map();
|
|
102
|
+
const rows = api.can()?.rows ?? [];
|
|
103
|
+
return {
|
|
104
|
+
routes: m.routes,
|
|
105
|
+
pages: m.pages.map((p) => ({
|
|
106
|
+
path: p.path,
|
|
107
|
+
title: p.title,
|
|
108
|
+
...(p.section ? { section: p.section } : {}),
|
|
109
|
+
})),
|
|
110
|
+
settings: m.settings,
|
|
111
|
+
cards: m.cards.map((c) => ({ id: c.id, title: c.title, kind: c.kind })),
|
|
112
|
+
...(rows.length ? { rows } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function ScreenProvider({ children }) {
|
|
91
116
|
const router = useRouter();
|
|
92
117
|
const slots = useRef(new Map());
|
|
93
118
|
const navPages = useRef(new Map());
|
package/dist/frame-script.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import type { FrameRun } from '@columenlabs/lumen-ui-types/frames';
|
|
2
|
+
export declare function FrameScriptDialog({ api, frame, name, runs, open, onOpenChange, }: {
|
|
2
3
|
api: string;
|
|
3
4
|
frame: string;
|
|
4
5
|
name: string;
|
|
6
|
+
/** The last runs, newest first: when, how many rows, how long, who, and why one failed. */
|
|
7
|
+
runs?: FrameRun[];
|
|
5
8
|
open: boolean;
|
|
6
9
|
onOpenChange: (open: boolean) => void;
|
|
7
10
|
}): import("react").JSX.Element;
|
package/dist/frame-script.js
CHANGED
|
@@ -47,7 +47,7 @@ function parseScript(was, text) {
|
|
|
47
47
|
args: (o.args ?? {}),
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
export function FrameScriptDialog({ api, frame, name, open, onOpenChange, }) {
|
|
50
|
+
export function FrameScriptDialog({ api, frame, name, runs, open, onOpenChange, }) {
|
|
51
51
|
const qc = useQueryClient();
|
|
52
52
|
const assistant = useAssistantName();
|
|
53
53
|
const read = useQuery({
|
|
@@ -96,8 +96,17 @@ export function FrameScriptDialog({ api, frame, name, open, onOpenChange, }) {
|
|
|
96
96
|
setError(e.message);
|
|
97
97
|
}
|
|
98
98
|
};
|
|
99
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex h-[min(44rem,calc(100dvh-2rem))] flex-col sm:max-w-4xl", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Script \u00B7 ", name] }), _jsxs(DialogDescription, { children: ["What ", assistant, " runs to make these rows. Saved only if it runs."] })] }), read.isPending ? (_jsx(Skeleton, { className: "min-h-0 flex-1" })) : read.error ? (_jsxs(Alert, { variant: "destructive", children: [_jsx(AlertTitle, { children: "Couldn't read the script" }), _jsx(AlertDescription, { children: read.error.message })] })) : !script ? (_jsxs(Alert, { children: [_jsx(AlertTitle, { children: "No script" }), _jsxs(AlertDescription, { children: [assistant, " wrote these rows once: nothing runs to make them."] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-end gap-1", children: [lang && (_jsx(Badge, { variant: "secondary", className: "font-mono", children: lang })), _jsx(IconButton, { icon: WrapTextIcon, label: wrap ? 'Stop wrapping lines' : 'Wrap long lines', pressed: wrap, onClick: () => setWrap(!wrap) })] }), _jsx(LumenCodeEditor, { className: "flex-1", value: text, lang: lang, wrap: wrap, onChange: setText }), error && (_jsxs(Alert, { variant: "destructive", className: "max-h-32 overflow-y-auto", children: [_jsx(AlertTitle, { children: "Didn't save" }), _jsx(AlertDescription, { className: "break-words font-mono text-xs", children: error })] }))] })), _jsxs(DialogFooter, { children: [_jsx(IconButton, { icon: RotateCcwIcon, label: "Undo my changes", disabled: !changed || save.isPending, onClick: () => {
|
|
99
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "flex h-[min(44rem,calc(100dvh-2rem))] flex-col sm:max-w-4xl", children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Script \u00B7 ", name] }), _jsxs(DialogDescription, { children: ["What ", assistant, " runs to make these rows. Saved only if it runs."] })] }), read.isPending ? (_jsx(Skeleton, { className: "min-h-0 flex-1" })) : read.error ? (_jsxs(Alert, { variant: "destructive", children: [_jsx(AlertTitle, { children: "Couldn't read the script" }), _jsx(AlertDescription, { children: read.error.message })] })) : !script ? (_jsxs(Alert, { children: [_jsx(AlertTitle, { children: "No script" }), _jsxs(AlertDescription, { children: [assistant, " wrote these rows once: nothing runs to make them."] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center justify-end gap-1", children: [lang && (_jsx(Badge, { variant: "secondary", className: "font-mono", children: lang })), _jsx(IconButton, { icon: WrapTextIcon, label: wrap ? 'Stop wrapping lines' : 'Wrap long lines', pressed: wrap, onClick: () => setWrap(!wrap) })] }), _jsx(LumenCodeEditor, { className: "flex-1", value: text, lang: lang, wrap: wrap, onChange: setText }), runs && runs.length > 0 && _jsx(RunList, { runs: runs, assistant: assistant }), error && (_jsxs(Alert, { variant: "destructive", className: "max-h-32 overflow-y-auto", children: [_jsx(AlertTitle, { children: "Didn't save" }), _jsx(AlertDescription, { className: "break-words font-mono text-xs", children: error })] }))] })), _jsxs(DialogFooter, { children: [_jsx(IconButton, { icon: RotateCcwIcon, label: "Undo my changes", disabled: !changed || save.isPending, onClick: () => {
|
|
100
100
|
setText(original);
|
|
101
101
|
setError(null);
|
|
102
102
|
} }), _jsxs(Button, { disabled: !script || save.isPending, onClick: run, children: [save.isPending ? _jsx(Spinner, {}) : _jsx(PlayIcon, {}), " Run and save"] })] })] }) }));
|
|
103
103
|
}
|
|
104
|
+
/** The frame's last runs, one line each; a failure's reason in its hover. */
|
|
105
|
+
function RunList({ runs, assistant }) {
|
|
106
|
+
return (_jsx("ul", { className: "flex max-h-28 flex-col gap-0.5 overflow-y-auto text-xs text-muted-foreground", children: runs.map((r) => (_jsxs("li", { className: "flex min-w-0 items-center gap-2", title: r.error, children: [_jsx("span", { className: `size-1.5 shrink-0 rounded-full ${r.ok ? 'bg-[var(--meter-ok)]' : 'bg-destructive'}` }), _jsx("time", { className: "shrink-0", dateTime: r.at, children: new Date(r.at).toLocaleString('en-US', {
|
|
107
|
+
month: 'short',
|
|
108
|
+
day: 'numeric',
|
|
109
|
+
hour: 'numeric',
|
|
110
|
+
minute: '2-digit',
|
|
111
|
+
}) }), _jsx("span", { className: "num shrink-0", children: r.ok ? `${r.rows.toLocaleString('en-US')} rows` : 'failed' }), _jsxs("span", { className: "num shrink-0", children: [(r.ms / 1000).toFixed(1), " s"] }), _jsx("span", { className: "min-w-0 truncate", children: r.ok ? `by ${r.by === 'you' ? 'you' : assistant}` : r.error })] }, r.at))) }));
|
|
112
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -102,10 +102,10 @@ export { useTell } from './hooks/use-tell.js';
|
|
|
102
102
|
export { useBackClose } from './hooks/use-back-close.js';
|
|
103
103
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
104
104
|
export { type AppViewState, useAppView } from './app-views.js';
|
|
105
|
-
export { type ScreenMap, type ScreenOutcome, type ScreenStatus, type WorkspaceOnScreen, type AssistantMove, type AssistantMoveHandlers, type AssistantPage, type AssistantPageType, AssistantScreenProvider, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
|
105
|
+
export { type ScreenApi, type ScreenMap, type ScreenOutcome, type ScreenStatus, type WorkspaceOnScreen, type AssistantMove, type AssistantMoveHandlers, type AssistantPage, type AssistantPageType, AssistantScreenProvider, screenMapFor, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
|
106
106
|
export { type AssistantActions, AssistantContext, type AssistantNote, type AssistantRef, type AssistantSlot, useAssistant, AssistantPendingContext, useAssistantName, useAssistantNote, useAssistantPending, } from './assistant-context.js';
|
|
107
107
|
export { WORKSPACES_KEY, frameKey, workspacesListQuery } from './workspace-keys.js';
|
|
108
|
-
export { type WorkspaceApp, type WorkspaceDraftProps, WorkspacePage, type WorkspacesConfig, WorkspacesProvider, WorkspacesSection, } from './workspaces.js';
|
|
108
|
+
export { type WorkspaceApp, type WorkspaceDraftProps, WorkspacePage, type WorkspaceRecipe, type WorkspacesConfig, WorkspacesProvider, WorkspacesSection, } from './workspaces.js';
|
|
109
109
|
export { CHART_INDICATORS, type ChartIndicator, type IndicatorValues, LINE_COLOR, computeIndicators, formulaIndicator, lineColor, resolveIndicator, runFormula, } from './market/indicators.js';
|
|
110
110
|
export { IndicatorPicker } from './market/indicator-picker.js';
|
|
111
111
|
export { type OptionChainField, type OptionChainRow, OptionChainTable, } from './market/option-chain.js';
|
package/dist/index.js
CHANGED
|
@@ -109,7 +109,7 @@ export { useTell } from './hooks/use-tell.js';
|
|
|
109
109
|
export { useBackClose } from './hooks/use-back-close.js';
|
|
110
110
|
export { VIEW_ICONS, iconOf, viewIcon } from './view-icons.js';
|
|
111
111
|
export { useAppView } from './app-views.js';
|
|
112
|
-
export { AssistantScreenProvider, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
|
112
|
+
export { AssistantScreenProvider, screenMapFor, useAssistantMoves, useAssistantPages, useAssistantScreen, useAssistantWorkspace, } from './assistant-screen.js';
|
|
113
113
|
export { AssistantContext, useAssistant, AssistantPendingContext, useAssistantName, useAssistantNote, useAssistantPending, } from './assistant-context.js';
|
|
114
114
|
export { WORKSPACES_KEY, frameKey, workspacesListQuery } from './workspace-keys.js';
|
|
115
115
|
export { WorkspacePage, WorkspacesProvider, WorkspacesSection, } from './workspaces.js';
|
package/dist/shell.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
7
7
|
import { useRouterState } from '@tanstack/react-router';
|
|
8
8
|
import { ChevronsUpDownIcon, LogOutIcon, ScrollTextIcon, SettingsIcon, SunMoonIcon, } from 'lucide-react';
|
|
9
9
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
10
|
+
import { AssistantScreenProvider } from './assistant-screen.js';
|
|
10
11
|
import { lazyPart } from './lazy-part.js';
|
|
11
12
|
import { cn } from './lib/utils.js';
|
|
12
13
|
import { LumenFinder } from './lumen-finder.js';
|
|
@@ -133,18 +134,18 @@ export function LumenShell({ app, appLabel, brand, nav, tabs, bar, status, healt
|
|
|
133
134
|
title: s.title,
|
|
134
135
|
})));
|
|
135
136
|
const findSettings = useMemo(() => JSON.parse(sectionNames), [sectionNames]);
|
|
136
|
-
return (_jsx(Ctx.Provider, { value: {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
return (_jsx(AssistantScreenProvider, { children: _jsx(Ctx.Provider, { value: {
|
|
138
|
+
app,
|
|
139
|
+
appLabel,
|
|
140
|
+
statusSlot,
|
|
141
|
+
openSettings,
|
|
142
|
+
closeSettings: () => setSettingsAt(null),
|
|
143
|
+
hasSettings: (section) => findSettings.some((s) => s.id === section),
|
|
144
|
+
refresh: onRefresh ? refresh : null,
|
|
145
|
+
refreshing,
|
|
146
|
+
hideAmounts,
|
|
147
|
+
headerActions,
|
|
148
|
+
}, children: _jsx(StatusBarProvider, { children: _jsx(DockSlot.Provider, { value: dockSlot, children: _jsx(StatusSignals, { app: app, model: bar, openSettings: openSettings, children: _jsx(SourceHealthProvider, { children: wrap(_jsx(TooltipProvider, { delayDuration: 200, children: _jsx(SidebarProvider, { defaultOpen: sidebarOpen(), children: _jsxs(LumenFinder, { app: app, settings: findSettings, openSettings: openSettings, config: finder, children: [_jsx(CloseDrawerOnNavigate, {}), _jsxs(Sidebar, { collapsible: "icon", children: [_jsx(SidebarHeader, { children: brand }), _jsx(SidebarContent, { children: nav }), _jsx(SidebarFooter, { children: _jsx(NavUser, { who: user, signOutUrl: signOutUrl, onSettings: () => openSettings(), onLogs: onLogs }) }), _jsx(SidebarRail, {})] }), _jsxs(SidebarInset, { className: "flex min-h-svh min-w-0 flex-col", children: [_jsx(SignalBanners, {}), _jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: children }, epoch), _jsx(LumenStatusBar, { app: app, model: bar, legacy: status, slot: setStatusSlot, end: _jsxs(_Fragment, { children: [health, releases && _jsx(WhatsNew, { api: releases, apps: releaseApps }), statusEnd, _jsx(StatusTimeZone, {})] }) }), tabs && _jsx(LumenTabBar, { items: tabs })] }), _jsx("div", { ref: setDockSlot, "data-slot": "shell-dock", className: "contents" }), panel, _jsx(LumenSettings, { open: settingsAt !== null, section: settingsAt ?? lastSection, onOpen: openSettings, onClose: () => setSettingsAt(null), appLabel: appLabel, profile: profile, security: security, sections: settings, shared: shared, sharedLabel: sharedLabel, groups: settingsGroups }), _jsx(Toaster, { position: "bottom-right" })] }) }) })) }) }) }) }) }) }));
|
|
148
149
|
}
|
|
149
150
|
/** A status bar entry: an optional health dot, a label and its value ("Mail · Up to date"). */
|
|
150
151
|
export function StatusItem({ label, value, ok, className, }) {
|
package/dist/workspaces.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FrameScript } from '@columenlabs/lumen-ui-types/frames';
|
|
1
2
|
import { type CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
2
3
|
import { type ComponentType, type ReactNode } from 'react';
|
|
3
4
|
import { type Crumb } from './page.js';
|
|
@@ -24,6 +25,20 @@ export interface WorkspacesConfig {
|
|
|
24
25
|
defaultConfig?: (domain: string) => CustomViewConfig | null;
|
|
25
26
|
/** The empty workspace's example ask, on an app: `put my holdings with their earnings dates in`. */
|
|
26
27
|
example?: (app: string) => string;
|
|
28
|
+
/** Recipes a person picks to start a tab by hand (+ in the tab bar); none hides the +. */
|
|
29
|
+
recipes?: WorkspaceRecipe[];
|
|
30
|
+
}
|
|
31
|
+
/** A saved data pull a person can start a tab from: one call to one of the product's read tools. */
|
|
32
|
+
export interface WorkspaceRecipe {
|
|
33
|
+
id: string;
|
|
34
|
+
/** The tab's name and the picker's row: `Jobs scoring 70+`. */
|
|
35
|
+
label: string;
|
|
36
|
+
/** When to pick it, under the label. */
|
|
37
|
+
description?: string;
|
|
38
|
+
/** The picker's section: the app the rows come from. */
|
|
39
|
+
group?: string;
|
|
40
|
+
icon?: Icon;
|
|
41
|
+
recipe: FrameScript;
|
|
27
42
|
}
|
|
28
43
|
/** The product's apps and domains for its workspaces. */
|
|
29
44
|
export declare const WorkspacesProvider: import("react").Provider<WorkspacesConfig>;
|
package/dist/workspaces.js
CHANGED
|
@@ -4,7 +4,7 @@ import { customViewConfigSchema, frameSpace, } from '@columenlabs/lumen-ui-types
|
|
|
4
4
|
import { workspacePath, } from '@columenlabs/lumen-ui-types/workspaces';
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { useNavigate, useParams, useSearch } from '@tanstack/react-router';
|
|
7
|
-
import { ArrowRightLeftIcon, CalendarIcon, ExternalLinkIcon, HashIcon, LayersIcon, PencilIcon, SparklesIcon, TableIcon, TextCursorInputIcon, Trash2Icon, TypeIcon, } from 'lucide-react';
|
|
7
|
+
import { ArrowRightLeftIcon, CalendarIcon, ExternalLinkIcon, HashIcon, LayersIcon, PencilIcon, PlusIcon, RefreshCwIcon, SparklesIcon, TableIcon, TextCursorInputIcon, Trash2Icon, TypeIcon, } from 'lucide-react';
|
|
8
8
|
import { createContext, useContext, useEffect, useMemo, useState, } from 'react';
|
|
9
9
|
import { apiJson } from './api-fetch.js';
|
|
10
10
|
import { useAssistant, useAssistantName, useAssistantPending } from './assistant-context.js';
|
|
@@ -206,9 +206,48 @@ function WorkspaceBody({ api, w, tab, draft, }) {
|
|
|
206
206
|
}).then(() => qc.invalidateQueries({ queryKey: WORKSPACES_KEY }));
|
|
207
207
|
}, { id: `ws-save:${tab.id}`, duration: 4000 });
|
|
208
208
|
};
|
|
209
|
-
|
|
209
|
+
// + in the tab bar: a tab from a recipe, run once now; ↻ in the header runs every tab again.
|
|
210
|
+
const addTab = useMutation({
|
|
211
|
+
mutationFn: (r) => json(`${api}/workspaces/${w.id}/tabs`, {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
body: JSON.stringify({ name: r.label, recipe: r.recipe }),
|
|
214
|
+
}),
|
|
215
|
+
onSuccess: ({ workspace, tab: t }) => {
|
|
216
|
+
put(workspace);
|
|
217
|
+
void qc.invalidateQueries({ queryKey: [...WORKSPACES_KEY, api, 'list'] });
|
|
218
|
+
void navigate({ href: workspacePath(w.id, t), replace: true });
|
|
219
|
+
},
|
|
220
|
+
onError: (e) => lumenToast.error(`Couldn’t add the tab: ${e.message}`),
|
|
221
|
+
});
|
|
222
|
+
const runAll = useMutation({
|
|
223
|
+
mutationFn: () => json(`${api}/workspaces/${w.id}/refresh`, {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
}),
|
|
226
|
+
onSuccess: (r) => {
|
|
227
|
+
void qc.invalidateQueries({ queryKey: WORKSPACES_KEY });
|
|
228
|
+
const ran = `Ran ${r.ran} ${r.ran === 1 ? 'tab' : 'tabs'}: ${r.rows.toLocaleString('en-US')} rows`;
|
|
229
|
+
if (r.failed.length)
|
|
230
|
+
lumenToast.warning(`${ran}. Failed: ${r.failed.join(', ')}`);
|
|
231
|
+
else
|
|
232
|
+
lumenToast.success(ran);
|
|
233
|
+
},
|
|
234
|
+
onError: (e) => lumenToast.error(`Couldn’t run the tabs: ${e.message}`),
|
|
235
|
+
});
|
|
236
|
+
const recipes = config.recipes ?? [];
|
|
237
|
+
const runnable = w.tabs.some((t) => 'frame' in t.source);
|
|
238
|
+
const intro = (_jsx(LumenTabs, { tabs: w.tabs.map((t) => ({ value: t.id, label: t.name, icon: tabIcon(t) })), value: tab?.id, onChange: (t) => void navigate({ href: workspacePath(w.id, t), replace: true }), actions: _jsxs(_Fragment, { children: [recipes.length > 0 && (_jsx(LumenSelector, { trigger: "icon", icon: PlusIcon, label: addTab.isPending ? 'Adding a tab…' : 'New tab from a recipe', align: "end", disabled: addTab.isPending, options: recipes.map((r) => ({
|
|
239
|
+
value: r.id,
|
|
240
|
+
label: r.label,
|
|
241
|
+
description: r.description,
|
|
242
|
+
group: r.group,
|
|
243
|
+
icon: r.icon,
|
|
244
|
+
})), value: null, onChange: (id) => {
|
|
245
|
+
const r = recipes.find((x) => x.id === id);
|
|
246
|
+
if (r)
|
|
247
|
+
addTab.mutate(r);
|
|
248
|
+
} })), tab && (_jsx(IconButton, { icon: PencilIcon, label: "Edit tab", onClick: () => setEditingTab(true) }))] }) }));
|
|
210
249
|
const crumbs = [{ label: 'Workspaces' }, { label: w.name }];
|
|
211
|
-
const actions = (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: TextCursorInputIcon, label: "Rename workspace", onClick: () => setNaming('workspace') }), moveTo.length > 0 && (_jsx(LumenSelector, { trigger: "icon", icon: ArrowRightLeftIcon, label: `Move to ${noun}`, align: "end", search: false, options: moveTo.map((a) => ({ value: a.id, label: a.label, icon: a.icon })), value: null, onChange: (a) => move.mutate(a) })), _jsx(IconButton, { icon: Trash2Icon, label: "Delete workspace", className: "text-muted-foreground hover:text-destructive", onClick: () => setRemoving('workspace') })] }));
|
|
250
|
+
const actions = (_jsxs(_Fragment, { children: [runnable && (_jsx(IconButton, { icon: RefreshCwIcon, label: runAll.isPending ? 'Running every tab…' : 'Run every tab again', disabled: runAll.isPending, className: runAll.isPending ? '[&_svg]:animate-spin' : undefined, onClick: () => runAll.mutate() })), _jsx(IconButton, { icon: TextCursorInputIcon, label: "Rename workspace", onClick: () => setNaming('workspace') }), moveTo.length > 0 && (_jsx(LumenSelector, { trigger: "icon", icon: ArrowRightLeftIcon, label: `Move to ${noun}`, align: "end", search: false, options: moveTo.map((a) => ({ value: a.id, label: a.label, icon: a.icon })), value: null, onChange: (a) => move.mutate(a) })), _jsx(IconButton, { icon: Trash2Icon, label: "Delete workspace", className: "text-muted-foreground hover:text-destructive", onClick: () => setRemoving('workspace') })] }));
|
|
212
251
|
const dialogs = (_jsxs(_Fragment, { children: [_jsx(NameDialog, { open: naming !== null, title: "Rename workspace", value: w.name, onClose: () => setNaming(null), onSave: (name) => rename.mutate(name) }), tab && (_jsx(LumenTabDrawer, { open: editingTab, onClose: () => setEditingTab(false), name: tab.name, icon: tab.config?.icon, fallback: tabIcon({ ...tab, config: null }), description: "Its filters and columns save as you change them.", maxName: 60, busy: editTab.isPending, error: editTab.error?.message, onSave: ({ name, icon }) => editTab.mutate(icon === (tab.config?.icon ?? null)
|
|
213
252
|
? { name }
|
|
214
253
|
: { name, config: { ...tabConfig(tab, w, config.defaultConfig), name, icon } }), remove: {
|
|
@@ -226,7 +265,9 @@ function WorkspaceBody({ api, w, tab, draft, }) {
|
|
|
226
265
|
title: 'No tabs yet',
|
|
227
266
|
icon: LayersIcon,
|
|
228
267
|
kind: 'widget',
|
|
229
|
-
children: (
|
|
268
|
+
children: (_jsx("p", { className: "text-sm text-muted-foreground", children: recipes.length > 0
|
|
269
|
+
? 'Pick a recipe from + above: its rows open here as a tab, and run again whenever you tap ↻.'
|
|
270
|
+
: `Ask ${assistant} for data and to open it here: “${config.example?.(here) ?? 'put this month’s numbers in'} ${w.name}”.` })),
|
|
230
271
|
},
|
|
231
272
|
] }));
|
|
232
273
|
}
|
|
@@ -290,6 +331,7 @@ assistant = 'Assistant') {
|
|
|
290
331
|
value: (r) => r.v[i] ?? null,
|
|
291
332
|
format: c.format,
|
|
292
333
|
amount: c.amount,
|
|
334
|
+
...(c.tags ? { tags: true } : {}),
|
|
293
335
|
filterable: true,
|
|
294
336
|
groupable: c.type !== 'number',
|
|
295
337
|
...(c.type === 'number' ? { aggregate: c.format === 'percent' ? 'mean' : 'sum' } : {}),
|
|
@@ -438,7 +480,7 @@ function FrameTab({ api, tab, meta, crumbs, actions, intro, onChange, }) {
|
|
|
438
480
|
day: 'numeric',
|
|
439
481
|
hour: 'numeric',
|
|
440
482
|
minute: '2-digit',
|
|
441
|
-
}) }), failed ? (_jsx(StatusItem, { label: "Refresh failed", className: "min-w-0", value: _jsx("span", { className: "inline-block max-w-72 truncate align-bottom", title: failed, children: failed }), ok: false })) : null] }) }, `frame:${tab.id}`), assistantMade && (_jsx(FrameScriptDialog, { api: api, frame: current.id, name: current.name, open: scriptOpen, onOpenChange: setScriptOpen }))] }));
|
|
483
|
+
}) }), failed ? (_jsx(StatusItem, { label: "Refresh failed", className: "min-w-0", value: _jsx("span", { className: "inline-block max-w-72 truncate align-bottom", title: failed, children: failed }), ok: false })) : null] }) }, `frame:${tab.id}`), assistantMade && (_jsx(FrameScriptDialog, { api: api, frame: current.id, name: current.name, runs: current.runs, open: scriptOpen, onOpenChange: setScriptOpen }))] }));
|
|
442
484
|
}
|
|
443
485
|
/** A tab's icon: the person's pick, else the assistant's mark on a frame, else a table. */
|
|
444
486
|
function tabIcon(t) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.191",
|
|
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.191",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|