新功能需求
August 22, 2026 · View on GitHub
状态:已实现(2026-08-22) 背景:AI 无法精确知道用户在浏览器预览里指的是哪个元素,纯文字描述反复对齐成本极高 (dsh-webui 玻璃质感主题迭代中已实际暴露此痛点)。
目标
在 AI 浏览器预览抽屉中支持「选取元素」:
- 用户点击 dock 工具条上的「选取元素」按钮进入选取模式;
- 在预览画面上点击任意元素;
- 该元素的定位信息自动填入 DSH 对话框输入框,作为给 AI 的上下文;
- 再点一次按钮或按 Esc 退出选取模式。
技术要点
预览链路
- 预览抽屉组件:
src/client/browser/BrowserActivityDock.tsx - 抽屉容器样式:
dsh-browser-drawer(z-index: 8801,fixed right:0全高) - 画面来源:screencast 流;相关 host 路由(
src/browser/index.ts):/api/dsh-browser/screencast | frame | session | input | navigate | tabs | status
交互与实现路径
- 选取模式开关:dock 头部加按钮(明显状态指示,开启时高亮)。
- 坐标捕获:开启后拦截抽屉画面的下一次点击,取画面内坐标, 按画面缩放比换算到目标页真实视口坐标。
- 元素信息采集:经 CDP
Runtime.evaluate在目标页执行脚本:document.elementFromPoint(x, y)- 生成唯一 CSS 选择器(id 优先 → nth-of-type 路径)
- 元素摘要:tag / id / class / role / 可见文本截断(~120 字符)
- 回传进对话框:走槽位组件的标准 kit
inputActions.setDraft(对齐 PromptOptimizeButton 的「唯一公开写入路径」,不碰 DOM、不用原生 setter), 把`[选择器] 元素摘要`追加到草稿尾部。 - 填入格式:
`[选择器] 元素摘要`追加到输入框尾部, 待用户补充问题后手动发送(未提供「立即发送」)。
实现记录(与需求稿的差异)
- 回填未采用「DOM 直填」:
BrowserSeat是conversation.input.left槽位, 组件天然拿到 ownerinput.draft+ kitinputActions.setDraft,比querySelector('textarea')+ 原生 setter 更可靠(React 受控组件不丢值)。 - 选取模式依赖「detach 原生视图 → 画面回到 img 帧流」:壳内 WebContentsView 贴合后会把画面区的鼠标/键盘事件直接吃进页面(React 收不到),必须先 detach 才能用 React 点击捕获;退出选取模式(或采集成功)再重新贴合。
- 帧兜底截图在壳内模式直走
captureScreenshot(..., fromSurface=false)(detached 视图等不到合成帧,surface 尝试会卡满超时),并从window.innerWidth/Height回填帧尺寸——toPage坐标换算依赖它,缺失会导致选取坐标错位。 - 选取模式下拦截全部 input 回传(move/down/up/click/wheel/key),只保留 「点击采集」,避免 detach 后仍经 Input 域误点页面。
- hover 范围提示(2026-08-22 追加):选取模式下鼠标移动不再直接 return,而是
经
/api/dsh-browser/element实时采集命中元素的getBoundingClientRect(页面视口 CSS 坐标);前端按截图缩放比换算后在画面叠加半透明高亮框 + 左上角<tag>标签,像浏览器 DevTools 一样「先看到范围再点」。hover 请求 用「合并发送」(一次在途只保留最新坐标)避免 mousemove 堆积;Esc/退出/采集 成功时用递增序号使在途请求失效并清除高亮框。 - 关键修复(2026-08-22):选取模式的 detach 方案有个致命坑——detach 后
WebContentsView 不再合成,
captureScreenshot(fromSurface 无论 true/false) 和Page.startScreencast都会超时/零帧,画面退回 img 后是空的,且document.elementFromPoint因视口归零(innerWidth=0)返回 null → 采集 found:false → 「选中后不进对话框」。解决:detach 前(attach 状态,视图参与 合成)先captureScreenshot(fromSurface=true)截一张「冻结帧」写入 st.frame, detach 后 img 帧流靠这张冻结帧兜底;同时 detach 后用Emulation.setDeviceMetricsOverride按最近一次 attach 的尺寸固定视口,保证 elementFromPoint 命中。attach 前clearDeviceMetricsOverride恢复真实视口。
经验教训(来自玻璃质感迭代)
- 选择器生成必须唯一且稳定,避免
[class*="panel"]这类宽泛子串匹配 (本次毛玻璃误伤webui-panel会话导航条的事故即源于此)。 - composer 定位经验:输入卡 =
[class*="_composerSeat"] [class*="_card"], 输入域 =[class*="_composerSeat"] textarea。 - 屏外/后台测试实例:CDP Input 可能整体失效、rAF 可能冻结、窗口可能最小化, 验证交互需在前台实例进行。
涉及文件
| 文件 | 改动 |
|---|---|
src/client/browser/BrowserActivityDock.tsx | 选取模式 UI + 点击捕获 + 回传 + inputActions.setDraft 回填 |
src/browser/index.ts | /api/dsh-browser/element 路由 + 帧兜底截图尺寸回填 |
src/browser/cdp.ts | inspectElementAt(CDP evaluate 采集唯一选择器 + 摘要)+ 截图超时参数 |
src/client/browser/styles.ts | 选取模式视觉样式(按钮高亮 / 十字准星 / 提示条) |