yaml-flow 5.0.0 → 5.2.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/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +103 -24
- package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
- package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
- package/browser/board-livegraph-engine.js.map +1 -0
- package/browser/live-cards.js +362 -38
- package/browser/live-cards.schema.json +20 -4
- package/dist/board-livegraph-runtime/index.cjs +210 -14
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +49 -5
- package/dist/board-livegraph-runtime/index.d.ts +49 -5
- package/dist/board-livegraph-runtime/index.js +209 -15
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +63 -7
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +2 -2
- package/dist/card-compute/index.d.ts +2 -2
- package/dist/card-compute/index.js +63 -7
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +664 -75
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +33 -5
- package/dist/cli/board-live-cards-cli.d.ts +33 -5
- package/dist/cli/board-live-cards-cli.js +661 -76
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
- package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
- package/dist/continuous-event-graph/index.cjs +47 -14
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +9 -9
- package/dist/continuous-event-graph/index.d.ts +9 -9
- package/dist/continuous-event-graph/index.js +47 -14
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +29 -12
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +29 -12
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +93 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +93 -20
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +29 -12
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +29 -12
- package/dist/inference/index.js.map +1 -1
- package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
- package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
- package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
- package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
- package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
- package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
- package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
- package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
- package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
- package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
- package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
- package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
- package/examples/example-board/agent-instructions-cardlayout.md +28 -0
- package/examples/example-board/agent-instructions.md +603 -0
- package/examples/example-board/cards/card-concentration.json +42 -0
- package/examples/example-board/cards/card-market-prices.json +51 -0
- package/examples/example-board/cards/card-portfolio-action.json +19 -0
- package/examples/example-board/cards/card-portfolio-risks.json +19 -0
- package/examples/example-board/cards/card-portfolio-value.json +62 -0
- package/examples/example-board/cards/card-portfolio.json +44 -0
- package/examples/example-board/demo-chat-handler.js +373 -33
- package/examples/example-board/demo-server-config.json +5 -0
- package/examples/example-board/demo-server.js +83 -7
- package/examples/example-board/demo-shell-browser.html +75 -207
- package/examples/example-board/demo-shell-with-server.html +14 -9
- package/examples/example-board/demo-shell.html +1 -1
- package/examples/example-board/demo-task-executor.js +259 -41
- package/package.json +6 -2
- package/schema/live-cards.schema.json +20 -4
- package/browser/board-livegraph-runtime.js.map +0 -1
- package/examples/example-board/board.yaml +0 -23
- package/examples/example-board/bootstrap_payload.json +0 -1
- package/examples/example-board/cards/card-chain-region-alert.json +0 -39
- package/examples/example-board/cards/card-chain-region-totals.json +0 -26
- package/examples/example-board/cards/card-chain-top-region.json +0 -24
- package/examples/example-board/cards/card-ex-actions.json +0 -32
- package/examples/example-board/cards/card-ex-chart.json +0 -30
- package/examples/example-board/cards/card-ex-filter.json +0 -36
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
- package/examples/example-board/cards/card-ex-form.json +0 -91
- package/examples/example-board/cards/card-ex-list.json +0 -22
- package/examples/example-board/cards/card-ex-markdown.json +0 -17
- package/examples/example-board/cards/card-ex-metric.json +0 -19
- package/examples/example-board/cards/card-ex-narrative.json +0 -36
- package/examples/example-board/cards/card-ex-source-http.json +0 -28
- package/examples/example-board/cards/card-ex-source.json +0 -21
- package/examples/example-board/cards/card-ex-status.json +0 -35
- package/examples/example-board/cards/card-ex-table.json +0 -30
- package/examples/example-board/cards/card-ex-todo.json +0 -29
- package/examples/example-board/mock.db +0 -15
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
|
@@ -1,19 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import http from 'node:http';
|
|
4
|
+
import fs from 'node:fs';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
6
8
|
|
|
7
9
|
import {
|
|
8
10
|
createMultiBoardServerRuntime,
|
|
9
11
|
createRuntimeRequestDispatcher,
|
|
10
12
|
isRuntimeRoute,
|
|
11
|
-
} from '
|
|
13
|
+
} from 'yaml-flow/board-livecards-server-runtime';
|
|
12
14
|
|
|
13
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
16
|
const __dirname = path.dirname(__filename);
|
|
17
|
+
const _require = createRequire(import.meta.url);
|
|
18
|
+
|
|
19
|
+
function resolveYamlFlowDir() {
|
|
20
|
+
try {
|
|
21
|
+
return path.dirname(_require.resolve('yaml-flow/package.json'));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const _yamlFlowDir = resolveYamlFlowDir();
|
|
28
|
+
const _pkgCliJs = _yamlFlowDir ? path.join(_yamlFlowDir, 'board-live-cards-cli.js') : null;
|
|
29
|
+
const _pkgStepMachineCli = _yamlFlowDir ? path.join(_yamlFlowDir, 'step-machine-cli.js') : null;
|
|
30
|
+
|
|
31
|
+
function loadServerConfig() {
|
|
32
|
+
const configPath = path.join(__dirname, 'demo-server-config.json');
|
|
33
|
+
if (!fs.existsSync(configPath)) return {};
|
|
34
|
+
try {
|
|
35
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function resolveFromConfig(configValue) {
|
|
44
|
+
if (typeof configValue !== 'string' || !configValue.trim()) return null;
|
|
45
|
+
return path.resolve(__dirname, configValue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const serverConfig = loadServerConfig();
|
|
49
|
+
const configuredCliJs = resolveFromConfig(serverConfig.boardLiveCardsCliJs) || _pkgCliJs;
|
|
50
|
+
const configuredTaskExecutorPath = resolveFromConfig(serverConfig.taskExecutorPath || serverConfig.demoTaskExecutorPath);
|
|
51
|
+
const configuredStepMachineCliPath = resolveFromConfig(serverConfig.stepMachineCliPath) || _pkgStepMachineCli;
|
|
52
|
+
const configuredChatHandlerPath = resolveFromConfig(serverConfig.chatHandlerPath);
|
|
53
|
+
const configuredInferenceAdapterPath = resolveFromConfig(serverConfig.inferenceAdapterPath);
|
|
54
|
+
|
|
55
|
+
if (!process.env.BOARD_LIVE_CARDS_CLI_JS && configuredCliJs) {
|
|
56
|
+
process.env.BOARD_LIVE_CARDS_CLI_JS = configuredCliJs;
|
|
57
|
+
}
|
|
58
|
+
if (!process.env.DEMO_STEP_MACHINE_CLI_PATH && configuredStepMachineCliPath) {
|
|
59
|
+
process.env.DEMO_STEP_MACHINE_CLI_PATH = configuredStepMachineCliPath;
|
|
60
|
+
}
|
|
61
|
+
if (!process.env.DEMO_CHAT_HANDLER_PATH && configuredChatHandlerPath) {
|
|
62
|
+
process.env.DEMO_CHAT_HANDLER_PATH = configuredChatHandlerPath;
|
|
63
|
+
}
|
|
64
|
+
if (!process.env.DEMO_INFERENCE_ADAPTER_PATH && configuredInferenceAdapterPath) {
|
|
65
|
+
process.env.DEMO_INFERENCE_ADAPTER_PATH = configuredInferenceAdapterPath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const PORT = Number(process.env.DEMO_SERVER_PORT || serverConfig.port || 7799);
|
|
69
|
+
const RESET_ON_START = process.argv.includes('--reset');
|
|
15
70
|
|
|
16
|
-
const PORT = Number(process.env.DEMO_SERVER_PORT || 7799);
|
|
17
71
|
const CORS_HEADERS = {
|
|
18
72
|
'Access-Control-Allow-Origin': '*',
|
|
19
73
|
'Access-Control-Allow-Headers': 'content-type,x-file-name',
|
|
@@ -22,9 +76,26 @@ const CORS_HEADERS = {
|
|
|
22
76
|
|
|
23
77
|
const runtime = createMultiBoardServerRuntime({
|
|
24
78
|
apiBasePath: '/api/boards',
|
|
25
|
-
|
|
79
|
+
defaultCardsDir: path.join(__dirname, 'cards'),
|
|
80
|
+
defaultTaskExecutorPath: process.env.DEMO_TASK_EXECUTOR_PATH || configuredTaskExecutorPath || path.join(__dirname, 'demo-task-executor.js'),
|
|
81
|
+
defaultStepMachineCliPath: process.env.DEMO_STEP_MACHINE_CLI_PATH || configuredStepMachineCliPath,
|
|
82
|
+
defaultChatHandlerPath: process.env.DEMO_CHAT_HANDLER_PATH || configuredChatHandlerPath || path.join(__dirname, 'demo-chat-handler.js'),
|
|
83
|
+
defaultInferenceAdapterPath: process.env.DEMO_INFERENCE_ADAPTER_PATH || configuredInferenceAdapterPath || null,
|
|
84
|
+
boardLiveCardsCliJs: process.env.BOARD_LIVE_CARDS_CLI_JS || configuredCliJs,
|
|
26
85
|
});
|
|
27
86
|
|
|
87
|
+
function resetRuntime() {
|
|
88
|
+
const setupDir = runtime.setupDir;
|
|
89
|
+
if (fs.existsSync(setupDir)) {
|
|
90
|
+
fs.rmSync(setupDir, { recursive: true, force: true });
|
|
91
|
+
console.log(`[demo-server] reset: wiped ${setupDir}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (RESET_ON_START) {
|
|
96
|
+
resetRuntime();
|
|
97
|
+
}
|
|
98
|
+
|
|
28
99
|
const dispatch = createRuntimeRequestDispatcher(runtime);
|
|
29
100
|
|
|
30
101
|
// Board-id segment regex: /api/boards/:boardId/...
|
|
@@ -37,11 +108,16 @@ function jsonReply(res, status, payload) {
|
|
|
37
108
|
}
|
|
38
109
|
|
|
39
110
|
async function handleDemoSetup(req, res, boardId) {
|
|
40
|
-
const url = new URL(req.url, 'http://localhost');
|
|
41
|
-
const reset = String(url.searchParams.get('reset') || '').toLowerCase() === 'true';
|
|
42
111
|
try {
|
|
43
|
-
const
|
|
44
|
-
|
|
112
|
+
const { service, boardRoot } = runtime.requireBoardService(boardId);
|
|
113
|
+
let setupPerformed = false;
|
|
114
|
+
|
|
115
|
+
if (!fs.existsSync(path.join(boardRoot, 'surface', 'tmp-cards'))) {
|
|
116
|
+
service.ensureDemoSetup();
|
|
117
|
+
setupPerformed = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
jsonReply(res, 200, { ok: true, setupPerformed });
|
|
45
121
|
} catch (err) {
|
|
46
122
|
jsonReply(res, err.statusCode || 500, { error: String((err && err.message) || err) });
|
|
47
123
|
}
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
<title>Example Board Demo (Browser Runtime)</title>
|
|
7
7
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
9
|
-
<script src="
|
|
10
|
-
<script src="
|
|
11
|
-
<script src="
|
|
12
|
-
<script src="./reusable-runtime-artifacts-adapter.js"></script>
|
|
9
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/card-compute.js"></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/live-cards.js"></script>
|
|
11
|
+
<script src="https://cdn.jsdelivr.net/npm/yaml-flow/browser/board-livegraph-engine.js"></script>
|
|
13
12
|
</head>
|
|
14
13
|
<body class="bg-light">
|
|
15
14
|
<div class="container-fluid py-3">
|
|
@@ -63,50 +62,44 @@
|
|
|
63
62
|
console.error(message);
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
// Get
|
|
65
|
+
// Get exports from board-livegraph-engine
|
|
67
66
|
const LocalStorageService = (window.BoardLiveGraph && window.BoardLiveGraph.LocalStorageService);
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const buildLiveCardModelsFromArtifacts = (window.BoardLiveGraph && window.BoardLiveGraph.buildLiveCardModelsFromArtifacts);
|
|
68
|
+
const buildBrowserArtifactsFromRuntime = (window.BoardLiveGraph && window.BoardLiveGraph.buildBrowserArtifactsFromRuntime);
|
|
69
|
+
if (!LocalStorageService || !buildLiveCardModelsFromArtifacts || !buildBrowserArtifactsFromRuntime) {
|
|
70
|
+
failInit('board-livegraph-engine.js not loaded. Run "npm run build:browser" first.');
|
|
70
71
|
return;
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// ========================================================================
|
|
74
|
-
// Demo Setup
|
|
75
|
+
// Demo Setup — Portfolio Domain
|
|
75
76
|
// ========================================================================
|
|
76
77
|
const CARD_FILES = [
|
|
77
|
-
'card-
|
|
78
|
-
'card-
|
|
79
|
-
'card-
|
|
80
|
-
'card-
|
|
81
|
-
'card-
|
|
82
|
-
'card-
|
|
83
|
-
'card-ex-table.json',
|
|
84
|
-
'card-ex-status.json',
|
|
85
|
-
'card-ex-markdown.json',
|
|
86
|
-
'card-ex-narrative.json',
|
|
87
|
-
'card-ex-todo.json',
|
|
88
|
-
'card-ex-form.json',
|
|
89
|
-
'card-ex-filtered-by-preference.json',
|
|
90
|
-
'card-ex-actions.json',
|
|
91
|
-
'card-chain-region-totals.json',
|
|
92
|
-
'card-chain-top-region.json',
|
|
93
|
-
'card-chain-region-alert.json',
|
|
78
|
+
'card-portfolio.json',
|
|
79
|
+
'card-market-prices.json',
|
|
80
|
+
'card-portfolio-value.json',
|
|
81
|
+
'card-concentration.json',
|
|
82
|
+
'card-portfolio-risks.json',
|
|
83
|
+
'card-portfolio-action.json',
|
|
94
84
|
];
|
|
95
85
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
86
|
+
const QUOTES_SEED = {
|
|
87
|
+
quoteResponse: {
|
|
88
|
+
result: [
|
|
89
|
+
{ symbol: 'AAPL', shortName: 'Apple Inc.', regularMarketPrice: 180, regularMarketChange: 2.5, regularMarketChangePercent: 1.41 },
|
|
90
|
+
{ symbol: 'MSFT', shortName: 'Microsoft', regularMarketPrice: 420, regularMarketChange: -1.2, regularMarketChangePercent: -0.28 },
|
|
91
|
+
{ symbol: 'GOOGL', shortName: 'Alphabet', regularMarketPrice: 165, regularMarketChange: 0.8, regularMarketChangePercent: 0.49 },
|
|
92
|
+
{ symbol: 'TSLA', shortName: 'Tesla', regularMarketPrice: 250, regularMarketChange: -5.0, regularMarketChangePercent: -1.96 },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const MOCK_ANALYSIS = {
|
|
98
|
+
mix: '- AAPL dominates at 36%\n- Tech-heavy portfolio',
|
|
99
|
+
pnl: '- Best: MSFT +35%\n- Worst: GOOGL -41%',
|
|
100
|
+
risks: '- AAPL: earnings risk\n- TSLA: high volatility',
|
|
101
|
+
action: '- Trim GOOGL — below cost basis and deteriorating momentum',
|
|
102
|
+
};
|
|
110
103
|
|
|
111
104
|
const clone = (x) => JSON.parse(JSON.stringify(x));
|
|
112
105
|
const nowIso = () => new Date().toISOString();
|
|
@@ -135,10 +128,6 @@
|
|
|
135
128
|
localStorage.setItem(mockFsKey(cardId, dirName), JSON.stringify(Array.isArray(items) ? items : []));
|
|
136
129
|
}
|
|
137
130
|
|
|
138
|
-
function clearMockDir(cardId, dirName) {
|
|
139
|
-
localStorage.removeItem(mockFsKey(cardId, dirName));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
131
|
function toFileMetadataOnly(entry) {
|
|
143
132
|
if (!entry || typeof entry !== 'object') return null;
|
|
144
133
|
return {
|
|
@@ -155,8 +144,7 @@
|
|
|
155
144
|
const input = String(name || '').trim();
|
|
156
145
|
if (!input) return 'upload.bin';
|
|
157
146
|
const parts = input.split(/[/\\]/);
|
|
158
|
-
|
|
159
|
-
return base;
|
|
147
|
+
return parts[parts.length - 1] || 'upload.bin';
|
|
160
148
|
}
|
|
161
149
|
|
|
162
150
|
function normalizeStem(rawStem) {
|
|
@@ -209,13 +197,11 @@
|
|
|
209
197
|
keepExt = '';
|
|
210
198
|
stemBudget = maxLen - prefix.length;
|
|
211
199
|
}
|
|
212
|
-
|
|
213
200
|
const stem = stemNorm.slice(0, Math.max(1, stemBudget));
|
|
214
201
|
let candidate = `${prefix}${stem}${keepExt}`;
|
|
215
202
|
if (candidate.length > maxLen) {
|
|
216
203
|
candidate = candidate.slice(0, maxLen).replace(/\.$/, '');
|
|
217
204
|
}
|
|
218
|
-
|
|
219
205
|
if (!names.includes(candidate)) return candidate;
|
|
220
206
|
serial += 1;
|
|
221
207
|
}
|
|
@@ -236,9 +222,7 @@
|
|
|
236
222
|
}
|
|
237
223
|
|
|
238
224
|
function readPersistedFileMetadata(cardId) {
|
|
239
|
-
return readMockDir(cardId, 'files')
|
|
240
|
-
.map(toFileMetadataOnly)
|
|
241
|
-
.filter(Boolean);
|
|
225
|
+
return readMockDir(cardId, 'files').map(toFileMetadataOnly).filter(Boolean);
|
|
242
226
|
}
|
|
243
227
|
|
|
244
228
|
function readPersistedChatMessages(cardId) {
|
|
@@ -262,8 +246,7 @@
|
|
|
262
246
|
let binary = '';
|
|
263
247
|
const chunkSize = 0x8000;
|
|
264
248
|
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
265
|
-
|
|
266
|
-
binary += String.fromCharCode.apply(null, chunk);
|
|
249
|
+
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunkSize));
|
|
267
250
|
}
|
|
268
251
|
return btoa(binary);
|
|
269
252
|
}
|
|
@@ -282,12 +265,8 @@
|
|
|
282
265
|
path: `${cardId}/files/${storedName}`,
|
|
283
266
|
uploaded_at: now,
|
|
284
267
|
};
|
|
285
|
-
|
|
286
268
|
const filesDir = readMockDir(cardId, 'files');
|
|
287
|
-
filesDir.push({
|
|
288
|
-
...metadata,
|
|
289
|
-
content_base64: await fileToBase64(fileLike),
|
|
290
|
-
});
|
|
269
|
+
filesDir.push({ ...metadata, content_base64: await fileToBase64(fileLike) });
|
|
291
270
|
writeMockDir(cardId, 'files', filesDir);
|
|
292
271
|
return metadata;
|
|
293
272
|
}
|
|
@@ -303,9 +282,8 @@
|
|
|
303
282
|
path: fileLike.path || `${cardId}/files/${storedName}`,
|
|
304
283
|
uploaded_at: fileLike.uploaded_at || now,
|
|
305
284
|
};
|
|
306
|
-
|
|
307
285
|
const filesDir = readMockDir(cardId, 'files');
|
|
308
|
-
const knownPaths = new Set(filesDir.map((
|
|
286
|
+
const knownPaths = new Set(filesDir.map((e) => e && e.path).filter(Boolean));
|
|
309
287
|
if (!knownPaths.has(metadata.path)) {
|
|
310
288
|
filesDir.push(metadata);
|
|
311
289
|
writeMockDir(cardId, 'files', filesDir);
|
|
@@ -313,32 +291,13 @@
|
|
|
313
291
|
return metadata;
|
|
314
292
|
}
|
|
315
293
|
|
|
316
|
-
if (typeof fileLike === 'string') {
|
|
317
|
-
const displayName = normalizeDisplayFileName(fileLike);
|
|
318
|
-
const storedName = buildStoredFileName(cardId, displayName);
|
|
319
|
-
const metadata = {
|
|
320
|
-
name: displayName,
|
|
321
|
-
stored_name: storedName,
|
|
322
|
-
size: null,
|
|
323
|
-
mime_type: 'application/octet-stream',
|
|
324
|
-
path: `${cardId}/files/${storedName}`,
|
|
325
|
-
uploaded_at: now,
|
|
326
|
-
};
|
|
327
|
-
const filesDir = readMockDir(cardId, 'files');
|
|
328
|
-
filesDir.push(metadata);
|
|
329
|
-
writeMockDir(cardId, 'files', filesDir);
|
|
330
|
-
return metadata;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
294
|
return null;
|
|
334
295
|
}
|
|
335
296
|
|
|
336
297
|
function appendChatRecord(cardId, record) {
|
|
337
298
|
const chatsDir = readMockDir(cardId, 'chats');
|
|
338
299
|
const next = { ...(record || {}) };
|
|
339
|
-
if (!next.path)
|
|
340
|
-
next.path = nextChatPath(cardId, next.role || 'system');
|
|
341
|
-
}
|
|
300
|
+
if (!next.path) next.path = nextChatPath(cardId, next.role || 'system');
|
|
342
301
|
chatsDir.push(next);
|
|
343
302
|
writeMockDir(cardId, 'chats', chatsDir);
|
|
344
303
|
}
|
|
@@ -348,11 +307,9 @@
|
|
|
348
307
|
if (!el) return;
|
|
349
308
|
|
|
350
309
|
const tasks = runtimeState && runtimeState.state && runtimeState.state.tasks
|
|
351
|
-
? runtimeState.state.tasks
|
|
352
|
-
: {};
|
|
310
|
+
? runtimeState.state.tasks : {};
|
|
353
311
|
const availableOutputs = runtimeState && runtimeState.state && Array.isArray(runtimeState.state.availableOutputs)
|
|
354
|
-
? runtimeState.state.availableOutputs
|
|
355
|
-
: [];
|
|
312
|
+
? runtimeState.state.availableOutputs : [];
|
|
356
313
|
|
|
357
314
|
const byStatus = {};
|
|
358
315
|
for (const task of Object.values(tasks)) {
|
|
@@ -361,38 +318,20 @@
|
|
|
361
318
|
}
|
|
362
319
|
|
|
363
320
|
const cardRuntimeById = artifacts && artifacts.cardRuntimeById ? artifacts.cardRuntimeById : {};
|
|
364
|
-
const metric = cardRuntimeById['card-ex-metric'] && cardRuntimeById['card-ex-metric'].computed_values
|
|
365
|
-
? cardRuntimeById['card-ex-metric'].computed_values.totalRevenue
|
|
366
|
-
: undefined;
|
|
367
|
-
const topProducts = cardRuntimeById['card-ex-list'] && cardRuntimeById['card-ex-list'].computed_values
|
|
368
|
-
? cardRuntimeById['card-ex-list'].computed_values.topProducts
|
|
369
|
-
: undefined;
|
|
370
|
-
const regionCounts = cardRuntimeById['card-ex-chart'] && cardRuntimeById['card-ex-chart'].computed_values
|
|
371
|
-
? cardRuntimeById['card-ex-chart'].computed_values.regionCounts
|
|
372
|
-
: undefined;
|
|
373
|
-
const filteredOrders = cardRuntimeById['card-ex-table'] && cardRuntimeById['card-ex-table'].computed_values
|
|
374
|
-
? cardRuntimeById['card-ex-table'].computed_values.filtered
|
|
375
|
-
: undefined;
|
|
376
|
-
|
|
377
|
-
const watched = {
|
|
378
|
-
orders: availableOutputs.includes('orders'),
|
|
379
|
-
prices: availableOutputs.includes('prices'),
|
|
380
|
-
selections: availableOutputs.includes('selections'),
|
|
381
|
-
formToken: availableOutputs.includes('card-ex-form'),
|
|
382
|
-
};
|
|
383
321
|
|
|
384
|
-
const
|
|
322
|
+
const portfolioValue = cardRuntimeById['card-portfolio-value'] && cardRuntimeById['card-portfolio-value'].computed_values;
|
|
323
|
+
const totalValue = portfolioValue && portfolioValue.totalValue;
|
|
324
|
+
const totalGain = portfolioValue && portfolioValue.totalGain;
|
|
325
|
+
const positions = portfolioValue && portfolioValue.positions;
|
|
326
|
+
|
|
327
|
+
el.textContent = [
|
|
385
328
|
`updatedAt: ${new Date().toISOString()}`,
|
|
386
329
|
`statusCounts: ${JSON.stringify(byStatus)}`,
|
|
387
330
|
`availableOutputs(${availableOutputs.length}): ${availableOutputs.slice().sort().join(', ')}`,
|
|
388
|
-
`
|
|
389
|
-
`card-
|
|
390
|
-
`card-
|
|
391
|
-
|
|
392
|
-
`card-ex-table.filtered.length: ${Array.isArray(filteredOrders) ? filteredOrders.length : 'n/a'}`,
|
|
393
|
-
];
|
|
394
|
-
|
|
395
|
-
el.textContent = lines.join('\n');
|
|
331
|
+
`card-portfolio-value.totalValue: ${JSON.stringify(totalValue)}`,
|
|
332
|
+
`card-portfolio-value.totalGain: ${JSON.stringify(totalGain)}`,
|
|
333
|
+
`card-portfolio-value.positions.length: ${Array.isArray(positions) ? positions.length : 'n/a'}`,
|
|
334
|
+
].join('\n');
|
|
396
335
|
}
|
|
397
336
|
|
|
398
337
|
function hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById) {
|
|
@@ -403,44 +342,30 @@
|
|
|
403
342
|
const artifact = cardRuntimeById[card.id];
|
|
404
343
|
if (!artifact || typeof artifact !== 'object') return false;
|
|
405
344
|
if (card.sources && card.sources.length > 0) {
|
|
406
|
-
if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources)))
|
|
407
|
-
return false;
|
|
408
|
-
}
|
|
345
|
+
if (!(artifact.fetched_sources && typeof artifact.fetched_sources === 'object' && !Array.isArray(artifact.fetched_sources))) return false;
|
|
409
346
|
}
|
|
410
347
|
if (card.requires && card.requires.length > 0) {
|
|
411
|
-
if (!(artifact.requires && typeof artifact.requires === 'object' && !Array.isArray(artifact.requires)))
|
|
412
|
-
return false;
|
|
413
|
-
}
|
|
348
|
+
if (!(artifact.requires && typeof artifact.requires === 'object' && !Array.isArray(artifact.requires))) return false;
|
|
414
349
|
}
|
|
415
350
|
return true;
|
|
416
351
|
});
|
|
417
352
|
}
|
|
418
353
|
|
|
419
|
-
function parseBoardMeta(yamlText) {
|
|
420
|
-
const nameMatch = yamlText.match(/^name:\s*(.+)$/m);
|
|
421
|
-
const descMatch = yamlText.match(/^desc:\s*(.+)$/m);
|
|
422
|
-
return {
|
|
423
|
-
name: nameMatch ? nameMatch[1].trim() : 'Example Board',
|
|
424
|
-
desc: descMatch ? descMatch[1].trim() : '',
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
|
|
428
354
|
async function loadBoardFromFiles() {
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
355
|
+
const boardMeta = {
|
|
356
|
+
name: 'Portfolio Tracker',
|
|
357
|
+
desc: 'Track your stock portfolio with live market prices and AI-powered risk analysis.',
|
|
358
|
+
};
|
|
359
|
+
const cards = await Promise.all(
|
|
360
|
+
CARD_FILES.map(name => fetch(`./cards/${name}`).then(r => r.json()))
|
|
361
|
+
);
|
|
433
362
|
|
|
434
|
-
const boardMeta = parseBoardMeta(boardYaml);
|
|
435
363
|
for (const c of cards) {
|
|
436
364
|
c.card_data = c.card_data || {};
|
|
437
365
|
const persistedFiles = readPersistedFileMetadata(c.id);
|
|
438
366
|
if (persistedFiles.length > 0) c.card_data.files = persistedFiles;
|
|
439
|
-
|
|
440
367
|
const persistedChats = readPersistedChatMessages(c.id);
|
|
441
368
|
if (persistedChats.length > 0) c.card_data.messages = persistedChats;
|
|
442
|
-
|
|
443
|
-
// Persist card definition to localStorage (mirrors server's tmp/cards/<id>.json)
|
|
444
369
|
LocalStorageService.writeCard(c.id, c);
|
|
445
370
|
}
|
|
446
371
|
|
|
@@ -448,22 +373,19 @@
|
|
|
448
373
|
}
|
|
449
374
|
|
|
450
375
|
function makeMockSourceServer() {
|
|
451
|
-
let
|
|
452
|
-
let
|
|
376
|
+
let quoteData = clone(QUOTES_SEED);
|
|
377
|
+
let mockAnalysis = clone(MOCK_ANALYSIS);
|
|
453
378
|
|
|
454
379
|
return {
|
|
455
380
|
fetchSource: async function (card, sourceDef) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
if (
|
|
460
|
-
if (card.id === 'card-ex-source' || script.includes('fetch-orders')) return clone(orderRows);
|
|
461
|
-
if (card.id === 'card-ex-source-http' || script.includes('fetch-prices')) return clone(priceRows);
|
|
381
|
+
// chartApi source with mock="quotes" → return mock quote data
|
|
382
|
+
if (sourceDef && sourceDef.mock === 'quotes') return clone(quoteData);
|
|
383
|
+
// copilot source → return mock analysis object
|
|
384
|
+
if (sourceDef && sourceDef.copilot) return clone(mockAnalysis);
|
|
462
385
|
return null;
|
|
463
386
|
},
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
if (Array.isArray(nextPrices)) priceRows = clone(nextPrices);
|
|
387
|
+
setQuotes: function (nextQuotes) {
|
|
388
|
+
if (nextQuotes && typeof nextQuotes === 'object') quoteData = clone(nextQuotes);
|
|
467
389
|
},
|
|
468
390
|
};
|
|
469
391
|
}
|
|
@@ -498,24 +420,10 @@
|
|
|
498
420
|
return `${hh}:${mm}:${ss}`;
|
|
499
421
|
}
|
|
500
422
|
|
|
501
|
-
function compactFiles(files) {
|
|
502
|
-
if (!Array.isArray(files)) return [];
|
|
503
|
-
return files
|
|
504
|
-
.map((f) => {
|
|
505
|
-
if (!f) return null;
|
|
506
|
-
if (typeof f === 'string') return { name: f };
|
|
507
|
-
if (typeof f === 'object' && typeof f.name === 'string') return { name: f.name, size: f.size || null };
|
|
508
|
-
return null;
|
|
509
|
-
})
|
|
510
|
-
.filter(Boolean);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
423
|
async function handleActionInBrowserRuntime(id, actionType, payload) {
|
|
514
424
|
const node = nodesById[id];
|
|
515
425
|
if (!node) return;
|
|
516
|
-
|
|
517
426
|
const now = nowIso();
|
|
518
|
-
const cardData = node.card_data || {};
|
|
519
427
|
|
|
520
428
|
if (actionType === 'chat-send') {
|
|
521
429
|
const text = payload && typeof payload.text === 'string' ? payload.text.trim() : '';
|
|
@@ -528,25 +436,18 @@
|
|
|
528
436
|
if (persisted) fileMetas.push(persisted);
|
|
529
437
|
}
|
|
530
438
|
|
|
531
|
-
appendChatRecord(id, {
|
|
532
|
-
at: now,
|
|
533
|
-
role: 'user',
|
|
534
|
-
text,
|
|
535
|
-
files: fileMetas,
|
|
536
|
-
});
|
|
439
|
+
appendChatRecord(id, { at: now, role: 'user', text, files: fileMetas });
|
|
537
440
|
|
|
538
441
|
fileMetas.forEach((fileMeta) => {
|
|
539
442
|
if (!fileMeta || !fileMeta.stored_name) return;
|
|
540
|
-
const display = fileMeta.name || 'file';
|
|
541
443
|
appendChatRecord(id, {
|
|
542
444
|
at: now,
|
|
543
445
|
role: 'system',
|
|
544
|
-
text: `File ${
|
|
446
|
+
text: `File ${fileMeta.name || 'file'} uploaded as ${fileMeta.stored_name}.`,
|
|
545
447
|
files: [],
|
|
546
448
|
});
|
|
547
449
|
});
|
|
548
450
|
|
|
549
|
-
// Keep chat render state in sync with local-storage-backed chat entries.
|
|
550
451
|
runtime.patchCardState(id, {
|
|
551
452
|
messages: readPersistedChatMessages(id),
|
|
552
453
|
lastInteractionAt: now,
|
|
@@ -557,7 +458,6 @@
|
|
|
557
458
|
if (actionType === 'action') {
|
|
558
459
|
const buttonId = payload && typeof payload.buttonId === 'string' ? payload.buttonId : '';
|
|
559
460
|
if (!buttonId) return;
|
|
560
|
-
|
|
561
461
|
runtime.patchCardState(id, {
|
|
562
462
|
lastAction: { buttonId, at: now },
|
|
563
463
|
lastActionText: `${buttonId} @ ${toIsoTimeTag(now)}`,
|
|
@@ -601,6 +501,7 @@
|
|
|
601
501
|
|
|
602
502
|
const cardDefinitions = loaded.cards;
|
|
603
503
|
const cardDefsById = Object.fromEntries(cardDefinitions.map(c => [c.id, c]));
|
|
504
|
+
|
|
604
505
|
const deepSet = (obj, path, value) => {
|
|
605
506
|
if (!path) return;
|
|
606
507
|
const parts = String(path).split('.');
|
|
@@ -624,11 +525,10 @@
|
|
|
624
525
|
},
|
|
625
526
|
});
|
|
626
527
|
|
|
627
|
-
// Restore previous runtime artifacts from localStorage
|
|
528
|
+
// Restore previous runtime artifacts from localStorage
|
|
628
529
|
const cardRuntimeById = LocalStorageService.readAllComputedArtifacts(cardDefinitions.map(c => c.id));
|
|
629
530
|
let statusSnapshot = LocalStorageService.readStatusSnapshot();
|
|
630
531
|
|
|
631
|
-
// Build initial renderable nodes from artifacts (same pattern as server shell)
|
|
632
532
|
const initialArtifacts = statusSnapshot && Object.keys(cardRuntimeById).length > 0 && hasCompatiblePersistedArtifacts(cardDefinitions, cardRuntimeById)
|
|
633
533
|
? { cardDefinitions, statusSnapshot, cardRuntimeById }
|
|
634
534
|
: buildBrowserArtifactsFromRuntime({
|
|
@@ -644,8 +544,6 @@
|
|
|
644
544
|
const engine = LiveCard.init({
|
|
645
545
|
resolve: (id) => nodesById[id],
|
|
646
546
|
onPatchState: (id, patch) => {
|
|
647
|
-
// Form submissions pass { fieldValues: {...} } from LiveCard form elements.
|
|
648
|
-
// Persist those values in localStorage card at the card's writeTo path.
|
|
649
547
|
if (patch && Object.keys(patch).length === 1 && patch.fieldValues) {
|
|
650
548
|
const cardDef = cardDefsById[id];
|
|
651
549
|
let writeTo = null;
|
|
@@ -657,43 +555,21 @@
|
|
|
657
555
|
}
|
|
658
556
|
}
|
|
659
557
|
}
|
|
660
|
-
|
|
661
558
|
const statePatch = {};
|
|
662
559
|
if (typeof writeTo === 'string' && writeTo.startsWith('card_data.')) {
|
|
663
560
|
deepSet(statePatch, writeTo.slice('card_data.'.length), patch.fieldValues);
|
|
664
561
|
} else {
|
|
665
562
|
Object.assign(statePatch, patch.fieldValues);
|
|
666
563
|
}
|
|
667
|
-
|
|
668
|
-
// Apply patch to card definition in memory and in localStorage
|
|
669
564
|
const cardInMem = cardDefsById[id];
|
|
670
565
|
if (cardInMem) {
|
|
671
566
|
deepSet(cardInMem, 'card_data', { ...cardInMem.card_data, ...statePatch });
|
|
672
567
|
LocalStorageService.writeCard(id, cardInMem);
|
|
673
568
|
}
|
|
674
|
-
|
|
675
|
-
// Also patch the runtime (which may be used by compute)
|
|
676
569
|
runtime.patchCardState(id, statePatch);
|
|
677
570
|
return;
|
|
678
571
|
}
|
|
679
572
|
|
|
680
|
-
// Auto-commit: file-upload element reports staged files via _stagedFiles.
|
|
681
|
-
// Persist metadata immediately and update the rendered list — no commit button needed.
|
|
682
|
-
if (patch && Array.isArray(patch._stagedFiles) && patch._stagedFiles.length > 0) {
|
|
683
|
-
const now = nowIso();
|
|
684
|
-
const existing = readPersistedFileMetadata(id);
|
|
685
|
-
const seen = new Set(existing.map((f) => f && f.name ? f.name : String(f)));
|
|
686
|
-
for (const item of patch._stagedFiles) {
|
|
687
|
-
if (!item || typeof item.name !== 'string') continue;
|
|
688
|
-
if (seen.has(item.name)) continue;
|
|
689
|
-
existing.push(persistStagedFileMetadata(id, item));
|
|
690
|
-
seen.add(item.name);
|
|
691
|
-
}
|
|
692
|
-
writeStoredFiles(id, existing);
|
|
693
|
-
runtime.patchCardState(id, { files: existing, _stagedFiles: [] });
|
|
694
|
-
return;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
573
|
runtime.patchCardState(id, patch || {});
|
|
698
574
|
},
|
|
699
575
|
onRefresh: (id) => runtime.retrigger(id),
|
|
@@ -701,8 +577,7 @@
|
|
|
701
577
|
void handleActionInBrowserRuntime(id, actionType, payload || {});
|
|
702
578
|
},
|
|
703
579
|
getChatMessages: (id) => {
|
|
704
|
-
|
|
705
|
-
return items.map((m) => ({
|
|
580
|
+
return readPersistedChatMessages(id).map((m) => ({
|
|
706
581
|
role: m && typeof m.role === 'string' ? m.role : 'system',
|
|
707
582
|
text: m && typeof m.text === 'string' ? m.text : '',
|
|
708
583
|
files: Array.isArray(m && m.files) ? m.files : [],
|
|
@@ -718,7 +593,6 @@
|
|
|
718
593
|
|
|
719
594
|
runtimeUnsub = runtime.subscribe(function () {
|
|
720
595
|
const runtimeState = runtime.getState();
|
|
721
|
-
// Build runtime artifacts (mirrors what CLI does after compute)
|
|
722
596
|
const artifacts = buildBrowserArtifactsFromRuntime({
|
|
723
597
|
boardPath: 'browser',
|
|
724
598
|
cardDefinitions,
|
|
@@ -726,28 +600,23 @@
|
|
|
726
600
|
graphState: runtimeState,
|
|
727
601
|
});
|
|
728
602
|
|
|
729
|
-
|
|
730
|
-
for (const [id, artifact] of Object.entries(artifacts.cardRuntimeById)) {
|
|
603
|
+
for (const [, artifact] of Object.entries(artifacts.cardRuntimeById)) {
|
|
731
604
|
LocalStorageService.writeComputedArtifact(artifact);
|
|
732
605
|
}
|
|
733
606
|
LocalStorageService.writeStatusSnapshot(artifacts.statusSnapshot);
|
|
734
607
|
|
|
735
|
-
// Rebuild renderable nodes from persisted artifacts (same pattern as server shell)
|
|
736
608
|
syncBoardNodes(buildLiveCardModelsFromArtifacts(artifacts));
|
|
737
|
-
|
|
738
|
-
// Keep a compact runtime snapshot visible for debugging token flow issues.
|
|
739
609
|
renderDebugPanel(runtimeState, artifacts);
|
|
740
610
|
});
|
|
741
611
|
|
|
742
612
|
runtime.push({ type: 'inject-tokens', tokens: [], timestamp: nowIso() });
|
|
743
|
-
// Force a full recompute pass to recover from stale/incomplete localStorage artifacts.
|
|
744
613
|
runtime.retriggerAll();
|
|
745
614
|
|
|
746
615
|
window.demoLiveGraph = {
|
|
747
616
|
mode: 'browser',
|
|
748
617
|
runtime,
|
|
749
|
-
|
|
750
|
-
mockServer.
|
|
618
|
+
setQuotes: function (nextQuotes) {
|
|
619
|
+
mockServer.setQuotes(nextQuotes);
|
|
751
620
|
runtime.retriggerAll();
|
|
752
621
|
},
|
|
753
622
|
clearLocalStorage: function () {
|
|
@@ -786,7 +655,6 @@
|
|
|
786
655
|
setDebugPanelVisibility(this.checked);
|
|
787
656
|
});
|
|
788
657
|
|
|
789
|
-
// Keep debug UI out of the way unless Dev Mode is explicitly enabled.
|
|
790
658
|
setDebugPanelVisibility(document.getElementById('devModeToggle').checked);
|
|
791
659
|
}
|
|
792
660
|
|