@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,563 @@
1
+ <script lang="ts">
2
+ import { untrack } from 'svelte';
3
+ import { sessionState, setCurrentSession, removeSession, reloadSessionsForProject } from '$frontend/lib/stores/core/sessions.svelte';
4
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
5
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
6
+ import ws from '$frontend/lib/utils/ws';
7
+ import type { ChatSession } from '$shared/types/database/schema';
8
+ import type { SDKMessage } from '$shared/types/messaging';
9
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
10
+ import AvatarBubble from '$frontend/lib/components/common/AvatarBubble.svelte';
11
+ import Modal from '$frontend/lib/components/common/Modal.svelte';
12
+ import Dialog from '$frontend/lib/components/common/Dialog.svelte';
13
+ import { presenceState } from '$frontend/lib/stores/core/presence.svelte';
14
+ import { userStore } from '$frontend/lib/stores/features/user.svelte';
15
+ import { debug } from '$shared/utils/logger';
16
+
17
+ interface Props {
18
+ isOpen: boolean;
19
+ onClose: () => void;
20
+ }
21
+
22
+ let { isOpen = $bindable(), onClose }: Props = $props();
23
+
24
+ // Use real session data from session store - filtered by current project
25
+ const sessions = $derived(
26
+ sessionState.sessions.filter(s => s.project_id === projectState.currentProject?.id)
27
+ );
28
+
29
+ // Helper to get relative time (last active)
30
+ function getRelativeTime(dateString: string): string {
31
+ const now = Date.now();
32
+ const date = new Date(dateString).getTime();
33
+ const diffMs = now - date;
34
+ const diffMins = Math.floor(diffMs / 1000 / 60);
35
+ const diffHours = Math.floor(diffMins / 60);
36
+ const diffDays = Math.floor(diffHours / 24);
37
+
38
+ if (diffMins < 1) return 'Just now';
39
+ if (diffMins < 60) return `${diffMins}m ago`;
40
+ if (diffHours < 24) return `${diffHours}h ago`;
41
+ if (diffDays < 7) return `${diffDays}d ago`;
42
+ if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
43
+ if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`;
44
+ return `${Math.floor(diffDays / 365)}y ago`;
45
+ }
46
+
47
+ // Helper to get last activity timestamp
48
+ function getLastActive(session: ChatSession): string {
49
+ const timestamp = session.ended_at && session.ended_at !== ''
50
+ ? session.ended_at
51
+ : session.started_at;
52
+ return getRelativeTime(timestamp);
53
+ }
54
+
55
+ // Cache for session data to avoid multiple API calls
56
+ let sessionDataCache = $state<Record<string, {
57
+ messages: SDKMessage[];
58
+ title: string;
59
+ summary: string;
60
+ count: number;
61
+ userCount: number;
62
+ assistantCount: number;
63
+ }>>({});
64
+ let loadingSessionData = $state(false);
65
+
66
+ // Helper to get session data from cache or API
67
+ async function getSessionData(sessionId: string) {
68
+ if (sessionDataCache[sessionId]) {
69
+ return sessionDataCache[sessionId];
70
+ }
71
+
72
+ try {
73
+ const messages = await ws.http('messages:list', { session_id: sessionId });
74
+
75
+ const firstUserMessage = messages.find((m: SDKMessage) => m.type === 'user');
76
+ let title = 'New Conversation';
77
+ if (firstUserMessage) {
78
+ let textContent = '';
79
+ if (typeof firstUserMessage.message.content === 'string') {
80
+ textContent = firstUserMessage.message.content;
81
+ } else if (Array.isArray(firstUserMessage.message.content)) {
82
+ const textBlocks = firstUserMessage.message.content.filter((c: any) => c.type === 'text');
83
+ textContent = textBlocks.map((b: any) => 'text' in b ? b.text : '').join(' ');
84
+ }
85
+
86
+ if (textContent) {
87
+ title = textContent.slice(0, 60) + (textContent.length > 60 ? '...' : '');
88
+ }
89
+ }
90
+
91
+ const assistantMessages = messages.filter((m: SDKMessage) => m.type === 'assistant');
92
+ let summary = 'No messages yet';
93
+ if (assistantMessages.length > 0) {
94
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
95
+ const textBlocks = lastMessage.message.content.filter((c: any) => c.type === 'text');
96
+ if (textBlocks.length > 0) {
97
+ const fullText = textBlocks.map((b: any) => 'text' in b ? b.text : '').join(' ');
98
+ const cleanText = fullText.replace(/```[\s\S]*?```/g, '').trim();
99
+ summary = cleanText.slice(0, 100) + (cleanText.length > 100 ? '...' : '');
100
+ }
101
+ }
102
+
103
+ const userMessages = messages.filter((m: SDKMessage) => {
104
+ if (m.type !== 'user') return false;
105
+ let textContent = '';
106
+ if (typeof m.message.content === 'string') {
107
+ textContent = m.message.content;
108
+ } else if (Array.isArray(m.message.content)) {
109
+ const textBlocks = m.message.content.filter(c => c.type === 'text');
110
+ textContent = textBlocks.map(b => 'text' in b ? b.text : '').join(' ');
111
+ }
112
+ return textContent.trim().length > 0;
113
+ });
114
+
115
+ const totalBubbles = userMessages.length + assistantMessages.length;
116
+
117
+ const data = {
118
+ messages,
119
+ title,
120
+ summary,
121
+ count: totalBubbles,
122
+ userCount: userMessages.length,
123
+ assistantCount: assistantMessages.length
124
+ };
125
+
126
+ sessionDataCache[sessionId] = data;
127
+ debug.log('session', `Loaded session ${sessionId}:`, {
128
+ title,
129
+ totalMessages: messages.length,
130
+ userCount: userMessages.length,
131
+ assistantCount: assistantMessages.length,
132
+ totalBubbles: totalBubbles,
133
+ summary: summary.substring(0, 50)
134
+ });
135
+ return data;
136
+ } catch (error) {
137
+ debug.error('session', 'Error fetching session data:', error);
138
+ return {
139
+ messages: [],
140
+ title: 'New Conversation',
141
+ summary: 'No messages yet',
142
+ count: 0,
143
+ userCount: 0,
144
+ assistantCount: 0
145
+ };
146
+ }
147
+ }
148
+
149
+ function getMessageCount(sessionId: string): number {
150
+ return sessionDataCache[sessionId]?.count || 0;
151
+ }
152
+
153
+ function getUserMessageCount(sessionId: string): number {
154
+ return sessionDataCache[sessionId]?.userCount || 0;
155
+ }
156
+
157
+ function getSessionTitle(sessionId: string): string {
158
+ return sessionDataCache[sessionId]?.title || 'New Conversation';
159
+ }
160
+
161
+ function getSessionSummary(sessionId: string): string {
162
+ return sessionDataCache[sessionId]?.summary || 'No messages yet';
163
+ }
164
+
165
+ async function preloadSessionData() {
166
+ loadingSessionData = true;
167
+ try {
168
+ // Sort newest first and load top 20 so new sessions are always included
169
+ const sortedSessions = [...sessions]
170
+ .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
171
+ .slice(0, 20);
172
+ await Promise.all(
173
+ sortedSessions.map(session => getSessionData(session.id))
174
+ );
175
+ } catch (error) {
176
+ debug.error('session', 'Error preloading session data:', error);
177
+ } finally {
178
+ loadingSessionData = false;
179
+ }
180
+ }
181
+
182
+ // Refresh sessions from server and reload data each time the modal opens.
183
+ // Uses untrack() so only isOpen is tracked — prevents infinite loops from
184
+ // session/cache mutations re-triggering this effect.
185
+ $effect(() => {
186
+ if (isOpen) {
187
+ untrack(() => {
188
+ // Clear stale cache so all data is fetched fresh
189
+ sessionDataCache = {};
190
+ // Fetch latest sessions from server, then load message data
191
+ reloadSessionsForProject().then(() => {
192
+ preloadSessionData();
193
+ });
194
+ });
195
+ }
196
+ });
197
+
198
+ // Incrementally load new sessions that arrive while the modal is open
199
+ // (e.g., from sessions:session-available broadcasts). No spinner — background load.
200
+ $effect(() => {
201
+ if (!isOpen || loadingSessionData) return;
202
+ const currentSessions = sessions;
203
+ if (currentSessions.length === 0) return;
204
+
205
+ const uncached = currentSessions.filter(s => !sessionDataCache[s.id]);
206
+ if (uncached.length > 0) {
207
+ debug.log('session', `Found ${uncached.length} new sessions, loading data...`);
208
+ // Load individually without showing spinner
209
+ Promise.all(uncached.map(s => getSessionData(s.id)));
210
+ }
211
+ });
212
+
213
+ // Track streaming sessions to detect when a stream completes.
214
+ // When a session stops streaming, refresh its cache to show the new summary.
215
+ let previouslyStreamingIds = new Set<string>();
216
+
217
+ $effect(() => {
218
+ if (!isOpen) return;
219
+
220
+ // Read presence state to detect stream changes
221
+ const projectId = projectState.currentProject?.id;
222
+ if (!projectId) return;
223
+ const status = presenceState.statuses.get(projectId);
224
+ const activeStreams = status?.streams?.filter((s: any) => s.status === 'active') || [];
225
+ const currentlyStreamingIds = new Set(activeStreams.map((s: any) => s.chatSessionId));
226
+
227
+ // Sessions that stopped streaming → refresh their cache for updated summary
228
+ for (const sessionId of previouslyStreamingIds) {
229
+ if (!currentlyStreamingIds.has(sessionId)) {
230
+ delete sessionDataCache[sessionId];
231
+ getSessionData(sessionId);
232
+ }
233
+ }
234
+
235
+ previouslyStreamingIds = currentlyStreamingIds;
236
+ });
237
+
238
+ // Get users in a specific chat session (excluding self)
239
+ function getSessionUsers(chatSessionId: string): { userId: string; userName: string }[] {
240
+ const projectId = projectState.currentProject?.id;
241
+ const currentUserId = userStore.currentUser?.id;
242
+ if (!projectId) return [];
243
+ const status = presenceState.statuses.get(projectId);
244
+ if (!status?.chatSessionUsers) return [];
245
+ const users = status.chatSessionUsers[chatSessionId] || [];
246
+ return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
247
+ }
248
+
249
+ // Check if a session has an active stream
250
+ function isSessionStreaming(chatSessionId: string): boolean {
251
+ const projectId = projectState.currentProject?.id;
252
+ if (!projectId) return false;
253
+ const status = presenceState.statuses.get(projectId);
254
+ if (!status?.streams) return false;
255
+ return status.streams.some(
256
+ (s: any) => s.status === 'active' && s.chatSessionId === chatSessionId
257
+ );
258
+ }
259
+
260
+ // Get the engine/model display for a session
261
+ function getSessionModel(session: ChatSession): string {
262
+ if (session.engine && session.model) {
263
+ const parts = session.model.split(':');
264
+ return parts.length > 1 ? parts[1] : session.model;
265
+ }
266
+ return '';
267
+ }
268
+
269
+ let searchQuery = $state('');
270
+
271
+ const filteredSessions = $derived(
272
+ sessions
273
+ .filter(session => {
274
+ const title = getSessionTitle(session.id);
275
+ const summary = getSessionSummary(session.id);
276
+ const messageCount = getMessageCount(session.id);
277
+
278
+ // Show sessions that are currently streaming even if 0 messages yet
279
+ if (messageCount === 0 && !isSessionStreaming(session.id)) {
280
+ return false;
281
+ }
282
+
283
+ const matchesSearch = searchQuery === '' ||
284
+ title.toLowerCase().includes(searchQuery.toLowerCase()) ||
285
+ summary.toLowerCase().includes(searchQuery.toLowerCase());
286
+
287
+ return matchesSearch;
288
+ })
289
+ .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
290
+ );
291
+
292
+ function isActiveSession(session: ChatSession): boolean {
293
+ return sessionState.currentSession?.id === session.id;
294
+ }
295
+
296
+ async function resumeSession(session: ChatSession | null) {
297
+ if (!session) return;
298
+
299
+ try {
300
+ let targetSession = session;
301
+ if (session.ended_at) {
302
+ const reactivatedSession = await ws.http('sessions:update', { id: session.id, reactivate: true });
303
+ if (reactivatedSession) {
304
+ const sessionIndex = sessionState.sessions.findIndex(s => s.id === session.id);
305
+ if (sessionIndex !== -1) {
306
+ sessionState.sessions[sessionIndex] = reactivatedSession;
307
+ }
308
+ targetSession = reactivatedSession;
309
+ }
310
+ }
311
+
312
+ await setCurrentSession(targetSession);
313
+ onClose();
314
+ } catch (error) {
315
+ debug.error('session', 'Error resuming session:', error);
316
+ addNotification({
317
+ type: 'error',
318
+ title: 'Resume Failed',
319
+ message: 'Failed to resume session',
320
+ duration: 5000
321
+ });
322
+ }
323
+ }
324
+
325
+ // Delete session state
326
+ let showDeleteDialog = $state(false);
327
+ let sessionToDelete = $state<ChatSession | null>(null);
328
+
329
+ function handleDeleteClick(session: ChatSession, event: MouseEvent) {
330
+ event.stopPropagation();
331
+ sessionToDelete = session;
332
+ showDeleteDialog = true;
333
+ }
334
+
335
+ async function confirmDeleteSession() {
336
+ if (!sessionToDelete) return;
337
+ const deleteId = sessionToDelete.id;
338
+
339
+ try {
340
+ await ws.http('sessions:delete', { id: deleteId });
341
+ removeSession(deleteId);
342
+
343
+ // Clean cache
344
+ delete sessionDataCache[deleteId];
345
+
346
+ addNotification({
347
+ type: 'success',
348
+ title: 'Session Deleted',
349
+ message: 'Chat session has been deleted',
350
+ duration: 3000
351
+ });
352
+
353
+ showDeleteDialog = false;
354
+ sessionToDelete = null;
355
+ } catch (error) {
356
+ debug.error('session', 'Failed to delete session:', error);
357
+ addNotification({
358
+ type: 'error',
359
+ title: 'Error',
360
+ message: 'Failed to delete session',
361
+ duration: 5000
362
+ });
363
+ }
364
+ }
365
+
366
+ function closeDeleteDialog() {
367
+ showDeleteDialog = false;
368
+ sessionToDelete = null;
369
+ }
370
+
371
+ function closeModal() {
372
+ searchQuery = '';
373
+ onClose();
374
+ }
375
+ </script>
376
+
377
+ <Modal bind:isOpen onClose={closeModal} size="md">
378
+ {#snippet header()}
379
+ <div class="flex items-center justify-between px-4 py-3 md:px-6 md:py-4">
380
+ <h2 class="text-base md:text-lg font-bold text-slate-900 dark:text-slate-100">Sessions</h2>
381
+ <button
382
+ type="button"
383
+ class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
384
+ onclick={closeModal}
385
+ aria-label="Close modal"
386
+ >
387
+ <svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
388
+ <path
389
+ stroke-linecap="round"
390
+ stroke-linejoin="round"
391
+ stroke-width="2"
392
+ d="M6 18L18 6M6 6l12 12"
393
+ />
394
+ </svg>
395
+ </button>
396
+ </div>
397
+ {/snippet}
398
+
399
+ {#snippet children()}
400
+ <!-- Search Box -->
401
+ {#if sessions.length > 0}
402
+ <div class="mb-4">
403
+ <div
404
+ class="flex items-center gap-2 py-2.5 px-3.5 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-lg"
405
+ >
406
+ <Icon name="lucide:search" class="w-4 h-4 text-slate-500 dark:text-slate-400 shrink-0" />
407
+ <input
408
+ type="text"
409
+ bind:value={searchQuery}
410
+ placeholder="Search sessions..."
411
+ class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-500 dark:placeholder:text-slate-400"
412
+ />
413
+ {#if searchQuery}
414
+ <button
415
+ type="button"
416
+ class="flex items-center justify-center w-5 h-5 bg-transparent border-none rounded text-slate-400 cursor-pointer transition-all duration-150 hover:text-slate-600 dark:hover:text-slate-300"
417
+ onclick={() => (searchQuery = '')}
418
+ aria-label="Clear search"
419
+ >
420
+ <Icon name="lucide:x" class="w-3.5 h-3.5" />
421
+ </button>
422
+ {/if}
423
+ </div>
424
+ </div>
425
+ {/if}
426
+
427
+ <!-- Sessions List -->
428
+ {#if loadingSessionData}
429
+ <div class="flex items-center justify-center gap-3 py-12">
430
+ <div class="animate-spin rounded-full h-6 w-6 border-b-2 border-violet-600"></div>
431
+ <span class="text-sm text-slate-600 dark:text-slate-400">Loading sessions...</span>
432
+ </div>
433
+ {:else if filteredSessions.length === 0}
434
+ <div class="flex flex-col items-center gap-3 py-8 text-slate-600 dark:text-slate-500 text-sm">
435
+ <Icon name="lucide:message-square-off" class="w-12 h-12 text-slate-400 opacity-40" />
436
+ <p class="font-medium">No sessions found</p>
437
+ <p class="text-xs text-slate-500 dark:text-slate-500">
438
+ {searchQuery ? 'Try adjusting your search' : 'Start a new chat to see it here'}
439
+ </p>
440
+ {#if searchQuery}
441
+ <button
442
+ type="button"
443
+ class="text-xs text-violet-600 dark:text-violet-400 underline cursor-pointer hover:text-violet-700 dark:hover:text-violet-300"
444
+ onclick={() => (searchQuery = '')}
445
+ >
446
+ Clear search
447
+ </button>
448
+ {/if}
449
+ </div>
450
+ {:else}
451
+ <div class="space-y-2">
452
+ {#each filteredSessions.slice(0, 20) as session (session.id)}
453
+ {@const isActive = isActiveSession(session)}
454
+ {@const sessionUsers = getSessionUsers(session.id)}
455
+ {@const streaming = isSessionStreaming(session.id)}
456
+ {@const modelName = getSessionModel(session)}
457
+ <div
458
+ class="flex items-center gap-2 w-full p-3 bg-transparent border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 text-sm text-left transition-all duration-150
459
+ {isActive
460
+ ? 'border-violet-300 dark:border-violet-600 bg-violet-50 dark:bg-violet-900/10'
461
+ : ''}"
462
+ >
463
+ <button
464
+ type="button"
465
+ class="flex items-center gap-3 flex-1 min-w-0 bg-transparent border-none cursor-pointer text-left"
466
+ onclick={() => resumeSession(session)}
467
+ >
468
+ <div
469
+ class="relative w-8 h-8 {isActive
470
+ ? 'bg-violet-200 dark:bg-violet-800/30'
471
+ : 'bg-violet-100 dark:bg-violet-900/20'} rounded-lg flex items-center justify-center flex-shrink-0"
472
+ >
473
+ <Icon name="lucide:message-square" class="text-violet-600 dark:text-violet-400 w-4 h-4" />
474
+ {#if streaming}
475
+ <span
476
+ class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-emerald-500"
477
+ ></span>
478
+ {:else}
479
+ <span
480
+ class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-slate-300 dark:bg-slate-600"
481
+ ></span>
482
+ {/if}
483
+ </div>
484
+ <div class="flex-1 min-w-0">
485
+ <div class="flex items-center gap-2">
486
+ <p class="font-semibold text-slate-900 dark:text-slate-100 truncate text-sm">
487
+ {getSessionTitle(session.id)}
488
+ </p>
489
+ {#if isActive}
490
+ <span
491
+ class="inline-flex items-center gap-1 px-2 py-0.5 bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-300 text-xs font-medium rounded-full shrink-0"
492
+ >
493
+ <Icon name="lucide:circle-check" class="w-3 h-3" />
494
+ Active
495
+ </span>
496
+ {/if}
497
+ </div>
498
+ <div class="flex items-center gap-2 mt-0.5 text-xs text-slate-500 dark:text-slate-400">
499
+ <span class="flex items-center gap-1 flex-none">
500
+ <Icon name="lucide:messages-square" class="w-3 h-3" />
501
+ {getUserMessageCount(session.id)}
502
+ </span>
503
+ <span>·</span>
504
+ <span class="flex items-center gap-1 flex-none">
505
+ <Icon name="lucide:clock" class="w-3 h-3" />
506
+ {getLastActive(session)}
507
+ </span>
508
+ {#if modelName}
509
+ <span>·</span>
510
+ <span class="truncate">{modelName}</span>
511
+ {/if}
512
+ </div>
513
+ {#if streaming}
514
+ <p class="text-xs text-violet-500 dark:text-violet-400 mt-0.5 flex items-center gap-1.5">
515
+ <span class="inline-block w-3 h-3 border-2 border-violet-400 border-t-transparent rounded-full animate-spin shrink-0"></span>
516
+ Processing...
517
+ </p>
518
+ {:else}
519
+ <p class="text-xs text-slate-400 dark:text-slate-500 truncate mt-0.5">
520
+ {getSessionSummary(session.id)}
521
+ </p>
522
+ {/if}
523
+ </div>
524
+ </button>
525
+ {#if sessionUsers.length > 0}
526
+ <div class="flex items-center -space-x-1 shrink-0">
527
+ {#each sessionUsers.slice(0, 2) as user}
528
+ <AvatarBubble {user} size="sm" />
529
+ {/each}
530
+ {#if sessionUsers.length > 2}
531
+ <span class="w-5 h-5 rounded-full bg-gradient-to-br from-slate-500 to-slate-600 text-white text-4xs font-bold flex items-center justify-center border-2 border-white dark:border-slate-900 z-10">
532
+ +{sessionUsers.length - 2}
533
+ </span>
534
+ {/if}
535
+ </div>
536
+ {/if}
537
+ <button
538
+ type="button"
539
+ class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg text-slate-400 dark:text-slate-500 cursor-pointer transition-all duration-150 hover:bg-red-500/15 hover:text-red-500 shrink-0"
540
+ onclick={(e) => handleDeleteClick(session, e)}
541
+ aria-label="Delete session"
542
+ title="Delete"
543
+ >
544
+ <Icon name="lucide:trash-2" class="w-4 h-4" />
545
+ </button>
546
+ </div>
547
+ {/each}
548
+ </div>
549
+ {/if}
550
+ {/snippet}
551
+ </Modal>
552
+
553
+ <!-- Delete Confirmation Dialog -->
554
+ <Dialog
555
+ bind:isOpen={showDeleteDialog}
556
+ onClose={closeDeleteDialog}
557
+ type="error"
558
+ title="Delete Session"
559
+ message="Are you sure you want to delete this session? All messages will be permanently removed."
560
+ confirmText="Delete"
561
+ cancelText="Cancel"
562
+ onConfirm={confirmDeleteSession}
563
+ />