新功能需求

August 22, 2026 · View on GitHub

状态:已实现(2026-08-22) 背景:AI 无法精确知道用户在浏览器预览里指的是哪个元素,纯文字描述反复对齐成本极高 (dsh-webui 玻璃质感主题迭代中已实际暴露此痛点)。

目标

在 AI 浏览器预览抽屉中支持「选取元素」:

  1. 用户点击 dock 工具条上的「选取元素」按钮进入选取模式;
  2. 在预览画面上点击任意元素;
  3. 该元素的定位信息自动填入 DSH 对话框输入框,作为给 AI 的上下文;
  4. 再点一次按钮或按 Esc 退出选取模式。

技术要点

预览链路

  • 预览抽屉组件:src/client/browser/BrowserActivityDock.tsx
  • 抽屉容器样式:dsh-browser-drawerz-index: 8801, fixed right:0 全高)
  • 画面来源:screencast 流;相关 host 路由(src/browser/index.ts): /api/dsh-browser/screencast | frame | session | input | navigate | tabs | status

交互与实现路径

  1. 选取模式开关:dock 头部加按钮(明显状态指示,开启时高亮)。
  2. 坐标捕获:开启后拦截抽屉画面的下一次点击,取画面内坐标, 按画面缩放比换算到目标页真实视口坐标。
  3. 元素信息采集:经 CDP Runtime.evaluate 在目标页执行脚本:
    • document.elementFromPoint(x, y)
    • 生成唯一 CSS 选择器(id 优先 → nth-of-type 路径)
    • 元素摘要:tag / id / class / role / 可见文本截断(~120 字符)
  4. 回传进对话框:走槽位组件的标准 kit inputActions.setDraft(对齐 PromptOptimizeButton 的「唯一公开写入路径」,不碰 DOM、不用原生 setter), 把 `[选择器] 元素摘要` 追加到草稿尾部。
  5. 填入格式:`[选择器] 元素摘要` 追加到输入框尾部, 待用户补充问题后手动发送(未提供「立即发送」)。

实现记录(与需求稿的差异)

  • 回填未采用「DOM 直填」:BrowserSeatconversation.input.left 槽位, 组件天然拿到 owner input.draft + kit inputActions.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.tsinspectElementAt(CDP evaluate 采集唯一选择器 + 摘要)+ 截图超时参数
src/client/browser/styles.ts选取模式视觉样式(按钮高亮 / 十字准星 / 提示条)