返回 DeepSeek-Reasonix
UpdateBanner.tsx
根目录 / desktop / frontend / src / components / UpdateBanner.tsx
1 import { useEffect, useState } from "react";
2 import { useT } from "../lib/i18n";
3 import { useUpdater } from "../lib/useUpdater";
4
5 const MB = 1024 * 1024;
6 const mb = (n: number) => (n / MB).toFixed(1);
7
8 // UpdateBanner checks for an update once on mount and, when one is available,
9 // shows a dismissible top banner with a single "update and restart" action
10 // (or, on macOS manual builds, links out to the download page). It renders
11 // nothing while idle, checking, or already current. A failed check can be
12 // dismissed here; Settings is where a manual check shows errors inline.
13 export function UpdateBanner({
14 enabled = true,
15 onShowReleaseNotes,
16 }: {
17 enabled?: boolean;
18 onShowReleaseNotes?: (version: string) => void;
19 }) {
20 const t = useT();
21 const { status, check, apply, openDownload, reset } = useUpdater();
22 const [dismissed, setDismissed] = useState<string | null>(null);
23
24 useEffect(() => {
25 if (!enabled) return;
26 void check();
27 }, [check, enabled]);
28
29 if (!enabled) return null;
30
31 switch (status.kind) {
32 case "available": {
33 const info = status.info;
34 if (info.latest === dismissed) return null;
35 return (
36 <div className="banner banner--update">
37 <span className="banner__msg">{t("updater.available", { v: info.latest })}</span>
38 {!info.canSelfUpdate && <span className="banner__hint">{info.manualReason || t("updater.macHint")}</span>}
39 <span className="banner__spacer" />
40 {onShowReleaseNotes && (
41 <button className="btn btn--small" onClick={() => onShowReleaseNotes(info.latest)}>
42 {t("updater.releaseNotes")}
43 </button>
44 )}
45 <button className="btn btn--small btn--primary" onClick={() => apply(info)}>
46 {info.canSelfUpdate ? t("updater.updateAndRestart") : t("updater.goToDownload")}
47 </button>
48 <button className="btn btn--small" onClick={() => setDismissed(info.latest)}>
49 {t("updater.dismiss")}
50 </button>
51 </div>
52 );
53 }
54 case "downloading": {
55 const pct = status.total > 0 ? Math.round((status.received / status.total) * 100) : 0;
56 return (
57 <div className="banner banner--update">
58 <span className="banner__msg">
59 {t("updater.downloading", { done: mb(status.received), total: mb(status.total), pct })}
60 </span>
61 <span className="banner__spacer" />
62 <progress className="banner__progress" value={status.received} max={status.total || undefined} />
63 </div>
64 );
65 }
66 case "verifying":
67 return <div className="banner banner--update">{t("updater.verifying")}</div>;
68 case "authorizing":
69 return <div className="banner banner--update">{t("updater.authorizing")}</div>;
70 case "installing":
71 return (
72 <div className="banner banner--update">
73 {status.info?.requiresElevation || status.info?.installMode === "deb"
74 ? t("updater.installingPackage")
75 : t("updater.installing")}
76 </div>
77 );
78 case "relaunching":
79 case "done":
80 return <div className="banner banner--update">{t("updater.done")}</div>;
81 case "error": {
82 const failedMessage = status.disposition === "recovery"
83 ? t("updater.recoveryBlocked")
84 : status.disposition === "manual"
85 ? t("updater.manualUpdateRequired")
86 : t("updater.failed", { msg: status.message });
87 const downloadFirst = status.disposition !== "retryable";
88 return (
89 <div className="banner banner--update banner--error banner--actionable">
90 <span className="banner__msg" title={failedMessage}>
91 {failedMessage}
92 </span>
93 <span className="banner__spacer" />
94 {downloadFirst && (
95 <button className="btn btn--small btn--primary" onClick={openDownload}>
96 {t("updater.officialDownload")}
97 </button>
98 )}
99 <button
100 className={`btn btn--small${downloadFirst ? "" : " btn--primary"}`}
101 onClick={() => {
102 if (status.info) apply(status.info);
103 else void check();
104 }}
105 >
106 {t("updater.retry")}
107 </button>
108 <button className="btn btn--small" onClick={() => reset()}>
109 {t("updater.dismiss")}
110 </button>
111 </div>
112 );
113 }
114 default:
115 // idle | checking | upToDate — nothing to show.
116 return null;
117 }
118 }
119
119 lines Plain Text