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