Files
Cyrene/debug/diagnose_targeted.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

263 lines
9.8 KiB
JavaScript

#!/usr/bin/env node
/**
* 精准诊断 — 定位 IoTStatusBar 和 ChatInput 在 DOM 中的确切位置
*/
import WebSocket from 'ws';
const CDP_PORT = parseInt(process.env.CDP_PORT || '9225');
const BASE = 'http://localhost:5199';
const API = 'http://localhost:8080/api/v1';
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, reject) => {
const id = msgId++;
const timer = setTimeout(() => { pending.delete(id); reject(new Error(`CDP timeout: ${method}`)); }, 15000);
ws.send(JSON.stringify({ id, method, params }));
pending.set(id, (r) => { clearTimeout(timer); resolve(r); });
});
}
async function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
async function evaluate(cdp, expression, label) {
try {
const r = await cdp('Runtime.evaluate', { returnByValue: true, expression });
const val = r?.result?.value ?? r?.value;
console.log(`\n=== ${label} ===`);
console.log(typeof val === 'string' ? val : JSON.stringify(val, null, 2));
return val;
} catch (e) {
console.error(`[${label}] ERROR:`, e.message);
return null;
}
}
async function main() {
// Login
const loginRes = await fetch(`${API}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'admin', password: 'admin123' }),
});
const { token, user_id: userId } = await loginRes.json();
if (!token) throw new Error('Login failed');
// Connect to existing page or create new one
let pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json();
console.log('Pages:', pages.map(p => ({ id: p.id?.slice(0, 20), url: p.url?.slice(0, 60) })));
// Prefer existing localhost page, or navigate a page to localhost
let ourPage = pages.find(p => p.url === BASE || p.url === `${BASE}/`);
let needNavigate = false;
if (!ourPage) {
// Create new target
const ver = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json/version`)).json();
const bw = new WebSocket(ver.webSocketDebuggerUrl);
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);
await bcdp('Target.createTarget', { url: 'about:blank', width: 1440, height: 900 });
bw.close();
await sleep(1000);
pages = await (await fetch(`http://127.0.0.1:${CDP_PORT}/json`)).json();
ourPage = pages.find(p => p.url === 'about:blank');
needNavigate = true;
}
if (!ourPage) throw new Error('No page found');
console.log('Using page:', { id: ourPage.id?.slice(0, 20), url: ourPage.url });
// Connect to page
const pw = new WebSocket(ourPage.webSocketDebuggerUrl);
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');
// Navigate, inject auth, reload
await cdp('Page.navigate', { url: BASE });
await sleep(3000);
await cdp('Runtime.evaluate', { expression: `localStorage.setItem('token', ${JSON.stringify(token)}); localStorage.setItem('user_id', '${userId}');` });
await cdp('Page.navigate', { url: BASE });
await sleep(5000);
// === DIAGNOSIS 1: Main element's full DOM structure ===
await evaluate(cdp, `
(() => {
const main = document.querySelector('main');
if (!main) return 'NO_MAIN';
function describe(el, maxDepth) {
if (!el || maxDepth <= 0) return null;
const r = el.getBoundingClientRect();
const cs = getComputedStyle(el);
const txt = (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 80);
return {
tag: el.tagName,
cls: (el.className?.slice?.(0, 200) || ''),
id: el.id || '',
rect: { t: Math.round(r.top), b: Math.round(r.bottom), l: Math.round(r.left), r: Math.round(r.right), w: Math.round(r.width), h: Math.round(r.height) },
cs: { display: cs.display, overflow: cs.overflow, height: cs.height, flexShrink: cs.flexShrink, flexGrow: cs.flexGrow },
clientH: el.clientHeight, scrollH: el.scrollHeight,
txt,
kids: maxDepth > 1 ? Array.from(el.children).map(c => describe(c, maxDepth - 1)) : undefined,
};
}
return describe(main, 5);
})()
`, 'MainDOMTree');
// === DIAGNOSIS 2: Find ChatContainer (.chat-background) and its children ===
await evaluate(cdp, `
(() => {
const cb = document.querySelector('.chat-background');
if (!cb) return 'NO_CHAT_BACKGROUND';
const r = cb.getBoundingClientRect();
const cs = getComputedStyle(cb);
const kids = Array.from(cb.children).map((c, i) => {
const cr = c.getBoundingClientRect();
const ccs = getComputedStyle(c);
return {
idx: i,
tag: c.tagName,
cls: (c.className?.slice?.(0, 200) || ''),
rect: { t: Math.round(cr.top), b: Math.round(cr.bottom), h: Math.round(cr.height) },
cs: { display: ccs.display, flexShrink: ccs.flexShrink, flexGrow: ccs.flexGrow, height: ccs.height },
clientH: c.clientHeight,
txt: (c.textContent || '').replace(/\\s+/g, ' ').slice(0, 100),
};
});
return {
chatBgRect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
chatBgCS: { height: cs.height, overflow: cs.overflow, display: cs.display },
chatBgClientH: cb.clientHeight,
children: kids,
};
})()
`, 'ChatContainerChildren');
// === DIAGNOSIS 3: Check if IoTStatusBar is rendered at all ===
await evaluate(cdp, `
(() => {
// Search for all elements that might be the IoTStatusBar
const results = [];
// Strategy 1: Find div with "IoT" text that is a direct child of .chat-background
const cb = document.querySelector('.chat-background');
if (cb) {
const iotInChatBg = Array.from(cb.querySelectorAll('*')).filter(el =>
el.textContent?.includes('IoT') && el.children.length === 0
);
results.push({
strategy: 'IoT_leaf_in_chatBg',
count: iotInChatBg.length,
elements: iotInChatBg.slice(0, 3).map(el => {
const r = el.getBoundingClientRect();
return {
tag: el.tagName,
txt: (el.textContent || '').slice(0, 80),
rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
};
}),
});
}
// Strategy 2: Find .border-t elements inside .chat-background
if (cb) {
const borderTInCb = Array.from(cb.querySelectorAll('.border-t'));
results.push({
strategy: 'borderT_in_chatBg',
count: borderTInCb.length,
elements: borderTInCb.map(el => {
const r = el.getBoundingClientRect();
let parentCls = '';
let p = el.parentElement;
for (let i = 0; i < 3 && p; i++) {
parentCls += (p.className?.slice?.(0, 100) || '') + ' | ';
p = p.parentElement;
}
return {
rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
txt: (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 100),
parentChain: parentCls,
};
}),
});
}
// Strategy 3: Find all flex-shrink-0 inside .chat-background
if (cb) {
const shrinkInCb = Array.from(cb.querySelectorAll('.flex-shrink-0'));
results.push({
strategy: 'flexShrink0_in_chatBg',
count: shrinkInCb.length,
elements: shrinkInCb.map(el => {
const r = el.getBoundingClientRect();
return {
rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
cls: (el.className?.slice?.(0, 200) || ''),
txt: (el.textContent || '').replace(/\\s+/g, ' ').slice(0, 100),
display: getComputedStyle(el).display,
};
}),
});
}
return results;
})()
`, 'IoTStatusBarSearch');
// === DIAGNOSIS 4: Get complete rect for every child of the App wrapper ===
await evaluate(cdp, `
(() => {
const main = document.querySelector('main');
if (!main?.firstElementChild) return 'NO_MAIN_CHILD';
const appWrapper = main.firstElementChild;
return {
appWrapperCls: appWrapper.className?.slice?.(0, 200),
appWrapperRect: (() => { const r = appWrapper.getBoundingClientRect(); return { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) }; })(),
children: Array.from(appWrapper.children).map((c, i) => {
const r = c.getBoundingClientRect();
const cs = getComputedStyle(c);
return {
idx: i,
cls: (c.className?.slice?.(0, 200) || ''),
rect: { t: Math.round(r.top), b: Math.round(r.bottom), h: Math.round(r.height) },
cs: { display: cs.display, flexGrow: cs.flexGrow, flexShrink: cs.flexShrink, height: cs.height, overflow: cs.overflow },
clientH: c.clientHeight,
childCount: c.children.length,
firstChildCls: c.firstElementChild ? (c.firstElementChild.className?.slice?.(0, 150) || '') : '',
};
}),
};
})()
`, 'AppWrapperChildren');
pw.close();
console.log('\n✅ Done.');
}
main().catch(err => { console.error('Fatal:', err); process.exit(1); });