@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,407 @@
1
+ <!--
2
+ Modern Claude Code SDK Chat Interface
3
+
4
+ Features:
5
+ - Real-time streaming with Server-Sent Events
6
+ - Modern AI-first design with solid backgrounds
7
+ - Proper SDK message handling
8
+ - Enhanced error handling and user feedback
9
+ - Responsive design with mobile optimization
10
+ -->
11
+
12
+ <script lang="ts">
13
+ import { sessionState, setCurrentSession, createNewChatSession, clearMessages, loadMessagesForSession } from '$frontend/lib/stores/core/sessions.svelte';
14
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
15
+ import { appState } from '$frontend/lib/stores/core/app.svelte';
16
+ import { presenceState } from '$frontend/lib/stores/core/presence.svelte';
17
+ import { userStore } from '$frontend/lib/stores/features/user.svelte';
18
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
19
+ import { onMount } from 'svelte';
20
+ import { fade } from 'svelte/transition';
21
+ import ChatMessages from './message/ChatMessages.svelte';
22
+ import ChatInput from './input/ChatInput.svelte';
23
+ import FloatingTodoList from './widgets/FloatingTodoList.svelte';
24
+ import TimelineModal from '$frontend/lib/components/checkpoint/TimelineModal.svelte';
25
+ // import TokenUsage from './widgets/TokenUsage.svelte'; // DISABLED
26
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
27
+ import AvatarBubble from '$frontend/lib/components/common/AvatarBubble.svelte';
28
+ import Button from '$frontend/lib/components/common/Button.svelte';
29
+ import PageTemplate from '$frontend/lib/components/common/PageTemplate.svelte';
30
+ import Modal from '$frontend/lib/components/common/Modal.svelte';
31
+ import type { IconName } from '$shared/types/ui/icons';
32
+ import type { ChatSession } from '$shared/types/database/schema';
33
+ import { debug } from '$shared/utils/logger';
34
+ import ws from '$frontend/lib/utils/ws';
35
+
36
+ // Welcome state - don't show during restoration
37
+ const isWelcomeState = $derived(
38
+ sessionState.messages.length === 0 &&
39
+ !appState.isRestoring
40
+ );
41
+
42
+ // Check if we should show input (not during restoration)
43
+ const showInput = $derived(!appState.isRestoring);
44
+
45
+ // Project-aware state
46
+ const hasActiveProject = $derived(projectState.currentProject !== null);
47
+ const projectName = $derived(projectState.currentProject?.name || 'No Project');
48
+
49
+ // Dynamic title for PageTemplate (static description)
50
+ const pageTitle = $derived(hasActiveProject ? `${projectName}` : 'No project selected');
51
+
52
+ // Scroll container for passing to PageTemplate
53
+ let scrollContainer: HTMLElement | undefined = $state();
54
+
55
+ // Session picker state
56
+ let showSessionPicker = $state(false);
57
+
58
+ // Active sessions for the current project (non-ended), sorted newest first
59
+ const activeSessions = $derived(
60
+ sessionState.sessions
61
+ .filter(s => s.project_id === projectState.currentProject?.id && !s.ended_at)
62
+ .sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
63
+ );
64
+
65
+ // Check if another session has an active stream (for badge indicator)
66
+ const otherSessionsStreaming = $derived.by(() => {
67
+ const projectId = projectState.currentProject?.id;
68
+ const currentSessionId = sessionState.currentSession?.id;
69
+ if (!projectId) return false;
70
+ const status = presenceState.statuses.get(projectId);
71
+ if (!status?.streams) return false;
72
+ return status.streams.some(
73
+ (s: any) => s.status === 'active' && s.chatSessionId !== currentSessionId
74
+ );
75
+ });
76
+
77
+ // Get users in a specific chat session (excluding self)
78
+ function getSessionUsers(chatSessionId: string): { userId: string; userName: string }[] {
79
+ const projectId = projectState.currentProject?.id;
80
+ const currentUserId = userStore.currentUser?.id;
81
+ if (!projectId) return [];
82
+ const status = presenceState.statuses.get(projectId);
83
+ if (!status?.chatSessionUsers) return [];
84
+ const users = status.chatSessionUsers[chatSessionId] || [];
85
+ return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
86
+ }
87
+
88
+ function toggleSessionPicker() {
89
+ showSessionPicker = !showSessionPicker;
90
+ }
91
+
92
+ function closeSessionPicker() {
93
+ showSessionPicker = false;
94
+ }
95
+
96
+ async function switchToSession(session: ChatSession) {
97
+ if (session.id === sessionState.currentSession?.id) {
98
+ closeSessionPicker();
99
+ return;
100
+ }
101
+ await setCurrentSession(session);
102
+ closeSessionPicker();
103
+ }
104
+
105
+ // Extract short title from session (first user message text)
106
+ function getSessionShortTitle(session: ChatSession): string {
107
+ return session.title?.replace(/^Shared Chat - /, '').slice(0, 40) || 'New Chat';
108
+ }
109
+
110
+ // Checkpoints modal state
111
+ let showCheckpoints = $state(false);
112
+
113
+ function openCheckpoints() {
114
+ showCheckpoints = true;
115
+ }
116
+
117
+ function closeCheckpoints() {
118
+ showCheckpoints = false;
119
+ }
120
+
121
+ // Extract text from message content
122
+ function extractMessageText(message: any): string {
123
+ if (!('message' in message) || !message.message?.content) {
124
+ return '';
125
+ }
126
+ const content = message.message.content;
127
+
128
+ if (typeof content === 'string') {
129
+ return content;
130
+ } else if (Array.isArray(content)) {
131
+ // Find text content in array
132
+ for (const item of content) {
133
+ if (typeof item === 'string') {
134
+ return item;
135
+ } else if (typeof item === 'object' && item !== null) {
136
+ if ('text' in item && typeof (item as any).text === 'string') {
137
+ return (item as any).text;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ return '';
143
+ }
144
+
145
+ // Process timeline messages with all necessary data
146
+ const timelineMessages = $derived(
147
+ sessionState.messages
148
+ .filter(m => {
149
+ if (m.type !== 'user') return false;
150
+ const text = extractMessageText(m);
151
+ return text.length > 0;
152
+ })
153
+ .map(msg => ({
154
+ id: msg.metadata?.message_id,
155
+ timestamp: msg.metadata?.created_at || '',
156
+ date: msg.metadata?.created_at ? new Date(msg.metadata.created_at).toLocaleDateString() : 'Unknown',
157
+ time: msg.metadata?.created_at ? new Date(msg.metadata.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'Unknown',
158
+ text: extractMessageText(msg)
159
+ }))
160
+ );
161
+
162
+ // Handle restore from timeline
163
+ async function handleTimelineRestore(messageId: string | undefined, messageTimestamp: string) {
164
+ if (!messageId) {
165
+ addNotification({
166
+ type: 'error',
167
+ title: 'Restore Failed',
168
+ message: 'Message ID not found',
169
+ duration: 3000
170
+ });
171
+ return;
172
+ }
173
+
174
+ try {
175
+ // Send restore request via WebSocket HTTP
176
+ await ws.http('snapshot:restore', {
177
+ messageId: messageId,
178
+ sessionId: sessionState.currentSession?.id || ''
179
+ });
180
+
181
+ // Close modal
182
+ showCheckpoints = false;
183
+
184
+ // Reload messages from database to update UI
185
+ if (sessionState.currentSession?.id) {
186
+ await loadMessagesForSession(sessionState.currentSession.id);
187
+ }
188
+
189
+ addNotification({
190
+ type: 'success',
191
+ title: 'Project Restored',
192
+ message: `Successfully restored to checkpoint at ${new Date(messageTimestamp).toLocaleTimeString()}`,
193
+ duration: 5000
194
+ });
195
+ } catch (error) {
196
+ debug.error('chat', 'Restore error:', error);
197
+ addNotification({
198
+ type: 'error',
199
+ title: 'Restore Failed',
200
+ message: error instanceof Error ? error.message : 'Unknown error',
201
+ duration: 5000
202
+ });
203
+ }
204
+ }
205
+
206
+ async function startNewChat() {
207
+ if (!hasActiveProject || !projectState.currentProject) {
208
+ addNotification({
209
+ type: 'warning',
210
+ title: 'No Project Selected',
211
+ message: 'Please select a project first',
212
+ duration: 3000
213
+ });
214
+ return;
215
+ }
216
+
217
+ // Clear messages for the local view
218
+ clearMessages();
219
+
220
+ // Create a new session (existing sessions stay active for other users)
221
+ const newSession = await createNewChatSession(projectState.currentProject.id);
222
+
223
+ if (newSession) {
224
+ await setCurrentSession(newSession);
225
+ } else {
226
+ addNotification({
227
+ type: 'error',
228
+ title: 'Failed to Create Session',
229
+ message: 'Could not create a new chat session',
230
+ duration: 3000
231
+ });
232
+ }
233
+ }
234
+
235
+ // Check for active stream on mount only if needed
236
+ onMount(async () => {
237
+ debug.log('chat', 'Component mounted');
238
+ // WebSocket reconnection is handled automatically by ws client
239
+ });
240
+ </script>
241
+
242
+ <PageTemplate
243
+ title={pageTitle}
244
+ description="AI-powered development assistant"
245
+ bind:scrollContainer
246
+ >
247
+ {#snippet actions()}
248
+ <div class="flex items-center space-x-2">
249
+ <!-- Session Picker (only when multiple active sessions) -->
250
+ {#if activeSessions.length > 1}
251
+ <div class="relative">
252
+ <button
253
+ onclick={toggleSessionPicker}
254
+ class="flex items-center gap-1.5 px-2 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm font-medium rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 hover:border-violet-500 dark:hover:border-violet-500 transition-all duration-200"
255
+ >
256
+ <Icon name="lucide:layers" class="w-3.5 h-3.5" />
257
+ <span class="hidden sm:inline max-w-[120px] truncate">{activeSessions.length} sessions</span>
258
+ {#if otherSessionsStreaming}
259
+ <span class="w-2 h-2 rounded-full bg-violet-500 animate-pulse"></span>
260
+ {/if}
261
+ <Icon name="lucide:chevron-down" class="w-3 h-3" />
262
+ </button>
263
+
264
+ {#if showSessionPicker}
265
+ <!-- Backdrop -->
266
+ <div class="fixed inset-0 z-40" onclick={closeSessionPicker}></div>
267
+
268
+ <!-- Dropdown -->
269
+ <div class="absolute top-full right-0 mt-1 z-50 w-72 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg overflow-hidden">
270
+ <div class="px-3 py-2 border-b border-slate-100 dark:border-slate-700">
271
+ <p class="text-xs font-medium text-slate-500 dark:text-slate-400">Active Sessions</p>
272
+ </div>
273
+ <div class="max-h-64 overflow-y-auto">
274
+ {#each activeSessions as session (session.id)}
275
+ {@const isCurrent = session.id === sessionState.currentSession?.id}
276
+ {@const projectId = projectState.currentProject?.id}
277
+ {@const status = projectId ? presenceState.statuses.get(projectId) : undefined}
278
+ {@const isStreaming = status?.streams?.some((s: any) => s.status === 'active' && s.chatSessionId === session.id) ?? false}
279
+ {@const sessionUsers = getSessionUsers(session.id)}
280
+ {@const sessionModel = session.model ? (session.model.includes(':') ? session.model.split(':')[1] : session.model) : ''}
281
+ <button
282
+ onclick={() => switchToSession(session)}
283
+ class="w-full text-left px-3 py-2.5 flex items-center gap-2.5 transition-colors duration-150
284
+ {isCurrent
285
+ ? 'bg-violet-50 dark:bg-violet-900/20 text-violet-700 dark:text-violet-300'
286
+ : 'text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700/50'}"
287
+ >
288
+ <div class="flex-1 min-w-0">
289
+ <div class="flex items-center gap-1.5">
290
+ <span class="w-1.5 h-1.5 rounded-full shrink-0 {isStreaming ? 'bg-emerald-500 animate-pulse' : isCurrent ? 'bg-green-500' : 'bg-slate-300 dark:bg-slate-600'}"></span>
291
+ <span class="text-sm font-medium truncate">{getSessionShortTitle(session)}</span>
292
+ </div>
293
+ <p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
294
+ {new Date(session.started_at).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
295
+ {#if sessionModel}
296
+ <span class="text-slate-400 dark:text-slate-500">· {sessionModel}</span>
297
+ {/if}
298
+ </p>
299
+ </div>
300
+ {#if sessionUsers.length > 0}
301
+ <div class="flex items-center -space-x-1 shrink-0">
302
+ {#each sessionUsers.slice(0, 2) as user}
303
+ <AvatarBubble {user} size="sm" />
304
+ {/each}
305
+ {#if sessionUsers.length > 2}
306
+ <span class="w-4 h-4 rounded-full bg-slate-400 text-white text-5xs font-bold flex items-center justify-center border border-white dark:border-slate-800">
307
+ +{sessionUsers.length - 2}
308
+ </span>
309
+ {/if}
310
+ </div>
311
+ {/if}
312
+ {#if isStreaming}
313
+ <span class="shrink-0 flex items-center gap-1 px-1.5 py-0.5 text-3xs font-medium rounded-full bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-400">
314
+ <span class="w-1.5 h-1.5 rounded-full bg-violet-500 animate-pulse"></span>
315
+ AI
316
+ </span>
317
+ {/if}
318
+ </button>
319
+ {/each}
320
+ </div>
321
+ </div>
322
+ {/if}
323
+ </div>
324
+ {/if}
325
+
326
+ <!-- Restore Checkpoint button -->
327
+ {#if sessionState.messages.length > 0}
328
+ <Button variant="outline" onclick={openCheckpoints} class="rounded-lg px-2 sm:px-4 py-1.5 sm:py-2">
329
+ <Icon name="lucide:undo-2" class="w-3.5 h-3.5 sm:w-4 sm:h-4 sm:mr-2" />
330
+ <span class="hidden sm:inline">Restore</span>
331
+ </Button>
332
+ {/if}
333
+
334
+ <!-- New Chat button -->
335
+ <Button variant="outline" onclick={startNewChat} class="rounded-lg px-2 sm:px-4 py-1.5 sm:py-2">
336
+ <Icon name="lucide:plus" class="w-3.5 h-3.5 sm:w-4 sm:h-4 sm:mr-2" />
337
+ <span class="hidden sm:inline">New Chat</span>
338
+ </Button>
339
+ </div>
340
+ {/snippet}
341
+
342
+ <div class="flex flex-col h-[calc(100%+3rem)] -my-6 -mx-4">
343
+ {#if isWelcomeState && !appState.isRestoring}
344
+ <!-- Welcome state with modern design -->
345
+ <div class="flex-1 overflow-y-auto overflow-x-hidden">
346
+ <div class="min-h-full flex items-center justify-center p-4">
347
+ <div class="w-full max-w-4xl space-y-6 md:space-y-8 lg:space-y-10">
348
+ <!-- Modern hero section -->
349
+ <div class="text-center space-y-3 md:space-y-4 px-6">
350
+ <div class="space-y-3 md:space-y-4">
351
+ <h1 class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-slate-100">
352
+ Build apps & websites with AI
353
+ </h1>
354
+ <p class="md:text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
355
+ Describe your idea. Get production-ready code.
356
+ </p>
357
+ </div>
358
+ </div>
359
+
360
+ <!-- Input area integrated in welcome state -->
361
+ {#if showInput}
362
+ <div class="w-full px-4 space-y-4" in:fade={{ duration: 200, delay: 100 }}>
363
+ <ChatInput />
364
+ </div>
365
+ {/if}
366
+ </div>
367
+ </div>
368
+ </div>
369
+ {:else}
370
+ <!-- Enhanced chat interface -->
371
+ <div class="flex-1 flex flex-col overflow-hidden">
372
+ <div class="flex-1 flex justify-center overflow-hidden">
373
+ <div class="w-full flex flex-col overflow-hidden">
374
+ <div class="flex-1 overflow-y-auto overflow-x-hidden">
375
+ <ChatMessages {scrollContainer} />
376
+ </div>
377
+ </div>
378
+ </div>
379
+ </div>
380
+
381
+ <!-- Input area with SDK integration -->
382
+ {#if showInput}
383
+ <div
384
+ class="sticky bottom-0 flex-shrink-0 bg-gradient-to-t from-slate-50 via-slate-50 dark:from-slate-900 dark:via-slate-900 to-transparent"
385
+ in:fade={{ duration: 200, delay: 100 }}
386
+ >
387
+ <div class="flex justify-center">
388
+ <div class="w-full max-w-5xl px-4 pb-4 pt-2">
389
+ <ChatInput />
390
+ </div>
391
+ </div>
392
+ </div>
393
+ {/if}
394
+ {/if}
395
+ </div>
396
+
397
+ <!-- Floating TodoList (only shown when there's an active session with todos) -->
398
+ {#if sessionState.currentSession}
399
+ <FloatingTodoList />
400
+ {/if}
401
+
402
+ <!-- Checkpoint Timeline Modal -->
403
+ <TimelineModal
404
+ bind:isOpen={showCheckpoints}
405
+ onClose={closeCheckpoints}
406
+ />
407
+ </PageTemplate>
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
3
+
4
+ const { errorText }: { errorText: string } = $props();
5
+
6
+ // Categorize error types and provide suggestions
7
+ function getErrorInfo(text: string): { type: string; suggestion: string } {
8
+ if (text.includes('max_turns') || text.includes('maximum number of conversation turns')) {
9
+ return {
10
+ type: 'Task Completion Limit',
11
+ suggestion: 'Try breaking your request into smaller, more specific tasks.'
12
+ };
13
+ } else if (text.includes('timeout') || text.includes('timed out')) {
14
+ return {
15
+ type: 'Timeout Error',
16
+ suggestion: 'The operation took too long. Try with a simpler request.'
17
+ };
18
+ } else if (text.includes('permission') || text.includes('denied')) {
19
+ return {
20
+ type: 'Permission Error',
21
+ suggestion: 'Check file permissions and ensure you have access to the project directory.'
22
+ };
23
+ } else if (text.includes('API key') || text.includes('authentication')) {
24
+ return {
25
+ type: 'Authentication Error',
26
+ suggestion: 'Please check your Anthropic API key configuration.'
27
+ };
28
+ } else if (text.includes('git-bash') || text.includes('Git')) {
29
+ return {
30
+ type: 'Git Configuration Error',
31
+ suggestion: 'Ensure Git is installed and accessible in your system PATH.'
32
+ };
33
+ }
34
+
35
+ return {
36
+ type: 'Error',
37
+ suggestion: ''
38
+ };
39
+ }
40
+
41
+ const errorInfo = $derived(getErrorInfo(errorText));
42
+ </script>
43
+
44
+ <div class="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
45
+ <div class="flex items-start gap-3">
46
+ <Icon name="lucide:circle-alert" class="text-red-500 w-6 h-6 mt-0.5" />
47
+ <div class="flex-1">
48
+ <div class="font-bold text-red-700 dark:text-red-300 mb-2">{errorInfo.type}</div>
49
+ <div class="text-sm text-red-600 dark:text-red-400 leading-relaxed">{errorText}</div>
50
+ {#if errorInfo.suggestion}
51
+ <div class="mt-3 text-xs text-red-500 dark:text-red-400 bg-red-100 dark:bg-red-900/30 p-2 rounded border-l-2 border-red-400">
52
+ {errorInfo.suggestion}
53
+ </div>
54
+ {/if}
55
+ </div>
56
+ </div>
57
+ </div>
@@ -0,0 +1,224 @@
1
+ <script lang="ts">
2
+ import ErrorMessage from './ErrorMessage.svelte';
3
+ import TextMessage from './TextMessage.svelte';
4
+ import Tools from './Tools.svelte';
5
+ import Lightbox from '$frontend/lib/components/common/Lightbox.svelte';
6
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
7
+ import { getFileIcon } from '$frontend/lib/utils/file-icon-mappings';
8
+ import { formatFileSize } from '../shared/utils';
9
+
10
+ import type { SDKMessage, SDKPartialAssistantMessage } from '$shared/types/messaging';
11
+ import type { SDKMessageFormatter } from '$shared/types/database/schema';
12
+
13
+ const { message }: { message: SDKMessageFormatter } = $props();
14
+
15
+ let lightboxOpen = $state(false);
16
+ let lightboxData = $state<{ type: 'image' | 'document', data: string, mediaType: string, fileName?: string }>({
17
+ type: 'image',
18
+ data: '',
19
+ mediaType: '',
20
+ fileName: ''
21
+ });
22
+
23
+ function openAttachment(type: 'image' | 'document', data: string, mediaType: string, fileName?: string) {
24
+ lightboxData = { type, data, mediaType, fileName };
25
+ lightboxOpen = true;
26
+ }
27
+
28
+ function closeLightbox() {
29
+ lightboxOpen = false;
30
+ }
31
+
32
+ type ContentElement = {
33
+ type: 'text' | 'tool_use' | 'error' | 'image' | 'document';
34
+ content: any;
35
+ fileName?: string;
36
+ };
37
+
38
+ // Parse content into structured elements
39
+ function parseContent() {
40
+ const elements: ContentElement[] = [];
41
+
42
+ // Handle partial messages (streaming) — both text and reasoning
43
+ if (message.type === 'stream_event') {
44
+ if ('partialText' in message && message.partialText) {
45
+ elements.push({
46
+ type: 'text',
47
+ content: message.partialText
48
+ });
49
+ }
50
+ return elements;
51
+ }
52
+
53
+ // Handle reasoning messages (final, with metadata.reasoning flag)
54
+ if (message.metadata?.reasoning && message.type === 'assistant') {
55
+ const content = message.message.content;
56
+ if (Array.isArray(content)) {
57
+ for (const item of content) {
58
+ if (item.type === 'text') {
59
+ elements.push({ type: 'text', content: item.text });
60
+ }
61
+ }
62
+ }
63
+ return elements;
64
+ }
65
+
66
+
67
+ if (message.type === 'assistant') {
68
+ const content = message.message.content;
69
+ const elements: ContentElement[] = [];
70
+
71
+ for (const contentItem of content) {
72
+ if (contentItem.type === 'text') {
73
+ // Text block
74
+ elements.push({
75
+ type: 'text',
76
+ content: contentItem.text
77
+ });
78
+ } else if (contentItem.type === 'tool_use') {
79
+ // Tool use block (may contain embedded $result)
80
+ elements.push({
81
+ type: 'tool_use',
82
+ content: contentItem
83
+ });
84
+ }
85
+ }
86
+
87
+ return elements;
88
+ }
89
+
90
+ else if (message.type === 'user') {
91
+ const content = message.message.content;
92
+ // Get attachment filenames from metadata if available (now an array)
93
+ const attachmentFilenames = (message as any).attachmentFilenames || [];
94
+
95
+ // Handle array content (for tool_result and file attachments)
96
+ if (Array.isArray(content)) {
97
+ for (let i = 0; i < content.length; i++) {
98
+ const contentItem = content[i];
99
+
100
+ if (typeof contentItem === 'string') {
101
+ elements.push({
102
+ type: 'text',
103
+ content: contentItem
104
+ });
105
+ } else if (contentItem.type === 'text') {
106
+ elements.push({
107
+ type: 'text',
108
+ content: contentItem.text
109
+ });
110
+ } else if (contentItem.type === 'image') {
111
+ // Handle image attachments with filename from metadata
112
+ elements.push({
113
+ type: 'image',
114
+ content: contentItem,
115
+ fileName: attachmentFilenames[i] || undefined
116
+ });
117
+ } else if (contentItem.type === 'document') {
118
+ // Handle document attachments with filename from metadata
119
+ elements.push({
120
+ type: 'document',
121
+ content: contentItem,
122
+ fileName: attachmentFilenames[i] || undefined
123
+ });
124
+ }
125
+ }
126
+ } else {
127
+ // String content
128
+ elements.push({
129
+ type: 'text',
130
+ content: content
131
+ });
132
+ }
133
+
134
+ return elements;
135
+ }
136
+
137
+ return elements;
138
+ }
139
+
140
+ // Main parsed content
141
+ const parsedElements = $derived.by(parseContent);
142
+ </script>
143
+
144
+ {#each parsedElements as element}
145
+ {#if element.type === 'error'}
146
+ <ErrorMessage errorText={element.content} />
147
+ {:else if element.type === 'text'}
148
+ <TextMessage content={element.content} />
149
+ {:else if element.type === 'tool_use'}
150
+ <Tools toolInput={element.content} />
151
+ {:else if element.type === 'image'}
152
+ <div class="inline-block mr-2 mb-2">
153
+ <div class="flex flex-col gap-1">
154
+ {#if element.content.source?.type === 'base64'}
155
+ <button
156
+ onclick={() => openAttachment('image', element.content.source.data, element.content.source.media_type, element.fileName || 'Image')}
157
+ class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-100 dark:bg-slate-800 cursor-pointer hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-violet-500"
158
+ aria-label="Click to view full image"
159
+ >
160
+ <img
161
+ src="data:{element.content.source.media_type};base64,{element.content.source.data}"
162
+ alt={element.fileName || "User uploaded content"}
163
+ class="absolute inset-0 w-full h-full object-cover"
164
+ loading="lazy"
165
+ />
166
+ </button>
167
+ {:else}
168
+ <div class="w-28 h-28 flex items-center justify-center bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-750 rounded-lg border border-slate-200 dark:border-slate-700">
169
+ <svg class="w-12 h-12 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
170
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
171
+ </svg>
172
+ </div>
173
+ {/if}
174
+ </div>
175
+ </div>
176
+ {:else if element.type === 'document'}
177
+ <div class="inline-block mr-2 mb-2">
178
+ <div class="flex flex-col gap-1">
179
+ {#if element.content.source?.type === 'base64'}
180
+ <button
181
+ onclick={() => openAttachment('document', element.content.source.data, element.content.source.media_type, element.fileName || 'Document')}
182
+ class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800 cursor-pointer hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-violet-500"
183
+ aria-label="Click to view document"
184
+ >
185
+ <div class="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-2">
186
+ <Icon
187
+ name={element.fileName ? getFileIcon(element.fileName) : 'material:document'}
188
+ class="w-8 h-8 text-slate-600 dark:text-slate-400 flex-shrink-0"
189
+ />
190
+ {#if element.fileName}
191
+ <span class="text-xs font-medium text-slate-700 dark:text-slate-300 line-clamp-2 w-full px-1 text-center leading-tight" title={element.fileName}>
192
+ {element.fileName}
193
+ </span>
194
+ {/if}
195
+ <span class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">
196
+ {formatFileSize(element.content.source.data.length * 0.75)}
197
+ </span>
198
+ </div>
199
+ </button>
200
+ {:else}
201
+ <div class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
202
+ <div class="absolute inset-0 flex flex-col items-center justify-center gap-1.5">
203
+ <Icon
204
+ name="material:document"
205
+ class="w-10 h-10 text-slate-600 dark:text-slate-400"
206
+ />
207
+ <span class="text-xs font-medium text-slate-700 dark:text-slate-300">DOCUMENT</span>
208
+ </div>
209
+ </div>
210
+ {/if}
211
+ </div>
212
+ </div>
213
+ {/if}
214
+ {/each}
215
+
216
+ <!-- Lightbox for viewing attachments -->
217
+ <Lightbox
218
+ bind:isOpen={lightboxOpen}
219
+ type={lightboxData.type}
220
+ mediaType={lightboxData.mediaType}
221
+ data={lightboxData.data}
222
+ fileName={lightboxData.fileName}
223
+ onClose={closeLightbox}
224
+ />