ltcai 3.3.0 → 3.4.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 +85 -66
- package/docs/CHANGELOG.md +36 -0
- package/docs/architecture.md +2 -1
- package/docs/assets/v3.4.0/agent-run.png +0 -0
- package/docs/assets/v3.4.0/agents.png +0 -0
- package/docs/assets/v3.4.0/before/chat-before.png +0 -0
- package/docs/assets/v3.4.0/before/files-before.png +0 -0
- package/docs/assets/v3.4.0/chat.png +0 -0
- package/docs/assets/v3.4.0/connect-folder.png +0 -0
- package/docs/assets/v3.4.0/files.png +0 -0
- package/docs/assets/v3.4.0/home.png +0 -0
- package/docs/assets/v3.4.0/hooks-dispatch.png +0 -0
- package/docs/assets/v3.4.0/knowledge-graph.png +0 -0
- package/docs/assets/v3.4.0/local-agent.png +0 -0
- package/docs/assets/v3.4.0/memory.png +0 -0
- package/docs/assets/v3.4.0/settings.png +0 -0
- package/docs/assets/v3.4.0/vision-input.png +0 -0
- package/docs/assets/v3.4.0/workflows.png +0 -0
- package/knowledge_graph.py +45 -0
- package/knowledge_graph_api.py +10 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/agents.py +3 -0
- package/latticeai/api/hooks.py +39 -0
- package/latticeai/api/local_files.py +41 -0
- package/latticeai/api/models.py +36 -1
- package/latticeai/api/tools.py +16 -1
- package/latticeai/api/workflow_designer.py +2 -1
- package/latticeai/core/hooks.py +398 -2
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workflow_engine.py +21 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/latticeai/server_app.py +40 -0
- package/latticeai/services/agent_runtime.py +46 -1
- package/latticeai/services/upload_service.py +17 -0
- package/package.json +1 -1
- package/scripts/capture/capture_v340.js +88 -0
- package/static/css/{tokens.8b8e31bd.css → tokens.3ba22e37.css} +109 -109
- package/static/css/tokens.css +109 -109
- package/static/v3/asset-manifest.json +24 -24
- package/static/v3/css/{lattice.components.011e988b.css → lattice.components.9b49d614.css} +57 -32
- package/static/v3/css/lattice.components.css +57 -32
- package/static/v3/css/{lattice.shell.4920f42d.css → lattice.shell.6ceea7c8.css} +75 -31
- package/static/v3/css/lattice.shell.css +75 -31
- package/static/v3/css/lattice.tokens.css +13 -13
- package/static/v3/css/{lattice.tokens.c597ff81.css → lattice.tokens.e7018963.css} +13 -13
- package/static/v3/css/{lattice.views.1d326beb.css → lattice.views.22f69117.css} +93 -15
- package/static/v3/css/lattice.views.css +93 -15
- package/static/v3/js/{app.cf5bb712.js → app.c4acfdd8.js} +1 -1
- package/static/v3/js/core/{api.113660c5.js → api.12b568ad.js} +67 -0
- package/static/v3/js/core/api.js +67 -0
- package/static/v3/js/core/{components.4c83e0a9.js → components.35f02e4c.js} +8 -0
- package/static/v3/js/core/components.js +8 -0
- package/static/v3/js/core/{routes.07ad6696.js → routes.d214b399.js} +16 -12
- package/static/v3/js/core/routes.js +16 -12
- package/static/v3/js/core/{shell.9e707234.js → shell.80a6ad82.js} +37 -9
- package/static/v3/js/core/shell.js +34 -6
- package/static/v3/js/views/agents.014d0b74.js +541 -0
- package/static/v3/js/views/agents.js +305 -57
- package/static/v3/js/views/{chat.c48fd9e2.js → chat.e6dd7dd0.js} +161 -9
- package/static/v3/js/views/chat.js +161 -9
- package/static/v3/js/views/files.adad14c1.js +365 -0
- package/static/v3/js/views/files.js +212 -79
- package/static/v3/js/views/home.24f8b8ae.js +200 -0
- package/static/v3/js/views/home.js +96 -15
- package/static/v3/js/views/hooks.13845954.js +215 -0
- package/static/v3/js/views/hooks.js +117 -1
- package/static/v3/js/views/{my-computer.1b2ff621.js → my-computer.c3ef5283.js} +224 -1
- package/static/v3/js/views/my-computer.js +224 -1
- package/static/v3/js/views/{settings.c7b0cc05.js → settings.8631fa5e.js} +54 -0
- package/static/v3/js/views/settings.js +54 -0
- package/static/v3/js/views/agents.c373d48c.js +0 -293
- package/static/v3/js/views/files.8464634a.js +0 -232
- package/static/v3/js/views/home.cdde3b32.js +0 -119
- package/static/v3/js/views/hooks.f3edebca.js +0 -99
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/* ============================================================================
|
|
2
|
-
* View: Home — the workspace command center.
|
|
3
|
-
* Leads with the product identity (the retrieval lattice: Knowledge Graph +
|
|
4
|
-
* Vector Index + Hybrid Search) and routes into every primary area.
|
|
5
|
-
*
|
|
6
|
-
* View contract (shared by all views):
|
|
7
|
-
* export async function render(ctx) -> single DOM node
|
|
8
|
-
* ctx = { h, icon, api, store, c, route, params, navigate, toast }
|
|
9
|
-
* ========================================================================== */
|
|
10
|
-
|
|
11
|
-
export async function render(ctx) {
|
|
12
|
-
const { h, icon, api, store, c, navigate } = ctx;
|
|
13
|
-
const ws = store.activeWorkspace();
|
|
14
|
-
|
|
15
|
-
const root = h("div.lt3-stack-6",
|
|
16
|
-
c.viewHeader({
|
|
17
|
-
eyebrow: "Local-first AI workspace",
|
|
18
|
-
title: `Welcome to ${ws.name}`,
|
|
19
|
-
sub: "Everything you index stays on this machine. Ask questions, explore the graph, and fuse structure with semantics — no data leaves your computer.",
|
|
20
|
-
actions: [
|
|
21
|
-
h("button.lt3-btn.lt3-btn--ghost", { on: { click: () => navigate("hybrid-search") } }, icon("arrows-join"), "Hybrid search"),
|
|
22
|
-
h("button.lt3-btn.lt3-btn--primary", { on: { click: () => navigate("chat", { new: "1" }) } }, icon("message-plus"), "New chat"),
|
|
23
|
-
],
|
|
24
|
-
}),
|
|
25
|
-
buildHero(ctx),
|
|
26
|
-
h("section",
|
|
27
|
-
c.sectionHead("Retrieval lattice", h("span", { id: "home-idx-src" }, c.sourceBadge("pending"))),
|
|
28
|
-
h("div", { id: "home-pillars" }, c.loading({ lines: 2, block: true })),
|
|
29
|
-
),
|
|
30
|
-
h("section",
|
|
31
|
-
c.sectionHead("Jump back in"),
|
|
32
|
-
buildQuickGrid(ctx),
|
|
33
|
-
),
|
|
34
|
-
h("div.lt3-grid-2",
|
|
35
|
-
c.panel({ eyebrow: "Index", title: "Connected sources", children: h("div", { id: "home-sources" }, c.loading({ lines: 3 })) }),
|
|
36
|
-
c.panel({ eyebrow: "Workspace", title: "At a glance", children: h("div", { id: "home-stats" }, c.loading({ lines: 3 })) }),
|
|
37
|
-
),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
hydrate(ctx, root);
|
|
41
|
-
return root;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function buildHero({ h, icon, navigate }) {
|
|
45
|
-
return h("div.lt3-hero",
|
|
46
|
-
h("div.lt3-eyebrow.lt3-hero__eyebrow", icon("sparkles"), "Knowledge Graph · Vector Index · Hybrid Search"),
|
|
47
|
-
h("h2.lt3-hero__title", "One workspace. Three ways to recall everything."),
|
|
48
|
-
h("p.lt3-hero__sub", "Lattice builds a knowledge graph and a vector field from your files, then fuses them so every answer is grounded in both structure and meaning."),
|
|
49
|
-
h("div.lt3-hero__actions",
|
|
50
|
-
h("button.lt3-btn.lt3-btn--primary.lt3-btn--lg", { on: { click: () => navigate("knowledge-graph") } }, icon("chart-dots-3"), "Explore the graph"),
|
|
51
|
-
h("button.lt3-btn.lt3-btn--ghost.lt3-btn--lg", { on: { click: () => navigate("files") } }, icon("folder-plus"), "Connect files"),
|
|
52
|
-
),
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const QUICK = [
|
|
57
|
-
{ key: "knowledge-graph", icon: "chart-dots-3", title: "Knowledge Graph", desc: "Browse entities and relations." },
|
|
58
|
-
{ key: "hybrid-search", icon: "arrows-join", title: "Hybrid Search", desc: "Fuse graph + vector recall." },
|
|
59
|
-
{ key: "chat", icon: "message-2", title: "Chat", desc: "Grounded conversation." },
|
|
60
|
-
{ key: "files", icon: "folders", title: "Files", desc: "Sources and indexing." },
|
|
61
|
-
{ key: "pipeline", icon: "git-branch", title: "Pipeline", desc: "Ingest and embed flows." },
|
|
62
|
-
{ key: "models", icon: "cpu", title: "Models", desc: "Local MLX runtime." },
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
function buildQuickGrid({ h, icon, navigate }) {
|
|
66
|
-
return h("div.lt3-quickgrid",
|
|
67
|
-
QUICK.map((q) => h("button.lt3-quick", { style: { "text-align": "left" }, on: { click: () => navigate(q.key) } },
|
|
68
|
-
h("div.lt3-quick__icon", icon(q.icon)),
|
|
69
|
-
h("div.lt3-quick__title", q.title),
|
|
70
|
-
h("div.lt3-quick__desc", q.desc),
|
|
71
|
-
)),
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async function hydrate(ctx, root) {
|
|
76
|
-
const { h, icon, api, store, c } = ctx;
|
|
77
|
-
const numFmt = c.fmtNum;
|
|
78
|
-
|
|
79
|
-
// Index status → pillars + sources + topbar chip.
|
|
80
|
-
const idx = store.get().indexStatus
|
|
81
|
-
? { data: store.get().indexStatus, source: "live" }
|
|
82
|
-
: await api.indexStatus().then((r) => { store.setIndexStatus(r.data); return r; });
|
|
83
|
-
|
|
84
|
-
root.querySelector("#home-idx-src")?.replaceChildren(c.sourceBadge(idx.source));
|
|
85
|
-
root.querySelector("#home-pillars")?.replaceChildren(c.pillars(idx.data));
|
|
86
|
-
|
|
87
|
-
const sources = (idx.data && idx.data.sources) || [];
|
|
88
|
-
const srcHost = root.querySelector("#home-sources");
|
|
89
|
-
if (srcHost) {
|
|
90
|
-
srcHost.replaceChildren(
|
|
91
|
-
sources.length
|
|
92
|
-
? h("div.lt3-stack-3", sources.map((s) => h("div.lt3-stack-2",
|
|
93
|
-
h("div.lt3-row", { style: { "justify-content": "space-between" } },
|
|
94
|
-
h("div.lt3-row-2", icon("database"), h("b", { style: { "font-size": "var(--lt3-text-sm)" } }, s.label)),
|
|
95
|
-
c.statePill(s.state),
|
|
96
|
-
),
|
|
97
|
-
c.meter(s.progress ?? (s.state === "indexed" ? 1 : 0.5), s.state === "indexing" ? "warn" : "vector"),
|
|
98
|
-
h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, `${numFmt(s.files)} files`),
|
|
99
|
-
)))
|
|
100
|
-
: c.emptyState({ icon: "database-off", title: "No sources connected", body: "Connect a folder to start indexing." }),
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Workspace counts.
|
|
105
|
-
const os = await api.workspaceOs();
|
|
106
|
-
const counts = (os.data && os.data.counts) || {};
|
|
107
|
-
const statHost = root.querySelector("#home-stats");
|
|
108
|
-
if (statHost) {
|
|
109
|
-
statHost.replaceChildren(
|
|
110
|
-
h("div.lt3-statrow",
|
|
111
|
-
c.stat({ label: "Memories", value: numFmt(counts.memories), icon: "brain" }),
|
|
112
|
-
c.stat({ label: "Traces", value: numFmt(counts.traces), icon: "route" }),
|
|
113
|
-
c.stat({ label: "Workflows", value: numFmt(counts.workflows), icon: "git-branch" }),
|
|
114
|
-
c.stat({ label: "Skills", value: numFmt(counts.skills), icon: "puzzle" }),
|
|
115
|
-
),
|
|
116
|
-
h("div", { style: { "margin-top": "var(--lt3-space-3)" } }, c.sourceBadge(os.source)),
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/* ============================================================================
|
|
2
|
-
* View: Hooks — the lifecycle hooks registry.
|
|
3
|
-
* Reads /api/hooks (built-in + user hooks across pre_run/post_run/pre_tool/
|
|
4
|
-
* post_tool/agent/pipeline/workflow), toggles enabled state, reorders, and
|
|
5
|
-
* registers custom hooks. Built-in hooks are platform-managed and labelled.
|
|
6
|
-
* ========================================================================== */
|
|
7
|
-
|
|
8
|
-
const KIND_LABEL = {
|
|
9
|
-
pre_run: "Pre-run", post_run: "Post-run", pre_tool: "Pre-tool", post_tool: "Post-tool",
|
|
10
|
-
agent: "Agent", pipeline: "Pipeline", workflow: "Workflow",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export async function render(ctx) {
|
|
14
|
-
const { h, c } = ctx;
|
|
15
|
-
const src = h("span", c.sourceBadge("pending"));
|
|
16
|
-
const statHost = h("div.lt3-statrow", c.loading({ lines: 1 }));
|
|
17
|
-
const groupsHost = h("div", c.loading({ lines: 4, block: true }));
|
|
18
|
-
|
|
19
|
-
const nameInput = h("input.lt3-input", { type: "text", placeholder: "Hook name" });
|
|
20
|
-
const kindSelect = h("select.lt3-select", Object.keys(KIND_LABEL).map((k) => h("option", { value: k }, KIND_LABEL[k])));
|
|
21
|
-
const descInput = h("input.lt3-input", { type: "text", placeholder: "What it does (optional)" });
|
|
22
|
-
|
|
23
|
-
const root = h("div.lt3-stack-6",
|
|
24
|
-
c.viewHeader({
|
|
25
|
-
eyebrow: "Platform",
|
|
26
|
-
title: "Hooks",
|
|
27
|
-
sub: "Lifecycle extension points across runs, tools, agents, pipelines, and workflows — visible, ordered, and individually toggleable.",
|
|
28
|
-
actions: [src],
|
|
29
|
-
}),
|
|
30
|
-
statHost,
|
|
31
|
-
c.panel({
|
|
32
|
-
title: "Register a hook", sub: "Custom hooks are listed, ordered, and inspectable.",
|
|
33
|
-
children: h("div.lt3-stack-3",
|
|
34
|
-
h("div.lt3-grid-2", h("div.lt3-field", h("label", "Name"), nameInput), h("div.lt3-field", h("label", "Kind"), kindSelect)),
|
|
35
|
-
h("div.lt3-field", h("label", "Description"), descInput),
|
|
36
|
-
h("div.lt3-row-2", h("button.lt3-btn.lt3-btn--primary", { on: { click: register } }, c.icon("plus"), "Register hook")),
|
|
37
|
-
),
|
|
38
|
-
}),
|
|
39
|
-
groupsHost,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
load();
|
|
43
|
-
return root;
|
|
44
|
-
|
|
45
|
-
async function load() {
|
|
46
|
-
const res = await ctx.api.hooks();
|
|
47
|
-
src.replaceChildren(c.sourceBadge(res.source));
|
|
48
|
-
const hooks = (res.data && res.data.hooks) || [];
|
|
49
|
-
if (!hooks.length) {
|
|
50
|
-
statHost.replaceChildren(c.stat({ label: "Hooks", value: "—", icon: "webhook" }));
|
|
51
|
-
groupsHost.replaceChildren(c.emptyState({ icon: "webhook-off", title: "Hooks unavailable", body: "Start the backend to read the hooks registry." }));
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const en = hooks.filter((x) => x.enabled).length;
|
|
55
|
-
statHost.replaceChildren(
|
|
56
|
-
c.stat({ label: "Hooks", value: c.fmtNum(hooks.length), icon: "webhook" }),
|
|
57
|
-
c.stat({ label: "Enabled", value: c.fmtNum(en), icon: "circle-check" }),
|
|
58
|
-
c.stat({ label: "Kinds", value: c.fmtNum((res.data.kinds || []).length), icon: "layers" }),
|
|
59
|
-
);
|
|
60
|
-
const byKind = {};
|
|
61
|
-
for (const hk of hooks) (byKind[hk.kind] = byKind[hk.kind] || []).push(hk);
|
|
62
|
-
groupsHost.replaceChildren(h("div.lt3-stack-6", Object.keys(byKind).map((kind) =>
|
|
63
|
-
h("section", c.sectionHead(KIND_LABEL[kind] || kind, c.pill(String(byKind[kind].length))),
|
|
64
|
-
h("div.lt3-stack-2", byKind[kind].map((hk) => hookRow(ctx, hk)))))));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function hookRow(ctx2, hk) {
|
|
68
|
-
return c.card(h("div.lt3-row", { style: { "justify-content": "space-between", "align-items": "center", gap: "var(--lt3-space-3)" } },
|
|
69
|
-
h("div", { style: { "min-width": 0 } },
|
|
70
|
-
h("div.lt3-row-2", h("b", hk.name), c.pill(hk.source === "builtin" ? "built-in" : "custom", hk.source === "builtin" ? "info" : ""), hk.managed === "platform" ? c.pill("managed", "") : null),
|
|
71
|
-
h("p.lt3-muted", { style: { margin: "2px 0 0", "font-size": "var(--lt3-text-sm)" } }, hk.description || ""),
|
|
72
|
-
hk.binding ? h("div.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)", "font-family": "var(--lt3-font-mono)" } }, hk.binding) : null,
|
|
73
|
-
),
|
|
74
|
-
h("div.lt3-row-2", { style: { "flex-shrink": 0 } },
|
|
75
|
-
h("span.lt3-faint", { style: { "font-size": "var(--lt3-text-2xs)" } }, `#${hk.order}`),
|
|
76
|
-
h("button.lt3-btn.lt3-btn--subtle.lt3-btn--sm", { on: { click: () => toggle(ctx2, hk) } }, c.icon(hk.enabled ? "toggle-right" : "toggle-left"), hk.enabled ? "On" : "Off"),
|
|
77
|
-
hk.removable ? h("button.lt3-btn.lt3-btn--ghost.lt3-btn--sm", { on: { click: () => remove(ctx2, hk) } }, c.icon("trash")) : null,
|
|
78
|
-
),
|
|
79
|
-
), { flat: true });
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function toggle(ctx2, hk) {
|
|
83
|
-
const res = hk.enabled ? await ctx2.api.hookDisable(hk.id) : await ctx2.api.hookEnable(hk.id, true);
|
|
84
|
-
ctx2.toast(res && res.ok ? `${hk.name}: ${hk.enabled ? "disabled" : "enabled"}` : "Action unavailable", res && res.ok ? "ok" : "err");
|
|
85
|
-
load();
|
|
86
|
-
}
|
|
87
|
-
async function remove(ctx2, hk) {
|
|
88
|
-
const res = await ctx2.api.hookRemove(hk.id);
|
|
89
|
-
ctx2.toast(res && res.ok ? `Removed ${hk.name}` : "Remove unavailable", res && res.ok ? "ok" : "err");
|
|
90
|
-
load();
|
|
91
|
-
}
|
|
92
|
-
async function register() {
|
|
93
|
-
const name = nameInput.value.trim();
|
|
94
|
-
if (!name) { ctx.toast("Enter a hook name", "info"); return; }
|
|
95
|
-
const res = await ctx.api.hookRegister({ name, kind: kindSelect.value, description: descInput.value.trim() });
|
|
96
|
-
if (res && res.ok) { ctx.toast(`Registered ${name}`, "ok"); nameInput.value = ""; descInput.value = ""; load(); }
|
|
97
|
-
else { ctx.toast("Register unavailable", "err"); }
|
|
98
|
-
}
|
|
99
|
-
}
|