ltcai 4.0.0 → 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 (195) hide show
  1. package/README.md +42 -33
  2. package/desktop/electron/main.cjs +44 -0
  3. package/docs/CHANGELOG.md +106 -0
  4. package/docs/REALTIME_COLLABORATION.md +3 -3
  5. package/docs/V3_FRONTEND.md +9 -8
  6. package/docs/V4_1_FRONTEND_ARCHITECTURE_REVIEW.md +65 -0
  7. package/docs/V4_1_FRONTEND_MIGRATION_REPORT.md +70 -0
  8. package/docs/V4_1_VALIDATION_REPORT.md +47 -0
  9. package/docs/V4_DIGITAL_BRAIN_RECOVERY.md +95 -45
  10. package/docs/kg-schema.md +6 -2
  11. package/docs/spec-vs-impl.md +10 -10
  12. package/frontend/index.html +24 -0
  13. package/frontend/openapi.json +14190 -0
  14. package/frontend/src/App.tsx +184 -0
  15. package/frontend/src/api/client.ts +317 -0
  16. package/frontend/src/api/openapi.ts +16637 -0
  17. package/frontend/src/components/primitives.tsx +204 -0
  18. package/frontend/src/components/ui/badge.tsx +27 -0
  19. package/frontend/src/components/ui/button.tsx +37 -0
  20. package/frontend/src/components/ui/card.tsx +22 -0
  21. package/frontend/src/components/ui/input.tsx +16 -0
  22. package/frontend/src/components/ui/textarea.tsx +16 -0
  23. package/frontend/src/lib/utils.ts +33 -0
  24. package/frontend/src/main.tsx +23 -0
  25. package/frontend/src/pages/Act.tsx +245 -0
  26. package/frontend/src/pages/Ask.tsx +200 -0
  27. package/frontend/src/pages/Brain.tsx +267 -0
  28. package/frontend/src/pages/Capture.tsx +158 -0
  29. package/frontend/src/pages/Library.tsx +187 -0
  30. package/frontend/src/pages/System.tsx +344 -0
  31. package/frontend/src/routes.ts +85 -0
  32. package/frontend/src/store/appStore.ts +54 -0
  33. package/frontend/src/styles.css +107 -0
  34. package/kg_schema.py +2 -603
  35. package/knowledge_graph.py +37 -4958
  36. package/latticeai/__init__.py +1 -1
  37. package/latticeai/api/admin.py +15 -16
  38. package/latticeai/api/agents.py +13 -6
  39. package/latticeai/api/auth.py +19 -11
  40. package/latticeai/api/invitations.py +100 -0
  41. package/latticeai/api/knowledge_graph.py +4 -11
  42. package/latticeai/api/plugins.py +3 -6
  43. package/latticeai/api/realtime.py +4 -7
  44. package/latticeai/api/setup.py +5 -4
  45. package/latticeai/api/static_routes.py +13 -16
  46. package/latticeai/api/ui_redirects.py +26 -0
  47. package/latticeai/api/workflow_designer.py +39 -6
  48. package/latticeai/api/workspace.py +24 -10
  49. package/latticeai/app_factory.py +88 -17
  50. package/latticeai/brain/_kg_common.py +1123 -0
  51. package/latticeai/brain/discovery.py +1455 -0
  52. package/latticeai/brain/documents.py +218 -0
  53. package/latticeai/brain/ingest.py +644 -0
  54. package/latticeai/brain/projection.py +561 -0
  55. package/latticeai/brain/provenance.py +401 -0
  56. package/latticeai/brain/retrieval.py +1316 -0
  57. package/latticeai/brain/schema.py +640 -0
  58. package/latticeai/brain/store.py +216 -0
  59. package/latticeai/brain/write_master.py +225 -0
  60. package/latticeai/core/invitations.py +131 -0
  61. package/latticeai/core/marketplace.py +1 -1
  62. package/latticeai/core/multi_agent.py +1 -1
  63. package/latticeai/core/policy.py +54 -0
  64. package/latticeai/core/realtime.py +65 -44
  65. package/latticeai/core/sessions.py +31 -5
  66. package/latticeai/core/users.py +147 -0
  67. package/latticeai/core/workspace_os.py +420 -20
  68. package/latticeai/services/agent_runtime.py +242 -4
  69. package/latticeai/services/run_executor.py +328 -0
  70. package/latticeai/services/workspace_service.py +27 -19
  71. package/package.json +54 -27
  72. package/scripts/build_frontend_assets.mjs +38 -0
  73. package/scripts/bump_version.py +1 -1
  74. package/scripts/export_openapi.py +31 -0
  75. package/scripts/lint_frontend.mjs +86 -0
  76. package/scripts/run_python.mjs +47 -0
  77. package/src-tauri/Cargo.lock +4833 -0
  78. package/src-tauri/Cargo.toml +19 -0
  79. package/src-tauri/build.rs +3 -0
  80. package/src-tauri/capabilities/default.json +7 -0
  81. package/src-tauri/src/main.rs +78 -0
  82. package/src-tauri/tauri.conf.json +36 -0
  83. package/static/app/asset-manifest.json +32 -0
  84. package/static/app/assets/core-CwxXejkd.js +2 -0
  85. package/static/app/assets/core-CwxXejkd.js.map +1 -0
  86. package/static/app/assets/index-CJRAzNnf.js +333 -0
  87. package/static/app/assets/index-CJRAzNnf.js.map +1 -0
  88. package/static/app/assets/index-CSwBBgf4.css +2 -0
  89. package/static/app/index.html +25 -0
  90. package/static/manifest.json +2 -2
  91. package/static/sw.js +4 -4
  92. package/scripts/build_v3_assets.mjs +0 -170
  93. package/scripts/lint_v3.mjs +0 -97
  94. package/static/account.html +0 -113
  95. package/static/activity.html +0 -73
  96. package/static/admin.html +0 -486
  97. package/static/agents.html +0 -139
  98. package/static/chat.html +0 -841
  99. package/static/css/reference/account.css +0 -439
  100. package/static/css/reference/admin.css +0 -610
  101. package/static/css/reference/base.css +0 -1661
  102. package/static/css/reference/chat.css +0 -4623
  103. package/static/css/reference/graph.css +0 -1016
  104. package/static/css/responsive.css +0 -861
  105. package/static/graph.html +0 -122
  106. package/static/platform.css +0 -104
  107. package/static/plugins.html +0 -136
  108. package/static/scripts/account.js +0 -238
  109. package/static/scripts/admin.js +0 -1614
  110. package/static/scripts/chat.js +0 -5081
  111. package/static/scripts/graph.js +0 -1804
  112. package/static/scripts/platform.js +0 -64
  113. package/static/scripts/ux.js +0 -167
  114. package/static/scripts/workspace.js +0 -948
  115. package/static/v3/asset-manifest.json +0 -56
  116. package/static/v3/css/lattice.base.49deefb5.css +0 -128
  117. package/static/v3/css/lattice.base.css +0 -128
  118. package/static/v3/css/lattice.components.cde18231.css +0 -472
  119. package/static/v3/css/lattice.components.css +0 -472
  120. package/static/v3/css/lattice.shell.29d36d85.css +0 -452
  121. package/static/v3/css/lattice.shell.css +0 -452
  122. package/static/v3/css/lattice.tokens.304cbc40.css +0 -135
  123. package/static/v3/css/lattice.tokens.css +0 -135
  124. package/static/v3/css/lattice.views.0a18b6c5.css +0 -360
  125. package/static/v3/css/lattice.views.css +0 -360
  126. package/static/v3/index.html +0 -68
  127. package/static/v3/js/app.356e6452.js +0 -26
  128. package/static/v3/js/app.js +0 -26
  129. package/static/v3/js/core/api.7a308b89.js +0 -568
  130. package/static/v3/js/core/api.js +0 -568
  131. package/static/v3/js/core/components.f25b3b93.js +0 -230
  132. package/static/v3/js/core/components.js +0 -230
  133. package/static/v3/js/core/dom.a2773eb0.js +0 -148
  134. package/static/v3/js/core/dom.js +0 -148
  135. package/static/v3/js/core/router.584570f2.js +0 -37
  136. package/static/v3/js/core/router.js +0 -37
  137. package/static/v3/js/core/routes.7222343d.js +0 -93
  138. package/static/v3/js/core/routes.js +0 -93
  139. package/static/v3/js/core/shell.a1657f20.js +0 -391
  140. package/static/v3/js/core/shell.js +0 -391
  141. package/static/v3/js/core/store.204a08b2.js +0 -113
  142. package/static/v3/js/core/store.js +0 -113
  143. package/static/v3/js/views/admin-audit.660a1fb1.js +0 -185
  144. package/static/v3/js/views/admin-audit.js +0 -185
  145. package/static/v3/js/views/admin-permissions.a7ae5f09.js +0 -177
  146. package/static/v3/js/views/admin-permissions.js +0 -177
  147. package/static/v3/js/views/admin-policies.3658fd86.js +0 -102
  148. package/static/v3/js/views/admin-policies.js +0 -102
  149. package/static/v3/js/views/admin-private-vpc.7d342d36.js +0 -135
  150. package/static/v3/js/views/admin-private-vpc.js +0 -135
  151. package/static/v3/js/views/admin-security.07c66b72.js +0 -180
  152. package/static/v3/js/views/admin-security.js +0 -180
  153. package/static/v3/js/views/admin-users.03bac88c.js +0 -168
  154. package/static/v3/js/views/admin-users.js +0 -168
  155. package/static/v3/js/views/agents.014d0b74.js +0 -541
  156. package/static/v3/js/views/agents.js +0 -541
  157. package/static/v3/js/views/chat.e6dd7dd0.js +0 -601
  158. package/static/v3/js/views/chat.js +0 -601
  159. package/static/v3/js/views/files.adad14c1.js +0 -365
  160. package/static/v3/js/views/files.js +0 -365
  161. package/static/v3/js/views/graph-canvas.17c15d65.js +0 -509
  162. package/static/v3/js/views/graph-canvas.js +0 -509
  163. package/static/v3/js/views/home.24f8b8ae.js +0 -200
  164. package/static/v3/js/views/home.js +0 -200
  165. package/static/v3/js/views/hooks.37895880.js +0 -220
  166. package/static/v3/js/views/hooks.js +0 -220
  167. package/static/v3/js/views/hybrid-search.2fb63ed9.js +0 -194
  168. package/static/v3/js/views/hybrid-search.js +0 -194
  169. package/static/v3/js/views/knowledge-graph.5e40cbeb.js +0 -509
  170. package/static/v3/js/views/knowledge-graph.js +0 -509
  171. package/static/v3/js/views/marketplace.ab0583d4.js +0 -141
  172. package/static/v3/js/views/marketplace.js +0 -141
  173. package/static/v3/js/views/mcp.99b5c6a7.js +0 -114
  174. package/static/v3/js/views/mcp.js +0 -114
  175. package/static/v3/js/views/memory.4ebdf474.js +0 -147
  176. package/static/v3/js/views/memory.js +0 -147
  177. package/static/v3/js/views/models.a1ffa147.js +0 -256
  178. package/static/v3/js/views/models.js +0 -256
  179. package/static/v3/js/views/my-computer.d9d9ae1c.js +0 -463
  180. package/static/v3/js/views/my-computer.js +0 -463
  181. package/static/v3/js/views/pipeline.c522f1ce.js +0 -157
  182. package/static/v3/js/views/pipeline.js +0 -157
  183. package/static/v3/js/views/planning.9ac3e313.js +0 -153
  184. package/static/v3/js/views/planning.js +0 -153
  185. package/static/v3/js/views/settings.8631fa5e.js +0 -318
  186. package/static/v3/js/views/settings.js +0 -318
  187. package/static/v3/js/views/skills.c6c2f965.js +0 -109
  188. package/static/v3/js/views/skills.js +0 -109
  189. package/static/v3/js/views/tools.e4f11276.js +0 -108
  190. package/static/v3/js/views/tools.js +0 -108
  191. package/static/v3/js/views/workflows.26c57290.js +0 -128
  192. package/static/v3/js/views/workflows.js +0 -128
  193. package/static/workflows.html +0 -146
  194. package/static/workspace.css +0 -1121
  195. package/static/workspace.html +0 -357
package/static/graph.html DELETED
@@ -1,122 +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="stylesheet" href="/static/vendor/fonts/inter.css">
9
- <link rel="stylesheet" href="/static/vendor/icons/tabler-icons.min.css">
10
- <link rel="stylesheet" href="/static/css/tokens.css">
11
- <link rel="stylesheet" href="/static/css/reference/base.css">
12
- <link rel="stylesheet" href="/static/css/reference/account.css">
13
- <link rel="stylesheet" href="/static/css/reference/admin.css">
14
- <link rel="stylesheet" href="/static/css/reference/graph.css">
15
- <link rel="stylesheet" href="/static/css/reference/chat.css">
16
- <link rel="stylesheet" href="/static/css/responsive.css">
17
- </head>
18
- <body class="lattice-ref-graph">
19
- <div class="sidebar-overlay" onclick="closeGraphNav&&closeGraphNav()"></div>
20
- <button class="graph-nav-toggle icon-btn" onclick="toggleGraphNav&&toggleGraphNav()" title="메뉴" aria-label="네비게이션 열기"><i class="ti ti-menu-2"></i></button>
21
- <aside class="reference-rail graph-rail">
22
- <div class="rail-brand"><i class="ti ti-chart-dots-3"></i><strong>Lattice AI</strong></div>
23
- <nav>
24
- <a href="/chat"><i class="ti ti-home"></i> 홈</a>
25
- <a href="/workspace"><i class="ti ti-layout-dashboard"></i> Workspace OS</a>
26
- <a class="active" href="/graph"><i class="ti ti-chart-dots-3"></i> 지식 그래프</a>
27
- <a href="/chat"><i class="ti ti-message-circle"></i> 대화</a>
28
- <a href="/chat"><i class="ti ti-file"></i> 파일</a>
29
- <a href="/chat"><i class="ti ti-code"></i> 코드</a>
30
- <a href="/chat"><i class="ti ti-settings"></i> 설정</a>
31
- </nav>
32
- <div class="rail-project">
33
- <span>프로젝트</span>
34
- <strong>Lattice AI Platform</strong>
35
- </div>
36
- </aside>
37
- <div class="app">
38
- <main class="stage">
39
- <canvas id="graph"></canvas>
40
- <canvas id="minimap" class="graph-minimap" width="180" height="120" title="미니맵"></canvas>
41
- <div id="graph-card-list" class="graph-card-list"></div>
42
-
43
- <section class="search-shell">
44
- <div class="search-head">
45
- <div class="search-title">
46
- <strong>Explore the graph</strong>
47
- <span>Search topics, files, conversations, decisions, and tasks.</span>
48
- </div>
49
- <div class="search-count" id="search-count">Ready</div>
50
- </div>
51
- <div class="search-input-wrap">
52
- <div class="search-input-row">
53
- <input id="search" class="search-input" placeholder="Search by topic, file, or conversation..." autocomplete="off">
54
- <button id="clear-search-btn" class="icon-btn" title="Clear search">×</button>
55
- </div>
56
- </div>
57
- <div id="search-results" class="search-results">
58
- <p class="search-empty">검색 결과는 여기에 표시됩니다. 키워드를 입력하면 서버 검색 결과를 불러오고, 항목을 누르면 해당 노드로 바로 이동합니다.</p>
59
- </div>
60
- </section>
61
-
62
- <div class="toolbar graph-toolbar">
63
- <button class="tb-btn" id="zoom-out-btn" title="축소" aria-label="축소"><i class="ti ti-minus"></i></button>
64
- <button class="tb-btn" id="zoom-in-btn" title="확대" aria-label="확대"><i class="ti ti-plus"></i></button>
65
- <button class="tb-btn" id="fullscreen-btn" title="전체화면" aria-label="전체화면"><i class="ti ti-maximize"></i></button>
66
- <button class="tb-btn graph-view-toggle" id="view-toggle-btn" title="그래프/카드 보기 전환" aria-label="그래프/카드 보기 전환"><i class="ti ti-layout-cards"></i></button>
67
- <button class="tb-btn" id="refresh-btn"><i class="ti ti-refresh"></i> Refresh</button>
68
- <button class="tb-btn" id="fit-btn" title="Fit graph"><i class="ti ti-arrows-maximize"></i> Fit</button>
69
- <button class="tb-btn" id="expand-btn" title="Expand selected node"><i class="ti ti-circle-plus"></i> Expand</button>
70
- <button class="tb-btn" id="collapse-btn" title="Collapse selected neighbors"><i class="ti ti-circle-minus"></i> Collapse</button>
71
- <button class="tb-btn" id="focus-btn" title="Focus selected subgraph"><i class="ti ti-focus-2"></i> Focus</button>
72
- <button class="tb-btn" id="path-btn" title="Shortest path from saved start"><i class="ti ti-route"></i> Path</button>
73
- <div class="lang-picker" id="graph-lang-picker">
74
- <button class="tb-btn" id="graph-lang-btn" type="button" onclick="toggleLangMenu('graph-lang-picker')">Language</button>
75
- <div class="lang-picker-menu" id="graph-lang-picker-menu">
76
- <div class="lang-option" id="graph-lang-ko" onclick="setLang('ko')">🇰🇷 한국어</div>
77
- <div class="lang-option" id="graph-lang-en" onclick="setLang('en')">🇺🇸 English</div>
78
- </div>
79
- </div>
80
- </div>
81
- <div id="graph-focus-chip" class="focus-chip" hidden></div>
82
- </main>
83
-
84
- <aside>
85
- <div class="sidebar-head">
86
- <div class="eyebrow">지식 그래프</div>
87
- <h1>Knowledge topology</h1>
88
- <p class="sidebar-sub">주제의 크기는 중요도 기반으로, 선의 굵기와 색은 관계 종류와 강도를 반영합니다.</p>
89
- <div class="stats-row">
90
- <div class="stat"><strong id="node-count">-</strong><span>Nodes</span></div>
91
- <div class="stat"><strong id="edge-count">-</strong><span>Edges</span></div>
92
- </div>
93
- </div>
94
-
95
- <div class="section">
96
- <div class="section-label" id="local-source-label">지식 소스</div>
97
- <div id="local-source-panel" class="local-source-panel"></div>
98
- </div>
99
-
100
- <div class="section">
101
- <div class="section-label" id="edge-label">Relationship legend</div>
102
- <div id="edge-legend" class="legend-grid"></div>
103
- </div>
104
-
105
- <div class="section">
106
- <div class="section-label" id="type-label">Node types</div>
107
- <div id="type-filters" class="filter-grid"></div>
108
- </div>
109
-
110
- <div class="detail-wrap">
111
- <div id="detail">
112
- <p class="empty-hint">노드를 클릭하면 요약, 중요도, 연결 강도, 메타데이터를 볼 수 있습니다. 검색 패널에서는 서버 검색 결과를 기준으로 더 정확하게 이동할 수 있습니다.</p>
113
- </div>
114
- </div>
115
- </aside>
116
- </div>
117
-
118
- <div id="tooltip"></div>
119
-
120
- <script src="/static/scripts/graph.js"></script>
121
- </body>
122
- </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();