返回 DeepSeek-Reasonix
RecoveryWaitBanner.tsx
根目录 / desktop / frontend / src / components / RecoveryWaitBanner.tsx
1 import { Square } from "lucide-react";
2 import { useI18n } from "../lib/i18n";
3 import { recoveryNextAttemptSeconds, type RecoveryRetry } from "../lib/recoveryStatus";
4
5 interface RecoveryWaitBannerProps {
6 retry: RecoveryRetry;
7 now: number;
8 onStop: () => void;
9 stopDisabled?: boolean;
10 }
11
12 export function RecoveryWaitBanner({ retry, now, onStop, stopDisabled = false }: RecoveryWaitBannerProps) {
13 const { t } = useI18n();
14 const recovery = retry.recovery;
15 if (!recovery?.waiting) return null;
16 const title = t(recovery.phase === "connect" ? "status.recoveryWaitTitleNetwork" : "status.recoveryWaitTitleProvider");
17 const waited = Math.max(0, Math.floor((recovery.waited_ms ?? 0) / 60_000));
18 const budget = Math.max(0, Math.round((recovery.wait_budget_ms ?? 0) / 60_000));
19 return (
20 <div className="recovery-wait-banner" role="region" aria-label={title} data-phase={recovery.phase ?? ""}>
21 <div className="recovery-wait-banner__body">
22 <div className="recovery-wait-banner__title">{title}</div>
23 <div className="recovery-wait-banner__meta">
24 <span className="recovery-wait-banner__countdown">
25 {t("status.recoveryWaitNext", { seconds: recoveryNextAttemptSeconds(recovery, now) })}
26 </span>
27 {budget > 0 && (
28 <span className="recovery-wait-banner__progress">{t("status.recoveryWaitProgress", { waited, budget })}</span>
29 )}
30 {recovery.reason && <code className="recovery-wait-banner__code">{recovery.reason}</code>}
31 </div>
32 <div className="recovery-wait-banner__hint">{t("status.recoveryWaitHint")}</div>
33 </div>
34 <button type="button" className="recovery-wait-banner__stop" onClick={onStop} disabled={stopDisabled}>
35 <Square size={12} fill="currentColor" aria-hidden="true" />
36 {t("status.recoveryWaitStop")}
37 </button>
38 </div>
39 );
40 }
41
41 lines Plain Text