@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,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
|
+
import type { GitConflictFile } from '$shared/types/git';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
conflictFiles: GitConflictFile[];
|
|
8
|
+
isLoading: boolean;
|
|
9
|
+
onResolve: (filePath: string, resolution: 'ours' | 'theirs' | 'custom', customContent?: string) => void;
|
|
10
|
+
onResolveWithAI: (filePath: string) => void;
|
|
11
|
+
onAbortMerge: () => void;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { isOpen, conflictFiles, isLoading, onResolve, onResolveWithAI, onAbortMerge, onClose }: Props = $props();
|
|
16
|
+
|
|
17
|
+
let selectedFile = $state<GitConflictFile | null>(null);
|
|
18
|
+
|
|
19
|
+
$effect(() => {
|
|
20
|
+
if (conflictFiles.length > 0 && !selectedFile) {
|
|
21
|
+
selectedFile = conflictFiles[0];
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
{#if isOpen}
|
|
27
|
+
<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick={onClose}>
|
|
28
|
+
<div
|
|
29
|
+
class="bg-white dark:bg-slate-800 rounded-lg shadow-xl max-w-4xl w-full mx-4 max-h-[85vh] flex flex-col"
|
|
30
|
+
onclick={(e) => e.stopPropagation()}
|
|
31
|
+
>
|
|
32
|
+
<!-- Header -->
|
|
33
|
+
<div class="flex items-center justify-between px-4 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
34
|
+
<div class="flex items-center gap-2">
|
|
35
|
+
<Icon name="lucide:triangle-alert" class="w-4 h-4 text-orange-500" />
|
|
36
|
+
<h3 class="text-sm font-semibold text-slate-900 dark:text-slate-100">
|
|
37
|
+
Merge Conflicts ({conflictFiles.length} files)
|
|
38
|
+
</h3>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="flex items-center gap-2">
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
class="px-3 py-1.5 text-xs font-medium text-red-600 bg-red-500/10 rounded-md hover:bg-red-500/20 transition-colors cursor-pointer border-none"
|
|
44
|
+
onclick={onAbortMerge}
|
|
45
|
+
>
|
|
46
|
+
Abort Merge
|
|
47
|
+
</button>
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
class="flex items-center justify-center w-7 h-7 rounded-md text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors cursor-pointer bg-transparent border-none"
|
|
51
|
+
onclick={onClose}
|
|
52
|
+
>
|
|
53
|
+
<Icon name="lucide:x" class="w-4 h-4" />
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
{#if isLoading}
|
|
59
|
+
<div class="flex-1 flex items-center justify-center py-12">
|
|
60
|
+
<div class="w-6 h-6 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
61
|
+
</div>
|
|
62
|
+
{:else}
|
|
63
|
+
<div class="flex flex-1 overflow-hidden">
|
|
64
|
+
<!-- File list sidebar -->
|
|
65
|
+
<div class="w-48 border-r border-slate-200 dark:border-slate-700 overflow-y-auto shrink-0">
|
|
66
|
+
{#each conflictFiles as file (file.path)}
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="flex items-center gap-1.5 w-full px-3 py-2 text-left text-xs bg-transparent border-none cursor-pointer transition-colors
|
|
70
|
+
{selectedFile?.path === file.path
|
|
71
|
+
? 'bg-violet-500/10 text-violet-600 font-medium'
|
|
72
|
+
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700/50'}"
|
|
73
|
+
onclick={() => selectedFile = file}
|
|
74
|
+
>
|
|
75
|
+
<Icon name="lucide:file-warning" class="w-3.5 h-3.5 text-orange-500 shrink-0" />
|
|
76
|
+
<span class="truncate">{file.path.split(/[\\/]/).pop()}</span>
|
|
77
|
+
</button>
|
|
78
|
+
{/each}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<!-- Conflict content -->
|
|
82
|
+
<div class="flex-1 flex flex-col overflow-hidden">
|
|
83
|
+
{#if selectedFile}
|
|
84
|
+
<!-- File path -->
|
|
85
|
+
<div class="px-4 py-2 bg-slate-50 dark:bg-slate-800/50 border-b border-slate-200 dark:border-slate-700 text-xs text-slate-600 dark:text-slate-400">
|
|
86
|
+
{selectedFile.path}
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Conflict markers -->
|
|
90
|
+
<div class="flex-1 overflow-y-auto">
|
|
91
|
+
{#each selectedFile.markers as marker, markerIndex}
|
|
92
|
+
<div class="border-b border-slate-200 dark:border-slate-700">
|
|
93
|
+
<!-- Conflict header -->
|
|
94
|
+
<div class="flex items-center gap-2 px-4 py-1.5 bg-orange-500/5 text-3xs font-medium text-orange-600 dark:text-orange-400">
|
|
95
|
+
<Icon name="lucide:triangle-alert" class="w-3 h-3" />
|
|
96
|
+
Conflict #{markerIndex + 1}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<div class="flex">
|
|
100
|
+
<!-- Ours (current) -->
|
|
101
|
+
<div class="w-1/2 border-r border-slate-200 dark:border-slate-700">
|
|
102
|
+
<div class="px-3 py-1 bg-emerald-500/10 text-3xs font-semibold text-emerald-600 dark:text-emerald-400 border-b border-emerald-500/10">
|
|
103
|
+
Current (Ours)
|
|
104
|
+
</div>
|
|
105
|
+
<pre class="px-3 py-2 text-2xs font-mono text-slate-700 dark:text-slate-300 whitespace-pre-wrap bg-emerald-500/5">{marker.ourContent}</pre>
|
|
106
|
+
</div>
|
|
107
|
+
<!-- Theirs (incoming) -->
|
|
108
|
+
<div class="w-1/2">
|
|
109
|
+
<div class="px-3 py-1 bg-blue-500/10 text-3xs font-semibold text-blue-600 dark:text-blue-400 border-b border-blue-500/10">
|
|
110
|
+
Incoming (Theirs)
|
|
111
|
+
</div>
|
|
112
|
+
<pre class="px-3 py-2 text-2xs font-mono text-slate-700 dark:text-slate-300 whitespace-pre-wrap bg-blue-500/5">{marker.theirContent}</pre>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
{/each}
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<!-- Resolution actions -->
|
|
120
|
+
<div class="flex items-center gap-2 px-4 py-3 bg-slate-50 dark:bg-slate-800/50 border-t border-slate-200 dark:border-slate-700">
|
|
121
|
+
<button
|
|
122
|
+
type="button"
|
|
123
|
+
class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium bg-emerald-600 text-white rounded-md hover:bg-emerald-700 transition-colors cursor-pointer border-none"
|
|
124
|
+
onclick={() => selectedFile && onResolve(selectedFile.path, 'ours')}
|
|
125
|
+
>
|
|
126
|
+
<Icon name="lucide:check" class="w-3.5 h-3.5" />
|
|
127
|
+
Accept Ours
|
|
128
|
+
</button>
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors cursor-pointer border-none"
|
|
132
|
+
onclick={() => selectedFile && onResolve(selectedFile.path, 'theirs')}
|
|
133
|
+
>
|
|
134
|
+
<Icon name="lucide:check" class="w-3.5 h-3.5" />
|
|
135
|
+
Accept Theirs
|
|
136
|
+
</button>
|
|
137
|
+
<div class="flex-1"></div>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium bg-violet-600 text-white rounded-md hover:bg-violet-700 transition-colors cursor-pointer border-none"
|
|
141
|
+
onclick={() => selectedFile && onResolveWithAI(selectedFile.path)}
|
|
142
|
+
title="Send conflict to AI chat for resolution"
|
|
143
|
+
>
|
|
144
|
+
<Icon name="lucide:bot" class="w-3.5 h-3.5" />
|
|
145
|
+
Resolve with AI
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
{:else}
|
|
149
|
+
<div class="flex-1 flex items-center justify-center text-slate-500 text-xs">
|
|
150
|
+
Select a file to view conflicts
|
|
151
|
+
</div>
|
|
152
|
+
{/if}
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
{/if}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
{/if}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy, untrack } from 'svelte';
|
|
3
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
4
|
+
import { getFileIcon } from '$frontend/lib/utils/file-icon-mappings';
|
|
5
|
+
import { getGitStatusBadgeLabel, getGitStatusBadgeColor } from '$frontend/lib/utils/git-status';
|
|
6
|
+
import { themeStore } from '$frontend/lib/stores/ui/theme.svelte';
|
|
7
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
8
|
+
import loader from '@monaco-editor/loader';
|
|
9
|
+
import type { editor } from 'monaco-editor';
|
|
10
|
+
import type { GitFileDiff } from '$shared/types/git';
|
|
11
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
12
|
+
import { debug } from '$shared/utils/logger';
|
|
13
|
+
import ws from '$frontend/lib/utils/ws';
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
diff: GitFileDiff | null;
|
|
17
|
+
diffs?: GitFileDiff[];
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
onSelectFile?: (index: number) => void;
|
|
20
|
+
selectedFileIndex?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const { diff, diffs = [], isLoading, onSelectFile, selectedFileIndex = 0 }: Props = $props();
|
|
24
|
+
|
|
25
|
+
const allDiffs = $derived(diffs.length > 0 ? diffs : diff ? [diff] : []);
|
|
26
|
+
const activeDiff = $derived(allDiffs.length > 0 ? allDiffs[selectedFileIndex] ?? allDiffs[0] : null);
|
|
27
|
+
|
|
28
|
+
// Monaco diff editor
|
|
29
|
+
let containerRef = $state<HTMLDivElement | null>(null);
|
|
30
|
+
let diffEditorInstance: editor.IDiffEditor | null = null;
|
|
31
|
+
let monacoInstance: typeof import('monaco-editor') | null = null;
|
|
32
|
+
const isDark = $derived(themeStore.isDark);
|
|
33
|
+
|
|
34
|
+
// Binary file preview state
|
|
35
|
+
let blobUrl = $state<string | null>(null);
|
|
36
|
+
let pdfBlobUrl = $state<string | null>(null);
|
|
37
|
+
let isBinaryLoading = $state(false);
|
|
38
|
+
|
|
39
|
+
// File type detection helpers
|
|
40
|
+
function isImageFile(fileName: string): boolean {
|
|
41
|
+
const ext = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
|
|
42
|
+
return ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico', '.bmp'].includes(ext);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isSvgFile(fileName: string): boolean {
|
|
46
|
+
return fileName.toLowerCase().endsWith('.svg');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isPdfFile(fileName: string): boolean {
|
|
50
|
+
return fileName.toLowerCase().endsWith('.pdf');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isPreviewableBinary(fileName: string): boolean {
|
|
54
|
+
return isImageFile(fileName) || isSvgFile(fileName) || isPdfFile(fileName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Load binary content for preview
|
|
58
|
+
async function loadBinaryPreview(filePath: string) {
|
|
59
|
+
const projectPath = projectState.currentProject?.path;
|
|
60
|
+
if (!projectPath) return;
|
|
61
|
+
|
|
62
|
+
const absolutePath = `${projectPath}/${filePath}`;
|
|
63
|
+
isBinaryLoading = true;
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const response = await ws.http('files:read-content', { path: absolutePath });
|
|
67
|
+
|
|
68
|
+
if (response.content) {
|
|
69
|
+
const binaryString = atob(response.content);
|
|
70
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
71
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
72
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
73
|
+
}
|
|
74
|
+
const blob = new Blob([bytes], { type: response.contentType || 'application/octet-stream' });
|
|
75
|
+
|
|
76
|
+
if (isPdfFile(filePath)) {
|
|
77
|
+
if (pdfBlobUrl) URL.revokeObjectURL(pdfBlobUrl);
|
|
78
|
+
pdfBlobUrl = URL.createObjectURL(blob);
|
|
79
|
+
} else {
|
|
80
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
81
|
+
blobUrl = URL.createObjectURL(blob);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
debug.error('git', 'Failed to load binary preview:', err);
|
|
86
|
+
} finally {
|
|
87
|
+
isBinaryLoading = false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function cleanupBlobUrls() {
|
|
92
|
+
if (blobUrl) {
|
|
93
|
+
URL.revokeObjectURL(blobUrl);
|
|
94
|
+
blobUrl = null;
|
|
95
|
+
}
|
|
96
|
+
if (pdfBlobUrl) {
|
|
97
|
+
URL.revokeObjectURL(pdfBlobUrl);
|
|
98
|
+
pdfBlobUrl = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Load binary preview when activeDiff changes to a binary file
|
|
103
|
+
$effect(() => {
|
|
104
|
+
const currentDiff = activeDiff;
|
|
105
|
+
if (currentDiff?.isBinary) {
|
|
106
|
+
const filePath = currentDiff.newPath || currentDiff.oldPath;
|
|
107
|
+
if (filePath && isPreviewableBinary(getFileName(filePath))) {
|
|
108
|
+
// Status 'D' means file is deleted, can't preview
|
|
109
|
+
if (currentDiff.status !== 'D') {
|
|
110
|
+
untrack(() => {
|
|
111
|
+
cleanupBlobUrls();
|
|
112
|
+
loadBinaryPreview(filePath);
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
untrack(() => cleanupBlobUrls());
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Build full content from hunks
|
|
122
|
+
function buildContent(diff: GitFileDiff, side: 'old' | 'new'): string {
|
|
123
|
+
if (!diff || diff.isBinary || diff.hunks.length === 0) return '';
|
|
124
|
+
const lines: string[] = [];
|
|
125
|
+
for (const hunk of diff.hunks) {
|
|
126
|
+
for (const line of hunk.lines) {
|
|
127
|
+
if (side === 'old') {
|
|
128
|
+
if (line.type === 'context' || line.type === 'delete') {
|
|
129
|
+
lines.push(line.content);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
if (line.type === 'context' || line.type === 'add') {
|
|
133
|
+
lines.push(line.content);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return lines.join('\n');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function getLanguageFromPath(filePath: string): string {
|
|
142
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
143
|
+
if (!ext) return 'plaintext';
|
|
144
|
+
const map: Record<string, string> = {
|
|
145
|
+
js: 'javascript', jsx: 'javascript', ts: 'typescript', tsx: 'typescript',
|
|
146
|
+
mjs: 'javascript', cjs: 'javascript',
|
|
147
|
+
html: 'html', htm: 'html', css: 'css', scss: 'scss', sass: 'sass', less: 'less',
|
|
148
|
+
py: 'python', java: 'java', c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
|
|
149
|
+
cs: 'csharp', go: 'go', rs: 'rust', php: 'php', rb: 'ruby',
|
|
150
|
+
swift: 'swift', kt: 'kotlin', scala: 'scala',
|
|
151
|
+
sh: 'shell', bash: 'shell', zsh: 'shell',
|
|
152
|
+
sql: 'sql', xml: 'xml', json: 'json', yaml: 'yaml', yml: 'yaml',
|
|
153
|
+
toml: 'toml', md: 'markdown', dockerfile: 'dockerfile',
|
|
154
|
+
svelte: 'html', vue: 'html', svg: 'xml',
|
|
155
|
+
gitignore: 'plaintext', env: 'plaintext', txt: 'plaintext', log: 'plaintext'
|
|
156
|
+
};
|
|
157
|
+
return map[ext] || 'plaintext';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function getFileName(path: string): string {
|
|
161
|
+
return path.split(/[\\/]/).pop() || path;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function initDiffEditor() {
|
|
165
|
+
if (!containerRef || !activeDiff) return;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
if (!monacoInstance) {
|
|
169
|
+
monacoInstance = await loader.init();
|
|
170
|
+
|
|
171
|
+
// Define themes
|
|
172
|
+
monacoInstance.editor.defineTheme('diff-dark', {
|
|
173
|
+
base: 'vs-dark',
|
|
174
|
+
inherit: true,
|
|
175
|
+
rules: [],
|
|
176
|
+
colors: {
|
|
177
|
+
'editor.background': '#0d1117',
|
|
178
|
+
'editor.foreground': '#e6edf3',
|
|
179
|
+
'editor.lineHighlightBackground': '#161b22',
|
|
180
|
+
'editorLineNumber.foreground': '#6e7681',
|
|
181
|
+
'editorLineNumber.activeForeground': '#f0f6fc',
|
|
182
|
+
'diffEditor.insertedTextBackground': '#23863633',
|
|
183
|
+
'diffEditor.removedTextBackground': '#f8514933',
|
|
184
|
+
'diffEditor.insertedLineBackground': '#23863620',
|
|
185
|
+
'diffEditor.removedLineBackground': '#f8514920',
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
monacoInstance.editor.defineTheme('diff-light', {
|
|
189
|
+
base: 'vs',
|
|
190
|
+
inherit: true,
|
|
191
|
+
rules: [],
|
|
192
|
+
colors: {
|
|
193
|
+
'editor.background': '#ffffff',
|
|
194
|
+
'diffEditor.insertedTextBackground': '#dafbe133',
|
|
195
|
+
'diffEditor.removedTextBackground': '#ffc3c333',
|
|
196
|
+
'diffEditor.insertedLineBackground': '#dafbe120',
|
|
197
|
+
'diffEditor.removedLineBackground': '#ffc3c320',
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Dispose previous
|
|
203
|
+
if (diffEditorInstance) {
|
|
204
|
+
diffEditorInstance.dispose();
|
|
205
|
+
diffEditorInstance = null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const language = getLanguageFromPath(activeDiff.newPath || activeDiff.oldPath || '');
|
|
209
|
+
const oldContent = buildContent(activeDiff, 'old');
|
|
210
|
+
const newContent = buildContent(activeDiff, 'new');
|
|
211
|
+
|
|
212
|
+
const originalModel = monacoInstance.editor.createModel(oldContent, language);
|
|
213
|
+
const modifiedModel = monacoInstance.editor.createModel(newContent, language);
|
|
214
|
+
|
|
215
|
+
diffEditorInstance = monacoInstance.editor.createDiffEditor(containerRef, {
|
|
216
|
+
theme: isDark ? 'diff-dark' : 'diff-light',
|
|
217
|
+
readOnly: true,
|
|
218
|
+
renderSideBySide: true,
|
|
219
|
+
minimap: { enabled: false },
|
|
220
|
+
scrollBeyondLastLine: false,
|
|
221
|
+
fontSize: 12,
|
|
222
|
+
lineHeight: 20,
|
|
223
|
+
renderOverviewRuler: false,
|
|
224
|
+
enableSplitViewResizing: true,
|
|
225
|
+
automaticLayout: true,
|
|
226
|
+
scrollbar: {
|
|
227
|
+
verticalScrollbarSize: 8,
|
|
228
|
+
horizontalScrollbarSize: 8
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
diffEditorInstance.setModel({
|
|
233
|
+
original: originalModel,
|
|
234
|
+
modified: modifiedModel
|
|
235
|
+
});
|
|
236
|
+
} catch (err) {
|
|
237
|
+
debug.error('git', 'Failed to init diff editor:', err);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Update theme when it changes
|
|
242
|
+
$effect(() => {
|
|
243
|
+
const theme = isDark ? 'diff-dark' : 'diff-light';
|
|
244
|
+
if (diffEditorInstance && monacoInstance) {
|
|
245
|
+
monacoInstance.editor.setTheme(theme);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Reinitialize when diff or selected file changes
|
|
250
|
+
$effect(() => {
|
|
251
|
+
if (activeDiff && containerRef) {
|
|
252
|
+
selectedFileIndex;
|
|
253
|
+
untrack(() => initDiffEditor());
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
onDestroy(() => {
|
|
258
|
+
if (diffEditorInstance) {
|
|
259
|
+
diffEditorInstance.dispose();
|
|
260
|
+
diffEditorInstance = null;
|
|
261
|
+
}
|
|
262
|
+
cleanupBlobUrls();
|
|
263
|
+
});
|
|
264
|
+
</script>
|
|
265
|
+
|
|
266
|
+
<div class="h-full flex flex-col">
|
|
267
|
+
{#if isLoading}
|
|
268
|
+
<div class="flex-1 flex items-center justify-center">
|
|
269
|
+
<div class="w-5 h-5 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
270
|
+
</div>
|
|
271
|
+
{:else if !activeDiff}
|
|
272
|
+
<div class="flex-1 flex flex-col items-center justify-center gap-2 text-slate-500 text-xs">
|
|
273
|
+
<Icon name="lucide:file-diff" class="w-8 h-8 opacity-30" />
|
|
274
|
+
<span>Select a file to view diff</span>
|
|
275
|
+
</div>
|
|
276
|
+
{:else}
|
|
277
|
+
<!-- File header (like FileViewer) -->
|
|
278
|
+
<div class="flex-shrink-0 flex items-center justify-between px-4 py-2.5 border-b border-slate-200 dark:border-slate-700">
|
|
279
|
+
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
280
|
+
<Icon name={getFileIcon(getFileName(activeDiff.newPath || activeDiff.oldPath)) as IconName} class="w-5 h-5 shrink-0" />
|
|
281
|
+
<div class="min-w-0 flex-1">
|
|
282
|
+
<h3 class="text-xs font-bold text-slate-900 dark:text-slate-100 truncate">
|
|
283
|
+
{getFileName(activeDiff.newPath || activeDiff.oldPath)}
|
|
284
|
+
</h3>
|
|
285
|
+
<p class="text-3xs text-slate-500 dark:text-slate-400 truncate mt-0.5">
|
|
286
|
+
{activeDiff.newPath || activeDiff.oldPath}
|
|
287
|
+
</p>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
<div class="flex items-center gap-2 shrink-0">
|
|
291
|
+
<span class="text-3xs font-bold px-1.5 py-0.5 rounded {getGitStatusBadgeColor(activeDiff.status)}">
|
|
292
|
+
{getGitStatusBadgeLabel(activeDiff.status)}
|
|
293
|
+
</span>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
{#if activeDiff.isBinary}
|
|
298
|
+
{@const fileName = getFileName(activeDiff.newPath || activeDiff.oldPath)}
|
|
299
|
+
{@const isDeleted = activeDiff.status === 'D'}
|
|
300
|
+
{#if isDeleted}
|
|
301
|
+
<!-- Deleted binary file -->
|
|
302
|
+
<div class="flex-1 flex flex-col items-center justify-center gap-3 p-8">
|
|
303
|
+
<Icon name="lucide:file-x" class="w-12 h-12 text-red-400 opacity-60" />
|
|
304
|
+
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">Binary File Deleted</h3>
|
|
305
|
+
<p class="text-xs text-slate-500 dark:text-slate-400 text-center">
|
|
306
|
+
This binary file has been deleted and cannot be previewed.
|
|
307
|
+
</p>
|
|
308
|
+
</div>
|
|
309
|
+
{:else if isImageFile(fileName) || isSvgFile(fileName)}
|
|
310
|
+
<!-- Image / SVG preview -->
|
|
311
|
+
<div class="flex-1 flex items-center justify-center p-4 overflow-hidden bg-[url('data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2220%22%20height%3D%2220%22%3E%3Crect%20width%3D%2220%22%20height%3D%2220%22%20fill%3D%22%23f0f0f0%22%2F%3E%3Crect%20width%3D%2210%22%20height%3D%2210%22%20fill%3D%22%23e0e0e0%22%2F%3E%3Crect%20x%3D%2210%22%20y%3D%2210%22%20width%3D%2210%22%20height%3D%2210%22%20fill%3D%22%23e0e0e0%22%2F%3E%3C%2Fsvg%3E')]">
|
|
312
|
+
{#if isBinaryLoading}
|
|
313
|
+
<div class="w-5 h-5 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
314
|
+
{:else if blobUrl}
|
|
315
|
+
<img
|
|
316
|
+
src={blobUrl}
|
|
317
|
+
alt={fileName}
|
|
318
|
+
class="max-w-full max-h-full object-contain"
|
|
319
|
+
/>
|
|
320
|
+
{:else}
|
|
321
|
+
<div class="flex flex-col items-center gap-2 text-slate-500 text-xs">
|
|
322
|
+
<Icon name="lucide:image-off" class="w-8 h-8 opacity-40" />
|
|
323
|
+
<span>Failed to load preview</span>
|
|
324
|
+
</div>
|
|
325
|
+
{/if}
|
|
326
|
+
</div>
|
|
327
|
+
{:else if isPdfFile(fileName)}
|
|
328
|
+
<!-- PDF preview -->
|
|
329
|
+
<div class="flex-1 h-full w-full">
|
|
330
|
+
{#if isBinaryLoading}
|
|
331
|
+
<div class="flex items-center justify-center h-full">
|
|
332
|
+
<div class="w-5 h-5 border-2 border-slate-200 dark:border-slate-700 border-t-violet-600 rounded-full animate-spin"></div>
|
|
333
|
+
</div>
|
|
334
|
+
{:else if pdfBlobUrl}
|
|
335
|
+
<iframe
|
|
336
|
+
src={pdfBlobUrl}
|
|
337
|
+
title={fileName}
|
|
338
|
+
class="w-full h-full border-0"
|
|
339
|
+
></iframe>
|
|
340
|
+
{:else}
|
|
341
|
+
<div class="flex flex-col items-center justify-center h-full gap-2 text-slate-500 text-xs">
|
|
342
|
+
<Icon name="lucide:file-x" class="w-8 h-8 opacity-40" />
|
|
343
|
+
<span>Failed to load PDF preview</span>
|
|
344
|
+
</div>
|
|
345
|
+
{/if}
|
|
346
|
+
</div>
|
|
347
|
+
{:else}
|
|
348
|
+
<!-- Generic binary file -->
|
|
349
|
+
<div class="flex-1 flex flex-col items-center justify-center gap-3 p-8">
|
|
350
|
+
<Icon name="lucide:file-archive" class="w-12 h-12 text-slate-400 opacity-60" />
|
|
351
|
+
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300">Binary File</h3>
|
|
352
|
+
<p class="text-xs text-slate-500 dark:text-slate-400 text-center">
|
|
353
|
+
This file cannot be previewed in the diff viewer.
|
|
354
|
+
</p>
|
|
355
|
+
</div>
|
|
356
|
+
{/if}
|
|
357
|
+
{:else}
|
|
358
|
+
<!-- Monaco Diff Editor -->
|
|
359
|
+
<div class="flex-1 overflow-hidden">
|
|
360
|
+
<div class="h-full w-full" bind:this={containerRef}></div>
|
|
361
|
+
</div>
|
|
362
|
+
{/if}
|
|
363
|
+
{/if}
|
|
364
|
+
</div>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
|
+
import { getFileIcon } from '$frontend/lib/utils/file-icon-mappings';
|
|
4
|
+
import { getGitStatusLabel, getGitStatusColor } from '$frontend/lib/utils/git-status';
|
|
5
|
+
import type { GitFileChange } from '$shared/types/git';
|
|
6
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
file: GitFileChange;
|
|
10
|
+
section: 'staged' | 'unstaged' | 'untracked' | 'conflicted';
|
|
11
|
+
isActive?: boolean;
|
|
12
|
+
onStage?: (path: string) => void;
|
|
13
|
+
onUnstage?: (path: string) => void;
|
|
14
|
+
onDiscard?: (path: string) => void;
|
|
15
|
+
onViewDiff?: (file: GitFileChange, section: string) => void;
|
|
16
|
+
onResolve?: (path: string) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { file, section, isActive = false, onStage, onUnstage, onDiscard, onViewDiff, onResolve }: Props = $props();
|
|
20
|
+
|
|
21
|
+
const statusCode = $derived(section === 'staged' ? file.indexStatus : file.workingStatus);
|
|
22
|
+
const statusLabel = $derived(getGitStatusLabel(statusCode));
|
|
23
|
+
const statusColor = $derived(getGitStatusColor(statusCode));
|
|
24
|
+
const fileName = $derived(file.path.split(/[\\/]/).pop() || file.path);
|
|
25
|
+
const dirPath = $derived(() => {
|
|
26
|
+
const parts = file.path.split(/[\\/]/);
|
|
27
|
+
parts.pop();
|
|
28
|
+
return parts.join('/');
|
|
29
|
+
});
|
|
30
|
+
const fileIcon = $derived(getFileIcon(fileName) as IconName);
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<div
|
|
34
|
+
class="group flex items-center gap-1.5 py-1.5 px-2 rounded-md cursor-pointer transition-colors
|
|
35
|
+
{isActive
|
|
36
|
+
? 'bg-violet-500/10 dark:bg-violet-500/15 text-slate-900 dark:text-slate-100'
|
|
37
|
+
: 'hover:bg-slate-100 dark:hover:bg-slate-800/60 text-slate-700 dark:text-slate-300'}"
|
|
38
|
+
role="button"
|
|
39
|
+
tabindex="0"
|
|
40
|
+
onclick={() => onViewDiff?.(file, section)}
|
|
41
|
+
onkeydown={(e) => e.key === 'Enter' && onViewDiff?.(file, section)}
|
|
42
|
+
title={file.path}
|
|
43
|
+
>
|
|
44
|
+
<!-- File icon -->
|
|
45
|
+
<Icon name={fileIcon} class="w-4 h-4 shrink-0" />
|
|
46
|
+
|
|
47
|
+
<!-- File name - truncated -->
|
|
48
|
+
<span class="text-xs font-medium truncate min-w-0 flex-1">{fileName}</span>
|
|
49
|
+
|
|
50
|
+
<!-- Dir path - truncated, dimmed -->
|
|
51
|
+
{#if dirPath()}
|
|
52
|
+
<span class="text-3xs text-slate-400 dark:text-slate-500 truncate max-w-20 shrink">{dirPath()}</span>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
<!-- Status badge -->
|
|
56
|
+
<span class="w-4 text-center text-xs font-bold {statusColor} shrink-0">{statusLabel}</span>
|
|
57
|
+
|
|
58
|
+
<!-- Actions - always visible -->
|
|
59
|
+
<div class="flex items-center gap-0.5 shrink-0">
|
|
60
|
+
{#if section === 'staged'}
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors bg-transparent border-none cursor-pointer"
|
|
64
|
+
onclick={(e) => { e.stopPropagation(); onUnstage?.(file.path); }}
|
|
65
|
+
title="Unstage"
|
|
66
|
+
>
|
|
67
|
+
<Icon name="lucide:minus" class="w-3.5 h-3.5" />
|
|
68
|
+
</button>
|
|
69
|
+
{:else if section === 'unstaged' || section === 'untracked'}
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-slate-400 hover:bg-red-500/10 hover:text-red-500 transition-colors bg-transparent border-none cursor-pointer"
|
|
73
|
+
onclick={(e) => { e.stopPropagation(); onDiscard?.(file.path); }}
|
|
74
|
+
title="Discard Changes"
|
|
75
|
+
>
|
|
76
|
+
<Icon name="lucide:undo-2" class="w-3.5 h-3.5" />
|
|
77
|
+
</button>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-slate-400 hover:bg-emerald-500/10 hover:text-emerald-500 transition-colors bg-transparent border-none cursor-pointer"
|
|
81
|
+
onclick={(e) => { e.stopPropagation(); onStage?.(file.path); }}
|
|
82
|
+
title="Stage Changes"
|
|
83
|
+
>
|
|
84
|
+
<Icon name="lucide:plus" class="w-3.5 h-3.5" />
|
|
85
|
+
</button>
|
|
86
|
+
{:else if section === 'conflicted'}
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
class="flex items-center justify-center w-6 h-6 rounded-md text-slate-400 hover:bg-violet-500/10 hover:text-violet-500 transition-colors bg-transparent border-none cursor-pointer"
|
|
90
|
+
onclick={(e) => { e.stopPropagation(); onResolve?.(file.path); }}
|
|
91
|
+
title="Resolve Conflict"
|
|
92
|
+
>
|
|
93
|
+
<Icon name="lucide:wrench" class="w-3.5 h-3.5" />
|
|
94
|
+
</button>
|
|
95
|
+
{/if}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
collapsed?: boolean;
|
|
6
|
+
onClick: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { collapsed = false, onClick }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if collapsed}
|
|
13
|
+
<!-- Collapsed: Icon Only -->
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
class="flex items-center justify-center w-9 h-9 bg-transparent border-none rounded-lg text-slate-500 cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100 relative"
|
|
17
|
+
onclick={onClick}
|
|
18
|
+
aria-label="Source Control"
|
|
19
|
+
title="Source Control"
|
|
20
|
+
>
|
|
21
|
+
<Icon name="lucide:git-branch" class="w-5 h-5" />
|
|
22
|
+
</button>
|
|
23
|
+
{:else}
|
|
24
|
+
<!-- Expanded: Full Width -->
|
|
25
|
+
<button
|
|
26
|
+
type="button"
|
|
27
|
+
class="flex items-center gap-2.5 w-full py-2.5 px-3 bg-transparent border-none rounded-lg text-slate-500 text-sm cursor-pointer transition-all duration-150 hover:bg-violet-500/10 hover:text-slate-900 dark:hover:text-slate-100"
|
|
28
|
+
onclick={onClick}
|
|
29
|
+
>
|
|
30
|
+
<Icon name="lucide:git-branch" class="w-4 h-4" />
|
|
31
|
+
<span class="flex-1 text-left">Source Control</span>
|
|
32
|
+
</button>
|
|
33
|
+
{/if}
|