@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,541 @@
1
+ <script lang="ts">
2
+ import { sessionState } from '$frontend/lib/stores/core/sessions.svelte';
3
+ import { appState } from '$frontend/lib/stores/core/app.svelte';
4
+ import ChatMessage from './ChatMessage.svelte';
5
+ import DateSeparator from './DateSeparator.svelte';
6
+ import { onMount, tick } from 'svelte';
7
+ import { fade } from 'svelte/transition';
8
+
9
+ // Import utilities
10
+ import { shouldFilterMessage } from '$frontend/lib/utils/chat/message-processor';
11
+ import { groupMessages, embedToolResults } from '$frontend/lib/utils/chat/message-grouper';
12
+ import { addDateSeparators, type DateSeparatorItem } from '$frontend/lib/utils/chat/date-separator';
13
+ import { editModeState } from '$frontend/lib/stores/ui/edit-mode.svelte';
14
+ import { createVirtualScroll, VS_CONFIG } from '$frontend/lib/utils/chat/virtual-scroll.svelte';
15
+
16
+ interface Props {
17
+ scrollContainer?: HTMLElement | undefined;
18
+ }
19
+
20
+ const { scrollContainer }: Props = $props();
21
+
22
+ // Internal scroll element - this is the actual scrollable div
23
+ let messagesScrollEl: HTMLElement | undefined = $state();
24
+
25
+ let previousMessageCount = $state(0);
26
+ let isUserAtBottom = $state(true);
27
+ let lastPartialTextHash = $state('');
28
+ let hasInitiallyScrolled = $state(false);
29
+ let isContentReady = $state(false);
30
+ let lastToolResultsHash = $state('');
31
+ // Prevent scroll events from overriding isUserAtBottom during programmatic scroll
32
+ let scrollLockUntil = 0;
33
+
34
+ // ========================================
35
+ // VIRTUAL SCROLL
36
+ // ========================================
37
+
38
+ const vs = createVirtualScroll();
39
+ let topSentinelEl: HTMLElement | undefined = $state();
40
+ let bottomSentinelEl: HTMLElement | undefined = $state();
41
+ let topObserver: IntersectionObserver | null = null;
42
+ let bottomObserver: IntersectionObserver | null = null;
43
+ let isLoadingOlder = $state(false);
44
+
45
+ // ========================================
46
+ // MESSAGE PROCESSING (full pipeline on all messages)
47
+ // ========================================
48
+
49
+ // Process messages through grouping and embedding
50
+ const processedMessages = $derived.by(() => {
51
+ const { groups, toolUseMap } = groupMessages(sessionState.messages);
52
+ return embedToolResults(groups, toolUseMap);
53
+ });
54
+
55
+ // Filter out messages with empty content arrays
56
+ const filteredMessages = $derived(
57
+ processedMessages.filter(message => !shouldFilterMessage(message))
58
+ );
59
+
60
+ // Windowed slice for rendering
61
+ const windowedMessages = $derived.by(() => {
62
+ if (!vs.isActive) return filteredMessages;
63
+ return filteredMessages.slice(vs.windowStart, vs.windowEnd);
64
+ });
65
+
66
+ // Add date separators to windowed messages (with global index offset for stable keys)
67
+ const messagesWithDateSeparators = $derived.by((): DateSeparatorItem[] => {
68
+ return addDateSeparators(windowedMessages, vs.isActive ? vs.windowStart : 0);
69
+ });
70
+
71
+ // Get last user message ID for undo button logic
72
+ const lastUserMessageId = $derived.by(() => {
73
+ const userMessages = filteredMessages.filter(m => m.type === 'user');
74
+ if (userMessages.length === 0) return undefined;
75
+ const lastUserMsg = userMessages[userMessages.length - 1];
76
+ return lastUserMsg.metadata?.message_id;
77
+ });
78
+
79
+ // ========================================
80
+ // SCROLL HELPERS
81
+ // ========================================
82
+
83
+ /**
84
+ * Resolve the best scroll container:
85
+ * - Use our own internal messagesScrollEl (which has overflow-y-auto)
86
+ * - Fall back to external scrollContainer from PageTemplate
87
+ */
88
+ function getScrollEl(): HTMLElement | undefined {
89
+ return messagesScrollEl ?? scrollContainer;
90
+ }
91
+
92
+ // Auto-scroll to bottom when new messages arrive
93
+ function scrollMessagesToBottom(smooth = true) {
94
+ const el = getScrollEl();
95
+ if (el) {
96
+ // During streaming, always use instant scroll to avoid jitter
97
+ // caused by smooth animation being outpaced by rapid DOM updates
98
+ const useSmooth = smooth && !appState.isLoading;
99
+ el.scrollTo({
100
+ top: el.scrollHeight,
101
+ behavior: useSmooth ? 'smooth' : 'auto'
102
+ });
103
+ // Immediately mark as at bottom after programmatic scroll
104
+ isUserAtBottom = true;
105
+ // Lock scroll detection briefly so scroll events fired during
106
+ // the programmatic scroll don't override isUserAtBottom
107
+ scrollLockUntil = Date.now() + 150;
108
+ }
109
+ }
110
+
111
+ // Scroll detection with bottom position tracking
112
+ function handleMessagesScroll() {
113
+ // Don't override isUserAtBottom during/after a programmatic scroll
114
+ if (Date.now() < scrollLockUntil) return;
115
+
116
+ const el = getScrollEl();
117
+ if (el) {
118
+ const { scrollTop, scrollHeight, clientHeight } = el;
119
+ const threshold = 200;
120
+ isUserAtBottom = scrollTop + clientHeight >= scrollHeight - threshold;
121
+ }
122
+ }
123
+
124
+ // ========================================
125
+ // VIRTUAL SCROLL: SENTINELS & OBSERVERS
126
+ // ========================================
127
+
128
+ function onTopSentinelVisible() {
129
+ if (!vs.hasMoreAbove || isLoadingOlder) return;
130
+ isLoadingOlder = true;
131
+
132
+ const el = getScrollEl();
133
+ if (!el) { isLoadingOlder = false; return; }
134
+
135
+ // Capture scroll state before expanding window
136
+ const prevScrollHeight = el.scrollHeight;
137
+ const prevScrollTop = el.scrollTop;
138
+
139
+ const added = vs.expandUp();
140
+ if (added > 0) {
141
+ // After DOM updates, restore scroll position so content doesn't jump
142
+ tick().then(() => {
143
+ requestAnimationFrame(() => {
144
+ const newScrollHeight = el.scrollHeight;
145
+ const heightAdded = newScrollHeight - prevScrollHeight;
146
+ el.scrollTop = prevScrollTop + heightAdded;
147
+
148
+ setTimeout(() => { isLoadingOlder = false; }, 150);
149
+
150
+ // Trim bottom if window grew too large and not streaming
151
+ if (vs.windowEnd - vs.windowStart > VS_CONFIG.TRIM_THRESHOLD && !appState.isLoading) {
152
+ vs.trimBottom();
153
+ }
154
+ });
155
+ });
156
+ } else {
157
+ isLoadingOlder = false;
158
+ }
159
+ }
160
+
161
+ function onBottomSentinelVisible() {
162
+ if (!vs.hasMoreBelow) return;
163
+
164
+ vs.expandDown();
165
+
166
+ // Trim top if window grew too large
167
+ if (vs.windowEnd - vs.windowStart > VS_CONFIG.TRIM_THRESHOLD) {
168
+ vs.trimTop();
169
+ }
170
+ }
171
+
172
+ function setupObservers() {
173
+ cleanupObservers();
174
+
175
+ if (!vs.isActive) return;
176
+
177
+ const root = getScrollEl();
178
+ if (!root) return;
179
+
180
+ topObserver = new IntersectionObserver(
181
+ (entries) => {
182
+ if (entries[0]?.isIntersecting) onTopSentinelVisible();
183
+ },
184
+ { root, rootMargin: '200px 0px 0px 0px', threshold: 0 }
185
+ );
186
+
187
+ bottomObserver = new IntersectionObserver(
188
+ (entries) => {
189
+ if (entries[0]?.isIntersecting) onBottomSentinelVisible();
190
+ },
191
+ { root, rootMargin: '0px 0px 200px 0px', threshold: 0 }
192
+ );
193
+
194
+ if (topSentinelEl) topObserver.observe(topSentinelEl);
195
+ if (bottomSentinelEl) bottomObserver.observe(bottomSentinelEl);
196
+ }
197
+
198
+ function cleanupObservers() {
199
+ topObserver?.disconnect();
200
+ bottomObserver?.disconnect();
201
+ topObserver = null;
202
+ bottomObserver = null;
203
+ }
204
+
205
+ // Re-setup observers when elements or activation state changes
206
+ $effect(() => {
207
+ const active = vs.isActive;
208
+ topSentinelEl;
209
+ bottomSentinelEl;
210
+ messagesScrollEl;
211
+
212
+ if (active && messagesScrollEl) {
213
+ setupObservers();
214
+ } else {
215
+ cleanupObservers();
216
+ }
217
+ });
218
+
219
+ // ========================================
220
+ // AUTO-SCROLL & VIRTUAL SCROLL SYNC
221
+ // ========================================
222
+
223
+ // Auto-scroll: track message count and partial message updates
224
+ $effect(() => {
225
+ const currentMessageCount = filteredMessages.length;
226
+ const isNewMessage = currentMessageCount > previousMessageCount;
227
+ const isMessageCountDecreased = currentMessageCount < previousMessageCount;
228
+
229
+ // Sync virtual scroll with message count changes
230
+ if (hasInitiallyScrolled) {
231
+ if (isMessageCountDecreased) {
232
+ vs.reset(currentMessageCount);
233
+ } else if (isNewMessage) {
234
+ vs.sync(currentMessageCount, isUserAtBottom, appState.isLoading);
235
+ }
236
+ }
237
+
238
+ // Track partial text changes across ALL streaming messages (not just the last)
239
+ // This handles reasoning + text streams both being active simultaneously
240
+ let partialTextChanged = false;
241
+ let hasStreamingMessage = false;
242
+ let currentPartialHash = '';
243
+ for (const msg of filteredMessages) {
244
+ if ('type' in msg && msg.type === 'stream_event' && 'partialText' in msg) {
245
+ hasStreamingMessage = true;
246
+ currentPartialHash += `${(msg as any).partialText?.length || 0}:`;
247
+ }
248
+ }
249
+ if (currentPartialHash !== lastPartialTextHash) {
250
+ if (currentPartialHash !== '') partialTextChanged = true;
251
+ lastPartialTextHash = currentPartialHash;
252
+ }
253
+
254
+ // Check for tool result updates ($result additions) across all messages
255
+ let toolResultChanged = false;
256
+ let currentToolResultsHash = '';
257
+
258
+ // Build a hash of all tool results to detect changes
259
+ for (const message of filteredMessages) {
260
+ if ('message' in message) {
261
+ const messageContent = (message as any).message?.content;
262
+ if (Array.isArray(messageContent)) {
263
+ for (const item of messageContent) {
264
+ if (item?.type === 'tool_use' && item.$result) {
265
+ currentToolResultsHash += `${item.id}:${JSON.stringify(item.$result).length}|`;
266
+ }
267
+ }
268
+ }
269
+ }
270
+ }
271
+
272
+ // Check if tool results have changed
273
+ if (currentToolResultsHash !== lastToolResultsHash) {
274
+ toolResultChanged = true;
275
+ lastToolResultsHash = currentToolResultsHash;
276
+ }
277
+
278
+ // When message count decreases (e.g. after undo/reload), scroll to bottom
279
+ if (isMessageCountDecreased && hasInitiallyScrolled) {
280
+ requestAnimationFrame(() => {
281
+ scrollMessagesToBottom(false);
282
+ });
283
+ previousMessageCount = currentMessageCount;
284
+ return;
285
+ }
286
+
287
+ // Scroll on new message, partial text changes, or tool result changes
288
+ if ((isNewMessage || partialTextChanged || toolResultChanged) && isUserAtBottom) {
289
+ requestAnimationFrame(() => {
290
+ if (editModeState.isEditing) return;
291
+
292
+ // During streaming, scroll immediately (instant via scrollMessagesToBottom)
293
+ // For non-streaming changes, add a small delay for smooth UX
294
+ if (partialTextChanged || hasStreamingMessage) {
295
+ scrollMessagesToBottom(false);
296
+ } else {
297
+ const delay = toolResultChanged ? 50 : 100;
298
+ setTimeout(() => {
299
+ if (!editModeState.isEditing) {
300
+ scrollMessagesToBottom(true);
301
+ }
302
+ }, delay);
303
+ }
304
+ });
305
+ }
306
+
307
+ // Reset partial text hash when not streaming
308
+ if (!hasStreamingMessage && lastPartialTextHash !== '') {
309
+ lastPartialTextHash = '';
310
+ }
311
+
312
+ previousMessageCount = currentMessageCount;
313
+ });
314
+
315
+ // ========================================
316
+ // TRANSITIONS & SCROLL LISTENERS
317
+ // ========================================
318
+
319
+ // Track if we should disable transitions (during restoration)
320
+ const disableTransitions = $derived(appState.isRestoring || (!hasInitiallyScrolled && filteredMessages.length > 5));
321
+
322
+ // Update scroll event listener when internal scroll element changes
323
+ let currentListenerEl: HTMLElement | null = null;
324
+ $effect(() => {
325
+ const el = messagesScrollEl ?? scrollContainer;
326
+
327
+ // Remove old listener if exists
328
+ if (currentListenerEl && currentListenerEl !== el) {
329
+ currentListenerEl.removeEventListener('scroll', handleMessagesScroll);
330
+ }
331
+
332
+ // Add new listener
333
+ if (el && el !== currentListenerEl) {
334
+ el.addEventListener('scroll', handleMessagesScroll, { passive: true });
335
+ currentListenerEl = el;
336
+ }
337
+ });
338
+
339
+ // ========================================
340
+ // RESTORATION & INITIAL SCROLL
341
+ // ========================================
342
+
343
+ // Handle restoration and initial scroll
344
+ $effect(() => {
345
+ const currentMessages = filteredMessages.length;
346
+ const isRestoring = appState.isRestoring;
347
+
348
+ if (isRestoring) {
349
+ // Keep content hidden during restoration
350
+ isContentReady = false;
351
+ hasInitiallyScrolled = false;
352
+ return;
353
+ }
354
+
355
+ // Restoration complete
356
+ if (!isRestoring && !hasInitiallyScrolled) {
357
+ const el = getScrollEl();
358
+ if (currentMessages > 0 && el) {
359
+ // Mark as scrolled
360
+ hasInitiallyScrolled = true;
361
+ previousMessageCount = currentMessages;
362
+
363
+ // Initialize virtual scroll
364
+ vs.reset(currentMessages);
365
+
366
+ // Scroll to bottom immediately (while still hidden)
367
+ // Skip if edit mode is active - scroll-to-edit-message will handle positioning
368
+ if (!editModeState.isEditing) {
369
+ el.scrollTop = el.scrollHeight;
370
+ }
371
+
372
+ // Show content after a micro delay to ensure scroll is set
373
+ requestAnimationFrame(() => {
374
+ isContentReady = true;
375
+ });
376
+ } else if (currentMessages === 0) {
377
+ // No messages, show immediately
378
+ isContentReady = true;
379
+ hasInitiallyScrolled = true;
380
+ } else {
381
+ // Wait for container
382
+ setTimeout(() => {
383
+ const el2 = getScrollEl();
384
+ if (el2) {
385
+ vs.reset(filteredMessages.length);
386
+ el2.scrollTop = el2.scrollHeight;
387
+ hasInitiallyScrolled = true;
388
+ }
389
+ isContentReady = true;
390
+ }, 50);
391
+ }
392
+ }
393
+ });
394
+
395
+ // ========================================
396
+ // EDIT MODE: SCROLL TO EDITED MESSAGE
397
+ // ========================================
398
+
399
+ // Auto-scroll to edited message when edit mode is activated (including refresh/project switch)
400
+ let lastEditMessageId: string | null = null;
401
+
402
+ function scrollToEditedMessage(messageId: string, retries = 10) {
403
+ if (!messagesScrollEl || !isContentReady) {
404
+ // Container not ready yet (refresh/project switch), retry
405
+ if (retries > 0) {
406
+ setTimeout(() => scrollToEditedMessage(messageId, retries - 1), 200);
407
+ }
408
+ return;
409
+ }
410
+
411
+ // Ensure message is within the virtual scroll window
412
+ if (vs.isActive) {
413
+ const msgIndex = filteredMessages.findIndex(m => m.metadata?.message_id === messageId);
414
+ if (msgIndex >= 0 && (msgIndex < vs.windowStart || msgIndex >= vs.windowEnd)) {
415
+ vs.ensureVisible(msgIndex);
416
+ // Wait for DOM to update after window change
417
+ tick().then(() => {
418
+ requestAnimationFrame(() => {
419
+ const el = messagesScrollEl?.querySelector(`[data-message-id="${messageId}"]`);
420
+ if (el) {
421
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
422
+ }
423
+ });
424
+ });
425
+ return;
426
+ }
427
+ }
428
+
429
+ const el = messagesScrollEl.querySelector(`[data-message-id="${messageId}"]`);
430
+ if (el) {
431
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
432
+ } else if (retries > 0) {
433
+ // Messages might not be rendered yet, retry
434
+ setTimeout(() => scrollToEditedMessage(messageId, retries - 1), 200);
435
+ }
436
+ }
437
+
438
+ $effect(() => {
439
+ const editMessageId = editModeState.isEditing ? editModeState.messageId : null;
440
+
441
+ if (editMessageId && editMessageId !== lastEditMessageId) {
442
+ // Wait for DOM to update then scroll to the edited message
443
+ requestAnimationFrame(() => {
444
+ scrollToEditedMessage(editMessageId);
445
+ });
446
+ }
447
+
448
+ lastEditMessageId = editMessageId;
449
+ });
450
+
451
+ // ========================================
452
+ // LIFECYCLE
453
+ // ========================================
454
+
455
+ onMount(() => {
456
+ // Reset flags on mount
457
+ hasInitiallyScrolled = false;
458
+
459
+ // Start with content hidden if restoring
460
+ if (appState.isRestoring) {
461
+ isContentReady = false;
462
+ } else {
463
+ // Not restoring, check if we need to scroll
464
+ const el = getScrollEl();
465
+ if (filteredMessages.length > 0 && el) {
466
+ // Initialize virtual scroll and scroll immediately
467
+ vs.reset(filteredMessages.length);
468
+ el.scrollTop = el.scrollHeight;
469
+ hasInitiallyScrolled = true;
470
+ isContentReady = true;
471
+ } else {
472
+ // No messages or no container yet, just show
473
+ isContentReady = true;
474
+ }
475
+ }
476
+
477
+ // Cleanup on component destroy
478
+ return () => {
479
+ cleanupObservers();
480
+ if (currentListenerEl) {
481
+ currentListenerEl.removeEventListener('scroll', handleMessagesScroll);
482
+ }
483
+ hasInitiallyScrolled = false;
484
+ };
485
+ });
486
+ </script>
487
+
488
+ <div class="flex flex-col h-full" style="position: relative">
489
+ <!-- Messages container -->
490
+ <div
491
+ bind:this={messagesScrollEl}
492
+ class="flex-1 overflow-y-auto pt-3 pb-14 lg:pb-16 px-3 lg:px-4 {isContentReady ? '' : 'invisible'}"
493
+ style="{!isContentReady ? 'scroll-behavior: auto' : ''}">
494
+
495
+ <!-- Top sentinel for virtual scroll (always present, observed only when active) -->
496
+ <div bind:this={topSentinelEl} class="h-px w-full" aria-hidden="true"></div>
497
+
498
+ <!-- Loading older messages indicator -->
499
+ {#if isLoadingOlder}
500
+ <div class="flex justify-center py-3">
501
+ <div class="flex items-center gap-2 text-xs text-slate-400 dark:text-slate-500">
502
+ <div class="w-3 h-3 border-2 border-slate-300 dark:border-slate-600 border-t-transparent rounded-full animate-spin"></div>
503
+ <span>Loading older messages...</span>
504
+ </div>
505
+ </div>
506
+ {/if}
507
+
508
+ {#each messagesWithDateSeparators as item (item.key)}
509
+ {#if disableTransitions}
510
+ <!-- No transition during restoration or initial load with many messages -->
511
+ <div>
512
+ {#if item.type === 'date'}
513
+ <DateSeparator date={item.data} />
514
+ {:else if item.type === 'message'}
515
+ {@const messageId = item.data.metadata?.message_id}
516
+ {@const isLastUser = messageId === lastUserMessageId}
517
+ <div class="mb-2 lg:mb-4" data-message-id={messageId}>
518
+ <ChatMessage message={item.data} isLastUserMessage={isLastUser} />
519
+ </div>
520
+ {/if}
521
+ </div>
522
+ {:else}
523
+ <!-- Normal transition for new messages -->
524
+ <div in:fade={{ duration: 300, delay: 0 }} out:fade={{ duration: 200 }}>
525
+ {#if item.type === 'date'}
526
+ <DateSeparator date={item.data} />
527
+ {:else if item.type === 'message'}
528
+ {@const messageId = item.data.metadata?.message_id}
529
+ {@const isLastUser = messageId === lastUserMessageId}
530
+ <div class="mb-2 lg:mb-4" data-message-id={messageId}>
531
+ <ChatMessage message={item.data} isLastUserMessage={isLastUser} />
532
+ </div>
533
+ {/if}
534
+ </div>
535
+ {/if}
536
+ {/each}
537
+
538
+ <!-- Bottom sentinel for virtual scroll -->
539
+ <div bind:this={bottomSentinelEl} class="h-px w-full" aria-hidden="true"></div>
540
+ </div>
541
+ </div>
@@ -0,0 +1,86 @@
1
+ <!--
2
+ Date Separator Component
3
+
4
+ Features:
5
+ - WhatsApp-style date separator
6
+ - Center-aligned with subtle styling
7
+ - Shows dates in user-friendly format
8
+ - Responsive design
9
+ -->
10
+
11
+ <script lang="ts">
12
+ const { date }: { date: string } = $props();
13
+
14
+ // Format date for display
15
+ const formatDate = (dateString: string) => {
16
+ const messageDate = new Date(dateString);
17
+ const today = new Date();
18
+ const yesterday = new Date(today);
19
+ yesterday.setDate(yesterday.getDate() - 1);
20
+
21
+ // Check if it's today
22
+ if (messageDate.toDateString() === today.toDateString()) {
23
+ return 'Today';
24
+ }
25
+
26
+ // Check if it's yesterday
27
+ if (messageDate.toDateString() === yesterday.toDateString()) {
28
+ return 'Yesterday';
29
+ }
30
+
31
+ // For older dates, show day and date
32
+ const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
33
+ const monthNames = [
34
+ 'January',
35
+ 'February',
36
+ 'March',
37
+ 'April',
38
+ 'May',
39
+ 'June',
40
+ 'July',
41
+ 'August',
42
+ 'September',
43
+ 'October',
44
+ 'November',
45
+ 'December'
46
+ ];
47
+
48
+ const dayName = dayNames[messageDate.getDay()];
49
+ const day = messageDate.getDate();
50
+ const month = monthNames[messageDate.getMonth()];
51
+ const year = messageDate.getFullYear();
52
+
53
+ // Show year only if it's not current year
54
+ if (year === today.getFullYear()) {
55
+ return `${dayName}, ${day} ${month}`;
56
+ } else {
57
+ return `${dayName}, ${day} ${month} ${year}`;
58
+ }
59
+ };
60
+
61
+ const displayDate = $derived(formatDate(date));
62
+ </script>
63
+
64
+ <!-- Date Separator -->
65
+ <div class="flex items-center justify-center my-3 md:my-4 px-4 md:px-6 select-none">
66
+ <div class="relative flex items-center w-full max-w-md">
67
+ <!-- Left line -->
68
+ <div
69
+ class="flex-grow h-px bg-gradient-to-r from-transparent via-violet-500/10 to-violet-500/20"
70
+ ></div>
71
+
72
+ <!-- Date badge - WhatsApp style -->
73
+ <div
74
+ class="px-3 py-1 mx-2.5 bg-slate-100/80 dark:bg-slate-600/20 backdrop-blur-sm rounded-lg border border-slate-200 dark:border-slate-800 shadow-sm"
75
+ >
76
+ <span class="text-xs font-medium text-slate-500 whitespace-nowrap tracking-wide">
77
+ {displayDate}
78
+ </span>
79
+ </div>
80
+
81
+ <!-- Right line -->
82
+ <div
83
+ class="flex-grow h-px bg-gradient-to-l from-transparent via-violet-500/10 to-violet-500/20"
84
+ ></div>
85
+ </div>
86
+ </div>
@@ -0,0 +1,86 @@
1
+ <!--
2
+ Message Bubble Component
3
+
4
+ Features:
5
+ - Message content wrapper
6
+ - Card styling
7
+ - Header and content sections
8
+ - Hover effects
9
+ -->
10
+
11
+ <script lang="ts">
12
+ import type { SDKMessageFormatter } from '$shared/types/database/schema';
13
+ import type { IconName } from '$shared/types/ui/icons';
14
+ import Card from '$frontend/lib/components/common/Card.svelte';
15
+ import MessageFormatter from '../formatters/MessageFormatter.svelte';
16
+ import MessageHeader from './MessageHeader.svelte';
17
+
18
+ const {
19
+ message,
20
+ messageTimestamp,
21
+ isLastUserMessage = false,
22
+ roleConfig,
23
+ roleCategory,
24
+ agentStatus,
25
+ senderName,
26
+ hasTokenUsageData,
27
+ formatTime,
28
+ onCopy,
29
+ onRestore,
30
+ onEdit,
31
+ onShowTokenUsage,
32
+ onShowDebug
33
+ }: {
34
+ message: SDKMessageFormatter;
35
+ messageTimestamp: string;
36
+ isLastUserMessage?: boolean;
37
+ roleConfig: { gradient: string; icon: IconName; name: string };
38
+ roleCategory: 'user' | 'assistant' | 'agent' | string;
39
+ agentStatus: 'processing' | 'success' | 'error' | null;
40
+ senderName: string | null;
41
+ hasTokenUsageData: any;
42
+ formatTime: (timestamp?: string) => string;
43
+ onCopy: () => void;
44
+ onRestore: () => void;
45
+ onEdit: () => void;
46
+ onShowTokenUsage: () => void;
47
+ onShowDebug: () => void;
48
+ } = $props();
49
+ </script>
50
+
51
+ <div class="relative overflow-hidden">
52
+ <Card
53
+ variant="outlined"
54
+ padding="none"
55
+ class="bg-white dark:bg-slate-900 text-slate-900 dark:text-slate-100 border border-slate-200 dark:border-slate-700 overflow-hidden"
56
+ >
57
+ <!-- Message Header -->
58
+ <MessageHeader
59
+ {message}
60
+ {messageTimestamp}
61
+ {isLastUserMessage}
62
+ {roleConfig}
63
+ {roleCategory}
64
+ {agentStatus}
65
+ {senderName}
66
+ {hasTokenUsageData}
67
+ {formatTime}
68
+ {onCopy}
69
+ {onRestore}
70
+ {onEdit}
71
+ {onShowTokenUsage}
72
+ {onShowDebug}
73
+ />
74
+
75
+ <!-- Message Content -->
76
+ <div class="p-3 md:p-4">
77
+ <div class="max-w-none">
78
+ <!-- Content rendering using MessageFormatter component -->
79
+ <MessageFormatter {message} />
80
+ </div>
81
+ </div>
82
+ </Card>
83
+
84
+ <!-- Hover glow effect -->
85
+ <div class="absolute inset-0 bg-gradient-to-r from-violet-500/5 to-violet-500/5 rounded-xl opacity-0 group-hover:opacity-100 transition-opacity -z-10 blur-xl"></div>
86
+ </div>