@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,357 @@
1
+ <script lang="ts">
2
+ import { settings, updateSettings } from '$frontend/lib/stores/features/settings.svelte';
3
+ import { modelStore } from '$frontend/lib/stores/features/models.svelte';
4
+ import { ENGINES, type EngineType } from '$shared/constants/engines';
5
+ import type { EngineModel } from '$shared/types/engine';
6
+
7
+ let searchQuery = $state('');
8
+ let refreshing = $state(false);
9
+ let collapsedProviders = $state<Set<string>>(new Set());
10
+
11
+ // Handle engine selection — restore remembered model or pick first
12
+ async function selectEngine(engineType: EngineType) {
13
+ updateSettings({ selectedEngine: engineType });
14
+ searchQuery = '';
15
+
16
+ // Restore remembered model for this engine
17
+ const memory = settings.engineModelMemory || {};
18
+ const remembered = memory[engineType];
19
+
20
+ if (engineType !== 'claude-code') {
21
+ const models = await modelStore.fetchModels(engineType);
22
+ const target = (remembered && models.find(m => m.id === remembered))
23
+ || models.find(m => m.recommended)
24
+ || models[0];
25
+ if (target) {
26
+ updateSettings({
27
+ selectedModel: target.id,
28
+ engineModelMemory: { ...memory, [engineType]: target.id }
29
+ });
30
+ } else {
31
+ // No models available — clear the model selection
32
+ updateSettings({ selectedModel: '' });
33
+ }
34
+ } else {
35
+ const models = modelStore.getByEngine('claude-code');
36
+ const target = (remembered && models.find(m => m.id === remembered))
37
+ || models.find(m => m.recommended)
38
+ || models[0];
39
+ if (target) {
40
+ updateSettings({
41
+ selectedModel: target.id,
42
+ engineModelMemory: { ...memory, [engineType]: target.id }
43
+ });
44
+ } else {
45
+ // No models available — clear the model selection
46
+ updateSettings({ selectedModel: '' });
47
+ }
48
+ }
49
+
50
+ // Open accordion for the selected model's provider
51
+ syncAccordionState();
52
+ }
53
+
54
+ // Handle model selection — also save to per-engine memory
55
+ function selectModel(modelId: string) {
56
+ const memory = settings.engineModelMemory || {};
57
+ updateSettings({
58
+ selectedModel: modelId,
59
+ engineModelMemory: { ...memory, [settings.selectedEngine]: modelId }
60
+ });
61
+ }
62
+
63
+ // Refresh models (bypass cache)
64
+ async function handleRefresh() {
65
+ refreshing = true;
66
+ try {
67
+ await modelStore.refreshModels(settings.selectedEngine);
68
+ } finally {
69
+ refreshing = false;
70
+ }
71
+ }
72
+
73
+ // Toggle provider accordion
74
+ function toggleProvider(provider: string) {
75
+ const next = new Set(collapsedProviders);
76
+ if (next.has(provider)) {
77
+ next.delete(provider);
78
+ } else {
79
+ next.add(provider);
80
+ }
81
+ collapsedProviders = next;
82
+ }
83
+
84
+ // Sync accordion state: open only the provider containing the selected model
85
+ function syncAccordionState() {
86
+ const allProviders = [...groupedModels.keys()];
87
+ const selectedModel = settings.selectedModel;
88
+ let selectedProvider: string | null = null;
89
+
90
+ for (const [provider, models] of groupedModels) {
91
+ if (models.some(m => m.id === selectedModel)) {
92
+ selectedProvider = provider;
93
+ break;
94
+ }
95
+ }
96
+
97
+ // Collapse all, then open the one with selected model
98
+ const collapsed = new Set(allProviders);
99
+ if (selectedProvider) {
100
+ collapsed.delete(selectedProvider);
101
+ }
102
+ collapsedProviders = collapsed;
103
+ }
104
+
105
+ // Get models for the currently selected engine, filtered by search
106
+ const filteredModels = $derived.by(() => {
107
+ const models = modelStore.getByEngine(settings.selectedEngine);
108
+ if (!searchQuery.trim()) return models;
109
+
110
+ const q = searchQuery.toLowerCase();
111
+ return models.filter(m =>
112
+ m.name.toLowerCase().includes(q) ||
113
+ m.modelId.toLowerCase().includes(q) ||
114
+ m.provider.toLowerCase().includes(q) ||
115
+ m.capabilities.some(c => c.toLowerCase().includes(q))
116
+ );
117
+ });
118
+
119
+ // Group models by provider
120
+ const groupedModels = $derived.by(() => {
121
+ const groups = new Map<string, EngineModel[]>();
122
+ for (const model of filteredModels) {
123
+ const key = model.provider;
124
+ if (!groups.has(key)) groups.set(key, []);
125
+ groups.get(key)!.push(model);
126
+ }
127
+ return groups;
128
+ });
129
+
130
+ // Fetch models on mount for non-claude-code, then sync accordion
131
+ $effect(() => {
132
+ if (settings.selectedEngine !== 'claude-code') {
133
+ modelStore.fetchModels(settings.selectedEngine);
134
+ }
135
+ });
136
+
137
+ // Sync accordion: open all when searching, restore default when cleared
138
+ $effect(() => {
139
+ if (searchQuery.trim()) {
140
+ // Searching — open all accordions
141
+ collapsedProviders = new Set();
142
+ } else if (groupedModels.size > 0) {
143
+ // Not searching — only open the one with selected model
144
+ syncAccordionState();
145
+ }
146
+ });
147
+
148
+ function formatContext(tokens: number): string {
149
+ return tokens >= 1000000
150
+ ? `${(tokens / 1000000).toFixed(1)}M`
151
+ : `${(tokens / 1000).toFixed(0)}K`;
152
+ }
153
+
154
+ function formatProvider(provider: string): string {
155
+ return provider
156
+ .split(/[-_]/)
157
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
158
+ .join(' ');
159
+ }
160
+ </script>
161
+
162
+ <!-- Claude SVG logo (Anthropic) -->
163
+ {#snippet claudeLogo(active: boolean)}
164
+ <svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
165
+ <path d="M16.091 4L9.115 20h-1.19L14.901 4h1.19zm-5.726 5.2L14.8 20h-1.218L9.2 9.6l1.165-.4z"
166
+ fill={active ? '#8b5cf6' : 'currentColor'} />
167
+ </svg>
168
+ {/snippet}
169
+
170
+ <!-- OpenCode SVG logo -->
171
+ {#snippet opencodeLogo(active: boolean)}
172
+ <svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
173
+ <path d="M8.5 6L3 12l5.5 6M15.5 6L21 12l-5.5 6M13.5 4l-3 16"
174
+ stroke={active ? '#8b5cf6' : 'currentColor'}
175
+ stroke-width="2"
176
+ stroke-linecap="round"
177
+ stroke-linejoin="round" />
178
+ </svg>
179
+ {/snippet}
180
+
181
+ <div class="py-1">
182
+ <!-- Engine Selection -->
183
+ <h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">AI Engine</h3>
184
+ <p class="text-sm text-slate-600 dark:text-slate-500 mb-4">
185
+ Select the AI engine to power your conversations
186
+ </p>
187
+
188
+ <div class="flex gap-3 mb-6">
189
+ {#each ENGINES as engine (engine.type)}
190
+ {@const isActive = settings.selectedEngine === engine.type}
191
+ <button
192
+ type="button"
193
+ class="flex-1 flex items-center gap-3 p-3.5 overflow-hidden border-2 rounded-xl text-left cursor-pointer transition-all duration-200
194
+ {isActive
195
+ ? 'border-violet-600 bg-gradient-to-br from-violet-500/10 to-purple-500/5 dark:from-violet-500/12 dark:to-purple-500/8'
196
+ : 'border-slate-200 dark:border-slate-800 bg-slate-100/80 dark:bg-slate-800/80 hover:border-violet-500/20 dark:hover:border-violet-500/35'}"
197
+ onclick={() => selectEngine(engine.type)}
198
+ >
199
+ <div>
200
+ <div class="flex dark:hidden items-center justify-center w-5 h-5">{@html engine.icon.light}</div>
201
+ <div class="hidden dark:flex items-center justify-center w-5 h-5">{@html engine.icon.dark}</div>
202
+ </div>
203
+ <div>
204
+ <div class="font-bold text-sm text-slate-900 dark:text-slate-100">{engine.name}</div>
205
+ <div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{engine.description}</div>
206
+ </div>
207
+ {#if isActive}
208
+ <div class="flex items-center justify-center w-5 h-5 bg-gradient-to-br from-violet-600 to-purple-600 rounded-full text-white ml-auto flex-shrink-0">
209
+ <svg viewBox="0 0 24 24" fill="none" class="w-3 h-3" aria-hidden="true">
210
+ <path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
211
+ </svg>
212
+ </div>
213
+ {/if}
214
+ </button>
215
+ {/each}
216
+ </div>
217
+
218
+ <!-- Model Selection -->
219
+ <div class="flex items-center justify-between mb-1.5">
220
+ <h3 class="text-base font-bold text-slate-900 dark:text-slate-100">Model</h3>
221
+ <button
222
+ type="button"
223
+ class="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg transition-colors cursor-pointer
224
+ text-slate-500 hover:text-violet-600 hover:bg-violet-500/10 dark:hover:text-violet-400 dark:hover:bg-violet-500/15
225
+ disabled:opacity-50 disabled:cursor-not-allowed"
226
+ onclick={handleRefresh}
227
+ disabled={refreshing || modelStore.loading}
228
+ >
229
+ <svg viewBox="0 0 24 24" fill="none" class="w-3.5 h-3.5 {refreshing ? 'animate-spin' : ''}" aria-hidden="true">
230
+ <path d="M21 12a9 9 0 11-2.636-6.364M21 3v5h-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
231
+ </svg>
232
+ {refreshing ? 'Refreshing...' : 'Refresh'}
233
+ </button>
234
+ </div>
235
+ <p class="text-sm text-slate-600 dark:text-slate-500 mb-3">
236
+ Select the AI model for the {ENGINES.find(e => e.type === settings.selectedEngine)?.name || 'selected'} engine
237
+ </p>
238
+
239
+ <!-- Search -->
240
+ <div class="relative mb-3">
241
+ <svg viewBox="0 0 24 24" fill="none" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" aria-hidden="true">
242
+ <circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" />
243
+ <path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
244
+ </svg>
245
+ <input
246
+ type="text"
247
+ bind:value={searchQuery}
248
+ placeholder="Search models..."
249
+ class="w-full pl-9 pr-3 py-2 text-sm bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-600 transition-colors text-slate-900 dark:text-slate-100 placeholder-slate-400"
250
+ />
251
+ </div>
252
+
253
+ <!-- Model List -->
254
+ <div class="flex flex-col gap-1.5">
255
+ {#if modelStore.loading && settings.selectedEngine !== 'claude-code' && !refreshing}
256
+ <!-- Loading skeleton for Open Code only -->
257
+ <div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
258
+ <div class="bg-white/80 dark:bg-slate-800/40 px-3 py-3 flex items-center gap-3">
259
+ <div class="w-4 h-4 rounded-full bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
260
+ <div class="h-3.5 w-32 rounded bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
261
+ </div>
262
+ <div class="px-4 py-2.5 space-y-2.5">
263
+ {#each Array(3) as _}
264
+ <div class="flex items-center gap-3 py-2">
265
+ <div class="w-4 h-4 rounded-full bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
266
+ <div class="flex-1 space-y-1.5">
267
+ <div class="h-3.5 w-40 rounded bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
268
+ <div class="flex gap-1.5">
269
+ <div class="h-3 w-14 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
270
+ <div class="h-3 w-12 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ {/each}
275
+ </div>
276
+ </div>
277
+ {:else if filteredModels.length === 0}
278
+ <div class="py-4 text-sm text-slate-500 text-center">
279
+ {searchQuery ? 'No models matching your search.' : 'No models available for this engine.'}
280
+ </div>
281
+ {:else}
282
+ <!-- Grouped by provider with accordion -->
283
+ {#each [...groupedModels.entries()] as [provider, providerModels] (provider)}
284
+ {@const isCollapsed = collapsedProviders.has(provider)}
285
+ {@const hasSelectedModel = providerModels.some(m => m.id === settings.selectedModel)}
286
+ <div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
287
+ <!-- Accordion header -->
288
+ <button
289
+ type="button"
290
+ class="flex items-center gap-2.5 w-full px-3 py-2.5 text-left cursor-pointer transition-colors
291
+ bg-white/80 dark:bg-slate-800/40 hover:bg-white dark:hover:bg-slate-800/60"
292
+ onclick={() => toggleProvider(provider)}
293
+ >
294
+ <svg viewBox="0 0 24 24" fill="none"
295
+ class="w-4 h-4 text-slate-400 transition-transform duration-200 flex-shrink-0
296
+ {isCollapsed ? '' : 'rotate-90'}"
297
+ aria-hidden="true">
298
+ <path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
299
+ </svg>
300
+ <span class="text-sm font-semibold text-slate-800 dark:text-slate-200">
301
+ {formatProvider(provider)}
302
+ </span>
303
+ <span class="text-xs text-slate-400 dark:text-slate-500">
304
+ {providerModels.length} {providerModels.length === 1 ? 'model' : 'models'}
305
+ </span>
306
+ {#if hasSelectedModel}
307
+ <div class="w-1.5 h-1.5 rounded-full bg-violet-500 ml-auto flex-shrink-0"></div>
308
+ {/if}
309
+ </button>
310
+
311
+ <!-- Accordion body -->
312
+ {#if !isCollapsed}
313
+ <div class="flex flex-col bg-white/40 dark:bg-slate-800/20">
314
+ {#each providerModels as model (model.id)}
315
+ {@const isSelected = settings.selectedModel === model.id}
316
+ {@const caps = model.capabilities}
317
+ <button
318
+ type="button"
319
+ class="flex items-start gap-3 px-3 py-2.5 text-left cursor-pointer transition-all duration-150
320
+ {isSelected
321
+ ? 'bg-violet-500/10 dark:bg-violet-500/12'
322
+ : 'hover:bg-slate-100/80 dark:hover:bg-slate-700/30'}"
323
+ onclick={() => selectModel(model.id)}
324
+ >
325
+ <!-- Radio indicator -->
326
+ <div class="flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center mt-0.5
327
+ {isSelected ? 'border-violet-600' : 'border-slate-300 dark:border-slate-600'}">
328
+ {#if isSelected}
329
+ <div class="w-2 h-2 rounded-full bg-violet-600"></div>
330
+ {/if}
331
+ </div>
332
+
333
+ <!-- Model info -->
334
+ <div class="flex-1 min-w-0">
335
+ <div class="flex items-center gap-2">
336
+ <span class="text-sm font-medium text-slate-900 dark:text-slate-100">{model.name}</span>
337
+ <!-- <span class="text-2xs text-slate-400 dark:text-slate-500">{formatContext(model.contextWindow)}</span> -->
338
+ </div>
339
+ {#if caps.length > 0}
340
+ <div class="flex flex-wrap gap-1 mt-1.5">
341
+ {#each caps as cap}
342
+ <span class="px-1.5 py-0.5 text-2xs rounded bg-slate-100 dark:bg-slate-700/50 text-slate-500 dark:text-slate-400 leading-none">
343
+ {cap}
344
+ </span>
345
+ {/each}
346
+ </div>
347
+ {/if}
348
+ </div>
349
+ </button>
350
+ {/each}
351
+ </div>
352
+ {/if}
353
+ </div>
354
+ {/each}
355
+ {/if}
356
+ </div>
357
+ </div>
@@ -0,0 +1,205 @@
1
+ <script lang="ts">
2
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
3
+ import { settings, updateSettings } from '$frontend/lib/stores/features/settings.svelte';
4
+ import { soundNotification, pushNotification } from '$frontend/lib/services/notification';
5
+ import Icon from '../../common/Icon.svelte';
6
+
7
+ let isTestingSound = $state(false);
8
+ let isTestingPush = $state(false);
9
+
10
+ // Test sound notification
11
+ async function testSoundNotification() {
12
+ isTestingSound = true;
13
+ try {
14
+ soundNotification.initialize();
15
+
16
+ const success = await soundNotification.testSound();
17
+ if (success) {
18
+ addNotification({
19
+ type: 'success',
20
+ title: 'Sound Test',
21
+ message: 'Sound notification is working correctly',
22
+ duration: 3000
23
+ });
24
+ } else {
25
+ throw new Error('Sound test failed');
26
+ }
27
+ } catch (error) {
28
+ addNotification({
29
+ type: 'error',
30
+ title: 'Sound Test Failed',
31
+ message: soundNotification.isSupported()
32
+ ? 'Unable to play sound notification'
33
+ : 'Sound notifications not supported on this browser',
34
+ duration: 4000
35
+ });
36
+ } finally {
37
+ isTestingSound = false;
38
+ }
39
+ }
40
+
41
+ // Test push notification
42
+ async function testPushNotification() {
43
+ isTestingPush = true;
44
+ try {
45
+ const initialized = await pushNotification.initialize();
46
+
47
+ if (initialized) {
48
+ const success = await pushNotification.testNotification();
49
+ if (success) {
50
+ addNotification({
51
+ type: 'success',
52
+ title: 'Push Notification Test',
53
+ message: 'Native push notification is working correctly',
54
+ duration: 3000
55
+ });
56
+ } else {
57
+ throw new Error('Push test failed');
58
+ }
59
+ } else {
60
+ throw new Error('Push notification permission denied or not supported');
61
+ }
62
+ } catch (error) {
63
+ const permissionStatus = pushNotification.getPermissionStatus();
64
+ let message = 'Unable to send push notification';
65
+
66
+ if (!pushNotification.isSupported()) {
67
+ message = 'Push notifications not supported on this browser';
68
+ } else if (permissionStatus === 'denied') {
69
+ message =
70
+ 'Push notification permission denied. Please allow notifications in browser settings.';
71
+ } else if (permissionStatus === 'default') {
72
+ message = 'Push notification permission not granted';
73
+ }
74
+
75
+ addNotification({
76
+ type: 'error',
77
+ title: 'Push Notification Test Failed',
78
+ message,
79
+ duration: 5000
80
+ });
81
+ } finally {
82
+ await new Promise((resolve) => setTimeout(resolve, 2000));
83
+ isTestingPush = false;
84
+ }
85
+ }
86
+ </script>
87
+
88
+ <div class="py-1">
89
+ <h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">Notifications</h3>
90
+ <p class="text-sm text-slate-600 dark:text-slate-500 mb-5">Configure sound and push notification preferences</p>
91
+
92
+ <div class="flex flex-col gap-3.5">
93
+ <!-- Sound notifications -->
94
+ <div
95
+ class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl transition-all duration-150 hover:border-violet-500/20"
96
+ >
97
+ <div class="flex items-center justify-between">
98
+ <div class="flex items-center gap-3.5 flex-1">
99
+ <div
100
+ class="flex items-center justify-center w-10 h-10 rounded-lg shrink-0 bg-violet-500/10 dark:bg-violet-500/15 text-violet-600 dark:text-violet-400"
101
+ >
102
+ <Icon name="lucide:volume-2" class="w-5 h-5" />
103
+ </div>
104
+ <div class="flex flex-col gap-0.5 min-w-0">
105
+ <div class="text-sm font-semibold text-slate-900 dark:text-slate-100">
106
+ Sound notifications
107
+ </div>
108
+ <div class="text-xs text-slate-600 dark:text-slate-500">
109
+ Play sound when response is completed
110
+ </div>
111
+ </div>
112
+ </div>
113
+ <label class="relative inline-block w-12 h-6.5 shrink-0">
114
+ <input
115
+ type="checkbox"
116
+ checked={settings.soundNotifications}
117
+ onchange={() => updateSettings({ soundNotifications: !settings.soundNotifications })}
118
+ class="opacity-0 w-0 h-0"
119
+ />
120
+ <span
121
+ class="absolute cursor-pointer inset-0 bg-slate-600/40 dark:bg-slate-600/40 rounded-3xl transition-all duration-200
122
+ before:absolute before:content-[''] before:h-5 before:w-5 before:left-0.75 before:bottom-0.75 before:bg-white before:rounded-full before:transition-all before:duration-200
123
+ {settings.soundNotifications
124
+ ? 'bg-gradient-to-br from-violet-600 to-purple-600 before:translate-x-5.5'
125
+ : ''}"
126
+ ></span>
127
+ </label>
128
+ </div>
129
+ <div class="mt-3 pt-3 border-t border-slate-200 dark:border-slate-800">
130
+ <button
131
+ type="button"
132
+ class="inline-flex items-center gap-1.5 py-2 px-3.5 bg-violet-500/10 dark:bg-violet-500/10 border border-violet-500/20 dark:border-violet-500/25 rounded-lg text-violet-600 dark:text-violet-400 text-xs font-semibold cursor-pointer transition-all duration-150 hover:bg-violet-500/20 dark:hover:bg-violet-500/20 hover:border-violet-600 dark:hover:border-violet-500/40 disabled:opacity-50 disabled:cursor-not-allowed"
133
+ onclick={testSoundNotification}
134
+ disabled={isTestingSound}
135
+ >
136
+ {#if isTestingSound}
137
+ <div
138
+ class="w-3 h-3 border-2 border-violet-600/30 dark:border-violet-400/30 border-t-violet-600 dark:border-t-violet-400 rounded-full animate-spin"
139
+ ></div>
140
+ <span>Testing...</span>
141
+ {:else}
142
+ <Icon name="lucide:play" class="w-3.5 h-3.5" />
143
+ <span>Test Sound</span>
144
+ {/if}
145
+ </button>
146
+ </div>
147
+ </div>
148
+
149
+ <!-- Push notifications -->
150
+ <div
151
+ class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl transition-all duration-150 hover:border-violet-500/20"
152
+ >
153
+ <div class="flex items-center justify-between">
154
+ <div class="flex items-center gap-3.5 flex-1">
155
+ <div
156
+ class="flex items-center justify-center w-10 h-10 rounded-lg shrink-0 bg-orange-400/15 text-orange-400"
157
+ >
158
+ <Icon name="lucide:bell" class="w-5 h-5" />
159
+ </div>
160
+ <div class="flex flex-col gap-0.5 min-w-0">
161
+ <div class="text-sm font-semibold text-slate-900 dark:text-slate-100">
162
+ Push notifications
163
+ </div>
164
+ <div class="text-xs text-slate-600 dark:text-slate-500">
165
+ Show native browser notifications when response is completed
166
+ </div>
167
+ </div>
168
+ </div>
169
+ <label class="relative inline-block w-12 h-6.5 shrink-0">
170
+ <input
171
+ type="checkbox"
172
+ checked={settings.pushNotifications}
173
+ onchange={() => updateSettings({ pushNotifications: !settings.pushNotifications })}
174
+ class="opacity-0 w-0 h-0"
175
+ />
176
+ <span
177
+ class="absolute cursor-pointer inset-0 bg-slate-600/40 dark:bg-slate-600/40 rounded-3xl transition-all duration-200
178
+ before:absolute before:content-[''] before:h-5 before:w-5 before:left-0.75 before:bottom-0.75 before:bg-white before:rounded-full before:transition-all before:duration-200
179
+ {settings.pushNotifications
180
+ ? 'bg-gradient-to-br from-violet-600 to-purple-600 before:translate-x-5.5'
181
+ : ''}"
182
+ ></span>
183
+ </label>
184
+ </div>
185
+ <div class="mt-3 pt-3 border-t border-slate-200 dark:border-slate-800">
186
+ <button
187
+ type="button"
188
+ class="inline-flex items-center gap-1.5 py-2 px-3.5 bg-violet-500/10 dark:bg-violet-500/10 border border-violet-500/20 dark:border-violet-500/25 rounded-lg text-violet-600 dark:text-violet-400 text-xs font-semibold cursor-pointer transition-all duration-150 hover:bg-violet-500/20 dark:hover:bg-violet-500/20 hover:border-violet-600 dark:hover:border-violet-500/40 disabled:opacity-50 disabled:cursor-not-allowed"
189
+ onclick={testPushNotification}
190
+ disabled={isTestingPush}
191
+ >
192
+ {#if isTestingPush}
193
+ <div
194
+ class="w-3 h-3 border-2 border-violet-600/30 dark:border-violet-400/30 border-t-violet-600 dark:border-t-violet-400 rounded-full animate-spin"
195
+ ></div>
196
+ <span>Testing...</span>
197
+ {:else}
198
+ <Icon name="lucide:send" class="w-3.5 h-3.5" />
199
+ <span>Test Push</span>
200
+ {/if}
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>