@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,512 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Page } from 'puppeteer';
|
|
3
|
+
import type {
|
|
4
|
+
BrowserTab,
|
|
5
|
+
BrowserSelectInfo,
|
|
6
|
+
BrowserSelectResponse,
|
|
7
|
+
BrowserContextMenuInfo,
|
|
8
|
+
BrowserContextMenuResponse,
|
|
9
|
+
BrowserContextMenuItem
|
|
10
|
+
} from './types';
|
|
11
|
+
import { debug } from '$shared/utils/logger';
|
|
12
|
+
import { nanoid } from 'nanoid';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Browser Native UI Handler
|
|
16
|
+
*
|
|
17
|
+
* Handles OS-native UI elements that cannot be rendered in headless browser:
|
|
18
|
+
* - <select> dropdown menus
|
|
19
|
+
* - Context menus (right-click)
|
|
20
|
+
*
|
|
21
|
+
* Detects these elements, extracts their data, and emits events to frontend
|
|
22
|
+
* for rendering as overlay components positioned over the canvas.
|
|
23
|
+
*/
|
|
24
|
+
export class BrowserNativeUIHandler extends EventEmitter {
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if clicked element is a <select> and extract options
|
|
31
|
+
*/
|
|
32
|
+
async checkForSelect(sessionId: string, page: Page, x: number, y: number): Promise<BrowserSelectInfo | null> {
|
|
33
|
+
try {
|
|
34
|
+
// Generate unique select ID
|
|
35
|
+
const selectId = nanoid(10);
|
|
36
|
+
|
|
37
|
+
const selectData = await page.evaluate((params) => {
|
|
38
|
+
const { x, y, selectId } = params;
|
|
39
|
+
const element = document.elementFromPoint(x, y);
|
|
40
|
+
|
|
41
|
+
if (!element) return null;
|
|
42
|
+
|
|
43
|
+
// Check if element is a select or inside a select
|
|
44
|
+
let selectElement: HTMLSelectElement | null = null;
|
|
45
|
+
if (element.tagName === 'SELECT') {
|
|
46
|
+
selectElement = element as HTMLSelectElement;
|
|
47
|
+
} else {
|
|
48
|
+
selectElement = element.closest('select') as HTMLSelectElement;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!selectElement) return null;
|
|
52
|
+
|
|
53
|
+
// IMPORTANT: Mark this select element with unique ID for later reference
|
|
54
|
+
selectElement.setAttribute('data-puppeteer-select-id', selectId);
|
|
55
|
+
|
|
56
|
+
// Extract select options
|
|
57
|
+
const options = Array.from(selectElement.options).map((opt, index) => ({
|
|
58
|
+
index,
|
|
59
|
+
value: opt.value || '',
|
|
60
|
+
text: opt.textContent || '',
|
|
61
|
+
selected: opt.selected,
|
|
62
|
+
disabled: opt.disabled
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
// Get bounding box
|
|
66
|
+
const rect = selectElement.getBoundingClientRect();
|
|
67
|
+
const boundingBox = {
|
|
68
|
+
x: rect.left,
|
|
69
|
+
y: rect.top,
|
|
70
|
+
width: rect.width,
|
|
71
|
+
height: rect.height
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
options,
|
|
76
|
+
selectedIndex: selectElement.selectedIndex,
|
|
77
|
+
boundingBox
|
|
78
|
+
};
|
|
79
|
+
}, { x, y, selectId });
|
|
80
|
+
|
|
81
|
+
if (!selectData) return null;
|
|
82
|
+
const selectInfo: any = {
|
|
83
|
+
sessionId, // Internal use only, converted to tabId at previewService layer
|
|
84
|
+
selectId,
|
|
85
|
+
x,
|
|
86
|
+
y,
|
|
87
|
+
boundingBox: selectData.boundingBox,
|
|
88
|
+
options: selectData.options,
|
|
89
|
+
selectedIndex: selectData.selectedIndex,
|
|
90
|
+
timestamp: Date.now()
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
debug.log('preview', `📋 Select element detected at (${x}, ${y}) with ${selectData.options.length} options`);
|
|
94
|
+
return selectInfo;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
debug.error('preview', 'Error checking for select element:', error);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Handle select option selection from frontend
|
|
103
|
+
*/
|
|
104
|
+
async handleSelectResponse(page: Page, response: BrowserSelectResponse): Promise<boolean> {
|
|
105
|
+
const { selectId, selectedIndex } = response;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
// Update the select value in the page
|
|
109
|
+
const result = await page.evaluate((params) => {
|
|
110
|
+
const { selectId, index } = params;
|
|
111
|
+
|
|
112
|
+
// Find the select element by the unique ID we set earlier
|
|
113
|
+
const selectElement = document.querySelector(`select[data-puppeteer-select-id="${selectId}"]`) as HTMLSelectElement;
|
|
114
|
+
|
|
115
|
+
if (!selectElement) {
|
|
116
|
+
console.error(`Select element with ID ${selectId} not found`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (index < 0 || index >= selectElement.options.length) {
|
|
121
|
+
console.error(`Invalid option index: ${index}`);
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Update selected index
|
|
126
|
+
selectElement.selectedIndex = index;
|
|
127
|
+
|
|
128
|
+
// Trigger change event
|
|
129
|
+
const changeEvent = new Event('change', { bubbles: true });
|
|
130
|
+
selectElement.dispatchEvent(changeEvent);
|
|
131
|
+
|
|
132
|
+
// Trigger input event for React/Vue compatibility
|
|
133
|
+
const inputEvent = new Event('input', { bubbles: true });
|
|
134
|
+
selectElement.dispatchEvent(inputEvent);
|
|
135
|
+
|
|
136
|
+
// Clean up the tracking attribute
|
|
137
|
+
selectElement.removeAttribute('data-puppeteer-select-id');
|
|
138
|
+
|
|
139
|
+
return true;
|
|
140
|
+
}, { selectId, index: selectedIndex });
|
|
141
|
+
|
|
142
|
+
if (result) {
|
|
143
|
+
debug.log('preview', `✅ Select option updated to index: ${selectedIndex}`);
|
|
144
|
+
} else {
|
|
145
|
+
debug.warn('preview', `⚠️ Failed to update select option to index: ${selectedIndex}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return result;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
debug.error('preview', 'Error handling select response:', error);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check element at coordinates and build context menu
|
|
157
|
+
*/
|
|
158
|
+
async checkForContextMenu(sessionId: string, page: Page, x: number, y: number): Promise<BrowserContextMenuInfo | null> {
|
|
159
|
+
try {
|
|
160
|
+
const contextData = await page.evaluate((coordinates) => {
|
|
161
|
+
const { x, y } = coordinates;
|
|
162
|
+
const element = document.elementFromPoint(x, y);
|
|
163
|
+
|
|
164
|
+
if (!element) return null;
|
|
165
|
+
|
|
166
|
+
// Get element information
|
|
167
|
+
const tagName = element.tagName;
|
|
168
|
+
const isLink = element.tagName === 'A' || element.closest('a') !== null;
|
|
169
|
+
const isImage = element.tagName === 'IMG';
|
|
170
|
+
const isInput = element.tagName === 'INPUT' || element.tagName === 'TEXTAREA';
|
|
171
|
+
|
|
172
|
+
// Check for text selection
|
|
173
|
+
const selection = window.getSelection();
|
|
174
|
+
const isTextSelected = selection ? selection.toString().length > 0 : false;
|
|
175
|
+
|
|
176
|
+
// Get link URL if it's a link
|
|
177
|
+
let linkUrl: string | undefined;
|
|
178
|
+
if (isLink) {
|
|
179
|
+
const linkElement = element.tagName === 'A' ? element as HTMLAnchorElement : element.closest('a') as HTMLAnchorElement;
|
|
180
|
+
linkUrl = linkElement?.href;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Get image URL if it's an image
|
|
184
|
+
let imageUrl: string | undefined;
|
|
185
|
+
if (isImage) {
|
|
186
|
+
imageUrl = (element as HTMLImageElement).src;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Get input type
|
|
190
|
+
let inputType: string | undefined;
|
|
191
|
+
if (isInput && element.tagName === 'INPUT') {
|
|
192
|
+
inputType = (element as HTMLInputElement).type;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
tagName,
|
|
197
|
+
isLink,
|
|
198
|
+
isImage,
|
|
199
|
+
isInput,
|
|
200
|
+
isTextSelected,
|
|
201
|
+
linkUrl,
|
|
202
|
+
imageUrl,
|
|
203
|
+
inputType
|
|
204
|
+
};
|
|
205
|
+
}, { x, y });
|
|
206
|
+
|
|
207
|
+
if (!contextData) return null;
|
|
208
|
+
|
|
209
|
+
// Build context menu items based on element type
|
|
210
|
+
const items = this.buildContextMenuItems(contextData);
|
|
211
|
+
|
|
212
|
+
const menuId = nanoid(10);
|
|
213
|
+
const menuInfo: any = {
|
|
214
|
+
sessionId, // Internal use only, converted to tabId at previewService layer
|
|
215
|
+
menuId,
|
|
216
|
+
x,
|
|
217
|
+
y,
|
|
218
|
+
items,
|
|
219
|
+
elementInfo: contextData,
|
|
220
|
+
timestamp: Date.now()
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
debug.log('preview', `📜 Context menu requested at (${x}, ${y}) for element: ${contextData.tagName}`);
|
|
224
|
+
return menuInfo;
|
|
225
|
+
} catch (error) {
|
|
226
|
+
debug.error('preview', 'Error checking for context menu:', error);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Build context menu items based on element type
|
|
233
|
+
*/
|
|
234
|
+
private buildContextMenuItems(elementInfo: any): BrowserContextMenuItem[] {
|
|
235
|
+
const items: BrowserContextMenuItem[] = [];
|
|
236
|
+
|
|
237
|
+
// Back / Forward
|
|
238
|
+
items.push(
|
|
239
|
+
{ id: 'back', label: 'Back', enabled: true },
|
|
240
|
+
{ id: 'forward', label: 'Forward', enabled: true },
|
|
241
|
+
{ id: 'reload', label: 'Reload', enabled: true },
|
|
242
|
+
{ id: 'separator-1', label: '', enabled: false, type: 'separator' }
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Link-specific actions
|
|
246
|
+
if (elementInfo.isLink && elementInfo.linkUrl) {
|
|
247
|
+
items.push(
|
|
248
|
+
{ id: 'open-link-new-tab', label: 'Open Link in New Tab', enabled: true },
|
|
249
|
+
{ id: 'copy-link', label: 'Copy Link Address', enabled: true },
|
|
250
|
+
{ id: 'separator-2', label: '', enabled: false, type: 'separator' }
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Image-specific actions
|
|
255
|
+
if (elementInfo.isImage && elementInfo.imageUrl) {
|
|
256
|
+
items.push(
|
|
257
|
+
{ id: 'open-image-new-tab', label: 'Open Image in New Tab', enabled: true },
|
|
258
|
+
{ id: 'save-image', label: 'Save Image As...', enabled: true },
|
|
259
|
+
{ id: 'copy-image', label: 'Copy Image', enabled: true },
|
|
260
|
+
{ id: 'copy-image-address', label: 'Copy Image Address', enabled: true },
|
|
261
|
+
{ id: 'separator-3', label: '', enabled: false, type: 'separator' }
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Text selection actions
|
|
266
|
+
if (elementInfo.isTextSelected) {
|
|
267
|
+
items.push(
|
|
268
|
+
{ id: 'copy', label: 'Copy', enabled: true },
|
|
269
|
+
{ id: 'separator-4', label: '', enabled: false, type: 'separator' }
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Input-specific actions
|
|
274
|
+
if (elementInfo.isInput) {
|
|
275
|
+
items.push(
|
|
276
|
+
{ id: 'cut', label: 'Cut', enabled: true },
|
|
277
|
+
{ id: 'copy', label: 'Copy', enabled: elementInfo.isTextSelected },
|
|
278
|
+
{ id: 'paste', label: 'Paste', enabled: true },
|
|
279
|
+
{ id: 'separator-5', label: '', enabled: false, type: 'separator' }
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return items;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Fetch image from page and emit download event with base64 data
|
|
288
|
+
*/
|
|
289
|
+
private async downloadImageFromPage(page: Page, imageUrl: string): Promise<void> {
|
|
290
|
+
try {
|
|
291
|
+
debug.log('preview', `💾 Fetching image for download: ${imageUrl}`);
|
|
292
|
+
|
|
293
|
+
// Fetch image as base64 using page.evaluate
|
|
294
|
+
const imageData = await page.evaluate(async (url) => {
|
|
295
|
+
try {
|
|
296
|
+
const response = await fetch(url);
|
|
297
|
+
const blob = await response.blob();
|
|
298
|
+
|
|
299
|
+
return new Promise<{ base64: string, type: string, filename: string }>((resolve, reject) => {
|
|
300
|
+
const reader = new FileReader();
|
|
301
|
+
reader.onloadend = () => {
|
|
302
|
+
const base64 = reader.result as string;
|
|
303
|
+
// Extract filename from URL
|
|
304
|
+
const urlParts = url.split('/');
|
|
305
|
+
let filename = urlParts[urlParts.length - 1].split('?')[0] || 'image';
|
|
306
|
+
|
|
307
|
+
// If no extension, add one based on blob type
|
|
308
|
+
if (!filename.includes('.')) {
|
|
309
|
+
const ext = blob.type.split('/')[1] || 'png';
|
|
310
|
+
filename = `image.${ext}`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
resolve({
|
|
314
|
+
base64: base64.split(',')[1], // Remove data:image/png;base64, prefix
|
|
315
|
+
type: blob.type,
|
|
316
|
+
filename
|
|
317
|
+
});
|
|
318
|
+
};
|
|
319
|
+
reader.onerror = reject;
|
|
320
|
+
reader.readAsDataURL(blob);
|
|
321
|
+
});
|
|
322
|
+
} catch (error) {
|
|
323
|
+
throw new Error(`Failed to fetch image: ${error}`);
|
|
324
|
+
}
|
|
325
|
+
}, imageUrl);
|
|
326
|
+
|
|
327
|
+
// Emit event with image data to frontend
|
|
328
|
+
this.emit('download-image', imageData);
|
|
329
|
+
debug.log('preview', `✅ Image data sent for download: ${imageData.filename}`);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
debug.error('preview', '❌ Failed to fetch image for download:', error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Paste text to element at coordinates
|
|
337
|
+
*/
|
|
338
|
+
private async pasteTextToPage(page: Page, x: number, y: number, text: string): Promise<void> {
|
|
339
|
+
try {
|
|
340
|
+
debug.log('preview', `📋 Pasting text to element at (${x}, ${y})`);
|
|
341
|
+
|
|
342
|
+
await page.evaluate((params) => {
|
|
343
|
+
const { x, y, text } = params;
|
|
344
|
+
const element = document.elementFromPoint(x, y) as HTMLElement;
|
|
345
|
+
|
|
346
|
+
if (element) {
|
|
347
|
+
// If it's an input element, insert text at cursor position
|
|
348
|
+
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
349
|
+
const input = element as HTMLInputElement | HTMLTextAreaElement;
|
|
350
|
+
const start = input.selectionStart || 0;
|
|
351
|
+
const end = input.selectionEnd || 0;
|
|
352
|
+
const currentValue = input.value;
|
|
353
|
+
|
|
354
|
+
// Insert text at cursor position
|
|
355
|
+
input.value = currentValue.substring(0, start) + text + currentValue.substring(end);
|
|
356
|
+
|
|
357
|
+
// Move cursor to end of inserted text
|
|
358
|
+
const newCursorPos = start + text.length;
|
|
359
|
+
input.setSelectionRange(newCursorPos, newCursorPos);
|
|
360
|
+
|
|
361
|
+
// Trigger input event for React/Vue compatibility
|
|
362
|
+
const inputEvent = new Event('input', { bubbles: true });
|
|
363
|
+
input.dispatchEvent(inputEvent);
|
|
364
|
+
|
|
365
|
+
// Trigger change event
|
|
366
|
+
const changeEvent = new Event('change', { bubbles: true });
|
|
367
|
+
input.dispatchEvent(changeEvent);
|
|
368
|
+
} else if (element.isContentEditable) {
|
|
369
|
+
// For contenteditable elements
|
|
370
|
+
document.execCommand('insertText', false, text);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}, { x, y, text });
|
|
374
|
+
|
|
375
|
+
debug.log('preview', `✅ Pasted ${text.length} characters successfully`);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
debug.error('preview', '❌ Failed to paste text:', error);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Fetch image from page and emit copy event with base64 data
|
|
383
|
+
*/
|
|
384
|
+
private async copyImageFromPage(page: Page, imageUrl: string): Promise<void> {
|
|
385
|
+
try {
|
|
386
|
+
debug.log('preview', `📋 Fetching image for clipboard: ${imageUrl}`);
|
|
387
|
+
|
|
388
|
+
// Fetch image as base64 using page.evaluate
|
|
389
|
+
const imageData = await page.evaluate(async (url) => {
|
|
390
|
+
try {
|
|
391
|
+
const response = await fetch(url);
|
|
392
|
+
const blob = await response.blob();
|
|
393
|
+
|
|
394
|
+
return new Promise<{ base64: string, type: string }>((resolve, reject) => {
|
|
395
|
+
const reader = new FileReader();
|
|
396
|
+
reader.onloadend = () => {
|
|
397
|
+
const base64 = reader.result as string;
|
|
398
|
+
resolve({
|
|
399
|
+
base64: base64.split(',')[1], // Remove data:image/png;base64, prefix
|
|
400
|
+
type: blob.type
|
|
401
|
+
});
|
|
402
|
+
};
|
|
403
|
+
reader.onerror = reject;
|
|
404
|
+
reader.readAsDataURL(blob);
|
|
405
|
+
});
|
|
406
|
+
} catch (error) {
|
|
407
|
+
throw new Error(`Failed to fetch image: ${error}`);
|
|
408
|
+
}
|
|
409
|
+
}, imageUrl);
|
|
410
|
+
|
|
411
|
+
// Emit event with image data to frontend
|
|
412
|
+
this.emit('copy-image-to-clipboard', imageData);
|
|
413
|
+
debug.log('preview', `✅ Image data sent for clipboard`);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
debug.error('preview', '❌ Failed to fetch image for clipboard:', error);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Handle context menu action from frontend
|
|
421
|
+
*/
|
|
422
|
+
async handleContextMenuResponse(page: Page, response: BrowserContextMenuResponse, menuInfo: BrowserContextMenuInfo, clipboardText?: string): Promise<boolean> {
|
|
423
|
+
const { itemId } = response;
|
|
424
|
+
|
|
425
|
+
try {
|
|
426
|
+
debug.log('preview', `🎯 Executing context menu action: ${itemId}`);
|
|
427
|
+
|
|
428
|
+
switch (itemId) {
|
|
429
|
+
case 'back':
|
|
430
|
+
await page.goBack();
|
|
431
|
+
break;
|
|
432
|
+
|
|
433
|
+
case 'forward':
|
|
434
|
+
await page.goForward();
|
|
435
|
+
break;
|
|
436
|
+
|
|
437
|
+
case 'reload':
|
|
438
|
+
await page.reload();
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
case 'copy':
|
|
442
|
+
await page.evaluate(() => {
|
|
443
|
+
document.execCommand('copy');
|
|
444
|
+
});
|
|
445
|
+
break;
|
|
446
|
+
|
|
447
|
+
case 'cut':
|
|
448
|
+
await page.evaluate(() => {
|
|
449
|
+
document.execCommand('cut');
|
|
450
|
+
});
|
|
451
|
+
break;
|
|
452
|
+
|
|
453
|
+
case 'paste':
|
|
454
|
+
// Paste clipboard content to the page
|
|
455
|
+
if (clipboardText !== undefined) {
|
|
456
|
+
await this.pasteTextToPage(page, menuInfo.x, menuInfo.y, clipboardText);
|
|
457
|
+
} else {
|
|
458
|
+
debug.warn('preview', '⚠️ No clipboard text provided for paste action');
|
|
459
|
+
}
|
|
460
|
+
break;
|
|
461
|
+
|
|
462
|
+
case 'copy-link':
|
|
463
|
+
if (menuInfo.elementInfo.linkUrl) {
|
|
464
|
+
// Emit event to frontend to copy to clipboard (can't access clipboard from backend)
|
|
465
|
+
this.emit('copy-to-clipboard', { text: menuInfo.elementInfo.linkUrl });
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
|
|
469
|
+
case 'copy-image-address':
|
|
470
|
+
if (menuInfo.elementInfo.imageUrl) {
|
|
471
|
+
this.emit('copy-to-clipboard', { text: menuInfo.elementInfo.imageUrl });
|
|
472
|
+
}
|
|
473
|
+
break;
|
|
474
|
+
|
|
475
|
+
case 'open-link-new-tab':
|
|
476
|
+
if (menuInfo.elementInfo.linkUrl) {
|
|
477
|
+
this.emit('open-url-new-tab', { url: menuInfo.elementInfo.linkUrl });
|
|
478
|
+
}
|
|
479
|
+
break;
|
|
480
|
+
|
|
481
|
+
case 'open-image-new-tab':
|
|
482
|
+
if (menuInfo.elementInfo.imageUrl) {
|
|
483
|
+
this.emit('open-url-new-tab', { url: menuInfo.elementInfo.imageUrl });
|
|
484
|
+
}
|
|
485
|
+
break;
|
|
486
|
+
|
|
487
|
+
case 'save-image':
|
|
488
|
+
if (menuInfo.elementInfo.imageUrl) {
|
|
489
|
+
// Fetch image from page and send to frontend
|
|
490
|
+
await this.downloadImageFromPage(page, menuInfo.elementInfo.imageUrl);
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
|
|
494
|
+
case 'copy-image':
|
|
495
|
+
if (menuInfo.elementInfo.imageUrl) {
|
|
496
|
+
// Fetch image from page and send to frontend
|
|
497
|
+
await this.copyImageFromPage(page, menuInfo.elementInfo.imageUrl);
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
|
|
501
|
+
default:
|
|
502
|
+
debug.warn('preview', `⚠️ Unknown context menu action: ${itemId}`);
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return true;
|
|
507
|
+
} catch (error) {
|
|
508
|
+
debug.error('preview', `Error handling context menu action ${itemId}:`, error);
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { Page, HTTPRequest, Frame } from 'puppeteer';
|
|
3
|
+
import type { BrowserTab } from './types';
|
|
4
|
+
|
|
5
|
+
export class BrowserNavigationTracker extends EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async setupNavigationTracking(sessionId: string, page: Page, session: BrowserTab) {
|
|
11
|
+
|
|
12
|
+
// Track navigation start (loading begins)
|
|
13
|
+
page.on('request', (request: HTTPRequest) => {
|
|
14
|
+
// Only track main frame document requests (not resources like images, CSS, etc.)
|
|
15
|
+
// Puppeteer uses resourceType() instead of isNavigationRequest()
|
|
16
|
+
if (request.resourceType() === 'document' && request.frame() === page.mainFrame()) {
|
|
17
|
+
const targetUrl = request.url();
|
|
18
|
+
|
|
19
|
+
// Emit navigation loading event to frontend
|
|
20
|
+
this.emit('navigation-loading', {
|
|
21
|
+
sessionId,
|
|
22
|
+
type: 'navigation-loading',
|
|
23
|
+
url: targetUrl,
|
|
24
|
+
timestamp: Date.now()
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Track all navigation events - including redirects, link clicks, and hash changes
|
|
30
|
+
page.on('framenavigated', (frame: Frame) => {
|
|
31
|
+
// Only track main frame navigation (not iframes)
|
|
32
|
+
if (frame === page.mainFrame()) {
|
|
33
|
+
const newUrl = frame.url();
|
|
34
|
+
|
|
35
|
+
// Update session URL
|
|
36
|
+
session.url = newUrl;
|
|
37
|
+
|
|
38
|
+
// Emit navigation completed event to frontend
|
|
39
|
+
this.emit('navigation', {
|
|
40
|
+
sessionId,
|
|
41
|
+
type: 'navigation',
|
|
42
|
+
url: newUrl,
|
|
43
|
+
timestamp: Date.now()
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Also track URL changes via JavaScript (for single page applications)
|
|
49
|
+
page.on('load', async () => {
|
|
50
|
+
const currentUrl = page.url();
|
|
51
|
+
if (currentUrl !== session.url) {
|
|
52
|
+
|
|
53
|
+
session.url = currentUrl;
|
|
54
|
+
|
|
55
|
+
this.emit('navigation', {
|
|
56
|
+
sessionId,
|
|
57
|
+
type: 'navigation',
|
|
58
|
+
url: currentUrl,
|
|
59
|
+
timestamp: Date.now()
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Track hash changes (fragment identifier changes like #contact-us)
|
|
65
|
+
await page.evaluateOnNewDocument(() => {
|
|
66
|
+
let lastUrl = window.location.href;
|
|
67
|
+
|
|
68
|
+
// Monitor for hash changes and other URL changes
|
|
69
|
+
const checkUrlChange = () => {
|
|
70
|
+
const currentUrl = window.location.href;
|
|
71
|
+
if (currentUrl !== lastUrl) {
|
|
72
|
+
lastUrl = currentUrl;
|
|
73
|
+
|
|
74
|
+
// Store the new URL for the backend to detect
|
|
75
|
+
(window as any).__urlChanged = {
|
|
76
|
+
url: currentUrl,
|
|
77
|
+
timestamp: Date.now()
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Listen to various events that might change URL
|
|
83
|
+
window.addEventListener('hashchange', checkUrlChange);
|
|
84
|
+
window.addEventListener('popstate', checkUrlChange);
|
|
85
|
+
|
|
86
|
+
// Periodically check for URL changes (for SPA navigation)
|
|
87
|
+
setInterval(checkUrlChange, 500);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async navigateSession(sessionId: string, session: BrowserTab, url: string): Promise<string> {
|
|
92
|
+
|
|
93
|
+
await session.page.goto(url);
|
|
94
|
+
|
|
95
|
+
// Get the final URL after any redirects
|
|
96
|
+
const finalUrl = session.page.url();
|
|
97
|
+
session.url = finalUrl;
|
|
98
|
+
|
|
99
|
+
// Update current URL tracking
|
|
100
|
+
session.currentUrl = finalUrl;
|
|
101
|
+
|
|
102
|
+
return finalUrl;
|
|
103
|
+
}
|
|
104
|
+
}
|