@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,681 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
// Persistent state that survives component destruction (mobile/desktop switch)
|
|
3
|
+
const projectSearchStates = new Map<string, any>();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
8
|
+
import type { FileNode as FileNodeType } from '$shared/types/filesystem';
|
|
9
|
+
import FileNode from './FileNode.svelte';
|
|
10
|
+
import SearchResults from './SearchResults.svelte';
|
|
11
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
12
|
+
import ws from '$frontend/lib/utils/ws';
|
|
13
|
+
import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
|
|
14
|
+
import { showConfirm } from '$frontend/lib/stores/ui/dialog.svelte';
|
|
15
|
+
import { normalizePath } from '$shared/utils/path';
|
|
16
|
+
import { onDestroy } from 'svelte';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
files: FileNodeType[];
|
|
20
|
+
onFileSelect?: (file: FileNodeType) => void;
|
|
21
|
+
onFileAction?: (action: string, file: FileNodeType) => void;
|
|
22
|
+
onPasteToRoot?: () => void;
|
|
23
|
+
onNewFileInRoot?: () => void;
|
|
24
|
+
onNewFolderInRoot?: () => void;
|
|
25
|
+
selectedFile?: FileNodeType | null;
|
|
26
|
+
expandedFolders?: Set<string>;
|
|
27
|
+
onToggle?: (folderPath: string) => void;
|
|
28
|
+
hasClipboard?: boolean;
|
|
29
|
+
onFileOpen?: (path: string, lineNumber?: number) => void;
|
|
30
|
+
onRefresh?: () => void;
|
|
31
|
+
modifiedFiles?: Set<string>;
|
|
32
|
+
activeFilePath?: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
files = [],
|
|
37
|
+
onFileSelect,
|
|
38
|
+
onFileAction,
|
|
39
|
+
onPasteToRoot,
|
|
40
|
+
onNewFileInRoot,
|
|
41
|
+
onNewFolderInRoot,
|
|
42
|
+
selectedFile = null,
|
|
43
|
+
expandedFolders,
|
|
44
|
+
onToggle,
|
|
45
|
+
hasClipboard = false,
|
|
46
|
+
onFileOpen,
|
|
47
|
+
onRefresh,
|
|
48
|
+
modifiedFiles = new Set(),
|
|
49
|
+
activeFilePath = null
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
// Create local state if expandedFolders is not provided
|
|
53
|
+
let localState = $state(new Set<string>());
|
|
54
|
+
|
|
55
|
+
// Use provided expandedFolders or local state (computed)
|
|
56
|
+
const localExpandedFolders = $derived(expandedFolders || localState);
|
|
57
|
+
|
|
58
|
+
// State to track which menu is currently open (only one menu at a time)
|
|
59
|
+
let openMenuPath = $state<string | null>(null);
|
|
60
|
+
|
|
61
|
+
// Search visibility
|
|
62
|
+
let searchVisible = $state(false);
|
|
63
|
+
|
|
64
|
+
// Search state
|
|
65
|
+
let searchQuery = $state('');
|
|
66
|
+
let submittedQuery = $state('');
|
|
67
|
+
let searchMode = $state<'files' | 'code'>('files');
|
|
68
|
+
let isSearching = $state(false);
|
|
69
|
+
let fileSearchResults = $state<any[]>([]);
|
|
70
|
+
let codeSearchResults = $state<any[]>([]);
|
|
71
|
+
let searchInputRef = $state<HTMLInputElement>();
|
|
72
|
+
|
|
73
|
+
// Search options
|
|
74
|
+
let caseSensitive = $state(false);
|
|
75
|
+
let wholeWord = $state(false);
|
|
76
|
+
let useRegex = $state(false);
|
|
77
|
+
|
|
78
|
+
// File filter state
|
|
79
|
+
let filesToInclude = $state('');
|
|
80
|
+
let filesToExclude = $state('');
|
|
81
|
+
let showFilters = $state(false);
|
|
82
|
+
|
|
83
|
+
// Replace state
|
|
84
|
+
let replaceQuery = $state('');
|
|
85
|
+
let showReplace = $state(false);
|
|
86
|
+
let isReplacing = $state(false);
|
|
87
|
+
|
|
88
|
+
// Abort controller for cancelling search
|
|
89
|
+
let searchAbortController: AbortController | null = null;
|
|
90
|
+
|
|
91
|
+
// projectSearchStates is at module level to survive component destruction (mobile/desktop switch)
|
|
92
|
+
let lastProjectId = $state('');
|
|
93
|
+
|
|
94
|
+
// Save/restore search state per project
|
|
95
|
+
$effect(() => {
|
|
96
|
+
const currentProjectId = projectState.currentProject?.id || '';
|
|
97
|
+
if (currentProjectId !== lastProjectId) {
|
|
98
|
+
// Save current state for old project
|
|
99
|
+
if (lastProjectId) {
|
|
100
|
+
projectSearchStates.set(lastProjectId, {
|
|
101
|
+
searchVisible,
|
|
102
|
+
searchQuery,
|
|
103
|
+
submittedQuery,
|
|
104
|
+
searchMode,
|
|
105
|
+
fileSearchResults,
|
|
106
|
+
codeSearchResults,
|
|
107
|
+
caseSensitive,
|
|
108
|
+
wholeWord,
|
|
109
|
+
useRegex,
|
|
110
|
+
filesToInclude,
|
|
111
|
+
filesToExclude,
|
|
112
|
+
showFilters,
|
|
113
|
+
showReplace,
|
|
114
|
+
replaceQuery
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
// Restore or reset for new project
|
|
118
|
+
const saved = projectSearchStates.get(currentProjectId);
|
|
119
|
+
if (saved) {
|
|
120
|
+
searchVisible = saved.searchVisible;
|
|
121
|
+
searchQuery = saved.searchQuery;
|
|
122
|
+
submittedQuery = saved.submittedQuery;
|
|
123
|
+
searchMode = saved.searchMode;
|
|
124
|
+
fileSearchResults = saved.fileSearchResults;
|
|
125
|
+
codeSearchResults = saved.codeSearchResults;
|
|
126
|
+
caseSensitive = saved.caseSensitive;
|
|
127
|
+
wholeWord = saved.wholeWord;
|
|
128
|
+
useRegex = saved.useRegex;
|
|
129
|
+
filesToInclude = saved.filesToInclude;
|
|
130
|
+
filesToExclude = saved.filesToExclude;
|
|
131
|
+
showFilters = saved.showFilters;
|
|
132
|
+
showReplace = saved.showReplace;
|
|
133
|
+
replaceQuery = saved.replaceQuery;
|
|
134
|
+
} else {
|
|
135
|
+
searchVisible = false;
|
|
136
|
+
searchQuery = '';
|
|
137
|
+
submittedQuery = '';
|
|
138
|
+
searchMode = 'files';
|
|
139
|
+
fileSearchResults = [];
|
|
140
|
+
codeSearchResults = [];
|
|
141
|
+
caseSensitive = false;
|
|
142
|
+
wholeWord = false;
|
|
143
|
+
useRegex = false;
|
|
144
|
+
filesToInclude = '';
|
|
145
|
+
filesToExclude = '';
|
|
146
|
+
showFilters = false;
|
|
147
|
+
showReplace = false;
|
|
148
|
+
replaceQuery = '';
|
|
149
|
+
}
|
|
150
|
+
lastProjectId = currentProjectId;
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Save search state to persistent storage on component destruction (mobile/desktop switch)
|
|
155
|
+
onDestroy(() => {
|
|
156
|
+
const currentProjectId = projectState.currentProject?.id || '';
|
|
157
|
+
if (currentProjectId) {
|
|
158
|
+
projectSearchStates.set(currentProjectId, {
|
|
159
|
+
searchVisible,
|
|
160
|
+
searchQuery,
|
|
161
|
+
submittedQuery,
|
|
162
|
+
searchMode,
|
|
163
|
+
fileSearchResults,
|
|
164
|
+
codeSearchResults,
|
|
165
|
+
caseSensitive,
|
|
166
|
+
wholeWord,
|
|
167
|
+
useRegex,
|
|
168
|
+
filesToInclude,
|
|
169
|
+
filesToExclude,
|
|
170
|
+
showFilters,
|
|
171
|
+
showReplace,
|
|
172
|
+
replaceQuery
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
function handleMenuToggle(filePath: string) {
|
|
178
|
+
openMenuPath = openMenuPath === filePath ? null : filePath;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function handleFileSelect(file: FileNodeType) {
|
|
182
|
+
onFileSelect?.(file);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleFileAction(action: string, file: FileNodeType) {
|
|
186
|
+
if (action === 'find-in-folder') {
|
|
187
|
+
const projectPath = projectState.currentProject?.path || '';
|
|
188
|
+
let relativePath = file.path;
|
|
189
|
+
if (projectPath && relativePath.startsWith(projectPath)) {
|
|
190
|
+
relativePath = relativePath.slice(projectPath.length);
|
|
191
|
+
if (relativePath.startsWith('/') || relativePath.startsWith('\\')) {
|
|
192
|
+
relativePath = relativePath.slice(1);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
searchVisible = true;
|
|
196
|
+
searchMode = 'code';
|
|
197
|
+
filesToInclude = relativePath;
|
|
198
|
+
showFilters = true;
|
|
199
|
+
setTimeout(() => searchInputRef?.focus(), 100);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
onFileAction?.(action, file);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toggleFolder(folderPath: string) {
|
|
206
|
+
if (onToggle) {
|
|
207
|
+
onToggle(folderPath);
|
|
208
|
+
} else if (expandedFolders) {
|
|
209
|
+
if (expandedFolders.has(folderPath)) {
|
|
210
|
+
expandedFolders.delete(folderPath);
|
|
211
|
+
} else {
|
|
212
|
+
expandedFolders.add(folderPath);
|
|
213
|
+
}
|
|
214
|
+
expandedFolders = new Set(expandedFolders);
|
|
215
|
+
} else {
|
|
216
|
+
if (localState.has(folderPath)) {
|
|
217
|
+
localState.delete(folderPath);
|
|
218
|
+
} else {
|
|
219
|
+
localState.add(folderPath);
|
|
220
|
+
}
|
|
221
|
+
localState = new Set(localState);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Toggle search visibility (preserve inputs, auto-submit on re-open)
|
|
226
|
+
function toggleSearch() {
|
|
227
|
+
searchVisible = !searchVisible;
|
|
228
|
+
if (searchVisible) {
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
searchInputRef?.focus();
|
|
231
|
+
// Auto-submit if there's already a query
|
|
232
|
+
if (searchQuery.trim()) {
|
|
233
|
+
performSearch();
|
|
234
|
+
}
|
|
235
|
+
}, 100);
|
|
236
|
+
}
|
|
237
|
+
// Don't clear search state on close - preserve inputs
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Search functions
|
|
241
|
+
async function performSearch() {
|
|
242
|
+
if (!searchQuery.trim()) {
|
|
243
|
+
fileSearchResults = [];
|
|
244
|
+
codeSearchResults = [];
|
|
245
|
+
submittedQuery = '';
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const projectPath = projectState.currentProject?.path;
|
|
250
|
+
if (!projectPath) return;
|
|
251
|
+
|
|
252
|
+
submittedQuery = searchQuery.trim();
|
|
253
|
+
isSearching = true;
|
|
254
|
+
|
|
255
|
+
searchAbortController = new AbortController();
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
if (searchMode === 'files') {
|
|
259
|
+
const results = await ws.http('files:search-files', {
|
|
260
|
+
project_path: projectPath,
|
|
261
|
+
query: submittedQuery
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
fileSearchResults = results || [];
|
|
265
|
+
} else {
|
|
266
|
+
// Only apply filters when the filter panel is visible
|
|
267
|
+
const includePattern = showFilters ? (filesToInclude || undefined) : undefined;
|
|
268
|
+
const excludePattern = showFilters ? (filesToExclude || undefined) : undefined;
|
|
269
|
+
const data = await ws.http('files:search-code', {
|
|
270
|
+
project_path: projectPath,
|
|
271
|
+
query: submittedQuery,
|
|
272
|
+
case_sensitive: caseSensitive,
|
|
273
|
+
whole_word: wholeWord,
|
|
274
|
+
use_regex: useRegex,
|
|
275
|
+
include_pattern: includePattern,
|
|
276
|
+
exclude_pattern: excludePattern
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
codeSearchResults = data || [];
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (searchAbortController?.signal.aborted) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
addNotification({
|
|
286
|
+
type: 'error',
|
|
287
|
+
title: 'Search Failed',
|
|
288
|
+
message: error instanceof Error ? error.message : 'Search failed',
|
|
289
|
+
duration: 3000
|
|
290
|
+
});
|
|
291
|
+
} finally {
|
|
292
|
+
isSearching = false;
|
|
293
|
+
searchAbortController = null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function cancelSearch() {
|
|
298
|
+
if (searchAbortController) {
|
|
299
|
+
searchAbortController.abort();
|
|
300
|
+
searchAbortController = null;
|
|
301
|
+
}
|
|
302
|
+
isSearching = false;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function handleReplaceAll() {
|
|
306
|
+
if (!submittedQuery) return;
|
|
307
|
+
|
|
308
|
+
const projectPath = projectState.currentProject?.path;
|
|
309
|
+
if (!projectPath) return;
|
|
310
|
+
|
|
311
|
+
const confirmed = await showConfirm({
|
|
312
|
+
title: 'Replace All',
|
|
313
|
+
message: `Replace all occurrences of "${submittedQuery}" with "${replaceQuery}" across all matching files? This action cannot be undone.`,
|
|
314
|
+
type: 'warning',
|
|
315
|
+
confirmText: 'Replace All',
|
|
316
|
+
cancelText: 'Cancel'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (!confirmed) return;
|
|
320
|
+
|
|
321
|
+
// Respect showFilters state - same as search
|
|
322
|
+
const includePattern = showFilters ? (filesToInclude || undefined) : undefined;
|
|
323
|
+
const excludePattern = showFilters ? (filesToExclude || undefined) : undefined;
|
|
324
|
+
|
|
325
|
+
isReplacing = true;
|
|
326
|
+
try {
|
|
327
|
+
const result = await ws.http('files:replace-in-files', {
|
|
328
|
+
project_path: projectPath,
|
|
329
|
+
search_query: submittedQuery,
|
|
330
|
+
replace_with: replaceQuery,
|
|
331
|
+
case_sensitive: caseSensitive,
|
|
332
|
+
whole_word: wholeWord,
|
|
333
|
+
use_regex: useRegex,
|
|
334
|
+
include_pattern: includePattern,
|
|
335
|
+
exclude_pattern: excludePattern
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
addNotification({
|
|
339
|
+
type: 'success',
|
|
340
|
+
title: 'Replace Complete',
|
|
341
|
+
message: `Replaced ${result.totalReplacements} occurrences in ${result.totalFiles} files`,
|
|
342
|
+
duration: 5000
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
onRefresh?.();
|
|
346
|
+
await performSearch();
|
|
347
|
+
} catch (error) {
|
|
348
|
+
addNotification({
|
|
349
|
+
type: 'error',
|
|
350
|
+
title: 'Replace Failed',
|
|
351
|
+
message: error instanceof Error ? error.message : 'Replace failed',
|
|
352
|
+
duration: 5000
|
|
353
|
+
});
|
|
354
|
+
} finally {
|
|
355
|
+
isReplacing = false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function handleSearchKeydown(e: KeyboardEvent) {
|
|
360
|
+
if (e.key === 'Enter') {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
performSearch();
|
|
363
|
+
} else if (e.key === 'Escape') {
|
|
364
|
+
clearSearch();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function clearSearch() {
|
|
369
|
+
searchQuery = '';
|
|
370
|
+
submittedQuery = '';
|
|
371
|
+
fileSearchResults = [];
|
|
372
|
+
codeSearchResults = [];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function switchSearchMode(mode: 'files' | 'code') {
|
|
376
|
+
searchMode = mode;
|
|
377
|
+
// Clear results but keep query, then auto-search
|
|
378
|
+
fileSearchResults = [];
|
|
379
|
+
codeSearchResults = [];
|
|
380
|
+
submittedQuery = '';
|
|
381
|
+
if (searchQuery.trim()) {
|
|
382
|
+
performSearch();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Search result handlers
|
|
387
|
+
function handleFileClick(result: any) {
|
|
388
|
+
if (onFileOpen) {
|
|
389
|
+
onFileOpen(result.path);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function handleCodeMatchClick(result: any, match: any) {
|
|
394
|
+
const projectPath = projectState.currentProject?.path || '';
|
|
395
|
+
const separator = projectPath.includes('\\') ? '\\' : '/';
|
|
396
|
+
// Normalize relativePath to use the OS-appropriate separator
|
|
397
|
+
const relPath = separator === '\\'
|
|
398
|
+
? result.relativePath.replace(/\//g, '\\')
|
|
399
|
+
: result.relativePath.replace(/\\/g, '/');
|
|
400
|
+
const fullPath = `${projectPath}${separator}${relPath}`;
|
|
401
|
+
|
|
402
|
+
if (onFileOpen) {
|
|
403
|
+
onFileOpen(fullPath, match.line);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Public method for search toggle
|
|
408
|
+
export function focusSearch(mode?: 'files' | 'code') {
|
|
409
|
+
searchVisible = true;
|
|
410
|
+
if (mode) {
|
|
411
|
+
searchMode = mode;
|
|
412
|
+
}
|
|
413
|
+
setTimeout(() => searchInputRef?.focus(), 100);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Public method for "Find in Folder"
|
|
417
|
+
export function openFindInFolder(folderRelativePath: string) {
|
|
418
|
+
searchVisible = true;
|
|
419
|
+
searchMode = 'code';
|
|
420
|
+
filesToInclude = folderRelativePath;
|
|
421
|
+
showFilters = true;
|
|
422
|
+
setTimeout(() => searchInputRef?.focus(), 100);
|
|
423
|
+
}
|
|
424
|
+
</script>
|
|
425
|
+
|
|
426
|
+
<div class="relative flex flex-col h-full overflow-hidden">
|
|
427
|
+
<!-- Modern Header -->
|
|
428
|
+
<div class="px-5 py-3 border-b border-slate-200 dark:border-slate-700">
|
|
429
|
+
<div class="flex items-start justify-between gap-2">
|
|
430
|
+
<div class="flex-1 min-w-0">
|
|
431
|
+
<h3 class="text-sm font-bold text-slate-900 dark:text-slate-100">
|
|
432
|
+
{projectState.currentProject?.name}
|
|
433
|
+
</h3>
|
|
434
|
+
<p class="text-xs text-slate-600 dark:text-slate-400 mt-0.5 font-mono truncate">
|
|
435
|
+
{projectState.currentProject?.path}
|
|
436
|
+
</p>
|
|
437
|
+
</div>
|
|
438
|
+
<div class="flex items-center gap-1">
|
|
439
|
+
{#if onNewFileInRoot}
|
|
440
|
+
<button
|
|
441
|
+
class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
|
|
442
|
+
onclick={onNewFileInRoot}
|
|
443
|
+
title="New File"
|
|
444
|
+
>
|
|
445
|
+
<Icon name="lucide:file-plus" class="w-4 h-4" />
|
|
446
|
+
</button>
|
|
447
|
+
{/if}
|
|
448
|
+
{#if onNewFolderInRoot}
|
|
449
|
+
<button
|
|
450
|
+
class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
|
|
451
|
+
onclick={onNewFolderInRoot}
|
|
452
|
+
title="New Folder"
|
|
453
|
+
>
|
|
454
|
+
<Icon name="lucide:folder-plus" class="w-4 h-4" />
|
|
455
|
+
</button>
|
|
456
|
+
{/if}
|
|
457
|
+
<!-- Search toggle button -->
|
|
458
|
+
<button
|
|
459
|
+
class="flex flex-shrink-0 p-1.5 rounded-md transition-colors {searchVisible ? 'text-violet-600 dark:text-violet-400 bg-violet-50 dark:bg-violet-900/30' : 'text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30'}"
|
|
460
|
+
onclick={toggleSearch}
|
|
461
|
+
title="Search"
|
|
462
|
+
>
|
|
463
|
+
<Icon name="lucide:search" class="w-4 h-4" />
|
|
464
|
+
</button>
|
|
465
|
+
{#if hasClipboard && onPasteToRoot}
|
|
466
|
+
<button
|
|
467
|
+
class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
|
|
468
|
+
onclick={onPasteToRoot}
|
|
469
|
+
title="Paste to root"
|
|
470
|
+
>
|
|
471
|
+
<Icon name="lucide:clipboard" class="w-4 h-4" />
|
|
472
|
+
</button>
|
|
473
|
+
{/if}
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
<!-- Search Bar (toggle) -->
|
|
479
|
+
{#if searchVisible}
|
|
480
|
+
<div class="px-3 py-2 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50">
|
|
481
|
+
<!-- Search Input -->
|
|
482
|
+
<div class="relative mb-2">
|
|
483
|
+
<input
|
|
484
|
+
bind:this={searchInputRef}
|
|
485
|
+
bind:value={searchQuery}
|
|
486
|
+
onkeydown={handleSearchKeydown}
|
|
487
|
+
type="text"
|
|
488
|
+
placeholder="Search {searchMode === 'files' ? 'files...' : 'code...'}"
|
|
489
|
+
class="w-full pl-3 pr-16 py-1.5 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
|
|
490
|
+
/>
|
|
491
|
+
<div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
|
|
492
|
+
{#if submittedQuery}
|
|
493
|
+
<button
|
|
494
|
+
onclick={clearSearch}
|
|
495
|
+
class="p-0.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
|
|
496
|
+
title="Clear search"
|
|
497
|
+
>
|
|
498
|
+
<Icon name="lucide:x" class="w-3.5 h-3.5" />
|
|
499
|
+
</button>
|
|
500
|
+
{/if}
|
|
501
|
+
{#if isSearching}
|
|
502
|
+
<button
|
|
503
|
+
onclick={cancelSearch}
|
|
504
|
+
class="px-1.5 py-0.5 text-xs font-medium rounded transition-colors bg-red-600 text-white hover:bg-red-700"
|
|
505
|
+
title="Cancel search"
|
|
506
|
+
>
|
|
507
|
+
<Icon name="lucide:circle-stop" class="w-3.5 h-3.5" />
|
|
508
|
+
</button>
|
|
509
|
+
{:else}
|
|
510
|
+
<button
|
|
511
|
+
onclick={performSearch}
|
|
512
|
+
disabled={!searchQuery.trim()}
|
|
513
|
+
class="px-1.5 py-0.5 text-xs font-medium rounded transition-colors {searchQuery.trim() ? 'bg-violet-600 text-white hover:bg-violet-700' : 'bg-slate-300 dark:bg-slate-700 text-slate-500 dark:text-slate-400 cursor-not-allowed'}"
|
|
514
|
+
title="Search (Enter)"
|
|
515
|
+
>
|
|
516
|
+
<Icon name="lucide:arrow-right" class="w-3.5 h-3.5" />
|
|
517
|
+
</button>
|
|
518
|
+
{/if}
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<!-- Replace input (code mode only) -->
|
|
523
|
+
{#if searchMode === 'code' && showReplace}
|
|
524
|
+
<div class="relative mb-2">
|
|
525
|
+
<input
|
|
526
|
+
bind:value={replaceQuery}
|
|
527
|
+
type="text"
|
|
528
|
+
placeholder="Replace with..."
|
|
529
|
+
class="w-full pl-3 pr-16 py-1.5 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
|
|
530
|
+
/>
|
|
531
|
+
<div class="absolute right-2 top-1/2 -translate-y-1/2">
|
|
532
|
+
<button
|
|
533
|
+
onclick={handleReplaceAll}
|
|
534
|
+
disabled={!submittedQuery || isReplacing}
|
|
535
|
+
class="p-0.5 rounded transition-colors {submittedQuery && !isReplacing ? 'text-orange-600 dark:text-orange-400 hover:bg-orange-50 dark:hover:bg-orange-900/30' : 'text-slate-400 dark:text-slate-600 cursor-not-allowed'}"
|
|
536
|
+
title="Replace All"
|
|
537
|
+
>
|
|
538
|
+
{#if isReplacing}
|
|
539
|
+
<div class="w-3.5 h-3.5 border-2 border-orange-600 border-t-transparent rounded-full animate-spin"></div>
|
|
540
|
+
{:else}
|
|
541
|
+
<Icon name="lucide:replace-all" class="w-3.5 h-3.5" />
|
|
542
|
+
{/if}
|
|
543
|
+
</button>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
{/if}
|
|
547
|
+
|
|
548
|
+
<!-- Search Mode Toggle & Options -->
|
|
549
|
+
<div class="flex items-center gap-2">
|
|
550
|
+
<div class="flex bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md overflow-hidden">
|
|
551
|
+
<button
|
|
552
|
+
onclick={() => switchSearchMode('files')}
|
|
553
|
+
class="px-3 py-1 text-xs font-medium transition-colors {searchMode === 'files' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
554
|
+
>
|
|
555
|
+
Files
|
|
556
|
+
</button>
|
|
557
|
+
<button
|
|
558
|
+
onclick={() => switchSearchMode('code')}
|
|
559
|
+
class="px-3 py-1 text-xs font-medium transition-colors {searchMode === 'code' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
560
|
+
>
|
|
561
|
+
Code
|
|
562
|
+
</button>
|
|
563
|
+
</div>
|
|
564
|
+
|
|
565
|
+
{#if searchMode === 'code'}
|
|
566
|
+
<div class="flex items-center gap-1 ml-auto">
|
|
567
|
+
<button
|
|
568
|
+
onclick={() => { caseSensitive = !caseSensitive; }}
|
|
569
|
+
class="font-medium text-xs w-6 flex p-1 rounded transition-colors {caseSensitive ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
570
|
+
title="Match Case"
|
|
571
|
+
>
|
|
572
|
+
Aa
|
|
573
|
+
</button>
|
|
574
|
+
<button
|
|
575
|
+
onclick={() => { wholeWord = !wholeWord; }}
|
|
576
|
+
class="underline underline-offset-2 font-medium text-xs w-6 flex p-1 rounded transition-colors {wholeWord ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
577
|
+
title="Match Whole Word"
|
|
578
|
+
>
|
|
579
|
+
Ab
|
|
580
|
+
</button>
|
|
581
|
+
<button
|
|
582
|
+
onclick={() => { useRegex = !useRegex; }}
|
|
583
|
+
class="flex p-1 rounded transition-colors {useRegex ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
584
|
+
title="Use Regular Expression"
|
|
585
|
+
>
|
|
586
|
+
<Icon name="lucide:regex" class="w-3.5 h-3.5" />
|
|
587
|
+
</button>
|
|
588
|
+
<button
|
|
589
|
+
onclick={() => { showReplace = !showReplace; }}
|
|
590
|
+
class="flex p-1 rounded transition-colors {showReplace ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
591
|
+
title="Toggle Replace"
|
|
592
|
+
>
|
|
593
|
+
<Icon name="lucide:replace" class="w-3.5 h-3.5" />
|
|
594
|
+
</button>
|
|
595
|
+
<button
|
|
596
|
+
onclick={() => { showFilters = !showFilters; }}
|
|
597
|
+
class="flex p-1 rounded transition-colors {showFilters ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
|
|
598
|
+
title="Toggle File Filters"
|
|
599
|
+
>
|
|
600
|
+
<Icon name="lucide:filter" class="w-3.5 h-3.5" />
|
|
601
|
+
</button>
|
|
602
|
+
</div>
|
|
603
|
+
{/if}
|
|
604
|
+
</div>
|
|
605
|
+
|
|
606
|
+
<!-- File filters (code mode only) - use hidden to preserve input values -->
|
|
607
|
+
<div class="mt-2 space-y-1.5" class:hidden={!(searchMode === 'code' && showFilters)}>
|
|
608
|
+
<input
|
|
609
|
+
bind:value={filesToInclude}
|
|
610
|
+
type="text"
|
|
611
|
+
placeholder="files to include (e.g. *.ts, src/)"
|
|
612
|
+
class="w-full px-2.5 py-1 text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring-1 focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
|
|
613
|
+
/>
|
|
614
|
+
<input
|
|
615
|
+
bind:value={filesToExclude}
|
|
616
|
+
type="text"
|
|
617
|
+
placeholder="files to exclude (e.g. *.min.js, *.map)"
|
|
618
|
+
class="w-full px-2.5 py-1 text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring-1 focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
|
|
619
|
+
/>
|
|
620
|
+
</div>
|
|
621
|
+
</div>
|
|
622
|
+
{/if}
|
|
623
|
+
|
|
624
|
+
<!-- Search Results OR File Tree -->
|
|
625
|
+
{#if searchVisible}
|
|
626
|
+
<!-- When search is visible, always show search area (no file tree) -->
|
|
627
|
+
{#if submittedQuery || isSearching}
|
|
628
|
+
<div class="flex-1 overflow-hidden">
|
|
629
|
+
<SearchResults
|
|
630
|
+
mode={searchMode}
|
|
631
|
+
query={submittedQuery}
|
|
632
|
+
fileResults={fileSearchResults}
|
|
633
|
+
codeResults={codeSearchResults}
|
|
634
|
+
isLoading={isSearching}
|
|
635
|
+
{useRegex}
|
|
636
|
+
onFileClick={handleFileClick}
|
|
637
|
+
onCodeMatchClick={handleCodeMatchClick}
|
|
638
|
+
/>
|
|
639
|
+
</div>
|
|
640
|
+
{:else}
|
|
641
|
+
<div class="flex-1 flex flex-col items-center justify-center gap-3 text-slate-500 dark:text-slate-400 px-6">
|
|
642
|
+
<Icon name="lucide:search" class="w-8 h-8 opacity-40" />
|
|
643
|
+
<p class="text-sm text-center">Enter a search query to find {searchMode === 'files' ? 'files' : 'code'}</p>
|
|
644
|
+
</div>
|
|
645
|
+
{/if}
|
|
646
|
+
{:else}
|
|
647
|
+
<div class="overflow-auto flex-1 p-2 select-none">
|
|
648
|
+
{#if files.length === 0}
|
|
649
|
+
<div class="text-center py-12">
|
|
650
|
+
<div class="bg-slate-100 dark:bg-slate-800 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
|
|
651
|
+
<Icon name="lucide:file-text" class="w-8 h-8 text-slate-400" />
|
|
652
|
+
</div>
|
|
653
|
+
<p class="text-sm font-semibold text-slate-600 dark:text-slate-300">No files in project</p>
|
|
654
|
+
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
|
655
|
+
Create a file or folder to get started
|
|
656
|
+
</p>
|
|
657
|
+
</div>
|
|
658
|
+
{:else}
|
|
659
|
+
<div class="space-y-1 h-0">
|
|
660
|
+
{#each files as file (file.path)}
|
|
661
|
+
<FileNode
|
|
662
|
+
{file}
|
|
663
|
+
isSelected={activeFilePath ? file.path === activeFilePath : selectedFile?.path === file.path}
|
|
664
|
+
isExpanded={localExpandedFolders.has(file.path)}
|
|
665
|
+
isModified={modifiedFiles.has(file.path)}
|
|
666
|
+
{openMenuPath}
|
|
667
|
+
expandedFolders={localExpandedFolders}
|
|
668
|
+
onSelect={handleFileSelect}
|
|
669
|
+
onAction={handleFileAction}
|
|
670
|
+
onToggle={toggleFolder}
|
|
671
|
+
onMenuToggle={handleMenuToggle}
|
|
672
|
+
{hasClipboard}
|
|
673
|
+
{modifiedFiles}
|
|
674
|
+
{activeFilePath}
|
|
675
|
+
/>
|
|
676
|
+
{/each}
|
|
677
|
+
</div>
|
|
678
|
+
{/if}
|
|
679
|
+
</div>
|
|
680
|
+
{/if}
|
|
681
|
+
</div>
|