@myrialabs/clopen 0.0.4 → 0.0.6
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 +5 -5
- package/.github/workflows/{release.yml → ci.yml} +86 -60
- package/CONTRIBUTING.md +499 -499
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/backend/index.ts +168 -156
- package/backend/lib/chat/helpers.ts +42 -42
- package/backend/lib/chat/index.ts +1 -1
- package/backend/lib/chat/stream-manager.ts +1126 -1126
- package/backend/lib/database/README.md +76 -76
- package/backend/lib/database/index.ts +118 -118
- package/backend/lib/database/migrations/001_create_projects_table.ts +30 -30
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +32 -32
- package/backend/lib/database/migrations/003_create_messages_table.ts +31 -31
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -34
- package/backend/lib/database/migrations/005_create_settings_table.ts +23 -23
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +57 -57
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +40 -40
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +61 -61
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -41
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -70
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -156
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -41
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -118
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -34
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -32
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -24
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -32
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -33
- package/backend/lib/database/migrations/index.ts +153 -153
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -87
- package/backend/lib/database/queries/engine-queries.ts +75 -75
- package/backend/lib/database/queries/index.ts +8 -8
- package/backend/lib/database/queries/message-queries.ts +471 -471
- package/backend/lib/database/queries/project-queries.ts +117 -117
- package/backend/lib/database/queries/session-queries.ts +270 -270
- package/backend/lib/database/queries/settings-queries.ts +33 -33
- package/backend/lib/database/queries/snapshot-queries.ts +325 -325
- package/backend/lib/database/queries/utils-queries.ts +58 -58
- package/backend/lib/database/seeders/index.ts +12 -12
- package/backend/lib/database/seeders/settings_seeder.ts +83 -83
- package/backend/lib/database/utils/connection.ts +173 -173
- package/backend/lib/database/utils/index.ts +3 -3
- package/backend/lib/database/utils/migration-runner.ts +117 -117
- package/backend/lib/database/utils/seeder-runner.ts +120 -120
- package/backend/lib/engine/adapters/claude/environment.ts +160 -164
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -60
- package/backend/lib/engine/adapters/claude/index.ts +1 -1
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -38
- package/backend/lib/engine/adapters/claude/stream.ts +177 -177
- package/backend/lib/engine/adapters/opencode/index.ts +2 -2
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -862
- package/backend/lib/engine/adapters/opencode/server.ts +104 -104
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -755
- package/backend/lib/engine/index.ts +196 -196
- package/backend/lib/engine/types.ts +58 -58
- package/backend/lib/files/file-operations.ts +478 -478
- package/backend/lib/files/file-reading.ts +308 -308
- package/backend/lib/files/file-watcher.ts +383 -383
- package/backend/lib/files/path-browsing.ts +382 -382
- package/backend/lib/git/git-executor.ts +89 -88
- package/backend/lib/git/git-parser.ts +411 -411
- package/backend/lib/git/git-service.ts +505 -505
- package/backend/lib/mcp/README.md +1144 -1144
- package/backend/lib/mcp/config.ts +317 -316
- package/backend/lib/mcp/index.ts +35 -35
- package/backend/lib/mcp/project-context.ts +236 -236
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -156
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -419
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -791
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -501
- package/backend/lib/mcp/servers/helper.ts +143 -143
- package/backend/lib/mcp/servers/index.ts +44 -44
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -56
- package/backend/lib/mcp/servers/weather/index.ts +31 -31
- package/backend/lib/mcp/stdio-server.ts +103 -103
- package/backend/lib/mcp/types.ts +65 -65
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -86
- package/backend/lib/preview/browser/browser-console-manager.ts +262 -262
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -222
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -421
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -415
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -512
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +103 -103
- package/backend/lib/preview/browser/browser-pool.ts +357 -357
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -882
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -935
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -695
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -292
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -85
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -438
- package/backend/lib/preview/browser/types.ts +359 -359
- package/backend/lib/preview/index.ts +23 -23
- package/backend/lib/project/index.ts +1 -1
- package/backend/lib/project/status-manager.ts +181 -181
- package/backend/lib/shared/env.ts +117 -0
- package/backend/lib/shared/index.ts +5 -2
- package/backend/lib/shared/port-utils.ts +25 -25
- package/backend/lib/shared/process-manager.ts +280 -280
- package/backend/lib/snapshot/blob-store.ts +227 -227
- package/backend/lib/snapshot/gitignore.ts +307 -307
- package/backend/lib/snapshot/helpers.ts +397 -397
- package/backend/lib/snapshot/snapshot-service.ts +483 -483
- package/backend/lib/terminal/helpers.ts +14 -14
- package/backend/lib/terminal/index.ts +7 -7
- package/backend/lib/terminal/pty-manager.ts +3 -3
- package/backend/lib/terminal/pty-session-manager.ts +370 -387
- package/backend/lib/terminal/shell-utils.ts +315 -312
- package/backend/lib/terminal/stream-manager.ts +292 -292
- package/backend/lib/tunnel/global-tunnel-manager.ts +266 -243
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -311
- package/backend/lib/user/helpers.ts +87 -87
- package/backend/lib/utils/ws.ts +944 -944
- package/backend/lib/vite-dev.ts +295 -295
- package/backend/middleware/cors.ts +16 -15
- package/backend/middleware/error-handler.ts +50 -49
- package/backend/middleware/logger.ts +9 -9
- package/backend/types/api.ts +24 -24
- package/backend/ws/README.md +1505 -1505
- package/backend/ws/chat/background.ts +198 -198
- package/backend/ws/chat/index.ts +21 -21
- package/backend/ws/chat/stream.ts +707 -707
- package/backend/ws/engine/claude/accounts.ts +399 -401
- package/backend/ws/engine/claude/index.ts +13 -13
- package/backend/ws/engine/claude/status.ts +43 -43
- package/backend/ws/engine/index.ts +14 -14
- package/backend/ws/engine/opencode/index.ts +11 -11
- package/backend/ws/engine/opencode/status.ts +30 -30
- package/backend/ws/engine/utils.ts +36 -36
- package/backend/ws/files/index.ts +30 -30
- package/backend/ws/files/read.ts +189 -189
- package/backend/ws/files/search.ts +453 -453
- package/backend/ws/files/watch.ts +124 -124
- package/backend/ws/files/write.ts +143 -143
- package/backend/ws/git/branch.ts +106 -106
- package/backend/ws/git/commit.ts +39 -39
- package/backend/ws/git/conflict.ts +68 -68
- package/backend/ws/git/diff.ts +69 -69
- package/backend/ws/git/index.ts +24 -24
- package/backend/ws/git/log.ts +41 -41
- package/backend/ws/git/remote.ts +214 -214
- package/backend/ws/git/staging.ts +84 -84
- package/backend/ws/git/status.ts +90 -90
- package/backend/ws/index.ts +69 -69
- package/backend/ws/mcp/index.ts +61 -61
- package/backend/ws/messages/crud.ts +74 -74
- package/backend/ws/messages/index.ts +14 -14
- package/backend/ws/preview/browser/cleanup.ts +129 -129
- package/backend/ws/preview/browser/console.ts +114 -114
- package/backend/ws/preview/browser/interact.ts +513 -513
- package/backend/ws/preview/browser/mcp.ts +129 -129
- package/backend/ws/preview/browser/native-ui.ts +235 -235
- package/backend/ws/preview/browser/stats.ts +55 -55
- package/backend/ws/preview/browser/tab-info.ts +126 -126
- package/backend/ws/preview/browser/tab.ts +166 -166
- package/backend/ws/preview/browser/webcodecs.ts +293 -293
- package/backend/ws/preview/index.ts +146 -146
- package/backend/ws/projects/crud.ts +113 -113
- package/backend/ws/projects/index.ts +25 -25
- package/backend/ws/projects/presence.ts +46 -46
- package/backend/ws/projects/status.ts +116 -116
- package/backend/ws/sessions/crud.ts +327 -327
- package/backend/ws/sessions/index.ts +33 -33
- package/backend/ws/settings/crud.ts +112 -112
- package/backend/ws/settings/index.ts +14 -14
- package/backend/ws/snapshot/index.ts +17 -17
- package/backend/ws/snapshot/restore.ts +173 -173
- package/backend/ws/snapshot/timeline.ts +141 -141
- package/backend/ws/system/index.ts +14 -14
- package/backend/ws/system/operations.ts +49 -49
- package/backend/ws/terminal/index.ts +40 -40
- package/backend/ws/terminal/persistence.ts +153 -153
- package/backend/ws/terminal/session.ts +382 -382
- package/backend/ws/terminal/stream.ts +79 -79
- package/backend/ws/tunnel/index.ts +14 -14
- package/backend/ws/tunnel/operations.ts +91 -91
- package/backend/ws/types.ts +20 -20
- package/backend/ws/user/crud.ts +156 -156
- package/backend/ws/user/index.ts +14 -14
- package/bin/clopen.ts +307 -307
- package/bun.lock +1353 -1352
- package/frontend/App.svelte +38 -34
- package/frontend/app.css +313 -313
- package/frontend/lib/app-environment.ts +10 -10
- package/frontend/lib/components/chat/ChatInterface.svelte +406 -406
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +56 -56
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +223 -223
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +394 -394
- package/frontend/lib/components/chat/formatters/Tools.svelte +69 -69
- package/frontend/lib/components/chat/formatters/index.ts +2 -2
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -421
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -78
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -30
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -33
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -619
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -48
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -31
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -201
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -148
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -216
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -357
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -57
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -478
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -541
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -86
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -86
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -157
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -59
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -124
- package/frontend/lib/components/chat/shared/index.ts +1 -1
- package/frontend/lib/components/chat/shared/utils.ts +115 -115
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -35
- package/frontend/lib/components/chat/tools/BashTool.svelte +45 -45
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -139
- package/frontend/lib/components/chat/tools/EditTool.svelte +47 -47
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +31 -31
- package/frontend/lib/components/chat/tools/GlobTool.svelte +50 -50
- package/frontend/lib/components/chat/tools/GrepTool.svelte +89 -89
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +25 -25
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +30 -30
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +37 -37
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +33 -33
- package/frontend/lib/components/chat/tools/ReadTool.svelte +40 -40
- package/frontend/lib/components/chat/tools/TaskTool.svelte +63 -63
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +74 -74
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +34 -34
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +83 -83
- package/frontend/lib/components/chat/tools/WriteTool.svelte +32 -32
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +78 -78
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +407 -407
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -45
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +18 -18
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +26 -26
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +53 -53
- package/frontend/lib/components/chat/tools/components/index.ts +7 -7
- package/frontend/lib/components/chat/tools/index.ts +25 -25
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +248 -248
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -78
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -391
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -26
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +86 -86
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -108
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -59
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -168
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -44
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -304
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -65
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -53
- package/frontend/lib/components/common/Alert.svelte +138 -138
- package/frontend/lib/components/common/AvatarBubble.svelte +55 -55
- package/frontend/lib/components/common/Button.svelte +71 -71
- package/frontend/lib/components/common/Card.svelte +102 -102
- package/frontend/lib/components/common/Checkbox.svelte +48 -48
- package/frontend/lib/components/common/Dialog.svelte +248 -248
- package/frontend/lib/components/common/FolderBrowser.svelte +842 -842
- package/frontend/lib/components/common/Icon.svelte +57 -57
- package/frontend/lib/components/common/Input.svelte +72 -72
- package/frontend/lib/components/common/Lightbox.svelte +232 -232
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -52
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -48
- package/frontend/lib/components/common/Modal.svelte +177 -177
- package/frontend/lib/components/common/ModalProvider.svelte +27 -27
- package/frontend/lib/components/common/ModelSelector.svelte +110 -110
- package/frontend/lib/components/common/MonacoEditor.svelte +568 -568
- package/frontend/lib/components/common/NotificationToast.svelte +113 -113
- package/frontend/lib/components/common/PageTemplate.svelte +75 -75
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -79
- package/frontend/lib/components/common/Select.svelte +97 -97
- package/frontend/lib/components/common/Textarea.svelte +79 -79
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -44
- package/frontend/lib/components/common/lucide-icons.ts +1642 -1642
- package/frontend/lib/components/common/material-icons.ts +1082 -1082
- package/frontend/lib/components/common/xterm/XTerm.svelte +809 -795
- package/frontend/lib/components/common/xterm/index.ts +15 -15
- package/frontend/lib/components/common/xterm/terminal-config.ts +67 -67
- package/frontend/lib/components/common/xterm/types.ts +30 -30
- package/frontend/lib/components/common/xterm/xterm-service.ts +379 -353
- package/frontend/lib/components/files/FileNode.svelte +383 -383
- package/frontend/lib/components/files/FileTree.svelte +681 -681
- package/frontend/lib/components/files/FileViewer.svelte +728 -728
- package/frontend/lib/components/files/SearchResults.svelte +303 -303
- package/frontend/lib/components/git/BranchManager.svelte +458 -458
- package/frontend/lib/components/git/ChangesSection.svelte +107 -107
- package/frontend/lib/components/git/CommitForm.svelte +76 -76
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -158
- package/frontend/lib/components/git/DiffViewer.svelte +364 -364
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -97
- package/frontend/lib/components/git/GitButton.svelte +33 -33
- package/frontend/lib/components/git/GitLog.svelte +361 -361
- package/frontend/lib/components/git/GitModal.svelte +80 -80
- package/frontend/lib/components/history/HistoryModal.svelte +563 -563
- package/frontend/lib/components/history/HistoryView.svelte +614 -614
- package/frontend/lib/components/index.ts +34 -34
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -549
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -1058
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +756 -756
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -450
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -236
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -224
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +338 -338
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +35 -35
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -155
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -837
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -113
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -296
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -391
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -231
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -210
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -239
- package/frontend/lib/components/preview/index.ts +1 -1
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -235
- package/frontend/lib/components/settings/SettingsView.svelte +36 -36
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -51
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -160
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -76
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -917
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -187
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -203
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -10
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -357
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -205
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -197
- package/frontend/lib/components/terminal/Terminal.svelte +367 -367
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -87
- package/frontend/lib/components/terminal/TerminalView.svelte +54 -54
- package/frontend/lib/components/tunnel/TunnelActive.svelte +157 -142
- package/frontend/lib/components/tunnel/TunnelButton.svelte +60 -54
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +285 -284
- package/frontend/lib/components/tunnel/TunnelModal.svelte +48 -47
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -49
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -382
- package/frontend/lib/components/workspace/MobileNavigator.svelte +394 -403
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -100
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -505
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -162
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -169
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -15
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -17
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -42
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +84 -84
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -37
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -274
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -1261
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -1560
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -150
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -73
- package/frontend/lib/constants/preview.ts +44 -44
- package/frontend/lib/services/chat/chat.service.ts +704 -704
- package/frontend/lib/services/chat/index.ts +6 -6
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -86
- package/frontend/lib/services/notification/index.ts +7 -7
- package/frontend/lib/services/notification/push.service.ts +143 -143
- package/frontend/lib/services/notification/sound.service.ts +126 -126
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -61
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -1499
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -67
- package/frontend/lib/services/preview/index.ts +22 -22
- package/frontend/lib/services/project/index.ts +7 -7
- package/frontend/lib/services/project/status.service.ts +159 -159
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -47
- package/frontend/lib/services/terminal/background/index.ts +129 -129
- package/frontend/lib/services/terminal/background/session-restore.ts +273 -273
- package/frontend/lib/services/terminal/background/stream-manager.ts +285 -285
- package/frontend/lib/services/terminal/index.ts +13 -13
- package/frontend/lib/services/terminal/persistence.service.ts +260 -260
- package/frontend/lib/services/terminal/project.service.ts +952 -952
- package/frontend/lib/services/terminal/session.service.ts +363 -363
- package/frontend/lib/services/terminal/terminal.service.ts +369 -369
- package/frontend/lib/stores/core/app.svelte.ts +117 -117
- package/frontend/lib/stores/core/files.svelte.ts +72 -72
- package/frontend/lib/stores/core/presence.svelte.ts +48 -48
- package/frontend/lib/stores/core/projects.svelte.ts +317 -317
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -383
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -58
- package/frontend/lib/stores/features/models.svelte.ts +89 -89
- package/frontend/lib/stores/features/settings.svelte.ts +87 -87
- package/frontend/lib/stores/features/terminal.svelte.ts +700 -700
- package/frontend/lib/stores/features/tunnel.svelte.ts +163 -161
- package/frontend/lib/stores/features/user.svelte.ts +95 -95
- package/frontend/lib/stores/ui/chat-input.svelte.ts +56 -56
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -61
- package/frontend/lib/stores/ui/dialog.svelte.ts +58 -58
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -214
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -166
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -88
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -179
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -754
- package/frontend/lib/types/native-ui.ts +73 -73
- package/frontend/lib/utils/chat/date-separator.ts +38 -38
- package/frontend/lib/utils/chat/message-grouper.ts +218 -218
- package/frontend/lib/utils/chat/message-processor.ts +134 -134
- package/frontend/lib/utils/chat/tool-handler.ts +160 -160
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -142
- package/frontend/lib/utils/click-outside.ts +20 -20
- package/frontend/lib/utils/context-manager.ts +256 -256
- package/frontend/lib/utils/file-icon-mappings.ts +768 -768
- package/frontend/lib/utils/folder-icon-mappings.ts +1029 -1029
- package/frontend/lib/utils/git-status.ts +68 -68
- package/frontend/lib/utils/platform.ts +112 -112
- package/frontend/lib/utils/port-check.ts +64 -64
- package/frontend/lib/utils/terminalFormatter.ts +206 -206
- package/frontend/lib/utils/theme.ts +6 -6
- package/frontend/lib/utils/tree-visualizer.ts +320 -320
- package/frontend/lib/utils/ws.ts +44 -44
- package/frontend/main.ts +13 -13
- package/index.html +70 -70
- package/package.json +111 -111
- package/scripts/generate-icons.ts +86 -86
- package/scripts/pre-publish-check.sh +142 -142
- package/scripts/setup-hooks.sh +134 -134
- package/scripts/validate-branch-name.sh +47 -47
- package/scripts/validate-commit-msg.sh +42 -42
- package/shared/constants/engines.ts +134 -134
- package/shared/types/database/connection.ts +15 -15
- package/shared/types/database/index.ts +5 -5
- package/shared/types/database/schema.ts +140 -140
- package/shared/types/engine/index.ts +45 -45
- package/shared/types/filesystem/index.ts +21 -21
- package/shared/types/git.ts +171 -171
- package/shared/types/messaging/index.ts +238 -238
- package/shared/types/messaging/tool.ts +525 -525
- package/shared/types/network/api.ts +17 -17
- package/shared/types/network/index.ts +4 -4
- package/shared/types/stores/app.ts +22 -22
- package/shared/types/stores/dialog.ts +20 -20
- package/shared/types/stores/index.ts +2 -2
- package/shared/types/stores/settings.ts +15 -15
- package/shared/types/terminal/index.ts +43 -43
- package/shared/types/ui/components.ts +60 -60
- package/shared/types/ui/icons.ts +22 -22
- package/shared/types/ui/index.ts +21 -21
- package/shared/types/ui/notifications.ts +13 -13
- package/shared/types/ui/theme.ts +11 -11
- package/shared/types/websocket/index.ts +43 -43
- package/shared/types/window.d.ts +12 -12
- package/shared/utils/anonymous-user.ts +167 -167
- package/shared/utils/async.ts +10 -10
- package/shared/utils/diff-calculator.ts +184 -184
- package/shared/utils/file-type-detection.ts +165 -165
- package/shared/utils/logger.ts +144 -158
- package/shared/utils/message-formatter.ts +79 -79
- package/shared/utils/path.ts +47 -47
- package/shared/utils/ws-client.ts +768 -768
- package/shared/utils/ws-server.ts +660 -660
- package/static/favicon.svg +7 -7
- package/static/fonts/dm-sans.css +96 -96
- package/svelte.config.js +20 -20
- package/tsconfig.json +41 -41
- package/vite.config.ts +33 -33
- package/.github/workflows/test.yml +0 -40
|
@@ -1,357 +1,357 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { settings, updateSettings } from '$frontend/lib/stores/features/settings.svelte';
|
|
3
|
-
import { modelStore } from '$frontend/lib/stores/features/models.svelte';
|
|
4
|
-
import { ENGINES, type EngineType } from '$shared/constants/engines';
|
|
5
|
-
import type { EngineModel } from '$shared/types/engine';
|
|
6
|
-
|
|
7
|
-
let searchQuery = $state('');
|
|
8
|
-
let refreshing = $state(false);
|
|
9
|
-
let collapsedProviders = $state<Set<string>>(new Set());
|
|
10
|
-
|
|
11
|
-
// Handle engine selection — restore remembered model or pick first
|
|
12
|
-
async function selectEngine(engineType: EngineType) {
|
|
13
|
-
updateSettings({ selectedEngine: engineType });
|
|
14
|
-
searchQuery = '';
|
|
15
|
-
|
|
16
|
-
// Restore remembered model for this engine
|
|
17
|
-
const memory = settings.engineModelMemory || {};
|
|
18
|
-
const remembered = memory[engineType];
|
|
19
|
-
|
|
20
|
-
if (engineType !== 'claude-code') {
|
|
21
|
-
const models = await modelStore.fetchModels(engineType);
|
|
22
|
-
const target = (remembered && models.find(m => m.id === remembered))
|
|
23
|
-
|| models.find(m => m.recommended)
|
|
24
|
-
|| models[0];
|
|
25
|
-
if (target) {
|
|
26
|
-
updateSettings({
|
|
27
|
-
selectedModel: target.id,
|
|
28
|
-
engineModelMemory: { ...memory, [engineType]: target.id }
|
|
29
|
-
});
|
|
30
|
-
} else {
|
|
31
|
-
// No models available — clear the model selection
|
|
32
|
-
updateSettings({ selectedModel: '' });
|
|
33
|
-
}
|
|
34
|
-
} else {
|
|
35
|
-
const models = modelStore.getByEngine('claude-code');
|
|
36
|
-
const target = (remembered && models.find(m => m.id === remembered))
|
|
37
|
-
|| models.find(m => m.recommended)
|
|
38
|
-
|| models[0];
|
|
39
|
-
if (target) {
|
|
40
|
-
updateSettings({
|
|
41
|
-
selectedModel: target.id,
|
|
42
|
-
engineModelMemory: { ...memory, [engineType]: target.id }
|
|
43
|
-
});
|
|
44
|
-
} else {
|
|
45
|
-
// No models available — clear the model selection
|
|
46
|
-
updateSettings({ selectedModel: '' });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Open accordion for the selected model's provider
|
|
51
|
-
syncAccordionState();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Handle model selection — also save to per-engine memory
|
|
55
|
-
function selectModel(modelId: string) {
|
|
56
|
-
const memory = settings.engineModelMemory || {};
|
|
57
|
-
updateSettings({
|
|
58
|
-
selectedModel: modelId,
|
|
59
|
-
engineModelMemory: { ...memory, [settings.selectedEngine]: modelId }
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Refresh models (bypass cache)
|
|
64
|
-
async function handleRefresh() {
|
|
65
|
-
refreshing = true;
|
|
66
|
-
try {
|
|
67
|
-
await modelStore.refreshModels(settings.selectedEngine);
|
|
68
|
-
} finally {
|
|
69
|
-
refreshing = false;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Toggle provider accordion
|
|
74
|
-
function toggleProvider(provider: string) {
|
|
75
|
-
const next = new Set(collapsedProviders);
|
|
76
|
-
if (next.has(provider)) {
|
|
77
|
-
next.delete(provider);
|
|
78
|
-
} else {
|
|
79
|
-
next.add(provider);
|
|
80
|
-
}
|
|
81
|
-
collapsedProviders = next;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Sync accordion state: open only the provider containing the selected model
|
|
85
|
-
function syncAccordionState() {
|
|
86
|
-
const allProviders = [...groupedModels.keys()];
|
|
87
|
-
const selectedModel = settings.selectedModel;
|
|
88
|
-
let selectedProvider: string | null = null;
|
|
89
|
-
|
|
90
|
-
for (const [provider, models] of groupedModels) {
|
|
91
|
-
if (models.some(m => m.id === selectedModel)) {
|
|
92
|
-
selectedProvider = provider;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Collapse all, then open the one with selected model
|
|
98
|
-
const collapsed = new Set(allProviders);
|
|
99
|
-
if (selectedProvider) {
|
|
100
|
-
collapsed.delete(selectedProvider);
|
|
101
|
-
}
|
|
102
|
-
collapsedProviders = collapsed;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Get models for the currently selected engine, filtered by search
|
|
106
|
-
const filteredModels = $derived.by(() => {
|
|
107
|
-
const models = modelStore.getByEngine(settings.selectedEngine);
|
|
108
|
-
if (!searchQuery.trim()) return models;
|
|
109
|
-
|
|
110
|
-
const q = searchQuery.toLowerCase();
|
|
111
|
-
return models.filter(m =>
|
|
112
|
-
m.name.toLowerCase().includes(q) ||
|
|
113
|
-
m.modelId.toLowerCase().includes(q) ||
|
|
114
|
-
m.provider.toLowerCase().includes(q) ||
|
|
115
|
-
m.capabilities.some(c => c.toLowerCase().includes(q))
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Group models by provider
|
|
120
|
-
const groupedModels = $derived.by(() => {
|
|
121
|
-
const groups = new Map<string, EngineModel[]>();
|
|
122
|
-
for (const model of filteredModels) {
|
|
123
|
-
const key = model.provider;
|
|
124
|
-
if (!groups.has(key)) groups.set(key, []);
|
|
125
|
-
groups.get(key)!.push(model);
|
|
126
|
-
}
|
|
127
|
-
return groups;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Fetch models on mount for non-claude-code, then sync accordion
|
|
131
|
-
$effect(() => {
|
|
132
|
-
if (settings.selectedEngine !== 'claude-code') {
|
|
133
|
-
modelStore.fetchModels(settings.selectedEngine);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Sync accordion: open all when searching, restore default when cleared
|
|
138
|
-
$effect(() => {
|
|
139
|
-
if (searchQuery.trim()) {
|
|
140
|
-
// Searching — open all accordions
|
|
141
|
-
collapsedProviders = new Set();
|
|
142
|
-
} else if (groupedModels.size > 0) {
|
|
143
|
-
// Not searching — only open the one with selected model
|
|
144
|
-
syncAccordionState();
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
function formatContext(tokens: number): string {
|
|
149
|
-
return tokens >= 1000000
|
|
150
|
-
? `${(tokens / 1000000).toFixed(1)}M`
|
|
151
|
-
: `${(tokens / 1000).toFixed(0)}K`;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function formatProvider(provider: string): string {
|
|
155
|
-
return provider
|
|
156
|
-
.split(/[-_]/)
|
|
157
|
-
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
158
|
-
.join(' ');
|
|
159
|
-
}
|
|
160
|
-
</script>
|
|
161
|
-
|
|
162
|
-
<!-- Claude SVG logo (Anthropic) -->
|
|
163
|
-
{#snippet claudeLogo(active: boolean)}
|
|
164
|
-
<svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
|
|
165
|
-
<path d="M16.091 4L9.115 20h-1.19L14.901 4h1.19zm-5.726 5.2L14.8 20h-1.218L9.2 9.6l1.165-.4z"
|
|
166
|
-
fill={active ? '#8b5cf6' : 'currentColor'} />
|
|
167
|
-
</svg>
|
|
168
|
-
{/snippet}
|
|
169
|
-
|
|
170
|
-
<!-- OpenCode SVG logo -->
|
|
171
|
-
{#snippet opencodeLogo(active: boolean)}
|
|
172
|
-
<svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
|
|
173
|
-
<path d="M8.5 6L3 12l5.5 6M15.5 6L21 12l-5.5 6M13.5 4l-3 16"
|
|
174
|
-
stroke={active ? '#8b5cf6' : 'currentColor'}
|
|
175
|
-
stroke-width="2"
|
|
176
|
-
stroke-linecap="round"
|
|
177
|
-
stroke-linejoin="round" />
|
|
178
|
-
</svg>
|
|
179
|
-
{/snippet}
|
|
180
|
-
|
|
181
|
-
<div class="py-1">
|
|
182
|
-
<!-- Engine Selection -->
|
|
183
|
-
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">AI Engine</h3>
|
|
184
|
-
<p class="text-sm text-slate-600 dark:text-slate-500 mb-4">
|
|
185
|
-
Select the AI engine to power your conversations
|
|
186
|
-
</p>
|
|
187
|
-
|
|
188
|
-
<div class="flex gap-3 mb-6">
|
|
189
|
-
{#each ENGINES as engine (engine.type)}
|
|
190
|
-
{@const isActive = settings.selectedEngine === engine.type}
|
|
191
|
-
<button
|
|
192
|
-
type="button"
|
|
193
|
-
class="flex-1 flex items-center gap-3 p-3.5 overflow-hidden border-2 rounded-xl text-left cursor-pointer transition-all duration-200
|
|
194
|
-
{isActive
|
|
195
|
-
? 'border-violet-600 bg-gradient-to-br from-violet-500/10 to-purple-500/5 dark:from-violet-500/12 dark:to-purple-500/8'
|
|
196
|
-
: 'border-slate-200 dark:border-slate-800 bg-slate-100/80 dark:bg-slate-800/80 hover:border-violet-500/20 dark:hover:border-violet-500/35'}"
|
|
197
|
-
onclick={() => selectEngine(engine.type)}
|
|
198
|
-
>
|
|
199
|
-
<div>
|
|
200
|
-
<div class="flex dark:hidden items-center justify-center w-5 h-5">{@html engine.icon.light}</div>
|
|
201
|
-
<div class="hidden dark:flex items-center justify-center w-5 h-5">{@html engine.icon.dark}</div>
|
|
202
|
-
</div>
|
|
203
|
-
<div>
|
|
204
|
-
<div class="font-bold text-sm text-slate-900 dark:text-slate-100">{engine.name}</div>
|
|
205
|
-
<div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{engine.description}</div>
|
|
206
|
-
</div>
|
|
207
|
-
{#if isActive}
|
|
208
|
-
<div class="flex items-center justify-center w-5 h-5 bg-gradient-to-br from-violet-600 to-purple-600 rounded-full text-white ml-auto flex-shrink-0">
|
|
209
|
-
<svg viewBox="0 0 24 24" fill="none" class="w-3 h-3" aria-hidden="true">
|
|
210
|
-
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
|
|
211
|
-
</svg>
|
|
212
|
-
</div>
|
|
213
|
-
{/if}
|
|
214
|
-
</button>
|
|
215
|
-
{/each}
|
|
216
|
-
</div>
|
|
217
|
-
|
|
218
|
-
<!-- Model Selection -->
|
|
219
|
-
<div class="flex items-center justify-between mb-1.5">
|
|
220
|
-
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100">Model</h3>
|
|
221
|
-
<button
|
|
222
|
-
type="button"
|
|
223
|
-
class="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg transition-colors cursor-pointer
|
|
224
|
-
text-slate-500 hover:text-violet-600 hover:bg-violet-500/10 dark:hover:text-violet-400 dark:hover:bg-violet-500/15
|
|
225
|
-
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
226
|
-
onclick={handleRefresh}
|
|
227
|
-
disabled={refreshing || modelStore.loading}
|
|
228
|
-
>
|
|
229
|
-
<svg viewBox="0 0 24 24" fill="none" class="w-3.5 h-3.5 {refreshing ? 'animate-spin' : ''}" aria-hidden="true">
|
|
230
|
-
<path d="M21 12a9 9 0 11-2.636-6.364M21 3v5h-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
231
|
-
</svg>
|
|
232
|
-
{refreshing ? 'Refreshing...' : 'Refresh'}
|
|
233
|
-
</button>
|
|
234
|
-
</div>
|
|
235
|
-
<p class="text-sm text-slate-600 dark:text-slate-500 mb-3">
|
|
236
|
-
Select the AI model for the {ENGINES.find(e => e.type === settings.selectedEngine)?.name || 'selected'} engine
|
|
237
|
-
</p>
|
|
238
|
-
|
|
239
|
-
<!-- Search -->
|
|
240
|
-
<div class="relative mb-3">
|
|
241
|
-
<svg viewBox="0 0 24 24" fill="none" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" aria-hidden="true">
|
|
242
|
-
<circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" />
|
|
243
|
-
<path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
244
|
-
</svg>
|
|
245
|
-
<input
|
|
246
|
-
type="text"
|
|
247
|
-
bind:value={searchQuery}
|
|
248
|
-
placeholder="Search models..."
|
|
249
|
-
class="w-full pl-9 pr-3 py-2 text-sm bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-600 transition-colors text-slate-900 dark:text-slate-100 placeholder-slate-400"
|
|
250
|
-
/>
|
|
251
|
-
</div>
|
|
252
|
-
|
|
253
|
-
<!-- Model List -->
|
|
254
|
-
<div class="flex flex-col gap-1.5">
|
|
255
|
-
{#if modelStore.loading && settings.selectedEngine !== 'claude-code' && !refreshing}
|
|
256
|
-
<!-- Loading skeleton for Open Code only -->
|
|
257
|
-
<div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
|
|
258
|
-
<div class="bg-white/80 dark:bg-slate-800/40 px-3 py-3 flex items-center gap-3">
|
|
259
|
-
<div class="w-4 h-4 rounded-full bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
|
|
260
|
-
<div class="h-3.5 w-32 rounded bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
|
|
261
|
-
</div>
|
|
262
|
-
<div class="px-4 py-2.5 space-y-2.5">
|
|
263
|
-
{#each Array(3) as _}
|
|
264
|
-
<div class="flex items-center gap-3 py-2">
|
|
265
|
-
<div class="w-4 h-4 rounded-full bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
|
|
266
|
-
<div class="flex-1 space-y-1.5">
|
|
267
|
-
<div class="h-3.5 w-40 rounded bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
|
|
268
|
-
<div class="flex gap-1.5">
|
|
269
|
-
<div class="h-3 w-14 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
|
|
270
|
-
<div class="h-3 w-12 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
|
|
271
|
-
</div>
|
|
272
|
-
</div>
|
|
273
|
-
</div>
|
|
274
|
-
{/each}
|
|
275
|
-
</div>
|
|
276
|
-
</div>
|
|
277
|
-
{:else if filteredModels.length === 0}
|
|
278
|
-
<div class="py-4 text-sm text-slate-500 text-center">
|
|
279
|
-
{searchQuery ? 'No models matching your search.' : 'No models available for this engine.'}
|
|
280
|
-
</div>
|
|
281
|
-
{:else}
|
|
282
|
-
<!-- Grouped by provider with accordion -->
|
|
283
|
-
{#each [...groupedModels.entries()] as [provider, providerModels] (provider)}
|
|
284
|
-
{@const isCollapsed = collapsedProviders.has(provider)}
|
|
285
|
-
{@const hasSelectedModel = providerModels.some(m => m.id === settings.selectedModel)}
|
|
286
|
-
<div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
|
|
287
|
-
<!-- Accordion header -->
|
|
288
|
-
<button
|
|
289
|
-
type="button"
|
|
290
|
-
class="flex items-center gap-2.5 w-full px-3 py-2.5 text-left cursor-pointer transition-colors
|
|
291
|
-
bg-white/80 dark:bg-slate-800/40 hover:bg-white dark:hover:bg-slate-800/60"
|
|
292
|
-
onclick={() => toggleProvider(provider)}
|
|
293
|
-
>
|
|
294
|
-
<svg viewBox="0 0 24 24" fill="none"
|
|
295
|
-
class="w-4 h-4 text-slate-400 transition-transform duration-200 flex-shrink-0
|
|
296
|
-
{isCollapsed ? '' : 'rotate-90'}"
|
|
297
|
-
aria-hidden="true">
|
|
298
|
-
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
299
|
-
</svg>
|
|
300
|
-
<span class="text-sm font-semibold text-slate-800 dark:text-slate-200">
|
|
301
|
-
{formatProvider(provider)}
|
|
302
|
-
</span>
|
|
303
|
-
<span class="text-xs text-slate-400 dark:text-slate-500">
|
|
304
|
-
{providerModels.length} {providerModels.length === 1 ? 'model' : 'models'}
|
|
305
|
-
</span>
|
|
306
|
-
{#if hasSelectedModel}
|
|
307
|
-
<div class="w-1.5 h-1.5 rounded-full bg-violet-500 ml-auto flex-shrink-0"></div>
|
|
308
|
-
{/if}
|
|
309
|
-
</button>
|
|
310
|
-
|
|
311
|
-
<!-- Accordion body -->
|
|
312
|
-
{#if !isCollapsed}
|
|
313
|
-
<div class="flex flex-col bg-white/40 dark:bg-slate-800/20">
|
|
314
|
-
{#each providerModels as model (model.id)}
|
|
315
|
-
{@const isSelected = settings.selectedModel === model.id}
|
|
316
|
-
{@const caps = model.capabilities}
|
|
317
|
-
<button
|
|
318
|
-
type="button"
|
|
319
|
-
class="flex items-start gap-3 px-3 py-2.5 text-left cursor-pointer transition-all duration-150
|
|
320
|
-
{isSelected
|
|
321
|
-
? 'bg-violet-500/10 dark:bg-violet-500/12'
|
|
322
|
-
: 'hover:bg-slate-100/80 dark:hover:bg-slate-700/30'}"
|
|
323
|
-
onclick={() => selectModel(model.id)}
|
|
324
|
-
>
|
|
325
|
-
<!-- Radio indicator -->
|
|
326
|
-
<div class="flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center mt-0.5
|
|
327
|
-
{isSelected ? 'border-violet-600' : 'border-slate-300 dark:border-slate-600'}">
|
|
328
|
-
{#if isSelected}
|
|
329
|
-
<div class="w-2 h-2 rounded-full bg-violet-600"></div>
|
|
330
|
-
{/if}
|
|
331
|
-
</div>
|
|
332
|
-
|
|
333
|
-
<!-- Model info -->
|
|
334
|
-
<div class="flex-1 min-w-0">
|
|
335
|
-
<div class="flex items-center gap-2">
|
|
336
|
-
<span class="text-sm font-medium text-slate-900 dark:text-slate-100">{model.name}</span>
|
|
337
|
-
<!-- <span class="text-2xs text-slate-400 dark:text-slate-500">{formatContext(model.contextWindow)}</span> -->
|
|
338
|
-
</div>
|
|
339
|
-
{#if caps.length > 0}
|
|
340
|
-
<div class="flex flex-wrap gap-1 mt-1.5">
|
|
341
|
-
{#each caps as cap}
|
|
342
|
-
<span class="px-1.5 py-0.5 text-2xs rounded bg-slate-100 dark:bg-slate-700/50 text-slate-500 dark:text-slate-400 leading-none">
|
|
343
|
-
{cap}
|
|
344
|
-
</span>
|
|
345
|
-
{/each}
|
|
346
|
-
</div>
|
|
347
|
-
{/if}
|
|
348
|
-
</div>
|
|
349
|
-
</button>
|
|
350
|
-
{/each}
|
|
351
|
-
</div>
|
|
352
|
-
{/if}
|
|
353
|
-
</div>
|
|
354
|
-
{/each}
|
|
355
|
-
{/if}
|
|
356
|
-
</div>
|
|
357
|
-
</div>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { settings, updateSettings } from '$frontend/lib/stores/features/settings.svelte';
|
|
3
|
+
import { modelStore } from '$frontend/lib/stores/features/models.svelte';
|
|
4
|
+
import { ENGINES, type EngineType } from '$shared/constants/engines';
|
|
5
|
+
import type { EngineModel } from '$shared/types/engine';
|
|
6
|
+
|
|
7
|
+
let searchQuery = $state('');
|
|
8
|
+
let refreshing = $state(false);
|
|
9
|
+
let collapsedProviders = $state<Set<string>>(new Set());
|
|
10
|
+
|
|
11
|
+
// Handle engine selection — restore remembered model or pick first
|
|
12
|
+
async function selectEngine(engineType: EngineType) {
|
|
13
|
+
updateSettings({ selectedEngine: engineType });
|
|
14
|
+
searchQuery = '';
|
|
15
|
+
|
|
16
|
+
// Restore remembered model for this engine
|
|
17
|
+
const memory = settings.engineModelMemory || {};
|
|
18
|
+
const remembered = memory[engineType];
|
|
19
|
+
|
|
20
|
+
if (engineType !== 'claude-code') {
|
|
21
|
+
const models = await modelStore.fetchModels(engineType);
|
|
22
|
+
const target = (remembered && models.find(m => m.id === remembered))
|
|
23
|
+
|| models.find(m => m.recommended)
|
|
24
|
+
|| models[0];
|
|
25
|
+
if (target) {
|
|
26
|
+
updateSettings({
|
|
27
|
+
selectedModel: target.id,
|
|
28
|
+
engineModelMemory: { ...memory, [engineType]: target.id }
|
|
29
|
+
});
|
|
30
|
+
} else {
|
|
31
|
+
// No models available — clear the model selection
|
|
32
|
+
updateSettings({ selectedModel: '' });
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
const models = modelStore.getByEngine('claude-code');
|
|
36
|
+
const target = (remembered && models.find(m => m.id === remembered))
|
|
37
|
+
|| models.find(m => m.recommended)
|
|
38
|
+
|| models[0];
|
|
39
|
+
if (target) {
|
|
40
|
+
updateSettings({
|
|
41
|
+
selectedModel: target.id,
|
|
42
|
+
engineModelMemory: { ...memory, [engineType]: target.id }
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
// No models available — clear the model selection
|
|
46
|
+
updateSettings({ selectedModel: '' });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Open accordion for the selected model's provider
|
|
51
|
+
syncAccordionState();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle model selection — also save to per-engine memory
|
|
55
|
+
function selectModel(modelId: string) {
|
|
56
|
+
const memory = settings.engineModelMemory || {};
|
|
57
|
+
updateSettings({
|
|
58
|
+
selectedModel: modelId,
|
|
59
|
+
engineModelMemory: { ...memory, [settings.selectedEngine]: modelId }
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Refresh models (bypass cache)
|
|
64
|
+
async function handleRefresh() {
|
|
65
|
+
refreshing = true;
|
|
66
|
+
try {
|
|
67
|
+
await modelStore.refreshModels(settings.selectedEngine);
|
|
68
|
+
} finally {
|
|
69
|
+
refreshing = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Toggle provider accordion
|
|
74
|
+
function toggleProvider(provider: string) {
|
|
75
|
+
const next = new Set(collapsedProviders);
|
|
76
|
+
if (next.has(provider)) {
|
|
77
|
+
next.delete(provider);
|
|
78
|
+
} else {
|
|
79
|
+
next.add(provider);
|
|
80
|
+
}
|
|
81
|
+
collapsedProviders = next;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Sync accordion state: open only the provider containing the selected model
|
|
85
|
+
function syncAccordionState() {
|
|
86
|
+
const allProviders = [...groupedModels.keys()];
|
|
87
|
+
const selectedModel = settings.selectedModel;
|
|
88
|
+
let selectedProvider: string | null = null;
|
|
89
|
+
|
|
90
|
+
for (const [provider, models] of groupedModels) {
|
|
91
|
+
if (models.some(m => m.id === selectedModel)) {
|
|
92
|
+
selectedProvider = provider;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Collapse all, then open the one with selected model
|
|
98
|
+
const collapsed = new Set(allProviders);
|
|
99
|
+
if (selectedProvider) {
|
|
100
|
+
collapsed.delete(selectedProvider);
|
|
101
|
+
}
|
|
102
|
+
collapsedProviders = collapsed;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get models for the currently selected engine, filtered by search
|
|
106
|
+
const filteredModels = $derived.by(() => {
|
|
107
|
+
const models = modelStore.getByEngine(settings.selectedEngine);
|
|
108
|
+
if (!searchQuery.trim()) return models;
|
|
109
|
+
|
|
110
|
+
const q = searchQuery.toLowerCase();
|
|
111
|
+
return models.filter(m =>
|
|
112
|
+
m.name.toLowerCase().includes(q) ||
|
|
113
|
+
m.modelId.toLowerCase().includes(q) ||
|
|
114
|
+
m.provider.toLowerCase().includes(q) ||
|
|
115
|
+
m.capabilities.some(c => c.toLowerCase().includes(q))
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Group models by provider
|
|
120
|
+
const groupedModels = $derived.by(() => {
|
|
121
|
+
const groups = new Map<string, EngineModel[]>();
|
|
122
|
+
for (const model of filteredModels) {
|
|
123
|
+
const key = model.provider;
|
|
124
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
125
|
+
groups.get(key)!.push(model);
|
|
126
|
+
}
|
|
127
|
+
return groups;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Fetch models on mount for non-claude-code, then sync accordion
|
|
131
|
+
$effect(() => {
|
|
132
|
+
if (settings.selectedEngine !== 'claude-code') {
|
|
133
|
+
modelStore.fetchModels(settings.selectedEngine);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Sync accordion: open all when searching, restore default when cleared
|
|
138
|
+
$effect(() => {
|
|
139
|
+
if (searchQuery.trim()) {
|
|
140
|
+
// Searching — open all accordions
|
|
141
|
+
collapsedProviders = new Set();
|
|
142
|
+
} else if (groupedModels.size > 0) {
|
|
143
|
+
// Not searching — only open the one with selected model
|
|
144
|
+
syncAccordionState();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
function formatContext(tokens: number): string {
|
|
149
|
+
return tokens >= 1000000
|
|
150
|
+
? `${(tokens / 1000000).toFixed(1)}M`
|
|
151
|
+
: `${(tokens / 1000).toFixed(0)}K`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function formatProvider(provider: string): string {
|
|
155
|
+
return provider
|
|
156
|
+
.split(/[-_]/)
|
|
157
|
+
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
|
158
|
+
.join(' ');
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
<!-- Claude SVG logo (Anthropic) -->
|
|
163
|
+
{#snippet claudeLogo(active: boolean)}
|
|
164
|
+
<svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
|
|
165
|
+
<path d="M16.091 4L9.115 20h-1.19L14.901 4h1.19zm-5.726 5.2L14.8 20h-1.218L9.2 9.6l1.165-.4z"
|
|
166
|
+
fill={active ? '#8b5cf6' : 'currentColor'} />
|
|
167
|
+
</svg>
|
|
168
|
+
{/snippet}
|
|
169
|
+
|
|
170
|
+
<!-- OpenCode SVG logo -->
|
|
171
|
+
{#snippet opencodeLogo(active: boolean)}
|
|
172
|
+
<svg viewBox="0 0 24 24" fill="none" class="w-5 h-5" aria-hidden="true">
|
|
173
|
+
<path d="M8.5 6L3 12l5.5 6M15.5 6L21 12l-5.5 6M13.5 4l-3 16"
|
|
174
|
+
stroke={active ? '#8b5cf6' : 'currentColor'}
|
|
175
|
+
stroke-width="2"
|
|
176
|
+
stroke-linecap="round"
|
|
177
|
+
stroke-linejoin="round" />
|
|
178
|
+
</svg>
|
|
179
|
+
{/snippet}
|
|
180
|
+
|
|
181
|
+
<div class="py-1">
|
|
182
|
+
<!-- Engine Selection -->
|
|
183
|
+
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">AI Engine</h3>
|
|
184
|
+
<p class="text-sm text-slate-600 dark:text-slate-500 mb-4">
|
|
185
|
+
Select the AI engine to power your conversations
|
|
186
|
+
</p>
|
|
187
|
+
|
|
188
|
+
<div class="flex gap-3 mb-6">
|
|
189
|
+
{#each ENGINES as engine (engine.type)}
|
|
190
|
+
{@const isActive = settings.selectedEngine === engine.type}
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
class="flex-1 flex items-center gap-3 p-3.5 overflow-hidden border-2 rounded-xl text-left cursor-pointer transition-all duration-200
|
|
194
|
+
{isActive
|
|
195
|
+
? 'border-violet-600 bg-gradient-to-br from-violet-500/10 to-purple-500/5 dark:from-violet-500/12 dark:to-purple-500/8'
|
|
196
|
+
: 'border-slate-200 dark:border-slate-800 bg-slate-100/80 dark:bg-slate-800/80 hover:border-violet-500/20 dark:hover:border-violet-500/35'}"
|
|
197
|
+
onclick={() => selectEngine(engine.type)}
|
|
198
|
+
>
|
|
199
|
+
<div>
|
|
200
|
+
<div class="flex dark:hidden items-center justify-center w-5 h-5">{@html engine.icon.light}</div>
|
|
201
|
+
<div class="hidden dark:flex items-center justify-center w-5 h-5">{@html engine.icon.dark}</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div>
|
|
204
|
+
<div class="font-bold text-sm text-slate-900 dark:text-slate-100">{engine.name}</div>
|
|
205
|
+
<div class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">{engine.description}</div>
|
|
206
|
+
</div>
|
|
207
|
+
{#if isActive}
|
|
208
|
+
<div class="flex items-center justify-center w-5 h-5 bg-gradient-to-br from-violet-600 to-purple-600 rounded-full text-white ml-auto flex-shrink-0">
|
|
209
|
+
<svg viewBox="0 0 24 24" fill="none" class="w-3 h-3" aria-hidden="true">
|
|
210
|
+
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" />
|
|
211
|
+
</svg>
|
|
212
|
+
</div>
|
|
213
|
+
{/if}
|
|
214
|
+
</button>
|
|
215
|
+
{/each}
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<!-- Model Selection -->
|
|
219
|
+
<div class="flex items-center justify-between mb-1.5">
|
|
220
|
+
<h3 class="text-base font-bold text-slate-900 dark:text-slate-100">Model</h3>
|
|
221
|
+
<button
|
|
222
|
+
type="button"
|
|
223
|
+
class="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-lg transition-colors cursor-pointer
|
|
224
|
+
text-slate-500 hover:text-violet-600 hover:bg-violet-500/10 dark:hover:text-violet-400 dark:hover:bg-violet-500/15
|
|
225
|
+
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
226
|
+
onclick={handleRefresh}
|
|
227
|
+
disabled={refreshing || modelStore.loading}
|
|
228
|
+
>
|
|
229
|
+
<svg viewBox="0 0 24 24" fill="none" class="w-3.5 h-3.5 {refreshing ? 'animate-spin' : ''}" aria-hidden="true">
|
|
230
|
+
<path d="M21 12a9 9 0 11-2.636-6.364M21 3v5h-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
231
|
+
</svg>
|
|
232
|
+
{refreshing ? 'Refreshing...' : 'Refresh'}
|
|
233
|
+
</button>
|
|
234
|
+
</div>
|
|
235
|
+
<p class="text-sm text-slate-600 dark:text-slate-500 mb-3">
|
|
236
|
+
Select the AI model for the {ENGINES.find(e => e.type === settings.selectedEngine)?.name || 'selected'} engine
|
|
237
|
+
</p>
|
|
238
|
+
|
|
239
|
+
<!-- Search -->
|
|
240
|
+
<div class="relative mb-3">
|
|
241
|
+
<svg viewBox="0 0 24 24" fill="none" class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-slate-400 pointer-events-none" aria-hidden="true">
|
|
242
|
+
<circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2" />
|
|
243
|
+
<path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
244
|
+
</svg>
|
|
245
|
+
<input
|
|
246
|
+
type="text"
|
|
247
|
+
bind:value={searchQuery}
|
|
248
|
+
placeholder="Search models..."
|
|
249
|
+
class="w-full pl-9 pr-3 py-2 text-sm bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-600 transition-colors text-slate-900 dark:text-slate-100 placeholder-slate-400"
|
|
250
|
+
/>
|
|
251
|
+
</div>
|
|
252
|
+
|
|
253
|
+
<!-- Model List -->
|
|
254
|
+
<div class="flex flex-col gap-1.5">
|
|
255
|
+
{#if modelStore.loading && settings.selectedEngine !== 'claude-code' && !refreshing}
|
|
256
|
+
<!-- Loading skeleton for Open Code only -->
|
|
257
|
+
<div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
|
|
258
|
+
<div class="bg-white/80 dark:bg-slate-800/40 px-3 py-3 flex items-center gap-3">
|
|
259
|
+
<div class="w-4 h-4 rounded-full bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
|
|
260
|
+
<div class="h-3.5 w-32 rounded bg-slate-200 dark:bg-slate-700 animate-pulse"></div>
|
|
261
|
+
</div>
|
|
262
|
+
<div class="px-4 py-2.5 space-y-2.5">
|
|
263
|
+
{#each Array(3) as _}
|
|
264
|
+
<div class="flex items-center gap-3 py-2">
|
|
265
|
+
<div class="w-4 h-4 rounded-full bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
|
|
266
|
+
<div class="flex-1 space-y-1.5">
|
|
267
|
+
<div class="h-3.5 w-40 rounded bg-slate-200/80 dark:bg-slate-700/60 animate-pulse"></div>
|
|
268
|
+
<div class="flex gap-1.5">
|
|
269
|
+
<div class="h-3 w-14 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
|
|
270
|
+
<div class="h-3 w-12 rounded bg-slate-200/60 dark:bg-slate-700/40 animate-pulse"></div>
|
|
271
|
+
</div>
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{/each}
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
{:else if filteredModels.length === 0}
|
|
278
|
+
<div class="py-4 text-sm text-slate-500 text-center">
|
|
279
|
+
{searchQuery ? 'No models matching your search.' : 'No models available for this engine.'}
|
|
280
|
+
</div>
|
|
281
|
+
{:else}
|
|
282
|
+
<!-- Grouped by provider with accordion -->
|
|
283
|
+
{#each [...groupedModels.entries()] as [provider, providerModels] (provider)}
|
|
284
|
+
{@const isCollapsed = collapsedProviders.has(provider)}
|
|
285
|
+
{@const hasSelectedModel = providerModels.some(m => m.id === settings.selectedModel)}
|
|
286
|
+
<div class="border border-slate-200/80 dark:border-slate-700/50 rounded-lg overflow-hidden">
|
|
287
|
+
<!-- Accordion header -->
|
|
288
|
+
<button
|
|
289
|
+
type="button"
|
|
290
|
+
class="flex items-center gap-2.5 w-full px-3 py-2.5 text-left cursor-pointer transition-colors
|
|
291
|
+
bg-white/80 dark:bg-slate-800/40 hover:bg-white dark:hover:bg-slate-800/60"
|
|
292
|
+
onclick={() => toggleProvider(provider)}
|
|
293
|
+
>
|
|
294
|
+
<svg viewBox="0 0 24 24" fill="none"
|
|
295
|
+
class="w-4 h-4 text-slate-400 transition-transform duration-200 flex-shrink-0
|
|
296
|
+
{isCollapsed ? '' : 'rotate-90'}"
|
|
297
|
+
aria-hidden="true">
|
|
298
|
+
<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
299
|
+
</svg>
|
|
300
|
+
<span class="text-sm font-semibold text-slate-800 dark:text-slate-200">
|
|
301
|
+
{formatProvider(provider)}
|
|
302
|
+
</span>
|
|
303
|
+
<span class="text-xs text-slate-400 dark:text-slate-500">
|
|
304
|
+
{providerModels.length} {providerModels.length === 1 ? 'model' : 'models'}
|
|
305
|
+
</span>
|
|
306
|
+
{#if hasSelectedModel}
|
|
307
|
+
<div class="w-1.5 h-1.5 rounded-full bg-violet-500 ml-auto flex-shrink-0"></div>
|
|
308
|
+
{/if}
|
|
309
|
+
</button>
|
|
310
|
+
|
|
311
|
+
<!-- Accordion body -->
|
|
312
|
+
{#if !isCollapsed}
|
|
313
|
+
<div class="flex flex-col bg-white/40 dark:bg-slate-800/20">
|
|
314
|
+
{#each providerModels as model (model.id)}
|
|
315
|
+
{@const isSelected = settings.selectedModel === model.id}
|
|
316
|
+
{@const caps = model.capabilities}
|
|
317
|
+
<button
|
|
318
|
+
type="button"
|
|
319
|
+
class="flex items-start gap-3 px-3 py-2.5 text-left cursor-pointer transition-all duration-150
|
|
320
|
+
{isSelected
|
|
321
|
+
? 'bg-violet-500/10 dark:bg-violet-500/12'
|
|
322
|
+
: 'hover:bg-slate-100/80 dark:hover:bg-slate-700/30'}"
|
|
323
|
+
onclick={() => selectModel(model.id)}
|
|
324
|
+
>
|
|
325
|
+
<!-- Radio indicator -->
|
|
326
|
+
<div class="flex-shrink-0 w-4 h-4 rounded-full border-2 flex items-center justify-center mt-0.5
|
|
327
|
+
{isSelected ? 'border-violet-600' : 'border-slate-300 dark:border-slate-600'}">
|
|
328
|
+
{#if isSelected}
|
|
329
|
+
<div class="w-2 h-2 rounded-full bg-violet-600"></div>
|
|
330
|
+
{/if}
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<!-- Model info -->
|
|
334
|
+
<div class="flex-1 min-w-0">
|
|
335
|
+
<div class="flex items-center gap-2">
|
|
336
|
+
<span class="text-sm font-medium text-slate-900 dark:text-slate-100">{model.name}</span>
|
|
337
|
+
<!-- <span class="text-2xs text-slate-400 dark:text-slate-500">{formatContext(model.contextWindow)}</span> -->
|
|
338
|
+
</div>
|
|
339
|
+
{#if caps.length > 0}
|
|
340
|
+
<div class="flex flex-wrap gap-1 mt-1.5">
|
|
341
|
+
{#each caps as cap}
|
|
342
|
+
<span class="px-1.5 py-0.5 text-2xs rounded bg-slate-100 dark:bg-slate-700/50 text-slate-500 dark:text-slate-400 leading-none">
|
|
343
|
+
{cap}
|
|
344
|
+
</span>
|
|
345
|
+
{/each}
|
|
346
|
+
</div>
|
|
347
|
+
{/if}
|
|
348
|
+
</div>
|
|
349
|
+
</button>
|
|
350
|
+
{/each}
|
|
351
|
+
</div>
|
|
352
|
+
{/if}
|
|
353
|
+
</div>
|
|
354
|
+
{/each}
|
|
355
|
+
{/if}
|
|
356
|
+
</div>
|
|
357
|
+
</div>
|