stellavault 0.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 (294) hide show
  1. package/.env.example +12 -0
  2. package/CLAUDE.md +39 -0
  3. package/CONTRIBUTING.md +65 -0
  4. package/LICENSE +21 -0
  5. package/README.md +182 -0
  6. package/memory/MEMORY.md +25 -0
  7. package/package.json +33 -0
  8. package/packages/cli/bin/ekh.js +2 -0
  9. package/packages/cli/bin/stellavault.js +2 -0
  10. package/packages/cli/dist/commands/brief-cmd.d.ts +2 -0
  11. package/packages/cli/dist/commands/brief-cmd.d.ts.map +1 -0
  12. package/packages/cli/dist/commands/brief-cmd.js +82 -0
  13. package/packages/cli/dist/commands/brief-cmd.js.map +1 -0
  14. package/packages/cli/dist/commands/capture-cmd.d.ts +7 -0
  15. package/packages/cli/dist/commands/capture-cmd.d.ts.map +1 -0
  16. package/packages/cli/dist/commands/capture-cmd.js +31 -0
  17. package/packages/cli/dist/commands/capture-cmd.js.map +1 -0
  18. package/packages/cli/dist/commands/card-cmd.d.ts +4 -0
  19. package/packages/cli/dist/commands/card-cmd.d.ts.map +1 -0
  20. package/packages/cli/dist/commands/card-cmd.js +26 -0
  21. package/packages/cli/dist/commands/card-cmd.js.map +1 -0
  22. package/packages/cli/dist/commands/clip-cmd.d.ts +4 -0
  23. package/packages/cli/dist/commands/clip-cmd.d.ts.map +1 -0
  24. package/packages/cli/dist/commands/clip-cmd.js +151 -0
  25. package/packages/cli/dist/commands/clip-cmd.js.map +1 -0
  26. package/packages/cli/dist/commands/cloud-cmd.d.ts +4 -0
  27. package/packages/cli/dist/commands/cloud-cmd.d.ts.map +1 -0
  28. package/packages/cli/dist/commands/cloud-cmd.js +64 -0
  29. package/packages/cli/dist/commands/cloud-cmd.js.map +1 -0
  30. package/packages/cli/dist/commands/contradictions-cmd.d.ts +2 -0
  31. package/packages/cli/dist/commands/contradictions-cmd.d.ts.map +1 -0
  32. package/packages/cli/dist/commands/contradictions-cmd.js +34 -0
  33. package/packages/cli/dist/commands/contradictions-cmd.js.map +1 -0
  34. package/packages/cli/dist/commands/decay-cmd.d.ts +2 -0
  35. package/packages/cli/dist/commands/decay-cmd.d.ts.map +1 -0
  36. package/packages/cli/dist/commands/decay-cmd.js +48 -0
  37. package/packages/cli/dist/commands/decay-cmd.js.map +1 -0
  38. package/packages/cli/dist/commands/digest-cmd.d.ts +4 -0
  39. package/packages/cli/dist/commands/digest-cmd.d.ts.map +1 -0
  40. package/packages/cli/dist/commands/digest-cmd.js +79 -0
  41. package/packages/cli/dist/commands/digest-cmd.js.map +1 -0
  42. package/packages/cli/dist/commands/duplicates-cmd.d.ts +4 -0
  43. package/packages/cli/dist/commands/duplicates-cmd.d.ts.map +1 -0
  44. package/packages/cli/dist/commands/duplicates-cmd.js +30 -0
  45. package/packages/cli/dist/commands/duplicates-cmd.js.map +1 -0
  46. package/packages/cli/dist/commands/federate-cmd.d.ts +5 -0
  47. package/packages/cli/dist/commands/federate-cmd.d.ts.map +1 -0
  48. package/packages/cli/dist/commands/federate-cmd.js +217 -0
  49. package/packages/cli/dist/commands/federate-cmd.js.map +1 -0
  50. package/packages/cli/dist/commands/gaps-cmd.d.ts +2 -0
  51. package/packages/cli/dist/commands/gaps-cmd.d.ts.map +1 -0
  52. package/packages/cli/dist/commands/gaps-cmd.js +33 -0
  53. package/packages/cli/dist/commands/gaps-cmd.js.map +1 -0
  54. package/packages/cli/dist/commands/graph-cmd.d.ts +2 -0
  55. package/packages/cli/dist/commands/graph-cmd.d.ts.map +1 -0
  56. package/packages/cli/dist/commands/graph-cmd.js +77 -0
  57. package/packages/cli/dist/commands/graph-cmd.js.map +1 -0
  58. package/packages/cli/dist/commands/index-cmd.d.ts +2 -0
  59. package/packages/cli/dist/commands/index-cmd.d.ts.map +1 -0
  60. package/packages/cli/dist/commands/index-cmd.js +57 -0
  61. package/packages/cli/dist/commands/index-cmd.js.map +1 -0
  62. package/packages/cli/dist/commands/init-cmd.d.ts +2 -0
  63. package/packages/cli/dist/commands/init-cmd.d.ts.map +1 -0
  64. package/packages/cli/dist/commands/init-cmd.js +123 -0
  65. package/packages/cli/dist/commands/init-cmd.js.map +1 -0
  66. package/packages/cli/dist/commands/learn-cmd.d.ts +2 -0
  67. package/packages/cli/dist/commands/learn-cmd.d.ts.map +1 -0
  68. package/packages/cli/dist/commands/learn-cmd.js +48 -0
  69. package/packages/cli/dist/commands/learn-cmd.js.map +1 -0
  70. package/packages/cli/dist/commands/pack-cmd.d.ts +15 -0
  71. package/packages/cli/dist/commands/pack-cmd.d.ts.map +1 -0
  72. package/packages/cli/dist/commands/pack-cmd.js +93 -0
  73. package/packages/cli/dist/commands/pack-cmd.js.map +1 -0
  74. package/packages/cli/dist/commands/review-cmd.d.ts +4 -0
  75. package/packages/cli/dist/commands/review-cmd.d.ts.map +1 -0
  76. package/packages/cli/dist/commands/review-cmd.js +107 -0
  77. package/packages/cli/dist/commands/review-cmd.js.map +1 -0
  78. package/packages/cli/dist/commands/search-cmd.d.ts +4 -0
  79. package/packages/cli/dist/commands/search-cmd.d.ts.map +1 -0
  80. package/packages/cli/dist/commands/search-cmd.js +38 -0
  81. package/packages/cli/dist/commands/search-cmd.js.map +1 -0
  82. package/packages/cli/dist/commands/serve-cmd.d.ts +2 -0
  83. package/packages/cli/dist/commands/serve-cmd.d.ts.map +1 -0
  84. package/packages/cli/dist/commands/serve-cmd.js +14 -0
  85. package/packages/cli/dist/commands/serve-cmd.js.map +1 -0
  86. package/packages/cli/dist/commands/status-cmd.d.ts +2 -0
  87. package/packages/cli/dist/commands/status-cmd.d.ts.map +1 -0
  88. package/packages/cli/dist/commands/status-cmd.js +33 -0
  89. package/packages/cli/dist/commands/status-cmd.js.map +1 -0
  90. package/packages/cli/dist/commands/sync-cmd.d.ts +5 -0
  91. package/packages/cli/dist/commands/sync-cmd.d.ts.map +1 -0
  92. package/packages/cli/dist/commands/sync-cmd.js +62 -0
  93. package/packages/cli/dist/commands/sync-cmd.js.map +1 -0
  94. package/packages/cli/dist/commands/vault-cmd.d.ts +10 -0
  95. package/packages/cli/dist/commands/vault-cmd.d.ts.map +1 -0
  96. package/packages/cli/dist/commands/vault-cmd.js +54 -0
  97. package/packages/cli/dist/commands/vault-cmd.js.map +1 -0
  98. package/packages/cli/dist/index.d.ts +2 -0
  99. package/packages/cli/dist/index.d.ts.map +1 -0
  100. package/packages/cli/dist/index.js +156 -0
  101. package/packages/cli/dist/index.js.map +1 -0
  102. package/packages/cli/package.json +24 -0
  103. package/packages/cli/src/commands/brief-cmd.ts +87 -0
  104. package/packages/cli/src/commands/capture-cmd.ts +34 -0
  105. package/packages/cli/src/commands/card-cmd.ts +29 -0
  106. package/packages/cli/src/commands/clip-cmd.ts +172 -0
  107. package/packages/cli/src/commands/cloud-cmd.ts +75 -0
  108. package/packages/cli/src/commands/contradictions-cmd.ts +41 -0
  109. package/packages/cli/src/commands/decay-cmd.ts +57 -0
  110. package/packages/cli/src/commands/digest-cmd.ts +89 -0
  111. package/packages/cli/src/commands/duplicates-cmd.ts +38 -0
  112. package/packages/cli/src/commands/federate-cmd.ts +236 -0
  113. package/packages/cli/src/commands/gaps-cmd.ts +40 -0
  114. package/packages/cli/src/commands/graph-cmd.ts +88 -0
  115. package/packages/cli/src/commands/index-cmd.ts +65 -0
  116. package/packages/cli/src/commands/init-cmd.ts +145 -0
  117. package/packages/cli/src/commands/learn-cmd.ts +56 -0
  118. package/packages/cli/src/commands/pack-cmd.ts +121 -0
  119. package/packages/cli/src/commands/review-cmd.ts +125 -0
  120. package/packages/cli/src/commands/search-cmd.ts +45 -0
  121. package/packages/cli/src/commands/serve-cmd.ts +17 -0
  122. package/packages/cli/src/commands/status-cmd.ts +37 -0
  123. package/packages/cli/src/commands/sync-cmd.ts +68 -0
  124. package/packages/cli/src/commands/vault-cmd.ts +64 -0
  125. package/packages/cli/src/index.ts +187 -0
  126. package/packages/core/package.json +40 -0
  127. package/packages/core/src/api/dashboard.ts +138 -0
  128. package/packages/core/src/api/graph-data.ts +286 -0
  129. package/packages/core/src/api/pwa.ts +82 -0
  130. package/packages/core/src/api/server.ts +660 -0
  131. package/packages/core/src/capture/voice.ts +168 -0
  132. package/packages/core/src/cloud/index.ts +2 -0
  133. package/packages/core/src/cloud/sync.ts +167 -0
  134. package/packages/core/src/config.ts +82 -0
  135. package/packages/core/src/federation/credits.ts +80 -0
  136. package/packages/core/src/federation/hyperswarm.d.ts +19 -0
  137. package/packages/core/src/federation/identity.ts +90 -0
  138. package/packages/core/src/federation/index.ts +8 -0
  139. package/packages/core/src/federation/node.ts +235 -0
  140. package/packages/core/src/federation/privacy.ts +52 -0
  141. package/packages/core/src/federation/reputation.ts +202 -0
  142. package/packages/core/src/federation/search.ts +129 -0
  143. package/packages/core/src/federation/sharing.ts +165 -0
  144. package/packages/core/src/federation/trust.ts +76 -0
  145. package/packages/core/src/federation/types.ts +25 -0
  146. package/packages/core/src/i18n/index.ts +85 -0
  147. package/packages/core/src/index.ts +133 -0
  148. package/packages/core/src/indexer/chunker.ts +180 -0
  149. package/packages/core/src/indexer/embedder.ts +9 -0
  150. package/packages/core/src/indexer/index.ts +113 -0
  151. package/packages/core/src/indexer/local-embedder.ts +35 -0
  152. package/packages/core/src/indexer/scanner.ts +142 -0
  153. package/packages/core/src/indexer/watcher.ts +62 -0
  154. package/packages/core/src/intelligence/contradiction-detector.ts +134 -0
  155. package/packages/core/src/intelligence/decay-engine.ts +229 -0
  156. package/packages/core/src/intelligence/duplicate-detector.ts +71 -0
  157. package/packages/core/src/intelligence/fsrs.ts +79 -0
  158. package/packages/core/src/intelligence/gap-detector.ts +109 -0
  159. package/packages/core/src/intelligence/learning-path.ts +86 -0
  160. package/packages/core/src/intelligence/notifications.ts +106 -0
  161. package/packages/core/src/intelligence/predictive-gaps.ts +94 -0
  162. package/packages/core/src/intelligence/semantic-versioning.ts +97 -0
  163. package/packages/core/src/intelligence/types.ts +28 -0
  164. package/packages/core/src/mcp/custom-tools.ts +97 -0
  165. package/packages/core/src/mcp/index.ts +1 -0
  166. package/packages/core/src/mcp/server.ts +142 -0
  167. package/packages/core/src/mcp/tools/agentic-graph.ts +96 -0
  168. package/packages/core/src/mcp/tools/brief.ts +49 -0
  169. package/packages/core/src/mcp/tools/decay.ts +40 -0
  170. package/packages/core/src/mcp/tools/decision-journal.ts +95 -0
  171. package/packages/core/src/mcp/tools/export.ts +72 -0
  172. package/packages/core/src/mcp/tools/federated-search.ts +43 -0
  173. package/packages/core/src/mcp/tools/generate-claude-md.ts +130 -0
  174. package/packages/core/src/mcp/tools/get-document.ts +26 -0
  175. package/packages/core/src/mcp/tools/get-related.ts +41 -0
  176. package/packages/core/src/mcp/tools/learning-path.ts +52 -0
  177. package/packages/core/src/mcp/tools/list-topics.ts +20 -0
  178. package/packages/core/src/mcp/tools/search.ts +35 -0
  179. package/packages/core/src/mcp/tools/snapshot.ts +98 -0
  180. package/packages/core/src/multi-vault/index.ts +118 -0
  181. package/packages/core/src/pack/creator.ts +127 -0
  182. package/packages/core/src/pack/exporter.ts +21 -0
  183. package/packages/core/src/pack/importer.ts +82 -0
  184. package/packages/core/src/pack/index.ts +5 -0
  185. package/packages/core/src/pack/marketplace.ts +103 -0
  186. package/packages/core/src/pack/pii-masker.ts +38 -0
  187. package/packages/core/src/pack/types.ts +39 -0
  188. package/packages/core/src/plugins/index.ts +100 -0
  189. package/packages/core/src/plugins/webhooks.ts +110 -0
  190. package/packages/core/src/search/bm25.ts +16 -0
  191. package/packages/core/src/search/index.ts +83 -0
  192. package/packages/core/src/search/rrf.ts +31 -0
  193. package/packages/core/src/search/semantic.ts +15 -0
  194. package/packages/core/src/store/index.ts +2 -0
  195. package/packages/core/src/store/sqlite-vec.ts +290 -0
  196. package/packages/core/src/store/types.ts +22 -0
  197. package/packages/core/src/team/index.ts +126 -0
  198. package/packages/core/src/types/chunk.ts +25 -0
  199. package/packages/core/src/types/document.ts +24 -0
  200. package/packages/core/src/types/graph.ts +44 -0
  201. package/packages/core/src/types/index.ts +15 -0
  202. package/packages/core/src/types/search.ts +38 -0
  203. package/packages/core/src/utils/retry.ts +85 -0
  204. package/packages/core/tests/api-card.test.ts +60 -0
  205. package/packages/core/tests/api-routes.test.ts +98 -0
  206. package/packages/core/tests/bm25.test.ts +87 -0
  207. package/packages/core/tests/chunker.test.ts +48 -0
  208. package/packages/core/tests/cluster.test.ts +75 -0
  209. package/packages/core/tests/constellation.test.ts +77 -0
  210. package/packages/core/tests/export-utils.test.ts +97 -0
  211. package/packages/core/tests/fsrs.test.ts +96 -0
  212. package/packages/core/tests/gesture-detector.test.ts +45 -0
  213. package/packages/core/tests/graph-data.test.ts +87 -0
  214. package/packages/core/tests/layout.test.ts +83 -0
  215. package/packages/core/tests/mcp.test.ts +148 -0
  216. package/packages/core/tests/pack.test.ts +127 -0
  217. package/packages/core/tests/pii-masker.test.ts +42 -0
  218. package/packages/core/tests/profile-card.test.ts +62 -0
  219. package/packages/core/tests/rrf.test.ts +29 -0
  220. package/packages/core/tests/search-integration.test.ts +139 -0
  221. package/packages/core/tests/store.test.ts +80 -0
  222. package/packages/graph/click-result.png +0 -0
  223. package/packages/graph/index.html +17 -0
  224. package/packages/graph/package.json +32 -0
  225. package/packages/graph/src/App.tsx +7 -0
  226. package/packages/graph/src/api/client.ts +39 -0
  227. package/packages/graph/src/components/ClusterFilter.tsx +73 -0
  228. package/packages/graph/src/components/ConstellationView.tsx +232 -0
  229. package/packages/graph/src/components/ExportPanel.tsx +177 -0
  230. package/packages/graph/src/components/Graph3D.tsx +230 -0
  231. package/packages/graph/src/components/GraphEdges.tsx +100 -0
  232. package/packages/graph/src/components/GraphNodes.tsx +386 -0
  233. package/packages/graph/src/components/HealthDashboard.tsx +173 -0
  234. package/packages/graph/src/components/Layout.tsx +214 -0
  235. package/packages/graph/src/components/MotionOverlay.tsx +81 -0
  236. package/packages/graph/src/components/MotionToggle.tsx +33 -0
  237. package/packages/graph/src/components/MultiverseView.tsx +286 -0
  238. package/packages/graph/src/components/NodeDetail.tsx +232 -0
  239. package/packages/graph/src/components/PulseParticle.tsx +232 -0
  240. package/packages/graph/src/components/SearchBar.tsx +107 -0
  241. package/packages/graph/src/components/StarField.tsx +197 -0
  242. package/packages/graph/src/components/StatusBar.tsx +53 -0
  243. package/packages/graph/src/components/Timeline.tsx +148 -0
  244. package/packages/graph/src/components/ToolsPanel.tsx +512 -0
  245. package/packages/graph/src/components/Tooltip.tsx +100 -0
  246. package/packages/graph/src/components/TypeFilter.tsx +131 -0
  247. package/packages/graph/src/embed/EmbedGraph.tsx +144 -0
  248. package/packages/graph/src/hooks/useConstellationLOD.ts +76 -0
  249. package/packages/graph/src/hooks/useDecay.ts +37 -0
  250. package/packages/graph/src/hooks/useExport.ts +165 -0
  251. package/packages/graph/src/hooks/useGraph.ts +69 -0
  252. package/packages/graph/src/hooks/useKeyboardNav.ts +122 -0
  253. package/packages/graph/src/hooks/useLayout.ts +45 -0
  254. package/packages/graph/src/hooks/useMotion.ts +120 -0
  255. package/packages/graph/src/hooks/usePulse.ts +58 -0
  256. package/packages/graph/src/hooks/useSearch.ts +71 -0
  257. package/packages/graph/src/lib/constellation.ts +107 -0
  258. package/packages/graph/src/lib/export-utils.ts +48 -0
  259. package/packages/graph/src/lib/gesture-detector.ts +123 -0
  260. package/packages/graph/src/lib/layout.worker.ts +153 -0
  261. package/packages/graph/src/lib/motion-controller.ts +83 -0
  262. package/packages/graph/src/lib/profile-card.ts +122 -0
  263. package/packages/graph/src/main.tsx +4 -0
  264. package/packages/graph/src/stores/graph-store.ts +155 -0
  265. package/packages/graph/success.png +0 -0
  266. package/packages/graph/test-click.mjs +49 -0
  267. package/packages/graph/test-explore.mjs +102 -0
  268. package/packages/graph/test-final.mjs +61 -0
  269. package/packages/graph/test-graph.mjs +139 -0
  270. package/packages/graph/test-hover.mjs +48 -0
  271. package/packages/graph/test-pulse.mjs +68 -0
  272. package/packages/graph/test-screenshot.mjs +56 -0
  273. package/packages/graph/test-v2.mjs +97 -0
  274. package/packages/graph/vite.config.ts +15 -0
  275. package/packages/sync/.env.example +11 -0
  276. package/packages/sync/.sync-state.json +317 -0
  277. package/packages/sync/.upload-state.json +1009 -0
  278. package/packages/sync/create-stella-network-notion.mjs +151 -0
  279. package/packages/sync/create-stellavault-project-notion.mjs +322 -0
  280. package/packages/sync/logs/sync-2026-03-28.log +6 -0
  281. package/packages/sync/logs/sync-2026-03-29.log +12 -0
  282. package/packages/sync/logs/sync-2026-03-30.log +6 -0
  283. package/packages/sync/logs/sync-2026-03-31.log +6 -0
  284. package/packages/sync/logs/sync-2026-04-01.log +6 -0
  285. package/packages/sync/logs/sync-2026-04-02.log +6 -0
  286. package/packages/sync/package-lock.json +373 -0
  287. package/packages/sync/package.json +16 -0
  288. package/packages/sync/run-sync.bat +18 -0
  289. package/packages/sync/run-sync.mjs +46 -0
  290. package/packages/sync/setup-scheduler.mjs +119 -0
  291. package/packages/sync/structured-sync.mjs +187 -0
  292. package/packages/sync/sync-to-obsidian.mjs +264 -0
  293. package/packages/sync/upload-pdca-to-notion.mjs +495 -0
  294. package/tsconfig.base.json +18 -0
@@ -0,0 +1,214 @@
1
+ // 대시보드 레이아웃 — 호버=툴팁, 클릭=사이드패널, 모션 제어
2
+
3
+ import { useState, useRef } from 'react';
4
+ import { Graph3D } from './Graph3D.js';
5
+ import { NodeDetail } from './NodeDetail.js';
6
+ import { StatusBar } from './StatusBar.js';
7
+ import { SearchBar } from './SearchBar.js';
8
+ import { ClusterFilter } from './ClusterFilter.js';
9
+ import { TypeFilter } from './TypeFilter.js';
10
+ import { Timeline } from './Timeline.js';
11
+ import { MotionToggle } from './MotionToggle.js';
12
+ import { MotionOverlay } from './MotionOverlay.js';
13
+ import { useMotion } from '../hooks/useMotion.js';
14
+ import { useGraphStore } from '../stores/graph-store.js';
15
+ import { ToolsPanel } from './ToolsPanel.js';
16
+ import { MultiverseView } from './MultiverseView.js';
17
+
18
+ export function Layout() {
19
+ const viewMode = useGraphStore((s) => s.viewMode);
20
+ const setViewMode = useGraphStore((s) => s.setViewMode);
21
+
22
+ // 멀티버스 모드면 멀티버스 뷰 렌더링
23
+ if (viewMode === 'multiverse') {
24
+ return <MultiverseView />;
25
+ }
26
+
27
+ // 기존 universe 모드
28
+ const error = useGraphStore((s) => s.error);
29
+ const loading = useGraphStore((s) => s.loading);
30
+ const selectedNodeId = useGraphStore((s) => s.selectedNodeId);
31
+ const mode = useGraphStore((s) => s.mode);
32
+ const setMode = useGraphStore((s) => s.setMode);
33
+ const theme = useGraphStore((s) => s.theme);
34
+ const toggleTheme = useGraphStore((s) => s.toggleTheme);
35
+ const showDecay = useGraphStore((s) => s.showDecayOverlay);
36
+ const toggleDecay = useGraphStore((s) => s.toggleDecayOverlay);
37
+ const showConstellation = useGraphStore((s) => s.showConstellation);
38
+ const toggleConstellation = useGraphStore((s) => s.toggleConstellation);
39
+ const showTimeline = useGraphStore((s) => s.showTimeline);
40
+ const toggleTimeline = useGraphStore((s) => s.toggleTimeline);
41
+ const isDark = theme === 'dark';
42
+
43
+ // 모션 제어
44
+ const controlsRef = useRef<any>(null);
45
+ // controlsRef는 Graph3D 안의 OrbitControls — window로 전달
46
+ const getControls = () => (window as any).__sv_controls ?? controlsRef;
47
+ const motion = useMotion(getControls());
48
+ const [motionActive, setMotionActive] = useState(false);
49
+ const [motionLoading, setMotionLoading] = useState(false);
50
+ const [motionVideo, setMotionVideo] = useState<HTMLVideoElement | null>(null);
51
+
52
+ const toggleMotion = async () => {
53
+ if (motionActive) {
54
+ motion.stop();
55
+ setMotionActive(false);
56
+ setMotionVideo(null);
57
+ } else {
58
+ setMotionLoading(true);
59
+ try {
60
+ await motion.start();
61
+ setMotionActive(true);
62
+ setMotionVideo(motion.videoRef.current);
63
+ } catch (err) {
64
+ console.error('Motion start failed:', err);
65
+ }
66
+ setMotionLoading(false);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <div style={{ display: 'flex', flexDirection: 'column', height: '100vh', background: isDark ? '#050510' : '#f0f2f8' }}>
72
+ {/* Header */}
73
+ <div style={{
74
+ padding: '8px 16px',
75
+ background: isDark ? 'rgba(10, 10, 20, 0.8)' : 'rgba(240, 242, 248, 0.95)',
76
+ borderBottom: `1px solid ${isDark ? 'rgba(100, 120, 255, 0.1)' : 'rgba(0, 0, 0, 0.08)'}`,
77
+ display: 'flex',
78
+ alignItems: 'center',
79
+ gap: '12px',
80
+ backdropFilter: 'blur(8px)',
81
+ position: 'relative',
82
+ zIndex: 10,
83
+ }}>
84
+ <button
85
+ onClick={() => setViewMode('multiverse')}
86
+ style={{
87
+ padding: '4px 10px', fontSize: '11px', border: `1px solid ${isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)'}`,
88
+ borderRadius: '4px', cursor: 'pointer',
89
+ background: isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)',
90
+ color: isDark ? '#88aaff' : '#4466aa', marginRight: '8px',
91
+ }}
92
+ >
93
+ Multiverse
94
+ </button>
95
+ <span style={{ fontSize: '14px', fontWeight: 600, color: isDark ? '#c0c0f0' : '#2a2a4a', letterSpacing: '0.5px' }}>
96
+ Stellavault
97
+ </span>
98
+ <span style={{ color: isDark ? 'rgba(100, 120, 255, 0.5)' : 'rgba(60, 60, 120, 0.5)', fontSize: '12px', marginRight: '12px' }}>
99
+ Neural Knowledge Graph
100
+ </span>
101
+ <SearchBar />
102
+ <div style={{
103
+ marginLeft: 'auto', display: 'flex', gap: '6px', alignItems: 'center',
104
+ }}>
105
+ <MotionToggle active={motionActive} loading={motionLoading} onToggle={toggleMotion} isDark={isDark} />
106
+ <div style={{
107
+ display: 'flex', gap: '2px',
108
+ background: isDark ? 'rgba(100, 120, 255, 0.08)' : 'rgba(0, 0, 0, 0.04)',
109
+ borderRadius: '6px', padding: '2px',
110
+ }}>
111
+ {(['semantic', 'folder'] as const).map((m) => {
112
+ const active = mode === m;
113
+ return (
114
+ <button
115
+ key={m}
116
+ onClick={() => setMode(m)}
117
+ style={{
118
+ padding: '4px 12px', fontSize: '11px', border: 'none', borderRadius: '4px', cursor: 'pointer',
119
+ background: active
120
+ ? (isDark ? 'rgba(100, 120, 255, 0.3)' : 'rgba(0, 0, 0, 0.08)')
121
+ : 'transparent',
122
+ color: active
123
+ ? (isDark ? '#c0d0ff' : '#2a2a4a')
124
+ : (isDark ? '#556' : '#888'),
125
+ fontWeight: active ? 600 : 400,
126
+ }}
127
+ >
128
+ {m === 'semantic' ? 'AI Semantic' : 'Obsidian Folders'}
129
+ </button>
130
+ );
131
+ })}
132
+ </div>
133
+ <button
134
+ onClick={toggleConstellation}
135
+ style={{
136
+ padding: '4px 8px', fontSize: '11px',
137
+ border: `1px solid ${showConstellation ? (isDark ? 'rgba(100,200,255,0.3)' : 'rgba(0,100,200,0.2)') : (isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)')}`,
138
+ borderRadius: '4px', cursor: 'pointer',
139
+ background: showConstellation ? (isDark ? 'rgba(100,200,255,0.15)' : 'rgba(0,100,200,0.08)') : (isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)'),
140
+ color: showConstellation ? (isDark ? '#66ccff' : '#0066aa') : (isDark ? '#aab' : '#555'),
141
+ }}
142
+ >
143
+ Stars
144
+ </button>
145
+ <ClusterFilter />
146
+ <TypeFilter />
147
+ <button
148
+ onClick={toggleTimeline}
149
+ style={{
150
+ padding: '4px 8px', fontSize: '11px',
151
+ border: `1px solid ${showTimeline ? (isDark ? 'rgba(100,180,255,0.3)' : 'rgba(0,100,200,0.2)') : (isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)')}`,
152
+ borderRadius: '4px', cursor: 'pointer',
153
+ background: showTimeline ? (isDark ? 'rgba(100,180,255,0.15)' : 'rgba(0,100,200,0.08)') : (isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)'),
154
+ color: showTimeline ? (isDark ? '#66aaff' : '#0066aa') : (isDark ? '#aab' : '#555'),
155
+ }}
156
+ >
157
+ Timeline
158
+ </button>
159
+ <button
160
+ onClick={toggleDecay}
161
+ style={{
162
+ padding: '4px 8px', fontSize: '11px',
163
+ border: `1px solid ${showDecay ? (isDark ? 'rgba(255,150,50,0.3)' : 'rgba(200,100,0,0.2)') : (isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)')}`,
164
+ borderRadius: '4px', cursor: 'pointer',
165
+ background: showDecay ? (isDark ? 'rgba(255,150,50,0.15)' : 'rgba(200,100,0,0.08)') : (isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)'),
166
+ color: showDecay ? (isDark ? '#ffaa44' : '#aa6600') : (isDark ? '#aab' : '#555'),
167
+ }}
168
+ >
169
+ Decay
170
+ </button>
171
+ <button
172
+ onClick={toggleTheme}
173
+ style={{
174
+ padding: '4px 8px', fontSize: '11px',
175
+ border: `1px solid ${isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)'}`,
176
+ borderRadius: '4px', cursor: 'pointer',
177
+ background: isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)',
178
+ color: isDark ? '#aab' : '#555',
179
+ }}
180
+ >
181
+ {isDark ? 'Light' : 'Dark'}
182
+ </button>
183
+ </div>
184
+ {loading && (
185
+ <span style={{ fontSize: '11px', color: '#88aaff' }}>
186
+ {mode === 'semantic' ? 'Mapping neural connections...' : 'Loading folder structure...'}
187
+ </span>
188
+ )}
189
+ </div>
190
+
191
+ {error && (
192
+ <div style={{ padding: '10px 16px', background: 'rgba(255, 50, 50, 0.1)', color: '#ef4444', fontSize: '12px' }}>
193
+ {error}
194
+ </div>
195
+ )}
196
+
197
+ {/* Main */}
198
+ <div style={{ flex: 1, display: 'flex', overflow: 'hidden', position: 'relative' }}>
199
+ <div style={{ flex: 1, minWidth: 0 }}>
200
+ <Graph3D />
201
+ {/* 모션 웹캠 PIP */}
202
+ {motionActive && (
203
+ <MotionOverlay videoElement={motionVideo} currentGesture={motion.currentGesture} />
204
+ )}
205
+ </div>
206
+ {selectedNodeId && <NodeDetail />}
207
+ </div>
208
+
209
+ <Timeline />
210
+ <ToolsPanel />
211
+ <StatusBar />
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,81 @@
1
+ // 웹캠 PIP 미리보기 + 현재 제스처 상태 표시
2
+
3
+ import { useEffect, useRef } from 'react';
4
+
5
+ const GESTURE_LABELS: Record<string, string> = {
6
+ rotate: '✋ Rotate',
7
+ pan: '✊ Pan',
8
+ zoom: '🤏 Zoom',
9
+ select: '👆 Select',
10
+ reset: '👋 Reset',
11
+ none: '...',
12
+ };
13
+
14
+ interface Props {
15
+ videoElement: HTMLVideoElement | null;
16
+ currentGesture: React.RefObject<string>;
17
+ }
18
+
19
+ export function MotionOverlay({ videoElement, currentGesture }: Props) {
20
+ const canvasRef = useRef<HTMLCanvasElement>(null);
21
+ const labelRef = useRef<HTMLDivElement>(null);
22
+
23
+ useEffect(() => {
24
+ if (!videoElement || !canvasRef.current) return;
25
+
26
+ const canvas = canvasRef.current;
27
+ const ctx = canvas.getContext('2d');
28
+ if (!ctx) return;
29
+
30
+ let animId: number;
31
+
32
+ function draw() {
33
+ if (!videoElement || !ctx) return;
34
+ ctx.save();
35
+ // 좌우 반전 (거울 모드)
36
+ ctx.scale(-1, 1);
37
+ ctx.drawImage(videoElement, -canvas.width, 0, canvas.width, canvas.height);
38
+ ctx.restore();
39
+
40
+ // 제스처 라벨 업데이트
41
+ if (labelRef.current) {
42
+ const g = currentGesture.current ?? 'none';
43
+ labelRef.current.textContent = GESTURE_LABELS[g] ?? g;
44
+ }
45
+
46
+ animId = requestAnimationFrame(draw);
47
+ }
48
+
49
+ draw();
50
+ return () => cancelAnimationFrame(animId);
51
+ }, [videoElement, currentGesture]);
52
+
53
+ if (!videoElement) return null;
54
+
55
+ return (
56
+ <div style={{
57
+ position: 'absolute', bottom: '16px', right: '16px',
58
+ borderRadius: '12px', overflow: 'hidden',
59
+ border: '1px solid rgba(100, 120, 255, 0.3)',
60
+ boxShadow: '0 4px 20px rgba(0,0,0,0.5)',
61
+ zIndex: 50,
62
+ }}>
63
+ <canvas
64
+ ref={canvasRef}
65
+ width={192}
66
+ height={144}
67
+ style={{ display: 'block', background: '#111' }}
68
+ />
69
+ <div
70
+ ref={labelRef}
71
+ style={{
72
+ position: 'absolute', bottom: '6px', left: '50%', transform: 'translateX(-50%)',
73
+ background: 'rgba(0,0,0,0.7)', padding: '2px 10px', borderRadius: '10px',
74
+ fontSize: '11px', color: '#88aaff', whiteSpace: 'nowrap',
75
+ }}
76
+ >
77
+ ...
78
+ </div>
79
+ </div>
80
+ );
81
+ }
@@ -0,0 +1,33 @@
1
+ // 모션 ON/OFF 토글 버튼
2
+
3
+ interface Props {
4
+ active: boolean;
5
+ loading: boolean;
6
+ onToggle: () => void;
7
+ isDark?: boolean;
8
+ }
9
+
10
+ export function MotionToggle({ active, loading, onToggle, isDark = true }: Props) {
11
+ return (
12
+ <button
13
+ onClick={onToggle}
14
+ disabled={loading}
15
+ style={{
16
+ padding: '4px 10px',
17
+ fontSize: '11px',
18
+ border: `1px solid ${isDark ? 'rgba(100,120,255,0.15)' : 'rgba(0,0,0,0.12)'}`,
19
+ borderRadius: '4px',
20
+ cursor: loading ? 'wait' : 'pointer',
21
+ background: active
22
+ ? (isDark ? 'rgba(100,255,120,0.2)' : 'rgba(40,160,80,0.1)')
23
+ : (isDark ? 'rgba(100,120,255,0.06)' : 'rgba(0,0,0,0.03)'),
24
+ color: active
25
+ ? (isDark ? '#88ff88' : '#2a8a4a')
26
+ : (isDark ? '#aab' : '#555'),
27
+ transition: 'all 0.2s',
28
+ }}
29
+ >
30
+ {loading ? 'Loading...' : active ? '✋ Motion ON' : '✋ Motion'}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,286 @@
1
+ // Design Ref: Phase 1c — 멀티버스 뷰
2
+ // "내 우주"가 대형 구체, 피어 우주들이 주변에 연결
3
+
4
+ import { useRef, useMemo, useCallback } from 'react';
5
+ import { Canvas, useFrame } from '@react-three/fiber';
6
+ import { OrbitControls, Text, Billboard } from '@react-three/drei';
7
+ import * as THREE from 'three';
8
+ import { useGraphStore } from '../stores/graph-store.js';
9
+
10
+ const UNIVERSE_COLORS = [
11
+ '#6366f1', '#ec4899', '#10b981', '#f59e0b', '#3b82f6',
12
+ '#8b5cf6', '#ef4444', '#06b6d4', '#84cc16', '#f97316',
13
+ ];
14
+
15
+ interface UniverseData {
16
+ id: string;
17
+ name: string;
18
+ documentCount: number;
19
+ topTopics: string[];
20
+ position: [number, number, number];
21
+ color: string;
22
+ isMe: boolean;
23
+ }
24
+
25
+ function UniverseNode({ data, onClick }: { data: UniverseData; onClick: () => void }) {
26
+ const meshRef = useRef<THREE.Mesh>(null);
27
+ const glowRef = useRef<THREE.Mesh>(null);
28
+ const theme = useGraphStore((s) => s.theme);
29
+ const isDark = theme === 'dark';
30
+
31
+ // 크기: 문서 수 기반 (최소 2, 최대 8)
32
+ const size = Math.max(2, Math.min(8, Math.sqrt(data.documentCount / 50) * 2));
33
+ const myScale = data.isMe ? 1.5 : 1;
34
+
35
+ useFrame((state) => {
36
+ if (meshRef.current) {
37
+ // 부드러운 회전
38
+ meshRef.current.rotation.y += data.isMe ? 0.003 : 0.001;
39
+ }
40
+ if (glowRef.current) {
41
+ // 호흡 효과
42
+ const pulse = 1 + Math.sin(state.clock.elapsedTime * (data.isMe ? 1.5 : 0.8)) * 0.1;
43
+ glowRef.current.scale.setScalar(pulse);
44
+ }
45
+ });
46
+
47
+ const color = new THREE.Color(data.color);
48
+
49
+ return (
50
+ <group position={data.position} onClick={onClick} onPointerOver={() => { document.body.style.cursor = 'pointer'; }} onPointerOut={() => { document.body.style.cursor = 'default'; }}>
51
+ {/* 글로우 */}
52
+ <mesh ref={glowRef}>
53
+ <sphereGeometry args={[size * myScale * 1.4, 16, 16]} />
54
+ <meshBasicMaterial
55
+ color={color}
56
+ transparent
57
+ opacity={isDark ? 0.08 : 0.04}
58
+ depthWrite={false}
59
+ />
60
+ </mesh>
61
+
62
+ {/* 코어 구체 */}
63
+ <mesh ref={meshRef}>
64
+ <sphereGeometry args={[size * myScale, 32, 32]} />
65
+ <meshStandardMaterial
66
+ color={color}
67
+ emissive={color}
68
+ emissiveIntensity={data.isMe ? 0.4 : 0.15}
69
+ roughness={0.3}
70
+ metalness={0.7}
71
+ transparent
72
+ opacity={0.9}
73
+ />
74
+ </mesh>
75
+
76
+ {/* 라벨 */}
77
+ <Billboard position={[0, size * myScale + 2, 0]}>
78
+ <Text
79
+ fontSize={data.isMe ? 1.8 : 1.2}
80
+ color={isDark ? '#c0c0f0' : '#2a2a4a'}
81
+ anchorX="center"
82
+ anchorY="bottom"
83
+ font={undefined}
84
+ >
85
+ {data.isMe ? 'My Universe' : data.name}
86
+ </Text>
87
+ <Text
88
+ fontSize={0.8}
89
+ color={isDark ? '#556' : '#999'}
90
+ anchorX="center"
91
+ anchorY="top"
92
+ position={[0, -0.3, 0]}
93
+ font={undefined}
94
+ >
95
+ {`${data.documentCount} docs`}
96
+ </Text>
97
+ </Billboard>
98
+
99
+ {/* 토픽 태그 (내 우주만) */}
100
+ {data.isMe && data.topTopics.length > 0 && (
101
+ <Billboard position={[0, -(size * myScale + 1.5), 0]}>
102
+ <Text
103
+ fontSize={0.6}
104
+ color={isDark ? '#445' : '#bbb'}
105
+ anchorX="center"
106
+ font={undefined}
107
+ >
108
+ {data.topTopics.slice(0, 3).map(t => `#${t}`).join(' ')}
109
+ </Text>
110
+ </Billboard>
111
+ )}
112
+ </group>
113
+ );
114
+ }
115
+
116
+ function MultiverseEdges({ universes }: { universes: UniverseData[] }) {
117
+ const theme = useGraphStore((s) => s.theme);
118
+ const isDark = theme === 'dark';
119
+ const myUniverse = universes.find(u => u.isMe);
120
+ if (!myUniverse) return null;
121
+
122
+ const positions = useMemo(() => {
123
+ const pts: number[] = [];
124
+ for (const u of universes) {
125
+ if (u.isMe) continue;
126
+ pts.push(...myUniverse.position, ...u.position);
127
+ }
128
+ return new Float32Array(pts);
129
+ }, [universes, myUniverse]);
130
+
131
+ return (
132
+ <lineSegments>
133
+ <bufferGeometry>
134
+ <bufferAttribute attach="attributes-position" args={[positions, 3]} />
135
+ </bufferGeometry>
136
+ <lineBasicMaterial
137
+ color={isDark ? '#6366f1' : '#3b82f6'}
138
+ transparent
139
+ opacity={0.2}
140
+ linewidth={1}
141
+ />
142
+ </lineSegments>
143
+ );
144
+ }
145
+
146
+ function MultiverseScene() {
147
+ const nodes = useGraphStore((s) => s.nodes);
148
+ const peers = useGraphStore((s) => s.federationPeers);
149
+ const setViewMode = useGraphStore((s) => s.setViewMode);
150
+ const theme = useGraphStore((s) => s.theme);
151
+ const isDark = theme === 'dark';
152
+
153
+ // 우주 데이터 구성
154
+ const universes = useMemo<UniverseData[]>(() => {
155
+ const result: UniverseData[] = [];
156
+
157
+ // 내 우주 (중앙)
158
+ result.push({
159
+ id: 'me',
160
+ name: 'My Universe',
161
+ documentCount: nodes.length,
162
+ topTopics: [],
163
+ position: [0, 0, 0],
164
+ color: '#6366f1',
165
+ isMe: true,
166
+ });
167
+
168
+ // 피어 우주들 (원형 배치)
169
+ for (let i = 0; i < peers.length; i++) {
170
+ const angle = (i / Math.max(peers.length, 1)) * Math.PI * 2 - Math.PI / 2;
171
+ const radius = 30 + peers.length * 5;
172
+ result.push({
173
+ id: peers[i].peerId,
174
+ name: peers[i].displayName,
175
+ documentCount: peers[i].documentCount,
176
+ topTopics: peers[i].topTopics,
177
+ position: [
178
+ radius * Math.cos(angle),
179
+ (Math.random() - 0.5) * 10,
180
+ radius * Math.sin(angle),
181
+ ],
182
+ color: UNIVERSE_COLORS[i % UNIVERSE_COLORS.length],
183
+ isMe: false,
184
+ });
185
+ }
186
+
187
+ return result;
188
+ }, [nodes.length, peers]);
189
+
190
+ const handleUniverseClick = useCallback((u: UniverseData) => {
191
+ if (u.isMe) {
192
+ setViewMode('universe');
193
+ }
194
+ }, [setViewMode]);
195
+
196
+ return (
197
+ <>
198
+ <ambientLight intensity={0.3} />
199
+ <pointLight position={[0, 50, 50]} intensity={0.8} />
200
+
201
+ {universes.map((u) => (
202
+ <UniverseNode key={u.id} data={u} onClick={() => handleUniverseClick(u)} />
203
+ ))}
204
+
205
+ <MultiverseEdges universes={universes} />
206
+
207
+ <OrbitControls
208
+ enablePan
209
+ enableZoom
210
+ minDistance={20}
211
+ maxDistance={200}
212
+ autoRotate
213
+ autoRotateSpeed={0.3}
214
+ />
215
+
216
+ {/* 배경 파티클 */}
217
+ <points>
218
+ <bufferGeometry>
219
+ <bufferAttribute
220
+ attach="attributes-position"
221
+ args={[new Float32Array(Array.from({ length: 300 }, () => (Math.random() - 0.5) * 300)), 3]}
222
+ />
223
+ </bufferGeometry>
224
+ <pointsMaterial
225
+ size={0.3}
226
+ color={isDark ? '#334' : '#ddd'}
227
+ transparent
228
+ opacity={0.5}
229
+ depthWrite={false}
230
+ />
231
+ </points>
232
+ </>
233
+ );
234
+ }
235
+
236
+ export function MultiverseView() {
237
+ const theme = useGraphStore((s) => s.theme);
238
+ const isDark = theme === 'dark';
239
+ const setViewMode = useGraphStore((s) => s.setViewMode);
240
+ const peers = useGraphStore((s) => s.federationPeers);
241
+
242
+ return (
243
+ <div style={{ width: '100%', height: '100%', position: 'relative' }}>
244
+ <Canvas camera={{ position: [0, 30, 80], fov: 50 }}>
245
+ <color attach="background" args={[isDark ? '#020208' : '#f0f2f8']} />
246
+ <fog attach="fog" args={[isDark ? '#020208' : '#f0f2f8', 100, 250]} />
247
+ <MultiverseScene />
248
+ </Canvas>
249
+
250
+ {/* 오버레이 UI */}
251
+ <div style={{
252
+ position: 'absolute', top: '16px', left: '16px',
253
+ color: isDark ? '#c0c0f0' : '#2a2a4a',
254
+ fontSize: '12px',
255
+ }}>
256
+ <div style={{ fontSize: '16px', fontWeight: 700, marginBottom: '4px' }}>
257
+ Stella Network
258
+ </div>
259
+ <div style={{ color: isDark ? '#556' : '#999' }}>
260
+ {peers.length} peer{peers.length !== 1 ? 's' : ''} connected
261
+ </div>
262
+ </div>
263
+
264
+ <div style={{
265
+ position: 'absolute', bottom: '16px', left: '50%', transform: 'translateX(-50%)',
266
+ color: isDark ? '#556' : '#999', fontSize: '11px',
267
+ }}>
268
+ Click "My Universe" to enter your knowledge graph
269
+ </div>
270
+
271
+ <button
272
+ onClick={() => setViewMode('universe')}
273
+ style={{
274
+ position: 'absolute', top: '16px', right: '16px',
275
+ padding: '6px 14px', fontSize: '11px',
276
+ background: isDark ? 'rgba(100,120,255,0.1)' : 'rgba(0,0,0,0.04)',
277
+ border: `1px solid ${isDark ? 'rgba(100,120,255,0.2)' : 'rgba(0,0,0,0.1)'}`,
278
+ borderRadius: '6px', cursor: 'pointer',
279
+ color: isDark ? '#88aaff' : '#4466aa',
280
+ }}
281
+ >
282
+ Enter My Universe
283
+ </button>
284
+ </div>
285
+ );
286
+ }