|
|
#!/usr/bin/env tsx
|
|
|
/**
|
|
|
* 有头可视化补录 — 逐条跑 canonical 路径,失败时保持浏览器供人工观测中断点
|
|
|
*
|
|
|
* npm run visual:priority1-canonical-inspect
|
|
|
* $env:PRIORITY1_INSPECT_FILTER="P01"; npm run visual:priority1-canonical-inspect
|
|
|
* $env:PRIORITY1_INSPECT_START="9"; npm run visual:priority1-canonical-inspect # 从 F01 起
|
|
|
* $env:PRIORITY1_INSPECT_STEP2_ONLY="true"; ... # 仅填 Step2,不等待报价
|
|
|
*/
|
|
|
process.env.RPA_HEADLESS = "false";
|
|
|
process.env.RPA_KEEP_BROWSER_OPEN = "true";
|
|
|
process.env.RPA_SLOW_MO_MS = process.env.RPA_SLOW_MO_MS ?? "200";
|
|
|
process.env.RPA_DWELL_MS = process.env.RPA_DWELL_MS ?? "3500";
|
|
|
process.env.RPA_QUOTE_WAIT_MS = process.env.RPA_QUOTE_WAIT_MS ?? "120000";
|
|
|
|
|
|
import { createInterface } from "node:readline/promises";
|
|
|
import { stdin as input, stdout as output } from "node:process";
|
|
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
import { join } from "node:path";
|
|
|
import {
|
|
|
formatPriority1InputSummary,
|
|
|
PRIORITY1_CANONICAL_PATHS,
|
|
|
} from "@/workers/rpa/priority1/demo-input";
|
|
|
import { buildPriority1FormPlan } from "@/workers/rpa/priority1/form-logic-map";
|
|
|
|
|
|
const outRoot = join(process.cwd(), ".rpa", "visual-priority1-canonical-inspect");
|
|
|
const FILTER = process.env.PRIORITY1_INSPECT_FILTER?.trim();
|
|
|
const START = Number(process.env.PRIORITY1_INSPECT_START ?? 0);
|
|
|
const LIMIT = Number(process.env.PRIORITY1_INSPECT_LIMIT ?? 0);
|
|
|
const STEP2_ONLY = process.env.PRIORITY1_INSPECT_STEP2_ONLY === "true";
|
|
|
|
|
|
function filterPaths() {
|
|
|
let paths = [...PRIORITY1_CANONICAL_PATHS];
|
|
|
if (FILTER) {
|
|
|
const re = new RegExp(FILTER, "i");
|
|
|
paths = paths.filter((p) => re.test(p.id) || re.test(p.title));
|
|
|
}
|
|
|
if (START > 0) paths = paths.slice(START);
|
|
|
if (LIMIT > 0) paths = paths.slice(0, LIMIT);
|
|
|
return paths;
|
|
|
}
|
|
|
|
|
|
async function waitContinue(rl: ReturnType<typeof createInterface>, msg: string) {
|
|
|
console.log(`\n${msg}`);
|
|
|
await rl.question("按 Enter 继续下一条路径(Ctrl+C 退出)… ");
|
|
|
}
|
|
|
|
|
|
async function main(): Promise<void> {
|
|
|
const paths = filterPaths();
|
|
|
if (paths.length === 0) {
|
|
|
console.error("[inspect-canonical] 无匹配路径");
|
|
|
process.exit(1);
|
|
|
}
|
|
|
|
|
|
mkdirSync(outRoot, { recursive: true });
|
|
|
const { runPriority1VisualChain } = await import("./visual-priority1-full-chain");
|
|
|
const rl = createInterface({ input, output });
|
|
|
|
|
|
console.log("\n[inspect-canonical] 有头可视化补录 — 人工观测链路中断点");
|
|
|
console.log(`[inspect-canonical] 共 ${paths.length} 条 / ${PRIORITY1_CANONICAL_PATHS.length} canonical`);
|
|
|
console.log(`[inspect-canonical] Step2 后${STEP2_ONLY ? "停止(不提交报价)" : "提交并等待报价"}`);
|
|
|
console.log(`[inspect-canonical] 截图目录: ${outRoot}\n`);
|
|
|
|
|
|
const sessionLog: Array<{
|
|
|
id: string;
|
|
|
ok: boolean;
|
|
|
error?: string;
|
|
|
quoteCount: number;
|
|
|
outDir: string;
|
|
|
}> = [];
|
|
|
|
|
|
for (let i = 0; i < paths.length; i += 1) {
|
|
|
const path = paths[i]!;
|
|
|
console.log(`\n${"═".repeat(64)}`);
|
|
|
console.log(`[inspect-canonical] ▶ ${i + 1}/${paths.length} ${path.id}`);
|
|
|
console.log(` ${path.title} — ${path.persona}`);
|
|
|
console.log(` 原验证状态: ${path.verified === "recorded" ? "已录制" : "待录制"}`);
|
|
|
console.log("─".repeat(64));
|
|
|
console.log(formatPriority1InputSummary(path.input));
|
|
|
console.log("─".repeat(64));
|
|
|
console.log("[form-logic-map] 填写计划:");
|
|
|
for (const row of buildPriority1FormPlan(path.input)) {
|
|
|
console.log(` ${row.order}. [${row.step}] ${row.field.label} ← ${row.value}`);
|
|
|
}
|
|
|
console.log("─".repeat(64));
|
|
|
|
|
|
const result = await runPriority1VisualChain({
|
|
|
demo: path.input,
|
|
|
caseId: path.id,
|
|
|
outDir: join(outRoot, path.id),
|
|
|
keepBrowserOpen: true,
|
|
|
stopAfterStep2: STEP2_ONLY,
|
|
|
});
|
|
|
|
|
|
sessionLog.push({
|
|
|
id: path.id,
|
|
|
ok: result.ok,
|
|
|
error: result.error,
|
|
|
quoteCount: result.quoteCount,
|
|
|
outDir: result.outDir,
|
|
|
});
|
|
|
|
|
|
if (result.ok && result.quoteCount > 0) {
|
|
|
console.log(`\n[inspect-canonical] ✓ ${path.id} 报价 ${result.quoteCount} 条`);
|
|
|
} else if (result.ok && STEP2_ONLY) {
|
|
|
console.log(`\n[inspect-canonical] ✓ ${path.id} Step2 填写观测通过`);
|
|
|
} else {
|
|
|
console.log(`\n[inspect-canonical] ✗ ${path.id} 中断: ${result.error ?? "未知"}`);
|
|
|
}
|
|
|
|
|
|
if (i < paths.length - 1) {
|
|
|
await waitContinue(rl, "[inspect-canonical] 请核对浏览器与截图后");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
rl.close();
|
|
|
|
|
|
const logPath = join(outRoot, "inspect-session.json");
|
|
|
writeFileSync(
|
|
|
logPath,
|
|
|
`${JSON.stringify({ finishedAt: new Date().toISOString(), paths: sessionLog }, null, 2)}\n`,
|
|
|
"utf8",
|
|
|
);
|
|
|
|
|
|
const pass = sessionLog.filter((r) => r.ok).length;
|
|
|
console.log(`\n[inspect-canonical] 会话结束 ${pass}/${sessionLog.length} 通过`);
|
|
|
console.log(`[inspect-canonical] 日志: ${logPath}`);
|
|
|
}
|
|
|
|
|
|
main().catch((e) => {
|
|
|
console.error(e);
|
|
|
process.exit(1);
|
|
|
});
|