const fs = require("node:fs"); const path = require("node:path"); const readline = require("node:readline/promises"); const { callServer, downloadFromUrl } = require("./server-client"); const { publishConfig } = require("./b-admin"); const { plotCsv } = require("./plot-csv"); const { plotJson } = require("./plot-json"); const API_URL = process.env.REINLOOP_API_URL; const B_ADMIN_TOKEN = process.env.B_ADMIN_TOKEN; const DEVICE_ID = process.env.REINLOOP_DEVICE_ID; const REVIEW_MODE = process.env.REVIEW_MODE || "manual"; const POLL_INTERVAL_MS = Number(process.env.POLL_INTERVAL_MS || 3000); let polling = false; async function reviewIdentification(message, imagePath) { if (REVIEW_MODE !== "manual") { console.log(`已生成 ${imagePath};REVIEW_MODE=${REVIEW_MODE},跳过人工评审`); return; } const prompt = readline.createInterface({ input: process.stdin, output: process.stdout }); try { console.log(`请查看辨识图像: ${imagePath}`); let decision; while (decision !== 0 && decision !== 1) { const answer = (await prompt.question("参数是否通过?输入 1=通过,0=不通过: ")).trim(); if (answer === "0" || answer === "1") decision = Number(answer); } if (decision === 0) { const configPath = (await prompt.question("请输入新的函数2参数 JSON 路径: ")).trim(); const config = JSON.parse(await fs.promises.readFile(path.resolve(configPath), "utf8")); await publishConfig("identification", config.parameters || config, { apiUrl: API_URL, adminToken: B_ADMIN_TOKEN, deviceId: DEVICE_ID }); } const result = await callServer({ type: "setIdentificationFeedback", deviceId: DEVICE_ID, runId: message.fileName, result: decision }); console.log(`辨识结果已提交: ${result.result === 1 ? "通过" : "不通过"}`); } finally { prompt.close(); } } async function processPendingMessage() { const message = await callServer({ type: "getPendingPanelFile", deviceId: DEVICE_ID }); if (!message.pending) return false; const sourcePath = await downloadFromUrl(message.url, message.fileName); if (message.mediaType === "json") { await plotJson(sourcePath); } else { const imagePath = await plotCsv(sourcePath); await reviewIdentification(message, imagePath); } await callServer({ type: "ackPanelFile", deviceId: DEVICE_ID, fileID: message.fileID }); return true; } async function poll() { if (polling) return; polling = true; try { while (await processPendingMessage()) { // Drain messages already queued on the server before waiting again. } } catch (error) { console.error(`[${new Date().toISOString()}] 消息处理失败: ${error.message}`); } finally { polling = false; } } function validateConfig() { if (!API_URL) throw new Error("缺少环境变量 REINLOOP_API_URL"); if (!DEVICE_ID) throw new Error("缺少环境变量 REINLOOP_DEVICE_ID"); if (REVIEW_MODE === "manual" && !B_ADMIN_TOKEN) { throw new Error("人工评审模式缺少环境变量 B_ADMIN_TOKEN"); } if (!Number.isFinite(POLL_INTERVAL_MS) || POLL_INTERVAL_MS < 1000) { throw new Error("POLL_INTERVAL_MS 必须是大于或等于 1000 的数字"); } } function main() { try { validateConfig(); console.log(`开始获取设备 ${DEVICE_ID} 的待处理消息,轮询间隔 ${POLL_INTERVAL_MS}ms`); void poll(); setInterval(poll, POLL_INTERVAL_MS); } catch (error) { console.error(error.message); process.exitCode = 1; } } if (require.main === module) main(); module.exports = { processPendingMessage };