@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,450 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
|
+
import Canvas from './Canvas.svelte';
|
|
4
|
+
import { scale } from 'svelte/transition';
|
|
5
|
+
import { cubicOut } from 'svelte/easing';
|
|
6
|
+
import { onDestroy } from 'svelte';
|
|
7
|
+
import { getViewportDimensions, type DeviceSize, type Rotation } from '$frontend/lib/constants/preview';
|
|
8
|
+
import { debug } from '$shared/utils/logger';
|
|
9
|
+
import { sendScaleUpdate } from '../core/interactions.svelte';
|
|
10
|
+
|
|
11
|
+
let {
|
|
12
|
+
projectId = '', // REQUIRED for project isolation (read-only from parent)
|
|
13
|
+
url = $bindable(''),
|
|
14
|
+
isLoading = $bindable(false),
|
|
15
|
+
isLaunchingBrowser = $bindable(false),
|
|
16
|
+
isNavigating = $bindable(false),
|
|
17
|
+
isReconnecting = $bindable(false), // True during fast reconnect after navigation
|
|
18
|
+
deviceSize = $bindable<DeviceSize>('laptop'),
|
|
19
|
+
rotation = $bindable<Rotation>('portrait'),
|
|
20
|
+
sessionId = $bindable<string | null>(null),
|
|
21
|
+
sessionInfo = $bindable<any>(null),
|
|
22
|
+
isConnected = $bindable(false),
|
|
23
|
+
isStreamReady = $bindable(false), // True when first frame received from WebCodecs
|
|
24
|
+
errorMessage = $bindable<string | null>(null),
|
|
25
|
+
virtualCursor = $bindable<{ x: number; y: number; visible: boolean; clicking?: boolean }>({
|
|
26
|
+
x: 0,
|
|
27
|
+
y: 0,
|
|
28
|
+
visible: false
|
|
29
|
+
}),
|
|
30
|
+
lastFrameData = $bindable<any>(null), // Add lastFrameData prop
|
|
31
|
+
|
|
32
|
+
// MCP Control State
|
|
33
|
+
isMcpControlled = $bindable(false),
|
|
34
|
+
|
|
35
|
+
// Canvas API
|
|
36
|
+
canvasAPI = $bindable<any>(null),
|
|
37
|
+
|
|
38
|
+
// Preview dimensions (bindable to parent)
|
|
39
|
+
previewDimensions = $bindable<any>({ scale: 1 }),
|
|
40
|
+
|
|
41
|
+
// Callbacks
|
|
42
|
+
onInteraction = $bindable<(action: any) => void>(() => {}),
|
|
43
|
+
onRetry = $bindable<() => void>(() => {})
|
|
44
|
+
} = $props();
|
|
45
|
+
|
|
46
|
+
let previewContainer = $state<HTMLDivElement | undefined>();
|
|
47
|
+
|
|
48
|
+
// Solid loading overlay: shown during initial load states
|
|
49
|
+
const showSolidOverlay = $derived(
|
|
50
|
+
isLaunchingBrowser || !sessionInfo || (!isStreamReady && !isNavigating && !isReconnecting)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Navigation overlay state with debounce to prevent flickering during state transitions
|
|
54
|
+
let showNavigationOverlay = $state(false);
|
|
55
|
+
let overlayHideTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
56
|
+
|
|
57
|
+
// Debounced navigation overlay - similar to progress bar logic
|
|
58
|
+
// Shows immediately when navigating/reconnecting, hides with delay to prevent flicker
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const shouldShowOverlay = (isNavigating || isReconnecting) && isStreamReady;
|
|
61
|
+
|
|
62
|
+
// Cancel any pending hide when overlay should show
|
|
63
|
+
if (shouldShowOverlay && overlayHideTimeout) {
|
|
64
|
+
clearTimeout(overlayHideTimeout);
|
|
65
|
+
overlayHideTimeout = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Show immediately
|
|
69
|
+
if (shouldShowOverlay && !showNavigationOverlay) {
|
|
70
|
+
showNavigationOverlay = true;
|
|
71
|
+
}
|
|
72
|
+
// Hide with debounce to handle state transitions
|
|
73
|
+
else if (!shouldShowOverlay && showNavigationOverlay && !overlayHideTimeout) {
|
|
74
|
+
overlayHideTimeout = setTimeout(() => {
|
|
75
|
+
overlayHideTimeout = null;
|
|
76
|
+
// Re-check if we should still hide
|
|
77
|
+
const stillShouldHide = !(isNavigating || isReconnecting) || !isStreamReady;
|
|
78
|
+
if (stillShouldHide) {
|
|
79
|
+
showNavigationOverlay = false;
|
|
80
|
+
}
|
|
81
|
+
}, 100); // 100ms debounce
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
onDestroy(() => {
|
|
86
|
+
if (overlayHideTimeout) {
|
|
87
|
+
clearTimeout(overlayHideTimeout);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Use imported device viewports from config
|
|
92
|
+
|
|
93
|
+
// Calculate and update preview dimensions
|
|
94
|
+
function calculatePreviewDimensions() {
|
|
95
|
+
if (!previewContainer) {
|
|
96
|
+
previewDimensions = {
|
|
97
|
+
width: '100%',
|
|
98
|
+
height: '100%',
|
|
99
|
+
scale: 1,
|
|
100
|
+
frameWidth: '120rem',
|
|
101
|
+
frameHeight: '67.5rem',
|
|
102
|
+
containerWidth: '100%',
|
|
103
|
+
containerHeight: '100%'
|
|
104
|
+
};
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const containerRect = previewContainer.getBoundingClientRect();
|
|
109
|
+
const availableWidth = containerRect.width * 1.2;
|
|
110
|
+
const availableHeight = containerRect.height * 1.2;
|
|
111
|
+
|
|
112
|
+
// Use getViewportDimensions for consistent viewport calculation
|
|
113
|
+
// This ensures portrait = height > width, landscape = width > height
|
|
114
|
+
const { width: deviceWidth, height: deviceHeight } = getViewportDimensions(deviceSize as DeviceSize, rotation as Rotation);
|
|
115
|
+
|
|
116
|
+
// Calculate scale to fit in container while maintaining aspect ratio
|
|
117
|
+
// Important: Never scale up beyond original size (scale max = 1)
|
|
118
|
+
const scaleX = Math.min(1, availableWidth / deviceWidth);
|
|
119
|
+
const scaleY = Math.min(1, availableHeight / deviceHeight);
|
|
120
|
+
const scale = Math.min(scaleX, scaleY);
|
|
121
|
+
|
|
122
|
+
// Calculate container dimensions (what user sees)
|
|
123
|
+
const containerWidth = deviceWidth * scale;
|
|
124
|
+
const containerHeight = deviceHeight * scale;
|
|
125
|
+
|
|
126
|
+
previewDimensions = {
|
|
127
|
+
width: `${containerWidth / 16}rem`,
|
|
128
|
+
height: `${containerHeight / 16}rem`,
|
|
129
|
+
scale: scale,
|
|
130
|
+
frameWidth: `${deviceWidth / 16}rem`,
|
|
131
|
+
frameHeight: `${deviceHeight / 16}rem`,
|
|
132
|
+
containerWidth: `${containerWidth / 16}rem`,
|
|
133
|
+
containerHeight: `${containerHeight / 16}rem`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle canvas interactions
|
|
138
|
+
function handleCanvasInteraction(action: any) {
|
|
139
|
+
// Block user interactions when MCP is controlling
|
|
140
|
+
if (isMcpControlled) {
|
|
141
|
+
debug.log('preview', '🚫 User interaction blocked - MCP is controlling');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
onInteraction(action);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function handleCursorUpdate(cursor: string) {
|
|
148
|
+
// Handle cursor updates if needed
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function handleFrameUpdate(data: any) {
|
|
152
|
+
// Handle frame updates if needed
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle retry button click
|
|
156
|
+
function handleRetryClick() {
|
|
157
|
+
// Clear error message immediately for instant UI feedback
|
|
158
|
+
errorMessage = null;
|
|
159
|
+
// Call parent retry handler
|
|
160
|
+
onRetry();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Handle screencast refresh request from Canvas (when stream is stuck)
|
|
164
|
+
// This sends a scale-update which triggers CDP screencast restart on backend
|
|
165
|
+
function handleScreencastRefresh() {
|
|
166
|
+
if (previewDimensions?.scale) {
|
|
167
|
+
debug.log('preview', `📐 Requesting screencast refresh with scale: ${previewDimensions.scale}`);
|
|
168
|
+
sendScaleUpdate(previewDimensions.scale);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Initial dimensions calculation
|
|
173
|
+
$effect(() => {
|
|
174
|
+
if (previewContainer) {
|
|
175
|
+
debug.log('preview', `📐 PreviewContainer: Initial calculation`);
|
|
176
|
+
calculatePreviewDimensions();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Recalculate dimensions when device size or rotation changes
|
|
181
|
+
$effect(() => {
|
|
182
|
+
if (previewContainer) {
|
|
183
|
+
// Trigger reactive recalculation by accessing reactive values
|
|
184
|
+
void deviceSize;
|
|
185
|
+
void rotation;
|
|
186
|
+
debug.log(
|
|
187
|
+
'preview',
|
|
188
|
+
`📐 PreviewContainer: Recalculating dimensions for ${deviceSize}/${rotation}`
|
|
189
|
+
);
|
|
190
|
+
// Force reflow for accurate container dimensions
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
if (previewContainer) {
|
|
193
|
+
calculatePreviewDimensions();
|
|
194
|
+
debug.log(
|
|
195
|
+
'preview',
|
|
196
|
+
`📐 PreviewContainer: New dimensions calculated - scale: ${previewDimensions.scale}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}, 50); // Small delay to ensure layout is updated
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Force recalculation when sessionId changes (tab switch)
|
|
204
|
+
$effect(() => {
|
|
205
|
+
if (previewContainer && sessionId) {
|
|
206
|
+
debug.log(
|
|
207
|
+
'preview',
|
|
208
|
+
`📐 PreviewContainer: SessionId changed to ${sessionId}, forcing recalculation`
|
|
209
|
+
);
|
|
210
|
+
setTimeout(() => {
|
|
211
|
+
if (previewContainer) {
|
|
212
|
+
calculatePreviewDimensions();
|
|
213
|
+
debug.log(
|
|
214
|
+
'preview',
|
|
215
|
+
`📐 PreviewContainer: Forced calculation done - scale: ${previewDimensions.scale}`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}, 100); // Slightly longer delay to ensure all state is synced
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Also force recalculation when URL changes (navigation within tab)
|
|
223
|
+
$effect(() => {
|
|
224
|
+
if (previewContainer && url) {
|
|
225
|
+
debug.log(
|
|
226
|
+
'preview',
|
|
227
|
+
`📐 PreviewContainer: URL changed to ${url}, checking if recalculation needed`
|
|
228
|
+
);
|
|
229
|
+
// Only recalculate if this might affect dimensions (shouldn't usually, but just in case)
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
if (previewContainer) {
|
|
232
|
+
calculatePreviewDimensions();
|
|
233
|
+
debug.log(
|
|
234
|
+
'preview',
|
|
235
|
+
`📐 PreviewContainer: URL-triggered calculation done - scale: ${previewDimensions.scale}`
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}, 150);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Track previous isNavigating state for navigation completion detection
|
|
243
|
+
let wasNavigating = $state(false);
|
|
244
|
+
|
|
245
|
+
// Detect navigation completion and notify Canvas for fast screencast refresh
|
|
246
|
+
$effect(() => {
|
|
247
|
+
// Navigation completed when isNavigating goes from true to false
|
|
248
|
+
if (wasNavigating && !isNavigating && sessionId && canvasAPI?.notifyNavigationComplete) {
|
|
249
|
+
debug.log('preview', `🧭 Navigation completed, notifying Canvas for fast refresh`);
|
|
250
|
+
canvasAPI.notifyNavigationComplete();
|
|
251
|
+
}
|
|
252
|
+
wasNavigating = isNavigating;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Recalculate on window resize
|
|
256
|
+
$effect(() => {
|
|
257
|
+
if (typeof window !== 'undefined') {
|
|
258
|
+
const handleResize = () => {
|
|
259
|
+
if (previewContainer) {
|
|
260
|
+
calculatePreviewDimensions();
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
window.addEventListener('resize', handleResize);
|
|
264
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Use ResizeObserver for precise container size tracking
|
|
269
|
+
$effect(() => {
|
|
270
|
+
if (previewContainer && typeof window !== 'undefined' && 'ResizeObserver' in window) {
|
|
271
|
+
let resizeTimeout: ReturnType<typeof setTimeout> | undefined;
|
|
272
|
+
|
|
273
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
274
|
+
// Clear previous timeout to debounce rapid resize events
|
|
275
|
+
if (resizeTimeout) {
|
|
276
|
+
clearTimeout(resizeTimeout);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Debounce resize events to prevent excessive canvas redraws
|
|
280
|
+
resizeTimeout = setTimeout(() => {
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
const previousDimensions = { ...previewDimensions };
|
|
283
|
+
calculatePreviewDimensions();
|
|
284
|
+
|
|
285
|
+
// Only trigger canvas setup if dimensions actually changed
|
|
286
|
+
const dimensionsChanged =
|
|
287
|
+
JSON.stringify(previousDimensions) !== JSON.stringify(previewDimensions);
|
|
288
|
+
|
|
289
|
+
if (dimensionsChanged && canvasAPI && canvasAPI.setupCanvas) {
|
|
290
|
+
canvasAPI.setupCanvas();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}, 16); // ~60fps debouncing
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
resizeObserver.observe(previewContainer);
|
|
297
|
+
|
|
298
|
+
return () => {
|
|
299
|
+
if (resizeTimeout) {
|
|
300
|
+
clearTimeout(resizeTimeout);
|
|
301
|
+
}
|
|
302
|
+
resizeObserver.disconnect();
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
</script>
|
|
307
|
+
|
|
308
|
+
<!-- Preview Container with scaling -->
|
|
309
|
+
<div
|
|
310
|
+
bind:this={previewContainer}
|
|
311
|
+
class="flex-1 relative overflow-hidden p-0.5 flex items-center justify-center min-h-0"
|
|
312
|
+
>
|
|
313
|
+
{#if errorMessage && !isLaunchingBrowser && !isLoading}
|
|
314
|
+
<!-- Error State - Outside viewport container like empty state -->
|
|
315
|
+
<!-- Only show error if NOT in any loading state (loading states have priority) -->
|
|
316
|
+
<div
|
|
317
|
+
class="text-center text-slate-500 absolute"
|
|
318
|
+
in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
|
|
319
|
+
out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
|
|
320
|
+
>
|
|
321
|
+
<Icon name="lucide:circle-alert" class="w-16 h-16 mx-auto mb-4 text-red-500 opacity-80" />
|
|
322
|
+
<p class="text-lg font-medium mb-2 text-slate-700 dark:text-slate-300">Failed to Load Page</p>
|
|
323
|
+
<p class="text-sm mb-4 text-slate-600 dark:text-slate-400 max-w-md">
|
|
324
|
+
{errorMessage}
|
|
325
|
+
</p>
|
|
326
|
+
<button
|
|
327
|
+
onclick={handleRetryClick}
|
|
328
|
+
class="px-5 py-2.5 bg-violet-600 hover:bg-violet-700 text-white text-sm font-medium rounded-lg transition-colors duration-200 inline-flex items-center gap-2"
|
|
329
|
+
>
|
|
330
|
+
<Icon name="lucide:refresh-cw" class="w-4 h-4" />
|
|
331
|
+
<span>Try Again</span>
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
{:else if url}
|
|
335
|
+
<!-- Scaled container for proper viewport simulation -->
|
|
336
|
+
<div
|
|
337
|
+
class="relative rounded overflow-hidden flex-shrink-0 {isMcpControlled ? 'ring-2 ring-amber-500 ring-offset-2 ring-offset-slate-900' : ''}"
|
|
338
|
+
class:mcp-control-border={isMcpControlled}
|
|
339
|
+
style="width: {previewDimensions.width}; height: {previewDimensions.height};"
|
|
340
|
+
in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
|
|
341
|
+
out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
|
|
342
|
+
>
|
|
343
|
+
<!-- Canvas container - always render when session exists so WebCodecs can start -->
|
|
344
|
+
{#if sessionInfo}
|
|
345
|
+
<div
|
|
346
|
+
class="w-full h-full flex items-center justify-center"
|
|
347
|
+
style="
|
|
348
|
+
width: {previewDimensions.frameWidth};
|
|
349
|
+
height: {previewDimensions.frameHeight};
|
|
350
|
+
transform: scale({previewDimensions.scale});
|
|
351
|
+
transform-origin: top left;
|
|
352
|
+
transition: none;
|
|
353
|
+
"
|
|
354
|
+
>
|
|
355
|
+
<Canvas
|
|
356
|
+
projectId={projectId}
|
|
357
|
+
bind:sessionId
|
|
358
|
+
bind:sessionInfo
|
|
359
|
+
bind:deviceSize
|
|
360
|
+
bind:rotation
|
|
361
|
+
bind:canvasAPI
|
|
362
|
+
bind:lastFrameData
|
|
363
|
+
bind:isConnected
|
|
364
|
+
bind:isStreamReady
|
|
365
|
+
bind:isNavigating
|
|
366
|
+
bind:isReconnecting
|
|
367
|
+
onInteraction={handleCanvasInteraction}
|
|
368
|
+
onCursorUpdate={handleCursorUpdate}
|
|
369
|
+
onFrameUpdate={handleFrameUpdate}
|
|
370
|
+
onRequestScreencastRefresh={handleScreencastRefresh}
|
|
371
|
+
/>
|
|
372
|
+
</div>
|
|
373
|
+
{/if}
|
|
374
|
+
|
|
375
|
+
<!-- Solid Loading Overlay: Initial load states (launching, no session, waiting for first frame) -->
|
|
376
|
+
{#if showSolidOverlay}
|
|
377
|
+
<div
|
|
378
|
+
class="absolute inset-0 bg-white dark:bg-slate-800 flex items-center justify-center z-10"
|
|
379
|
+
>
|
|
380
|
+
<div class="flex flex-col items-center gap-2">
|
|
381
|
+
<Icon name="lucide:loader-circle" class="w-8 h-8 animate-spin text-violet-600" />
|
|
382
|
+
<div class="text-slate-500 text-center">
|
|
383
|
+
<div class="text-sm">Loading preview...</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
{/if}
|
|
388
|
+
|
|
389
|
+
<!-- Navigation Overlay: Semi-transparent overlay during navigation/reconnect (shows last frame behind) -->
|
|
390
|
+
{#if showNavigationOverlay}
|
|
391
|
+
<div
|
|
392
|
+
class="absolute inset-0 bg-white/60 dark:bg-slate-800/60 backdrop-blur-[2px] flex items-center justify-center z-10"
|
|
393
|
+
>
|
|
394
|
+
<div class="flex flex-col items-center gap-2">
|
|
395
|
+
<Icon name="lucide:loader-circle" class="w-8 h-8 animate-spin text-violet-600" />
|
|
396
|
+
<div class="text-slate-600 dark:text-slate-300 text-center">
|
|
397
|
+
<div class="text-sm font-medium">Loading preview...</div>
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
</div>
|
|
401
|
+
{/if}
|
|
402
|
+
|
|
403
|
+
<!-- MCP Control Overlay - blocks user interaction -->
|
|
404
|
+
{#if isMcpControlled && isStreamReady}
|
|
405
|
+
<div
|
|
406
|
+
class="absolute inset-0 z-20 pointer-events-auto cursor-not-allowed"
|
|
407
|
+
role="presentation"
|
|
408
|
+
aria-hidden="true"
|
|
409
|
+
style="background: transparent;"
|
|
410
|
+
onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}
|
|
411
|
+
onmousedown={(e) => { e.preventDefault(); e.stopPropagation(); }}
|
|
412
|
+
onmousemove={(e) => { e.preventDefault(); e.stopPropagation(); }}
|
|
413
|
+
onkeydown={(e) => { e.preventDefault(); e.stopPropagation(); }}
|
|
414
|
+
>
|
|
415
|
+
<!-- MCP Control Badge at top -->
|
|
416
|
+
<!-- <div class="absolute top-2 left-1/2 -translate-x-1/2 bg-amber-500/90 text-black text-xs font-semibold px-3 py-1.5 rounded-full flex items-center gap-1.5 shadow-lg">
|
|
417
|
+
<Icon name="lucide:bot" class="w-3.5 h-3.5" />
|
|
418
|
+
<span>MCP Controlling</span>
|
|
419
|
+
</div> -->
|
|
420
|
+
</div>
|
|
421
|
+
{/if}
|
|
422
|
+
</div>
|
|
423
|
+
{:else}
|
|
424
|
+
<div
|
|
425
|
+
class="text-center text-slate-500 absolute"
|
|
426
|
+
in:scale={{ duration: 250, easing: cubicOut, start: 0.95 }}
|
|
427
|
+
out:scale={{ duration: 200, easing: cubicOut, start: 0.95 }}
|
|
428
|
+
>
|
|
429
|
+
<Icon name="lucide:monitor" class="w-16 h-16 mx-auto mb-4 opacity-20" />
|
|
430
|
+
<p class="text-lg font-medium mb-2">Real Browser Preview</p>
|
|
431
|
+
<p class="text-sm">Enter a URL to preview your web application</p>
|
|
432
|
+
</div>
|
|
433
|
+
{/if}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<style>
|
|
437
|
+
/* MCP Control Border Animation */
|
|
438
|
+
.mcp-control-border {
|
|
439
|
+
animation: mcp-border-pulse 2s ease-in-out infinite;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
@keyframes mcp-border-pulse {
|
|
443
|
+
0%, 100% {
|
|
444
|
+
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.5), 0 0 20px rgba(245, 158, 11, 0.3);
|
|
445
|
+
}
|
|
446
|
+
50% {
|
|
447
|
+
box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.8), 0 0 30px rgba(245, 158, 11, 0.5);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
</style>
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { BrowserContextMenuInfo, BrowserContextMenuItem } from '$frontend/lib/types/native-ui';
|
|
3
|
+
import { onMount } from 'svelte';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
menuInfo = $bindable<BrowserContextMenuInfo | null>(null),
|
|
7
|
+
onSelectItem = $bindable<(itemId: string) => void>(() => {}),
|
|
8
|
+
onClose = $bindable<() => void>(() => {})
|
|
9
|
+
} = $props();
|
|
10
|
+
|
|
11
|
+
let menuElement = $state<HTMLDivElement | undefined>(undefined);
|
|
12
|
+
let highlightedItemId = $state<string | null>(null);
|
|
13
|
+
|
|
14
|
+
// Position the context menu
|
|
15
|
+
function getMenuPosition() {
|
|
16
|
+
if (!menuInfo) return { top: '0px', left: '0px' };
|
|
17
|
+
|
|
18
|
+
let x = menuInfo.x;
|
|
19
|
+
let y = menuInfo.y;
|
|
20
|
+
|
|
21
|
+
// Adjust if menu would go off screen
|
|
22
|
+
if (menuElement) {
|
|
23
|
+
const menuRect = menuElement.getBoundingClientRect();
|
|
24
|
+
const windowWidth = window.innerWidth;
|
|
25
|
+
const windowHeight = window.innerHeight;
|
|
26
|
+
|
|
27
|
+
if (x + menuRect.width > windowWidth) {
|
|
28
|
+
x = windowWidth - menuRect.width - 10;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (y + menuRect.height > windowHeight) {
|
|
32
|
+
y = windowHeight - menuRect.height - 10;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
top: `${y}px`,
|
|
38
|
+
left: `${x}px`
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handle item click
|
|
43
|
+
function handleItemClick(item: BrowserContextMenuItem) {
|
|
44
|
+
if (!item.enabled || item.type === 'separator') return;
|
|
45
|
+
onSelectItem(item.id);
|
|
46
|
+
onClose();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle keyboard navigation
|
|
50
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
51
|
+
if (!menuInfo) return;
|
|
52
|
+
|
|
53
|
+
const enabledItems = menuInfo.items.filter(
|
|
54
|
+
(item: BrowserContextMenuItem) => item.enabled && item.type !== 'separator'
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
switch (event.key) {
|
|
58
|
+
case 'ArrowDown': {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
const currentIndex = enabledItems.findIndex((item: BrowserContextMenuItem) => item.id === highlightedItemId);
|
|
61
|
+
const nextIndex = (currentIndex + 1) % enabledItems.length;
|
|
62
|
+
highlightedItemId = enabledItems[nextIndex]?.id || null;
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
case 'ArrowUp': {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
const currentIndex = enabledItems.findIndex((item: BrowserContextMenuItem) => item.id === highlightedItemId);
|
|
69
|
+
const prevIndex = currentIndex <= 0 ? enabledItems.length - 1 : currentIndex - 1;
|
|
70
|
+
highlightedItemId = enabledItems[prevIndex]?.id || null;
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case 'Enter': {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
const item = enabledItems.find((item: BrowserContextMenuItem) => item.id === highlightedItemId);
|
|
77
|
+
if (item) {
|
|
78
|
+
handleItemClick(item);
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
case 'Escape':
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
onClose();
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Handle click outside to close
|
|
91
|
+
function handleClickOutside(event: MouseEvent) {
|
|
92
|
+
if (menuElement && !menuElement.contains(event.target as Node)) {
|
|
93
|
+
onClose();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Initialize
|
|
98
|
+
$effect(() => {
|
|
99
|
+
if (menuInfo) {
|
|
100
|
+
// Focus menu for keyboard navigation
|
|
101
|
+
if (menuElement) {
|
|
102
|
+
menuElement.focus();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add click outside listener
|
|
106
|
+
setTimeout(() => {
|
|
107
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
108
|
+
}, 0);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return () => {
|
|
112
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const position = $derived(getMenuPosition());
|
|
117
|
+
</script>
|
|
118
|
+
|
|
119
|
+
{#if menuInfo}
|
|
120
|
+
<div
|
|
121
|
+
bind:this={menuElement}
|
|
122
|
+
class="browser-context-menu"
|
|
123
|
+
style="top: {position.top}; left: {position.left};"
|
|
124
|
+
tabindex="-1"
|
|
125
|
+
onkeydown={handleKeydown}
|
|
126
|
+
role="menu"
|
|
127
|
+
aria-label="Context menu"
|
|
128
|
+
>
|
|
129
|
+
{#each menuInfo.items as item (item.id)}
|
|
130
|
+
{#if item.type === 'separator'}
|
|
131
|
+
<div class="context-menu-separator" role="separator"></div>
|
|
132
|
+
{:else}
|
|
133
|
+
<div
|
|
134
|
+
class="context-menu-item"
|
|
135
|
+
class:disabled={!item.enabled}
|
|
136
|
+
class:highlighted={item.id === highlightedItemId}
|
|
137
|
+
role="menuitem"
|
|
138
|
+
tabindex={item.enabled ? 0 : -1}
|
|
139
|
+
aria-disabled={!item.enabled}
|
|
140
|
+
onclick={() => handleItemClick(item)}
|
|
141
|
+
onkeydown={(e) => {
|
|
142
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
handleItemClick(item);
|
|
145
|
+
}
|
|
146
|
+
}}
|
|
147
|
+
onmouseenter={() => {
|
|
148
|
+
if (item.enabled) highlightedItemId = item.id;
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<span class="menu-item-label">{item.label}</span>
|
|
152
|
+
</div>
|
|
153
|
+
{/if}
|
|
154
|
+
{/each}
|
|
155
|
+
</div>
|
|
156
|
+
{/if}
|
|
157
|
+
|
|
158
|
+
<style>
|
|
159
|
+
.browser-context-menu {
|
|
160
|
+
position: fixed;
|
|
161
|
+
z-index: 999999;
|
|
162
|
+
min-width: 200px;
|
|
163
|
+
background: white;
|
|
164
|
+
border: 1px solid #ccc;
|
|
165
|
+
border-radius: 4px;
|
|
166
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
|
|
167
|
+
padding: 4px 0;
|
|
168
|
+
outline: none;
|
|
169
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.context-menu-item {
|
|
173
|
+
padding: 8px 24px 8px 16px;
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
user-select: none;
|
|
176
|
+
font-size: 14px;
|
|
177
|
+
line-height: 1.5;
|
|
178
|
+
color: #333;
|
|
179
|
+
display: flex;
|
|
180
|
+
align-items: center;
|
|
181
|
+
transition: background-color 0.1s ease;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.context-menu-item:hover:not(.disabled) {
|
|
185
|
+
background: #f0f0f0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.context-menu-item.highlighted:not(.disabled) {
|
|
189
|
+
background: #e6f7ff;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.context-menu-item.disabled {
|
|
193
|
+
color: #999;
|
|
194
|
+
cursor: not-allowed;
|
|
195
|
+
opacity: 0.5;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.context-menu-separator {
|
|
199
|
+
height: 1px;
|
|
200
|
+
background: #e0e0e0;
|
|
201
|
+
margin: 4px 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.menu-item-label {
|
|
205
|
+
flex: 1;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* Dark mode support */
|
|
209
|
+
@media (prefers-color-scheme: dark) {
|
|
210
|
+
.browser-context-menu {
|
|
211
|
+
background: #2d2d2d;
|
|
212
|
+
border-color: #444;
|
|
213
|
+
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.4);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.context-menu-item {
|
|
217
|
+
color: #e0e0e0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.context-menu-item:hover:not(.disabled) {
|
|
221
|
+
background: #3a3a3a;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.context-menu-item.highlighted:not(.disabled) {
|
|
225
|
+
background: #003d66;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.context-menu-item.disabled {
|
|
229
|
+
color: #666;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.context-menu-separator {
|
|
233
|
+
background: #444;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
</style>
|