@myrialabs/clopen 0.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 (456) hide show
  1. package/.env.example +6 -0
  2. package/.github/workflows/release.yml +60 -0
  3. package/.github/workflows/test.yml +40 -0
  4. package/CONTRIBUTING.md +499 -0
  5. package/LICENSE +21 -0
  6. package/README.md +209 -0
  7. package/backend/index.ts +156 -0
  8. package/backend/lib/chat/helpers.ts +42 -0
  9. package/backend/lib/chat/index.ts +2 -0
  10. package/backend/lib/chat/stream-manager.ts +1126 -0
  11. package/backend/lib/database/README.md +77 -0
  12. package/backend/lib/database/index.ts +119 -0
  13. package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
  14. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
  15. package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
  16. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
  17. package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
  18. package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
  19. package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
  20. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
  21. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
  22. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
  23. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
  24. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
  25. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
  26. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
  27. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
  28. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
  29. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
  30. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
  31. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
  32. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
  33. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
  34. package/backend/lib/database/migrations/index.ts +154 -0
  35. package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
  36. package/backend/lib/database/queries/engine-queries.ts +75 -0
  37. package/backend/lib/database/queries/index.ts +9 -0
  38. package/backend/lib/database/queries/message-queries.ts +472 -0
  39. package/backend/lib/database/queries/project-queries.ts +117 -0
  40. package/backend/lib/database/queries/session-queries.ts +271 -0
  41. package/backend/lib/database/queries/settings-queries.ts +34 -0
  42. package/backend/lib/database/queries/snapshot-queries.ts +326 -0
  43. package/backend/lib/database/queries/utils-queries.ts +59 -0
  44. package/backend/lib/database/seeders/index.ts +13 -0
  45. package/backend/lib/database/seeders/settings_seeder.ts +84 -0
  46. package/backend/lib/database/utils/connection.ts +174 -0
  47. package/backend/lib/database/utils/index.ts +4 -0
  48. package/backend/lib/database/utils/migration-runner.ts +118 -0
  49. package/backend/lib/database/utils/seeder-runner.ts +121 -0
  50. package/backend/lib/engine/adapters/claude/environment.ts +164 -0
  51. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
  52. package/backend/lib/engine/adapters/claude/index.ts +1 -0
  53. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
  54. package/backend/lib/engine/adapters/claude/stream.ts +177 -0
  55. package/backend/lib/engine/adapters/opencode/index.ts +2 -0
  56. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
  57. package/backend/lib/engine/adapters/opencode/server.ts +104 -0
  58. package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
  59. package/backend/lib/engine/index.ts +196 -0
  60. package/backend/lib/engine/types.ts +58 -0
  61. package/backend/lib/files/file-operations.ts +478 -0
  62. package/backend/lib/files/file-reading.ts +308 -0
  63. package/backend/lib/files/file-watcher.ts +383 -0
  64. package/backend/lib/files/path-browsing.ts +382 -0
  65. package/backend/lib/git/git-executor.ts +88 -0
  66. package/backend/lib/git/git-parser.ts +411 -0
  67. package/backend/lib/git/git-service.ts +505 -0
  68. package/backend/lib/mcp/README.md +1144 -0
  69. package/backend/lib/mcp/config.ts +316 -0
  70. package/backend/lib/mcp/index.ts +35 -0
  71. package/backend/lib/mcp/project-context.ts +236 -0
  72. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
  73. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
  74. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
  75. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
  76. package/backend/lib/mcp/servers/helper.ts +143 -0
  77. package/backend/lib/mcp/servers/index.ts +45 -0
  78. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
  79. package/backend/lib/mcp/servers/weather/index.ts +31 -0
  80. package/backend/lib/mcp/stdio-server.ts +103 -0
  81. package/backend/lib/mcp/types.ts +65 -0
  82. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
  83. package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
  84. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
  85. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
  86. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
  87. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
  88. package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
  89. package/backend/lib/preview/browser/browser-pool.ts +357 -0
  90. package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
  91. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
  92. package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
  93. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
  94. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
  95. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
  96. package/backend/lib/preview/browser/types.ts +359 -0
  97. package/backend/lib/preview/index.ts +24 -0
  98. package/backend/lib/project/index.ts +2 -0
  99. package/backend/lib/project/status-manager.ts +182 -0
  100. package/backend/lib/shared/index.ts +2 -0
  101. package/backend/lib/shared/port-utils.ts +25 -0
  102. package/backend/lib/shared/process-manager.ts +281 -0
  103. package/backend/lib/snapshot/blob-store.ts +227 -0
  104. package/backend/lib/snapshot/gitignore.ts +307 -0
  105. package/backend/lib/snapshot/helpers.ts +397 -0
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -0
  107. package/backend/lib/terminal/helpers.ts +14 -0
  108. package/backend/lib/terminal/index.ts +8 -0
  109. package/backend/lib/terminal/pty-manager.ts +4 -0
  110. package/backend/lib/terminal/pty-session-manager.ts +387 -0
  111. package/backend/lib/terminal/shell-utils.ts +313 -0
  112. package/backend/lib/terminal/stream-manager.ts +293 -0
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
  115. package/backend/lib/user/helpers.ts +87 -0
  116. package/backend/lib/utils/ws.ts +944 -0
  117. package/backend/lib/vite-dev.ts +353 -0
  118. package/backend/middleware/cors.ts +15 -0
  119. package/backend/middleware/error-handler.ts +49 -0
  120. package/backend/middleware/logger.ts +9 -0
  121. package/backend/types/api.ts +24 -0
  122. package/backend/ws/README.md +1505 -0
  123. package/backend/ws/chat/background.ts +198 -0
  124. package/backend/ws/chat/index.ts +21 -0
  125. package/backend/ws/chat/stream.ts +707 -0
  126. package/backend/ws/engine/claude/accounts.ts +401 -0
  127. package/backend/ws/engine/claude/index.ts +13 -0
  128. package/backend/ws/engine/claude/status.ts +43 -0
  129. package/backend/ws/engine/index.ts +14 -0
  130. package/backend/ws/engine/opencode/index.ts +11 -0
  131. package/backend/ws/engine/opencode/status.ts +30 -0
  132. package/backend/ws/engine/utils.ts +36 -0
  133. package/backend/ws/files/index.ts +30 -0
  134. package/backend/ws/files/read.ts +189 -0
  135. package/backend/ws/files/search.ts +453 -0
  136. package/backend/ws/files/watch.ts +124 -0
  137. package/backend/ws/files/write.ts +143 -0
  138. package/backend/ws/git/branch.ts +106 -0
  139. package/backend/ws/git/commit.ts +39 -0
  140. package/backend/ws/git/conflict.ts +68 -0
  141. package/backend/ws/git/diff.ts +69 -0
  142. package/backend/ws/git/index.ts +24 -0
  143. package/backend/ws/git/log.ts +41 -0
  144. package/backend/ws/git/remote.ts +214 -0
  145. package/backend/ws/git/staging.ts +84 -0
  146. package/backend/ws/git/status.ts +90 -0
  147. package/backend/ws/index.ts +69 -0
  148. package/backend/ws/mcp/index.ts +61 -0
  149. package/backend/ws/messages/crud.ts +74 -0
  150. package/backend/ws/messages/index.ts +14 -0
  151. package/backend/ws/preview/browser/cleanup.ts +129 -0
  152. package/backend/ws/preview/browser/console.ts +114 -0
  153. package/backend/ws/preview/browser/interact.ts +513 -0
  154. package/backend/ws/preview/browser/mcp.ts +129 -0
  155. package/backend/ws/preview/browser/native-ui.ts +235 -0
  156. package/backend/ws/preview/browser/stats.ts +55 -0
  157. package/backend/ws/preview/browser/tab-info.ts +126 -0
  158. package/backend/ws/preview/browser/tab.ts +166 -0
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -0
  160. package/backend/ws/preview/index.ts +146 -0
  161. package/backend/ws/projects/crud.ts +113 -0
  162. package/backend/ws/projects/index.ts +25 -0
  163. package/backend/ws/projects/presence.ts +46 -0
  164. package/backend/ws/projects/status.ts +116 -0
  165. package/backend/ws/sessions/crud.ts +327 -0
  166. package/backend/ws/sessions/index.ts +33 -0
  167. package/backend/ws/settings/crud.ts +112 -0
  168. package/backend/ws/settings/index.ts +14 -0
  169. package/backend/ws/snapshot/index.ts +17 -0
  170. package/backend/ws/snapshot/restore.ts +173 -0
  171. package/backend/ws/snapshot/timeline.ts +141 -0
  172. package/backend/ws/system/index.ts +14 -0
  173. package/backend/ws/system/operations.ts +49 -0
  174. package/backend/ws/terminal/index.ts +40 -0
  175. package/backend/ws/terminal/persistence.ts +153 -0
  176. package/backend/ws/terminal/session.ts +382 -0
  177. package/backend/ws/terminal/stream.ts +79 -0
  178. package/backend/ws/tunnel/index.ts +14 -0
  179. package/backend/ws/tunnel/operations.ts +91 -0
  180. package/backend/ws/types.ts +20 -0
  181. package/backend/ws/user/crud.ts +156 -0
  182. package/backend/ws/user/index.ts +14 -0
  183. package/bin/clopen.ts +307 -0
  184. package/bun.lock +1352 -0
  185. package/frontend/App.svelte +34 -0
  186. package/frontend/app.css +313 -0
  187. package/frontend/lib/app-environment.ts +10 -0
  188. package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
  193. package/frontend/lib/components/chat/formatters/index.ts +3 -0
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
  213. package/frontend/lib/components/chat/shared/index.ts +2 -0
  214. package/frontend/lib/components/chat/shared/utils.ts +116 -0
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -0
  239. package/frontend/lib/components/chat/tools/index.ts +26 -0
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
  252. package/frontend/lib/components/common/Alert.svelte +139 -0
  253. package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
  254. package/frontend/lib/components/common/Button.svelte +71 -0
  255. package/frontend/lib/components/common/Card.svelte +102 -0
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -0
  257. package/frontend/lib/components/common/Dialog.svelte +249 -0
  258. package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
  259. package/frontend/lib/components/common/Icon.svelte +58 -0
  260. package/frontend/lib/components/common/Input.svelte +72 -0
  261. package/frontend/lib/components/common/Lightbox.svelte +233 -0
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
  264. package/frontend/lib/components/common/Modal.svelte +177 -0
  265. package/frontend/lib/components/common/ModalProvider.svelte +28 -0
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -0
  267. package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -0
  269. package/frontend/lib/components/common/PageTemplate.svelte +76 -0
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
  271. package/frontend/lib/components/common/Select.svelte +98 -0
  272. package/frontend/lib/components/common/Textarea.svelte +80 -0
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -0
  275. package/frontend/lib/components/common/material-icons.ts +1082 -0
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
  277. package/frontend/lib/components/common/xterm/index.ts +16 -0
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
  279. package/frontend/lib/components/common/xterm/types.ts +31 -0
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
  281. package/frontend/lib/components/files/FileNode.svelte +384 -0
  282. package/frontend/lib/components/files/FileTree.svelte +681 -0
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -0
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -0
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -0
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -0
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -0
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -0
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
  291. package/frontend/lib/components/git/GitButton.svelte +33 -0
  292. package/frontend/lib/components/git/GitLog.svelte +361 -0
  293. package/frontend/lib/components/git/GitModal.svelte +80 -0
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -0
  295. package/frontend/lib/components/history/HistoryView.svelte +615 -0
  296. package/frontend/lib/components/index.ts +34 -0
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
  313. package/frontend/lib/components/preview/index.ts +2 -0
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -0
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
  326. package/frontend/lib/components/terminal/Terminal.svelte +368 -0
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
  328. package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
  350. package/frontend/lib/constants/preview.ts +45 -0
  351. package/frontend/lib/services/chat/chat.service.ts +704 -0
  352. package/frontend/lib/services/chat/index.ts +7 -0
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
  354. package/frontend/lib/services/notification/index.ts +8 -0
  355. package/frontend/lib/services/notification/push.service.ts +144 -0
  356. package/frontend/lib/services/notification/sound.service.ts +127 -0
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
  360. package/frontend/lib/services/preview/index.ts +23 -0
  361. package/frontend/lib/services/project/index.ts +8 -0
  362. package/frontend/lib/services/project/status.service.ts +159 -0
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
  364. package/frontend/lib/services/terminal/background/index.ts +130 -0
  365. package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
  367. package/frontend/lib/services/terminal/index.ts +14 -0
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -0
  369. package/frontend/lib/services/terminal/project.service.ts +953 -0
  370. package/frontend/lib/services/terminal/session.service.ts +364 -0
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -0
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -0
  373. package/frontend/lib/stores/core/files.svelte.ts +73 -0
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -0
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -0
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -0
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -0
  380. package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
  382. package/frontend/lib/stores/features/user.svelte.ts +96 -0
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
  391. package/frontend/lib/types/native-ui.ts +73 -0
  392. package/frontend/lib/utils/chat/date-separator.ts +39 -0
  393. package/frontend/lib/utils/chat/message-grouper.ts +219 -0
  394. package/frontend/lib/utils/chat/message-processor.ts +135 -0
  395. package/frontend/lib/utils/chat/tool-handler.ts +161 -0
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
  397. package/frontend/lib/utils/click-outside.ts +20 -0
  398. package/frontend/lib/utils/context-manager.ts +257 -0
  399. package/frontend/lib/utils/file-icon-mappings.ts +769 -0
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
  401. package/frontend/lib/utils/git-status.ts +68 -0
  402. package/frontend/lib/utils/platform.ts +113 -0
  403. package/frontend/lib/utils/port-check.ts +65 -0
  404. package/frontend/lib/utils/terminalFormatter.ts +207 -0
  405. package/frontend/lib/utils/theme.ts +6 -0
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -0
  407. package/frontend/lib/utils/ws.ts +44 -0
  408. package/frontend/main.ts +13 -0
  409. package/index.html +70 -0
  410. package/package.json +111 -0
  411. package/scripts/generate-icons.ts +87 -0
  412. package/scripts/pre-publish-check.sh +142 -0
  413. package/scripts/setup-hooks.sh +134 -0
  414. package/scripts/validate-branch-name.sh +47 -0
  415. package/scripts/validate-commit-msg.sh +42 -0
  416. package/shared/constants/engines.ts +134 -0
  417. package/shared/types/database/connection.ts +16 -0
  418. package/shared/types/database/index.ts +6 -0
  419. package/shared/types/database/schema.ts +141 -0
  420. package/shared/types/engine/index.ts +45 -0
  421. package/shared/types/filesystem/index.ts +22 -0
  422. package/shared/types/git.ts +171 -0
  423. package/shared/types/messaging/index.ts +239 -0
  424. package/shared/types/messaging/tool.ts +526 -0
  425. package/shared/types/network/api.ts +18 -0
  426. package/shared/types/network/index.ts +5 -0
  427. package/shared/types/stores/app.ts +23 -0
  428. package/shared/types/stores/dialog.ts +21 -0
  429. package/shared/types/stores/index.ts +3 -0
  430. package/shared/types/stores/settings.ts +15 -0
  431. package/shared/types/terminal/index.ts +44 -0
  432. package/shared/types/ui/components.ts +61 -0
  433. package/shared/types/ui/icons.ts +23 -0
  434. package/shared/types/ui/index.ts +22 -0
  435. package/shared/types/ui/notifications.ts +14 -0
  436. package/shared/types/ui/theme.ts +12 -0
  437. package/shared/types/websocket/index.ts +43 -0
  438. package/shared/types/window.d.ts +13 -0
  439. package/shared/utils/anonymous-user.ts +168 -0
  440. package/shared/utils/async.ts +10 -0
  441. package/shared/utils/diff-calculator.ts +184 -0
  442. package/shared/utils/file-type-detection.ts +166 -0
  443. package/shared/utils/logger.ts +158 -0
  444. package/shared/utils/message-formatter.ts +79 -0
  445. package/shared/utils/path.ts +47 -0
  446. package/shared/utils/ws-client.ts +768 -0
  447. package/shared/utils/ws-server.ts +660 -0
  448. package/static/audio/notification.ogg +0 -0
  449. package/static/favicon.svg +8 -0
  450. package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
  451. package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
  452. package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
  453. package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
  454. package/static/fonts/dm-sans.css +96 -0
  455. package/svelte.config.js +20 -0
  456. package/vite.config.ts +33 -0
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Browser Tab Manager
3
+ * Manages tab state and operations for BrowserPreview
4
+ */
5
+
6
+ import { debug } from '$shared/utils/logger';
7
+ import type { DeviceSize, Rotation } from '$frontend/lib/constants/preview';
8
+
9
+ // Console message type (temporary placeholder)
10
+ type ConsoleMessage = any;
11
+
12
+ // Tab interface
13
+ export interface PreviewTab {
14
+ id: string;
15
+ url: string;
16
+ title: string;
17
+ sessionId: string | null;
18
+ sessionInfo: any;
19
+ isConnected: boolean;
20
+ isStreamReady: boolean;
21
+ isLoading: boolean;
22
+ isLaunchingBrowser: boolean;
23
+ isNavigating: boolean; // True when navigating within same session (e.g., clicking a link)
24
+ deviceSize: DeviceSize;
25
+ rotation: Rotation;
26
+ consoleLogs: ConsoleMessage[];
27
+ canvasAPI: any;
28
+ previewDimensions: any;
29
+ lastFrameData: any;
30
+ errorMessage: string | null;
31
+ }
32
+
33
+ /**
34
+ * Helper function to get tab title from URL
35
+ */
36
+ export function getTabTitle(url: string): string {
37
+ if (!url) return 'New Tab';
38
+ try {
39
+ return new URL(url).hostname;
40
+ } catch {
41
+ return url.length > 30 ? url.slice(0, 30) + '...' : url;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Create browser tab manager state
47
+ */
48
+ export function createTabManager() {
49
+ let tabs = $state<PreviewTab[]>([]);
50
+ let activeTabId = $state<string | null>(null);
51
+ let nextTabId = $state(1);
52
+
53
+ // Get active tab (derived)
54
+ const activeTab = $derived.by(() => tabs.find(tab => tab.id === activeTabId));
55
+
56
+ /**
57
+ * Create a new tab
58
+ */
59
+ function createTab(tabUrl: string = ''): string {
60
+ const tabId = `tab-${nextTabId++}`;
61
+ debug.log('preview', `📁 Creating new tab: ${tabId} with URL: ${tabUrl || '(empty)'}`);
62
+
63
+ // Default device size is laptop
64
+ const deviceSize: DeviceSize = 'laptop';
65
+ // Default rotation: landscape for laptop (matches new default)
66
+ const rotation: Rotation = 'landscape';
67
+
68
+ const newTab: PreviewTab = {
69
+ id: tabId,
70
+ url: tabUrl,
71
+ title: getTabTitle(tabUrl),
72
+ sessionId: null,
73
+ sessionInfo: null,
74
+ isConnected: false,
75
+ isStreamReady: false,
76
+ isLoading: false,
77
+ isLaunchingBrowser: false,
78
+ isNavigating: false,
79
+ deviceSize,
80
+ rotation,
81
+ consoleLogs: [],
82
+ canvasAPI: null,
83
+ previewDimensions: { scale: 1 },
84
+ lastFrameData: null,
85
+ errorMessage: null
86
+ };
87
+
88
+ tabs = [...tabs, newTab];
89
+ activeTabId = tabId;
90
+
91
+ return tabId;
92
+ }
93
+
94
+ /**
95
+ * Switch to a specific tab
96
+ */
97
+ function switchTab(tabId: string): PreviewTab | null {
98
+ const tab = tabs.find(t => t.id === tabId);
99
+ if (!tab || activeTabId === tabId) return null;
100
+
101
+ debug.log('preview', `🔄 Switching tab from ${activeTabId} to ${tabId}`);
102
+ activeTabId = tabId;
103
+
104
+ return tab;
105
+ }
106
+
107
+ /**
108
+ * Close a tab
109
+ */
110
+ function closeTab(tabId: string): { removedTab: PreviewTab | null; newActiveTab: PreviewTab | null } {
111
+ const tabIndex = tabs.findIndex(tab => tab.id === tabId);
112
+ if (tabIndex === -1) return { removedTab: null, newActiveTab: null };
113
+
114
+ const removedTab = tabs[tabIndex];
115
+ tabs = tabs.filter(t => t.id !== tabId);
116
+
117
+ let newActiveTab: PreviewTab | null = null;
118
+
119
+ // Switch to adjacent tab if closing active tab
120
+ if (activeTabId === tabId && tabs.length > 0) {
121
+ const newIndex = tabIndex < tabs.length ? tabIndex : tabs.length - 1;
122
+ newActiveTab = tabs[newIndex];
123
+ if (newActiveTab) {
124
+ activeTabId = newActiveTab.id;
125
+ }
126
+ } else if (tabs.length === 0) {
127
+ activeTabId = null;
128
+ }
129
+
130
+ return { removedTab, newActiveTab };
131
+ }
132
+
133
+ /**
134
+ * Update tab state
135
+ */
136
+ function updateTab(tabId: string, updates: Partial<PreviewTab>): void {
137
+ tabs = tabs.map(tab => {
138
+ if (tab.id === tabId) {
139
+ return { ...tab, ...updates };
140
+ }
141
+ return tab;
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Update active tab
147
+ */
148
+ function updateActiveTab(updates: Partial<PreviewTab>): void {
149
+ if (!activeTabId) return;
150
+ updateTab(activeTabId, updates);
151
+ }
152
+
153
+ /**
154
+ * Get tab by ID
155
+ */
156
+ function getTab(tabId: string): PreviewTab | undefined {
157
+ return tabs.find(t => t.id === tabId);
158
+ }
159
+
160
+ /**
161
+ * Get all tabs
162
+ */
163
+ function getAllTabs(): PreviewTab[] {
164
+ return tabs;
165
+ }
166
+
167
+ /**
168
+ * Get active tab ID
169
+ */
170
+ function getActiveTabId(): string | null {
171
+ return activeTabId;
172
+ }
173
+
174
+ /**
175
+ * Set tabs (for external state sync)
176
+ */
177
+ function setTabs(newTabs: PreviewTab[]): void {
178
+ tabs = newTabs;
179
+ }
180
+
181
+ /**
182
+ * Clear all tabs (used when switching projects)
183
+ */
184
+ function clearAllTabs(): void {
185
+ debug.log('preview', '🧹 Clearing all tabs');
186
+ tabs = [];
187
+ activeTabId = null;
188
+ }
189
+
190
+ return {
191
+ // Getters
192
+ get tabs() { return tabs; },
193
+ get activeTabId() { return activeTabId; },
194
+ get activeTab() { return activeTab; },
195
+
196
+ // Methods
197
+ createTab,
198
+ switchTab,
199
+ closeTab,
200
+ updateTab,
201
+ updateActiveTab,
202
+ getTab,
203
+ getAllTabs,
204
+ getActiveTabId,
205
+ setTabs,
206
+ clearAllTabs
207
+ };
208
+ }
209
+
210
+ export type TabManager = ReturnType<typeof createTabManager>;
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Browser Tab Operations
3
+ *
4
+ * Handles browser session lifecycle (launch, navigate, destroy) via WebSocket.
5
+ * All operations work with active tab on backend (tab-centric architecture).
6
+ *
7
+ * Session Persistence:
8
+ * - Sessions survive page refresh
9
+ * - On page load, existing sessions can be recovered via getExistingTabs()
10
+ * - Frontend reconnects to backend tabs and restarts streaming
11
+ */
12
+
13
+ import ws from '$frontend/lib/utils/ws';
14
+ import { debug } from '$shared/utils/logger';
15
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
16
+ import type { DeviceSize, Rotation } from '$frontend/lib/constants/preview';
17
+
18
+ export interface BrowserSessionInfo {
19
+ quality: string;
20
+ url: string;
21
+ deviceSize?: DeviceSize;
22
+ rotation?: Rotation;
23
+ }
24
+
25
+ export interface LaunchResult {
26
+ success: boolean;
27
+ sessionId?: string;
28
+ sessionInfo?: BrowserSessionInfo;
29
+ error?: string;
30
+ }
31
+
32
+ export interface NavigateResult {
33
+ success: boolean;
34
+ finalUrl?: string;
35
+ error?: string;
36
+ }
37
+
38
+ export interface ExistingTabInfo {
39
+ tabId: string;
40
+ url: string;
41
+ title: string;
42
+ quality: string;
43
+ isStreaming: boolean;
44
+ deviceSize: string;
45
+ rotation: string;
46
+ isActive: boolean;
47
+ }
48
+
49
+ export interface ExistingTabsResult {
50
+ tabs: ExistingTabInfo[];
51
+ activeTabId: string | null;
52
+ count: number;
53
+ }
54
+
55
+ /**
56
+ * Launch browser for active tab
57
+ */
58
+ export async function launchBrowser(
59
+ url: string,
60
+ deviceSize: DeviceSize,
61
+ rotation: Rotation,
62
+ projectId: string,
63
+ mcpSessionId?: string
64
+ ): Promise<LaunchResult> {
65
+ debug.log('preview', `🌐 launchBrowser - URL: ${url}, device: ${deviceSize}, rotation: ${rotation}, projectId: ${projectId}${mcpSessionId ? `, mcpSessionId: ${mcpSessionId}` : ''}`);
66
+
67
+ if (!url) {
68
+ debug.error('preview', '❌ launchBrowser: No URL provided');
69
+ addNotification({
70
+ type: 'error',
71
+ title: 'URL Required',
72
+ message: 'Please enter a URL to launch browser preview'
73
+ });
74
+ return { success: false, error: 'URL is required' };
75
+ }
76
+
77
+ if (!projectId) {
78
+ debug.error('preview', '❌ launchBrowser: No projectId provided');
79
+ addNotification({
80
+ type: 'error',
81
+ title: 'Project Required',
82
+ message: 'Please select a project first'
83
+ });
84
+ return { success: false, error: 'Project ID is required' };
85
+ }
86
+
87
+ try {
88
+ debug.log('preview', `📡 Sending browser:launch via WebSocket...`);
89
+
90
+ // Backend will create tab automatically with projectId
91
+ const data = await ws.http(
92
+ 'preview:browser-tab-open',
93
+ { url, deviceSize, rotation },
94
+ 60000
95
+ );
96
+
97
+ debug.log('preview', `✅ Browser launched successfully - sessionId: ${data.tabId}`);
98
+
99
+ return {
100
+ success: true,
101
+ sessionId: data.tabId,
102
+ sessionInfo: {
103
+ quality: data.quality,
104
+ url: data.url,
105
+ deviceSize,
106
+ rotation
107
+ }
108
+ };
109
+ } catch (error) {
110
+ debug.error('preview', '💥 Error launching browser:', error);
111
+ addNotification({
112
+ type: 'error',
113
+ title: 'Launch Failed',
114
+ message: error instanceof Error ? error.message : 'Failed to launch browser preview'
115
+ });
116
+ return {
117
+ success: false,
118
+ error: error instanceof Error ? error.message : 'Unknown error'
119
+ };
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Navigate active tab to new URL
125
+ */
126
+ export async function navigateBrowser(newUrl: string, projectId: string): Promise<NavigateResult> {
127
+ if (!newUrl) {
128
+ return { success: false, error: 'No URL provided' };
129
+ }
130
+
131
+ if (!projectId) {
132
+ return { success: false, error: 'Project ID is required' };
133
+ }
134
+
135
+ try {
136
+ // Backend uses active tab automatically
137
+ const data = await ws.http('preview:browser-tab-navigate', { url: newUrl }, 30000);
138
+
139
+ return { success: true, finalUrl: data.finalUrl };
140
+ } catch (error) {
141
+ addNotification({
142
+ type: 'error',
143
+ title: 'Navigation Failed',
144
+ message: error instanceof Error ? error.message : 'Failed to navigate'
145
+ });
146
+ return {
147
+ success: false,
148
+ error: error instanceof Error ? error.message : 'Unknown error'
149
+ };
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Destroy browser session for active tab
155
+ */
156
+ export async function destroyBrowser(projectId: string): Promise<void> {
157
+ if (!projectId) {
158
+ debug.error('preview', `❌ destroyBrowser: No projectId provided`);
159
+ return;
160
+ }
161
+
162
+ try {
163
+ // Backend uses active tab automatically
164
+ await ws.http('preview:browser-tab-close', {});
165
+ debug.log('preview', `✅ Browser session destroyed`);
166
+ } catch (error) {
167
+ debug.error('preview', `❌ Error destroying browser:`, error);
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Destroy browser session for a specific tab
173
+ */
174
+ export async function destroyBrowserTab(tabId: string, projectId: string): Promise<void> {
175
+ if (!projectId) {
176
+ debug.error('preview', `❌ destroyBrowserTab: No projectId provided`);
177
+ return;
178
+ }
179
+
180
+ try {
181
+ await ws.http('preview:browser-tab-close', { tabId });
182
+ debug.log('preview', `✅ Browser tab ${tabId} destroyed`);
183
+ } catch (error) {
184
+ debug.error('preview', `❌ Error destroying browser tab ${tabId}:`, error);
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Get all existing tabs from backend (for session recovery after refresh)
190
+ */
191
+ export async function getExistingTabs(projectId: string): Promise<ExistingTabsResult | null> {
192
+ if (!projectId) {
193
+ debug.error('preview', `❌ getExistingTabs: No projectId provided`);
194
+ return null;
195
+ }
196
+
197
+ try {
198
+ debug.log('preview', `🔍 Checking for existing browser tabs (project: ${projectId})...`);
199
+
200
+ const data = await ws.http('preview:browser-tabs-list', {}, 5000);
201
+
202
+ if (data.count > 0) {
203
+ debug.log('preview', `✅ Found ${data.count} existing browser tabs`);
204
+ } else {
205
+ debug.log('preview', `📭 No existing browser tabs found`);
206
+ }
207
+
208
+ return {
209
+ tabs: data.tabs,
210
+ activeTabId: data.activeTabId,
211
+ count: data.count
212
+ };
213
+ } catch (error) {
214
+ debug.warn('preview', `⚠️ Could not get existing tabs:`, error);
215
+ return null;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Switch to a specific tab on backend
221
+ */
222
+ export async function switchToBackendTab(tabId: string, projectId: string): Promise<boolean> {
223
+ if (!projectId) {
224
+ debug.error('preview', `❌ switchToBackendTab: No projectId provided`);
225
+ return false;
226
+ }
227
+
228
+ try {
229
+ debug.log('preview', `🔄 Switching to backend tab: ${tabId} (project: ${projectId})`);
230
+
231
+ await ws.http('preview:browser-tab-switch', { tabId }, 5000);
232
+
233
+ debug.log('preview', `✅ Switched to backend tab: ${tabId}`);
234
+ return true;
235
+ } catch (error) {
236
+ debug.error('preview', `❌ Error switching to backend tab:`, error);
237
+ return false;
238
+ }
239
+ }
@@ -0,0 +1,2 @@
1
+ // Browser-specific components
2
+ export { default as BrowserPreview } from './browser/BrowserPreview.svelte';
@@ -0,0 +1,235 @@
1
+ <script lang="ts">
2
+ import { fade, scale, fly } from 'svelte/transition';
3
+ import { cubicOut } from 'svelte/easing';
4
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
5
+ import {
6
+ settingsModalState,
7
+ closeSettingsModal,
8
+ setActiveSection,
9
+ settingsSections,
10
+ type SettingsSection
11
+ } from '$frontend/lib/stores/ui/settings-modal.svelte';
12
+
13
+ // Import settings components
14
+ import ModelSettings from './model/ModelSettings.svelte';
15
+ import AIEnginesSettings from './engines/AIEnginesSettings.svelte';
16
+ import AppearanceSettings from './appearance/AppearanceSettings.svelte';
17
+ import UserSettings from './user/UserSettings.svelte';
18
+ import NotificationSettings from './notifications/NotificationSettings.svelte';
19
+ import GeneralSettings from './general/GeneralSettings.svelte';
20
+ import pkg from '../../../../package.json';
21
+
22
+ // Responsive state
23
+ let isMobileMenuOpen = $state(false);
24
+ let windowWidth = $state(typeof window !== 'undefined' ? window.innerWidth : 1024);
25
+
26
+ const isMobile = $derived(windowWidth < 768);
27
+ const isOpen = $derived(settingsModalState.isOpen);
28
+ const activeSection = $derived(settingsModalState.activeSection);
29
+
30
+ // Handle section change
31
+ function handleSectionChange(section: SettingsSection) {
32
+ setActiveSection(section);
33
+ if (isMobile) {
34
+ isMobileMenuOpen = false;
35
+ }
36
+ }
37
+
38
+ // Handle backdrop click
39
+ function handleBackdropClick(event: MouseEvent) {
40
+ if (event.target === event.currentTarget) {
41
+ closeSettingsModal();
42
+ }
43
+ }
44
+
45
+ // Handle keyboard events
46
+ function handleKeydown(event: KeyboardEvent) {
47
+ if (event.key === 'Escape') {
48
+ closeSettingsModal();
49
+ }
50
+ }
51
+
52
+ // Get current section info
53
+ const currentSectionInfo = $derived(
54
+ settingsSections.find((s) => s.id === activeSection) || settingsSections[0]
55
+ );
56
+
57
+ // Update window width on resize
58
+ function handleResize() {
59
+ windowWidth = window.innerWidth;
60
+ if (!isMobile) {
61
+ isMobileMenuOpen = false;
62
+ }
63
+ }
64
+ </script>
65
+
66
+ <svelte:window on:keydown={handleKeydown} on:resize={handleResize} />
67
+
68
+ {#if isOpen}
69
+ <!-- Backdrop -->
70
+ <div
71
+ class="fixed inset-0 z-[100] flex items-center justify-center md:p-4 bg-black/60 backdrop-blur-sm"
72
+ role="dialog"
73
+ aria-modal="true"
74
+ aria-labelledby="settings-title"
75
+ tabindex="-1"
76
+ onclick={handleBackdropClick}
77
+ onkeydown={handleKeydown}
78
+ in:fade={{ duration: 200, easing: cubicOut }}
79
+ out:fade={{ duration: 150, easing: cubicOut }}
80
+ >
81
+ <!-- Modal Container -->
82
+ <div
83
+ role="dialog"
84
+ aria-labelledby="settings-title"
85
+ tabindex="-1"
86
+ class="flex flex-col w-full max-w-225 h-[85vh] max-h-175 bg-slate-50 dark:bg-slate-950 border border-violet-500/20 rounded-2xl overflow-hidden shadow-[0_25px_50px_-12px_rgba(0,0,0,0.25)] dark:shadow-[0_25px_50px_-12px_rgba(0,0,0,0.5)] max-md:max-w-full max-md:h-screen max-md:max-h-screen max-md:rounded-none"
87
+ onclick={(e) => e.stopPropagation()}
88
+ onkeydown={(e) => e.stopPropagation()}
89
+ in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
90
+ out:scale={{ duration: 150, easing: cubicOut, start: 0.95 }}
91
+ >
92
+ <!-- Mobile Header -->
93
+ {#if isMobile}
94
+ <header
95
+ class="flex items-center justify-between py-3 px-4 bg-slate-100 dark:bg-slate-900/95 border-b border-slate-200 dark:border-slate-800"
96
+ >
97
+ <button
98
+ type="button"
99
+ class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
100
+ onclick={() => (isMobileMenuOpen = !isMobileMenuOpen)}
101
+ aria-label="Toggle menu"
102
+ >
103
+ <Icon name={isMobileMenuOpen ? 'lucide:x' : 'lucide:menu'} class="w-5 h-5" />
104
+ </button>
105
+ <h2
106
+ id="settings-title"
107
+ class="flex items-center gap-2 text-base font-semibold text-slate-900 dark:text-slate-100 m-0"
108
+ >
109
+ <Icon name={currentSectionInfo.icon} class="w-5 h-5" />
110
+ {currentSectionInfo.label}
111
+ </h2>
112
+ <button
113
+ type="button"
114
+ class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
115
+ onclick={closeSettingsModal}
116
+ aria-label="Close settings"
117
+ >
118
+ <Icon name="lucide:x" class="w-5 h-5" />
119
+ </button>
120
+ </header>
121
+ {/if}
122
+
123
+ <div class="flex flex-1 min-h-0 relative">
124
+ <!-- Sidebar -->
125
+ <aside
126
+ class="flex flex-col w-68 shrink-0 bg-white dark:bg-slate-900/98 border-r border-slate-200 dark:border-slate-800
127
+ {isMobile
128
+ ? 'absolute left-0 top-0 bottom-0 z-10 w-70 shadow-[4px_0_20px_rgba(0,0,0,0.15)] dark:shadow-[4px_0_20px_rgba(0,0,0,0.3)] transition-transform duration-[250ms] ease-out'
129
+ : ''}
130
+ {isMobile && !isMobileMenuOpen ? '-translate-x-full' : 'translate-x-0'}"
131
+ >
132
+ {#if !isMobile}
133
+ <header
134
+ class="flex items-center justify-between py-5 px-4 pl-6.5 border-b border-slate-200 dark:border-slate-800"
135
+ >
136
+ <div
137
+ class="flex items-center gap-2.5 text-lg font-bold text-slate-900 dark:text-slate-100"
138
+ >
139
+ <span>Settings</span>
140
+ </div>
141
+ <button
142
+ type="button"
143
+ class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
144
+ onclick={closeSettingsModal}
145
+ aria-label="Close settings"
146
+ >
147
+ <Icon name="lucide:x" class="w-5 h-5" />
148
+ </button>
149
+ </header>
150
+ {/if}
151
+
152
+ <nav class="flex-1 overflow-y-auto p-3">
153
+ {#each settingsSections as section (section.id)}
154
+ <button
155
+ type="button"
156
+ class="flex items-start gap-3 w-full py-3 px-3.5 bg-transparent border-none rounded-lg text-slate-500 text-sm text-left cursor-pointer transition-all duration-150 mb-1
157
+ hover:bg-violet-500/10 hover:text-slate-600 dark:hover:text-slate-400
158
+ {activeSection === section.id
159
+ ? 'bg-violet-500/10 dark:bg-violet-500/20 text-slate-900 dark:text-slate-100'
160
+ : ''}"
161
+ onclick={() => handleSectionChange(section.id)}
162
+ >
163
+ <Icon
164
+ name={section.icon}
165
+ class="w-5 h-5 shrink-0 mt-0.5 {activeSection === section.id
166
+ ? 'text-violet-600'
167
+ : ''}"
168
+ />
169
+ <div class="flex flex-col gap-0.5">
170
+ <span class="font-semibold">{section.label}</span>
171
+ <span
172
+ class="text-xs text-slate-600 dark:text-slate-500 leading-tight {activeSection ===
173
+ section.id
174
+ ? 'text-violet-600 dark:text-violet-400'
175
+ : ''}">{section.description}</span
176
+ >
177
+ </div>
178
+ </button>
179
+ {/each}
180
+ </nav>
181
+
182
+ <footer class="p-4 border-t border-slate-200 dark:border-slate-800">
183
+ <div class="flex items-center gap-2 text-xs text-slate-600 dark:text-slate-500">
184
+ <Icon name="lucide:info" class="w-4 h-4" />
185
+ <span>Clopen v{pkg.version}</span>
186
+ </div>
187
+ </footer>
188
+ </aside>
189
+
190
+ <!-- Mobile Menu Overlay -->
191
+ {#if isMobile && isMobileMenuOpen}
192
+ <button
193
+ type="button"
194
+ class="absolute inset-0 z-[5] bg-black/40 border-none p-0 cursor-default"
195
+ onclick={() => (isMobileMenuOpen = false)}
196
+ aria-label="Close menu"
197
+ in:fade={{ duration: 150 }}
198
+ out:fade={{ duration: 100 }}
199
+ ></button>
200
+ {/if}
201
+
202
+ <!-- Content Area -->
203
+ <main class="flex-1 flex flex-col min-w-0 overflow-hidden">
204
+ <div class="flex-1 overflow-y-auto p-4 md:p-5">
205
+ {#if activeSection === 'model'}
206
+ <div in:fly={{ x: 20, duration: 200 }}>
207
+ <ModelSettings />
208
+ </div>
209
+ {:else if activeSection === 'engines'}
210
+ <div in:fly={{ x: 20, duration: 200 }}>
211
+ <AIEnginesSettings />
212
+ </div>
213
+ {:else if activeSection === 'appearance'}
214
+ <div in:fly={{ x: 20, duration: 200 }}>
215
+ <AppearanceSettings />
216
+ </div>
217
+ {:else if activeSection === 'user'}
218
+ <div in:fly={{ x: 20, duration: 200 }}>
219
+ <UserSettings />
220
+ </div>
221
+ {:else if activeSection === 'notifications'}
222
+ <div in:fly={{ x: 20, duration: 200 }}>
223
+ <NotificationSettings />
224
+ </div>
225
+ {:else if activeSection === 'general'}
226
+ <div in:fly={{ x: 20, duration: 200 }}>
227
+ <GeneralSettings />
228
+ </div>
229
+ {/if}
230
+ </div>
231
+ </main>
232
+ </div>
233
+ </div>
234
+ </div>
235
+ {/if}