From f6d5cf5beca9fd51ee5f9f754e5a52be52364a07 Mon Sep 17 00:00:00 2001 From: zhoujun0601 Date: Thu, 13 Aug 2026 07:41:12 -0400 Subject: [PATCH] Improve quota balance display --- frontend/src/main.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index fb3f08d..662de06 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,16 +12,16 @@ const Splash=()=>
Codex He function Setup({onDone}:{onDone:()=>void}){const[form,set]=useState({username:'admin',password:'',timezone:Intl.DateTimeFormat().resolvedOptions().timeZone});const[err,setErr]=useState('');async function submit(e:React.FormEvent){e.preventDefault();try{await post('setup',form);onDone()}catch(x){setErr((x as Error).message)}}return
Codex Helper

连接你的 Codex
看见每一次增长

一个容器内完成用量洞察、重置提醒与账户管理。

首次初始化 · 1 / 1

创建管理员

{err&&

{err}

}

Codex、Telegram 与 SMTP 可在进入系统后通过设置中心完成。

} function Login({done}:{done:()=>void}){const[u,setU]=useState('admin'),[p,setP]=useState(''),[e,setE]=useState('');return
{x.preventDefault();try{await post('auth/login',{username:u,password:p});done()}catch(q){setE((q as Error).message)}}}>
Codex Helper

欢迎回来

{e&&

{e}

}
} function Shell({logout}:{logout:()=>void}){const nav=useNavigate();const[theme,setTheme]=useState(localStorage.theme||'system');useEffect(()=>{document.documentElement.dataset.theme=theme==='system'?(matchMedia('(prefers-color-scheme:dark)').matches?'dark':'light'):theme;localStorage.theme=theme},[theme]);return
}/>}/>}/>
} -function Dashboard(){const[accounts,setAccounts]=useState([]),[id,setId]=useState(+(localStorage.accountId||0)),[d,setD]=useState(null),[e,setE]=useState(''),[refresh,setRefresh]=useState<'idle'|'loading'|'done'>('idle');const loadAccounts=()=>api('accounts').then(xs=>{setAccounts(xs);const next=xs.some(x=>x.id===id)?id:xs[0]?.id||0;if(next!==id)setId(next)});const load=async(accountId=id)=>{if(!accountId)return;try{setD(await api('dashboard?accountId='+accountId));setE('')}catch(x){setE((x as Error).message)}};useEffect(()=>{loadAccounts()},[]);useEffect(()=>{if(!id)return;localStorage.accountId=String(id);setD(null);load(id);const t=setInterval(()=>load(id),30000);return()=>clearInterval(t)},[id]);const sync=async()=>{setRefresh('loading');setE('');try{await post(`accounts/${id}/sync`);await load(id);setRefresh('done');setTimeout(()=>setRefresh('idle'),2000)}catch(x){setE((x as Error).message);setRefresh('idle')}};if(!accounts.length)return <>
尚未添加 Codex 账号
;if(!d)return ;return <>
{e&&
{e}
}
Codex Account{d.displayName} · {d.account.email||'尚未连接'}
{d.account.planType||'未识别套餐'}更新于 {d.fetchedAt?new Date(d.fetchedAt*1000).toLocaleString():'尚未同步'}
{d.limits.map((x,i)=>)}
{!d.limits.length&&
该连接暂无限额数据,请确认登录后刷新。
}
} -function LimitCard({x}:{x:Limit}){const used=Math.min(100,Math.max(0,x.usedPercent)),left=100-used;return
{x.limitName||x.limitId} · {x.windowType}
{left.toFixed(1)}%剩余
下次重置{x.resetsAt?new Date(x.resetsAt*1000).toLocaleString():'暂未提供'}{x.resetsAt?relative(x.resetsAt):''}
已使用 {used.toFixed(1)}%剩余 {left.toFixed(1)}%{x.windowDurationMinutes||'—'} 分钟窗口
} +function Dashboard(){const[accounts,setAccounts]=useState([]),[id,setId]=useState(+(localStorage.accountId||0)),[d,setD]=useState(null),[e,setE]=useState(''),[refresh,setRefresh]=useState<'idle'|'loading'|'done'>('idle');const loadAccounts=()=>api('accounts').then(xs=>{setAccounts(xs);const next=xs.some(x=>x.id===id)?id:xs[0]?.id||0;if(next!==id)setId(next)});const load=async(accountId=id)=>{if(!accountId)return;try{setD(await api('dashboard?accountId='+accountId));setE('')}catch(x){setE((x as Error).message)}};useEffect(()=>{loadAccounts()},[]);useEffect(()=>{if(!id)return;localStorage.accountId=String(id);setD(null);load(id);const t=setInterval(()=>load(id),30000);return()=>clearInterval(t)},[id]);const sync=async()=>{setRefresh('loading');setE('');try{await post(`accounts/${id}/sync`);await load(id);setRefresh('done');setTimeout(()=>setRefresh('idle'),2000)}catch(x){setE((x as Error).message);setRefresh('idle')}};if(!accounts.length)return <>
尚未添加 Codex 账号
;if(!d)return ;return <>
{e&&
{e}
}
Codex Account{d.displayName} · {d.account.email||'尚未连接'}
{d.account.planType||'未识别套餐'}更新于 {d.fetchedAt?new Date(d.fetchedAt*1000).toLocaleString():'尚未同步'}
{d.limits.map((x,i)=>)}
{!d.limits.length&&
该连接暂无限额数据,请确认登录后刷新。
}
} +function LimitCard({x}:{x:Limit}){const used=Math.min(100,Math.max(0,x.usedPercent)),left=100-used,usedLabel=Math.round(used),leftLabel=Math.round(left),leftColor=`hsl(${left*1.2} 70% 45%)`;return
余额 · {x.windowType}
{leftLabel}%剩余
下次重置{x.resetsAt?new Date(x.resetsAt*1000).toLocaleString():'暂未提供'}{x.resetsAt?relative(x.resetsAt):''}
已使用 {usedLabel}%剩余 {leftLabel}%
} const Stat=({label,v,muted=false}:{label:string,v:string,muted?:boolean})=>
{label}{v}
; -const Chart=({data}:{data:Point[]})=>

每日 Token 趋势

Codex app server 提供的每日总量
[new Intl.NumberFormat('zh-CN').format(Number(v)),'Tokens']} contentStyle={{background:'var(--panel)',border:'1px solid var(--border)',borderRadius:12}}/>
; -function SettingsPage(){const[tab,setTab]=useState('general');return <>
{tab==='general'?:tab==='codex'?:tab==='telegram'?:}
} +const Chart=({data}:{data:Point[]})=>

每日 Token 趋势

[new Intl.NumberFormat('zh-CN').format(Number(v)),'Tokens']} contentStyle={{background:'var(--panel)',border:'1px solid var(--border)',borderRadius:12}}/>
; +function SettingsPage(){const[tab,setTab]=useState('general');return <>
{tab==='general'?:tab==='codex'?:tab==='telegram'?:}
} function General(){const[v,setV]=useState(null),[msg,setMsg]=useState('');useEffect(()=>{api('settings/general').then(setV)},[]);if(!v)return ;return
{e.preventDefault();try{await api('settings/general',{method:'PUT',body:JSON.stringify(v)});setMsg('设置已保存')}catch(x){setMsg((x as Error).message)}}}>

通用设置

{msg&&

{msg}

}
} function CodexSettings(){const[xs,setXs]=useState([]),[result,setResult]=useState(null),[active,setActive]=useState(0),[busy,setBusy]=useState(false),[err,setErr]=useState('');const load=()=>api('accounts').then(setXs);useEffect(()=>{load()},[]);const login=async(id:number)=>{try{setBusy(true);setActive(id);setErr('');setResult(await post(`accounts/${id}/login/device`))}catch(q){setErr((q as Error).message)}finally{setBusy(false)}};const add=async()=>{try{setBusy(true);setActive(0);setErr('');const x=await post('accounts',{displayName:`账号 ${xs.length+1}`});await load();setActive(x.id);setResult(await post(`accounts/${x.id}/login/device`))}catch(q){setErr((q as Error).message)}finally{setBusy(false)}};return

Codex 账户与工作区

个人订阅和 Team 工作区请分别添加为独立连接;同一邮箱可以添加多次。

{xs.map(x=>
{const name=e.target.value.trim();if(name&&name!==x.displayName){await put(`accounts/${x.id}`,{displayName:name});load()}}}/>{x.email||'尚未登录'} · {x.planType||'未识别套餐'}
)}
{result&&
为“{xs.find(x=>x.id===active)?.displayName}”访问 {result.verificationUrl}{result.userCode}
}{err&&

{err}

}
} function Telegram(){const[v,setV]=useState(null),[msg,setMsg]=useState(''),[code,setCode]=useState('');useEffect(()=>{api('settings/telegram').then(setV)},[]);if(!v)return ;return
{e.preventDefault();try{const x:any=await api('settings/telegram',{method:'PUT',body:JSON.stringify(v)});setV(x);setMsg('Bot 已验证并保存')}catch(x){setMsg((x as Error).message)}}}>

Telegram Bot

{code&&
向 Bot 发送/bind {code}
}{msg&&

{msg}

}
} function SMTP(){const[v,setV]=useState(null),[msg,setMsg]=useState('');useEffect(()=>{api('settings/smtp').then(setV)},[]);if(!v)return ;const save=async(e:React.FormEvent)=>{e.preventDefault();try{setV(await api('settings/smtp',{method:'PUT',body:JSON.stringify(v)}));setMsg('SMTP 设置已保存')}catch(x){setMsg((x as Error).message)}};return

SMTP 邮件

{msg&&

{msg}

}
} -function Header({title,sub,children}:{title:string,sub:string,children?:React.ReactNode}){return

{title}

{sub}

{children}
} +function Header({title,sub,children}:{title:string,sub?:string,children?:React.ReactNode}){return

{title}

{sub&&

{sub}

}
{children}
} const num=(v?:number)=>v==null?'暂无':new Intl.NumberFormat('zh-CN',{notation:'compact',maximumFractionDigits:1}).format(v); const compactAxis=(v:number)=>new Intl.NumberFormat('en',{notation:'compact',maximumFractionDigits:1}).format(v); const duration=(v?:number)=>v==null?'暂无':v<60?`${v} 秒`:v<3600?`${Math.floor(v/60)} 分 ${v%60} 秒`:`${(v/3600).toFixed(1)} 小时`;