返回 DeepSeek-Reasonix
InboxRecoveryBanner.tsx
根目录 / desktop / frontend / src / components / InboxRecoveryBanner.tsx
1 import { useEffect, useRef, useState } from "react";
2 import { ShieldAlert } from "lucide-react";
3 import { app } from "../lib/bridge";
4 import { useI18n } from "../lib/i18n";
5
6 const COPY = {
7 en: ["Recovered {n} pending instructions", "Inbox is paused", "Review queued work before continuing.", "Review queue", "Continue", "Keep paused", "Paused"],
8 zh: ["已恢复 {n} 条待处理指令", "收件箱已暂停", "请检查队列后再继续。", "查看队列", "继续执行", "保持暂停", "已暂停"],
9 "zh-TW": ["已恢復 {n} 條待處理指令", "收件匣已暫停", "請檢查佇列後再繼續。", "查看佇列", "繼續執行", "保持暫停", "已暫停"],
10 } as const;
11
12 export function InboxRecoveryBanner({
13 count,
14 recovered,
15 disabled,
16 tabId,
17 onReview,
18 onResumed,
19 onError,
20 }: {
21 count: number;
22 recovered: boolean;
23 disabled: boolean;
24 tabId: string;
25 onReview: () => void;
26 onResumed: () => void;
27 onError: (error: unknown) => void;
28 }) {
29 const { locale } = useI18n();
30 const [recoveredTitle, pausedTitle, body, review, resume, keepPaused, paused] = COPY[locale];
31 const title = recovered ? recoveredTitle.replace("{n}", String(count)) : pausedTitle;
32 const [busy, setBusy] = useState(false);
33 const [keptPaused, setKeptPaused] = useState(false);
34 const mountedRef = useRef(true);
35 useEffect(() => {
36 mountedRef.current = true;
37 return () => { mountedRef.current = false; };
38 }, []);
39
40 const setPaused = async (paused: boolean) => {
41 if (busy) return;
42 setBusy(true);
43 try {
44 await app.SetInboxPaused(tabId, paused);
45 if (!mountedRef.current) return;
46 if (paused) setKeptPaused(true);
47 else onResumed();
48 } catch (error) {
49 if (mountedRef.current) onError(error);
50 } finally {
51 if (mountedRef.current) setBusy(false);
52 }
53 };
54
55 return (
56 <section className="composer-guidance-shelf composer-inbox-recovery" role="status" aria-label={title}>
57 <div className="banner banner--warning banner--actionable" style={{ flexWrap: "wrap" }}>
58 <ShieldAlert size={17} aria-hidden="true" />
59 <span className="banner__msg">
60 <strong>{title}</strong>
61 {` — ${body}`}
62 </span>
63 <span className="banner__spacer" />
64 <button className="btn btn--small" type="button" disabled={busy} onClick={onReview}>
65 {review}
66 </button>
67 <button className="btn btn--primary btn--small" type="button" disabled={disabled || busy} onClick={() => void setPaused(false)}>
68 {resume}
69 </button>
70 <button className="btn btn--small" type="button" disabled={disabled || busy || keptPaused} onClick={() => void setPaused(true)}>
71 {keptPaused ? paused : keepPaused}
72 </button>
73 </div>
74 </section>
75 );
76 }
77
77 lines Plain Text