ltcai 3.6.0 → 4.0.1

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 (238) hide show
  1. package/README.md +39 -31
  2. package/docs/CHANGELOG.md +64 -0
  3. package/docs/REALTIME_COLLABORATION.md +3 -3
  4. package/docs/V3_FRONTEND.md +9 -8
  5. package/docs/V4_BRAIN_ARCHITECTURE.md +322 -0
  6. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +552 -0
  7. package/docs/V4_IMPLEMENTATION_PLAN.md +470 -0
  8. package/docs/kg-schema.md +51 -53
  9. package/docs/spec-vs-impl.md +10 -10
  10. package/kg_schema.py +2 -520
  11. package/knowledge_graph.py +37 -4629
  12. package/knowledge_graph_api.py +11 -127
  13. package/latticeai/__init__.py +1 -1
  14. package/latticeai/api/admin.py +16 -17
  15. package/latticeai/api/agents.py +20 -7
  16. package/latticeai/api/auth.py +46 -15
  17. package/latticeai/api/chat.py +112 -76
  18. package/latticeai/api/health.py +1 -1
  19. package/latticeai/api/hooks.py +1 -1
  20. package/latticeai/api/invitations.py +100 -0
  21. package/latticeai/api/knowledge_graph.py +139 -0
  22. package/latticeai/api/local_files.py +1 -1
  23. package/latticeai/api/mcp.py +23 -11
  24. package/latticeai/api/memory.py +1 -1
  25. package/latticeai/api/models.py +1 -1
  26. package/latticeai/api/network.py +81 -0
  27. package/latticeai/api/plugins.py +3 -6
  28. package/latticeai/api/realtime.py +5 -8
  29. package/latticeai/api/search.py +26 -2
  30. package/latticeai/api/security_dashboard.py +2 -3
  31. package/latticeai/api/setup.py +2 -2
  32. package/latticeai/api/static_routes.py +11 -16
  33. package/latticeai/api/tools.py +3 -0
  34. package/latticeai/api/ui_redirects.py +26 -0
  35. package/latticeai/api/workflow_designer.py +85 -6
  36. package/latticeai/api/workspace.py +93 -57
  37. package/latticeai/app_factory.py +1781 -0
  38. package/latticeai/brain/__init__.py +18 -0
  39. package/latticeai/brain/_kg_common.py +1123 -0
  40. package/latticeai/brain/context.py +213 -0
  41. package/latticeai/brain/conversations.py +236 -0
  42. package/latticeai/brain/discovery.py +1455 -0
  43. package/latticeai/brain/documents.py +218 -0
  44. package/latticeai/brain/identity.py +175 -0
  45. package/latticeai/brain/ingest.py +644 -0
  46. package/latticeai/brain/memory.py +102 -0
  47. package/latticeai/brain/network.py +205 -0
  48. package/latticeai/brain/projection.py +561 -0
  49. package/latticeai/brain/provenance.py +401 -0
  50. package/latticeai/brain/retrieval.py +1316 -0
  51. package/latticeai/brain/schema.py +640 -0
  52. package/latticeai/brain/store.py +216 -0
  53. package/latticeai/brain/write_master.py +225 -0
  54. package/latticeai/core/agent.py +31 -7
  55. package/latticeai/core/audit.py +0 -7
  56. package/latticeai/core/config.py +1 -1
  57. package/latticeai/core/context_builder.py +1 -2
  58. package/latticeai/core/enterprise.py +1 -1
  59. package/latticeai/core/graph_curator.py +2 -2
  60. package/latticeai/core/invitations.py +131 -0
  61. package/latticeai/core/marketplace.py +1 -1
  62. package/latticeai/core/mcp_registry.py +791 -0
  63. package/latticeai/core/model_compat.py +1 -1
  64. package/latticeai/core/model_resolution.py +0 -1
  65. package/latticeai/core/multi_agent.py +238 -4
  66. package/latticeai/core/policy.py +54 -0
  67. package/latticeai/core/realtime.py +65 -44
  68. package/latticeai/core/security.py +1 -1
  69. package/latticeai/core/sessions.py +66 -10
  70. package/latticeai/core/users.py +147 -0
  71. package/latticeai/core/workflow_engine.py +114 -2
  72. package/latticeai/core/workspace_os.py +477 -29
  73. package/latticeai/models/__init__.py +7 -0
  74. package/latticeai/models/router.py +779 -0
  75. package/latticeai/server_app.py +29 -1536
  76. package/latticeai/services/agent_runtime.py +243 -4
  77. package/latticeai/services/app_context.py +75 -14
  78. package/latticeai/services/ingestion.py +47 -0
  79. package/latticeai/services/kg_portability.py +33 -3
  80. package/latticeai/services/memory_service.py +39 -11
  81. package/latticeai/services/model_runtime.py +2 -5
  82. package/latticeai/services/platform_runtime.py +100 -23
  83. package/latticeai/services/run_executor.py +328 -0
  84. package/latticeai/services/search_service.py +17 -8
  85. package/latticeai/services/tool_dispatch.py +12 -2
  86. package/latticeai/services/triggers.py +241 -0
  87. package/latticeai/services/upload_service.py +37 -12
  88. package/latticeai/services/workspace_service.py +55 -16
  89. package/llm_router.py +29 -772
  90. package/ltcai_cli.py +1 -2
  91. package/mcp_registry.py +25 -788
  92. package/p_reinforce.py +124 -14
  93. package/package.json +10 -20
  94. package/scripts/bump_version.py +99 -0
  95. package/scripts/generate_diagrams.py +0 -1
  96. package/scripts/lint_v3.mjs +105 -18
  97. package/scripts/validate_release_artifacts.py +0 -1
  98. package/scripts/wheel_smoke.py +142 -0
  99. package/server.py +11 -7
  100. package/setup_wizard.py +1142 -0
  101. package/static/sw.js +81 -52
  102. package/static/v3/asset-manifest.json +33 -25
  103. package/static/v3/css/{lattice.base.e4cdd05d.css → lattice.base.49deefb5.css} +1 -1
  104. package/static/v3/css/lattice.base.css +1 -1
  105. package/static/v3/css/{lattice.components.9b49d614.css → lattice.components.cde18231.css} +1 -1
  106. package/static/v3/css/lattice.components.css +1 -1
  107. package/static/v3/css/{lattice.shell.8fcc9d33.css → lattice.shell.29d36d85.css} +1 -1
  108. package/static/v3/css/lattice.shell.css +1 -1
  109. package/static/v3/css/{lattice.tokens.e7018963.css → lattice.tokens.304cbc40.css} +3 -0
  110. package/static/v3/css/lattice.tokens.css +3 -0
  111. package/static/v3/css/{lattice.views.22f69117.css → lattice.views.0a18b6c5.css} +2 -2
  112. package/static/v3/css/lattice.views.css +2 -2
  113. package/static/v3/index.html +3 -4
  114. package/static/v3/js/{app.c541f955.js → app.c5c80c46.js} +1 -1
  115. package/static/v3/js/core/{api.33d6320e.js → api.ba0fbf14.js} +58 -1
  116. package/static/v3/js/core/api.js +57 -0
  117. package/static/v3/js/core/i18n.880e1fec.js +575 -0
  118. package/static/v3/js/core/i18n.js +575 -0
  119. package/static/v3/js/core/routes.37522821.js +101 -0
  120. package/static/v3/js/core/routes.js +71 -63
  121. package/static/v3/js/core/{shell.8c163e0e.js → shell.e3f6bbfa.js} +68 -39
  122. package/static/v3/js/core/shell.js +66 -37
  123. package/static/v3/js/core/{store.34ebd5e6.js → store.7b2aa044.js} +11 -1
  124. package/static/v3/js/core/store.js +11 -1
  125. package/static/v3/js/views/account.eff40715.js +143 -0
  126. package/static/v3/js/views/account.js +143 -0
  127. package/static/v3/js/views/activity.0d271ef9.js +67 -0
  128. package/static/v3/js/views/activity.js +67 -0
  129. package/static/v3/js/views/{admin-users.03bac88c.js → admin-users.f7ac7b43.js} +4 -6
  130. package/static/v3/js/views/admin-users.js +4 -6
  131. package/static/v3/js/views/{agents.014d0b74.js → agents.17c5288d.js} +35 -12
  132. package/static/v3/js/views/agents.js +35 -12
  133. package/static/v3/js/views/{chat.e6dd7dd0.js → chat.e250e2cc.js} +23 -0
  134. package/static/v3/js/views/chat.js +23 -0
  135. package/static/v3/js/views/graph-canvas.17c15d65.js +509 -0
  136. package/static/v3/js/views/graph-canvas.js +509 -0
  137. package/static/v3/js/views/{hybrid-search.b22b97e0.js → hybrid-search.2fb63ed9.js} +1 -2
  138. package/static/v3/js/views/hybrid-search.js +1 -2
  139. package/static/v3/js/views/{knowledge-graph.a96040a5.js → knowledge-graph.4d09c537.js} +60 -44
  140. package/static/v3/js/views/knowledge-graph.js +60 -44
  141. package/static/v3/js/views/network.52a4f181.js +97 -0
  142. package/static/v3/js/views/network.js +97 -0
  143. package/static/v3/js/views/{planning.9ac3e313.js → planning.4876fd77.js} +26 -5
  144. package/static/v3/js/views/planning.js +26 -5
  145. package/static/v3/js/views/runs.b63b2afa.js +144 -0
  146. package/static/v3/js/views/runs.js +144 -0
  147. package/static/v3/js/views/{settings.8631fa5e.js → settings.b7140634.js} +7 -8
  148. package/static/v3/js/views/settings.js +7 -8
  149. package/static/v3/js/views/snapshots.6f5db095.js +135 -0
  150. package/static/v3/js/views/snapshots.js +135 -0
  151. package/static/v3/js/views/{workflows.26c57290.js → workflows.7752225a.js} +87 -2
  152. package/static/v3/js/views/workflows.js +87 -2
  153. package/static/v3/js/views/workspace-admin.c466029b.js +156 -0
  154. package/static/v3/js/views/workspace-admin.js +156 -0
  155. package/static/vendor/chart.umd.min.js +20 -0
  156. package/static/vendor/fonts/inter-latin-300-normal.woff2 +0 -0
  157. package/static/vendor/fonts/inter-latin-400-normal.woff2 +0 -0
  158. package/static/vendor/fonts/inter-latin-500-normal.woff2 +0 -0
  159. package/static/vendor/fonts/inter-latin-600-normal.woff2 +0 -0
  160. package/static/vendor/fonts/inter-latin-700-normal.woff2 +0 -0
  161. package/static/vendor/fonts/inter-latin-800-normal.woff2 +0 -0
  162. package/static/vendor/fonts/inter.css +44 -0
  163. package/static/vendor/icons/tabler-icons.min.css +4 -0
  164. package/static/vendor/icons/tabler-icons.woff2 +0 -0
  165. package/static/vendor/marked.min.js +69 -0
  166. package/telegram_bot.py +1 -2
  167. package/tools/commands.py +4 -2
  168. package/tools/computer.py +1 -1
  169. package/tools/documents.py +1 -3
  170. package/tools/filesystem.py +0 -4
  171. package/tools/knowledge.py +1 -3
  172. package/tools/network.py +1 -3
  173. package/codex_telegram_bot.py +0 -195
  174. package/docs/assets/v3.4.0/agent-run.png +0 -0
  175. package/docs/assets/v3.4.0/agents.png +0 -0
  176. package/docs/assets/v3.4.0/before/chat-before.png +0 -0
  177. package/docs/assets/v3.4.0/before/files-before.png +0 -0
  178. package/docs/assets/v3.4.0/chat.png +0 -0
  179. package/docs/assets/v3.4.0/connect-folder.png +0 -0
  180. package/docs/assets/v3.4.0/files.png +0 -0
  181. package/docs/assets/v3.4.0/home.png +0 -0
  182. package/docs/assets/v3.4.0/hooks-dispatch.png +0 -0
  183. package/docs/assets/v3.4.0/knowledge-graph.png +0 -0
  184. package/docs/assets/v3.4.0/local-agent.png +0 -0
  185. package/docs/assets/v3.4.0/memory.png +0 -0
  186. package/docs/assets/v3.4.0/settings.png +0 -0
  187. package/docs/assets/v3.4.0/vision-input.png +0 -0
  188. package/docs/assets/v3.4.0/workflows.png +0 -0
  189. package/docs/assets/v3.4.1/e2e_runtime_log.txt +0 -42
  190. package/docs/assets/v3.4.1/hooks-dispatch.png +0 -0
  191. package/docs/assets/v3.4.1/local-agent.png +0 -0
  192. package/docs/images/admin-dashboard.png +0 -0
  193. package/docs/images/architecture.png +0 -0
  194. package/docs/images/enterprise.png +0 -0
  195. package/docs/images/graph.png +0 -0
  196. package/docs/images/hero.gif +0 -0
  197. package/docs/images/knowledge-graph.png +0 -0
  198. package/docs/images/lattice-ai-demo.gif +0 -0
  199. package/docs/images/lattice-ai-hero.png +0 -0
  200. package/docs/images/logo.svg +0 -33
  201. package/docs/images/mobile-responsive.png +0 -0
  202. package/docs/images/model-recommendation.png +0 -0
  203. package/docs/images/onboarding.png +0 -0
  204. package/docs/images/organization.png +0 -0
  205. package/docs/images/pipeline.png +0 -0
  206. package/docs/images/screenshot-admin.png +0 -0
  207. package/docs/images/screenshot-chat.png +0 -0
  208. package/docs/images/screenshot-graph.png +0 -0
  209. package/docs/images/skills.png +0 -0
  210. package/docs/images/workspace-dark.png +0 -0
  211. package/docs/images/workspace-light.png +0 -0
  212. package/docs/images/workspace.png +0 -0
  213. package/requirements.txt +0 -16
  214. package/static/account.html +0 -115
  215. package/static/activity.html +0 -73
  216. package/static/admin.html +0 -488
  217. package/static/agents.html +0 -139
  218. package/static/chat.html +0 -844
  219. package/static/css/reference/account.css +0 -439
  220. package/static/css/reference/admin.css +0 -610
  221. package/static/css/reference/base.css +0 -1661
  222. package/static/css/reference/chat.css +0 -4623
  223. package/static/css/reference/graph.css +0 -1016
  224. package/static/css/responsive.css +0 -861
  225. package/static/graph.html +0 -124
  226. package/static/platform.css +0 -104
  227. package/static/plugins.html +0 -136
  228. package/static/scripts/account.js +0 -238
  229. package/static/scripts/admin.js +0 -1614
  230. package/static/scripts/chat.js +0 -5081
  231. package/static/scripts/graph.js +0 -1804
  232. package/static/scripts/platform.js +0 -64
  233. package/static/scripts/ux.js +0 -167
  234. package/static/scripts/workspace.js +0 -948
  235. package/static/v3/js/core/routes.2ce3815a.js +0 -93
  236. package/static/workflows.html +0 -146
  237. package/static/workspace.css +0 -1121
  238. package/static/workspace.html +0 -357
package/static/graph.html DELETED
@@ -1,124 +0,0 @@
1
- <!doctype html>
2
- <html lang="ko">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
6
- <title>Lattice AI - 지식 그래프</title>
7
- <script src="/static/scripts/ux.js"></script>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap">
11
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
12
- <link rel="stylesheet" href="/static/css/tokens.css">
13
- <link rel="stylesheet" href="/static/css/reference/base.css">
14
- <link rel="stylesheet" href="/static/css/reference/account.css">
15
- <link rel="stylesheet" href="/static/css/reference/admin.css">
16
- <link rel="stylesheet" href="/static/css/reference/graph.css">
17
- <link rel="stylesheet" href="/static/css/reference/chat.css">
18
- <link rel="stylesheet" href="/static/css/responsive.css">
19
- </head>
20
- <body class="lattice-ref-graph">
21
- <div class="sidebar-overlay" onclick="closeGraphNav&&closeGraphNav()"></div>
22
- <button class="graph-nav-toggle icon-btn" onclick="toggleGraphNav&&toggleGraphNav()" title="메뉴" aria-label="네비게이션 열기"><i class="ti ti-menu-2"></i></button>
23
- <aside class="reference-rail graph-rail">
24
- <div class="rail-brand"><i class="ti ti-chart-dots-3"></i><strong>Lattice AI</strong></div>
25
- <nav>
26
- <a href="/chat"><i class="ti ti-home"></i> 홈</a>
27
- <a href="/workspace"><i class="ti ti-layout-dashboard"></i> Workspace OS</a>
28
- <a class="active" href="/graph"><i class="ti ti-chart-dots-3"></i> 지식 그래프</a>
29
- <a href="/chat"><i class="ti ti-message-circle"></i> 대화</a>
30
- <a href="/chat"><i class="ti ti-file"></i> 파일</a>
31
- <a href="/chat"><i class="ti ti-code"></i> 코드</a>
32
- <a href="/chat"><i class="ti ti-settings"></i> 설정</a>
33
- </nav>
34
- <div class="rail-project">
35
- <span>프로젝트</span>
36
- <strong>Lattice AI Platform</strong>
37
- </div>
38
- </aside>
39
- <div class="app">
40
- <main class="stage">
41
- <canvas id="graph"></canvas>
42
- <canvas id="minimap" class="graph-minimap" width="180" height="120" title="미니맵"></canvas>
43
- <div id="graph-card-list" class="graph-card-list"></div>
44
-
45
- <section class="search-shell">
46
- <div class="search-head">
47
- <div class="search-title">
48
- <strong>Explore the graph</strong>
49
- <span>Search topics, files, conversations, decisions, and tasks.</span>
50
- </div>
51
- <div class="search-count" id="search-count">Ready</div>
52
- </div>
53
- <div class="search-input-wrap">
54
- <div class="search-input-row">
55
- <input id="search" class="search-input" placeholder="Search by topic, file, or conversation..." autocomplete="off">
56
- <button id="clear-search-btn" class="icon-btn" title="Clear search">×</button>
57
- </div>
58
- </div>
59
- <div id="search-results" class="search-results">
60
- <p class="search-empty">검색 결과는 여기에 표시됩니다. 키워드를 입력하면 서버 검색 결과를 불러오고, 항목을 누르면 해당 노드로 바로 이동합니다.</p>
61
- </div>
62
- </section>
63
-
64
- <div class="toolbar graph-toolbar">
65
- <button class="tb-btn" id="zoom-out-btn" title="축소" aria-label="축소"><i class="ti ti-minus"></i></button>
66
- <button class="tb-btn" id="zoom-in-btn" title="확대" aria-label="확대"><i class="ti ti-plus"></i></button>
67
- <button class="tb-btn" id="fullscreen-btn" title="전체화면" aria-label="전체화면"><i class="ti ti-maximize"></i></button>
68
- <button class="tb-btn graph-view-toggle" id="view-toggle-btn" title="그래프/카드 보기 전환" aria-label="그래프/카드 보기 전환"><i class="ti ti-layout-cards"></i></button>
69
- <button class="tb-btn" id="refresh-btn"><i class="ti ti-refresh"></i> Refresh</button>
70
- <button class="tb-btn" id="fit-btn" title="Fit graph"><i class="ti ti-arrows-maximize"></i> Fit</button>
71
- <button class="tb-btn" id="expand-btn" title="Expand selected node"><i class="ti ti-circle-plus"></i> Expand</button>
72
- <button class="tb-btn" id="collapse-btn" title="Collapse selected neighbors"><i class="ti ti-circle-minus"></i> Collapse</button>
73
- <button class="tb-btn" id="focus-btn" title="Focus selected subgraph"><i class="ti ti-focus-2"></i> Focus</button>
74
- <button class="tb-btn" id="path-btn" title="Shortest path from saved start"><i class="ti ti-route"></i> Path</button>
75
- <div class="lang-picker" id="graph-lang-picker">
76
- <button class="tb-btn" id="graph-lang-btn" type="button" onclick="toggleLangMenu('graph-lang-picker')">Language</button>
77
- <div class="lang-picker-menu" id="graph-lang-picker-menu">
78
- <div class="lang-option" id="graph-lang-ko" onclick="setLang('ko')">🇰🇷 한국어</div>
79
- <div class="lang-option" id="graph-lang-en" onclick="setLang('en')">🇺🇸 English</div>
80
- </div>
81
- </div>
82
- </div>
83
- <div id="graph-focus-chip" class="focus-chip" hidden></div>
84
- </main>
85
-
86
- <aside>
87
- <div class="sidebar-head">
88
- <div class="eyebrow">지식 그래프</div>
89
- <h1>Knowledge topology</h1>
90
- <p class="sidebar-sub">주제의 크기는 중요도 기반으로, 선의 굵기와 색은 관계 종류와 강도를 반영합니다.</p>
91
- <div class="stats-row">
92
- <div class="stat"><strong id="node-count">-</strong><span>Nodes</span></div>
93
- <div class="stat"><strong id="edge-count">-</strong><span>Edges</span></div>
94
- </div>
95
- </div>
96
-
97
- <div class="section">
98
- <div class="section-label" id="local-source-label">지식 소스</div>
99
- <div id="local-source-panel" class="local-source-panel"></div>
100
- </div>
101
-
102
- <div class="section">
103
- <div class="section-label" id="edge-label">Relationship legend</div>
104
- <div id="edge-legend" class="legend-grid"></div>
105
- </div>
106
-
107
- <div class="section">
108
- <div class="section-label" id="type-label">Node types</div>
109
- <div id="type-filters" class="filter-grid"></div>
110
- </div>
111
-
112
- <div class="detail-wrap">
113
- <div id="detail">
114
- <p class="empty-hint">노드를 클릭하면 요약, 중요도, 연결 강도, 메타데이터를 볼 수 있습니다. 검색 패널에서는 서버 검색 결과를 기준으로 더 정확하게 이동할 수 있습니다.</p>
115
- </div>
116
- </div>
117
- </aside>
118
- </div>
119
-
120
- <div id="tooltip"></div>
121
-
122
- <script src="/static/scripts/graph.js"></script>
123
- </body>
124
- </html>
@@ -1,104 +0,0 @@
1
- /* Lattice AI v2.0 — shared styling for the Agentic Workspace Platform pages
2
- (Plugin SDK, Workflow Designer, Multi-Agent Runtime, Realtime Activity). */
3
- :root {
4
- /* Consume tokens.css semantic tokens so these pages follow light & dark themes.
5
- tokens.css is linked before platform.css, so var() resolves at runtime. */
6
- --bg: var(--lt-bg, #0f1115);
7
- --panel: var(--lt-surface, #16191f);
8
- --panel-2: var(--lt-surface-2, #1c2027);
9
- --border: var(--lt-line, rgba(255, 255, 255, 0.08));
10
- --text: var(--lt-ink, #e7ecf3);
11
- --muted: var(--lt-ink-soft, #64748b);
12
- --accent: var(--lt-accent, #6E4AE6);
13
- --accent-2: var(--lt-accent, #5ea7ec);
14
- --ok: #34d399;
15
- --warn: #fbbf24;
16
- --err: #f87171;
17
- }
18
- * { box-sizing: border-box; }
19
- html, body { overflow-x: hidden; }
20
- body { min-width: 320px; }
21
- body {
22
- margin: 0;
23
- background: var(--bg);
24
- color: var(--text);
25
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
26
- line-height: 1.5;
27
- }
28
- a { color: var(--accent-2); text-decoration: none; }
29
- header.app {
30
- display: flex; align-items: center; gap: 18px;
31
- padding: 14px 24px; border-bottom: 1px solid var(--border);
32
- background: var(--panel); position: sticky; top: 0; z-index: 5;
33
- }
34
- header.app .brand { font-weight: 700; font-size: 16px; color: var(--text); letter-spacing: .3px; }
35
- header.app .brand small { color: var(--accent); font-weight: 600; margin-left: 6px; }
36
- header.app nav { display: flex; gap: 14px; flex-wrap: wrap; }
37
- header.app nav a { color: var(--muted); font-size: 13px; padding: 4px 6px; border-radius: 6px; min-height: 44px; display: inline-flex; align-items: center; }
38
- header.app nav a:hover, header.app nav a.active { color: var(--text); background: var(--panel-2); }
39
- main { max-width: 1080px; margin: 0 auto; padding: 28px 24px 80px; }
40
- h1 { font-size: 22px; margin: 0 0 4px; }
41
- .sub { color: var(--muted); font-size: 13px; margin: 0 0 24px; }
42
- .grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr)); }
43
- .card {
44
- background: var(--panel); border: 1px solid var(--border); border-radius: 14px;
45
- padding: 16px 18px;
46
- }
47
- .card h3 { margin: 0 0 6px; font-size: 15px; }
48
- .card .meta { color: var(--muted); font-size: 12px; }
49
- .row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
50
- .spacer { flex: 1; }
51
- .badge {
52
- display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: 999px;
53
- background: var(--panel-2); color: var(--muted); border: 1px solid var(--border);
54
- }
55
- .badge.ok { color: var(--ok); border-color: rgba(52,211,153,.4); }
56
- .badge.warn { color: var(--warn); border-color: rgba(251,191,36,.4); }
57
- .badge.err { color: var(--err); border-color: rgba(248,113,113,.4); }
58
- button, .btn {
59
- background: var(--accent); color: #fff; border: none; border-radius: 8px;
60
- padding: 7px 14px; font-size: 13px; cursor: pointer; font-weight: 600;
61
- min-height: 44px;
62
- }
63
- button.ghost { background: var(--panel-2); color: var(--text); border: 1px solid var(--border); }
64
- button:hover { filter: brightness(1.08); }
65
- button:disabled { opacity: .5; cursor: not-allowed; }
66
- textarea, input, select {
67
- width: 100%; background: var(--panel-2); color: var(--text); border: 1px solid var(--border);
68
- border-radius: 8px; padding: 9px 11px; font-size: 16px; font-family: inherit;
69
- min-height: 44px;
70
- }
71
- textarea { min-height: 90px; resize: vertical; }
72
- label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 4px; }
73
- pre {
74
- background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
75
- padding: 12px; overflow: auto; font-size: 12px; color: var(--text); max-height: 360px;
76
- max-width: 100%; overflow-wrap: anywhere;
77
- }
78
- .empty { color: var(--muted); text-align: center; padding: 50px 0; }
79
- .section { margin-top: 28px; }
80
- .timeline-item { border-left: 2px solid var(--border); padding: 6px 0 6px 14px; margin-left: 6px; font-size: 13px; }
81
- .timeline-item .t-meta { color: var(--muted); font-size: 11px; }
82
- .toast {
83
- position: fixed;
84
- bottom: max(20px, env(safe-area-inset-bottom));
85
- left: auto;
86
- right: max(12px, env(safe-area-inset-right));
87
- background: var(--panel-2); border: 1px solid var(--border);
88
- padding: 12px 16px; border-radius: 10px; font-size: 13px;
89
- max-width: min(360px, calc(100vw - 24px));
90
- }
91
-
92
- /* 키보드 포커스 링 (tokens.css 의 :focus-visible 와 별개로 이 페이지에서도 보장) */
93
- :focus-visible {
94
- outline: 2px solid var(--accent, #6E4AE6);
95
- outline-offset: 2px;
96
- }
97
-
98
- /* 폰: 패딩 축소 + 단일 열 + 헤더 컴팩트 */
99
- @media (max-width: 600px) {
100
- header.app { padding: 12px 14px; }
101
- main { padding: 18px 14px 64px; }
102
- .grid { grid-template-columns: 1fr; }
103
- h1 { font-size: 20px; }
104
- }
@@ -1,136 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
6
- <title>Plugin SDK — Lattice AI</title>
7
- <script src="/static/scripts/ux.js"></script>
8
- <link rel="stylesheet" href="/static/css/tokens.css" />
9
- <link rel="stylesheet" href="/static/platform.css" />
10
- <link rel="stylesheet" href="/static/css/responsive.css" />
11
- </head>
12
- <body>
13
- <main>
14
- <h1>Plugin SDK</h1>
15
- <p class="sub" id="sub">Versioned, permissioned plugins that extend skills, tools, and workflows.</p>
16
- <div id="list" class="grid"><div class="empty">Loading plugins…</div></div>
17
-
18
- <div class="section">
19
- <h3>Template foundation</h3>
20
- <div id="templates" class="grid"><div class="empty">Loading templates…</div></div>
21
- </div>
22
-
23
- <div class="section">
24
- <h3>Plugin execution viewer</h3>
25
- <div id="pluginEvents"><div class="empty">Loading plugin events…</div></div>
26
- </div>
27
-
28
- <div class="section">
29
- <h3>Validate a manifest</h3>
30
- <p class="sub">Paste a <code>plugin.json</code> to check it against the SDK schema and permission allow-list.</p>
31
- <textarea id="manifest" spellcheck="false">{
32
- "id": "my-plugin",
33
- "name": "My Plugin",
34
- "version": "1.0.0",
35
- "lattice_version": ">=2.0.0",
36
- "permissions": ["read_workspace"],
37
- "provides": { "skills": [] }
38
- }</textarea>
39
- <div class="row" style="margin-top:10px"><button id="validate">Validate</button></div>
40
- <pre id="validateOut" style="display:none"></pre>
41
- </div>
42
- </main>
43
-
44
- <script type="module">
45
- import { mountHeader, api, escapeHtml, badge, toast } from "/static/scripts/platform.js";
46
- mountHeader("/plugins/sdk");
47
-
48
- async function load() {
49
- const data = await api("/plugins/registry");
50
- document.getElementById("sub").textContent =
51
- `SDK v${data.sdk_version} · ${data.total} plugin(s) discovered in ${data.plugins_dir}`;
52
- const list = document.getElementById("list");
53
- if (!data.plugins.length) { list.innerHTML = `<div class="empty">No plugins found.</div>`; return; }
54
- list.innerHTML = data.plugins.map((p) => `
55
- <div class="card">
56
- <div class="row"><h3>${escapeHtml(p.name)}</h3><div class="spacer"></div>
57
- ${badge(p.installed ? (p.enabled ? "ready" : "available") : "available")}</div>
58
- <div class="meta">v${escapeHtml(p.version)} · ${escapeHtml(p.author || "unknown")} · ${p.compatible ? "compatible" : "<span class='badge err'>incompatible</span>"}</div>
59
- <p style="font-size:13px;color:#cbd5e1">${escapeHtml(p.description)}</p>
60
- <div class="meta">Permissions: ${(p.permissions||[]).map(x=>`<span class="badge">${escapeHtml(x)}</span>`).join(" ") || "none"}</div>
61
- <div class="meta" style="margin-top:6px">Provides: ${Object.entries(p.provides||{}).map(([k,v])=>`${k}(${(v||[]).length})`).join(", ") || "—"}</div>
62
- <div class="row" style="margin-top:12px">
63
- ${p.installed
64
- ? `<button class="ghost" data-act="${p.enabled?"disable":"enable"}" data-id="${p.id}">${p.enabled?"Disable":"Enable"}</button>
65
- <button class="ghost" data-act="uninstall" data-id="${p.id}">Uninstall</button>`
66
- : `<button data-act="install" data-id="${p.id}" ${p.compatible?"":"disabled"}>Install</button>`}
67
- </div>
68
- </div>`).join("");
69
- }
70
-
71
- async function loadTemplates() {
72
- const data = await api("/marketplace/templates");
73
- const box = document.getElementById("templates");
74
- if (!data.templates.length) { box.innerHTML = `<div class="empty">No templates available.</div>`; return; }
75
- box.innerHTML = data.templates.map((t) => `
76
- <div class="card">
77
- <div class="row"><h3>${escapeHtml(t.name)}</h3><div class="spacer"></div>${badge(t.kind)}</div>
78
- <div class="meta">v${escapeHtml(t.version)} · ${escapeHtml((t.metadata||{}).category || "foundation")}</div>
79
- <p style="font-size:13px;color:#cbd5e1">${escapeHtml(t.description || "")}</p>
80
- <div class="row" style="margin-top:12px">
81
- <a class="btn ghost" target="_blank" href="/marketplace/templates/${t.kind}/${t.id}/export">Export</a>
82
- <button data-template="${t.kind}:${t.id}">Install</button>
83
- </div>
84
- </div>`).join("");
85
- }
86
-
87
- async function loadPluginEvents() {
88
- const data = await api("/realtime/feed?limit=80");
89
- const events = (data.events || []).filter((e) => e.area === "plugins" || e.event_type?.startsWith("plugin_"));
90
- const box = document.getElementById("pluginEvents");
91
- if (!events.length) { box.innerHTML = `<div class="empty">No plugin executions yet.</div>`; return; }
92
- box.innerHTML = events.slice(0, 20).map((ev) => `<div class="timeline-item">
93
- <div class="row"><strong>${escapeHtml(ev.event_type)}</strong><div class="spacer"></div>${badge((ev.payload||{}).status || ev.area)}</div>
94
- <div class="t-meta">${escapeHtml((ev.payload||{}).plugin_id || (ev.payload||{}).plugin || "")} · ${escapeHtml(ev.received_at || ev.timestamp || "")}</div>
95
- </div>`).join("");
96
- }
97
-
98
- document.getElementById("templates").addEventListener("click", async (e) => {
99
- const btn = e.target.closest("button[data-template]");
100
- if (!btn) return;
101
- btn.disabled = true;
102
- const [kind, id] = btn.dataset.template.split(":");
103
- try {
104
- const exported = await api(`/marketplace/templates/${kind}/${id}/export`);
105
- await api("/marketplace/templates/install", { method: "POST", body: JSON.stringify({ data: exported }) });
106
- toast(`Installed template: ${id}`);
107
- } catch (err) { toast(err.message); } finally { btn.disabled = false; }
108
- });
109
-
110
- document.getElementById("list").addEventListener("click", async (e) => {
111
- const btn = e.target.closest("button[data-act]");
112
- if (!btn) return;
113
- btn.disabled = true;
114
- try {
115
- await api(`/plugins/${btn.dataset.act}`, { method: "POST", body: JSON.stringify({ plugin_id: btn.dataset.id }) });
116
- toast(`${btn.dataset.act}: ${btn.dataset.id}`);
117
- await load();
118
- } catch (err) { toast(err.message); btn.disabled = false; }
119
- });
120
-
121
- document.getElementById("validate").addEventListener("click", async () => {
122
- const out = document.getElementById("validateOut");
123
- out.style.display = "block";
124
- try {
125
- const manifest = JSON.parse(document.getElementById("manifest").value);
126
- const res = await api("/plugins/validate", { method: "POST", body: JSON.stringify({ manifest }) });
127
- out.textContent = JSON.stringify(res, null, 2);
128
- } catch (err) { out.textContent = "Error: " + err.message; }
129
- });
130
-
131
- load().catch((e) => toast(e.message));
132
- loadTemplates().catch((e) => toast(e.message));
133
- loadPluginEvents().catch(() => {});
134
- </script>
135
- </body>
136
- </html>
@@ -1,238 +0,0 @@
1
- /* Lattice AI — account.html scripts */
2
-
3
- const API_BASE = window.location.protocol === 'file:' ? 'http://localhost:4825' : '';
4
- function apiFetch(path, opts = {}) {
5
- const headers = { ...(opts.headers || {}) };
6
- return fetch(API_BASE + path, { credentials: 'include', ...opts, headers });
7
- }
8
-
9
- // ── i18n ──────────────────────────────────────────────
10
- const I18N = {
11
- ko: {
12
- login_title: 'Lattice AI', login_sub: '내 PC에서 시작하는<br>개인 AI 워크스페이스',
13
- ph_email: '이메일 주소', ph_pw: '비밀번호', ph_new_pw: '비밀번호 (4자 이상)',
14
- ph_pw_confirm: '비밀번호 확인', ph_name: '이름', ph_nick: '닉네임',
15
- btn_login: '로그인', btn_register: '가입하기',
16
- no_account: '계정이 없으신가요?', go_register: '회원가입',
17
- have_account: '이미 계정이 있나요?', go_login: '로그인',
18
- reg_title: '계정 만들기', reg_sub: 'Lattice AI 워크스페이스에 참여하세요',
19
- err_pw_mismatch: '비밀번호가 일치하지 않습니다.',
20
- err_fill: '모든 항목을 입력해주세요.',
21
- err_login_fail: '이메일 또는 비밀번호가 틀렸습니다.',
22
- err_server: '서버 연결 실패',
23
- sso_divider: '조직 계정으로 로그인', sso_btn: '로 로그인',
24
- ms_sso: 'Microsoft Entra ID로 계속하기', okta_sso: 'Okta SSO로 계속하기',
25
- local_start: '로컬 계정으로 시작', help: '도움말', privacy: '개인정보 처리방침',
26
- language_btn: '🌐 한국어',
27
- sso_unavailable: 'SSO가 아직 설정되지 않았습니다. 로컬 계정으로 시작하거나 관리자에게 문의하세요.',
28
- },
29
- en: {
30
- login_title: 'Lattice AI', login_sub: 'Your personal AI workspace<br>starts on this PC',
31
- ph_email: 'Email address', ph_pw: 'Password', ph_new_pw: 'Password (min. 4 chars)',
32
- ph_pw_confirm: 'Confirm password', ph_name: 'Full name', ph_nick: 'Nickname',
33
- btn_login: 'Log in', btn_register: 'Sign up',
34
- no_account: "Don't have an account?", go_register: 'Sign up',
35
- have_account: 'Already have an account?', go_login: 'Log in',
36
- reg_title: 'Create Account', reg_sub: 'Join the Lattice AI workspace',
37
- err_pw_mismatch: 'Passwords do not match.',
38
- err_fill: 'Please fill in all fields.',
39
- err_login_fail: 'Invalid email or password.',
40
- err_server: 'Server connection failed',
41
- sso_divider: 'Sign in with organization account', sso_btn: 'Sign in with',
42
- ms_sso: 'Continue with Microsoft Entra ID', okta_sso: 'Continue with Okta SSO',
43
- local_start: 'Start with a local account', help: 'Help', privacy: 'Privacy Policy',
44
- language_btn: '🌐 English',
45
- sso_unavailable: 'SSO is not configured yet. Start with a local account or contact your administrator.',
46
- }
47
- };
48
-
49
- let lang = localStorage.getItem('ltcai_lang') || 'ko';
50
- function t(k) { return (I18N[lang] || I18N.ko)[k] || k; }
51
-
52
- function applyI18n() {
53
- document.documentElement.lang = lang;
54
- document.getElementById('lang-btn').textContent = t('language_btn');
55
- document.getElementById('login-title').textContent = t('login_title');
56
- document.getElementById('login-sub').innerHTML = t('login_sub');
57
- document.getElementById('reg-title').textContent = t('reg_title');
58
- document.getElementById('reg-sub').textContent = t('reg_sub');
59
- document.getElementById('login-btn').textContent = t('btn_login');
60
- document.getElementById('reg-btn').textContent = t('btn_register');
61
- document.getElementById('go-register-link').textContent = t('go_register');
62
- document.getElementById('have-account-text').textContent = t('have_account');
63
- document.getElementById('go-login-link').textContent = t('go_login');
64
- document.getElementById('login-email').placeholder = t('ph_email');
65
- document.getElementById('login-pw').placeholder = t('ph_pw');
66
- document.getElementById('reg-email').placeholder = t('ph_email');
67
- document.getElementById('reg-pw').placeholder = t('ph_new_pw');
68
- document.getElementById('reg-pw2').placeholder = t('ph_pw_confirm');
69
- document.getElementById('reg-name').placeholder = t('ph_name');
70
- document.getElementById('reg-nick').placeholder = t('ph_nick');
71
- document.getElementById('sso-divider-text').textContent = t('sso_divider');
72
- document.getElementById('sso-ms-label').textContent = t('ms_sso');
73
- document.getElementById('sso-okta-label').textContent = t('okta_sso');
74
- document.getElementById('local-start-label').textContent = t('local_start');
75
- document.getElementById('help-link').textContent = t('help');
76
- document.getElementById('privacy-link').textContent = t('privacy');
77
- ['ko', 'en'].forEach(l => {
78
- const el = document.getElementById(`opt-${l}`);
79
- if (el) el.classList.toggle('active', l === lang);
80
- });
81
- }
82
-
83
- async function initSSO() {
84
- try {
85
- const res = await apiFetch('/auth/sso/config');
86
- if (!res.ok) return;
87
- const cfg = await res.json();
88
- if (cfg.enabled) {
89
- window._ssoEnabled = true;
90
- window._ssoProviderName = cfg.provider_name;
91
- applyI18n();
92
- }
93
- } catch {}
94
- }
95
-
96
- function doSSOLogin(provider) {
97
- if (!window._ssoEnabled) {
98
- setMsg('login-msg', t('sso_unavailable'));
99
- return;
100
- }
101
- if (provider) sessionStorage.setItem('ltcai_sso_provider_hint', provider);
102
- window.location.href = '/auth/sso/login';
103
- }
104
-
105
- function togglePasswordVisibility() {
106
- const input = document.getElementById('login-pw');
107
- input.type = input.type === 'password' ? 'text' : 'password';
108
- }
109
-
110
- function toggleLang() {
111
- const m = document.getElementById('lang-menu');
112
- m.classList.toggle('open');
113
- }
114
-
115
- function setLang(l) {
116
- lang = l;
117
- localStorage.setItem('ltcai_lang', l);
118
- document.getElementById('lang-menu').classList.remove('open');
119
- applyI18n();
120
- }
121
-
122
- document.addEventListener('click', e => {
123
- if (!e.target.closest('.lang-wrap'))
124
- document.getElementById('lang-menu').classList.remove('open');
125
- });
126
-
127
- function showSection(name) {
128
- document.getElementById('login-section').style.display = name === 'login' ? '' : 'none';
129
- document.getElementById('register-section').style.display = name === 'register' ? '' : 'none';
130
- document.getElementById('login-msg').textContent = '';
131
- document.getElementById('reg-msg').textContent = '';
132
- }
133
-
134
- function setMsg(id, text, ok = false) {
135
- const el = document.getElementById(id);
136
- el.textContent = text;
137
- el.className = 'msg' + (ok ? ' ok' : '');
138
- }
139
-
140
- function requestSetupAfterLogin() {
141
- try {
142
- sessionStorage.setItem('ltcai_force_setup_after_login', 'true');
143
- } catch (_) {}
144
- }
145
-
146
- async function doLogin() {
147
- const email = document.getElementById('login-email').value.trim();
148
- const password = document.getElementById('login-pw').value;
149
- if (!email || !password) { setMsg('login-msg', t('err_fill')); return; }
150
- const btn = document.getElementById('login-btn');
151
- btn.disabled = true;
152
- btn.textContent = '...';
153
- try {
154
- const res = await apiFetch('/login', {
155
- method: 'POST',
156
- headers: { 'Content-Type': 'application/json' },
157
- body: JSON.stringify({ email, password })
158
- });
159
- if (res.ok) {
160
- const data = await res.json();
161
- localStorage.setItem('ltcai_user_email', data.email);
162
- localStorage.setItem('ltcai_user_nickname', data.nickname || data.name || data.email);
163
- localStorage.setItem('ltcai_is_admin', data.is_admin ? 'true' : 'false');
164
- requestSetupAfterLogin();
165
- window.location.href = '/app';
166
- } else {
167
- const data = await res.json().catch(() => ({}));
168
- setMsg('login-msg', data.detail || t('err_login_fail'));
169
- btn.disabled = false;
170
- btn.textContent = t('btn_login');
171
- }
172
- } catch {
173
- setMsg('login-msg', t('err_server'));
174
- btn.disabled = false;
175
- btn.textContent = t('btn_login');
176
- }
177
- }
178
-
179
- async function doRegister() {
180
- const email = document.getElementById('reg-email').value.trim();
181
- const pw = document.getElementById('reg-pw').value;
182
- const pw2 = document.getElementById('reg-pw2').value;
183
- const name = document.getElementById('reg-name').value.trim();
184
- const nickname = document.getElementById('reg-nick').value.trim();
185
- if (!email || !pw || !name || !nickname) { setMsg('reg-msg', t('err_fill')); return; }
186
- if (pw !== pw2) { setMsg('reg-msg', t('err_pw_mismatch')); return; }
187
- const btn = document.getElementById('reg-btn');
188
- btn.disabled = true;
189
- btn.textContent = '...';
190
- try {
191
- const res = await apiFetch('/register', {
192
- method: 'POST',
193
- headers: { 'Content-Type': 'application/json' },
194
- body: JSON.stringify({ email, password: pw, name, nickname })
195
- });
196
- if (res.ok) {
197
- setMsg('reg-msg', lang === 'ko' ? '가입 완료! 로그인 중...' : 'Registered! Logging in...', true);
198
- await apiFetch('/login', {
199
- method: 'POST',
200
- headers: { 'Content-Type': 'application/json' },
201
- body: JSON.stringify({ email, password: pw })
202
- }).then(r => r.ok ? r.json() : null).then(data => {
203
- if (data) {
204
- localStorage.setItem('ltcai_user_email', data.email);
205
- localStorage.setItem('ltcai_user_nickname', data.nickname || data.name || data.email);
206
- localStorage.setItem('ltcai_is_admin', data.is_admin ? 'true' : 'false');
207
- requestSetupAfterLogin();
208
- window.location.href = '/app';
209
- }
210
- });
211
- } else {
212
- const data = await res.json().catch(() => ({}));
213
- setMsg('reg-msg', data.detail || '가입 실패');
214
- btn.disabled = false;
215
- btn.textContent = t('btn_register');
216
- }
217
- } catch {
218
- setMsg('reg-msg', t('err_server'));
219
- btn.disabled = false;
220
- btn.textContent = t('btn_register');
221
- }
222
- }
223
-
224
- // If already logged in, skip to the v3 workspace shell.
225
- apiFetch('/account/profile').then(r => {
226
- if (r.ok) window.location.href = '/app';
227
- }).catch(() => {});
228
-
229
- initSSO();
230
-
231
- // Handle invite code in URL
232
- const urlCode = new URLSearchParams(window.location.search).get('code');
233
- if (urlCode) {
234
- document.getElementById('reg-email').focus();
235
- showSection('register');
236
- }
237
-
238
- applyI18n();