@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,197 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { userStore } from '$frontend/lib/stores/features/user.svelte';
|
|
3
|
+
import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
|
|
4
|
+
import Icon from '../../common/Icon.svelte';
|
|
5
|
+
import { debug } from '$shared/utils/logger';
|
|
6
|
+
|
|
7
|
+
// State
|
|
8
|
+
let userNameInput = $state('');
|
|
9
|
+
let isEditing = $state(false);
|
|
10
|
+
let isSaving = $state(false);
|
|
11
|
+
|
|
12
|
+
// Update input when user changes
|
|
13
|
+
$effect(() => {
|
|
14
|
+
if (userStore.currentUser?.name) {
|
|
15
|
+
userNameInput = userStore.currentUser.name;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Handle save user name
|
|
20
|
+
async function saveUserName() {
|
|
21
|
+
if (!userNameInput.trim()) {
|
|
22
|
+
addNotification({
|
|
23
|
+
type: 'error',
|
|
24
|
+
title: 'Validation Error',
|
|
25
|
+
message: 'Name cannot be empty'
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
isSaving = true;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const success = await userStore.updateName(userNameInput.trim());
|
|
34
|
+
|
|
35
|
+
if (success) {
|
|
36
|
+
isEditing = false;
|
|
37
|
+
addNotification({
|
|
38
|
+
type: 'success',
|
|
39
|
+
title: 'Updated',
|
|
40
|
+
message: 'Display name updated successfully'
|
|
41
|
+
});
|
|
42
|
+
} else {
|
|
43
|
+
addNotification({
|
|
44
|
+
type: 'error',
|
|
45
|
+
title: 'Error',
|
|
46
|
+
message: 'Failed to update user name'
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
debug.error('settings', 'Error updating user name:', error);
|
|
51
|
+
addNotification({
|
|
52
|
+
type: 'error',
|
|
53
|
+
title: 'Error',
|
|
54
|
+
message: 'An error occurred while updating user name'
|
|
55
|
+
});
|
|
56
|
+
} finally {
|
|
57
|
+
isSaving = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle cancel edit
|
|
62
|
+
function cancelEdit() {
|
|
63
|
+
userNameInput = userStore.currentUser?.name || '';
|
|
64
|
+
isEditing = false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle start edit
|
|
68
|
+
function startEdit() {
|
|
69
|
+
isEditing = true;
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<div class="py-1">
|
|
74
|
+
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">User Profile</h3>
|
|
75
|
+
<p class="text-sm text-slate-600 dark:text-slate-500 mb-5">
|
|
76
|
+
Manage your identity and display preferences
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
{#if !userStore.currentUser}
|
|
80
|
+
<div class="flex items-center justify-center gap-3 py-10 text-slate-600 dark:text-slate-500 text-sm">
|
|
81
|
+
<div
|
|
82
|
+
class="w-5 h-5 border-2 border-violet-500/20 border-t-violet-600 rounded-full animate-spin"
|
|
83
|
+
></div>
|
|
84
|
+
<span>Loading user settings...</span>
|
|
85
|
+
</div>
|
|
86
|
+
{:else}
|
|
87
|
+
<div class="flex flex-col gap-4">
|
|
88
|
+
<!-- Current User Card -->
|
|
89
|
+
<div
|
|
90
|
+
class="flex items-center gap-3.5 p-4.5 bg-gradient-to-br from-violet-500/10 to-purple-500/5 dark:from-violet-500/10 dark:to-purple-500/8 border border-violet-500/20 rounded-xl"
|
|
91
|
+
>
|
|
92
|
+
<div
|
|
93
|
+
class="flex items-center justify-center w-12 h-12 rounded-xl text-lg font-bold text-white shrink-0"
|
|
94
|
+
style="background-color: {userStore.currentUser?.color || '#7c3aed'}"
|
|
95
|
+
>
|
|
96
|
+
{userStore.currentUser?.avatar || 'U'}
|
|
97
|
+
</div>
|
|
98
|
+
<div class="flex-1 min-w-0">
|
|
99
|
+
<div class="text-base font-semibold text-slate-900 dark:text-slate-100 mb-0.5">
|
|
100
|
+
{userStore.currentUser?.name || 'Anonymous User'}
|
|
101
|
+
</div>
|
|
102
|
+
<div class="text-xs text-slate-600 dark:text-slate-500">Anonymous user identity</div>
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
class="flex items-center gap-1.5 py-1.5 px-3 bg-emerald-500/15 rounded-full text-xs font-medium text-emerald-500"
|
|
106
|
+
>
|
|
107
|
+
<span class="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-pulse"></span>
|
|
108
|
+
<span>Active</span>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<!-- Edit Display Name -->
|
|
113
|
+
<div
|
|
114
|
+
class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl"
|
|
115
|
+
>
|
|
116
|
+
<div
|
|
117
|
+
class="flex items-center gap-2 text-sm font-semibold text-slate-500 mb-3"
|
|
118
|
+
>
|
|
119
|
+
<Icon name="lucide:pencil" class="w-4 h-4 opacity-70" />
|
|
120
|
+
<span>Display Name</span>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{#if isEditing}
|
|
124
|
+
<div class="flex flex-col gap-3">
|
|
125
|
+
<input
|
|
126
|
+
type="text"
|
|
127
|
+
bind:value={userNameInput}
|
|
128
|
+
placeholder="Enter your display name"
|
|
129
|
+
class="w-full py-3 px-3.5 bg-slate-50 dark:bg-slate-900/80 border border-violet-500/20 rounded-lg text-slate-900 dark:text-slate-100 text-sm outline-none transition-all duration-150 placeholder:text-slate-600 dark:placeholder:text-slate-500 focus:border-violet-600 focus:shadow-[0_0_0_3px_rgba(124,58,237,0.1)]"
|
|
130
|
+
/>
|
|
131
|
+
<div class="flex gap-2.5">
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
class="flex items-center justify-center gap-1.5 py-2.5 px-4 border-none rounded-lg text-sm font-semibold cursor-pointer transition-all duration-150 bg-gradient-to-br from-violet-600 to-purple-600 text-white hover:shadow-violet-500/30 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
135
|
+
onclick={saveUserName}
|
|
136
|
+
disabled={!userNameInput.trim() || isSaving}
|
|
137
|
+
>
|
|
138
|
+
{#if isSaving}
|
|
139
|
+
<div
|
|
140
|
+
class="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin"
|
|
141
|
+
></div>
|
|
142
|
+
Saving...
|
|
143
|
+
{:else}
|
|
144
|
+
<Icon name="lucide:check" class="w-4 h-4" />
|
|
145
|
+
Save
|
|
146
|
+
{/if}
|
|
147
|
+
</button>
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
class="flex items-center justify-center gap-1.5 py-2.5 px-4 border-none rounded-lg text-sm font-semibold cursor-pointer transition-all duration-150 bg-slate-100 dark:bg-slate-600/20 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-600/30 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
151
|
+
onclick={cancelEdit}
|
|
152
|
+
disabled={isSaving}
|
|
153
|
+
>
|
|
154
|
+
Cancel
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
{:else}
|
|
159
|
+
<div class="flex items-center justify-between gap-3">
|
|
160
|
+
<div class="text-sm text-slate-900 dark:text-slate-100">
|
|
161
|
+
{userStore.currentUser?.name || 'Not set'}
|
|
162
|
+
</div>
|
|
163
|
+
<button
|
|
164
|
+
type="button"
|
|
165
|
+
class="flex items-center gap-1.5 py-2 px-3.5 bg-transparent border border-violet-500/20 dark:border-violet-500/30 rounded-lg text-sm font-semibold text-violet-600 dark:text-violet-400 cursor-pointer transition-all duration-150 hover:bg-violet-500/10"
|
|
166
|
+
onclick={startEdit}
|
|
167
|
+
>
|
|
168
|
+
<Icon name="lucide:pencil" class="w-4 h-4" />
|
|
169
|
+
Edit
|
|
170
|
+
</button>
|
|
171
|
+
</div>
|
|
172
|
+
{/if}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<!-- User ID -->
|
|
176
|
+
<div
|
|
177
|
+
class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl"
|
|
178
|
+
>
|
|
179
|
+
<div
|
|
180
|
+
class="flex items-center gap-2 text-sm font-semibold text-slate-500 mb-3"
|
|
181
|
+
>
|
|
182
|
+
<Icon name="lucide:fingerprint" class="w-4 h-4 opacity-70" />
|
|
183
|
+
<span>User ID</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="flex flex-col gap-1.5">
|
|
186
|
+
<code
|
|
187
|
+
class="py-2.5 px-3.5 bg-slate-50 dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-lg font-mono text-xs text-slate-500 break-all"
|
|
188
|
+
>{userStore.currentUser?.id || 'Not available'}</code
|
|
189
|
+
>
|
|
190
|
+
<span class="text-xs text-slate-600 dark:text-slate-500"
|
|
191
|
+
>Unique identifier for this session</span
|
|
192
|
+
>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{/if}
|
|
197
|
+
</div>
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Pure XTerminal Integration - Clean Terminal Component
|
|
3
|
+
All terminal functionality handled by xterm.js
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { terminalStore } from '$frontend/lib/stores/features/terminal.svelte';
|
|
7
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
8
|
+
import { getShortcutLabels } from '$frontend/lib/utils/platform';
|
|
9
|
+
import TerminalTabs from './TerminalTabs.svelte';
|
|
10
|
+
import LoadingSpinner from '../common/LoadingSpinner.svelte';
|
|
11
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
12
|
+
import XTerm from '$frontend/lib/components/common/xterm/XTerm.svelte';
|
|
13
|
+
|
|
14
|
+
// Project-aware state
|
|
15
|
+
const hasActiveProject = $derived(projectState.currentProject !== null);
|
|
16
|
+
const projectPath = $derived(projectState.currentProject?.path || '');
|
|
17
|
+
const projectId = $derived(projectState.currentProject?.id || '');
|
|
18
|
+
|
|
19
|
+
// Terminal state from store
|
|
20
|
+
const activeSession = $derived(terminalStore.activeSession);
|
|
21
|
+
// Use session-specific execution state instead of global
|
|
22
|
+
const isExecuting = $derived(activeSession ? terminalStore.isSessionExecuting(activeSession.id) : false);
|
|
23
|
+
|
|
24
|
+
// XTerminal reference and cancellation state
|
|
25
|
+
let xterminalRef = $state<{ scrollToBottom: () => void; clear: () => void; writeData: (data: string) => void }>();
|
|
26
|
+
let isCancelling = $state(false);
|
|
27
|
+
let terminalContainer: HTMLDivElement | undefined = $state();
|
|
28
|
+
|
|
29
|
+
// Get platform-specific shortcut labels
|
|
30
|
+
const shortcuts = $derived(getShortcutLabels());
|
|
31
|
+
|
|
32
|
+
// Initialize terminal only once when component mounts
|
|
33
|
+
let isInitialized = false;
|
|
34
|
+
$effect(() => {
|
|
35
|
+
if (hasActiveProject && !isInitialized) {
|
|
36
|
+
// Wait for background service to complete restoration
|
|
37
|
+
const checkAndInitialize = async () => {
|
|
38
|
+
if (typeof window !== 'undefined') {
|
|
39
|
+
try {
|
|
40
|
+
const { backgroundTerminalService } = await import('$frontend/lib/services/terminal/background');
|
|
41
|
+
const { terminalProjectManager } = await import('$frontend/lib/services/terminal');
|
|
42
|
+
|
|
43
|
+
// Initialize terminalProjectManager first
|
|
44
|
+
terminalProjectManager.initialize();
|
|
45
|
+
|
|
46
|
+
// First ensure background service is initialized
|
|
47
|
+
await backgroundTerminalService.initialize();
|
|
48
|
+
|
|
49
|
+
// Wait for restoration to complete (max 2 seconds)
|
|
50
|
+
let attempts = 0;
|
|
51
|
+
while (!backgroundTerminalService.isRestorationDone() && attempts < 20) {
|
|
52
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
53
|
+
attempts++;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Now safe to initialize terminal store
|
|
57
|
+
terminalStore.initialize(hasActiveProject, projectPath);
|
|
58
|
+
|
|
59
|
+
// Ensure project context is set up in terminalProjectManager
|
|
60
|
+
if (projectId && projectPath) {
|
|
61
|
+
await terminalProjectManager.switchToProject(projectId, projectPath);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
isInitialized = true;
|
|
65
|
+
} catch (error) {
|
|
66
|
+
// Failed to initialize terminal services
|
|
67
|
+
// If background service fails, just initialize normally
|
|
68
|
+
terminalStore.initialize(hasActiveProject, projectPath);
|
|
69
|
+
isInitialized = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
checkAndInitialize();
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Setup event listeners for Ctrl+C handling
|
|
79
|
+
$effect(() => {
|
|
80
|
+
if (typeof window !== 'undefined') {
|
|
81
|
+
// Use capture phase to intercept Ctrl+C before XTerm
|
|
82
|
+
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
|
83
|
+
// Check if terminal is focused
|
|
84
|
+
const terminalElement = document.querySelector('.xterm');
|
|
85
|
+
const isTerminalFocused = terminalElement && terminalElement.contains(document.activeElement);
|
|
86
|
+
|
|
87
|
+
if (isTerminalFocused && e.ctrlKey && e.key === 'c' && isExecuting && !isCancelling) {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
e.stopImmediatePropagation();
|
|
91
|
+
// Global Ctrl+C intercepted during execution
|
|
92
|
+
handleCancel();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Add listener with capture to intercept before bubbling
|
|
97
|
+
document.addEventListener('keydown', handleGlobalKeyDown, true);
|
|
98
|
+
|
|
99
|
+
// Also listen for custom event from XTerm as backup
|
|
100
|
+
const handleTerminalCancel = (event: Event) => {
|
|
101
|
+
const customEvent = event as CustomEvent;
|
|
102
|
+
// Received terminal-cancel event
|
|
103
|
+
if (isExecuting && !isCancelling) {
|
|
104
|
+
handleCancel();
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (terminalContainer) {
|
|
109
|
+
terminalContainer.addEventListener('terminal-cancel', handleTerminalCancel);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
document.removeEventListener('keydown', handleGlobalKeyDown, true);
|
|
114
|
+
terminalContainer?.removeEventListener('terminal-cancel', handleTerminalCancel);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Connect to PTY session (initial auto-connect)
|
|
120
|
+
async function connectToPtySession(command: string, terminalSize?: { cols: number; rows: number }) {
|
|
121
|
+
if (!hasActiveProject || !projectId) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Only connect, ignore command parameter (kept for XTerm compatibility)
|
|
126
|
+
await terminalStore.connectToSession(hasActiveProject ? projectPath : undefined, projectId, terminalSize);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Handle keyboard shortcuts
|
|
130
|
+
function handleKeyDown(e: KeyboardEvent) {
|
|
131
|
+
// Handle Ctrl+C to cancel running command
|
|
132
|
+
if (e.ctrlKey && e.key === 'c' && isExecuting) {
|
|
133
|
+
// Ctrl+C pressed - cancelling command
|
|
134
|
+
handleCancel();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Send Ctrl+C signal to terminal
|
|
139
|
+
// This function always sends the interrupt signal regardless of execution state
|
|
140
|
+
// It's useful as a utility shortcut, especially for mobile accessibility
|
|
141
|
+
async function handleCancel() {
|
|
142
|
+
// UI: Cancel button clicked - sending Ctrl+C signal
|
|
143
|
+
isCancelling = true;
|
|
144
|
+
|
|
145
|
+
if (!activeSession) {
|
|
146
|
+
// No active session to send signal
|
|
147
|
+
isCancelling = false;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
// Always attempt to send cancel signal to the active session
|
|
153
|
+
// This sends Ctrl+C (\x03) directly to the PTY process
|
|
154
|
+
await terminalStore.cancelCommand();
|
|
155
|
+
// Ctrl+C signal sent to terminal
|
|
156
|
+
} catch (error) {
|
|
157
|
+
// Error sending Ctrl+C signal
|
|
158
|
+
} finally {
|
|
159
|
+
isCancelling = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Clear terminal session
|
|
164
|
+
function handleClear() {
|
|
165
|
+
if (activeSession) {
|
|
166
|
+
// Clear the terminal store session
|
|
167
|
+
terminalStore.clearSession(activeSession.id);
|
|
168
|
+
|
|
169
|
+
// Also immediately clear the XTerm display
|
|
170
|
+
if (xterminalRef) {
|
|
171
|
+
xterminalRef.clear();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Create new terminal session
|
|
177
|
+
async function handleCloseSession(sessionId: string) {
|
|
178
|
+
// Check if this is the last session before closing
|
|
179
|
+
const isLastSession = terminalStore.sessions.length <= 1;
|
|
180
|
+
|
|
181
|
+
// Close the session
|
|
182
|
+
const closed = await terminalStore.closeSession(sessionId);
|
|
183
|
+
|
|
184
|
+
// If it was the last session and it was closed successfully, create a new one
|
|
185
|
+
if (closed && isLastSession) {
|
|
186
|
+
await handleNewSession();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handleNewSession() {
|
|
191
|
+
if (!hasActiveProject || !projectId || !projectPath) {
|
|
192
|
+
// Cannot create new terminal: missing project information
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Use terminalProjectManager to create new session for current project
|
|
197
|
+
try {
|
|
198
|
+
const { terminalProjectManager } = await import('$frontend/lib/services/terminal');
|
|
199
|
+
|
|
200
|
+
// Pass project ID and path directly to ensure it works even if currentProjectId isn't set
|
|
201
|
+
const newSessionId = terminalProjectManager.addTerminalToCurrentProject(projectId, projectPath);
|
|
202
|
+
|
|
203
|
+
if (!newSessionId) {
|
|
204
|
+
// If it still fails, try switching to project first then adding
|
|
205
|
+
// Failed to add terminal, switching to project first
|
|
206
|
+
await terminalProjectManager.switchToProject(projectId, projectPath);
|
|
207
|
+
const retrySessionId = terminalProjectManager.addTerminalToCurrentProject(projectId, projectPath);
|
|
208
|
+
|
|
209
|
+
if (!retrySessionId) {
|
|
210
|
+
// Final fallback
|
|
211
|
+
// Using fallback terminal creation
|
|
212
|
+
terminalStore.createNewSession(projectPath, projectPath, projectId);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// Failed to create new terminal session
|
|
217
|
+
// Fallback to direct creation with projectId for proper isolation
|
|
218
|
+
terminalStore.createNewSession(projectPath, projectPath, projectId);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Format directory path for display in header
|
|
223
|
+
function formatDirectory(dir: string): string {
|
|
224
|
+
if (!dir || typeof dir !== 'string') return '~';
|
|
225
|
+
|
|
226
|
+
// Convert backslashes to forward slashes for consistent display
|
|
227
|
+
const normalizedDir = dir.replace(/\\/g, '/');
|
|
228
|
+
|
|
229
|
+
// For long paths, shorten them
|
|
230
|
+
const maxLength = 50;
|
|
231
|
+
if (normalizedDir.length > maxLength) {
|
|
232
|
+
const parts = normalizedDir.split('/');
|
|
233
|
+
if (parts.length > 3) {
|
|
234
|
+
return parts[0] + '/.../' + parts.slice(-1).join('/');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return normalizedDir;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Export actions and state for parent components
|
|
242
|
+
export const terminalActions = {
|
|
243
|
+
handleClear,
|
|
244
|
+
handleNewSession,
|
|
245
|
+
handleCancel,
|
|
246
|
+
getSessions: () => terminalStore.sessions,
|
|
247
|
+
getActiveSession: () => activeSession,
|
|
248
|
+
isExecuting: () => isExecuting,
|
|
249
|
+
isCancelling: () => isCancelling
|
|
250
|
+
};
|
|
251
|
+
</script>
|
|
252
|
+
|
|
253
|
+
<!-- Terminal container -->
|
|
254
|
+
<div class="h-full bg-slate-50 dark:bg-slate-950 flex flex-col overflow-hidden min-h-0"
|
|
255
|
+
onkeydown={handleKeyDown}
|
|
256
|
+
tabindex="-1"
|
|
257
|
+
role="application"
|
|
258
|
+
aria-label="Terminal application">
|
|
259
|
+
|
|
260
|
+
<!-- Terminal Header with Tabs -->
|
|
261
|
+
<div class="flex-shrink-0 px-3 py-2.5 bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
|
|
262
|
+
<!-- Terminal Tabs -->
|
|
263
|
+
<TerminalTabs
|
|
264
|
+
sessions={terminalStore.sessions}
|
|
265
|
+
activeSessionId={terminalStore.activeSessionId}
|
|
266
|
+
onSwitchSession={(sessionId) => terminalStore.switchToSession(sessionId)}
|
|
267
|
+
onCloseSession={handleCloseSession}
|
|
268
|
+
onNewSession={handleNewSession}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<!-- Pure XTerminal integration -->
|
|
273
|
+
{#if activeSession}
|
|
274
|
+
<div class="flex-1 relative min-h-0 overflow-hidden font-mono" bind:this={terminalContainer}>
|
|
275
|
+
<XTerm
|
|
276
|
+
bind:this={xterminalRef}
|
|
277
|
+
session={activeSession}
|
|
278
|
+
hasActiveProject={hasActiveProject}
|
|
279
|
+
projectPath={projectPath}
|
|
280
|
+
isExecuting={isExecuting}
|
|
281
|
+
onExecuteCommand={connectToPtySession}
|
|
282
|
+
onClearSession={() => handleClear()}
|
|
283
|
+
class="absolute inset-0"
|
|
284
|
+
/>
|
|
285
|
+
</div>
|
|
286
|
+
{:else}
|
|
287
|
+
<!-- No active session -->
|
|
288
|
+
<div class="flex-1 flex items-center justify-center font-mono">
|
|
289
|
+
<div class="text-center text-slate-600 dark:text-slate-400">
|
|
290
|
+
<p>No terminal sessions available</p>
|
|
291
|
+
<button
|
|
292
|
+
class="mt-2 px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white rounded-md transition-colors"
|
|
293
|
+
onclick={handleNewSession}
|
|
294
|
+
>
|
|
295
|
+
Create New Terminal
|
|
296
|
+
</button>
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
{/if}
|
|
300
|
+
|
|
301
|
+
<!-- Terminal status bar -->
|
|
302
|
+
<div class="flex-shrink-0 px-2 py-0.5 bg-slate-100 dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 text-3xs text-slate-500 dark:text-slate-500 font-mono">
|
|
303
|
+
<div class="flex items-center justify-between">
|
|
304
|
+
<div class="flex items-center space-x-3">
|
|
305
|
+
<span class="hidden sm:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">↑↓</kbd> History</span>
|
|
306
|
+
<span class="hidden md:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">Ctrl+L</kbd> Clear</span>
|
|
307
|
+
<span class="hidden sm:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">{shortcuts.cancel}</kbd> Interrupt
|
|
308
|
+
{#if isCancelling}
|
|
309
|
+
<span class="animate-pulse ml-1">(cancelling...)</span>
|
|
310
|
+
{/if}
|
|
311
|
+
</span>
|
|
312
|
+
</div>
|
|
313
|
+
<div class="flex items-center space-x-1.5">
|
|
314
|
+
{#if hasActiveProject}
|
|
315
|
+
<span class="text-emerald-500 text-xs">●</span>
|
|
316
|
+
<span class="hidden sm:inline">Ready</span>
|
|
317
|
+
{:else}
|
|
318
|
+
<span class="text-amber-500 text-xs">●</span>
|
|
319
|
+
<span class="hidden sm:inline">No Project</span>
|
|
320
|
+
{/if}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<style>
|
|
327
|
+
/* Terminal cursor animation */
|
|
328
|
+
@keyframes terminal-cursor {
|
|
329
|
+
0%, 50% { background-color: #22c55e; }
|
|
330
|
+
51%, 100% { background-color: transparent; }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
:global(.animate-terminal-cursor) {
|
|
334
|
+
animation: terminal-cursor 1s infinite;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/* Terminal scrollbar styling */
|
|
338
|
+
:global(.terminal-scrollbar) {
|
|
339
|
+
scrollbar-width: thin;
|
|
340
|
+
scrollbar-color: rgb(148 163 184 / 0.3) transparent;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
:global(.terminal-scrollbar::-webkit-scrollbar) {
|
|
344
|
+
width: 6px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
:global(.terminal-scrollbar::-webkit-scrollbar-track) {
|
|
348
|
+
background: transparent;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
:global(.terminal-scrollbar::-webkit-scrollbar-thumb) {
|
|
352
|
+
background-color: rgb(148 163 184 / 0.3);
|
|
353
|
+
border-radius: 3px;
|
|
354
|
+
transition: background-color 0.2s ease;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
:global(.dark .terminal-scrollbar::-webkit-scrollbar-thumb) {
|
|
358
|
+
background-color: rgb(71 85 105 / 0.3);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
:global(.terminal-scrollbar::-webkit-scrollbar-thumb:hover) {
|
|
362
|
+
background-color: rgb(148 163 184 / 0.6);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
:global(.dark .terminal-scrollbar::-webkit-scrollbar-thumb:hover) {
|
|
366
|
+
background-color: rgb(71 85 105 / 0.6);
|
|
367
|
+
}
|
|
368
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Terminal Tabs Component
|
|
3
|
+
Manages terminal session tabs with close and new tab functionality
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import type { TerminalSession } from '$shared/types/terminal';
|
|
7
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
sessions = [],
|
|
11
|
+
activeSessionId,
|
|
12
|
+
onSwitchSession,
|
|
13
|
+
onCloseSession,
|
|
14
|
+
onNewSession
|
|
15
|
+
}: {
|
|
16
|
+
sessions: TerminalSession[];
|
|
17
|
+
activeSessionId: string | null;
|
|
18
|
+
onSwitchSession?: (sessionId: string) => void;
|
|
19
|
+
onCloseSession?: (sessionId: string) => void;
|
|
20
|
+
onNewSession?: () => void;
|
|
21
|
+
} = $props();
|
|
22
|
+
|
|
23
|
+
// Check for duplicate sessions (for debugging)
|
|
24
|
+
$effect(() => {
|
|
25
|
+
const ids = sessions.map(s => s.id);
|
|
26
|
+
const uniqueIds = new Set(ids);
|
|
27
|
+
if (ids.length !== uniqueIds.size) {
|
|
28
|
+
// Duplicate session IDs detected in TerminalTabs
|
|
29
|
+
// Sessions:
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- Compact Terminal Tabs -->
|
|
35
|
+
<div class="flex items-center gap-1.5 overflow-x-auto flex-1">
|
|
36
|
+
{#each sessions as session (session.id)}
|
|
37
|
+
<div
|
|
38
|
+
class="group relative flex items-center gap-2 pl-3 pr-2 py-1.5 border border-slate-200 dark:border-slate-700 rounded-lg transition-all duration-200 min-w-0 max-w-xs cursor-pointer
|
|
39
|
+
{session.isActive
|
|
40
|
+
? 'bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-slate-100'
|
|
41
|
+
: 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700'}"
|
|
42
|
+
onclick={() => onSwitchSession?.(session.id)}
|
|
43
|
+
role="tab"
|
|
44
|
+
tabindex="0"
|
|
45
|
+
onkeydown={(e) => {
|
|
46
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
onSwitchSession?.(session.id);
|
|
49
|
+
}
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
<!-- Terminal icon -->
|
|
53
|
+
<Icon name="lucide:terminal" class="w-3 h-3 flex-shrink-0" />
|
|
54
|
+
|
|
55
|
+
<!-- Session name -->
|
|
56
|
+
<span class="text-xs font-medium truncate max-w-37.5">
|
|
57
|
+
{session.name}
|
|
58
|
+
</span>
|
|
59
|
+
|
|
60
|
+
<!-- Close button -->
|
|
61
|
+
<button
|
|
62
|
+
onclick={(e) => {
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
onCloseSession?.(session.id);
|
|
65
|
+
}}
|
|
66
|
+
class="flex hover:bg-slate-300 dark:hover:bg-slate-600 rounded p-0.5 transition-all duration-200 flex-shrink-0"
|
|
67
|
+
title="Close terminal"
|
|
68
|
+
aria-label="Close terminal session"
|
|
69
|
+
>
|
|
70
|
+
<Icon name="lucide:x" class="w-3 h-3" />
|
|
71
|
+
</button>
|
|
72
|
+
</div>
|
|
73
|
+
{/each}
|
|
74
|
+
|
|
75
|
+
<!-- New terminal button -->
|
|
76
|
+
{#if onNewSession}
|
|
77
|
+
<button
|
|
78
|
+
onclick={onNewSession}
|
|
79
|
+
class="flex items-center justify-center w-5 h-5 rounded-md hover:bg-slate-200 dark:hover:bg-slate-700 transition-all duration-200 flex-shrink-0"
|
|
80
|
+
title="New terminal"
|
|
81
|
+
aria-label="New terminal session"
|
|
82
|
+
>
|
|
83
|
+
<Icon name="lucide:plus" class="w-3 h-3" />
|
|
84
|
+
</button>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
87
|
+
|