chainlesschain 0.37.10 → 0.37.12
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/README.md +166 -10
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/init.js +184 -0
- package/src/commands/lowcode.js +320 -0
- package/src/commands/plugin.js +55 -2
- package/src/commands/sandbox.js +366 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +44 -0
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/plugin-manager.js +118 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +117 -112
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-Code App Builder — create, design, preview, publish, and manage
|
|
3
|
+
* low-code applications with built-in components and data sources.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/** @type {Map<string, object>} In-memory app cache */
|
|
9
|
+
const _apps = new Map();
|
|
10
|
+
|
|
11
|
+
/** @type {Map<string, object>} In-memory data sources */
|
|
12
|
+
const _dataSources = new Map();
|
|
13
|
+
|
|
14
|
+
/** @type {Map<string, object[]>} In-memory version history */
|
|
15
|
+
const _versions = new Map();
|
|
16
|
+
|
|
17
|
+
/** @type {object[]|null} Cached built-in component list */
|
|
18
|
+
let _components = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return the 15 built-in components, initializing on first call.
|
|
22
|
+
*
|
|
23
|
+
* @returns {object[]}
|
|
24
|
+
*/
|
|
25
|
+
export function listComponents() {
|
|
26
|
+
if (!_components) {
|
|
27
|
+
_components = [
|
|
28
|
+
{
|
|
29
|
+
name: "Form",
|
|
30
|
+
category: "input",
|
|
31
|
+
props: ["fields", "onSubmit", "validation"],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: "DataTable",
|
|
35
|
+
category: "display",
|
|
36
|
+
props: ["columns", "data", "pagination", "sortable"],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "BarChart",
|
|
40
|
+
category: "chart",
|
|
41
|
+
props: ["data", "xAxis", "yAxis", "colors"],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "LineChart",
|
|
45
|
+
category: "chart",
|
|
46
|
+
props: ["data", "xAxis", "yAxis", "smooth"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "PieChart",
|
|
50
|
+
category: "chart",
|
|
51
|
+
props: ["data", "labels", "colors"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "Dashboard",
|
|
55
|
+
category: "layout",
|
|
56
|
+
props: ["widgets", "layout", "refreshInterval"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "Button",
|
|
60
|
+
category: "input",
|
|
61
|
+
props: ["label", "onClick", "variant", "disabled"],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "TextInput",
|
|
65
|
+
category: "input",
|
|
66
|
+
props: ["placeholder", "value", "onChange", "type"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "Select",
|
|
70
|
+
category: "input",
|
|
71
|
+
props: ["options", "value", "onChange", "multiple"],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "Modal",
|
|
75
|
+
category: "overlay",
|
|
76
|
+
props: ["visible", "title", "onClose", "width"],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "Card",
|
|
80
|
+
category: "layout",
|
|
81
|
+
props: ["title", "content", "footer"],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "List",
|
|
85
|
+
category: "display",
|
|
86
|
+
props: ["items", "renderItem", "pagination"],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "Image",
|
|
90
|
+
category: "display",
|
|
91
|
+
props: ["src", "alt", "width", "height"],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "Tabs",
|
|
95
|
+
category: "layout",
|
|
96
|
+
props: ["tabs", "activeKey", "onChange"],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "Calendar",
|
|
100
|
+
category: "display",
|
|
101
|
+
props: ["events", "view", "onSelect"],
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
return _components;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Ensure low-code tables exist in the database.
|
|
110
|
+
*
|
|
111
|
+
* @param {object} db
|
|
112
|
+
*/
|
|
113
|
+
export function ensureLowcodeTables(db) {
|
|
114
|
+
db.exec(`
|
|
115
|
+
CREATE TABLE IF NOT EXISTS lowcode_apps (
|
|
116
|
+
id TEXT PRIMARY KEY,
|
|
117
|
+
name TEXT NOT NULL,
|
|
118
|
+
description TEXT,
|
|
119
|
+
design TEXT,
|
|
120
|
+
status TEXT DEFAULT 'draft',
|
|
121
|
+
version INTEGER DEFAULT 1,
|
|
122
|
+
platform TEXT DEFAULT 'web',
|
|
123
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
124
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
125
|
+
)
|
|
126
|
+
`);
|
|
127
|
+
db.exec(`
|
|
128
|
+
CREATE TABLE IF NOT EXISTS lowcode_datasources (
|
|
129
|
+
id TEXT PRIMARY KEY,
|
|
130
|
+
app_id TEXT NOT NULL,
|
|
131
|
+
name TEXT NOT NULL,
|
|
132
|
+
type TEXT NOT NULL,
|
|
133
|
+
config TEXT,
|
|
134
|
+
status TEXT DEFAULT 'active',
|
|
135
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
136
|
+
)
|
|
137
|
+
`);
|
|
138
|
+
db.exec(`
|
|
139
|
+
CREATE TABLE IF NOT EXISTS lowcode_versions (
|
|
140
|
+
id TEXT PRIMARY KEY,
|
|
141
|
+
app_id TEXT NOT NULL,
|
|
142
|
+
version INTEGER NOT NULL,
|
|
143
|
+
snapshot TEXT,
|
|
144
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
145
|
+
)
|
|
146
|
+
`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Create a new low-code application.
|
|
151
|
+
*
|
|
152
|
+
* @param {object} db
|
|
153
|
+
* @param {{ name: string, description?: string, platform?: string, design?: object }} definition
|
|
154
|
+
* @returns {{ id: string, name: string, status: string }}
|
|
155
|
+
*/
|
|
156
|
+
export function createApp(db, definition) {
|
|
157
|
+
const id = crypto.randomUUID().slice(0, 12);
|
|
158
|
+
const name = definition.name || "Untitled App";
|
|
159
|
+
const description = definition.description || "";
|
|
160
|
+
const platform = definition.platform || "web";
|
|
161
|
+
const design = definition.design || { components: [], layout: {} };
|
|
162
|
+
|
|
163
|
+
const stmt = db.prepare(
|
|
164
|
+
`INSERT INTO lowcode_apps (id, name, description, design, status, version, platform)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
166
|
+
);
|
|
167
|
+
stmt.run(id, name, description, JSON.stringify(design), "draft", 1, platform);
|
|
168
|
+
|
|
169
|
+
const app = {
|
|
170
|
+
id,
|
|
171
|
+
name,
|
|
172
|
+
description,
|
|
173
|
+
platform,
|
|
174
|
+
design,
|
|
175
|
+
status: "draft",
|
|
176
|
+
version: 1,
|
|
177
|
+
};
|
|
178
|
+
_apps.set(id, app);
|
|
179
|
+
|
|
180
|
+
// Create initial version snapshot
|
|
181
|
+
const versionId = crypto.randomUUID().slice(0, 12);
|
|
182
|
+
const vStmt = db.prepare(
|
|
183
|
+
`INSERT INTO lowcode_versions (id, app_id, version, snapshot)
|
|
184
|
+
VALUES (?, ?, ?, ?)`,
|
|
185
|
+
);
|
|
186
|
+
vStmt.run(versionId, id, 1, JSON.stringify(design));
|
|
187
|
+
|
|
188
|
+
if (!_versions.has(id)) _versions.set(id, []);
|
|
189
|
+
_versions
|
|
190
|
+
.get(id)
|
|
191
|
+
.push({ id: versionId, app_id: id, version: 1, snapshot: design });
|
|
192
|
+
|
|
193
|
+
return { id, name, status: "draft" };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Save a new design for an app, bump the version, and create a snapshot.
|
|
198
|
+
*
|
|
199
|
+
* @param {object} db
|
|
200
|
+
* @param {string} appId
|
|
201
|
+
* @param {object} design
|
|
202
|
+
* @returns {{ appId: string, version: number }}
|
|
203
|
+
*/
|
|
204
|
+
export function saveDesign(db, appId, design) {
|
|
205
|
+
// Get current version
|
|
206
|
+
const row = db
|
|
207
|
+
.prepare(`SELECT version FROM lowcode_apps WHERE id = ?`)
|
|
208
|
+
.get(appId);
|
|
209
|
+
const currentVersion = row ? row.version || 1 : 1;
|
|
210
|
+
const newVersion = currentVersion + 1;
|
|
211
|
+
|
|
212
|
+
db.prepare(
|
|
213
|
+
`UPDATE lowcode_apps SET design = ?, version = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
214
|
+
).run(JSON.stringify(design), newVersion, appId);
|
|
215
|
+
|
|
216
|
+
// Create version snapshot
|
|
217
|
+
const versionId = crypto.randomUUID().slice(0, 12);
|
|
218
|
+
db.prepare(
|
|
219
|
+
`INSERT INTO lowcode_versions (id, app_id, version, snapshot) VALUES (?, ?, ?, ?)`,
|
|
220
|
+
).run(versionId, appId, newVersion, JSON.stringify(design));
|
|
221
|
+
|
|
222
|
+
if (!_versions.has(appId)) _versions.set(appId, []);
|
|
223
|
+
_versions
|
|
224
|
+
.get(appId)
|
|
225
|
+
.push({
|
|
226
|
+
id: versionId,
|
|
227
|
+
app_id: appId,
|
|
228
|
+
version: newVersion,
|
|
229
|
+
snapshot: design,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
if (_apps.has(appId)) {
|
|
233
|
+
_apps.get(appId).design = design;
|
|
234
|
+
_apps.get(appId).version = newVersion;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { appId, version: newVersion };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get preview info for an application.
|
|
242
|
+
*
|
|
243
|
+
* @param {string} appId
|
|
244
|
+
* @returns {{ appId: string, design: object, previewUrl: string, platform: string }}
|
|
245
|
+
*/
|
|
246
|
+
export function previewApp(appId) {
|
|
247
|
+
const app = _apps.get(appId);
|
|
248
|
+
const design = app ? app.design : { components: [], layout: {} };
|
|
249
|
+
const platform = app ? app.platform : "web";
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
appId,
|
|
253
|
+
design,
|
|
254
|
+
previewUrl: `http://localhost:5173/lowcode/preview/${appId}`,
|
|
255
|
+
platform,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Publish an application (set status to 'published').
|
|
261
|
+
*
|
|
262
|
+
* @param {object} db
|
|
263
|
+
* @param {string} appId
|
|
264
|
+
* @returns {{ appId: string, status: string }}
|
|
265
|
+
*/
|
|
266
|
+
export function publishApp(db, appId) {
|
|
267
|
+
db.prepare(
|
|
268
|
+
`UPDATE lowcode_apps SET status = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
269
|
+
).run("published", appId);
|
|
270
|
+
|
|
271
|
+
if (_apps.has(appId)) {
|
|
272
|
+
_apps.get(appId).status = "published";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return { appId, status: "published" };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Add a data source to an application.
|
|
280
|
+
*
|
|
281
|
+
* @param {object} db
|
|
282
|
+
* @param {string} appId
|
|
283
|
+
* @param {string} name
|
|
284
|
+
* @param {string} type - e.g. "rest", "graphql", "database", "csv"
|
|
285
|
+
* @param {object} config
|
|
286
|
+
* @returns {{ id: string, appId: string, name: string, type: string }}
|
|
287
|
+
*/
|
|
288
|
+
export function addDataSource(db, appId, name, type, config) {
|
|
289
|
+
const id = crypto.randomUUID().slice(0, 12);
|
|
290
|
+
const stmt = db.prepare(
|
|
291
|
+
`INSERT INTO lowcode_datasources (id, app_id, name, type, config, status)
|
|
292
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
293
|
+
);
|
|
294
|
+
stmt.run(id, appId, name, type, JSON.stringify(config || {}), "active");
|
|
295
|
+
|
|
296
|
+
_dataSources.set(id, { id, appId, name, type, config, status: "active" });
|
|
297
|
+
|
|
298
|
+
return { id, appId, name, type };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Get version history for an application.
|
|
303
|
+
*
|
|
304
|
+
* @param {string} appId
|
|
305
|
+
* @returns {object[]}
|
|
306
|
+
*/
|
|
307
|
+
export function getVersions(appId) {
|
|
308
|
+
return _versions.get(appId) || [];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Rollback an application to a previous version.
|
|
313
|
+
*
|
|
314
|
+
* @param {object} db
|
|
315
|
+
* @param {string} appId
|
|
316
|
+
* @param {number} version
|
|
317
|
+
* @returns {{ appId: string, version: number, restored: boolean }}
|
|
318
|
+
*/
|
|
319
|
+
export function rollbackApp(db, appId, version) {
|
|
320
|
+
const versions = _versions.get(appId) || [];
|
|
321
|
+
const target = versions.find((v) => v.version === version);
|
|
322
|
+
|
|
323
|
+
if (!target) {
|
|
324
|
+
return { appId, version, restored: false };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const design = target.snapshot;
|
|
328
|
+
db.prepare(
|
|
329
|
+
`UPDATE lowcode_apps SET design = ?, version = ?, updated_at = datetime('now') WHERE id = ?`,
|
|
330
|
+
).run(JSON.stringify(design), version, appId);
|
|
331
|
+
|
|
332
|
+
if (_apps.has(appId)) {
|
|
333
|
+
_apps.get(appId).design = design;
|
|
334
|
+
_apps.get(appId).version = version;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return { appId, version, restored: true };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Export an app definition with data sources.
|
|
342
|
+
*
|
|
343
|
+
* @param {string} appId
|
|
344
|
+
* @returns {{ appId: string, app: object|null, dataSources: object[], versions: object[] }}
|
|
345
|
+
*/
|
|
346
|
+
export function exportApp(appId) {
|
|
347
|
+
const app = _apps.get(appId) || null;
|
|
348
|
+
const dataSources = [];
|
|
349
|
+
for (const [, ds] of _dataSources) {
|
|
350
|
+
if (ds.appId === appId) dataSources.push(ds);
|
|
351
|
+
}
|
|
352
|
+
const versions = _versions.get(appId) || [];
|
|
353
|
+
|
|
354
|
+
return { appId, app, dataSources, versions };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* List all applications from the database.
|
|
359
|
+
*
|
|
360
|
+
* @param {object} db
|
|
361
|
+
* @returns {object[]}
|
|
362
|
+
*/
|
|
363
|
+
export function listApps(db) {
|
|
364
|
+
const rows = db
|
|
365
|
+
.prepare(`SELECT * FROM lowcode_apps ORDER BY updated_at DESC`)
|
|
366
|
+
.all();
|
|
367
|
+
return rows.map((r) => ({
|
|
368
|
+
id: r.id,
|
|
369
|
+
name: r.name,
|
|
370
|
+
description: r.description,
|
|
371
|
+
status: r.status,
|
|
372
|
+
version: r.version,
|
|
373
|
+
platform: r.platform,
|
|
374
|
+
created_at: r.created_at,
|
|
375
|
+
updated_at: r.updated_at,
|
|
376
|
+
}));
|
|
377
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BI Engine — Business intelligence with NL→SQL queries, dashboards,
|
|
3
|
+
* reports, anomaly detection, trend prediction, and scheduling.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/* ── In-memory stores ──────────────────────────────────────── */
|
|
9
|
+
const _dashboards = new Map();
|
|
10
|
+
const _reports = new Map();
|
|
11
|
+
const _scheduledReports = new Map();
|
|
12
|
+
|
|
13
|
+
const _templates = [
|
|
14
|
+
{
|
|
15
|
+
id: "tpl-kpi",
|
|
16
|
+
name: "KPI Dashboard",
|
|
17
|
+
description: "Key performance indicators overview",
|
|
18
|
+
widgets: ["metric-card", "sparkline", "gauge"],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "tpl-sales",
|
|
22
|
+
name: "Sales Report",
|
|
23
|
+
description: "Sales pipeline and revenue analysis",
|
|
24
|
+
widgets: ["bar-chart", "funnel", "table"],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "tpl-ops",
|
|
28
|
+
name: "Operations Dashboard",
|
|
29
|
+
description: "System health and operational metrics",
|
|
30
|
+
widgets: ["heatmap", "timeline", "alert-list"],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "tpl-hr",
|
|
34
|
+
name: "HR Analytics",
|
|
35
|
+
description: "Workforce analytics and headcount",
|
|
36
|
+
widgets: ["pie-chart", "trend-line", "scorecard"],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "tpl-finance",
|
|
40
|
+
name: "Financial Overview",
|
|
41
|
+
description: "Revenue, expenses, and cash flow",
|
|
42
|
+
widgets: ["waterfall", "stacked-bar", "summary-table"],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
/* ── Schema ────────────────────────────────────────────────── */
|
|
47
|
+
|
|
48
|
+
export function ensureBITables(db) {
|
|
49
|
+
db.exec(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS bi_dashboards (
|
|
51
|
+
id TEXT PRIMARY KEY,
|
|
52
|
+
name TEXT NOT NULL,
|
|
53
|
+
widgets TEXT,
|
|
54
|
+
layout TEXT,
|
|
55
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
56
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
db.exec(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS bi_reports (
|
|
61
|
+
id TEXT PRIMARY KEY,
|
|
62
|
+
name TEXT NOT NULL,
|
|
63
|
+
query TEXT,
|
|
64
|
+
result TEXT,
|
|
65
|
+
format TEXT DEFAULT 'pdf',
|
|
66
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
67
|
+
)
|
|
68
|
+
`);
|
|
69
|
+
db.exec(`
|
|
70
|
+
CREATE TABLE IF NOT EXISTS bi_scheduled (
|
|
71
|
+
id TEXT PRIMARY KEY,
|
|
72
|
+
report_id TEXT NOT NULL,
|
|
73
|
+
cron TEXT NOT NULL,
|
|
74
|
+
recipients TEXT,
|
|
75
|
+
last_run TEXT,
|
|
76
|
+
status TEXT DEFAULT 'active'
|
|
77
|
+
)
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ── NL → SQL Query ────────────────────────────────────────── */
|
|
82
|
+
|
|
83
|
+
export function nlQuery(query) {
|
|
84
|
+
if (!query || typeof query !== "string") {
|
|
85
|
+
throw new Error("Query must be a non-empty string");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Mock NL→SQL translation: generate a SELECT LIKE query
|
|
89
|
+
const sanitized = query.replace(/['"]/g, "").trim();
|
|
90
|
+
const words = sanitized.split(/\s+/).slice(0, 3).join("_").toLowerCase();
|
|
91
|
+
const generatedSQL = `SELECT * FROM data WHERE content LIKE '%${words}%'`;
|
|
92
|
+
|
|
93
|
+
const id = crypto.randomUUID();
|
|
94
|
+
const results = [];
|
|
95
|
+
const visualization = {
|
|
96
|
+
type: "table",
|
|
97
|
+
title: query,
|
|
98
|
+
columns: ["id", "content", "value"],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
id,
|
|
103
|
+
query,
|
|
104
|
+
generatedSQL,
|
|
105
|
+
results,
|
|
106
|
+
rowCount: results.length,
|
|
107
|
+
visualization,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* ── Reports ───────────────────────────────────────────────── */
|
|
112
|
+
|
|
113
|
+
export function generateReport(db, name, options) {
|
|
114
|
+
const id = crypto.randomUUID();
|
|
115
|
+
const now = new Date().toISOString();
|
|
116
|
+
const format = (options && options.format) || "pdf";
|
|
117
|
+
|
|
118
|
+
const sectionNames = (options && options.sections) || [
|
|
119
|
+
"summary",
|
|
120
|
+
"details",
|
|
121
|
+
"conclusion",
|
|
122
|
+
];
|
|
123
|
+
const sections = sectionNames.map((s) => ({
|
|
124
|
+
name: s,
|
|
125
|
+
content: `Auto-generated ${s} section`,
|
|
126
|
+
generatedAt: now,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
const report = {
|
|
130
|
+
id,
|
|
131
|
+
name,
|
|
132
|
+
format,
|
|
133
|
+
sections,
|
|
134
|
+
generatedAt: now,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
_reports.set(id, report);
|
|
138
|
+
|
|
139
|
+
db.prepare(
|
|
140
|
+
`INSERT INTO bi_reports (id, name, query, result, format, created_at)
|
|
141
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
142
|
+
).run(id, name, "", JSON.stringify(report), format, now);
|
|
143
|
+
|
|
144
|
+
return report;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── Dashboards ────────────────────────────────────────────── */
|
|
148
|
+
|
|
149
|
+
export function createDashboard(db, name, widgets, layout) {
|
|
150
|
+
const id = crypto.randomUUID();
|
|
151
|
+
const now = new Date().toISOString();
|
|
152
|
+
|
|
153
|
+
const dashboard = {
|
|
154
|
+
id,
|
|
155
|
+
name,
|
|
156
|
+
widgets: widgets || [],
|
|
157
|
+
layout: layout || { type: "grid", columns: 2 },
|
|
158
|
+
createdAt: now,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
_dashboards.set(id, dashboard);
|
|
162
|
+
|
|
163
|
+
db.prepare(
|
|
164
|
+
`INSERT INTO bi_dashboards (id, name, widgets, layout, created_at, updated_at)
|
|
165
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
166
|
+
).run(
|
|
167
|
+
id,
|
|
168
|
+
name,
|
|
169
|
+
JSON.stringify(dashboard.widgets),
|
|
170
|
+
JSON.stringify(dashboard.layout),
|
|
171
|
+
now,
|
|
172
|
+
now,
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
return dashboard;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* ── Anomaly Detection ─────────────────────────────────────── */
|
|
179
|
+
|
|
180
|
+
export function detectAnomaly(data, options) {
|
|
181
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
182
|
+
throw new Error("Data must be a non-empty array of numbers");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const threshold = (options && options.threshold) || 2;
|
|
186
|
+
const n = data.length;
|
|
187
|
+
const mean = data.reduce((s, v) => s + v, 0) / n;
|
|
188
|
+
const variance = data.reduce((s, v) => s + (v - mean) ** 2, 0) / n;
|
|
189
|
+
const std = Math.sqrt(variance);
|
|
190
|
+
|
|
191
|
+
const anomalies = [];
|
|
192
|
+
if (std > 0) {
|
|
193
|
+
for (let i = 0; i < data.length; i++) {
|
|
194
|
+
const zScore = Math.abs((data[i] - mean) / std);
|
|
195
|
+
if (zScore > threshold) {
|
|
196
|
+
anomalies.push({ index: i, value: data[i], zScore });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return { anomalies, mean, std, threshold };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ── Trend Prediction ──────────────────────────────────────── */
|
|
205
|
+
|
|
206
|
+
export function predictTrend(data, periods) {
|
|
207
|
+
if (!Array.isArray(data) || data.length < 2) {
|
|
208
|
+
throw new Error("Data must be an array with at least 2 points");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const n = data.length;
|
|
212
|
+
const periodsToPredict = periods || 3;
|
|
213
|
+
|
|
214
|
+
// Simple linear regression: y = slope * x + intercept
|
|
215
|
+
let sumX = 0;
|
|
216
|
+
let sumY = 0;
|
|
217
|
+
let sumXY = 0;
|
|
218
|
+
let sumX2 = 0;
|
|
219
|
+
|
|
220
|
+
for (let i = 0; i < n; i++) {
|
|
221
|
+
sumX += i;
|
|
222
|
+
sumY += data[i];
|
|
223
|
+
sumXY += i * data[i];
|
|
224
|
+
sumX2 += i * i;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
|
|
228
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
229
|
+
|
|
230
|
+
const predictions = [];
|
|
231
|
+
for (let i = 0; i < periodsToPredict; i++) {
|
|
232
|
+
const x = n + i;
|
|
233
|
+
predictions.push(Math.round((slope * x + intercept) * 100) / 100);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
let trend;
|
|
237
|
+
if (Math.abs(slope) < 0.001) {
|
|
238
|
+
trend = "flat";
|
|
239
|
+
} else if (slope > 0) {
|
|
240
|
+
trend = "up";
|
|
241
|
+
} else {
|
|
242
|
+
trend = "down";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return { predictions, trend, slope: Math.round(slope * 1000) / 1000 };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* ── Templates ─────────────────────────────────────────────── */
|
|
249
|
+
|
|
250
|
+
export function listTemplates() {
|
|
251
|
+
return [..._templates];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* ── Scheduling ────────────────────────────────────────────── */
|
|
255
|
+
|
|
256
|
+
export function scheduleReport(db, reportId, cron, recipients) {
|
|
257
|
+
const id = crypto.randomUUID();
|
|
258
|
+
const schedule = {
|
|
259
|
+
id,
|
|
260
|
+
reportId,
|
|
261
|
+
cron,
|
|
262
|
+
recipients: recipients || [],
|
|
263
|
+
lastRun: null,
|
|
264
|
+
status: "active",
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
_scheduledReports.set(id, schedule);
|
|
268
|
+
|
|
269
|
+
db.prepare(
|
|
270
|
+
`INSERT INTO bi_scheduled (id, report_id, cron, recipients, last_run, status)
|
|
271
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
272
|
+
).run(id, reportId, cron, JSON.stringify(schedule.recipients), "", "active");
|
|
273
|
+
|
|
274
|
+
return schedule;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/* ── Export ─────────────────────────────────────────────────── */
|
|
278
|
+
|
|
279
|
+
export function exportReport(reportId, format) {
|
|
280
|
+
const report = _reports.get(reportId);
|
|
281
|
+
if (!report) throw new Error(`Report not found: ${reportId}`);
|
|
282
|
+
|
|
283
|
+
const exportFormat = format || report.format || "pdf";
|
|
284
|
+
return {
|
|
285
|
+
reportId,
|
|
286
|
+
format: exportFormat,
|
|
287
|
+
filename: `${report.name.replace(/\s+/g, "_")}.${exportFormat}`,
|
|
288
|
+
size: 0,
|
|
289
|
+
exportedAt: new Date().toISOString(),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ── Reset (for testing) ───────────────────────────────────── */
|
|
294
|
+
|
|
295
|
+
export function _resetState() {
|
|
296
|
+
_dashboards.clear();
|
|
297
|
+
_reports.clear();
|
|
298
|
+
_scheduledReports.clear();
|
|
299
|
+
}
|