ltcai 3.2.0 → 3.4.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 +87 -67
- package/docs/CHANGELOG.md +36 -0
- package/docs/architecture.md +2 -1
- package/docs/assets/v3.4.0/agent-run.png +0 -0
- package/docs/assets/v3.4.0/agents.png +0 -0
- package/docs/assets/v3.4.0/before/chat-before.png +0 -0
- package/docs/assets/v3.4.0/before/files-before.png +0 -0
- package/docs/assets/v3.4.0/chat.png +0 -0
- package/docs/assets/v3.4.0/connect-folder.png +0 -0
- package/docs/assets/v3.4.0/files.png +0 -0
- package/docs/assets/v3.4.0/home.png +0 -0
- package/docs/assets/v3.4.0/hooks-dispatch.png +0 -0
- package/docs/assets/v3.4.0/knowledge-graph.png +0 -0
- package/docs/assets/v3.4.0/local-agent.png +0 -0
- package/docs/assets/v3.4.0/memory.png +0 -0
- package/docs/assets/v3.4.0/settings.png +0 -0
- package/docs/assets/v3.4.0/vision-input.png +0 -0
- package/docs/assets/v3.4.0/workflows.png +0 -0
- package/knowledge_graph.py +45 -0
- package/knowledge_graph_api.py +10 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +3 -0
- package/latticeai/api/hooks.py +39 -0
- package/latticeai/api/local_files.py +41 -0
- package/latticeai/api/models.py +36 -1
- package/latticeai/api/tools.py +16 -1
- package/latticeai/api/workflow_designer.py +2 -1
- package/latticeai/core/hooks.py +398 -2
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workflow_engine.py +21 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +40 -0
- package/latticeai/services/agent_runtime.py +46 -1
- package/latticeai/services/upload_service.py +17 -0
- package/package.json +1 -1
- package/scripts/build_v3_assets.mjs +7 -1
- package/scripts/capture/capture_v340.js +88 -0
- package/static/css/{tokens.5a595671.css → tokens.3ba22e37.css} +109 -109
- package/static/css/tokens.css +109 -109
- package/static/v3/asset-manifest.json +25 -25
- package/static/v3/css/{lattice.components.011e988b.css → lattice.components.9b49d614.css} +57 -32
- package/static/v3/css/lattice.components.css +57 -32
- package/static/v3/css/{lattice.shell.4920f42d.css → lattice.shell.6ceea7c8.css} +75 -31
- package/static/v3/css/lattice.shell.css +75 -31
- package/static/v3/css/lattice.tokens.css +13 -13
- package/static/v3/css/{lattice.tokens.c597ff81.css → lattice.tokens.e7018963.css} +13 -13
- package/static/v3/css/{lattice.views.3ee19d4e.css → lattice.views.22f69117.css} +98 -15
- package/static/v3/css/lattice.views.css +98 -15
- package/static/v3/js/{app.a5adc0f3.js → app.c4acfdd8.js} +1 -1
- package/static/v3/js/core/{api.603b978f.js → api.12b568ad.js} +126 -4
- package/static/v3/js/core/api.js +126 -4
- package/static/v3/js/core/{components.4c83e0a9.js → components.35f02e4c.js} +8 -0
- package/static/v3/js/core/components.js +8 -0
- package/static/v3/js/core/{routes.07ad6696.js → routes.d214b399.js} +16 -12
- package/static/v3/js/core/routes.js +16 -12
- package/static/v3/js/core/{shell.ea0b9ae5.js → shell.80a6ad82.js} +37 -9
- package/static/v3/js/core/shell.js +34 -6
- package/static/v3/js/views/agents.014d0b74.js +541 -0
- package/static/v3/js/views/agents.js +305 -57
- package/static/v3/js/views/{chat.718144ce.js → chat.e6dd7dd0.js} +162 -10
- package/static/v3/js/views/chat.js +162 -10
- package/static/v3/js/views/files.adad14c1.js +365 -0
- package/static/v3/js/views/files.js +269 -90
- package/static/v3/js/views/home.24f8b8ae.js +200 -0
- package/static/v3/js/views/home.js +96 -15
- package/static/v3/js/views/hooks.13845954.js +215 -0
- package/static/v3/js/views/hooks.js +117 -1
- package/static/v3/js/views/{memory.d2ed7a7c.js → memory.4ebdf474.js} +5 -4
- package/static/v3/js/views/memory.js +5 -4
- package/static/v3/js/views/{my-computer.1b2ff621.js → my-computer.c3ef5283.js} +224 -1
- package/static/v3/js/views/my-computer.js +224 -1
- package/static/v3/js/views/{settings.4f777210.js → settings.8631fa5e.js} +70 -2
- package/static/v3/js/views/settings.js +70 -2
- package/static/v3/js/views/agents.c373d48c.js +0 -293
- package/static/v3/js/views/files.4935197e.js +0 -186
- package/static/v3/js/views/home.cdde3b32.js +0 -119
- package/static/v3/js/views/hooks.f3edebca.js +0 -99
|
@@ -82,9 +82,35 @@ export const api = {
|
|
|
82
82
|
|
|
83
83
|
/* ── Documented future surfaces ─────────────────────────────────────── */
|
|
84
84
|
|
|
85
|
-
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
+
* The backend endpoint is vector-centric (status/storage/source_items/…); the
|
|
87
|
+
* home pillars + topbar chip want a `pipelines` view keyed by
|
|
88
|
+
* knowledge_graph / vector_index / hybrid. Synthesize that shape from the real
|
|
89
|
+
* index status (vectors) plus the KG stats endpoint (entities). Nothing is
|
|
90
|
+
* fabricated: if the index endpoint is unavailable we report unavailable (so
|
|
91
|
+
* the UI shows the honest empty state), and a missing graph-stats count yields
|
|
92
|
+
* an "unavailable" graph pillar rather than a fake number. */
|
|
93
|
+
async indexStatus() {
|
|
94
|
+
const res = await raw("/api/index/status");
|
|
95
|
+
if (!(res.ok && res.data && !res.data.raw)) {
|
|
96
|
+
return { ok: false, status: res.status, data: EMPTY_INDEX_STATUS, source: "unavailable", error: res.error };
|
|
97
|
+
}
|
|
98
|
+
const idx = res.data;
|
|
99
|
+
let entities = null;
|
|
100
|
+
const gs = await raw("/knowledge-graph/stats");
|
|
101
|
+
if (gs.ok && gs.data && !gs.data.raw) {
|
|
102
|
+
const g = gs.data;
|
|
103
|
+
const n = g.total_nodes ?? g.nodes_total ?? (g.nodes && (g.nodes.total ?? g.nodes.count));
|
|
104
|
+
if (n !== undefined && n !== null) entities = Number(n) || 0;
|
|
105
|
+
}
|
|
106
|
+
const vectors = Number(idx.indexed_items ?? idx.ready_items) || 0;
|
|
107
|
+
const vstate = idx.status === "ready" ? "ready" : "pending";
|
|
108
|
+
const pipelines = {
|
|
109
|
+
knowledge_graph: { state: entities === null ? "unavailable" : "ready", entities: entities ?? 0 },
|
|
110
|
+
vector_index: { state: vstate, vectors },
|
|
111
|
+
hybrid: { state: vstate, strategy: vstate === "ready" ? "fused" : "pending" },
|
|
112
|
+
};
|
|
113
|
+
return { ok: true, status: res.status, data: { ...idx, pipelines }, source: "live" };
|
|
88
114
|
},
|
|
89
115
|
|
|
90
116
|
/** POST /api/index/rebuild — rebuild the derived vector index (real run). */
|
|
@@ -176,6 +202,31 @@ export const api = {
|
|
|
176
202
|
},
|
|
177
203
|
sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
|
|
178
204
|
|
|
205
|
+
/** POST /upload/document — manual document ingest (multipart/form-data).
|
|
206
|
+
* Real backend path: parse → chunk → embed → knowledge-graph ingest
|
|
207
|
+
* (latticeai/api/tools.py:/upload/document). Returns { ok, status, data,
|
|
208
|
+
* source }; never throws. FormData must NOT carry a JSON Content-Type — the
|
|
209
|
+
* browser sets the multipart boundary itself. */
|
|
210
|
+
async uploadDocument(file) {
|
|
211
|
+
const ws = store.get().workspaceId;
|
|
212
|
+
const form = new FormData();
|
|
213
|
+
form.append("file", file);
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch("/upload/document", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
credentials: "same-origin",
|
|
218
|
+
headers: { "Accept": "application/json", ...(ws ? { "X-Workspace-Id": ws } : {}) },
|
|
219
|
+
body: form,
|
|
220
|
+
});
|
|
221
|
+
let data = null;
|
|
222
|
+
const text = await res.text();
|
|
223
|
+
if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
|
|
224
|
+
return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
179
230
|
adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
|
|
180
231
|
adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
|
|
181
232
|
adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
|
|
@@ -310,7 +361,11 @@ export const api = {
|
|
|
310
361
|
const rawData = line.slice(5).trim();
|
|
311
362
|
if (rawData === "[DONE]") return { source: "live", text, trace, model };
|
|
312
363
|
let data; try { data = JSON.parse(rawData); } catch { continue; }
|
|
313
|
-
|
|
364
|
+
// Standard chat streams `chunk`; the document-generation path streams
|
|
365
|
+
// `text` (report body + footnotes). Accept both so doc requests render
|
|
366
|
+
// instead of falsely reporting the backend as unreachable.
|
|
367
|
+
const delta = data.chunk || data.text;
|
|
368
|
+
if (delta) { text += delta; onChunk && onChunk(delta, text); }
|
|
314
369
|
if (data.model) model = data.model;
|
|
315
370
|
if (data.trace) { trace = data.trace; onTrace && onTrace(trace); }
|
|
316
371
|
}
|
|
@@ -385,6 +440,73 @@ export const api = {
|
|
|
385
440
|
mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
|
|
386
441
|
mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
|
|
387
442
|
mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
|
|
443
|
+
|
|
444
|
+
/* ── v3.4 Platform Completion ───────────────────────────────────────────
|
|
445
|
+
* Uploaded documents in Files, Connect Folder + Folder Watch over the real
|
|
446
|
+
* on-device runtime, the Local Agent status, and Hooks dispatch/run-log.
|
|
447
|
+
* All endpoints are real (latticeai/api + knowledge_graph_api); fallback-safe. */
|
|
448
|
+
|
|
449
|
+
/** GET /knowledge-graph/documents — uploaded + indexed docs with index state. */
|
|
450
|
+
async documents(limit = 200) {
|
|
451
|
+
const res = await raw(`/knowledge-graph/documents?limit=${encodeURIComponent(limit)}`);
|
|
452
|
+
if (res.ok && res.data && Array.isArray(res.data.documents)) {
|
|
453
|
+
return { ok: true, status: res.status, data: res.data.documents, source: "live", total: res.data.total };
|
|
454
|
+
}
|
|
455
|
+
return { ok: false, status: res.status, data: [], source: "unavailable", error: res.error };
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
// Local Agent (the on-device Lattice runtime: real GET /api/local-agent/status)
|
|
459
|
+
async localAgent() {
|
|
460
|
+
const res = await raw("/api/local-agent/status");
|
|
461
|
+
if (res.ok && res.data && res.data.agent) {
|
|
462
|
+
return { ok: true, status: res.status, data: res.data, source: "live" };
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: false, status: res.status, source: "unavailable",
|
|
466
|
+
data: { agent: { online: false }, health: {}, folders: { connected: 0, watching: 0 }, watch: { available: false, active: {} }, sources: [] },
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
// Connect Folder + Folder Watch (real backend: /knowledge-graph/local/*)
|
|
471
|
+
localRoots() { return withFallback("/knowledge-graph/local/roots", {}, { roots: [] }); },
|
|
472
|
+
async localSources() {
|
|
473
|
+
const res = await raw("/knowledge-graph/local/sources");
|
|
474
|
+
if (res.ok && res.data && Array.isArray(res.data.sources)) {
|
|
475
|
+
return { ok: true, status: res.status, data: res.data, source: "live" };
|
|
476
|
+
}
|
|
477
|
+
return { ok: false, status: res.status, data: { sources: [], watch: { available: false, active: {} } }, source: "unavailable" };
|
|
478
|
+
},
|
|
479
|
+
localWatchStatus() { return raw("/knowledge-graph/local/watch/status"); },
|
|
480
|
+
localWatchStop(source_id) { return raw("/knowledge-graph/local/watch/stop", { method: "POST", body: { source_id } }); },
|
|
481
|
+
approvePermission(token) { return raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" }); },
|
|
482
|
+
indexFolder(path, opts = {}) {
|
|
483
|
+
return raw("/knowledge-graph/local/index", { method: "POST", body: { path, ...opts } });
|
|
484
|
+
},
|
|
485
|
+
/** One-call Connect Folder: request → self-approve (the click is the consent)
|
|
486
|
+
* → index (+ optional watch). Returns { ok, data, error }. */
|
|
487
|
+
async connectFolder(path, { watch = true, includeOcr = false } = {}) {
|
|
488
|
+
const probe = await raw("/knowledge-graph/local/index", { method: "POST", body: { path, approved: false } });
|
|
489
|
+
const token = probe.data && probe.data.approval_token;
|
|
490
|
+
if (!token) {
|
|
491
|
+
const detail = (probe.data && (probe.data.detail || probe.data.error)) || "the runtime did not return an approval token";
|
|
492
|
+
return { ok: false, error: detail, status: probe.status };
|
|
493
|
+
}
|
|
494
|
+
const approved = await raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" });
|
|
495
|
+
if (!approved.ok) {
|
|
496
|
+
const detail = (approved.data && (approved.data.detail || approved.data.error)) || "approval failed";
|
|
497
|
+
return { ok: false, error: detail, status: approved.status };
|
|
498
|
+
}
|
|
499
|
+
const res = await raw("/knowledge-graph/local/index", {
|
|
500
|
+
method: "POST",
|
|
501
|
+
body: { path, approved: true, approval_token: token, watch_enabled: watch, include_ocr: includeOcr, consent: { approved: true, source: "files-ui" } },
|
|
502
|
+
});
|
|
503
|
+
if (res.ok && res.data && !res.data.detail) return { ok: true, data: res.data, status: res.status };
|
|
504
|
+
return { ok: false, error: (res.data && (res.data.detail || res.data.error)) || "indexing failed", status: res.status, data: res.data };
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
// Hooks dispatch (real backend: POST /api/hooks/run + GET /api/hooks/runs)
|
|
508
|
+
hookRun(body) { return raw("/api/hooks/run", { method: "POST", body }); },
|
|
509
|
+
hookRuns(limit = 50, kind) { return withFallback(`/api/hooks/runs?limit=${encodeURIComponent(limit)}${kind ? "&kind=" + encodeURIComponent(kind) : ""}`, {}, { runs: [], total: 0 }); },
|
|
388
510
|
};
|
|
389
511
|
|
|
390
512
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
package/static/v3/js/core/api.js
CHANGED
|
@@ -82,9 +82,35 @@ export const api = {
|
|
|
82
82
|
|
|
83
83
|
/* ── Documented future surfaces ─────────────────────────────────────── */
|
|
84
84
|
|
|
85
|
-
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
/** GET /api/index/status — KG + Vector + Hybrid pipeline state.
|
|
86
|
+
* The backend endpoint is vector-centric (status/storage/source_items/…); the
|
|
87
|
+
* home pillars + topbar chip want a `pipelines` view keyed by
|
|
88
|
+
* knowledge_graph / vector_index / hybrid. Synthesize that shape from the real
|
|
89
|
+
* index status (vectors) plus the KG stats endpoint (entities). Nothing is
|
|
90
|
+
* fabricated: if the index endpoint is unavailable we report unavailable (so
|
|
91
|
+
* the UI shows the honest empty state), and a missing graph-stats count yields
|
|
92
|
+
* an "unavailable" graph pillar rather than a fake number. */
|
|
93
|
+
async indexStatus() {
|
|
94
|
+
const res = await raw("/api/index/status");
|
|
95
|
+
if (!(res.ok && res.data && !res.data.raw)) {
|
|
96
|
+
return { ok: false, status: res.status, data: EMPTY_INDEX_STATUS, source: "unavailable", error: res.error };
|
|
97
|
+
}
|
|
98
|
+
const idx = res.data;
|
|
99
|
+
let entities = null;
|
|
100
|
+
const gs = await raw("/knowledge-graph/stats");
|
|
101
|
+
if (gs.ok && gs.data && !gs.data.raw) {
|
|
102
|
+
const g = gs.data;
|
|
103
|
+
const n = g.total_nodes ?? g.nodes_total ?? (g.nodes && (g.nodes.total ?? g.nodes.count));
|
|
104
|
+
if (n !== undefined && n !== null) entities = Number(n) || 0;
|
|
105
|
+
}
|
|
106
|
+
const vectors = Number(idx.indexed_items ?? idx.ready_items) || 0;
|
|
107
|
+
const vstate = idx.status === "ready" ? "ready" : "pending";
|
|
108
|
+
const pipelines = {
|
|
109
|
+
knowledge_graph: { state: entities === null ? "unavailable" : "ready", entities: entities ?? 0 },
|
|
110
|
+
vector_index: { state: vstate, vectors },
|
|
111
|
+
hybrid: { state: vstate, strategy: vstate === "ready" ? "fused" : "pending" },
|
|
112
|
+
};
|
|
113
|
+
return { ok: true, status: res.status, data: { ...idx, pipelines }, source: "live" };
|
|
88
114
|
},
|
|
89
115
|
|
|
90
116
|
/** POST /api/index/rebuild — rebuild the derived vector index (real run). */
|
|
@@ -176,6 +202,31 @@ export const api = {
|
|
|
176
202
|
},
|
|
177
203
|
sysinfo() { return withFallback("/local/sysinfo", {}, EMPTY_SYSINFO); },
|
|
178
204
|
|
|
205
|
+
/** POST /upload/document — manual document ingest (multipart/form-data).
|
|
206
|
+
* Real backend path: parse → chunk → embed → knowledge-graph ingest
|
|
207
|
+
* (latticeai/api/tools.py:/upload/document). Returns { ok, status, data,
|
|
208
|
+
* source }; never throws. FormData must NOT carry a JSON Content-Type — the
|
|
209
|
+
* browser sets the multipart boundary itself. */
|
|
210
|
+
async uploadDocument(file) {
|
|
211
|
+
const ws = store.get().workspaceId;
|
|
212
|
+
const form = new FormData();
|
|
213
|
+
form.append("file", file);
|
|
214
|
+
try {
|
|
215
|
+
const res = await fetch("/upload/document", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
credentials: "same-origin",
|
|
218
|
+
headers: { "Accept": "application/json", ...(ws ? { "X-Workspace-Id": ws } : {}) },
|
|
219
|
+
body: form,
|
|
220
|
+
});
|
|
221
|
+
let data = null;
|
|
222
|
+
const text = await res.text();
|
|
223
|
+
if (text) { try { data = JSON.parse(text); } catch { data = { raw: text }; } }
|
|
224
|
+
return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
|
|
225
|
+
} catch (err) {
|
|
226
|
+
return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
179
230
|
adminSummary() { return withFallback("/admin/summary", {}, EMPTY_ADMIN.summary); },
|
|
180
231
|
adminUsers() { return withFallback("/admin/users", {}, EMPTY_ADMIN.users); },
|
|
181
232
|
adminAudit() { return withFallback("/admin/audit", {}, EMPTY_ADMIN.audit); },
|
|
@@ -310,7 +361,11 @@ export const api = {
|
|
|
310
361
|
const rawData = line.slice(5).trim();
|
|
311
362
|
if (rawData === "[DONE]") return { source: "live", text, trace, model };
|
|
312
363
|
let data; try { data = JSON.parse(rawData); } catch { continue; }
|
|
313
|
-
|
|
364
|
+
// Standard chat streams `chunk`; the document-generation path streams
|
|
365
|
+
// `text` (report body + footnotes). Accept both so doc requests render
|
|
366
|
+
// instead of falsely reporting the backend as unreachable.
|
|
367
|
+
const delta = data.chunk || data.text;
|
|
368
|
+
if (delta) { text += delta; onChunk && onChunk(delta, text); }
|
|
314
369
|
if (data.model) model = data.model;
|
|
315
370
|
if (data.trace) { trace = data.trace; onTrace && onTrace(trace); }
|
|
316
371
|
}
|
|
@@ -385,6 +440,73 @@ export const api = {
|
|
|
385
440
|
mcpClaudeServers() { return withFallback("/mcp/claude-code-servers", {}, { servers: [] }); },
|
|
386
441
|
mcpCustom() { return withFallback("/mcp/custom", {}, { custom: [] }); },
|
|
387
442
|
mcpRecommend(query, limit = 6) { return raw("/mcp/recommend", { method: "POST", body: { query, limit } }); },
|
|
443
|
+
|
|
444
|
+
/* ── v3.4 Platform Completion ───────────────────────────────────────────
|
|
445
|
+
* Uploaded documents in Files, Connect Folder + Folder Watch over the real
|
|
446
|
+
* on-device runtime, the Local Agent status, and Hooks dispatch/run-log.
|
|
447
|
+
* All endpoints are real (latticeai/api + knowledge_graph_api); fallback-safe. */
|
|
448
|
+
|
|
449
|
+
/** GET /knowledge-graph/documents — uploaded + indexed docs with index state. */
|
|
450
|
+
async documents(limit = 200) {
|
|
451
|
+
const res = await raw(`/knowledge-graph/documents?limit=${encodeURIComponent(limit)}`);
|
|
452
|
+
if (res.ok && res.data && Array.isArray(res.data.documents)) {
|
|
453
|
+
return { ok: true, status: res.status, data: res.data.documents, source: "live", total: res.data.total };
|
|
454
|
+
}
|
|
455
|
+
return { ok: false, status: res.status, data: [], source: "unavailable", error: res.error };
|
|
456
|
+
},
|
|
457
|
+
|
|
458
|
+
// Local Agent (the on-device Lattice runtime: real GET /api/local-agent/status)
|
|
459
|
+
async localAgent() {
|
|
460
|
+
const res = await raw("/api/local-agent/status");
|
|
461
|
+
if (res.ok && res.data && res.data.agent) {
|
|
462
|
+
return { ok: true, status: res.status, data: res.data, source: "live" };
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
ok: false, status: res.status, source: "unavailable",
|
|
466
|
+
data: { agent: { online: false }, health: {}, folders: { connected: 0, watching: 0 }, watch: { available: false, active: {} }, sources: [] },
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
// Connect Folder + Folder Watch (real backend: /knowledge-graph/local/*)
|
|
471
|
+
localRoots() { return withFallback("/knowledge-graph/local/roots", {}, { roots: [] }); },
|
|
472
|
+
async localSources() {
|
|
473
|
+
const res = await raw("/knowledge-graph/local/sources");
|
|
474
|
+
if (res.ok && res.data && Array.isArray(res.data.sources)) {
|
|
475
|
+
return { ok: true, status: res.status, data: res.data, source: "live" };
|
|
476
|
+
}
|
|
477
|
+
return { ok: false, status: res.status, data: { sources: [], watch: { available: false, active: {} } }, source: "unavailable" };
|
|
478
|
+
},
|
|
479
|
+
localWatchStatus() { return raw("/knowledge-graph/local/watch/status"); },
|
|
480
|
+
localWatchStop(source_id) { return raw("/knowledge-graph/local/watch/stop", { method: "POST", body: { source_id } }); },
|
|
481
|
+
approvePermission(token) { return raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" }); },
|
|
482
|
+
indexFolder(path, opts = {}) {
|
|
483
|
+
return raw("/knowledge-graph/local/index", { method: "POST", body: { path, ...opts } });
|
|
484
|
+
},
|
|
485
|
+
/** One-call Connect Folder: request → self-approve (the click is the consent)
|
|
486
|
+
* → index (+ optional watch). Returns { ok, data, error }. */
|
|
487
|
+
async connectFolder(path, { watch = true, includeOcr = false } = {}) {
|
|
488
|
+
const probe = await raw("/knowledge-graph/local/index", { method: "POST", body: { path, approved: false } });
|
|
489
|
+
const token = probe.data && probe.data.approval_token;
|
|
490
|
+
if (!token) {
|
|
491
|
+
const detail = (probe.data && (probe.data.detail || probe.data.error)) || "the runtime did not return an approval token";
|
|
492
|
+
return { ok: false, error: detail, status: probe.status };
|
|
493
|
+
}
|
|
494
|
+
const approved = await raw(`/permissions/approve/${encodeURIComponent(token)}`, { method: "POST" });
|
|
495
|
+
if (!approved.ok) {
|
|
496
|
+
const detail = (approved.data && (approved.data.detail || approved.data.error)) || "approval failed";
|
|
497
|
+
return { ok: false, error: detail, status: approved.status };
|
|
498
|
+
}
|
|
499
|
+
const res = await raw("/knowledge-graph/local/index", {
|
|
500
|
+
method: "POST",
|
|
501
|
+
body: { path, approved: true, approval_token: token, watch_enabled: watch, include_ocr: includeOcr, consent: { approved: true, source: "files-ui" } },
|
|
502
|
+
});
|
|
503
|
+
if (res.ok && res.data && !res.data.detail) return { ok: true, data: res.data, status: res.status };
|
|
504
|
+
return { ok: false, error: (res.data && (res.data.detail || res.data.error)) || "indexing failed", status: res.status, data: res.data };
|
|
505
|
+
},
|
|
506
|
+
|
|
507
|
+
// Hooks dispatch (real backend: POST /api/hooks/run + GET /api/hooks/runs)
|
|
508
|
+
hookRun(body) { return raw("/api/hooks/run", { method: "POST", body }); },
|
|
509
|
+
hookRuns(limit = 50, kind) { return withFallback(`/api/hooks/runs?limit=${encodeURIComponent(limit)}${kind ? "&kind=" + encodeURIComponent(kind) : ""}`, {}, { runs: [], total: 0 }); },
|
|
388
510
|
};
|
|
389
511
|
|
|
390
512
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -70,6 +70,14 @@ const STATE_VARIANT = {
|
|
|
70
70
|
ready: "ok", active: "ok", indexed: "ok", loaded: "ok", ok: "ok", available: "info",
|
|
71
71
|
idle: "", standby: "", pending: "warn", indexing: "warn", building: "warn",
|
|
72
72
|
failed: "err", error: "err", disabled: "err", not_configured: "",
|
|
73
|
+
// v3.4.0 platform-completion states (Files / Folder Watch / Local Agent /
|
|
74
|
+
// Agent runs / Hook dispatch). Keep these honest: amber for in-progress,
|
|
75
|
+
// green for healthy/active, red for blocked/failed, neutral for inert.
|
|
76
|
+
ingested: "warn", ingesting: "warn", watching: "ok", watched: "ok",
|
|
77
|
+
connected: "ok", online: "ok", offline: "err", synced: "ok",
|
|
78
|
+
queued: "warn", running: "warn", retrying: "warn", retried_ok: "ok",
|
|
79
|
+
rejected: "err", cancelled: "", stopped: "", blocked: "err",
|
|
80
|
+
advisory: "warn", skipped: "", complete: "ok", partial: "warn",
|
|
73
81
|
};
|
|
74
82
|
export function statePill(state) {
|
|
75
83
|
return pill(String(state || "unknown"), STATE_VARIANT[String(state).toLowerCase()] ?? "", { dot: true });
|
|
@@ -70,6 +70,14 @@ const STATE_VARIANT = {
|
|
|
70
70
|
ready: "ok", active: "ok", indexed: "ok", loaded: "ok", ok: "ok", available: "info",
|
|
71
71
|
idle: "", standby: "", pending: "warn", indexing: "warn", building: "warn",
|
|
72
72
|
failed: "err", error: "err", disabled: "err", not_configured: "",
|
|
73
|
+
// v3.4.0 platform-completion states (Files / Folder Watch / Local Agent /
|
|
74
|
+
// Agent runs / Hook dispatch). Keep these honest: amber for in-progress,
|
|
75
|
+
// green for healthy/active, red for blocked/failed, neutral for inert.
|
|
76
|
+
ingested: "warn", ingesting: "warn", watching: "ok", watched: "ok",
|
|
77
|
+
connected: "ok", online: "ok", offline: "err", synced: "ok",
|
|
78
|
+
queued: "warn", running: "warn", retrying: "warn", retried_ok: "ok",
|
|
79
|
+
rejected: "err", cancelled: "", stopped: "", blocked: "err",
|
|
80
|
+
advisory: "warn", skipped: "", complete: "ok", partial: "warn",
|
|
73
81
|
};
|
|
74
82
|
export function statePill(state) {
|
|
75
83
|
return pill(String(state || "unknown"), STATE_VARIANT[String(state).toLowerCase()] ?? "", { dot: true });
|
|
@@ -11,8 +11,8 @@ export const MODE_RANK = { basic: 0, advanced: 1, admin: 2 };
|
|
|
11
11
|
/** Nav groups in display order. */
|
|
12
12
|
export const GROUPS = [
|
|
13
13
|
{ id: "workspace", label: "Workspace" },
|
|
14
|
-
{ id: "retrieval", label: "Retrieval" },
|
|
15
14
|
{ id: "data", label: "Data" },
|
|
15
|
+
{ id: "retrieval", label: "Retrieval" },
|
|
16
16
|
{ id: "compute", label: "Compute" },
|
|
17
17
|
{ id: "platform", label: "Platform" },
|
|
18
18
|
{ id: "system", label: "System" },
|
|
@@ -28,32 +28,35 @@ export const ROUTES = [
|
|
|
28
28
|
{ key: "home", label: "Home", icon: "layout-dashboard", group: "workspace", minMode: "basic", view: "home", title: "Home", desc: "Your local-first AI workspace at a glance." },
|
|
29
29
|
{ key: "chat", label: "Chat", icon: "message-2", group: "workspace", minMode: "basic", view: "chat", title: "Chat", desc: "Grounded conversation over your indexed workspace." },
|
|
30
30
|
|
|
31
|
-
// Retrieval (the product identity)
|
|
32
|
-
{ key: "knowledge-graph", label: "Knowledge Graph", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
33
|
-
{ key: "hybrid-search", label: "Hybrid Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
34
|
-
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
35
|
-
|
|
36
31
|
// Data
|
|
37
32
|
{ key: "files", label: "Files", icon: "folders", group: "data", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
|
|
38
|
-
|
|
33
|
+
|
|
34
|
+
// Retrieval (the product identity)
|
|
35
|
+
{ key: "hybrid-search", label: "Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
36
|
+
{ key: "knowledge-graph", label: "Knowledge", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
37
|
+
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
39
38
|
|
|
40
39
|
// Compute
|
|
40
|
+
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
41
41
|
{ key: "agents", label: "Agents", icon: "robot", group: "compute", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
|
|
42
42
|
{ key: "workflows", label: "Workflows", icon: "sitemap", group: "compute", minMode: "advanced", view: "workflows", title: "Workflow Agents", desc: "Trigger → agent chain → tools → memory → result." },
|
|
43
|
-
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan." },
|
|
44
|
-
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
45
|
-
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime." },
|
|
46
43
|
|
|
47
44
|
// Platform (the agent ecosystem)
|
|
48
|
-
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills." },
|
|
49
45
|
{ key: "skills", label: "Skills", icon: "puzzle", group: "platform", minMode: "advanced", view: "skills", title: "Skills", desc: "Install, enable, and manage skills." },
|
|
50
46
|
{ key: "hooks", label: "Hooks", icon: "webhook", group: "platform", minMode: "advanced", view: "hooks", title: "Hooks", desc: "Lifecycle hooks across runs, tools, and workflows." },
|
|
51
|
-
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance." },
|
|
52
47
|
{ key: "mcp", label: "MCP", icon: "plug-connected", group: "platform", minMode: "advanced", view: "mcp", title: "MCP Manager", desc: "Connected MCP servers, available tools, and health." },
|
|
53
48
|
|
|
54
49
|
// System
|
|
55
50
|
{ key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
|
|
56
51
|
|
|
52
|
+
// Deep-linkable legacy/experimental surfaces. They remain renderable for
|
|
53
|
+
// compatibility, but are not promoted in the production navigation.
|
|
54
|
+
{ key: "pipeline", label: "Pipeline", icon: "git-branch", group: "data", minMode: "advanced", view: "pipeline", title: "Pipeline", desc: "Ingest, embed, and graph-build flows.", hidden: true },
|
|
55
|
+
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan.", hidden: true },
|
|
56
|
+
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime.", hidden: true },
|
|
57
|
+
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills.", hidden: true },
|
|
58
|
+
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance.", hidden: true },
|
|
59
|
+
|
|
57
60
|
// Admin
|
|
58
61
|
{ key: "admin/users", label: "Users", icon: "users", group: "admin", minMode: "admin", view: "admin-users", title: "Users", desc: "Workspace members and access.", admin: true },
|
|
59
62
|
{ key: "admin/permissions", label: "Permissions", icon: "key", group: "admin", minMode: "admin", view: "admin-permissions", title: "Permissions", desc: "Roles and capability mapping.", admin: true },
|
|
@@ -69,6 +72,7 @@ export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((r) => [r.key, r]));
|
|
|
69
72
|
export function visibleRoutes(mode) {
|
|
70
73
|
const rank = MODE_RANK[mode] ?? 0;
|
|
71
74
|
return ROUTES.filter((r) => {
|
|
75
|
+
if (r.hidden) return false;
|
|
72
76
|
if (r.admin) return mode === "admin";
|
|
73
77
|
return (MODE_RANK[r.minMode] ?? 0) <= rank;
|
|
74
78
|
});
|
|
@@ -11,8 +11,8 @@ export const MODE_RANK = { basic: 0, advanced: 1, admin: 2 };
|
|
|
11
11
|
/** Nav groups in display order. */
|
|
12
12
|
export const GROUPS = [
|
|
13
13
|
{ id: "workspace", label: "Workspace" },
|
|
14
|
-
{ id: "retrieval", label: "Retrieval" },
|
|
15
14
|
{ id: "data", label: "Data" },
|
|
15
|
+
{ id: "retrieval", label: "Retrieval" },
|
|
16
16
|
{ id: "compute", label: "Compute" },
|
|
17
17
|
{ id: "platform", label: "Platform" },
|
|
18
18
|
{ id: "system", label: "System" },
|
|
@@ -28,32 +28,35 @@ export const ROUTES = [
|
|
|
28
28
|
{ key: "home", label: "Home", icon: "layout-dashboard", group: "workspace", minMode: "basic", view: "home", title: "Home", desc: "Your local-first AI workspace at a glance." },
|
|
29
29
|
{ key: "chat", label: "Chat", icon: "message-2", group: "workspace", minMode: "basic", view: "chat", title: "Chat", desc: "Grounded conversation over your indexed workspace." },
|
|
30
30
|
|
|
31
|
-
// Retrieval (the product identity)
|
|
32
|
-
{ key: "knowledge-graph", label: "Knowledge Graph", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
33
|
-
{ key: "hybrid-search", label: "Hybrid Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
34
|
-
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
35
|
-
|
|
36
31
|
// Data
|
|
37
32
|
{ key: "files", label: "Files", icon: "folders", group: "data", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
|
|
38
|
-
|
|
33
|
+
|
|
34
|
+
// Retrieval (the product identity)
|
|
35
|
+
{ key: "hybrid-search", label: "Search", icon: "arrows-join", group: "retrieval", minMode: "basic", view: "hybrid-search", title: "Hybrid Search", desc: "Graph structure fused with vector similarity." },
|
|
36
|
+
{ key: "knowledge-graph", label: "Knowledge", icon: "chart-dots-3", group: "retrieval", minMode: "basic", view: "knowledge-graph", title: "Knowledge Graph", desc: "Entities and relations extracted from your workspace." },
|
|
37
|
+
{ key: "memory", label: "Memory", icon: "brain", group: "retrieval", minMode: "basic", view: "memory", title: "Memory", desc: "Long-term workspace, project, agent, and conversation memory." },
|
|
39
38
|
|
|
40
39
|
// Compute
|
|
40
|
+
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
41
41
|
{ key: "agents", label: "Agents", icon: "robot", group: "compute", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
|
|
42
42
|
{ key: "workflows", label: "Workflows", icon: "sitemap", group: "compute", minMode: "advanced", view: "workflows", title: "Workflow Agents", desc: "Trigger → agent chain → tools → memory → result." },
|
|
43
|
-
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan." },
|
|
44
|
-
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
45
|
-
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime." },
|
|
46
43
|
|
|
47
44
|
// Platform (the agent ecosystem)
|
|
48
|
-
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills." },
|
|
49
45
|
{ key: "skills", label: "Skills", icon: "puzzle", group: "platform", minMode: "advanced", view: "skills", title: "Skills", desc: "Install, enable, and manage skills." },
|
|
50
46
|
{ key: "hooks", label: "Hooks", icon: "webhook", group: "platform", minMode: "advanced", view: "hooks", title: "Hooks", desc: "Lifecycle hooks across runs, tools, and workflows." },
|
|
51
|
-
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance." },
|
|
52
47
|
{ key: "mcp", label: "MCP", icon: "plug-connected", group: "platform", minMode: "advanced", view: "mcp", title: "MCP Manager", desc: "Connected MCP servers, available tools, and health." },
|
|
53
48
|
|
|
54
49
|
// System
|
|
55
50
|
{ key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
|
|
56
51
|
|
|
52
|
+
// Deep-linkable legacy/experimental surfaces. They remain renderable for
|
|
53
|
+
// compatibility, but are not promoted in the production navigation.
|
|
54
|
+
{ key: "pipeline", label: "Pipeline", icon: "git-branch", group: "data", minMode: "advanced", view: "pipeline", title: "Pipeline", desc: "Ingest, embed, and graph-build flows.", hidden: true },
|
|
55
|
+
{ key: "planning", label: "Planning", icon: "target-arrow", group: "compute", minMode: "advanced", view: "planning", title: "Autonomous Planning", desc: "Goal → plan → execute → review → replan.", hidden: true },
|
|
56
|
+
{ key: "my-computer", label: "My Computer", icon: "device-desktop-analytics", group: "compute", minMode: "advanced", view: "my-computer", title: "My Computer", desc: "Local hardware, memory, and runtime.", hidden: true },
|
|
57
|
+
{ key: "marketplace", label: "Marketplace", icon: "building-store", group: "platform", minMode: "advanced", view: "marketplace", title: "Marketplace", desc: "Agent templates, agents, plugins, and skills.", hidden: true },
|
|
58
|
+
{ key: "tools", label: "Tools", icon: "tools", group: "platform", minMode: "advanced", view: "tools", title: "Tool Registry", desc: "Local, workspace, and MCP tools with governance.", hidden: true },
|
|
59
|
+
|
|
57
60
|
// Admin
|
|
58
61
|
{ key: "admin/users", label: "Users", icon: "users", group: "admin", minMode: "admin", view: "admin-users", title: "Users", desc: "Workspace members and access.", admin: true },
|
|
59
62
|
{ key: "admin/permissions", label: "Permissions", icon: "key", group: "admin", minMode: "admin", view: "admin-permissions", title: "Permissions", desc: "Roles and capability mapping.", admin: true },
|
|
@@ -69,6 +72,7 @@ export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((r) => [r.key, r]));
|
|
|
69
72
|
export function visibleRoutes(mode) {
|
|
70
73
|
const rank = MODE_RANK[mode] ?? 0;
|
|
71
74
|
return ROUTES.filter((r) => {
|
|
75
|
+
if (r.hidden) return false;
|
|
72
76
|
if (r.admin) return mode === "admin";
|
|
73
77
|
return (MODE_RANK[r.minMode] ?? 0) <= rank;
|
|
74
78
|
});
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { h, icon, $, $$ } from "./dom.a2773eb0.js";
|
|
9
9
|
import { store } from "./store.34ebd5e6.js";
|
|
10
|
-
import { api } from "./api.
|
|
11
|
-
import * as c from "./components.
|
|
10
|
+
import { api } from "./api.12b568ad.js";
|
|
11
|
+
import * as c from "./components.35f02e4c.js";
|
|
12
12
|
import { createRouter } from "./router.584570f2.js";
|
|
13
|
-
import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.
|
|
13
|
+
import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.d214b399.js";
|
|
14
14
|
|
|
15
15
|
const MODES = [
|
|
16
16
|
{ key: "basic", label: "Basic", icon: "circle" },
|
|
@@ -49,14 +49,17 @@ function buildRail() {
|
|
|
49
49
|
return h("aside.lt3-rail", { id: "lt3-rail", "aria-label": "Primary" },
|
|
50
50
|
h("div.lt3-rail__brand",
|
|
51
51
|
h("div.lt3-rail__logo", { html: latticeMark() }),
|
|
52
|
-
h("div.lt3-rail__word", h("b", "Lattice AI"), h("small", "
|
|
52
|
+
h("div.lt3-rail__word", h("b", "Lattice AI"), h("small", "Private runtime")),
|
|
53
53
|
h("button.lt3-iconbtn.lt3-iconbtn--sm.lt3-rail__close", { "aria-label": "Close menu", on: { click: closeDrawer } }, icon("x")),
|
|
54
54
|
),
|
|
55
55
|
h("div.lt3-rail__scope", { id: "lt3-scope" }),
|
|
56
56
|
h("nav.lt3-rail__nav", { id: "lt3-nav", "aria-label": "Sections" }),
|
|
57
57
|
h("div.lt3-rail__foot",
|
|
58
|
-
h("
|
|
59
|
-
h("
|
|
58
|
+
h("div.lt3-rail__status", { id: "lt3-rail-status" }),
|
|
59
|
+
h("div.lt3-rail__foot-row",
|
|
60
|
+
h("button.lt3-rail__user", { id: "lt3-user", "aria-label": "Account", on: { click: () => router.navigate("settings") } }),
|
|
61
|
+
h("button.lt3-iconbtn", { id: "lt3-theme", "aria-label": "Toggle theme", title: "Toggle theme", on: { click: () => store.toggleTheme() } }, icon("moon")),
|
|
62
|
+
),
|
|
60
63
|
),
|
|
61
64
|
);
|
|
62
65
|
}
|
|
@@ -82,10 +85,14 @@ function navItem(route) {
|
|
|
82
85
|
return h("a.lt3-navitem", {
|
|
83
86
|
href: "#/" + route.key,
|
|
84
87
|
dataset: { key: route.key },
|
|
88
|
+
title: route.title || route.label,
|
|
85
89
|
on: { click: () => closeDrawer() },
|
|
86
90
|
},
|
|
87
91
|
icon(route.icon),
|
|
88
|
-
h("span.lt3-
|
|
92
|
+
h("span.lt3-navitem__copy",
|
|
93
|
+
h("span.lt3-navitem__label", route.label),
|
|
94
|
+
route.desc ? h("span.lt3-navitem__meta", route.desc) : null,
|
|
95
|
+
),
|
|
89
96
|
route.key === "hybrid-search" ? h("span.lt3-navitem__dot", { style: { background: "var(--lt3-pillar-hybrid)" } }) : null,
|
|
90
97
|
);
|
|
91
98
|
}
|
|
@@ -161,6 +168,23 @@ function renderCrumbs() {
|
|
|
161
168
|
|
|
162
169
|
function renderIndexChip() {
|
|
163
170
|
els.idxchip.replaceChildren(c.indexChip(store.get().indexStatus));
|
|
171
|
+
renderRailStatus();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderRailStatus() {
|
|
175
|
+
if (!els.railStatus) return;
|
|
176
|
+
const status = store.get().indexStatus;
|
|
177
|
+
const pipes = status?.pipelines || {};
|
|
178
|
+
const keys = ["knowledge_graph", "vector_index", "hybrid"];
|
|
179
|
+
const ready = keys.filter((key) => String(pipes[key]?.state || "").toLowerCase() === "ready").length;
|
|
180
|
+
const unavailable = !Object.keys(pipes).length;
|
|
181
|
+
els.railStatus.replaceChildren(
|
|
182
|
+
h("div.lt3-rail__status-top",
|
|
183
|
+
h("span.lt3-rail__status-dot", { dataset: { state: unavailable ? "pending" : ready === keys.length ? "ready" : "partial" } }),
|
|
184
|
+
h("span", unavailable ? "Local index pending" : `${ready}/${keys.length} retrieval signals ready`),
|
|
185
|
+
),
|
|
186
|
+
h("div.lt3-rail__status-sub", unavailable ? "Start backend to sync live state" : "Graph · vector · hybrid"),
|
|
187
|
+
);
|
|
164
188
|
}
|
|
165
189
|
|
|
166
190
|
/* ── View rendering ─────────────────────────────────────────────────────── */
|
|
@@ -240,8 +264,10 @@ function closeDrawer() { delete els.root.dataset.drawer; }
|
|
|
240
264
|
|
|
241
265
|
/* ── Command palette ────────────────────────────────────────────────────── */
|
|
242
266
|
function paletteItems() {
|
|
243
|
-
const
|
|
244
|
-
|
|
267
|
+
const mode = store.get().mode;
|
|
268
|
+
const currentRoutes = visibleRoutes(mode);
|
|
269
|
+
const nav = currentRoutes.map((r) => ({
|
|
270
|
+
group: "Go to", label: r.title || r.label, icon: r.icon, hint: r.label === r.title ? r.group : r.label,
|
|
245
271
|
run: () => router.navigate(r.key),
|
|
246
272
|
}));
|
|
247
273
|
const actions = [
|
|
@@ -340,6 +366,7 @@ function cacheEls(root) {
|
|
|
340
366
|
theme: $("#lt3-theme", root),
|
|
341
367
|
crumbs: $("#lt3-crumbs", root),
|
|
342
368
|
idxchip: $("#lt3-idxchip", root),
|
|
369
|
+
railStatus: $("#lt3-rail-status", root),
|
|
343
370
|
outlet: $("#lt3-outlet", root),
|
|
344
371
|
view: $("#lt3-view", root),
|
|
345
372
|
};
|
|
@@ -349,6 +376,7 @@ function cacheEls(root) {
|
|
|
349
376
|
renderMode();
|
|
350
377
|
updateThemeIcon();
|
|
351
378
|
renderIndexChip();
|
|
379
|
+
renderRailStatus();
|
|
352
380
|
}
|
|
353
381
|
|
|
354
382
|
function latticeMark() {
|