@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,31 @@
1
+ <script lang="ts">
2
+ import { appState } from '$frontend/lib/stores/core/app.svelte';
3
+ import { fly } from 'svelte/transition';
4
+
5
+ interface Props {
6
+ visibleLoadingText: string;
7
+ isWelcomeState: boolean;
8
+ }
9
+
10
+ const { visibleLoadingText, isWelcomeState }: Props = $props();
11
+ </script>
12
+
13
+ {#if appState.isLoading}
14
+ <div
15
+ class="absolute z-20 {isWelcomeState ? '-top-16' : '-top-14'} left-0 right-0 flex justify-center pointer-events-none"
16
+ transition:fly={{ y: 100, duration: 300 }}
17
+ >
18
+ <div class="flex items-center gap-2.5 px-4 py-2 bg-slate-100 dark:bg-slate-800 rounded-full border border-slate-300 dark:border-slate-600 shadow-sm">
19
+ <!-- Simple spinner -->
20
+ <svg class="animate-spin h-4 w-4 text-slate-700 dark:text-slate-300" viewBox="0 0 24 24">
21
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"></circle>
22
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
23
+ </svg>
24
+
25
+ <!-- Text with typewriter effect -->
26
+ <span class="text-sm font-medium text-slate-700 dark:text-slate-300 capitalize">
27
+ {visibleLoadingText}
28
+ </span>
29
+ </div>
30
+ </div>
31
+ {/if}
@@ -0,0 +1,201 @@
1
+ import { onDestroy } from 'svelte';
2
+
3
+ /**
4
+ * Composable for managing placeholder and loading text animations
5
+ * Combines placeholder typewriter effect and loading text rotation
6
+ */
7
+
8
+ // ============================================================================
9
+ // PLACEHOLDER ANIMATION
10
+ // ============================================================================
11
+
12
+ export function usePlaceholderAnimation(placeholderTexts: string[]) {
13
+ let currentPlaceholderIndex = $state(0);
14
+ let placeholderText = $state('');
15
+ let placeholderTypewriterInterval: number | null = null;
16
+ let placeholderRotationInterval: number | null = null;
17
+ let placeholderDeleteTimeout: number | null = null;
18
+
19
+ // Typewriter effect for placeholder
20
+ function typewritePlaceholder(text: string) {
21
+ if (placeholderTypewriterInterval) {
22
+ clearInterval(placeholderTypewriterInterval);
23
+ }
24
+
25
+ let currentIndex = 0;
26
+ placeholderText = '';
27
+
28
+ placeholderTypewriterInterval = window.setInterval(() => {
29
+ if (currentIndex < text.length) {
30
+ placeholderText = text.substring(0, currentIndex + 1);
31
+ currentIndex++;
32
+ } else {
33
+ clearInterval(placeholderTypewriterInterval!);
34
+ placeholderTypewriterInterval = null;
35
+ }
36
+ }, 20); // Typing speed
37
+ }
38
+
39
+ // Update placeholder with typewriter effect
40
+ function updatePlaceholder() {
41
+ const fullText = placeholderTexts[currentPlaceholderIndex];
42
+
43
+ // Clear any existing delete timeout
44
+ if (placeholderDeleteTimeout) {
45
+ clearTimeout(placeholderDeleteTimeout);
46
+ }
47
+
48
+ // Use a tracked timeout that can be cleared properly
49
+ placeholderDeleteTimeout = window.setTimeout(() => {
50
+ // Clear current text with backspace effect
51
+ let deleteInterval = window.setInterval(() => {
52
+ if (placeholderText.length > 0) {
53
+ placeholderText = placeholderText.substring(0, placeholderText.length - 1);
54
+ } else {
55
+ clearInterval(deleteInterval);
56
+ // Start typing new text
57
+ typewritePlaceholder(fullText);
58
+ }
59
+ }, 15); // Delete speed
60
+ }, 2000); // Wait 2 seconds before deleting
61
+ }
62
+
63
+ function startPlaceholderAnimation() {
64
+ // Clear any existing intervals first
65
+ stopPlaceholderAnimation();
66
+
67
+ currentPlaceholderIndex = Math.floor(Math.random() * placeholderTexts.length);
68
+ // Initial placeholder
69
+ const initialText = placeholderTexts[currentPlaceholderIndex];
70
+ typewritePlaceholder(initialText);
71
+
72
+ // Rotate placeholders
73
+ placeholderRotationInterval = window.setInterval(() => {
74
+ currentPlaceholderIndex = (currentPlaceholderIndex + 1) % placeholderTexts.length;
75
+ updatePlaceholder();
76
+ }, 7000); // Change every 7 seconds
77
+ }
78
+
79
+ function stopPlaceholderAnimation() {
80
+ if (placeholderTypewriterInterval) {
81
+ clearInterval(placeholderTypewriterInterval);
82
+ placeholderTypewriterInterval = null;
83
+ }
84
+ if (placeholderRotationInterval) {
85
+ clearInterval(placeholderRotationInterval);
86
+ placeholderRotationInterval = null;
87
+ }
88
+ if (placeholderDeleteTimeout) {
89
+ clearTimeout(placeholderDeleteTimeout);
90
+ placeholderDeleteTimeout = null;
91
+ }
92
+ }
93
+
94
+ function setStaticPlaceholder(text: string) {
95
+ stopPlaceholderAnimation();
96
+ placeholderText = text;
97
+ }
98
+
99
+ // Cleanup on destroy
100
+ onDestroy(() => {
101
+ stopPlaceholderAnimation();
102
+ });
103
+
104
+ return {
105
+ get placeholderText() {
106
+ return placeholderText;
107
+ },
108
+ startAnimation: startPlaceholderAnimation,
109
+ stopAnimation: stopPlaceholderAnimation,
110
+ setStaticPlaceholder
111
+ };
112
+ }
113
+
114
+ // ============================================================================
115
+ // LOADING TEXT ANIMATION
116
+ // ============================================================================
117
+
118
+ export function useLoadingTextAnimation(loadingTexts: string[]) {
119
+ let currentLoadingText = $state('thinking');
120
+ let visibleLoadingText = $state('thinking');
121
+ let loadingTextIntervalId: number | null = null;
122
+ let typewriterIntervalId: number | null = null;
123
+
124
+ // Typewriter effect for smooth text transition
125
+ function animateTextTransition(newText: string) {
126
+ if (typewriterIntervalId) {
127
+ clearInterval(typewriterIntervalId);
128
+ }
129
+
130
+ const oldText = visibleLoadingText;
131
+ let deleteIndex = oldText.length;
132
+ let typeIndex = 0;
133
+ let isDeleting = true;
134
+
135
+ typewriterIntervalId = window.setInterval(() => {
136
+ if (isDeleting) {
137
+ // Delete characters
138
+ if (deleteIndex > 0) {
139
+ visibleLoadingText = oldText.substring(0, deleteIndex - 1);
140
+ deleteIndex--;
141
+ } else {
142
+ // Finished deleting, start typing
143
+ isDeleting = false;
144
+ }
145
+ } else {
146
+ // Type new characters
147
+ if (typeIndex < newText.length) {
148
+ visibleLoadingText = newText.substring(0, typeIndex + 1);
149
+ typeIndex++;
150
+ } else {
151
+ // Finished typing
152
+ clearInterval(typewriterIntervalId!);
153
+ typewriterIntervalId = null;
154
+ }
155
+ }
156
+ }, 50); // Adjust speed here (lower = faster)
157
+ }
158
+
159
+ function startLoadingAnimation() {
160
+ // Clear any existing intervals first to prevent duplication
161
+ stopLoadingAnimation();
162
+
163
+ // Start loading text rotation with typewriter effect
164
+ currentLoadingText = loadingTexts[Math.floor(Math.random() * loadingTexts.length)];
165
+ visibleLoadingText = currentLoadingText;
166
+ loadingTextIntervalId = window.setInterval(() => {
167
+ // Get a different text than the current one
168
+ let newText = currentLoadingText;
169
+ while (newText === currentLoadingText && loadingTexts.length > 1) {
170
+ newText = loadingTexts[Math.floor(Math.random() * loadingTexts.length)];
171
+ }
172
+ currentLoadingText = newText;
173
+ animateTextTransition(newText);
174
+ }, 15000);
175
+ }
176
+
177
+ function stopLoadingAnimation() {
178
+ // Clear loading text interval
179
+ if (loadingTextIntervalId) {
180
+ window.clearInterval(loadingTextIntervalId);
181
+ loadingTextIntervalId = null;
182
+ }
183
+ if (typewriterIntervalId) {
184
+ window.clearInterval(typewriterIntervalId);
185
+ typewriterIntervalId = null;
186
+ }
187
+ }
188
+
189
+ // Cleanup on destroy
190
+ onDestroy(() => {
191
+ stopLoadingAnimation();
192
+ });
193
+
194
+ return {
195
+ get visibleLoadingText() {
196
+ return visibleLoadingText;
197
+ },
198
+ startAnimation: startLoadingAnimation,
199
+ stopAnimation: stopLoadingAnimation
200
+ };
201
+ }
@@ -0,0 +1,148 @@
1
+ import { appState } from '$frontend/lib/stores/core/app.svelte';
2
+ import { sessionState, loadMessagesForSession } from '$frontend/lib/stores/core/sessions.svelte';
3
+ import { chatService } from '$frontend/lib/services/chat/chat.service';
4
+ import { snapshotService } from '$frontend/lib/services/snapshot/snapshot.service';
5
+ import { soundNotification } from '$frontend/lib/services/notification';
6
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
7
+ import { editModeState, cancelEdit } from '$frontend/lib/stores/ui/edit-mode.svelte';
8
+ import { clearInput, setSkipNextRestore } from '$frontend/lib/stores/ui/chat-input.svelte';
9
+ import { debug } from '$shared/utils/logger';
10
+ import type { FileAttachment } from './use-file-handling.svelte';
11
+
12
+ interface ChatActionsParams {
13
+ messageText: string;
14
+ attachedFiles: FileAttachment[];
15
+ clearAllAttachments: () => void;
16
+ adjustTextareaHeight: () => void;
17
+ focusTextarea: () => void;
18
+ startLoadingAnimation: () => void;
19
+ stopLoadingAnimation: () => void;
20
+ clearDraft: () => void;
21
+ }
22
+
23
+ export function useChatActions(params: ChatActionsParams) {
24
+ let isInputComposing = $state(false);
25
+
26
+ // Handle cancel edit
27
+ function handleCancelEdit() {
28
+ cancelEdit();
29
+ clearInput();
30
+ params.messageText = ''; // This won't work directly, need to pass setter
31
+ params.clearAllAttachments();
32
+ params.adjustTextareaHeight();
33
+ }
34
+
35
+ // Handle send message with SDK streaming
36
+ async function sendMessage(messageText: string, setMessageText: (value: string) => void) {
37
+ if ((!messageText.trim() && params.attachedFiles.length === 0) || appState.isLoading) return;
38
+
39
+ // Initialize sound notifications on first user interaction (browser policy requirement)
40
+ soundNotification.initialize();
41
+
42
+ const userMessage = messageText.trim();
43
+ const files = [...params.attachedFiles]; // Copy current attachments
44
+
45
+ // If in edit mode, restore to parent of edited message first
46
+ if (editModeState.isEditing) {
47
+ try {
48
+ // Restore to parent of edited message (if exists)
49
+ const restoreTargetId = editModeState.parentMessageId || editModeState.messageId;
50
+
51
+ if (restoreTargetId && sessionState.currentSession?.id) {
52
+ await snapshotService.restore(restoreTargetId, sessionState.currentSession.id);
53
+ }
54
+
55
+ // Reload messages from database to update UI
56
+ if (sessionState.currentSession?.id) {
57
+ await loadMessagesForSession(sessionState.currentSession.id);
58
+ }
59
+
60
+ // Exit edit mode
61
+ cancelEdit();
62
+ } catch (error) {
63
+ debug.error('chat', 'Edit restore error:', error);
64
+ addNotification({
65
+ type: 'error',
66
+ title: 'Edit Failed',
67
+ message: error instanceof Error ? error.message : 'Unknown error',
68
+ duration: 5000
69
+ });
70
+ return; // Don't send message if restore failed
71
+ }
72
+ }
73
+
74
+ // Clear input and attachments
75
+ // Set skip flag BEFORE clearing - prevents stale input restoration
76
+ // when ChatInput is remounted during welcome→chat transition
77
+ setSkipNextRestore(true);
78
+ params.clearDraft();
79
+ setMessageText('');
80
+ params.clearAllAttachments();
81
+ params.adjustTextareaHeight();
82
+
83
+ // Focus back to textarea
84
+ params.focusTextarea();
85
+
86
+ // Start loading text rotation with typewriter effect
87
+ params.startLoadingAnimation();
88
+
89
+ // Send message via WebSocket with file attachments
90
+ const attachedFiles = files
91
+ .filter(f => f.base64)
92
+ .map(f => ({
93
+ type: f.type,
94
+ data: f.base64!,
95
+ mediaType: f.file.type,
96
+ fileName: f.file.name
97
+ }));
98
+
99
+ await chatService.sendMessage(userMessage, {
100
+ attachedFiles: attachedFiles.length > 0 ? attachedFiles : undefined,
101
+ onStreamEnd: () => {
102
+ params.stopLoadingAnimation();
103
+ },
104
+ onError: () => {
105
+ params.stopLoadingAnimation();
106
+ }
107
+ });
108
+ }
109
+
110
+ // Cancel current request
111
+ async function cancelRequest() {
112
+ chatService.cancelRequest();
113
+ params.stopLoadingAnimation();
114
+ }
115
+
116
+ // Handle key press
117
+ function handleKeyPress(
118
+ event: KeyboardEvent,
119
+ messageText: string,
120
+ setMessageText: (value: string) => void
121
+ ) {
122
+ if (event.key === 'Enter' && !event.shiftKey && !isInputComposing) {
123
+ event.preventDefault();
124
+ sendMessage(messageText, setMessageText);
125
+ }
126
+ }
127
+
128
+ // Handle composition events for international keyboards
129
+ function handleCompositionStart() {
130
+ isInputComposing = true;
131
+ }
132
+
133
+ function handleCompositionEnd() {
134
+ isInputComposing = false;
135
+ }
136
+
137
+ return {
138
+ get isInputComposing() {
139
+ return isInputComposing;
140
+ },
141
+ handleCancelEdit,
142
+ sendMessage,
143
+ cancelRequest,
144
+ handleKeyPress,
145
+ handleCompositionStart,
146
+ handleCompositionEnd
147
+ };
148
+ }
@@ -0,0 +1,216 @@
1
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
2
+ import { debug } from '$shared/utils/logger';
3
+
4
+ // File attachments interface
5
+ export interface FileAttachment {
6
+ id: string;
7
+ file: File;
8
+ type: 'image' | 'document';
9
+ base64?: string;
10
+ previewUrl?: string;
11
+ }
12
+
13
+ // Maximum file size (10MB)
14
+ const MAX_FILE_SIZE = 10 * 1024 * 1024;
15
+
16
+ // Supported file types - Images and PDF only
17
+ const SUPPORTED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
18
+ const SUPPORTED_DOCUMENT_TYPES = ['application/pdf'];
19
+
20
+ export function useFileHandling() {
21
+ let attachedFiles = $state<FileAttachment[]>([]);
22
+ let isDragging = $state(false);
23
+ let isProcessingFiles = $state(false);
24
+
25
+ function getFileType(mimeType: string): 'image' | 'document' {
26
+ if (SUPPORTED_IMAGE_TYPES.includes(mimeType)) return 'image';
27
+ return 'document';
28
+ }
29
+
30
+ async function fileToBase64(file: File): Promise<string> {
31
+ return new Promise((resolve, reject) => {
32
+ const reader = new FileReader();
33
+ reader.onload = () => {
34
+ const base64 = reader.result as string;
35
+ // Remove the data:type/subtype;base64, prefix
36
+ const base64Data = base64.split(',')[1];
37
+ resolve(base64Data);
38
+ };
39
+ reader.onerror = reject;
40
+ reader.readAsDataURL(file);
41
+ });
42
+ }
43
+
44
+ async function processFiles(files: FileList | File[]) {
45
+ isProcessingFiles = true;
46
+ const fileArray = Array.from(files);
47
+
48
+ for (const file of fileArray) {
49
+ // Check file size
50
+ if (file.size > MAX_FILE_SIZE) {
51
+ addNotification({
52
+ type: 'error',
53
+ title: 'File Too Large',
54
+ message: `${file.name} exceeds the 10MB limit`,
55
+ duration: 3000
56
+ });
57
+ continue;
58
+ }
59
+
60
+ // Check if file type is supported
61
+ const isSupported = [...SUPPORTED_IMAGE_TYPES, ...SUPPORTED_DOCUMENT_TYPES].includes(
62
+ file.type
63
+ );
64
+ if (!isSupported) {
65
+ addNotification({
66
+ type: 'error',
67
+ title: 'Unsupported File Type',
68
+ message: `${file.name} is not supported. Only images (JPEG, PNG, GIF, WebP) and PDF documents are allowed.`,
69
+ duration: 4000
70
+ });
71
+ continue;
72
+ }
73
+
74
+ // Check for duplicates
75
+ if (attachedFiles.some((f) => f.file.name === file.name && f.file.size === file.size)) {
76
+ continue;
77
+ }
78
+
79
+ const fileType = getFileType(file.type);
80
+ const attachment: FileAttachment = {
81
+ id: crypto.randomUUID(),
82
+ file,
83
+ type: fileType
84
+ };
85
+
86
+ // Convert to base64
87
+ try {
88
+ attachment.base64 = await fileToBase64(file);
89
+
90
+ // Create preview URL for images
91
+ if (fileType === 'image') {
92
+ attachment.previewUrl = URL.createObjectURL(file);
93
+ }
94
+
95
+ attachedFiles = [...attachedFiles, attachment];
96
+ } catch (error) {
97
+ debug.error('chat', 'Error processing file:', error);
98
+ addNotification({
99
+ type: 'error',
100
+ title: 'File Processing Error',
101
+ message: `Failed to process ${file.name}`,
102
+ duration: 3000
103
+ });
104
+ }
105
+ }
106
+
107
+ isProcessingFiles = false;
108
+ }
109
+
110
+ function removeAttachment(id: string) {
111
+ const attachment = attachedFiles.find((f) => f.id === id);
112
+ if (attachment?.previewUrl) {
113
+ URL.revokeObjectURL(attachment.previewUrl);
114
+ }
115
+ attachedFiles = attachedFiles.filter((f) => f.id !== id);
116
+ }
117
+
118
+ function clearAllAttachments() {
119
+ attachedFiles.forEach((attachment) => {
120
+ if (attachment.previewUrl) {
121
+ URL.revokeObjectURL(attachment.previewUrl);
122
+ }
123
+ });
124
+ attachedFiles = [];
125
+ }
126
+
127
+ // File input handlers
128
+ function handleFileSelect(fileInputElement: HTMLInputElement | undefined) {
129
+ fileInputElement?.click();
130
+ }
131
+
132
+ async function handleFileInputChange(event: Event) {
133
+ const input = event.target as HTMLInputElement;
134
+ if (input.files && input.files.length > 0) {
135
+ await processFiles(input.files);
136
+ // Reset input so same file can be selected again
137
+ input.value = '';
138
+ }
139
+ }
140
+
141
+ // Drag and drop handlers
142
+ function handleDragOver(event: DragEvent) {
143
+ event.preventDefault();
144
+ isDragging = true;
145
+ }
146
+
147
+ function handleDragLeave(event: DragEvent) {
148
+ event.preventDefault();
149
+ isDragging = false;
150
+ }
151
+
152
+ async function handleDrop(event: DragEvent) {
153
+ event.preventDefault();
154
+ isDragging = false;
155
+
156
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
157
+ await processFiles(event.dataTransfer.files);
158
+ }
159
+ }
160
+
161
+ // Paste handler for images and documents
162
+ async function handlePaste(event: ClipboardEvent) {
163
+ const items = event.clipboardData?.items;
164
+ if (!items) return;
165
+
166
+ const files: File[] = [];
167
+
168
+ for (let i = 0; i < items.length; i++) {
169
+ const item = items[i];
170
+
171
+ // Check if the item is a file
172
+ if (item.kind === 'file') {
173
+ const file = item.getAsFile();
174
+ if (file) {
175
+ files.push(file);
176
+ }
177
+ }
178
+ }
179
+
180
+ // Process the pasted files if any
181
+ if (files.length > 0) {
182
+ event.preventDefault(); // Prevent default paste behavior for files
183
+ await processFiles(files);
184
+ }
185
+ // If no files, let the default paste behavior handle text
186
+ }
187
+
188
+ return {
189
+ // State
190
+ get attachedFiles() {
191
+ return attachedFiles;
192
+ },
193
+ set attachedFiles(value: FileAttachment[]) {
194
+ attachedFiles = value;
195
+ },
196
+ get isDragging() {
197
+ return isDragging;
198
+ },
199
+ get isProcessingFiles() {
200
+ return isProcessingFiles;
201
+ },
202
+ // Methods
203
+ processFiles,
204
+ removeAttachment,
205
+ clearAllAttachments,
206
+ handleFileSelect,
207
+ handleFileInputChange,
208
+ handleDragOver,
209
+ handleDragLeave,
210
+ handleDrop,
211
+ handlePaste,
212
+ // Constants
213
+ SUPPORTED_IMAGE_TYPES,
214
+ SUPPORTED_DOCUMENT_TYPES
215
+ };
216
+ }