import './wb-legacy-disabled.mjs' /** * 采集首页的垂直结构(各区块 y 坐标与尺寸)。 * * 用法:node scripts/wb-home-layout.mjs [outJson] */ import { writeFile } from 'node:fs/promises' const port = Number(process.argv[2] ?? 9223) const outJson = process.argv[3] ?? null async function main() { const list = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json() const page = list.filter((t) => t.type === 'page')[0] if (!page) throw new Error('未找到页面') const ws = new WebSocket(page.webSocketDebuggerUrl) await new Promise((r, j) => { const t = setTimeout(() => j(new Error('连接超时')), 8000) ws.addEventListener('open', () => { clearTimeout(t) r() }) ws.addEventListener('error', () => { clearTimeout(t) j(new Error('连接失败')) }) }) const send = (method, params = {}) => new Promise((res, rej) => { const id = Math.floor(Math.random() * 1e9) const timer = setTimeout(() => rej(new Error(`超时 ${method}`)), 25000) const h = (ev) => { const m = JSON.parse(ev.data) if (m.id === id) { clearTimeout(timer) ws.removeEventListener('message', h) res(m.result) } } ws.addEventListener('message', h) ws.send(JSON.stringify({ id, method, params })) }) // 逐类名精确取矩形;找不到就跳过 const expr = `(() => { const SEL = [ '.workbuddy-topbar', '.conversation-sidebar', '[class*="welcome"]', '[class*="greeting"]', '.wb-segmented', '.quick-actions', '.quick-actions__list', '[class*="input-box"]', '[class*="cr-input"]', '.cr-input-container', '.cr-input-editor-host', '[class*="permission-setting"]', '.cr-input-footer', '[class*="suggestion"]', '[class*="quick-prompt"]', 'img[class*="mascot"]', '[class*="mascot"]', '[class*="empty"]', '[class*="scene"]' ]; const clean = (v) => (v == null ? '' : String(v).replace(/\\s+/g, ' ').trim()); const out = []; for (const sel of SEL) { let nodes = []; try { nodes = [...document.querySelectorAll(sel)]; } catch { continue; } if (!nodes.length) continue; out.push({ sel, count: nodes.length, items: nodes.slice(0, 6).map((el) => { const r = el.getBoundingClientRect(); const cs = getComputedStyle(el); return { cls: clean(el.className).slice(0, 90), rect: [Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height)], display: cs.display, flexDirection: cs.flexDirection, alignItems: cs.alignItems, justifyContent: cs.justifyContent, gap: cs.gap, padding: cs.padding, margin: cs.margin, radius: cs.borderRadius, bg: cs.backgroundColor, shadow: cs.boxShadow === 'none' ? '' : cs.boxShadow, fontSize: cs.fontSize, fontWeight: cs.fontWeight }; }) }); } // 主内容容器的直接子元素序列(看垂直堆叠顺序) const side = document.querySelector('.conversation-sidebar'); const sideRight = side ? side.getBoundingClientRect().right : 240; let best = null, bestArea = 0; document.querySelectorAll('div').forEach((el) => { const r = el.getBoundingClientRect(); if (r.x >= sideRight - 4 && r.width > 500 && r.height > 300) { const a = r.width * r.height; if (a > bestArea) { bestArea = a; best = el; } } }); let stack = []; if (best) { const walk = (el, depth, path) => { if (depth > 4 || stack.length > 60) return; [...el.children].forEach((c, i) => { const r = c.getBoundingClientRect(); if (r.width < 60 || r.height < 8) return; stack.push({ depth, path: path + '/' + i, tag: c.tagName.toLowerCase(), cls: clean(c.className).slice(0, 70), rect: [Math.round(r.x), Math.round(r.y), Math.round(r.width), Math.round(r.height)], text: clean(c.textContent).slice(0, 40) }); walk(c, depth + 1, path + '/' + i); }); }; walk(best, 0, ''); } return JSON.stringify({ viewport: { w: innerWidth, h: innerHeight }, out, stack }); })()` const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true }) const data = JSON.parse(r.result.value) console.log(`viewport ${data.viewport.w}x${data.viewport.h}\n`) for (const entry of data.out) { console.log(`### ${entry.sel} (${entry.count})`) for (const it of entry.items) { console.log(` .${it.cls}`) console.log(` rect ${JSON.stringify(it.rect)} display=${it.display}/${it.flexDirection} align=${it.alignItems} justify=${it.justifyContent} gap=${it.gap}`) console.log(` pad=${it.padding} margin=${it.margin}`) console.log(` radius=${it.radius} bg=${it.bg} fs=${it.fontSize}/${it.fontWeight}`) if (it.shadow) console.log(` shadow=${it.shadow}`) } console.log('') } console.log('### 垂直堆叠(y 升序)') const sorted = [...data.stack].sort((a, b) => a.rect[1] - b.rect[1]) for (const s of sorted) { console.log(` y=${String(s.rect[1]).padStart(4)} h=${String(s.rect[3]).padStart(4)} x=${String(s.rect[0]).padStart(4)} w=${String(s.rect[2]).padStart(4)} ${' '.repeat(s.depth)}${s.tag}.${s.cls.split(/\s+/)[0]} "${s.text}"`) } if (outJson) { await writeFile(outJson, JSON.stringify(data, null, 2), 'utf8') console.log(`\n已写出 ${outJson}`) } ws.close() } main().catch((e) => { console.error('失败:', e.message) process.exit(1) })