@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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tunnel Store
|
|
3
|
+
* Manages cloudflared tunnel state for projects
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { debug } from '$shared/utils/logger';
|
|
7
|
+
import ws from '$frontend/lib/utils/ws';
|
|
8
|
+
|
|
9
|
+
type TunnelProgress =
|
|
10
|
+
| { stage: 'idle' }
|
|
11
|
+
| { stage: 'checking-binary' }
|
|
12
|
+
| { stage: 'downloading-binary'; progress?: number }
|
|
13
|
+
| { stage: 'binary-ready' }
|
|
14
|
+
| { stage: 'starting-tunnel' }
|
|
15
|
+
| { stage: 'generating-url' }
|
|
16
|
+
| { stage: 'connected' }
|
|
17
|
+
| { stage: 'failed'; error: string };
|
|
18
|
+
|
|
19
|
+
interface TunnelInfo {
|
|
20
|
+
port: number;
|
|
21
|
+
publicUrl: string;
|
|
22
|
+
startedAt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PortState {
|
|
26
|
+
isLoading: boolean;
|
|
27
|
+
error: string | null;
|
|
28
|
+
progress: TunnelProgress;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface TunnelState {
|
|
32
|
+
tunnels: TunnelInfo[];
|
|
33
|
+
portStates: Record<number, PortState>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Tunnel store state
|
|
37
|
+
const tunnelState = $state<TunnelState>({
|
|
38
|
+
tunnels: [],
|
|
39
|
+
portStates: {}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const tunnelStore = {
|
|
43
|
+
get tunnels() {
|
|
44
|
+
return tunnelState.tunnels;
|
|
45
|
+
},
|
|
46
|
+
isLoading(port: number) {
|
|
47
|
+
return tunnelState.portStates[port]?.isLoading ?? false;
|
|
48
|
+
},
|
|
49
|
+
getError(port: number) {
|
|
50
|
+
return tunnelState.portStates[port]?.error ?? null;
|
|
51
|
+
},
|
|
52
|
+
getProgress(port: number) {
|
|
53
|
+
return tunnelState.portStates[port]?.progress ?? { stage: 'idle' };
|
|
54
|
+
},
|
|
55
|
+
getTunnel(port: number) {
|
|
56
|
+
return tunnelState.tunnels.find((t) => t.port === port) || null;
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Start tunnel globally
|
|
61
|
+
* Note: First time may take 30-90 seconds (downloading binary + starting tunnel)
|
|
62
|
+
*/
|
|
63
|
+
async startTunnel(port: number, autoStopMinutes?: number) {
|
|
64
|
+
// Initialize port state
|
|
65
|
+
tunnelState.portStates[port] = {
|
|
66
|
+
isLoading: true,
|
|
67
|
+
error: null,
|
|
68
|
+
progress: { stage: 'starting-tunnel' }
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
debug.log('tunnel', `[Frontend] Starting tunnel for port ${port}...`);
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Use HTTP pattern directly
|
|
75
|
+
const result = await ws.http('tunnel:start', { port, autoStopMinutes });
|
|
76
|
+
|
|
77
|
+
if (!result || !result.publicUrl) {
|
|
78
|
+
throw new Error('No result received from server');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Add tunnel to the list
|
|
82
|
+
tunnelState.tunnels.push({
|
|
83
|
+
port,
|
|
84
|
+
publicUrl: result.publicUrl,
|
|
85
|
+
startedAt: new Date().toISOString()
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
tunnelState.portStates[port].progress = { stage: 'connected' };
|
|
89
|
+
tunnelState.portStates[port].isLoading = false;
|
|
90
|
+
|
|
91
|
+
debug.log('tunnel', `[Frontend] ✅ Tunnel started successfully on port ${port}:`, result.publicUrl);
|
|
92
|
+
if (result.timings) {
|
|
93
|
+
debug.log('tunnel', '[Frontend] Timings:', result.timings);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Reset progress after a short delay
|
|
97
|
+
setTimeout(() => {
|
|
98
|
+
if (tunnelState.portStates[port]) {
|
|
99
|
+
tunnelState.portStates[port].progress = { stage: 'idle' };
|
|
100
|
+
}
|
|
101
|
+
}, 1500);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
tunnelState.portStates[port].error = error.message;
|
|
105
|
+
tunnelState.portStates[port].progress = { stage: 'failed', error: error.message };
|
|
106
|
+
debug.error('tunnel', '[Frontend] Error:', error.message);
|
|
107
|
+
} else {
|
|
108
|
+
const errorMsg = 'Unknown error';
|
|
109
|
+
tunnelState.portStates[port].error = errorMsg;
|
|
110
|
+
tunnelState.portStates[port].progress = { stage: 'failed', error: errorMsg };
|
|
111
|
+
debug.error('tunnel', '[Frontend] Unknown error:', error);
|
|
112
|
+
}
|
|
113
|
+
tunnelState.portStates[port].isLoading = false;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Stop tunnel for a port
|
|
120
|
+
*/
|
|
121
|
+
async stopTunnel(port: number) {
|
|
122
|
+
try {
|
|
123
|
+
const response = await ws.http('tunnel:stop', { port });
|
|
124
|
+
|
|
125
|
+
if (!response.stopped) {
|
|
126
|
+
throw new Error('Failed to stop tunnel');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Remove tunnel from the list
|
|
130
|
+
tunnelState.tunnels = tunnelState.tunnels.filter((t) => t.port !== port);
|
|
131
|
+
delete tunnelState.portStates[port];
|
|
132
|
+
|
|
133
|
+
debug.log('tunnel', `Tunnel stopped on port ${port}`);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
debug.error('tunnel', 'Failed to stop tunnel:', error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check tunnel status globally
|
|
142
|
+
*/
|
|
143
|
+
async checkStatus() {
|
|
144
|
+
try {
|
|
145
|
+
const response = await ws.http('tunnel:status', {});
|
|
146
|
+
|
|
147
|
+
// Update tunnels list from server
|
|
148
|
+
tunnelState.tunnels = response.tunnels || [];
|
|
149
|
+
} catch (error) {
|
|
150
|
+
debug.error('tunnel', 'Failed to check tunnel status:', error);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Reset tunnel state
|
|
156
|
+
*/
|
|
157
|
+
reset() {
|
|
158
|
+
tunnelState.tunnels = [];
|
|
159
|
+
tunnelState.portStates = {};
|
|
160
|
+
}
|
|
161
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Store - Svelte 5 Runes
|
|
3
|
+
* Manages anonymous user state and provides reactive updates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { getOrCreateAnonymousUser, updateAnonymousUserName, getCurrentAnonymousUser, type AnonymousUser } from '$shared/utils/anonymous-user';
|
|
7
|
+
import { debug } from '$shared/utils/logger';
|
|
8
|
+
import ws from '$frontend/lib/utils/ws';
|
|
9
|
+
|
|
10
|
+
// User state - initialize with null, will be properly set after async load
|
|
11
|
+
let currentUser = $state<AnonymousUser | null>(null);
|
|
12
|
+
let isInitializing = $state<boolean>(false);
|
|
13
|
+
|
|
14
|
+
// User store
|
|
15
|
+
export const userStore = {
|
|
16
|
+
get currentUser() {
|
|
17
|
+
return currentUser;
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
get isInitializing() {
|
|
21
|
+
return isInitializing;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Initialize user (called on app start)
|
|
25
|
+
async initialize() {
|
|
26
|
+
if (typeof window === 'undefined') {
|
|
27
|
+
debug.warn('user', 'Cannot initialize user on server side');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isInitializing) {
|
|
32
|
+
debug.warn('user', 'User initialization already in progress');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
isInitializing = true;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// First check if user already exists in localStorage (fast path)
|
|
40
|
+
const existingUser = getCurrentAnonymousUser();
|
|
41
|
+
if (existingUser) {
|
|
42
|
+
currentUser = existingUser;
|
|
43
|
+
// Sync user context with WebSocket (for user-targeted broadcasting)
|
|
44
|
+
// IMPORTANT: Must await to ensure server has context before other operations
|
|
45
|
+
await ws.setUser(existingUser.id);
|
|
46
|
+
debug.log('user', '✅ Loaded existing user from localStorage:', existingUser.name);
|
|
47
|
+
} else {
|
|
48
|
+
// Generate new user from server
|
|
49
|
+
debug.log('user', 'No existing user, generating from server...');
|
|
50
|
+
const newUser = await getOrCreateAnonymousUser();
|
|
51
|
+
currentUser = newUser;
|
|
52
|
+
// Sync user context with WebSocket
|
|
53
|
+
// IMPORTANT: Must await to ensure server has context before other operations
|
|
54
|
+
if (newUser) {
|
|
55
|
+
await ws.setUser(newUser.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
debug.error('user', 'Failed to initialize user:', error);
|
|
60
|
+
} finally {
|
|
61
|
+
isInitializing = false;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Update user name
|
|
66
|
+
async updateName(newName: string): Promise<boolean> {
|
|
67
|
+
if (typeof window === 'undefined') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const updatedUser = await updateAnonymousUserName(newName);
|
|
73
|
+
|
|
74
|
+
if (updatedUser) {
|
|
75
|
+
currentUser = updatedUser;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
debug.error('user', 'Failed to update user name:', error);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Refresh user from localStorage
|
|
87
|
+
refresh() {
|
|
88
|
+
if (typeof window !== 'undefined') {
|
|
89
|
+
const user = getCurrentAnonymousUser();
|
|
90
|
+
if (user) {
|
|
91
|
+
currentUser = user;
|
|
92
|
+
debug.log('user', '✅ Refreshed user from localStorage:', user.name);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Input State Store
|
|
3
|
+
* Manages the chat input text and focus state
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
interface ChatInputState {
|
|
7
|
+
text: string;
|
|
8
|
+
shouldFocus: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Create reactive state
|
|
12
|
+
const state = $state<ChatInputState>({
|
|
13
|
+
text: '',
|
|
14
|
+
shouldFocus: false
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set input text and optionally focus
|
|
19
|
+
*/
|
|
20
|
+
export function setInputText(text: string, focus: boolean = true) {
|
|
21
|
+
state.text = text;
|
|
22
|
+
state.shouldFocus = focus;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Clear input text
|
|
27
|
+
*/
|
|
28
|
+
export function clearInput() {
|
|
29
|
+
state.text = '';
|
|
30
|
+
state.shouldFocus = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Reset focus flag after focusing
|
|
35
|
+
*/
|
|
36
|
+
export function resetFocus() {
|
|
37
|
+
state.shouldFocus = false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Flag to skip next server restore (prevents stale input restoration
|
|
41
|
+
// when ChatInput is remounted during welcome→chat transition)
|
|
42
|
+
let _skipNextRestore = false;
|
|
43
|
+
|
|
44
|
+
export function setSkipNextRestore(skip: boolean) {
|
|
45
|
+
_skipNextRestore = skip;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function shouldSkipRestore(): boolean {
|
|
49
|
+
if (_skipNextRestore) {
|
|
50
|
+
_skipNextRestore = false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Export reactive state
|
|
57
|
+
export const chatInputState = state;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat Model State Store
|
|
3
|
+
*
|
|
4
|
+
* Holds the local engine/model selection for the chat input.
|
|
5
|
+
* Isolated from Settings — Settings only provides the initial default.
|
|
6
|
+
* Changes here do NOT persist to Settings, and Settings changes do NOT
|
|
7
|
+
* affect the current session's selection after the first message is sent.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { DEFAULT_ENGINE, DEFAULT_MODEL } from '$shared/constants/engines';
|
|
11
|
+
import type { EngineType } from '$shared/types/engine';
|
|
12
|
+
|
|
13
|
+
interface ChatModelState {
|
|
14
|
+
engine: EngineType;
|
|
15
|
+
model: string;
|
|
16
|
+
engineModelMemory: Record<string, string>;
|
|
17
|
+
claudeAccountId: number | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Local reactive state — starts from compile-time defaults.
|
|
21
|
+
// Initialized from Settings on each new session via initChatModel().
|
|
22
|
+
export const chatModelState = $state<ChatModelState>({
|
|
23
|
+
engine: DEFAULT_ENGINE,
|
|
24
|
+
model: DEFAULT_MODEL,
|
|
25
|
+
engineModelMemory: { 'claude-code': DEFAULT_MODEL },
|
|
26
|
+
claudeAccountId: null
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialize the local chat model state from Settings defaults.
|
|
31
|
+
* Called when a new session starts (no messages yet).
|
|
32
|
+
*/
|
|
33
|
+
export function initChatModel(
|
|
34
|
+
engine: EngineType,
|
|
35
|
+
model: string,
|
|
36
|
+
memory: Record<string, string>
|
|
37
|
+
): void {
|
|
38
|
+
chatModelState.engine = engine;
|
|
39
|
+
chatModelState.model = model;
|
|
40
|
+
chatModelState.engineModelMemory = { ...memory };
|
|
41
|
+
// claudeAccountId will be set by EngineModelPicker after fetching accounts
|
|
42
|
+
chatModelState.claudeAccountId = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Restore the local chat model state from a session's persisted engine/model.
|
|
47
|
+
* Called when continuing an existing session (has messages).
|
|
48
|
+
* IMPORTANT: Must NOT read from chatModelState to avoid circular tracking in $effect.
|
|
49
|
+
*/
|
|
50
|
+
export function restoreChatModelFromSession(
|
|
51
|
+
engine: EngineType,
|
|
52
|
+
model: string,
|
|
53
|
+
claudeAccountId?: number | null
|
|
54
|
+
): void {
|
|
55
|
+
chatModelState.engine = engine;
|
|
56
|
+
chatModelState.model = model;
|
|
57
|
+
// Only set the current engine's model — avoids reading chatModelState.engineModelMemory
|
|
58
|
+
// which would cause UpdatedAtError in Svelte 5 $effect tracking
|
|
59
|
+
chatModelState.engineModelMemory = { [engine]: model };
|
|
60
|
+
chatModelState.claudeAccountId = claudeAccountId ?? null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AlertOptions, ConfirmOptions, DialogState } from '$shared/types/stores/dialog';
|
|
2
|
+
|
|
3
|
+
const dialogState = $state<DialogState>({
|
|
4
|
+
alert: {
|
|
5
|
+
isOpen: false,
|
|
6
|
+
options: {
|
|
7
|
+
message: ''
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
confirm: {
|
|
11
|
+
isOpen: false,
|
|
12
|
+
options: {
|
|
13
|
+
message: ''
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const showAlert = (options: AlertOptions): Promise<void> => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
dialogState.alert = {
|
|
21
|
+
isOpen: true,
|
|
22
|
+
options,
|
|
23
|
+
resolve
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const showConfirm = (options: ConfirmOptions): Promise<boolean> => {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
dialogState.confirm = {
|
|
31
|
+
isOpen: true,
|
|
32
|
+
options,
|
|
33
|
+
resolve
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const closeAlert = () => {
|
|
39
|
+
if (dialogState.alert.resolve) {
|
|
40
|
+
dialogState.alert.resolve();
|
|
41
|
+
}
|
|
42
|
+
dialogState.alert.isOpen = false;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const closeConfirm = (confirmed: boolean) => {
|
|
46
|
+
if (dialogState.confirm.resolve) {
|
|
47
|
+
dialogState.confirm.resolve(confirmed);
|
|
48
|
+
}
|
|
49
|
+
dialogState.confirm.isOpen = false;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const dialogStore = {
|
|
53
|
+
get alert() {
|
|
54
|
+
return dialogState.alert;
|
|
55
|
+
},
|
|
56
|
+
get confirm() {
|
|
57
|
+
return dialogState.confirm;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit Mode State Store
|
|
3
|
+
* Manages message editing state with collaborative sync
|
|
4
|
+
* Server is single source of truth - no client storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import ws from '$frontend/lib/utils/ws';
|
|
8
|
+
import { userStore } from '$frontend/lib/stores/features/user.svelte';
|
|
9
|
+
import { sessionState } from '$frontend/lib/stores/core/sessions.svelte';
|
|
10
|
+
import { debug } from '$shared/utils/logger';
|
|
11
|
+
|
|
12
|
+
export interface EditAttachment {
|
|
13
|
+
type: 'image' | 'document';
|
|
14
|
+
data: string; // base64
|
|
15
|
+
mediaType: string;
|
|
16
|
+
fileName: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface EditModeState {
|
|
20
|
+
isEditing: boolean;
|
|
21
|
+
messageId: string | null;
|
|
22
|
+
messageText: string;
|
|
23
|
+
messageTimestamp: string | null;
|
|
24
|
+
attachments: EditAttachment[];
|
|
25
|
+
parentMessageId: string | null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Create reactive state
|
|
29
|
+
const state = $state<EditModeState>({
|
|
30
|
+
isEditing: false,
|
|
31
|
+
messageId: null,
|
|
32
|
+
messageText: '',
|
|
33
|
+
messageTimestamp: null,
|
|
34
|
+
attachments: [],
|
|
35
|
+
parentMessageId: null
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// BROADCAST
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
function broadcastEditMode(isEditing: boolean, messageId: string | null, messageTimestamp: string | null) {
|
|
43
|
+
const currentUserId = userStore.currentUser?.id;
|
|
44
|
+
const chatSessionId = sessionState.currentSession?.id;
|
|
45
|
+
if (!currentUserId || !chatSessionId) return;
|
|
46
|
+
|
|
47
|
+
ws.emit('chat:edit-mode', {
|
|
48
|
+
senderId: currentUserId,
|
|
49
|
+
chatSessionId,
|
|
50
|
+
isEditing,
|
|
51
|
+
messageId,
|
|
52
|
+
messageTimestamp
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// PUBLIC API
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Enter edit mode for a message
|
|
62
|
+
*/
|
|
63
|
+
export function startEdit(
|
|
64
|
+
messageId: string,
|
|
65
|
+
messageText: string,
|
|
66
|
+
messageTimestamp: string,
|
|
67
|
+
attachments: EditAttachment[] = [],
|
|
68
|
+
parentMessageId: string | null = null
|
|
69
|
+
) {
|
|
70
|
+
state.isEditing = true;
|
|
71
|
+
state.messageId = messageId;
|
|
72
|
+
state.messageText = messageText;
|
|
73
|
+
state.messageTimestamp = messageTimestamp;
|
|
74
|
+
state.attachments = attachments;
|
|
75
|
+
state.parentMessageId = parentMessageId;
|
|
76
|
+
|
|
77
|
+
broadcastEditMode(true, messageId, messageTimestamp);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cancel edit mode
|
|
82
|
+
*/
|
|
83
|
+
export function cancelEdit() {
|
|
84
|
+
state.isEditing = false;
|
|
85
|
+
state.messageId = null;
|
|
86
|
+
state.messageText = '';
|
|
87
|
+
state.messageTimestamp = null;
|
|
88
|
+
state.attachments = [];
|
|
89
|
+
state.parentMessageId = null;
|
|
90
|
+
|
|
91
|
+
broadcastEditMode(false, null, null);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if a message is after the edit point
|
|
96
|
+
*/
|
|
97
|
+
export function isMessageAfterEditPoint(messageTimestamp: string): boolean {
|
|
98
|
+
if (!state.isEditing || !state.messageTimestamp) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return messageTimestamp > state.messageTimestamp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if a message is the one being edited
|
|
106
|
+
*/
|
|
107
|
+
export function isMessageBeingEdited(messageId: string | undefined): boolean {
|
|
108
|
+
if (!state.isEditing || !state.messageId || !messageId) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return messageId === state.messageId;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if a message should be dimmed (not the one being edited)
|
|
116
|
+
*/
|
|
117
|
+
export function shouldDimMessage(messageId: string | undefined): boolean {
|
|
118
|
+
if (!state.isEditing) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return !isMessageBeingEdited(messageId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// SERVER STATE MANAGEMENT
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Clear state without broadcasting (for project switching)
|
|
130
|
+
*/
|
|
131
|
+
function clearStateQuietly() {
|
|
132
|
+
state.isEditing = false;
|
|
133
|
+
state.messageId = null;
|
|
134
|
+
state.messageText = '';
|
|
135
|
+
state.messageTimestamp = null;
|
|
136
|
+
state.attachments = [];
|
|
137
|
+
state.parentMessageId = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Fetch edit mode state from server for the current project.
|
|
142
|
+
* Server is the single source of truth.
|
|
143
|
+
*/
|
|
144
|
+
export async function restoreEditMode() {
|
|
145
|
+
try {
|
|
146
|
+
const chatSessionId = sessionState.currentSession?.id || '';
|
|
147
|
+
const serverState = await ws.http('chat:get-edit-mode', { chatSessionId });
|
|
148
|
+
if (serverState && serverState.isEditing && serverState.messageId) {
|
|
149
|
+
state.isEditing = true;
|
|
150
|
+
state.messageId = serverState.messageId;
|
|
151
|
+
state.messageText = '';
|
|
152
|
+
state.messageTimestamp = serverState.messageTimestamp;
|
|
153
|
+
state.attachments = [];
|
|
154
|
+
state.parentMessageId = null;
|
|
155
|
+
} else {
|
|
156
|
+
clearStateQuietly();
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
clearStateQuietly();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Called when switching away from a project.
|
|
165
|
+
* Clears in-memory state without broadcasting (server retains per-project state).
|
|
166
|
+
*/
|
|
167
|
+
export function onProjectLeave() {
|
|
168
|
+
clearStateQuietly();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Called when entering a new project.
|
|
173
|
+
* Fetches edit mode state from server for the new project.
|
|
174
|
+
* MUST be called AFTER ws.setProject() has completed.
|
|
175
|
+
*/
|
|
176
|
+
export async function onProjectEnter() {
|
|
177
|
+
await restoreEditMode();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// COLLABORATIVE LISTENER
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Setup WebSocket listener for edit mode events from other users.
|
|
186
|
+
* Call once during app initialization.
|
|
187
|
+
*/
|
|
188
|
+
export function setupEditModeListener() {
|
|
189
|
+
ws.on('chat:edit-mode', (data: {
|
|
190
|
+
senderId: string;
|
|
191
|
+
isEditing: boolean;
|
|
192
|
+
messageId: string | null;
|
|
193
|
+
messageTimestamp: string | null;
|
|
194
|
+
}) => {
|
|
195
|
+
const currentUserId = userStore.currentUser?.id;
|
|
196
|
+
if (!currentUserId || data.senderId === currentUserId) return;
|
|
197
|
+
|
|
198
|
+
if (data.isEditing && data.messageId) {
|
|
199
|
+
debug.log('chat', `User ${data.senderId} started editing message ${data.messageId}`);
|
|
200
|
+
state.isEditing = true;
|
|
201
|
+
state.messageId = data.messageId;
|
|
202
|
+
state.messageText = '';
|
|
203
|
+
state.messageTimestamp = data.messageTimestamp;
|
|
204
|
+
state.attachments = [];
|
|
205
|
+
state.parentMessageId = null;
|
|
206
|
+
} else {
|
|
207
|
+
debug.log('chat', `User ${data.senderId} cancelled edit mode`);
|
|
208
|
+
clearStateQuietly();
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Export reactive state
|
|
214
|
+
export const editModeState = state;
|