ltcai 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -23
- package/desktop/electron/main.cjs +44 -0
- package/docs/CHANGELOG.md +42 -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_DIGITAL_BRAIN_RECOVERY.md +26 -19
- package/frontend/index.html +24 -0
- package/frontend/openapi.json +14190 -0
- package/frontend/src/App.tsx +184 -0
- package/frontend/src/api/client.ts +317 -0
- package/frontend/src/api/openapi.ts +16637 -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 +344 -0
- package/frontend/src/routes.ts +85 -0
- package/frontend/src/store/appStore.ts +54 -0
- package/frontend/src/styles.css +107 -0
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/setup.py +5 -4
- package/latticeai/api/static_routes.py +4 -4
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/multi_agent.py +1 -1
- package/latticeai/core/workspace_os.py +1 -1
- package/package.json +54 -15
- package/scripts/build_frontend_assets.mjs +38 -0
- package/scripts/bump_version.py +1 -1
- package/scripts/export_openapi.py +31 -0
- package/scripts/lint_frontend.mjs +86 -0
- package/scripts/run_python.mjs +47 -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 +36 -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-CJRAzNnf.js +333 -0
- package/static/app/assets/index-CJRAzNnf.js.map +1 -0
- package/static/app/assets/index-CSwBBgf4.css +2 -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,54 @@
|
|
|
1
|
+
import { create } from "zustand";
|
|
2
|
+
|
|
3
|
+
export type Theme = "dark" | "light";
|
|
4
|
+
export type WorkspaceMode = "basic" | "advanced" | "admin";
|
|
5
|
+
|
|
6
|
+
type AppState = {
|
|
7
|
+
theme: Theme;
|
|
8
|
+
mode: WorkspaceMode;
|
|
9
|
+
workspaceId: string | null;
|
|
10
|
+
apiBase: string | null;
|
|
11
|
+
setTheme: (theme: Theme) => void;
|
|
12
|
+
setMode: (mode: WorkspaceMode) => void;
|
|
13
|
+
setWorkspaceId: (workspaceId: string | null) => void;
|
|
14
|
+
setApiBase: (apiBase: string | null) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function readTheme(): Theme {
|
|
18
|
+
try {
|
|
19
|
+
const saved = localStorage.getItem("lattice.theme");
|
|
20
|
+
if (saved === "light" || saved === "dark") return saved;
|
|
21
|
+
} catch {}
|
|
22
|
+
return "dark";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readMode(): WorkspaceMode {
|
|
26
|
+
try {
|
|
27
|
+
const saved = localStorage.getItem("lattice.mode");
|
|
28
|
+
if (saved === "basic" || saved === "advanced" || saved === "admin") return saved;
|
|
29
|
+
} catch {}
|
|
30
|
+
return "basic";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const useAppStore = create<AppState>((set) => ({
|
|
34
|
+
theme: readTheme(),
|
|
35
|
+
mode: readMode(),
|
|
36
|
+
workspaceId: null,
|
|
37
|
+
apiBase: null,
|
|
38
|
+
setTheme: (theme) => {
|
|
39
|
+
document.documentElement.dataset.theme = theme;
|
|
40
|
+
try { localStorage.setItem("lattice.theme", theme); } catch {}
|
|
41
|
+
set({ theme });
|
|
42
|
+
},
|
|
43
|
+
setMode: (mode) => {
|
|
44
|
+
try { localStorage.setItem("lattice.mode", mode); } catch {}
|
|
45
|
+
set({ mode });
|
|
46
|
+
},
|
|
47
|
+
setWorkspaceId: (workspaceId) => {
|
|
48
|
+
if (workspaceId) {
|
|
49
|
+
try { localStorage.setItem("lattice.workspace", workspaceId); } catch {}
|
|
50
|
+
}
|
|
51
|
+
set({ workspaceId });
|
|
52
|
+
},
|
|
53
|
+
setApiBase: (apiBase) => set({ apiBase }),
|
|
54
|
+
}));
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "reactflow/dist/style.css";
|
|
3
|
+
|
|
4
|
+
:root {
|
|
5
|
+
color-scheme: dark;
|
|
6
|
+
--background: 220 18% 8%;
|
|
7
|
+
--foreground: 210 30% 96%;
|
|
8
|
+
--card: 220 16% 11%;
|
|
9
|
+
--card-foreground: 210 30% 96%;
|
|
10
|
+
--muted: 218 13% 18%;
|
|
11
|
+
--muted-foreground: 215 14% 66%;
|
|
12
|
+
--primary: 176 68% 48%;
|
|
13
|
+
--primary-foreground: 212 26% 8%;
|
|
14
|
+
--secondary: 221 14% 20%;
|
|
15
|
+
--secondary-foreground: 210 30% 96%;
|
|
16
|
+
--destructive: 355 78% 58%;
|
|
17
|
+
--destructive-foreground: 0 0% 100%;
|
|
18
|
+
--border: 218 13% 23%;
|
|
19
|
+
--input: 218 13% 24%;
|
|
20
|
+
--ring: 176 68% 48%;
|
|
21
|
+
--brain: 176 68% 48%;
|
|
22
|
+
--ask: 216 78% 66%;
|
|
23
|
+
--capture: 142 58% 56%;
|
|
24
|
+
--act: 33 92% 58%;
|
|
25
|
+
--library: 262 68% 70%;
|
|
26
|
+
--system: 350 72% 68%;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
:root[data-theme="light"] {
|
|
30
|
+
color-scheme: light;
|
|
31
|
+
--background: 210 30% 98%;
|
|
32
|
+
--foreground: 222 38% 9%;
|
|
33
|
+
--card: 0 0% 100%;
|
|
34
|
+
--card-foreground: 222 38% 9%;
|
|
35
|
+
--muted: 213 24% 92%;
|
|
36
|
+
--muted-foreground: 219 11% 38%;
|
|
37
|
+
--primary: 178 72% 32%;
|
|
38
|
+
--primary-foreground: 0 0% 100%;
|
|
39
|
+
--secondary: 211 24% 90%;
|
|
40
|
+
--secondary-foreground: 222 38% 9%;
|
|
41
|
+
--destructive: 355 72% 48%;
|
|
42
|
+
--destructive-foreground: 0 0% 100%;
|
|
43
|
+
--border: 214 20% 83%;
|
|
44
|
+
--input: 214 20% 78%;
|
|
45
|
+
--ring: 178 72% 32%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@theme inline {
|
|
49
|
+
--color-background: hsl(var(--background));
|
|
50
|
+
--color-foreground: hsl(var(--foreground));
|
|
51
|
+
--color-card: hsl(var(--card));
|
|
52
|
+
--color-card-foreground: hsl(var(--card-foreground));
|
|
53
|
+
--color-muted: hsl(var(--muted));
|
|
54
|
+
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
55
|
+
--color-primary: hsl(var(--primary));
|
|
56
|
+
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
57
|
+
--color-secondary: hsl(var(--secondary));
|
|
58
|
+
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
59
|
+
--color-destructive: hsl(var(--destructive));
|
|
60
|
+
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
61
|
+
--color-border: hsl(var(--border));
|
|
62
|
+
--color-input: hsl(var(--input));
|
|
63
|
+
--color-ring: hsl(var(--ring));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
* {
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
body {
|
|
71
|
+
min-width: 320px;
|
|
72
|
+
margin: 0;
|
|
73
|
+
background: hsl(var(--background));
|
|
74
|
+
color: hsl(var(--foreground));
|
|
75
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
button,
|
|
79
|
+
input,
|
|
80
|
+
textarea,
|
|
81
|
+
select {
|
|
82
|
+
font: inherit;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.brain-grid {
|
|
86
|
+
background-image:
|
|
87
|
+
linear-gradient(hsl(var(--border) / 0.34) 1px, transparent 1px),
|
|
88
|
+
linear-gradient(90deg, hsl(var(--border) / 0.34) 1px, transparent 1px);
|
|
89
|
+
background-size: 32px 32px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.scrollbar-thin {
|
|
93
|
+
scrollbar-width: thin;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.react-flow__node {
|
|
97
|
+
border: 1px solid hsl(var(--border));
|
|
98
|
+
border-radius: 8px;
|
|
99
|
+
background: hsl(var(--card));
|
|
100
|
+
color: hsl(var(--foreground));
|
|
101
|
+
padding: 8px 10px;
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.react-flow__edge-path {
|
|
106
|
+
stroke: hsl(var(--primary));
|
|
107
|
+
}
|
package/latticeai/__init__.py
CHANGED
package/latticeai/api/setup.py
CHANGED
|
@@ -19,15 +19,16 @@ from latticeai.models.router import parse_model_ref
|
|
|
19
19
|
from setup_wizard import get_recommendations, install_stream, open_url, scan_environment
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
class SetupInstallRequest(BaseModel):
|
|
23
|
+
items: List[Dict]
|
|
24
|
+
|
|
25
|
+
|
|
22
26
|
def create_setup_router(*, model_router, require_user) -> APIRouter:
|
|
23
27
|
api_router = APIRouter()
|
|
24
28
|
router = model_router
|
|
25
29
|
|
|
26
30
|
# ── Setup Wizard ─────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
class SetupInstallRequest(BaseModel):
|
|
29
|
-
items: List[Dict]
|
|
30
|
-
|
|
31
|
+
|
|
31
32
|
def setup_auto_state() -> Dict[str, object]:
|
|
32
33
|
"""Return the PPT-aligned zero-config setup state used by setup UI/API."""
|
|
33
34
|
profile = auto_setup_probe()
|
|
@@ -84,7 +84,7 @@ def create_static_routes_router(
|
|
|
84
84
|
raise HTTPException(status_code=404)
|
|
85
85
|
return FileResponse(str(p), media_type="application/manifest+json")
|
|
86
86
|
|
|
87
|
-
@api_router.api_route("/favicon.ico", methods=["GET", "HEAD"])
|
|
87
|
+
@api_router.api_route("/favicon.ico", methods=["GET", "HEAD"], include_in_schema=False)
|
|
88
88
|
async def favicon():
|
|
89
89
|
ico = STATIC_DIR / "favicon.ico"
|
|
90
90
|
png = STATIC_DIR / "icons" / "favicon-32.png"
|
|
@@ -112,10 +112,10 @@ def create_static_routes_router(
|
|
|
112
112
|
|
|
113
113
|
@api_router.get("/app")
|
|
114
114
|
async def app_shell(request: Request):
|
|
115
|
-
"""
|
|
116
|
-
page = STATIC_DIR / "
|
|
115
|
+
"""React desktop single-page workspace shell."""
|
|
116
|
+
page = STATIC_DIR / "app" / "index.html"
|
|
117
117
|
if not page.exists():
|
|
118
|
-
raise HTTPException(status_code=404, detail="
|
|
118
|
+
raise HTTPException(status_code=404, detail="React shell not found.")
|
|
119
119
|
return ui_file_response(page)
|
|
120
120
|
|
|
121
121
|
|
|
@@ -14,7 +14,7 @@ from datetime import datetime
|
|
|
14
14
|
from typing import Any, Callable, Dict, List, Optional
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
MULTI_AGENT_VERSION = "4.0
|
|
17
|
+
MULTI_AGENT_VERSION = "4.1.0"
|
|
18
18
|
|
|
19
19
|
AGENT_ROLES = ("researcher", "planner", "executor", "reviewer", "release")
|
|
20
20
|
CORE_PIPELINE = ("planner", "executor", "reviewer")
|
|
@@ -19,7 +19,7 @@ from pathlib import Path
|
|
|
19
19
|
from typing import Any, Callable, Dict, Iterable, List, Optional
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
WORKSPACE_OS_VERSION = "4.0
|
|
22
|
+
WORKSPACE_OS_VERSION = "4.1.0"
|
|
23
23
|
|
|
24
24
|
# Workspace types separate single-user Personal workspaces from shared
|
|
25
25
|
# Organization workspaces. Both keep the same local-first JSON store; the type
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ltcai",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Lattice AI — local-first Digital Brain Platform (knowledge graph, durable memory, hybrid search, agents, signed brain exchange)",
|
|
5
5
|
"homepage": "https://github.com/TaeSooPark-PTS/LatticeAI#readme",
|
|
6
6
|
"repository": {
|
|
@@ -18,29 +18,36 @@
|
|
|
18
18
|
"start": "LTCAI",
|
|
19
19
|
"dev": "python3 ltcai_cli.py --reload",
|
|
20
20
|
"build": "npm run build:assets && npm run build:python",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
21
|
+
"frontend:dev": "vite --host 127.0.0.1",
|
|
22
|
+
"frontend:openapi": "node scripts/run_python.mjs scripts/export_openapi.py frontend/openapi.json && npx openapi-typescript frontend/openapi.json -o frontend/src/api/openapi.ts",
|
|
23
|
+
"build:assets": "vite build && node scripts/build_frontend_assets.mjs",
|
|
24
|
+
"build:python": "node scripts/run_python.mjs -m build",
|
|
25
|
+
"check:python": "node scripts/run_python.mjs scripts/check_python.py",
|
|
26
|
+
"lint": "node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:frontend",
|
|
27
|
+
"lint:frontend": "node scripts/lint_frontend.mjs",
|
|
28
|
+
"typecheck": "npm run typecheck:frontend && cd vscode-extension && npm run build",
|
|
29
|
+
"typecheck:frontend": "npx tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test": "node scripts/run_python.mjs -m pytest tests/ -v",
|
|
31
|
+
"test:unit": "node scripts/run_python.mjs -m pytest tests/unit/ -v",
|
|
32
|
+
"test:integration": "node scripts/run_python.mjs -m pytest tests/integration/ -v",
|
|
30
33
|
"test:visual": "playwright test",
|
|
31
34
|
"capture:workspace": "node scripts/capture/capture_workspace.js",
|
|
32
35
|
"capture:graph": "node scripts/capture/capture_graph.js",
|
|
33
36
|
"capture:skills": "node scripts/capture/capture_skills.js",
|
|
34
37
|
"capture:enterprise": "node scripts/capture/capture_enterprise.js",
|
|
35
38
|
"capture:onboarding": "node scripts/capture/capture_onboarding.js",
|
|
39
|
+
"desktop:tauri": "tauri dev",
|
|
40
|
+
"desktop:tauri:build": "tauri build",
|
|
41
|
+
"desktop:tauri:check": "cd src-tauri && cargo check",
|
|
42
|
+
"desktop:electron": "electron desktop/electron/main.cjs",
|
|
36
43
|
"package:vsix": "node scripts/build_vsix.mjs",
|
|
37
44
|
"release:artifacts": "npm run build:assets && npm run build:python && npm pack && npm run package:vsix",
|
|
38
|
-
"release:validate": "
|
|
45
|
+
"release:validate": "node scripts/run_python.mjs scripts/validate_release_artifacts.py $npm_package_version --require-vsix --require-tgz",
|
|
39
46
|
"publish:npm": "npm pack && npm publish ltcai-$npm_package_version.tgz --access public",
|
|
40
|
-
"publish:pypi": "npm run build:python &&
|
|
47
|
+
"publish:pypi": "npm run build:python && node scripts/run_python.mjs -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl",
|
|
41
48
|
"publish:vscode": "cd vscode-extension && npm run package:vsix && npm run publish:vscode",
|
|
42
49
|
"publish:openvsx": "cd vscode-extension && npm run package:vsix && npm run publish:openvsx",
|
|
43
|
-
"publish:all": "npm run release:artifacts && npm run release:validate && npm publish ltcai-$npm_package_version.tgz --access public &&
|
|
50
|
+
"publish:all": "npm run release:artifacts && npm run release:validate && npm publish ltcai-$npm_package_version.tgz --access public && node scripts/run_python.mjs -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl && cd vscode-extension && npm run publish:vscode && npm run publish:openvsx"
|
|
44
51
|
},
|
|
45
52
|
"keywords": [
|
|
46
53
|
"ltcai",
|
|
@@ -78,7 +85,8 @@
|
|
|
78
85
|
"static/manifest.json",
|
|
79
86
|
"static/sw.js",
|
|
80
87
|
"static/css/",
|
|
81
|
-
"
|
|
88
|
+
"frontend/",
|
|
89
|
+
"static/app/",
|
|
82
90
|
"static/icons/",
|
|
83
91
|
"plugins/",
|
|
84
92
|
"scripts/",
|
|
@@ -89,6 +97,12 @@
|
|
|
89
97
|
"setup_wizard.py",
|
|
90
98
|
"knowledge_graph_api.py",
|
|
91
99
|
"static/vendor/",
|
|
100
|
+
"src-tauri/",
|
|
101
|
+
"!src-tauri/gen/",
|
|
102
|
+
"!src-tauri/gen/**",
|
|
103
|
+
"!src-tauri/target/",
|
|
104
|
+
"!src-tauri/target/**",
|
|
105
|
+
"desktop/electron/",
|
|
92
106
|
"docs/*.md",
|
|
93
107
|
"!docs/assets/",
|
|
94
108
|
"!docs/images/"
|
|
@@ -97,6 +111,31 @@
|
|
|
97
111
|
"access": "public"
|
|
98
112
|
},
|
|
99
113
|
"devDependencies": {
|
|
100
|
-
"@playwright/test": "^1.60.0"
|
|
114
|
+
"@playwright/test": "^1.60.0",
|
|
115
|
+
"@tailwindcss/postcss": "^4.3.0",
|
|
116
|
+
"@tanstack/react-query": "^5.101.0",
|
|
117
|
+
"@tauri-apps/api": "^2.0.0",
|
|
118
|
+
"@tauri-apps/cli": "^2.0.0",
|
|
119
|
+
"@types/cytoscape": "^3.21.9",
|
|
120
|
+
"@types/react": "^19.2.17",
|
|
121
|
+
"@types/react-dom": "^19.2.3",
|
|
122
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
123
|
+
"autoprefixer": "^10.5.0",
|
|
124
|
+
"class-variance-authority": "^0.7.1",
|
|
125
|
+
"clsx": "^2.1.1",
|
|
126
|
+
"cytoscape": "^3.34.0",
|
|
127
|
+
"electron": "^42.4.0",
|
|
128
|
+
"lucide-react": "^1.17.0",
|
|
129
|
+
"openapi-fetch": "^0.17.0",
|
|
130
|
+
"openapi-typescript": "^7.13.0",
|
|
131
|
+
"postcss": "^8.5.15",
|
|
132
|
+
"react": "^19.2.7",
|
|
133
|
+
"react-dom": "^19.2.7",
|
|
134
|
+
"reactflow": "^11.11.4",
|
|
135
|
+
"tailwind-merge": "^3.6.0",
|
|
136
|
+
"tailwindcss": "^4.3.0",
|
|
137
|
+
"typescript": "^5.9.3",
|
|
138
|
+
"vite": "^8.0.16",
|
|
139
|
+
"zustand": "^5.0.14"
|
|
101
140
|
}
|
|
102
141
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const repo = join(import.meta.dirname, "..");
|
|
6
|
+
const appDir = join(repo, "static", "app");
|
|
7
|
+
const nestedViteManifest = join(appDir, ".vite", "asset-manifest.json");
|
|
8
|
+
const publicManifest = join(appDir, "asset-manifest.json");
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(repo, "package.json"), "utf8"));
|
|
10
|
+
|
|
11
|
+
const viteManifest = existsSync(nestedViteManifest) ? nestedViteManifest : publicManifest;
|
|
12
|
+
if (!existsSync(viteManifest)) {
|
|
13
|
+
console.error("Vite manifest missing. Run `vite build` before build_frontend_assets.mjs.");
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const raw = JSON.parse(readFileSync(viteManifest, "utf8"));
|
|
18
|
+
const assets = {};
|
|
19
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
20
|
+
if (value && typeof value === "object") {
|
|
21
|
+
if (value.file) assets[key] = `/static/app/${value.file}`;
|
|
22
|
+
for (const css of value.css || []) assets[css] = `/static/app/${css}`;
|
|
23
|
+
for (const asset of value.assets || []) assets[asset] = `/static/app/${asset}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const manifest = {
|
|
28
|
+
version: pkg.version,
|
|
29
|
+
generated_at: "vite",
|
|
30
|
+
entrypoints: {
|
|
31
|
+
app: assets["frontend/index.html"] || "/static/app/index.html",
|
|
32
|
+
},
|
|
33
|
+
assets,
|
|
34
|
+
vite: raw,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
writeFileSync(publicManifest, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
38
|
+
console.log(`wrote static/app/asset-manifest.json with ${Object.keys(assets).length} assets`);
|
package/scripts/bump_version.py
CHANGED
|
@@ -31,7 +31,7 @@ TARGETS = [
|
|
|
31
31
|
("package-lock.json", "package-lock", None),
|
|
32
32
|
("vscode-extension/package.json", "json", "version"),
|
|
33
33
|
("vscode-extension/package-lock.json", "package-lock", None),
|
|
34
|
-
("static/
|
|
34
|
+
("static/app/asset-manifest.json", "json", "version"),
|
|
35
35
|
]
|
|
36
36
|
|
|
37
37
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Export the FastAPI OpenAPI schema used by the desktop frontend client."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
REPO = Path(__file__).resolve().parents[1]
|
|
12
|
+
sys.path.insert(0, str(REPO))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> int:
|
|
16
|
+
from latticeai.app_factory import create_app
|
|
17
|
+
|
|
18
|
+
target = Path(sys.argv[1] if len(sys.argv) > 1 else "frontend/openapi.json")
|
|
19
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
20
|
+
os.environ.setdefault("LATTICEAI_REQUIRE_AUTH", "false")
|
|
21
|
+
os.environ.setdefault("LATTICEAI_TUNNEL", "false")
|
|
22
|
+
os.environ.setdefault("LATTICEAI_AUTOLOAD_MODELS", "false")
|
|
23
|
+
app = create_app()
|
|
24
|
+
schema = app.openapi()
|
|
25
|
+
target.write_text(json.dumps(schema, sort_keys=True, indent=2) + "\n", encoding="utf-8")
|
|
26
|
+
print(f"wrote {target} with {len(schema.get('paths', {}))} paths")
|
|
27
|
+
return 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const repo = join(import.meta.dirname, "..");
|
|
7
|
+
const frontend = join(repo, "frontend");
|
|
8
|
+
const staticRoot = join(repo, "static");
|
|
9
|
+
let failures = 0;
|
|
10
|
+
|
|
11
|
+
function fail(message) {
|
|
12
|
+
failures += 1;
|
|
13
|
+
console.error(`FAIL ${message}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function walk(dir, exts) {
|
|
17
|
+
if (!existsSync(dir)) return [];
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const name of readdirSync(dir)) {
|
|
20
|
+
const path = join(dir, name);
|
|
21
|
+
const stat = statSync(path);
|
|
22
|
+
if (stat.isDirectory()) {
|
|
23
|
+
if (name === "node_modules") continue;
|
|
24
|
+
out.push(...walk(path, exts));
|
|
25
|
+
} else if (exts.some((ext) => name.endsWith(ext))) {
|
|
26
|
+
out.push(path);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tsc = spawnSync("npx", ["tsc", "-p", "tsconfig.json", "--noEmit"], { cwd: repo, encoding: "utf8" });
|
|
33
|
+
if (tsc.status !== 0) fail(`frontend typecheck\n${tsc.stdout}${tsc.stderr}`);
|
|
34
|
+
else console.log("typecheck: frontend TS passes");
|
|
35
|
+
|
|
36
|
+
const files = [
|
|
37
|
+
...walk(frontend, [".ts", ".tsx", ".css", ".html"]),
|
|
38
|
+
...walk(join(staticRoot, "app"), [".js", ".css", ".html", ".json"]),
|
|
39
|
+
join(staticRoot, "sw.js"),
|
|
40
|
+
join(staticRoot, "manifest.json"),
|
|
41
|
+
].filter(existsSync);
|
|
42
|
+
|
|
43
|
+
const external = /https?:\/\/(fonts\.googleapis\.com|fonts\.gstatic\.com|cdn\.jsdelivr\.net|unpkg\.com|cdnjs\.cloudflare\.com)/;
|
|
44
|
+
const stale = /static\/v3|lint_v3|build_v3_assets/;
|
|
45
|
+
let scanned = 0;
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
scanned += 1;
|
|
48
|
+
const text = readFileSync(file, "utf8");
|
|
49
|
+
if (external.test(text)) fail(`${relative(repo, file)}: CDN reference`);
|
|
50
|
+
if (stale.test(text)) fail(`${relative(repo, file)}: stale v3 frontend reference`);
|
|
51
|
+
}
|
|
52
|
+
console.log(`privacy: ${scanned} frontend/static files scanned`);
|
|
53
|
+
|
|
54
|
+
const client = readFileSync(join(frontend, "src", "api", "client.ts"), "utf8");
|
|
55
|
+
if (!client.includes("openapi-fetch") || !client.includes("./openapi")) {
|
|
56
|
+
fail("frontend/src/api/client.ts must use the generated OpenAPI client");
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(join(frontend, "src", "api", "openapi.ts"))) {
|
|
59
|
+
fail("generated OpenAPI types missing");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const openapi = JSON.parse(readFileSync(join(frontend, "openapi.json"), "utf8"));
|
|
63
|
+
const openapiPaths = Object.keys(openapi.paths || {});
|
|
64
|
+
const requiredPaths = [
|
|
65
|
+
"/health",
|
|
66
|
+
"/chat",
|
|
67
|
+
"/api/graph",
|
|
68
|
+
"/api/search/hybrid",
|
|
69
|
+
"/api/knowledge-graph/export",
|
|
70
|
+
"/api/memory/recall",
|
|
71
|
+
"/agents/api/run",
|
|
72
|
+
"/workflows/api/definitions",
|
|
73
|
+
"/workspace/os",
|
|
74
|
+
"/models",
|
|
75
|
+
];
|
|
76
|
+
if (openapiPaths.length < 300) fail(`OpenAPI path count too low: ${openapiPaths.length}`);
|
|
77
|
+
for (const path of requiredPaths) {
|
|
78
|
+
if (!openapiPaths.includes(path)) fail(`OpenAPI path missing: ${path}`);
|
|
79
|
+
}
|
|
80
|
+
console.log(`api: generated OpenAPI schema exposes ${openapiPaths.length} paths`);
|
|
81
|
+
|
|
82
|
+
if (failures) {
|
|
83
|
+
console.error(`\nfrontend lint: ${failures} failure(s)`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
console.log("\nfrontend lint: all checks pass");
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
if (args.length === 0) {
|
|
9
|
+
console.error("usage: node scripts/run_python.mjs <python-args...>");
|
|
10
|
+
process.exit(2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const candidates = [
|
|
14
|
+
process.env.LTCAI_PYTHON,
|
|
15
|
+
path.join(process.cwd(), ".venv", "bin", "python"),
|
|
16
|
+
path.join(process.cwd(), ".venv", "Scripts", "python.exe"),
|
|
17
|
+
"python3",
|
|
18
|
+
"python",
|
|
19
|
+
].filter(Boolean);
|
|
20
|
+
|
|
21
|
+
let lastError = null;
|
|
22
|
+
|
|
23
|
+
for (const candidate of candidates) {
|
|
24
|
+
const isPath = candidate.includes(path.sep);
|
|
25
|
+
if (isPath && !existsSync(candidate)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const result = spawnSync(candidate, args, {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
env: process.env,
|
|
32
|
+
stdio: "inherit",
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (result.error) {
|
|
36
|
+
if (result.error.code === "ENOENT") {
|
|
37
|
+
lastError = result.error;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
throw result.error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
process.exit(result.status ?? 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.error(`No usable Python executable found. Last error: ${lastError?.message ?? "none"}`);
|
|
47
|
+
process.exit(127);
|