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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useQuery } from "@tanstack/react-query";
|
|
3
|
+
import { Command, Menu, Moon, Search, Sun, X } from "lucide-react";
|
|
4
|
+
import { latticeApi } from "@/api/client";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Input } from "@/components/ui/input";
|
|
7
|
+
import { useAppStore } from "@/store/appStore";
|
|
8
|
+
import { commandRoutes, go, parseHash, primaryRoutes, PrimaryRoute } from "@/routes";
|
|
9
|
+
import { BrainPage } from "@/pages/Brain";
|
|
10
|
+
import { AskPage } from "@/pages/Ask";
|
|
11
|
+
import { CapturePage } from "@/pages/Capture";
|
|
12
|
+
import { ActPage } from "@/pages/Act";
|
|
13
|
+
import { LibraryPage } from "@/pages/Library";
|
|
14
|
+
import { SystemPage } from "@/pages/System";
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
|
|
17
|
+
function useRoute() {
|
|
18
|
+
const [route, setRoute] = React.useState(parseHash);
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
const onHash = () => setRoute(parseHash());
|
|
21
|
+
window.addEventListener("hashchange", onHash);
|
|
22
|
+
return () => window.removeEventListener("hashchange", onHash);
|
|
23
|
+
}, []);
|
|
24
|
+
return route;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function Page({ primary, tab }: { primary: PrimaryRoute; tab?: string }) {
|
|
28
|
+
if (primary === "ask") return <AskPage />;
|
|
29
|
+
if (primary === "capture") return <CapturePage initialTab={tab} />;
|
|
30
|
+
if (primary === "act") return <ActPage initialTab={tab} />;
|
|
31
|
+
if (primary === "library") return <LibraryPage initialTab={tab} />;
|
|
32
|
+
if (primary === "system") return <SystemPage initialTab={tab} />;
|
|
33
|
+
return <BrainPage initialTab={tab} />;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CommandPalette({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
37
|
+
const [query, setQuery] = React.useState("");
|
|
38
|
+
const matches = commandRoutes.filter((route) => route.label.toLowerCase().includes(query.toLowerCase()) || route.key.includes(query.toLowerCase()));
|
|
39
|
+
React.useEffect(() => {
|
|
40
|
+
if (!open) return;
|
|
41
|
+
const onKey = (event: KeyboardEvent) => {
|
|
42
|
+
if (event.key === "Escape") onClose();
|
|
43
|
+
};
|
|
44
|
+
window.addEventListener("keydown", onKey);
|
|
45
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
46
|
+
}, [open, onClose]);
|
|
47
|
+
if (!open) return null;
|
|
48
|
+
return (
|
|
49
|
+
<div className="fixed inset-0 z-50 bg-background/80 p-4 backdrop-blur-sm" role="dialog" aria-modal="true">
|
|
50
|
+
<div className="mx-auto mt-16 max-w-xl rounded-lg border border-border bg-card shadow-xl">
|
|
51
|
+
<div className="flex items-center gap-2 border-b border-border p-3">
|
|
52
|
+
<Search className="h-4 w-4 text-muted-foreground" />
|
|
53
|
+
<Input value={query} onChange={(e) => setQuery(e.target.value)} autoFocus placeholder="Jump to a capability" />
|
|
54
|
+
<Button variant="ghost" size="icon" onClick={onClose}><X className="h-4 w-4" /></Button>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="max-h-96 overflow-auto p-2">
|
|
57
|
+
{matches.map((route) => {
|
|
58
|
+
const Icon = route.icon;
|
|
59
|
+
return (
|
|
60
|
+
<button
|
|
61
|
+
key={route.key}
|
|
62
|
+
onClick={() => {
|
|
63
|
+
go(route.key);
|
|
64
|
+
onClose();
|
|
65
|
+
}}
|
|
66
|
+
className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left text-sm hover:bg-muted"
|
|
67
|
+
>
|
|
68
|
+
<Icon className="h-4 w-4 text-primary" />
|
|
69
|
+
{route.label}
|
|
70
|
+
</button>
|
|
71
|
+
);
|
|
72
|
+
})}
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default function App() {
|
|
80
|
+
const route = useRoute();
|
|
81
|
+
const { theme, setTheme, mode, setMode } = useAppStore();
|
|
82
|
+
const [drawer, setDrawer] = React.useState(false);
|
|
83
|
+
const [palette, setPalette] = React.useState(false);
|
|
84
|
+
const health = useQuery({ queryKey: ["health"], queryFn: latticeApi.health });
|
|
85
|
+
const workspace = useQuery({ queryKey: ["workspaceOs"], queryFn: latticeApi.workspaceOs });
|
|
86
|
+
|
|
87
|
+
React.useEffect(() => {
|
|
88
|
+
document.documentElement.dataset.theme = theme;
|
|
89
|
+
}, [theme]);
|
|
90
|
+
React.useEffect(() => {
|
|
91
|
+
const onKey = (event: KeyboardEvent) => {
|
|
92
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
setPalette(true);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
window.addEventListener("keydown", onKey);
|
|
98
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
const rail = (
|
|
102
|
+
<aside className="flex h-full w-64 shrink-0 flex-col border-r border-border bg-card">
|
|
103
|
+
<div className="flex h-16 items-center gap-3 border-b border-border px-4">
|
|
104
|
+
<div className="grid h-9 w-9 place-items-center rounded-md bg-primary text-primary-foreground font-bold">LA</div>
|
|
105
|
+
<div>
|
|
106
|
+
<div className="font-semibold">Lattice AI</div>
|
|
107
|
+
<div className="text-xs text-muted-foreground">Digital Brain Desktop</div>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<nav className="flex-1 space-y-1 overflow-auto p-3">
|
|
111
|
+
{primaryRoutes.map((item) => {
|
|
112
|
+
const Icon = item.icon;
|
|
113
|
+
const active = route.primary === item.id;
|
|
114
|
+
return (
|
|
115
|
+
<button
|
|
116
|
+
key={item.id}
|
|
117
|
+
onClick={() => {
|
|
118
|
+
go(item.id);
|
|
119
|
+
setDrawer(false);
|
|
120
|
+
}}
|
|
121
|
+
className={cn(
|
|
122
|
+
"flex min-h-14 w-full items-center gap-3 rounded-md px-3 py-2 text-left transition",
|
|
123
|
+
active ? "bg-primary/14 text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
<Icon className="h-5 w-5" />
|
|
127
|
+
<span>
|
|
128
|
+
<span className="block text-sm font-medium">{item.label}</span>
|
|
129
|
+
<span className="block text-xs">{item.description}</span>
|
|
130
|
+
</span>
|
|
131
|
+
</button>
|
|
132
|
+
);
|
|
133
|
+
})}
|
|
134
|
+
</nav>
|
|
135
|
+
<div className="border-t border-border p-3 text-xs text-muted-foreground">
|
|
136
|
+
<div>Server: {health.data?.ok ? "online" : "unavailable"}</div>
|
|
137
|
+
<div>Workspace: {String((workspace.data?.data as Record<string, unknown>)?.active_workspace || "local")}</div>
|
|
138
|
+
</div>
|
|
139
|
+
</aside>
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div className="min-h-screen bg-background text-foreground">
|
|
144
|
+
<CommandPalette open={palette} onClose={() => setPalette(false)} />
|
|
145
|
+
<div className="hidden lg:fixed lg:inset-y-0 lg:left-0 lg:block">{rail}</div>
|
|
146
|
+
{drawer ? (
|
|
147
|
+
<div className="fixed inset-0 z-40 lg:hidden">
|
|
148
|
+
<button className="absolute inset-0 bg-background/70" aria-label="Close navigation" onClick={() => setDrawer(false)} />
|
|
149
|
+
<div className="relative h-full">{rail}</div>
|
|
150
|
+
</div>
|
|
151
|
+
) : null}
|
|
152
|
+
<div className="lg:pl-64">
|
|
153
|
+
<header className="sticky top-0 z-30 flex h-16 items-center justify-between gap-3 border-b border-border bg-background/95 px-4 backdrop-blur">
|
|
154
|
+
<div className="flex min-w-0 items-center gap-2">
|
|
155
|
+
<Button variant="ghost" size="icon" className="lg:hidden" onClick={() => setDrawer(true)}><Menu className="h-5 w-5" /></Button>
|
|
156
|
+
<div className="min-w-0">
|
|
157
|
+
<div className="truncate text-sm text-muted-foreground">v4.1.0 Release Candidate</div>
|
|
158
|
+
<div className="truncate font-medium">{primaryRoutes.find((item) => item.id === route.primary)?.label}</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>
|
|
161
|
+
<div className="flex items-center gap-2">
|
|
162
|
+
<Button variant="outline" onClick={() => setPalette(true)}><Command className="h-4 w-4" /> Search</Button>
|
|
163
|
+
<select
|
|
164
|
+
value={mode}
|
|
165
|
+
onChange={(e) => setMode(e.target.value as "basic" | "advanced" | "admin")}
|
|
166
|
+
className="h-9 rounded-md border border-border bg-background px-2 text-sm"
|
|
167
|
+
aria-label="Workspace mode"
|
|
168
|
+
>
|
|
169
|
+
<option value="basic">Basic</option>
|
|
170
|
+
<option value="advanced">Advanced</option>
|
|
171
|
+
<option value="admin">Admin</option>
|
|
172
|
+
</select>
|
|
173
|
+
<Button variant="outline" size="icon" onClick={() => setTheme(theme === "dark" ? "light" : "dark")} aria-label="Toggle theme">
|
|
174
|
+
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
|
|
175
|
+
</Button>
|
|
176
|
+
</div>
|
|
177
|
+
</header>
|
|
178
|
+
<main className="p-4 lg:p-6">
|
|
179
|
+
<Page primary={route.primary} tab={route.tab} />
|
|
180
|
+
</main>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import type { paths } from "./openapi";
|
|
3
|
+
import { useAppStore } from "@/store/appStore";
|
|
4
|
+
|
|
5
|
+
export type ApiResult<T = unknown> = {
|
|
6
|
+
ok: boolean;
|
|
7
|
+
status: number;
|
|
8
|
+
data: T;
|
|
9
|
+
source: "live" | "unavailable";
|
|
10
|
+
error?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type HttpMethod = "GET" | "POST" | "PATCH" | "DELETE";
|
|
14
|
+
type Query = Record<string, string | number | boolean | null | undefined>;
|
|
15
|
+
|
|
16
|
+
const TIMEOUT_MS = 10_000;
|
|
17
|
+
const clients = new Map<string, ReturnType<typeof createClient<paths>>>();
|
|
18
|
+
let desktopBase: Promise<string | null> | null = null;
|
|
19
|
+
|
|
20
|
+
declare global {
|
|
21
|
+
interface Window {
|
|
22
|
+
__TAURI_INTERNALS__?: unknown;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sameOriginBase() {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function tauriBackendOrigin(): Promise<string | null> {
|
|
31
|
+
if (!window.__TAURI_INTERNALS__) return null;
|
|
32
|
+
if (!desktopBase) {
|
|
33
|
+
desktopBase = import("@tauri-apps/api/core")
|
|
34
|
+
.then(({ invoke }) => invoke<string>("backend_origin"))
|
|
35
|
+
.then((origin) => origin || null)
|
|
36
|
+
.catch(() => null);
|
|
37
|
+
}
|
|
38
|
+
return desktopBase;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function apiBase() {
|
|
42
|
+
const stateBase = useAppStore.getState().apiBase;
|
|
43
|
+
if (stateBase) return stateBase;
|
|
44
|
+
const desktop = await tauriBackendOrigin();
|
|
45
|
+
if (desktop) {
|
|
46
|
+
useAppStore.getState().setApiBase(desktop);
|
|
47
|
+
return desktop;
|
|
48
|
+
}
|
|
49
|
+
return sameOriginBase();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function clientFor(baseUrl: string) {
|
|
53
|
+
if (!clients.has(baseUrl)) {
|
|
54
|
+
clients.set(baseUrl, createClient<paths>({ baseUrl, credentials: "same-origin" }));
|
|
55
|
+
}
|
|
56
|
+
return clients.get(baseUrl)!;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function emptyFor<T>(shape: T): T {
|
|
60
|
+
if (Array.isArray(shape)) return [] as T;
|
|
61
|
+
if (shape && typeof shape === "object") return { ...(shape as Record<string, unknown>) } as T;
|
|
62
|
+
return shape;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function workspaceHeaders(): Record<string, string> {
|
|
66
|
+
const workspaceId = useAppStore.getState().workspaceId;
|
|
67
|
+
return workspaceId ? { "X-Workspace-Id": workspaceId } : {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function apiJson<T>(
|
|
71
|
+
method: HttpMethod,
|
|
72
|
+
path: string,
|
|
73
|
+
opts: { body?: unknown; query?: Query; headers?: Record<string, string>; shape: T },
|
|
74
|
+
): Promise<ApiResult<T>> {
|
|
75
|
+
const base = await apiBase();
|
|
76
|
+
const client = clientFor(base);
|
|
77
|
+
const ctrl = new AbortController();
|
|
78
|
+
const timer = window.setTimeout(() => ctrl.abort(), TIMEOUT_MS);
|
|
79
|
+
try {
|
|
80
|
+
const request = {
|
|
81
|
+
body: opts.body,
|
|
82
|
+
params: { query: opts.query || {} },
|
|
83
|
+
headers: { ...workspaceHeaders(), ...(opts.headers || {}) },
|
|
84
|
+
signal: ctrl.signal,
|
|
85
|
+
} as never;
|
|
86
|
+
const call =
|
|
87
|
+
method === "GET" ? client.GET :
|
|
88
|
+
method === "POST" ? client.POST :
|
|
89
|
+
method === "PATCH" ? client.PATCH :
|
|
90
|
+
client.DELETE;
|
|
91
|
+
const result = await (call as unknown as (p: never, r: never) => Promise<{ data?: unknown; error?: unknown; response: Response }>)(path as never, request);
|
|
92
|
+
const { data, error, response } = result;
|
|
93
|
+
if (response.ok && data !== undefined) {
|
|
94
|
+
return { ok: true, status: response.status, data: data as T, source: "live" };
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
ok: false,
|
|
98
|
+
status: response.status,
|
|
99
|
+
data: emptyFor(opts.shape),
|
|
100
|
+
source: "unavailable",
|
|
101
|
+
error: error ? JSON.stringify(error) : response.statusText,
|
|
102
|
+
};
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
status: 0,
|
|
107
|
+
data: emptyFor(opts.shape),
|
|
108
|
+
source: "unavailable",
|
|
109
|
+
error: err instanceof Error ? err.message : String(err),
|
|
110
|
+
};
|
|
111
|
+
} finally {
|
|
112
|
+
window.clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function get<T>(path: string, shape: T, query?: Query) {
|
|
117
|
+
return apiJson<T>("GET", path, { query, shape });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function post<T>(path: string, body: unknown, shape: T) {
|
|
121
|
+
return apiJson<T>("POST", path, { body, shape });
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function patch<T>(path: string, body: unknown, shape: T) {
|
|
125
|
+
return apiJson<T>("PATCH", path, { body, shape });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function del<T>(path: string, shape: T) {
|
|
129
|
+
return apiJson<T>("DELETE", path, { shape });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function uploadDocument(file: File): Promise<ApiResult<Record<string, unknown> | null>> {
|
|
133
|
+
const base = await apiBase();
|
|
134
|
+
const form = new FormData();
|
|
135
|
+
form.append("file", file);
|
|
136
|
+
try {
|
|
137
|
+
const res = await fetch(`${base}/upload/document`, {
|
|
138
|
+
method: "POST",
|
|
139
|
+
credentials: "same-origin",
|
|
140
|
+
headers: { Accept: "application/json", ...workspaceHeaders() } satisfies HeadersInit,
|
|
141
|
+
body: form,
|
|
142
|
+
});
|
|
143
|
+
const data = await res.json().catch(() => null);
|
|
144
|
+
return { ok: res.ok, status: res.status, data, source: res.ok ? "live" : "unavailable" };
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return { ok: false, status: 0, data: null, source: "unavailable", error: String(err) };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export type ChatEventHandlers = {
|
|
151
|
+
onChunk?: (delta: string, fullText: string) => void;
|
|
152
|
+
onTrace?: (trace: unknown) => void;
|
|
153
|
+
signal?: AbortSignal;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
async function streamChat(body: Record<string, unknown>, handlers: ChatEventHandlers = {}) {
|
|
157
|
+
const base = await apiBase();
|
|
158
|
+
const res = await fetch(`${base}/chat`, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
credentials: "same-origin",
|
|
161
|
+
signal: handlers.signal,
|
|
162
|
+
headers: {
|
|
163
|
+
"Content-Type": "application/json",
|
|
164
|
+
Accept: "text/event-stream",
|
|
165
|
+
...workspaceHeaders(),
|
|
166
|
+
} satisfies HeadersInit,
|
|
167
|
+
body: JSON.stringify({ stream: true, max_tokens: 2048, temperature: 0.2, ...body }),
|
|
168
|
+
});
|
|
169
|
+
if (!res.ok || !res.body || !(res.headers.get("content-type") || "").includes("text/event-stream")) {
|
|
170
|
+
const payload = await res.json().catch(() => null);
|
|
171
|
+
return { source: "live", text: "", trace: null, error: payload?.error || payload?.detail || res.statusText };
|
|
172
|
+
}
|
|
173
|
+
const reader = res.body.getReader();
|
|
174
|
+
const decoder = new TextDecoder();
|
|
175
|
+
let buffer = "";
|
|
176
|
+
let text = "";
|
|
177
|
+
let trace: unknown = null;
|
|
178
|
+
for (;;) {
|
|
179
|
+
const { done, value } = await reader.read();
|
|
180
|
+
if (done) break;
|
|
181
|
+
buffer += decoder.decode(value, { stream: true });
|
|
182
|
+
const parts = buffer.split("\n\n");
|
|
183
|
+
buffer = parts.pop() || "";
|
|
184
|
+
for (const part of parts) {
|
|
185
|
+
const line = part.split("\n").find((item) => item.startsWith("data:"));
|
|
186
|
+
if (!line) continue;
|
|
187
|
+
const raw = line.slice(5).trim();
|
|
188
|
+
if (raw === "[DONE]") return { source: "live", text, trace };
|
|
189
|
+
const data = JSON.parse(raw);
|
|
190
|
+
const delta = data.chunk || data.text || "";
|
|
191
|
+
if (delta) {
|
|
192
|
+
text += delta;
|
|
193
|
+
handlers.onChunk?.(delta, text);
|
|
194
|
+
}
|
|
195
|
+
if (data.trace) {
|
|
196
|
+
trace = data.trace;
|
|
197
|
+
handlers.onTrace?.(trace);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { source: "live", text, trace };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const latticeApi = {
|
|
205
|
+
raw: get,
|
|
206
|
+
health: () => get("/health", {}),
|
|
207
|
+
workspaceOs: () => get("/workspace/os", { counts: {}, models: {}, workspace_registry: { workspaces: [] } }),
|
|
208
|
+
indexStatus: () => get("/api/index/status", {}),
|
|
209
|
+
rebuildIndex: () => post("/api/index/rebuild", { full: false, include_nodes: true, include_chunks: true }, {}),
|
|
210
|
+
graph: () => get("/knowledge-graph/graph", { nodes: [], edges: [] }),
|
|
211
|
+
graphStats: () => get("/knowledge-graph/stats", { nodes: {}, edges: {}, total_nodes: 0, total_edges: 0 }),
|
|
212
|
+
graphPortability: () => get("/api/knowledge-graph/portability", {}),
|
|
213
|
+
brainStorage: () => get("/api/brain/storage", {}),
|
|
214
|
+
dockerPostgres: (body: { consent: boolean; dry_run?: boolean; port?: number }) => post("/api/brain/storage/postgres/docker", body, {}),
|
|
215
|
+
migratePostgres: (body: { dsn: string; schema_name?: string; dry_run?: boolean }) => post("/api/brain/storage/migrate-postgres", body, {}),
|
|
216
|
+
graphProvenance: (limit = 50) => get("/api/knowledge-graph/provenance", { items: [] }, { limit }),
|
|
217
|
+
graphCoverage: () => get("/knowledge-graph/provenance/coverage", {}),
|
|
218
|
+
graphExport: () => post("/api/knowledge-graph/export", {}, {}),
|
|
219
|
+
graphBackup: () => post("/api/knowledge-graph/backup", {}, {}),
|
|
220
|
+
graphImport: (artifact: unknown, dry_run = true) => post("/api/knowledge-graph/import", { artifact, mode: "merge", dry_run }, {}),
|
|
221
|
+
hybridSearch: async (query: string, weights?: unknown) => {
|
|
222
|
+
const res = await post<Record<string, unknown>>("/api/search/hybrid", { query, ...(weights ? { weights } : {}) }, { matches: [] });
|
|
223
|
+
const data = res.data as Record<string, unknown>;
|
|
224
|
+
if (res.ok && !Array.isArray(data.matches) && Array.isArray(data.results)) {
|
|
225
|
+
return { ...res, data: { ...data, matches: data.results } };
|
|
226
|
+
}
|
|
227
|
+
return res;
|
|
228
|
+
},
|
|
229
|
+
browserReadUrl: (url: string) => post("/api/browser/read-url", { url }, {}),
|
|
230
|
+
memoryManager: () => get("/api/memory/manager", { sources: [], tiers: [], usage: {} }),
|
|
231
|
+
memoryRecall: (query: string, limit = 20) => post("/api/memory/recall", { query, limit }, { matches: [] }),
|
|
232
|
+
memoryCompact: () => post("/api/memory/compact", {}, {}),
|
|
233
|
+
memoryRebuild: () => post("/api/memory/rebuild", { target: "vector" }, {}),
|
|
234
|
+
chatHistory: () => get("/history/conversations", []),
|
|
235
|
+
conversation: (id: string) => get(`/history/conversations/${encodeURIComponent(id)}`, { messages: [] }),
|
|
236
|
+
deleteConversation: (id: string) => del(`/history/conversations/${encodeURIComponent(id)}`, {}),
|
|
237
|
+
streamChat,
|
|
238
|
+
uploadDocument,
|
|
239
|
+
documents: (limit = 200) => get("/knowledge-graph/documents", { documents: [] }, { limit }),
|
|
240
|
+
localSources: () => get("/knowledge-graph/local/sources", { sources: [], watch: { available: false, active: {} } }),
|
|
241
|
+
localAgent: () => get("/api/local-agent/status", { agent: { online: false }, sources: [] }),
|
|
242
|
+
connectFolder: (path: string) => post("/knowledge-graph/local/index", { path, approved: true, watch_enabled: true, consent: { approved: true, source: "desktop-spa" } }, {}),
|
|
243
|
+
localWatchStop: (source_id: string) => post("/knowledge-graph/local/watch/stop", { source_id }, {}),
|
|
244
|
+
models: () => get("/models", { catalog: [], loaded: [], recommended: [] }),
|
|
245
|
+
loadModel: (model_id: string, engine?: string) => post("/models/load", { model_id, engine: engine || null }, {}),
|
|
246
|
+
unloadModel: (model_id: string) => del(`/models/unload/${encodeURIComponent(model_id)}`, {}),
|
|
247
|
+
embeddingsStatus: () => get("/api/embeddings/status", {}),
|
|
248
|
+
agentRuntime: () => get("/agents/api/runtime/status", { runtime: {}, agents: [], runs: [] }),
|
|
249
|
+
runAgent: (goal: string, roles: string[]) => post("/agents/api/run", { goal, roles }, {}),
|
|
250
|
+
agentRun: (id: string) => get(`/agents/api/runs/${encodeURIComponent(id)}`, {}),
|
|
251
|
+
stopAgentRun: (id: string) => post(`/agents/api/runs/${encodeURIComponent(id)}/stop`, {}, {}),
|
|
252
|
+
agentRegistry: () => get("/agents/api/registry", { agents: [] }),
|
|
253
|
+
agentCapabilities: () => get("/agents/api/registry/capabilities", { capabilities: {} }),
|
|
254
|
+
registerAgent: (body: unknown) => post("/agents/api/registry", body, {}),
|
|
255
|
+
updateAgent: (id: string, body: unknown) => patch(`/agents/api/registry/${encodeURIComponent(id)}`, body, {}),
|
|
256
|
+
removeAgent: (id: string) => del(`/agents/api/registry/${encodeURIComponent(id)}`, {}),
|
|
257
|
+
workflowDefinitions: () => get("/workflows/api/definitions", { workflows: [] }),
|
|
258
|
+
workflowRuns: () => get("/workflows/api/runs", { runs: [] }),
|
|
259
|
+
workflowTriggers: () => get("/workflows/api/triggers", { armed: [] }),
|
|
260
|
+
runWorkflow: (id: string) => post(`/workflows/api/definitions/${encodeURIComponent(id)}/run`, {}, {}),
|
|
261
|
+
updateWorkflow: (id: string, body: unknown) => patch(`/workflows/api/definitions/${encodeURIComponent(id)}`, body, {}),
|
|
262
|
+
stopWorkflowRun: (id: string) => post(`/workflows/api/runs/${encodeURIComponent(id)}/stop`, {}, {}),
|
|
263
|
+
resumeWorkflowRun: (id: string, approved: boolean) => post(`/workflows/api/runs/${encodeURIComponent(id)}/resume`, { approved }, {}),
|
|
264
|
+
hooks: () => get("/api/hooks", { hooks: [] }),
|
|
265
|
+
hookRuns: () => get("/api/hooks/runs", { runs: [] }, { limit: 50 }),
|
|
266
|
+
hookRun: (body: unknown) => post("/api/hooks/run", body, {}),
|
|
267
|
+
permissionsPending: () => get("/permissions/pending", { pending: {}, count: 0 }),
|
|
268
|
+
approvePermission: (token: string) => post(`/permissions/approve/${encodeURIComponent(token)}`, {}, {}),
|
|
269
|
+
denyPermission: (token: string) => post(`/permissions/deny/${encodeURIComponent(token)}`, {}, {}),
|
|
270
|
+
skills: () => get("/workspace/skills", { skills: [] }),
|
|
271
|
+
skillToggle: (skill: string, enabled: boolean) => post(enabled ? "/workspace/skills/disable" : "/workspace/skills/enable", { skill }, {}),
|
|
272
|
+
skillsMarketplace: () => get("/skills/marketplace", { skills: [] }),
|
|
273
|
+
skillInstall: (skill: string, plugin?: string) => post("/workspace/skills/install", { skill, plugin: plugin || "" }, {}),
|
|
274
|
+
mcpTools: () => get("/mcp/tools", { tools: [], installed_mcps: [] }),
|
|
275
|
+
mcpRecommend: (query: string) => post("/mcp/recommend", { query, limit: 6 }, {}),
|
|
276
|
+
templates: () => get("/marketplace/templates", { templates: [], kinds: [] }),
|
|
277
|
+
installTemplate: (data: unknown) => post("/marketplace/templates/install", { data }, {}),
|
|
278
|
+
pluginsRegistry: () => get("/plugins/registry", { plugins: [] }),
|
|
279
|
+
pluginsDirectory: () => get("/plugins/directory", { plugins: [] }),
|
|
280
|
+
profile: () => get("/account/profile", {}),
|
|
281
|
+
login: (email: string, password: string) => post("/login", { email, password }, {}),
|
|
282
|
+
register: (body: unknown) => post("/register", body, {}),
|
|
283
|
+
logout: () => post("/logout", {}, {}),
|
|
284
|
+
updateProfile: (body: unknown) => patch("/account/profile", body, {}),
|
|
285
|
+
changePassword: (current_password: string, new_password: string) => post("/account/change-password", { current_password, new_password }, {}),
|
|
286
|
+
ssoConfig: () => get("/auth/sso/config", { enabled: false, providers: [] }),
|
|
287
|
+
workspaceRegistry: () => get("/workspace/registry", { workspaces: [] }),
|
|
288
|
+
createOrg: (name: string) => post("/workspace/orgs", { name }, {}),
|
|
289
|
+
activateWorkspace: (workspace_id: string) => post("/workspace/activate", { workspace_id }, {}),
|
|
290
|
+
archiveWorkspace: (workspace_id: string) => post(`/workspace/orgs/${encodeURIComponent(workspace_id)}/archive`, {}, {}),
|
|
291
|
+
addWorkspaceMember: (workspace_id: string, user_id: string, role: string) => post(`/workspace/orgs/${encodeURIComponent(workspace_id)}/members`, { user_id, role }, {}),
|
|
292
|
+
removeWorkspaceMember: (workspace_id: string, user_id: string) => del(`/workspace/orgs/${encodeURIComponent(workspace_id)}/members/${encodeURIComponent(user_id)}`, {}),
|
|
293
|
+
invitations: () => get("/invitations", { invitations: [] }),
|
|
294
|
+
createInvitation: (body: unknown) => post("/invitations", body, {}),
|
|
295
|
+
acceptInvitation: (token: string) => post(`/invitations/${encodeURIComponent(token)}/accept`, {}, {}),
|
|
296
|
+
snapshots: () => get("/workspace/snapshots", { snapshots: [] }),
|
|
297
|
+
createSnapshot: (name: string) => post("/workspace/snapshots", { name }, {}),
|
|
298
|
+
compareSnapshots: (before_id: string, after_id: string) => post("/workspace/snapshots/compare", { before_id, after_id }, {}),
|
|
299
|
+
restoreSnapshot: (id: string) => post(`/workspace/snapshots/${encodeURIComponent(id)}/restore`, {}, {}),
|
|
300
|
+
exportSnapshot: (id: string) => post(`/workspace/snapshots/${encodeURIComponent(id)}/export`, {}, {}),
|
|
301
|
+
timeMachine: () => get("/workspace/time-machine", { events: [] }, { limit: 100 }),
|
|
302
|
+
realtimeFeed: () => get("/realtime/feed", { events: [] }, { limit: 80 }),
|
|
303
|
+
presence: () => get("/realtime/presence", { presence: [] }),
|
|
304
|
+
networkIdentity: () => get("/network/identity", {}),
|
|
305
|
+
networkPeers: () => get("/network/peers", { peers: [] }),
|
|
306
|
+
pairPeer: (body: unknown) => post("/network/peers", body, {}),
|
|
307
|
+
unpairPeer: (id: string) => del(`/network/peers/${encodeURIComponent(id)}`, {}),
|
|
308
|
+
pushPeer: (id: string, workspace_id?: string | null) => post(`/network/push/${encodeURIComponent(id)}`, { workspace_id }, {}),
|
|
309
|
+
sysinfo: () => get("/local/sysinfo", {}),
|
|
310
|
+
computerMemory: () => get("/workspace/computer-memory", {}),
|
|
311
|
+
setComputerMemory: (enabled: boolean) => post("/workspace/computer-memory", { enabled, consent: { approved: enabled } }, {}),
|
|
312
|
+
adminSummary: () => get("/admin/summary", {}),
|
|
313
|
+
adminUsers: () => get("/admin/users", []),
|
|
314
|
+
adminAudit: () => get("/admin/audit", { recent_events: [] }),
|
|
315
|
+
adminRoles: () => get("/admin/roles", { roles: [] }),
|
|
316
|
+
adminPolicies: () => get("/admin/policies", { policies: [] }),
|
|
317
|
+
adminSecurity: () => get("/admin/security/overview", {}),
|
|
318
|
+
vpcStatus: () => get("/vpc/status", {}),
|
|
319
|
+
toolPermissions: () => get("/tools/permissions", { permissions: [] }),
|
|
320
|
+
};
|