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,262 @@
|
||||
#!/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); });
|
||||
Reference in New Issue
Block a user