ltcai 4.0.1 → 4.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.
Files changed (150) hide show
  1. package/README.md +28 -23
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +42 -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_DIGITAL_BRAIN_RECOVERY.md +26 -19
  8. package/frontend/index.html +24 -0
  9. package/frontend/openapi.json +14190 -0
  10. package/frontend/src/App.tsx +184 -0
  11. package/frontend/src/api/client.ts +317 -0
  12. package/frontend/src/api/openapi.ts +16637 -0
  13. package/frontend/src/components/primitives.tsx +204 -0
  14. package/frontend/src/components/ui/badge.tsx +27 -0
  15. package/frontend/src/components/ui/button.tsx +37 -0
  16. package/frontend/src/components/ui/card.tsx +22 -0
  17. package/frontend/src/components/ui/input.tsx +16 -0
  18. package/frontend/src/components/ui/textarea.tsx +16 -0
  19. package/frontend/src/lib/utils.ts +33 -0
  20. package/frontend/src/main.tsx +23 -0
  21. package/frontend/src/pages/Act.tsx +245 -0
  22. package/frontend/src/pages/Ask.tsx +200 -0
  23. package/frontend/src/pages/Brain.tsx +267 -0
  24. package/frontend/src/pages/Capture.tsx +158 -0
  25. package/frontend/src/pages/Library.tsx +187 -0
  26. package/frontend/src/pages/System.tsx +344 -0
  27. package/frontend/src/routes.ts +85 -0
  28. package/frontend/src/store/appStore.ts +54 -0
  29. package/frontend/src/styles.css +107 -0
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/api/setup.py +5 -4
  32. package/latticeai/api/static_routes.py +4 -4
  33. package/latticeai/core/marketplace.py +1 -1
  34. package/latticeai/core/multi_agent.py +1 -1
  35. package/latticeai/core/workspace_os.py +1 -1
  36. package/package.json +54 -15
  37. package/scripts/build_frontend_assets.mjs +38 -0
  38. package/scripts/bump_version.py +1 -1
  39. package/scripts/export_openapi.py +31 -0
  40. package/scripts/lint_frontend.mjs +86 -0
  41. package/scripts/run_python.mjs +47 -0
  42. package/src-tauri/Cargo.lock +4833 -0
  43. package/src-tauri/Cargo.toml +19 -0
  44. package/src-tauri/build.rs +3 -0
  45. package/src-tauri/capabilities/default.json +7 -0
  46. package/src-tauri/src/main.rs +78 -0
  47. package/src-tauri/tauri.conf.json +36 -0
  48. package/static/app/asset-manifest.json +32 -0
  49. package/static/app/assets/core-CwxXejkd.js +2 -0
  50. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  51. package/static/app/assets/index-CJRAzNnf.js +333 -0
  52. package/static/app/assets/index-CJRAzNnf.js.map +1 -0
  53. package/static/app/assets/index-CSwBBgf4.css +2 -0
  54. package/static/app/index.html +25 -0
  55. package/static/manifest.json +2 -2
  56. package/static/sw.js +4 -4
  57. package/scripts/build_v3_assets.mjs +0 -170
  58. package/scripts/lint_v3.mjs +0 -120
  59. package/static/v3/asset-manifest.json +0 -63
  60. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  61. package/static/v3/css/lattice.base.css +0 -128
  62. package/static/v3/css/lattice.components.cde18231.css +0 -472
  63. package/static/v3/css/lattice.components.css +0 -472
  64. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  65. package/static/v3/css/lattice.shell.css +0 -452
  66. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  67. package/static/v3/css/lattice.tokens.css +0 -135
  68. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  69. package/static/v3/css/lattice.views.css +0 -360
  70. package/static/v3/index.html +0 -68
  71. package/static/v3/js/app.c5c80c46.js +0 -26
  72. package/static/v3/js/app.js +0 -26
  73. package/static/v3/js/core/api.ba0fbf14.js +0 -625
  74. package/static/v3/js/core/api.js +0 -625
  75. package/static/v3/js/core/components.f25b3b93.js +0 -230
  76. package/static/v3/js/core/components.js +0 -230
  77. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  78. package/static/v3/js/core/dom.js +0 -148
  79. package/static/v3/js/core/i18n.880e1fec.js +0 -575
  80. package/static/v3/js/core/i18n.js +0 -575
  81. package/static/v3/js/core/router.584570f2.js +0 -37
  82. package/static/v3/js/core/router.js +0 -37
  83. package/static/v3/js/core/routes.37522821.js +0 -101
  84. package/static/v3/js/core/routes.js +0 -101
  85. package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
  86. package/static/v3/js/core/shell.js +0 -420
  87. package/static/v3/js/core/store.7b2aa044.js +0 -123
  88. package/static/v3/js/core/store.js +0 -123
  89. package/static/v3/js/views/account.eff40715.js +0 -143
  90. package/static/v3/js/views/account.js +0 -143
  91. package/static/v3/js/views/activity.0d271ef9.js +0 -67
  92. package/static/v3/js/views/activity.js +0 -67
  93. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  94. package/static/v3/js/views/admin-audit.js +0 -185
  95. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  96. package/static/v3/js/views/admin-permissions.js +0 -177
  97. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  98. package/static/v3/js/views/admin-policies.js +0 -102
  99. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  100. package/static/v3/js/views/admin-private-vpc.js +0 -135
  101. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  102. package/static/v3/js/views/admin-security.js +0 -180
  103. package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
  104. package/static/v3/js/views/admin-users.js +0 -166
  105. package/static/v3/js/views/agents.17c5288d.js +0 -564
  106. package/static/v3/js/views/agents.js +0 -564
  107. package/static/v3/js/views/chat.e250e2cc.js +0 -624
  108. package/static/v3/js/views/chat.js +0 -624
  109. package/static/v3/js/views/files.adad14c1.js +0 -365
  110. package/static/v3/js/views/files.js +0 -365
  111. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  112. package/static/v3/js/views/graph-canvas.js +0 -509
  113. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  114. package/static/v3/js/views/home.js +0 -200
  115. package/static/v3/js/views/hooks.37895880.js +0 -220
  116. package/static/v3/js/views/hooks.js +0 -220
  117. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  118. package/static/v3/js/views/hybrid-search.js +0 -194
  119. package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
  120. package/static/v3/js/views/knowledge-graph.js +0 -529
  121. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  122. package/static/v3/js/views/marketplace.js +0 -141
  123. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  124. package/static/v3/js/views/mcp.js +0 -114
  125. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  126. package/static/v3/js/views/memory.js +0 -147
  127. package/static/v3/js/views/models.a1ffa147.js +0 -256
  128. package/static/v3/js/views/models.js +0 -256
  129. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  130. package/static/v3/js/views/my-computer.js +0 -463
  131. package/static/v3/js/views/network.52a4f181.js +0 -97
  132. package/static/v3/js/views/network.js +0 -97
  133. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  134. package/static/v3/js/views/pipeline.js +0 -157
  135. package/static/v3/js/views/planning.4876fd77.js +0 -174
  136. package/static/v3/js/views/planning.js +0 -174
  137. package/static/v3/js/views/runs.b63b2afa.js +0 -144
  138. package/static/v3/js/views/runs.js +0 -144
  139. package/static/v3/js/views/settings.b7140634.js +0 -317
  140. package/static/v3/js/views/settings.js +0 -317
  141. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  142. package/static/v3/js/views/skills.js +0 -109
  143. package/static/v3/js/views/snapshots.6f5db095.js +0 -135
  144. package/static/v3/js/views/snapshots.js +0 -135
  145. package/static/v3/js/views/tools.e4f11276.js +0 -108
  146. package/static/v3/js/views/tools.js +0 -108
  147. package/static/v3/js/views/workflows.7752225a.js +0 -213
  148. package/static/v3/js/views/workflows.js +0 -213
  149. package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
  150. package/static/v3/js/views/workspace-admin.js +0 -156
@@ -1,144 +0,0 @@
1
- import { t } from "../core/i18n.880e1fec.js";
2
-
3
- const ACTIVE = new Set(["queued", "running", "in_progress", "active", "cancelling"]);
4
-
5
- export async function render(ctx) {
6
- const { h, icon, api, c, toast } = ctx;
7
- const agentHost = h("div", c.loading({ lines: 4 }));
8
- const workflowHost = h("div", c.loading({ lines: 4 }));
9
- const approvalHost = h("div", c.loading({ lines: 4 }));
10
- const progressHost = h("div", c.loading({ lines: 3 }));
11
-
12
- const root = h("div.lt3-stack-6",
13
- c.viewHeader({ eyebrow: t("runs.eyebrow"), title: t("runs.title"), sub: t("runs.sub") }),
14
- h("div.lt3-statrow", progressHost),
15
- c.panel({ title: t("runs.approvals"), children: approvalHost }),
16
- c.panel({ title: t("runs.agentRuns"), children: agentHost }),
17
- c.panel({ title: t("runs.workflowRuns"), children: workflowHost }),
18
- );
19
-
20
- await load();
21
- const poll = setInterval(load, 5000);
22
- root.addEventListener("DOMNodeRemovedFromDocument", () => clearInterval(poll), { once: true });
23
- return root;
24
-
25
- async function load() {
26
- const [agent, workflow, pending] = await Promise.all([
27
- api.agentRuntime(),
28
- api.workflowRuns(),
29
- api.permissionsPending(),
30
- ]);
31
- const agentRuns = Array.isArray(agent.data?.runs) ? agent.data.runs : [];
32
- const workflowRuns = Array.isArray(workflow.data?.runs) ? workflow.data.runs : [];
33
- renderProgress(agentRuns, workflowRuns, pending.data || {});
34
- agentHost.replaceChildren(runTable(ctx, agentRuns, "agent", agent.source));
35
- workflowHost.replaceChildren(runTable(ctx, workflowRuns, "workflow", workflow.source));
36
- approvalHost.replaceChildren(approvalList(ctx, workflowRuns, pending.data || {}, workflow.source || pending.source));
37
- }
38
-
39
- function renderProgress(agentRuns, workflowRuns, pending) {
40
- const all = [...agentRuns, ...workflowRuns];
41
- const active = all.filter((r) => ACTIVE.has(String(r.status || "").toLowerCase())).length;
42
- const paused = workflowRuns.filter((r) => String(r.status || "").toLowerCase() === "awaiting_approval").length;
43
- const approvals = Object.keys(pending.pending || {}).length + paused;
44
- progressHost.replaceChildren(
45
- c.stat({ label: t("runs.progress"), value: String(active), icon: "progress" }),
46
- c.stat({ label: t("runs.approvals"), value: String(approvals), icon: "circle-check" }),
47
- c.stat({ label: t("runs.agentRuns"), value: String(agentRuns.length), icon: "robot" }),
48
- c.stat({ label: t("runs.workflowRuns"), value: String(workflowRuns.length), icon: "sitemap" }),
49
- );
50
- }
51
-
52
- function runTable(ctx2, rows, kind, source) {
53
- const { h, c } = ctx2;
54
- return h("div.lt3-stack-3",
55
- h("div.lt3-row-2", c.sourceBadge(source)),
56
- rows.length ? c.table([
57
- { key: "status", label: t("common.status"), width: "1%", render: (r) => c.statePill(mapStatus(r.status)) },
58
- { key: "mode", label: t("runs.mode"), width: "1%", render: (r) => c.pill(r.mode || r.execution_mode || "live") },
59
- { key: "name", label: t("common.name"), render: (r) => h("div", h("b", r.name || r.workflow_name || r.agent_id || r.workflow_id || r.id), h("div.lt3-faint", { style: { "font-family": "var(--lt3-font-mono)", "font-size": "var(--lt3-text-2xs)" } }, r.id || r.run_id || "")) },
60
- { key: "when", label: t("common.updated"), width: "1%", render: (r) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(r.updated_at || r.created_at || r.completed_at)) },
61
- { key: "timeline", label: t("runs.progress"), render: (r) => miniTimeline(ctx2, r.timeline || []) },
62
- { key: "act", label: "", width: "1%", render: (r) => ACTIVE.has(String(r.status || "").toLowerCase())
63
- ? h("button.lt3-btn.lt3-btn--danger.lt3-btn--sm", { on: { click: () => cancelRun(kind, r.id || r.run_id) } }, c.icon("player-stop"), t("common.stop"))
64
- : null },
65
- ], rows.slice(0, 40)) : c.emptyState({ icon: "history-off", title: kind === "agent" ? t("runs.agentRuns") : t("runs.workflowRuns"), body: t("common.none") }),
66
- );
67
- }
68
-
69
- function approvalList(ctx2, workflowRuns, pending, source) {
70
- const workflowApprovals = workflowRuns.filter((r) => String(r.status || "").toLowerCase() === "awaiting_approval");
71
- const permissionRows = Object.entries(pending.pending || {}).map(([token, rec]) => ({ token, ...rec }));
72
- const nodes = [];
73
- nodes.push(h("div.lt3-row-2", c.sourceBadge(source)));
74
- if (workflowApprovals.length) {
75
- nodes.push(...workflowApprovals.map((run) => c.card(h("div.lt3-stack-3",
76
- h("div.lt3-row", { style: { "justify-content": "space-between" } },
77
- h("div", h("b", run.name || run.workflow_name || run.workflow_id), h("div.lt3-faint", t("runs.approvalPaused")), run.pause?.node ? h("div.lt3-faint", run.pause.node) : null),
78
- c.statePill("pending"),
79
- ),
80
- miniTimeline(ctx2, run.timeline || []),
81
- h("div.lt3-row-2",
82
- h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => decideWorkflow(run.id || run.run_id, true) } }, icon("circle-check"), t("common.approve")),
83
- h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => decideWorkflow(run.id || run.run_id, false) } }, icon("circle-x"), t("common.deny")),
84
- ),
85
- ), { flat: true })));
86
- }
87
- if (permissionRows.length) {
88
- nodes.push(...permissionRows.map((rec) => c.card(h("div.lt3-stack-3",
89
- h("div", h("b", rec.action_label || rec.action || "permission"), h("div.lt3-faint", rec.path || rec.token), h("div.lt3-faint", { style: { "font-family": "var(--lt3-font-mono)", "font-size": "var(--lt3-text-2xs)" } }, rec.token)),
90
- h("div.lt3-row-2",
91
- h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => decidePermission(rec.token, true) } }, icon("circle-check"), t("common.approve")),
92
- h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => decidePermission(rec.token, false) } }, icon("circle-x"), t("common.deny")),
93
- ),
94
- ), { flat: true })));
95
- }
96
- if (nodes.length === 1) nodes.push(c.emptyState({ icon: "circle-check", title: t("runs.approvals"), body: t("common.none") }));
97
- return h("div.lt3-stack-3", nodes);
98
- }
99
-
100
- function miniTimeline(ctx2, timeline) {
101
- const { h, c } = ctx2;
102
- if (!timeline.length) return h("span.lt3-faint", t("common.none"));
103
- return h("div.lt3-stack-2", timeline.slice(-3).map((item) =>
104
- h("div.lt3-row-2", c.statePill(mapStatus(item.status || item.event)), h("span.lt3-faint", item.event || item.message || item.step || "event"))));
105
- }
106
-
107
- async function cancelRun(kind, runId) {
108
- if (!runId) return;
109
- const res = kind === "agent" ? await api.stopAgentRun(runId) : await api.stopWorkflowRun(runId);
110
- toast(resultText(res, t("runs.cancelled")), res.ok ? "ok" : "err");
111
- load();
112
- }
113
- async function decideWorkflow(runId, approved) {
114
- const res = await api.resumeWorkflowRun(runId, approved);
115
- toast(resultText(res, t("runs.decided")), res.ok ? "ok" : "err");
116
- load();
117
- }
118
- async function decidePermission(token, approved) {
119
- const res = approved ? await api.approvePermission(token) : await api.denyPermission(token);
120
- toast(resultText(res, t("runs.decided")), res.ok ? "ok" : "err");
121
- load();
122
- }
123
- }
124
-
125
- function mapStatus(status) {
126
- const s = String(status || "").toLowerCase();
127
- if (s === "ok" || s === "completed" || s === "success" || s === "resumed") return "ready";
128
- if (s === "failed" || s === "error" || s === "denied" || s === "rejected") return "failed";
129
- if (s === "running" || s === "queued" || s === "in_progress" || s === "cancelling") return "active";
130
- if (s === "awaiting_approval" || s === "pending") return "pending";
131
- if (s === "cancelled" || s === "interrupted") return "warn";
132
- return s || "idle";
133
- }
134
-
135
- function fmt(ts) {
136
- if (!ts) return "—";
137
- try { return new Date(ts).toLocaleString(); } catch { return String(ts); }
138
- }
139
-
140
- function resultText(res, okText) {
141
- if (res && res.ok) return okText;
142
- const data = (res && res.data) || {};
143
- return String(data.detail || data.error || res?.error || t("common.unavailable"));
144
- }
@@ -1,144 +0,0 @@
1
- import { t } from "../core/i18n.js";
2
-
3
- const ACTIVE = new Set(["queued", "running", "in_progress", "active", "cancelling"]);
4
-
5
- export async function render(ctx) {
6
- const { h, icon, api, c, toast } = ctx;
7
- const agentHost = h("div", c.loading({ lines: 4 }));
8
- const workflowHost = h("div", c.loading({ lines: 4 }));
9
- const approvalHost = h("div", c.loading({ lines: 4 }));
10
- const progressHost = h("div", c.loading({ lines: 3 }));
11
-
12
- const root = h("div.lt3-stack-6",
13
- c.viewHeader({ eyebrow: t("runs.eyebrow"), title: t("runs.title"), sub: t("runs.sub") }),
14
- h("div.lt3-statrow", progressHost),
15
- c.panel({ title: t("runs.approvals"), children: approvalHost }),
16
- c.panel({ title: t("runs.agentRuns"), children: agentHost }),
17
- c.panel({ title: t("runs.workflowRuns"), children: workflowHost }),
18
- );
19
-
20
- await load();
21
- const poll = setInterval(load, 5000);
22
- root.addEventListener("DOMNodeRemovedFromDocument", () => clearInterval(poll), { once: true });
23
- return root;
24
-
25
- async function load() {
26
- const [agent, workflow, pending] = await Promise.all([
27
- api.agentRuntime(),
28
- api.workflowRuns(),
29
- api.permissionsPending(),
30
- ]);
31
- const agentRuns = Array.isArray(agent.data?.runs) ? agent.data.runs : [];
32
- const workflowRuns = Array.isArray(workflow.data?.runs) ? workflow.data.runs : [];
33
- renderProgress(agentRuns, workflowRuns, pending.data || {});
34
- agentHost.replaceChildren(runTable(ctx, agentRuns, "agent", agent.source));
35
- workflowHost.replaceChildren(runTable(ctx, workflowRuns, "workflow", workflow.source));
36
- approvalHost.replaceChildren(approvalList(ctx, workflowRuns, pending.data || {}, workflow.source || pending.source));
37
- }
38
-
39
- function renderProgress(agentRuns, workflowRuns, pending) {
40
- const all = [...agentRuns, ...workflowRuns];
41
- const active = all.filter((r) => ACTIVE.has(String(r.status || "").toLowerCase())).length;
42
- const paused = workflowRuns.filter((r) => String(r.status || "").toLowerCase() === "awaiting_approval").length;
43
- const approvals = Object.keys(pending.pending || {}).length + paused;
44
- progressHost.replaceChildren(
45
- c.stat({ label: t("runs.progress"), value: String(active), icon: "progress" }),
46
- c.stat({ label: t("runs.approvals"), value: String(approvals), icon: "circle-check" }),
47
- c.stat({ label: t("runs.agentRuns"), value: String(agentRuns.length), icon: "robot" }),
48
- c.stat({ label: t("runs.workflowRuns"), value: String(workflowRuns.length), icon: "sitemap" }),
49
- );
50
- }
51
-
52
- function runTable(ctx2, rows, kind, source) {
53
- const { h, c } = ctx2;
54
- return h("div.lt3-stack-3",
55
- h("div.lt3-row-2", c.sourceBadge(source)),
56
- rows.length ? c.table([
57
- { key: "status", label: t("common.status"), width: "1%", render: (r) => c.statePill(mapStatus(r.status)) },
58
- { key: "mode", label: t("runs.mode"), width: "1%", render: (r) => c.pill(r.mode || r.execution_mode || "live") },
59
- { key: "name", label: t("common.name"), render: (r) => h("div", h("b", r.name || r.workflow_name || r.agent_id || r.workflow_id || r.id), h("div.lt3-faint", { style: { "font-family": "var(--lt3-font-mono)", "font-size": "var(--lt3-text-2xs)" } }, r.id || r.run_id || "")) },
60
- { key: "when", label: t("common.updated"), width: "1%", render: (r) => h("span.lt3-faint", { style: { "white-space": "nowrap" } }, fmt(r.updated_at || r.created_at || r.completed_at)) },
61
- { key: "timeline", label: t("runs.progress"), render: (r) => miniTimeline(ctx2, r.timeline || []) },
62
- { key: "act", label: "", width: "1%", render: (r) => ACTIVE.has(String(r.status || "").toLowerCase())
63
- ? h("button.lt3-btn.lt3-btn--danger.lt3-btn--sm", { on: { click: () => cancelRun(kind, r.id || r.run_id) } }, c.icon("player-stop"), t("common.stop"))
64
- : null },
65
- ], rows.slice(0, 40)) : c.emptyState({ icon: "history-off", title: kind === "agent" ? t("runs.agentRuns") : t("runs.workflowRuns"), body: t("common.none") }),
66
- );
67
- }
68
-
69
- function approvalList(ctx2, workflowRuns, pending, source) {
70
- const workflowApprovals = workflowRuns.filter((r) => String(r.status || "").toLowerCase() === "awaiting_approval");
71
- const permissionRows = Object.entries(pending.pending || {}).map(([token, rec]) => ({ token, ...rec }));
72
- const nodes = [];
73
- nodes.push(h("div.lt3-row-2", c.sourceBadge(source)));
74
- if (workflowApprovals.length) {
75
- nodes.push(...workflowApprovals.map((run) => c.card(h("div.lt3-stack-3",
76
- h("div.lt3-row", { style: { "justify-content": "space-between" } },
77
- h("div", h("b", run.name || run.workflow_name || run.workflow_id), h("div.lt3-faint", t("runs.approvalPaused")), run.pause?.node ? h("div.lt3-faint", run.pause.node) : null),
78
- c.statePill("pending"),
79
- ),
80
- miniTimeline(ctx2, run.timeline || []),
81
- h("div.lt3-row-2",
82
- h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => decideWorkflow(run.id || run.run_id, true) } }, icon("circle-check"), t("common.approve")),
83
- h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => decideWorkflow(run.id || run.run_id, false) } }, icon("circle-x"), t("common.deny")),
84
- ),
85
- ), { flat: true })));
86
- }
87
- if (permissionRows.length) {
88
- nodes.push(...permissionRows.map((rec) => c.card(h("div.lt3-stack-3",
89
- h("div", h("b", rec.action_label || rec.action || "permission"), h("div.lt3-faint", rec.path || rec.token), h("div.lt3-faint", { style: { "font-family": "var(--lt3-font-mono)", "font-size": "var(--lt3-text-2xs)" } }, rec.token)),
90
- h("div.lt3-row-2",
91
- h("button.lt3-btn.lt3-btn--primary.lt3-btn--sm", { on: { click: () => decidePermission(rec.token, true) } }, icon("circle-check"), t("common.approve")),
92
- h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => decidePermission(rec.token, false) } }, icon("circle-x"), t("common.deny")),
93
- ),
94
- ), { flat: true })));
95
- }
96
- if (nodes.length === 1) nodes.push(c.emptyState({ icon: "circle-check", title: t("runs.approvals"), body: t("common.none") }));
97
- return h("div.lt3-stack-3", nodes);
98
- }
99
-
100
- function miniTimeline(ctx2, timeline) {
101
- const { h, c } = ctx2;
102
- if (!timeline.length) return h("span.lt3-faint", t("common.none"));
103
- return h("div.lt3-stack-2", timeline.slice(-3).map((item) =>
104
- h("div.lt3-row-2", c.statePill(mapStatus(item.status || item.event)), h("span.lt3-faint", item.event || item.message || item.step || "event"))));
105
- }
106
-
107
- async function cancelRun(kind, runId) {
108
- if (!runId) return;
109
- const res = kind === "agent" ? await api.stopAgentRun(runId) : await api.stopWorkflowRun(runId);
110
- toast(resultText(res, t("runs.cancelled")), res.ok ? "ok" : "err");
111
- load();
112
- }
113
- async function decideWorkflow(runId, approved) {
114
- const res = await api.resumeWorkflowRun(runId, approved);
115
- toast(resultText(res, t("runs.decided")), res.ok ? "ok" : "err");
116
- load();
117
- }
118
- async function decidePermission(token, approved) {
119
- const res = approved ? await api.approvePermission(token) : await api.denyPermission(token);
120
- toast(resultText(res, t("runs.decided")), res.ok ? "ok" : "err");
121
- load();
122
- }
123
- }
124
-
125
- function mapStatus(status) {
126
- const s = String(status || "").toLowerCase();
127
- if (s === "ok" || s === "completed" || s === "success" || s === "resumed") return "ready";
128
- if (s === "failed" || s === "error" || s === "denied" || s === "rejected") return "failed";
129
- if (s === "running" || s === "queued" || s === "in_progress" || s === "cancelling") return "active";
130
- if (s === "awaiting_approval" || s === "pending") return "pending";
131
- if (s === "cancelled" || s === "interrupted") return "warn";
132
- return s || "idle";
133
- }
134
-
135
- function fmt(ts) {
136
- if (!ts) return "—";
137
- try { return new Date(ts).toLocaleString(); } catch { return String(ts); }
138
- }
139
-
140
- function resultText(res, okText) {
141
- if (res && res.ok) return okText;
142
- const data = (res && res.data) || {};
143
- return String(data.detail || data.error || res?.error || t("common.unavailable"));
144
- }
@@ -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.880e1fec.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
- }