返回 DeepSeek-Reasonix
graphicsRecoveryHost.ts
根目录 / desktop / electron / src / main / graphicsRecoveryHost.ts
1 import { app, dialog, shell } from "electron";
2 import { join } from "node:path";
3 import { GRAPHICS_RECOVERY_ARG, type GraphicsSettingsStore } from "./graphics.js";
4 import { GraphicsFaultRecord, GraphicsRecovery } from "./graphicsRecovery.js";
5 import type { QuitSequencer } from "./lifecycle.js";
6 import { errorText, type Logger } from "./log.js";
7
8 export async function confirmRecoveryDraftLoss(): Promise<boolean> {
9 const result = await dialog.showMessageBox({
10 type: "warning", title: "Draft not saved / 草稿未保存",
11 message: "The unresponsive interface could not save your draft. Restart anyway? / 界面未能保存当前草稿,仍要重启吗?",
12 detail: "Unsaved draft changes may be lost. Saved conversations will be kept. / 未保存的草稿修改可能丢失,已保存的会话会保留。",
13 buttons: ["Restart anyway / 仍然重启", "Cancel / 取消"], defaultId: 1, cancelId: 1, noLink: true,
14 });
15 return result.response === 0;
16 }
17
18 export function installGraphicsRecovery(options: {
19 graphics: GraphicsSettingsStore; lifecycle: QuitSequencer; log: Logger;
20 logsDir: string; build: string; temporary: boolean;
21 }) {
22 const { graphics, lifecycle, log, logsDir } = options;
23 const record = new GraphicsFaultRecord(join(app.getPath("userData"), "graphics-fault.json"), options.build);
24 const previousFailure = record.pending();
25 const stopping = () => lifecycle.currentPhase !== "idle";
26 const error = (value: unknown) => log.warn(`graphics recovery: ${errorText(value)}`);
27 let actualAcceleration = graphics.current.startupEnabled;
28 let keepOffered = false;
29 const recovery = new GraphicsRecovery({
30 stopping, accelerationEnabled: () => actualAcceleration,
31 record: fault => {
32 log.error(`graphics fault: ${JSON.stringify({ ...fault, requested: graphics.current.startupEnabled, actual: actualAcceleration })}`);
33 try { log.info(`graphics fault processes: ${JSON.stringify(app.getAppMetrics())}`); } catch (e) { error(e); }
34 record.record(fault);
35 },
36 acknowledge: () => record.acknowledge(), error,
37 offer: async gpu => {
38 await app.whenReady();
39 if (stopping()) return;
40 let response: number;
41 do {
42 const shownFault = record.checkpoint();
43 const result = await dialog.showMessageBox({
44 type: "warning", title: "Reasonix recovery / Reasonix 恢复",
45 message: gpu
46 ? "A graphics process failed. Try restarting without hardware acceleration. / 图形进程发生异常,可尝试关闭硬件加速后重启。"
47 : "The interface failed or stopped responding. / 界面发生异常或持续无响应。",
48 detail: "Restarting interrupts running tasks. Reasonix will try to save your draft first. / 重启会中断正在执行的任务,Reasonix 会先尝试保存草稿。",
49 buttons: [gpu ? "Restart in compatibility mode / 以兼容模式重启" : "Restart / 重启", "Open diagnostics / 查看诊断", "Not now / 暂不重启"],
50 defaultId: 2, cancelId: 2, noLink: true,
51 });
52 response = result.response;
53 if (response !== 1) {
54 try { record.acknowledge(shownFault); } catch (e) { error(e); }
55 }
56 if (response === 1 && !stopping()) await shell.openPath(logsDir);
57 } while (response === 1 && !stopping());
58 if (stopping()) return;
59 if (response !== 0) return;
60 const args = process.argv.slice(1).filter(arg => arg !== GRAPHICS_RECOVERY_ARG);
61 if (gpu || options.temporary) args.push(GRAPHICS_RECOVERY_ARG);
62 lifecycle.recoverRenderer(args);
63 },
64 });
65 app.on("child-process-gone", (_event, details) => {
66 if (details.type === "GPU") recovery.fault({ role: "GPU", reason: details.reason, exitCode: details.exitCode });
67 });
68 app.on("gpu-info-update", () => {
69 try {
70 actualAcceleration = app.isHardwareAccelerationEnabled();
71 log.info(`graphics actual acceleration: ${actualAcceleration}`);
72 void app.getGPUInfo("basic").then(info => log.info(`graphics device: ${JSON.stringify(info)}`)).catch(error);
73 } catch (e) { error(e); }
74 });
75 const timer = setInterval(() => recovery.tick(), 1000);
76 timer.unref();
77 app.once("will-quit", () => clearInterval(timer));
78 void app.whenReady().then(() => {
79 if (previousFailure && !options.temporary) recovery.offer(true);
80 }).catch(error);
81 return {
82 recovery,
83 healthy: () => {
84 recovery.healthy();
85 if (!options.temporary || keepOffered || stopping() || recovery.isPrompting) return;
86 keepOffered = recovery.notice(async () => {
87 const shownFault = record.checkpoint();
88 const result = await dialog.showMessageBox({
89 type: "info", title: "Compatibility mode / 兼容模式",
90 message: "Reasonix started with hardware acceleration disabled. / Reasonix 已关闭硬件加速并成功启动。",
91 detail: "You can keep it disabled for future launches and change it later in Settings. This does not establish the cause of the failure. / 可将关闭状态用于后续启动,之后可在设置中修改。成功启动不代表已经确认故障原因。",
92 buttons: ["Keep disabled / 保持关闭", "This launch only / 仅本次关闭"], defaultId: 1, cancelId: 1, noLink: true,
93 });
94 try { record.acknowledge(shownFault); } catch (e) { error(e); }
95 if (result.response === 0 && !stopping()) {
96 try { await graphics.setHardwareAcceleration(false); }
97 catch (e) {
98 error(e);
99 await dialog.showMessageBox({
100 type: "error", title: "Setting not saved / 设置未保存",
101 message: "The preference could not be saved. Compatibility mode still applies to this launch. / 无法保存设置,本次启动仍保持兼容模式。",
102 buttons: ["OK / 确定"], noLink: true,
103 });
104 }
105 }
106 });
107 },
108 };
109 }
110
110 lines TYPESCRIPT