返回 DeepSeek-Reasonix
BrowserEvidenceControls.tsx
根目录 / desktop / frontend / src / components / BrowserEvidenceControls.tsx
1 import { useEffect, useState } from "react";
2 import type { DesktopBrowserHost } from "../lib/browserHost";
3 import { useBrowserPanelStore } from "../lib/browserPanelStore";
4 import { useI18n } from "../lib/i18n";
5
6 type Recording = Awaited<ReturnType<NonNullable<DesktopBrowserHost["record"]>>>;
7 export function BrowserEvidenceControls({ tabId }: { tabId: string }) {
8 const chinese = useI18n().locale !== "en";
9 const [recording, setRecording] = useState<Recording>(null);
10 const [busy, setBusy] = useState(false);
11 const [diagnostics, setDiagnostics] = useState<string | null>(null);
12 const [capture, setCapture] = useState<{ path: string; width: number; height: number } | null>(null);
13 useEffect(() => {
14 let disposed = false;
15 const poll = async () => {
16 try { const result = await useBrowserPanelStore.getState().host?.record?.(tabId, "status"); if (!disposed) setRecording(result ?? null); }
17 catch { /* Availability is reported by the explicit action. */ }
18 };
19 void poll(); const timer = setInterval(() => void poll(), 1000);
20 return () => { disposed = true; clearInterval(timer); };
21 }, [tabId]);
22 const active = recording && ["preparing", "recording", "finalizing"].includes(recording.state);
23 const run = async (action: "start" | "stop" | "cancel") => {
24 const { host, notify } = useBrowserPanelStore.getState();
25 if (!host?.record) { notify(chinese ? "当前宿主不支持录制" : "Recording is unavailable"); return; }
26 setBusy(true);
27 try { setRecording(await host.record(tabId, action)); } catch (error) { notify(String(error)); }
28 finally { setBusy(false); }
29 };
30 return <div className="browser-evidence">
31 <button type="button" className="btn btn--small" onClick={() => {
32 const { host, notify } = useBrowserPanelStore.getState();
33 void host?.screenshot?.(tabId).then(setCapture).catch(error => notify(String(error)));
34 }}>{chinese ? "截图" : "Screenshot"}</button>
35 {capture && <button type="button" className="btn btn--small" title={capture.path} onClick={() => { void navigator.clipboard.writeText(capture.path); }}>{capture.width}×{capture.height} · {chinese ? "复制路径" : "Copy path"}</button>}
36 <button type="button" className="btn btn--small" title={chinese ? "关闭面板后也可通过 View 菜单或 Ctrl/⌘+Shift+R 停止录制" : "Stop from the View menu or Ctrl/⌘+Shift+R even when this panel is closed"} disabled={busy} onClick={() => void run(active ? "stop" : "start")}>{active ? (chinese ? "停止录制" : "Stop recording") : (chinese ? "录制 · 20 秒" : "Record · 20 s")}</button>
37 {active && <button type="button" className="btn btn--small" onClick={() => void run("cancel")}>{chinese ? "取消录制" : "Cancel recording"}</button>}
38 {recording && <span role="status" title={recording.path ?? recording.error}>{recording.state} · {Math.ceil(recording.bytes / 1024)} KiB</span>}
39 {recording?.path && <button type="button" className="btn btn--small" onClick={() => { void navigator.clipboard.writeText(recording.path!); }}>{chinese ? "复制产物路径" : "Copy artifact path"}</button>}
40 <button type="button" className="btn btn--small" onClick={() => {
41 const { host, notify } = useBrowserPanelStore.getState();
42 void host?.diagnostics?.(tabId).then(result => setDiagnostics(result.available ? JSON.stringify(result.entries ?? [], null, 2) : (chinese ? "诊断不可用" : "Diagnostics unavailable"))).catch(error => notify(String(error)));
43 }}>{chinese ? "页面诊断" : "Page diagnostics"}</button>
44 {diagnostics !== null && <details open><summary onClick={() => setDiagnostics(null)}>{chinese ? "关闭诊断" : "Close diagnostics"}</summary><pre>{diagnostics}</pre></details>}
45 </div>;
46 }
47
47 lines Plain Text