feat: Implement the ChatGPT Owner Demotion Console with new frontend and backend components.

This commit is contained in:
2026-02-05 03:13:30 +08:00
parent 61712cf4fb
commit 2bccb06359
6 changed files with 454 additions and 13 deletions

View File

@@ -13,7 +13,8 @@ import {
Globe,
Zap,
Monitor,
Network
Network,
UserMinus
} from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common'
import { useConfig } from '../hooks/useConfig'
@@ -25,6 +26,8 @@ export default function Config() {
const [saving, setSaving] = useState(false)
const [authMethod, setAuthMethod] = useState<'api' | 'browser'>('browser')
const [savingAuthMethod, setSavingAuthMethod] = useState(false)
const [demoteAfterUse, setDemoteAfterUse] = useState(false)
const [savingDemote, setSavingDemote] = useState(false)
const [proxyPoolCount, setProxyPoolCount] = useState<number>(0)
const { toasts, toast, removeToast } = useToast()
@@ -40,6 +43,8 @@ export default function Config() {
if (data.data.auth_method) {
setAuthMethod(data.data.auth_method === 'api' ? 'api' : 'browser')
}
// 加载母号降级开关
setDemoteAfterUse(data.data.demote_after_use === true)
}
} catch (error) {
console.error('Failed to fetch config:', error)
@@ -113,6 +118,33 @@ export default function Config() {
}
}
// 保存母号降级开关
const handleToggleDemote = async () => {
setSavingDemote(true)
const newValue = !demoteAfterUse
setDemoteAfterUse(newValue) // 立即更新 UI
try {
const res = await fetch('/api/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ demote_after_use: newValue }),
})
const data = await res.json()
if (data.code === 0) {
toast.success(newValue ? '母号降级已开启' : '母号降级已关闭')
refreshConfig()
} else {
setDemoteAfterUse(!newValue) // 回滚
toast.error(data.message || '保存失败')
}
} catch {
setDemoteAfterUse(!newValue) // 回滚
toast.error('网络错误')
} finally {
setSavingDemote(false)
}
}
const configItems = [
{
to: '/config/s2a',
@@ -287,6 +319,52 @@ export default function Config() {
</div>
)}
</div>
{/* 母号降级开关 */}
<div className="pt-4 border-t border-slate-200 dark:border-slate-700">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${demoteAfterUse
? 'bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400'
: 'bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400'
}`}>
<UserMinus className="h-5 w-5" />
</div>
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">
使
</label>
<p className="text-xs text-slate-500 dark:text-slate-400">
</p>
</div>
</div>
<button
onClick={handleToggleDemote}
disabled={savingDemote}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 ${demoteAfterUse
? 'bg-orange-500'
: 'bg-slate-300 dark:bg-slate-600'
} ${savingDemote ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow-sm transition-transform duration-200 ${demoteAfterUse ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{demoteAfterUse && (
<div className="mt-3 p-3 rounded-lg bg-gradient-to-r from-orange-500 to-amber-500 text-white shadow-sm">
<div className="flex items-center gap-2">
<UserMinus className="h-4 w-4" />
<span className="font-medium"></span>
</div>
<p className="text-xs text-orange-100 mt-1">
API
</p>
</div>
)}
</div>
</CardContent>
</Card>