Files
codexautopool/frontend/src/context/ConfigContext.tsx

182 lines
5.0 KiB
TypeScript

import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react'
import type { AppConfig } from '../types'
import { defaultConfig } from '../types'
import { saveConfig } from '../utils/storage'
import { S2AClient } from '../api/s2a'
interface ConfigContextValue {
config: AppConfig
updateConfig: (updates: Partial<AppConfig>) => void
updateS2AConfig: (updates: Partial<AppConfig['s2a']>) => void
updatePoolingConfig: (updates: Partial<AppConfig['pooling']>) => void
updateCheckConfig: (updates: Partial<AppConfig['check']>) => void
updateEmailConfig: (updates: Partial<AppConfig['email']>) => void
isConnected: boolean
testConnection: () => Promise<boolean>
s2aClient: S2AClient | null
refreshConfig: () => Promise<void>
siteName: string
}
const ConfigContext = createContext<ConfigContextValue | null>(null)
export function ConfigProvider({ children }: { children: ReactNode }) {
const [config, setConfig] = useState<AppConfig>(defaultConfig)
const [isConnected, setIsConnected] = useState(false)
const [s2aClient, setS2aClient] = useState<S2AClient | null>(null)
const [siteName, setSiteName] = useState('Codex Pool')
// Load config from server on mount
const refreshConfig = useCallback(async () => {
try {
const res = await fetch('/api/config')
const data = await res.json()
if (data.code === 0 && data.data) {
const serverConfig = data.data
setConfig(prev => ({
...prev,
s2a: {
...prev.s2a,
apiBase: serverConfig.s2a_api_base || '',
adminKey: serverConfig.s2a_admin_key || '',
},
pooling: {
...prev.pooling,
concurrency: serverConfig.concurrency || 2,
priority: serverConfig.priority || 0,
groupIds: serverConfig.group_ids || [],
},
}))
// 更新站点名称
if (serverConfig.site_name) {
setSiteName(serverConfig.site_name)
}
}
} catch (error) {
console.error('Failed to load config from server:', error)
}
}, [])
useEffect(() => {
refreshConfig()
}, [refreshConfig])
// Update S2A client when config changes and auto-test connection
useEffect(() => {
if (config.s2a.apiBase && config.s2a.adminKey) {
const client = new S2AClient({
baseUrl: config.s2a.apiBase,
apiKey: config.s2a.adminKey,
})
setS2aClient(client)
// 自动测试连接
fetch('/api/s2a/test')
.then(res => res.json())
.then(data => {
setIsConnected(data.code === 0)
})
.catch(() => {
setIsConnected(false)
})
} else {
setS2aClient(null)
setIsConnected(false)
}
}, [config.s2a.apiBase, config.s2a.adminKey])
const updateConfig = useCallback((updates: Partial<AppConfig>) => {
setConfig((prev) => {
const newConfig = { ...prev, ...updates }
saveConfig(newConfig)
return newConfig
})
}, [])
const updateS2AConfig = useCallback((updates: Partial<AppConfig['s2a']>) => {
setConfig((prev) => {
const newConfig = {
...prev,
s2a: { ...prev.s2a, ...updates },
}
saveConfig(newConfig)
return newConfig
})
}, [])
const updatePoolingConfig = useCallback((updates: Partial<AppConfig['pooling']>) => {
setConfig((prev) => {
const newConfig = {
...prev,
pooling: { ...prev.pooling, ...updates },
}
saveConfig(newConfig)
return newConfig
})
}, [])
const updateCheckConfig = useCallback((updates: Partial<AppConfig['check']>) => {
setConfig((prev) => {
const newConfig = {
...prev,
check: { ...prev.check, ...updates },
}
saveConfig(newConfig)
return newConfig
})
}, [])
const updateEmailConfig = useCallback((updates: Partial<AppConfig['email']>) => {
setConfig((prev) => {
const newConfig = {
...prev,
email: { ...prev.email, ...updates },
}
saveConfig(newConfig)
return newConfig
})
}, [])
const testConnection = useCallback(async (): Promise<boolean> => {
try {
// 使用后端代理 API 来测试 S2A 连接(避免 CORS 问题)
const res = await fetch('/api/s2a/test')
const data = await res.json()
const connected = data.code === 0
setIsConnected(connected)
return connected
} catch {
setIsConnected(false)
return false
}
}, [])
return (
<ConfigContext.Provider
value={{
config,
updateConfig,
updateS2AConfig,
updatePoolingConfig,
updateCheckConfig,
updateEmailConfig,
isConnected,
testConnection,
s2aClient,
refreshConfig,
siteName,
}}
>
{children}
</ConfigContext.Provider>
)
}
export function useConfigContext(): ConfigContextValue {
const context = useContext(ConfigContext)
if (!context) {
throw new Error('useConfigContext must be used within a ConfigProvider')
}
return context
}