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.
- package/README.md +33 -24
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +84 -0
- package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
- package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
- package/docs/V4_1_VALIDATION_REPORT.md +47 -0
- package/docs/V4_2_BRAIN_CORE_ARCHITECTURE.md +97 -0
- package/docs/V4_2_STORAGE_MIGRATION_REPORT.md +91 -0
- package/docs/V4_2_VALIDATION_REPORT.md +89 -0
- package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +31 -26
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14436 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +320 -0
- package/frontend/src/api/openapi.ts +16921 -0
- package/frontend/src/components/primitives.tsx +204 -0
- package/frontend/src/components/ui/badge.tsx +27 -0
- package/frontend/src/components/ui/button.tsx +37 -0
- package/frontend/src/components/ui/card.tsx +22 -0
- package/frontend/src/components/ui/input.tsx +16 -0
- package/frontend/src/components/ui/textarea.tsx +16 -0
- package/frontend/src/lib/utils.ts +33 -0
- package/frontend/src/main.tsx +23 -0
- package/frontend/src/pages/Act.tsx +245 -0
- package/frontend/src/pages/Ask.tsx +200 -0
- package/frontend/src/pages/Brain.tsx +267 -0
- package/frontend/src/pages/Capture.tsx +158 -0
- package/frontend/src/pages/Library.tsx +187 -0
- package/frontend/src/pages/System.tsx +378 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/kg_schema.py +1 -1
- package/knowledge_graph.py +4 -4
- package/lattice_brain/__init__.py +70 -0
- package/lattice_brain/_kg_common.py +1 -0
- package/lattice_brain/archive.py +133 -0
- package/lattice_brain/context.py +3 -0
- package/lattice_brain/conversations.py +3 -0
- package/lattice_brain/core.py +82 -0
- package/lattice_brain/discovery.py +1 -0
- package/lattice_brain/documents.py +1 -0
- package/lattice_brain/embeddings.py +82 -0
- package/lattice_brain/identity.py +13 -0
- package/lattice_brain/ingest.py +1 -0
- package/lattice_brain/memory.py +3 -0
- package/lattice_brain/network.py +1 -0
- package/lattice_brain/projection.py +1 -0
- package/lattice_brain/provenance.py +1 -0
- package/lattice_brain/retrieval.py +1 -0
- package/lattice_brain/schema.py +1 -0
- package/lattice_brain/storage/__init__.py +22 -0
- package/lattice_brain/storage/base.py +72 -0
- package/lattice_brain/storage/docker.py +105 -0
- package/lattice_brain/storage/factory.py +31 -0
- package/lattice_brain/storage/migration.py +190 -0
- package/lattice_brain/storage/postgres.py +123 -0
- package/lattice_brain/storage/sqlite.py +128 -0
- package/lattice_brain/store.py +3 -0
- package/lattice_brain/write_master.py +1 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/portability.py +69 -0
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +4 -4
- package/latticeai/app_factory.py +17 -10
- package/latticeai/brain/__init__.py +6 -6
- package/latticeai/brain/_kg_common.py +1 -1
- package/latticeai/brain/network.py +1 -1
- package/latticeai/brain/retrieval.py +15 -0
- package/latticeai/brain/store.py +22 -6
- package/latticeai/core/config.py +8 -0
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/services/kg_portability.py +82 -1
- package/package.json +55 -15
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +4 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +91 -0
- package/scripts/migrate_brain_storage.py +53 -0
- package/scripts/run_python.mjs +47 -0
- package/scripts/wheel_smoke.py +3 -0
- package/src-tauri/Cargo.lock +4833 -0
- package/src-tauri/Cargo.toml +19 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +7 -0
- package/src-tauri/src/main.rs +78 -0
- package/src-tauri/tauri.conf.json +39 -0
- package/static/app/asset-manifest.json +32 -0
- package/static/app/assets/core-CwxXejkd.js +2 -0
- package/static/app/assets/core-CwxXejkd.js.map +1 -0
- package/static/app/assets/index-CDjiH_se.css +2 -0
- package/static/app/assets/index-C_HAkbAg.js +333 -0
- package/static/app/assets/index-C_HAkbAg.js.map +1 -0
- package/static/app/index.html +25 -0
- package/static/manifest.json +2 -2
- package/static/sw.js +4 -4
- package/scripts/build_v3_assets.mjs +0 -170
- package/scripts/lint_v3.mjs +0 -120
- package/static/v3/asset-manifest.json +0 -63
- package/static/v3/css/lattice.base.49deefb5.css +0 -128
- package/static/v3/css/lattice.base.css +0 -128
- package/static/v3/css/lattice.components.cde18231.css +0 -472
- package/static/v3/css/lattice.components.css +0 -472
- package/static/v3/css/lattice.shell.29d36d85.css +0 -452
- package/static/v3/css/lattice.shell.css +0 -452
- package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
- package/static/v3/css/lattice.tokens.css +0 -135
- package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
- package/static/v3/css/lattice.views.css +0 -360
- package/static/v3/index.html +0 -68
- package/static/v3/js/app.c5c80c46.js +0 -26
- package/static/v3/js/app.js +0 -26
- package/static/v3/js/core/api.ba0fbf14.js +0 -625
- package/static/v3/js/core/api.js +0 -625
- package/static/v3/js/core/components.f25b3b93.js +0 -230
- package/static/v3/js/core/components.js +0 -230
- package/static/v3/js/core/dom.a2773eb0.js +0 -148
- package/static/v3/js/core/dom.js +0 -148
- package/static/v3/js/core/i18n.880e1fec.js +0 -575
- package/static/v3/js/core/i18n.js +0 -575
- package/static/v3/js/core/router.584570f2.js +0 -37
- package/static/v3/js/core/router.js +0 -37
- package/static/v3/js/core/routes.37522821.js +0 -101
- package/static/v3/js/core/routes.js +0 -101
- package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
- package/static/v3/js/core/shell.js +0 -420
- package/static/v3/js/core/store.7b2aa044.js +0 -123
- package/static/v3/js/core/store.js +0 -123
- package/static/v3/js/views/account.eff40715.js +0 -143
- package/static/v3/js/views/account.js +0 -143
- package/static/v3/js/views/activity.0d271ef9.js +0 -67
- package/static/v3/js/views/activity.js +0 -67
- package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
- package/static/v3/js/views/admin-audit.js +0 -185
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
- package/static/v3/js/views/admin-permissions.js +0 -177
- package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
- package/static/v3/js/views/admin-policies.js +0 -102
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
- package/static/v3/js/views/admin-private-vpc.js +0 -135
- package/static/v3/js/views/admin-security.07c66b72.js +0 -180
- package/static/v3/js/views/admin-security.js +0 -180
- package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
- package/static/v3/js/views/admin-users.js +0 -166
- package/static/v3/js/views/agents.17c5288d.js +0 -564
- package/static/v3/js/views/agents.js +0 -564
- package/static/v3/js/views/chat.e250e2cc.js +0 -624
- package/static/v3/js/views/chat.js +0 -624
- package/static/v3/js/views/files.adad14c1.js +0 -365
- package/static/v3/js/views/files.js +0 -365
- package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
- package/static/v3/js/views/graph-canvas.js +0 -509
- package/static/v3/js/views/home.24f8b8ae.js +0 -200
- package/static/v3/js/views/home.js +0 -200
- package/static/v3/js/views/hooks.37895880.js +0 -220
- package/static/v3/js/views/hooks.js +0 -220
- package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
- package/static/v3/js/views/hybrid-search.js +0 -194
- package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
- package/static/v3/js/views/knowledge-graph.js +0 -529
- package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
- package/static/v3/js/views/marketplace.js +0 -141
- package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
- package/static/v3/js/views/mcp.js +0 -114
- package/static/v3/js/views/memory.4ebdf474.js +0 -147
- package/static/v3/js/views/memory.js +0 -147
- package/static/v3/js/views/models.a1ffa147.js +0 -256
- package/static/v3/js/views/models.js +0 -256
- package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
- package/static/v3/js/views/my-computer.js +0 -463
- package/static/v3/js/views/network.52a4f181.js +0 -97
- package/static/v3/js/views/network.js +0 -97
- package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
- package/static/v3/js/views/pipeline.js +0 -157
- package/static/v3/js/views/planning.4876fd77.js +0 -174
- package/static/v3/js/views/planning.js +0 -174
- package/static/v3/js/views/runs.b63b2afa.js +0 -144
- package/static/v3/js/views/runs.js +0 -144
- package/static/v3/js/views/settings.b7140634.js +0 -317
- package/static/v3/js/views/settings.js +0 -317
- package/static/v3/js/views/skills.c6c2f965.js +0 -109
- package/static/v3/js/views/skills.js +0 -109
- package/static/v3/js/views/snapshots.6f5db095.js +0 -135
- package/static/v3/js/views/snapshots.js +0 -135
- package/static/v3/js/views/tools.e4f11276.js +0 -108
- package/static/v3/js/views/tools.js +0 -108
- package/static/v3/js/views/workflows.7752225a.js +0 -213
- package/static/v3/js/views/workflows.js +0 -213
- package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
- 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
|
-
}
|