@columenlabs/lumen-ui 0.1.231 → 0.1.233
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
CHANGED
|
@@ -72,6 +72,8 @@ function itemMarkdown(i, depth, name) {
|
|
|
72
72
|
].join('\n\n');
|
|
73
73
|
case 'advice':
|
|
74
74
|
return `_${i.error ? 'Could not ask' : 'Asked'} ${i.label} for advice._`;
|
|
75
|
+
case 'fresh':
|
|
76
|
+
return '_Offered a fresh chat for this._';
|
|
75
77
|
case 'compacted':
|
|
76
78
|
return '_Compacted._';
|
|
77
79
|
case 'stopped':
|
|
@@ -193,9 +193,9 @@ export function LumenAssistant({ client, context, onSettings, keepDays = 30, cha
|
|
|
193
193
|
title: c.title,
|
|
194
194
|
updated_at: new Date(c.at).toISOString(),
|
|
195
195
|
...(chatScope && allScopes && c.scope ? { where: chatScope.name?.(c.scope) } : {}),
|
|
196
|
-
})) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, toggle: chatScope && { label: chatScope.label, on: allScopes, onChange: setAllScopes }, 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(ImageTool.Provider, { value: imageTool ?? null, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings, onRetry: (at, user) => chat.retry(at, user, extra()) }) }) }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, queue: chat.queue, canSteer: chat.canSteer, onSteerQueued: chat.steerQueued, onUnqueue: chat.unqueue, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, images: images, modes: modes, onSend: (text, more, how) => chat.send(text, { ...extra(), ...more }, how), onStop: chat.stop }), _jsx(UsageLine, { usage: chat.usage, client: client, spent: s?.spend.usd, compactAt: compactAt })] }));
|
|
196
|
+
})) ?? (chats.isFetched ? [] : null), value: chat.chatId ?? undefined, toggle: chatScope && { label: chatScope.label, on: allScopes, onChange: setAllScopes }, 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(ImageTool.Provider, { value: imageTool ?? null, children: _jsx(Messages, { chat: chat, status: s, onSettings: onSettings, onRetry: (at, user) => chat.retry(at, user, extra()), onFresh: (choice) => chat.fresh(choice, extra()) }) }) }) }), _jsx(LumenComposer, { name: s?.name ?? 'the assistant', busy: chat.busy, queued: chat.queued, queue: chat.queue, canSteer: chat.canSteer, onSteerQueued: chat.steerQueued, onUnqueue: chat.unqueue, disabled: !s || !!s.wait, commands: allCommands, tiers: tiers, sources: allSources, page: context?.label, images: images, modes: modes, onSend: (text, more, how) => chat.send(text, { ...extra(), ...more }, how), onStop: chat.stop }), _jsx(UsageLine, { usage: chat.usage, client: client, spent: s?.spend.usd, compactAt: compactAt })] }));
|
|
197
197
|
}
|
|
198
|
-
function Messages({ chat, status, onSettings, onRetry, }) {
|
|
198
|
+
function Messages({ chat, status, onSettings, onRetry, onFresh, }) {
|
|
199
199
|
const end = useRef(null);
|
|
200
200
|
const box = useRef(null);
|
|
201
201
|
const count = JSON.stringify(chat.items).length;
|
|
@@ -214,7 +214,7 @@ function Messages({ chat, status, onSettings, onRetry, }) {
|
|
|
214
214
|
status.allowance &&
|
|
215
215
|
status.allowance.request?.status !== 'open'
|
|
216
216
|
? 'Request more'
|
|
217
|
-
: '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(RetryTurn.Provider, { value: chat.busy || status?.wait ? null : onRetry, 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 })] }));
|
|
217
|
+
: '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(RetryTurn.Provider, { value: chat.busy || status?.wait ? null : onRetry, children: _jsx(FreshChoice.Provider, { value: chat.busy ? null : onFresh, 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 })] }));
|
|
218
218
|
}
|
|
219
219
|
const isLive = (i) => !!i.live || (i.kind === 'handoff' && i.items.some(isLive));
|
|
220
220
|
const WAIT = {
|
|
@@ -243,6 +243,16 @@ const TierNames = createContext((id) => id);
|
|
|
243
243
|
/** The app's image tool (`LumenAssistant` `imageTool`). */
|
|
244
244
|
const ImageTool = createContext(null);
|
|
245
245
|
const UndoWrite = createContext(null);
|
|
246
|
+
/** Answers the fresh-chat card; null while an answer runs. */
|
|
247
|
+
const FreshChoice = createContext(null);
|
|
248
|
+
/**
|
|
249
|
+
* The app asks before answering: the message above starts new work and this chat is long, so a
|
|
250
|
+
* fresh chat answers it faster and for less. + takes it there, × keeps it here.
|
|
251
|
+
*/
|
|
252
|
+
function FreshCard() {
|
|
253
|
+
const choose = useContext(FreshChoice);
|
|
254
|
+
return (_jsxs("div", { "data-slot": "assistant-fresh", className: "flex min-w-0 items-center gap-2 rounded-lg border bg-card py-2 pr-2 pl-3 text-sm", children: [_jsx("span", { className: "min-w-0 flex-1", children: "This looks like new work. Start a fresh chat for it?" }), choose && (_jsxs("span", { className: "flex shrink-0 gap-1", children: [_jsx(IconButton, { icon: XIcon, label: "Keep it in this chat", onClick: () => choose('stay') }), _jsx(IconButton, { icon: PlusIcon, label: "New chat with this message", variant: "default", onClick: () => choose('new') })] }))] }));
|
|
255
|
+
}
|
|
246
256
|
/** Sends a failed turn again; null while it can't (an answer runs, nobody can pay). */
|
|
247
257
|
const RetryTurn = createContext(null);
|
|
248
258
|
/** Where a turn's unretried error sits in the chat, when the turn starts with a message. */
|
|
@@ -310,6 +320,8 @@ function Item({ item, onSettle, busy, }) {
|
|
|
310
320
|
: `Asked ${item.label}`] }));
|
|
311
321
|
case 'compacted':
|
|
312
322
|
return _jsx(Rule, { children: "Compacted: earlier messages are sent as a summary" });
|
|
323
|
+
case 'fresh':
|
|
324
|
+
return _jsx(FreshCard, {});
|
|
313
325
|
case 'stopped':
|
|
314
326
|
return _jsx(Rule, { children: item.reason === 'cap' ? "Stopped at this month's spend cap" : 'Stopped' });
|
|
315
327
|
case 'error':
|
|
@@ -52,6 +52,11 @@ export interface AssistantChatState {
|
|
|
52
52
|
undo: (step: StepItem) => Promise<void>;
|
|
53
53
|
/** A screen call ended in the browser: shown on its chip, and sent with the next message. */
|
|
54
54
|
screenEnded: (outcome: AssistantScreenOutcome) => void;
|
|
55
|
+
/**
|
|
56
|
+
* Answers the fresh-chat card (`fresh` item): `new` sends its message in a new chat, `stay`
|
|
57
|
+
* sends it here again, kept in this chat.
|
|
58
|
+
*/
|
|
59
|
+
fresh: (choice: 'new' | 'stay', extra?: Extra) => void;
|
|
55
60
|
}
|
|
56
61
|
/** A copy of the items with one event applied (hand-off sections copied too). */
|
|
57
62
|
export declare function applyEvent(prev: LiveItem[], e: AssistantEvent): LiveItem[];
|
|
@@ -33,6 +33,19 @@ function keepOutcomes(m) {
|
|
|
33
33
|
}
|
|
34
34
|
let queuedSeq = 0;
|
|
35
35
|
const queuedId = () => `q${++queuedSeq}`;
|
|
36
|
+
/** What a sent message carried besides its words, to send it again (Retry, a fresh chat). */
|
|
37
|
+
function resendOf(user) {
|
|
38
|
+
const images = (user.images ?? []).flatMap((src) => {
|
|
39
|
+
const m = /^data:([^;]+);base64,(.*)$/.exec(src);
|
|
40
|
+
return m ? [{ media_type: m[1], data: m[2] }] : [];
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
...(user.refs?.length ? { refs: user.refs } : {}),
|
|
44
|
+
...(user.quote ? { quote: user.quote } : {}),
|
|
45
|
+
...(user.tier ? { tier: user.tier } : {}),
|
|
46
|
+
...(images.length ? { images } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
36
49
|
/** Where an event lands: the chat, or the last hand-off section when it came from inside one. */
|
|
37
50
|
function sinkOf(items, handoff) {
|
|
38
51
|
if (!handoff)
|
|
@@ -119,6 +132,9 @@ export function applyEvent(prev, e) {
|
|
|
119
132
|
case 'compacted':
|
|
120
133
|
items.push({ kind: 'compacted' });
|
|
121
134
|
break;
|
|
135
|
+
case 'fresh':
|
|
136
|
+
items.push({ kind: 'fresh' });
|
|
137
|
+
break;
|
|
122
138
|
case 'stopped':
|
|
123
139
|
items.push({ kind: 'stopped', ...(e.data.reason ? { reason: e.data.reason } : {}) });
|
|
124
140
|
break;
|
|
@@ -305,17 +321,34 @@ export function useAssistantChat(client) {
|
|
|
305
321
|
if (abort.current)
|
|
306
322
|
return;
|
|
307
323
|
setItems((p) => p.map((x, i) => (i === at && x.kind === 'error' ? { ...x, retried: true } : x)));
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
324
|
+
void run(user.text, { ...extra, ...resendOf(user) }, true);
|
|
325
|
+
}, [run]);
|
|
326
|
+
const itemsRef = useRef([]);
|
|
327
|
+
itemsRef.current = items;
|
|
328
|
+
const fresh = useCallback((choice, extra) => {
|
|
329
|
+
if (abort.current)
|
|
330
|
+
return;
|
|
331
|
+
const list = itemsRef.current;
|
|
332
|
+
const card = list.findLastIndex((i) => i.kind === 'fresh');
|
|
333
|
+
const at = list.findLastIndex((i, n) => n < card && i.kind === 'user');
|
|
334
|
+
const user = list[at];
|
|
335
|
+
if (card < 0 || user?.kind !== 'user')
|
|
336
|
+
return;
|
|
337
|
+
if (choice === 'new') {
|
|
338
|
+
// A new chat, as New chat makes one, and the message is its first.
|
|
339
|
+
setQueue([]);
|
|
340
|
+
setChatId(null);
|
|
341
|
+
idRef.current = null;
|
|
342
|
+
setTitle('');
|
|
343
|
+
setItems([]);
|
|
344
|
+
setUsage(null);
|
|
345
|
+
void run(user.text, { ...extra, ...resendOf(user) });
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
// The message goes again in its place, without the card.
|
|
349
|
+
setItems((p) => p.slice(0, at));
|
|
350
|
+
void run(user.text, { ...extra, ...resendOf(user), stay: true });
|
|
351
|
+
}
|
|
319
352
|
}, [run]);
|
|
320
353
|
const steerQueued = useCallback((id) => {
|
|
321
354
|
const m = queueRef.current.find((x) => x.id === id && !x.steering);
|
|
@@ -424,5 +457,6 @@ export function useAssistantChat(client) {
|
|
|
424
457
|
settle,
|
|
425
458
|
undo,
|
|
426
459
|
screenEnded,
|
|
460
|
+
fresh,
|
|
427
461
|
};
|
|
428
462
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.233",
|
|
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.233",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|