182 lines
5.0 KiB
TypeScript
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
|
|
}
|