@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.
- package/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- 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
|
+
}
|