@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,407 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Modern Claude Code SDK Chat Interface
|
|
3
|
+
|
|
4
|
+
Features:
|
|
5
|
+
- Real-time streaming with Server-Sent Events
|
|
6
|
+
- Modern AI-first design with solid backgrounds
|
|
7
|
+
- Proper SDK message handling
|
|
8
|
+
- Enhanced error handling and user feedback
|
|
9
|
+
- Responsive design with mobile optimization
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { sessionState, setCurrentSession, createNewChatSession, clearMessages, loadMessagesForSession } from '$frontend/lib/stores/core/sessions.svelte';
|
|
14
|
+
import { projectState } from '$frontend/lib/stores/core/projects.svelte';
|
|
15
|
+
import { appState } from '$frontend/lib/stores/core/app.svelte';
|
|
16
|
+
import { presenceState } from '$frontend/lib/stores/core/presence.svelte';
|
|
17
|
+
import { userStore } from '$frontend/lib/stores/features/user.svelte';
|
|
18
|
+
import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
|
|
19
|
+
import { onMount } from 'svelte';
|
|
20
|
+
import { fade } from 'svelte/transition';
|
|
21
|
+
import ChatMessages from './message/ChatMessages.svelte';
|
|
22
|
+
import ChatInput from './input/ChatInput.svelte';
|
|
23
|
+
import FloatingTodoList from './widgets/FloatingTodoList.svelte';
|
|
24
|
+
import TimelineModal from '$frontend/lib/components/checkpoint/TimelineModal.svelte';
|
|
25
|
+
// import TokenUsage from './widgets/TokenUsage.svelte'; // DISABLED
|
|
26
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
27
|
+
import AvatarBubble from '$frontend/lib/components/common/AvatarBubble.svelte';
|
|
28
|
+
import Button from '$frontend/lib/components/common/Button.svelte';
|
|
29
|
+
import PageTemplate from '$frontend/lib/components/common/PageTemplate.svelte';
|
|
30
|
+
import Modal from '$frontend/lib/components/common/Modal.svelte';
|
|
31
|
+
import type { IconName } from '$shared/types/ui/icons';
|
|
32
|
+
import type { ChatSession } from '$shared/types/database/schema';
|
|
33
|
+
import { debug } from '$shared/utils/logger';
|
|
34
|
+
import ws from '$frontend/lib/utils/ws';
|
|
35
|
+
|
|
36
|
+
// Welcome state - don't show during restoration
|
|
37
|
+
const isWelcomeState = $derived(
|
|
38
|
+
sessionState.messages.length === 0 &&
|
|
39
|
+
!appState.isRestoring
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Check if we should show input (not during restoration)
|
|
43
|
+
const showInput = $derived(!appState.isRestoring);
|
|
44
|
+
|
|
45
|
+
// Project-aware state
|
|
46
|
+
const hasActiveProject = $derived(projectState.currentProject !== null);
|
|
47
|
+
const projectName = $derived(projectState.currentProject?.name || 'No Project');
|
|
48
|
+
|
|
49
|
+
// Dynamic title for PageTemplate (static description)
|
|
50
|
+
const pageTitle = $derived(hasActiveProject ? `${projectName}` : 'No project selected');
|
|
51
|
+
|
|
52
|
+
// Scroll container for passing to PageTemplate
|
|
53
|
+
let scrollContainer: HTMLElement | undefined = $state();
|
|
54
|
+
|
|
55
|
+
// Session picker state
|
|
56
|
+
let showSessionPicker = $state(false);
|
|
57
|
+
|
|
58
|
+
// Active sessions for the current project (non-ended), sorted newest first
|
|
59
|
+
const activeSessions = $derived(
|
|
60
|
+
sessionState.sessions
|
|
61
|
+
.filter(s => s.project_id === projectState.currentProject?.id && !s.ended_at)
|
|
62
|
+
.sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime())
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Check if another session has an active stream (for badge indicator)
|
|
66
|
+
const otherSessionsStreaming = $derived.by(() => {
|
|
67
|
+
const projectId = projectState.currentProject?.id;
|
|
68
|
+
const currentSessionId = sessionState.currentSession?.id;
|
|
69
|
+
if (!projectId) return false;
|
|
70
|
+
const status = presenceState.statuses.get(projectId);
|
|
71
|
+
if (!status?.streams) return false;
|
|
72
|
+
return status.streams.some(
|
|
73
|
+
(s: any) => s.status === 'active' && s.chatSessionId !== currentSessionId
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Get users in a specific chat session (excluding self)
|
|
78
|
+
function getSessionUsers(chatSessionId: string): { userId: string; userName: string }[] {
|
|
79
|
+
const projectId = projectState.currentProject?.id;
|
|
80
|
+
const currentUserId = userStore.currentUser?.id;
|
|
81
|
+
if (!projectId) return [];
|
|
82
|
+
const status = presenceState.statuses.get(projectId);
|
|
83
|
+
if (!status?.chatSessionUsers) return [];
|
|
84
|
+
const users = status.chatSessionUsers[chatSessionId] || [];
|
|
85
|
+
return currentUserId ? users.filter(u => u.userId !== currentUserId) : users;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toggleSessionPicker() {
|
|
89
|
+
showSessionPicker = !showSessionPicker;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function closeSessionPicker() {
|
|
93
|
+
showSessionPicker = false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function switchToSession(session: ChatSession) {
|
|
97
|
+
if (session.id === sessionState.currentSession?.id) {
|
|
98
|
+
closeSessionPicker();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
await setCurrentSession(session);
|
|
102
|
+
closeSessionPicker();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Extract short title from session (first user message text)
|
|
106
|
+
function getSessionShortTitle(session: ChatSession): string {
|
|
107
|
+
return session.title?.replace(/^Shared Chat - /, '').slice(0, 40) || 'New Chat';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Checkpoints modal state
|
|
111
|
+
let showCheckpoints = $state(false);
|
|
112
|
+
|
|
113
|
+
function openCheckpoints() {
|
|
114
|
+
showCheckpoints = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function closeCheckpoints() {
|
|
118
|
+
showCheckpoints = false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Extract text from message content
|
|
122
|
+
function extractMessageText(message: any): string {
|
|
123
|
+
if (!('message' in message) || !message.message?.content) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
const content = message.message.content;
|
|
127
|
+
|
|
128
|
+
if (typeof content === 'string') {
|
|
129
|
+
return content;
|
|
130
|
+
} else if (Array.isArray(content)) {
|
|
131
|
+
// Find text content in array
|
|
132
|
+
for (const item of content) {
|
|
133
|
+
if (typeof item === 'string') {
|
|
134
|
+
return item;
|
|
135
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
136
|
+
if ('text' in item && typeof (item as any).text === 'string') {
|
|
137
|
+
return (item as any).text;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Process timeline messages with all necessary data
|
|
146
|
+
const timelineMessages = $derived(
|
|
147
|
+
sessionState.messages
|
|
148
|
+
.filter(m => {
|
|
149
|
+
if (m.type !== 'user') return false;
|
|
150
|
+
const text = extractMessageText(m);
|
|
151
|
+
return text.length > 0;
|
|
152
|
+
})
|
|
153
|
+
.map(msg => ({
|
|
154
|
+
id: msg.metadata?.message_id,
|
|
155
|
+
timestamp: msg.metadata?.created_at || '',
|
|
156
|
+
date: msg.metadata?.created_at ? new Date(msg.metadata.created_at).toLocaleDateString() : 'Unknown',
|
|
157
|
+
time: msg.metadata?.created_at ? new Date(msg.metadata.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : 'Unknown',
|
|
158
|
+
text: extractMessageText(msg)
|
|
159
|
+
}))
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Handle restore from timeline
|
|
163
|
+
async function handleTimelineRestore(messageId: string | undefined, messageTimestamp: string) {
|
|
164
|
+
if (!messageId) {
|
|
165
|
+
addNotification({
|
|
166
|
+
type: 'error',
|
|
167
|
+
title: 'Restore Failed',
|
|
168
|
+
message: 'Message ID not found',
|
|
169
|
+
duration: 3000
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Send restore request via WebSocket HTTP
|
|
176
|
+
await ws.http('snapshot:restore', {
|
|
177
|
+
messageId: messageId,
|
|
178
|
+
sessionId: sessionState.currentSession?.id || ''
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Close modal
|
|
182
|
+
showCheckpoints = false;
|
|
183
|
+
|
|
184
|
+
// Reload messages from database to update UI
|
|
185
|
+
if (sessionState.currentSession?.id) {
|
|
186
|
+
await loadMessagesForSession(sessionState.currentSession.id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
addNotification({
|
|
190
|
+
type: 'success',
|
|
191
|
+
title: 'Project Restored',
|
|
192
|
+
message: `Successfully restored to checkpoint at ${new Date(messageTimestamp).toLocaleTimeString()}`,
|
|
193
|
+
duration: 5000
|
|
194
|
+
});
|
|
195
|
+
} catch (error) {
|
|
196
|
+
debug.error('chat', 'Restore error:', error);
|
|
197
|
+
addNotification({
|
|
198
|
+
type: 'error',
|
|
199
|
+
title: 'Restore Failed',
|
|
200
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
201
|
+
duration: 5000
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function startNewChat() {
|
|
207
|
+
if (!hasActiveProject || !projectState.currentProject) {
|
|
208
|
+
addNotification({
|
|
209
|
+
type: 'warning',
|
|
210
|
+
title: 'No Project Selected',
|
|
211
|
+
message: 'Please select a project first',
|
|
212
|
+
duration: 3000
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Clear messages for the local view
|
|
218
|
+
clearMessages();
|
|
219
|
+
|
|
220
|
+
// Create a new session (existing sessions stay active for other users)
|
|
221
|
+
const newSession = await createNewChatSession(projectState.currentProject.id);
|
|
222
|
+
|
|
223
|
+
if (newSession) {
|
|
224
|
+
await setCurrentSession(newSession);
|
|
225
|
+
} else {
|
|
226
|
+
addNotification({
|
|
227
|
+
type: 'error',
|
|
228
|
+
title: 'Failed to Create Session',
|
|
229
|
+
message: 'Could not create a new chat session',
|
|
230
|
+
duration: 3000
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Check for active stream on mount only if needed
|
|
236
|
+
onMount(async () => {
|
|
237
|
+
debug.log('chat', 'Component mounted');
|
|
238
|
+
// WebSocket reconnection is handled automatically by ws client
|
|
239
|
+
});
|
|
240
|
+
</script>
|
|
241
|
+
|
|
242
|
+
<PageTemplate
|
|
243
|
+
title={pageTitle}
|
|
244
|
+
description="AI-powered development assistant"
|
|
245
|
+
bind:scrollContainer
|
|
246
|
+
>
|
|
247
|
+
{#snippet actions()}
|
|
248
|
+
<div class="flex items-center space-x-2">
|
|
249
|
+
<!-- Session Picker (only when multiple active sessions) -->
|
|
250
|
+
{#if activeSessions.length > 1}
|
|
251
|
+
<div class="relative">
|
|
252
|
+
<button
|
|
253
|
+
onclick={toggleSessionPicker}
|
|
254
|
+
class="flex items-center gap-1.5 px-2 sm:px-3 py-1.5 sm:py-2 text-xs sm:text-sm font-medium rounded-lg border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-900 text-slate-700 dark:text-slate-300 hover:border-violet-500 dark:hover:border-violet-500 transition-all duration-200"
|
|
255
|
+
>
|
|
256
|
+
<Icon name="lucide:layers" class="w-3.5 h-3.5" />
|
|
257
|
+
<span class="hidden sm:inline max-w-[120px] truncate">{activeSessions.length} sessions</span>
|
|
258
|
+
{#if otherSessionsStreaming}
|
|
259
|
+
<span class="w-2 h-2 rounded-full bg-violet-500 animate-pulse"></span>
|
|
260
|
+
{/if}
|
|
261
|
+
<Icon name="lucide:chevron-down" class="w-3 h-3" />
|
|
262
|
+
</button>
|
|
263
|
+
|
|
264
|
+
{#if showSessionPicker}
|
|
265
|
+
<!-- Backdrop -->
|
|
266
|
+
<div class="fixed inset-0 z-40" onclick={closeSessionPicker}></div>
|
|
267
|
+
|
|
268
|
+
<!-- Dropdown -->
|
|
269
|
+
<div class="absolute top-full right-0 mt-1 z-50 w-72 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg shadow-lg overflow-hidden">
|
|
270
|
+
<div class="px-3 py-2 border-b border-slate-100 dark:border-slate-700">
|
|
271
|
+
<p class="text-xs font-medium text-slate-500 dark:text-slate-400">Active Sessions</p>
|
|
272
|
+
</div>
|
|
273
|
+
<div class="max-h-64 overflow-y-auto">
|
|
274
|
+
{#each activeSessions as session (session.id)}
|
|
275
|
+
{@const isCurrent = session.id === sessionState.currentSession?.id}
|
|
276
|
+
{@const projectId = projectState.currentProject?.id}
|
|
277
|
+
{@const status = projectId ? presenceState.statuses.get(projectId) : undefined}
|
|
278
|
+
{@const isStreaming = status?.streams?.some((s: any) => s.status === 'active' && s.chatSessionId === session.id) ?? false}
|
|
279
|
+
{@const sessionUsers = getSessionUsers(session.id)}
|
|
280
|
+
{@const sessionModel = session.model ? (session.model.includes(':') ? session.model.split(':')[1] : session.model) : ''}
|
|
281
|
+
<button
|
|
282
|
+
onclick={() => switchToSession(session)}
|
|
283
|
+
class="w-full text-left px-3 py-2.5 flex items-center gap-2.5 transition-colors duration-150
|
|
284
|
+
{isCurrent
|
|
285
|
+
? 'bg-violet-50 dark:bg-violet-900/20 text-violet-700 dark:text-violet-300'
|
|
286
|
+
: 'text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700/50'}"
|
|
287
|
+
>
|
|
288
|
+
<div class="flex-1 min-w-0">
|
|
289
|
+
<div class="flex items-center gap-1.5">
|
|
290
|
+
<span class="w-1.5 h-1.5 rounded-full shrink-0 {isStreaming ? 'bg-emerald-500 animate-pulse' : isCurrent ? 'bg-green-500' : 'bg-slate-300 dark:bg-slate-600'}"></span>
|
|
291
|
+
<span class="text-sm font-medium truncate">{getSessionShortTitle(session)}</span>
|
|
292
|
+
</div>
|
|
293
|
+
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
|
|
294
|
+
{new Date(session.started_at).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}
|
|
295
|
+
{#if sessionModel}
|
|
296
|
+
<span class="text-slate-400 dark:text-slate-500">· {sessionModel}</span>
|
|
297
|
+
{/if}
|
|
298
|
+
</p>
|
|
299
|
+
</div>
|
|
300
|
+
{#if sessionUsers.length > 0}
|
|
301
|
+
<div class="flex items-center -space-x-1 shrink-0">
|
|
302
|
+
{#each sessionUsers.slice(0, 2) as user}
|
|
303
|
+
<AvatarBubble {user} size="sm" />
|
|
304
|
+
{/each}
|
|
305
|
+
{#if sessionUsers.length > 2}
|
|
306
|
+
<span class="w-4 h-4 rounded-full bg-slate-400 text-white text-5xs font-bold flex items-center justify-center border border-white dark:border-slate-800">
|
|
307
|
+
+{sessionUsers.length - 2}
|
|
308
|
+
</span>
|
|
309
|
+
{/if}
|
|
310
|
+
</div>
|
|
311
|
+
{/if}
|
|
312
|
+
{#if isStreaming}
|
|
313
|
+
<span class="shrink-0 flex items-center gap-1 px-1.5 py-0.5 text-3xs font-medium rounded-full bg-violet-100 dark:bg-violet-900/30 text-violet-600 dark:text-violet-400">
|
|
314
|
+
<span class="w-1.5 h-1.5 rounded-full bg-violet-500 animate-pulse"></span>
|
|
315
|
+
AI
|
|
316
|
+
</span>
|
|
317
|
+
{/if}
|
|
318
|
+
</button>
|
|
319
|
+
{/each}
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
{/if}
|
|
323
|
+
</div>
|
|
324
|
+
{/if}
|
|
325
|
+
|
|
326
|
+
<!-- Restore Checkpoint button -->
|
|
327
|
+
{#if sessionState.messages.length > 0}
|
|
328
|
+
<Button variant="outline" onclick={openCheckpoints} class="rounded-lg px-2 sm:px-4 py-1.5 sm:py-2">
|
|
329
|
+
<Icon name="lucide:undo-2" class="w-3.5 h-3.5 sm:w-4 sm:h-4 sm:mr-2" />
|
|
330
|
+
<span class="hidden sm:inline">Restore</span>
|
|
331
|
+
</Button>
|
|
332
|
+
{/if}
|
|
333
|
+
|
|
334
|
+
<!-- New Chat button -->
|
|
335
|
+
<Button variant="outline" onclick={startNewChat} class="rounded-lg px-2 sm:px-4 py-1.5 sm:py-2">
|
|
336
|
+
<Icon name="lucide:plus" class="w-3.5 h-3.5 sm:w-4 sm:h-4 sm:mr-2" />
|
|
337
|
+
<span class="hidden sm:inline">New Chat</span>
|
|
338
|
+
</Button>
|
|
339
|
+
</div>
|
|
340
|
+
{/snippet}
|
|
341
|
+
|
|
342
|
+
<div class="flex flex-col h-[calc(100%+3rem)] -my-6 -mx-4">
|
|
343
|
+
{#if isWelcomeState && !appState.isRestoring}
|
|
344
|
+
<!-- Welcome state with modern design -->
|
|
345
|
+
<div class="flex-1 overflow-y-auto overflow-x-hidden">
|
|
346
|
+
<div class="min-h-full flex items-center justify-center p-4">
|
|
347
|
+
<div class="w-full max-w-4xl space-y-6 md:space-y-8 lg:space-y-10">
|
|
348
|
+
<!-- Modern hero section -->
|
|
349
|
+
<div class="text-center space-y-3 md:space-y-4 px-6">
|
|
350
|
+
<div class="space-y-3 md:space-y-4">
|
|
351
|
+
<h1 class="text-3xl md:text-4xl font-semibold text-slate-900 dark:text-slate-100">
|
|
352
|
+
Build apps & websites with AI
|
|
353
|
+
</h1>
|
|
354
|
+
<p class="md:text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
|
|
355
|
+
Describe your idea. Get production-ready code.
|
|
356
|
+
</p>
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
|
|
360
|
+
<!-- Input area integrated in welcome state -->
|
|
361
|
+
{#if showInput}
|
|
362
|
+
<div class="w-full px-4 space-y-4" in:fade={{ duration: 200, delay: 100 }}>
|
|
363
|
+
<ChatInput />
|
|
364
|
+
</div>
|
|
365
|
+
{/if}
|
|
366
|
+
</div>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
{:else}
|
|
370
|
+
<!-- Enhanced chat interface -->
|
|
371
|
+
<div class="flex-1 flex flex-col overflow-hidden">
|
|
372
|
+
<div class="flex-1 flex justify-center overflow-hidden">
|
|
373
|
+
<div class="w-full flex flex-col overflow-hidden">
|
|
374
|
+
<div class="flex-1 overflow-y-auto overflow-x-hidden">
|
|
375
|
+
<ChatMessages {scrollContainer} />
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
<!-- Input area with SDK integration -->
|
|
382
|
+
{#if showInput}
|
|
383
|
+
<div
|
|
384
|
+
class="sticky bottom-0 flex-shrink-0 bg-gradient-to-t from-slate-50 via-slate-50 dark:from-slate-900 dark:via-slate-900 to-transparent"
|
|
385
|
+
in:fade={{ duration: 200, delay: 100 }}
|
|
386
|
+
>
|
|
387
|
+
<div class="flex justify-center">
|
|
388
|
+
<div class="w-full max-w-5xl px-4 pb-4 pt-2">
|
|
389
|
+
<ChatInput />
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
{/if}
|
|
394
|
+
{/if}
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
<!-- Floating TodoList (only shown when there's an active session with todos) -->
|
|
398
|
+
{#if sessionState.currentSession}
|
|
399
|
+
<FloatingTodoList />
|
|
400
|
+
{/if}
|
|
401
|
+
|
|
402
|
+
<!-- Checkpoint Timeline Modal -->
|
|
403
|
+
<TimelineModal
|
|
404
|
+
bind:isOpen={showCheckpoints}
|
|
405
|
+
onClose={closeCheckpoints}
|
|
406
|
+
/>
|
|
407
|
+
</PageTemplate>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
3
|
+
|
|
4
|
+
const { errorText }: { errorText: string } = $props();
|
|
5
|
+
|
|
6
|
+
// Categorize error types and provide suggestions
|
|
7
|
+
function getErrorInfo(text: string): { type: string; suggestion: string } {
|
|
8
|
+
if (text.includes('max_turns') || text.includes('maximum number of conversation turns')) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'Task Completion Limit',
|
|
11
|
+
suggestion: 'Try breaking your request into smaller, more specific tasks.'
|
|
12
|
+
};
|
|
13
|
+
} else if (text.includes('timeout') || text.includes('timed out')) {
|
|
14
|
+
return {
|
|
15
|
+
type: 'Timeout Error',
|
|
16
|
+
suggestion: 'The operation took too long. Try with a simpler request.'
|
|
17
|
+
};
|
|
18
|
+
} else if (text.includes('permission') || text.includes('denied')) {
|
|
19
|
+
return {
|
|
20
|
+
type: 'Permission Error',
|
|
21
|
+
suggestion: 'Check file permissions and ensure you have access to the project directory.'
|
|
22
|
+
};
|
|
23
|
+
} else if (text.includes('API key') || text.includes('authentication')) {
|
|
24
|
+
return {
|
|
25
|
+
type: 'Authentication Error',
|
|
26
|
+
suggestion: 'Please check your Anthropic API key configuration.'
|
|
27
|
+
};
|
|
28
|
+
} else if (text.includes('git-bash') || text.includes('Git')) {
|
|
29
|
+
return {
|
|
30
|
+
type: 'Git Configuration Error',
|
|
31
|
+
suggestion: 'Ensure Git is installed and accessible in your system PATH.'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
type: 'Error',
|
|
37
|
+
suggestion: ''
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const errorInfo = $derived(getErrorInfo(errorText));
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<div class="p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
|
45
|
+
<div class="flex items-start gap-3">
|
|
46
|
+
<Icon name="lucide:circle-alert" class="text-red-500 w-6 h-6 mt-0.5" />
|
|
47
|
+
<div class="flex-1">
|
|
48
|
+
<div class="font-bold text-red-700 dark:text-red-300 mb-2">{errorInfo.type}</div>
|
|
49
|
+
<div class="text-sm text-red-600 dark:text-red-400 leading-relaxed">{errorText}</div>
|
|
50
|
+
{#if errorInfo.suggestion}
|
|
51
|
+
<div class="mt-3 text-xs text-red-500 dark:text-red-400 bg-red-100 dark:bg-red-900/30 p-2 rounded border-l-2 border-red-400">
|
|
52
|
+
{errorInfo.suggestion}
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ErrorMessage from './ErrorMessage.svelte';
|
|
3
|
+
import TextMessage from './TextMessage.svelte';
|
|
4
|
+
import Tools from './Tools.svelte';
|
|
5
|
+
import Lightbox from '$frontend/lib/components/common/Lightbox.svelte';
|
|
6
|
+
import Icon from '$frontend/lib/components/common/Icon.svelte';
|
|
7
|
+
import { getFileIcon } from '$frontend/lib/utils/file-icon-mappings';
|
|
8
|
+
import { formatFileSize } from '../shared/utils';
|
|
9
|
+
|
|
10
|
+
import type { SDKMessage, SDKPartialAssistantMessage } from '$shared/types/messaging';
|
|
11
|
+
import type { SDKMessageFormatter } from '$shared/types/database/schema';
|
|
12
|
+
|
|
13
|
+
const { message }: { message: SDKMessageFormatter } = $props();
|
|
14
|
+
|
|
15
|
+
let lightboxOpen = $state(false);
|
|
16
|
+
let lightboxData = $state<{ type: 'image' | 'document', data: string, mediaType: string, fileName?: string }>({
|
|
17
|
+
type: 'image',
|
|
18
|
+
data: '',
|
|
19
|
+
mediaType: '',
|
|
20
|
+
fileName: ''
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function openAttachment(type: 'image' | 'document', data: string, mediaType: string, fileName?: string) {
|
|
24
|
+
lightboxData = { type, data, mediaType, fileName };
|
|
25
|
+
lightboxOpen = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function closeLightbox() {
|
|
29
|
+
lightboxOpen = false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type ContentElement = {
|
|
33
|
+
type: 'text' | 'tool_use' | 'error' | 'image' | 'document';
|
|
34
|
+
content: any;
|
|
35
|
+
fileName?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Parse content into structured elements
|
|
39
|
+
function parseContent() {
|
|
40
|
+
const elements: ContentElement[] = [];
|
|
41
|
+
|
|
42
|
+
// Handle partial messages (streaming) — both text and reasoning
|
|
43
|
+
if (message.type === 'stream_event') {
|
|
44
|
+
if ('partialText' in message && message.partialText) {
|
|
45
|
+
elements.push({
|
|
46
|
+
type: 'text',
|
|
47
|
+
content: message.partialText
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return elements;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle reasoning messages (final, with metadata.reasoning flag)
|
|
54
|
+
if (message.metadata?.reasoning && message.type === 'assistant') {
|
|
55
|
+
const content = message.message.content;
|
|
56
|
+
if (Array.isArray(content)) {
|
|
57
|
+
for (const item of content) {
|
|
58
|
+
if (item.type === 'text') {
|
|
59
|
+
elements.push({ type: 'text', content: item.text });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return elements;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if (message.type === 'assistant') {
|
|
68
|
+
const content = message.message.content;
|
|
69
|
+
const elements: ContentElement[] = [];
|
|
70
|
+
|
|
71
|
+
for (const contentItem of content) {
|
|
72
|
+
if (contentItem.type === 'text') {
|
|
73
|
+
// Text block
|
|
74
|
+
elements.push({
|
|
75
|
+
type: 'text',
|
|
76
|
+
content: contentItem.text
|
|
77
|
+
});
|
|
78
|
+
} else if (contentItem.type === 'tool_use') {
|
|
79
|
+
// Tool use block (may contain embedded $result)
|
|
80
|
+
elements.push({
|
|
81
|
+
type: 'tool_use',
|
|
82
|
+
content: contentItem
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return elements;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
else if (message.type === 'user') {
|
|
91
|
+
const content = message.message.content;
|
|
92
|
+
// Get attachment filenames from metadata if available (now an array)
|
|
93
|
+
const attachmentFilenames = (message as any).attachmentFilenames || [];
|
|
94
|
+
|
|
95
|
+
// Handle array content (for tool_result and file attachments)
|
|
96
|
+
if (Array.isArray(content)) {
|
|
97
|
+
for (let i = 0; i < content.length; i++) {
|
|
98
|
+
const contentItem = content[i];
|
|
99
|
+
|
|
100
|
+
if (typeof contentItem === 'string') {
|
|
101
|
+
elements.push({
|
|
102
|
+
type: 'text',
|
|
103
|
+
content: contentItem
|
|
104
|
+
});
|
|
105
|
+
} else if (contentItem.type === 'text') {
|
|
106
|
+
elements.push({
|
|
107
|
+
type: 'text',
|
|
108
|
+
content: contentItem.text
|
|
109
|
+
});
|
|
110
|
+
} else if (contentItem.type === 'image') {
|
|
111
|
+
// Handle image attachments with filename from metadata
|
|
112
|
+
elements.push({
|
|
113
|
+
type: 'image',
|
|
114
|
+
content: contentItem,
|
|
115
|
+
fileName: attachmentFilenames[i] || undefined
|
|
116
|
+
});
|
|
117
|
+
} else if (contentItem.type === 'document') {
|
|
118
|
+
// Handle document attachments with filename from metadata
|
|
119
|
+
elements.push({
|
|
120
|
+
type: 'document',
|
|
121
|
+
content: contentItem,
|
|
122
|
+
fileName: attachmentFilenames[i] || undefined
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// String content
|
|
128
|
+
elements.push({
|
|
129
|
+
type: 'text',
|
|
130
|
+
content: content
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return elements;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return elements;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Main parsed content
|
|
141
|
+
const parsedElements = $derived.by(parseContent);
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
{#each parsedElements as element}
|
|
145
|
+
{#if element.type === 'error'}
|
|
146
|
+
<ErrorMessage errorText={element.content} />
|
|
147
|
+
{:else if element.type === 'text'}
|
|
148
|
+
<TextMessage content={element.content} />
|
|
149
|
+
{:else if element.type === 'tool_use'}
|
|
150
|
+
<Tools toolInput={element.content} />
|
|
151
|
+
{:else if element.type === 'image'}
|
|
152
|
+
<div class="inline-block mr-2 mb-2">
|
|
153
|
+
<div class="flex flex-col gap-1">
|
|
154
|
+
{#if element.content.source?.type === 'base64'}
|
|
155
|
+
<button
|
|
156
|
+
onclick={() => openAttachment('image', element.content.source.data, element.content.source.media_type, element.fileName || 'Image')}
|
|
157
|
+
class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-100 dark:bg-slate-800 cursor-pointer hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-violet-500"
|
|
158
|
+
aria-label="Click to view full image"
|
|
159
|
+
>
|
|
160
|
+
<img
|
|
161
|
+
src="data:{element.content.source.media_type};base64,{element.content.source.data}"
|
|
162
|
+
alt={element.fileName || "User uploaded content"}
|
|
163
|
+
class="absolute inset-0 w-full h-full object-cover"
|
|
164
|
+
loading="lazy"
|
|
165
|
+
/>
|
|
166
|
+
</button>
|
|
167
|
+
{:else}
|
|
168
|
+
<div class="w-28 h-28 flex items-center justify-center bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-750 rounded-lg border border-slate-200 dark:border-slate-700">
|
|
169
|
+
<svg class="w-12 h-12 text-slate-400 dark:text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
170
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
|
171
|
+
</svg>
|
|
172
|
+
</div>
|
|
173
|
+
{/if}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
{:else if element.type === 'document'}
|
|
177
|
+
<div class="inline-block mr-2 mb-2">
|
|
178
|
+
<div class="flex flex-col gap-1">
|
|
179
|
+
{#if element.content.source?.type === 'base64'}
|
|
180
|
+
<button
|
|
181
|
+
onclick={() => openAttachment('document', element.content.source.data, element.content.source.media_type, element.fileName || 'Document')}
|
|
182
|
+
class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800 cursor-pointer hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-violet-500"
|
|
183
|
+
aria-label="Click to view document"
|
|
184
|
+
>
|
|
185
|
+
<div class="absolute inset-0 flex flex-col items-center justify-center gap-1.5 p-2">
|
|
186
|
+
<Icon
|
|
187
|
+
name={element.fileName ? getFileIcon(element.fileName) : 'material:document'}
|
|
188
|
+
class="w-8 h-8 text-slate-600 dark:text-slate-400 flex-shrink-0"
|
|
189
|
+
/>
|
|
190
|
+
{#if element.fileName}
|
|
191
|
+
<span class="text-xs font-medium text-slate-700 dark:text-slate-300 line-clamp-2 w-full px-1 text-center leading-tight" title={element.fileName}>
|
|
192
|
+
{element.fileName}
|
|
193
|
+
</span>
|
|
194
|
+
{/if}
|
|
195
|
+
<span class="text-xs text-slate-500 dark:text-slate-400 -mt-0.5">
|
|
196
|
+
{formatFileSize(element.content.source.data.length * 0.75)}
|
|
197
|
+
</span>
|
|
198
|
+
</div>
|
|
199
|
+
</button>
|
|
200
|
+
{:else}
|
|
201
|
+
<div class="relative w-28 h-28 overflow-hidden rounded-lg border border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800">
|
|
202
|
+
<div class="absolute inset-0 flex flex-col items-center justify-center gap-1.5">
|
|
203
|
+
<Icon
|
|
204
|
+
name="material:document"
|
|
205
|
+
class="w-10 h-10 text-slate-600 dark:text-slate-400"
|
|
206
|
+
/>
|
|
207
|
+
<span class="text-xs font-medium text-slate-700 dark:text-slate-300">DOCUMENT</span>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
{/if}
|
|
211
|
+
</div>
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
{/each}
|
|
215
|
+
|
|
216
|
+
<!-- Lightbox for viewing attachments -->
|
|
217
|
+
<Lightbox
|
|
218
|
+
bind:isOpen={lightboxOpen}
|
|
219
|
+
type={lightboxData.type}
|
|
220
|
+
mediaType={lightboxData.mediaType}
|
|
221
|
+
data={lightboxData.data}
|
|
222
|
+
fileName={lightboxData.fileName}
|
|
223
|
+
onClose={closeLightbox}
|
|
224
|
+
/>
|