@robosystems/core 0.11.5 → 0.13.0
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/components/console/ConsoleContent.js +57 -7
- package/components/console/graphAwareConfig.js +2 -0
- package/components/console/types.d.ts +6 -0
- package/demos/runtime.d.ts +66 -0
- package/demos/runtime.js +278 -0
- package/hooks/index.d.ts +1 -0
- package/hooks/index.js +1 -0
- package/hooks/use-graph-writes.d.ts +6 -0
- package/hooks/use-graph-writes.js +22 -0
- package/index.d.ts +2 -2
- package/index.js +2 -2
- package/lib/graph-writes.d.ts +20 -0
- package/lib/graph-writes.js +19 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/package.json +1 -1
- package/ui-components/layout/LiveDemo.d.ts +22 -0
- package/ui-components/layout/LiveDemo.js +44 -0
- package/ui-components/layout/index.d.ts +1 -0
- package/ui-components/layout/index.js +1 -0
|
@@ -6,6 +6,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
6
6
|
import { HiTerminal } from 'react-icons/hi';
|
|
7
7
|
import { useGraphContext } from '../../contexts';
|
|
8
8
|
import { useStreamingQuery } from '../../hooks';
|
|
9
|
+
import { emitGraphWrites, readGraphWrites, } from '../../lib/graph-writes';
|
|
9
10
|
import { ConsoleMarkdown } from './ConsoleMarkdown';
|
|
10
11
|
import { ProgressiveText } from './ProgressiveText';
|
|
11
12
|
function generateMessageId() {
|
|
@@ -53,6 +54,23 @@ function downloadText(filename, text, mime) {
|
|
|
53
54
|
anchor.click();
|
|
54
55
|
URL.revokeObjectURL(url);
|
|
55
56
|
}
|
|
57
|
+
const WRITE_LABELS = {
|
|
58
|
+
'create-taxonomy-block': 'Created taxonomy block',
|
|
59
|
+
'create-information-block': 'Created block',
|
|
60
|
+
'update-information-block': 'Updated block',
|
|
61
|
+
'assert-metrics': 'Asserted metrics',
|
|
62
|
+
'compute-metrics': 'Computed metrics',
|
|
63
|
+
'compute-forecast': 'Computed forecast',
|
|
64
|
+
'create-agent': 'Created counterparty',
|
|
65
|
+
'update-agent': 'Updated counterparty',
|
|
66
|
+
remember: 'Saved memory',
|
|
67
|
+
};
|
|
68
|
+
function describeWrite(write) {
|
|
69
|
+
var _a, _b;
|
|
70
|
+
const label = (_a = WRITE_LABELS[write.operation]) !== null && _a !== void 0 ? _a : write.operation;
|
|
71
|
+
const subject = (_b = write.name) !== null && _b !== void 0 ? _b : write.id;
|
|
72
|
+
return subject ? `${label}: ${subject}` : label;
|
|
73
|
+
}
|
|
56
74
|
function copyRowsJson(rows) {
|
|
57
75
|
var _a;
|
|
58
76
|
(_a = navigator.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(JSON.stringify(rows, null, 2));
|
|
@@ -102,6 +120,9 @@ export function ConsoleContent({ config }) {
|
|
|
102
120
|
(config.enableRecall
|
|
103
121
|
? ` /recall - Recall semantic memories\n`
|
|
104
122
|
: '') +
|
|
123
|
+
(config.enableAuthor
|
|
124
|
+
? ` /do - Make a change (a block, a counterparty, a memory)\n`
|
|
125
|
+
: '') +
|
|
105
126
|
` /mcp - Connect an MCP client (sign in — no key)\n` +
|
|
106
127
|
` /mcp key - Mint a graph-scoped API key for header-only clients\n` +
|
|
107
128
|
` /help - Show this help message\n` +
|
|
@@ -145,6 +166,7 @@ export function ConsoleContent({ config }) {
|
|
|
145
166
|
setTerminalMessages((prev) => [...prev, message]);
|
|
146
167
|
}, []);
|
|
147
168
|
const addResultMessage = useCallback((content, data, cypher, opts) => {
|
|
169
|
+
var _a;
|
|
148
170
|
const message = {
|
|
149
171
|
id: generateMessageId(),
|
|
150
172
|
type: 'system',
|
|
@@ -154,6 +176,7 @@ export function ConsoleContent({ config }) {
|
|
|
154
176
|
cypher,
|
|
155
177
|
markdown: opts === null || opts === void 0 ? void 0 : opts.markdown,
|
|
156
178
|
footer: opts === null || opts === void 0 ? void 0 : opts.footer,
|
|
179
|
+
writes: ((_a = opts === null || opts === void 0 ? void 0 : opts.writes) === null || _a === void 0 ? void 0 : _a.length) ? opts.writes : undefined,
|
|
157
180
|
};
|
|
158
181
|
setTerminalMessages((prev) => [...prev, message]);
|
|
159
182
|
}, []);
|
|
@@ -328,7 +351,9 @@ export function ConsoleContent({ config }) {
|
|
|
328
351
|
setCurrentQueryStartTime(null);
|
|
329
352
|
}
|
|
330
353
|
};
|
|
331
|
-
|
|
354
|
+
// `operatorType` names an operator (`author` for /do); unset, the API
|
|
355
|
+
// chooses one for a question.
|
|
356
|
+
const executeOperatorQuery = async (userQuery, operatorType) => {
|
|
332
357
|
var _a;
|
|
333
358
|
if (!graphId) {
|
|
334
359
|
addErrorMessage(config.noSelectionError);
|
|
@@ -342,10 +367,11 @@ export function ConsoleContent({ config }) {
|
|
|
342
367
|
try {
|
|
343
368
|
const { clients } = await import('@robosystems/client/clients');
|
|
344
369
|
const history = conversationRef.current;
|
|
345
|
-
const
|
|
370
|
+
const request = Object.assign({ message: userQuery,
|
|
346
371
|
// `standard` gives the operator loop six tool turns; `quick` (three)
|
|
347
372
|
// was exhausted by schema + examples before the first query ran.
|
|
348
|
-
mode: 'standard' }, (history.length > 0 ? { history } : {}))
|
|
373
|
+
mode: 'standard' }, (history.length > 0 ? { history } : {}));
|
|
374
|
+
const options = {
|
|
349
375
|
mode: 'auto',
|
|
350
376
|
onProgress: (message, percentage) => {
|
|
351
377
|
if (isStale())
|
|
@@ -356,7 +382,10 @@ export function ConsoleContent({ config }) {
|
|
|
356
382
|
percentage,
|
|
357
383
|
});
|
|
358
384
|
},
|
|
359
|
-
}
|
|
385
|
+
};
|
|
386
|
+
const result = operatorType
|
|
387
|
+
? await clients.operator.executeOperator(graphId, operatorType, request, options)
|
|
388
|
+
: await clients.operator.executeQuery(graphId, request, options);
|
|
360
389
|
// The user switched graphs while the operator ran: the reset already
|
|
361
390
|
// cleared the console, so drop the answer rather than print it (and
|
|
362
391
|
// record it as history) under the new graph.
|
|
@@ -378,6 +407,9 @@ export function ConsoleContent({ config }) {
|
|
|
378
407
|
}
|
|
379
408
|
const duration = Date.now() - startTime;
|
|
380
409
|
const metadata = (result.metadata || {});
|
|
410
|
+
const writes = readGraphWrites(metadata);
|
|
411
|
+
// Pages showing this graph reload what the console just changed.
|
|
412
|
+
emitGraphWrites(requestGraphId, writes);
|
|
381
413
|
const creditsUsed = metadata.credits_consumed;
|
|
382
414
|
const resultCount = metadata.result_count;
|
|
383
415
|
// Prefer the structured outputs the operator loop now returns
|
|
@@ -429,7 +461,11 @@ export function ConsoleContent({ config }) {
|
|
|
429
461
|
// otherwise collapse under markdown). With no narrative, fall back to the
|
|
430
462
|
// plain footer, matching the direct-query result style.
|
|
431
463
|
if (narrative) {
|
|
432
|
-
addResultMessage(narrative, rows, cypher, {
|
|
464
|
+
addResultMessage(narrative, rows, cypher, {
|
|
465
|
+
markdown: true,
|
|
466
|
+
footer,
|
|
467
|
+
writes,
|
|
468
|
+
});
|
|
433
469
|
// Record the exchange for follow-ups. Only a narrative answer is
|
|
434
470
|
// worth carrying; the answer is clipped so history stays cheap.
|
|
435
471
|
conversationRef.current = [
|
|
@@ -439,7 +475,7 @@ export function ConsoleContent({ config }) {
|
|
|
439
475
|
].slice(-6);
|
|
440
476
|
}
|
|
441
477
|
else {
|
|
442
|
-
addResultMessage(footer, rows, cypher);
|
|
478
|
+
addResultMessage(footer, rows, cypher, { writes });
|
|
443
479
|
}
|
|
444
480
|
}
|
|
445
481
|
catch (error) {
|
|
@@ -596,6 +632,17 @@ export function ConsoleContent({ config }) {
|
|
|
596
632
|
}
|
|
597
633
|
return;
|
|
598
634
|
}
|
|
635
|
+
// Handle /do — the author operator, which may change the graph. Writes
|
|
636
|
+
// are never inferred from a question; they need this command.
|
|
637
|
+
if (config.enableAuthor && /^\/do(\s|$)/i.test(command)) {
|
|
638
|
+
const request = command.slice(3).trim();
|
|
639
|
+
if (!request) {
|
|
640
|
+
addErrorMessage('Usage: /do <change>\n\nExamples:\n /do add Notion Labs as a vendor\n /do remember that we report ARPC on subscription revenue only');
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
await executeOperatorQuery(request, 'author');
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
599
646
|
// Handle /recall command — built-in only when the target supports
|
|
600
647
|
// semantic memory (user graphs; shared repositories reject it). When
|
|
601
648
|
// disabled it falls through to the unknown-command branch below.
|
|
@@ -702,7 +749,10 @@ export function ConsoleContent({ config }) {
|
|
|
702
749
|
? 'text-green-400'
|
|
703
750
|
: message.type === 'error'
|
|
704
751
|
? 'text-red-400'
|
|
705
|
-
: 'text-gray-300'}`, children: [message.type === 'user' && (_jsx("span", { className: "mr-2 text-green-500", children: "$" })), message.isAnimating ? (_jsx(ProgressiveText, { text: message.content, onComplete: () => handleAnimationComplete(message.id), onUpdate: scrollToBottom })) : message.markdown ? (_jsx(ConsoleMarkdown, { content: message.content })) : (message.content)] }), message.
|
|
752
|
+
: 'text-gray-300'}`, children: [message.type === 'user' && (_jsx("span", { className: "mr-2 text-green-500", children: "$" })), message.isAnimating ? (_jsx(ProgressiveText, { text: message.content, onComplete: () => handleAnimationComplete(message.id), onUpdate: scrollToBottom })) : message.markdown ? (_jsx(ConsoleMarkdown, { content: message.content })) : (message.content)] }), message.writes && (_jsxs("div", { className: "mt-3 rounded border border-emerald-900/60 bg-emerald-950/30 px-3 py-2", children: [_jsx("div", { className: "text-xs tracking-wider text-emerald-400 uppercase", children: "Changes made" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-sm text-emerald-200", children: message.writes.map((write, idx) => {
|
|
753
|
+
var _a;
|
|
754
|
+
return (_jsxs("li", { children: ["\u2713 ", describeWrite(write)] }, `${write.operation}-${(_a = write.id) !== null && _a !== void 0 ? _a : idx}`));
|
|
755
|
+
}) })] })), message.footer && (_jsx("div", { className: "mt-3 text-xs whitespace-pre-wrap text-gray-500", children: message.footer })), message.cypher && (_jsxs("div", { className: "mt-3 overflow-hidden rounded border border-gray-800 bg-gray-900/40", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-800 px-3 py-1.5", children: [_jsx("span", { className: "text-xs tracking-wider text-gray-500 uppercase", children: "Generated Cypher" }), _jsx("button", { onClick: () => { var _a; return handleCommand(`/query ${(_a = message.cypher) !== null && _a !== void 0 ? _a : ''}`); }, className: "rounded bg-cyan-600/90 px-2 py-0.5 text-xs font-medium text-white transition-colors hover:bg-cyan-600", children: "Run" })] }), _jsx("pre", { className: "overflow-x-auto px-3 py-2 font-mono text-xs whitespace-pre-wrap text-cyan-300", children: message.cypher })] })), message.data && message.data.length > 0 && (_jsxs("div", { className: "mt-3 overflow-hidden rounded border border-gray-800", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-gray-800 bg-gray-900 px-4 py-2", children: [_jsxs("span", { className: "text-xs text-gray-500", children: [message.data.length, " row", message.data.length === 1 ? '' : 's'] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: () => copyRowsJson(message.data), className: "rounded border border-gray-700 px-2 py-0.5 text-xs text-gray-300 transition-colors hover:bg-gray-800", children: "Copy JSON" }), _jsx("button", { onClick: () => downloadText('results.csv', rowsToCsv(message.data), 'text/csv'), className: "rounded border border-gray-700 px-2 py-0.5 text-xs text-gray-300 transition-colors hover:bg-gray-800", children: "Download CSV" })] })] }), _jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs("table", { className: "w-full border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0", children: _jsx("tr", { className: "border-b border-gray-800 bg-gray-900", children: Object.keys(message.data[0]).map((key) => (_jsx("th", { className: "px-4 py-2 text-left font-semibold whitespace-nowrap text-cyan-400", children: key }, key))) }) }), _jsx("tbody", { children: message.data
|
|
706
756
|
.slice(0, MAX_TABLE_ROWS)
|
|
707
757
|
.map((row, idx) => (_jsx("tr", { className: "border-b border-gray-900 hover:bg-gray-900/50", children: Object.keys(message.data[0]).map((key) => {
|
|
708
758
|
const value = row[key];
|
|
@@ -404,6 +404,8 @@ export function buildGraphAwareConsoleConfig(graph, branding) {
|
|
|
404
404
|
noSelectionError: (_c = branding.noSelectionError) !== null && _c !== void 0 ? _c : 'No graph selected. Please select a graph first.',
|
|
405
405
|
// Semantic memory is per-user-graph; shared repositories reject it.
|
|
406
406
|
enableRecall: !isRepository,
|
|
407
|
+
// Writes need a user graph; the API also checks the write role.
|
|
408
|
+
enableAuthor: !isRepository,
|
|
407
409
|
};
|
|
408
410
|
}
|
|
409
411
|
/**
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GraphWrite } from '../../lib/graph-writes';
|
|
1
2
|
export interface SampleQuery {
|
|
2
3
|
name: string;
|
|
3
4
|
query: string;
|
|
@@ -65,6 +66,9 @@ export interface ConsoleConfig {
|
|
|
65
66
|
* shared repositories don't support memory, so the graph-aware builder
|
|
66
67
|
* enables it only for user graphs. */
|
|
67
68
|
enableRecall?: boolean;
|
|
69
|
+
/** Enable `/do`, which runs the write-capable author operator. The API
|
|
70
|
+
* gates it on the graph write role; off for shared repositories. */
|
|
71
|
+
enableAuthor?: boolean;
|
|
68
72
|
/** Extra slash commands beyond the built-in set */
|
|
69
73
|
extraCommands?: ConsoleExtraCommand[];
|
|
70
74
|
}
|
|
@@ -90,4 +94,6 @@ export interface TerminalMessage {
|
|
|
90
94
|
* breaks survive markdown rendering.
|
|
91
95
|
*/
|
|
92
96
|
footer?: string;
|
|
97
|
+
/** Changes the author operator made, shown as a receipt below the answer. */
|
|
98
|
+
writes?: GraphWrite[];
|
|
93
99
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export interface Rect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
w: number;
|
|
5
|
+
h: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DemoTheme {
|
|
8
|
+
/** Selector for the sidebar that holds `.pill` and the `.nv[data-k]` items. */
|
|
9
|
+
side: string;
|
|
10
|
+
/** Resting item colour, as RGB. */
|
|
11
|
+
rest: [number, number, number];
|
|
12
|
+
/** Resting colour of an indented `.nv.sub` item; defaults to `rest`. */
|
|
13
|
+
restSub?: [number, number, number];
|
|
14
|
+
/** Colour of the item under the pill. */
|
|
15
|
+
active: [number, number, number];
|
|
16
|
+
}
|
|
17
|
+
export interface DemoContext {
|
|
18
|
+
$: (id: string) => HTMLElement;
|
|
19
|
+
root: ShadowRoot;
|
|
20
|
+
stage: HTMLElement;
|
|
21
|
+
rel: (el: Element) => Rect;
|
|
22
|
+
ring: (hl: HTMLElement, target: Element, p: number, pad?: number) => void;
|
|
23
|
+
nav: (k: string, from?: string, m?: number, scope?: ParentNode) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface DemoLayout {
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
css?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DemoDefinition extends DemoLayout {
|
|
31
|
+
total: number;
|
|
32
|
+
poster?: number;
|
|
33
|
+
html: string;
|
|
34
|
+
setup: (ctx: DemoContext) => (t: number) => void;
|
|
35
|
+
mobile?: DemoLayout;
|
|
36
|
+
}
|
|
37
|
+
export interface MountOptions {
|
|
38
|
+
autoplay?: boolean;
|
|
39
|
+
phone?: boolean;
|
|
40
|
+
/** The app's base CSS: its tokens, window chrome and shared components. */
|
|
41
|
+
css?: string;
|
|
42
|
+
theme?: DemoTheme;
|
|
43
|
+
}
|
|
44
|
+
export interface DemoHandle {
|
|
45
|
+
seek: (t: number) => void;
|
|
46
|
+
destroy: () => void;
|
|
47
|
+
}
|
|
48
|
+
export declare const clamp01: (x: number) => number;
|
|
49
|
+
export declare const seg: (t: number, a: number, b: number) => number;
|
|
50
|
+
export declare const eo: (x: number) => number;
|
|
51
|
+
export declare const eio: (x: number) => number;
|
|
52
|
+
export declare const typed: (s: string, t: number, t0: number, cps?: number) => string;
|
|
53
|
+
export declare const spin: (t: number) => string;
|
|
54
|
+
export declare const money: (v: number, dp?: number) => string;
|
|
55
|
+
export declare function rise(el: HTMLElement, p: number, dy?: number): void;
|
|
56
|
+
export declare const dip: (t: number, at: number, d?: number) => number;
|
|
57
|
+
export declare function swap(el: HTMLElement, t: number, at: number, a?: string | null, b?: string | null, cls?: [string, string]): boolean;
|
|
58
|
+
export declare function steps(el: HTMLElement, t: number, times: number[], texts?: string[] | null, classes?: string[] | null): number;
|
|
59
|
+
export declare function blurIn(el: HTMLElement, p: number, dy?: number): void;
|
|
60
|
+
export declare function push(el: HTMLElement, t: number, total: number, ox: number, oy: number, amount?: number): void;
|
|
61
|
+
export declare function pointer(ctx: DemoContext, cur: HTMLElement, el: Element, t: number, t0: number, t1: number, { from, out, }?: {
|
|
62
|
+
from?: [number, number];
|
|
63
|
+
out?: number;
|
|
64
|
+
}): boolean;
|
|
65
|
+
export declare function variant(def: DemoDefinition, phone: boolean): DemoDefinition;
|
|
66
|
+
export declare function mount(host: HTMLElement, def: DemoDefinition, { autoplay, phone, css, theme }?: MountOptions): DemoHandle;
|
package/demos/runtime.js
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The runtime behind the landing-page demos in each app's /public/demos.
|
|
3
|
+
*
|
|
4
|
+
* A demo module default-exports { width, height, total, poster, css, html,
|
|
5
|
+
* setup(ctx), mobile? } where setup returns seek(t), a pure function of time.
|
|
6
|
+
* The same module runs live on the landing page (mount, in a shadow root,
|
|
7
|
+
* looping while visible) and frame by frame under the content machine's
|
|
8
|
+
* renderer (render.html), so the page and the social cut share one source.
|
|
9
|
+
*
|
|
10
|
+
* This file compiles to dependency-free ESM that a browser loads as-is: each
|
|
11
|
+
* app commits a copy at public/demos/runtime.js (npm run sync:demos) and a
|
|
12
|
+
* test there fails when the copy drifts from the installed package. The app's
|
|
13
|
+
* own kit.js wraps mount() with its window chrome, base CSS and sidebar theme.
|
|
14
|
+
*/
|
|
15
|
+
export const clamp01 = (x) => Math.max(0, Math.min(1, x));
|
|
16
|
+
export const seg = (t, a, b) => clamp01((t - a) / (b - a));
|
|
17
|
+
export const eo = (x) => 1 - Math.pow(1 - clamp01(x), 3);
|
|
18
|
+
export const eio = (x) => {
|
|
19
|
+
x = clamp01(x);
|
|
20
|
+
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
|
|
21
|
+
};
|
|
22
|
+
export const typed = (s, t, t0, cps = 34) => s.slice(0, Math.max(0, Math.floor((t - t0) * cps)));
|
|
23
|
+
export const spin = (t) => '◐◓◑◒'[Math.floor(t * 8) % 4];
|
|
24
|
+
export const money = (v, dp = 2) => '$' +
|
|
25
|
+
v.toLocaleString('en-US', {
|
|
26
|
+
minimumFractionDigits: dp,
|
|
27
|
+
maximumFractionDigits: dp,
|
|
28
|
+
});
|
|
29
|
+
export function rise(el, p, dy = 40) {
|
|
30
|
+
el.style.opacity = String(p);
|
|
31
|
+
el.style.transform = `translateY(${(1 - p) * dy}px)`;
|
|
32
|
+
}
|
|
33
|
+
/* Opacity for an element whose content changes at `at`: dips through 0.15 over `d` seconds. */
|
|
34
|
+
export const dip = (t, at, d = 0.3) => Math.min(1, 0.15 + 0.85 * (Math.abs(t - at) / (d / 2)));
|
|
35
|
+
/*
|
|
36
|
+
* Change a label or badge without a one-frame pop: before `at` it shows `a`,
|
|
37
|
+
* after it `b` (text, and optional class names), with an opacity dip across
|
|
38
|
+
* the change. With `a` null only the dip applies, for text set elsewhere.
|
|
39
|
+
*/
|
|
40
|
+
export function swap(el, t, at, a, b, cls) {
|
|
41
|
+
const after = t >= at;
|
|
42
|
+
if (a != null)
|
|
43
|
+
el.textContent = after ? (b !== null && b !== void 0 ? b : '') : a;
|
|
44
|
+
if (cls)
|
|
45
|
+
el.className = after ? cls[1] : cls[0];
|
|
46
|
+
el.style.opacity = String(dip(t, at));
|
|
47
|
+
return after;
|
|
48
|
+
}
|
|
49
|
+
/* A badge that moves through states at `times`: texts[k] and classes[k], dipping at each change. */
|
|
50
|
+
export function steps(el, t, times, texts, classes) {
|
|
51
|
+
let k = 0;
|
|
52
|
+
times.forEach((x) => (k += t >= x ? 1 : 0));
|
|
53
|
+
if (texts)
|
|
54
|
+
el.textContent = texts[k];
|
|
55
|
+
if (classes)
|
|
56
|
+
el.className = classes[k];
|
|
57
|
+
el.style.opacity = String(Math.min(1, ...times.map((x) => dip(t, x))));
|
|
58
|
+
return k;
|
|
59
|
+
}
|
|
60
|
+
/* Headline entrance: blur to sharp, rising, as p goes 0 to 1. */
|
|
61
|
+
export function blurIn(el, p, dy = 24) {
|
|
62
|
+
const e = eo(p);
|
|
63
|
+
el.style.opacity = String(clamp01(p * 1.4));
|
|
64
|
+
el.style.filter = e < 1 ? `blur(${(1 - e) * 14}px)` : '';
|
|
65
|
+
el.style.transform = `translateY(${(1 - e) * dy}px)`;
|
|
66
|
+
}
|
|
67
|
+
/*
|
|
68
|
+
* A slow camera push over the loop toward (ox, oy), in stage px, easing back
|
|
69
|
+
* before the loop wraps, so no screen sits still like a slide.
|
|
70
|
+
*/
|
|
71
|
+
export function push(el, t, total, ox, oy, amount = 0.04) {
|
|
72
|
+
const k = amount *
|
|
73
|
+
eio(seg(t, 0.4, total - 1.2)) *
|
|
74
|
+
(1 - eio(seg(t, total - 1.2, total)));
|
|
75
|
+
el.style.transformOrigin = `${ox}px ${oy}px`;
|
|
76
|
+
el.style.transform = `scale(${1 + k})`;
|
|
77
|
+
}
|
|
78
|
+
/* A drawn pointer that travels from `from` to the centre of `el`, presses, and fades out. */
|
|
79
|
+
export function pointer(ctx, cur, el, t, t0, t1, { from = [260, 160], out = 0.6, } = {}) {
|
|
80
|
+
if (t < t0 - 0.15 || t > t1 + out) {
|
|
81
|
+
cur.style.opacity = '0';
|
|
82
|
+
return t > t1;
|
|
83
|
+
}
|
|
84
|
+
const r = ctx.rel(el);
|
|
85
|
+
const tx = r.x + r.w * 0.55;
|
|
86
|
+
const ty = r.y + r.h * 0.55;
|
|
87
|
+
const m = eio(seg(t, t0, t1));
|
|
88
|
+
cur.style.left = tx + (1 - m) * from[0] + 'px';
|
|
89
|
+
cur.style.top = ty + (1 - m) * from[1] + 'px';
|
|
90
|
+
cur.style.opacity = String(seg(t, t0 - 0.15, t0) * (1 - seg(t, t1 + out - 0.2, t1 + out)));
|
|
91
|
+
const pressed = t > t1 && t < t1 + 0.18;
|
|
92
|
+
cur.style.transform = `scale(${pressed ? 0.85 : 1})`;
|
|
93
|
+
return t > t1;
|
|
94
|
+
}
|
|
95
|
+
/* The stage's own reset; everything else, including the tokens, comes from the app. */
|
|
96
|
+
const STAGE_CSS = `
|
|
97
|
+
:host { display: block; }
|
|
98
|
+
.stage { position: absolute; left: 0; top: 0; transform-origin: 0 0; overflow: hidden; }
|
|
99
|
+
:where(.stage *) { box-sizing: border-box; margin: 0; padding: 0; }
|
|
100
|
+
`;
|
|
101
|
+
function makeCtx(root, stage, getScale, theme) {
|
|
102
|
+
const $ = (id) => root.getElementById(id);
|
|
103
|
+
const rel = (el) => {
|
|
104
|
+
const s = stage.getBoundingClientRect();
|
|
105
|
+
const r = el.getBoundingClientRect();
|
|
106
|
+
const k = getScale();
|
|
107
|
+
return {
|
|
108
|
+
x: (r.left - s.left) / k,
|
|
109
|
+
y: (r.top - s.top) / k,
|
|
110
|
+
w: r.width / k,
|
|
111
|
+
h: r.height / k,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
/* ring `hl` (absolutely positioned in its offsetParent) around `target` */
|
|
115
|
+
const ring = (hl, target, p, pad = 6) => {
|
|
116
|
+
const parent = hl.offsetParent;
|
|
117
|
+
// a hidden scene has no layout to measure; skip rather than throw
|
|
118
|
+
if (!parent) {
|
|
119
|
+
hl.style.opacity = '0';
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const a = rel(target);
|
|
123
|
+
const b = rel(parent);
|
|
124
|
+
// a camera push scales the parent; undo it so the ring sits in its local px
|
|
125
|
+
const k = b.w / parent.offsetWidth || 1;
|
|
126
|
+
hl.style.left = (a.x - b.x) / k - pad + 'px';
|
|
127
|
+
hl.style.top = (a.y - b.y) / k - pad * 0.7 + 'px';
|
|
128
|
+
hl.style.width = a.w / k + pad * 2 + 'px';
|
|
129
|
+
hl.style.height = a.h / k + pad * 1.4 + 'px';
|
|
130
|
+
hl.style.opacity = String(p);
|
|
131
|
+
};
|
|
132
|
+
/* slide the pill of the sidebar inside `scope` to `k`, blending from `from` by m */
|
|
133
|
+
const nav = (k, from = k, m = 1, scope = root) => {
|
|
134
|
+
if (!theme)
|
|
135
|
+
return;
|
|
136
|
+
const side = scope.querySelector(theme.side);
|
|
137
|
+
if (!side)
|
|
138
|
+
return;
|
|
139
|
+
const item = (key) => side.querySelector(`[data-k="${key}"]`);
|
|
140
|
+
const y = (key) => { var _a, _b; return (_b = (_a = item(key)) === null || _a === void 0 ? void 0 : _a.offsetTop) !== null && _b !== void 0 ? _b : 0; };
|
|
141
|
+
const pill = side.querySelector('.pill');
|
|
142
|
+
if (pill)
|
|
143
|
+
pill.style.top = y(from) + (y(k) - y(from)) * eio(m) + 'px';
|
|
144
|
+
// the active item's colour blends across the move instead of switching
|
|
145
|
+
const w = eio(m);
|
|
146
|
+
side.querySelectorAll('.nv').forEach((d) => {
|
|
147
|
+
var _a;
|
|
148
|
+
const key = d.dataset.k;
|
|
149
|
+
const on = (key === k ? w : 0) + (key === from && from !== k ? 1 - w : 0);
|
|
150
|
+
const rest = d.classList.contains('sub')
|
|
151
|
+
? ((_a = theme.restSub) !== null && _a !== void 0 ? _a : theme.rest)
|
|
152
|
+
: theme.rest;
|
|
153
|
+
const c = rest.map((v, i) => Math.round(v + (theme.active[i] - v) * on));
|
|
154
|
+
d.classList.remove('on');
|
|
155
|
+
d.style.color = `rgb(${c.join(',')})`;
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
return { $, root, stage, rel, ring, nav };
|
|
159
|
+
}
|
|
160
|
+
/* The phone layout of a demo: its `mobile` block overrides the stage size and adds CSS. */
|
|
161
|
+
export function variant(def, phone) {
|
|
162
|
+
if (!phone || !def.mobile)
|
|
163
|
+
return def;
|
|
164
|
+
return Object.assign(Object.assign(Object.assign({}, def), def.mobile), { css: (def.css || '') + (def.mobile.css || '') });
|
|
165
|
+
}
|
|
166
|
+
/* Phones: screens narrower than Tailwind's sm breakpoint get a demo's phone layout. */
|
|
167
|
+
const PHONE = '(max-width: 639px)';
|
|
168
|
+
/*
|
|
169
|
+
* Mount a demo into `host` (a shadow root keeps its CSS off the page). With
|
|
170
|
+
* autoplay it scales to the host's width, uses the phone layout on narrow
|
|
171
|
+
* screens, loops while on screen, and holds the poster frame for reduced
|
|
172
|
+
* motion; without, it sits at native size for the renderer (`phone` picks the
|
|
173
|
+
* phone layout there).
|
|
174
|
+
*/
|
|
175
|
+
export function mount(host, def, { autoplay = true, phone = false, css = '', theme } = {}) {
|
|
176
|
+
var _a;
|
|
177
|
+
const root = host.shadowRoot || host.attachShadow({ mode: 'open' });
|
|
178
|
+
let scale = 1;
|
|
179
|
+
let v = def;
|
|
180
|
+
let stage;
|
|
181
|
+
let seek = () => { };
|
|
182
|
+
const build = (isPhone) => {
|
|
183
|
+
v = variant(def, isPhone);
|
|
184
|
+
root.innerHTML = `<style>${STAGE_CSS}${css}${v.css || ''}</style>
|
|
185
|
+
<div class="stage" style="width:${v.width}px;height:${v.height}px">${v.html}</div>`;
|
|
186
|
+
stage = root.querySelector('.stage');
|
|
187
|
+
const ctx = makeCtx(root, stage, () => scale, theme);
|
|
188
|
+
// each window's pill starts under the item its chrome marked active, so a
|
|
189
|
+
// demo that never calls nav() does not show it on the first item
|
|
190
|
+
if (theme)
|
|
191
|
+
root.querySelectorAll(theme.side).forEach((side) => {
|
|
192
|
+
const on = side.querySelector('.nv.on');
|
|
193
|
+
if ((on === null || on === void 0 ? void 0 : on.dataset.k) && side.parentNode)
|
|
194
|
+
ctx.nav(on.dataset.k, on.dataset.k, 1, side.parentNode);
|
|
195
|
+
});
|
|
196
|
+
const pose = v.setup(ctx);
|
|
197
|
+
// [data-loop] content fades in at the start of the loop and out at its end,
|
|
198
|
+
// so the wrap back to the first frame is a dissolve, not a jump.
|
|
199
|
+
const looped = [...root.querySelectorAll('[data-loop]')];
|
|
200
|
+
const total = v.total;
|
|
201
|
+
seek = (t) => {
|
|
202
|
+
pose(t);
|
|
203
|
+
const f = Math.min(seg(t, 0, 0.35), 1 - seg(t, total - 0.35, total));
|
|
204
|
+
looped.forEach((el) => (el.style.opacity = String(f)));
|
|
205
|
+
};
|
|
206
|
+
};
|
|
207
|
+
const poster = (_a = def.poster) !== null && _a !== void 0 ? _a : 0;
|
|
208
|
+
if (!autoplay) {
|
|
209
|
+
build(phone);
|
|
210
|
+
seek(poster);
|
|
211
|
+
return { seek: (t) => seek(t), destroy() { } };
|
|
212
|
+
}
|
|
213
|
+
const phoneQuery = matchMedia(PHONE);
|
|
214
|
+
const fit = () => {
|
|
215
|
+
scale = host.clientWidth / v.width || 1;
|
|
216
|
+
stage.style.transform = `scale(${scale})`;
|
|
217
|
+
};
|
|
218
|
+
build(phoneQuery.matches);
|
|
219
|
+
fit();
|
|
220
|
+
const ro = new ResizeObserver(fit);
|
|
221
|
+
ro.observe(host);
|
|
222
|
+
const reduce = matchMedia('(prefers-reduced-motion: reduce)');
|
|
223
|
+
let t = poster;
|
|
224
|
+
let started = false;
|
|
225
|
+
let last = null;
|
|
226
|
+
let raf = 0;
|
|
227
|
+
let visible = false;
|
|
228
|
+
seek(t);
|
|
229
|
+
const loop = (now) => {
|
|
230
|
+
if (last != null)
|
|
231
|
+
t = (t + Math.min(0.1, (now - last) / 1000)) % def.total;
|
|
232
|
+
last = now;
|
|
233
|
+
seek(t);
|
|
234
|
+
raf = requestAnimationFrame(loop);
|
|
235
|
+
};
|
|
236
|
+
const update = () => {
|
|
237
|
+
cancelAnimationFrame(raf);
|
|
238
|
+
last = null;
|
|
239
|
+
if (reduce.matches) {
|
|
240
|
+
t = poster;
|
|
241
|
+
seek(t);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (visible && !document.hidden) {
|
|
245
|
+
if (!started) {
|
|
246
|
+
started = true;
|
|
247
|
+
t = 0;
|
|
248
|
+
}
|
|
249
|
+
raf = requestAnimationFrame(loop);
|
|
250
|
+
}
|
|
251
|
+
else
|
|
252
|
+
seek(t);
|
|
253
|
+
};
|
|
254
|
+
const relayout = () => {
|
|
255
|
+
build(phoneQuery.matches);
|
|
256
|
+
fit();
|
|
257
|
+
update();
|
|
258
|
+
};
|
|
259
|
+
const io = new IntersectionObserver(([e]) => {
|
|
260
|
+
visible = e.isIntersecting;
|
|
261
|
+
update();
|
|
262
|
+
}, { threshold: 0.2 });
|
|
263
|
+
io.observe(host);
|
|
264
|
+
document.addEventListener('visibilitychange', update);
|
|
265
|
+
reduce.addEventListener('change', update);
|
|
266
|
+
phoneQuery.addEventListener('change', relayout);
|
|
267
|
+
return {
|
|
268
|
+
seek: (x) => seek(x),
|
|
269
|
+
destroy() {
|
|
270
|
+
cancelAnimationFrame(raf);
|
|
271
|
+
io.disconnect();
|
|
272
|
+
ro.disconnect();
|
|
273
|
+
document.removeEventListener('visibilitychange', update);
|
|
274
|
+
reduce.removeEventListener('change', update);
|
|
275
|
+
phoneQuery.removeEventListener('change', relayout);
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
package/hooks/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { useApiError } from './use-api-error';
|
|
2
2
|
export { useCrossAppLink, type CrossAppTarget, type OpenCrossAppOptions, type UseCrossAppLinkResult, } from './use-cross-app-link';
|
|
3
|
+
export { useGraphWrites } from './use-graph-writes';
|
|
3
4
|
export { useMediaQuery } from './use-media-query';
|
|
4
5
|
export { useStreamingQuery } from './use-streaming-query';
|
|
5
6
|
export { useToast, type ToastMessage } from './use-toast';
|
package/hooks/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { useApiError } from './use-api-error';
|
|
2
2
|
export { useCrossAppLink, } from './use-cross-app-link';
|
|
3
|
+
export { useGraphWrites } from './use-graph-writes';
|
|
3
4
|
export { useMediaQuery } from './use-media-query';
|
|
4
5
|
export { useStreamingQuery } from './use-streaming-query';
|
|
5
6
|
export { useToast } from './use-toast';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type GraphWrite } from '../lib/graph-writes';
|
|
2
|
+
/**
|
|
3
|
+
* Calls `onWrites` when the console changes the graph a page is showing, so
|
|
4
|
+
* the page can reload what it displays. Writes to other graphs are ignored.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useGraphWrites(graphId: string | null | undefined, onWrites: (writes: GraphWrite[]) => void): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { GRAPH_WRITES_EVENT, } from '../lib/graph-writes';
|
|
4
|
+
/**
|
|
5
|
+
* Calls `onWrites` when the console changes the graph a page is showing, so
|
|
6
|
+
* the page can reload what it displays. Writes to other graphs are ignored.
|
|
7
|
+
*/
|
|
8
|
+
export function useGraphWrites(graphId, onWrites) {
|
|
9
|
+
const onWritesRef = useRef(onWrites);
|
|
10
|
+
onWritesRef.current = onWrites;
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (!graphId)
|
|
13
|
+
return;
|
|
14
|
+
const handler = (event) => {
|
|
15
|
+
const detail = event.detail;
|
|
16
|
+
if ((detail === null || detail === void 0 ? void 0 : detail.graphId) === graphId)
|
|
17
|
+
onWritesRef.current(detail.writes);
|
|
18
|
+
};
|
|
19
|
+
window.addEventListener(GRAPH_WRITES_EVENT, handler);
|
|
20
|
+
return () => window.removeEventListener(GRAPH_WRITES_EVENT, handler);
|
|
21
|
+
}, [graphId]);
|
|
22
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ export * as AuthCore from './auth-core';
|
|
|
2
2
|
export { clearToken, getAuthHeader, getRefreshToken, getToken, getValidToken, handleAuthResponse, hasValidToken, storeToken, } from './auth-core/token-storage';
|
|
3
3
|
export { clearUserCookies, clearUserLocalStorage, clearUserSessionStorage, listUserData, performLogoutCleanup, resetUIState, } from './auth-core/cleanup';
|
|
4
4
|
export { AppSwitcher, AuthGuard, AuthProvider, LoginRedirector, SignInForm, SignUpForm, useAuth, } from './auth-components';
|
|
5
|
-
export { useApiError, useMediaQuery, useToast, useUser, useUserLimits, type ToastMessage, } from './hooks';
|
|
5
|
+
export { useApiError, useGraphWrites, useMediaQuery, useToast, useUser, useUserLimits, type ToastMessage, } from './hooks';
|
|
6
6
|
export { createGraphContext, createGraphProvider, createUseGraphContext, EntityProvider, GraphContext, GraphProvider, OrgProvider, ServiceOfferingsProvider, SidebarProvider, useEntity, useGraphContext, useOrg, useServiceOfferings, useSidebarContext, type EntityContextValue, type EntityProviderProps, type GraphContextValue, type GraphProviderProps, type GraphState, type OrgContextState, type OrgContextValue, type OrgProviderProps, type ServiceOfferings, } from './contexts';
|
|
7
|
-
export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, entityCookie, graphCookie, sidebarCookie, type EntityCookie, type GraphCookie, type McpConnectorUrl, type SidebarCookie, } from './lib';
|
|
7
|
+
export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, emitGraphWrites, entityCookie, GRAPH_WRITES_EVENT, graphCookie, readGraphWrites, sidebarCookie, type EntityCookie, type GraphCookie, type GraphWrite, type GraphWritesDetail, type McpConnectorUrl, type SidebarCookie, } from './lib';
|
|
8
8
|
export { ApiError, errorStatus, extractErrorDetail, isApiError, isSessionRejection, isTransientError, toApiError, unwrapSdk, type SdkResult, } from './lib/sdk-errors';
|
|
9
9
|
export { generateEntityUri, isUUID, UUID_REGEX } from './utils';
|
|
10
10
|
export { clearGraphSelection, getGraphSelection, persistGraphSelection, } from './actions/graph-actions';
|
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { clearUserCookies, clearUserLocalStorage, clearUserSessionStorage, listU
|
|
|
8
8
|
// Export auth-components
|
|
9
9
|
export { AppSwitcher, AuthGuard, AuthProvider, LoginRedirector, SignInForm, SignUpForm, useAuth, } from './auth-components';
|
|
10
10
|
// Export hooks
|
|
11
|
-
export { useApiError, useMediaQuery, useToast, useUser, useUserLimits, } from './hooks';
|
|
11
|
+
export { useApiError, useGraphWrites, useMediaQuery, useToast, useUser, useUserLimits, } from './hooks';
|
|
12
12
|
// Export contexts
|
|
13
13
|
export {
|
|
14
14
|
// Graph context exports
|
|
@@ -20,7 +20,7 @@ OrgProvider,
|
|
|
20
20
|
// Service offerings context
|
|
21
21
|
ServiceOfferingsProvider, SidebarProvider, useEntity, useGraphContext, useOrg, useServiceOfferings, useSidebarContext, } from './contexts';
|
|
22
22
|
// Export lib utilities
|
|
23
|
-
export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, entityCookie, graphCookie, sidebarCookie, } from './lib';
|
|
23
|
+
export { clientGraphCookie, clientSidebarCookie, createMcpConnectorUrl, emitGraphWrites, entityCookie, GRAPH_WRITES_EVENT, graphCookie, readGraphWrites, sidebarCookie, } from './lib';
|
|
24
24
|
// Export the SDK error seam (unwrap an SDK result or throw ApiError)
|
|
25
25
|
export { ApiError, errorStatus, extractErrorDetail, isApiError, isSessionRejection, isTransientError, toApiError, unwrapSdk, } from './lib/sdk-errors';
|
|
26
26
|
// Export utils
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A write an operator made, as the API reports it in `metadata.writes`.
|
|
3
|
+
* `id` is null when the tool returned no object id (for example a memory).
|
|
4
|
+
*/
|
|
5
|
+
export interface GraphWrite {
|
|
6
|
+
operation: string;
|
|
7
|
+
id: string | null;
|
|
8
|
+
name: string | null;
|
|
9
|
+
block_type?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface GraphWritesDetail {
|
|
12
|
+
graphId: string;
|
|
13
|
+
writes: GraphWrite[];
|
|
14
|
+
}
|
|
15
|
+
/** Window event fired after the console changes a graph. */
|
|
16
|
+
export declare const GRAPH_WRITES_EVENT = "robosystems:graph-writes";
|
|
17
|
+
/** Tell any page showing `graphId` that it changed. No-op when nothing was written. */
|
|
18
|
+
export declare function emitGraphWrites(graphId: string, writes: GraphWrite[]): void;
|
|
19
|
+
/** Read `metadata.writes` defensively: anything malformed yields no writes. */
|
|
20
|
+
export declare function readGraphWrites(metadata: unknown): GraphWrite[];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Window event fired after the console changes a graph. */
|
|
2
|
+
export const GRAPH_WRITES_EVENT = 'robosystems:graph-writes';
|
|
3
|
+
/** Tell any page showing `graphId` that it changed. No-op when nothing was written. */
|
|
4
|
+
export function emitGraphWrites(graphId, writes) {
|
|
5
|
+
if (typeof window === 'undefined' || writes.length === 0)
|
|
6
|
+
return;
|
|
7
|
+
window.dispatchEvent(new CustomEvent(GRAPH_WRITES_EVENT, {
|
|
8
|
+
detail: { graphId, writes },
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
/** Read `metadata.writes` defensively: anything malformed yields no writes. */
|
|
12
|
+
export function readGraphWrites(metadata) {
|
|
13
|
+
const writes = metadata === null || metadata === void 0 ? void 0 : metadata.writes;
|
|
14
|
+
if (!Array.isArray(writes))
|
|
15
|
+
return [];
|
|
16
|
+
return writes.filter((w) => typeof w === 'object' &&
|
|
17
|
+
w !== null &&
|
|
18
|
+
typeof w.operation === 'string');
|
|
19
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { clientSidebarCookie, sidebarCookie, type SidebarCookie, } from './sidebar-cookie';
|
|
2
2
|
export { clientGraphCookie, graphCookie, type GraphCookie, } from './graph-cookie';
|
|
3
3
|
export { entityCookie, type EntityCookie } from './entity-cookie';
|
|
4
|
+
export { GRAPH_WRITES_EVENT, emitGraphWrites, readGraphWrites, type GraphWrite, type GraphWritesDetail, } from './graph-writes';
|
|
4
5
|
export { createMcpConnectorUrl, type McpConnectorUrl } from './mcp-connector';
|
|
5
6
|
export { ApiError, errorStatus, extractErrorDetail, isApiError, isSessionRejection, isTransientError, toApiError, unwrapSdk, type SdkResult, } from './sdk-errors';
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { clientSidebarCookie, sidebarCookie, } from './sidebar-cookie';
|
|
2
2
|
export { clientGraphCookie, graphCookie, } from './graph-cookie';
|
|
3
3
|
export { entityCookie } from './entity-cookie';
|
|
4
|
+
export { GRAPH_WRITES_EVENT, emitGraphWrites, readGraphWrites, } from './graph-writes';
|
|
4
5
|
export { createMcpConnectorUrl } from './mcp-connector';
|
|
5
6
|
export { ApiError, errorStatus, extractErrorDetail, isApiError, isSessionRejection, isTransientError, toApiError, unwrapSdk, } from './sdk-errors';
|
package/package.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface LiveDemoProps {
|
|
2
|
+
/** Demo module name under /public/demos (e.g. "hero", "inbox"). */
|
|
3
|
+
name: string;
|
|
4
|
+
/** Stage aspect ratio as width / height, reserved before the demo loads. */
|
|
5
|
+
aspect: number;
|
|
6
|
+
/**
|
|
7
|
+
* The phone layout's aspect ratio (the demo module's `mobile` width / height),
|
|
8
|
+
* used below the sm breakpoint, where kit.js mounts that layout.
|
|
9
|
+
*/
|
|
10
|
+
phoneAspect?: number;
|
|
11
|
+
/** What the animation shows, for screen readers. */
|
|
12
|
+
label: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* An animated product demo from the app's /public/demos, mounted in a shadow root so its
|
|
17
|
+
* styles stay off the page. The demos are plain ES modules shared with the
|
|
18
|
+
* content machine's renderer, which shoots the same files for the social cuts;
|
|
19
|
+
* see demos/runtime.ts in this package. They loop only while on screen and hold one frame
|
|
20
|
+
* for reduced motion.
|
|
21
|
+
*/
|
|
22
|
+
export declare function LiveDemo({ name, aspect, label, phoneAspect, className, }: LiveDemoProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from 'react';
|
|
4
|
+
/**
|
|
5
|
+
* An animated product demo from the app's /public/demos, mounted in a shadow root so its
|
|
6
|
+
* styles stay off the page. The demos are plain ES modules shared with the
|
|
7
|
+
* content machine's renderer, which shoots the same files for the social cuts;
|
|
8
|
+
* see demos/runtime.ts in this package. They loop only while on screen and hold one frame
|
|
9
|
+
* for reduced motion.
|
|
10
|
+
*/
|
|
11
|
+
export function LiveDemo({ name, aspect, label, phoneAspect, className = '', }) {
|
|
12
|
+
const host = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let handle;
|
|
15
|
+
let cancelled = false;
|
|
16
|
+
const load = async () => {
|
|
17
|
+
// Served from /public at runtime, never bundled.
|
|
18
|
+
const base = '/demos/';
|
|
19
|
+
const [kit, demo] = await Promise.all([
|
|
20
|
+
import(
|
|
21
|
+
/* webpackIgnore: true */ /* turbopackIgnore: true */ base + 'kit.js'),
|
|
22
|
+
import(
|
|
23
|
+
/* webpackIgnore: true */ /* turbopackIgnore: true */ base +
|
|
24
|
+
name +
|
|
25
|
+
'.js'),
|
|
26
|
+
]);
|
|
27
|
+
if (cancelled || !host.current)
|
|
28
|
+
return;
|
|
29
|
+
handle = kit.mount(host.current, demo.default);
|
|
30
|
+
};
|
|
31
|
+
load().catch((err) => {
|
|
32
|
+
// The page stands without the demo, but a broken module should still show up.
|
|
33
|
+
console.error(`LiveDemo "${name}" failed to load`, err);
|
|
34
|
+
});
|
|
35
|
+
return () => {
|
|
36
|
+
cancelled = true;
|
|
37
|
+
handle === null || handle === void 0 ? void 0 : handle.destroy();
|
|
38
|
+
};
|
|
39
|
+
}, [name]);
|
|
40
|
+
return (_jsx("div", { ref: host, role: "img", "aria-label": label, className: `relative aspect-(--aspect) w-full overflow-hidden max-sm:aspect-(--phone-aspect) ${className}`, style: {
|
|
41
|
+
'--aspect': aspect,
|
|
42
|
+
'--phone-aspect': phoneAspect !== null && phoneAspect !== void 0 ? phoneAspect : aspect,
|
|
43
|
+
} }));
|
|
44
|
+
}
|
|
@@ -2,6 +2,7 @@ export { ACCOUNT_SETTINGS_PATH, useAccountSettingsLink, type AccountSettingsLink
|
|
|
2
2
|
export { CoreNavbar } from './CoreNavbar';
|
|
3
3
|
export { CoreSidebar, type SidebarItemData } from './CoreSidebar';
|
|
4
4
|
export { LandingFooter, type FooterLink, type LandingFooterProps, } from './LandingFooter';
|
|
5
|
+
export { LiveDemo, type LiveDemoProps } from './LiveDemo';
|
|
5
6
|
export { PageContainer } from './PageContainer';
|
|
6
7
|
export { PageHeader, type PageHeaderProps } from './PageHeader';
|
|
7
8
|
export { SettingsContainer } from './SettingsContainer';
|
|
@@ -2,6 +2,7 @@ export { ACCOUNT_SETTINGS_PATH, useAccountSettingsLink, } from './account-settin
|
|
|
2
2
|
export { CoreNavbar } from './CoreNavbar';
|
|
3
3
|
export { CoreSidebar } from './CoreSidebar';
|
|
4
4
|
export { LandingFooter, } from './LandingFooter';
|
|
5
|
+
export { LiveDemo } from './LiveDemo';
|
|
5
6
|
export { PageContainer } from './PageContainer';
|
|
6
7
|
export { PageHeader } from './PageHeader';
|
|
7
8
|
export { SettingsContainer } from './SettingsContainer';
|