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.
Files changed (150) hide show
  1. package/README.md +28 -23
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +42 -0
  4. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  5. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  6. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  7. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +26 -19
  8. package/frontend/index.html +24 -0
  9. package/frontend/openapi.json +14190 -0
  10. package/frontend/src/App.tsx +184 -0
  11. package/frontend/src/api/client.ts +317 -0
  12. package/frontend/src/api/openapi.ts +16637 -0
  13. package/frontend/src/components/primitives.tsx +204 -0
  14. package/frontend/src/components/ui/badge.tsx +27 -0
  15. package/frontend/src/components/ui/button.tsx +37 -0
  16. package/frontend/src/components/ui/card.tsx +22 -0
  17. package/frontend/src/components/ui/input.tsx +16 -0
  18. package/frontend/src/components/ui/textarea.tsx +16 -0
  19. package/frontend/src/lib/utils.ts +33 -0
  20. package/frontend/src/main.tsx +23 -0
  21. package/frontend/src/pages/Act.tsx +245 -0
  22. package/frontend/src/pages/Ask.tsx +200 -0
  23. package/frontend/src/pages/Brain.tsx +267 -0
  24. package/frontend/src/pages/Capture.tsx +158 -0
  25. package/frontend/src/pages/Library.tsx +187 -0
  26. package/frontend/src/pages/System.tsx +344 -0
  27. package/frontend/src/routes.ts +85 -0
  28. package/frontend/src/store/appStore.ts +54 -0
  29. package/frontend/src/styles.css +107 -0
  30. package/latticeai/__init__.py +1 -1
  31. package/latticeai/api/setup.py +5 -4
  32. package/latticeai/api/static_routes.py +4 -4
  33. package/latticeai/core/marketplace.py +1 -1
  34. package/latticeai/core/multi_agent.py +1 -1
  35. package/latticeai/core/workspace_os.py +1 -1
  36. package/package.json +54 -15
  37. package/scripts/build_frontend_assets.mjs +38 -0
  38. package/scripts/bump_version.py +1 -1
  39. package/scripts/export_openapi.py +31 -0
  40. package/scripts/lint_frontend.mjs +86 -0
  41. package/scripts/run_python.mjs +47 -0
  42. package/src-tauri/Cargo.lock +4833 -0
  43. package/src-tauri/Cargo.toml +19 -0
  44. package/src-tauri/build.rs +3 -0
  45. package/src-tauri/capabilities/default.json +7 -0
  46. package/src-tauri/src/main.rs +78 -0
  47. package/src-tauri/tauri.conf.json +36 -0
  48. package/static/app/asset-manifest.json +32 -0
  49. package/static/app/assets/core-CwxXejkd.js +2 -0
  50. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  51. package/static/app/assets/index-CJRAzNnf.js +333 -0
  52. package/static/app/assets/index-CJRAzNnf.js.map +1 -0
  53. package/static/app/assets/index-CSwBBgf4.css +2 -0
  54. package/static/app/index.html +25 -0
  55. package/static/manifest.json +2 -2
  56. package/static/sw.js +4 -4
  57. package/scripts/build_v3_assets.mjs +0 -170
  58. package/scripts/lint_v3.mjs +0 -120
  59. package/static/v3/asset-manifest.json +0 -63
  60. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  61. package/static/v3/css/lattice.base.css +0 -128
  62. package/static/v3/css/lattice.components.cde18231.css +0 -472
  63. package/static/v3/css/lattice.components.css +0 -472
  64. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  65. package/static/v3/css/lattice.shell.css +0 -452
  66. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  67. package/static/v3/css/lattice.tokens.css +0 -135
  68. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  69. package/static/v3/css/lattice.views.css +0 -360
  70. package/static/v3/index.html +0 -68
  71. package/static/v3/js/app.c5c80c46.js +0 -26
  72. package/static/v3/js/app.js +0 -26
  73. package/static/v3/js/core/api.ba0fbf14.js +0 -625
  74. package/static/v3/js/core/api.js +0 -625
  75. package/static/v3/js/core/components.f25b3b93.js +0 -230
  76. package/static/v3/js/core/components.js +0 -230
  77. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  78. package/static/v3/js/core/dom.js +0 -148
  79. package/static/v3/js/core/i18n.880e1fec.js +0 -575
  80. package/static/v3/js/core/i18n.js +0 -575
  81. package/static/v3/js/core/router.584570f2.js +0 -37
  82. package/static/v3/js/core/router.js +0 -37
  83. package/static/v3/js/core/routes.37522821.js +0 -101
  84. package/static/v3/js/core/routes.js +0 -101
  85. package/static/v3/js/core/shell.e3f6bbfa.js +0 -420
  86. package/static/v3/js/core/shell.js +0 -420
  87. package/static/v3/js/core/store.7b2aa044.js +0 -123
  88. package/static/v3/js/core/store.js +0 -123
  89. package/static/v3/js/views/account.eff40715.js +0 -143
  90. package/static/v3/js/views/account.js +0 -143
  91. package/static/v3/js/views/activity.0d271ef9.js +0 -67
  92. package/static/v3/js/views/activity.js +0 -67
  93. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  94. package/static/v3/js/views/admin-audit.js +0 -185
  95. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  96. package/static/v3/js/views/admin-permissions.js +0 -177
  97. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  98. package/static/v3/js/views/admin-policies.js +0 -102
  99. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  100. package/static/v3/js/views/admin-private-vpc.js +0 -135
  101. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  102. package/static/v3/js/views/admin-security.js +0 -180
  103. package/static/v3/js/views/admin-users.f7ac7b43.js +0 -166
  104. package/static/v3/js/views/admin-users.js +0 -166
  105. package/static/v3/js/views/agents.17c5288d.js +0 -564
  106. package/static/v3/js/views/agents.js +0 -564
  107. package/static/v3/js/views/chat.e250e2cc.js +0 -624
  108. package/static/v3/js/views/chat.js +0 -624
  109. package/static/v3/js/views/files.adad14c1.js +0 -365
  110. package/static/v3/js/views/files.js +0 -365
  111. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  112. package/static/v3/js/views/graph-canvas.js +0 -509
  113. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  114. package/static/v3/js/views/home.js +0 -200
  115. package/static/v3/js/views/hooks.37895880.js +0 -220
  116. package/static/v3/js/views/hooks.js +0 -220
  117. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  118. package/static/v3/js/views/hybrid-search.js +0 -194
  119. package/static/v3/js/views/knowledge-graph.4d09c537.js +0 -529
  120. package/static/v3/js/views/knowledge-graph.js +0 -529
  121. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  122. package/static/v3/js/views/marketplace.js +0 -141
  123. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  124. package/static/v3/js/views/mcp.js +0 -114
  125. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  126. package/static/v3/js/views/memory.js +0 -147
  127. package/static/v3/js/views/models.a1ffa147.js +0 -256
  128. package/static/v3/js/views/models.js +0 -256
  129. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  130. package/static/v3/js/views/my-computer.js +0 -463
  131. package/static/v3/js/views/network.52a4f181.js +0 -97
  132. package/static/v3/js/views/network.js +0 -97
  133. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  134. package/static/v3/js/views/pipeline.js +0 -157
  135. package/static/v3/js/views/planning.4876fd77.js +0 -174
  136. package/static/v3/js/views/planning.js +0 -174
  137. package/static/v3/js/views/runs.b63b2afa.js +0 -144
  138. package/static/v3/js/views/runs.js +0 -144
  139. package/static/v3/js/views/settings.b7140634.js +0 -317
  140. package/static/v3/js/views/settings.js +0 -317
  141. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  142. package/static/v3/js/views/skills.js +0 -109
  143. package/static/v3/js/views/snapshots.6f5db095.js +0 -135
  144. package/static/v3/js/views/snapshots.js +0 -135
  145. package/static/v3/js/views/tools.e4f11276.js +0 -108
  146. package/static/v3/js/views/tools.js +0 -108
  147. package/static/v3/js/views/workflows.7752225a.js +0 -213
  148. package/static/v3/js/views/workflows.js +0 -213
  149. package/static/v3/js/views/workspace-admin.c466029b.js +0 -156
  150. 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
+ }
@@ -1,3 +1,3 @@
1
1
  """Lattice AI - modular server package."""
2
2
 
3
- __version__ = "4.0.1"
3
+ __version__ = "4.1.0"
@@ -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
- """v3 single-page workspace shell (token-native design system)."""
116
- page = STATIC_DIR / "v3" / "index.html"
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="v3 shell not found.")
118
+ raise HTTPException(status_code=404, detail="React shell not found.")
119
119
  return ui_file_response(page)
120
120
 
121
121
 
@@ -11,7 +11,7 @@ from copy import deepcopy
11
11
  from typing import Any, Dict, List, Optional
12
12
 
13
13
 
14
- MARKETPLACE_VERSION = "4.0.1"
14
+ MARKETPLACE_VERSION = "4.1.0"
15
15
  TEMPLATE_KINDS = ("plugin", "workflow", "agent")
16
16
 
17
17
 
@@ -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.1"
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.1"
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.1",
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
- "build:assets": "node scripts/build_v3_assets.mjs",
22
- "build:python": "python3 -m build",
23
- "check:python": "python3 scripts/check_python.py",
24
- "lint": "node --check tests/visual/mock_server.cjs && node --check tests/visual/v3.spec.js && npm run lint:v3",
25
- "lint:v3": "node scripts/lint_v3.mjs",
26
- "typecheck": "cd vscode-extension && npm run build",
27
- "test": "python3 -m pytest tests/ -v",
28
- "test:unit": "python3 -m pytest tests/unit/ -v",
29
- "test:integration": "python3 -m pytest tests/integration/ -v",
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": "python3 scripts/validate_release_artifacts.py $npm_package_version --require-vsix --require-tgz",
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 && python3 -m twine upload --skip-existing dist/ltcai-$npm_package_version.tar.gz dist/ltcai-$npm_package_version-py3-none-any.whl",
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 && python3 -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"
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
- "static/v3/",
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`);
@@ -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/v3/asset-manifest.json", "json", "version"),
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);