ltcai 3.0.1 → 3.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/README.md +54 -21
- package/docs/CHANGELOG.md +90 -0
- package/docs/V3_2_AUDIT.md +82 -0
- package/docs/V3_FRONTEND.md +20 -17
- package/docs/architecture.md +6 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agent_registry.py +103 -0
- package/latticeai/api/auth.py +4 -1
- package/latticeai/api/hooks.py +113 -0
- package/latticeai/api/marketplace.py +13 -0
- package/latticeai/api/memory.py +109 -0
- package/latticeai/api/search.py +4 -0
- package/latticeai/core/agent_registry.py +234 -0
- package/latticeai/core/config.py +2 -0
- package/latticeai/core/embedding_providers.py +123 -0
- package/latticeai/core/hooks.py +284 -0
- package/latticeai/core/marketplace.py +87 -2
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +63 -6
- package/latticeai/services/memory_service.py +324 -0
- package/package.json +9 -4
- package/scripts/build_v3_assets.mjs +164 -0
- package/scripts/capture/README.md +28 -0
- package/scripts/capture/capture_enterprise.js +8 -0
- package/scripts/capture/capture_graph.js +8 -0
- package/scripts/capture/capture_onboarding.js +8 -0
- package/scripts/capture/capture_page.js +43 -0
- package/scripts/capture/capture_release_media.js +125 -0
- package/scripts/capture/capture_skills.js +8 -0
- package/scripts/capture/capture_workspace.js +8 -0
- package/scripts/generate_diagrams.py +513 -0
- package/scripts/lint_v3.mjs +33 -0
- package/scripts/release-0.3.1.sh +105 -0
- package/scripts/take_screenshots.js +69 -0
- package/scripts/validate_release_artifacts.py +167 -0
- package/static/account.html +9 -9
- package/static/activity.html +4 -4
- package/static/admin.html +8 -8
- package/static/agents.html +4 -4
- package/static/chat.html +9 -9
- package/static/css/tokens.5a595671.css +260 -0
- package/static/css/tokens.css +1 -1
- package/static/graph.html +9 -9
- package/static/plugins.html +4 -4
- package/static/sw.js +3 -1
- package/static/v3/asset-manifest.json +55 -0
- package/static/v3/css/lattice.base.e4cdd05d.css +128 -0
- package/static/v3/css/lattice.components.011e988b.css +447 -0
- package/static/v3/css/lattice.components.css +2 -2
- package/static/v3/css/lattice.shell.4920f42d.css +407 -0
- package/static/v3/css/lattice.tokens.c597ff81.css +132 -0
- package/static/v3/css/lattice.views.3ee19d4e.css +277 -0
- package/static/v3/index.html +38 -9
- package/static/v3/js/app.a5adc0f3.js +26 -0
- package/static/v3/js/core/api.603b978f.js +408 -0
- package/static/v3/js/core/api.js +132 -51
- package/static/v3/js/core/components.4c83e0a9.js +222 -0
- package/static/v3/js/core/components.js +9 -2
- package/static/v3/js/core/dom.a2773eb0.js +148 -0
- package/static/v3/js/core/router.584570f2.js +37 -0
- package/static/v3/js/core/routes.07ad6696.js +89 -0
- package/static/v3/js/core/routes.js +17 -1
- package/static/v3/js/core/shell.ea0b9ae5.js +363 -0
- package/static/v3/js/core/store.34ebd5e6.js +113 -0
- package/static/v3/js/views/admin-audit.660a1fb1.js +185 -0
- package/static/v3/js/views/admin-audit.js +1 -1
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +177 -0
- package/static/v3/js/views/admin-permissions.js +4 -5
- package/static/v3/js/views/admin-policies.3658fd86.js +102 -0
- package/static/v3/js/views/admin-policies.js +4 -5
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +135 -0
- package/static/v3/js/views/admin-private-vpc.js +2 -5
- package/static/v3/js/views/admin-security.07c66b72.js +180 -0
- package/static/v3/js/views/admin-security.js +4 -5
- package/static/v3/js/views/admin-users.03bac88c.js +168 -0
- package/static/v3/js/views/admin-users.js +6 -6
- package/static/v3/js/views/agents.c373d48c.js +293 -0
- package/static/v3/js/views/agents.js +101 -2
- package/static/v3/js/views/chat.718144ce.js +449 -0
- package/static/v3/js/views/chat.js +2 -3
- package/static/v3/js/views/files.4935197e.js +186 -0
- package/static/v3/js/views/files.js +27 -21
- package/static/v3/js/views/home.cdde3b32.js +119 -0
- package/static/v3/js/views/hooks.f3edebca.js +99 -0
- package/static/v3/js/views/hooks.js +99 -0
- package/static/v3/js/views/hybrid-search.b22b97e0.js +195 -0
- package/static/v3/js/views/hybrid-search.js +1 -1
- package/static/v3/js/views/knowledge-graph.a14ea7e7.js +237 -0
- package/static/v3/js/views/knowledge-graph.js +2 -3
- package/static/v3/js/views/marketplace.ab0583d4.js +141 -0
- package/static/v3/js/views/marketplace.js +141 -0
- package/static/v3/js/views/mcp.99b5c6a7.js +114 -0
- package/static/v3/js/views/mcp.js +114 -0
- package/static/v3/js/views/memory.d2ed7a7c.js +146 -0
- package/static/v3/js/views/memory.js +146 -0
- package/static/v3/js/views/models.a1ffa147.js +256 -0
- package/static/v3/js/views/models.js +17 -8
- package/static/v3/js/views/my-computer.1b2ff621.js +237 -0
- package/static/v3/js/views/my-computer.js +5 -5
- package/static/v3/js/views/pipeline.c522f1ce.js +157 -0
- package/static/v3/js/views/pipeline.js +3 -7
- package/static/v3/js/views/planning.9ac3e313.js +153 -0
- package/static/v3/js/views/planning.js +153 -0
- package/static/v3/js/views/settings.4f777210.js +250 -0
- package/static/v3/js/views/settings.js +6 -14
- package/static/v3/js/views/skills.c6c2f965.js +109 -0
- package/static/v3/js/views/skills.js +109 -0
- package/static/v3/js/views/tools.e4f11276.js +108 -0
- package/static/v3/js/views/tools.js +108 -0
- package/static/v3/js/views/workflows.26c57290.js +128 -0
- package/static/v3/js/views/workflows.js +128 -0
- package/static/workflows.html +4 -4
- package/static/workspace.html +5 -5
- package/docs/images/tmp_frames/frame_00.png +0 -0
- package/docs/images/tmp_frames/frame_01.png +0 -0
- package/docs/images/tmp_frames/frame_02.png +0 -0
- package/docs/images/tmp_frames/frame_03.png +0 -0
- package/docs/images/tmp_frames/hero_00.png +0 -0
- package/docs/images/tmp_frames/hero_01.png +0 -0
- package/docs/images/tmp_frames/hero_02.png +0 -0
- package/docs/images/tmp_frames/hero_03.png +0 -0
- package/static/v3/js/core/fixtures.js +0 -171
package/static/v3/js/core/api.js
CHANGED
|
@@ -2,21 +2,31 @@
|
|
|
2
2
|
* Lattice AI v3 — Integration adapter
|
|
3
3
|
*
|
|
4
4
|
* Every adapter call hits the real endpoint first (including /api/index/status,
|
|
5
|
-
* /api/graph, /api/search/hybrid, and /chat). If that
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* badge it. No backend logic is implemented here — only transport + graceful
|
|
9
|
-
* fallback, which keeps the v3 frontend resilient during local setup.
|
|
5
|
+
* /api/graph, /api/search/hybrid, and /chat). If that endpoint is
|
|
6
|
+
* missing/unavailable, it returns an unavailable source with empty data so the
|
|
7
|
+
* UI can render a clear unavailable state without inventing counters or health.
|
|
10
8
|
*
|
|
11
9
|
* Return shape (never throws): { ok, status, data, source, error }
|
|
12
10
|
* source: "live" → returned by a real backend endpoint
|
|
13
|
-
* "
|
|
11
|
+
* "unavailable" → endpoint missing/down; no fake payload
|
|
14
12
|
* ========================================================================== */
|
|
15
13
|
|
|
16
14
|
import { store } from "./store.js";
|
|
17
|
-
import * as fx from "./fixtures.js";
|
|
18
15
|
|
|
19
16
|
const TIMEOUT_MS = 8000;
|
|
17
|
+
const EMPTY_INDEX_STATUS = { generated_at: null, pipelines: {}, sources: [] };
|
|
18
|
+
const EMPTY_GRAPH_STATS = { nodes: {}, edges: {}, total_nodes: 0, total_edges: 0 };
|
|
19
|
+
const EMPTY_WORKSPACE_OS = { counts: {}, models: {} };
|
|
20
|
+
const EMPTY_SYSINFO = { cpu_pct: null, ram_pct: null, gpu_mem_pct: null, gpu_mem_gb: null };
|
|
21
|
+
const EMPTY_ADMIN = {
|
|
22
|
+
summary: { total_users: null, active_users: null, admin_users: null, total_messages: null },
|
|
23
|
+
users: [],
|
|
24
|
+
audit: { recent_events: [] },
|
|
25
|
+
security: {},
|
|
26
|
+
roles: { roles: [] },
|
|
27
|
+
policies: { policies: [] },
|
|
28
|
+
vpc: {},
|
|
29
|
+
};
|
|
20
30
|
|
|
21
31
|
async function raw(path, { method = "GET", body, headers } = {}) {
|
|
22
32
|
const ctrl = new AbortController();
|
|
@@ -46,28 +56,35 @@ async function raw(path, { method = "GET", body, headers } = {}) {
|
|
|
46
56
|
}
|
|
47
57
|
}
|
|
48
58
|
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
function unavailableData(shape) {
|
|
60
|
+
const value = typeof shape === "function" ? shape() : shape;
|
|
61
|
+
if (Array.isArray(value)) return [];
|
|
62
|
+
if (value && typeof value === "object") return {};
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Try the live endpoint; on any non-2xx/transport failure, return empty data. */
|
|
67
|
+
async function withFallback(path, opts, shape) {
|
|
51
68
|
const res = await raw(path, opts);
|
|
52
69
|
if (res.ok && res.data && !res.data.raw) {
|
|
53
70
|
return { ...res, source: "live" };
|
|
54
71
|
}
|
|
55
|
-
return { ok:
|
|
72
|
+
return { ok: false, status: res.status, data: unavailableData(shape), source: "unavailable", error: res.error };
|
|
56
73
|
}
|
|
57
74
|
|
|
58
75
|
export const api = {
|
|
59
76
|
raw,
|
|
60
77
|
|
|
61
|
-
/** Generic GET with
|
|
62
|
-
async get(path,
|
|
63
|
-
return withFallback(path, {},
|
|
78
|
+
/** Generic GET with unavailable fallback. */
|
|
79
|
+
async get(path, shape = null) {
|
|
80
|
+
return withFallback(path, {}, shape);
|
|
64
81
|
},
|
|
65
82
|
|
|
66
83
|
/* ── Documented future surfaces ─────────────────────────────────────── */
|
|
67
84
|
|
|
68
85
|
/** GET /api/index/status — KG + Vector + Hybrid pipeline state. */
|
|
69
86
|
indexStatus() {
|
|
70
|
-
return withFallback("/api/index/status", {},
|
|
87
|
+
return withFallback("/api/index/status", {}, EMPTY_INDEX_STATUS);
|
|
71
88
|
},
|
|
72
89
|
|
|
73
90
|
/** POST /api/index/rebuild — rebuild the derived vector index (real run). */
|
|
@@ -76,7 +93,7 @@ export const api = {
|
|
|
76
93
|
},
|
|
77
94
|
|
|
78
95
|
/** GET /api/graph — knowledge graph (nodes + edges). Falls back through the
|
|
79
|
-
* current /knowledge-graph/graph route before
|
|
96
|
+
* current /knowledge-graph/graph route before reporting unavailable. */
|
|
80
97
|
async graph(params = {}) {
|
|
81
98
|
const qs = new URLSearchParams(params).toString();
|
|
82
99
|
const primary = await raw(`/api/graph${qs ? "?" + qs : ""}`);
|
|
@@ -87,11 +104,11 @@ export const api = {
|
|
|
87
104
|
if (legacy.ok && legacy.data && Array.isArray(legacy.data.nodes)) {
|
|
88
105
|
return { ...legacy, source: "live" };
|
|
89
106
|
}
|
|
90
|
-
return { ok:
|
|
107
|
+
return { ok: false, status: primary.status || legacy.status || 0, data: { nodes: [], edges: [] }, source: "unavailable", error: primary.error || legacy.error };
|
|
91
108
|
},
|
|
92
109
|
|
|
93
110
|
graphStats() {
|
|
94
|
-
return withFallback("/knowledge-graph/stats", {},
|
|
111
|
+
return withFallback("/knowledge-graph/stats", {}, EMPTY_GRAPH_STATS);
|
|
95
112
|
},
|
|
96
113
|
|
|
97
114
|
/** POST /api/search/hybrid — fused KG + vector retrieval.
|
|
@@ -123,11 +140,11 @@ export const api = {
|
|
|
123
140
|
});
|
|
124
141
|
return { ok: true, status: res.status, data: items, source: "live", weights: res.data.weights || null };
|
|
125
142
|
}
|
|
126
|
-
return { ok:
|
|
143
|
+
return { ok: false, status: res.status, data: [], source: "unavailable", error: res.error };
|
|
127
144
|
},
|
|
128
145
|
|
|
129
146
|
/* ── Existing surfaces (used where helpful, all fallback-safe) ──────── */
|
|
130
|
-
workspaceOs() { return withFallback("/workspace/os", {},
|
|
147
|
+
workspaceOs() { return withFallback("/workspace/os", {}, EMPTY_WORKSPACE_OS); },
|
|
131
148
|
async models() {
|
|
132
149
|
const res = await raw("/models");
|
|
133
150
|
if (res.ok && res.data && !res.data.raw) {
|
|
@@ -149,17 +166,23 @@ export const api = {
|
|
|
149
166
|
data: { ...data, catalog: Array.isArray(data.catalog) ? data.catalog : [...recommended, ...loadedOnly] },
|
|
150
167
|
};
|
|
151
168
|
}
|
|
152
|
-
return { ok:
|
|
169
|
+
return { ok: false, status: res.status, data: { current: null, catalog: [] }, source: "unavailable", error: res.error };
|
|
170
|
+
},
|
|
171
|
+
loadModel(modelId, engine) {
|
|
172
|
+
return raw("/models/load", { method: "POST", body: { model_id: modelId, engine: engine || null } });
|
|
153
173
|
},
|
|
154
|
-
|
|
174
|
+
unloadModel(modelId) {
|
|
175
|
+
return raw(`/models/unload/${encodeURIComponent(modelId)}`, { method: "DELETE" });
|
|
176
|
+
},
|
|
177
|
+
sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
|
|
155
178
|
|
|
156
|
-
adminSummary() { return withFallback("/admin/summary", {},
|
|
157
|
-
adminUsers() { return withFallback("/admin/users", {},
|
|
158
|
-
adminAudit() { return withFallback("/admin/audit", {},
|
|
159
|
-
adminSecurity() { return withFallback("/admin/security/overview", {},
|
|
160
|
-
adminRoles() { return withFallback("/admin/roles", {},
|
|
161
|
-
adminPolicies() { return withFallback("/admin/policies", {},
|
|
162
|
-
vpcStatus() { return withFallback("/vpc/status", {},
|
|
179
|
+
adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
|
|
180
|
+
adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
|
|
181
|
+
adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
|
|
182
|
+
adminSecurity() { return withFallback("/admin/security/overview", {}, EMPTY_ADMIN.security); },
|
|
183
|
+
adminRoles() { return withFallback("/admin/roles", {}, EMPTY_ADMIN.roles); },
|
|
184
|
+
adminPolicies() { return withFallback("/admin/policies", {}, EMPTY_ADMIN.policies); },
|
|
185
|
+
vpcStatus() { return withFallback("/vpc/status", {}, EMPTY_ADMIN.vpc); },
|
|
163
186
|
|
|
164
187
|
/* ── Embeddings (real backend: /api/embeddings/*) ───────────────────── */
|
|
165
188
|
/** GET /api/embeddings/status — active provider, grade, dimensions, last index. */
|
|
@@ -170,10 +193,10 @@ export const api = {
|
|
|
170
193
|
}
|
|
171
194
|
// No backend → report unavailable honestly (never fabricate a provider).
|
|
172
195
|
return {
|
|
173
|
-
ok:
|
|
174
|
-
data: { provider:
|
|
175
|
-
model_id:
|
|
176
|
-
state: "
|
|
196
|
+
ok: false, status: res.status, source: "unavailable",
|
|
197
|
+
data: { provider: null, active_provider: null, model: null,
|
|
198
|
+
model_id: null, dimensions: null, grade: "unavailable",
|
|
199
|
+
state: "unavailable", fell_back: false, health: { status: "unavailable", detail: "backend unavailable" },
|
|
177
200
|
last_indexed_at: null },
|
|
178
201
|
};
|
|
179
202
|
},
|
|
@@ -186,12 +209,12 @@ export const api = {
|
|
|
186
209
|
if (res.ok && res.data && res.data.runtime && Array.isArray(res.data.agents)) {
|
|
187
210
|
return { ok: true, status: res.status, data: res.data, source: "live" };
|
|
188
211
|
}
|
|
189
|
-
// Fallback:
|
|
212
|
+
// Fallback: unavailable roster, no fabricated run ledger.
|
|
190
213
|
return {
|
|
191
|
-
ok:
|
|
214
|
+
ok: false, status: res.status, source: "unavailable",
|
|
192
215
|
data: { runtime: { ready: false, total_runs: 0, active_runs: 0 },
|
|
193
216
|
health: { status: "unknown", checks: {} }, roles: [],
|
|
194
|
-
agents:
|
|
217
|
+
agents: [], runs: [] },
|
|
195
218
|
};
|
|
196
219
|
},
|
|
197
220
|
/** POST /agents/api/run — execute the multi-agent pipeline for a goal. */
|
|
@@ -215,7 +238,7 @@ export const api = {
|
|
|
215
238
|
: res.ok && res.data && Array.isArray(res.data.conversations) ? res.data.conversations
|
|
216
239
|
: null;
|
|
217
240
|
if (list) return { ok: true, status: res.status, data: list, source: "live" };
|
|
218
|
-
return { ok:
|
|
241
|
+
return { ok: false, status: res.status, data: [], source: "unavailable" };
|
|
219
242
|
},
|
|
220
243
|
|
|
221
244
|
/** GET /history/conversations/{id} — messages for one conversation. */
|
|
@@ -224,8 +247,7 @@ export const api = {
|
|
|
224
247
|
if (res.ok && res.data && Array.isArray(res.data.messages)) {
|
|
225
248
|
return { ok: true, status: res.status, data: res.data.messages, source: "live" };
|
|
226
249
|
}
|
|
227
|
-
|
|
228
|
-
return { ok: true, status: res.status, data: sample, source: "placeholder" };
|
|
250
|
+
return { ok: false, status: res.status, data: [], source: "unavailable" };
|
|
229
251
|
},
|
|
230
252
|
|
|
231
253
|
deleteConversation(id) {
|
|
@@ -236,8 +258,8 @@ export const api = {
|
|
|
236
258
|
* POST /chat — streams the assistant reply over SSE.
|
|
237
259
|
* Parses `data: {chunk, model, trace}` events (terminator `[DONE]`), calling
|
|
238
260
|
* onChunk(delta, fullText) and onTrace(trace). If the endpoint is missing or
|
|
239
|
-
|
|
240
|
-
|
|
261
|
+
* not an event-stream, reports that chat is unavailable (no generated answer is
|
|
262
|
+
* invented). Resolves { source, text, trace, model, aborted }.
|
|
241
263
|
*/
|
|
242
264
|
async streamChat(body, { onChunk, onTrace, signal } = {}) {
|
|
243
265
|
const ws = store.get().workspaceId;
|
|
@@ -299,29 +321,88 @@ export const api = {
|
|
|
299
321
|
}
|
|
300
322
|
return { source: "live", text, trace, model };
|
|
301
323
|
},
|
|
324
|
+
|
|
325
|
+
/* ── v3.2 platform surfaces (all fallback-safe; never fabricate) ─────── */
|
|
326
|
+
|
|
327
|
+
// Agent Registry (Part 2)
|
|
328
|
+
agentRegistry(type) { return withFallback(`/agents/api/registry${type ? "?type=" + encodeURIComponent(type) : ""}`, {}, { agents: [], counts: {}, types: [] }); },
|
|
329
|
+
agentCapabilities() { return withFallback("/agents/api/registry/capabilities", {}, { capabilities: {} }); },
|
|
330
|
+
registerAgent(body) { return raw("/agents/api/registry", { method: "POST", body }); },
|
|
331
|
+
updateAgent(id, body) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "PATCH", body }); },
|
|
332
|
+
removeAgent(id) { return raw(`/agents/api/registry/${encodeURIComponent(id)}`, { method: "DELETE" }); },
|
|
333
|
+
agentRunDetail(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}`); },
|
|
334
|
+
agentRunReplay(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
335
|
+
stopAgentRun(runId) { return raw(`/agents/api/runs/${encodeURIComponent(runId)}/stop`, { method: "POST" }); },
|
|
336
|
+
|
|
337
|
+
// Marketplace + Templates (Parts 3, 4)
|
|
338
|
+
templates(kind) { return withFallback(`/marketplace/templates${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { templates: [], kinds: [] }); },
|
|
339
|
+
templateRegistry() { return withFallback("/marketplace/templates/registry", {}, { registry: [] }); },
|
|
340
|
+
exportTemplate(kind, id) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/export`); },
|
|
341
|
+
importTemplate(data) { return raw("/marketplace/templates/import", { method: "POST", body: { data } }); },
|
|
342
|
+
installTemplate(data) { return raw("/marketplace/templates/install", { method: "POST", body: { data } }); },
|
|
343
|
+
cloneTemplate(kind, id, name) { return raw(`/marketplace/templates/${encodeURIComponent(kind)}/${encodeURIComponent(id)}/clone`, { method: "POST", body: { name } }); },
|
|
344
|
+
pluginsRegistry() { return withFallback("/plugins/registry", {}, { plugins: [] }); },
|
|
345
|
+
pluginsDirectory() { return withFallback("/plugins/directory", {}, { plugins: [], categories: [] }); },
|
|
346
|
+
|
|
347
|
+
// Workflow Agents (Part 5)
|
|
348
|
+
workflowDefinitions() { return withFallback("/workflows/api/definitions", {}, { workflows: [] }); },
|
|
349
|
+
createWorkflow(body) { return raw("/workflows/api/definitions", { method: "POST", body }); },
|
|
350
|
+
runWorkflow(id, body = {}) { return raw(`/workflows/api/definitions/${encodeURIComponent(id)}/run`, { method: "POST", body }); },
|
|
351
|
+
workflowRuns() { return withFallback("/workflows/api/runs", {}, { runs: [] }); },
|
|
352
|
+
workflowReplay(runId) { return raw(`/workflows/api/runs/${encodeURIComponent(runId)}/replay`); },
|
|
353
|
+
|
|
354
|
+
// Long-Term Memory + Memory Manager (Parts 7, 8)
|
|
355
|
+
memoryManager() { return withFallback("/api/memory/manager", {}, { sources: [], tiers: [], usage: {} }); },
|
|
356
|
+
memoryTiers() { return withFallback("/api/memory/tiers", {}, { tiers: [], workspace_kinds: [] }); },
|
|
357
|
+
memoryInspect(source, limit = 50) { return withFallback(`/api/memory/inspect?source=${encodeURIComponent(source)}&limit=${limit}`, {}, { items: [] }); },
|
|
358
|
+
memoryRecall(query, limit = 20) { return raw("/api/memory/recall", { method: "POST", body: { query, limit } }); },
|
|
359
|
+
memoryPrune(body) { return raw("/api/memory/prune", { method: "POST", body }); },
|
|
360
|
+
memoryCompact() { return raw("/api/memory/compact", { method: "POST", body: {} }); },
|
|
361
|
+
memoryRebuild(target = "vector") { return raw("/api/memory/rebuild", { method: "POST", body: { target } }); },
|
|
362
|
+
memoryClear(scope, confirm = true) { return raw("/api/memory/clear", { method: "POST", body: { scope, confirm } }); },
|
|
363
|
+
workspaceMemories(kind) { return withFallback(`/workspace/memories${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { memories: [] }); },
|
|
364
|
+
|
|
365
|
+
// Skills Registry (Part 9)
|
|
366
|
+
skills() { return withFallback("/workspace/skills", {}, { skills: [] }); },
|
|
367
|
+
skillEnable(skill) { return raw("/workspace/skills/enable", { method: "POST", body: { skill } }); },
|
|
368
|
+
skillDisable(skill) { return raw("/workspace/skills/disable", { method: "POST", body: { skill } }); },
|
|
369
|
+
skillInstall(skill, plugin) { return raw("/workspace/skills/install", { method: "POST", body: { skill, plugin: plugin || "" } }); },
|
|
370
|
+
skillUninstall(skill) { return raw("/workspace/skills/uninstall", { method: "POST", body: { skill } }); },
|
|
371
|
+
skillsMarketplace() { return withFallback("/skills/marketplace", {}, { skills: [], categories: [] }); },
|
|
372
|
+
|
|
373
|
+
// Hooks Registry (Part 10)
|
|
374
|
+
hooks(kind) { return withFallback(`/api/hooks${kind ? "?kind=" + encodeURIComponent(kind) : ""}`, {}, { hooks: [], kinds: [], counts: {} }); },
|
|
375
|
+
hookEnable(hook_id, enabled = true) { return raw("/api/hooks/enable", { method: "POST", body: { hook_id, enabled } }); },
|
|
376
|
+
hookDisable(hook_id) { return raw("/api/hooks/disable", { method: "POST", body: { hook_id, enabled: false } }); },
|
|
377
|
+
hookReorder(kind, ordered_ids) { return raw("/api/hooks/reorder", { method: "POST", body: { kind, ordered_ids } }); },
|
|
378
|
+
hookRegister(body) { return raw("/api/hooks/register", { method: "POST", body }); },
|
|
379
|
+
hookRemove(hook_id) { return raw(`/api/hooks/${encodeURIComponent(hook_id)}`, { method: "DELETE" }); },
|
|
380
|
+
|
|
381
|
+
// Tool Registry + MCP (Parts 11, 12)
|
|
382
|
+
toolPermissions() { return withFallback("/tools/permissions", {}, { permissions: [] }); },
|
|
383
|
+
mcpTools() { return withFallback("/mcp/tools", {}, { tools: [], installed_mcps: [] }); },
|
|
384
|
+
mcpInstalled() { return withFallback("/mcp/installed", {}, { installed: [] }); },
|
|
385
|
+
mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
|
|
386
|
+
mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
|
|
387
|
+
mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
|
|
302
388
|
};
|
|
303
389
|
|
|
304
390
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
305
391
|
|
|
306
|
-
/** Transparent
|
|
392
|
+
/** Transparent unavailable stream — used only when no chat backend is available. */
|
|
307
393
|
async function simulateChat(body, { onChunk, onTrace, signal } = {}) {
|
|
308
394
|
const q = (body && body.message) || "your question";
|
|
309
395
|
const reply =
|
|
310
|
-
`
|
|
311
|
-
`
|
|
312
|
-
`The context panel shows the knowledge-graph entities, vector matches, and indexed ` +
|
|
313
|
-
`files that would be used to answer — this preview does not run a model.`;
|
|
396
|
+
`Chat is unavailable because the Lattice backend or active model is not reachable. ` +
|
|
397
|
+
`Start the server, load a model, and rebuild retrieval before sending “${q}”.`;
|
|
314
398
|
let text = "";
|
|
315
399
|
for (const word of reply.split(" ")) {
|
|
316
|
-
if (signal && signal.aborted) return { source: "
|
|
400
|
+
if (signal && signal.aborted) return { source: "unavailable", text, aborted: true };
|
|
317
401
|
const delta = (text ? " " : "") + word;
|
|
318
402
|
text += delta;
|
|
319
403
|
onChunk && onChunk(delta, text);
|
|
320
404
|
await sleep(16);
|
|
321
405
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return { source: "placeholder", text, trace };
|
|
406
|
+
onTrace && onTrace(null);
|
|
407
|
+
return { source: "unavailable", text, trace: null };
|
|
325
408
|
}
|
|
326
|
-
|
|
327
|
-
export { fx };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — Component factories
|
|
3
|
+
* Build the shared vocabulary (cards, panels, stats, tables, states, the
|
|
4
|
+
* retrieval-lattice pillars …) on top of dom.js + the component CSS. Views
|
|
5
|
+
* compose these to stay visually consistent.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import { h, icon, fmtNum } from "./dom.a2773eb0.js";
|
|
9
|
+
|
|
10
|
+
/* ── View + section headers ─────────────────────────────────────────────── */
|
|
11
|
+
export function viewHeader({ eyebrow, title, sub, actions } = {}) {
|
|
12
|
+
return h("header.lt3-vhead",
|
|
13
|
+
h("div",
|
|
14
|
+
eyebrow && h("div.lt3-eyebrow", eyebrow),
|
|
15
|
+
h("h1.lt3-vhead__title", title),
|
|
16
|
+
sub && h("p.lt3-vhead__sub", sub),
|
|
17
|
+
),
|
|
18
|
+
actions && actions.length && h("div.lt3-vhead__actions", actions),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function sectionHead(title, ...actions) {
|
|
23
|
+
return h("div.lt3-section__head",
|
|
24
|
+
h("h2.lt3-section__title", title),
|
|
25
|
+
actions.length && h("div.lt3-row-2", actions),
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* ── Panel / card ───────────────────────────────────────────────────────── */
|
|
30
|
+
export function panel({ title, sub, actions, head, children, eyebrow, className } = {}) {
|
|
31
|
+
return h(`section.lt3-panel${className ? "." + className : ""}`,
|
|
32
|
+
(title || head || actions) && h("div.lt3-panel__head",
|
|
33
|
+
head || h("div",
|
|
34
|
+
eyebrow && h("div.lt3-eyebrow", eyebrow),
|
|
35
|
+
title && h("h3.lt3-panel__title", title),
|
|
36
|
+
sub && h("p.lt3-panel__sub", sub),
|
|
37
|
+
),
|
|
38
|
+
actions && h("div.lt3-row-2", actions),
|
|
39
|
+
),
|
|
40
|
+
children,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function card(children, opts = {}) {
|
|
45
|
+
const cls = ["lt3-card"];
|
|
46
|
+
if (opts.interactive) cls.push("lt3-card--interactive");
|
|
47
|
+
if (opts.flat) cls.push("lt3-card--flat");
|
|
48
|
+
if (opts.ghost) cls.push("lt3-card--ghost");
|
|
49
|
+
return h(`div.${cls.join(".")}`, opts.attrs || {}, children);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* ── Stat tile ──────────────────────────────────────────────────────────── */
|
|
53
|
+
export function stat({ label, value, icon: ic, delta, deltaDir }) {
|
|
54
|
+
return h("div.lt3-stat",
|
|
55
|
+
h("div.lt3-stat__label", ic && icon(ic), label),
|
|
56
|
+
h("div.lt3-stat__value", value == null ? "—" : value),
|
|
57
|
+
delta && h(`div.lt3-stat__delta${deltaDir ? ".lt3-stat__delta--" + deltaDir : ""}`, delta),
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ── Pills / badges ─────────────────────────────────────────────────────── */
|
|
62
|
+
export function pill(text, variant = "", { dot } = {}) {
|
|
63
|
+
const cls = ["lt3-pill"];
|
|
64
|
+
if (variant) cls.push("lt3-pill--" + variant);
|
|
65
|
+
if (dot) cls.push("lt3-pill--dot");
|
|
66
|
+
return h(`span.${cls.join(".")}`, text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const STATE_VARIANT = {
|
|
70
|
+
ready: "ok", active: "ok", indexed: "ok", loaded: "ok", ok: "ok", available: "info",
|
|
71
|
+
idle: "", standby: "", pending: "warn", indexing: "warn", building: "warn",
|
|
72
|
+
failed: "err", error: "err", disabled: "err", not_configured: "",
|
|
73
|
+
};
|
|
74
|
+
export function statePill(state) {
|
|
75
|
+
return pill(String(state || "unknown"), STATE_VARIANT[String(state).toLowerCase()] ?? "", { dot: true });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Provenance badge — makes live vs unavailable data explicit. */
|
|
79
|
+
export function sourceBadge(source) {
|
|
80
|
+
if (source === "live") return h("span.lt3-source.lt3-source--live", icon("circle-filled"), "Live");
|
|
81
|
+
if (source === "unavailable") return h("span.lt3-source.lt3-source--unavailable", icon("alert-circle"), "Unavailable");
|
|
82
|
+
return h("span.lt3-source.lt3-source--pending", "—");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ── States ─────────────────────────────────────────────────────────────── */
|
|
86
|
+
export function emptyState({ icon: ic = "inbox", title, body, action } = {}) {
|
|
87
|
+
return h("div.lt3-empty",
|
|
88
|
+
h("div.lt3-empty__icon", icon(ic)),
|
|
89
|
+
title && h("div.lt3-empty__title", title),
|
|
90
|
+
body && h("div.lt3-empty__body", body),
|
|
91
|
+
action,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function loading({ lines = 3, block = false } = {}) {
|
|
96
|
+
const kids = [];
|
|
97
|
+
if (block) kids.push(h("div.lt3-skel.lt3-skel--block"));
|
|
98
|
+
for (let i = 0; i < lines; i++) {
|
|
99
|
+
kids.push(h("div.lt3-skel.lt3-skel--line", { style: { width: 100 - i * 14 + "%" } }));
|
|
100
|
+
}
|
|
101
|
+
return h("div", { "aria-busy": "true", "aria-label": "Loading" }, kids);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function errorState(message, onRetry) {
|
|
105
|
+
return h("div.lt3-banner.lt3-banner--err",
|
|
106
|
+
icon("alert-triangle"),
|
|
107
|
+
h("div", h("div", { style: { fontWeight: 600 } }, "Couldn't load"), h("div.lt3-faint", message || "Request failed")),
|
|
108
|
+
onRetry && h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { style: { "margin-left": "auto" }, on: { click: onRetry } }, icon("refresh"), "Retry"),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function banner(text, variant = "info", ic = "info-circle") {
|
|
113
|
+
return h(`div.lt3-banner.lt3-banner--${variant}`, icon(ic), h("div", text));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ── Table ──────────────────────────────────────────────────────────────── */
|
|
117
|
+
export function table(columns, rows, { empty } = {}) {
|
|
118
|
+
if (!rows || !rows.length) {
|
|
119
|
+
return empty || emptyState({ title: "Nothing here yet", body: "Data will appear once connected." });
|
|
120
|
+
}
|
|
121
|
+
return h("div.lt3-table--clip", { style: { overflow: "auto" } },
|
|
122
|
+
h("table.lt3-table",
|
|
123
|
+
h("thead", h("tr", columns.map((c) => h("th", { style: c.width ? { width: c.width } : {} }, c.label)))),
|
|
124
|
+
h("tbody", rows.map((row) => h("tr", columns.map((c) => h("td", c.render ? c.render(row) : row[c.key]))))),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* ── Tabs / segmented ───────────────────────────────────────────────────── */
|
|
130
|
+
export function tabs(items, active, onChange) {
|
|
131
|
+
return h("div.lt3-tabs", { role: "tablist" },
|
|
132
|
+
items.map((it) => h("button.lt3-tab", {
|
|
133
|
+
role: "tab", type: "button",
|
|
134
|
+
dataset: { active: String(it.key === active) },
|
|
135
|
+
"aria-selected": String(it.key === active),
|
|
136
|
+
on: { click: () => onChange(it.key) },
|
|
137
|
+
}, it.label)),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function segmented(items, active, onChange) {
|
|
142
|
+
return h("div.lt3-seg", { role: "tablist" },
|
|
143
|
+
items.map((it) => h("button", {
|
|
144
|
+
type: "button", role: "tab",
|
|
145
|
+
dataset: { active: String(it.key === active) },
|
|
146
|
+
"aria-selected": String(it.key === active),
|
|
147
|
+
on: { click: () => onChange(it.key) },
|
|
148
|
+
}, it.label)),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ── Meter ──────────────────────────────────────────────────────────────── */
|
|
153
|
+
export function meter(value, variant = "") {
|
|
154
|
+
const pct = Math.max(0, Math.min(1, Number(value) || 0)) * 100;
|
|
155
|
+
return h("div.lt3-meter",
|
|
156
|
+
h(`div.lt3-meter__fill${variant ? ".lt3-meter__fill--" + variant : ""}`, { style: { width: pct + "%" } }),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ── Retrieval lattice (signature) ──────────────────────────────────────── */
|
|
161
|
+
const PILLAR_DEFS = [
|
|
162
|
+
{ key: "knowledge_graph", kind: "graph", name: "Knowledge Graph", desc: "Entities & relations", icon: "chart-dots-3", unit: "entities", read: (p) => p?.entities },
|
|
163
|
+
{ key: "vector_index", kind: "vector", name: "Vector Index", desc: "Local embedding vectors", icon: "grid-dots", unit: "vectors", read: (p) => p?.vectors },
|
|
164
|
+
{ key: "hybrid", kind: "hybrid", name: "Hybrid Search", desc: "Fused graph + vector", icon: "arrows-join", unit: "fusion", read: (p) => p?.strategy },
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
export function pillars(indexStatus) {
|
|
168
|
+
const pipes = indexStatus?.pipelines || {};
|
|
169
|
+
if (!Object.keys(pipes).length) {
|
|
170
|
+
return emptyState({
|
|
171
|
+
icon: "database-off",
|
|
172
|
+
title: "Retrieval status unavailable",
|
|
173
|
+
body: "Start the backend with Knowledge Graph enabled to see live index state.",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return h("div.lt3-pillars",
|
|
177
|
+
PILLAR_DEFS.map((def) => {
|
|
178
|
+
const p = pipes[def.key] || {};
|
|
179
|
+
const raw = def.read(p);
|
|
180
|
+
const num = typeof raw === "number" ? fmtNum(raw) : (raw || "ready");
|
|
181
|
+
return h(`article.lt3-pillar.lt3-pillar--${def.kind}`,
|
|
182
|
+
h("div.lt3-pillar__icon", icon(def.icon)),
|
|
183
|
+
h("div.lt3-row", { style: { "justify-content": "space-between" } },
|
|
184
|
+
h("div",
|
|
185
|
+
h("div.lt3-pillar__name", def.name),
|
|
186
|
+
h("div.lt3-pillar__desc", def.desc),
|
|
187
|
+
),
|
|
188
|
+
statePill(p.state || "ready"),
|
|
189
|
+
),
|
|
190
|
+
h("div.lt3-pillar__stat",
|
|
191
|
+
h("span.lt3-pillar__num", num),
|
|
192
|
+
h("span.lt3-pillar__unit", def.unit),
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Compact 3-dot index chip for the topbar. */
|
|
200
|
+
export function indexChip(indexStatus) {
|
|
201
|
+
const pipes = indexStatus?.pipelines || {};
|
|
202
|
+
const dot = (kind, key) => h("span.lt3-idxchip__dot", {
|
|
203
|
+
dataset: { kind, on: String((pipes[key]?.state || "ready") === "ready") },
|
|
204
|
+
title: `${kind}: ${pipes[key]?.state || "—"}`,
|
|
205
|
+
});
|
|
206
|
+
return h("div.lt3-idxchip", { title: "Retrieval index status" },
|
|
207
|
+
h("span.lt3-idxchip__dots", dot("graph", "knowledge_graph"), dot("vector", "vector_index"), dot("hybrid", "hybrid")),
|
|
208
|
+
h("span", "Index"),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── Toast ──────────────────────────────────────────────────────────────── */
|
|
213
|
+
export function toast(message, variant = "info") {
|
|
214
|
+
let host = document.querySelector(".lt3-toasts");
|
|
215
|
+
if (!host) { host = h("div.lt3-toasts"); document.body.append(host); }
|
|
216
|
+
const ic = variant === "ok" ? "circle-check" : variant === "err" ? "alert-circle" : "info-circle";
|
|
217
|
+
const node = h(`div.lt3-toast.lt3-toast--${variant}`, icon(ic), h("div", message));
|
|
218
|
+
host.append(node);
|
|
219
|
+
setTimeout(() => { node.style.opacity = "0"; node.style.transition = "opacity .3s"; setTimeout(() => node.remove(), 300); }, 3200);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export { icon, fmtNum };
|
|
@@ -75,10 +75,10 @@ export function statePill(state) {
|
|
|
75
75
|
return pill(String(state || "unknown"), STATE_VARIANT[String(state).toLowerCase()] ?? "", { dot: true });
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
/** Provenance badge — makes
|
|
78
|
+
/** Provenance badge — makes live vs unavailable data explicit. */
|
|
79
79
|
export function sourceBadge(source) {
|
|
80
80
|
if (source === "live") return h("span.lt3-source.lt3-source--live", icon("circle-filled"), "Live");
|
|
81
|
-
if (source === "
|
|
81
|
+
if (source === "unavailable") return h("span.lt3-source.lt3-source--unavailable", icon("alert-circle"), "Unavailable");
|
|
82
82
|
return h("span.lt3-source.lt3-source--pending", "—");
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -166,6 +166,13 @@ const PILLAR_DEFS = [
|
|
|
166
166
|
|
|
167
167
|
export function pillars(indexStatus) {
|
|
168
168
|
const pipes = indexStatus?.pipelines || {};
|
|
169
|
+
if (!Object.keys(pipes).length) {
|
|
170
|
+
return emptyState({
|
|
171
|
+
icon: "database-off",
|
|
172
|
+
title: "Retrieval status unavailable",
|
|
173
|
+
body: "Start the backend with Knowledge Graph enabled to see live index state.",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
169
176
|
return h("div.lt3-pillars",
|
|
170
177
|
PILLAR_DEFS.map((def) => {
|
|
171
178
|
const p = pipes[def.key] || {};
|