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