Files
codexautopool/frontend/src/pages/S2AConfig.tsx

242 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { TestTube, CheckCircle, XCircle, Loader2, Save, Server, Plus, X } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
import { useConfig } from '../hooks/useConfig'
export default function S2AConfig() {
const {
config,
updateS2AConfig,
updatePoolingConfig,
testConnection,
isConnected,
} = useConfig()
const [testing, setTesting] = useState(false)
const [testResult, setTestResult] = useState<boolean | null>(null)
const [saved, setSaved] = useState(false)
// Local form state - S2A 连接
const [s2aApiBase, setS2aApiBase] = useState(config.s2a.apiBase)
const [s2aAdminKey, setS2aAdminKey] = useState(config.s2a.adminKey)
// Local form state - 入库设置
const [poolingConcurrency, setPoolingConcurrency] = useState(config.pooling.concurrency)
const [poolingPriority, setPoolingPriority] = useState(config.pooling.priority)
const [groupIds, setGroupIds] = useState<number[]>(config.pooling.groupIds || [])
const [newGroupId, setNewGroupId] = useState('')
const handleTestConnection = async () => {
// Save first
updateS2AConfig({ apiBase: s2aApiBase, adminKey: s2aAdminKey })
setTesting(true)
setTestResult(null)
// Wait a bit for the client to be recreated
await new Promise((resolve) => setTimeout(resolve, 100))
const result = await testConnection()
setTestResult(result)
setTesting(false)
}
const handleSave = () => {
updateS2AConfig({ apiBase: s2aApiBase, adminKey: s2aAdminKey })
updatePoolingConfig({
concurrency: poolingConcurrency,
priority: poolingPriority,
groupIds: groupIds,
})
setSaved(true)
setTimeout(() => setSaved(false), 2000)
}
const handleAddGroupId = () => {
const id = parseInt(newGroupId, 10)
if (!isNaN(id) && !groupIds.includes(id)) {
setGroupIds([...groupIds, id])
setNewGroupId('')
}
}
const handleRemoveGroupId = (id: number) => {
setGroupIds(groupIds.filter(g => g !== id))
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<h1 className="text-2xl font-bold text-slate-900 dark:text-slate-100 flex items-center gap-2">
<Server className="h-7 w-7 text-blue-500" />
S2A
</h1>
<p className="text-sm text-slate-500 dark:text-slate-400"> S2A </p>
</div>
<Button
onClick={handleSave}
icon={saved ? <CheckCircle className="h-4 w-4" /> : <Save className="h-4 w-4" />}
>
{saved ? '已保存' : '保存配置'}
</Button>
</div>
{/* S2A Connection */}
<Card>
<CardHeader>
<CardTitle>S2A </CardTitle>
<div className="flex items-center gap-2">
{isConnected ? (
<span className="flex items-center gap-1 text-sm text-green-600 dark:text-green-400">
<CheckCircle className="h-4 w-4" />
</span>
) : (
<span className="flex items-center gap-1 text-sm text-slate-500 dark:text-slate-400">
<XCircle className="h-4 w-4" />
</span>
)}
</div>
</CardHeader>
<CardContent className="space-y-4">
<Input
label="S2A API 地址"
placeholder="http://localhost:8080"
value={s2aApiBase}
onChange={(e) => setS2aApiBase(e.target.value)}
hint="S2A 服务的 API 地址,例如 http://localhost:8080"
/>
<Input
label="Admin API Key"
type="password"
placeholder="admin-xxxxxxxxxxxxxxxx"
value={s2aAdminKey}
onChange={(e) => setS2aAdminKey(e.target.value)}
hint="S2A 管理密钥,可在 S2A 后台 Settings 页面获取"
/>
<div className="flex items-center gap-4">
<Button
variant="outline"
onClick={handleTestConnection}
disabled={testing || !s2aApiBase || !s2aAdminKey}
icon={
testing ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<TestTube className="h-4 w-4" />
)
}
>
{testing ? '测试中...' : '测试连接'}
</Button>
{testResult !== null && (
<span
className={`text-sm ${testResult
? 'text-green-600 dark:text-green-400'
: 'text-red-600 dark:text-red-400'
}`}
>
{testResult ? '连接成功' : '连接失败'}
</span>
)}
</div>
</CardContent>
</Card>
{/* Pooling Settings */}
<Card>
<CardHeader>
<CardTitle></CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Input
label="默认并发数"
type="number"
min={1}
max={10}
value={poolingConcurrency}
onChange={(e) => setPoolingConcurrency(Number(e.target.value))}
hint="账号的默认并发请求数"
/>
<Input
label="默认优先级"
type="number"
min={0}
max={100}
value={poolingPriority}
onChange={(e) => setPoolingPriority(Number(e.target.value))}
hint="账号的默认优先级,数值越大优先级越高"
/>
</div>
{/* Group IDs */}
<div className="space-y-2">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
ID
</label>
<div className="flex flex-wrap gap-2">
{groupIds.map(id => (
<span
key={id}
className="inline-flex items-center gap-1 px-3 py-1 rounded-full text-sm bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
>
{id}
<button
onClick={() => handleRemoveGroupId(id)}
className="hover:text-red-500 transition-colors"
>
<X className="h-3 w-3" />
</button>
</span>
))}
{groupIds.length === 0 && (
<span className="text-sm text-slate-400"></span>
)}
</div>
<div className="flex gap-2 mt-2">
<Input
placeholder="输入分组 ID"
type="number"
min={1}
value={newGroupId}
onChange={(e) => setNewGroupId(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleAddGroupId()}
/>
<Button
variant="outline"
onClick={handleAddGroupId}
disabled={!newGroupId}
icon={<Plus className="h-4 w-4" />}
>
</Button>
</div>
<p className="text-xs text-slate-500 dark:text-slate-400">
</p>
</div>
</CardContent>
</Card>
{/* Info */}
<Card>
<CardContent>
<div className="text-sm text-slate-500 dark:text-slate-400">
<p className="font-medium mb-2"></p>
<ul className="list-disc list-inside space-y-1">
<li>S2A API S2A URL</li>
<li>Admin API Key </li>
<li></li>
<li> ID </li>
</ul>
</div>
</CardContent>
</Card>
</div>
)
}