ltcai 2.2.2 → 3.0.1
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 +66 -27
- package/codex_telegram_bot.py +6 -2
- package/docs/CHANGELOG.md +154 -0
- package/docs/V3_BACKEND_ARCHITECTURE.md +138 -0
- package/docs/V3_FRONTEND.md +136 -0
- package/knowledge_graph.py +649 -21
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +47 -0
- package/latticeai/api/agents.py +54 -31
- package/latticeai/api/auth.py +1 -1
- package/latticeai/api/chat.py +10 -2
- package/latticeai/api/search.py +236 -0
- package/latticeai/api/static_routes.py +21 -2
- package/latticeai/core/config.py +16 -0
- package/latticeai/core/embedding_providers.py +502 -0
- package/latticeai/core/local_embeddings.py +86 -0
- package/latticeai/core/logging_safety.py +62 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +49 -1
- package/latticeai/services/agent_runtime.py +245 -0
- package/latticeai/services/search_service.py +346 -0
- package/package.json +8 -4
- package/static/account.html +9 -4
- package/static/activity.html +4 -4
- package/static/admin.html +8 -3
- package/static/agents.html +4 -4
- package/static/chat.html +16 -11
- package/static/css/reference/account.css +439 -0
- package/static/css/reference/admin.css +610 -0
- package/static/css/reference/base.css +1658 -0
- package/static/{lattice-reference.css → css/reference/chat.css} +271 -3633
- package/static/css/reference/graph.css +1016 -0
- package/static/css/responsive.css +248 -1
- package/static/css/tokens.css +132 -126
- package/static/favicon.ico +0 -0
- package/static/graph.html +9 -4
- package/static/manifest.json +3 -3
- package/static/platform.css +1 -1
- package/static/plugins.html +4 -4
- package/static/scripts/account.js +4 -4
- package/static/scripts/chat.js +227 -77
- package/static/scripts/workspace.js +78 -0
- package/static/sw.js +5 -3
- package/static/v3/css/lattice.base.css +128 -0
- package/static/v3/css/lattice.components.css +447 -0
- package/static/v3/css/lattice.shell.css +407 -0
- package/static/v3/css/lattice.tokens.css +132 -0
- package/static/v3/css/lattice.views.css +277 -0
- package/static/v3/index.html +40 -0
- package/static/v3/js/app.js +26 -0
- package/static/v3/js/core/api.js +327 -0
- package/static/v3/js/core/components.js +215 -0
- package/static/v3/js/core/dom.js +148 -0
- package/static/v3/js/core/fixtures.js +171 -0
- package/static/v3/js/core/router.js +37 -0
- package/static/v3/js/core/routes.js +73 -0
- package/static/v3/js/core/shell.js +363 -0
- package/static/v3/js/core/store.js +113 -0
- package/static/v3/js/views/admin-audit.js +185 -0
- package/static/v3/js/views/admin-permissions.js +178 -0
- package/static/v3/js/views/admin-policies.js +103 -0
- package/static/v3/js/views/admin-private-vpc.js +138 -0
- package/static/v3/js/views/admin-security.js +181 -0
- package/static/v3/js/views/admin-users.js +168 -0
- package/static/v3/js/views/agents.js +194 -0
- package/static/v3/js/views/chat.js +450 -0
- package/static/v3/js/views/files.js +180 -0
- package/static/v3/js/views/home.js +119 -0
- package/static/v3/js/views/hybrid-search.js +195 -0
- package/static/v3/js/views/knowledge-graph.js +238 -0
- package/static/v3/js/views/models.js +247 -0
- package/static/v3/js/views/my-computer.js +237 -0
- package/static/v3/js/views/pipeline.js +161 -0
- package/static/v3/js/views/settings.js +258 -0
- package/static/workflows.html +4 -4
- package/static/workspace.css +408 -14
- package/static/workspace.html +43 -24
- package/telegram_bot.py +18 -14
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — DOM helpers
|
|
3
|
+
* A tiny, dependency-free hyperscript. Builds real DOM nodes (no innerHTML for
|
|
4
|
+
* dynamic content → no injection surface). Ergonomic enough to author views.
|
|
5
|
+
* ========================================================================== */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* h("div.card#id", { props }, ...children)
|
|
9
|
+
* - tag selector supports .class (many) and #id
|
|
10
|
+
* - props: class | className, id, on:{event:fn}, dataset:{k:v},
|
|
11
|
+
* style:{k:v} or string, html (trusted innerHTML), attrs (any other)
|
|
12
|
+
* - children: Node | string | number | falsy (skipped) | array (flattened)
|
|
13
|
+
*/
|
|
14
|
+
export function h(selector, props, ...children) {
|
|
15
|
+
const { tag, id, classes } = parseSelector(selector);
|
|
16
|
+
const el = document.createElement(tag);
|
|
17
|
+
if (id) el.id = id;
|
|
18
|
+
if (classes.length) el.classList.add(...classes);
|
|
19
|
+
|
|
20
|
+
if (props && (props.nodeType || typeof props === "string" || Array.isArray(props))) {
|
|
21
|
+
children.unshift(props);
|
|
22
|
+
props = null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (props) {
|
|
26
|
+
for (const [key, val] of Object.entries(props)) {
|
|
27
|
+
if (val == null || val === false) continue;
|
|
28
|
+
if (key === "class" || key === "className") {
|
|
29
|
+
for (const c of String(val).split(/\s+/).filter(Boolean)) el.classList.add(c);
|
|
30
|
+
} else if (key === "on" && typeof val === "object") {
|
|
31
|
+
for (const [ev, fn] of Object.entries(val)) el.addEventListener(ev, fn);
|
|
32
|
+
} else if (key === "dataset" && typeof val === "object") {
|
|
33
|
+
for (const [k, v] of Object.entries(val)) { if (v != null) el.dataset[k] = v; }
|
|
34
|
+
} else if (key === "style" && typeof val === "object") {
|
|
35
|
+
for (const [k, v] of Object.entries(val)) el.style.setProperty(k, v);
|
|
36
|
+
} else if (key === "style") {
|
|
37
|
+
el.setAttribute("style", val);
|
|
38
|
+
} else if (key === "html") {
|
|
39
|
+
el.innerHTML = val;
|
|
40
|
+
} else if (key === "ref" && typeof val === "function") {
|
|
41
|
+
val(el);
|
|
42
|
+
} else if (key in el && key !== "list" && typeof val !== "object") {
|
|
43
|
+
try { el[key] = val; } catch { el.setAttribute(key, val); }
|
|
44
|
+
} else {
|
|
45
|
+
el.setAttribute(key, val === true ? "" : val);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
appendChildren(el, children);
|
|
51
|
+
return el;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseSelector(sel) {
|
|
55
|
+
if (typeof sel !== "string") return { tag: "div", id: "", classes: [] };
|
|
56
|
+
const idMatch = sel.match(/#([\w-]+)/);
|
|
57
|
+
const id = idMatch ? idMatch[1] : "";
|
|
58
|
+
const classes = (sel.match(/\.([\w-]+)/g) || []).map((c) => c.slice(1));
|
|
59
|
+
const tag = (sel.match(/^([\w-]+)/) || [, "div"])[1];
|
|
60
|
+
return { tag, id, classes };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function appendChildren(el, children) {
|
|
64
|
+
for (const child of children.flat(Infinity)) {
|
|
65
|
+
if (child == null || child === false || child === true) continue;
|
|
66
|
+
el.append(child.nodeType ? child : document.createTextNode(String(child)));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Fragment of many children. */
|
|
71
|
+
export function frag(...children) {
|
|
72
|
+
const f = document.createDocumentFragment();
|
|
73
|
+
appendChildren(f, children);
|
|
74
|
+
return f;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Replace the contents of a node. */
|
|
78
|
+
export function render(host, ...children) {
|
|
79
|
+
host.replaceChildren();
|
|
80
|
+
appendChildren(host, children);
|
|
81
|
+
return host;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function clear(host) { host.replaceChildren(); return host; }
|
|
85
|
+
|
|
86
|
+
export function escapeHtml(s) {
|
|
87
|
+
return String(s == null ? "" : s).replace(/[&<>"']/g, (c) =>
|
|
88
|
+
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const $ = (sel, root = document) => root.querySelector(sel);
|
|
92
|
+
export const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
|
93
|
+
|
|
94
|
+
/** Tabler icon element: icon("home") → <i class="ti ti-home"> */
|
|
95
|
+
export function icon(name, extra = "") {
|
|
96
|
+
const i = document.createElement("i");
|
|
97
|
+
i.className = `ti ti-${name}${extra ? " " + extra : ""}`;
|
|
98
|
+
i.setAttribute("aria-hidden", "true");
|
|
99
|
+
i.setAttribute("data-fallback", iconFallback(name));
|
|
100
|
+
return i;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function iconFallback(name) {
|
|
104
|
+
const map = {
|
|
105
|
+
home: "H", search: "?", menu: "=", x: "x", refresh: "R",
|
|
106
|
+
"arrow-up": "^", "arrow-up-right": ">", "arrows-join": "H",
|
|
107
|
+
"chart-dots-3": "G", "grid-dots": "V", message: "C", "message-2": "C",
|
|
108
|
+
"message-plus": "+", "message-off": "C", user: "U", users: "U",
|
|
109
|
+
settings: "S", files: "F", cpu: "M", "cpu-off": "M", "player-play": ">",
|
|
110
|
+
"player-stop": "s", "layout-sidebar": "<", "layout-sidebar-right": ">",
|
|
111
|
+
"external-link": "o", trash: "x", sparkles: "*", "alert-triangle": "!",
|
|
112
|
+
"alert-circle": "!", "info-circle": "i", "circle-check": "v",
|
|
113
|
+
"circle-filled": "*", "stack-2": "S", "ruler-2": "R", binary: "B",
|
|
114
|
+
category: "T", abc: "K", "search-off": "?"
|
|
115
|
+
};
|
|
116
|
+
if (map[name]) return map[name];
|
|
117
|
+
return String(name || "?").split("-").map((part) => part[0] || "").join("").slice(0, 2).toUpperCase() || "?";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Compact relative-time formatter. */
|
|
121
|
+
export function timeAgo(value) {
|
|
122
|
+
if (!value) return "";
|
|
123
|
+
const then = new Date(value).getTime();
|
|
124
|
+
if (Number.isNaN(then)) return String(value);
|
|
125
|
+
const diff = Math.max(0, Date.now() - then);
|
|
126
|
+
const mins = Math.floor(diff / 60000);
|
|
127
|
+
if (mins < 1) return "just now";
|
|
128
|
+
if (mins < 60) return `${mins}m ago`;
|
|
129
|
+
const hrs = Math.floor(mins / 60);
|
|
130
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
131
|
+
const days = Math.floor(hrs / 24);
|
|
132
|
+
if (days < 30) return `${days}d ago`;
|
|
133
|
+
return new Date(then).toLocaleDateString();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Format a number with thousands separators / compact suffix. */
|
|
137
|
+
export function fmtNum(n) {
|
|
138
|
+
if (n == null || Number.isNaN(Number(n))) return "—";
|
|
139
|
+
const v = Number(n);
|
|
140
|
+
if (Math.abs(v) >= 1_000_000) return (v / 1_000_000).toFixed(1).replace(/\.0$/, "") + "M";
|
|
141
|
+
if (Math.abs(v) >= 1_000) return (v / 1_000).toFixed(1).replace(/\.0$/, "") + "k";
|
|
142
|
+
return v.toLocaleString();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function debounce(fn, ms = 220) {
|
|
146
|
+
let t;
|
|
147
|
+
return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); };
|
|
148
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — Sample fixtures
|
|
3
|
+
* Clearly-labeled SAMPLE data used ONLY as a graceful fallback when a backend
|
|
4
|
+
* endpoint is not yet available. The UI always renders a "Sample data" badge
|
|
5
|
+
* when these are used, so nothing here is presented as real backend output.
|
|
6
|
+
* No backend logic is implemented — these are static shapes that mirror the
|
|
7
|
+
* live API contracts so views remain usable during local setup.
|
|
8
|
+
* ========================================================================== */
|
|
9
|
+
|
|
10
|
+
export const INDEX_STATUS = {
|
|
11
|
+
generated_at: null,
|
|
12
|
+
pipelines: {
|
|
13
|
+
knowledge_graph: { state: "ready", entities: 1284, relations: 3960, last_built: null, coverage: 0.91 },
|
|
14
|
+
vector_index: { state: "ready", vectors: 48230, dimensions: 384, model: "lattice-local-hash-v1", coverage: 0.87 },
|
|
15
|
+
hybrid: { state: "ready", strategy: "weighted-rank-fusion", weights: { keyword: 0.35, vector: 0.40, graph: 0.25 }, last_eval: null },
|
|
16
|
+
},
|
|
17
|
+
sources: [
|
|
18
|
+
{ id: "src-notes", label: "Workspace Notes", files: 312, state: "indexed", progress: 1 },
|
|
19
|
+
{ id: "src-repo", label: "Connected Repo", files: 1840, state: "indexed", progress: 1 },
|
|
20
|
+
{ id: "src-uploads", label: "Uploads", files: 96, state: "indexing", progress: 0.62 },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const GRAPH = {
|
|
25
|
+
nodes: [
|
|
26
|
+
{ id: "n:lattice", label: "Lattice AI", type: "Topic", weight: 0.98, x: 0.5, y: 0.46 },
|
|
27
|
+
{ id: "n:hybrid", label: "Hybrid Search", type: "Concept", weight: 0.86, x: 0.72, y: 0.30 },
|
|
28
|
+
{ id: "n:vector", label: "Vector Index", type: "Concept", weight: 0.84, x: 0.30, y: 0.28 },
|
|
29
|
+
{ id: "n:graph", label: "Knowledge Graph", type: "Concept", weight: 0.9, x: 0.5, y: 0.74 },
|
|
30
|
+
{ id: "n:embed", label: "lattice-local-hash-v1", type: "Model", weight: 0.6, x: 0.16, y: 0.5 },
|
|
31
|
+
{ id: "n:rrf", label: "Rank Fusion", type: "Method", weight: 0.58, x: 0.86, y: 0.54 },
|
|
32
|
+
{ id: "n:notes", label: "Workspace Notes", type: "File", weight: 0.52, x: 0.36, y: 0.9 },
|
|
33
|
+
{ id: "n:repo", label: "Connected Repo", type: "File", weight: 0.55, x: 0.66, y: 0.9 },
|
|
34
|
+
],
|
|
35
|
+
edges: [
|
|
36
|
+
{ from: "n:lattice", to: "n:hybrid", type: "provides", weight: 1.0 },
|
|
37
|
+
{ from: "n:lattice", to: "n:graph", type: "builds", weight: 1.2 },
|
|
38
|
+
{ from: "n:lattice", to: "n:vector", type: "builds", weight: 1.1 },
|
|
39
|
+
{ from: "n:hybrid", to: "n:vector", type: "fuses", weight: 0.9 },
|
|
40
|
+
{ from: "n:hybrid", to: "n:graph", type: "fuses", weight: 0.9 },
|
|
41
|
+
{ from: "n:hybrid", to: "n:rrf", type: "uses", weight: 0.7 },
|
|
42
|
+
{ from: "n:vector", to: "n:embed", type: "uses", weight: 0.8 },
|
|
43
|
+
{ from: "n:graph", to: "n:notes", type: "from", weight: 0.6 },
|
|
44
|
+
{ from: "n:graph", to: "n:repo", type: "from", weight: 0.6 },
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const GRAPH_STATS = {
|
|
49
|
+
nodes: { Topic: 1, Concept: 3, Model: 1, Method: 1, File: 2 },
|
|
50
|
+
edges: { provides: 1, builds: 2, fuses: 2, uses: 2, from: 2 },
|
|
51
|
+
total_nodes: 8,
|
|
52
|
+
total_edges: 9,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function hybridResults(query) {
|
|
56
|
+
const q = (query || "retrieval").trim() || "retrieval";
|
|
57
|
+
const base = [
|
|
58
|
+
{ id: "doc-1", title: "Hybrid retrieval design notes", path: "notes/retrieval.md", snippet: `…blends the knowledge graph and the vector field for "${q}", reconciling structure with semantic proximity…`, vector: 0.91, lexical: 0.64, graph: 0.78 },
|
|
59
|
+
{ id: "doc-2", title: "Vector index configuration", path: "config/index.yaml", snippet: `…embedding model and dimensions used to build the dense field that answers "${q}"…`, vector: 0.88, lexical: 0.41, graph: 0.35 },
|
|
60
|
+
{ id: "doc-3", title: "Graph entity: Rank Fusion", path: "graph://method/rrf", snippet: `…reciprocal-rank fusion merges ranked lists so a strong "${q}" signal in either modality surfaces…`, vector: 0.54, lexical: 0.33, graph: 0.95 },
|
|
61
|
+
{ id: "doc-4", title: "Workspace memory — decisions", path: "memory/decisions.md", snippet: `…prior decision relevant to "${q}", retrieved via graph adjacency from the active answer…`, vector: 0.49, lexical: 0.58, graph: 0.71 },
|
|
62
|
+
{ id: "doc-5", title: "Connected repo: README", path: "repo/README.md", snippet: `…lexical hit on "${q}" reinforced by neighboring entities in the knowledge graph…`, vector: 0.4, lexical: 0.82, graph: 0.4 },
|
|
63
|
+
];
|
|
64
|
+
return base.map((r) => ({ ...r, score: Number((0.4 * r.vector + 0.35 * r.lexical + 0.25 * r.graph).toFixed(3)) }))
|
|
65
|
+
.sort((a, b) => b.score - a.score);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const WORKSPACE_OS = {
|
|
69
|
+
version: "3.0.0",
|
|
70
|
+
counts: { snapshots: 4, traces: 18, memories: 36, agent_runs: 12, workflows: 5, skills: 7, timeline: 24 },
|
|
71
|
+
models: { current_model: "mlx-community/local-model-4bit", loaded_models: ["mlx-community/local-model-4bit"] },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const MODELS = {
|
|
75
|
+
current: "mlx-community/local-model-4bit",
|
|
76
|
+
catalog: [
|
|
77
|
+
{ id: "mlx-community/local-model-4bit", name: "Local Model 4bit", family: "local", params: "12B", quant: "4bit", state: "loaded", context: 32768, recommended: true },
|
|
78
|
+
{ id: "mlx-community/reasoner-8bit", name: "Reasoner 8bit", family: "local", params: "8B", quant: "8bit", state: "available", context: 16384 },
|
|
79
|
+
{ id: "lattice-local-hash-v1", name: "lattice-local-hash-v1 (fallback vectors)", family: "embedding", params: "hash", quant: "local", state: "loaded", context: 384 },
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const AGENTS = [
|
|
84
|
+
{ id: "agent:planner", name: "Planner", role: "Decomposes goals into steps", state: "available", runs: 42, handoffs: ["agent:builder"] },
|
|
85
|
+
{ id: "agent:builder", name: "Builder", role: "Implements and edits files", state: "available", runs: 38, handoffs: ["agent:reviewer"] },
|
|
86
|
+
{ id: "agent:reviewer", name: "Reviewer", role: "Reviews diffs for risk", state: "idle", runs: 31, handoffs: [] },
|
|
87
|
+
{ id: "agent:retriever", name: "Retriever", role: "Runs hybrid search over the workspace", state: "available", runs: 57, handoffs: ["agent:planner"] },
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
export const PIPELINES = [
|
|
91
|
+
{ id: "pl-ingest", name: "Ingest → Embed → Graph", state: "active", stages: ["Watch source", "Chunk", "Embed", "Extract entities", "Link graph"], last_run: null, throughput: "1.8k files/run" },
|
|
92
|
+
{ id: "pl-eval", name: "Retrieval Eval", state: "idle", stages: ["Sample queries", "Hybrid search", "Score fusion", "Report"], last_run: null, throughput: "120 q/run" },
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
export const FILES = [
|
|
96
|
+
{ name: "retrieval.md", kind: "markdown", size: 4821, path: "notes/retrieval.md", indexed: true, updated: null },
|
|
97
|
+
{ name: "index.yaml", kind: "config", size: 932, path: "config/index.yaml", indexed: true, updated: null },
|
|
98
|
+
{ name: "decisions.md", kind: "markdown", size: 6210, path: "memory/decisions.md", indexed: true, updated: null },
|
|
99
|
+
{ name: "diagram.png", kind: "image", size: 184320, path: "assets/diagram.png", indexed: false, updated: null },
|
|
100
|
+
{ name: "dataset.csv", kind: "data", size: 51200, path: "data/dataset.csv", indexed: true, updated: null },
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
export const SYSINFO = { cpu_pct: 28.4, ram_pct: 61.2, gpu_mem_pct: 44.0, gpu_mem_gb: 12.6 };
|
|
104
|
+
|
|
105
|
+
export const CHAT = {
|
|
106
|
+
conversations: [
|
|
107
|
+
{ id: "conv-hybrid", title: "How hybrid search ranks", updated_at: "2026-06-06T13:20:00", messages: [
|
|
108
|
+
{ role: "user", content: "How does hybrid search rank results?", timestamp: "2026-06-06T13:19:00" },
|
|
109
|
+
{ role: "assistant", content: "It fuses two signals: the vector index scores semantic similarity, while the knowledge graph scores structural proximity. Reciprocal-rank fusion merges the two ranked lists so a strong hit in either modality surfaces.", timestamp: "2026-06-06T13:20:00" },
|
|
110
|
+
] },
|
|
111
|
+
{ id: "conv-reindex", title: "Reindex the workspace", updated_at: "2026-06-06T11:05:00", messages: [
|
|
112
|
+
{ role: "user", content: "How do I rebuild the vector index?", timestamp: "2026-06-06T11:04:00" },
|
|
113
|
+
{ role: "assistant", content: "Trigger a rebuild from the Pipeline view, or call the index rebuild endpoint. The embedding model re-encodes every chunk and the graph is relinked.", timestamp: "2026-06-06T11:05:00" },
|
|
114
|
+
] },
|
|
115
|
+
{ id: "conv-entities", title: "Entities in retrieval.md", updated_at: "2026-06-05T18:40:00", messages: [
|
|
116
|
+
{ role: "user", content: "What entities were extracted from retrieval.md?", timestamp: "2026-06-05T18:39:00" },
|
|
117
|
+
{ role: "assistant", content: "Hybrid Search, Vector Index, and Rank Fusion, each linked back to Lattice AI in the knowledge graph.", timestamp: "2026-06-05T18:40:00" },
|
|
118
|
+
] },
|
|
119
|
+
],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Build a sample graph-RAG trace (mirrors the backend /chat trace shape). */
|
|
123
|
+
export function sampleTrace(query) {
|
|
124
|
+
return {
|
|
125
|
+
question: query || "",
|
|
126
|
+
confidence: 0.86,
|
|
127
|
+
graph_nodes: GRAPH.nodes.slice(0, 4).map((n) => ({ id: n.id, title: n.label, type: n.type })),
|
|
128
|
+
source_files: FILES.slice(0, 3).map((f) => ({ source: f.path })),
|
|
129
|
+
vector_matches: [
|
|
130
|
+
{ path: "notes/retrieval.md", score: 0.91 },
|
|
131
|
+
{ path: "config/index.yaml", score: 0.78 },
|
|
132
|
+
{ path: "memory/decisions.md", score: 0.66 },
|
|
133
|
+
],
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const ADMIN = {
|
|
138
|
+
summary: { total_users: 6, active_users: 5, admin_users: 2, total_messages: 1284 },
|
|
139
|
+
users: [
|
|
140
|
+
{ email: "owner@acme.dev", nickname: "Owner", role: "owner", disabled: false, last_seen: null },
|
|
141
|
+
{ email: "admin@acme.dev", nickname: "Admin", role: "admin", disabled: false, last_seen: null },
|
|
142
|
+
{ email: "ml@acme.dev", nickname: "ML Eng", role: "member", disabled: false, last_seen: null },
|
|
143
|
+
{ email: "guest@acme.dev", nickname: "Guest", role: "viewer", disabled: true, last_seen: null },
|
|
144
|
+
],
|
|
145
|
+
roles: [
|
|
146
|
+
{ role: "owner", members: 1, caps: ["all"] },
|
|
147
|
+
{ role: "admin", members: 1, caps: ["users", "policies", "audit", "security"] },
|
|
148
|
+
{ role: "member", members: 3, caps: ["chat", "search", "files", "pipeline"] },
|
|
149
|
+
{ role: "viewer", members: 1, caps: ["chat", "search"] },
|
|
150
|
+
],
|
|
151
|
+
audit: [
|
|
152
|
+
{ ts: null, actor: "admin@acme.dev", action: "policy.update", target: "local_file_access", severity: "informational" },
|
|
153
|
+
{ ts: null, actor: "ml@acme.dev", action: "search.hybrid", target: "q: retrieval design", severity: "informational" },
|
|
154
|
+
{ ts: null, actor: "owner@acme.dev", action: "user.invite", target: "guest@acme.dev", severity: "notice" },
|
|
155
|
+
{ ts: null, actor: "system", action: "index.rebuild", target: "vector_index", severity: "informational" },
|
|
156
|
+
],
|
|
157
|
+
security: {
|
|
158
|
+
risk_rate: 1.2,
|
|
159
|
+
risky_messages: 3,
|
|
160
|
+
compliant_messages: 1281,
|
|
161
|
+
severity_counts: { high: 0, medium: 1, low: 2 },
|
|
162
|
+
dlp_fields: [{ field: "email", hits: 4 }, { field: "api_key", hits: 1 }],
|
|
163
|
+
},
|
|
164
|
+
policies: [
|
|
165
|
+
{ id: "local_file_access", label: "Local file access", value: "Approval-token gated (per path/user/action)", enforced: true },
|
|
166
|
+
{ id: "package_install", label: "Package install", value: "Admin-only with audit trail", enforced: true },
|
|
167
|
+
{ id: "data_residency", label: "Data residency", value: "Single-tenant local storage (~/.ltcai)", enforced: true },
|
|
168
|
+
{ id: "model_egress", label: "Model egress", value: "Local-only (no external inference)", enforced: true },
|
|
169
|
+
],
|
|
170
|
+
vpc: { provider: "local", region: "on-prem", vpn_status: "standby", peering_status: "not_configured", private_subnets: [], enabled: false },
|
|
171
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — Hash router
|
|
3
|
+
* Maps location.hash → { key, params }. Hash routing keeps the SPA shell
|
|
4
|
+
* served by a single static route (/app) with no server-side rewrites.
|
|
5
|
+
* #/home → { key: "home" }
|
|
6
|
+
* #/admin/users → { key: "admin/users" }
|
|
7
|
+
* #/chat?new=1 → { key: "chat", params: { new: "1" } }
|
|
8
|
+
* ========================================================================== */
|
|
9
|
+
|
|
10
|
+
export function createRouter({ onRoute, fallback = "home" }) {
|
|
11
|
+
function parse() {
|
|
12
|
+
let hash = location.hash.replace(/^#\/?/, "");
|
|
13
|
+
let query = "";
|
|
14
|
+
const qi = hash.indexOf("?");
|
|
15
|
+
if (qi >= 0) { query = hash.slice(qi + 1); hash = hash.slice(0, qi); }
|
|
16
|
+
const key = hash.replace(/\/+$/, "") || fallback;
|
|
17
|
+
const params = {};
|
|
18
|
+
if (query) new URLSearchParams(query).forEach((v, k) => { params[k] = v; });
|
|
19
|
+
return { key, params };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function handle() { onRoute(parse()); }
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
start() {
|
|
26
|
+
window.addEventListener("hashchange", handle);
|
|
27
|
+
if (!location.hash) { location.replace("#/" + fallback); }
|
|
28
|
+
else { handle(); }
|
|
29
|
+
},
|
|
30
|
+
current: parse,
|
|
31
|
+
navigate(key, params) {
|
|
32
|
+
const qs = params && Object.keys(params).length ? "?" + new URLSearchParams(params).toString() : "";
|
|
33
|
+
const next = "#/" + String(key).replace(/^#?\/?/, "") + qs;
|
|
34
|
+
if (location.hash === next) handle(); else location.hash = next;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — Information architecture (single source of truth)
|
|
3
|
+
*
|
|
4
|
+
* One declarative table drives the nav rail, the command palette, the router,
|
|
5
|
+
* breadcrumbs, and lazy view loading. Mode gating (Basic < Advanced < Admin)
|
|
6
|
+
* and the Admin section live here so the whole shell stays consistent.
|
|
7
|
+
* ========================================================================== */
|
|
8
|
+
|
|
9
|
+
export const MODE_RANK = { basic: 0, advanced: 1, admin: 2 };
|
|
10
|
+
|
|
11
|
+
/** Nav groups in display order. */
|
|
12
|
+
export const GROUPS = [
|
|
13
|
+
{ id: "workspace", label: "Workspace" },
|
|
14
|
+
{ id: "retrieval", label: "Retrieval" },
|
|
15
|
+
{ id: "data", label: "Data" },
|
|
16
|
+
{ id: "compute", label: "Compute" },
|
|
17
|
+
{ id: "system", label: "System" },
|
|
18
|
+
{ id: "admin", label: "Administration", adminOnly: true },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Route table. `minMode` = lowest mode in which the item appears in the rail
|
|
23
|
+
* (deep-links still resolve). `view` = module basename under js/views/.
|
|
24
|
+
*/
|
|
25
|
+
export const ROUTES = [
|
|
26
|
+
// Workspace
|
|
27
|
+
{ key: "home", label: "Home", icon: "layout-dashboard", group: "workspace", minMode: "basic", view: "home", title: "Home", desc: "Your local-first AI workspace at a glance." },
|
|
28
|
+
{ key: "chat", label: "Chat", icon: "message-2", group: "workspace", minMode: "basic", view: "chat", title: "Chat", desc: "Grounded conversation over your indexed workspace." },
|
|
29
|
+
|
|
30
|
+
// Retrieval (the product identity)
|
|
31
|
+
{ 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." },
|
|
32
|
+
{ 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." },
|
|
33
|
+
|
|
34
|
+
// Data
|
|
35
|
+
{ key: "files", label: "Files", icon: "folders", group: "data", minMode: "basic", view: "files", title: "Files", desc: "Connected sources and indexed documents." },
|
|
36
|
+
{ key: "pipeline", label: "Pipeline", icon: "git-branch", group: "data", minMode: "advanced", view: "pipeline", title: "Pipeline", desc: "Ingest, embed, and graph-build flows." },
|
|
37
|
+
|
|
38
|
+
// Compute
|
|
39
|
+
{ key: "agents", label: "Agents", icon: "robot", group: "compute", minMode: "advanced", view: "agents", title: "Agents", desc: "Multi-agent roles, runs, and handoffs." },
|
|
40
|
+
{ key: "models", label: "Models", icon: "cpu", group: "compute", minMode: "basic", view: "models", title: "Models", desc: "Local MLX models and embeddings." },
|
|
41
|
+
{ 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." },
|
|
42
|
+
|
|
43
|
+
// System
|
|
44
|
+
{ key: "settings", label: "Settings", icon: "settings", group: "system", minMode: "basic", view: "settings", title: "Settings", desc: "Appearance, workspace, and integrations." },
|
|
45
|
+
|
|
46
|
+
// Admin
|
|
47
|
+
{ key: "admin/users", label: "Users", icon: "users", group: "admin", minMode: "admin", view: "admin-users", title: "Users", desc: "Workspace members and access.", admin: true },
|
|
48
|
+
{ key: "admin/permissions", label: "Permissions", icon: "key", group: "admin", minMode: "admin", view: "admin-permissions", title: "Permissions", desc: "Roles and capability mapping.", admin: true },
|
|
49
|
+
{ key: "admin/audit", label: "Audit Logs", icon: "report-search", group: "admin", minMode: "admin", view: "admin-audit", title: "Audit Logs", desc: "Activity and access trail.", admin: true },
|
|
50
|
+
{ key: "admin/security", label: "Security", icon: "shield-check", group: "admin", minMode: "admin", view: "admin-security", title: "Security", desc: "Sensitive-data signals and DLP.", admin: true },
|
|
51
|
+
{ key: "admin/policies", label: "Policies", icon: "file-certificate", group: "admin", minMode: "admin", view: "admin-policies", title: "Policies", desc: "Governance and enforcement.", admin: true },
|
|
52
|
+
{ key: "admin/private-vpc", label: "Private VPC", icon: "cloud-lock", group: "admin", minMode: "admin", view: "admin-private-vpc", title: "Private VPC", desc: "Network isolation and peering.", admin: true },
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
export const ROUTE_BY_KEY = Object.fromEntries(ROUTES.map((r) => [r.key, r]));
|
|
56
|
+
|
|
57
|
+
/** Routes visible in the rail for a given mode. */
|
|
58
|
+
export function visibleRoutes(mode) {
|
|
59
|
+
const rank = MODE_RANK[mode] ?? 0;
|
|
60
|
+
return ROUTES.filter((r) => {
|
|
61
|
+
if (r.admin) return mode === "admin";
|
|
62
|
+
return (MODE_RANK[r.minMode] ?? 0) <= rank;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Lazy-load a view module by basename. Cached. */
|
|
67
|
+
const cache = new Map();
|
|
68
|
+
export async function loadView(view) {
|
|
69
|
+
if (cache.has(view)) return cache.get(view);
|
|
70
|
+
const mod = await import(`../views/${view}.js`);
|
|
71
|
+
cache.set(view, mod);
|
|
72
|
+
return mod;
|
|
73
|
+
}
|