@ottocode/web-sdk 0.1.226 → 0.1.227
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/dist/components/chat/FileMentionPopup.d.ts.map +1 -1
- package/dist/components/index.js +25148 -80
- package/dist/components/index.js.map +180 -1
- package/dist/components/messages/AssistantMessageGroup.d.ts.map +1 -1
- package/dist/components/messages/CompactActivityGroup.d.ts +10 -0
- package/dist/components/messages/CompactActivityGroup.d.ts.map +1 -0
- package/dist/components/messages/MessagePartItem.d.ts.map +1 -1
- package/dist/components/messages/MessageThread.d.ts +1 -0
- package/dist/components/messages/MessageThread.d.ts.map +1 -1
- package/dist/components/messages/MessageThreadContainer.d.ts.map +1 -1
- package/dist/components/messages/compactActivity.d.ts +34 -0
- package/dist/components/messages/compactActivity.d.ts.map +1 -0
- package/dist/components/messages/renderers/EditRenderer.d.ts +1 -1
- package/dist/components/messages/renderers/EditRenderer.d.ts.map +1 -1
- package/dist/components/messages/renderers/index.d.ts.map +1 -1
- package/dist/components/messages/renderers/types.d.ts +3 -1
- package/dist/components/messages/renderers/types.d.ts.map +1 -1
- package/dist/components/settings/SettingsSidebar.d.ts.map +1 -1
- package/dist/components/settings/SetuTopupModal.d.ts.map +1 -1
- package/dist/hooks/index.js +4812 -29
- package/dist/hooks/index.js.map +67 -1
- package/dist/hooks/useAuthStatus.d.ts.map +1 -1
- package/dist/hooks/useMCP.d.ts +9 -0
- package/dist/hooks/useMCP.d.ts.map +1 -1
- package/dist/hooks/usePreferences.d.ts +1 -0
- package/dist/hooks/usePreferences.d.ts.map +1 -1
- package/dist/hooks/useSessionStream.d.ts.map +1 -1
- package/dist/index.js +25918 -7
- package/dist/index.js.map +185 -1
- package/dist/lib/api-client/approval.d.ts.map +1 -1
- package/dist/lib/index.js +1007 -5
- package/dist/lib/index.js.map +22 -1
- package/dist/stores/index.js +693 -22
- package/dist/stores/index.js.map +30 -1
- package/dist/stores/toastStore.d.ts +1 -0
- package/dist/stores/toastStore.d.ts.map +1 -1
- package/dist/types/api.js +2 -2
- package/dist/types/api.js.map +9 -1
- package/package.json +4 -4
- package/dist/assets/provider-logos.js +0 -43
- package/dist/assets/provider-logos.js.map +0 -1
- package/dist/components/branch/BranchModal.js +0 -45
- package/dist/components/branch/BranchModal.js.map +0 -1
- package/dist/components/chat/ChatInput.js +0 -267
- package/dist/components/chat/ChatInput.js.map +0 -1
- package/dist/components/chat/ChatInputContainer.js +0 -295
- package/dist/components/chat/ChatInputContainer.js.map +0 -1
- package/dist/components/chat/ChatInputKeyHandler.js +0 -75
- package/dist/components/chat/ChatInputKeyHandler.js.map +0 -1
- package/dist/components/chat/CommandSuggestionsPopup.js +0 -45
- package/dist/components/chat/CommandSuggestionsPopup.js.map +0 -1
- package/dist/components/chat/ConfigModal.js +0 -46
- package/dist/components/chat/ConfigModal.js.map +0 -1
- package/dist/components/chat/ConfigSelector.js +0 -47
- package/dist/components/chat/ConfigSelector.js.map +0 -1
- package/dist/components/chat/FileMentionPopup.js +0 -138
- package/dist/components/chat/FileMentionPopup.js.map +0 -1
- package/dist/components/chat/ShortcutsModal.js +0 -275
- package/dist/components/chat/ShortcutsModal.js.map +0 -1
- package/dist/components/chat/StopButton.js +0 -25
- package/dist/components/chat/StopButton.js.map +0 -1
- package/dist/components/chat/UnifiedAgentSelector.js +0 -107
- package/dist/components/chat/UnifiedAgentSelector.js.map +0 -1
- package/dist/components/chat/UnifiedModelSelector.js +0 -209
- package/dist/components/chat/UnifiedModelSelector.js.map +0 -1
- package/dist/components/common/ProviderLogo.js +0 -30
- package/dist/components/common/ProviderLogo.js.map +0 -1
- package/dist/components/common/StatusIndicator.js +0 -54
- package/dist/components/common/StatusIndicator.js.map +0 -1
- package/dist/components/common/UsageModal.js +0 -55
- package/dist/components/common/UsageModal.js.map +0 -1
- package/dist/components/common/UsageRing.js +0 -22
- package/dist/components/common/UsageRing.js.map +0 -1
- package/dist/components/file-browser/FileBrowserSidebar.js +0 -50
- package/dist/components/file-browser/FileBrowserSidebar.js.map +0 -1
- package/dist/components/file-browser/FileBrowserSidebarToggle.js +0 -12
- package/dist/components/file-browser/FileBrowserSidebarToggle.js.map +0 -1
- package/dist/components/file-browser/FileViewerPanel.js +0 -85
- package/dist/components/file-browser/FileViewerPanel.js.map +0 -1
- package/dist/components/file-browser/QuickFilePicker.js +0 -118
- package/dist/components/file-browser/QuickFilePicker.js.map +0 -1
- package/dist/components/git/GitCommitModal.js +0 -70
- package/dist/components/git/GitCommitModal.js.map +0 -1
- package/dist/components/git/GitDiffPanel.js +0 -60
- package/dist/components/git/GitDiffPanel.js.map +0 -1
- package/dist/components/git/GitDiffViewer.js +0 -224
- package/dist/components/git/GitDiffViewer.js.map +0 -1
- package/dist/components/git/GitFileItem.js +0 -145
- package/dist/components/git/GitFileItem.js.map +0 -1
- package/dist/components/git/GitFileList.js +0 -108
- package/dist/components/git/GitFileList.js.map +0 -1
- package/dist/components/git/GitSidebar.js +0 -156
- package/dist/components/git/GitSidebar.js.map +0 -1
- package/dist/components/git/GitSidebarToggle.js +0 -18
- package/dist/components/git/GitSidebarToggle.js.map +0 -1
- package/dist/components/mcp/AddMCPServerModal.js +0 -166
- package/dist/components/mcp/AddMCPServerModal.js.map +0 -1
- package/dist/components/mcp/MCPSidebar.js +0 -192
- package/dist/components/mcp/MCPSidebar.js.map +0 -1
- package/dist/components/mcp/MCPSidebarToggle.js +0 -14
- package/dist/components/mcp/MCPSidebarToggle.js.map +0 -1
- package/dist/components/mcp/index.js +0 -4
- package/dist/components/mcp/index.js.map +0 -1
- package/dist/components/messages/AssistantMessageGroup.js +0 -176
- package/dist/components/messages/AssistantMessageGroup.js.map +0 -1
- package/dist/components/messages/MessagePartItem.js +0 -362
- package/dist/components/messages/MessagePartItem.js.map +0 -1
- package/dist/components/messages/MessageThread.js +0 -239
- package/dist/components/messages/MessageThread.js.map +0 -1
- package/dist/components/messages/MessageThreadContainer.js +0 -22
- package/dist/components/messages/MessageThreadContainer.js.map +0 -1
- package/dist/components/messages/ToolApprovalCard.js +0 -143
- package/dist/components/messages/ToolApprovalCard.js.map +0 -1
- package/dist/components/messages/TopupApprovalCard.js +0 -52
- package/dist/components/messages/TopupApprovalCard.js.map +0 -1
- package/dist/components/messages/UserMessageGroup.js +0 -132
- package/dist/components/messages/UserMessageGroup.js.map +0 -1
- package/dist/components/messages/renderers/ApplyPatchRenderer.js +0 -55
- package/dist/components/messages/renderers/ApplyPatchRenderer.js.map +0 -1
- package/dist/components/messages/renderers/BashRenderer.js +0 -55
- package/dist/components/messages/renderers/BashRenderer.js.map +0 -1
- package/dist/components/messages/renderers/DatabaseToolRenderer.js +0 -151
- package/dist/components/messages/renderers/DatabaseToolRenderer.js.map +0 -1
- package/dist/components/messages/renderers/DebugRenderer.js +0 -5
- package/dist/components/messages/renderers/DebugRenderer.js.map +0 -1
- package/dist/components/messages/renderers/DiffView.js +0 -206
- package/dist/components/messages/renderers/DiffView.js.map +0 -1
- package/dist/components/messages/renderers/EditRenderer.js +0 -25
- package/dist/components/messages/renderers/EditRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ErrorRenderer.js +0 -162
- package/dist/components/messages/renderers/ErrorRenderer.js.map +0 -1
- package/dist/components/messages/renderers/FinishRenderer.js +0 -7
- package/dist/components/messages/renderers/FinishRenderer.js.map +0 -1
- package/dist/components/messages/renderers/GenericRenderer.js +0 -50
- package/dist/components/messages/renderers/GenericRenderer.js.map +0 -1
- package/dist/components/messages/renderers/GitCommitRenderer.js +0 -21
- package/dist/components/messages/renderers/GitCommitRenderer.js.map +0 -1
- package/dist/components/messages/renderers/GitDiffRenderer.js +0 -34
- package/dist/components/messages/renderers/GitDiffRenderer.js.map +0 -1
- package/dist/components/messages/renderers/GitStatusRenderer.js +0 -60
- package/dist/components/messages/renderers/GitStatusRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ListRenderer.js +0 -28
- package/dist/components/messages/renderers/ListRenderer.js.map +0 -1
- package/dist/components/messages/renderers/LoadMcpToolsRenderer.js +0 -20
- package/dist/components/messages/renderers/LoadMcpToolsRenderer.js.map +0 -1
- package/dist/components/messages/renderers/McpToolRenderer.js +0 -68
- package/dist/components/messages/renderers/McpToolRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ProgressUpdateRenderer.js +0 -9
- package/dist/components/messages/renderers/ProgressUpdateRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ReadRenderer.js +0 -87
- package/dist/components/messages/renderers/ReadRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ReasoningRenderer.js +0 -34
- package/dist/components/messages/renderers/ReasoningRenderer.js.map +0 -1
- package/dist/components/messages/renderers/SearchRenderer.js +0 -52
- package/dist/components/messages/renderers/SearchRenderer.js.map +0 -1
- package/dist/components/messages/renderers/SkillRenderer.js +0 -36
- package/dist/components/messages/renderers/SkillRenderer.js.map +0 -1
- package/dist/components/messages/renderers/TerminalRenderer.js +0 -84
- package/dist/components/messages/renderers/TerminalRenderer.js.map +0 -1
- package/dist/components/messages/renderers/TodosRenderer.js +0 -15
- package/dist/components/messages/renderers/TodosRenderer.js.map +0 -1
- package/dist/components/messages/renderers/ToolErrorDisplay.js +0 -10
- package/dist/components/messages/renderers/ToolErrorDisplay.js.map +0 -1
- package/dist/components/messages/renderers/TreeRenderer.js +0 -33
- package/dist/components/messages/renderers/TreeRenderer.js.map +0 -1
- package/dist/components/messages/renderers/WebSearchRenderer.js +0 -29
- package/dist/components/messages/renderers/WebSearchRenderer.js.map +0 -1
- package/dist/components/messages/renderers/WriteRenderer.js +0 -28
- package/dist/components/messages/renderers/WriteRenderer.js.map +0 -1
- package/dist/components/messages/renderers/index.js +0 -129
- package/dist/components/messages/renderers/index.js.map +0 -1
- package/dist/components/messages/renderers/shared/CopyButton.js +0 -27
- package/dist/components/messages/renderers/shared/CopyButton.js.map +0 -1
- package/dist/components/messages/renderers/shared/ImagePreview.js +0 -14
- package/dist/components/messages/renderers/shared/ImagePreview.js.map +0 -1
- package/dist/components/messages/renderers/shared/ToolContentBox.js +0 -9
- package/dist/components/messages/renderers/shared/ToolContentBox.js.map +0 -1
- package/dist/components/messages/renderers/shared/ToolHeader.js +0 -69
- package/dist/components/messages/renderers/shared/ToolHeader.js.map +0 -1
- package/dist/components/messages/renderers/shared/index.js +0 -5
- package/dist/components/messages/renderers/shared/index.js.map +0 -1
- package/dist/components/messages/renderers/types.js +0 -2
- package/dist/components/messages/renderers/types.js.map +0 -1
- package/dist/components/messages/renderers/utils.js +0 -60
- package/dist/components/messages/renderers/utils.js.map +0 -1
- package/dist/components/onboarding/OnboardingModal.js +0 -22
- package/dist/components/onboarding/OnboardingModal.js.map +0 -1
- package/dist/components/onboarding/index.js +0 -4
- package/dist/components/onboarding/index.js.map +0 -1
- package/dist/components/onboarding/steps/DefaultsStep.js +0 -104
- package/dist/components/onboarding/steps/DefaultsStep.js.map +0 -1
- package/dist/components/onboarding/steps/ProviderSetupStep.js +0 -398
- package/dist/components/onboarding/steps/ProviderSetupStep.js.map +0 -1
- package/dist/components/research/ResearchSidebar.js +0 -288
- package/dist/components/research/ResearchSidebar.js.map +0 -1
- package/dist/components/research/ResearchSidebarToggle.js +0 -15
- package/dist/components/research/ResearchSidebarToggle.js.map +0 -1
- package/dist/components/research/index.js +0 -3
- package/dist/components/research/index.js.map +0 -1
- package/dist/components/session-files/SessionFilesDiffPanel.js +0 -345
- package/dist/components/session-files/SessionFilesDiffPanel.js.map +0 -1
- package/dist/components/session-files/SessionFilesSidebar.js +0 -87
- package/dist/components/session-files/SessionFilesSidebar.js.map +0 -1
- package/dist/components/session-files/SessionFilesSidebarToggle.js +0 -15
- package/dist/components/session-files/SessionFilesSidebarToggle.js.map +0 -1
- package/dist/components/session-files/index.js +0 -4
- package/dist/components/session-files/index.js.map +0 -1
- package/dist/components/sessions/EditableTitle.js +0 -40
- package/dist/components/sessions/EditableTitle.js.map +0 -1
- package/dist/components/sessions/LeanHeader.js +0 -70
- package/dist/components/sessions/LeanHeader.js.map +0 -1
- package/dist/components/sessions/SessionHeader.js +0 -75
- package/dist/components/sessions/SessionHeader.js.map +0 -1
- package/dist/components/sessions/SessionItem.js +0 -28
- package/dist/components/sessions/SessionItem.js.map +0 -1
- package/dist/components/sessions/SessionListContainer.js +0 -86
- package/dist/components/sessions/SessionListContainer.js.map +0 -1
- package/dist/components/settings/SettingsSidebar.js +0 -212
- package/dist/components/settings/SettingsSidebar.js.map +0 -1
- package/dist/components/settings/SettingsSidebarToggle.js +0 -12
- package/dist/components/settings/SettingsSidebarToggle.js.map +0 -1
- package/dist/components/settings/SetuTopupModal.js +0 -306
- package/dist/components/settings/SetuTopupModal.js.map +0 -1
- package/dist/components/skills/SkillViewerPanel.js +0 -89
- package/dist/components/skills/SkillViewerPanel.js.map +0 -1
- package/dist/components/skills/SkillsSidebar.js +0 -59
- package/dist/components/skills/SkillsSidebar.js.map +0 -1
- package/dist/components/skills/SkillsSidebarToggle.js +0 -12
- package/dist/components/skills/SkillsSidebarToggle.js.map +0 -1
- package/dist/components/skills/index.js +0 -4
- package/dist/components/skills/index.js.map +0 -1
- package/dist/components/terminals/TerminalPanelToggle.js +0 -13
- package/dist/components/terminals/TerminalPanelToggle.js.map +0 -1
- package/dist/components/terminals/TerminalTabBar.js +0 -22
- package/dist/components/terminals/TerminalTabBar.js.map +0 -1
- package/dist/components/terminals/TerminalViewer.js +0 -371
- package/dist/components/terminals/TerminalViewer.js.map +0 -1
- package/dist/components/terminals/TerminalsPanel.js +0 -134
- package/dist/components/terminals/TerminalsPanel.js.map +0 -1
- package/dist/components/terminals/index.js +0 -11
- package/dist/components/terminals/index.js.map +0 -1
- package/dist/components/tunnel/TunnelSidebar.js +0 -55
- package/dist/components/tunnel/TunnelSidebar.js.map +0 -1
- package/dist/components/tunnel/TunnelSidebarToggle.js +0 -14
- package/dist/components/tunnel/TunnelSidebarToggle.js.map +0 -1
- package/dist/components/tunnel/index.js +0 -3
- package/dist/components/tunnel/index.js.map +0 -1
- package/dist/components/ui/Button.js +0 -19
- package/dist/components/ui/Button.js.map +0 -1
- package/dist/components/ui/Card.js +0 -7
- package/dist/components/ui/Card.js.map +0 -1
- package/dist/components/ui/ConfirmationDialog.js +0 -72
- package/dist/components/ui/ConfirmationDialog.js.map +0 -1
- package/dist/components/ui/Input.js +0 -7
- package/dist/components/ui/Input.js.map +0 -1
- package/dist/components/ui/Modal.js +0 -51
- package/dist/components/ui/Modal.js.map +0 -1
- package/dist/components/ui/ResizeHandle.js +0 -39
- package/dist/components/ui/ResizeHandle.js.map +0 -1
- package/dist/components/ui/Textarea.js +0 -7
- package/dist/components/ui/Textarea.js.map +0 -1
- package/dist/components/ui/Toaster.js +0 -41
- package/dist/components/ui/Toaster.js.map +0 -1
- package/dist/components/ui/ToolApprovalDialog.js +0 -57
- package/dist/components/ui/ToolApprovalDialog.js.map +0 -1
- package/dist/hooks/useAuthStatus.js +0 -287
- package/dist/hooks/useAuthStatus.js.map +0 -1
- package/dist/hooks/useBranch.js +0 -44
- package/dist/hooks/useBranch.js.map +0 -1
- package/dist/hooks/useCommandSuggestions.js +0 -86
- package/dist/hooks/useCommandSuggestions.js.map +0 -1
- package/dist/hooks/useConfig.js +0 -32
- package/dist/hooks/useConfig.js.map +0 -1
- package/dist/hooks/useFileBrowser.js +0 -30
- package/dist/hooks/useFileBrowser.js.map +0 -1
- package/dist/hooks/useFileMention.js +0 -56
- package/dist/hooks/useFileMention.js.map +0 -1
- package/dist/hooks/useFileUpload.js +0 -317
- package/dist/hooks/useFileUpload.js.map +0 -1
- package/dist/hooks/useFiles.js +0 -15
- package/dist/hooks/useFiles.js.map +0 -1
- package/dist/hooks/useGit.js +0 -149
- package/dist/hooks/useGit.js.map +0 -1
- package/dist/hooks/useImageUpload.js +0 -197
- package/dist/hooks/useImageUpload.js.map +0 -1
- package/dist/hooks/useKeyboardShortcuts.js +0 -249
- package/dist/hooks/useKeyboardShortcuts.js.map +0 -1
- package/dist/hooks/useMCP.js +0 -202
- package/dist/hooks/useMCP.js.map +0 -1
- package/dist/hooks/useMessages.js +0 -26
- package/dist/hooks/useMessages.js.map +0 -1
- package/dist/hooks/usePreferences.js +0 -61
- package/dist/hooks/usePreferences.js.map +0 -1
- package/dist/hooks/useProviderUsage.js +0 -44
- package/dist/hooks/useProviderUsage.js.map +0 -1
- package/dist/hooks/useQueueState.js +0 -31
- package/dist/hooks/useQueueState.js.map +0 -1
- package/dist/hooks/useResearch.js +0 -139
- package/dist/hooks/useResearch.js.map +0 -1
- package/dist/hooks/useSessionFiles.js +0 -15
- package/dist/hooks/useSessionFiles.js.map +0 -1
- package/dist/hooks/useSessionStream.js +0 -572
- package/dist/hooks/useSessionStream.js.map +0 -1
- package/dist/hooks/useSessions.js +0 -83
- package/dist/hooks/useSessions.js.map +0 -1
- package/dist/hooks/useSetuBalance.js +0 -71
- package/dist/hooks/useSetuBalance.js.map +0 -1
- package/dist/hooks/useSetuPayments.js +0 -171
- package/dist/hooks/useSetuPayments.js.map +0 -1
- package/dist/hooks/useShareStatus.js +0 -12
- package/dist/hooks/useShareStatus.js.map +0 -1
- package/dist/hooks/useSkills.js +0 -54
- package/dist/hooks/useSkills.js.map +0 -1
- package/dist/hooks/useTerminals.js +0 -67
- package/dist/hooks/useTerminals.js.map +0 -1
- package/dist/hooks/useTheme.js +0 -57
- package/dist/hooks/useTheme.js.map +0 -1
- package/dist/hooks/useToolApprovalShortcuts.js +0 -88
- package/dist/hooks/useToolApprovalShortcuts.js.map +0 -1
- package/dist/hooks/useTopupCallback.js +0 -74
- package/dist/hooks/useTopupCallback.js.map +0 -1
- package/dist/hooks/useTunnel.js +0 -158
- package/dist/hooks/useTunnel.js.map +0 -1
- package/dist/hooks/useVimMode.js +0 -298
- package/dist/hooks/useVimMode.js.map +0 -1
- package/dist/hooks/useWorkingDirectory.js +0 -34
- package/dist/hooks/useWorkingDirectory.js.map +0 -1
- package/dist/lib/api-client/approval.js +0 -25
- package/dist/lib/api-client/approval.js.map +0 -1
- package/dist/lib/api-client/auth.js +0 -139
- package/dist/lib/api-client/auth.js.map +0 -1
- package/dist/lib/api-client/branches.js +0 -45
- package/dist/lib/api-client/branches.js.map +0 -1
- package/dist/lib/api-client/config.js +0 -38
- package/dist/lib/api-client/config.js.map +0 -1
- package/dist/lib/api-client/files.js +0 -35
- package/dist/lib/api-client/files.js.map +0 -1
- package/dist/lib/api-client/git.js +0 -154
- package/dist/lib/api-client/git.js.map +0 -1
- package/dist/lib/api-client/index.js +0 -92
- package/dist/lib/api-client/index.js.map +0 -1
- package/dist/lib/api-client/sessions.js +0 -106
- package/dist/lib/api-client/sessions.js.map +0 -1
- package/dist/lib/api-client/setu.js +0 -144
- package/dist/lib/api-client/setu.js.map +0 -1
- package/dist/lib/api-client/skills.js +0 -31
- package/dist/lib/api-client/skills.js.map +0 -1
- package/dist/lib/api-client/utils.js +0 -65
- package/dist/lib/api-client/utils.js.map +0 -1
- package/dist/lib/commands.js +0 -124
- package/dist/lib/commands.js.map +0 -1
- package/dist/lib/config.js +0 -28
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/nerd-font.js +0 -55
- package/dist/lib/nerd-font.js.map +0 -1
- package/dist/lib/open-url.js +0 -9
- package/dist/lib/open-url.js.map +0 -1
- package/dist/lib/parseResearchContext.js +0 -30
- package/dist/lib/parseResearchContext.js.map +0 -1
- package/dist/lib/sse-client.js +0 -113
- package/dist/lib/sse-client.js.map +0 -1
- package/dist/stores/confirmationStore.js +0 -27
- package/dist/stores/confirmationStore.js.map +0 -1
- package/dist/stores/fileBrowserStore.js +0 -63
- package/dist/stores/fileBrowserStore.js.map +0 -1
- package/dist/stores/filePickerStore.js +0 -8
- package/dist/stores/filePickerStore.js.map +0 -1
- package/dist/stores/focusStore.js +0 -12
- package/dist/stores/focusStore.js.map +0 -1
- package/dist/stores/gitStore.js +0 -63
- package/dist/stores/gitStore.js.map +0 -1
- package/dist/stores/mcpStore.js +0 -63
- package/dist/stores/mcpStore.js.map +0 -1
- package/dist/stores/onboardingStore.js +0 -38
- package/dist/stores/onboardingStore.js.map +0 -1
- package/dist/stores/panelWidthStore.js +0 -12
- package/dist/stores/panelWidthStore.js.map +0 -1
- package/dist/stores/pendingResearchStore.js +0 -38
- package/dist/stores/pendingResearchStore.js.map +0 -1
- package/dist/stores/queueStore.js +0 -11
- package/dist/stores/queueStore.js.map +0 -1
- package/dist/stores/researchStore.js +0 -54
- package/dist/stores/researchStore.js.map +0 -1
- package/dist/stores/sessionFilesStore.js +0 -62
- package/dist/stores/sessionFilesStore.js.map +0 -1
- package/dist/stores/settingsStore.js +0 -29
- package/dist/stores/settingsStore.js.map +0 -1
- package/dist/stores/setuStore.js +0 -29
- package/dist/stores/setuStore.js.map +0 -1
- package/dist/stores/sidebarStore.js +0 -10
- package/dist/stores/sidebarStore.js.map +0 -1
- package/dist/stores/skillsStore.js +0 -46
- package/dist/stores/skillsStore.js.map +0 -1
- package/dist/stores/terminalStore.js +0 -32
- package/dist/stores/terminalStore.js.map +0 -1
- package/dist/stores/toastStore.js +0 -46
- package/dist/stores/toastStore.js.map +0 -1
- package/dist/stores/toolApprovalStore.js +0 -16
- package/dist/stores/toolApprovalStore.js.map +0 -1
- package/dist/stores/topupApprovalStore.js +0 -11
- package/dist/stores/topupApprovalStore.js.map +0 -1
- package/dist/stores/tunnelStore.js +0 -55
- package/dist/stores/tunnelStore.js.map +0 -1
- package/dist/stores/usageStore.js +0 -14
- package/dist/stores/usageStore.js.map +0 -1
package/dist/hooks/index.js
CHANGED
|
@@ -1,29 +1,4812 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
// src/hooks/useConfig.ts
|
|
2
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
|
|
4
|
+
// src/lib/api-client/sessions.ts
|
|
5
|
+
import {
|
|
6
|
+
createSession as apiCreateSession,
|
|
7
|
+
listSessions as apiListSessions,
|
|
8
|
+
listMessages as apiListMessages,
|
|
9
|
+
createMessage as apiCreateMessage,
|
|
10
|
+
abortSession as apiAbortSession,
|
|
11
|
+
deleteSession as apiDeleteSession,
|
|
12
|
+
updateSession as apiUpdateSession,
|
|
13
|
+
getSessionQueue as apiGetSessionQueue,
|
|
14
|
+
removeFromQueue as apiRemoveFromQueue,
|
|
15
|
+
retryMessage as apiRetryMessage
|
|
16
|
+
} from "@ottocode/api";
|
|
17
|
+
|
|
18
|
+
// src/lib/api-client/utils.ts
|
|
19
|
+
import { client } from "@ottocode/api";
|
|
20
|
+
|
|
21
|
+
// src/lib/config.ts
|
|
22
|
+
function computeApiBaseUrl() {
|
|
23
|
+
const envUrl = import.meta.env?.VITE_API_BASE_URL;
|
|
24
|
+
if (envUrl) {
|
|
25
|
+
return envUrl;
|
|
26
|
+
}
|
|
27
|
+
if (typeof window !== "undefined") {
|
|
28
|
+
const win = window;
|
|
29
|
+
if (win.OTTO_SERVER_URL) {
|
|
30
|
+
return win.OTTO_SERVER_URL;
|
|
31
|
+
}
|
|
32
|
+
if (win.__OTTO_API_URL__) {
|
|
33
|
+
return win.__OTTO_API_URL__;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return "http://localhost:9100";
|
|
37
|
+
}
|
|
38
|
+
function getRuntimeApiBaseUrl() {
|
|
39
|
+
return computeApiBaseUrl();
|
|
40
|
+
}
|
|
41
|
+
var API_BASE_URL = computeApiBaseUrl();
|
|
42
|
+
var config = {
|
|
43
|
+
apiBaseUrl: API_BASE_URL
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/lib/api-client/utils.ts
|
|
47
|
+
function extractErrorMessage(error) {
|
|
48
|
+
if (!error)
|
|
49
|
+
return "Unknown error";
|
|
50
|
+
if (typeof error === "string")
|
|
51
|
+
return error;
|
|
52
|
+
if (error && typeof error === "object") {
|
|
53
|
+
const errObj = error;
|
|
54
|
+
if (errObj.error && typeof errObj.error === "object") {
|
|
55
|
+
const innerError = errObj.error;
|
|
56
|
+
if (typeof innerError.message === "string")
|
|
57
|
+
return innerError.message;
|
|
58
|
+
}
|
|
59
|
+
if (typeof errObj.error === "string")
|
|
60
|
+
return errObj.error;
|
|
61
|
+
if (typeof errObj.message === "string")
|
|
62
|
+
return errObj.message;
|
|
63
|
+
try {
|
|
64
|
+
return JSON.stringify(error);
|
|
65
|
+
} catch {
|
|
66
|
+
return "Error occurred (unable to parse)";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return "Unknown error";
|
|
70
|
+
}
|
|
71
|
+
function configureApiClient() {
|
|
72
|
+
const win = window;
|
|
73
|
+
const baseURL = win.OTTO_SERVER_URL || API_BASE_URL;
|
|
74
|
+
client.setConfig({ baseURL });
|
|
75
|
+
}
|
|
76
|
+
configureApiClient();
|
|
77
|
+
function getBaseUrl() {
|
|
78
|
+
const win = window;
|
|
79
|
+
if (win.OTTO_SERVER_URL)
|
|
80
|
+
return win.OTTO_SERVER_URL;
|
|
81
|
+
return API_BASE_URL;
|
|
82
|
+
}
|
|
83
|
+
function convertSession(apiSession) {
|
|
84
|
+
return {
|
|
85
|
+
...apiSession,
|
|
86
|
+
title: apiSession.title ?? null,
|
|
87
|
+
createdAt: typeof apiSession.createdAt === "string" ? new Date(apiSession.createdAt).getTime() : apiSession.createdAt,
|
|
88
|
+
lastActiveAt: typeof apiSession.lastActiveAt === "string" ? new Date(apiSession.lastActiveAt).getTime() : apiSession.lastActiveAt
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function convertMessage(apiMessage) {
|
|
92
|
+
return {
|
|
93
|
+
...apiMessage,
|
|
94
|
+
createdAt: typeof apiMessage.createdAt === "string" ? new Date(apiMessage.createdAt).getTime() : apiMessage.createdAt,
|
|
95
|
+
completedAt: apiMessage.completedAt ? typeof apiMessage.completedAt === "string" ? new Date(apiMessage.completedAt).getTime() : apiMessage.completedAt : null
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/lib/api-client/sessions.ts
|
|
100
|
+
var sessionsMixin = {
|
|
101
|
+
async getSessions() {
|
|
102
|
+
const page = await this.getSessionsPage({ limit: 200 });
|
|
103
|
+
return page.items;
|
|
104
|
+
},
|
|
105
|
+
async getSessionsPage(params = {}) {
|
|
106
|
+
const { limit = 50, offset = 0 } = params;
|
|
107
|
+
const response = await apiListSessions({
|
|
108
|
+
query: { limit, offset }
|
|
109
|
+
});
|
|
110
|
+
if (response.error)
|
|
111
|
+
throw new Error(extractErrorMessage(response.error));
|
|
112
|
+
const data = response.data;
|
|
113
|
+
return {
|
|
114
|
+
items: (data?.items ?? []).map((s) => convertSession(s)),
|
|
115
|
+
hasMore: data?.hasMore ?? false,
|
|
116
|
+
nextOffset: data?.nextOffset ?? null
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
async createSession(data) {
|
|
120
|
+
const response = await apiCreateSession({
|
|
121
|
+
body: data
|
|
122
|
+
});
|
|
123
|
+
if (response.error)
|
|
124
|
+
throw new Error(extractErrorMessage(response.error));
|
|
125
|
+
if (!response.data)
|
|
126
|
+
throw new Error("No data returned from create session");
|
|
127
|
+
return convertSession(response.data);
|
|
128
|
+
},
|
|
129
|
+
async updateSession(sessionId, data) {
|
|
130
|
+
const response = await apiUpdateSession({
|
|
131
|
+
path: { sessionId },
|
|
132
|
+
body: data
|
|
133
|
+
});
|
|
134
|
+
if (response.error)
|
|
135
|
+
throw new Error(extractErrorMessage(response.error));
|
|
136
|
+
return convertSession(response.data);
|
|
137
|
+
},
|
|
138
|
+
async deleteSession(sessionId) {
|
|
139
|
+
const response = await apiDeleteSession({ path: { sessionId } });
|
|
140
|
+
if (response.error)
|
|
141
|
+
throw new Error(extractErrorMessage(response.error));
|
|
142
|
+
return response.data;
|
|
143
|
+
},
|
|
144
|
+
async abortSession(sessionId) {
|
|
145
|
+
const response = await apiAbortSession({ path: { sessionId } });
|
|
146
|
+
if (response.error)
|
|
147
|
+
throw new Error(extractErrorMessage(response.error));
|
|
148
|
+
return response.data;
|
|
149
|
+
},
|
|
150
|
+
async abortMessage(sessionId, _messageId) {
|
|
151
|
+
const response = await apiAbortSession({
|
|
152
|
+
path: { sessionId }
|
|
153
|
+
});
|
|
154
|
+
if (response.error)
|
|
155
|
+
throw new Error("Failed to abort message");
|
|
156
|
+
return response.data;
|
|
157
|
+
},
|
|
158
|
+
async getQueueState(sessionId) {
|
|
159
|
+
const response = await apiGetSessionQueue({ path: { sessionId } });
|
|
160
|
+
if (response.error)
|
|
161
|
+
throw new Error("Failed to get queue state");
|
|
162
|
+
return response.data;
|
|
163
|
+
},
|
|
164
|
+
async removeFromQueue(sessionId, messageId) {
|
|
165
|
+
const response = await apiRemoveFromQueue({
|
|
166
|
+
path: { sessionId, messageId }
|
|
167
|
+
});
|
|
168
|
+
if (response.error)
|
|
169
|
+
throw new Error("Failed to remove from queue");
|
|
170
|
+
return response.data;
|
|
171
|
+
},
|
|
172
|
+
async getMessages(sessionId) {
|
|
173
|
+
const response = await apiListMessages({ path: { id: sessionId } });
|
|
174
|
+
if (response.error)
|
|
175
|
+
throw new Error(extractErrorMessage(response.error));
|
|
176
|
+
return (response.data || []).map(convertMessage);
|
|
177
|
+
},
|
|
178
|
+
async sendMessage(sessionId, data) {
|
|
179
|
+
const response = await apiCreateMessage({
|
|
180
|
+
path: { id: sessionId },
|
|
181
|
+
body: data
|
|
182
|
+
});
|
|
183
|
+
if (response.error)
|
|
184
|
+
throw new Error(extractErrorMessage(response.error));
|
|
185
|
+
return response.data;
|
|
186
|
+
},
|
|
187
|
+
getStreamUrl(sessionId) {
|
|
188
|
+
return `${getBaseUrl()}/v1/sessions/${sessionId}/stream`;
|
|
189
|
+
},
|
|
190
|
+
async retryMessage(sessionId, messageId) {
|
|
191
|
+
const response = await apiRetryMessage({
|
|
192
|
+
path: { sessionId, messageId }
|
|
193
|
+
});
|
|
194
|
+
if (response.error)
|
|
195
|
+
throw new Error(extractErrorMessage(response.error));
|
|
196
|
+
return response.data;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/lib/api-client/git.ts
|
|
201
|
+
import {
|
|
202
|
+
getGitStatus as apiGetGitStatus,
|
|
203
|
+
getGitDiff as apiGetGitDiff,
|
|
204
|
+
getGitBranch as apiGetGitBranch,
|
|
205
|
+
stageFiles as apiStageFiles,
|
|
206
|
+
unstageFiles as apiUnstageFiles,
|
|
207
|
+
restoreFiles as apiRestoreFiles,
|
|
208
|
+
deleteFiles as apiDeleteFiles,
|
|
209
|
+
commitChanges as apiCommitChanges,
|
|
210
|
+
generateCommitMessage as apiGenerateCommitMessage,
|
|
211
|
+
pushCommits as apiPushCommits,
|
|
212
|
+
pullChanges as apiPullChanges,
|
|
213
|
+
initGitRepo as apiInitGitRepo,
|
|
214
|
+
getGitRemotes as apiGetGitRemotes,
|
|
215
|
+
addGitRemote as apiAddGitRemote,
|
|
216
|
+
removeGitRemote as apiRemoveGitRemote
|
|
217
|
+
} from "@ottocode/api";
|
|
218
|
+
var gitMixin = {
|
|
219
|
+
async initGitRepo() {
|
|
220
|
+
const response = await apiInitGitRepo();
|
|
221
|
+
if (response.error)
|
|
222
|
+
throw new Error(extractErrorMessage(response.error));
|
|
223
|
+
return response.data?.data;
|
|
224
|
+
},
|
|
225
|
+
async getGitStatus() {
|
|
226
|
+
const response = await apiGetGitStatus();
|
|
227
|
+
if (response.error)
|
|
228
|
+
throw new Error(extractErrorMessage(response.error));
|
|
229
|
+
return response.data?.data;
|
|
230
|
+
},
|
|
231
|
+
async getGitDiff(file, staged = false) {
|
|
232
|
+
const response = await apiGetGitDiff({
|
|
233
|
+
query: { file, staged: staged ? "true" : "false" }
|
|
234
|
+
});
|
|
235
|
+
if (response.error)
|
|
236
|
+
throw new Error(extractErrorMessage(response.error));
|
|
237
|
+
return response.data?.data;
|
|
238
|
+
},
|
|
239
|
+
async getGitDiffFullFile(file, staged = false) {
|
|
240
|
+
const response = await apiGetGitDiff({
|
|
241
|
+
query: {
|
|
242
|
+
file,
|
|
243
|
+
staged: staged ? "true" : "false",
|
|
244
|
+
fullFile: "true"
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
if (response.error)
|
|
248
|
+
throw new Error(extractErrorMessage(response.error));
|
|
249
|
+
return response.data?.data;
|
|
250
|
+
},
|
|
251
|
+
async generateCommitMessage(sessionId) {
|
|
252
|
+
const response = await apiGenerateCommitMessage({
|
|
253
|
+
body: sessionId ? { sessionId } : {}
|
|
254
|
+
});
|
|
255
|
+
if (response.error)
|
|
256
|
+
throw new Error(extractErrorMessage(response.error));
|
|
257
|
+
return response.data?.data;
|
|
258
|
+
},
|
|
259
|
+
async stageFiles(files) {
|
|
260
|
+
const response = await apiStageFiles({
|
|
261
|
+
body: { files }
|
|
262
|
+
});
|
|
263
|
+
if (response.error)
|
|
264
|
+
throw new Error(extractErrorMessage(response.error));
|
|
265
|
+
return response.data?.data;
|
|
266
|
+
},
|
|
267
|
+
async unstageFiles(files) {
|
|
268
|
+
const response = await apiUnstageFiles({
|
|
269
|
+
body: { files }
|
|
270
|
+
});
|
|
271
|
+
if (response.error)
|
|
272
|
+
throw new Error(extractErrorMessage(response.error));
|
|
273
|
+
return response.data?.data;
|
|
274
|
+
},
|
|
275
|
+
async restoreFiles(files) {
|
|
276
|
+
const response = await apiRestoreFiles({
|
|
277
|
+
body: { files }
|
|
278
|
+
});
|
|
279
|
+
if (response.error)
|
|
280
|
+
throw new Error(extractErrorMessage(response.error));
|
|
281
|
+
return response.data?.data;
|
|
282
|
+
},
|
|
283
|
+
async deleteFiles(files) {
|
|
284
|
+
const response = await apiDeleteFiles({
|
|
285
|
+
body: { files }
|
|
286
|
+
});
|
|
287
|
+
if (response.error)
|
|
288
|
+
throw new Error(extractErrorMessage(response.error));
|
|
289
|
+
return response.data?.data;
|
|
290
|
+
},
|
|
291
|
+
async commitChanges(message) {
|
|
292
|
+
const response = await apiCommitChanges({
|
|
293
|
+
body: { message }
|
|
294
|
+
});
|
|
295
|
+
if (response.error)
|
|
296
|
+
throw new Error(extractErrorMessage(response.error));
|
|
297
|
+
return response.data?.data;
|
|
298
|
+
},
|
|
299
|
+
async getGitBranch() {
|
|
300
|
+
const response = await apiGetGitBranch();
|
|
301
|
+
if (response.error)
|
|
302
|
+
throw new Error(extractErrorMessage(response.error));
|
|
303
|
+
return response.data?.data;
|
|
304
|
+
},
|
|
305
|
+
async pushCommits() {
|
|
306
|
+
const response = await apiPushCommits({
|
|
307
|
+
body: {}
|
|
308
|
+
});
|
|
309
|
+
if (response.error)
|
|
310
|
+
throw new Error(extractErrorMessage(response.error));
|
|
311
|
+
return response.data?.data;
|
|
312
|
+
},
|
|
313
|
+
async pullChanges() {
|
|
314
|
+
const response = await apiPullChanges({
|
|
315
|
+
body: {}
|
|
316
|
+
});
|
|
317
|
+
if (response.error)
|
|
318
|
+
throw new Error(extractErrorMessage(response.error));
|
|
319
|
+
return response.data?.data;
|
|
320
|
+
},
|
|
321
|
+
async getRemotes() {
|
|
322
|
+
const response = await apiGetGitRemotes();
|
|
323
|
+
if (response.error)
|
|
324
|
+
throw new Error(extractErrorMessage(response.error));
|
|
325
|
+
return response.data?.data?.remotes;
|
|
326
|
+
},
|
|
327
|
+
async addRemote(name, url) {
|
|
328
|
+
const response = await apiAddGitRemote({
|
|
329
|
+
body: { name, url }
|
|
330
|
+
});
|
|
331
|
+
if (response.error)
|
|
332
|
+
throw new Error(extractErrorMessage(response.error));
|
|
333
|
+
return response.data?.data;
|
|
334
|
+
},
|
|
335
|
+
async removeRemote(name) {
|
|
336
|
+
const response = await apiRemoveGitRemote({
|
|
337
|
+
body: { name }
|
|
338
|
+
});
|
|
339
|
+
if (response.error)
|
|
340
|
+
throw new Error(extractErrorMessage(response.error));
|
|
341
|
+
return response.data?.data;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
// src/lib/api-client/config.ts
|
|
346
|
+
import {
|
|
347
|
+
getConfig as apiGetConfig,
|
|
348
|
+
getProviderModels as apiGetProviderModels,
|
|
349
|
+
getAllModels as apiGetAllModels,
|
|
350
|
+
updateDefaults as apiUpdateDefaults
|
|
351
|
+
} from "@ottocode/api";
|
|
352
|
+
var configMixin = {
|
|
353
|
+
async getConfig() {
|
|
354
|
+
const response = await apiGetConfig();
|
|
355
|
+
if (response.error)
|
|
356
|
+
throw new Error(extractErrorMessage(response.error));
|
|
357
|
+
return response.data;
|
|
358
|
+
},
|
|
359
|
+
async getModels(providerId) {
|
|
360
|
+
const response = await apiGetProviderModels({
|
|
361
|
+
path: { provider: providerId }
|
|
362
|
+
});
|
|
363
|
+
if (response.error)
|
|
364
|
+
throw new Error(extractErrorMessage(response.error));
|
|
365
|
+
return response.data;
|
|
366
|
+
},
|
|
367
|
+
async getAllModels() {
|
|
368
|
+
const response = await apiGetAllModels();
|
|
369
|
+
if (response.error)
|
|
370
|
+
throw new Error(extractErrorMessage(response.error));
|
|
371
|
+
return response.data;
|
|
372
|
+
},
|
|
373
|
+
async updateDefaults(data) {
|
|
374
|
+
const response = await apiUpdateDefaults({
|
|
375
|
+
body: data
|
|
376
|
+
});
|
|
377
|
+
if (response.error)
|
|
378
|
+
throw new Error(extractErrorMessage(response.error));
|
|
379
|
+
return response.data;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// src/lib/api-client/files.ts
|
|
384
|
+
import {
|
|
385
|
+
listFiles as apiListFiles,
|
|
386
|
+
getFileTree as apiGetFileTree,
|
|
387
|
+
readFile as apiReadFile,
|
|
388
|
+
getSessionFiles as apiGetSessionFiles
|
|
389
|
+
} from "@ottocode/api";
|
|
390
|
+
var filesMixin = {
|
|
391
|
+
async listFiles() {
|
|
392
|
+
const response = await apiListFiles();
|
|
393
|
+
if (response.error)
|
|
394
|
+
throw new Error(extractErrorMessage(response.error));
|
|
395
|
+
return response.data;
|
|
396
|
+
},
|
|
397
|
+
async getFileTree(dirPath = ".") {
|
|
398
|
+
const response = await apiGetFileTree({
|
|
399
|
+
query: { path: dirPath }
|
|
400
|
+
});
|
|
401
|
+
if (response.error)
|
|
402
|
+
throw new Error(extractErrorMessage(response.error));
|
|
403
|
+
return response.data;
|
|
404
|
+
},
|
|
405
|
+
async readFileContent(filePath) {
|
|
406
|
+
const response = await apiReadFile({
|
|
407
|
+
query: { path: filePath }
|
|
408
|
+
});
|
|
409
|
+
if (response.error)
|
|
410
|
+
throw new Error(extractErrorMessage(response.error));
|
|
411
|
+
return response.data;
|
|
412
|
+
},
|
|
413
|
+
async getSessionFiles(sessionId) {
|
|
414
|
+
const response = await apiGetSessionFiles({ path: { sessionId } });
|
|
415
|
+
if (response.error)
|
|
416
|
+
throw new Error(extractErrorMessage(response.error));
|
|
417
|
+
return response.data;
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/lib/api-client/branches.ts
|
|
422
|
+
import {
|
|
423
|
+
createBranch as apiCreateBranch,
|
|
424
|
+
listBranches as apiListBranches,
|
|
425
|
+
getParentSession as apiGetParentSession,
|
|
426
|
+
getShareStatus as apiGetShareStatus,
|
|
427
|
+
shareSession as apiShareSession,
|
|
428
|
+
syncShare as apiSyncShare
|
|
429
|
+
} from "@ottocode/api";
|
|
430
|
+
var branchesMixin = {
|
|
431
|
+
async createBranch(sessionId, data) {
|
|
432
|
+
const response = await apiCreateBranch({
|
|
433
|
+
path: { sessionId },
|
|
434
|
+
body: data
|
|
435
|
+
});
|
|
436
|
+
if (response.error)
|
|
437
|
+
throw new Error(extractErrorMessage(response.error));
|
|
438
|
+
return response.data;
|
|
439
|
+
},
|
|
440
|
+
async listBranches(sessionId) {
|
|
441
|
+
const response = await apiListBranches({ path: { sessionId } });
|
|
442
|
+
if (response.error)
|
|
443
|
+
throw new Error(extractErrorMessage(response.error));
|
|
444
|
+
return response.data;
|
|
445
|
+
},
|
|
446
|
+
async getParentSession(sessionId) {
|
|
447
|
+
const response = await apiGetParentSession({ path: { sessionId } });
|
|
448
|
+
if (response.error)
|
|
449
|
+
throw new Error(extractErrorMessage(response.error));
|
|
450
|
+
return response.data;
|
|
451
|
+
},
|
|
452
|
+
async getShareStatus(sessionId) {
|
|
453
|
+
const response = await apiGetShareStatus({ path: { sessionId } });
|
|
454
|
+
if (response.error)
|
|
455
|
+
return { shared: false };
|
|
456
|
+
return response.data;
|
|
457
|
+
},
|
|
458
|
+
async shareSession(sessionId) {
|
|
459
|
+
const response = await apiShareSession({ path: { sessionId } });
|
|
460
|
+
if (response.error)
|
|
461
|
+
throw new Error(extractErrorMessage(response.error));
|
|
462
|
+
return response.data;
|
|
463
|
+
},
|
|
464
|
+
async syncSession(sessionId) {
|
|
465
|
+
const response = await apiSyncShare({ path: { sessionId } });
|
|
466
|
+
if (response.error)
|
|
467
|
+
throw new Error(extractErrorMessage(response.error));
|
|
468
|
+
return response.data;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
// src/lib/api-client/approval.ts
|
|
473
|
+
import {
|
|
474
|
+
resolveApproval as apiResolveApproval,
|
|
475
|
+
getPendingApprovals as apiGetPendingApprovals
|
|
476
|
+
} from "@ottocode/api";
|
|
477
|
+
var approvalMixin = {
|
|
478
|
+
async approveToolCall(sessionId, callId, approved) {
|
|
479
|
+
const response = await apiResolveApproval({
|
|
480
|
+
path: { id: sessionId },
|
|
481
|
+
body: { callId, approved, sessionId }
|
|
482
|
+
});
|
|
483
|
+
if (response.error)
|
|
484
|
+
throw new Error("Failed to send tool approval");
|
|
485
|
+
return response.data;
|
|
486
|
+
},
|
|
487
|
+
async getPendingApprovals(sessionId) {
|
|
488
|
+
const response = await apiGetPendingApprovals({
|
|
489
|
+
path: { id: sessionId }
|
|
490
|
+
});
|
|
491
|
+
if (response.error)
|
|
492
|
+
return { ok: false, pending: [] };
|
|
493
|
+
return response.data;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/lib/api-client/setu.ts
|
|
498
|
+
import {
|
|
499
|
+
getSetuBalance as apiGetSetuBalance,
|
|
500
|
+
getSetuWallet as apiGetSetuWallet,
|
|
501
|
+
getSetuUsdcBalance as apiGetSetuUsdcBalance,
|
|
502
|
+
createPolarCheckout as apiCreatePolarCheckout,
|
|
503
|
+
getPolarTopupEstimate as apiGetPolarTopupEstimate,
|
|
504
|
+
getPolarTopupStatus as apiGetPolarTopupStatus,
|
|
505
|
+
getRazorpayTopupEstimate as apiGetRazorpayTopupEstimate,
|
|
506
|
+
createRazorpayOrder as apiCreateRazorpayOrder,
|
|
507
|
+
verifyRazorpayPayment as apiVerifyRazorpayPayment,
|
|
508
|
+
selectTopupMethod as apiSelectTopupMethod,
|
|
509
|
+
cancelTopup as apiCancelTopup,
|
|
510
|
+
getPendingTopup as apiGetPendingTopup
|
|
511
|
+
} from "@ottocode/api";
|
|
512
|
+
var setuMixin = {
|
|
513
|
+
async getSetuBalance() {
|
|
514
|
+
try {
|
|
515
|
+
const response = await apiGetSetuBalance();
|
|
516
|
+
if (response.error)
|
|
517
|
+
return null;
|
|
518
|
+
return response.data;
|
|
519
|
+
} catch {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
async getSetuWallet() {
|
|
524
|
+
try {
|
|
525
|
+
const response = await apiGetSetuWallet();
|
|
526
|
+
if (response.error)
|
|
527
|
+
return { configured: false };
|
|
528
|
+
return response.data;
|
|
529
|
+
} catch {
|
|
530
|
+
return { configured: false };
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
async getSetuUsdcBalance(network = "mainnet") {
|
|
534
|
+
try {
|
|
535
|
+
const response = await apiGetSetuUsdcBalance({
|
|
536
|
+
query: { network }
|
|
537
|
+
});
|
|
538
|
+
if (response.error)
|
|
539
|
+
return null;
|
|
540
|
+
return response.data;
|
|
541
|
+
} catch {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
async getPolarTopupEstimate(amount) {
|
|
546
|
+
try {
|
|
547
|
+
const response = await apiGetPolarTopupEstimate({
|
|
548
|
+
query: { amount }
|
|
549
|
+
});
|
|
550
|
+
if (response.error)
|
|
551
|
+
return null;
|
|
552
|
+
return response.data;
|
|
553
|
+
} catch {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
async createPolarCheckout(amount, successUrl) {
|
|
558
|
+
const response = await apiCreatePolarCheckout({
|
|
559
|
+
body: { amount, successUrl }
|
|
560
|
+
});
|
|
561
|
+
if (response.error)
|
|
562
|
+
throw new Error(extractErrorMessage(response.error));
|
|
563
|
+
return response.data;
|
|
564
|
+
},
|
|
565
|
+
async selectTopupMethod(sessionId, method) {
|
|
566
|
+
const response = await apiSelectTopupMethod({
|
|
567
|
+
body: { sessionId, method }
|
|
568
|
+
});
|
|
569
|
+
if (response.error)
|
|
570
|
+
throw new Error(extractErrorMessage(response.error));
|
|
571
|
+
return response.data;
|
|
572
|
+
},
|
|
573
|
+
async cancelTopup(sessionId, reason) {
|
|
574
|
+
const response = await apiCancelTopup({
|
|
575
|
+
body: { sessionId, reason }
|
|
576
|
+
});
|
|
577
|
+
if (response.error)
|
|
578
|
+
throw new Error(extractErrorMessage(response.error));
|
|
579
|
+
return response.data;
|
|
580
|
+
},
|
|
581
|
+
async getPendingTopup(sessionId) {
|
|
582
|
+
try {
|
|
583
|
+
const response = await apiGetPendingTopup({
|
|
584
|
+
query: { sessionId }
|
|
585
|
+
});
|
|
586
|
+
if (response.error)
|
|
587
|
+
return { hasPending: false };
|
|
588
|
+
return response.data;
|
|
589
|
+
} catch {
|
|
590
|
+
return { hasPending: false };
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
async getPolarTopupStatus(checkoutId) {
|
|
594
|
+
try {
|
|
595
|
+
const response = await apiGetPolarTopupStatus({
|
|
596
|
+
query: { checkoutId }
|
|
597
|
+
});
|
|
598
|
+
if (response.error)
|
|
599
|
+
return null;
|
|
600
|
+
return response.data;
|
|
601
|
+
} catch {
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
async getRazorpayTopupEstimate(amount) {
|
|
606
|
+
try {
|
|
607
|
+
const response = await apiGetRazorpayTopupEstimate({
|
|
608
|
+
query: { amount }
|
|
609
|
+
});
|
|
610
|
+
if (response.error)
|
|
611
|
+
return null;
|
|
612
|
+
return response.data;
|
|
613
|
+
} catch {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
async createRazorpayOrder(amount) {
|
|
618
|
+
const response = await apiCreateRazorpayOrder({
|
|
619
|
+
body: { amount }
|
|
620
|
+
});
|
|
621
|
+
if (response.error)
|
|
622
|
+
throw new Error(extractErrorMessage(response.error));
|
|
623
|
+
return response.data;
|
|
624
|
+
},
|
|
625
|
+
async verifyRazorpayPayment(params) {
|
|
626
|
+
const response = await apiVerifyRazorpayPayment({
|
|
627
|
+
body: params
|
|
628
|
+
});
|
|
629
|
+
if (response.error)
|
|
630
|
+
throw new Error(extractErrorMessage(response.error));
|
|
631
|
+
return response.data;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/lib/api-client/auth.ts
|
|
636
|
+
import {
|
|
637
|
+
getAuthStatus as apiGetAuthStatus,
|
|
638
|
+
setupSetuWallet as apiSetupSetuWallet,
|
|
639
|
+
importSetuWallet as apiImportSetuWallet,
|
|
640
|
+
exportSetuWallet as apiExportSetuWallet,
|
|
641
|
+
addProviderApiKey as apiAddProviderApiKey,
|
|
642
|
+
removeProvider as apiRemoveProvider,
|
|
643
|
+
completeOnboarding as apiCompleteOnboarding,
|
|
644
|
+
getOAuthUrl as apiGetOAuthUrl,
|
|
645
|
+
exchangeOAuthCode as apiExchangeOAuthCode,
|
|
646
|
+
startCopilotDeviceFlow as apiStartCopilotDeviceFlow,
|
|
647
|
+
pollCopilotDeviceFlow as apiPollCopilotDeviceFlow,
|
|
648
|
+
getCopilotAuthMethods as apiGetCopilotAuthMethods,
|
|
649
|
+
saveCopilotToken as apiSaveCopilotToken,
|
|
650
|
+
importCopilotTokenFromGh as apiImportCopilotTokenFromGh,
|
|
651
|
+
getCopilotDiagnostics as apiGetCopilotDiagnostics,
|
|
652
|
+
getProviderUsage as apiGetProviderUsage
|
|
653
|
+
} from "@ottocode/api";
|
|
654
|
+
var authMixin = {
|
|
655
|
+
async getAuthStatus() {
|
|
656
|
+
const response = await apiGetAuthStatus();
|
|
657
|
+
if (response.error)
|
|
658
|
+
throw new Error(extractErrorMessage(response.error));
|
|
659
|
+
return response.data;
|
|
660
|
+
},
|
|
661
|
+
async setupSetuWallet() {
|
|
662
|
+
const response = await apiSetupSetuWallet();
|
|
663
|
+
if (response.error)
|
|
664
|
+
throw new Error(extractErrorMessage(response.error));
|
|
665
|
+
return response.data;
|
|
666
|
+
},
|
|
667
|
+
async importSetuWallet(privateKey) {
|
|
668
|
+
const response = await apiImportSetuWallet({
|
|
669
|
+
body: { privateKey }
|
|
670
|
+
});
|
|
671
|
+
if (response.error)
|
|
672
|
+
throw new Error(extractErrorMessage(response.error));
|
|
673
|
+
return response.data;
|
|
674
|
+
},
|
|
675
|
+
async exportSetuWallet() {
|
|
676
|
+
const response = await apiExportSetuWallet();
|
|
677
|
+
if (response.error)
|
|
678
|
+
throw new Error(extractErrorMessage(response.error));
|
|
679
|
+
return response.data;
|
|
680
|
+
},
|
|
681
|
+
async addProvider(provider, apiKey) {
|
|
682
|
+
const response = await apiAddProviderApiKey({
|
|
683
|
+
path: { provider },
|
|
684
|
+
body: { apiKey }
|
|
685
|
+
});
|
|
686
|
+
if (response.error)
|
|
687
|
+
throw new Error(extractErrorMessage(response.error));
|
|
688
|
+
return response.data;
|
|
689
|
+
},
|
|
690
|
+
async removeProvider(provider) {
|
|
691
|
+
const response = await apiRemoveProvider({ path: { provider } });
|
|
692
|
+
if (response.error)
|
|
693
|
+
throw new Error(extractErrorMessage(response.error));
|
|
694
|
+
return response.data;
|
|
695
|
+
},
|
|
696
|
+
async completeOnboarding() {
|
|
697
|
+
const response = await apiCompleteOnboarding();
|
|
698
|
+
if (response.error)
|
|
699
|
+
throw new Error(extractErrorMessage(response.error));
|
|
700
|
+
return response.data;
|
|
701
|
+
},
|
|
702
|
+
getOAuthStartUrl(provider, mode) {
|
|
703
|
+
const baseUrl = `${getBaseUrl()}/v1/auth/${provider}/oauth/start`;
|
|
704
|
+
if (mode)
|
|
705
|
+
return `${baseUrl}?mode=${mode}`;
|
|
706
|
+
return baseUrl;
|
|
707
|
+
},
|
|
708
|
+
async getOAuthUrl(provider, mode) {
|
|
709
|
+
const response = await apiGetOAuthUrl({
|
|
710
|
+
path: { provider },
|
|
711
|
+
body: { mode }
|
|
712
|
+
});
|
|
713
|
+
if (response.error)
|
|
714
|
+
throw new Error(extractErrorMessage(response.error));
|
|
715
|
+
return response.data;
|
|
716
|
+
},
|
|
717
|
+
async exchangeOAuthCode(provider, code, sessionId) {
|
|
718
|
+
const response = await apiExchangeOAuthCode({
|
|
719
|
+
path: { provider },
|
|
720
|
+
body: { code, sessionId }
|
|
721
|
+
});
|
|
722
|
+
if (response.error)
|
|
723
|
+
throw new Error(extractErrorMessage(response.error));
|
|
724
|
+
return response.data;
|
|
725
|
+
},
|
|
726
|
+
async startCopilotDeviceFlow() {
|
|
727
|
+
const response = await apiStartCopilotDeviceFlow();
|
|
728
|
+
if (response.error)
|
|
729
|
+
throw new Error(extractErrorMessage(response.error));
|
|
730
|
+
return response.data;
|
|
731
|
+
},
|
|
732
|
+
async pollCopilotDeviceFlow(sessionId) {
|
|
733
|
+
const response = await apiPollCopilotDeviceFlow({
|
|
734
|
+
body: { sessionId }
|
|
735
|
+
});
|
|
736
|
+
if (response.error)
|
|
737
|
+
throw new Error(extractErrorMessage(response.error));
|
|
738
|
+
return response.data;
|
|
739
|
+
},
|
|
740
|
+
async getCopilotAuthMethods() {
|
|
741
|
+
const response = await apiGetCopilotAuthMethods();
|
|
742
|
+
if (response.error)
|
|
743
|
+
throw new Error(extractErrorMessage(response.error));
|
|
744
|
+
return response.data;
|
|
745
|
+
},
|
|
746
|
+
async saveCopilotToken(token) {
|
|
747
|
+
const response = await apiSaveCopilotToken({ body: { token } });
|
|
748
|
+
if (response.error)
|
|
749
|
+
throw new Error(extractErrorMessage(response.error));
|
|
750
|
+
return response.data;
|
|
751
|
+
},
|
|
752
|
+
async importCopilotTokenFromGh() {
|
|
753
|
+
const response = await apiImportCopilotTokenFromGh();
|
|
754
|
+
if (response.error)
|
|
755
|
+
throw new Error(extractErrorMessage(response.error));
|
|
756
|
+
return response.data;
|
|
757
|
+
},
|
|
758
|
+
async getCopilotDiagnostics() {
|
|
759
|
+
const response = await apiGetCopilotDiagnostics();
|
|
760
|
+
if (response.error)
|
|
761
|
+
throw new Error(extractErrorMessage(response.error));
|
|
762
|
+
return response.data;
|
|
763
|
+
},
|
|
764
|
+
async getProviderUsage(provider) {
|
|
765
|
+
const response = await apiGetProviderUsage({
|
|
766
|
+
path: { provider }
|
|
767
|
+
});
|
|
768
|
+
if (response.error)
|
|
769
|
+
throw new Error(extractErrorMessage(response.error));
|
|
770
|
+
return response.data;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
// src/lib/api-client/skills.ts
|
|
775
|
+
import {
|
|
776
|
+
listSkills as apiListSkills,
|
|
777
|
+
getSkill as apiGetSkill,
|
|
778
|
+
listSkillFiles as apiListSkillFiles,
|
|
779
|
+
getSkillFile as apiGetSkillFile
|
|
780
|
+
} from "@ottocode/api";
|
|
781
|
+
var skillsMixin = {
|
|
782
|
+
async listSkills() {
|
|
783
|
+
const response = await apiListSkills();
|
|
784
|
+
if (response.error)
|
|
785
|
+
throw new Error(extractErrorMessage(response.error));
|
|
786
|
+
return response.data;
|
|
787
|
+
},
|
|
788
|
+
async getSkill(name) {
|
|
789
|
+
const response = await apiGetSkill({ path: { name } });
|
|
790
|
+
if (response.error)
|
|
791
|
+
throw new Error(extractErrorMessage(response.error));
|
|
792
|
+
return response.data;
|
|
793
|
+
},
|
|
794
|
+
async getSkillFiles(name) {
|
|
795
|
+
const response = await apiListSkillFiles({ path: { name } });
|
|
796
|
+
if (response.error)
|
|
797
|
+
throw new Error(extractErrorMessage(response.error));
|
|
798
|
+
return response.data;
|
|
799
|
+
},
|
|
800
|
+
async getSkillFileContent(name, filePath) {
|
|
801
|
+
const response = await apiGetSkillFile({
|
|
802
|
+
path: { name, filePath }
|
|
803
|
+
});
|
|
804
|
+
if (response.error)
|
|
805
|
+
throw new Error(extractErrorMessage(response.error));
|
|
806
|
+
return response.data;
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
// src/lib/api-client/index.ts
|
|
811
|
+
class ApiClient {
|
|
812
|
+
getSessions = sessionsMixin.getSessions;
|
|
813
|
+
getSessionsPage = sessionsMixin.getSessionsPage;
|
|
814
|
+
createSession = sessionsMixin.createSession;
|
|
815
|
+
updateSession = sessionsMixin.updateSession;
|
|
816
|
+
deleteSession = sessionsMixin.deleteSession;
|
|
817
|
+
abortSession = sessionsMixin.abortSession;
|
|
818
|
+
abortMessage = sessionsMixin.abortMessage;
|
|
819
|
+
getQueueState = sessionsMixin.getQueueState;
|
|
820
|
+
removeFromQueue = sessionsMixin.removeFromQueue;
|
|
821
|
+
getMessages = sessionsMixin.getMessages;
|
|
822
|
+
sendMessage = sessionsMixin.sendMessage;
|
|
823
|
+
getStreamUrl = sessionsMixin.getStreamUrl;
|
|
824
|
+
retryMessage = sessionsMixin.retryMessage;
|
|
825
|
+
initGitRepo = gitMixin.initGitRepo;
|
|
826
|
+
getGitStatus = gitMixin.getGitStatus;
|
|
827
|
+
getGitDiff = gitMixin.getGitDiff;
|
|
828
|
+
getGitDiffFullFile = gitMixin.getGitDiffFullFile;
|
|
829
|
+
generateCommitMessage = gitMixin.generateCommitMessage;
|
|
830
|
+
stageFiles = gitMixin.stageFiles;
|
|
831
|
+
unstageFiles = gitMixin.unstageFiles;
|
|
832
|
+
restoreFiles = gitMixin.restoreFiles;
|
|
833
|
+
deleteFiles = gitMixin.deleteFiles;
|
|
834
|
+
commitChanges = gitMixin.commitChanges;
|
|
835
|
+
getGitBranch = gitMixin.getGitBranch;
|
|
836
|
+
pushCommits = gitMixin.pushCommits;
|
|
837
|
+
pullChanges = gitMixin.pullChanges;
|
|
838
|
+
getRemotes = gitMixin.getRemotes;
|
|
839
|
+
addRemote = gitMixin.addRemote;
|
|
840
|
+
removeRemote = gitMixin.removeRemote;
|
|
841
|
+
getConfig = configMixin.getConfig;
|
|
842
|
+
getModels = configMixin.getModels;
|
|
843
|
+
getAllModels = configMixin.getAllModels;
|
|
844
|
+
updateDefaults = configMixin.updateDefaults;
|
|
845
|
+
listFiles = filesMixin.listFiles;
|
|
846
|
+
getFileTree = filesMixin.getFileTree;
|
|
847
|
+
readFileContent = filesMixin.readFileContent;
|
|
848
|
+
getSessionFiles = filesMixin.getSessionFiles;
|
|
849
|
+
createBranch = branchesMixin.createBranch;
|
|
850
|
+
listBranches = branchesMixin.listBranches;
|
|
851
|
+
getParentSession = branchesMixin.getParentSession;
|
|
852
|
+
getShareStatus = branchesMixin.getShareStatus;
|
|
853
|
+
shareSession = branchesMixin.shareSession;
|
|
854
|
+
syncSession = branchesMixin.syncSession;
|
|
855
|
+
approveToolCall = approvalMixin.approveToolCall;
|
|
856
|
+
getPendingApprovals = approvalMixin.getPendingApprovals;
|
|
857
|
+
getSetuBalance = setuMixin.getSetuBalance;
|
|
858
|
+
getSetuWallet = setuMixin.getSetuWallet;
|
|
859
|
+
getSetuUsdcBalance = setuMixin.getSetuUsdcBalance;
|
|
860
|
+
getPolarTopupEstimate = setuMixin.getPolarTopupEstimate;
|
|
861
|
+
createPolarCheckout = setuMixin.createPolarCheckout;
|
|
862
|
+
selectTopupMethod = setuMixin.selectTopupMethod;
|
|
863
|
+
cancelTopup = setuMixin.cancelTopup;
|
|
864
|
+
getPendingTopup = setuMixin.getPendingTopup;
|
|
865
|
+
getPolarTopupStatus = setuMixin.getPolarTopupStatus;
|
|
866
|
+
getRazorpayTopupEstimate = setuMixin.getRazorpayTopupEstimate;
|
|
867
|
+
createRazorpayOrder = setuMixin.createRazorpayOrder;
|
|
868
|
+
verifyRazorpayPayment = setuMixin.verifyRazorpayPayment;
|
|
869
|
+
getAuthStatus = authMixin.getAuthStatus;
|
|
870
|
+
setupSetuWallet = authMixin.setupSetuWallet;
|
|
871
|
+
importSetuWallet = authMixin.importSetuWallet;
|
|
872
|
+
exportSetuWallet = authMixin.exportSetuWallet;
|
|
873
|
+
addProvider = authMixin.addProvider;
|
|
874
|
+
removeProvider = authMixin.removeProvider;
|
|
875
|
+
completeOnboarding = authMixin.completeOnboarding;
|
|
876
|
+
getOAuthStartUrl = authMixin.getOAuthStartUrl;
|
|
877
|
+
getOAuthUrl = authMixin.getOAuthUrl;
|
|
878
|
+
exchangeOAuthCode = authMixin.exchangeOAuthCode;
|
|
879
|
+
startCopilotDeviceFlow = authMixin.startCopilotDeviceFlow;
|
|
880
|
+
pollCopilotDeviceFlow = authMixin.pollCopilotDeviceFlow;
|
|
881
|
+
getCopilotAuthMethods = authMixin.getCopilotAuthMethods;
|
|
882
|
+
saveCopilotToken = authMixin.saveCopilotToken;
|
|
883
|
+
importCopilotTokenFromGh = authMixin.importCopilotTokenFromGh;
|
|
884
|
+
getCopilotDiagnostics = authMixin.getCopilotDiagnostics;
|
|
885
|
+
getProviderUsage = authMixin.getProviderUsage;
|
|
886
|
+
listSkills = skillsMixin.listSkills;
|
|
887
|
+
getSkill = skillsMixin.getSkill;
|
|
888
|
+
getSkillFiles = skillsMixin.getSkillFiles;
|
|
889
|
+
getSkillFileContent = skillsMixin.getSkillFileContent;
|
|
890
|
+
}
|
|
891
|
+
var apiClient = new ApiClient;
|
|
892
|
+
|
|
893
|
+
// src/hooks/useConfig.ts
|
|
894
|
+
function useConfig() {
|
|
895
|
+
return useQuery({
|
|
896
|
+
queryKey: ["config"],
|
|
897
|
+
queryFn: () => apiClient.getConfig(),
|
|
898
|
+
staleTime: 30000
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
function useModels(provider) {
|
|
902
|
+
return useQuery({
|
|
903
|
+
queryKey: ["models", provider],
|
|
904
|
+
queryFn: () => provider ? apiClient.getModels(provider) : null,
|
|
905
|
+
enabled: !!provider
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
function useAllModels() {
|
|
909
|
+
return useQuery({
|
|
910
|
+
queryKey: ["models", "all"],
|
|
911
|
+
queryFn: () => apiClient.getAllModels()
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
function useUpdateDefaults() {
|
|
915
|
+
const queryClient = useQueryClient();
|
|
916
|
+
return useMutation({
|
|
917
|
+
mutationFn: (data) => apiClient.updateDefaults(data),
|
|
918
|
+
onSuccess: () => {
|
|
919
|
+
queryClient.invalidateQueries({ queryKey: ["config"] });
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
// src/hooks/usePreferences.ts
|
|
924
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
925
|
+
var STORAGE_KEY = "otto-preferences";
|
|
926
|
+
var DEFAULT_PREFERENCES = {
|
|
927
|
+
vimMode: false,
|
|
928
|
+
compactThread: true
|
|
929
|
+
};
|
|
930
|
+
function resolveInitialPreferences() {
|
|
931
|
+
if (typeof window === "undefined") {
|
|
932
|
+
return DEFAULT_PREFERENCES;
|
|
933
|
+
}
|
|
934
|
+
try {
|
|
935
|
+
const stored = window.localStorage.getItem(STORAGE_KEY);
|
|
936
|
+
if (stored) {
|
|
937
|
+
const parsed = JSON.parse(stored);
|
|
938
|
+
return {
|
|
939
|
+
...DEFAULT_PREFERENCES,
|
|
940
|
+
...parsed
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
} catch (error) {
|
|
944
|
+
console.warn("Failed to load preferences", error);
|
|
945
|
+
}
|
|
946
|
+
return DEFAULT_PREFERENCES;
|
|
947
|
+
}
|
|
948
|
+
var preferences = resolveInitialPreferences();
|
|
949
|
+
var listeners = new Set;
|
|
950
|
+
function getSnapshot() {
|
|
951
|
+
return preferences;
|
|
952
|
+
}
|
|
953
|
+
function getServerSnapshot() {
|
|
954
|
+
return DEFAULT_PREFERENCES;
|
|
955
|
+
}
|
|
956
|
+
function subscribe(listener) {
|
|
957
|
+
listeners.add(listener);
|
|
958
|
+
return () => listeners.delete(listener);
|
|
959
|
+
}
|
|
960
|
+
function notifyListeners() {
|
|
961
|
+
for (const listener of listeners) {
|
|
962
|
+
listener();
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
function updateStore(updates) {
|
|
966
|
+
preferences = { ...preferences, ...updates };
|
|
967
|
+
if (typeof window !== "undefined") {
|
|
968
|
+
try {
|
|
969
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(preferences));
|
|
970
|
+
} catch (error) {
|
|
971
|
+
console.warn("Failed to persist preferences", error);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
notifyListeners();
|
|
975
|
+
}
|
|
976
|
+
function usePreferences() {
|
|
977
|
+
const currentPreferences = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
978
|
+
const updatePreferences = useCallback((updates) => {
|
|
979
|
+
updateStore(updates);
|
|
980
|
+
}, []);
|
|
981
|
+
return useMemo(() => ({ preferences: currentPreferences, updatePreferences }), [currentPreferences, updatePreferences]);
|
|
982
|
+
}
|
|
983
|
+
// src/hooks/useFiles.ts
|
|
984
|
+
import { useQuery as useQuery2 } from "@tanstack/react-query";
|
|
985
|
+
function useFiles() {
|
|
986
|
+
return useQuery2({
|
|
987
|
+
queryKey: ["files"],
|
|
988
|
+
queryFn: async () => {
|
|
989
|
+
const result = await apiClient.listFiles();
|
|
990
|
+
return result;
|
|
991
|
+
},
|
|
992
|
+
staleTime: 1e4,
|
|
993
|
+
refetchOnWindowFocus: true,
|
|
994
|
+
retry: 1
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
// src/hooks/useGit.ts
|
|
998
|
+
import { useQuery as useQuery3, useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
999
|
+
|
|
1000
|
+
// src/stores/gitStore.ts
|
|
1001
|
+
import { create as create8 } from "zustand";
|
|
1002
|
+
|
|
1003
|
+
// src/stores/sessionFilesStore.ts
|
|
1004
|
+
import { create as create7 } from "zustand";
|
|
1005
|
+
|
|
1006
|
+
// src/stores/researchStore.ts
|
|
1007
|
+
import { create as create6 } from "zustand";
|
|
1008
|
+
|
|
1009
|
+
// src/stores/settingsStore.ts
|
|
1010
|
+
import { create as create5 } from "zustand";
|
|
1011
|
+
|
|
1012
|
+
// src/stores/tunnelStore.ts
|
|
1013
|
+
import { create as create4 } from "zustand";
|
|
1014
|
+
|
|
1015
|
+
// src/stores/fileBrowserStore.ts
|
|
1016
|
+
import { create as create3 } from "zustand";
|
|
1017
|
+
|
|
1018
|
+
// src/stores/mcpStore.ts
|
|
1019
|
+
import { create as create2 } from "zustand";
|
|
1020
|
+
|
|
1021
|
+
// src/stores/skillsStore.ts
|
|
1022
|
+
import { create } from "zustand";
|
|
1023
|
+
var useSkillsStore = create((set) => ({
|
|
1024
|
+
isExpanded: false,
|
|
1025
|
+
skills: [],
|
|
1026
|
+
selectedSkill: null,
|
|
1027
|
+
isViewerOpen: false,
|
|
1028
|
+
viewingFile: null,
|
|
1029
|
+
toggleSidebar: () => {
|
|
1030
|
+
set((state) => {
|
|
1031
|
+
const newExpanded = !state.isExpanded;
|
|
1032
|
+
if (newExpanded) {
|
|
1033
|
+
useGitStore.getState().collapseSidebar();
|
|
1034
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1035
|
+
useResearchStore.getState().collapseSidebar();
|
|
1036
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1037
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1038
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1039
|
+
useMCPStore.getState().collapseSidebar();
|
|
1040
|
+
}
|
|
1041
|
+
return { isExpanded: newExpanded };
|
|
1042
|
+
});
|
|
1043
|
+
},
|
|
1044
|
+
expandSidebar: () => {
|
|
1045
|
+
useGitStore.getState().collapseSidebar();
|
|
1046
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1047
|
+
useResearchStore.getState().collapseSidebar();
|
|
1048
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1049
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1050
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1051
|
+
useMCPStore.getState().collapseSidebar();
|
|
1052
|
+
set({ isExpanded: true });
|
|
1053
|
+
},
|
|
1054
|
+
collapseSidebar: () => set({ isExpanded: false }),
|
|
1055
|
+
setSkills: (skills) => set({ skills }),
|
|
1056
|
+
selectSkill: (name) => set({ selectedSkill: name, isViewerOpen: false, viewingFile: null }),
|
|
1057
|
+
openViewer: (file) => set({ isViewerOpen: true, viewingFile: file }),
|
|
1058
|
+
closeViewer: () => set({ isViewerOpen: false, viewingFile: null })
|
|
1059
|
+
}));
|
|
1060
|
+
|
|
1061
|
+
// src/stores/mcpStore.ts
|
|
1062
|
+
var useMCPStore = create2((set) => ({
|
|
1063
|
+
isExpanded: false,
|
|
1064
|
+
servers: [],
|
|
1065
|
+
loading: new Set,
|
|
1066
|
+
authUrls: new Map,
|
|
1067
|
+
copilotDevice: null,
|
|
1068
|
+
toggleSidebar: () => {
|
|
1069
|
+
set((state) => {
|
|
1070
|
+
const newExpanded = !state.isExpanded;
|
|
1071
|
+
if (newExpanded) {
|
|
1072
|
+
useGitStore.getState().collapseSidebar();
|
|
1073
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1074
|
+
useResearchStore.getState().collapseSidebar();
|
|
1075
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1076
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1077
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1078
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1079
|
+
}
|
|
1080
|
+
return { isExpanded: newExpanded };
|
|
1081
|
+
});
|
|
1082
|
+
},
|
|
1083
|
+
expandSidebar: () => {
|
|
1084
|
+
useGitStore.getState().collapseSidebar();
|
|
1085
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1086
|
+
useResearchStore.getState().collapseSidebar();
|
|
1087
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1088
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1089
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1090
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1091
|
+
set({ isExpanded: true });
|
|
1092
|
+
},
|
|
1093
|
+
collapseSidebar: () => set({ isExpanded: false }),
|
|
1094
|
+
setServers: (servers) => set({ servers }),
|
|
1095
|
+
setLoading: (name, loading) => set((state) => {
|
|
1096
|
+
const next = new Set(state.loading);
|
|
1097
|
+
if (loading)
|
|
1098
|
+
next.add(name);
|
|
1099
|
+
else
|
|
1100
|
+
next.delete(name);
|
|
1101
|
+
return { loading: next };
|
|
1102
|
+
}),
|
|
1103
|
+
updateServer: (name, updates) => set((state) => ({
|
|
1104
|
+
servers: state.servers.map((s) => s.name === name ? { ...s, ...updates } : s)
|
|
1105
|
+
})),
|
|
1106
|
+
setAuthUrl: (name, url) => set((state) => {
|
|
1107
|
+
const next = new Map(state.authUrls);
|
|
1108
|
+
if (url)
|
|
1109
|
+
next.set(name, url);
|
|
1110
|
+
else
|
|
1111
|
+
next.delete(name);
|
|
1112
|
+
return { authUrls: next };
|
|
1113
|
+
}),
|
|
1114
|
+
setCopilotDevice: (info) => set({ copilotDevice: info })
|
|
1115
|
+
}));
|
|
1116
|
+
|
|
1117
|
+
// src/stores/fileBrowserStore.ts
|
|
1118
|
+
var useFileBrowserStore = create3((set) => ({
|
|
1119
|
+
isExpanded: false,
|
|
1120
|
+
selectedFile: null,
|
|
1121
|
+
isViewerOpen: false,
|
|
1122
|
+
expandedDirs: new Set,
|
|
1123
|
+
toggleSidebar: () => {
|
|
1124
|
+
set((state) => {
|
|
1125
|
+
const newExpanded = !state.isExpanded;
|
|
1126
|
+
if (newExpanded) {
|
|
1127
|
+
useGitStore.getState().collapseSidebar();
|
|
1128
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1129
|
+
useResearchStore.getState().collapseSidebar();
|
|
1130
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1131
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1132
|
+
useMCPStore.getState().collapseSidebar();
|
|
1133
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1134
|
+
}
|
|
1135
|
+
return { isExpanded: newExpanded };
|
|
1136
|
+
});
|
|
1137
|
+
},
|
|
1138
|
+
expandSidebar: () => {
|
|
1139
|
+
useGitStore.getState().collapseSidebar();
|
|
1140
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1141
|
+
useResearchStore.getState().collapseSidebar();
|
|
1142
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1143
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1144
|
+
useMCPStore.getState().collapseSidebar();
|
|
1145
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1146
|
+
set({ isExpanded: true });
|
|
1147
|
+
},
|
|
1148
|
+
collapseSidebar: () => set({
|
|
1149
|
+
isExpanded: false,
|
|
1150
|
+
isViewerOpen: false,
|
|
1151
|
+
selectedFile: null
|
|
1152
|
+
}),
|
|
1153
|
+
openFile: (path) => set({
|
|
1154
|
+
selectedFile: path,
|
|
1155
|
+
isViewerOpen: true
|
|
1156
|
+
}),
|
|
1157
|
+
closeViewer: () => set({
|
|
1158
|
+
isViewerOpen: false,
|
|
1159
|
+
selectedFile: null
|
|
1160
|
+
}),
|
|
1161
|
+
toggleDir: (path) => set((state) => {
|
|
1162
|
+
const next = new Set(state.expandedDirs);
|
|
1163
|
+
if (next.has(path)) {
|
|
1164
|
+
next.delete(path);
|
|
1165
|
+
} else {
|
|
1166
|
+
next.add(path);
|
|
1167
|
+
}
|
|
1168
|
+
return { expandedDirs: next };
|
|
1169
|
+
})
|
|
1170
|
+
}));
|
|
1171
|
+
|
|
1172
|
+
// src/stores/tunnelStore.ts
|
|
1173
|
+
var useTunnelStore = create4((set) => ({
|
|
1174
|
+
isExpanded: false,
|
|
1175
|
+
status: "idle",
|
|
1176
|
+
url: null,
|
|
1177
|
+
qrCode: null,
|
|
1178
|
+
error: null,
|
|
1179
|
+
progress: null,
|
|
1180
|
+
toggleSidebar: () => {
|
|
1181
|
+
set((state) => {
|
|
1182
|
+
const newExpanded = !state.isExpanded;
|
|
1183
|
+
if (newExpanded) {
|
|
1184
|
+
useGitStore.getState().collapseSidebar();
|
|
1185
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1186
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1187
|
+
useResearchStore.getState().collapseSidebar();
|
|
1188
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1189
|
+
useMCPStore.getState().collapseSidebar();
|
|
1190
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1191
|
+
}
|
|
1192
|
+
return { isExpanded: newExpanded };
|
|
1193
|
+
});
|
|
1194
|
+
},
|
|
1195
|
+
expandSidebar: () => {
|
|
1196
|
+
useGitStore.getState().collapseSidebar();
|
|
1197
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1198
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1199
|
+
useResearchStore.getState().collapseSidebar();
|
|
1200
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1201
|
+
useMCPStore.getState().collapseSidebar();
|
|
1202
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1203
|
+
set({ isExpanded: true });
|
|
1204
|
+
},
|
|
1205
|
+
collapseSidebar: () => set({ isExpanded: false }),
|
|
1206
|
+
setStatus: (status) => set({ status }),
|
|
1207
|
+
setUrl: (url) => set({ url }),
|
|
1208
|
+
setQrCode: (qrCode) => set({ qrCode }),
|
|
1209
|
+
setError: (error) => set({ error }),
|
|
1210
|
+
setProgress: (progress) => set({ progress }),
|
|
1211
|
+
reset: () => set({
|
|
1212
|
+
status: "idle",
|
|
1213
|
+
url: null,
|
|
1214
|
+
qrCode: null,
|
|
1215
|
+
error: null,
|
|
1216
|
+
progress: null
|
|
1217
|
+
})
|
|
1218
|
+
}));
|
|
1219
|
+
|
|
1220
|
+
// src/stores/settingsStore.ts
|
|
1221
|
+
var useSettingsStore = create5((set) => ({
|
|
1222
|
+
isExpanded: false,
|
|
1223
|
+
toggleSidebar: () => {
|
|
1224
|
+
set((state) => {
|
|
1225
|
+
const newExpanded = !state.isExpanded;
|
|
1226
|
+
if (newExpanded) {
|
|
1227
|
+
useGitStore.getState().collapseSidebar();
|
|
1228
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1229
|
+
useResearchStore.getState().collapseSidebar();
|
|
1230
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1231
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1232
|
+
useMCPStore.getState().collapseSidebar();
|
|
1233
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1234
|
+
}
|
|
1235
|
+
return { isExpanded: newExpanded };
|
|
1236
|
+
});
|
|
1237
|
+
},
|
|
1238
|
+
expandSidebar: () => set({ isExpanded: true }),
|
|
1239
|
+
collapseSidebar: () => set({ isExpanded: false })
|
|
1240
|
+
}));
|
|
1241
|
+
|
|
1242
|
+
// src/stores/researchStore.ts
|
|
1243
|
+
var useResearchStore = create6((set, get) => ({
|
|
1244
|
+
isExpanded: false,
|
|
1245
|
+
activeResearchSessionId: null,
|
|
1246
|
+
parentSessionId: null,
|
|
1247
|
+
toggleSidebar: () => {
|
|
1248
|
+
set((state) => {
|
|
1249
|
+
const newExpanded = !state.isExpanded;
|
|
1250
|
+
if (newExpanded) {
|
|
1251
|
+
useGitStore.getState().collapseSidebar();
|
|
1252
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1253
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1254
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1255
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1256
|
+
useMCPStore.getState().collapseSidebar();
|
|
1257
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1258
|
+
}
|
|
1259
|
+
return { isExpanded: newExpanded };
|
|
1260
|
+
});
|
|
1261
|
+
},
|
|
1262
|
+
expandSidebar: () => {
|
|
1263
|
+
useGitStore.getState().collapseSidebar();
|
|
1264
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1265
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1266
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1267
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1268
|
+
useMCPStore.getState().collapseSidebar();
|
|
1269
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1270
|
+
set({ isExpanded: true });
|
|
1271
|
+
},
|
|
1272
|
+
collapseSidebar: () => set({ isExpanded: false }),
|
|
1273
|
+
selectResearchSession: (id) => set({ activeResearchSessionId: id }),
|
|
1274
|
+
setParentSessionId: (id) => {
|
|
1275
|
+
const currentParentId = get().parentSessionId;
|
|
1276
|
+
if (currentParentId !== id) {
|
|
1277
|
+
set({
|
|
1278
|
+
parentSessionId: id,
|
|
1279
|
+
activeResearchSessionId: null
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
reset: () => set({
|
|
1284
|
+
activeResearchSessionId: null,
|
|
1285
|
+
parentSessionId: null
|
|
1286
|
+
})
|
|
1287
|
+
}));
|
|
1288
|
+
|
|
1289
|
+
// src/stores/sessionFilesStore.ts
|
|
1290
|
+
var useSessionFilesStore = create7((set) => ({
|
|
1291
|
+
isExpanded: false,
|
|
1292
|
+
selectedFile: null,
|
|
1293
|
+
allOperations: [],
|
|
1294
|
+
selectedOperationIndex: 0,
|
|
1295
|
+
isDiffOpen: false,
|
|
1296
|
+
toggleSidebar: () => {
|
|
1297
|
+
set((state) => {
|
|
1298
|
+
const newExpanded = !state.isExpanded;
|
|
1299
|
+
if (newExpanded) {
|
|
1300
|
+
useGitStore.getState().collapseSidebar();
|
|
1301
|
+
useResearchStore.getState().collapseSidebar();
|
|
1302
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1303
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1304
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1305
|
+
useMCPStore.getState().collapseSidebar();
|
|
1306
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1307
|
+
}
|
|
1308
|
+
return { isExpanded: newExpanded };
|
|
1309
|
+
});
|
|
1310
|
+
},
|
|
1311
|
+
expandSidebar: () => {
|
|
1312
|
+
useGitStore.getState().collapseSidebar();
|
|
1313
|
+
useResearchStore.getState().collapseSidebar();
|
|
1314
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1315
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1316
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1317
|
+
useMCPStore.getState().collapseSidebar();
|
|
1318
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1319
|
+
set({ isExpanded: true });
|
|
1320
|
+
},
|
|
1321
|
+
collapseSidebar: () => set({
|
|
1322
|
+
isExpanded: false,
|
|
1323
|
+
isDiffOpen: false,
|
|
1324
|
+
selectedFile: null,
|
|
1325
|
+
allOperations: [],
|
|
1326
|
+
selectedOperationIndex: 0
|
|
1327
|
+
}),
|
|
1328
|
+
openDiff: (file, operations) => set({
|
|
1329
|
+
selectedFile: file,
|
|
1330
|
+
allOperations: operations,
|
|
1331
|
+
selectedOperationIndex: operations.length - 1,
|
|
1332
|
+
isDiffOpen: true,
|
|
1333
|
+
isExpanded: true
|
|
1334
|
+
}),
|
|
1335
|
+
selectOperation: (index) => set({ selectedOperationIndex: index }),
|
|
1336
|
+
closeDiff: () => set({
|
|
1337
|
+
isDiffOpen: false,
|
|
1338
|
+
selectedFile: null,
|
|
1339
|
+
allOperations: [],
|
|
1340
|
+
selectedOperationIndex: 0
|
|
1341
|
+
})
|
|
1342
|
+
}));
|
|
1343
|
+
|
|
1344
|
+
// src/stores/gitStore.ts
|
|
1345
|
+
var useGitStore = create8((set) => ({
|
|
1346
|
+
isExpanded: false,
|
|
1347
|
+
activeSessionId: null,
|
|
1348
|
+
selectedFile: null,
|
|
1349
|
+
selectedFileStaged: false,
|
|
1350
|
+
isDiffOpen: false,
|
|
1351
|
+
isCommitModalOpen: false,
|
|
1352
|
+
commitSessionId: null,
|
|
1353
|
+
wasSessionListCollapsed: false,
|
|
1354
|
+
toggleSidebar: () => {
|
|
1355
|
+
set((state) => {
|
|
1356
|
+
const newExpanded = !state.isExpanded;
|
|
1357
|
+
if (newExpanded) {
|
|
1358
|
+
useSessionFilesStore.getState().collapseSidebar();
|
|
1359
|
+
useResearchStore.getState().collapseSidebar();
|
|
1360
|
+
useSettingsStore.getState().collapseSidebar();
|
|
1361
|
+
useTunnelStore.getState().collapseSidebar();
|
|
1362
|
+
useFileBrowserStore.getState().collapseSidebar();
|
|
1363
|
+
useMCPStore.getState().collapseSidebar();
|
|
1364
|
+
useSkillsStore.getState().collapseSidebar();
|
|
1365
|
+
}
|
|
1366
|
+
return { isExpanded: newExpanded };
|
|
1367
|
+
});
|
|
1368
|
+
},
|
|
1369
|
+
expandSidebar: () => set({ isExpanded: true }),
|
|
1370
|
+
collapseSidebar: () => set({ isExpanded: false, isDiffOpen: false, selectedFile: null }),
|
|
1371
|
+
openDiff: (file, staged) => set({
|
|
1372
|
+
selectedFile: file,
|
|
1373
|
+
selectedFileStaged: staged,
|
|
1374
|
+
isDiffOpen: true,
|
|
1375
|
+
isExpanded: true
|
|
1376
|
+
}),
|
|
1377
|
+
closeDiff: () => set({
|
|
1378
|
+
isDiffOpen: false,
|
|
1379
|
+
selectedFile: null
|
|
1380
|
+
}),
|
|
1381
|
+
switchFile: (file, staged) => set({
|
|
1382
|
+
selectedFile: file,
|
|
1383
|
+
selectedFileStaged: staged
|
|
1384
|
+
}),
|
|
1385
|
+
openCommitModal: () => set((state) => ({
|
|
1386
|
+
isCommitModalOpen: true,
|
|
1387
|
+
commitSessionId: state.activeSessionId
|
|
1388
|
+
})),
|
|
1389
|
+
openCommitModalForSession: (sessionId) => set({ isCommitModalOpen: true, commitSessionId: sessionId }),
|
|
1390
|
+
closeCommitModal: () => set({ isCommitModalOpen: false, commitSessionId: null }),
|
|
1391
|
+
setActiveSessionId: (sessionId) => set({ activeSessionId: sessionId }),
|
|
1392
|
+
setSessionListCollapsed: (collapsed) => set({ wasSessionListCollapsed: collapsed })
|
|
1393
|
+
}));
|
|
1394
|
+
|
|
1395
|
+
// src/hooks/useGit.ts
|
|
1396
|
+
function useGitStatus() {
|
|
1397
|
+
const isExpanded = useGitStore((state) => state.isExpanded);
|
|
1398
|
+
return useQuery3({
|
|
1399
|
+
queryKey: ["git", "status"],
|
|
1400
|
+
queryFn: () => apiClient.getGitStatus(),
|
|
1401
|
+
refetchInterval: isExpanded ? 5000 : false,
|
|
1402
|
+
retry: 1,
|
|
1403
|
+
staleTime: 3000
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
function useGitDiff(file, staged = false) {
|
|
1407
|
+
return useQuery3({
|
|
1408
|
+
queryKey: ["git", "diff", file, staged],
|
|
1409
|
+
queryFn: () => file ? apiClient.getGitDiff(file, staged) : null,
|
|
1410
|
+
enabled: !!file,
|
|
1411
|
+
retry: 1,
|
|
1412
|
+
refetchInterval: false
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
function useGitBranch() {
|
|
1416
|
+
const isExpanded = useGitStore((state) => state.isExpanded);
|
|
1417
|
+
return useQuery3({
|
|
1418
|
+
queryKey: ["git", "branch"],
|
|
1419
|
+
queryFn: () => apiClient.getGitBranch(),
|
|
1420
|
+
refetchInterval: isExpanded ? 1e4 : false,
|
|
1421
|
+
retry: 1,
|
|
1422
|
+
staleTime: 5000
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
function useGenerateCommitMessage(sessionId) {
|
|
1426
|
+
return useMutation2({
|
|
1427
|
+
mutationFn: () => apiClient.generateCommitMessage(sessionId ?? undefined)
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
function useStageFiles() {
|
|
1431
|
+
const queryClient = useQueryClient2();
|
|
1432
|
+
return useMutation2({
|
|
1433
|
+
mutationFn: (files) => apiClient.stageFiles(files),
|
|
1434
|
+
onSuccess: () => {
|
|
1435
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
function useUnstageFiles() {
|
|
1440
|
+
const queryClient = useQueryClient2();
|
|
1441
|
+
return useMutation2({
|
|
1442
|
+
mutationFn: (files) => apiClient.unstageFiles(files),
|
|
1443
|
+
onSuccess: () => {
|
|
1444
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
function useRestoreFiles() {
|
|
1449
|
+
const queryClient = useQueryClient2();
|
|
1450
|
+
return useMutation2({
|
|
1451
|
+
mutationFn: (files) => apiClient.restoreFiles(files),
|
|
1452
|
+
onSuccess: () => {
|
|
1453
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
function useDeleteFiles() {
|
|
1458
|
+
const queryClient = useQueryClient2();
|
|
1459
|
+
return useMutation2({
|
|
1460
|
+
mutationFn: (files) => apiClient.deleteFiles(files),
|
|
1461
|
+
onSuccess: () => {
|
|
1462
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1463
|
+
}
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
function useCommitChanges() {
|
|
1467
|
+
const queryClient = useQueryClient2();
|
|
1468
|
+
return useMutation2({
|
|
1469
|
+
mutationFn: (message) => apiClient.commitChanges(message),
|
|
1470
|
+
onSuccess: () => {
|
|
1471
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1472
|
+
queryClient.invalidateQueries({ queryKey: ["git", "branch"] });
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
function usePushCommits() {
|
|
1477
|
+
const queryClient = useQueryClient2();
|
|
1478
|
+
return useMutation2({
|
|
1479
|
+
mutationFn: () => apiClient.pushCommits(),
|
|
1480
|
+
onSuccess: () => {
|
|
1481
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1482
|
+
queryClient.invalidateQueries({ queryKey: ["git", "branch"] });
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
function usePullChanges() {
|
|
1487
|
+
const queryClient = useQueryClient2();
|
|
1488
|
+
return useMutation2({
|
|
1489
|
+
mutationFn: () => apiClient.pullChanges(),
|
|
1490
|
+
onSuccess: () => {
|
|
1491
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1492
|
+
queryClient.invalidateQueries({ queryKey: ["git", "branch"] });
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
function useGitInit() {
|
|
1497
|
+
const queryClient = useQueryClient2();
|
|
1498
|
+
return useMutation2({
|
|
1499
|
+
mutationFn: () => apiClient.initGitRepo(),
|
|
1500
|
+
onSuccess: () => {
|
|
1501
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1502
|
+
queryClient.invalidateQueries({ queryKey: ["git", "branch"] });
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
function useGitRemotes() {
|
|
1507
|
+
const isExpanded = useGitStore((state) => state.isExpanded);
|
|
1508
|
+
return useQuery3({
|
|
1509
|
+
queryKey: ["git", "remotes"],
|
|
1510
|
+
queryFn: () => apiClient.getRemotes(),
|
|
1511
|
+
enabled: isExpanded,
|
|
1512
|
+
retry: 1,
|
|
1513
|
+
staleTime: 1e4
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
function useAddRemote() {
|
|
1517
|
+
const queryClient = useQueryClient2();
|
|
1518
|
+
return useMutation2({
|
|
1519
|
+
mutationFn: ({ name, url }) => apiClient.addRemote(name, url),
|
|
1520
|
+
onSuccess: () => {
|
|
1521
|
+
queryClient.invalidateQueries({ queryKey: ["git", "remotes"] });
|
|
1522
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1523
|
+
}
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
function useRemoveRemote() {
|
|
1527
|
+
const queryClient = useQueryClient2();
|
|
1528
|
+
return useMutation2({
|
|
1529
|
+
mutationFn: (name) => apiClient.removeRemote(name),
|
|
1530
|
+
onSuccess: () => {
|
|
1531
|
+
queryClient.invalidateQueries({ queryKey: ["git", "remotes"] });
|
|
1532
|
+
queryClient.invalidateQueries({ queryKey: ["git", "status"] });
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
// src/hooks/useMessages.ts
|
|
1537
|
+
import { useQuery as useQuery4, useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
1538
|
+
|
|
1539
|
+
// src/hooks/useSessions.ts
|
|
1540
|
+
import {
|
|
1541
|
+
useInfiniteQuery,
|
|
1542
|
+
useMutation as useMutation3,
|
|
1543
|
+
useQueryClient as useQueryClient3
|
|
1544
|
+
} from "@tanstack/react-query";
|
|
1545
|
+
import { useMemo as useMemo2 } from "react";
|
|
1546
|
+
var SESSIONS_PAGE_SIZE = 50;
|
|
1547
|
+
var sessionsQueryKey = ["sessions", "list"];
|
|
1548
|
+
function useSessionsInfinite() {
|
|
1549
|
+
return useInfiniteQuery({
|
|
1550
|
+
queryKey: sessionsQueryKey,
|
|
1551
|
+
queryFn: ({ pageParam = 0 }) => apiClient.getSessionsPage({
|
|
1552
|
+
limit: SESSIONS_PAGE_SIZE,
|
|
1553
|
+
offset: pageParam
|
|
1554
|
+
}),
|
|
1555
|
+
getNextPageParam: (lastPage) => lastPage.nextOffset ?? undefined,
|
|
1556
|
+
initialPageParam: 0,
|
|
1557
|
+
staleTime: 30000,
|
|
1558
|
+
refetchInterval: 30000,
|
|
1559
|
+
refetchOnWindowFocus: false
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
function useSessions() {
|
|
1563
|
+
const query = useSessionsInfinite();
|
|
1564
|
+
const data = useMemo2(() => {
|
|
1565
|
+
if (!query.data?.pages)
|
|
1566
|
+
return [];
|
|
1567
|
+
return query.data.pages.flatMap((p) => p.items ?? []);
|
|
1568
|
+
}, [query.data]);
|
|
1569
|
+
return {
|
|
1570
|
+
data,
|
|
1571
|
+
isLoading: query.isLoading,
|
|
1572
|
+
isError: query.isError,
|
|
1573
|
+
error: query.error,
|
|
1574
|
+
hasNextPage: query.hasNextPage,
|
|
1575
|
+
fetchNextPage: query.fetchNextPage,
|
|
1576
|
+
isFetchingNextPage: query.isFetchingNextPage
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
function useSession(sessionId) {
|
|
1580
|
+
const { data: sessions } = useSessions();
|
|
1581
|
+
return sessions?.find((s) => s.id === sessionId);
|
|
1582
|
+
}
|
|
1583
|
+
function useCreateSession() {
|
|
1584
|
+
const queryClient = useQueryClient3();
|
|
1585
|
+
return useMutation3({
|
|
1586
|
+
mutationFn: (data) => apiClient.createSession(data),
|
|
1587
|
+
onSuccess: (newSession) => {
|
|
1588
|
+
queryClient.setQueryData(sessionsQueryKey, (old) => {
|
|
1589
|
+
if (!old)
|
|
1590
|
+
return old;
|
|
1591
|
+
const firstPage = old.pages[0];
|
|
1592
|
+
if (!firstPage)
|
|
1593
|
+
return old;
|
|
1594
|
+
return {
|
|
1595
|
+
...old,
|
|
1596
|
+
pages: [
|
|
1597
|
+
{ ...firstPage, items: [newSession, ...firstPage.items] },
|
|
1598
|
+
...old.pages.slice(1)
|
|
1599
|
+
]
|
|
1600
|
+
};
|
|
1601
|
+
});
|
|
1602
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
function useUpdateSession(sessionId) {
|
|
1607
|
+
const queryClient = useQueryClient3();
|
|
1608
|
+
return useMutation3({
|
|
1609
|
+
mutationFn: (data) => apiClient.updateSession(sessionId, data),
|
|
1610
|
+
onSuccess: async () => {
|
|
1611
|
+
await queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
1612
|
+
await queryClient.invalidateQueries({ queryKey: ["session", sessionId] });
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
function useDeleteSession() {
|
|
1617
|
+
const queryClient = useQueryClient3();
|
|
1618
|
+
return useMutation3({
|
|
1619
|
+
mutationFn: (sessionId) => apiClient.deleteSession(sessionId),
|
|
1620
|
+
onSuccess: () => {
|
|
1621
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
// src/hooks/useMessages.ts
|
|
1627
|
+
function useMessages(sessionId) {
|
|
1628
|
+
return useQuery4({
|
|
1629
|
+
queryKey: ["messages", sessionId],
|
|
1630
|
+
queryFn: () => {
|
|
1631
|
+
if (!sessionId) {
|
|
1632
|
+
throw new Error("Session ID is required");
|
|
1633
|
+
}
|
|
1634
|
+
return apiClient.getMessages(sessionId);
|
|
1635
|
+
},
|
|
1636
|
+
enabled: !!sessionId
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
function useSendMessage(sessionId) {
|
|
1640
|
+
const queryClient = useQueryClient4();
|
|
1641
|
+
return useMutation4({
|
|
1642
|
+
mutationFn: (data) => apiClient.sendMessage(sessionId, data),
|
|
1643
|
+
onSuccess: () => {
|
|
1644
|
+
queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
|
|
1645
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1649
|
+
// src/hooks/useSessionStream.ts
|
|
1650
|
+
import { useEffect, useRef } from "react";
|
|
1651
|
+
import { useQueryClient as useQueryClient5 } from "@tanstack/react-query";
|
|
1652
|
+
|
|
1653
|
+
// src/lib/sse-client.ts
|
|
1654
|
+
class SSEClient {
|
|
1655
|
+
abortController = null;
|
|
1656
|
+
handlers = new Map;
|
|
1657
|
+
running = false;
|
|
1658
|
+
async connect(url) {
|
|
1659
|
+
if (this.abortController) {
|
|
1660
|
+
this.abortController.abort();
|
|
1661
|
+
}
|
|
1662
|
+
this.abortController = new AbortController;
|
|
1663
|
+
this.running = true;
|
|
1664
|
+
try {
|
|
1665
|
+
const response = await fetch(url, {
|
|
1666
|
+
headers: { Accept: "text/event-stream" },
|
|
1667
|
+
signal: this.abortController.signal
|
|
1668
|
+
});
|
|
1669
|
+
if (!response.ok) {
|
|
1670
|
+
console.error("[SSE] Connection failed:", response.status);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
const reader = response.body?.getReader();
|
|
1674
|
+
if (!reader) {
|
|
1675
|
+
console.error("[SSE] No response body");
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
const decoder = new TextDecoder;
|
|
1679
|
+
let buffer = "";
|
|
1680
|
+
while (this.running) {
|
|
1681
|
+
const { done, value } = await reader.read();
|
|
1682
|
+
if (done)
|
|
1683
|
+
break;
|
|
1684
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1685
|
+
let idx = buffer.indexOf(`
|
|
1686
|
+
|
|
1687
|
+
`);
|
|
1688
|
+
while (idx !== -1) {
|
|
1689
|
+
const raw = buffer.slice(0, idx);
|
|
1690
|
+
buffer = buffer.slice(idx + 2);
|
|
1691
|
+
const lines = raw.split(`
|
|
1692
|
+
`);
|
|
1693
|
+
let eventType = "message";
|
|
1694
|
+
let data = "";
|
|
1695
|
+
for (const line of lines) {
|
|
1696
|
+
if (line.startsWith("event: ")) {
|
|
1697
|
+
eventType = line.slice(7).trim();
|
|
1698
|
+
} else if (line.startsWith("data: ")) {
|
|
1699
|
+
data += (data ? `
|
|
1700
|
+
` : "") + line.slice(6);
|
|
1701
|
+
} else if (line.startsWith(":")) {}
|
|
1702
|
+
}
|
|
1703
|
+
if (data) {
|
|
1704
|
+
try {
|
|
1705
|
+
const payload = JSON.parse(data);
|
|
1706
|
+
this.emit({ type: eventType, payload });
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
console.error(`[SSE] Failed to parse ${eventType}:`, error);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
idx = buffer.indexOf(`
|
|
1712
|
+
|
|
1713
|
+
`);
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
} catch (error) {
|
|
1717
|
+
if (error instanceof Error && error.name === "AbortError") {} else if (error instanceof TypeError && error.message === "Load failed") {} else {
|
|
1718
|
+
console.error("[SSE] Connection error:", error);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
disconnect() {
|
|
1723
|
+
this.running = false;
|
|
1724
|
+
if (this.abortController) {
|
|
1725
|
+
this.abortController.abort();
|
|
1726
|
+
this.abortController = null;
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
on(eventType, handler) {
|
|
1730
|
+
if (!this.handlers.has(eventType)) {
|
|
1731
|
+
this.handlers.set(eventType, new Set);
|
|
1732
|
+
}
|
|
1733
|
+
this.handlers.get(eventType)?.add(handler);
|
|
1734
|
+
return () => {
|
|
1735
|
+
this.off(eventType, handler);
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
off(eventType, handler) {
|
|
1739
|
+
const handlers = this.handlers.get(eventType);
|
|
1740
|
+
if (handlers) {
|
|
1741
|
+
handlers.delete(handler);
|
|
1742
|
+
if (handlers.size === 0) {
|
|
1743
|
+
this.handlers.delete(eventType);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
emit(event) {
|
|
1748
|
+
const handlers = this.handlers.get(event.type);
|
|
1749
|
+
if (handlers) {
|
|
1750
|
+
for (const handler of handlers) {
|
|
1751
|
+
handler(event);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
const allHandlers = this.handlers.get("*");
|
|
1755
|
+
if (allHandlers) {
|
|
1756
|
+
for (const handler of allHandlers) {
|
|
1757
|
+
handler(event);
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
// src/stores/toolApprovalStore.ts
|
|
1764
|
+
import { create as create9 } from "zustand";
|
|
1765
|
+
var useToolApprovalStore = create9((set) => ({
|
|
1766
|
+
pendingApprovals: [],
|
|
1767
|
+
addPendingApproval: (approval) => set((state) => ({
|
|
1768
|
+
pendingApprovals: [...state.pendingApprovals, approval]
|
|
1769
|
+
})),
|
|
1770
|
+
removePendingApproval: (callId) => set((state) => ({
|
|
1771
|
+
pendingApprovals: state.pendingApprovals.filter((a) => a.callId !== callId)
|
|
1772
|
+
})),
|
|
1773
|
+
updatePendingApproval: (callId, args) => set((state) => ({
|
|
1774
|
+
pendingApprovals: state.pendingApprovals.map((a) => a.callId === callId ? { ...a, args } : a)
|
|
1775
|
+
})),
|
|
1776
|
+
clearPendingApprovals: () => set({ pendingApprovals: [] }),
|
|
1777
|
+
setPendingApprovals: (approvals) => set({ pendingApprovals: approvals })
|
|
1778
|
+
}));
|
|
1779
|
+
|
|
1780
|
+
// src/hooks/useSessionStream.ts
|
|
1781
|
+
function useSessionStream(sessionId) {
|
|
1782
|
+
const queryClient = useQueryClient5();
|
|
1783
|
+
const clientRef = useRef(null);
|
|
1784
|
+
const assistantMessageIdRef = useRef(null);
|
|
1785
|
+
const lastInvalidationRef = useRef(0);
|
|
1786
|
+
const {
|
|
1787
|
+
addPendingApproval,
|
|
1788
|
+
removePendingApproval,
|
|
1789
|
+
updatePendingApproval,
|
|
1790
|
+
setPendingApprovals
|
|
1791
|
+
} = useToolApprovalStore();
|
|
1792
|
+
useEffect(() => {
|
|
1793
|
+
if (!sessionId) {
|
|
1794
|
+
console.log("[useSessionStream] No sessionId, skipping");
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
assistantMessageIdRef.current = null;
|
|
1798
|
+
let lastSessionInvalidation = 0;
|
|
1799
|
+
apiClient.getPendingApprovals(sessionId).then((result) => {
|
|
1800
|
+
if (result.ok && result.pending.length > 0) {
|
|
1801
|
+
setPendingApprovals(result.pending);
|
|
1802
|
+
} else {
|
|
1803
|
+
setPendingApprovals([]);
|
|
1804
|
+
}
|
|
1805
|
+
}).catch(() => {
|
|
1806
|
+
setPendingApprovals([]);
|
|
1807
|
+
});
|
|
1808
|
+
const client2 = new SSEClient;
|
|
1809
|
+
clientRef.current = client2;
|
|
1810
|
+
const url = apiClient.getStreamUrl(sessionId);
|
|
1811
|
+
console.log("[useSessionStream] Connecting to stream:", url);
|
|
1812
|
+
client2.connect(url);
|
|
1813
|
+
const resolveAssistantTargetIndex = (messages) => {
|
|
1814
|
+
if (assistantMessageIdRef.current) {
|
|
1815
|
+
const byId = messages.findIndex((message) => message.id === assistantMessageIdRef.current);
|
|
1816
|
+
if (byId !== -1)
|
|
1817
|
+
return byId;
|
|
1818
|
+
}
|
|
1819
|
+
for (let i = messages.length - 1;i >= 0; i -= 1) {
|
|
1820
|
+
const candidate = messages[i];
|
|
1821
|
+
if (candidate.role === "assistant" && candidate.status !== "complete") {
|
|
1822
|
+
return i;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
return -1;
|
|
1826
|
+
};
|
|
1827
|
+
const extractText = (part) => {
|
|
1828
|
+
if (part.contentJson && typeof part.contentJson === "object" && !Array.isArray(part.contentJson) && "text" in part.contentJson) {
|
|
1829
|
+
return String(part.contentJson.text ?? "");
|
|
1830
|
+
}
|
|
1831
|
+
if (typeof part.content === "string") {
|
|
1832
|
+
try {
|
|
1833
|
+
const parsed = JSON.parse(part.content);
|
|
1834
|
+
if (parsed && typeof parsed.text === "string")
|
|
1835
|
+
return parsed.text;
|
|
1836
|
+
} catch {}
|
|
1837
|
+
return part.content;
|
|
1838
|
+
}
|
|
1839
|
+
return "";
|
|
1840
|
+
};
|
|
1841
|
+
const getOptimisticPartIndex = (parts, stepIndex) => {
|
|
1842
|
+
if (typeof stepIndex !== "number") {
|
|
1843
|
+
return parts.length;
|
|
1844
|
+
}
|
|
1845
|
+
const sameStepIndexes = parts.filter((part) => part.stepIndex === stepIndex).map((part) => part.index).filter((index) => Number.isFinite(index));
|
|
1846
|
+
if (sameStepIndexes.length > 0) {
|
|
1847
|
+
return Math.max(...sameStepIndexes) + 0.001;
|
|
1848
|
+
}
|
|
1849
|
+
const previousStepIndexes = parts.filter((part) => typeof part.stepIndex === "number" && part.stepIndex < stepIndex).map((part) => part.index).filter((index) => Number.isFinite(index));
|
|
1850
|
+
const nextStepIndexes = parts.filter((part) => typeof part.stepIndex === "number" && part.stepIndex > stepIndex).map((part) => part.index).filter((index) => Number.isFinite(index));
|
|
1851
|
+
const lowerBound = previousStepIndexes.length > 0 ? Math.max(...previousStepIndexes) : null;
|
|
1852
|
+
const upperBound = nextStepIndexes.length > 0 ? Math.min(...nextStepIndexes) : null;
|
|
1853
|
+
if (lowerBound !== null && upperBound !== null) {
|
|
1854
|
+
return (lowerBound + upperBound) / 2;
|
|
1855
|
+
}
|
|
1856
|
+
if (lowerBound !== null) {
|
|
1857
|
+
return lowerBound + 1;
|
|
1858
|
+
}
|
|
1859
|
+
if (upperBound !== null) {
|
|
1860
|
+
return upperBound - 1;
|
|
1861
|
+
}
|
|
1862
|
+
return parts.length;
|
|
1863
|
+
};
|
|
1864
|
+
const applyReasoningDelta = (payload) => {
|
|
1865
|
+
const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
1866
|
+
const partId = typeof payload?.partId === "string" ? payload.partId : null;
|
|
1867
|
+
const delta = typeof payload?.delta === "string" ? payload.delta : null;
|
|
1868
|
+
if (!messageId || !partId || delta === null)
|
|
1869
|
+
return;
|
|
1870
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
1871
|
+
if (!oldMessages)
|
|
1872
|
+
return oldMessages;
|
|
1873
|
+
const nextMessages = [...oldMessages];
|
|
1874
|
+
const messageIndex = nextMessages.findIndex((message) => message.id === messageId);
|
|
1875
|
+
if (messageIndex === -1)
|
|
1876
|
+
return oldMessages;
|
|
1877
|
+
const targetMessage = nextMessages[messageIndex];
|
|
1878
|
+
const parts = targetMessage.parts ? [...targetMessage.parts] : [];
|
|
1879
|
+
let partIndex = parts.findIndex((part) => part.id === partId);
|
|
1880
|
+
const stepIndex = typeof payload?.stepIndex === "number" ? payload.stepIndex : null;
|
|
1881
|
+
if (partIndex === -1) {
|
|
1882
|
+
const newPart = {
|
|
1883
|
+
id: partId,
|
|
1884
|
+
messageId,
|
|
1885
|
+
index: getOptimisticPartIndex(parts, stepIndex),
|
|
1886
|
+
stepIndex,
|
|
1887
|
+
type: "reasoning",
|
|
1888
|
+
content: JSON.stringify({ text: delta }),
|
|
1889
|
+
contentJson: { text: delta },
|
|
1890
|
+
agent: targetMessage.agent,
|
|
1891
|
+
provider: targetMessage.provider,
|
|
1892
|
+
model: targetMessage.model,
|
|
1893
|
+
startedAt: Date.now(),
|
|
1894
|
+
completedAt: null,
|
|
1895
|
+
toolName: null,
|
|
1896
|
+
toolCallId: null,
|
|
1897
|
+
toolDurationMs: null
|
|
1898
|
+
};
|
|
1899
|
+
parts.push(newPart);
|
|
1900
|
+
partIndex = parts.length - 1;
|
|
1901
|
+
} else {
|
|
1902
|
+
const existing = parts[partIndex];
|
|
1903
|
+
const previous = extractText(existing);
|
|
1904
|
+
const nextText = `${previous}${delta}`;
|
|
1905
|
+
parts[partIndex] = {
|
|
1906
|
+
...existing,
|
|
1907
|
+
content: JSON.stringify({ text: nextText }),
|
|
1908
|
+
contentJson: { text: nextText },
|
|
1909
|
+
stepIndex: stepIndex ?? existing.stepIndex ?? null,
|
|
1910
|
+
completedAt: null
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
nextMessages[messageIndex] = { ...targetMessage, parts };
|
|
1914
|
+
return nextMessages;
|
|
1915
|
+
});
|
|
1916
|
+
};
|
|
1917
|
+
const applyMessageDelta = (payload) => {
|
|
1918
|
+
const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
1919
|
+
const partId = typeof payload?.partId === "string" ? payload.partId : null;
|
|
1920
|
+
const delta = typeof payload?.delta === "string" ? payload.delta : null;
|
|
1921
|
+
if (!messageId || !partId || delta === null)
|
|
1922
|
+
return;
|
|
1923
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
1924
|
+
if (!oldMessages)
|
|
1925
|
+
return oldMessages;
|
|
1926
|
+
const nextMessages = [...oldMessages];
|
|
1927
|
+
const messageIndex = nextMessages.findIndex((message) => message.id === messageId);
|
|
1928
|
+
if (messageIndex === -1)
|
|
1929
|
+
return oldMessages;
|
|
1930
|
+
const targetMessage = nextMessages[messageIndex];
|
|
1931
|
+
const parts = targetMessage.parts ? [...targetMessage.parts] : [];
|
|
1932
|
+
let partIndex = parts.findIndex((part) => part.id === partId);
|
|
1933
|
+
const stepIndex = typeof payload?.stepIndex === "number" ? payload.stepIndex : null;
|
|
1934
|
+
if (partIndex === -1) {
|
|
1935
|
+
const newPart = {
|
|
1936
|
+
id: partId,
|
|
1937
|
+
messageId,
|
|
1938
|
+
index: getOptimisticPartIndex(parts, stepIndex),
|
|
1939
|
+
stepIndex,
|
|
1940
|
+
type: "text",
|
|
1941
|
+
content: JSON.stringify({ text: delta }),
|
|
1942
|
+
contentJson: { text: delta },
|
|
1943
|
+
agent: targetMessage.agent,
|
|
1944
|
+
provider: targetMessage.provider,
|
|
1945
|
+
model: targetMessage.model,
|
|
1946
|
+
startedAt: Date.now(),
|
|
1947
|
+
completedAt: null,
|
|
1948
|
+
toolName: null,
|
|
1949
|
+
toolCallId: null,
|
|
1950
|
+
toolDurationMs: null
|
|
1951
|
+
};
|
|
1952
|
+
parts.push(newPart);
|
|
1953
|
+
partIndex = parts.length - 1;
|
|
1954
|
+
} else {
|
|
1955
|
+
const existing = parts[partIndex];
|
|
1956
|
+
const previous = extractText(existing);
|
|
1957
|
+
const nextText = `${previous}${delta}`;
|
|
1958
|
+
parts[partIndex] = {
|
|
1959
|
+
...existing,
|
|
1960
|
+
content: JSON.stringify({ text: nextText }),
|
|
1961
|
+
contentJson: { text: nextText },
|
|
1962
|
+
stepIndex: stepIndex ?? existing.stepIndex ?? null,
|
|
1963
|
+
completedAt: null
|
|
1964
|
+
};
|
|
1965
|
+
}
|
|
1966
|
+
nextMessages[messageIndex] = { ...targetMessage, parts };
|
|
1967
|
+
return nextMessages;
|
|
1968
|
+
});
|
|
1969
|
+
};
|
|
1970
|
+
const upsertEphemeralToolCall = (payload) => {
|
|
1971
|
+
if (!payload)
|
|
1972
|
+
return;
|
|
1973
|
+
const callId = typeof payload.callId === "string" ? payload.callId : null;
|
|
1974
|
+
const name = typeof payload.name === "string" ? payload.name : null;
|
|
1975
|
+
if (!name)
|
|
1976
|
+
return;
|
|
1977
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
1978
|
+
if (!oldMessages)
|
|
1979
|
+
return oldMessages;
|
|
1980
|
+
const nextMessages = [...oldMessages];
|
|
1981
|
+
let targetIndex = resolveAssistantTargetIndex(nextMessages);
|
|
1982
|
+
if (typeof payload.messageId === "string") {
|
|
1983
|
+
const explicitIndex = nextMessages.findIndex((message) => message.id === payload.messageId);
|
|
1984
|
+
if (explicitIndex !== -1)
|
|
1985
|
+
targetIndex = explicitIndex;
|
|
1986
|
+
}
|
|
1987
|
+
if (targetIndex === -1)
|
|
1988
|
+
return oldMessages;
|
|
1989
|
+
const targetMessage = nextMessages[targetIndex];
|
|
1990
|
+
const parts = targetMessage.parts ? [...targetMessage.parts] : [];
|
|
1991
|
+
let partIndex = -1;
|
|
1992
|
+
if (callId) {
|
|
1993
|
+
partIndex = parts.findIndex((part) => part.toolCallId === callId && part.ephemeral);
|
|
1994
|
+
}
|
|
1995
|
+
if (partIndex === -1 && !callId) {
|
|
1996
|
+
partIndex = parts.findIndex((part) => part.ephemeral && part.toolName === name);
|
|
1997
|
+
}
|
|
1998
|
+
const args = payload.args;
|
|
1999
|
+
const stepIndex = typeof payload.stepIndex === "number" ? payload.stepIndex : null;
|
|
2000
|
+
const contentJsonBase = { name };
|
|
2001
|
+
if (callId)
|
|
2002
|
+
contentJsonBase.callId = callId;
|
|
2003
|
+
if (args !== undefined)
|
|
2004
|
+
contentJsonBase.args = args;
|
|
2005
|
+
if (partIndex === -1) {
|
|
2006
|
+
const newPart = {
|
|
2007
|
+
id: callId ? `ephemeral-tool-call-${callId}` : `ephemeral-tool-call-${name}-${Date.now()}`,
|
|
2008
|
+
messageId: targetMessage.id,
|
|
2009
|
+
index: getOptimisticPartIndex(parts, stepIndex),
|
|
2010
|
+
stepIndex,
|
|
2011
|
+
type: "tool_call",
|
|
2012
|
+
content: JSON.stringify(contentJsonBase),
|
|
2013
|
+
contentJson: contentJsonBase,
|
|
2014
|
+
agent: targetMessage.agent,
|
|
2015
|
+
provider: targetMessage.provider,
|
|
2016
|
+
model: targetMessage.model,
|
|
2017
|
+
startedAt: Date.now(),
|
|
2018
|
+
completedAt: null,
|
|
2019
|
+
toolName: name,
|
|
2020
|
+
toolCallId: callId,
|
|
2021
|
+
toolDurationMs: null,
|
|
2022
|
+
ephemeral: true
|
|
2023
|
+
};
|
|
2024
|
+
parts.push(newPart);
|
|
2025
|
+
} else {
|
|
2026
|
+
const existing = parts[partIndex];
|
|
2027
|
+
const nextContentJson = {
|
|
2028
|
+
...typeof existing.contentJson === "object" && !Array.isArray(existing.contentJson) ? existing.contentJson : {},
|
|
2029
|
+
name
|
|
2030
|
+
};
|
|
2031
|
+
if (callId)
|
|
2032
|
+
nextContentJson.callId = callId;
|
|
2033
|
+
if (args !== undefined)
|
|
2034
|
+
nextContentJson.args = args;
|
|
2035
|
+
parts[partIndex] = {
|
|
2036
|
+
...existing,
|
|
2037
|
+
content: JSON.stringify(nextContentJson),
|
|
2038
|
+
contentJson: nextContentJson,
|
|
2039
|
+
stepIndex: stepIndex ?? existing.stepIndex ?? null,
|
|
2040
|
+
toolCallId: callId ?? existing.toolCallId,
|
|
2041
|
+
toolName: name
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
nextMessages[targetIndex] = { ...targetMessage, parts };
|
|
2045
|
+
return nextMessages;
|
|
2046
|
+
});
|
|
2047
|
+
};
|
|
2048
|
+
const resolveEphemeralToolCall = (payload) => {
|
|
2049
|
+
const callId = typeof payload?.callId === "string" ? payload.callId : null;
|
|
2050
|
+
if (!callId)
|
|
2051
|
+
return;
|
|
2052
|
+
const payloadName = typeof payload?.name === "string" ? payload.name : null;
|
|
2053
|
+
const payloadStepIndex = typeof payload?.stepIndex === "number" ? payload.stepIndex : null;
|
|
2054
|
+
const payloadResult = payload?.result;
|
|
2055
|
+
const payloadArtifact = payload?.artifact;
|
|
2056
|
+
const payloadArgs = payload?.args;
|
|
2057
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2058
|
+
if (!oldMessages)
|
|
2059
|
+
return oldMessages;
|
|
2060
|
+
let changed = false;
|
|
2061
|
+
const now = Date.now();
|
|
2062
|
+
const nextMessages = oldMessages.map((message) => {
|
|
2063
|
+
if (!message.parts?.length)
|
|
2064
|
+
return message;
|
|
2065
|
+
let messageChanged = false;
|
|
2066
|
+
const updatedParts = message.parts.map((part) => {
|
|
2067
|
+
if (!(part.ephemeral && part.toolCallId === callId)) {
|
|
2068
|
+
return part;
|
|
2069
|
+
}
|
|
2070
|
+
messageChanged = true;
|
|
2071
|
+
changed = true;
|
|
2072
|
+
const nextContentJson = {
|
|
2073
|
+
...typeof part.contentJson === "object" && !Array.isArray(part.contentJson) ? part.contentJson : {},
|
|
2074
|
+
name: payloadName ?? part.toolName ?? "tool",
|
|
2075
|
+
callId
|
|
2076
|
+
};
|
|
2077
|
+
if (payloadArgs !== undefined)
|
|
2078
|
+
nextContentJson.args = payloadArgs;
|
|
2079
|
+
if (payloadResult !== undefined)
|
|
2080
|
+
nextContentJson.result = payloadResult;
|
|
2081
|
+
if (payloadArtifact !== undefined)
|
|
2082
|
+
nextContentJson.artifact = payloadArtifact;
|
|
2083
|
+
const durationMs = part.startedAt && Number.isFinite(part.startedAt) ? Math.max(0, now - part.startedAt) : part.toolDurationMs;
|
|
2084
|
+
const resolvedPart = {
|
|
2085
|
+
...part,
|
|
2086
|
+
type: "tool_result",
|
|
2087
|
+
content: JSON.stringify(nextContentJson),
|
|
2088
|
+
contentJson: nextContentJson,
|
|
2089
|
+
stepIndex: payloadStepIndex ?? part.stepIndex ?? null,
|
|
2090
|
+
completedAt: now,
|
|
2091
|
+
toolName: payloadName ?? part.toolName,
|
|
2092
|
+
toolDurationMs: durationMs ?? null
|
|
2093
|
+
};
|
|
2094
|
+
return resolvedPart;
|
|
2095
|
+
});
|
|
2096
|
+
if (!messageChanged)
|
|
2097
|
+
return message;
|
|
2098
|
+
return { ...message, parts: updatedParts };
|
|
2099
|
+
});
|
|
2100
|
+
return changed ? nextMessages : oldMessages;
|
|
2101
|
+
});
|
|
2102
|
+
};
|
|
2103
|
+
const removeEphemeralToolCall = (payload) => {
|
|
2104
|
+
const callId = typeof payload?.callId === "string" ? payload.callId : null;
|
|
2105
|
+
if (!callId)
|
|
2106
|
+
return;
|
|
2107
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2108
|
+
if (!oldMessages)
|
|
2109
|
+
return oldMessages;
|
|
2110
|
+
let changed = false;
|
|
2111
|
+
const nextMessages = oldMessages.map((message) => {
|
|
2112
|
+
if (!message.parts?.length)
|
|
2113
|
+
return message;
|
|
2114
|
+
const filtered = message.parts.filter((part) => !(part.ephemeral && part.toolCallId === callId));
|
|
2115
|
+
if (filtered.length === message.parts.length)
|
|
2116
|
+
return message;
|
|
2117
|
+
changed = true;
|
|
2118
|
+
return { ...message, parts: filtered };
|
|
2119
|
+
});
|
|
2120
|
+
return changed ? nextMessages : oldMessages;
|
|
2121
|
+
});
|
|
2122
|
+
};
|
|
2123
|
+
const clearEphemeralForMessage = (messageId) => {
|
|
2124
|
+
if (!messageId)
|
|
2125
|
+
return;
|
|
2126
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2127
|
+
if (!oldMessages)
|
|
2128
|
+
return oldMessages;
|
|
2129
|
+
const targetIndex = oldMessages.findIndex((message) => message.id === messageId);
|
|
2130
|
+
if (targetIndex === -1)
|
|
2131
|
+
return oldMessages;
|
|
2132
|
+
const target = oldMessages[targetIndex];
|
|
2133
|
+
if (!target.parts?.some((part) => part.ephemeral && part.type === "tool_call"))
|
|
2134
|
+
return oldMessages;
|
|
2135
|
+
const nextMessages = [...oldMessages];
|
|
2136
|
+
nextMessages[targetIndex] = {
|
|
2137
|
+
...target,
|
|
2138
|
+
parts: target.parts?.filter((part) => !(part.ephemeral && part.type === "tool_call")) ?? []
|
|
2139
|
+
};
|
|
2140
|
+
return nextMessages;
|
|
2141
|
+
});
|
|
2142
|
+
};
|
|
2143
|
+
const markMessageCompleted = (payload) => {
|
|
2144
|
+
const id = typeof payload?.id === "string" ? payload.id : null;
|
|
2145
|
+
if (!id)
|
|
2146
|
+
return;
|
|
2147
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2148
|
+
if (!oldMessages)
|
|
2149
|
+
return oldMessages;
|
|
2150
|
+
const nextMessages = [...oldMessages];
|
|
2151
|
+
const messageIndex = nextMessages.findIndex((message) => message.id === id);
|
|
2152
|
+
if (messageIndex === -1)
|
|
2153
|
+
return oldMessages;
|
|
2154
|
+
const existing = nextMessages[messageIndex];
|
|
2155
|
+
nextMessages[messageIndex] = {
|
|
2156
|
+
...existing,
|
|
2157
|
+
status: "complete",
|
|
2158
|
+
completedAt: Date.now()
|
|
2159
|
+
};
|
|
2160
|
+
return nextMessages;
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
const throttledInvalidate = () => {
|
|
2164
|
+
const now = Date.now();
|
|
2165
|
+
if (now - lastInvalidationRef.current < 500) {
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
lastInvalidationRef.current = now;
|
|
2169
|
+
queryClient.invalidateQueries({ queryKey: ["messages", sessionId] });
|
|
2170
|
+
};
|
|
2171
|
+
const invalidatingEvents = new Set([
|
|
2172
|
+
"message.completed",
|
|
2173
|
+
"message.updated",
|
|
2174
|
+
"finish-step",
|
|
2175
|
+
"error"
|
|
2176
|
+
]);
|
|
2177
|
+
const unsubscribe = client2.on("*", (event) => {
|
|
2178
|
+
const payload = event.payload;
|
|
2179
|
+
switch (event.type) {
|
|
2180
|
+
case "message.created": {
|
|
2181
|
+
const role = typeof payload?.role === "string" ? payload.role : null;
|
|
2182
|
+
const id = typeof payload?.id === "string" ? payload.id : null;
|
|
2183
|
+
if (role === "assistant" && id) {
|
|
2184
|
+
assistantMessageIdRef.current = id;
|
|
2185
|
+
}
|
|
2186
|
+
if (id && role) {
|
|
2187
|
+
const agent = typeof payload?.agent === "string" ? payload.agent : "";
|
|
2188
|
+
const provider = typeof payload?.provider === "string" ? payload.provider : "";
|
|
2189
|
+
const model = typeof payload?.model === "string" ? payload.model : "";
|
|
2190
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2191
|
+
if (!oldMessages)
|
|
2192
|
+
return oldMessages;
|
|
2193
|
+
if (oldMessages.some((m) => m.id === id))
|
|
2194
|
+
return oldMessages;
|
|
2195
|
+
const newMessage = {
|
|
2196
|
+
id,
|
|
2197
|
+
sessionId,
|
|
2198
|
+
role,
|
|
2199
|
+
status: "pending",
|
|
2200
|
+
agent,
|
|
2201
|
+
provider,
|
|
2202
|
+
model,
|
|
2203
|
+
createdAt: Date.now(),
|
|
2204
|
+
completedAt: null,
|
|
2205
|
+
latencyMs: null,
|
|
2206
|
+
promptTokens: null,
|
|
2207
|
+
completionTokens: null,
|
|
2208
|
+
totalTokens: null,
|
|
2209
|
+
error: null,
|
|
2210
|
+
parts: []
|
|
2211
|
+
};
|
|
2212
|
+
const next = [...oldMessages, newMessage];
|
|
2213
|
+
next.sort((a, b) => a.createdAt - b.createdAt);
|
|
2214
|
+
return next;
|
|
2215
|
+
});
|
|
2216
|
+
throttledInvalidate();
|
|
2217
|
+
}
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
case "message.part.delta": {
|
|
2221
|
+
applyMessageDelta(payload);
|
|
2222
|
+
break;
|
|
2223
|
+
}
|
|
2224
|
+
case "reasoning.delta": {
|
|
2225
|
+
applyReasoningDelta(payload);
|
|
2226
|
+
break;
|
|
2227
|
+
}
|
|
2228
|
+
case "message.completed": {
|
|
2229
|
+
const id = typeof payload?.id === "string" ? payload.id : null;
|
|
2230
|
+
if (id && assistantMessageIdRef.current === id) {
|
|
2231
|
+
assistantMessageIdRef.current = null;
|
|
2232
|
+
}
|
|
2233
|
+
markMessageCompleted(payload);
|
|
2234
|
+
clearEphemeralForMessage(id);
|
|
2235
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
2236
|
+
break;
|
|
2237
|
+
}
|
|
2238
|
+
case "tool.delta": {
|
|
2239
|
+
const channel = typeof payload?.channel === "string" ? payload.channel : null;
|
|
2240
|
+
if (channel === "input") {
|
|
2241
|
+
upsertEphemeralToolCall(payload);
|
|
2242
|
+
}
|
|
2243
|
+
break;
|
|
2244
|
+
}
|
|
2245
|
+
case "tool.call": {
|
|
2246
|
+
upsertEphemeralToolCall(payload);
|
|
2247
|
+
break;
|
|
2248
|
+
}
|
|
2249
|
+
case "tool.result": {
|
|
2250
|
+
resolveEphemeralToolCall(payload);
|
|
2251
|
+
break;
|
|
2252
|
+
}
|
|
2253
|
+
case "tool.approval.required": {
|
|
2254
|
+
const callId = typeof payload?.callId === "string" ? payload.callId : null;
|
|
2255
|
+
const toolName = typeof payload?.toolName === "string" ? payload.toolName : null;
|
|
2256
|
+
const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
2257
|
+
const args = payload?.args;
|
|
2258
|
+
if (callId && toolName && messageId) {
|
|
2259
|
+
addPendingApproval({
|
|
2260
|
+
callId,
|
|
2261
|
+
toolName,
|
|
2262
|
+
args,
|
|
2263
|
+
messageId,
|
|
2264
|
+
createdAt: Date.now()
|
|
2265
|
+
});
|
|
2266
|
+
}
|
|
2267
|
+
break;
|
|
2268
|
+
}
|
|
2269
|
+
case "tool.approval.resolved": {
|
|
2270
|
+
const callId = typeof payload?.callId === "string" ? payload.callId : null;
|
|
2271
|
+
if (callId) {
|
|
2272
|
+
removePendingApproval(callId);
|
|
2273
|
+
}
|
|
2274
|
+
break;
|
|
2275
|
+
}
|
|
2276
|
+
case "tool.approval.updated": {
|
|
2277
|
+
const callId = typeof payload?.callId === "string" ? payload.callId : null;
|
|
2278
|
+
const args = payload?.args;
|
|
2279
|
+
if (callId) {
|
|
2280
|
+
updatePendingApproval(callId, args);
|
|
2281
|
+
}
|
|
2282
|
+
break;
|
|
2283
|
+
}
|
|
2284
|
+
case "error": {
|
|
2285
|
+
removeEphemeralToolCall(payload);
|
|
2286
|
+
const messageId = typeof payload?.messageId === "string" ? payload.messageId : null;
|
|
2287
|
+
if (messageId) {
|
|
2288
|
+
clearEphemeralForMessage(messageId);
|
|
2289
|
+
}
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
case "message.updated": {
|
|
2293
|
+
const id = typeof payload?.id === "string" ? payload.id : null;
|
|
2294
|
+
const status = typeof payload?.status === "string" ? payload.status : null;
|
|
2295
|
+
if (id && status) {
|
|
2296
|
+
queryClient.setQueryData(["messages", sessionId], (oldMessages) => {
|
|
2297
|
+
if (!oldMessages)
|
|
2298
|
+
return oldMessages;
|
|
2299
|
+
const idx = oldMessages.findIndex((m) => m.id === id);
|
|
2300
|
+
if (idx === -1)
|
|
2301
|
+
return oldMessages;
|
|
2302
|
+
const next = [...oldMessages];
|
|
2303
|
+
next[idx] = {
|
|
2304
|
+
...next[idx],
|
|
2305
|
+
status
|
|
2306
|
+
};
|
|
2307
|
+
return next;
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
break;
|
|
2311
|
+
}
|
|
2312
|
+
case "queue.updated": {
|
|
2313
|
+
const queueState = {
|
|
2314
|
+
currentMessageId: payload?.currentMessageId,
|
|
2315
|
+
queuedMessages: payload?.queuedMessages ?? [],
|
|
2316
|
+
queueLength: payload?.queueLength ?? 0
|
|
2317
|
+
};
|
|
2318
|
+
queryClient.setQueryData(["queueState", sessionId], queueState);
|
|
2319
|
+
break;
|
|
2320
|
+
}
|
|
2321
|
+
default:
|
|
2322
|
+
break;
|
|
2323
|
+
}
|
|
2324
|
+
if (invalidatingEvents.has(event.type)) {
|
|
2325
|
+
throttledInvalidate();
|
|
2326
|
+
}
|
|
2327
|
+
if (event.type === "finish-step") {
|
|
2328
|
+
const now = Date.now();
|
|
2329
|
+
if (now - lastSessionInvalidation >= 2000) {
|
|
2330
|
+
lastSessionInvalidation = now;
|
|
2331
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
});
|
|
2335
|
+
return () => {
|
|
2336
|
+
unsubscribe();
|
|
2337
|
+
client2.disconnect();
|
|
2338
|
+
};
|
|
2339
|
+
}, [
|
|
2340
|
+
sessionId,
|
|
2341
|
+
queryClient,
|
|
2342
|
+
addPendingApproval,
|
|
2343
|
+
removePendingApproval,
|
|
2344
|
+
setPendingApprovals,
|
|
2345
|
+
updatePendingApproval
|
|
2346
|
+
]);
|
|
2347
|
+
}
|
|
2348
|
+
// src/hooks/useTheme.ts
|
|
2349
|
+
import { useEffect as useEffect2, useState, useCallback as useCallback2, useMemo as useMemo3 } from "react";
|
|
2350
|
+
var STORAGE_KEY2 = "otto-theme";
|
|
2351
|
+
function resolveInitialTheme() {
|
|
2352
|
+
if (typeof window === "undefined") {
|
|
2353
|
+
return "dark";
|
|
2354
|
+
}
|
|
2355
|
+
const stored = window.localStorage.getItem(STORAGE_KEY2);
|
|
2356
|
+
if (stored === "light" || stored === "dark") {
|
|
2357
|
+
return stored;
|
|
2358
|
+
}
|
|
2359
|
+
if (window.matchMedia?.("(prefers-color-scheme: light)").matches) {
|
|
2360
|
+
return "light";
|
|
2361
|
+
}
|
|
2362
|
+
return "dark";
|
|
2363
|
+
}
|
|
2364
|
+
function useTheme() {
|
|
2365
|
+
const [theme, setTheme] = useState(() => resolveInitialTheme());
|
|
2366
|
+
useEffect2(() => {
|
|
2367
|
+
if (typeof document === "undefined")
|
|
2368
|
+
return;
|
|
2369
|
+
const root = document.documentElement;
|
|
2370
|
+
if (theme === "dark") {
|
|
2371
|
+
root.classList.add("dark");
|
|
2372
|
+
} else {
|
|
2373
|
+
root.classList.remove("dark");
|
|
2374
|
+
}
|
|
2375
|
+
try {
|
|
2376
|
+
window.localStorage.setItem(STORAGE_KEY2, theme);
|
|
2377
|
+
} catch (error) {
|
|
2378
|
+
console.warn("Failed to persist theme preference", error);
|
|
2379
|
+
}
|
|
2380
|
+
if (window.parent && window.parent !== window) {
|
|
2381
|
+
window.parent.postMessage({ type: "otto-set-theme", theme }, "*");
|
|
2382
|
+
}
|
|
2383
|
+
}, [theme]);
|
|
2384
|
+
useEffect2(() => {
|
|
2385
|
+
if (typeof window === "undefined")
|
|
2386
|
+
return;
|
|
2387
|
+
const handler = (e) => {
|
|
2388
|
+
if (e.data?.type === "otto-set-theme" && (e.data.theme === "light" || e.data.theme === "dark")) {
|
|
2389
|
+
setTheme(e.data.theme);
|
|
2390
|
+
}
|
|
2391
|
+
};
|
|
2392
|
+
window.addEventListener("message", handler);
|
|
2393
|
+
return () => window.removeEventListener("message", handler);
|
|
2394
|
+
}, []);
|
|
2395
|
+
const toggleTheme = useCallback2(() => {
|
|
2396
|
+
setTheme((prev) => prev === "dark" ? "light" : "dark");
|
|
2397
|
+
}, []);
|
|
2398
|
+
return useMemo3(() => ({ theme, setTheme, toggleTheme }), [theme, toggleTheme]);
|
|
2399
|
+
}
|
|
2400
|
+
// src/hooks/useWorkingDirectory.ts
|
|
2401
|
+
import { useEffect as useEffect3, useState as useState2 } from "react";
|
|
2402
|
+
function useWorkingDirectory() {
|
|
2403
|
+
const [dirName, setDirName] = useState2(null);
|
|
2404
|
+
useEffect3(() => {
|
|
2405
|
+
const fetchWorkingDirectory = async () => {
|
|
2406
|
+
try {
|
|
2407
|
+
const win = window;
|
|
2408
|
+
const baseUrl = win.OTTO_SERVER_URL || API_BASE_URL;
|
|
2409
|
+
const url = `${baseUrl}/v1/config/cwd`;
|
|
2410
|
+
console.log("[useWorkingDirectory] Fetching from:", url);
|
|
2411
|
+
const response = await fetch(url);
|
|
2412
|
+
if (!response.ok) {
|
|
2413
|
+
console.error("[useWorkingDirectory] Failed:", response.status, response.statusText);
|
|
2414
|
+
throw new Error(`Failed to fetch working directory: ${response.status}`);
|
|
2415
|
+
}
|
|
2416
|
+
const data = await response.json();
|
|
2417
|
+
console.log("[useWorkingDirectory] Success:", data);
|
|
2418
|
+
if (data.dirName) {
|
|
2419
|
+
console.log("[useWorkingDirectory] Setting title to:", data.dirName);
|
|
2420
|
+
setDirName(data.dirName);
|
|
2421
|
+
document.title = data.dirName;
|
|
2422
|
+
}
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
console.error("[useWorkingDirectory] Error:", error);
|
|
2425
|
+
document.title = "otto";
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
fetchWorkingDirectory();
|
|
2429
|
+
}, []);
|
|
2430
|
+
return dirName;
|
|
2431
|
+
}
|
|
2432
|
+
// src/hooks/useKeyboardShortcuts.ts
|
|
2433
|
+
import { useEffect as useEffect4, useCallback as useCallback3 } from "react";
|
|
2434
|
+
|
|
2435
|
+
// src/stores/focusStore.ts
|
|
2436
|
+
import { create as create10 } from "zustand";
|
|
2437
|
+
var useFocusStore = create10((set) => ({
|
|
2438
|
+
currentFocus: null,
|
|
2439
|
+
sessionIndex: 0,
|
|
2440
|
+
gitFileIndex: 0,
|
|
2441
|
+
setFocus: (area) => set({ currentFocus: area }),
|
|
2442
|
+
setSessionIndex: (index) => set({ sessionIndex: index }),
|
|
2443
|
+
setGitFileIndex: (index) => set({ gitFileIndex: index }),
|
|
2444
|
+
resetGitFileIndex: () => set({ gitFileIndex: 0 }),
|
|
2445
|
+
resetSessionIndex: () => set({ sessionIndex: 0 })
|
|
2446
|
+
}));
|
|
2447
|
+
|
|
2448
|
+
// src/stores/sidebarStore.ts
|
|
2449
|
+
import { create as create11 } from "zustand";
|
|
2450
|
+
import { persist } from "zustand/middleware";
|
|
2451
|
+
var useSidebarStore = create11()(persist((set) => ({
|
|
2452
|
+
isCollapsed: false,
|
|
2453
|
+
toggleCollapse: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
|
|
2454
|
+
setCollapsed: (collapsed) => set({ isCollapsed: collapsed })
|
|
2455
|
+
}), {
|
|
2456
|
+
name: "sidebar-storage"
|
|
2457
|
+
}));
|
|
2458
|
+
|
|
2459
|
+
// src/stores/filePickerStore.ts
|
|
2460
|
+
import { create as create12 } from "zustand";
|
|
2461
|
+
var useFilePickerStore = create12((set) => ({
|
|
2462
|
+
isOpen: false,
|
|
2463
|
+
open: () => set({ isOpen: true }),
|
|
2464
|
+
close: () => set({ isOpen: false }),
|
|
2465
|
+
toggle: () => set((state) => ({ isOpen: !state.isOpen }))
|
|
2466
|
+
}));
|
|
2467
|
+
|
|
2468
|
+
// src/hooks/useKeyboardShortcuts.ts
|
|
2469
|
+
function useKeyboardShortcuts({
|
|
2470
|
+
sessionIds,
|
|
2471
|
+
activeSessionId,
|
|
2472
|
+
gitFiles,
|
|
2473
|
+
onSelectSession,
|
|
2474
|
+
onNewSession,
|
|
2475
|
+
onStageFile,
|
|
2476
|
+
onUnstageFile,
|
|
2477
|
+
onRestoreFile,
|
|
2478
|
+
onDeleteFile,
|
|
2479
|
+
onStageAll,
|
|
2480
|
+
onUnstageAll,
|
|
2481
|
+
onOpenCommitModal,
|
|
2482
|
+
onViewDiff,
|
|
2483
|
+
onReturnToInput
|
|
2484
|
+
}) {
|
|
2485
|
+
const {
|
|
2486
|
+
currentFocus,
|
|
2487
|
+
sessionIndex,
|
|
2488
|
+
gitFileIndex,
|
|
2489
|
+
setFocus,
|
|
2490
|
+
setSessionIndex,
|
|
2491
|
+
setGitFileIndex,
|
|
2492
|
+
resetGitFileIndex
|
|
2493
|
+
} = useFocusStore();
|
|
2494
|
+
const {
|
|
2495
|
+
setCollapsed: setSessionListCollapsed,
|
|
2496
|
+
toggleCollapse: toggleSessionList
|
|
2497
|
+
} = useSidebarStore();
|
|
2498
|
+
const { isExpanded: isGitExpanded, toggleSidebar: toggleGit } = useGitStore();
|
|
2499
|
+
const closeDiff = useGitStore((state) => state.closeDiff);
|
|
2500
|
+
const toggleResearch = useResearchStore((state) => state.toggleSidebar);
|
|
2501
|
+
const currentSessionIndex = sessionIds.indexOf(activeSessionId || "");
|
|
2502
|
+
const handleKeyDown = useCallback3((e) => {
|
|
2503
|
+
const target = e.target;
|
|
2504
|
+
const isInInput = target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable;
|
|
2505
|
+
const isInTerminal = !!target.closest("[data-terminal-viewer]");
|
|
2506
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "h") {
|
|
2507
|
+
e.preventDefault();
|
|
2508
|
+
if (currentFocus === "sessions") {
|
|
2509
|
+
document.activeElement?.blur();
|
|
2510
|
+
setFocus("input");
|
|
2511
|
+
setSessionListCollapsed(true);
|
|
2512
|
+
setTimeout(() => onReturnToInput?.(), 50);
|
|
2513
|
+
} else if (currentFocus === "git") {
|
|
2514
|
+
document.activeElement?.blur();
|
|
2515
|
+
setFocus("input");
|
|
2516
|
+
toggleGit();
|
|
2517
|
+
closeDiff();
|
|
2518
|
+
setTimeout(() => onReturnToInput?.(), 50);
|
|
2519
|
+
} else {
|
|
2520
|
+
document.activeElement?.blur();
|
|
2521
|
+
setFocus("sessions");
|
|
2522
|
+
setSessionListCollapsed(false);
|
|
2523
|
+
if (currentSessionIndex >= 0) {
|
|
2524
|
+
setSessionIndex(currentSessionIndex);
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "l") {
|
|
2530
|
+
e.preventDefault();
|
|
2531
|
+
if (currentFocus === "git") {
|
|
2532
|
+
document.activeElement?.blur();
|
|
2533
|
+
setFocus("input");
|
|
2534
|
+
toggleGit();
|
|
2535
|
+
closeDiff();
|
|
2536
|
+
setTimeout(() => onReturnToInput?.(), 50);
|
|
2537
|
+
} else if (currentFocus === "sessions") {
|
|
2538
|
+
document.activeElement?.blur();
|
|
2539
|
+
setFocus("input");
|
|
2540
|
+
setSessionListCollapsed(true);
|
|
2541
|
+
setTimeout(() => onReturnToInput?.(), 50);
|
|
2542
|
+
} else {
|
|
2543
|
+
document.activeElement?.blur();
|
|
2544
|
+
if (!isGitExpanded) {
|
|
2545
|
+
toggleGit();
|
|
2546
|
+
}
|
|
2547
|
+
setFocus("git");
|
|
2548
|
+
resetGitFileIndex();
|
|
2549
|
+
}
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "/") {
|
|
2553
|
+
e.preventDefault();
|
|
2554
|
+
toggleSessionList();
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "\\") {
|
|
2558
|
+
e.preventDefault();
|
|
2559
|
+
toggleGit();
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "r") {
|
|
2563
|
+
e.preventDefault();
|
|
2564
|
+
toggleResearch();
|
|
2565
|
+
return;
|
|
2566
|
+
}
|
|
2567
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "n") {
|
|
2568
|
+
e.preventDefault();
|
|
2569
|
+
onNewSession();
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "p") {
|
|
2573
|
+
e.preventDefault();
|
|
2574
|
+
useFilePickerStore.getState().toggle();
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
if (e.key === "Escape" && !isInTerminal || e.key === "q" && !isInInput && (currentFocus === "sessions" || currentFocus === "git")) {
|
|
2578
|
+
e.preventDefault();
|
|
2579
|
+
if (currentFocus === "sessions") {
|
|
2580
|
+
setSessionListCollapsed(true);
|
|
2581
|
+
} else if (currentFocus === "git") {
|
|
2582
|
+
toggleGit();
|
|
2583
|
+
closeDiff();
|
|
2584
|
+
}
|
|
2585
|
+
setFocus("input");
|
|
2586
|
+
onReturnToInput?.();
|
|
2587
|
+
return;
|
|
2588
|
+
}
|
|
2589
|
+
if (currentFocus === "sessions" && !isInInput) {
|
|
2590
|
+
if (e.key === "j" && sessionIds.length > 0) {
|
|
2591
|
+
e.preventDefault();
|
|
2592
|
+
const nextIndex = Math.min(sessionIndex + 1, sessionIds.length - 1);
|
|
2593
|
+
setSessionIndex(nextIndex);
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
if (e.key === "k" && sessionIds.length > 0) {
|
|
2597
|
+
e.preventDefault();
|
|
2598
|
+
const prevIndex = Math.max(sessionIndex - 1, 0);
|
|
2599
|
+
setSessionIndex(prevIndex);
|
|
2600
|
+
return;
|
|
2601
|
+
}
|
|
2602
|
+
if (e.key === "Enter" && sessionIds[sessionIndex]) {
|
|
2603
|
+
e.preventDefault();
|
|
2604
|
+
onSelectSession(sessionIds[sessionIndex]);
|
|
2605
|
+
setFocus("input");
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
}
|
|
2609
|
+
if (currentFocus === "git" && !isInInput) {
|
|
2610
|
+
if (e.key === "j" && gitFiles.length > 0) {
|
|
2611
|
+
e.preventDefault();
|
|
2612
|
+
const nextIndex = Math.min(gitFileIndex + 1, gitFiles.length - 1);
|
|
2613
|
+
setGitFileIndex(nextIndex);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
if (e.key === "k" && gitFiles.length > 0) {
|
|
2617
|
+
e.preventDefault();
|
|
2618
|
+
const prevIndex = Math.max(gitFileIndex - 1, 0);
|
|
2619
|
+
setGitFileIndex(prevIndex);
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
if (e.key === " " && gitFiles[gitFileIndex]) {
|
|
2623
|
+
e.preventDefault();
|
|
2624
|
+
const file = gitFiles[gitFileIndex];
|
|
2625
|
+
if (file.staged) {
|
|
2626
|
+
onUnstageFile?.(file.path);
|
|
2627
|
+
} else {
|
|
2628
|
+
onStageFile?.(file.path);
|
|
2629
|
+
}
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
if (e.key === "a") {
|
|
2633
|
+
e.preventDefault();
|
|
2634
|
+
onStageAll?.();
|
|
2635
|
+
return;
|
|
2636
|
+
}
|
|
2637
|
+
if (e.key === "u") {
|
|
2638
|
+
e.preventDefault();
|
|
2639
|
+
onUnstageAll?.();
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
if (e.key === "R" && gitFiles[gitFileIndex]) {
|
|
2643
|
+
e.preventDefault();
|
|
2644
|
+
const file = gitFiles[gitFileIndex];
|
|
2645
|
+
const canRestore = !file.staged && file.status !== "untracked" && file.status !== "added";
|
|
2646
|
+
if (canRestore) {
|
|
2647
|
+
onRestoreFile?.(file.path);
|
|
2648
|
+
}
|
|
2649
|
+
return;
|
|
2650
|
+
}
|
|
2651
|
+
if (e.shiftKey && e.key === "D" || e.key === "Backspace") {
|
|
2652
|
+
e.preventDefault();
|
|
2653
|
+
const file = gitFiles[gitFileIndex];
|
|
2654
|
+
if (file && !file.staged && file.status === "untracked") {
|
|
2655
|
+
onDeleteFile?.(file.path);
|
|
2656
|
+
}
|
|
2657
|
+
return;
|
|
2658
|
+
}
|
|
2659
|
+
if (e.key === "c") {
|
|
2660
|
+
e.preventDefault();
|
|
2661
|
+
onOpenCommitModal?.();
|
|
2662
|
+
return;
|
|
2663
|
+
}
|
|
2664
|
+
if (e.key === "Enter" && gitFiles[gitFileIndex]) {
|
|
2665
|
+
e.preventDefault();
|
|
2666
|
+
const file = gitFiles[gitFileIndex];
|
|
2667
|
+
onViewDiff?.(file.path, file.staged);
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}, [
|
|
2672
|
+
currentFocus,
|
|
2673
|
+
sessionIndex,
|
|
2674
|
+
gitFileIndex,
|
|
2675
|
+
sessionIds,
|
|
2676
|
+
gitFiles,
|
|
2677
|
+
currentSessionIndex,
|
|
2678
|
+
isGitExpanded,
|
|
2679
|
+
setFocus,
|
|
2680
|
+
setSessionIndex,
|
|
2681
|
+
setGitFileIndex,
|
|
2682
|
+
resetGitFileIndex,
|
|
2683
|
+
setSessionListCollapsed,
|
|
2684
|
+
toggleGit,
|
|
2685
|
+
toggleResearch,
|
|
2686
|
+
toggleSessionList,
|
|
2687
|
+
onSelectSession,
|
|
2688
|
+
onNewSession,
|
|
2689
|
+
onStageFile,
|
|
2690
|
+
onUnstageFile,
|
|
2691
|
+
onRestoreFile,
|
|
2692
|
+
onDeleteFile,
|
|
2693
|
+
onStageAll,
|
|
2694
|
+
onUnstageAll,
|
|
2695
|
+
onOpenCommitModal,
|
|
2696
|
+
onViewDiff,
|
|
2697
|
+
onReturnToInput,
|
|
2698
|
+
closeDiff
|
|
2699
|
+
]);
|
|
2700
|
+
useEffect4(() => {
|
|
2701
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
2702
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
2703
|
+
}, [handleKeyDown]);
|
|
2704
|
+
return {
|
|
2705
|
+
currentFocus,
|
|
2706
|
+
sessionIndex,
|
|
2707
|
+
gitFileIndex
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
// src/hooks/useImageUpload.ts
|
|
2711
|
+
import {
|
|
2712
|
+
useState as useState3,
|
|
2713
|
+
useCallback as useCallback4,
|
|
2714
|
+
useEffect as useEffect5
|
|
2715
|
+
} from "react";
|
|
2716
|
+
var SUPPORTED_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
2717
|
+
function generateId() {
|
|
2718
|
+
return `img-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2719
|
+
}
|
|
2720
|
+
async function fileToBase64(file) {
|
|
2721
|
+
return new Promise((resolve, reject) => {
|
|
2722
|
+
const reader = new FileReader;
|
|
2723
|
+
reader.onload = () => {
|
|
2724
|
+
const result = reader.result;
|
|
2725
|
+
const base64 = result.split(",")[1];
|
|
2726
|
+
resolve(base64);
|
|
2727
|
+
};
|
|
2728
|
+
reader.onerror = reject;
|
|
2729
|
+
reader.readAsDataURL(file);
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
async function fileToPreview(file) {
|
|
2733
|
+
return new Promise((resolve, reject) => {
|
|
2734
|
+
const reader = new FileReader;
|
|
2735
|
+
reader.onload = () => resolve(reader.result);
|
|
2736
|
+
reader.onerror = reject;
|
|
2737
|
+
reader.readAsDataURL(file);
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
function useImageUpload(options = {}) {
|
|
2741
|
+
const { maxImages = 5, maxSizeMB = 5, pageWide = true } = options;
|
|
2742
|
+
const [images, setImages] = useState3([]);
|
|
2743
|
+
const [isDragging, setIsDragging] = useState3(false);
|
|
2744
|
+
const [error, setError] = useState3(null);
|
|
2745
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
2746
|
+
const validateFile = useCallback4((file) => {
|
|
2747
|
+
if (!SUPPORTED_TYPES.includes(file.type)) {
|
|
2748
|
+
return `Unsupported file type: ${file.type}. Supported: PNG, JPEG, GIF, WebP`;
|
|
2749
|
+
}
|
|
2750
|
+
if (file.size > maxSizeBytes) {
|
|
2751
|
+
return `File too large: ${(file.size / 1024 / 1024).toFixed(1)}MB. Max: ${maxSizeMB}MB`;
|
|
2752
|
+
}
|
|
2753
|
+
return null;
|
|
2754
|
+
}, [maxSizeBytes, maxSizeMB]);
|
|
2755
|
+
const addImages = useCallback4(async (files) => {
|
|
2756
|
+
setError(null);
|
|
2757
|
+
const fileArray = Array.from(files);
|
|
2758
|
+
const remaining = maxImages - images.length;
|
|
2759
|
+
if (remaining <= 0) {
|
|
2760
|
+
setError(`Maximum ${maxImages} images allowed`);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
const filesToAdd = fileArray.slice(0, remaining);
|
|
2764
|
+
const newImages = [];
|
|
2765
|
+
for (const file of filesToAdd) {
|
|
2766
|
+
const validationError = validateFile(file);
|
|
2767
|
+
if (validationError) {
|
|
2768
|
+
setError(validationError);
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
try {
|
|
2772
|
+
const [preview, data] = await Promise.all([
|
|
2773
|
+
fileToPreview(file),
|
|
2774
|
+
fileToBase64(file)
|
|
2775
|
+
]);
|
|
2776
|
+
newImages.push({
|
|
2777
|
+
id: generateId(),
|
|
2778
|
+
file,
|
|
2779
|
+
preview,
|
|
2780
|
+
data,
|
|
2781
|
+
mediaType: file.type
|
|
2782
|
+
});
|
|
2783
|
+
} catch {
|
|
2784
|
+
setError("Failed to process image");
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
if (newImages.length > 0) {
|
|
2788
|
+
setImages((prev) => [...prev, ...newImages]);
|
|
2789
|
+
}
|
|
2790
|
+
}, [images.length, maxImages, validateFile]);
|
|
2791
|
+
const removeImage = useCallback4((id) => {
|
|
2792
|
+
setImages((prev) => prev.filter((img) => img.id !== id));
|
|
2793
|
+
setError(null);
|
|
2794
|
+
}, []);
|
|
2795
|
+
const clearImages = useCallback4(() => {
|
|
2796
|
+
setImages([]);
|
|
2797
|
+
setError(null);
|
|
2798
|
+
}, []);
|
|
2799
|
+
const handleDragEnter = useCallback4((e) => {
|
|
2800
|
+
e.preventDefault();
|
|
2801
|
+
e.stopPropagation();
|
|
2802
|
+
if (e.dataTransfer.types.includes("Files")) {
|
|
2803
|
+
setIsDragging(true);
|
|
2804
|
+
}
|
|
2805
|
+
}, []);
|
|
2806
|
+
const handleDragLeave = useCallback4((e) => {
|
|
2807
|
+
e.preventDefault();
|
|
2808
|
+
e.stopPropagation();
|
|
2809
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
2810
|
+
const x = e.clientX;
|
|
2811
|
+
const y = e.clientY;
|
|
2812
|
+
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
|
|
2813
|
+
setIsDragging(false);
|
|
2814
|
+
}
|
|
2815
|
+
}, []);
|
|
2816
|
+
const handleDragOver = useCallback4((e) => {
|
|
2817
|
+
e.preventDefault();
|
|
2818
|
+
e.stopPropagation();
|
|
2819
|
+
}, []);
|
|
2820
|
+
const handleDrop = useCallback4((e) => {
|
|
2821
|
+
e.preventDefault();
|
|
2822
|
+
e.stopPropagation();
|
|
2823
|
+
setIsDragging(false);
|
|
2824
|
+
const files = e.dataTransfer.files;
|
|
2825
|
+
if (files.length > 0) {
|
|
2826
|
+
const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
2827
|
+
if (imageFiles.length > 0) {
|
|
2828
|
+
addImages(imageFiles);
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
}, [addImages]);
|
|
2832
|
+
const handlePaste = useCallback4((e) => {
|
|
2833
|
+
const items = e.clipboardData?.items;
|
|
2834
|
+
if (!items)
|
|
2835
|
+
return;
|
|
2836
|
+
const imageFiles = [];
|
|
2837
|
+
for (const item of Array.from(items)) {
|
|
2838
|
+
if (item.type.startsWith("image/")) {
|
|
2839
|
+
const file = item.getAsFile();
|
|
2840
|
+
if (file) {
|
|
2841
|
+
imageFiles.push(file);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
if (imageFiles.length > 0) {
|
|
2846
|
+
e.preventDefault();
|
|
2847
|
+
addImages(imageFiles);
|
|
2848
|
+
}
|
|
2849
|
+
}, [addImages]);
|
|
2850
|
+
useEffect5(() => {
|
|
2851
|
+
if (!pageWide)
|
|
2852
|
+
return;
|
|
2853
|
+
let dragCounter = 0;
|
|
2854
|
+
const onDragEnter = (e) => {
|
|
2855
|
+
e.preventDefault();
|
|
2856
|
+
if (e.dataTransfer?.types.includes("Files")) {
|
|
2857
|
+
dragCounter++;
|
|
2858
|
+
if (dragCounter === 1) {
|
|
2859
|
+
setIsDragging(true);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
};
|
|
2863
|
+
const onDragLeave = (e) => {
|
|
2864
|
+
e.preventDefault();
|
|
2865
|
+
dragCounter--;
|
|
2866
|
+
if (dragCounter === 0) {
|
|
2867
|
+
setIsDragging(false);
|
|
2868
|
+
}
|
|
2869
|
+
};
|
|
2870
|
+
const onDragOver = (e) => {
|
|
2871
|
+
e.preventDefault();
|
|
2872
|
+
};
|
|
2873
|
+
const onDrop = (e) => {
|
|
2874
|
+
e.preventDefault();
|
|
2875
|
+
dragCounter = 0;
|
|
2876
|
+
setIsDragging(false);
|
|
2877
|
+
const files = e.dataTransfer?.files;
|
|
2878
|
+
if (files && files.length > 0) {
|
|
2879
|
+
const imageFiles = Array.from(files).filter((f) => f.type.startsWith("image/"));
|
|
2880
|
+
if (imageFiles.length > 0) {
|
|
2881
|
+
addImages(imageFiles);
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
document.addEventListener("dragenter", onDragEnter);
|
|
2886
|
+
document.addEventListener("dragleave", onDragLeave);
|
|
2887
|
+
document.addEventListener("dragover", onDragOver);
|
|
2888
|
+
document.addEventListener("drop", onDrop);
|
|
2889
|
+
return () => {
|
|
2890
|
+
document.removeEventListener("dragenter", onDragEnter);
|
|
2891
|
+
document.removeEventListener("dragleave", onDragLeave);
|
|
2892
|
+
document.removeEventListener("dragover", onDragOver);
|
|
2893
|
+
document.removeEventListener("drop", onDrop);
|
|
2894
|
+
};
|
|
2895
|
+
}, [pageWide, addImages]);
|
|
2896
|
+
return {
|
|
2897
|
+
images,
|
|
2898
|
+
isDragging,
|
|
2899
|
+
error,
|
|
2900
|
+
addImages,
|
|
2901
|
+
removeImage,
|
|
2902
|
+
clearImages,
|
|
2903
|
+
handleDragEnter,
|
|
2904
|
+
handleDragLeave,
|
|
2905
|
+
handleDragOver,
|
|
2906
|
+
handleDrop,
|
|
2907
|
+
handlePaste
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
// src/hooks/useFileUpload.ts
|
|
2911
|
+
import {
|
|
2912
|
+
useState as useState4,
|
|
2913
|
+
useCallback as useCallback5,
|
|
2914
|
+
useEffect as useEffect6
|
|
2915
|
+
} from "react";
|
|
2916
|
+
var IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
2917
|
+
var PDF_TYPES = ["application/pdf"];
|
|
2918
|
+
var TEXT_TYPES = [
|
|
2919
|
+
"text/plain",
|
|
2920
|
+
"text/markdown",
|
|
2921
|
+
"text/x-markdown",
|
|
2922
|
+
"application/json",
|
|
2923
|
+
"text/csv",
|
|
2924
|
+
"text/xml",
|
|
2925
|
+
"application/xml",
|
|
2926
|
+
"text/yaml",
|
|
2927
|
+
"text/x-yaml",
|
|
2928
|
+
"application/x-yaml",
|
|
2929
|
+
"text/html",
|
|
2930
|
+
"text/css",
|
|
2931
|
+
"text/javascript",
|
|
2932
|
+
"application/javascript",
|
|
2933
|
+
"application/typescript"
|
|
2934
|
+
];
|
|
2935
|
+
var TEXT_EXTENSIONS = [
|
|
2936
|
+
".txt",
|
|
2937
|
+
".md",
|
|
2938
|
+
".markdown",
|
|
2939
|
+
".json",
|
|
2940
|
+
".csv",
|
|
2941
|
+
".xml",
|
|
2942
|
+
".yaml",
|
|
2943
|
+
".yml",
|
|
2944
|
+
".html",
|
|
2945
|
+
".css",
|
|
2946
|
+
".js",
|
|
2947
|
+
".ts",
|
|
2948
|
+
".jsx",
|
|
2949
|
+
".tsx",
|
|
2950
|
+
".py",
|
|
2951
|
+
".rs",
|
|
2952
|
+
".go",
|
|
2953
|
+
".java",
|
|
2954
|
+
".c",
|
|
2955
|
+
".cpp",
|
|
2956
|
+
".h",
|
|
2957
|
+
".hpp",
|
|
2958
|
+
".rb",
|
|
2959
|
+
".php",
|
|
2960
|
+
".sh",
|
|
2961
|
+
".bash",
|
|
2962
|
+
".zsh",
|
|
2963
|
+
".toml",
|
|
2964
|
+
".ini",
|
|
2965
|
+
".cfg",
|
|
2966
|
+
".env",
|
|
2967
|
+
".log",
|
|
2968
|
+
".sql",
|
|
2969
|
+
".graphql",
|
|
2970
|
+
".svelte",
|
|
2971
|
+
".vue"
|
|
2972
|
+
];
|
|
2973
|
+
var SUPPORTED_TYPES2 = [...IMAGE_TYPES, ...PDF_TYPES, ...TEXT_TYPES];
|
|
2974
|
+
function generateId2() {
|
|
2975
|
+
return `file-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
2976
|
+
}
|
|
2977
|
+
function getFileType(file) {
|
|
2978
|
+
if (IMAGE_TYPES.includes(file.type))
|
|
2979
|
+
return "image";
|
|
2980
|
+
if (PDF_TYPES.includes(file.type))
|
|
2981
|
+
return "pdf";
|
|
2982
|
+
if (TEXT_TYPES.includes(file.type))
|
|
2983
|
+
return "text";
|
|
2984
|
+
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf("."));
|
|
2985
|
+
if (TEXT_EXTENSIONS.includes(ext))
|
|
2986
|
+
return "text";
|
|
2987
|
+
return null;
|
|
2988
|
+
}
|
|
2989
|
+
function isSupported(file) {
|
|
2990
|
+
if (SUPPORTED_TYPES2.includes(file.type))
|
|
2991
|
+
return true;
|
|
2992
|
+
const ext = file.name.toLowerCase().slice(file.name.lastIndexOf("."));
|
|
2993
|
+
return TEXT_EXTENSIONS.includes(ext);
|
|
2994
|
+
}
|
|
2995
|
+
async function fileToBase642(file) {
|
|
2996
|
+
return new Promise((resolve, reject) => {
|
|
2997
|
+
const reader = new FileReader;
|
|
2998
|
+
reader.onload = () => {
|
|
2999
|
+
const result = reader.result;
|
|
3000
|
+
const base64 = result.split(",")[1];
|
|
3001
|
+
resolve(base64);
|
|
3002
|
+
};
|
|
3003
|
+
reader.onerror = reject;
|
|
3004
|
+
reader.readAsDataURL(file);
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
async function fileToText(file) {
|
|
3008
|
+
return new Promise((resolve, reject) => {
|
|
3009
|
+
const reader = new FileReader;
|
|
3010
|
+
reader.onload = () => resolve(reader.result);
|
|
3011
|
+
reader.onerror = reject;
|
|
3012
|
+
reader.readAsText(file);
|
|
3013
|
+
});
|
|
3014
|
+
}
|
|
3015
|
+
async function fileToPreview2(file) {
|
|
3016
|
+
return new Promise((resolve, reject) => {
|
|
3017
|
+
const reader = new FileReader;
|
|
3018
|
+
reader.onload = () => resolve(reader.result);
|
|
3019
|
+
reader.onerror = reject;
|
|
3020
|
+
reader.readAsDataURL(file);
|
|
3021
|
+
});
|
|
3022
|
+
}
|
|
3023
|
+
function useFileUpload(options = {}) {
|
|
3024
|
+
const {
|
|
3025
|
+
maxFiles = 10,
|
|
3026
|
+
maxSizeMB = 10,
|
|
3027
|
+
pageWide = true,
|
|
3028
|
+
supportsImages = true,
|
|
3029
|
+
supportsFileAttachments = true,
|
|
3030
|
+
onError
|
|
3031
|
+
} = options;
|
|
3032
|
+
const [files, setFiles] = useState4([]);
|
|
3033
|
+
const [isDragging, setIsDragging] = useState4(false);
|
|
3034
|
+
const [error, setError] = useState4(null);
|
|
3035
|
+
const maxSizeBytes = maxSizeMB * 1024 * 1024;
|
|
3036
|
+
const validateFile = useCallback5((file) => {
|
|
3037
|
+
if (!supportsImages && IMAGE_TYPES.includes(file.type)) {
|
|
3038
|
+
return "This model does not support image attachments";
|
|
3039
|
+
}
|
|
3040
|
+
if (!supportsFileAttachments && PDF_TYPES.includes(file.type)) {
|
|
3041
|
+
return "This model does not support PDF attachments";
|
|
3042
|
+
}
|
|
3043
|
+
if (!isSupported(file)) {
|
|
3044
|
+
const ext = file.name.slice(file.name.lastIndexOf("."));
|
|
3045
|
+
return `Unsupported file type: ${ext || file.type || "unknown"}`;
|
|
3046
|
+
}
|
|
3047
|
+
if (file.size > maxSizeBytes) {
|
|
3048
|
+
return `File too large: ${(file.size / 1024 / 1024).toFixed(1)}MB. Max: ${maxSizeMB}MB`;
|
|
3049
|
+
}
|
|
3050
|
+
return null;
|
|
3051
|
+
}, [maxSizeBytes, maxSizeMB, supportsImages, supportsFileAttachments]);
|
|
3052
|
+
const addFiles = useCallback5(async (inputFiles) => {
|
|
3053
|
+
setError(null);
|
|
3054
|
+
const fileArray = Array.from(inputFiles);
|
|
3055
|
+
const remaining = maxFiles - files.length;
|
|
3056
|
+
if (remaining <= 0) {
|
|
3057
|
+
const msg = `Maximum ${maxFiles} files allowed`;
|
|
3058
|
+
setError(msg);
|
|
3059
|
+
onError?.(msg);
|
|
3060
|
+
return;
|
|
3061
|
+
}
|
|
3062
|
+
const filesToAdd = fileArray.slice(0, remaining);
|
|
3063
|
+
const newFiles = [];
|
|
3064
|
+
for (const file of filesToAdd) {
|
|
3065
|
+
const validationError = validateFile(file);
|
|
3066
|
+
if (validationError) {
|
|
3067
|
+
setError(validationError);
|
|
3068
|
+
onError?.(validationError);
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
3071
|
+
const fileType = getFileType(file);
|
|
3072
|
+
if (!fileType)
|
|
3073
|
+
continue;
|
|
3074
|
+
try {
|
|
3075
|
+
let preview;
|
|
3076
|
+
let data;
|
|
3077
|
+
let textContent;
|
|
3078
|
+
let mediaType = file.type;
|
|
3079
|
+
if (fileType === "image") {
|
|
3080
|
+
[preview, data] = await Promise.all([
|
|
3081
|
+
fileToPreview2(file),
|
|
3082
|
+
fileToBase642(file)
|
|
3083
|
+
]);
|
|
3084
|
+
} else if (fileType === "pdf") {
|
|
3085
|
+
data = await fileToBase642(file);
|
|
3086
|
+
mediaType = "application/pdf";
|
|
3087
|
+
} else {
|
|
3088
|
+
textContent = await fileToText(file);
|
|
3089
|
+
data = textContent;
|
|
3090
|
+
if (!mediaType) {
|
|
3091
|
+
const ext = file.name.toLowerCase();
|
|
3092
|
+
mediaType = ext.endsWith(".md") || ext.endsWith(".markdown") ? "text/markdown" : "text/plain";
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
newFiles.push({
|
|
3096
|
+
id: generateId2(),
|
|
3097
|
+
file,
|
|
3098
|
+
type: fileType,
|
|
3099
|
+
name: file.name,
|
|
3100
|
+
preview,
|
|
3101
|
+
data,
|
|
3102
|
+
mediaType,
|
|
3103
|
+
textContent
|
|
3104
|
+
});
|
|
3105
|
+
} catch {
|
|
3106
|
+
const msg = `Failed to process file: ${file.name}`;
|
|
3107
|
+
setError(msg);
|
|
3108
|
+
onError?.(msg);
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
if (newFiles.length > 0) {
|
|
3112
|
+
setFiles((prev) => [...prev, ...newFiles]);
|
|
3113
|
+
}
|
|
3114
|
+
}, [files.length, maxFiles, validateFile, onError]);
|
|
3115
|
+
const removeFile = useCallback5((id) => {
|
|
3116
|
+
setFiles((prev) => prev.filter((f) => f.id !== id));
|
|
3117
|
+
setError(null);
|
|
3118
|
+
}, []);
|
|
3119
|
+
const clearFiles = useCallback5(() => {
|
|
3120
|
+
setFiles([]);
|
|
3121
|
+
setError(null);
|
|
3122
|
+
}, []);
|
|
3123
|
+
const handleDragEnter = useCallback5((e) => {
|
|
3124
|
+
e.preventDefault();
|
|
3125
|
+
e.stopPropagation();
|
|
3126
|
+
if (e.dataTransfer.types.includes("Files")) {
|
|
3127
|
+
setIsDragging(true);
|
|
3128
|
+
}
|
|
3129
|
+
}, []);
|
|
3130
|
+
const handleDragLeave = useCallback5((e) => {
|
|
3131
|
+
e.preventDefault();
|
|
3132
|
+
e.stopPropagation();
|
|
3133
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
3134
|
+
const x = e.clientX;
|
|
3135
|
+
const y = e.clientY;
|
|
3136
|
+
if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
|
|
3137
|
+
setIsDragging(false);
|
|
3138
|
+
}
|
|
3139
|
+
}, []);
|
|
3140
|
+
const handleDragOver = useCallback5((e) => {
|
|
3141
|
+
e.preventDefault();
|
|
3142
|
+
e.stopPropagation();
|
|
3143
|
+
}, []);
|
|
3144
|
+
const handleDrop = useCallback5((e) => {
|
|
3145
|
+
e.preventDefault();
|
|
3146
|
+
e.stopPropagation();
|
|
3147
|
+
setIsDragging(false);
|
|
3148
|
+
const droppedFiles = e.dataTransfer.files;
|
|
3149
|
+
if (droppedFiles.length > 0) {
|
|
3150
|
+
addFiles(Array.from(droppedFiles));
|
|
3151
|
+
}
|
|
3152
|
+
}, [addFiles]);
|
|
3153
|
+
const handlePaste = useCallback5((e) => {
|
|
3154
|
+
const items = e.clipboardData?.items;
|
|
3155
|
+
if (!items)
|
|
3156
|
+
return;
|
|
3157
|
+
const pastedFiles = [];
|
|
3158
|
+
for (const item of Array.from(items)) {
|
|
3159
|
+
if (item.kind === "file") {
|
|
3160
|
+
const file = item.getAsFile();
|
|
3161
|
+
if (file) {
|
|
3162
|
+
pastedFiles.push(file);
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
if (pastedFiles.length > 0) {
|
|
3167
|
+
e.preventDefault();
|
|
3168
|
+
addFiles(pastedFiles);
|
|
3169
|
+
}
|
|
3170
|
+
}, [addFiles]);
|
|
3171
|
+
useEffect6(() => {
|
|
3172
|
+
if (!pageWide)
|
|
3173
|
+
return;
|
|
3174
|
+
let dragCounter = 0;
|
|
3175
|
+
const onDragEnter = (e) => {
|
|
3176
|
+
e.preventDefault();
|
|
3177
|
+
if (e.dataTransfer?.types.includes("Files")) {
|
|
3178
|
+
dragCounter++;
|
|
3179
|
+
if (dragCounter === 1) {
|
|
3180
|
+
setIsDragging(true);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
};
|
|
3184
|
+
const onDragLeave = (e) => {
|
|
3185
|
+
e.preventDefault();
|
|
3186
|
+
dragCounter--;
|
|
3187
|
+
if (dragCounter === 0) {
|
|
3188
|
+
setIsDragging(false);
|
|
3189
|
+
}
|
|
3190
|
+
};
|
|
3191
|
+
const onDragOver = (e) => {
|
|
3192
|
+
e.preventDefault();
|
|
3193
|
+
};
|
|
3194
|
+
const onDrop = (e) => {
|
|
3195
|
+
e.preventDefault();
|
|
3196
|
+
dragCounter = 0;
|
|
3197
|
+
setIsDragging(false);
|
|
3198
|
+
const droppedFiles = e.dataTransfer?.files;
|
|
3199
|
+
if (droppedFiles && droppedFiles.length > 0) {
|
|
3200
|
+
addFiles(Array.from(droppedFiles));
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
3203
|
+
document.addEventListener("dragenter", onDragEnter);
|
|
3204
|
+
document.addEventListener("dragleave", onDragLeave);
|
|
3205
|
+
document.addEventListener("dragover", onDragOver);
|
|
3206
|
+
document.addEventListener("drop", onDrop);
|
|
3207
|
+
return () => {
|
|
3208
|
+
document.removeEventListener("dragenter", onDragEnter);
|
|
3209
|
+
document.removeEventListener("dragleave", onDragLeave);
|
|
3210
|
+
document.removeEventListener("dragover", onDragOver);
|
|
3211
|
+
document.removeEventListener("drop", onDrop);
|
|
3212
|
+
};
|
|
3213
|
+
}, [pageWide, addFiles]);
|
|
3214
|
+
const images = files.filter((f) => f.type === "image");
|
|
3215
|
+
const documents = files.filter((f) => f.type === "pdf" || f.type === "text");
|
|
3216
|
+
return {
|
|
3217
|
+
files,
|
|
3218
|
+
images,
|
|
3219
|
+
documents,
|
|
3220
|
+
isDragging,
|
|
3221
|
+
error,
|
|
3222
|
+
addFiles,
|
|
3223
|
+
removeFile,
|
|
3224
|
+
clearFiles,
|
|
3225
|
+
handleDragEnter,
|
|
3226
|
+
handleDragLeave,
|
|
3227
|
+
handleDragOver,
|
|
3228
|
+
handleDrop,
|
|
3229
|
+
handlePaste
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
// src/hooks/useSessionFiles.ts
|
|
3233
|
+
import { useQuery as useQuery5 } from "@tanstack/react-query";
|
|
3234
|
+
function useSessionFiles(sessionId) {
|
|
3235
|
+
const isExpanded = useSessionFilesStore((state) => state.isExpanded);
|
|
3236
|
+
return useQuery5({
|
|
3237
|
+
queryKey: ["session", sessionId, "files"],
|
|
3238
|
+
queryFn: () => sessionId ? apiClient.getSessionFiles(sessionId) : null,
|
|
3239
|
+
enabled: !!sessionId,
|
|
3240
|
+
refetchInterval: isExpanded ? 5000 : false,
|
|
3241
|
+
retry: 1,
|
|
3242
|
+
staleTime: 3000
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3245
|
+
// src/hooks/useQueueState.ts
|
|
3246
|
+
import { useQuery as useQuery6 } from "@tanstack/react-query";
|
|
3247
|
+
var defaultQueueState = {
|
|
3248
|
+
currentMessageId: null,
|
|
3249
|
+
queuedMessages: [],
|
|
3250
|
+
queueLength: 0
|
|
3251
|
+
};
|
|
3252
|
+
function useQueueState(sessionId) {
|
|
3253
|
+
const { data } = useQuery6({
|
|
3254
|
+
queryKey: ["queueState", sessionId],
|
|
3255
|
+
queryFn: () => defaultQueueState,
|
|
3256
|
+
enabled: !!sessionId,
|
|
3257
|
+
initialData: defaultQueueState,
|
|
3258
|
+
staleTime: Infinity
|
|
3259
|
+
});
|
|
3260
|
+
return data ?? defaultQueueState;
|
|
3261
|
+
}
|
|
3262
|
+
function useMessageQueuePosition(sessionId, messageId) {
|
|
3263
|
+
const queueState = useQueueState(sessionId);
|
|
3264
|
+
if (!sessionId || !queueState) {
|
|
3265
|
+
return { isQueued: false, isRunning: false, position: null };
|
|
3266
|
+
}
|
|
3267
|
+
if (queueState.currentMessageId === messageId) {
|
|
3268
|
+
return { isQueued: false, isRunning: true, position: null };
|
|
3269
|
+
}
|
|
3270
|
+
const queuedItem = queueState.queuedMessages.find((item) => item.messageId === messageId);
|
|
3271
|
+
if (queuedItem) {
|
|
3272
|
+
return { isQueued: true, isRunning: false, position: queuedItem.position };
|
|
3273
|
+
}
|
|
3274
|
+
return { isQueued: false, isRunning: false, position: null };
|
|
3275
|
+
}
|
|
3276
|
+
// src/hooks/useBranch.ts
|
|
3277
|
+
import { useQuery as useQuery7, useMutation as useMutation5, useQueryClient as useQueryClient6 } from "@tanstack/react-query";
|
|
3278
|
+
function useCreateBranch(sessionId) {
|
|
3279
|
+
const queryClient = useQueryClient6();
|
|
3280
|
+
return useMutation5({
|
|
3281
|
+
mutationFn: (data) => {
|
|
3282
|
+
if (!sessionId)
|
|
3283
|
+
throw new Error("No session ID");
|
|
3284
|
+
return apiClient.createBranch(sessionId, data);
|
|
3285
|
+
},
|
|
3286
|
+
onSuccess: () => {
|
|
3287
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
3288
|
+
if (sessionId) {
|
|
3289
|
+
queryClient.invalidateQueries({
|
|
3290
|
+
queryKey: ["branches", sessionId]
|
|
3291
|
+
});
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3296
|
+
function useBranches(sessionId) {
|
|
3297
|
+
return useQuery7({
|
|
3298
|
+
queryKey: ["branches", sessionId],
|
|
3299
|
+
queryFn: () => {
|
|
3300
|
+
if (!sessionId)
|
|
3301
|
+
throw new Error("No session ID");
|
|
3302
|
+
return apiClient.listBranches(sessionId);
|
|
3303
|
+
},
|
|
3304
|
+
enabled: Boolean(sessionId)
|
|
3305
|
+
});
|
|
3306
|
+
}
|
|
3307
|
+
function useParentSession(sessionId) {
|
|
3308
|
+
return useQuery7({
|
|
3309
|
+
queryKey: ["parentSession", sessionId],
|
|
3310
|
+
queryFn: () => {
|
|
3311
|
+
if (!sessionId)
|
|
3312
|
+
throw new Error("No session ID");
|
|
3313
|
+
return apiClient.getParentSession(sessionId);
|
|
3314
|
+
},
|
|
3315
|
+
enabled: Boolean(sessionId)
|
|
3316
|
+
});
|
|
3317
|
+
}
|
|
3318
|
+
// src/hooks/useResearch.ts
|
|
3319
|
+
import { useQuery as useQuery8, useMutation as useMutation6, useQueryClient as useQueryClient7 } from "@tanstack/react-query";
|
|
3320
|
+
|
|
3321
|
+
// src/stores/pendingResearchStore.ts
|
|
3322
|
+
import { create as create13 } from "zustand";
|
|
3323
|
+
var usePendingResearchStore = create13((set, get) => ({
|
|
3324
|
+
pendingContexts: new Map,
|
|
3325
|
+
addContext: (parentSessionId, context) => {
|
|
3326
|
+
set((state) => {
|
|
3327
|
+
const newMap = new Map(state.pendingContexts);
|
|
3328
|
+
const existing = newMap.get(parentSessionId) || [];
|
|
3329
|
+
if (!existing.some((c) => c.id === context.id)) {
|
|
3330
|
+
newMap.set(parentSessionId, [...existing, context]);
|
|
3331
|
+
}
|
|
3332
|
+
return { pendingContexts: newMap };
|
|
3333
|
+
});
|
|
3334
|
+
},
|
|
3335
|
+
removeContext: (parentSessionId, contextId) => {
|
|
3336
|
+
set((state) => {
|
|
3337
|
+
const newMap = new Map(state.pendingContexts);
|
|
3338
|
+
const existing = newMap.get(parentSessionId) || [];
|
|
3339
|
+
newMap.set(parentSessionId, existing.filter((c) => c.id !== contextId));
|
|
3340
|
+
return { pendingContexts: newMap };
|
|
3341
|
+
});
|
|
3342
|
+
},
|
|
3343
|
+
getContexts: (parentSessionId) => {
|
|
3344
|
+
return get().pendingContexts.get(parentSessionId) || [];
|
|
3345
|
+
},
|
|
3346
|
+
clearContexts: (parentSessionId) => {
|
|
3347
|
+
set((state) => {
|
|
3348
|
+
const newMap = new Map(state.pendingContexts);
|
|
3349
|
+
newMap.delete(parentSessionId);
|
|
3350
|
+
return { pendingContexts: newMap };
|
|
3351
|
+
});
|
|
3352
|
+
},
|
|
3353
|
+
consumeContexts: (parentSessionId) => {
|
|
3354
|
+
const contexts = get().getContexts(parentSessionId);
|
|
3355
|
+
get().clearContexts(parentSessionId);
|
|
3356
|
+
return contexts;
|
|
3357
|
+
}
|
|
3358
|
+
}));
|
|
3359
|
+
|
|
3360
|
+
// src/hooks/useResearch.ts
|
|
3361
|
+
class ResearchApiClient {
|
|
3362
|
+
get baseUrl() {
|
|
3363
|
+
const win = window;
|
|
3364
|
+
if (win.OTTO_SERVER_URL) {
|
|
3365
|
+
return win.OTTO_SERVER_URL;
|
|
3366
|
+
}
|
|
3367
|
+
if (import.meta.env?.VITE_API_BASE_URL) {
|
|
3368
|
+
return import.meta.env.VITE_API_BASE_URL;
|
|
3369
|
+
}
|
|
3370
|
+
return API_BASE_URL;
|
|
3371
|
+
}
|
|
3372
|
+
async listResearchSessions(parentSessionId) {
|
|
3373
|
+
const response = await fetch(`${this.baseUrl}/v1/sessions/${parentSessionId}/research`, {
|
|
3374
|
+
method: "GET",
|
|
3375
|
+
headers: { "Content-Type": "application/json" }
|
|
3376
|
+
});
|
|
3377
|
+
if (!response.ok) {
|
|
3378
|
+
const error = await response.json().catch(() => ({ error: "Failed to fetch research sessions" }));
|
|
3379
|
+
throw new Error(error.error || "Failed to fetch research sessions");
|
|
3380
|
+
}
|
|
3381
|
+
return response.json();
|
|
3382
|
+
}
|
|
3383
|
+
async createResearchSession(parentSessionId, data) {
|
|
3384
|
+
const response = await fetch(`${this.baseUrl}/v1/sessions/${parentSessionId}/research`, {
|
|
3385
|
+
method: "POST",
|
|
3386
|
+
headers: { "Content-Type": "application/json" },
|
|
3387
|
+
body: JSON.stringify(data)
|
|
3388
|
+
});
|
|
3389
|
+
if (!response.ok) {
|
|
3390
|
+
const error = await response.json().catch(() => ({ error: "Failed to create research session" }));
|
|
3391
|
+
throw new Error(error.error || "Failed to create research session");
|
|
3392
|
+
}
|
|
3393
|
+
return response.json();
|
|
3394
|
+
}
|
|
3395
|
+
async deleteResearchSession(researchId) {
|
|
3396
|
+
const response = await fetch(`${this.baseUrl}/v1/research/${researchId}`, {
|
|
3397
|
+
method: "DELETE",
|
|
3398
|
+
headers: { "Content-Type": "application/json" }
|
|
3399
|
+
});
|
|
3400
|
+
if (!response.ok) {
|
|
3401
|
+
const error = await response.json().catch(() => ({ error: "Failed to delete research session" }));
|
|
3402
|
+
throw new Error(error.error || "Failed to delete research session");
|
|
3403
|
+
}
|
|
3404
|
+
return response.json();
|
|
3405
|
+
}
|
|
3406
|
+
async injectContext(parentSessionId, researchSessionId, label) {
|
|
3407
|
+
const response = await fetch(`${this.baseUrl}/v1/sessions/${parentSessionId}/inject`, {
|
|
3408
|
+
method: "POST",
|
|
3409
|
+
headers: { "Content-Type": "application/json" },
|
|
3410
|
+
body: JSON.stringify({ researchSessionId, label })
|
|
3411
|
+
});
|
|
3412
|
+
if (!response.ok) {
|
|
3413
|
+
const error = await response.json().catch(() => ({ error: "Failed to inject context" }));
|
|
3414
|
+
throw new Error(error.error || "Failed to inject context");
|
|
3415
|
+
}
|
|
3416
|
+
return response.json();
|
|
3417
|
+
}
|
|
3418
|
+
async exportToNewSession(researchId, data) {
|
|
3419
|
+
const response = await fetch(`${this.baseUrl}/v1/research/${researchId}/export`, {
|
|
3420
|
+
method: "POST",
|
|
3421
|
+
headers: { "Content-Type": "application/json" },
|
|
3422
|
+
body: JSON.stringify(data ?? {})
|
|
3423
|
+
});
|
|
3424
|
+
if (!response.ok) {
|
|
3425
|
+
const error = await response.json().catch(() => ({ error: "Failed to export to session" }));
|
|
3426
|
+
throw new Error(error.error || "Failed to export to session");
|
|
3427
|
+
}
|
|
3428
|
+
return response.json();
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
var researchApi = new ResearchApiClient;
|
|
3432
|
+
function useResearchSessions(parentSessionId) {
|
|
3433
|
+
return useQuery8({
|
|
3434
|
+
queryKey: ["research", "sessions", parentSessionId],
|
|
3435
|
+
queryFn: () => researchApi.listResearchSessions(parentSessionId),
|
|
3436
|
+
enabled: !!parentSessionId,
|
|
3437
|
+
staleTime: 30000
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
function useCreateResearchSession() {
|
|
3441
|
+
const queryClient = useQueryClient7();
|
|
3442
|
+
return useMutation6({
|
|
3443
|
+
mutationFn: ({
|
|
3444
|
+
parentSessionId,
|
|
3445
|
+
data
|
|
3446
|
+
}) => researchApi.createResearchSession(parentSessionId, data ?? {}),
|
|
3447
|
+
onSuccess: (_, { parentSessionId }) => {
|
|
3448
|
+
queryClient.invalidateQueries({
|
|
3449
|
+
queryKey: ["research", "sessions", parentSessionId]
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
function useDeleteResearchSession() {
|
|
3455
|
+
const queryClient = useQueryClient7();
|
|
3456
|
+
return useMutation6({
|
|
3457
|
+
mutationFn: (researchId) => researchApi.deleteResearchSession(researchId),
|
|
3458
|
+
onSuccess: () => {
|
|
3459
|
+
queryClient.invalidateQueries({ queryKey: ["research", "sessions"] });
|
|
3460
|
+
}
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
function useInjectContext() {
|
|
3464
|
+
const addContext = usePendingResearchStore((state) => state.addContext);
|
|
3465
|
+
return useMutation6({
|
|
3466
|
+
mutationFn: ({
|
|
3467
|
+
parentSessionId,
|
|
3468
|
+
researchSessionId,
|
|
3469
|
+
label
|
|
3470
|
+
}) => researchApi.injectContext(parentSessionId, researchSessionId, label),
|
|
3471
|
+
onSuccess: (data, { parentSessionId }) => {
|
|
3472
|
+
addContext(parentSessionId, {
|
|
3473
|
+
id: data.sessionId,
|
|
3474
|
+
sessionId: data.sessionId,
|
|
3475
|
+
label: data.label,
|
|
3476
|
+
content: data.content
|
|
3477
|
+
});
|
|
3478
|
+
}
|
|
3479
|
+
});
|
|
3480
|
+
}
|
|
3481
|
+
function useExportToSession() {
|
|
3482
|
+
const queryClient = useQueryClient7();
|
|
3483
|
+
return useMutation6({
|
|
3484
|
+
mutationFn: ({
|
|
3485
|
+
researchId,
|
|
3486
|
+
data
|
|
3487
|
+
}) => researchApi.exportToNewSession(researchId, data),
|
|
3488
|
+
onSuccess: () => {
|
|
3489
|
+
queryClient.invalidateQueries({ queryKey: sessionsQueryKey });
|
|
3490
|
+
}
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3493
|
+
// src/hooks/useSetuPayments.ts
|
|
3494
|
+
import { useEffect as useEffect7, useRef as useRef2 } from "react";
|
|
3495
|
+
|
|
3496
|
+
// src/stores/toastStore.ts
|
|
3497
|
+
import { create as create14 } from "zustand";
|
|
3498
|
+
var toastId = 0;
|
|
3499
|
+
var useToastStore = create14((set) => ({
|
|
3500
|
+
toasts: [],
|
|
3501
|
+
addToast: (toast) => {
|
|
3502
|
+
const id = `toast-${++toastId}`;
|
|
3503
|
+
set((state) => ({
|
|
3504
|
+
toasts: [...state.toasts, { ...toast, id }]
|
|
3505
|
+
}));
|
|
3506
|
+
return id;
|
|
3507
|
+
},
|
|
3508
|
+
removeToast: (id) => set((state) => ({
|
|
3509
|
+
toasts: state.toasts.filter((t) => t.id !== id)
|
|
3510
|
+
})),
|
|
3511
|
+
updateToast: (id, updates) => set((state) => ({
|
|
3512
|
+
toasts: state.toasts.map((t) => t.id === id ? { ...t, ...updates } : t)
|
|
3513
|
+
})),
|
|
3514
|
+
clearToasts: () => set({ toasts: [] })
|
|
3515
|
+
}));
|
|
3516
|
+
function toast(message, type = "default", duration = 4000) {
|
|
3517
|
+
const id = useToastStore.getState().addToast({ message, type, duration });
|
|
3518
|
+
if (duration > 0) {
|
|
3519
|
+
setTimeout(() => {
|
|
3520
|
+
useToastStore.getState().removeToast(id);
|
|
3521
|
+
}, duration);
|
|
3522
|
+
}
|
|
3523
|
+
return id;
|
|
3524
|
+
}
|
|
3525
|
+
toast.success = (message, duration = 4000) => toast(message, "success", duration);
|
|
3526
|
+
toast.error = (message, duration = 5000) => toast(message, "error", duration);
|
|
3527
|
+
toast.info = (message, duration = 4000) => toast(message, "default", duration);
|
|
3528
|
+
toast.loading = (message) => toast(message, "loading", 0);
|
|
3529
|
+
toast.successWithAction = (message, action, duration = 6000) => {
|
|
3530
|
+
const id = useToastStore.getState().addToast({
|
|
3531
|
+
message,
|
|
3532
|
+
type: "success",
|
|
3533
|
+
duration,
|
|
3534
|
+
action
|
|
3535
|
+
});
|
|
3536
|
+
if (duration > 0) {
|
|
3537
|
+
setTimeout(() => {
|
|
3538
|
+
useToastStore.getState().removeToast(id);
|
|
3539
|
+
}, duration);
|
|
3540
|
+
}
|
|
3541
|
+
return id;
|
|
3542
|
+
};
|
|
3543
|
+
|
|
3544
|
+
// src/stores/setuStore.ts
|
|
3545
|
+
import { create as create15 } from "zustand";
|
|
3546
|
+
var useSetuStore = create15((set) => ({
|
|
3547
|
+
balance: null,
|
|
3548
|
+
usdcBalance: null,
|
|
3549
|
+
network: "mainnet",
|
|
3550
|
+
isPaymentPending: false,
|
|
3551
|
+
lastPaymentAmount: null,
|
|
3552
|
+
walletAddress: null,
|
|
3553
|
+
isLoading: false,
|
|
3554
|
+
isTopupModalOpen: false,
|
|
3555
|
+
scope: null,
|
|
3556
|
+
payg: null,
|
|
3557
|
+
subscription: null,
|
|
3558
|
+
limits: null,
|
|
3559
|
+
setBalance: (balance) => set({ balance }),
|
|
3560
|
+
setUsdcBalance: (usdcBalance) => set({ usdcBalance }),
|
|
3561
|
+
setNetwork: (network) => set({ network }),
|
|
3562
|
+
setPaymentPending: (isPaymentPending) => set({ isPaymentPending }),
|
|
3563
|
+
setLastPaymentAmount: (lastPaymentAmount) => set({ lastPaymentAmount }),
|
|
3564
|
+
setWalletAddress: (walletAddress) => set({ walletAddress }),
|
|
3565
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
3566
|
+
openTopupModal: () => set({ isTopupModalOpen: true }),
|
|
3567
|
+
closeTopupModal: () => set({ isTopupModalOpen: false }),
|
|
3568
|
+
setScope: (scope) => set({ scope }),
|
|
3569
|
+
setPayg: (payg) => set({ payg }),
|
|
3570
|
+
setSubscription: (subscription) => set({ subscription }),
|
|
3571
|
+
setLimits: (limits) => set({ limits })
|
|
3572
|
+
}));
|
|
3573
|
+
|
|
3574
|
+
// src/stores/topupApprovalStore.ts
|
|
3575
|
+
import { create as create16 } from "zustand";
|
|
3576
|
+
var useTopupApprovalStore = create16((set) => ({
|
|
3577
|
+
pendingTopup: null,
|
|
3578
|
+
isProcessing: false,
|
|
3579
|
+
selectedMethod: null,
|
|
3580
|
+
setPendingTopup: (pendingTopup) => set({ pendingTopup, isProcessing: false, selectedMethod: null }),
|
|
3581
|
+
setProcessing: (isProcessing) => set({ isProcessing }),
|
|
3582
|
+
setSelectedMethod: (selectedMethod) => set({ selectedMethod }),
|
|
3583
|
+
clearPendingTopup: () => set({ pendingTopup: null, isProcessing: false, selectedMethod: null })
|
|
3584
|
+
}));
|
|
3585
|
+
|
|
3586
|
+
// src/hooks/useSetuPayments.ts
|
|
3587
|
+
function useSetuPayments(sessionId) {
|
|
3588
|
+
const clientRef = useRef2(null);
|
|
3589
|
+
const loadingToastIdRef = useRef2(null);
|
|
3590
|
+
const setBalance = useSetuStore((s) => s.setBalance);
|
|
3591
|
+
const setPaymentPending = useSetuStore((s) => s.setPaymentPending);
|
|
3592
|
+
const removeToast = useToastStore((s) => s.removeToast);
|
|
3593
|
+
const updateToast = useToastStore((s) => s.updateToast);
|
|
3594
|
+
const setPendingTopup = useTopupApprovalStore((s) => s.setPendingTopup);
|
|
3595
|
+
const clearPendingTopup = useTopupApprovalStore((s) => s.clearPendingTopup);
|
|
3596
|
+
useEffect7(() => {
|
|
3597
|
+
if (!sessionId)
|
|
3598
|
+
return;
|
|
3599
|
+
const client2 = new SSEClient;
|
|
3600
|
+
clientRef.current = client2;
|
|
3601
|
+
const url = apiClient.getStreamUrl(sessionId);
|
|
3602
|
+
client2.connect(url);
|
|
3603
|
+
const unsubscribe = client2.on("*", (event) => {
|
|
3604
|
+
const payload = event.payload;
|
|
3605
|
+
switch (event.type) {
|
|
3606
|
+
case "setu.topup.required": {
|
|
3607
|
+
const amountUsd = typeof payload?.amountUsd === "number" ? payload.amountUsd : 0;
|
|
3608
|
+
const currentBalance = typeof payload?.currentBalance === "number" ? payload.currentBalance : 0;
|
|
3609
|
+
const minTopupUsd = typeof payload?.minTopupUsd === "number" ? payload.minTopupUsd : 5;
|
|
3610
|
+
const suggestedTopupUsd = typeof payload?.suggestedTopupUsd === "number" ? payload.suggestedTopupUsd : 10;
|
|
3611
|
+
const messageId = typeof payload?.messageId === "string" ? payload.messageId : "";
|
|
3612
|
+
setPendingTopup({
|
|
3613
|
+
sessionId,
|
|
3614
|
+
messageId,
|
|
3615
|
+
amountUsd,
|
|
3616
|
+
currentBalance,
|
|
3617
|
+
minTopupUsd,
|
|
3618
|
+
suggestedTopupUsd
|
|
3619
|
+
});
|
|
3620
|
+
break;
|
|
3621
|
+
}
|
|
3622
|
+
case "setu.topup.method_selected": {
|
|
3623
|
+
const method = payload?.method;
|
|
3624
|
+
if (method === "crypto") {
|
|
3625
|
+
setPaymentPending(true);
|
|
3626
|
+
loadingToastIdRef.current = toast.loading("\uD83D\uDCB3 Processing crypto payment...");
|
|
3627
|
+
}
|
|
3628
|
+
break;
|
|
3629
|
+
}
|
|
3630
|
+
case "setu.topup.cancelled": {
|
|
3631
|
+
clearPendingTopup();
|
|
3632
|
+
const reason = typeof payload?.reason === "string" ? payload.reason : "Request cancelled";
|
|
3633
|
+
toast(`⚠️ ${reason}`);
|
|
3634
|
+
break;
|
|
3635
|
+
}
|
|
3636
|
+
case "setu.payment.required": {
|
|
3637
|
+
const amountUsd = typeof payload?.amountUsd === "number" ? payload.amountUsd : 0;
|
|
3638
|
+
setPaymentPending(true);
|
|
3639
|
+
if (!loadingToastIdRef.current) {
|
|
3640
|
+
loadingToastIdRef.current = toast.loading(`\uD83D\uDCB3 Payment required: $${amountUsd.toFixed(2)}`);
|
|
3641
|
+
}
|
|
3642
|
+
break;
|
|
3643
|
+
}
|
|
3644
|
+
case "setu.payment.signing": {
|
|
3645
|
+
if (loadingToastIdRef.current) {
|
|
3646
|
+
updateToast(loadingToastIdRef.current, {
|
|
3647
|
+
message: "✍️ Signing transaction..."
|
|
3648
|
+
});
|
|
3649
|
+
} else {
|
|
3650
|
+
loadingToastIdRef.current = toast.loading("✍️ Signing transaction...");
|
|
3651
|
+
}
|
|
3652
|
+
break;
|
|
3653
|
+
}
|
|
3654
|
+
case "setu.payment.complete": {
|
|
3655
|
+
clearPendingTopup();
|
|
3656
|
+
const rawAmount = payload?.amountUsd;
|
|
3657
|
+
const rawBalance = payload?.newBalance;
|
|
3658
|
+
const transactionId = typeof payload?.transactionId === "string" ? payload.transactionId : undefined;
|
|
3659
|
+
const amountUsd = typeof rawAmount === "number" ? rawAmount : typeof rawAmount === "string" ? parseFloat(rawAmount) : 0;
|
|
3660
|
+
const newBalance = typeof rawBalance === "number" ? rawBalance : typeof rawBalance === "string" ? parseFloat(rawBalance) : 0;
|
|
3661
|
+
setBalance(newBalance);
|
|
3662
|
+
setPaymentPending(false);
|
|
3663
|
+
if (loadingToastIdRef.current) {
|
|
3664
|
+
removeToast(loadingToastIdRef.current);
|
|
3665
|
+
loadingToastIdRef.current = null;
|
|
3666
|
+
}
|
|
3667
|
+
const message = `✅ Paid $${amountUsd.toFixed(2)}`;
|
|
3668
|
+
if (transactionId) {
|
|
3669
|
+
toast.successWithAction(message, {
|
|
3670
|
+
label: "View Tx",
|
|
3671
|
+
href: `https://orbmarkets.io/tx/${transactionId}`
|
|
3672
|
+
});
|
|
3673
|
+
} else {
|
|
3674
|
+
toast.success(message);
|
|
3675
|
+
}
|
|
3676
|
+
break;
|
|
3677
|
+
}
|
|
3678
|
+
case "setu.fiat.checkout_created": {
|
|
3679
|
+
clearPendingTopup();
|
|
3680
|
+
setPaymentPending(false);
|
|
3681
|
+
if (loadingToastIdRef.current) {
|
|
3682
|
+
removeToast(loadingToastIdRef.current);
|
|
3683
|
+
loadingToastIdRef.current = null;
|
|
3684
|
+
}
|
|
3685
|
+
toast.success("\uD83D\uDCB3 Complete payment, then retry your message");
|
|
3686
|
+
break;
|
|
3687
|
+
}
|
|
3688
|
+
case "setu.balance.updated": {
|
|
3689
|
+
const rawBalance = payload?.balanceRemaining;
|
|
3690
|
+
const newBalance = typeof rawBalance === "number" ? rawBalance : typeof rawBalance === "string" ? parseFloat(rawBalance) : null;
|
|
3691
|
+
if (newBalance !== null && !Number.isNaN(newBalance)) {
|
|
3692
|
+
setBalance(newBalance);
|
|
3693
|
+
}
|
|
3694
|
+
break;
|
|
3695
|
+
}
|
|
3696
|
+
case "setu.payment.error": {
|
|
3697
|
+
clearPendingTopup();
|
|
3698
|
+
const error = typeof payload?.error === "string" ? payload.error : "Payment failed";
|
|
3699
|
+
setPaymentPending(false);
|
|
3700
|
+
if (loadingToastIdRef.current) {
|
|
3701
|
+
removeToast(loadingToastIdRef.current);
|
|
3702
|
+
loadingToastIdRef.current = null;
|
|
3703
|
+
}
|
|
3704
|
+
toast.error(`❌ ${error}`);
|
|
3705
|
+
break;
|
|
3706
|
+
}
|
|
3707
|
+
default:
|
|
3708
|
+
break;
|
|
3709
|
+
}
|
|
3710
|
+
});
|
|
3711
|
+
return () => {
|
|
3712
|
+
unsubscribe();
|
|
3713
|
+
client2.disconnect();
|
|
3714
|
+
};
|
|
3715
|
+
}, [
|
|
3716
|
+
sessionId,
|
|
3717
|
+
setBalance,
|
|
3718
|
+
setPaymentPending,
|
|
3719
|
+
removeToast,
|
|
3720
|
+
updateToast,
|
|
3721
|
+
setPendingTopup,
|
|
3722
|
+
clearPendingTopup
|
|
3723
|
+
]);
|
|
3724
|
+
}
|
|
3725
|
+
// src/hooks/useSetuBalance.ts
|
|
3726
|
+
import { useEffect as useEffect8, useCallback as useCallback6 } from "react";
|
|
3727
|
+
function useSetuBalance(providerName) {
|
|
3728
|
+
const setBalance = useSetuStore((s) => s.setBalance);
|
|
3729
|
+
const setUsdcBalance = useSetuStore((s) => s.setUsdcBalance);
|
|
3730
|
+
const setWalletAddress = useSetuStore((s) => s.setWalletAddress);
|
|
3731
|
+
const setLoading = useSetuStore((s) => s.setLoading);
|
|
3732
|
+
const setScope = useSetuStore((s) => s.setScope);
|
|
3733
|
+
const setPayg = useSetuStore((s) => s.setPayg);
|
|
3734
|
+
const setSubscription = useSetuStore((s) => s.setSubscription);
|
|
3735
|
+
const setLimits = useSetuStore((s) => s.setLimits);
|
|
3736
|
+
const balance = useSetuStore((s) => s.balance);
|
|
3737
|
+
const usdcBalance = useSetuStore((s) => s.usdcBalance);
|
|
3738
|
+
const network = useSetuStore((s) => s.network);
|
|
3739
|
+
const fetchBalance = useCallback6(async () => {
|
|
3740
|
+
if (providerName !== "setu") {
|
|
3741
|
+
return;
|
|
3742
|
+
}
|
|
3743
|
+
setLoading(true);
|
|
3744
|
+
try {
|
|
3745
|
+
const [setuData, usdcData, walletData] = await Promise.all([
|
|
3746
|
+
apiClient.getSetuBalance(),
|
|
3747
|
+
apiClient.getSetuUsdcBalance(network),
|
|
3748
|
+
apiClient.getSetuWallet()
|
|
3749
|
+
]);
|
|
3750
|
+
if (setuData) {
|
|
3751
|
+
setBalance(setuData.balance);
|
|
3752
|
+
setWalletAddress(setuData.walletAddress);
|
|
3753
|
+
setScope(setuData.scope ?? null);
|
|
3754
|
+
setPayg(setuData.payg ?? null);
|
|
3755
|
+
setSubscription(setuData.subscription ?? null);
|
|
3756
|
+
setLimits(setuData.limits ?? null);
|
|
3757
|
+
} else if (walletData?.configured && walletData.publicKey) {
|
|
3758
|
+
setWalletAddress(walletData.publicKey);
|
|
3759
|
+
}
|
|
3760
|
+
if (usdcData) {
|
|
3761
|
+
setUsdcBalance(usdcData.usdcBalance);
|
|
3762
|
+
if (!setuData && usdcData.walletAddress) {
|
|
3763
|
+
setWalletAddress(usdcData.walletAddress);
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
} catch {} finally {
|
|
3767
|
+
setLoading(false);
|
|
3768
|
+
}
|
|
3769
|
+
}, [
|
|
3770
|
+
providerName,
|
|
3771
|
+
network,
|
|
3772
|
+
setBalance,
|
|
3773
|
+
setUsdcBalance,
|
|
3774
|
+
setWalletAddress,
|
|
3775
|
+
setLoading,
|
|
3776
|
+
setScope,
|
|
3777
|
+
setPayg,
|
|
3778
|
+
setSubscription,
|
|
3779
|
+
setLimits
|
|
3780
|
+
]);
|
|
3781
|
+
useEffect8(() => {
|
|
3782
|
+
if (providerName === "setu" && (balance === null || usdcBalance === null)) {
|
|
3783
|
+
fetchBalance();
|
|
3784
|
+
}
|
|
3785
|
+
}, [providerName, balance, usdcBalance, fetchBalance]);
|
|
3786
|
+
return {
|
|
3787
|
+
fetchBalance
|
|
3788
|
+
};
|
|
3789
|
+
}
|
|
3790
|
+
// src/hooks/useShareStatus.ts
|
|
3791
|
+
import { useQuery as useQuery9 } from "@tanstack/react-query";
|
|
3792
|
+
function useShareStatus(sessionId) {
|
|
3793
|
+
const { data, isLoading, error } = useQuery9({
|
|
3794
|
+
queryKey: ["share-status", sessionId],
|
|
3795
|
+
queryFn: () => apiClient.getShareStatus(sessionId),
|
|
3796
|
+
enabled: !!sessionId,
|
|
3797
|
+
staleTime: 30000
|
|
3798
|
+
});
|
|
3799
|
+
return { data, isLoading, error };
|
|
3800
|
+
}
|
|
3801
|
+
// src/hooks/useToolApprovalShortcuts.ts
|
|
3802
|
+
import { useEffect as useEffect9, useCallback as useCallback7 } from "react";
|
|
3803
|
+
function useToolApprovalShortcuts(sessionId) {
|
|
3804
|
+
const { pendingApprovals, removePendingApproval } = useToolApprovalStore();
|
|
3805
|
+
const sessionPendingApprovals = pendingApprovals;
|
|
3806
|
+
const handleApprove = useCallback7(async (callId) => {
|
|
3807
|
+
if (!sessionId)
|
|
3808
|
+
return;
|
|
3809
|
+
try {
|
|
3810
|
+
await apiClient.approveToolCall(sessionId, callId, true);
|
|
3811
|
+
removePendingApproval(callId);
|
|
3812
|
+
} catch (error) {
|
|
3813
|
+
console.error("Failed to approve tool call:", error);
|
|
3814
|
+
}
|
|
3815
|
+
}, [sessionId, removePendingApproval]);
|
|
3816
|
+
const handleReject = useCallback7(async (callId) => {
|
|
3817
|
+
if (!sessionId)
|
|
3818
|
+
return;
|
|
3819
|
+
try {
|
|
3820
|
+
await apiClient.approveToolCall(sessionId, callId, false);
|
|
3821
|
+
removePendingApproval(callId);
|
|
3822
|
+
} catch (error) {
|
|
3823
|
+
console.error("Failed to reject tool call:", error);
|
|
3824
|
+
}
|
|
3825
|
+
}, [sessionId, removePendingApproval]);
|
|
3826
|
+
const handleApproveAll = useCallback7(async () => {
|
|
3827
|
+
if (!sessionId)
|
|
3828
|
+
return;
|
|
3829
|
+
try {
|
|
3830
|
+
await Promise.all(sessionPendingApprovals.map((a) => apiClient.approveToolCall(sessionId, a.callId, true)));
|
|
3831
|
+
for (const a of sessionPendingApprovals) {
|
|
3832
|
+
removePendingApproval(a.callId);
|
|
3833
|
+
}
|
|
3834
|
+
} catch (error) {
|
|
3835
|
+
console.error("Failed to approve all tool calls:", error);
|
|
3836
|
+
}
|
|
3837
|
+
}, [sessionId, sessionPendingApprovals, removePendingApproval]);
|
|
3838
|
+
useEffect9(() => {
|
|
3839
|
+
if (!sessionId || sessionPendingApprovals.length === 0)
|
|
3840
|
+
return;
|
|
3841
|
+
const handleKeyDown = (e) => {
|
|
3842
|
+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement || e.target?.isContentEditable) {
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
const firstPending = sessionPendingApprovals[0];
|
|
3846
|
+
if (e.key === "y" || e.key === "Y") {
|
|
3847
|
+
e.preventDefault();
|
|
3848
|
+
handleApprove(firstPending.callId);
|
|
3849
|
+
} else if (e.key === "n" || e.key === "N" || e.key === "Escape") {
|
|
3850
|
+
e.preventDefault();
|
|
3851
|
+
handleReject(firstPending.callId);
|
|
3852
|
+
} else if (e.key === "a" || e.key === "A") {
|
|
3853
|
+
e.preventDefault();
|
|
3854
|
+
handleApproveAll();
|
|
3855
|
+
}
|
|
3856
|
+
};
|
|
3857
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3858
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
3859
|
+
}, [
|
|
3860
|
+
sessionId,
|
|
3861
|
+
sessionPendingApprovals,
|
|
3862
|
+
handleApprove,
|
|
3863
|
+
handleReject,
|
|
3864
|
+
handleApproveAll
|
|
3865
|
+
]);
|
|
3866
|
+
}
|
|
3867
|
+
// src/hooks/useTopupCallback.ts
|
|
3868
|
+
import { useEffect as useEffect10, useRef as useRef3 } from "react";
|
|
3869
|
+
var STORAGE_KEY3 = "pendingPolarCheckout";
|
|
3870
|
+
function useTopupCallback() {
|
|
3871
|
+
const hasHandled = useRef3(false);
|
|
3872
|
+
const loadingToastId = useRef3(null);
|
|
3873
|
+
const setBalance = useSetuStore((s) => s.setBalance);
|
|
3874
|
+
const removeToast = useToastStore((s) => s.removeToast);
|
|
3875
|
+
useEffect10(() => {
|
|
3876
|
+
if (hasHandled.current)
|
|
3877
|
+
return;
|
|
3878
|
+
const params = new URLSearchParams(window.location.search);
|
|
3879
|
+
const topupStatus = params.get("topup");
|
|
3880
|
+
const pendingCheckoutId = localStorage.getItem(STORAGE_KEY3);
|
|
3881
|
+
if (topupStatus === "pending" || pendingCheckoutId) {
|
|
3882
|
+
hasHandled.current = true;
|
|
3883
|
+
const url = new URL(window.location.href);
|
|
3884
|
+
url.searchParams.delete("topup");
|
|
3885
|
+
window.history.replaceState({}, "", url.toString());
|
|
3886
|
+
if (!pendingCheckoutId) {
|
|
3887
|
+
toast.info("Checking top-up status...");
|
|
3888
|
+
return;
|
|
3889
|
+
}
|
|
3890
|
+
loadingToastId.current = toast.loading("Verifying top-up...");
|
|
3891
|
+
let attempts = 0;
|
|
3892
|
+
const maxAttempts = 10;
|
|
3893
|
+
const delayMs = 2000;
|
|
3894
|
+
const dismissLoading = () => {
|
|
3895
|
+
if (loadingToastId.current) {
|
|
3896
|
+
removeToast(loadingToastId.current);
|
|
3897
|
+
loadingToastId.current = null;
|
|
3898
|
+
}
|
|
3899
|
+
};
|
|
3900
|
+
const checkStatus = async () => {
|
|
3901
|
+
attempts++;
|
|
3902
|
+
try {
|
|
3903
|
+
const status = await apiClient.getPolarTopupStatus(pendingCheckoutId);
|
|
3904
|
+
if (status?.confirmed) {
|
|
3905
|
+
localStorage.removeItem(STORAGE_KEY3);
|
|
3906
|
+
dismissLoading();
|
|
3907
|
+
const balanceData = await apiClient.getSetuBalance();
|
|
3908
|
+
if (balanceData?.balance !== undefined) {
|
|
3909
|
+
setBalance(balanceData.balance);
|
|
3910
|
+
}
|
|
3911
|
+
toast.success(`Top-up confirmed! +$${status.amountUsd?.toFixed(2)} credited`);
|
|
3912
|
+
return;
|
|
3913
|
+
}
|
|
3914
|
+
if (attempts < maxAttempts) {
|
|
3915
|
+
setTimeout(checkStatus, delayMs);
|
|
3916
|
+
} else {
|
|
3917
|
+
localStorage.removeItem(STORAGE_KEY3);
|
|
3918
|
+
dismissLoading();
|
|
3919
|
+
toast.info("Top-up is still processing. Balance will update automatically.");
|
|
3920
|
+
}
|
|
3921
|
+
} catch {
|
|
3922
|
+
if (attempts < maxAttempts) {
|
|
3923
|
+
setTimeout(checkStatus, delayMs);
|
|
3924
|
+
} else {
|
|
3925
|
+
localStorage.removeItem(STORAGE_KEY3);
|
|
3926
|
+
dismissLoading();
|
|
3927
|
+
toast.error("Could not verify top-up. Please check your balance.");
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
};
|
|
3931
|
+
setTimeout(checkStatus, 1500);
|
|
3932
|
+
}
|
|
3933
|
+
}, [setBalance, removeToast]);
|
|
3934
|
+
}
|
|
3935
|
+
// src/hooks/useAuthStatus.ts
|
|
3936
|
+
import { useEffect as useEffect11, useCallback as useCallback8, useState as useState5, useRef as useRef4 } from "react";
|
|
3937
|
+
import { useQueryClient as useQueryClient8 } from "@tanstack/react-query";
|
|
3938
|
+
|
|
3939
|
+
// src/stores/onboardingStore.ts
|
|
3940
|
+
import { create as create17 } from "zustand";
|
|
3941
|
+
var STEPS = ["wallet", "defaults"];
|
|
3942
|
+
var useOnboardingStore = create17((set, get) => ({
|
|
3943
|
+
isOpen: false,
|
|
3944
|
+
currentStep: "wallet",
|
|
3945
|
+
manageMode: false,
|
|
3946
|
+
isLoading: false,
|
|
3947
|
+
error: null,
|
|
3948
|
+
authStatus: null,
|
|
3949
|
+
setOpen: (isOpen) => set({ isOpen }),
|
|
3950
|
+
setStep: (currentStep) => set({ currentStep }),
|
|
3951
|
+
setManageMode: (manageMode) => set({ manageMode }),
|
|
3952
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
3953
|
+
setError: (error) => set({ error }),
|
|
3954
|
+
setAuthStatus: (authStatus) => set({ authStatus }),
|
|
3955
|
+
nextStep: () => {
|
|
3956
|
+
const { currentStep } = get();
|
|
3957
|
+
const currentIndex = STEPS.indexOf(currentStep);
|
|
3958
|
+
if (currentIndex < STEPS.length - 1) {
|
|
3959
|
+
set({ currentStep: STEPS[currentIndex + 1] });
|
|
3960
|
+
}
|
|
3961
|
+
},
|
|
3962
|
+
prevStep: () => {
|
|
3963
|
+
const { currentStep } = get();
|
|
3964
|
+
const currentIndex = STEPS.indexOf(currentStep);
|
|
3965
|
+
if (currentIndex > 0) {
|
|
3966
|
+
set({ currentStep: STEPS[currentIndex - 1] });
|
|
3967
|
+
}
|
|
3968
|
+
},
|
|
3969
|
+
reset: () => set({
|
|
3970
|
+
isOpen: false,
|
|
3971
|
+
currentStep: "wallet",
|
|
3972
|
+
manageMode: false,
|
|
3973
|
+
isLoading: false,
|
|
3974
|
+
error: null
|
|
3975
|
+
})
|
|
3976
|
+
}));
|
|
3977
|
+
|
|
3978
|
+
// src/hooks/useAuthStatus.ts
|
|
3979
|
+
var isInIframe = typeof window !== "undefined" && window.self !== window.top;
|
|
3980
|
+
function useAuthStatus() {
|
|
3981
|
+
const setAuthStatus = useOnboardingStore((s) => s.setAuthStatus);
|
|
3982
|
+
const setOpen = useOnboardingStore((s) => s.setOpen);
|
|
3983
|
+
const setLoading = useOnboardingStore((s) => s.setLoading);
|
|
3984
|
+
const setError = useOnboardingStore((s) => s.setError);
|
|
3985
|
+
const authStatus = useOnboardingStore((s) => s.authStatus);
|
|
3986
|
+
const isOpen = useOnboardingStore((s) => s.isOpen);
|
|
3987
|
+
const queryClient = useQueryClient8();
|
|
3988
|
+
const [initialized, setInitialized] = useState5(false);
|
|
3989
|
+
const [oauthPolling, setOauthPolling] = useState5(false);
|
|
3990
|
+
const oauthPollingRef = useRef4(null);
|
|
3991
|
+
const preOauthProvidersRef = useRef4(new Set);
|
|
3992
|
+
const fetchAuthStatus = useCallback8(async () => {
|
|
3993
|
+
setLoading(true);
|
|
3994
|
+
setError(null);
|
|
3995
|
+
try {
|
|
3996
|
+
const status = await apiClient.getAuthStatus();
|
|
3997
|
+
setAuthStatus(status);
|
|
3998
|
+
queryClient.invalidateQueries({ queryKey: ["config"] });
|
|
3999
|
+
queryClient.invalidateQueries({ queryKey: ["models"] });
|
|
4000
|
+
return status;
|
|
4001
|
+
} catch (err) {
|
|
4002
|
+
const message = err instanceof Error ? err.message : "Failed to load";
|
|
4003
|
+
setError(message);
|
|
4004
|
+
return null;
|
|
4005
|
+
} finally {
|
|
4006
|
+
setLoading(false);
|
|
4007
|
+
}
|
|
4008
|
+
}, [setAuthStatus, setLoading, setError, queryClient]);
|
|
4009
|
+
const checkOnboarding = useCallback8(async () => {
|
|
4010
|
+
const status = await fetchAuthStatus();
|
|
4011
|
+
if (status) {
|
|
4012
|
+
const hasAnyProvider = Object.values(status.providers).some((p) => p.configured);
|
|
4013
|
+
const needsOnboarding = !status.onboardingComplete || !hasAnyProvider || !status.setu.configured;
|
|
4014
|
+
if (needsOnboarding) {
|
|
4015
|
+
setOpen(true);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
setInitialized(true);
|
|
4019
|
+
}, [fetchAuthStatus, setOpen]);
|
|
4020
|
+
const setupWallet = useCallback8(async () => {
|
|
4021
|
+
setLoading(true);
|
|
4022
|
+
setError(null);
|
|
4023
|
+
try {
|
|
4024
|
+
const result = await apiClient.setupSetuWallet();
|
|
4025
|
+
await fetchAuthStatus();
|
|
4026
|
+
return result;
|
|
4027
|
+
} catch (err) {
|
|
4028
|
+
const message = err instanceof Error ? err.message : "Setup failed";
|
|
4029
|
+
setError(message);
|
|
4030
|
+
throw err;
|
|
4031
|
+
} finally {
|
|
4032
|
+
setLoading(false);
|
|
4033
|
+
}
|
|
4034
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4035
|
+
const importWallet = useCallback8(async (privateKey) => {
|
|
4036
|
+
setLoading(true);
|
|
4037
|
+
setError(null);
|
|
4038
|
+
try {
|
|
4039
|
+
const result = await apiClient.importSetuWallet(privateKey);
|
|
4040
|
+
await fetchAuthStatus();
|
|
4041
|
+
return result;
|
|
4042
|
+
} catch (err) {
|
|
4043
|
+
const message = err instanceof Error ? err.message : "Import failed";
|
|
4044
|
+
setError(message);
|
|
4045
|
+
throw err;
|
|
4046
|
+
} finally {
|
|
4047
|
+
setLoading(false);
|
|
4048
|
+
}
|
|
4049
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4050
|
+
const addProvider = useCallback8(async (provider, apiKey) => {
|
|
4051
|
+
setLoading(true);
|
|
4052
|
+
setError(null);
|
|
4053
|
+
try {
|
|
4054
|
+
const result = await apiClient.addProvider(provider, apiKey);
|
|
4055
|
+
await fetchAuthStatus();
|
|
4056
|
+
return result;
|
|
4057
|
+
} catch (err) {
|
|
4058
|
+
const message = err instanceof Error ? err.message : "Failed to add provider";
|
|
4059
|
+
setError(message);
|
|
4060
|
+
throw err;
|
|
4061
|
+
} finally {
|
|
4062
|
+
setLoading(false);
|
|
4063
|
+
}
|
|
4064
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4065
|
+
const removeProvider = useCallback8(async (provider) => {
|
|
4066
|
+
setLoading(true);
|
|
4067
|
+
setError(null);
|
|
4068
|
+
try {
|
|
4069
|
+
const result = await apiClient.removeProvider(provider);
|
|
4070
|
+
await fetchAuthStatus();
|
|
4071
|
+
return result;
|
|
4072
|
+
} catch (err) {
|
|
4073
|
+
const message = err instanceof Error ? err.message : "Failed to remove provider";
|
|
4074
|
+
setError(message);
|
|
4075
|
+
throw err;
|
|
4076
|
+
} finally {
|
|
4077
|
+
setLoading(false);
|
|
4078
|
+
}
|
|
4079
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4080
|
+
const completeOnboarding = useCallback8(async () => {
|
|
4081
|
+
setLoading(true);
|
|
4082
|
+
setError(null);
|
|
4083
|
+
try {
|
|
4084
|
+
await apiClient.completeOnboarding();
|
|
4085
|
+
await fetchAuthStatus();
|
|
4086
|
+
setOpen(false);
|
|
4087
|
+
} catch (err) {
|
|
4088
|
+
const message = err instanceof Error ? err.message : "Failed to complete onboarding";
|
|
4089
|
+
setError(message);
|
|
4090
|
+
throw err;
|
|
4091
|
+
} finally {
|
|
4092
|
+
setLoading(false);
|
|
4093
|
+
}
|
|
4094
|
+
}, [fetchAuthStatus, setLoading, setError, setOpen]);
|
|
4095
|
+
const snapshotConfiguredProviders = useCallback8(() => {
|
|
4096
|
+
const status = useOnboardingStore.getState().authStatus;
|
|
4097
|
+
if (status) {
|
|
4098
|
+
preOauthProvidersRef.current = new Set(Object.entries(status.providers).filter(([, p]) => p.configured).map(([id]) => id));
|
|
4099
|
+
}
|
|
4100
|
+
}, []);
|
|
4101
|
+
const startOAuth = useCallback8((provider, mode) => {
|
|
4102
|
+
const url = apiClient.getOAuthStartUrl(provider, mode);
|
|
4103
|
+
if (isInIframe) {
|
|
4104
|
+
snapshotConfiguredProviders();
|
|
4105
|
+
window.parent.postMessage({ type: "otto-open-url", url }, "*");
|
|
4106
|
+
setOauthPolling(true);
|
|
4107
|
+
return null;
|
|
4108
|
+
}
|
|
4109
|
+
const width = 600;
|
|
4110
|
+
const height = 700;
|
|
4111
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
4112
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
4113
|
+
const popup = window.open(url, "oauth_popup", `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`);
|
|
4114
|
+
return popup;
|
|
4115
|
+
}, [snapshotConfiguredProviders]);
|
|
4116
|
+
const startOAuthManual = useCallback8(async (provider, mode) => {
|
|
4117
|
+
const { url, sessionId } = await apiClient.getOAuthUrl(provider, mode);
|
|
4118
|
+
if (isInIframe) {
|
|
4119
|
+
snapshotConfiguredProviders();
|
|
4120
|
+
window.parent.postMessage({ type: "otto-open-url", url }, "*");
|
|
4121
|
+
setOauthPolling(true);
|
|
4122
|
+
return { popup: null, sessionId };
|
|
4123
|
+
}
|
|
4124
|
+
const width = 600;
|
|
4125
|
+
const height = 700;
|
|
4126
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
4127
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
4128
|
+
const popup = window.open(url, "oauth_popup", `width=${width},height=${height},left=${left},top=${top},toolbar=no,menubar=no`);
|
|
4129
|
+
return { popup, sessionId };
|
|
4130
|
+
}, [snapshotConfiguredProviders]);
|
|
4131
|
+
const exchangeOAuthCode = useCallback8(async (provider, code, sessionId) => {
|
|
4132
|
+
setLoading(true);
|
|
4133
|
+
setError(null);
|
|
4134
|
+
try {
|
|
4135
|
+
await apiClient.exchangeOAuthCode(provider, code, sessionId);
|
|
4136
|
+
await fetchAuthStatus();
|
|
4137
|
+
return true;
|
|
4138
|
+
} catch (err) {
|
|
4139
|
+
const message = err instanceof Error ? err.message : "Failed to exchange code";
|
|
4140
|
+
setError(message);
|
|
4141
|
+
throw err;
|
|
4142
|
+
} finally {
|
|
4143
|
+
setLoading(false);
|
|
4144
|
+
}
|
|
4145
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4146
|
+
useEffect11(() => {
|
|
4147
|
+
if (!oauthPolling || !isInIframe)
|
|
4148
|
+
return;
|
|
4149
|
+
oauthPollingRef.current = setInterval(() => {
|
|
4150
|
+
fetchAuthStatus();
|
|
4151
|
+
}, 3000);
|
|
4152
|
+
const timeout = setTimeout(() => {
|
|
4153
|
+
setOauthPolling(false);
|
|
4154
|
+
}, 300000);
|
|
4155
|
+
return () => {
|
|
4156
|
+
clearInterval(oauthPollingRef.current);
|
|
4157
|
+
clearTimeout(timeout);
|
|
4158
|
+
};
|
|
4159
|
+
}, [oauthPolling, fetchAuthStatus]);
|
|
4160
|
+
useEffect11(() => {
|
|
4161
|
+
if (!oauthPolling || !authStatus)
|
|
4162
|
+
return;
|
|
4163
|
+
const currentConfigured = Object.entries(authStatus.providers).filter(([, p]) => p.configured);
|
|
4164
|
+
const hasNewProvider = currentConfigured.some(([id]) => !preOauthProvidersRef.current.has(id));
|
|
4165
|
+
if (hasNewProvider) {
|
|
4166
|
+
setOauthPolling(false);
|
|
4167
|
+
}
|
|
4168
|
+
}, [authStatus, oauthPolling]);
|
|
4169
|
+
useEffect11(() => {
|
|
4170
|
+
const handleOAuthMessage = (event) => {
|
|
4171
|
+
if (event.data?.type === "oauth-success") {
|
|
4172
|
+
fetchAuthStatus();
|
|
4173
|
+
}
|
|
4174
|
+
};
|
|
4175
|
+
window.addEventListener("message", handleOAuthMessage);
|
|
4176
|
+
return () => window.removeEventListener("message", handleOAuthMessage);
|
|
4177
|
+
}, [fetchAuthStatus]);
|
|
4178
|
+
const pollCopilotDeviceFlow = useCallback8(async (sessionId) => {
|
|
4179
|
+
const result = await apiClient.pollCopilotDeviceFlow(sessionId);
|
|
4180
|
+
if (result.status === "complete") {
|
|
4181
|
+
await fetchAuthStatus();
|
|
4182
|
+
}
|
|
4183
|
+
return result;
|
|
4184
|
+
}, [fetchAuthStatus]);
|
|
4185
|
+
const saveCopilotToken = useCallback8(async (token) => {
|
|
4186
|
+
setLoading(true);
|
|
4187
|
+
setError(null);
|
|
4188
|
+
try {
|
|
4189
|
+
const result = await apiClient.saveCopilotToken(token);
|
|
4190
|
+
await fetchAuthStatus();
|
|
4191
|
+
return result;
|
|
4192
|
+
} catch (err) {
|
|
4193
|
+
const message = err instanceof Error ? err.message : "Failed to save Copilot token";
|
|
4194
|
+
setError(message);
|
|
4195
|
+
throw err;
|
|
4196
|
+
} finally {
|
|
4197
|
+
setLoading(false);
|
|
4198
|
+
}
|
|
4199
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4200
|
+
const importCopilotTokenFromGh = useCallback8(async () => {
|
|
4201
|
+
setLoading(true);
|
|
4202
|
+
setError(null);
|
|
4203
|
+
try {
|
|
4204
|
+
const result = await apiClient.importCopilotTokenFromGh();
|
|
4205
|
+
await fetchAuthStatus();
|
|
4206
|
+
return result;
|
|
4207
|
+
} catch (err) {
|
|
4208
|
+
const message = err instanceof Error ? err.message : "Failed to import Copilot token from GH";
|
|
4209
|
+
setError(message);
|
|
4210
|
+
throw err;
|
|
4211
|
+
} finally {
|
|
4212
|
+
setLoading(false);
|
|
4213
|
+
}
|
|
4214
|
+
}, [fetchAuthStatus, setLoading, setError]);
|
|
4215
|
+
return {
|
|
4216
|
+
authStatus,
|
|
4217
|
+
isOpen,
|
|
4218
|
+
initialized,
|
|
4219
|
+
fetchAuthStatus,
|
|
4220
|
+
checkOnboarding,
|
|
4221
|
+
setupWallet,
|
|
4222
|
+
importWallet,
|
|
4223
|
+
addProvider,
|
|
4224
|
+
removeProvider,
|
|
4225
|
+
completeOnboarding,
|
|
4226
|
+
startOAuth,
|
|
4227
|
+
startOAuthManual,
|
|
4228
|
+
exchangeOAuthCode,
|
|
4229
|
+
startCopilotDeviceFlow: apiClient.startCopilotDeviceFlow.bind(apiClient),
|
|
4230
|
+
pollCopilotDeviceFlow,
|
|
4231
|
+
getCopilotAuthMethods: apiClient.getCopilotAuthMethods.bind(apiClient),
|
|
4232
|
+
saveCopilotToken,
|
|
4233
|
+
importCopilotTokenFromGh,
|
|
4234
|
+
getCopilotDiagnostics: apiClient.getCopilotDiagnostics.bind(apiClient)
|
|
4235
|
+
};
|
|
4236
|
+
}
|
|
4237
|
+
// src/hooks/useTunnel.ts
|
|
4238
|
+
import { useQuery as useQuery10, useMutation as useMutation7, useQueryClient as useQueryClient9 } from "@tanstack/react-query";
|
|
4239
|
+
import { useEffect as useEffect12, useCallback as useCallback9, useRef as useRef5 } from "react";
|
|
4240
|
+
async function fetchTunnelStatus() {
|
|
4241
|
+
const response = await fetch(`${API_BASE_URL}/v1/tunnel/status`);
|
|
4242
|
+
if (!response.ok) {
|
|
4243
|
+
throw new Error("Failed to fetch tunnel status");
|
|
4244
|
+
}
|
|
4245
|
+
return response.json();
|
|
4246
|
+
}
|
|
4247
|
+
async function startTunnel() {
|
|
4248
|
+
const response = await fetch(`${API_BASE_URL}/v1/tunnel/start`, {
|
|
4249
|
+
method: "POST",
|
|
4250
|
+
headers: { "Content-Type": "application/json" },
|
|
4251
|
+
body: JSON.stringify({})
|
|
4252
|
+
});
|
|
4253
|
+
return response.json();
|
|
4254
|
+
}
|
|
4255
|
+
async function stopTunnel() {
|
|
4256
|
+
const response = await fetch(`${API_BASE_URL}/v1/tunnel/stop`, {
|
|
4257
|
+
method: "POST"
|
|
4258
|
+
});
|
|
4259
|
+
return response.json();
|
|
4260
|
+
}
|
|
4261
|
+
async function fetchTunnelQr() {
|
|
4262
|
+
const response = await fetch(`${API_BASE_URL}/v1/tunnel/qr`);
|
|
4263
|
+
return response.json();
|
|
4264
|
+
}
|
|
4265
|
+
function useTunnelStatus() {
|
|
4266
|
+
const setStatus = useTunnelStore((s) => s.setStatus);
|
|
4267
|
+
const setUrl = useTunnelStore((s) => s.setUrl);
|
|
4268
|
+
const setError = useTunnelStore((s) => s.setError);
|
|
4269
|
+
const query = useQuery10({
|
|
4270
|
+
queryKey: ["tunnel", "status"],
|
|
4271
|
+
queryFn: fetchTunnelStatus,
|
|
4272
|
+
refetchInterval: 3000
|
|
4273
|
+
});
|
|
4274
|
+
useEffect12(() => {
|
|
4275
|
+
if (query.data) {
|
|
4276
|
+
setStatus(query.data.status);
|
|
4277
|
+
setUrl(query.data.url);
|
|
4278
|
+
setError(query.data.error);
|
|
4279
|
+
}
|
|
4280
|
+
}, [query.data, setStatus, setUrl, setError]);
|
|
4281
|
+
return query;
|
|
4282
|
+
}
|
|
4283
|
+
function useStartTunnel() {
|
|
4284
|
+
const queryClient = useQueryClient9();
|
|
4285
|
+
const setStatus = useTunnelStore((s) => s.setStatus);
|
|
4286
|
+
const setUrl = useTunnelStore((s) => s.setUrl);
|
|
4287
|
+
const setError = useTunnelStore((s) => s.setError);
|
|
4288
|
+
const setProgress = useTunnelStore((s) => s.setProgress);
|
|
4289
|
+
return useMutation7({
|
|
4290
|
+
mutationFn: () => startTunnel(),
|
|
4291
|
+
onMutate: () => {
|
|
4292
|
+
setStatus("starting");
|
|
4293
|
+
setProgress("Connecting...");
|
|
4294
|
+
setError(null);
|
|
4295
|
+
},
|
|
4296
|
+
onSuccess: (data) => {
|
|
4297
|
+
if (data.ok && data.url) {
|
|
4298
|
+
setStatus("connected");
|
|
4299
|
+
setUrl(data.url);
|
|
4300
|
+
setProgress(null);
|
|
4301
|
+
} else if (!data.ok) {
|
|
4302
|
+
setStatus("error");
|
|
4303
|
+
setError(data.error || "Failed to start tunnel");
|
|
4304
|
+
setProgress(null);
|
|
4305
|
+
}
|
|
4306
|
+
queryClient.invalidateQueries({ queryKey: ["tunnel"] });
|
|
4307
|
+
},
|
|
4308
|
+
onError: (error) => {
|
|
4309
|
+
setStatus("error");
|
|
4310
|
+
setError(error.message);
|
|
4311
|
+
setProgress(null);
|
|
4312
|
+
}
|
|
4313
|
+
});
|
|
4314
|
+
}
|
|
4315
|
+
function useStopTunnel() {
|
|
4316
|
+
const queryClient = useQueryClient9();
|
|
4317
|
+
const reset = useTunnelStore((s) => s.reset);
|
|
4318
|
+
return useMutation7({
|
|
4319
|
+
mutationFn: stopTunnel,
|
|
4320
|
+
onSuccess: () => {
|
|
4321
|
+
reset();
|
|
4322
|
+
queryClient.invalidateQueries({ queryKey: ["tunnel"] });
|
|
4323
|
+
}
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
function useTunnelQr() {
|
|
4327
|
+
const url = useTunnelStore((s) => s.url);
|
|
4328
|
+
const setQrCode = useTunnelStore((s) => s.setQrCode);
|
|
4329
|
+
const query = useQuery10({
|
|
4330
|
+
queryKey: ["tunnel", "qr", url],
|
|
4331
|
+
queryFn: fetchTunnelQr,
|
|
4332
|
+
enabled: !!url
|
|
4333
|
+
});
|
|
4334
|
+
useEffect12(() => {
|
|
4335
|
+
if (query.data?.ok && query.data.qrCode) {
|
|
4336
|
+
setQrCode(query.data.qrCode);
|
|
4337
|
+
}
|
|
4338
|
+
}, [query.data, setQrCode]);
|
|
4339
|
+
return query;
|
|
4340
|
+
}
|
|
4341
|
+
function useTunnelStream() {
|
|
4342
|
+
const setStatus = useTunnelStore((s) => s.setStatus);
|
|
4343
|
+
const setUrl = useTunnelStore((s) => s.setUrl);
|
|
4344
|
+
const setError = useTunnelStore((s) => s.setError);
|
|
4345
|
+
const setProgress = useTunnelStore((s) => s.setProgress);
|
|
4346
|
+
const isExpanded = useTunnelStore((s) => s.isExpanded);
|
|
4347
|
+
const eventSourceRef = useRef5(null);
|
|
4348
|
+
const connect = useCallback9(() => {
|
|
4349
|
+
if (eventSourceRef.current) {
|
|
4350
|
+
eventSourceRef.current.close();
|
|
4351
|
+
}
|
|
4352
|
+
const es = new EventSource(`${API_BASE_URL}/v1/tunnel/stream`);
|
|
4353
|
+
eventSourceRef.current = es;
|
|
4354
|
+
es.onmessage = (event) => {
|
|
4355
|
+
try {
|
|
4356
|
+
const data = JSON.parse(event.data);
|
|
4357
|
+
if (data.type === "status") {
|
|
4358
|
+
setStatus(data.status);
|
|
4359
|
+
setUrl(data.url);
|
|
4360
|
+
setError(data.error);
|
|
4361
|
+
setProgress(data.progress);
|
|
4362
|
+
}
|
|
4363
|
+
} catch {}
|
|
4364
|
+
};
|
|
4365
|
+
es.onerror = () => {
|
|
4366
|
+
es.close();
|
|
4367
|
+
eventSourceRef.current = null;
|
|
4368
|
+
};
|
|
4369
|
+
return () => {
|
|
4370
|
+
es.close();
|
|
4371
|
+
eventSourceRef.current = null;
|
|
4372
|
+
};
|
|
4373
|
+
}, [setStatus, setUrl, setError, setProgress]);
|
|
4374
|
+
useEffect12(() => {
|
|
4375
|
+
if (isExpanded) {
|
|
4376
|
+
const cleanup = connect();
|
|
4377
|
+
return cleanup;
|
|
4378
|
+
}
|
|
4379
|
+
return () => {
|
|
4380
|
+
if (eventSourceRef.current) {
|
|
4381
|
+
eventSourceRef.current.close();
|
|
4382
|
+
eventSourceRef.current = null;
|
|
4383
|
+
}
|
|
4384
|
+
};
|
|
4385
|
+
}, [isExpanded, connect]);
|
|
4386
|
+
return { connect };
|
|
4387
|
+
}
|
|
4388
|
+
// src/hooks/useProviderUsage.ts
|
|
4389
|
+
import { useEffect as useEffect13, useCallback as useCallback10 } from "react";
|
|
4390
|
+
|
|
4391
|
+
// src/stores/usageStore.ts
|
|
4392
|
+
import { create as create18 } from "zustand";
|
|
4393
|
+
var useUsageStore = create18((set) => ({
|
|
4394
|
+
usage: {},
|
|
4395
|
+
isLoading: {},
|
|
4396
|
+
lastFetched: {},
|
|
4397
|
+
isModalOpen: false,
|
|
4398
|
+
modalProvider: null,
|
|
4399
|
+
setUsage: (provider, data) => set((s) => ({ usage: { ...s.usage, [provider]: data } })),
|
|
4400
|
+
setLoading: (provider, loading) => set((s) => ({ isLoading: { ...s.isLoading, [provider]: loading } })),
|
|
4401
|
+
setLastFetched: (provider, time) => set((s) => ({ lastFetched: { ...s.lastFetched, [provider]: time } })),
|
|
4402
|
+
openModal: (provider) => set({ isModalOpen: true, modalProvider: provider }),
|
|
4403
|
+
closeModal: () => set({ isModalOpen: false, modalProvider: null })
|
|
4404
|
+
}));
|
|
4405
|
+
|
|
4406
|
+
// src/hooks/useProviderUsage.ts
|
|
4407
|
+
var POLL_INTERVAL = 60000;
|
|
4408
|
+
var STALE_THRESHOLD = 30000;
|
|
4409
|
+
function useProviderUsage(provider, authType) {
|
|
4410
|
+
const setUsage = useUsageStore((s) => s.setUsage);
|
|
4411
|
+
const setLoading = useUsageStore((s) => s.setLoading);
|
|
4412
|
+
const setLastFetched = useUsageStore((s) => s.setLastFetched);
|
|
4413
|
+
const usage = useUsageStore((s) => provider ? s.usage[provider] : undefined);
|
|
4414
|
+
const lastFetched = useUsageStore((s) => provider ? s.lastFetched[provider] : 0);
|
|
4415
|
+
const isOAuthProvider = authType === "oauth" && (provider === "anthropic" || provider === "openai");
|
|
4416
|
+
const fetchUsage = useCallback10(async () => {
|
|
4417
|
+
if (!provider || !isOAuthProvider)
|
|
4418
|
+
return;
|
|
4419
|
+
setLoading(provider, true);
|
|
4420
|
+
try {
|
|
4421
|
+
const data = await apiClient.getProviderUsage(provider);
|
|
4422
|
+
setUsage(provider, data);
|
|
4423
|
+
setLastFetched(provider, Date.now());
|
|
4424
|
+
} catch {} finally {
|
|
4425
|
+
setLoading(provider, false);
|
|
4426
|
+
}
|
|
4427
|
+
}, [provider, isOAuthProvider, setUsage, setLoading, setLastFetched]);
|
|
4428
|
+
useEffect13(() => {
|
|
4429
|
+
if (!isOAuthProvider)
|
|
4430
|
+
return;
|
|
4431
|
+
const isStale = !lastFetched || Date.now() - lastFetched > STALE_THRESHOLD;
|
|
4432
|
+
if (isStale) {
|
|
4433
|
+
fetchUsage();
|
|
4434
|
+
}
|
|
4435
|
+
const interval = setInterval(fetchUsage, POLL_INTERVAL);
|
|
4436
|
+
return () => clearInterval(interval);
|
|
4437
|
+
}, [isOAuthProvider, fetchUsage, lastFetched]);
|
|
4438
|
+
return {
|
|
4439
|
+
usage,
|
|
4440
|
+
fetchUsage,
|
|
4441
|
+
isOAuthProvider
|
|
4442
|
+
};
|
|
4443
|
+
}
|
|
4444
|
+
// src/hooks/useFileBrowser.ts
|
|
4445
|
+
import { useQuery as useQuery11 } from "@tanstack/react-query";
|
|
4446
|
+
function useFileTree(dirPath, enabled = true) {
|
|
4447
|
+
return useQuery11({
|
|
4448
|
+
queryKey: ["files", "tree", dirPath],
|
|
4449
|
+
queryFn: () => apiClient.getFileTree(dirPath),
|
|
4450
|
+
enabled,
|
|
4451
|
+
staleTime: 1e4,
|
|
4452
|
+
retry: 1
|
|
4453
|
+
});
|
|
4454
|
+
}
|
|
4455
|
+
function useFileContent(filePath) {
|
|
4456
|
+
return useQuery11({
|
|
4457
|
+
queryKey: ["files", "read", filePath],
|
|
4458
|
+
queryFn: () => filePath ? apiClient.readFileContent(filePath) : null,
|
|
4459
|
+
enabled: !!filePath,
|
|
4460
|
+
staleTime: 5000,
|
|
4461
|
+
retry: 1
|
|
4462
|
+
});
|
|
4463
|
+
}
|
|
4464
|
+
function useGitDiffFullFile(file, staged = false, enabled = false) {
|
|
4465
|
+
return useQuery11({
|
|
4466
|
+
queryKey: ["git", "diff", "fullFile", file, staged],
|
|
4467
|
+
queryFn: () => file ? apiClient.getGitDiffFullFile(file, staged) : null,
|
|
4468
|
+
enabled: enabled && !!file,
|
|
4469
|
+
retry: 1,
|
|
4470
|
+
refetchInterval: false
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
// src/hooks/useMCP.ts
|
|
4474
|
+
import { useQuery as useQuery12, useMutation as useMutation8, useQueryClient as useQueryClient10 } from "@tanstack/react-query";
|
|
4475
|
+
import { useEffect as useEffect14, useRef as useRef6, useCallback as useCallback11 } from "react";
|
|
4476
|
+
import {
|
|
4477
|
+
listMcpServers,
|
|
4478
|
+
startMcpServer,
|
|
4479
|
+
stopMcpServer,
|
|
4480
|
+
addMcpServer,
|
|
4481
|
+
removeMcpServer,
|
|
4482
|
+
initiateMcpAuth,
|
|
4483
|
+
revokeMcpAuth,
|
|
4484
|
+
getMcpAuthStatus,
|
|
4485
|
+
completeMcpAuth
|
|
4486
|
+
} from "@ottocode/api";
|
|
4487
|
+
function useMCPServers() {
|
|
4488
|
+
const setServers = useMCPStore((s) => s.setServers);
|
|
4489
|
+
const query = useQuery12({
|
|
4490
|
+
queryKey: ["mcp", "servers"],
|
|
4491
|
+
queryFn: async () => {
|
|
4492
|
+
const { data } = await listMcpServers();
|
|
4493
|
+
return data;
|
|
4494
|
+
},
|
|
4495
|
+
refetchInterval: 1e4
|
|
4496
|
+
});
|
|
4497
|
+
useEffect14(() => {
|
|
4498
|
+
if (query.data?.servers) {
|
|
4499
|
+
setServers(query.data.servers);
|
|
4500
|
+
}
|
|
4501
|
+
}, [query.data, setServers]);
|
|
4502
|
+
return query;
|
|
4503
|
+
}
|
|
4504
|
+
function useStartMCPServer() {
|
|
4505
|
+
const queryClient = useQueryClient10();
|
|
4506
|
+
return useMutation8({
|
|
4507
|
+
mutationFn: async (name) => {
|
|
4508
|
+
const { data, error } = await startMcpServer({
|
|
4509
|
+
path: { name }
|
|
4510
|
+
});
|
|
4511
|
+
if (error)
|
|
4512
|
+
throw new Error("Failed to start server");
|
|
4513
|
+
const result = data;
|
|
4514
|
+
if (!result.ok)
|
|
4515
|
+
throw new Error(result.error || "Failed to start server");
|
|
4516
|
+
return result;
|
|
4517
|
+
},
|
|
4518
|
+
onSettled: () => {
|
|
4519
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
function useStopMCPServer() {
|
|
4524
|
+
const queryClient = useQueryClient10();
|
|
4525
|
+
const setLoading = useMCPStore((s) => s.setLoading);
|
|
4526
|
+
return useMutation8({
|
|
4527
|
+
mutationFn: async (name) => {
|
|
4528
|
+
setLoading(name, true);
|
|
4529
|
+
const { data, error } = await stopMcpServer({
|
|
4530
|
+
path: { name }
|
|
4531
|
+
});
|
|
4532
|
+
if (error)
|
|
4533
|
+
throw new Error("Failed to stop server");
|
|
4534
|
+
const result = data;
|
|
4535
|
+
if (!result.ok)
|
|
4536
|
+
throw new Error(result.error || "Failed to stop server");
|
|
4537
|
+
return result;
|
|
4538
|
+
},
|
|
4539
|
+
onSettled: (_data, _error, name) => {
|
|
4540
|
+
setLoading(name, false);
|
|
4541
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4542
|
+
}
|
|
4543
|
+
});
|
|
4544
|
+
}
|
|
4545
|
+
function useAddMCPServer() {
|
|
4546
|
+
const queryClient = useQueryClient10();
|
|
4547
|
+
return useMutation8({
|
|
4548
|
+
mutationFn: async (params) => {
|
|
4549
|
+
const { data, error } = await addMcpServer({
|
|
4550
|
+
body: params
|
|
4551
|
+
});
|
|
4552
|
+
if (error)
|
|
4553
|
+
throw new Error("Failed to add MCP server");
|
|
4554
|
+
const result = data;
|
|
4555
|
+
if (!result.ok)
|
|
4556
|
+
throw new Error(result.error || "Failed to add MCP server");
|
|
4557
|
+
return result;
|
|
4558
|
+
},
|
|
4559
|
+
onSuccess: () => {
|
|
4560
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4561
|
+
}
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
function useRemoveMCPServer() {
|
|
4565
|
+
const queryClient = useQueryClient10();
|
|
4566
|
+
return useMutation8({
|
|
4567
|
+
mutationFn: async (name) => {
|
|
4568
|
+
const { data, error } = await removeMcpServer({
|
|
4569
|
+
path: { name }
|
|
4570
|
+
});
|
|
4571
|
+
if (error)
|
|
4572
|
+
throw new Error("Failed to remove MCP server");
|
|
4573
|
+
const result = data;
|
|
4574
|
+
if (!result.ok)
|
|
4575
|
+
throw new Error(result.error || "Failed to remove MCP server");
|
|
4576
|
+
return result;
|
|
4577
|
+
},
|
|
4578
|
+
onSuccess: () => {
|
|
4579
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4580
|
+
}
|
|
4581
|
+
});
|
|
4582
|
+
}
|
|
4583
|
+
function useAuthenticateMCPServer() {
|
|
4584
|
+
const queryClient = useQueryClient10();
|
|
4585
|
+
return useMutation8({
|
|
4586
|
+
mutationFn: async (name) => {
|
|
4587
|
+
const { data, error } = await initiateMcpAuth({
|
|
4588
|
+
path: { name }
|
|
4589
|
+
});
|
|
4590
|
+
if (error)
|
|
4591
|
+
throw new Error("Failed to initiate auth");
|
|
4592
|
+
const result = data;
|
|
4593
|
+
if (!result.ok)
|
|
4594
|
+
throw new Error(result.error || "Failed to initiate auth");
|
|
4595
|
+
return result;
|
|
4596
|
+
},
|
|
4597
|
+
onSettled: () => {
|
|
4598
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
function useRevokeMCPAuth() {
|
|
4603
|
+
const queryClient = useQueryClient10();
|
|
4604
|
+
return useMutation8({
|
|
4605
|
+
mutationFn: async (name) => {
|
|
4606
|
+
const { data, error } = await revokeMcpAuth({
|
|
4607
|
+
path: { name }
|
|
4608
|
+
});
|
|
4609
|
+
if (error)
|
|
4610
|
+
throw new Error("Failed to revoke auth");
|
|
4611
|
+
const result = data;
|
|
4612
|
+
if (!result.ok)
|
|
4613
|
+
throw new Error(result.error || "Failed to revoke auth");
|
|
4614
|
+
return result;
|
|
4615
|
+
},
|
|
4616
|
+
onSuccess: () => {
|
|
4617
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4618
|
+
}
|
|
4619
|
+
});
|
|
4620
|
+
}
|
|
4621
|
+
function useMCPAuthStatus(name) {
|
|
4622
|
+
return useQuery12({
|
|
4623
|
+
queryKey: ["mcp", "auth", name],
|
|
4624
|
+
queryFn: async () => {
|
|
4625
|
+
if (!name)
|
|
4626
|
+
return { authenticated: false };
|
|
4627
|
+
const { data } = await getMcpAuthStatus({
|
|
4628
|
+
path: { name }
|
|
4629
|
+
});
|
|
4630
|
+
return data;
|
|
4631
|
+
},
|
|
4632
|
+
enabled: !!name,
|
|
4633
|
+
refetchInterval: 3000
|
|
4634
|
+
});
|
|
4635
|
+
}
|
|
4636
|
+
function useCopilotDevicePoller() {
|
|
4637
|
+
const copilotDevice = useMCPStore((s) => s.copilotDevice);
|
|
4638
|
+
const setCopilotDevice = useMCPStore((s) => s.setCopilotDevice);
|
|
4639
|
+
const setLoading = useMCPStore((s) => s.setLoading);
|
|
4640
|
+
const queryClient = useQueryClient10();
|
|
4641
|
+
const timerRef = useRef6(null);
|
|
4642
|
+
const stopPolling = useCallback11(() => {
|
|
4643
|
+
if (timerRef.current) {
|
|
4644
|
+
clearInterval(timerRef.current);
|
|
4645
|
+
timerRef.current = null;
|
|
4646
|
+
}
|
|
4647
|
+
}, []);
|
|
4648
|
+
useEffect14(() => {
|
|
4649
|
+
if (!copilotDevice) {
|
|
4650
|
+
stopPolling();
|
|
4651
|
+
return;
|
|
4652
|
+
}
|
|
4653
|
+
const { sessionId, serverName, interval } = copilotDevice;
|
|
4654
|
+
const pollMs = (interval || 5) * 1000 + 1000;
|
|
4655
|
+
timerRef.current = setInterval(async () => {
|
|
4656
|
+
try {
|
|
4657
|
+
const { data } = await completeMcpAuth({
|
|
4658
|
+
path: { name: serverName },
|
|
4659
|
+
body: { sessionId }
|
|
4660
|
+
});
|
|
4661
|
+
const result = data;
|
|
4662
|
+
if (result?.status === "complete") {
|
|
4663
|
+
stopPolling();
|
|
4664
|
+
setCopilotDevice(null);
|
|
4665
|
+
setLoading(serverName, false);
|
|
4666
|
+
queryClient.invalidateQueries({ queryKey: ["mcp", "servers"] });
|
|
4667
|
+
} else if (result?.status === "error") {
|
|
4668
|
+
stopPolling();
|
|
4669
|
+
setCopilotDevice(null);
|
|
4670
|
+
setLoading(serverName, false);
|
|
4671
|
+
}
|
|
4672
|
+
} catch {
|
|
4673
|
+
stopPolling();
|
|
4674
|
+
setCopilotDevice(null);
|
|
4675
|
+
setLoading(serverName, false);
|
|
4676
|
+
}
|
|
4677
|
+
}, pollMs);
|
|
4678
|
+
return stopPolling;
|
|
4679
|
+
}, [copilotDevice, setCopilotDevice, setLoading, queryClient, stopPolling]);
|
|
4680
|
+
return copilotDevice;
|
|
4681
|
+
}
|
|
4682
|
+
// src/hooks/useSkills.ts
|
|
4683
|
+
import { useQuery as useQuery13 } from "@tanstack/react-query";
|
|
4684
|
+
import { useEffect as useEffect15 } from "react";
|
|
4685
|
+
function useSkills() {
|
|
4686
|
+
const setSkills = useSkillsStore((s) => s.setSkills);
|
|
4687
|
+
const query = useQuery13({
|
|
4688
|
+
queryKey: ["skills"],
|
|
4689
|
+
queryFn: async () => {
|
|
4690
|
+
return apiClient.listSkills();
|
|
4691
|
+
},
|
|
4692
|
+
refetchInterval: 30000
|
|
4693
|
+
});
|
|
4694
|
+
useEffect15(() => {
|
|
4695
|
+
if (query.data?.skills) {
|
|
4696
|
+
setSkills(query.data.skills);
|
|
4697
|
+
}
|
|
4698
|
+
}, [query.data, setSkills]);
|
|
4699
|
+
return query;
|
|
4700
|
+
}
|
|
4701
|
+
function useSkillDetail(name) {
|
|
4702
|
+
return useQuery13({
|
|
4703
|
+
queryKey: ["skills", name],
|
|
4704
|
+
queryFn: async () => {
|
|
4705
|
+
if (!name)
|
|
4706
|
+
return null;
|
|
4707
|
+
return apiClient.getSkill(name);
|
|
4708
|
+
},
|
|
4709
|
+
enabled: !!name
|
|
4710
|
+
});
|
|
4711
|
+
}
|
|
4712
|
+
function useSkillFiles(name) {
|
|
4713
|
+
return useQuery13({
|
|
4714
|
+
queryKey: ["skills", name, "files"],
|
|
4715
|
+
queryFn: async () => {
|
|
4716
|
+
if (!name)
|
|
4717
|
+
return null;
|
|
4718
|
+
return apiClient.getSkillFiles(name);
|
|
4719
|
+
},
|
|
4720
|
+
enabled: !!name
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4723
|
+
function useSkillFileContent(name, filePath) {
|
|
4724
|
+
return useQuery13({
|
|
4725
|
+
queryKey: ["skills", name, "files", filePath],
|
|
4726
|
+
queryFn: async () => {
|
|
4727
|
+
if (!name || !filePath)
|
|
4728
|
+
return null;
|
|
4729
|
+
return apiClient.getSkillFileContent(name, filePath);
|
|
4730
|
+
},
|
|
4731
|
+
enabled: !!name && !!filePath
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4734
|
+
export {
|
|
4735
|
+
useWorkingDirectory,
|
|
4736
|
+
useUpdateSession,
|
|
4737
|
+
useUpdateDefaults,
|
|
4738
|
+
useUnstageFiles,
|
|
4739
|
+
useTunnelStream,
|
|
4740
|
+
useTunnelStatus,
|
|
4741
|
+
useTunnelQr,
|
|
4742
|
+
useTopupCallback,
|
|
4743
|
+
useToolApprovalShortcuts,
|
|
4744
|
+
useTheme,
|
|
4745
|
+
useStopTunnel,
|
|
4746
|
+
useStopMCPServer,
|
|
4747
|
+
useStartTunnel,
|
|
4748
|
+
useStartMCPServer,
|
|
4749
|
+
useStageFiles,
|
|
4750
|
+
useSkills,
|
|
4751
|
+
useSkillFiles,
|
|
4752
|
+
useSkillFileContent,
|
|
4753
|
+
useSkillDetail,
|
|
4754
|
+
useShareStatus,
|
|
4755
|
+
useSetuPayments,
|
|
4756
|
+
useSetuBalance,
|
|
4757
|
+
useSessionsInfinite,
|
|
4758
|
+
useSessions,
|
|
4759
|
+
useSessionStream,
|
|
4760
|
+
useSessionFiles,
|
|
4761
|
+
useSession,
|
|
4762
|
+
useSendMessage,
|
|
4763
|
+
useRevokeMCPAuth,
|
|
4764
|
+
useRestoreFiles,
|
|
4765
|
+
useResearchSessions,
|
|
4766
|
+
useRemoveRemote,
|
|
4767
|
+
useRemoveMCPServer,
|
|
4768
|
+
useQueueState,
|
|
4769
|
+
usePushCommits,
|
|
4770
|
+
usePullChanges,
|
|
4771
|
+
useProviderUsage,
|
|
4772
|
+
usePreferences,
|
|
4773
|
+
useParentSession,
|
|
4774
|
+
useModels,
|
|
4775
|
+
useMessages,
|
|
4776
|
+
useMessageQueuePosition,
|
|
4777
|
+
useMCPServers,
|
|
4778
|
+
useMCPAuthStatus,
|
|
4779
|
+
useKeyboardShortcuts,
|
|
4780
|
+
useInjectContext,
|
|
4781
|
+
useImageUpload,
|
|
4782
|
+
useGitStatus,
|
|
4783
|
+
useGitRemotes,
|
|
4784
|
+
useGitInit,
|
|
4785
|
+
useGitDiffFullFile,
|
|
4786
|
+
useGitDiff,
|
|
4787
|
+
useGitBranch,
|
|
4788
|
+
useGenerateCommitMessage,
|
|
4789
|
+
useFiles,
|
|
4790
|
+
useFileUpload,
|
|
4791
|
+
useFileTree,
|
|
4792
|
+
useFileContent,
|
|
4793
|
+
useExportToSession,
|
|
4794
|
+
useDeleteSession,
|
|
4795
|
+
useDeleteResearchSession,
|
|
4796
|
+
useDeleteFiles,
|
|
4797
|
+
useCreateSession,
|
|
4798
|
+
useCreateResearchSession,
|
|
4799
|
+
useCreateBranch,
|
|
4800
|
+
useCopilotDevicePoller,
|
|
4801
|
+
useConfig,
|
|
4802
|
+
useCommitChanges,
|
|
4803
|
+
useBranches,
|
|
4804
|
+
useAuthenticateMCPServer,
|
|
4805
|
+
useAuthStatus,
|
|
4806
|
+
useAllModels,
|
|
4807
|
+
useAddRemote,
|
|
4808
|
+
useAddMCPServer,
|
|
4809
|
+
sessionsQueryKey
|
|
4810
|
+
};
|
|
4811
|
+
|
|
4812
|
+
//# debugId=2C17502CCCF7892C64756E2164756E21
|