a058b0ab8e
- 修复记忆管理数据库连接不可用 (ai-core重编译+Unicode修复) - 修复IoT子会话工具调用链路日志缺失 - 新增最终审查子会话(review_provider) 支持消息格式解析拆分 - 实现历史消息持久化(后端存储+前端分页加载) - 前端新增动作消息(ActionMessage)类型和渲染 - 优化对话链路速度(非阻塞子会话+快速问候通道) - JWT密钥环境变量化(无默认值启动panic) - Token自动刷新机制(401拦截器+refresh接口) - WebSocket指数退避重连(jitter+最大10次) - localStorage清理一致性(cyrene_前缀+版本检查) - IoT环境变量统一为IOT_SERVICE_URL
206 lines
7.4 KiB
JavaScript
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);
|
|
});
|