Files
Cyrene/debug/diagnose_layout.mjs
AskaEth a058b0ab8e fix: 第一轮修复 - 记忆管理/IoT操控/历史消息持久化/动作消息/链路优化/安全配置
- 修复记忆管理数据库连接不可用 (ai-core重编译+Unicode修复)
- 修复IoT子会话工具调用链路日志缺失
- 新增最终审查子会话(review_provider) 支持消息格式解析拆分
- 实现历史消息持久化(后端存储+前端分页加载)
- 前端新增动作消息(ActionMessage)类型和渲染
- 优化对话链路速度(非阻塞子会话+快速问候通道)
- JWT密钥环境变量化(无默认值启动panic)
- Token自动刷新机制(401拦截器+refresh接口)
- WebSocket指数退避重连(jitter+最大10次)
- localStorage清理一致性(cyrene_前缀+版本检查)
- IoT环境变量统一为IOT_SERVICE_URL
2026-05-21 23:10:07 +08:00

206 lines
7.4 KiB
JavaScript

#!/usr/bin/env node
/**
* 布局诊断 v3 — 正确使用 CDP Target.createTarget + Page target
*/
import { writeFileSync, mkdirSync } from 'fs';
import WebSocket from 'ws';
const BASE = 'http://localhost:5199';
const API = 'http://localhost:8080/api/v1';
const CDP_PORT = parseInt(process.env.CDP_PORT || '9225');
const OUT = '/home/aska/Code/Cyrene/debug/logs/chromium';
mkdirSync(OUT, { recursive: true });
function makeCDPHelper(ws) {
let msgId = 1;
const pending = new Map();
ws.on('message', (raw) => {
try {
const msg = JSON.parse(raw.toString());
if (msg.id && pending.has(msg.id)) {
pending.get(msg.id)(msg.result || msg);
pending.delete(msg.id);
}
} catch {}
});
return (method, params = {}) => new Promise((resolve) => {
const id = msgId++;
ws.send(JSON.stringify({ id, method, params }));
pending.set(id, resolve);
});
}
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
async function main() {
// Step 1: Login
console.log('[1] Logging in...');
const loginRes = await fetch(`${API}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
});
const loginData = await loginRes.json();
const token = loginData?.token;
const userId = loginData?.user_id || 'admin';
console.log(' userId:', userId, 'token:', token?.slice(0, 20) + '...');
if (!token) throw new Error('Login failed');
// Step 2: Connect to browser CDP, create target
console.log('[2] Getting browser WS URL...');
const ver = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)).json();
const browserWsUrl = ver.webSocketDebuggerUrl;
console.log(' Browser WS:', browserWsUrl.slice(0, 80));
// Connect to browser
const bw = new WebSocket(browserWsUrl);
await new Promise((resolve, reject) => {
bw.once('open', resolve);
bw.once('error', reject);
setTimeout(() => reject(new Error('browser WS timeout')), 5000);
});
const bcdp = makeCDPHelper(bw);
// Create a new page target
console.log('[3] Creating page target...');
const targetResult = await bcdp('Target.createTarget', { url: 'about:blank', width: 1440, height: 900 });
const targetId = targetResult?.targetId;
console.log(' TargetId:', targetId);
// Get page list to find our target's WS URL
const pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json();
const ourPage = pages.find(p => p.id === targetId || p.url === 'about:blank');
const pageWsUrl = ourPage?.webSocketDebuggerUrl;
console.log(' Page WS:', pageWsUrl?.slice(0, 80));
if (!pageWsUrl) throw new Error('Could not find page WS URL');
// Close browser connection, connect to page
bw.close();
// Step 4: Connect to page CDP
console.log('[4] Connecting to page target...');
const pw = new WebSocket(pageWsUrl);
await new Promise((resolve, reject) => {
pw.once('open', resolve);
pw.once('error', reject);
setTimeout(() => reject(new Error('page WS timeout')), 5000);
});
const cdp = makeCDPHelper(pw);
await cdp('Page.enable');
await cdp('Runtime.enable');
await cdp('DOM.enable');
// Step 5: Navigate to set origin, inject auth, reload
console.log('[5] Setting auth...');
await cdp('Page.navigate', { url: BASE });
await sleep(3000);
// Inject localStorage
const injectResult = await cdp('Runtime.evaluate', {
expression: `localStorage.setItem('token', ${JSON.stringify(token)}); localStorage.setItem('user_id', '${userId}'); 'injected';`
});
console.log(' Inject result:', JSON.stringify(injectResult).slice(0, 200));
// Reload to pick up auth
console.log('[6] Reloading with auth...');
await cdp('Page.navigate', { url: BASE });
await sleep(4000);
// Check if on chat page
const checkResult = await cdp('Runtime.evaluate', {
returnByValue: true,
expression: `JSON.stringify({ title: document.title, hasTextarea: !!document.querySelector('textarea'), bodyText: document.body?.innerText?.slice(0, 200) })`
});
console.log(' Page state:', checkResult?.result?.value || 'N/A');
// Step 7: Screenshot
console.log('[7] Taking screenshot...');
const ss = await cdp('Page.captureScreenshot', { format: 'png', clip: { x: 0, y: 0, width: 1440, height: 900, scale: 1 } });
if (ss?.data) {
writeFileSync(`${OUT}/screenshot_layout.png`, Buffer.from(ss.data, 'base64'));
console.log(' ✅ Saved', ss.data.length, 'chars base64');
} else {
console.log(' ⚠️ No screenshot data');
}
// Step 8: Extract layout diagnostics
console.log('[8] Extracting layout info...');
const diagResult = await cdp('Runtime.evaluate', {
returnByValue: true,
expression: `
(() => {
function info(el) {
if (!el) return null;
const s = getComputedStyle(el);
const r = el.getBoundingClientRect();
return {
tag: el.tagName,
cls: (el.className && typeof el.className === 'string') ? el.className.slice(0,300) : '',
rect: { t: Math.round(r.top), l: Math.round(r.left), b: Math.round(r.bottom), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) },
display: s.display, visibility: s.visibility, opacity: s.opacity,
overflow: s.overflow, overflowY: s.overflowY, position: s.position,
zIndex: s.zIndex, flexShrink: s.flexShrink, flexGrow: s.flexGrow,
clientH: el.clientHeight, scrollH: el.scrollHeight,
offsetParent: el.offsetParent ? el.offsetParent.tagName : null,
};
}
const R = {};
R.viewport = { w: innerWidth, h: innerHeight };
R.root = info(document.getElementById('root'));
R.hScreen = info(document.querySelector('.h-screen'));
R.main = info(document.querySelector('main'));
const m = document.querySelector('main');
if (m && m.firstElementChild) {
R.appWrapper = info(m.firstElementChild);
R.appWrapperChildren = Array.from(m.firstElementChild.children).map((c,i) => ({
idx: i,
cls: (c.className && typeof c.className === 'string') ? c.className.slice(0,200) : '',
...info(c),
}));
}
R.chatBackground = info(document.querySelector('.chat-background'));
const cb = document.querySelector('.chat-background');
if (cb) {
R.chatBgChildren = Array.from(cb.children).map((c,i) => ({
idx: i,
cls: (c.className && typeof c.className === 'string') ? c.className.slice(0,200) : '',
...info(c),
}));
}
R.textarea = info(document.querySelector('textarea'));
R.shrink0 = Array.from(document.querySelectorAll('.flex-shrink-0')).map(el => ({
text: (el.textContent||'').slice(0,60),
...info(el),
}));
R.borderT = Array.from(document.querySelectorAll('.border-t')).map(el => ({
text: (el.textContent||'').slice(0,100),
...info(el),
}));
return JSON.stringify(R);
})()
`
});
const diagValue = diagResult?.result?.value || diagResult?.value || '{}';
const result = typeof diagValue === 'string' ? JSON.parse(diagValue) : diagValue;
console.log(JSON.stringify(result, null, 2));
writeFileSync(`${OUT}/diagnostics.json`, JSON.stringify(result, null, 2));
pw.close();
console.log('\n✅ Done');
}
main().catch(err => {
console.error('Fatal:', err);
process.exit(1);
});