@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,563 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { sessionState, setCurrentSession, removeSession, reloadSessionsForProject } from '$frontend/lib/stores/core/sessions.svelte';
|
|
4
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
5
|
+
import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
|
|
6
|
+
import ws from '$frontend/lib/utils/ws';
|
|
7
|
+
import type { ChatSession } from '$shared/types/database/schema';
|
|
8
|
+
import type { SDKMessage } from '$shared/types/messaging';
|
|
9
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
10
|
+
import AvatarBubble from '$frontend/lib/components/common/AvatarBubble.svelte';
|
|
11
|
+
import Modal from '$frontend/lib/components/common/Modal.svelte';
|
|
12
|
+
import Dialog from '$frontend/lib/components/common/Dialog.svelte';
|
|
13
|
+
import { presenceState } from '$frontend/lib/stores/core/presence.svelte';
|
|
14
|
+
import { userStore } from '$frontend/lib/stores/features/user.svelte';
|
|
15
|
+
import { debug } from '$shared/utils/logger';
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
isOpen: boolean;
|
|
19
|
+
onClose: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let { isOpen = $bindable(), onClose }: Props = $props();
|
|
23
|
+
|
|
24
|
+
// Use real session data from session store - filtered by current project
|
|
25
|
+
const sessions = $derived(
|
|
26
|
+
sessionState.sessions.filter(s => s.project_id === projectState.currentProject?.id)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// Helper to get relative time (last active)
|
|
30
|
+
function getRelativeTime(dateString: string): string {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const date = new Date(dateString).getTime();
|
|
33
|
+
const diffMs = now - date;
|
|
34
|
+
const diffMins = Math.floor(diffMs / 1000 / 60);
|
|
35
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
36
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
37
|
+
|
|
38
|
+
if (diffMins < 1) return 'Just now';
|
|
39
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
40
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
41
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
42
|
+
if (diffDays < 30) return `${Math.floor(diffDays / 7)}w ago`;
|
|
43
|
+
if (diffDays < 365) return `${Math.floor(diffDays / 30)}mo ago`;
|
|
44
|
+
return `${Math.floor(diffDays / 365)}y ago`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper to get last activity timestamp
|
|
48
|
+
function getLastActive(session: ChatSession): string {
|
|
49
|
+
const timestamp = session.ended_at && session.ended_at !== ''
|
|
50
|
+
? session.ended_at
|
|
51
|
+
: session.started_at;
|
|
52
|
+
return getRelativeTime(timestamp);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Cache for session data to avoid multiple API calls
|
|
56
|
+
let sessionDataCache = $state<Record<string, {
|
|
57
|
+
messages: SDKMessage[];
|
|
58
|
+
title: string;
|
|
59
|
+
summary: string;
|
|
60
|
+
count: number;
|
|
61
|
+
userCount: number;
|
|
62
|
+
assistantCount: number;
|
|
63
|
+
}>>({});
|
|
64
|
+
let loadingSessionData = $state(false);
|
|
65
|
+
|
|
66
|
+
// Helper to get session data from cache or API
|
|
67
|
+
async function getSessionData(sessionId: string) {
|
|
68
|
+
if (sessionDataCache[sessionId]) {
|
|
69
|
+
return sessionDataCache[sessionId];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const messages = await ws.http('messages:list', { session_id: sessionId });
|
|
74
|
+
|
|
75
|
+
const firstUserMessage = messages.find((m: SDKMessage) => m.type === 'user');
|
|
76
|
+
let title = 'New Conversation';
|
|
77
|
+
if (firstUserMessage) {
|
|
78
|
+
let textContent = '';
|
|
79
|
+
if (typeof firstUserMessage.message.content === 'string') {
|
|
80
|
+
textContent = firstUserMessage.message.content;
|
|
81
|
+
} else if (Array.isArray(firstUserMessage.message.content)) {
|
|
82
|
+
const textBlocks = firstUserMessage.message.content.filter((c: any) => c.type === 'text');
|
|
83
|
+
textContent = textBlocks.map((b: any) => 'text' in b ? b.text : '').join(' ');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (textContent) {
|
|
87
|
+
title = textContent.slice(0, 60) + (textContent.length > 60 ? '...' : '');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const assistantMessages = messages.filter((m: SDKMessage) => m.type === 'assistant');
|
|
92
|
+
let summary = 'No messages yet';
|
|
93
|
+
if (assistantMessages.length > 0) {
|
|
94
|
+
const lastMessage = assistantMessages[assistantMessages.length - 1];
|
|
95
|
+
const textBlocks = lastMessage.message.content.filter((c: any) => c.type === 'text');
|
|
96
|
+
if (textBlocks.length > 0) {
|
|
97
|
+
const fullText = textBlocks.map((b: any) => 'text' in b ? b.text : '').join(' ');
|
|
98
|
+
const cleanText = fullText.replace(/```[\s\S]*?```/g, '').trim();
|
|
99
|
+
summary = cleanText.slice(0, 100) + (cleanText.length > 100 ? '...' : '');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const userMessages = messages.filter((m: SDKMessage) => {
|
|
104
|
+
if (m.type !== 'user') return false;
|
|
105
|
+
let textContent = '';
|
|
106
|
+
if (typeof m.message.content === 'string') {
|
|
107
|
+
textContent = m.message.content;
|
|
108
|
+
} else if (Array.isArray(m.message.content)) {
|
|
109
|
+
const textBlocks = m.message.content.filter(c => c.type === 'text');
|
|
110
|
+
textContent = textBlocks.map(b => 'text' in b ? b.text : '').join(' ');
|
|
111
|
+
}
|
|
112
|
+
return textContent.trim().length > 0;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const totalBubbles = userMessages.length + assistantMessages.length;
|
|
116
|
+
|
|
117
|
+
const data = {
|
|
118
|
+
messages,
|
|
119
|
+
title,
|
|
120
|
+
summary,
|
|
121
|
+
count: totalBubbles,
|
|
122
|
+
userCount: userMessages.length,
|
|
123
|
+
assistantCount: assistantMessages.length
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
sessionDataCache[sessionId] = data;
|
|
127
|
+
debug.log('session', `Loaded session ${sessionId}:`, {
|
|
128
|
+
title,
|
|
129
|
+
totalMessages: messages.length,
|
|
130
|
+
userCount: userMessages.length,
|
|
131
|
+
assistantCount: assistantMessages.length,
|
|
132
|
+
totalBubbles: totalBubbles,
|
|
133
|
+
summary: summary.substring(0, 50)
|
|
134
|
+
});
|
|
135
|
+
return data;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
debug.error('session', 'Error fetching session data:', error);
|
|
138
|
+
return {
|
|
139
|
+
messages: [],
|
|
140
|
+
title: 'New Conversation',
|
|
141
|
+
summary: 'No messages yet',
|
|
142
|
+
count: 0,
|
|
143
|
+
userCount: 0,
|
|
144
|
+
assistantCount: 0
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getMessageCount(sessionId: string): number {
|
|
150
|
+
return sessionDataCache[sessionId]?.count || 0;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getUserMessageCount(sessionId: string): number {
|
|
154
|
+
return sessionDataCache[sessionId]?.userCount || 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getSessionTitle(sessionId: string): string {
|
|
158
|
+
return sessionDataCache[sessionId]?.title || 'New Conversation';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getSessionSummary(sessionId: string): string {
|
|
162
|
+
return sessionDataCache[sessionId]?.summary || 'No messages yet';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function preloadSessionData() {
|
|
166
|
+
loadingSessionData = true;
|
|
167
|
+
try {
|
|
168
|
+
// Sort newest first and load top 20 so new sessions are always included
|
|
169
|
+
const sortedSessions = [...sessions]
|
|
170
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
|
|
171
|
+
.slice(0, 20);
|
|
172
|
+
await Promise.all(
|
|
173
|
+
sortedSessions.map(session => getSessionData(session.id))
|
|
174
|
+
);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
debug.error('session', 'Error preloading session data:', error);
|
|
177
|
+
} finally {
|
|
178
|
+
loadingSessionData = false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Refresh sessions from server and reload data each time the modal opens.
|
|
183
|
+
// Uses untrack() so only isOpen is tracked — prevents infinite loops from
|
|
184
|
+
// session/cache mutations re-triggering this effect.
|
|
185
|
+
$effect(() => {
|
|
186
|
+
if (isOpen) {
|
|
187
|
+
untrack(() => {
|
|
188
|
+
// Clear stale cache so all data is fetched fresh
|
|
189
|
+
sessionDataCache = {};
|
|
190
|
+
// Fetch latest sessions from server, then load message data
|
|
191
|
+
reloadSessionsForProject().then(() => {
|
|
192
|
+
preloadSessionData();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Incrementally load new sessions that arrive while the modal is open
|
|
199
|
+
// (e.g., from sessions:session-available broadcasts). No spinner — background load.
|
|
200
|
+
$effect(() => {
|
|
201
|
+
if (!isOpen || loadingSessionData) return;
|
|
202
|
+
const currentSessions = sessions;
|
|
203
|
+
if (currentSessions.length === 0) return;
|
|
204
|
+
|
|
205
|
+
const uncached = currentSessions.filter(s => !sessionDataCache[s.id]);
|
|
206
|
+
if (uncached.length > 0) {
|
|
207
|
+
debug.log('session', `Found ${uncached.length} new sessions, loading data...`);
|
|
208
|
+
// Load individually without showing spinner
|
|
209
|
+
Promise.all(uncached.map(s => getSessionData(s.id)));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Track streaming sessions to detect when a stream completes.
|
|
214
|
+
// When a session stops streaming, refresh its cache to show the new summary.
|
|
215
|
+
let previouslyStreamingIds = new Set<string>();
|
|
216
|
+
|
|
217
|
+
$effect(() => {
|
|
218
|
+
if (!isOpen) return;
|
|
219
|
+
|
|
220
|
+
// Read presence state to detect stream changes
|
|
221
|
+
const projectId = projectState.currentProject?.id;
|
|
222
|
+
if (!projectId) return;
|
|
223
|
+
const status = presenceState.statuses.get(projectId);
|
|
224
|
+
const activeStreams = status?.streams?.filter((s: any) => s.status === 'active') || [];
|
|
225
|
+
const currentlyStreamingIds = new Set(activeStreams.map((s: any) => s.chatSessionId));
|
|
226
|
+
|
|
227
|
+
// Sessions that stopped streaming → refresh their cache for updated summary
|
|
228
|
+
for (const sessionId of previouslyStreamingIds) {
|
|
229
|
+
if (!currentlyStreamingIds.has(sessionId)) {
|
|
230
|
+
delete sessionDataCache[sessionId];
|
|
231
|
+
getSessionData(sessionId);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
previouslyStreamingIds = currentlyStreamingIds;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Get users in a specific chat session (excluding self)
|
|
239
|
+
function getSessionUsers(chatSessionId: string): { userId: string; userName: string }[] {
|
|
240
|
+
const projectId = projectState.currentProject?.id;
|
|
241
|
+
const currentUserId = userStore.currentUser?.id;
|
|
242
|
+
if (!projectId) return [];
|
|
243
|
+
const status = presenceState.statuses.get(projectId);
|
|
244
|
+
if (!status?.chatSessionUsers) return [];
|
|
245
|
+
const users = status.chatSessionUsers[chatSessionId] || [];
|
|
246
|
+
return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check if a session has an active stream
|
|
250
|
+
function isSessionStreaming(chatSessionId: string): boolean {
|
|
251
|
+
const projectId = projectState.currentProject?.id;
|
|
252
|
+
if (!projectId) return false;
|
|
253
|
+
const status = presenceState.statuses.get(projectId);
|
|
254
|
+
if (!status?.streams) return false;
|
|
255
|
+
return status.streams.some(
|
|
256
|
+
(s: any) => s.status === 'active' && s.chatSessionId === chatSessionId
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Get the engine/model display for a session
|
|
261
|
+
function getSessionModel(session: ChatSession): string {
|
|
262
|
+
if (session.engine && session.model) {
|
|
263
|
+
const parts = session.model.split(':');
|
|
264
|
+
return parts.length > 1 ? parts[1] : session.model;
|
|
265
|
+
}
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let searchQuery = $state('');
|
|
270
|
+
|
|
271
|
+
const filteredSessions = $derived(
|
|
272
|
+
sessions
|
|
273
|
+
.filter(session => {
|
|
274
|
+
const title = getSessionTitle(session.id);
|
|
275
|
+
const summary = getSessionSummary(session.id);
|
|
276
|
+
const messageCount = getMessageCount(session.id);
|
|
277
|
+
|
|
278
|
+
// Show sessions that are currently streaming even if 0 messages yet
|
|
279
|
+
if (messageCount === 0 && !isSessionStreaming(session.id)) {
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const matchesSearch = searchQuery === '' ||
|
|
284
|
+
title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
|
285
|
+
summary.toLowerCase().includes(searchQuery.toLowerCase());
|
|
286
|
+
|
|
287
|
+
return matchesSearch;
|
|
288
|
+
})
|
|
289
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
function isActiveSession(session: ChatSession): boolean {
|
|
293
|
+
return sessionState.currentSession?.id === session.id;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function resumeSession(session: ChatSession | null) {
|
|
297
|
+
if (!session) return;
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
let targetSession = session;
|
|
301
|
+
if (session.ended_at) {
|
|
302
|
+
const reactivatedSession = await ws.http('sessions:update', { id: session.id, reactivate: true });
|
|
303
|
+
if (reactivatedSession) {
|
|
304
|
+
const sessionIndex = sessionState.sessions.findIndex(s => s.id === session.id);
|
|
305
|
+
if (sessionIndex !== -1) {
|
|
306
|
+
sessionState.sessions[sessionIndex] = reactivatedSession;
|
|
307
|
+
}
|
|
308
|
+
targetSession = reactivatedSession;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
await setCurrentSession(targetSession);
|
|
313
|
+
onClose();
|
|
314
|
+
} catch (error) {
|
|
315
|
+
debug.error('session', 'Error resuming session:', error);
|
|
316
|
+
addNotification({
|
|
317
|
+
type: 'error',
|
|
318
|
+
title: 'Resume Failed',
|
|
319
|
+
message: 'Failed to resume session',
|
|
320
|
+
duration: 5000
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Delete session state
|
|
326
|
+
let showDeleteDialog = $state(false);
|
|
327
|
+
let sessionToDelete = $state<ChatSession | null>(null);
|
|
328
|
+
|
|
329
|
+
function handleDeleteClick(session: ChatSession, event: MouseEvent) {
|
|
330
|
+
event.stopPropagation();
|
|
331
|
+
sessionToDelete = session;
|
|
332
|
+
showDeleteDialog = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function confirmDeleteSession() {
|
|
336
|
+
if (!sessionToDelete) return;
|
|
337
|
+
const deleteId = sessionToDelete.id;
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
await ws.http('sessions:delete', { id: deleteId });
|
|
341
|
+
removeSession(deleteId);
|
|
342
|
+
|
|
343
|
+
// Clean cache
|
|
344
|
+
delete sessionDataCache[deleteId];
|
|
345
|
+
|
|
346
|
+
addNotification({
|
|
347
|
+
type: 'success',
|
|
348
|
+
title: 'Session Deleted',
|
|
349
|
+
message: 'Chat session has been deleted',
|
|
350
|
+
duration: 3000
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
showDeleteDialog = false;
|
|
354
|
+
sessionToDelete = null;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
debug.error('session', 'Failed to delete session:', error);
|
|
357
|
+
addNotification({
|
|
358
|
+
type: 'error',
|
|
359
|
+
title: 'Error',
|
|
360
|
+
message: 'Failed to delete session',
|
|
361
|
+
duration: 5000
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function closeDeleteDialog() {
|
|
367
|
+
showDeleteDialog = false;
|
|
368
|
+
sessionToDelete = null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function closeModal() {
|
|
372
|
+
searchQuery = '';
|
|
373
|
+
onClose();
|
|
374
|
+
}
|
|
375
|
+
</script>
|
|
376
|
+
|
|
377
|
+
<Modal bind:isOpen onClose={closeModal} size="md">
|
|
378
|
+
{#snippet header()}
|
|
379
|
+
<div class="flex items-center justify-between px-4 py-3 md:px-6 md:py-4">
|
|
380
|
+
<h2 class="text-base md:text-lg font-bold text-slate-900 dark:text-slate-100">Sessions</h2>
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
class="p-1.5 md:p-2 rounded-lg text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 hover:bg-violet-500/10 transition-colors"
|
|
384
|
+
onclick={closeModal}
|
|
385
|
+
aria-label="Close modal"
|
|
386
|
+
>
|
|
387
|
+
<svg class="w-4 h-4 md:w-5 md:h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
388
|
+
<path
|
|
389
|
+
stroke-linecap="round"
|
|
390
|
+
stroke-linejoin="round"
|
|
391
|
+
stroke-width="2"
|
|
392
|
+
d="M6 18L18 6M6 6l12 12"
|
|
393
|
+
/>
|
|
394
|
+
</svg>
|
|
395
|
+
</button>
|
|
396
|
+
</div>
|
|
397
|
+
{/snippet}
|
|
398
|
+
|
|
399
|
+
{#snippet children()}
|
|
400
|
+
<!-- Search Box -->
|
|
401
|
+
{#if sessions.length > 0}
|
|
402
|
+
<div class="mb-4">
|
|
403
|
+
<div
|
|
404
|
+
class="flex items-center gap-2 py-2.5 px-3.5 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-lg"
|
|
405
|
+
>
|
|
406
|
+
<Icon name="lucide:search" class="w-4 h-4 text-slate-500 dark:text-slate-400 shrink-0" />
|
|
407
|
+
<input
|
|
408
|
+
type="text"
|
|
409
|
+
bind:value={searchQuery}
|
|
410
|
+
placeholder="Search sessions..."
|
|
411
|
+
class="flex-1 bg-transparent border-none outline-none text-slate-900 dark:text-slate-100 text-sm placeholder:text-slate-500 dark:placeholder:text-slate-400"
|
|
412
|
+
/>
|
|
413
|
+
{#if searchQuery}
|
|
414
|
+
<button
|
|
415
|
+
type="button"
|
|
416
|
+
class="flex items-center justify-center w-5 h-5 bg-transparent border-none rounded text-slate-400 cursor-pointer transition-all duration-150 hover:text-slate-600 dark:hover:text-slate-300"
|
|
417
|
+
onclick={() => (searchQuery = '')}
|
|
418
|
+
aria-label="Clear search"
|
|
419
|
+
>
|
|
420
|
+
<Icon name="lucide:x" class="w-3.5 h-3.5" />
|
|
421
|
+
</button>
|
|
422
|
+
{/if}
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
{/if}
|
|
426
|
+
|
|
427
|
+
<!-- Sessions List -->
|
|
428
|
+
{#if loadingSessionData}
|
|
429
|
+
<div class="flex items-center justify-center gap-3 py-12">
|
|
430
|
+
<div class="animate-spin rounded-full h-6 w-6 border-b-2 border-violet-600"></div>
|
|
431
|
+
<span class="text-sm text-slate-600 dark:text-slate-400">Loading sessions...</span>
|
|
432
|
+
</div>
|
|
433
|
+
{:else if filteredSessions.length === 0}
|
|
434
|
+
<div class="flex flex-col items-center gap-3 py-8 text-slate-600 dark:text-slate-500 text-sm">
|
|
435
|
+
<Icon name="lucide:message-square-off" class="w-12 h-12 text-slate-400 opacity-40" />
|
|
436
|
+
<p class="font-medium">No sessions found</p>
|
|
437
|
+
<p class="text-xs text-slate-500 dark:text-slate-500">
|
|
438
|
+
{searchQuery ? 'Try adjusting your search' : 'Start a new chat to see it here'}
|
|
439
|
+
</p>
|
|
440
|
+
{#if searchQuery}
|
|
441
|
+
<button
|
|
442
|
+
type="button"
|
|
443
|
+
class="text-xs text-violet-600 dark:text-violet-400 underline cursor-pointer hover:text-violet-700 dark:hover:text-violet-300"
|
|
444
|
+
onclick={() => (searchQuery = '')}
|
|
445
|
+
>
|
|
446
|
+
Clear search
|
|
447
|
+
</button>
|
|
448
|
+
{/if}
|
|
449
|
+
</div>
|
|
450
|
+
{:else}
|
|
451
|
+
<div class="space-y-2">
|
|
452
|
+
{#each filteredSessions.slice(0, 20) as session (session.id)}
|
|
453
|
+
{@const isActive = isActiveSession(session)}
|
|
454
|
+
{@const sessionUsers = getSessionUsers(session.id)}
|
|
455
|
+
{@const streaming = isSessionStreaming(session.id)}
|
|
456
|
+
{@const modelName = getSessionModel(session)}
|
|
457
|
+
<div
|
|
458
|
+
class="flex items-center gap-2 w-full p-3 bg-transparent border border-slate-200 dark:border-slate-700 rounded-lg text-slate-900 dark:text-slate-100 text-sm text-left transition-all duration-150
|
|
459
|
+
{isActive
|
|
460
|
+
? 'border-violet-300 dark:border-violet-600 bg-violet-50 dark:bg-violet-900/10'
|
|
461
|
+
: ''}"
|
|
462
|
+
>
|
|
463
|
+
<button
|
|
464
|
+
type="button"
|
|
465
|
+
class="flex items-center gap-3 flex-1 min-w-0 bg-transparent border-none cursor-pointer text-left"
|
|
466
|
+
onclick={() => resumeSession(session)}
|
|
467
|
+
>
|
|
468
|
+
<div
|
|
469
|
+
class="relative w-8 h-8 {isActive
|
|
470
|
+
? 'bg-violet-200 dark:bg-violet-800/30'
|
|
471
|
+
: 'bg-violet-100 dark:bg-violet-900/20'} rounded-lg flex items-center justify-center flex-shrink-0"
|
|
472
|
+
>
|
|
473
|
+
<Icon name="lucide:message-square" class="text-violet-600 dark:text-violet-400 w-4 h-4" />
|
|
474
|
+
{#if streaming}
|
|
475
|
+
<span
|
|
476
|
+
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-emerald-500"
|
|
477
|
+
></span>
|
|
478
|
+
{:else}
|
|
479
|
+
<span
|
|
480
|
+
class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full border-2 border-white dark:border-slate-900 bg-slate-300 dark:bg-slate-600"
|
|
481
|
+
></span>
|
|
482
|
+
{/if}
|
|
483
|
+
</div>
|
|
484
|
+
<div class="flex-1 min-w-0">
|
|
485
|
+
<div class="flex items-center gap-2">
|
|
486
|
+
<p class="font-semibold text-slate-900 dark:text-slate-100 truncate text-sm">
|
|
487
|
+
{getSessionTitle(session.id)}
|
|
488
|
+
</p>
|
|
489
|
+
{#if isActive}
|
|
490
|
+
<span
|
|
491
|
+
class="inline-flex items-center gap-1 px-2 py-0.5 bg-green-100 dark:bg-green-900/20 text-green-700 dark:text-green-300 text-xs font-medium rounded-full shrink-0"
|
|
492
|
+
>
|
|
493
|
+
<Icon name="lucide:circle-check" class="w-3 h-3" />
|
|
494
|
+
Active
|
|
495
|
+
</span>
|
|
496
|
+
{/if}
|
|
497
|
+
</div>
|
|
498
|
+
<div class="flex items-center gap-2 mt-0.5 text-xs text-slate-500 dark:text-slate-400">
|
|
499
|
+
<span class="flex items-center gap-1 flex-none">
|
|
500
|
+
<Icon name="lucide:messages-square" class="w-3 h-3" />
|
|
501
|
+
{getUserMessageCount(session.id)}
|
|
502
|
+
</span>
|
|
503
|
+
<span>·</span>
|
|
504
|
+
<span class="flex items-center gap-1 flex-none">
|
|
505
|
+
<Icon name="lucide:clock" class="w-3 h-3" />
|
|
506
|
+
{getLastActive(session)}
|
|
507
|
+
</span>
|
|
508
|
+
{#if modelName}
|
|
509
|
+
<span>·</span>
|
|
510
|
+
<span class="truncate">{modelName}</span>
|
|
511
|
+
{/if}
|
|
512
|
+
</div>
|
|
513
|
+
{#if streaming}
|
|
514
|
+
<p class="text-xs text-violet-500 dark:text-violet-400 mt-0.5 flex items-center gap-1.5">
|
|
515
|
+
<span class="inline-block w-3 h-3 border-2 border-violet-400 border-t-transparent rounded-full animate-spin shrink-0"></span>
|
|
516
|
+
Processing...
|
|
517
|
+
</p>
|
|
518
|
+
{:else}
|
|
519
|
+
<p class="text-xs text-slate-400 dark:text-slate-500 truncate mt-0.5">
|
|
520
|
+
{getSessionSummary(session.id)}
|
|
521
|
+
</p>
|
|
522
|
+
{/if}
|
|
523
|
+
</div>
|
|
524
|
+
</button>
|
|
525
|
+
{#if sessionUsers.length > 0}
|
|
526
|
+
<div class="flex items-center -space-x-1 shrink-0">
|
|
527
|
+
{#each sessionUsers.slice(0, 2) as user}
|
|
528
|
+
<AvatarBubble {user} size="sm" />
|
|
529
|
+
{/each}
|
|
530
|
+
{#if sessionUsers.length > 2}
|
|
531
|
+
<span class="w-5 h-5 rounded-full bg-gradient-to-br from-slate-500 to-slate-600 text-white text-4xs font-bold flex items-center justify-center border-2 border-white dark:border-slate-900 z-10">
|
|
532
|
+
+{sessionUsers.length - 2}
|
|
533
|
+
</span>
|
|
534
|
+
{/if}
|
|
535
|
+
</div>
|
|
536
|
+
{/if}
|
|
537
|
+
<button
|
|
538
|
+
type="button"
|
|
539
|
+
class="flex items-center justify-center w-8 h-8 bg-transparent border-none rounded-lg text-slate-400 dark:text-slate-500 cursor-pointer transition-all duration-150 hover:bg-red-500/15 hover:text-red-500 shrink-0"
|
|
540
|
+
onclick={(e) => handleDeleteClick(session, e)}
|
|
541
|
+
aria-label="Delete session"
|
|
542
|
+
title="Delete"
|
|
543
|
+
>
|
|
544
|
+
<Icon name="lucide:trash-2" class="w-4 h-4" />
|
|
545
|
+
</button>
|
|
546
|
+
</div>
|
|
547
|
+
{/each}
|
|
548
|
+
</div>
|
|
549
|
+
{/if}
|
|
550
|
+
{/snippet}
|
|
551
|
+
</Modal>
|
|
552
|
+
|
|
553
|
+
<!-- Delete Confirmation Dialog -->
|
|
554
|
+
<Dialog
|
|
555
|
+
bind:isOpen={showDeleteDialog}
|
|
556
|
+
onClose={closeDeleteDialog}
|
|
557
|
+
type="error"
|
|
558
|
+
title="Delete Session"
|
|
559
|
+
message="Are you sure you want to delete this session? All messages will be permanently removed."
|
|
560
|
+
confirmText="Delete"
|
|
561
|
+
cancelText="Cancel"
|
|
562
|
+
onConfirm={confirmDeleteSession}
|
|
563
|
+
/>
|