add notice after config

This commit is contained in:
2026-07-31 10:20:33 +08:00
parent 692d8dd18a
commit d45acaf50f
12 changed files with 2721 additions and 2277 deletions
+76 -1
View File
@@ -10,6 +10,9 @@ const { plotJson } = require("./plot-json");
const VOLUME_FIELDS = [
"q_in_val", "dt", "p_max", "fit_low", "fit_high", "T_delta", "xa_full", "num_runs"
];
const IDENTIFICATION_FIELDS = [
"q_in_val", "dt", "n_order", "t_c", "levels", "dead_area", "xa_full", "V_val", "repeat"
];
const DEFAULT_API_URL = "https://ReinLoop.dominatedconvergence.com";
const API_URL = process.env.REINLOOP_API_URL || DEFAULT_API_URL;
const INBOX_POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS || 1000);
@@ -35,13 +38,42 @@ function startPanelInboxPoller(window) {
}
let polling = false;
let notificationDeviceId = null;
const deliveredNotificationIds = new Set();
let notificationFailureReported = false;
async function pollNotifications(requestContext) {
if (notificationDeviceId !== requestContext.deviceId) {
notificationDeviceId = requestContext.deviceId;
deliveredNotificationIds.clear();
}
const pending = await callServer(
{ type: "getPendingPanelNotification", deviceId: requestContext.deviceId },
requestContext
);
notificationFailureReported = false;
const notification = pending.notification;
if (!pending.pending || !notification?.notificationId) return;
if (deliveredNotificationIds.has(notification.notificationId)) return;
deliveredNotificationIds.add(notification.notificationId);
window.webContents.send("panel:notification", notification);
}
const poll = async () => {
if (polling || window.isDestroyed()) return;
if (!connectionState.deviceId || !connectionState.adminToken) return;
if (pendingReview) return;
const requestContext = { ...connectionState };
polling = true;
try {
try {
await pollNotifications(requestContext);
} catch (error) {
if (!notificationFailureReported) {
console.warn(`[${new Date().toISOString()}] Panel 通知轮询不可用: ${error.message}`);
notificationFailureReported = true;
}
}
if (pendingReview) return;
const pending = await callServer(
{ type: "getPendingPanelFile", deviceId: requestContext.deviceId },
requestContext
@@ -128,7 +160,35 @@ function validateParameters(configType, parameters) {
if (!Number.isInteger(parameters.num_runs)) {
throw new Error("num_runs 必须是整数");
}
return;
}
if (configType === "identification") {
const fields = Object.keys(parameters);
const missing = IDENTIFICATION_FIELDS.filter((field) => !(field in parameters));
const extra = fields.filter((field) => !IDENTIFICATION_FIELDS.includes(field));
if (missing.length || extra.length) {
throw new Error(`系统辨识配置字段不匹配。缺少: ${missing.join(", ") || "无"};多余: ${extra.join(", ") || "无"}`);
}
for (const field of IDENTIFICATION_FIELDS) {
if (field === "levels") continue;
if (typeof parameters[field] !== "number" || !Number.isFinite(parameters[field])) {
throw new Error(`${field} 必须是有效数字`);
}
}
if (!Array.isArray(parameters.levels) || parameters.levels.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
throw new Error("levels 必须是有效数字数组");
}
if (parameters.levels.length < 2 || (parameters.levels.length & (parameters.levels.length - 1)) !== 0) {
throw new Error("levels 元素个数必须是不小于 2 的 2 的整数次幂");
}
if (!Number.isInteger(parameters.n_order) || !Number.isInteger(parameters.repeat)) {
throw new Error("n_order 和 repeat 必须是整数");
}
return;
}
throw new Error(`不支持的配置类型: ${configType}`);
}
function toDataUrl(filePath) {
@@ -155,6 +215,21 @@ function registerHandlers() {
return { success: true };
});
ipcMain.handle("panel:ack-notification", (_event, request) =>
callServer({
type: "ackPanelNotification",
deviceId: request.deviceId,
notificationId: request.notificationId
}, request.credentials));
ipcMain.handle("panel:open-notification-file", async (_event, request) => {
if (!request.fileID) throw new Error("该提醒未关联结果文件");
const download = await callServer({ type: "downloadModel", fileID: request.fileID }, request.credentials);
const sourcePath = await downloadFromUrl(download.url, download.fileName || "volume_result.json", request.credentials);
const error = await shell.openPath(sourcePath);
if (error) throw new Error(`无法打开结果文件: ${error}`);
return { filePath: sourcePath };
});
ipcMain.handle("connection:test", async (_event, request) => {
const result = await callServer({ type: "listOrganizations" }, request);
connectionState.apiUrl = String(request.apiUrl || API_URL).trim();