diff --git a/src/components/providers/ClaudeSection/ClaudeModal.tsx b/src/components/providers/ClaudeSection/ClaudeModal.tsx index 7c5c5a1..980a933 100644 --- a/src/components/providers/ClaudeSection/ClaudeModal.tsx +++ b/src/components/providers/ClaudeSection/ClaudeModal.tsx @@ -4,7 +4,8 @@ import { Button } from '@/components/ui/Button'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; -import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; +import { ModelInputList } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import type { ProviderKeyConfig } from '@/types'; import { headersToEntries } from '@/utils/headers'; import { excludedModelsToText } from '../utils'; diff --git a/src/components/providers/CodexSection/CodexModal.tsx b/src/components/providers/CodexSection/CodexModal.tsx index 8e84bf2..0dbe0ec 100644 --- a/src/components/providers/CodexSection/CodexModal.tsx +++ b/src/components/providers/CodexSection/CodexModal.tsx @@ -6,7 +6,7 @@ import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; import type { ProviderKeyConfig } from '@/types'; import { headersToEntries } from '@/utils/headers'; -import { modelsToEntries } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import { excludedModelsToText } from '../utils'; import type { ProviderFormState, ProviderModalProps } from '../types'; diff --git a/src/components/providers/OpenAISection/OpenAIModal.tsx b/src/components/providers/OpenAISection/OpenAIModal.tsx index d9ff0c7..3d76e4a 100644 --- a/src/components/providers/OpenAISection/OpenAIModal.tsx +++ b/src/components/providers/OpenAISection/OpenAIModal.tsx @@ -4,7 +4,8 @@ import { Button } from '@/components/ui/Button'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; -import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; +import { ModelInputList } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import { useNotificationStore } from '@/stores'; import { apiCallApi, getApiCallErrorMessage } from '@/services/api'; import type { OpenAIProviderConfig, ApiKeyEntry } from '@/types'; diff --git a/src/components/providers/ProviderNav/ProviderNav.tsx b/src/components/providers/ProviderNav/ProviderNav.tsx index f716ece..ef4ef3e 100644 --- a/src/components/providers/ProviderNav/ProviderNav.tsx +++ b/src/components/providers/ProviderNav/ProviderNav.tsx @@ -29,14 +29,8 @@ const PROVIDERS: ProviderNavItem[] = [ export function ProviderNav() { const resolvedTheme = useThemeStore((state) => state.resolvedTheme); const [activeProvider, setActiveProvider] = useState(null); - const [mounted, setMounted] = useState(false); const scrollContainerRef = useRef(null); - useEffect(() => { - setMounted(true); - return () => setMounted(false); - }, []); - const getScrollContainer = useCallback(() => { if (scrollContainerRef.current) return scrollContainerRef.current; const container = document.querySelector('.content') as HTMLElement | null; @@ -74,7 +68,6 @@ export function ProviderNav() { const container = getScrollContainer(); if (!container) return; - handleScroll(); container.addEventListener('scroll', handleScroll, { passive: true }); return () => container.removeEventListener('scroll', handleScroll); }, [handleScroll, getScrollContainer]); @@ -120,7 +113,7 @@ export function ProviderNav() { ); - if (!mounted) return null; + if (typeof document === 'undefined') return null; return createPortal(navContent, document.body); } diff --git a/src/components/providers/VertexSection/VertexModal.tsx b/src/components/providers/VertexSection/VertexModal.tsx index 169af79..1eb2f91 100644 --- a/src/components/providers/VertexSection/VertexModal.tsx +++ b/src/components/providers/VertexSection/VertexModal.tsx @@ -4,7 +4,8 @@ import { Button } from '@/components/ui/Button'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; import { Input } from '@/components/ui/Input'; import { Modal } from '@/components/ui/Modal'; -import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; +import { ModelInputList } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import type { ProviderKeyConfig } from '@/types'; import { headersToEntries } from '@/utils/headers'; import type { ProviderModalProps, VertexFormState } from '../types'; diff --git a/src/components/ui/ModelInputList.tsx b/src/components/ui/ModelInputList.tsx index e4340ac..7616330 100644 --- a/src/components/ui/ModelInputList.tsx +++ b/src/components/ui/ModelInputList.tsx @@ -1,12 +1,7 @@ import { Fragment } from 'react'; import { Button } from './Button'; import { IconX } from './icons'; -import type { ModelAlias } from '@/types'; - -interface ModelEntry { - name: string; - alias: string; -} +import type { ModelEntry } from './modelInputListUtils'; interface ModelInputListProps { entries: ModelEntry[]; @@ -17,29 +12,6 @@ interface ModelInputListProps { aliasPlaceholder?: string; } -export const modelsToEntries = (models?: ModelAlias[]): ModelEntry[] => { - if (!Array.isArray(models) || models.length === 0) { - return [{ name: '', alias: '' }]; - } - return models.map((m) => ({ - name: m.name || '', - alias: m.alias || '' - })); -}; - -export const entriesToModels = (entries: ModelEntry[]): ModelAlias[] => { - return entries - .filter((entry) => entry.name.trim()) - .map((entry) => { - const model: ModelAlias = { name: entry.name.trim() }; - const alias = entry.alias.trim(); - if (alias && alias !== model.name) { - model.alias = alias; - } - return model; - }); -}; - export function ModelInputList({ entries, onChange, diff --git a/src/components/ui/modelInputListUtils.ts b/src/components/ui/modelInputListUtils.ts new file mode 100644 index 0000000..d7f8453 --- /dev/null +++ b/src/components/ui/modelInputListUtils.ts @@ -0,0 +1,29 @@ +import type { ModelAlias } from '@/types'; + +export interface ModelEntry { + name: string; + alias: string; +} + +export const modelsToEntries = (models?: ModelAlias[]): ModelEntry[] => { + if (!Array.isArray(models) || models.length === 0) { + return [{ name: '', alias: '' }]; + } + return models.map((model) => ({ + name: model.name || '', + alias: model.alias || '' + })); +}; + +export const entriesToModels = (entries: ModelEntry[]): ModelAlias[] => { + return entries + .filter((entry) => entry.name.trim()) + .map((entry) => { + const model: ModelAlias = { name: entry.name.trim() }; + const alias = entry.alias.trim(); + if (alias && alias !== model.name) { + model.alias = alias; + } + return model; + }); +}; diff --git a/src/pages/AiProvidersClaudeEditPage.tsx b/src/pages/AiProvidersClaudeEditPage.tsx index dcf4384..bccf7d6 100644 --- a/src/pages/AiProvidersClaudeEditPage.tsx +++ b/src/pages/AiProvidersClaudeEditPage.tsx @@ -5,7 +5,8 @@ import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; -import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; +import { ModelInputList } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import { useEdgeSwipeBack } from '@/hooks/useEdgeSwipeBack'; import { SecondaryScreenShell } from '@/components/common/SecondaryScreenShell'; import { providersApi } from '@/services/api'; diff --git a/src/pages/AiProvidersCodexEditPage.tsx b/src/pages/AiProvidersCodexEditPage.tsx index f17faaf..5685682 100644 --- a/src/pages/AiProvidersCodexEditPage.tsx +++ b/src/pages/AiProvidersCodexEditPage.tsx @@ -11,7 +11,7 @@ import { providersApi } from '@/services/api'; import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; import type { ProviderKeyConfig } from '@/types'; import { buildHeaderObject, headersToEntries } from '@/utils/headers'; -import { entriesToModels } from '@/components/ui/ModelInputList'; +import { entriesToModels } from '@/components/ui/modelInputListUtils'; import { excludedModelsToText, parseExcludedModels } from '@/components/providers/utils'; import type { ProviderFormState } from '@/components/providers'; import layoutStyles from './AiProvidersEditLayout.module.scss'; diff --git a/src/pages/AiProvidersOpenAIEditLayout.tsx b/src/pages/AiProvidersOpenAIEditLayout.tsx index f165b6d..70280b1 100644 --- a/src/pages/AiProvidersOpenAIEditLayout.tsx +++ b/src/pages/AiProvidersOpenAIEditLayout.tsx @@ -4,7 +4,7 @@ import { Outlet, useLocation, useNavigate, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { providersApi } from '@/services/api'; import { useAuthStore, useConfigStore, useNotificationStore } from '@/stores'; -import { entriesToModels, modelsToEntries } from '@/components/ui/ModelInputList'; +import { entriesToModels, modelsToEntries } from '@/components/ui/modelInputListUtils'; import type { ApiKeyEntry, OpenAIProviderConfig } from '@/types'; import type { ModelInfo } from '@/utils/models'; import { buildHeaderObject, headersToEntries } from '@/utils/headers'; diff --git a/src/pages/AiProvidersVertexEditPage.tsx b/src/pages/AiProvidersVertexEditPage.tsx index de15a66..f02792c 100644 --- a/src/pages/AiProvidersVertexEditPage.tsx +++ b/src/pages/AiProvidersVertexEditPage.tsx @@ -5,7 +5,8 @@ import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { HeaderInputList } from '@/components/ui/HeaderInputList'; -import { ModelInputList, modelsToEntries } from '@/components/ui/ModelInputList'; +import { ModelInputList } from '@/components/ui/ModelInputList'; +import { modelsToEntries } from '@/components/ui/modelInputListUtils'; import { useEdgeSwipeBack } from '@/hooks/useEdgeSwipeBack'; import { SecondaryScreenShell } from '@/components/common/SecondaryScreenShell'; import { providersApi } from '@/services/api'; diff --git a/src/pages/OAuthPage.tsx b/src/pages/OAuthPage.tsx index a21beae..d4aae80 100644 --- a/src/pages/OAuthPage.tsx +++ b/src/pages/OAuthPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, type ChangeEvent } from 'react'; +import { useCallback, useEffect, useRef, useState, type ChangeEvent } from 'react'; import { useTranslation } from 'react-i18next'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; @@ -85,11 +85,16 @@ export function OAuthPage() { const timers = useRef>({}); const vertexFileInputRef = useRef(null); + const clearTimers = useCallback(() => { + Object.values(timers.current).forEach((timer) => window.clearInterval(timer)); + timers.current = {}; + }, []); + useEffect(() => { return () => { - Object.values(timers.current).forEach((timer) => window.clearInterval(timer)); + clearTimers(); }; - }, []); + }, [clearTimers]); const updateProviderState = (provider: OAuthProvider, next: Partial) => { setStates((prev) => ({ diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 5588342..85ccc89 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -97,7 +97,7 @@ export function SettingsPage() { setRoutingStrategy(config.routingStrategy); } } - }, [config?.proxyUrl, config?.requestRetry, config?.logsMaxTotalSizeMb, config?.routingStrategy]); + }, [config]); const setPendingFlag = (key: PendingKey, value: boolean) => { setPending((prev) => ({ ...prev, [key]: value })); diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts index cf7905d..dcd5721 100644 --- a/src/stores/useAuthStore.ts +++ b/src/stores/useAuthStore.ts @@ -163,7 +163,7 @@ export const useAuthStore = create()( }); return true; - } catch (error) { + } catch { set({ isAuthenticated: false, connectionStatus: 'error'