debug_redbox_recognition.js 5.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. // 测试:默认识别规则(红框)识别用户户型图内红框柜子并报价
  2. // 运行:node debug_redbox_recognition.js
  3. const { chromium } = require('playwright-core');
  4. const fs = require('fs');
  5. const IMG = 'd:/AI文件夹/预算报价/test_redbox_input.png';
  6. const URL = 'http://127.0.0.1:8931/AI%E5%BF%AB%E9%80%9F%E6%8A%A5%E4%BB%B7_Demo2.optimizing.html?v=redbox-test';
  7. const log = o => { fs.writeFileSync('debug_redbox_result.json', JSON.stringify(o, null, 1)); console.log(JSON.stringify(o, null, 1)); };
  8. (async () => {
  9. const stage = s => fs.writeFileSync('debug_redbox_stage.txt', s + ' @ ' + new Date().toISOString());
  10. process.on('exit', code => { try { fs.appendFileSync('debug_redbox_stage.txt', '\nexit:' + code); } catch (e) {} });
  11. process.on('uncaughtException', e => { try { fs.appendFileSync('debug_redbox_stage.txt', '\nuncaught:' + e.message); } catch (e2) {} });
  12. process.on('unhandledRejection', e => { try { fs.appendFileSync('debug_redbox_stage.txt', '\nrejection:' + String(e && e.message || e).slice(0, 300)); } catch (e2) {} });
  13. stage('launching');
  14. const b = await chromium.launchPersistentContext('d:/AI文件夹/预算报价/.edge-profile-' + Date.now(), { headless: true, executablePath: 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', args: ['--no-first-run', '--no-default-browser-check', '--no-sandbox', '--disable-gpu', '--disable-breakpad', '--disable-crash-reporter'] });
  15. b.on('close', () => { try { fs.appendFileSync('debug_redbox_stage.txt', '\nbrowser-close @ ' + new Date().toISOString()); } catch (e) {} });
  16. stage('launched');
  17. const p = await b.newPage({ viewport: { width: 1400, height: 950 } });
  18. // 只放行本地服务与 data/blob,阻断外部网络(qwen 等调用按设计静默回退本地识别)
  19. await p.route('**/*', r => { const u = r.request().url(); if (u.startsWith('http://127.0.0.1') || u.startsWith('data:') || u.startsWith('blob:')) return r.continue(); return r.abort(); });
  20. const errors = [];
  21. const consoleLog = [];
  22. p.on('pageerror', e => errors.push('pageerror: ' + e.message));
  23. p.on('console', m => { consoleLog.push(m.type() + ': ' + m.text().slice(0, 200)); if (consoleLog.length > 150) consoleLog.shift(); });
  24. await p.goto(URL, { waitUntil: 'load' });
  25. await p.evaluate(() => { try { localStorage.clear(); } catch (e) {} });
  26. await p.reload({ waitUntil: 'load' });
  27. await p.waitForTimeout(1000);
  28. stage('loaded');
  29. // 1) 默认识别规则应为红框
  30. stage('pre-rule');
  31. const rule = await p.evaluate(() => ({ color: window.qCadRuleColor, name: window.qCadRuleColorName, mode: state.quoteMode }));
  32. stage('rule:' + rule.color);
  33. // 2) 上传户型图(统一上传入口,图片自动路由到图纸处理)
  34. await p.setInputFiles('#q-file-unified', IMG);
  35. stage('uploaded');
  36. // 3) 轮询等待识别完成(每10秒记录进度,最长300秒)
  37. let done = false, diag = null;
  38. for (let i = 0; i < 30; i++) {
  39. await p.waitForTimeout(10000);
  40. diag = await p.evaluate(() => ({
  41. hasRecog: !!(window.state && state.qRecogResult),
  42. boxes: (state.qRecogResult && state.qRecogResult.boxes) ? state.qRecogResult.boxes.length : -1,
  43. qwen: state.qRecogResult ? state.qRecogResult.qwen : null,
  44. cabs: window.qAIPlacedCabinets ? qAIPlacedCabinets.length : -1
  45. })).catch(e => ({ pollErr: String(e.message).slice(0, 120) }));
  46. fs.appendFileSync('debug_redbox_stage.txt', '\npoll' + i + ': ' + JSON.stringify(diag));
  47. if (diag && diag.cabs > 0) { done = true; break; }
  48. }
  49. if (!done) {
  50. const tail = await p.evaluate(() => { const el = document.getElementById('q-ai-log'); return el ? el.innerText.slice(-1500) : null; }).catch(() => null);
  51. log({ fail: 'recognition not done in 300s', diag, logTail: tail, consoleTail: consoleLog.slice(-40), errors });
  52. await b.close();
  53. process.exit(1);
  54. }
  55. await p.waitForTimeout(800);
  56. stage('recognized');
  57. const recog = await p.evaluate(() => ({
  58. ratio: state.qRecogResult.ratio,
  59. ratioSource: state.qRecogResult.source,
  60. dimCount: state.qRecogResult.dimCount,
  61. labelCount: state.qRecogResult.labelCount,
  62. qwen: state.qRecogResult.qwen,
  63. qwenNamed: state.qRecogResult.qwenNamed,
  64. applied: state.qRecogResult.applied,
  65. boxes: state.qRecogResult.boxes.map(b => ({ x: b.x, y: b.y, w: b.w, h: b.h, wm: b.wm, hm: b.hm })),
  66. cabinets: qAIPlacedCabinets.map(c => ({ name: c.type, width: c.width, height: c.height, source: c.source, pos: c.posLabel || '', kitchenWalls: c.kitchenWalls || null })),
  67. rooms: qRooms.map(r => ({ room: r.roomName, cab: r.cabinetType, w: r.cabinetWidth, h: r.cabinetHeight, area: +(+r.projectArea).toFixed(2) }))
  68. }));
  69. await p.screenshot({ path: 'debug_redbox_step1.png' });
  70. // 4) 进入计价与报价单
  71. await p.evaluate(() => goToStep(3));
  72. await p.waitForTimeout(800);
  73. await p.evaluate(() => goToStep(4));
  74. await p.waitForTimeout(1500);
  75. stage('step4');
  76. const quote = await p.evaluate(() => {
  77. const t = id => { const el = document.getElementById(id); return el ? el.textContent.trim() : null; };
  78. const s4 = document.getElementById('step-4');
  79. return {
  80. grandTotal: t('q-grand-total-value'),
  81. grandArea: t('q-grand-total-area'),
  82. grandNote: t('q-grand-total-note'),
  83. step4Text: s4 ? s4.innerText.replace(/\n{2,}/g, '\n').slice(0, 2500) : null
  84. };
  85. });
  86. await p.screenshot({ path: 'debug_redbox_step4.png', fullPage: true });
  87. log({ rule, recog, quote, errors });
  88. await b.close();
  89. })().catch(e => { log({ fail: String(e && e.message || e), stack: String(e && e.stack || '').slice(0, 500) }); process.exit(1); });