ltcai 4.0.1 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +33 -24
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +84 -0
  4. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  5. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  6. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  7. package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
  8. package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
  9. package/docs/V4_2_VALIDATION_REPORT.md +89 -0
  10. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
  11. package/frontend/index.html +24 -0
  12. package/frontend/openapi.json +14436 -0
  13. package/frontend/src/App.tsx +184 -0
  14. package/frontend/src/api/client.ts +320 -0
  15. package/frontend/src/api/openapi.ts +16921 -0
  16. package/frontend/src/components/primitives.tsx +204 -0
  17. package/frontend/src/components/ui/badge.tsx +27 -0
  18. package/frontend/src/components/ui/button.tsx +37 -0
  19. package/frontend/src/components/ui/card.tsx +22 -0
  20. package/frontend/src/components/ui/input.tsx +16 -0
  21. package/frontend/src/components/ui/textarea.tsx +16 -0
  22. package/frontend/src/lib/utils.ts +33 -0
  23. package/frontend/src/main.tsx +23 -0
  24. package/frontend/src/pages/Act.tsx +245 -0
  25. package/frontend/src/pages/Ask.tsx +200 -0
  26. package/frontend/src/pages/Brain.tsx +267 -0
  27. package/frontend/src/pages/Capture.tsx +158 -0
  28. package/frontend/src/pages/Library.tsx +187 -0
  29. package/frontend/src/pages/System.tsx +378 -0
  30. package/frontend/src/routes.ts +85 -0
  31. package/frontend/src/store/appStore.ts +54 -0
  32. package/frontend/src/styles.css +107 -0
  33. package/kg_schema.py +1 -1
  34. package/knowledge_graph.py +4 -4
  35. package/lattice_brain/__init__.py +70 -0
  36. package/lattice_brain/_kg_common.py +1 -0
  37. package/lattice_brain/archive.py +133 -0
  38. package/lattice_brain/context.py +3 -0
  39. package/lattice_brain/conversations.py +3 -0
  40. package/lattice_brain/core.py +82 -0
  41. package/lattice_brain/discovery.py +1 -0
  42. package/lattice_brain/documents.py +1 -0
  43. package/lattice_brain/embeddings.py +82 -0
  44. package/lattice_brain/identity.py +13 -0
  45. package/lattice_brain/ingest.py +1 -0
  46. package/lattice_brain/memory.py +3 -0
  47. package/lattice_brain/network.py +1 -0
  48. package/lattice_brain/projection.py +1 -0
  49. package/lattice_brain/provenance.py +1 -0
  50. package/lattice_brain/retrieval.py +1 -0
  51. package/lattice_brain/schema.py +1 -0
  52. package/lattice_brain/storage/__init__.py +22 -0
  53. package/lattice_brain/storage/base.py +72 -0
  54. package/lattice_brain/storage/docker.py +105 -0
  55. package/lattice_brain/storage/factory.py +31 -0
  56. package/lattice_brain/storage/migration.py +190 -0
  57. package/lattice_brain/storage/postgres.py +123 -0
  58. package/lattice_brain/storage/sqlite.py +128 -0
  59. package/lattice_brain/store.py +3 -0
  60. package/lattice_brain/write_master.py +1 -0
  61. package/latticeai/__init__.py +1 -1
  62. package/latticeai/api/portability.py +69 -0
  63. package/latticeai/api/setup.py +5 -4
  64. package/latticeai/api/static_routes.py +4 -4
  65. package/latticeai/app_factory.py +17 -10
  66. package/latticeai/brain/__init__.py +6 -6
  67. package/latticeai/brain/_kg_common.py +1 -1
  68. package/latticeai/brain/network.py +1 -1
  69. package/latticeai/brain/retrieval.py +15 -0
  70. package/latticeai/brain/store.py +22 -6
  71. package/latticeai/core/config.py +8 -0
  72. package/latticeai/core/marketplace.py +1 -1
  73. package/latticeai/core/multi_agent.py +1 -1
  74. package/latticeai/core/workspace_os.py +1 -1
  75. package/latticeai/services/kg_portability.py +82 -1
  76. package/package.json +55 -15
  77. package/scripts/build_frontend_assets.mjs +38 -0
  78. package/scripts/bump_version.py +4 -1
  79. package/scripts/export_openapi.py +31 -0
  80. package/scripts/lint_frontend.mjs +91 -0
  81. package/scripts/migrate_brain_storage.py +53 -0
  82. package/scripts/run_python.mjs +47 -0
  83. package/scripts/wheel_smoke.py +3 -0
  84. package/src-tauri/Cargo.lock +4833 -0
  85. package/src-tauri/Cargo.toml +19 -0
  86. package/src-tauri/build.rs +3 -0
  87. package/src-tauri/capabilities/default.json +7 -0
  88. package/src-tauri/src/main.rs +78 -0
  89. package/src-tauri/tauri.conf.json +39 -0
  90. package/static/app/asset-manifest.json +32 -0
  91. package/static/app/assets/core-CwxXejkd.js +2 -0
  92. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  93. package/static/app/assets/index-CDjiH_se.css +2 -0
  94. package/static/app/assets/index-C_HAkbAg.js +333 -0
  95. package/static/app/assets/index-C_HAkbAg.js.map +1 -0
  96. package/static/app/index.html +25 -0
  97. package/static/manifest.json +2 -2
  98. package/static/sw.js +4 -4
  99. package/scripts/build_v3_assets.mjs +0 -170
  100. package/scripts/lint_v3.mjs +0 -120
  101. package/static/v3/asset-manifest.json +0 -63
  102. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  103. package/static/v3/css/lattice.base.css +0 -128
  104. package/static/v3/css/lattice.components.cde18231.css +0 -472
  105. package/static/v3/css/lattice.components.css +0 -472
  106. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  107. package/static/v3/css/lattice.shell.css +0 -452
  108. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  109. package/static/v3/css/lattice.tokens.css +0 -135
  110. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  111. package/static/v3/css/lattice.views.css +0 -360
  112. package/static/v3/index.html +0 -68
  113. package/static/v3/js/app.c5c80c46.js +0 -26
  114. package/static/v3/js/app.js +0 -26
  115. package/static/v3/js/core/api.ba0fbf14.js +0 -625
  116. package/static/v3/js/core/api.js +0 -625
  117. package/static/v3/js/core/components.f25b3b93.js +0 -230
  118. package/static/v3/js/core/components.js +0 -230
  119. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  120. package/static/v3/js/core/dom.js +0 -148
  121. package/static/v3/js/core/i18n.880e1fec.js +0 -575
  122. package/static/v3/js/core/i18n.js +0 -575
  123. package/static/v3/js/core/router.584570f2.js +0 -37
  124. package/static/v3/js/core/router.js +0 -37
  125. package/static/v3/js/core/routes.37522821.js +0 -101
  126. package/static/v3/js/core/routes.js +0 -101
  127. package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
  128. package/static/v3/js/core/shell.js +0 -420
  129. package/static/v3/js/core/store.7b2aa044.js +0 -123
  130. package/static/v3/js/core/store.js +0 -123
  131. package/static/v3/js/views/account.eff40715.js +0 -143
  132. package/static/v3/js/views/account.js +0 -143
  133. package/static/v3/js/views/activity.0d271ef9.js +0 -67
  134. package/static/v3/js/views/activity.js +0 -67
  135. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  136. package/static/v3/js/views/admin-audit.js +0 -185
  137. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  138. package/static/v3/js/views/admin-permissions.js +0 -177
  139. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  140. package/static/v3/js/views/admin-policies.js +0 -102
  141. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  142. package/static/v3/js/views/admin-private-vpc.js +0 -135
  143. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  144. package/static/v3/js/views/admin-security.js +0 -180
  145. package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
  146. package/static/v3/js/views/admin-users.js +0 -166
  147. package/static/v3/js/views/agents.17c5288d.js +0 -564
  148. package/static/v3/js/views/agents.js +0 -564
  149. package/static/v3/js/views/chat.e250e2cc.js +0 -624
  150. package/static/v3/js/views/chat.js +0 -624
  151. package/static/v3/js/views/files.adad14c1.js +0 -365
  152. package/static/v3/js/views/files.js +0 -365
  153. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  154. package/static/v3/js/views/graph-canvas.js +0 -509
  155. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  156. package/static/v3/js/views/home.js +0 -200
  157. package/static/v3/js/views/hooks.37895880.js +0 -220
  158. package/static/v3/js/views/hooks.js +0 -220
  159. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  160. package/static/v3/js/views/hybrid-search.js +0 -194
  161. package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
  162. package/static/v3/js/views/knowledge-graph.js +0 -529
  163. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  164. package/static/v3/js/views/marketplace.js +0 -141
  165. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  166. package/static/v3/js/views/mcp.js +0 -114
  167. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  168. package/static/v3/js/views/memory.js +0 -147
  169. package/static/v3/js/views/models.a1ffa147.js +0 -256
  170. package/static/v3/js/views/models.js +0 -256
  171. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  172. package/static/v3/js/views/my-computer.js +0 -463
  173. package/static/v3/js/views/network.52a4f181.js +0 -97
  174. package/static/v3/js/views/network.js +0 -97
  175. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  176. package/static/v3/js/views/pipeline.js +0 -157
  177. package/static/v3/js/views/planning.4876fd77.js +0 -174
  178. package/static/v3/js/views/planning.js +0 -174
  179. package/static/v3/js/views/runs.b63b2afa.js +0 -144
  180. package/static/v3/js/views/runs.js +0 -144
  181. package/static/v3/js/views/settings.b7140634.js +0 -317
  182. package/static/v3/js/views/settings.js +0 -317
  183. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  184. package/static/v3/js/views/skills.js +0 -109
  185. package/static/v3/js/views/snapshots.6f5db095.js +0 -135
  186. package/static/v3/js/views/snapshots.js +0 -135
  187. package/static/v3/js/views/tools.e4f11276.js +0 -108
  188. package/static/v3/js/views/tools.js +0 -108
  189. package/static/v3/js/views/workflows.7752225a.js +0 -213
  190. package/static/v3/js/views/workflows.js +0 -213
  191. package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
  192. package/static/v3/js/views/workspace-admin.js +0 -156
@@ -1,317 +0,0 @@
1
- /* ============================================================================
2
- * View: Settings — appearance, workspace, and integration readiness.
3
- * This view WIRES real store state (theme + mode persist immediately) and
4
- * probes the documented endpoints so the v3 shell visibly reports whether it
5
- * is talking to a live backend or an unavailable surface.
6
- * ========================================================================== */
7
-
8
- import { getI18nLanguage, languageOptions, t } from "../core/i18n.js";
9
-
10
- const MODE_DEFS = [
11
- { key: "basic", label: "Basic", desc: "Chat, search, and files — the essentials, nothing else." },
12
- { key: "advanced", label: "Advanced", desc: "Adds the pipeline, agents, and model runtime surfaces." },
13
- { key: "admin", label: "Admin", desc: "Reveals users, permissions, audit, security, and policies." },
14
- ];
15
-
16
- // Endpoints the views light up against once the backend exposes them.
17
- const PROBES = [
18
- { path: "/api/index/status", method: "GET", call: (api) => api.indexStatus() },
19
- { path: "/api/graph", method: "GET", call: (api) => api.graph() },
20
- { path: "/api/search/hybrid", method: "POST", call: (api) => api.hybridSearch("ping") },
21
- ];
22
-
23
- export async function render(ctx) {
24
- const { h, icon, api, store, c, navigate, toast } = ctx;
25
-
26
- const probesHost = h("div", c.loading({ lines: 3 }));
27
-
28
- const embedHost = h("div", c.loading({ lines: 2 }));
29
- const runtimeHost = h("div", c.loading({ lines: 3 }));
30
-
31
- const root = h("div.lt3-stack-6",
32
- c.viewHeader({
33
- eyebrow: "System",
34
- title: "Settings",
35
- sub: "Appearance, workspace, and integrations.",
36
- }),
37
-
38
- appearancePanel(ctx),
39
- workspacePanel(ctx),
40
-
41
- c.panel({
42
- eyebrow: "Runtime",
43
- title: "Local readiness",
44
- sub: "Backend, local-agent, and host signals used by Chat, Files, Search, and Models.",
45
- children: runtimeHost,
46
- }),
47
-
48
- c.panel({
49
- eyebrow: "Models",
50
- title: "Embeddings",
51
- sub: "The vector signal behind retrieval. Configure the provider with LATTICEAI_EMBEDDING_PROVIDER (hash · mlx · ollama · openai · custom).",
52
- children: embedHost,
53
- }),
54
-
55
- c.panel({
56
- eyebrow: "Status",
57
- title: "Integration readiness",
58
- sub: "Each view probes its endpoint and reports unavailable state until the backend answers.",
59
- children: h("div.lt3-stack-3",
60
- probesHost,
61
- h("p.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } },
62
- "Views automatically switch to live data once these endpoints respond; unreachable endpoints are labeled unavailable."),
63
- ),
64
- }),
65
-
66
- aboutPanel(ctx),
67
- );
68
-
69
- probeEndpoints(ctx, probesHost);
70
- renderEmbeddings(ctx, embedHost);
71
- renderRuntime(ctx, runtimeHost);
72
- return root;
73
- }
74
-
75
- async function renderRuntime(ctx, host) {
76
- const { h, icon, api, c } = ctx;
77
- const [health, sysinfo, models] = await Promise.all([
78
- api.raw("/health"),
79
- api.sysinfo(),
80
- api.models(),
81
- ]);
82
- const backendLive = !!(health && health.ok);
83
- const currentModel = models.data && models.data.current;
84
- host.replaceChildren(
85
- h("div.lt3-readiness",
86
- runtimeRow(ctx, "server", "Backend API", backendLive ? `Live${health.data?.version ? ` · v${health.data.version}` : ""}` : "Unavailable", backendLive ? "ready" : "pending"),
87
- runtimeRow(ctx, "folder-plus", "Desktop local agent", "Not available in this browser build; manual upload remains available", "idle"),
88
- runtimeRow(ctx, "cpu", "Model runtime", currentModel ? shortModel(currentModel) : "No model loaded", currentModel ? "ready" : "pending"),
89
- runtimeRow(ctx, "activity", "Host telemetry", sysinfo.source === "live" ? `CPU ${pct(sysinfo.data?.cpu_pct)} · RAM ${pct(sysinfo.data?.ram_pct)}` : "Unavailable", sysinfo.source === "live" ? "ready" : "idle"),
90
- ),
91
- h("div.lt3-code", { style: { "margin-top": "var(--lt3-space-4)" } },
92
- [
93
- "LATTICEAI_EMBEDDING_PROVIDER=hash | mlx | ollama | openai | custom",
94
- "Folder watching requires the desktop local agent.",
95
- "Cloud deployment is not reported as ready from this local-first shell.",
96
- ].join("\n")),
97
- );
98
- }
99
-
100
- function runtimeRow(ctx, ic, title, meta, state) {
101
- const { h, icon, c } = ctx;
102
- return h("div.lt3-readiness__row",
103
- h("div.lt3-readiness__icon", icon(ic)),
104
- h("div", h("div.lt3-readiness__title", title), h("div.lt3-readiness__meta", meta)),
105
- c.statePill(state),
106
- );
107
- }
108
-
109
- /* ── Embeddings (Settings → Models → Embeddings) ────────────────────────── */
110
- export function embeddingStatePill({ h, c }, st) {
111
- const state = String(st.state || st.grade || "fallback").toLowerCase();
112
- if (state === "production") return c.pill("Production", "ok");
113
- if (state === "unavailable") return c.pill("Unavailable", "err");
114
- return c.pill("Fallback", "warn");
115
- }
116
-
117
- async function renderEmbeddings(ctx, host) {
118
- const { h, c } = ctx;
119
- const res = await ctx.api.embeddingsStatus();
120
- const d = res.data || {};
121
- const lastIndexed = d.last_indexed_at ? new Date(d.last_indexed_at).toLocaleString() : "Never";
122
- host.replaceChildren(
123
- h("div.lt3-stack-4",
124
- h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "center", "flex-wrap": "wrap", gap: "var(--lt3-space-3)" } },
125
- h("div.lt3-row-2",
126
- h("span", { style: { color: "var(--lt3-pillar-vector, var(--accent))", display: "inline-flex" } }, ctx.icon("grid-dots")),
127
- h("b", { style: { "font-size": "var(--lt3-text-md)" } }, providerLabel(d.active_provider || d.provider)),
128
- ),
129
- h("div.lt3-row-2", embeddingStatePill(ctx, d), c.sourceBadge(res.source)),
130
- ),
131
- d.fell_back
132
- ? c.banner(`Requested “${d.requested_provider}” is unavailable (${(d.health && d.health.detail) || "no detail"}); using the local hash fallback. Retrieval still works, but vectors are non-semantic until the provider is reachable.`, "warn", "alert-triangle")
133
- : null,
134
- h("dl.lt3-keyval",
135
- h("dt", "Provider"), h("dd", providerLabel(d.active_provider || d.provider)),
136
- h("dt", "Model"), h("dd", h("span.lt3-mono", d.model || d.model_id || "—")),
137
- h("dt", "Dimensions"), h("dd", h("span.lt3-mono", String(d.dimensions || "—"))),
138
- h("dt", "Status"), h("dd", embeddingStatePill(ctx, d)),
139
- h("dt", "Last index"), h("dd", lastIndexed),
140
- ),
141
- ),
142
- );
143
- }
144
-
145
- function providerLabel(p) {
146
- return ({ hash: "Local hash (fallback)", mlx: "MLX (Apple Silicon)", ollama: "Ollama",
147
- openai: "OpenAI-compatible", custom: "Custom" }[String(p || "hash")]) || String(p || "—");
148
- }
149
-
150
- /* ── Appearance ─────────────────────────────────────────────────────────── */
151
- function appearancePanel({ h, icon, store, c }) {
152
- const themeKey = () => {
153
- const t = store.get().theme;
154
- return t === "light" || t === "dark" ? t : "";
155
- };
156
-
157
- const themeSlot = h("div");
158
- const buildTheme = () => c.segmented(
159
- [{ key: "light", label: "Light" }, { key: "dark", label: "Dark" }, { key: "", label: "System" }],
160
- themeKey(),
161
- (k) => { store.setTheme(k); themeSlot.replaceChildren(buildTheme()); },
162
- );
163
- themeSlot.append(buildTheme());
164
-
165
- const modeSeg = c.segmented(
166
- MODE_DEFS.map((m) => ({ key: m.key, label: m.label })),
167
- store.get().mode,
168
- (k) => { store.setMode(k); modeNote.replaceChildren(noteFor(k)); },
169
- );
170
- const noteFor = (k) => h("span", (MODE_DEFS.find((m) => m.key === k) || MODE_DEFS[0]).desc);
171
- const modeNote = h("p.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, noteFor(store.get().mode));
172
-
173
- return c.panel({
174
- eyebrow: "Appearance",
175
- title: "Look and density",
176
- sub: "Theme and surface mode persist on this machine and apply across every view.",
177
- children: h("div.lt3-stack-6",
178
- h("div.lt3-field",
179
- h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("palette"), "Theme"),
180
- themeSlot,
181
- h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "System follows your OS appearance preference."),
182
- ),
183
- h("div.lt3-field",
184
- h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("adjustments"), "Mode"),
185
- h("div", modeSeg),
186
- modeNote,
187
- ),
188
- ),
189
- });
190
- }
191
-
192
- /* ── Workspace ──────────────────────────────────────────────────────────── */
193
- function workspacePanel({ h, icon, store, c, toast, api }) {
194
- const ws = store.activeWorkspace();
195
-
196
- const orgInput = h("input.lt3-input", {
197
- type: "text", placeholder: "Organization name…", "aria-label": "New organization name",
198
- style: { "flex": "1 1 220px" },
199
- });
200
- const createBtn = h("button.lt3-btn.lt3-btn--primary", { type: "button" }, icon("plus"), "Create organization");
201
- const createOrg = async () => {
202
- const name = (orgInput.value || "").trim();
203
- if (!name) { toast("Enter an organization name first.", "info"); return; }
204
- createBtn.disabled = true;
205
- const res = await api.createOrg(name);
206
- createBtn.disabled = false;
207
- if (res && res.ok && res.data && !res.data.detail && !res.data.error) {
208
- toast(`Organization “${name}” created.`, "ok");
209
- orgInput.value = "";
210
- } else {
211
- const detail = (res && res.data && (res.data.detail || res.data.error)) || "the runtime is unavailable";
212
- toast(`Could not create organization — ${detail}.`, "warn");
213
- }
214
- };
215
- createBtn.addEventListener("click", createOrg);
216
-
217
- const langSelect = h("select.lt3-select", {
218
- "aria-label": t("settings.language"), value: getI18nLanguage(),
219
- on: { change: (e) => {
220
- store.setLang(e.target.value);
221
- toast(t("settings.languageSaved", { language: e.target.selectedOptions[0].text }), "ok");
222
- } },
223
- },
224
- languageOptions().map((lang) => h("option", { value: lang.key }, lang.label)),
225
- );
226
-
227
- return c.panel({
228
- eyebrow: "Workspace",
229
- title: "Active workspace",
230
- sub: "Where your indexed knowledge, agents, and policies live.",
231
- children: h("div.lt3-stack-6",
232
- h("dl.lt3-keyval",
233
- h("dt", "Name"), h("dd", ws.name),
234
- h("dt", "Type"), h("dd", h("span.lt3-row-2", icon(ws.type === "personal" ? "user" : "building"), titleCase(ws.type || "personal"))),
235
- h("dt", "Your role"), h("dd", c.pill(titleCase(ws.your_role || "owner"), "info")),
236
- ),
237
- h("hr.lt3-divider"),
238
- h("div.lt3-field",
239
- h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("building-community"), "Create organization"),
240
- h("div.lt3-cluster",
241
- orgInput,
242
- createBtn,
243
- ),
244
- h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "Creates a shared organization workspace on this server."),
245
- ),
246
- h("div.lt3-field",
247
- h("label.lt3-label", { for: "lt3-set-lang", style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("language"), t("settings.language")),
248
- h("div", { style: { "max-width": "260px" } }, langSelect),
249
- ),
250
- ),
251
- });
252
- }
253
-
254
- /* ── Integration readiness ──────────────────────────────────────────────── */
255
- async function probeEndpoints({ h, icon, api, c }, host) {
256
- const results = await Promise.all(PROBES.map((p) => p.call(api)));
257
- const rows = PROBES.map((p, i) => {
258
- const res = results[i] || {};
259
- return h("div.lt3-card.lt3-card--flat",
260
- h("div.lt3-row", { style: { "justify-content": "space-between", "gap": "var(--lt3-space-3)", "flex-wrap": "wrap" } },
261
- h("div.lt3-row-2",
262
- h("span.lt3-pill", { style: { "font-weight": "var(--lt3-weight-medium)" } }, p.method),
263
- h("code.lt3-mono", p.path),
264
- ),
265
- c.sourceBadge(res.source === "live" ? "live" : "unavailable"),
266
- ),
267
- );
268
- });
269
- host.replaceChildren(h("div.lt3-stack-2", rows));
270
- }
271
-
272
- /* ── About ──────────────────────────────────────────────────────────────── */
273
- /* Version is read live from /health (which derives it from the backend's single
274
- * source of truth, WORKSPACE_OS_VERSION) — never hard-coded in the frontend.
275
- * If the backend is unreachable we say "unavailable" rather than inventing a
276
- * number. */
277
- function aboutPanel({ h, icon, c, api }) {
278
- const versionSlot = h("dd", h("span.lt3-mono.lt3-faint", "checking…"));
279
- (async () => {
280
- const res = await api.raw("/health");
281
- const v = res && res.ok && res.data && res.data.version;
282
- versionSlot.replaceChildren(
283
- v
284
- ? h("span.lt3-mono", `v${String(v).replace(/^v/i, "")}`)
285
- : h("span.lt3-mono.lt3-faint", "unavailable"),
286
- );
287
- })();
288
- return c.panel({
289
- eyebrow: "About",
290
- title: "Lattice AI",
291
- sub: "Local-first AI workspace.",
292
- children: h("div.lt3-stack-4",
293
- h("dl.lt3-keyval",
294
- h("dt", "Application"), h("dd", "Lattice AI"),
295
- h("dt", "Version"), versionSlot,
296
- h("dt", "Edition"), h("dd", "Local-first AI workspace"),
297
- ),
298
- ),
299
- });
300
- }
301
-
302
- /* ── helpers ────────────────────────────────────────────────────────────── */
303
- function titleCase(s) {
304
- s = String(s || "");
305
- return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
306
- }
307
-
308
- function pct(value) {
309
- const n = Number(value);
310
- return Number.isFinite(n) ? `${Math.round(n)}%` : "—";
311
- }
312
-
313
- function shortModel(id) {
314
- const s = String(id || "");
315
- const tail = s.includes("/") ? s.split("/").pop() : s;
316
- return tail.length > 30 ? tail.slice(0, 29) + "…" : tail;
317
- }
@@ -1,109 +0,0 @@
1
- /* ============================================================================
2
- * View: Skills — the skills registry (install / enable / disable / remove).
3
- * Reads the live workspace skill registry (/workspace/skills) and toggles real
4
- * state. The discovery catalog lives in Marketplace; this is management.
5
- * ========================================================================== */
6
-
7
- export async function render(ctx) {
8
- const { h, c } = ctx;
9
- const src = h("span", c.sourceBadge("pending"));
10
- const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
11
- const listHost = h("div", c.loading({ lines: 4, block: true }));
12
-
13
- const root = h("div.lt3-stack-6",
14
- c.viewHeader({
15
- eyebrow: "Platform",
16
- title: "Skills",
17
- sub: "Install, enable, disable, and remove skills. Installed skills are shared machine-global capabilities the agent can use.",
18
- actions: [src],
19
- }),
20
- statHost,
21
- h("section", c.sectionHead("Installed & available skills"), listHost),
22
- );
23
-
24
- load();
25
- return root;
26
-
27
- async function load() {
28
- const res = await ctx.api.skills();
29
- src.replaceChildren(c.sourceBadge(res.source));
30
- const skills = normalize(res.data);
31
- if (!skills.length) {
32
- statHost.replaceChildren(c.stat({ label: "Skills", value: "—", icon: "puzzle" }));
33
- listHost.replaceChildren(c.emptyState({ icon: "puzzle-off", title: "Skills registry unavailable", body: res.source === "live" ? "No skills are registered yet — install one from the Marketplace." : "Start the backend to read the skills registry." }));
34
- return;
35
- }
36
- const enabled = skills.filter((s) => s.enabled).length;
37
- const installed = skills.filter((s) => s.installed).length;
38
- statHost.replaceChildren(
39
- c.stat({ label: "Skills", value: c.fmtNum(skills.length), icon: "puzzle" }),
40
- c.stat({ label: "Enabled", value: c.fmtNum(enabled), icon: "circle-check" }),
41
- c.stat({ label: "Installed", value: c.fmtNum(installed), icon: "download" }),
42
- );
43
- listHost.replaceChildren(h("div.lt3-grid-auto", skills.map((s) => skillCard(ctx, s))));
44
- }
45
-
46
- function skillCard(ctx2, s) {
47
- return c.card(h("div.lt3-stack-3",
48
- h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start" } },
49
- h("div", h("b", s.name), h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, [s.source, s.version ? `v${s.version}` : null, s.category].filter(Boolean).join(" · "))),
50
- c.statePill(s.enabled ? "ready" : s.installed ? "idle" : "available"),
51
- ),
52
- s.description ? h("p.lt3-muted", { style: { margin: 0, "font-size": "var(--lt3-text-sm)" } }, s.description) : null,
53
- h("div.lt3-row-2",
54
- s.installed
55
- ? h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => toggle(ctx2, s) } }, c.icon(s.enabled ? "player-pause" : "player-play"), s.enabled ? "Disable" : "Enable")
56
- : h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => install(ctx2, s) } }, c.icon("download"), "Install"),
57
- s.installed ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => uninstall(ctx2, s) } }, c.icon("trash"), "Remove") : null,
58
- ),
59
- ), { interactive: false });
60
- }
61
-
62
- async function toggle(ctx2, s) {
63
- const res = s.enabled ? await ctx2.api.skillDisable(s.name) : await ctx2.api.skillEnable(s.name);
64
- ctx2.toast(res && res.ok ? `${s.enabled ? "Disabled" : "Enabled"} ${s.name}` : "Action unavailable", res && res.ok ? "ok" : "err");
65
- load();
66
- }
67
- async function install(ctx2, s) {
68
- const res = await ctx2.api.skillInstall(s.name, s.plugin);
69
- ctx2.toast(res && res.ok ? `Installed ${s.name}` : (res && res.status === 403 ? "Admin required" : "Install unavailable"), res && res.ok ? "ok" : "err");
70
- load();
71
- }
72
- async function uninstall(ctx2, s) {
73
- const res = await ctx2.api.skillUninstall(s.name);
74
- ctx2.toast(res && res.ok ? `Removed ${s.name}` : (res && res.status === 403 ? "Admin required" : "Remove unavailable"), res && res.ok ? "ok" : "err");
75
- load();
76
- }
77
- }
78
-
79
- function normalize(data) {
80
- if (!data) return [];
81
- const raw = [];
82
- if (Array.isArray(data.skills)) raw.push(...data.skills);
83
- if (Array.isArray(data.installed)) raw.push(...data.installed);
84
- if (Array.isArray(data.available)) raw.push(...data.available);
85
- if (Array.isArray(data.registry)) raw.push(...data.registry);
86
- else if (data.registry && typeof data.registry === "object") {
87
- raw.push(...Object.entries(data.registry).map(([name, value]) => ({ name, ...(value || {}) })));
88
- }
89
- if (Array.isArray(data)) raw.push(...data);
90
-
91
- const byName = new Map();
92
- for (const item of raw) {
93
- const name = item && (item.name || item.skill || item.id);
94
- if (!name) continue;
95
- const prior = byName.get(name) || {};
96
- byName.set(name, { ...prior, ...item, name });
97
- }
98
-
99
- return [...byName.values()].map((s) => ({
100
- name: s.name || s.skill || s.id || "skill",
101
- description: s.description || "",
102
- version: s.version || "",
103
- source: s.source || "",
104
- category: s.category || "",
105
- plugin: s.plugin || "",
106
- enabled: s.enabled != null ? !!s.enabled : !!s.installed,
107
- installed: s.installed != null ? !!s.installed : true,
108
- }));
109
- }
@@ -1,109 +0,0 @@
1
- /* ============================================================================
2
- * View: Skills — the skills registry (install / enable / disable / remove).
3
- * Reads the live workspace skill registry (/workspace/skills) and toggles real
4
- * state. The discovery catalog lives in Marketplace; this is management.
5
- * ========================================================================== */
6
-
7
- export async function render(ctx) {
8
- const { h, c } = ctx;
9
- const src = h("span", c.sourceBadge("pending"));
10
- const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
11
- const listHost = h("div", c.loading({ lines: 4, block: true }));
12
-
13
- const root = h("div.lt3-stack-6",
14
- c.viewHeader({
15
- eyebrow: "Platform",
16
- title: "Skills",
17
- sub: "Install, enable, disable, and remove skills. Installed skills are shared machine-global capabilities the agent can use.",
18
- actions: [src],
19
- }),
20
- statHost,
21
- h("section", c.sectionHead("Installed & available skills"), listHost),
22
- );
23
-
24
- load();
25
- return root;
26
-
27
- async function load() {
28
- const res = await ctx.api.skills();
29
- src.replaceChildren(c.sourceBadge(res.source));
30
- const skills = normalize(res.data);
31
- if (!skills.length) {
32
- statHost.replaceChildren(c.stat({ label: "Skills", value: "—", icon: "puzzle" }));
33
- listHost.replaceChildren(c.emptyState({ icon: "puzzle-off", title: "Skills registry unavailable", body: res.source === "live" ? "No skills are registered yet — install one from the Marketplace." : "Start the backend to read the skills registry." }));
34
- return;
35
- }
36
- const enabled = skills.filter((s) => s.enabled).length;
37
- const installed = skills.filter((s) => s.installed).length;
38
- statHost.replaceChildren(
39
- c.stat({ label: "Skills", value: c.fmtNum(skills.length), icon: "puzzle" }),
40
- c.stat({ label: "Enabled", value: c.fmtNum(enabled), icon: "circle-check" }),
41
- c.stat({ label: "Installed", value: c.fmtNum(installed), icon: "download" }),
42
- );
43
- listHost.replaceChildren(h("div.lt3-grid-auto", skills.map((s) => skillCard(ctx, s))));
44
- }
45
-
46
- function skillCard(ctx2, s) {
47
- return c.card(h("div.lt3-stack-3",
48
- h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "flex-start" } },
49
- h("div", h("b", s.name), h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, [s.source, s.version ? `v${s.version}` : null, s.category].filter(Boolean).join(" · "))),
50
- c.statePill(s.enabled ? "ready" : s.installed ? "idle" : "available"),
51
- ),
52
- s.description ? h("p.lt3-muted", { style: { margin: 0, "font-size": "var(--lt3-text-sm)" } }, s.description) : null,
53
- h("div.lt3-row-2",
54
- s.installed
55
- ? h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => toggle(ctx2, s) } }, c.icon(s.enabled ? "player-pause" : "player-play"), s.enabled ? "Disable" : "Enable")
56
- : h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => install(ctx2, s) } }, c.icon("download"), "Install"),
57
- s.installed ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => uninstall(ctx2, s) } }, c.icon("trash"), "Remove") : null,
58
- ),
59
- ), { interactive: false });
60
- }
61
-
62
- async function toggle(ctx2, s) {
63
- const res = s.enabled ? await ctx2.api.skillDisable(s.name) : await ctx2.api.skillEnable(s.name);
64
- ctx2.toast(res && res.ok ? `${s.enabled ? "Disabled" : "Enabled"} ${s.name}` : "Action unavailable", res && res.ok ? "ok" : "err");
65
- load();
66
- }
67
- async function install(ctx2, s) {
68
- const res = await ctx2.api.skillInstall(s.name, s.plugin);
69
- ctx2.toast(res && res.ok ? `Installed ${s.name}` : (res && res.status === 403 ? "Admin required" : "Install unavailable"), res && res.ok ? "ok" : "err");
70
- load();
71
- }
72
- async function uninstall(ctx2, s) {
73
- const res = await ctx2.api.skillUninstall(s.name);
74
- ctx2.toast(res && res.ok ? `Removed ${s.name}` : (res && res.status === 403 ? "Admin required" : "Remove unavailable"), res && res.ok ? "ok" : "err");
75
- load();
76
- }
77
- }
78
-
79
- function normalize(data) {
80
- if (!data) return [];
81
- const raw = [];
82
- if (Array.isArray(data.skills)) raw.push(...data.skills);
83
- if (Array.isArray(data.installed)) raw.push(...data.installed);
84
- if (Array.isArray(data.available)) raw.push(...data.available);
85
- if (Array.isArray(data.registry)) raw.push(...data.registry);
86
- else if (data.registry && typeof data.registry === "object") {
87
- raw.push(...Object.entries(data.registry).map(([name, value]) => ({ name, ...(value || {}) })));
88
- }
89
- if (Array.isArray(data)) raw.push(...data);
90
-
91
- const byName = new Map();
92
- for (const item of raw) {
93
- const name = item && (item.name || item.skill || item.id);
94
- if (!name) continue;
95
- const prior = byName.get(name) || {};
96
- byName.set(name, { ...prior, ...item, name });
97
- }
98
-
99
- return [...byName.values()].map((s) => ({
100
- name: s.name || s.skill || s.id || "skill",
101
- description: s.description || "",
102
- version: s.version || "",
103
- source: s.source || "",
104
- category: s.category || "",
105
- plugin: s.plugin || "",
106
- enabled: s.enabled != null ? !!s.enabled : !!s.installed,
107
- installed: s.installed != null ? !!s.installed : true,
108
- }));
109
- }
@@ -1,135 +0,0 @@
1
- import { t } from "../core/i18n.880e1fec.js";
2
-
3
- export async function render(ctx) {
4
- const { h, icon, api, c, toast } = ctx;
5
- const host = h("div.lt3-stack-6", c.loading({ lines: 5, block: true }));
6
-
7
- async function load() {
8
- const [snaps, timeline] = await Promise.all([api.snapshots(), api.timeMachine(80)]);
9
- const rows = normalize(snaps.data);
10
- host.replaceChildren(
11
- c.viewHeader({
12
- eyebrow: t("snapshots.eyebrow"),
13
- title: t("snapshots.title"),
14
- sub: t("snapshots.sub"),
15
- actions: [c.sourceBadge(snaps.source)],
16
- }),
17
- createPanel(),
18
- comparePanel(rows),
19
- snapshotTable(rows),
20
- timelinePanel(timeline),
21
- );
22
- }
23
-
24
- function createPanel() {
25
- const name = h("input.lt3-input", { type: "text", placeholder: t("snapshots.name"), "aria-label": t("snapshots.name") });
26
- return c.panel({
27
- title: t("snapshots.create"),
28
- children: h("div.lt3-row-2",
29
- name,
30
- h("button.lt3-btn.lt3-btn--primary", { on: { click: async () => {
31
- const res = await api.createSnapshot(name.value.trim() || t("snapshots.title"));
32
- toast(resultText(res, t("snapshots.created")), res.ok ? "ok" : "err");
33
- if (res.ok) { name.value = ""; load(); }
34
- } } }, icon("camera"), t("snapshots.create")),
35
- ),
36
- });
37
- }
38
-
39
- function comparePanel(rows) {
40
- const before = select(rows);
41
- const after = select(rows);
42
- const result = h("div");
43
- return c.panel({
44
- title: t("snapshots.compare"),
45
- children: h("div.lt3-stack-4",
46
- h("div.lt3-row-2", before, after, h("button.lt3-btn.lt3-btn--primary", { on: { click: async () => {
47
- if (!before.value || !after.value) return;
48
- result.replaceChildren(c.loading({ lines: 2 }));
49
- const res = await api.compareSnapshots(before.value, after.value);
50
- const d = res.data || {};
51
- result.replaceChildren(res.ok
52
- ? h("dl.lt3-keyval",
53
- h("dt", "nodes_added"), h("dd", String(d.summary?.nodes_added ?? 0)),
54
- h("dt", "nodes_removed"), h("dd", String(d.summary?.nodes_removed ?? 0)),
55
- h("dt", "edges_added"), h("dd", String(d.summary?.edges_added ?? 0)),
56
- h("dt", "edges_removed"), h("dd", String(d.summary?.edges_removed ?? 0)),
57
- )
58
- : c.banner(resultText(res, t("common.unavailable")), "err"));
59
- } } }, icon("git-compare"), t("snapshots.compare"))),
60
- result,
61
- ),
62
- });
63
- }
64
-
65
- function snapshotTable(rows) {
66
- if (!rows.length) return c.emptyState({ icon: "history", title: t("snapshots.title"), body: t("common.none") });
67
- return c.panel({
68
- title: t("snapshots.title"),
69
- children: c.table([
70
- { key: "name", label: t("common.name"), render: (r) => h("div", h("b", r.name || r.id), h("div.lt3-faint", { style: { "font-family": "var(--lt3-font-mono)", "font-size": "var(--lt3-text-2xs)" } }, r.id)) },
71
- { key: "created", label: t("common.created"), width: "1%", render: (r) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(r.created_at)) },
72
- { key: "nodes", label: "nodes", width: "1%", render: (r) => String(r.node_count ?? 0) },
73
- { key: "edges", label: "edges", width: "1%", render: (r) => String(r.edge_count ?? 0) },
74
- { key: "actions", label: "", width: "1%", render: (r) => h("div.lt3-row-2",
75
- h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => exportSnapshot(r.id) } }, icon("download"), t("snapshots.export")),
76
- h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => restoreSnapshot(r.id) } }, icon("restore"), t("snapshots.restore")),
77
- ) },
78
- ], rows),
79
- });
80
- }
81
-
82
- function timelinePanel(res) {
83
- const events = Array.isArray(res.data?.events) ? res.data.events : [];
84
- return c.panel({
85
- title: t("snapshots.timeline"),
86
- actions: [c.sourceBadge(res.source)],
87
- children: events.length ? c.table([
88
- { key: "event", label: t("common.status"), render: (e) => h("span", e.event_type || e.area || "event") },
89
- { key: "area", label: t("common.type"), width: "1%", render: (e) => c.pill(e.area || "workspace") },
90
- { key: "when", label: t("common.created"), width: "1%", render: (e) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(e.timestamp)) },
91
- ], events.slice(0, 40)) : c.emptyState({ icon: "history-off", title: t("snapshots.timeline"), body: t("common.none") }),
92
- });
93
- }
94
-
95
- async function exportSnapshot(id) {
96
- const res = await api.snapshotExport(id);
97
- toast(resultText(res, res.data?.export_path || t("snapshots.export")), res.ok ? "ok" : "err");
98
- }
99
- async function restoreSnapshot(id) {
100
- const res = await api.snapshotRestore(id);
101
- toast(resultText(res, t("snapshots.restored")), res.ok ? "ok" : "err");
102
- if (res.ok) load();
103
- }
104
-
105
- await load();
106
- return host;
107
- }
108
-
109
- function select(rows) {
110
- const sel = document.createElement("select");
111
- sel.className = "lt3-select";
112
- sel.setAttribute("aria-label", t("snapshots.title"));
113
- for (const row of rows) {
114
- const opt = document.createElement("option");
115
- opt.value = row.id;
116
- opt.textContent = row.name || row.id;
117
- sel.append(opt);
118
- }
119
- return sel;
120
- }
121
-
122
- function normalize(data) {
123
- return Array.isArray(data?.snapshots) ? data.snapshots : [];
124
- }
125
-
126
- function fmt(ts) {
127
- if (!ts) return "—";
128
- try { return new Date(ts).toLocaleString(); } catch { return String(ts); }
129
- }
130
-
131
- function resultText(res, okText) {
132
- if (res && res.ok) return okText;
133
- const data = (res && res.data) || {};
134
- return String(data.detail || data.error || res?.error || t("common.unavailable"));
135
- }