ltcai 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -20
- package/docs/CHANGELOG.md +37 -0
- package/docs/V3_FRONTEND.md +20 -17
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/auth.py +4 -1
- package/latticeai/api/search.py +4 -0
- package/latticeai/core/config.py +2 -0
- package/latticeai/core/embedding_providers.py +123 -0
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +22 -6
- package/package.json +9 -4
- package/scripts/build_v3_assets.mjs +164 -0
- package/scripts/capture/README.md +28 -0
- package/scripts/capture/capture_enterprise.js +8 -0
- package/scripts/capture/capture_graph.js +8 -0
- package/scripts/capture/capture_onboarding.js +8 -0
- package/scripts/capture/capture_page.js +43 -0
- package/scripts/capture/capture_release_media.js +125 -0
- package/scripts/capture/capture_skills.js +8 -0
- package/scripts/capture/capture_workspace.js +8 -0
- package/scripts/generate_diagrams.py +513 -0
- package/scripts/lint_v3.mjs +33 -0
- package/scripts/release-0.3.1.sh +105 -0
- package/scripts/take_screenshots.js +69 -0
- package/scripts/validate_release_artifacts.py +167 -0
- package/static/account.html +9 -9
- package/static/activity.html +4 -4
- package/static/admin.html +8 -8
- package/static/agents.html +4 -4
- package/static/chat.html +9 -9
- package/static/css/tokens.5a595671.css +260 -0
- package/static/css/tokens.css +1 -1
- package/static/graph.html +9 -9
- package/static/plugins.html +4 -4
- package/static/sw.js +3 -1
- package/static/v3/asset-manifest.json +47 -0
- package/static/v3/css/lattice.base.e4cdd05d.css +128 -0
- package/static/v3/css/lattice.components.011e988b.css +447 -0
- package/static/v3/css/lattice.components.css +2 -2
- package/static/v3/css/lattice.shell.4920f42d.css +407 -0
- package/static/v3/css/lattice.tokens.c597ff81.css +132 -0
- package/static/v3/css/lattice.views.3ee19d4e.css +277 -0
- package/static/v3/index.html +38 -9
- package/static/v3/js/app.46fb61d9.js +26 -0
- package/static/v3/js/core/api.22a41d42.js +344 -0
- package/static/v3/js/core/api.js +68 -51
- package/static/v3/js/core/components.4c83e0a9.js +222 -0
- package/static/v3/js/core/components.js +9 -2
- package/static/v3/js/core/dom.a2773eb0.js +148 -0
- package/static/v3/js/core/router.584570f2.js +37 -0
- package/static/v3/js/core/routes.f935dd50.js +78 -0
- package/static/v3/js/core/routes.js +6 -1
- package/static/v3/js/core/shell.1b6199d6.js +363 -0
- package/static/v3/js/core/store.34ebd5e6.js +113 -0
- package/static/v3/js/views/admin-audit.660a1fb1.js +185 -0
- package/static/v3/js/views/admin-audit.js +1 -1
- package/static/v3/js/views/admin-permissions.a7ae5f09.js +177 -0
- package/static/v3/js/views/admin-permissions.js +4 -5
- package/static/v3/js/views/admin-policies.3658fd86.js +102 -0
- package/static/v3/js/views/admin-policies.js +4 -5
- package/static/v3/js/views/admin-private-vpc.7d342d36.js +135 -0
- package/static/v3/js/views/admin-private-vpc.js +2 -5
- package/static/v3/js/views/admin-security.07c66b72.js +180 -0
- package/static/v3/js/views/admin-security.js +4 -5
- package/static/v3/js/views/admin-users.03bac88c.js +168 -0
- package/static/v3/js/views/admin-users.js +6 -6
- package/static/v3/js/views/agents.14e48bdd.js +193 -0
- package/static/v3/js/views/agents.js +1 -2
- package/static/v3/js/views/chat.718144ce.js +449 -0
- package/static/v3/js/views/chat.js +2 -3
- package/static/v3/js/views/files.4935197e.js +186 -0
- package/static/v3/js/views/files.js +27 -21
- package/static/v3/js/views/home.cdde3b32.js +119 -0
- package/static/v3/js/views/hybrid-search.b22b97e0.js +195 -0
- package/static/v3/js/views/hybrid-search.js +1 -1
- package/static/v3/js/views/knowledge-graph.a14ea7e7.js +237 -0
- package/static/v3/js/views/knowledge-graph.js +2 -3
- package/static/v3/js/views/models.a1ffa147.js +256 -0
- package/static/v3/js/views/models.js +17 -8
- package/static/v3/js/views/my-computer.1b2ff621.js +237 -0
- package/static/v3/js/views/my-computer.js +5 -5
- package/static/v3/js/views/pipeline.c522f1ce.js +157 -0
- package/static/v3/js/views/pipeline.js +3 -7
- package/static/v3/js/views/settings.4f777210.js +250 -0
- package/static/v3/js/views/settings.js +6 -14
- package/static/workflows.html +4 -4
- package/static/workspace.html +5 -5
- package/docs/images/tmp_frames/frame_00.png +0 -0
- package/docs/images/tmp_frames/frame_01.png +0 -0
- package/docs/images/tmp_frames/frame_02.png +0 -0
- package/docs/images/tmp_frames/frame_03.png +0 -0
- package/docs/images/tmp_frames/hero_00.png +0 -0
- package/docs/images/tmp_frames/hero_01.png +0 -0
- package/docs/images/tmp_frames/hero_02.png +0 -0
- package/docs/images/tmp_frames/hero_03.png +0 -0
- package/static/v3/js/core/fixtures.js +0 -171
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* View: Pipeline — ingest / embed / graph-build flows.
|
|
3
|
+
* Renders each workspace workflow as a horizontal stage flow (integration-ready
|
|
4
|
+
* against /workspace/workflows and the index APIs). Pipelines execute on the
|
|
5
|
+
* local runtime; this surface visualizes their stages and run state.
|
|
6
|
+
* ========================================================================== */
|
|
7
|
+
|
|
8
|
+
import { timeAgo } from "../core/dom.a2773eb0.js";
|
|
9
|
+
|
|
10
|
+
export async function render(ctx) {
|
|
11
|
+
const { h, icon, api, c, toast } = ctx;
|
|
12
|
+
|
|
13
|
+
const unavailable = (label) => () => toast(`${label} is not available from this read-only pipeline view.`, "warn");
|
|
14
|
+
|
|
15
|
+
const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
|
|
16
|
+
const srcSlot = h("span", c.sourceBadge("pending"));
|
|
17
|
+
const flowsHost = h("div.lt3-stack-6", c.loading({ lines: 3, block: true }));
|
|
18
|
+
|
|
19
|
+
const rebuildBtn = h("button.lt3-btn.lt3-btn--primary", { on: { click: () => rebuild() } }, icon("refresh"), "Rebuild index");
|
|
20
|
+
|
|
21
|
+
const root = h("div.lt3-stack-6",
|
|
22
|
+
c.viewHeader({
|
|
23
|
+
eyebrow: "Data",
|
|
24
|
+
title: "Pipeline",
|
|
25
|
+
sub: "Ingest, embed, and graph-build flows that turn your sources into the retrieval lattice — chunk, embed, extract entities, and link the graph.",
|
|
26
|
+
actions: [rebuildBtn],
|
|
27
|
+
}),
|
|
28
|
+
c.banner(
|
|
29
|
+
"Pipelines execute on this machine's local runtime. Use Rebuild index to re-embed every chunk and relink the knowledge graph from your current sources.",
|
|
30
|
+
"info",
|
|
31
|
+
"server-bolt",
|
|
32
|
+
),
|
|
33
|
+
statHost,
|
|
34
|
+
h("section",
|
|
35
|
+
c.sectionHead("Flows", srcSlot),
|
|
36
|
+
flowsHost,
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
load();
|
|
41
|
+
return root;
|
|
42
|
+
|
|
43
|
+
async function load() {
|
|
44
|
+
const res = await api.get("/workspace/workflows", { workflows: [] });
|
|
45
|
+
const pipelines = normalize(res.data);
|
|
46
|
+
srcSlot.replaceChildren(c.sourceBadge(res.source));
|
|
47
|
+
renderStats(pipelines);
|
|
48
|
+
renderFlows(pipelines);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Real pipeline run: rebuild the vector index (re-embed chunks, relink graph).
|
|
52
|
+
async function rebuild() {
|
|
53
|
+
rebuildBtn.disabled = true;
|
|
54
|
+
rebuildBtn.replaceChildren(icon("loader"), "Rebuilding…");
|
|
55
|
+
const res = await api.rebuildIndex();
|
|
56
|
+
rebuildBtn.disabled = false;
|
|
57
|
+
rebuildBtn.replaceChildren(icon("refresh"), "Rebuild index");
|
|
58
|
+
if (res && res.ok && res.data && res.data.status === "completed") {
|
|
59
|
+
const d = res.data;
|
|
60
|
+
toast(`Index rebuilt — ${d.items_indexed} indexed, ${d.items_skipped} unchanged (${d.embedding_model}).`, "ok");
|
|
61
|
+
load();
|
|
62
|
+
} else {
|
|
63
|
+
const detail = (res && res.data && (res.data.detail || res.data.error)) || "the knowledge graph is unavailable";
|
|
64
|
+
toast(`Could not rebuild the index — ${detail}.`, "warn");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderStats(pipelines) {
|
|
69
|
+
const active = pipelines.filter((p) => isActive(p.state)).length;
|
|
70
|
+
const stages = pipelines.reduce((sum, p) => sum + p.stages.length, 0);
|
|
71
|
+
const throughput = pipelines.find((p) => p.throughput)?.throughput || "—";
|
|
72
|
+
const lastRun = pipelines
|
|
73
|
+
.map((p) => p.last_run)
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.sort((a, b) => new Date(b) - new Date(a))[0];
|
|
76
|
+
statHost.replaceChildren(
|
|
77
|
+
c.stat({ label: "Active pipelines", value: c.fmtNum(active), icon: "player-play" }),
|
|
78
|
+
c.stat({ label: "Total stages", value: c.fmtNum(stages), icon: "stack-2" }),
|
|
79
|
+
c.stat({ label: "Throughput", value: throughput, icon: "gauge" }),
|
|
80
|
+
c.stat({ label: "Last run", value: lastRun ? timeAgo(lastRun) : "—", icon: "history" }),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderFlows(pipelines) {
|
|
85
|
+
if (!pipelines.length) {
|
|
86
|
+
flowsHost.replaceChildren(
|
|
87
|
+
c.emptyState({
|
|
88
|
+
icon: "git-branch-deleted",
|
|
89
|
+
title: "No pipelines yet",
|
|
90
|
+
body: "Connect a source and create a pipeline to ingest, embed, and build the graph.",
|
|
91
|
+
action: h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => rebuild() } }, icon("refresh"), "Rebuild index"),
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
flowsHost.replaceChildren(...pipelines.map((p) => pipelinePanel(p)));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function pipelinePanel(p) {
|
|
100
|
+
return c.panel({
|
|
101
|
+
head: h("div.lt3-row", { style: { "justify-content": "space-between", "flex-wrap": "wrap", gap: "var(--lt3-space-3)" } },
|
|
102
|
+
h("div.lt3-row-2",
|
|
103
|
+
h("div.lt3-eyebrow", icon("git-branch"), "Pipeline"),
|
|
104
|
+
),
|
|
105
|
+
c.statePill(p.state),
|
|
106
|
+
),
|
|
107
|
+
children: h("div.lt3-stack-4",
|
|
108
|
+
h("h3.lt3-panel__title", { style: { "margin-top": "calc(-1 * var(--lt3-space-2))" } }, p.name),
|
|
109
|
+
flowDiagram(p.stages),
|
|
110
|
+
pipelineFooter(p),
|
|
111
|
+
),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function flowDiagram(stages) {
|
|
116
|
+
const cells = [];
|
|
117
|
+
stages.forEach((stage, i) => {
|
|
118
|
+
if (i > 0) cells.push(h("div.lt3-flow__arrow", { "aria-hidden": "true" }, icon("chevron-right")));
|
|
119
|
+
cells.push(
|
|
120
|
+
h("div.lt3-stage",
|
|
121
|
+
h("div.lt3-stage__num", String(i + 1).padStart(2, "0")),
|
|
122
|
+
h("div.lt3-stage__name", stage),
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
return h("div.lt3-flow", { role: "list", "aria-label": "Pipeline stages" }, cells);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function pipelineFooter(p) {
|
|
130
|
+
return h("div.lt3-row", { style: { "justify-content": "space-between", "flex-wrap": "wrap", gap: "var(--lt3-space-3)" } },
|
|
131
|
+
h("div.lt3-cluster",
|
|
132
|
+
h("span.lt3-faint.lt3-row-2", { style: { "font-size": "var(--lt3-text-xs)" } }, icon("history"), p.last_run ? timeAgo(p.last_run) : "—"),
|
|
133
|
+
h("span.lt3-faint.lt3-row-2", { style: { "font-size": "var(--lt3-text-xs)" } }, icon("gauge"), p.throughput || "—"),
|
|
134
|
+
),
|
|
135
|
+
h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: unavailable(`Running "${p.name}"`) } }, icon("player-play"), "Run"),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* ── helpers ─────────────────────────────────────────────────────────────── */
|
|
141
|
+
function normalize(data) {
|
|
142
|
+
const list = Array.isArray(data) ? data : (data && data.workflows) || [];
|
|
143
|
+
return list.map((p, i) => ({
|
|
144
|
+
id: p.id || `pl-${i}`,
|
|
145
|
+
name: p.name || p.label || "Untitled pipeline",
|
|
146
|
+
state: p.state || "idle",
|
|
147
|
+
stages: Array.isArray(p.stages) ? p.stages.map((s) => String(s))
|
|
148
|
+
: Array.isArray(p.steps) ? p.steps.map((s) => (s && (s.action || s.name)) || String(s))
|
|
149
|
+
: [],
|
|
150
|
+
last_run: p.last_run || p.created_at || null,
|
|
151
|
+
throughput: p.throughput || "",
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isActive(state) {
|
|
156
|
+
return ["active", "running", "indexing", "building"].includes(String(state).toLowerCase());
|
|
157
|
+
}
|
|
@@ -2,19 +2,15 @@
|
|
|
2
2
|
* View: Pipeline — ingest / embed / graph-build flows.
|
|
3
3
|
* Renders each workspace workflow as a horizontal stage flow (integration-ready
|
|
4
4
|
* against /workspace/workflows and the index APIs). Pipelines execute on the
|
|
5
|
-
* local runtime; this surface visualizes their stages and run state
|
|
6
|
-
* back to clearly-badged sample data until the backend route is available.
|
|
5
|
+
* local runtime; this surface visualizes their stages and run state.
|
|
7
6
|
* ========================================================================== */
|
|
8
7
|
|
|
9
8
|
import { timeAgo } from "../core/dom.js";
|
|
10
|
-
import * as fx from "../core/fixtures.js";
|
|
11
9
|
|
|
12
10
|
export async function render(ctx) {
|
|
13
11
|
const { h, icon, api, c, toast } = ctx;
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
// this view in this build — say so plainly instead of implying it's coming.
|
|
17
|
-
const unavailable = (label) => () => toast(`${label} is managed from the classic workflow designer — not available from this view.`, "warn");
|
|
13
|
+
const unavailable = (label) => () => toast(`${label} is not available from this read-only pipeline view.`, "warn");
|
|
18
14
|
|
|
19
15
|
const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
|
|
20
16
|
const srcSlot = h("span", c.sourceBadge("pending"));
|
|
@@ -45,7 +41,7 @@ export async function render(ctx) {
|
|
|
45
41
|
return root;
|
|
46
42
|
|
|
47
43
|
async function load() {
|
|
48
|
-
const res = await api.get("/workspace/workflows", { workflows:
|
|
44
|
+
const res = await api.get("/workspace/workflows", { workflows: [] });
|
|
49
45
|
const pipelines = normalize(res.data);
|
|
50
46
|
srcSlot.replaceChildren(c.sourceBadge(res.source));
|
|
51
47
|
renderStats(pipelines);
|
|
@@ -0,0 +1,250 @@
|
|
|
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
|
+
const MODE_DEFS = [
|
|
9
|
+
{ key: "basic", label: "Basic", desc: "Chat, search, and files — the essentials, nothing else." },
|
|
10
|
+
{ key: "advanced", label: "Advanced", desc: "Adds the pipeline, agents, and model runtime surfaces." },
|
|
11
|
+
{ key: "admin", label: "Admin", desc: "Reveals users, permissions, audit, security, and policies." },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
// Endpoints the views light up against once the backend exposes them.
|
|
15
|
+
const PROBES = [
|
|
16
|
+
{ path: "/api/index/status", method: "GET", call: (api) => api.indexStatus() },
|
|
17
|
+
{ path: "/api/graph", method: "GET", call: (api) => api.graph() },
|
|
18
|
+
{ path: "/api/search/hybrid", method: "POST", call: (api) => api.hybridSearch("ping") },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export async function render(ctx) {
|
|
22
|
+
const { h, icon, api, store, c, navigate, toast } = ctx;
|
|
23
|
+
|
|
24
|
+
const probesHost = h("div", c.loading({ lines: 3 }));
|
|
25
|
+
|
|
26
|
+
const embedHost = h("div", c.loading({ lines: 2 }));
|
|
27
|
+
|
|
28
|
+
const root = h("div.lt3-stack-6",
|
|
29
|
+
c.viewHeader({
|
|
30
|
+
eyebrow: "System",
|
|
31
|
+
title: "Settings",
|
|
32
|
+
sub: "Appearance, workspace, and integrations.",
|
|
33
|
+
}),
|
|
34
|
+
|
|
35
|
+
appearancePanel(ctx),
|
|
36
|
+
workspacePanel(ctx),
|
|
37
|
+
|
|
38
|
+
c.panel({
|
|
39
|
+
eyebrow: "Models",
|
|
40
|
+
title: "Embeddings",
|
|
41
|
+
sub: "The vector signal behind retrieval. Configure the provider with LATTICEAI_EMBEDDING_PROVIDER (hash · mlx · ollama · openai · custom).",
|
|
42
|
+
children: embedHost,
|
|
43
|
+
}),
|
|
44
|
+
|
|
45
|
+
c.panel({
|
|
46
|
+
eyebrow: "Status",
|
|
47
|
+
title: "Integration readiness",
|
|
48
|
+
sub: "Each view probes its endpoint and reports unavailable state until the backend answers.",
|
|
49
|
+
children: h("div.lt3-stack-3",
|
|
50
|
+
probesHost,
|
|
51
|
+
h("p.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } },
|
|
52
|
+
"Views automatically switch to live data once these endpoints respond; unreachable endpoints are labeled unavailable."),
|
|
53
|
+
),
|
|
54
|
+
}),
|
|
55
|
+
|
|
56
|
+
aboutPanel(ctx),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
probeEndpoints(ctx, probesHost);
|
|
60
|
+
renderEmbeddings(ctx, embedHost);
|
|
61
|
+
return root;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ── Embeddings (Settings → Models → Embeddings) ────────────────────────── */
|
|
65
|
+
export function embeddingStatePill({ h, c }, st) {
|
|
66
|
+
const state = String(st.state || st.grade || "fallback").toLowerCase();
|
|
67
|
+
if (state === "production") return c.pill("Production", "ok");
|
|
68
|
+
if (state === "unavailable") return c.pill("Unavailable", "err");
|
|
69
|
+
return c.pill("Fallback", "warn");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function renderEmbeddings(ctx, host) {
|
|
73
|
+
const { h, c } = ctx;
|
|
74
|
+
const res = await ctx.api.embeddingsStatus();
|
|
75
|
+
const d = res.data || {};
|
|
76
|
+
const lastIndexed = d.last_indexed_at ? new Date(d.last_indexed_at).toLocaleString() : "Never";
|
|
77
|
+
host.replaceChildren(
|
|
78
|
+
h("div.lt3-stack-4",
|
|
79
|
+
h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "center", "flex-wrap": "wrap", gap: "var(--lt3-space-3)" } },
|
|
80
|
+
h("div.lt3-row-2",
|
|
81
|
+
h("span", { style: { color: "var(--lt3-pillar-vector, var(--accent))", display: "inline-flex" } }, ctx.icon("grid-dots")),
|
|
82
|
+
h("b", { style: { "font-size": "var(--lt3-text-md)" } }, providerLabel(d.active_provider || d.provider)),
|
|
83
|
+
),
|
|
84
|
+
h("div.lt3-row-2", embeddingStatePill(ctx, d), c.sourceBadge(res.source)),
|
|
85
|
+
),
|
|
86
|
+
d.fell_back
|
|
87
|
+
? 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")
|
|
88
|
+
: null,
|
|
89
|
+
h("dl.lt3-keyval",
|
|
90
|
+
h("dt", "Provider"), h("dd", providerLabel(d.active_provider || d.provider)),
|
|
91
|
+
h("dt", "Model"), h("dd", h("span.lt3-mono", d.model || d.model_id || "—")),
|
|
92
|
+
h("dt", "Dimensions"), h("dd", h("span.lt3-mono", String(d.dimensions || "—"))),
|
|
93
|
+
h("dt", "Status"), h("dd", embeddingStatePill(ctx, d)),
|
|
94
|
+
h("dt", "Last index"), h("dd", lastIndexed),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function providerLabel(p) {
|
|
101
|
+
return ({ hash: "Local hash (fallback)", mlx: "MLX (Apple Silicon)", ollama: "Ollama",
|
|
102
|
+
openai: "OpenAI-compatible", custom: "Custom" }[String(p || "hash")]) || String(p || "—");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* ── Appearance ─────────────────────────────────────────────────────────── */
|
|
106
|
+
function appearancePanel({ h, icon, store, c }) {
|
|
107
|
+
const themeKey = () => {
|
|
108
|
+
const t = store.get().theme;
|
|
109
|
+
return t === "light" || t === "dark" ? t : "";
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const themeSlot = h("div");
|
|
113
|
+
const buildTheme = () => c.segmented(
|
|
114
|
+
[{ key: "light", label: "Light" }, { key: "dark", label: "Dark" }, { key: "", label: "System" }],
|
|
115
|
+
themeKey(),
|
|
116
|
+
(k) => { store.setTheme(k); themeSlot.replaceChildren(buildTheme()); },
|
|
117
|
+
);
|
|
118
|
+
themeSlot.append(buildTheme());
|
|
119
|
+
|
|
120
|
+
const modeSeg = c.segmented(
|
|
121
|
+
MODE_DEFS.map((m) => ({ key: m.key, label: m.label })),
|
|
122
|
+
store.get().mode,
|
|
123
|
+
(k) => { store.setMode(k); modeNote.replaceChildren(noteFor(k)); },
|
|
124
|
+
);
|
|
125
|
+
const noteFor = (k) => h("span", (MODE_DEFS.find((m) => m.key === k) || MODE_DEFS[0]).desc);
|
|
126
|
+
const modeNote = h("p.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, noteFor(store.get().mode));
|
|
127
|
+
|
|
128
|
+
return c.panel({
|
|
129
|
+
eyebrow: "Appearance",
|
|
130
|
+
title: "Look and density",
|
|
131
|
+
sub: "Theme and surface mode persist on this machine and apply across every view.",
|
|
132
|
+
children: h("div.lt3-stack-6",
|
|
133
|
+
h("div.lt3-field",
|
|
134
|
+
h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("palette"), "Theme"),
|
|
135
|
+
themeSlot,
|
|
136
|
+
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "System follows your OS appearance preference."),
|
|
137
|
+
),
|
|
138
|
+
h("div.lt3-field",
|
|
139
|
+
h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("adjustments"), "Mode"),
|
|
140
|
+
h("div", modeSeg),
|
|
141
|
+
modeNote,
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* ── Workspace ──────────────────────────────────────────────────────────── */
|
|
148
|
+
function workspacePanel({ h, icon, store, c, toast, api }) {
|
|
149
|
+
const ws = store.activeWorkspace();
|
|
150
|
+
|
|
151
|
+
const orgInput = h("input.lt3-input", {
|
|
152
|
+
type: "text", placeholder: "Organization name…", "aria-label": "New organization name",
|
|
153
|
+
style: { "flex": "1 1 220px" },
|
|
154
|
+
});
|
|
155
|
+
const createBtn = h("button.lt3-btn.lt3-btn--primary", { type: "button" }, icon("plus"), "Create organization");
|
|
156
|
+
const createOrg = async () => {
|
|
157
|
+
const name = (orgInput.value || "").trim();
|
|
158
|
+
if (!name) { toast("Enter an organization name first.", "info"); return; }
|
|
159
|
+
createBtn.disabled = true;
|
|
160
|
+
const res = await api.createOrg(name);
|
|
161
|
+
createBtn.disabled = false;
|
|
162
|
+
if (res && res.ok && res.data && !res.data.detail && !res.data.error) {
|
|
163
|
+
toast(`Organization “${name}” created.`, "ok");
|
|
164
|
+
orgInput.value = "";
|
|
165
|
+
} else {
|
|
166
|
+
const detail = (res && res.data && (res.data.detail || res.data.error)) || "the runtime is unavailable";
|
|
167
|
+
toast(`Could not create organization — ${detail}.`, "warn");
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
createBtn.addEventListener("click", createOrg);
|
|
171
|
+
|
|
172
|
+
let savedLang = "en";
|
|
173
|
+
try { savedLang = localStorage.getItem("lt3-lang") || "en"; } catch {}
|
|
174
|
+
const langSelect = h("select.lt3-select", {
|
|
175
|
+
"aria-label": "Interface language", value: savedLang,
|
|
176
|
+
on: { change: (e) => {
|
|
177
|
+
try { localStorage.setItem("lt3-lang", e.target.value); } catch {}
|
|
178
|
+
toast(`Interface language set to ${e.target.selectedOptions[0].text} (saved on this device).`, "ok");
|
|
179
|
+
} },
|
|
180
|
+
},
|
|
181
|
+
h("option", { value: "en" }, "English"),
|
|
182
|
+
h("option", { value: "ko" }, "한국어"),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return c.panel({
|
|
186
|
+
eyebrow: "Workspace",
|
|
187
|
+
title: "Active workspace",
|
|
188
|
+
sub: "Where your indexed knowledge, agents, and policies live.",
|
|
189
|
+
children: h("div.lt3-stack-6",
|
|
190
|
+
h("dl.lt3-keyval",
|
|
191
|
+
h("dt", "Name"), h("dd", ws.name),
|
|
192
|
+
h("dt", "Type"), h("dd", h("span.lt3-row-2", icon(ws.type === "personal" ? "user" : "building"), titleCase(ws.type || "personal"))),
|
|
193
|
+
h("dt", "Your role"), h("dd", c.pill(titleCase(ws.your_role || "owner"), "info")),
|
|
194
|
+
),
|
|
195
|
+
h("hr.lt3-divider"),
|
|
196
|
+
h("div.lt3-field",
|
|
197
|
+
h("label.lt3-label", { style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("building-community"), "Create organization"),
|
|
198
|
+
h("div.lt3-cluster",
|
|
199
|
+
orgInput,
|
|
200
|
+
createBtn,
|
|
201
|
+
),
|
|
202
|
+
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "Creates a shared organization workspace on this server."),
|
|
203
|
+
),
|
|
204
|
+
h("div.lt3-field",
|
|
205
|
+
h("label.lt3-label", { for: "lt3-set-lang", style: { "display": "flex", "gap": "var(--lt3-space-2)", "align-items": "center" } }, icon("language"), "Language"),
|
|
206
|
+
h("div", { style: { "max-width": "260px" } }, langSelect),
|
|
207
|
+
),
|
|
208
|
+
),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ── Integration readiness ──────────────────────────────────────────────── */
|
|
213
|
+
async function probeEndpoints({ h, icon, api, c }, host) {
|
|
214
|
+
const results = await Promise.all(PROBES.map((p) => p.call(api)));
|
|
215
|
+
const rows = PROBES.map((p, i) => {
|
|
216
|
+
const res = results[i] || {};
|
|
217
|
+
return h("div.lt3-card.lt3-card--flat",
|
|
218
|
+
h("div.lt3-row", { style: { "justify-content": "space-between", "gap": "var(--lt3-space-3)", "flex-wrap": "wrap" } },
|
|
219
|
+
h("div.lt3-row-2",
|
|
220
|
+
h("span.lt3-pill", { style: { "font-weight": "var(--lt3-weight-medium)" } }, p.method),
|
|
221
|
+
h("code.lt3-mono", p.path),
|
|
222
|
+
),
|
|
223
|
+
c.sourceBadge(res.source === "live" ? "live" : "unavailable"),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
host.replaceChildren(h("div.lt3-stack-2", rows));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/* ── About ──────────────────────────────────────────────────────────────── */
|
|
231
|
+
function aboutPanel({ h, icon, c }) {
|
|
232
|
+
return c.panel({
|
|
233
|
+
eyebrow: "About",
|
|
234
|
+
title: "Lattice AI",
|
|
235
|
+
sub: "Local-first AI workspace.",
|
|
236
|
+
children: h("div.lt3-stack-4",
|
|
237
|
+
h("dl.lt3-keyval",
|
|
238
|
+
h("dt", "Application"), h("dd", "Lattice AI"),
|
|
239
|
+
h("dt", "Version"), h("dd", h("span.lt3-mono", "v3.1.0")),
|
|
240
|
+
h("dt", "Edition"), h("dd", "Local-first AI workspace"),
|
|
241
|
+
),
|
|
242
|
+
),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/* ── helpers ────────────────────────────────────────────────────────────── */
|
|
247
|
+
function titleCase(s) {
|
|
248
|
+
s = String(s || "");
|
|
249
|
+
return s ? s.charAt(0).toUpperCase() + s.slice(1) : s;
|
|
250
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
2
|
* View: Settings — appearance, workspace, and integration readiness.
|
|
3
3
|
* This view WIRES real store state (theme + mode persist immediately) and
|
|
4
|
-
* probes the documented
|
|
5
|
-
*
|
|
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
6
|
* ========================================================================== */
|
|
7
7
|
|
|
8
8
|
const MODE_DEFS = [
|
|
@@ -45,11 +45,11 @@ export async function render(ctx) {
|
|
|
45
45
|
c.panel({
|
|
46
46
|
eyebrow: "Status",
|
|
47
47
|
title: "Integration readiness",
|
|
48
|
-
sub: "Each view probes its endpoint and
|
|
48
|
+
sub: "Each view probes its endpoint and reports unavailable state until the backend answers.",
|
|
49
49
|
children: h("div.lt3-stack-3",
|
|
50
50
|
probesHost,
|
|
51
51
|
h("p.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } },
|
|
52
|
-
"Views automatically switch to live data once these endpoints respond
|
|
52
|
+
"Views automatically switch to live data once these endpoints respond; unreachable endpoints are labeled unavailable."),
|
|
53
53
|
),
|
|
54
54
|
}),
|
|
55
55
|
|
|
@@ -220,7 +220,7 @@ async function probeEndpoints({ h, icon, api, c }, host) {
|
|
|
220
220
|
h("span.lt3-pill", { style: { "font-weight": "var(--lt3-weight-medium)" } }, p.method),
|
|
221
221
|
h("code.lt3-mono", p.path),
|
|
222
222
|
),
|
|
223
|
-
c.sourceBadge(res.source === "live" ? "live" : "
|
|
223
|
+
c.sourceBadge(res.source === "live" ? "live" : "unavailable"),
|
|
224
224
|
),
|
|
225
225
|
);
|
|
226
226
|
});
|
|
@@ -236,17 +236,9 @@ function aboutPanel({ h, icon, c }) {
|
|
|
236
236
|
children: h("div.lt3-stack-4",
|
|
237
237
|
h("dl.lt3-keyval",
|
|
238
238
|
h("dt", "Application"), h("dd", "Lattice AI"),
|
|
239
|
-
h("dt", "Version"), h("dd", h("span.lt3-mono", "v3.0
|
|
239
|
+
h("dt", "Version"), h("dd", h("span.lt3-mono", "v3.1.0")),
|
|
240
240
|
h("dt", "Edition"), h("dd", "Local-first AI workspace"),
|
|
241
241
|
),
|
|
242
|
-
h("hr.lt3-divider"),
|
|
243
|
-
h("div.lt3-cluster",
|
|
244
|
-
h("button.lt3-btn.lt3-btn--ghost", {
|
|
245
|
-
type: "button",
|
|
246
|
-
on: { click: () => { window.location.href = "/workspace"; } },
|
|
247
|
-
}, icon("layout-dashboard"), "Open classic workspace"),
|
|
248
|
-
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-xs)" } }, "The original Lattice surface remains available."),
|
|
249
|
-
),
|
|
250
242
|
),
|
|
251
243
|
});
|
|
252
244
|
}
|
package/static/workflows.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Workflow Designer — Lattice AI</title>
|
|
7
|
-
<script src="/static/scripts/ux.js
|
|
8
|
-
<link rel="stylesheet" href="/static/css/tokens.css
|
|
9
|
-
<link rel="stylesheet" href="/static/platform.css
|
|
10
|
-
<link rel="stylesheet" href="/static/css/responsive.css
|
|
7
|
+
<script src="/static/scripts/ux.js"></script>
|
|
8
|
+
<link rel="stylesheet" href="/static/css/tokens.css" />
|
|
9
|
+
<link rel="stylesheet" href="/static/platform.css" />
|
|
10
|
+
<link rel="stylesheet" href="/static/css/responsive.css" />
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<main>
|
package/static/workspace.html
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
6
6
|
<title>Lattice AI Workspace OS</title>
|
|
7
|
-
<script src="/static/scripts/ux.js
|
|
7
|
+
<script src="/static/scripts/ux.js"></script>
|
|
8
8
|
<link rel="manifest" href="/manifest.json">
|
|
9
9
|
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
|
|
10
10
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap">
|
|
11
11
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
|
|
12
|
-
<link rel="stylesheet" href="/static/css/tokens.css
|
|
13
|
-
<link rel="stylesheet" href="/static/workspace.css
|
|
14
|
-
<link rel="stylesheet" href="/static/css/responsive.css
|
|
12
|
+
<link rel="stylesheet" href="/static/css/tokens.css">
|
|
13
|
+
<link rel="stylesheet" href="/static/workspace.css">
|
|
14
|
+
<link rel="stylesheet" href="/static/css/responsive.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body class="workspace-page">
|
|
17
17
|
<div class="workspace-shell" data-workspace-mode="basic">
|
|
@@ -352,6 +352,6 @@
|
|
|
352
352
|
</div>
|
|
353
353
|
|
|
354
354
|
<div class="toast" id="toast"></div>
|
|
355
|
-
<script src="/static/scripts/workspace.js
|
|
355
|
+
<script src="/static/scripts/workspace.js"></script>
|
|
356
356
|
</body>
|
|
357
357
|
</html>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|