ltcai 2.2.7 → 3.1.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 +72 -34
- package/docs/CHANGELOG.md +119 -0
- package/docs/V3_BACKEND_ARCHITECTURE.md +138 -0
- package/docs/V3_FRONTEND.md +139 -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 +5 -2
- package/latticeai/api/chat.py +10 -2
- package/latticeai/api/search.py +240 -0
- package/latticeai/api/static_routes.py +11 -2
- package/latticeai/core/config.py +18 -0
- package/latticeai/core/embedding_providers.py +625 -0
- package/latticeai/core/local_embeddings.py +86 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +65 -1
- package/latticeai/services/agent_runtime.py +245 -0
- package/latticeai/services/search_service.py +346 -0
- package/package.json +13 -6
- 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 +10 -10
- package/static/css/reference/account.css +137 -1
- package/static/css/reference/chat.css +31 -37
- package/static/css/responsive.css +42 -0
- package/static/css/tokens.5a595671.css +260 -0
- package/static/css/tokens.css +125 -130
- package/static/graph.html +9 -9
- package/static/manifest.json +3 -3
- package/static/plugins.html +4 -4
- package/static/scripts/account.js +4 -4
- package/static/scripts/chat.js +40 -8
- package/static/scripts/workspace.js +78 -0
- package/static/sw.js +3 -1
- package/static/v3/asset-manifest.json +47 -0
- package/static/v3/css/lattice.base.css +128 -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 +447 -0
- package/static/v3/css/lattice.shell.4920f42d.css +407 -0
- package/static/v3/css/lattice.shell.css +407 -0
- package/static/v3/css/lattice.tokens.c597ff81.css +132 -0
- package/static/v3/css/lattice.tokens.css +132 -0
- package/static/v3/css/lattice.views.3ee19d4e.css +277 -0
- package/static/v3/css/lattice.views.css +277 -0
- package/static/v3/index.html +69 -0
- package/static/v3/js/app.46fb61d9.js +26 -0
- package/static/v3/js/app.js +26 -0
- package/static/v3/js/core/api.22a41d42.js +344 -0
- package/static/v3/js/core/api.js +344 -0
- package/static/v3/js/core/components.4c83e0a9.js +222 -0
- package/static/v3/js/core/components.js +222 -0
- package/static/v3/js/core/dom.a2773eb0.js +148 -0
- package/static/v3/js/core/dom.js +148 -0
- package/static/v3/js/core/router.584570f2.js +37 -0
- package/static/v3/js/core/router.js +37 -0
- package/static/v3/js/core/routes.f935dd50.js +78 -0
- package/static/v3/js/core/routes.js +78 -0
- package/static/v3/js/core/shell.1b6199d6.js +363 -0
- package/static/v3/js/core/shell.js +363 -0
- package/static/v3/js/core/store.34ebd5e6.js +113 -0
- package/static/v3/js/core/store.js +113 -0
- package/static/v3/js/views/admin-audit.660a1fb1.js +185 -0
- package/static/v3/js/views/admin-audit.js +185 -0
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +177 -0
- package/static/v3/js/views/admin-permissions.js +177 -0
- package/static/v3/js/views/admin-policies.3658fd86.js +102 -0
- package/static/v3/js/views/admin-policies.js +102 -0
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +135 -0
- package/static/v3/js/views/admin-private-vpc.js +135 -0
- package/static/v3/js/views/admin-security.07c66b72.js +180 -0
- package/static/v3/js/views/admin-security.js +180 -0
- package/static/v3/js/views/admin-users.03bac88c.js +168 -0
- package/static/v3/js/views/admin-users.js +168 -0
- package/static/v3/js/views/agents.14e48bdd.js +193 -0
- package/static/v3/js/views/agents.js +193 -0
- package/static/v3/js/views/chat.718144ce.js +449 -0
- package/static/v3/js/views/chat.js +449 -0
- package/static/v3/js/views/files.4935197e.js +186 -0
- package/static/v3/js/views/files.js +186 -0
- package/static/v3/js/views/home.cdde3b32.js +119 -0
- package/static/v3/js/views/home.js +119 -0
- package/static/v3/js/views/hybrid-search.b22b97e0.js +195 -0
- package/static/v3/js/views/hybrid-search.js +195 -0
- package/static/v3/js/views/knowledge-graph.a14ea7e7.js +237 -0
- package/static/v3/js/views/knowledge-graph.js +237 -0
- package/static/v3/js/views/models.a1ffa147.js +256 -0
- package/static/v3/js/views/models.js +256 -0
- package/static/v3/js/views/my-computer.1b2ff621.js +237 -0
- package/static/v3/js/views/my-computer.js +237 -0
- package/static/v3/js/views/pipeline.c522f1ce.js +157 -0
- package/static/v3/js/views/pipeline.js +157 -0
- package/static/v3/js/views/settings.4f777210.js +250 -0
- package/static/v3/js/views/settings.js +250 -0
- package/static/workflows.html +4 -4
- package/static/workspace.css +340 -2
- package/static/workspace.html +43 -24
- 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
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
function assetUrl(key, fallback) {
|
|
69
|
+
const manifest = window.__LT_ASSET_MANIFEST__;
|
|
70
|
+
return (manifest && manifest.assets && manifest.assets[key]) || fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function loadView(view) {
|
|
74
|
+
if (cache.has(view)) return cache.get(view);
|
|
75
|
+
const mod = await import(assetUrl(`static/v3/js/views/${view}.js`, `../views/${view}.js`));
|
|
76
|
+
cache.set(view, mod);
|
|
77
|
+
return mod;
|
|
78
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
function assetUrl(key, fallback) {
|
|
69
|
+
const manifest = window.__LT_ASSET_MANIFEST__;
|
|
70
|
+
return (manifest && manifest.assets && manifest.assets[key]) || fallback;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function loadView(view) {
|
|
74
|
+
if (cache.has(view)) return cache.get(view);
|
|
75
|
+
const mod = await import(assetUrl(`static/v3/js/views/${view}.js`, `../views/${view}.js`));
|
|
76
|
+
cache.set(view, mod);
|
|
77
|
+
return mod;
|
|
78
|
+
}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Lattice AI v3 — Application shell
|
|
3
|
+
* Builds the persistent chrome (nav rail, topbar, view outlet), wires the
|
|
4
|
+
* router, workspace + mode + theme switchers, command palette, and mobile
|
|
5
|
+
* drawer. Renders views by lazy-loading their module and calling render(ctx).
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import { h, icon, $, $$ } from "./dom.a2773eb0.js";
|
|
9
|
+
import { store } from "./store.34ebd5e6.js";
|
|
10
|
+
import { api } from "./api.22a41d42.js";
|
|
11
|
+
import * as c from "./components.4c83e0a9.js";
|
|
12
|
+
import { createRouter } from "./router.584570f2.js";
|
|
13
|
+
import { GROUPS, ROUTES, ROUTE_BY_KEY, MODE_RANK, visibleRoutes, loadView } from "./routes.f935dd50.js";
|
|
14
|
+
|
|
15
|
+
const MODES = [
|
|
16
|
+
{ key: "basic", label: "Basic", icon: "circle" },
|
|
17
|
+
{ key: "advanced", label: "Advanced", icon: "circles" },
|
|
18
|
+
{ key: "admin", label: "Admin", icon: "shield-half" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const ctxBase = { h, icon, api, store, c };
|
|
22
|
+
|
|
23
|
+
let els = {};
|
|
24
|
+
let router;
|
|
25
|
+
let currentRoute = null;
|
|
26
|
+
|
|
27
|
+
export function boot(rootEl) {
|
|
28
|
+
rootEl.classList.add("lt3-app");
|
|
29
|
+
rootEl.append(
|
|
30
|
+
h("a.lt3-skip", { href: "#lt3-view" }, "Skip to content"),
|
|
31
|
+
h("div.lt3-rail__scrim", { on: { click: closeDrawer } }),
|
|
32
|
+
buildRail(),
|
|
33
|
+
buildMain(),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
cacheEls(rootEl);
|
|
37
|
+
store.subscribe(onStateChange);
|
|
38
|
+
|
|
39
|
+
router = createRouter({ onRoute: renderRoute, fallback: "home" });
|
|
40
|
+
wireGlobalKeys();
|
|
41
|
+
router.start();
|
|
42
|
+
|
|
43
|
+
// Background: hydrate workspaces, identity, index status.
|
|
44
|
+
hydrate();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* ── Rail ────────────────────────────────────────────────────────────────── */
|
|
48
|
+
function buildRail() {
|
|
49
|
+
return h("aside.lt3-rail", { id: "lt3-rail", "aria-label": "Primary" },
|
|
50
|
+
h("div.lt3-rail__brand",
|
|
51
|
+
h("div.lt3-rail__logo", { html: latticeMark() }),
|
|
52
|
+
h("div.lt3-rail__word", h("b", "Lattice AI"), h("small", "Local-First Workspace")),
|
|
53
|
+
h("button.lt3-iconbtn.lt3-iconbtn--sm.lt3-rail__close", { "aria-label": "Close menu", on: { click: closeDrawer } }, icon("x")),
|
|
54
|
+
),
|
|
55
|
+
h("div.lt3-rail__scope", { id: "lt3-scope" }),
|
|
56
|
+
h("nav.lt3-rail__nav", { id: "lt3-nav", "aria-label": "Sections" }),
|
|
57
|
+
h("div.lt3-rail__foot",
|
|
58
|
+
h("button.lt3-rail__user", { id: "lt3-user", "aria-label": "Account", on: { click: () => router.navigate("settings") } }),
|
|
59
|
+
h("button.lt3-iconbtn", { id: "lt3-theme", "aria-label": "Toggle theme", title: "Toggle theme", on: { click: () => store.toggleTheme() } }, icon("moon")),
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderNav() {
|
|
65
|
+
const nav = els.nav;
|
|
66
|
+
const mode = store.get().mode;
|
|
67
|
+
const routes = visibleRoutes(mode);
|
|
68
|
+
nav.replaceChildren();
|
|
69
|
+
for (const group of GROUPS) {
|
|
70
|
+
const items = routes.filter((r) => r.group === group.id);
|
|
71
|
+
if (!items.length) continue;
|
|
72
|
+
const groupEl = h("div.lt3-navgroup",
|
|
73
|
+
h("div.lt3-navgroup__label", group.label),
|
|
74
|
+
items.map((r) => navItem(r)),
|
|
75
|
+
);
|
|
76
|
+
nav.append(groupEl);
|
|
77
|
+
}
|
|
78
|
+
markActive();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function navItem(route) {
|
|
82
|
+
return h("a.lt3-navitem", {
|
|
83
|
+
href: "#/" + route.key,
|
|
84
|
+
dataset: { key: route.key },
|
|
85
|
+
on: { click: () => closeDrawer() },
|
|
86
|
+
},
|
|
87
|
+
icon(route.icon),
|
|
88
|
+
h("span.lt3-navitem__label", route.label),
|
|
89
|
+
route.key === "hybrid-search" ? h("span.lt3-navitem__dot", { style: { background: "var(--lt3-pillar-hybrid)" } }) : null,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function markActive() {
|
|
94
|
+
$$(".lt3-navitem", els.nav).forEach((a) => {
|
|
95
|
+
const on = a.dataset.key === (currentRoute && currentRoute.key);
|
|
96
|
+
if (on) a.setAttribute("aria-current", "page"); else a.removeAttribute("aria-current");
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function renderScope() {
|
|
101
|
+
const ws = store.activeWorkspace();
|
|
102
|
+
els.scope.replaceChildren(
|
|
103
|
+
h("button.lt3-scope", { "aria-haspopup": "listbox", on: { click: openScopeMenu } },
|
|
104
|
+
h("div.lt3-scope__icon", icon(ws.type === "organization" ? "building-community" : "user")),
|
|
105
|
+
h("div.lt3-scope__meta", h("b", ws.name), h("small", `${ws.type} · ${ws.your_role || "member"}`)),
|
|
106
|
+
icon("selector"),
|
|
107
|
+
),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function renderUser() {
|
|
112
|
+
const u = store.get().user;
|
|
113
|
+
const initials = (u.nickname || u.email || "U").slice(0, 2);
|
|
114
|
+
els.user.replaceChildren(
|
|
115
|
+
h("span.lt3-avatar", initials),
|
|
116
|
+
h("div.lt3-rail__user-meta", h("b", u.nickname || u.email || "You"), h("small", u.role || "local")),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function updateThemeIcon() {
|
|
121
|
+
const dark = document.documentElement.getAttribute("data-lt-theme") === "dark"
|
|
122
|
+
|| (!store.get().theme && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches);
|
|
123
|
+
els.theme.replaceChildren(icon(dark ? "sun" : "moon"));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ── Main / topbar ──────────────────────────────────────────────────────── */
|
|
127
|
+
function buildMain() {
|
|
128
|
+
return h("div.lt3-main",
|
|
129
|
+
h("header.lt3-topbar",
|
|
130
|
+
h("button.lt3-iconbtn.lt3-topbar__menu", { "aria-label": "Open menu", on: { click: openDrawer } }, icon("menu-2")),
|
|
131
|
+
h("div.lt3-topbar__crumbs", { id: "lt3-crumbs" }),
|
|
132
|
+
h("div.lt3-spacer"),
|
|
133
|
+
h("button.lt3-cmd-trigger", { "aria-label": "Search and commands", on: { click: openPalette } },
|
|
134
|
+
icon("search"), h("span", "Search & commands"), h("span.lt3-kbd", "⌘K")),
|
|
135
|
+
h("div", { id: "lt3-idxchip" }),
|
|
136
|
+
h("div.lt3-mode", { id: "lt3-mode", role: "tablist", "aria-label": "Workspace mode" },
|
|
137
|
+
MODES.map((m) => h("button", {
|
|
138
|
+
type: "button", role: "tab", dataset: { mode: m.key },
|
|
139
|
+
on: { click: () => store.setMode(m.key) },
|
|
140
|
+
}, icon(m.icon), h("span", m.label))),
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
h("main.lt3-view", { id: "lt3-view", tabindex: "-1" },
|
|
144
|
+
h("div.lt3-view__inner", { id: "lt3-outlet" }),
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function renderMode() {
|
|
150
|
+
$$("#lt3-mode button", els.root).forEach((b) => b.dataset.active = String(b.dataset.mode === store.get().mode));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderCrumbs() {
|
|
154
|
+
const r = currentRoute;
|
|
155
|
+
if (!r) return;
|
|
156
|
+
const parts = [h("span.lt3-crumb", store.activeWorkspace().name)];
|
|
157
|
+
if (r.group === "admin") parts.push(icon("chevron-right"), h("span.lt3-crumb", "Admin"));
|
|
158
|
+
parts.push(icon("chevron-right"), h("span.lt3-crumb.lt3-crumb--current", r.title || r.label));
|
|
159
|
+
els.crumbs.replaceChildren(...parts);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderIndexChip() {
|
|
163
|
+
els.idxchip.replaceChildren(c.indexChip(store.get().indexStatus));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ── View rendering ─────────────────────────────────────────────────────── */
|
|
167
|
+
async function renderRoute({ key, params }) {
|
|
168
|
+
let route = ROUTE_BY_KEY[key] || ROUTE_BY_KEY.home;
|
|
169
|
+
// Deep-linking into an admin area surfaces Admin mode so the rail matches.
|
|
170
|
+
if (route.admin && store.get().mode !== "admin") store.setMode("admin");
|
|
171
|
+
currentRoute = route;
|
|
172
|
+
store.setRoute({ key: route.key, params });
|
|
173
|
+
|
|
174
|
+
document.title = `${route.title || route.label} · Lattice AI`;
|
|
175
|
+
markActive();
|
|
176
|
+
renderCrumbs();
|
|
177
|
+
|
|
178
|
+
const outlet = els.outlet;
|
|
179
|
+
outlet.replaceChildren(c.loading({ lines: 4, block: true }));
|
|
180
|
+
els.view.scrollTop = 0;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const mod = await loadView(route.view);
|
|
184
|
+
if (currentRoute !== route) return; // navigated away during load
|
|
185
|
+
els.view.classList.toggle("lt3-view--flush", mod.layout === "flush");
|
|
186
|
+
const ctx = { ...ctxBase, route, params, navigate: router.navigate, toast: c.toast };
|
|
187
|
+
const node = await mod.render(ctx);
|
|
188
|
+
if (currentRoute !== route) return;
|
|
189
|
+
outlet.replaceChildren(node);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error("[shell] view render failed:", route.view, err);
|
|
192
|
+
outlet.replaceChildren(c.errorState(`View "${route.label}" failed to load.`, () => renderRoute({ key: route.key, params })));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function renderCurrent() {
|
|
197
|
+
if (currentRoute) renderRoute({ key: currentRoute.key, params: store.get().route.params || {} });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ── State reactions ────────────────────────────────────────────────────── */
|
|
201
|
+
function onStateChange(_state, change) {
|
|
202
|
+
switch (change.type) {
|
|
203
|
+
case "mode": renderNav(); renderMode(); renderCurrent(); break;
|
|
204
|
+
case "workspace": renderScope(); renderCrumbs(); renderCurrent(); break;
|
|
205
|
+
case "workspaces": renderScope(); break;
|
|
206
|
+
case "user": renderUser(); break;
|
|
207
|
+
case "theme": updateThemeIcon(); break;
|
|
208
|
+
case "index": renderIndexChip(); break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── Workspace scope menu ───────────────────────────────────────────────── */
|
|
213
|
+
function openScopeMenu(ev) {
|
|
214
|
+
ev.stopPropagation();
|
|
215
|
+
closeMenus();
|
|
216
|
+
const rect = ev.currentTarget.getBoundingClientRect();
|
|
217
|
+
const list = store.get().workspaces;
|
|
218
|
+
const menu = h("div.lt3-menu", { id: "lt3-scope-menu", role: "listbox", style: { top: rect.bottom + 6 + "px", left: rect.left + "px" } },
|
|
219
|
+
list.map((w) => h("button.lt3-menu__item", {
|
|
220
|
+
role: "option", dataset: { active: String(w.workspace_id === store.get().workspaceId) },
|
|
221
|
+
on: { click: () => { store.setWorkspace(w.workspace_id); closeMenus(); } },
|
|
222
|
+
},
|
|
223
|
+
icon(w.type === "organization" ? "building-community" : "user"),
|
|
224
|
+
h("div", h("div", { style: { fontWeight: 600 } }, w.name), h("small.lt3-faint", { style: { textTransform: "capitalize" } }, w.type)),
|
|
225
|
+
w.workspace_id === store.get().workspaceId ? icon("check", "") : null,
|
|
226
|
+
)),
|
|
227
|
+
h("div.lt3-menu__sep"),
|
|
228
|
+
h("button.lt3-menu__item", { on: { click: () => { c.toast("Organization creation opens in Settings", "info"); closeMenus(); router.navigate("settings"); } } },
|
|
229
|
+
icon("plus"), "New organization"),
|
|
230
|
+
);
|
|
231
|
+
document.body.append(menu);
|
|
232
|
+
setTimeout(() => document.addEventListener("click", closeMenusOnce, { once: true }), 0);
|
|
233
|
+
}
|
|
234
|
+
function closeMenus() { $$(".lt3-menu").forEach((m) => m.remove()); }
|
|
235
|
+
function closeMenusOnce() { closeMenus(); }
|
|
236
|
+
|
|
237
|
+
/* ── Mobile drawer ──────────────────────────────────────────────────────── */
|
|
238
|
+
function openDrawer() { els.root.dataset.drawer = "open"; }
|
|
239
|
+
function closeDrawer() { delete els.root.dataset.drawer; }
|
|
240
|
+
|
|
241
|
+
/* ── Command palette ────────────────────────────────────────────────────── */
|
|
242
|
+
function paletteItems() {
|
|
243
|
+
const nav = ROUTES.map((r) => ({
|
|
244
|
+
group: "Go to", label: r.label, icon: r.icon, hint: r.group,
|
|
245
|
+
run: () => router.navigate(r.key),
|
|
246
|
+
}));
|
|
247
|
+
const actions = [
|
|
248
|
+
{ group: "Actions", label: "Toggle light / dark theme", icon: "contrast", run: () => store.toggleTheme() },
|
|
249
|
+
{ group: "Actions", label: "Mode: Basic", icon: "circle", run: () => store.setMode("basic") },
|
|
250
|
+
{ group: "Actions", label: "Mode: Advanced", icon: "circles", run: () => store.setMode("advanced") },
|
|
251
|
+
{ group: "Actions", label: "Mode: Admin", icon: "shield-half", run: () => store.setMode("admin") },
|
|
252
|
+
{ group: "Actions", label: "New chat", icon: "message-plus", run: () => router.navigate("chat", { new: "1" }) },
|
|
253
|
+
{ group: "Actions", label: "Run hybrid search", icon: "arrows-join", run: () => router.navigate("hybrid-search") },
|
|
254
|
+
];
|
|
255
|
+
return [...nav, ...actions];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function openPalette() {
|
|
259
|
+
if ($("#lt3-palette")) return;
|
|
260
|
+
const all = paletteItems();
|
|
261
|
+
let active = 0, filtered = all;
|
|
262
|
+
|
|
263
|
+
const listEl = h("div.lt3-palette__list");
|
|
264
|
+
const input = h("input", { type: "text", placeholder: "Search views, run a command…", "aria-label": "Command palette", autocomplete: "off" });
|
|
265
|
+
const palette = h("div.lt3-palette", { id: "lt3-palette", role: "dialog", "aria-modal": "true", "aria-label": "Command palette" },
|
|
266
|
+
h("div.lt3-palette__input", icon("search"), input, h("span.lt3-kbd", "Esc")),
|
|
267
|
+
listEl,
|
|
268
|
+
);
|
|
269
|
+
const scrim = h("div.lt3-scrim", { id: "lt3-palette-scrim", on: { click: close } });
|
|
270
|
+
document.body.append(scrim, palette);
|
|
271
|
+
input.focus();
|
|
272
|
+
|
|
273
|
+
function renderList() {
|
|
274
|
+
listEl.replaceChildren();
|
|
275
|
+
if (!filtered.length) { listEl.append(h("div.lt3-palette__empty", "No matches")); return; }
|
|
276
|
+
let lastGroup = null;
|
|
277
|
+
filtered.forEach((item, i) => {
|
|
278
|
+
if (item.group !== lastGroup) { listEl.append(h("div.lt3-palette__group-label", item.group)); lastGroup = item.group; }
|
|
279
|
+
listEl.append(h("button.lt3-palette__item", {
|
|
280
|
+
dataset: { active: String(i === active) },
|
|
281
|
+
on: { click: () => { item.run(); close(); }, mousemove: () => { if (active !== i) { active = i; paint(); } } },
|
|
282
|
+
}, icon(item.icon), h("span", item.label), item.hint && h("small", item.hint)));
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
function paint() { $$(".lt3-palette__item", listEl).forEach((el, i) => el.dataset.active = String(i === active)); ensureVisible(); }
|
|
286
|
+
function ensureVisible() {
|
|
287
|
+
const el = $$(".lt3-palette__item", listEl)[active];
|
|
288
|
+
if (el) el.scrollIntoView({ block: "nearest" });
|
|
289
|
+
}
|
|
290
|
+
function filter() {
|
|
291
|
+
const q = input.value.trim().toLowerCase();
|
|
292
|
+
filtered = !q ? all : all.filter((it) => (it.label + " " + (it.hint || "")).toLowerCase().includes(q));
|
|
293
|
+
active = 0; renderList();
|
|
294
|
+
}
|
|
295
|
+
function close() { palette.remove(); scrim.remove(); document.removeEventListener("keydown", onKey, true); }
|
|
296
|
+
function onKey(e) {
|
|
297
|
+
if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); close(); }
|
|
298
|
+
else if (e.key === "ArrowDown") { e.preventDefault(); active = Math.min(filtered.length - 1, active + 1); paint(); }
|
|
299
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); active = Math.max(0, active - 1); paint(); }
|
|
300
|
+
else if (e.key === "Enter") { e.preventDefault(); const it = filtered[active]; if (it) { it.run(); close(); } }
|
|
301
|
+
}
|
|
302
|
+
input.addEventListener("input", filter);
|
|
303
|
+
document.addEventListener("keydown", onKey, true);
|
|
304
|
+
renderList();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function wireGlobalKeys() {
|
|
308
|
+
document.addEventListener("keydown", (e) => {
|
|
309
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); openPalette(); }
|
|
310
|
+
if (e.key === "Escape") { closeMenus(); closeDrawer(); }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* ── Hydration ──────────────────────────────────────────────────────────── */
|
|
315
|
+
async function hydrate() {
|
|
316
|
+
// Identity (best-effort; never blocks the UI).
|
|
317
|
+
api.raw("/account/profile").then((r) => {
|
|
318
|
+
if (r.ok && r.data && (r.data.email || r.data.nickname)) {
|
|
319
|
+
store.setUser({ email: r.data.email, nickname: r.data.nickname || r.data.email, role: r.data.role || "user" });
|
|
320
|
+
} else { renderUser(); }
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Workspaces from the OS payload (fallback-safe).
|
|
324
|
+
api.workspaceOs().then((r) => {
|
|
325
|
+
const reg = r.data && r.data.workspace_registry;
|
|
326
|
+
if (reg && Array.isArray(reg.workspaces) && reg.workspaces.length) store.setWorkspaces(reg.workspaces);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Index status powers the topbar chip + Home pillars.
|
|
330
|
+
api.indexStatus().then((r) => store.setIndexStatus(r.data));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ── Init helpers ───────────────────────────────────────────────────────── */
|
|
334
|
+
function cacheEls(root) {
|
|
335
|
+
els = {
|
|
336
|
+
root,
|
|
337
|
+
nav: $("#lt3-nav", root),
|
|
338
|
+
scope: $("#lt3-scope", root),
|
|
339
|
+
user: $("#lt3-user", root),
|
|
340
|
+
theme: $("#lt3-theme", root),
|
|
341
|
+
crumbs: $("#lt3-crumbs", root),
|
|
342
|
+
idxchip: $("#lt3-idxchip", root),
|
|
343
|
+
outlet: $("#lt3-outlet", root),
|
|
344
|
+
view: $("#lt3-view", root),
|
|
345
|
+
};
|
|
346
|
+
renderNav();
|
|
347
|
+
renderScope();
|
|
348
|
+
renderUser();
|
|
349
|
+
renderMode();
|
|
350
|
+
updateThemeIcon();
|
|
351
|
+
renderIndexChip();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function latticeMark() {
|
|
355
|
+
// Crystalline lattice glyph — the product mark.
|
|
356
|
+
return `<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
357
|
+
<path d="M12 2.5 4 7v10l8 4.5L20 17V7L12 2.5Z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round" opacity=".55"/>
|
|
358
|
+
<path d="M12 7.5 7.5 10v4L12 16.5 16.5 14v-4L12 7.5Z" fill="currentColor" opacity=".9"/>
|
|
359
|
+
<circle cx="12" cy="2.5" r="1.3" fill="currentColor"/><circle cx="4" cy="7" r="1.1" fill="currentColor"/>
|
|
360
|
+
<circle cx="20" cy="7" r="1.1" fill="currentColor"/><circle cx="4" cy="17" r="1.1" fill="currentColor"/>
|
|
361
|
+
<circle cx="20" cy="17" r="1.1" fill="currentColor"/><circle cx="12" cy="21.5" r="1.3" fill="currentColor"/>
|
|
362
|
+
</svg>`;
|
|
363
|
+
}
|