@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,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Native UI Handlers
|
|
3
|
+
* Handles native UI events (dialogs, select, context menu, etc.) for BrowserPreview
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debug } from '$shared/utils/logger';
|
|
7
|
+
import ws from '$frontend/lib/utils/ws';
|
|
8
|
+
import type { BrowserDialogEvent, BrowserPrintEvent, BrowserSelectInfo, BrowserContextMenuInfo } from '$frontend/lib/types/native-ui';
|
|
9
|
+
import type { TabManager } from './tab-manager.svelte';
|
|
10
|
+
|
|
11
|
+
export interface NativeUIHandlerConfig {
|
|
12
|
+
tabManager: TabManager;
|
|
13
|
+
transformBrowserToDisplayCoordinates?: (x: number, y: number) => { x: number, y: number } | null;
|
|
14
|
+
onSelectOpen?: (selectInfo: BrowserSelectInfo) => void;
|
|
15
|
+
onContextMenuOpen?: (menuInfo: BrowserContextMenuInfo) => void;
|
|
16
|
+
onCopyToClipboard?: (text: string) => void;
|
|
17
|
+
onOpenUrlNewTab?: (url: string) => void;
|
|
18
|
+
onDownloadImage?: (base64: string, type: string, filename: string) => void;
|
|
19
|
+
onCopyImageToClipboard?: (base64: string, type: string) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create native UI event handler
|
|
24
|
+
*/
|
|
25
|
+
export function createNativeUIHandler(config: NativeUIHandlerConfig) {
|
|
26
|
+
const {
|
|
27
|
+
tabManager,
|
|
28
|
+
transformBrowserToDisplayCoordinates,
|
|
29
|
+
onSelectOpen,
|
|
30
|
+
onContextMenuOpen,
|
|
31
|
+
onCopyToClipboard,
|
|
32
|
+
onOpenUrlNewTab,
|
|
33
|
+
onDownloadImage,
|
|
34
|
+
onCopyImageToClipboard
|
|
35
|
+
} = config;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Setup WebSocket event listeners for native UI events
|
|
39
|
+
*/
|
|
40
|
+
function setupEventListeners() {
|
|
41
|
+
// Listen to dialog events
|
|
42
|
+
ws.on('preview:browser-dialog', handleDialogEvent);
|
|
43
|
+
|
|
44
|
+
// Listen to print events
|
|
45
|
+
ws.on('preview:browser-print', handlePrintEvent);
|
|
46
|
+
|
|
47
|
+
// Listen to select events
|
|
48
|
+
ws.on('preview:browser-select', handleSelectEvent);
|
|
49
|
+
|
|
50
|
+
// Listen to context menu events
|
|
51
|
+
ws.on('preview:browser-context-menu', handleContextMenuEvent);
|
|
52
|
+
|
|
53
|
+
// Listen to clipboard copy events
|
|
54
|
+
ws.on('preview:browser-copy-to-clipboard', handleCopyToClipboard);
|
|
55
|
+
|
|
56
|
+
// Listen to open URL events
|
|
57
|
+
ws.on('preview:browser-open-url-new-tab', handleOpenUrlNewTab);
|
|
58
|
+
|
|
59
|
+
// Listen to download image events
|
|
60
|
+
ws.on('preview:browser-download-image', handleDownloadImage);
|
|
61
|
+
|
|
62
|
+
// Listen to copy image to clipboard events
|
|
63
|
+
ws.on('preview:browser-copy-image-to-clipboard', handleCopyImageToClipboard);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Handle dialog events (alert, confirm, prompt)
|
|
68
|
+
*/
|
|
69
|
+
async function handleDialogEvent(data: BrowserDialogEvent) {
|
|
70
|
+
debug.log('preview', `🎭 Dialog event received: ${data.type} - ${data.message} (dialogId: ${data.dialogId})`);
|
|
71
|
+
|
|
72
|
+
let response: boolean | null = null;
|
|
73
|
+
let promptText: string | undefined = undefined;
|
|
74
|
+
|
|
75
|
+
// Show native browser dialog based on type
|
|
76
|
+
switch (data.type) {
|
|
77
|
+
case 'alert':
|
|
78
|
+
window.alert(data.message);
|
|
79
|
+
response = true; // Alert always accepts
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'confirm':
|
|
83
|
+
response = window.confirm(data.message);
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case 'prompt':
|
|
87
|
+
const result = window.prompt(data.message, data.defaultValue || '');
|
|
88
|
+
if (result !== null) {
|
|
89
|
+
response = true;
|
|
90
|
+
promptText = result;
|
|
91
|
+
} else {
|
|
92
|
+
response = false;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 'beforeunload':
|
|
97
|
+
response = window.confirm(data.message);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Send response back to backend
|
|
102
|
+
if (response !== null) {
|
|
103
|
+
debug.log('preview', `📤 Sending dialog response - dialogId: ${data.dialogId}, accept: ${response}${promptText ? `, promptText: "${promptText}"` : ''}`);
|
|
104
|
+
|
|
105
|
+
ws.emit('preview:browser-dialog-input', {
|
|
106
|
+
dialogId: data.dialogId,
|
|
107
|
+
accept: response,
|
|
108
|
+
promptText
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
debug.log('preview', `✅ Dialog response sent successfully`);
|
|
112
|
+
} else {
|
|
113
|
+
debug.warn('preview', `⚠️ No response to send for dialog: ${data.dialogId}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle print events
|
|
119
|
+
*/
|
|
120
|
+
async function handlePrintEvent(data: BrowserPrintEvent) {
|
|
121
|
+
debug.log('preview', `🖨️ Print event received for session: ${data.sessionId}`);
|
|
122
|
+
window.print();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Handle select dropdown events
|
|
127
|
+
*/
|
|
128
|
+
function handleSelectEvent(data: BrowserSelectInfo) {
|
|
129
|
+
debug.log('preview', `📋 Select event received at (${data.x}, ${data.y}) with ${data.options.length} options for session ${data.sessionId}`);
|
|
130
|
+
|
|
131
|
+
// Check if this is for the active tab
|
|
132
|
+
const activeTab = tabManager.activeTab;
|
|
133
|
+
if (!activeTab) {
|
|
134
|
+
debug.warn('preview', `Select event ignored - no active tab`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (activeTab.sessionId !== data.sessionId) {
|
|
139
|
+
debug.warn('preview', `Select event ignored - session mismatch (active: ${activeTab.sessionId}, event: ${data.sessionId})`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!transformBrowserToDisplayCoordinates) {
|
|
144
|
+
debug.error('preview', 'transformBrowserToDisplayCoordinates not available');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Transform coordinates from browser (Puppeteer) to display coordinates
|
|
149
|
+
// The transformation function will handle cases where canvas is not ready (returns null)
|
|
150
|
+
const topLeft = transformBrowserToDisplayCoordinates(data.boundingBox.x, data.boundingBox.y);
|
|
151
|
+
const bottomRight = transformBrowserToDisplayCoordinates(
|
|
152
|
+
data.boundingBox.x + data.boundingBox.width,
|
|
153
|
+
data.boundingBox.y + data.boundingBox.height
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (!topLeft || !bottomRight) {
|
|
157
|
+
debug.warn('preview', `Select dropdown skipped - coordinate transformation failed (${data.boundingBox.x}, ${data.boundingBox.y})`);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create transformed select info with display coordinates
|
|
162
|
+
const transformedSelectInfo: BrowserSelectInfo = {
|
|
163
|
+
...data,
|
|
164
|
+
boundingBox: {
|
|
165
|
+
x: topLeft.x,
|
|
166
|
+
y: topLeft.y,
|
|
167
|
+
width: bottomRight.x - topLeft.x,
|
|
168
|
+
height: bottomRight.y - topLeft.y
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
debug.log('preview', `📋 Transformed select position: (${transformedSelectInfo.boundingBox.x}, ${transformedSelectInfo.boundingBox.y})`);
|
|
173
|
+
|
|
174
|
+
// Show select dropdown overlay
|
|
175
|
+
if (onSelectOpen) {
|
|
176
|
+
onSelectOpen(transformedSelectInfo);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle select option selection
|
|
182
|
+
*/
|
|
183
|
+
function respondSelectOption(selectInfo: BrowserSelectInfo, selectedIndex: number) {
|
|
184
|
+
debug.log('preview', `📋 Select option selected: ${selectedIndex}`);
|
|
185
|
+
|
|
186
|
+
ws.emit('preview:browser-select-input', {
|
|
187
|
+
sessionId: selectInfo.sessionId,
|
|
188
|
+
selectId: selectInfo.selectId,
|
|
189
|
+
selectedIndex
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Handle context menu events
|
|
195
|
+
*/
|
|
196
|
+
function handleContextMenuEvent(data: BrowserContextMenuInfo) {
|
|
197
|
+
debug.log('preview', `📜 Context menu event received at (${data.x}, ${data.y}) for session ${data.sessionId}`);
|
|
198
|
+
|
|
199
|
+
// Check if this is for the active tab
|
|
200
|
+
const activeTab = tabManager.activeTab;
|
|
201
|
+
if (!activeTab) {
|
|
202
|
+
debug.warn('preview', `Context menu event ignored - no active tab`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (activeTab.sessionId !== data.sessionId) {
|
|
207
|
+
debug.warn('preview', `Context menu event ignored - session mismatch (active: ${activeTab.sessionId}, event: ${data.sessionId})`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!transformBrowserToDisplayCoordinates) {
|
|
212
|
+
debug.error('preview', 'transformBrowserToDisplayCoordinates not available');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Transform coordinates from browser (Puppeteer) to display coordinates
|
|
217
|
+
// The transformation function will handle cases where canvas is not ready (returns null)
|
|
218
|
+
const position = transformBrowserToDisplayCoordinates(data.x, data.y);
|
|
219
|
+
|
|
220
|
+
if (!position) {
|
|
221
|
+
debug.warn('preview', `Context menu skipped - coordinate transformation failed (${data.x}, ${data.y})`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Create transformed context menu info with display coordinates
|
|
226
|
+
const transformedMenuInfo: BrowserContextMenuInfo = {
|
|
227
|
+
...data,
|
|
228
|
+
x: position.x,
|
|
229
|
+
y: position.y
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
debug.log('preview', `📜 Transformed context menu position: (${transformedMenuInfo.x}, ${transformedMenuInfo.y})`);
|
|
233
|
+
|
|
234
|
+
// Show context menu overlay
|
|
235
|
+
if (onContextMenuOpen) {
|
|
236
|
+
onContextMenuOpen(transformedMenuInfo);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Handle context menu item selection
|
|
242
|
+
*/
|
|
243
|
+
async function respondContextMenuItem(menuInfo: BrowserContextMenuInfo, itemId: string) {
|
|
244
|
+
debug.log('preview', `📜 Context menu item selected: ${itemId}`);
|
|
245
|
+
|
|
246
|
+
// For paste action, read clipboard first
|
|
247
|
+
let clipboardText: string | undefined = undefined;
|
|
248
|
+
if (itemId === 'paste') {
|
|
249
|
+
try {
|
|
250
|
+
if (navigator.clipboard && navigator.clipboard.readText) {
|
|
251
|
+
clipboardText = await navigator.clipboard.readText();
|
|
252
|
+
debug.log('preview', `📋 Clipboard text read: ${clipboardText.length} characters`);
|
|
253
|
+
} else {
|
|
254
|
+
debug.warn('preview', '⚠️ Clipboard API not available');
|
|
255
|
+
}
|
|
256
|
+
} catch (error) {
|
|
257
|
+
debug.error('preview', '❌ Failed to read clipboard:', error);
|
|
258
|
+
// Continue without clipboard text - backend will show warning
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Send selection back to backend
|
|
263
|
+
ws.emit('preview:browser-context-menu-input', {
|
|
264
|
+
sessionId: menuInfo.sessionId,
|
|
265
|
+
menuId: menuInfo.menuId,
|
|
266
|
+
itemId,
|
|
267
|
+
clipboardText
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Handle copy to clipboard
|
|
273
|
+
*/
|
|
274
|
+
function handleCopyToClipboard(data: { text: string }) {
|
|
275
|
+
debug.log('preview', `📋 Copy to clipboard: ${data.text}`);
|
|
276
|
+
|
|
277
|
+
// Copy to clipboard
|
|
278
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
279
|
+
navigator.clipboard.writeText(data.text).then(() => {
|
|
280
|
+
debug.log('preview', '✅ Copied to clipboard');
|
|
281
|
+
}).catch((error) => {
|
|
282
|
+
debug.error('preview', '❌ Failed to copy to clipboard:', error);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (onCopyToClipboard) {
|
|
287
|
+
onCopyToClipboard(data.text);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Handle open URL in new tab
|
|
293
|
+
*/
|
|
294
|
+
function handleOpenUrlNewTab(data: { url: string }) {
|
|
295
|
+
debug.log('preview', `🔗 Open URL in new tab: ${data.url}`);
|
|
296
|
+
|
|
297
|
+
if (onOpenUrlNewTab) {
|
|
298
|
+
onOpenUrlNewTab(data.url);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Handle download image
|
|
304
|
+
*/
|
|
305
|
+
async function handleDownloadImage(data: { base64: string; type: string; filename: string }) {
|
|
306
|
+
debug.log('preview', `💾 Download image: ${data.filename}`);
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
// Convert base64 to blob using Data URL
|
|
310
|
+
const res = await fetch(`data:${data.type};base64,${data.base64}`);
|
|
311
|
+
const blob = await res.blob();
|
|
312
|
+
|
|
313
|
+
// Ask user for filename (works in all browsers)
|
|
314
|
+
const userFilename = window.prompt('Save image as:', data.filename);
|
|
315
|
+
|
|
316
|
+
// User cancelled
|
|
317
|
+
if (userFilename === null) {
|
|
318
|
+
debug.log('preview', '⚠️ Save cancelled by user');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Use provided filename or original if empty
|
|
323
|
+
const finalFilename = userFilename.trim() || data.filename;
|
|
324
|
+
|
|
325
|
+
// Trigger download
|
|
326
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
327
|
+
const link = document.createElement('a');
|
|
328
|
+
link.href = objectUrl;
|
|
329
|
+
link.download = finalFilename;
|
|
330
|
+
document.body.appendChild(link);
|
|
331
|
+
link.click();
|
|
332
|
+
document.body.removeChild(link);
|
|
333
|
+
URL.revokeObjectURL(objectUrl);
|
|
334
|
+
|
|
335
|
+
debug.log('preview', `✅ Image downloaded: ${finalFilename}`);
|
|
336
|
+
} catch (error) {
|
|
337
|
+
debug.error('preview', '❌ Failed to download image:', error);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (onDownloadImage) {
|
|
341
|
+
onDownloadImage(data.base64, data.type, data.filename);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Handle copy image to clipboard
|
|
347
|
+
*/
|
|
348
|
+
async function handleCopyImageToClipboard(data: { base64: string; type: string }) {
|
|
349
|
+
debug.log('preview', `📋 Copy image to clipboard`);
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
// Convert base64 to blob using Data URL
|
|
353
|
+
const res = await fetch(`data:${data.type};base64,${data.base64}`);
|
|
354
|
+
const blob = await res.blob();
|
|
355
|
+
|
|
356
|
+
// Copy to clipboard using Clipboard API
|
|
357
|
+
if (navigator.clipboard && navigator.clipboard.write) {
|
|
358
|
+
await navigator.clipboard.write([
|
|
359
|
+
new ClipboardItem({
|
|
360
|
+
[data.type]: blob
|
|
361
|
+
})
|
|
362
|
+
]);
|
|
363
|
+
debug.log('preview', '✅ Image copied to clipboard');
|
|
364
|
+
} else {
|
|
365
|
+
debug.error('preview', '❌ Clipboard API not supported');
|
|
366
|
+
}
|
|
367
|
+
} catch (error) {
|
|
368
|
+
debug.error('preview', '❌ Failed to copy image to clipboard:', error);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (onCopyImageToClipboard) {
|
|
372
|
+
onCopyImageToClipboard(data.base64, data.type);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return {
|
|
377
|
+
setupEventListeners,
|
|
378
|
+
handleDialogEvent,
|
|
379
|
+
handlePrintEvent,
|
|
380
|
+
handleSelectEvent,
|
|
381
|
+
respondSelectOption,
|
|
382
|
+
handleContextMenuEvent,
|
|
383
|
+
respondContextMenuItem,
|
|
384
|
+
handleCopyToClipboard,
|
|
385
|
+
handleOpenUrlNewTab,
|
|
386
|
+
handleDownloadImage,
|
|
387
|
+
handleCopyImageToClipboard
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export type NativeUIHandler = ReturnType<typeof createNativeUIHandler>;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Stream Message Handler
|
|
3
|
+
* Handles stream messages from backend for BrowserPreview
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debug } from '$shared/utils/logger';
|
|
7
|
+
import type { PreviewTab, TabManager } from './tab-manager.svelte';
|
|
8
|
+
import { getTabTitle } from './tab-manager.svelte';
|
|
9
|
+
|
|
10
|
+
export interface StreamMessageHandlerConfig {
|
|
11
|
+
tabManager: TabManager;
|
|
12
|
+
onNavigationUpdate?: (tabId: string, url: string) => void;
|
|
13
|
+
onCursorUpdate?: (x: number, y: number, clicking?: boolean) => void;
|
|
14
|
+
onTestCompleted?: () => void;
|
|
15
|
+
transformBrowserToDisplayCoordinates?: (x: number, y: number) => { x: number, y: number } | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create stream message handler
|
|
20
|
+
*/
|
|
21
|
+
export function createStreamMessageHandler(config: StreamMessageHandlerConfig) {
|
|
22
|
+
const { tabManager, onNavigationUpdate, onCursorUpdate, onTestCompleted, transformBrowserToDisplayCoordinates } = config;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Handle stream messages for a specific tab
|
|
26
|
+
*/
|
|
27
|
+
function handleStreamMessage(message: any, tabId?: string) {
|
|
28
|
+
const targetTabId = tabId || tabManager.activeTabId;
|
|
29
|
+
if (!targetTabId) return;
|
|
30
|
+
|
|
31
|
+
const tab = tabManager.getTab(targetTabId);
|
|
32
|
+
if (!tab) return;
|
|
33
|
+
|
|
34
|
+
// Debug logging for navigation events
|
|
35
|
+
if (message.type === 'navigation' || message.type === 'navigation-loading') {
|
|
36
|
+
debug.log('preview', `📢 [${message.type}] Tab: ${targetTabId}, URL: ${message.data?.url}, Active: ${targetTabId === tabManager.activeTabId}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (message.type) {
|
|
40
|
+
case 'session-info':
|
|
41
|
+
handleSessionInfo(targetTabId, message.data, tab);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case 'screencast-frame':
|
|
45
|
+
handleScreencastFrame(targetTabId, message.data);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'cursor-position':
|
|
49
|
+
handleCursorPosition(targetTabId, message.data);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'cursor-click':
|
|
53
|
+
handleCursorClick(targetTabId, message.data);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'test-completed':
|
|
57
|
+
handleTestCompleted(targetTabId);
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'console-message':
|
|
61
|
+
handleConsoleMessage(targetTabId, message.data, tab);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'console-clear':
|
|
65
|
+
handleConsoleClear(targetTabId);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'navigation-loading':
|
|
69
|
+
handleNavigationLoading(targetTabId, message.data);
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case 'navigation':
|
|
73
|
+
handleNavigation(targetTabId, message.data, tab);
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 'new-window':
|
|
77
|
+
handleNewWindow(message.data);
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
case 'ping':
|
|
81
|
+
// Ignore ping messages
|
|
82
|
+
break;
|
|
83
|
+
|
|
84
|
+
case 'error':
|
|
85
|
+
debug.error('preview', 'Stream error:', message.message);
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
default:
|
|
89
|
+
debug.warn('preview', 'Unknown stream message type:', message.type);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Handle connection change
|
|
95
|
+
*/
|
|
96
|
+
function handleConnectionChange(connected: boolean, tabId?: string) {
|
|
97
|
+
const targetTabId = tabId || tabManager.activeTabId;
|
|
98
|
+
if (!targetTabId) return;
|
|
99
|
+
|
|
100
|
+
tabManager.updateTab(targetTabId, { isConnected: connected });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Private handlers
|
|
104
|
+
|
|
105
|
+
function handleSessionInfo(tabId: string, data: any, tab: PreviewTab) {
|
|
106
|
+
tabManager.updateTab(tabId, {
|
|
107
|
+
sessionInfo: data,
|
|
108
|
+
deviceSize: data.deviceSize || tab.deviceSize,
|
|
109
|
+
rotation: data.rotation || tab.rotation
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Setup canvas if this is the active tab
|
|
113
|
+
if (tabId === tabManager.activeTabId && tab.canvasAPI) {
|
|
114
|
+
tab.canvasAPI.setupCanvas();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function handleScreencastFrame(tabId: string, data: any) {
|
|
119
|
+
const tab = tabManager.getTab(tabId);
|
|
120
|
+
const updates: any = { lastFrameData: data };
|
|
121
|
+
|
|
122
|
+
// If tab is navigating, complete navigation when new frame is received
|
|
123
|
+
if (tab?.isNavigating) {
|
|
124
|
+
updates.isNavigating = false;
|
|
125
|
+
debug.log('preview', `✅ Navigation frame received for tab: ${tabId}, completing progress`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
tabManager.updateTab(tabId, updates);
|
|
129
|
+
|
|
130
|
+
if (tabId === tabManager.activeTabId) {
|
|
131
|
+
debug.log('preview', `🎬 Screencast frame received for active tab: ${tabId}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function handleCursorPosition(tabId: string, data: any) {
|
|
136
|
+
if (data && tabId === tabManager.activeTabId && transformBrowserToDisplayCoordinates) {
|
|
137
|
+
const transformedPosition = transformBrowserToDisplayCoordinates(data.x, data.y);
|
|
138
|
+
if (transformedPosition && onCursorUpdate) {
|
|
139
|
+
onCursorUpdate(transformedPosition.x, transformedPosition.y, false);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function handleCursorClick(tabId: string, data: any) {
|
|
145
|
+
if (data && tabId === tabManager.activeTabId && transformBrowserToDisplayCoordinates) {
|
|
146
|
+
const transformedPosition = transformBrowserToDisplayCoordinates(data.x, data.y);
|
|
147
|
+
if (transformedPosition && onCursorUpdate) {
|
|
148
|
+
onCursorUpdate(transformedPosition.x, transformedPosition.y, true);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleTestCompleted(tabId: string) {
|
|
154
|
+
if (tabId === tabManager.activeTabId && onTestCompleted) {
|
|
155
|
+
onTestCompleted();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function handleConsoleMessage(tabId: string, data: any, tab: PreviewTab) {
|
|
160
|
+
if (data && data.message) {
|
|
161
|
+
const tabLogs = [...(tab.consoleLogs || []), data.message];
|
|
162
|
+
// Keep only last 1000 messages for performance
|
|
163
|
+
const trimmedLogs = tabLogs.length > 1000 ? tabLogs.slice(-500) : tabLogs;
|
|
164
|
+
tabManager.updateTab(tabId, { consoleLogs: trimmedLogs });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleConsoleClear(tabId: string) {
|
|
169
|
+
tabManager.updateTab(tabId, { consoleLogs: [] });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleNavigationLoading(tabId: string, data: any) {
|
|
173
|
+
if (data && data.url) {
|
|
174
|
+
const tab = tabManager.getTab(tabId);
|
|
175
|
+
// isNavigating: true if session already exists (navigating within same session)
|
|
176
|
+
// isNavigating: false if no session yet (initial load)
|
|
177
|
+
const isNavigating = tab?.sessionId ? true : false;
|
|
178
|
+
|
|
179
|
+
tabManager.updateTab(tabId, {
|
|
180
|
+
isLoading: true,
|
|
181
|
+
isNavigating,
|
|
182
|
+
url: data.url,
|
|
183
|
+
title: getTabTitle(data.url)
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Only update parent if this is the active tab AND not already navigating via HTTP
|
|
187
|
+
// When navigating via HTTP (Go button), the HTTP response will handle URL updates
|
|
188
|
+
// to avoid race conditions with stream events overwriting the final redirected URL
|
|
189
|
+
if (tabId === tabManager.activeTabId && onNavigationUpdate && !tab?.isNavigating) {
|
|
190
|
+
onNavigationUpdate(tabId, data.url);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function handleNavigation(tabId: string, data: any, tab: PreviewTab) {
|
|
196
|
+
if (data && data.url && data.url !== tab.url) {
|
|
197
|
+
debug.log('preview', `🧭 Navigation completed for tab ${tabId}: ${tab.url} → ${data.url}`);
|
|
198
|
+
tabManager.updateTab(tabId, {
|
|
199
|
+
isLoading: false,
|
|
200
|
+
isNavigating: false,
|
|
201
|
+
url: data.url,
|
|
202
|
+
title: getTabTitle(data.url)
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Only update parent if this is the active tab
|
|
206
|
+
if (tabId === tabManager.activeTabId && onNavigationUpdate) {
|
|
207
|
+
onNavigationUpdate(tabId, data.url);
|
|
208
|
+
}
|
|
209
|
+
} else if (data && data.url === tab.url) {
|
|
210
|
+
// Same URL but navigation completed (e.g., page refresh)
|
|
211
|
+
debug.log('preview', `🔄 Same URL navigation completed for tab ${tabId}: ${data.url}`);
|
|
212
|
+
tabManager.updateTab(tabId, {
|
|
213
|
+
isLoading: false,
|
|
214
|
+
isNavigating: false
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function handleNewWindow(data: any) {
|
|
220
|
+
if (data && data.url) {
|
|
221
|
+
tabManager.createTab(data.url);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
handleStreamMessage,
|
|
227
|
+
handleConnectionChange
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export type StreamMessageHandler = ReturnType<typeof createStreamMessageHandler>;
|