From 42c6dcec2a33a9f569e9653630326baddbca1673 Mon Sep 17 00:00:00 2001 From: caiji <522caiji@gmail.com> Date: Fri, 13 Nov 2026 15:57:11 +0800 Subject: [PATCH] feat(console): model cooldown list and bulk check-in feedback Every account card gets a model-cooldown button that opens an inline list of models currently cooling down, with a live countdown and an empty state. The page creates a fresh feedback object per check-in, so the card derives the visible notice and copies the full raw upstream error without a synchronous setState in an effect. A one-click "check in all" run now pushes the same per-account feedback that a single-account check-in already showed (success / already / error with the raw upstream message and copy button), instead of silently swallowing results. Remove baseUrl and ignoreDeprecations from tsconfig.app.json. Under moduleResolution "bundler", paths does not need baseUrl, and Vite already maps the @ alias; both the workspace TypeScript 6.0 and the global 5.9 then type-check without errors. Rebuild and sync the embedded static assets. Add a bilingual changelog fragment. --- ...del-cooldowns-and-bulk-checkin-feedback.md | 16 ++ .../src/components/account/AccountCard.tsx | 56 ++++-- frontend/src/i18n/messages.ts | 6 + frontend/src/pages/AccountsPage.tsx | 19 +- frontend/tsconfig.app.json | 2 - ...-DGKYgzmQ.js => CartesianGrid-1HegCBTm.js} | 2 +- ...hart-Q8Gb4JP1.js => CostChart-Dpyzl54f.js} | 2 +- ...-qv2z3a7d.js => LiveSparkline-DXsyY9T8.js} | 2 +- ...t-C0Gk23oN.js => TrafficChart-BMjlJkKA.js} | 2 +- .../webui/static/assets/index-BTLD14uK.js | 168 ++++++++++++++++++ .../webui/static/assets/index-g3oQBJ5b.js | 168 ------------------ internal/webui/static/index.html | 2 +- 12 files changed, 254 insertions(+), 191 deletions(-) create mode 100644 changelog/unreleased/model-cooldowns-and-bulk-checkin-feedback.md rename internal/webui/static/assets/{CartesianGrid-DGKYgzmQ.js => CartesianGrid-1HegCBTm.js} (99%) rename internal/webui/static/assets/{CostChart-Q8Gb4JP1.js => CostChart-Dpyzl54f.js} (96%) rename internal/webui/static/assets/{LiveSparkline-qv2z3a7d.js => LiveSparkline-DXsyY9T8.js} (80%) rename internal/webui/static/assets/{TrafficChart-C0Gk23oN.js => TrafficChart-BMjlJkKA.js} (99%) create mode 100644 internal/webui/static/assets/index-BTLD14uK.js delete mode 100644 internal/webui/static/assets/index-g3oQBJ5b.js diff --git a/changelog/unreleased/model-cooldowns-and-bulk-checkin-feedback.md b/changelog/unreleased/model-cooldowns-and-bulk-checkin-feedback.md new file mode 100644 index 00000000..83c06896 --- /dev/null +++ b/changelog/unreleased/model-cooldowns-and-bulk-checkin-feedback.md @@ -0,0 +1,16 @@ +### English + +- Add a model-cooldown button to every account card. It opens an inline list of models currently in cooldown with a live remaining-time countdown, and shows an empty state when nothing is cooling down. + +- Show per-account check-in feedback after a one-click "check in all" run, not only after a single-account check-in. Each card reports success, already checked in, or the raw upstream error with a copy button. + +- Remove `baseUrl` and `ignoreDeprecations` from the frontend TypeScript config so the console type-checks under both the workspace TypeScript 6.0 and the globally installed 5.9. + +### 中文 + +- 每个账号卡片新增「模型冷却」按钮,点开可在卡片内查看当前处于冷却中的模型及实时剩余时间;没有模型冷却时显示空状态。 + +- 一键签到后每个账号卡片也会显示签到反馈(成功 / 已签到 / 失败并展示上游原始错误及复制按钮),此前只有单账号签到才有反馈。 + +- 移除前端 TypeScript 配置中的 `baseUrl` 与 `ignoreDeprecations`,让工作区 TypeScript 6.0 和全局 5.9 都能通过类型检查。 + diff --git a/frontend/src/components/account/AccountCard.tsx b/frontend/src/components/account/AccountCard.tsx index ee6819c1..5080bcf0 100644 --- a/frontend/src/components/account/AccountCard.tsx +++ b/frontend/src/components/account/AccountCard.tsx @@ -13,6 +13,7 @@ import { ListBullets, PencilSimple, ShieldCheck, + Timer, TrashSimple, WarningCircle, XCircle, @@ -115,6 +116,7 @@ export function AccountCard({ const chipRef = useRef(null) const authRef = useRef(null) const [authCopied, setAuthCopied] = useState(false) + const [cooldownsOpen, setCooldownsOpen] = useState(false) const lastStateRef = useRef(null) const [, refreshCooldowns] = useState(0) const state = accountState(account) @@ -131,18 +133,19 @@ export function AccountCard({ // Transient check-in feedback: a green check / yellow circle / red cross row // that the page pushes after a manual check-in. It shows for three seconds // and then disappears, regardless of whether the check-in succeeded. - const [feedback, setFeedback] = useState(null) - const [feedbackCopied, setFeedbackCopied] = useState(false) + // The page creates a fresh object per check-in, so reference equality is + // enough to know which notice has been dismissed. Both the visible notice + // and the copied flag are derived during render; only the timer callback + // and user events update state (never a synchronous setState in an effect). + const [dismissed, setDismissed] = useState(null) + const [copiedKey, setCopiedKey] = useState(null) const feedbackTimer = useRef(null) + const feedback: CheckinFeedback | null = checkinFeedback && checkinFeedback !== dismissed ? checkinFeedback : null + const feedbackCopied = Boolean(feedback) && copiedKey === feedback useEffect(() => { - if (!checkinFeedback) { - setFeedback(null) - return - } - setFeedback(checkinFeedback) - setFeedbackCopied(false) + if (!checkinFeedback) return if (feedbackTimer.current) window.clearTimeout(feedbackTimer.current) - feedbackTimer.current = window.setTimeout(() => setFeedback(null), 3000) + feedbackTimer.current = window.setTimeout(() => setDismissed(checkinFeedback), 3000) return () => { if (feedbackTimer.current) window.clearTimeout(feedbackTimer.current) } @@ -342,9 +345,9 @@ export function AccountCard({ className="h-6 min-w-0 gap-1 px-1.5 text-caption" onPress={() => { void copyText(feedback.message || '').then((ok) => { - if (!ok) return - setFeedbackCopied(true) - window.setTimeout(() => setFeedbackCopied(false), 1100) + if (!ok || !feedback) return + setCopiedKey(feedback) + window.setTimeout(() => setCopiedKey(null), 1100) }) }} > @@ -502,6 +505,14 @@ export function AccountCard({ {t('checkinNow')} ) : null} + + + + + {t('modelCooldowns')}{modelCooldowns.length ? ` · ${modelCooldowns.length}` : ''} +