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}` : ''} +