dsh-browser-view 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # dsh-browser-view
2
2
 
3
- DSH 插件:共享 Playwright 浏览器自动化 + 原生界面实时画面面板。一次安装,三件事:
3
+ DSH 插件 + 独立 MCP server:共享 Playwright 浏览器自动化 + 原生界面实时画面面板。一次安装,三件事(+DSH 之外任意 MCP 客户端可直连):
4
4
 
5
5
  | 模块 | 做什么 |
6
6
  | --- | --- |
7
7
  | 🧭 **浏览器自动化** | 共享 Playwright 浏览器:多标签、多 profile、隔离 context、下载/上传、Cookie、what 意图语义定位(`browser_click { what: "登录" }` 一次调用替代快照→查找→点击三轮,零命中返回相近候选 + SoM 看图兜底)、SoM 标徽点击(`mark: N`)、open Shadow DOM 组合树穿透、SPA 路由后 ref 软失效重锚定、点击效果验证(`effect: true`,点了没动静自动换候选)、跨 Frame/IFrame 穿透定位、视口感知动态快照(`viewportOnly`)、多模态 SoM 视口标徽(`som: true`)、批量表单智能填充(`browser_form_fill`)、增量快照(历史版本环形缓存)、抽取字段自动建议(action:suggest),38 个 `browser_*` 工具 |
8
- | 🖥️ **实时画面** | 注入 DSH 界面右下角悬浮面板:浏览器状态 + 实时缩略图,点开大屏模态框(2 秒轮询画面 + 访问历史);**双向协作**——agent 等待人工操作/敏感审批时显示交互横幅、执行点击/输入前目标元素琥珀色高亮预告(confirmMode:auto 时敏感动作由宿主层强制拦截弹审批,agent 无法绕过),**面板点选教学**(browser_pick_element:疑难元素用户在大图上点一下,直接拿到稳定 selector+指纹+ref),「最近回归」行与报告模态可一键重跑(agent 忙碌自动置灰) |
8
+ | 🖥️ **实时画面** | 注入 DSH 界面右下角悬浮面板:浏览器状态 + 实时缩略图,点开大屏模态框(CDP MJPEG 实时流,自动跟随活动标签换页/新开自愈,流不可用时回退轮询截图;访问历史 2 秒轮询);**双向协作**——agent 等待人工操作/敏感审批时显示交互横幅、执行点击/输入前目标元素琥珀色高亮预告(confirmMode:auto 时敏感动作由宿主层强制拦截弹审批,agent 无法绕过),**面板点选教学**(browser_pick_element:疑难元素用户在大图上点一下,直接拿到稳定 selector+指纹+ref),「最近回归」行与报告模态可一键重跑(agent 忙碌自动置灰) |
9
9
  | ✅ **回归测试闭环** | 录制回放 + 选择器自愈 + 参数化/数据驱动 + 失败自动重试(`retryFailed`)+ 视觉回归 + 运行历史/报告导出 + **一键导出原生 Playwright 规范代码(`action: "export"`)** + 面板一键重跑(`/runs/rerun`,agent 忙碌置灰) |
10
+ | 🌐 **MCP 接入** | 独立 stdio server(`dsh-browser-mcp`):Claude Desktop/ZCode/Cursor 等任意 MCP 客户端直连全部 38 个工具,不依赖 DSH;审批闸/录制/打点等安全语义与 DSH 宿主一致 |
10
11
 
11
- 设计原则:能做的一次性操作交给 agent 用 bash 完成,插件只做 bash 做不到的常驻浏览器自动化。唯一第三方运行时依赖是 `playwright-core`(复用系统已装的 Edge/Chrome,无需下载 Chromium),不依赖任何 `@deepseek-ai` 运行时包。
12
+ 设计原则:能做的一次性操作交给 agent 用 bash 完成,插件只做 bash 做不到的常驻浏览器自动化。运行时依赖 `playwright-core`(复用系统已装的 Edge/Chrome,无需下载 Chromium)与 `@modelcontextprotocol/sdk`(仅 MCP 入口使用,不进 DSH 插件加载链),不依赖任何 `@deepseek-ai` 运行时包。
12
13
 
13
14
  ## 安装
14
15
 
@@ -282,6 +283,37 @@ browser_confirm({
282
283
  - **实时缩略图**:浏览器打开时显示,点击弹出大屏模态框——2 秒轮询实时画面,下方列出**访问历史**(最近 50 条,点击可在新标签打开);
283
284
  - **最近回归**:面板底部一行显示最近一次 `browser_replay` / `browser_batch` 的结果(通过数 / 自愈数 / 时间)。
284
285
 
286
+ ## MCP 接入(独立 server,不依赖 DSH)
287
+
288
+ v0.8.0 起本包附带独立 MCP 入口 `dsh-browser-mcp`(stdio),DSH 之外的 MCP 客户端(Claude Desktop / ZCode / Cursor 等)可直接调用全部 38 个工具——走与 DSH 宿主**同一条** registerTools 装配链,审批闸/录制打点/时光机行为一致:
289
+
290
+ ```jsonc
291
+ // 客户端 mcpServers 配置示例
292
+ {
293
+ "mcpServers": {
294
+ "dsh-browser": {
295
+ "command": "npx",
296
+ "args": ["-y", "dsh-browser-view"],
297
+ "env": {
298
+ "DSH_BROWSER_HEADLESS": "true",
299
+ "DSH_BROWSER_USER_DATA_DIR": "~/.dsh-browser/mcp-profile",
300
+ "DSH_BROWSER_TOOLSET": "core"
301
+ }
302
+ }
303
+ }
304
+ }
305
+ ```
306
+
307
+ - 已安装本包时可直接用 `"command": "dsh-browser-mcp"` 或 `"dsh-browser-view"`(双 bin 别名);
308
+ - 配置走环境变量 `DSH_BROWSER_<大写蛇形>` → 同名配置键(`TIMEOUT_MS`→`timeoutMs`),`true/false` 转 boolean、纯数字转 number、列表键逗号分隔;校验与默认值复用同一套(见上方配置表,不新增配置键);
309
+ - 工具使用指引由 server `instructions` 下发(与 DSH systemPrompt 同源),`what:"登录"` 语义定位等用法完全一致;
310
+ - 工具元数据支持 MCP 2025 标准 `annotations`(`readOnlyHint` / `destructiveHint` 等属性);
311
+ - 工具返回同时带 text 渲染与 `structuredContent`;`browser_screenshot` 额外返回原生多模态 `ImageContent`(base64 内联 PNG),多模态客户端(如 Claude Desktop)可直接渲染截图。
312
+
313
+ **与 DSH 并行运行的注意**:两者是独立进程、各自独立浏览器会话——同一持久化 `userDataDir` 会因 Chromium 单例锁让后启动者失败,截图目录相同会写入交错,并行时务必给 MCP 配独立的 `DSH_BROWSER_USER_DATA_DIR` / `DSH_BROWSER_SCREENSHOT_DIR`。
314
+
315
+ **人机协作降级(Fast-fail)**:MCP 模式没有 DSH 前端面板,`confirmMode: auto` 时敏感操作与 `browser_confirm` 均**立即 fail-closed 抛错拒绝**(Fast-fail,不再无谓空转 60s);`browser_pick_element` 快速返回失败;需要人工登录或人机辅助的会话设 `DSH_BROWSER_HEADLESS=false`,用户直接在弹出的真实浏览器窗口操作。
316
+
285
317
  ## 开发
286
318
 
287
319
  ```sh
package/lib/client.js CHANGED
@@ -1,2 +1,2 @@
1
- window.__ModuleLoader__.load({id:`dsh-browser-view`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=null;async function a(){if(i!==null)return i;try{let e=await fetch(`/__dsh-browser-view__/config`,{cache:`no-store`});if(e.ok){let t=await e.json();if(typeof t?.basePath==`string`&&t.basePath)return i=t.basePath,t.basePath}}catch{}return i=`/browser`,i}async function o(e){try{let t=await fetch(`${e}/status`,{cache:`no-store`});return t.ok?await t.json():null}catch{return null}}async function s(e){try{let t=await fetch(`${e}/history`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.history)?n.history:[]}catch{return[]}}async function c(e){try{let t=await fetch(`${e}/log`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.calls)?n.calls:[]}catch{return[]}}async function l(e){try{let t=await fetch(`${e}/runs/latest`,{cache:`no-store`});if(!t.ok)return{run:null,busy:!1};let n=await t.json();return{run:n?.run??null,busy:n?.busy===!0}}catch{return{run:null,busy:!1}}}async function u(e){try{let t=await fetch(`${e}/confirm`,{cache:`no-store`});if(!t.ok)return null;let n=await t.json();return n?.confirm??n?.pending??null}catch{return null}}async function d(e,t,n){try{return(await fetch(`${e}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:n})})).ok}catch{return!1}}async function f(e){try{let t=await fetch(`${e}/pick`,{cache:`no-store`});return t.ok?(await t.json())?.pick??null:null}catch{return null}}async function p(e,t,n,r,i,a){try{return(await fetch(`${e}/pick`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,x:n,y:r,imgW:i,imgH:a})})).ok}catch{return!1}}async function m(e,t,n){try{let r=await fetch(`${e}/runs/rerun`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t,...n?{retryOf:n}:{}})});if(!r.ok){let e=await r.text().catch(()=>``);return{ok:!1,status:r.status,error:e}}return{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function h(e){try{let t=await fetch(`${e}/recordings`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.recordings)?n.recordings:[]}catch{return[]}}async function g(e,t){try{let n=await fetch(`${e}/recordings/detail?name=${encodeURIComponent(t)}`,{cache:`no-store`});return n.ok?await n.json():null}catch{return null}}async function _(e,t){try{return(await fetch(`${e}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:t})})).ok}catch{return!1}}function v(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)([]),[s,u]=(0,r.useState)(Date.now()),[d,f]=(0,r.useState)(null),[p,h]=(0,r.useState)(!1),[g,_]=(0,r.useState)(``);return(0,r.useEffect)(()=>{let t=!0,r=async()=>{let[r,i,s]=await Promise.all([o(e),c(e),l(e)]);t&&(n(r),r?.open&&u(Date.now()),a(i.slice(0,8)),f(s.run),h(s.busy))};r();let i=setInterval(r,3e3);return()=>{t=!1,clearInterval(i)}},[e]),{status:t,ops:i,shotVersion:s,lastRun:d,agentBusy:p,rerunMsg:g,rerunLast:async()=>{let t=d;if(!t||p)return;let n=t.source.replace(/\(已从磁盘重载\)$/,``);if(!n||[`steps`,`recording`,`recent-log`].includes(n)){_(`该来源不是已保存录制,无法一键重跑`);return}_(`重跑中…`),h(!0);let r=await m(e,n,t.runId);r.ok?_(`重跑完成,报告稍后刷新`):(h(!1),_(r.error??`重跑失败`))}}}function y(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await u(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingConfirm:t,resolveConfirm:async(t,r)=>{await d(e,t,r)&&n(null)}}}function b(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await f(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingPick:t}}let x={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},S={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function C(){let[e,t]=(0,r.useState)(()=>typeof document<`u`&&document.body?.getAttribute(`data-ds-dark-theme`)!==null);return(0,r.useEffect)(()=>{if(typeof document>`u`)return;let e=new MutationObserver(()=>t(document.body?.getAttribute(`data-ds-dark-theme`)!==null));return e.observe(document.body,{attributes:!0,attributeFilter:[`data-ds-dark-theme`]}),()=>e.disconnect()},[]),e}function w(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function T({confirm:e,dark:t,onDecision:n}){return(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:t?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:t?`#fca5a5`:`#b91c1c`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 请求执行敏感操作,请审批`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`⚠`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`,fontWeight:600}},`敏感操作待审批`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`}},e.reason,e.tool?` (${e.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>n(`allowed`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`允许`),(0,r.createElement)(`button`,{onClick:()=>n(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`)))}function E({status:e,ops:t,lastRun:n,agentBusy:i,rerunMsg:a,pendingConfirm:o,pendingPick:s,shotSrc:c,streamSrc:l,dark:u,palette:d,onOpenModal:f,onOpenReport:p,onConfirmDecision:m,onRerunLast:h}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`320px`,maxHeight:`78vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${d.border}`,borderRadius:`12px`,background:u?`#181e29`:`#ffffff`,boxShadow:`0 8px 32px rgba(0, 0, 0, 0.3)`,padding:`12px`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`4px 0`}},(0,r.createElement)(`span`,{style:{width:`8px`,height:`8px`,borderRadius:`999px`,flex:`none`,background:e?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:d.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:d.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},e===null?`未启用`:e.open?`${e.tabs??1} 标签 · ${(e.url||``).slice(0,20)}`:`未打开`)),o?(0,r.createElement)(T,{confirm:o,dark:u,onDecision:m}):null,e?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 正在等待人工操作,请在浏览器窗口中完成`},(0,r.createElement)(`span`,{style:{flex:`none`}},`⏸`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待人工操作: ${e.pause.instruction}`)):null,s?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`,cursor:`pointer`},title:`点击放大实时画面,在画面上点选目标元素`,onClick:f},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${s.instruction}(点此放大画面)`)):null,e?.open?(0,r.createElement)(`img`,{src:l||c,onError:e=>{e.currentTarget.src!==c&&(e.currentTarget.src=c)},alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:f,style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${d.border}`,cursor:`pointer`,background:u?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:u?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${t.length} 条)`),t.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{flex:`none`,color:e.ok?`#10b981`:`#ef4444`,fontWeight:600}},e.ok?`✓`:`✗`),(0,r.createElement)(`span`,{style:{color:d.tertiary,fontSize:`11px`,flex:`none`}},w(e.ts)),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`},title:`${e.tool}${e.error?`: ${e.error}`:``}`},`${e.tool}${e.error?`: ${e.error}`:``}`)))),n!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${n.runId} · 点击查看报告`,onClick:p},(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:n.failed===0?`#10b981`:`#ef4444`}},n.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${n.source}${n.row===void 0?``:` 行${n.row}`} ${n.passed}/${n.total}${n.healedCount>0?` · 自愈${n.healedCount}`:``}${n.retries!==void 0&&n.retries.recovered>0?` · 重试挽回${n.retries.recovered}`:``} · ${new Date(n.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`),a!==``&&(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},a),(0,r.createElement)(`button`,{type:`button`,title:i?`agent 忙碌,稍后可重跑`:`重跑 ${n.source}(结果进运行历史,标 retryOf)`,disabled:i,onClick:e=>{e.stopPropagation(),h()},style:{flex:`none`,height:`20px`,padding:`0 8px`,border:`1px solid ${d.border}`,borderRadius:`6px`,background:`transparent`,color:i?d.tertiary:d.secondary,fontSize:`11px`,cursor:i?`not-allowed`:`pointer`,opacity:i?.5:1}},i?`重跑(忙碌)`:`↻ 重跑`)))}function D({status:e,history:t,shotSrc:n,streamSrc:i,dark:a,palette:o,onClose:s,pendingPick:c,onPick:l}){(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&s()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s]);let u=e=>{if(c==null||l===void 0)return;let t=e.currentTarget,n=t.getBoundingClientRect(),r=t.naturalWidth||n.width,i=t.naturalHeight||n.height;if(r<=0||i<=0)return;let a=Math.min(n.width/r,n.height/i),o=(n.width-r*a)/2,s=(n.height-i*a)/2,u=Math.min(Math.max((e.clientX-n.left-o)/a,0),r),d=Math.min(Math.max((e.clientY-n.top-s)/a,0),i);l(Math.round(u),Math.round(d),Math.round(r),Math.round(i))},d=c!=null;return(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&s()},style:{position:`fixed`,inset:0,zIndex:2147483001,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1100px, 94vw)`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${o.border}`,borderRadius:`12px`,background:a?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,color:o.primary,fontSize:`14px`,fontWeight:600}},e?.open?`${e.title||``} (${e.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,onClick:s,style:{width:`28px`,height:`28px`,border:`1px solid ${o.border}`,borderRadius:`6px`,background:`transparent`,color:o.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),d?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 10px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:a?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:a?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${c.instruction}(在下方画面上点击目标元素)`)):null,e?.open?(0,r.createElement)(`img`,{src:i||n,onError:e=>{e.currentTarget.src!==n&&(e.currentTarget.src=n)},alt:`浏览器大图`,...d?{onClick:u}:{},style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:d?`2px dashed #f59e0b`:`1px solid ${o.border}`,background:a?`#10141c`:`#f8fafc`,cursor:d?`crosshair`:`default`}}):(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${o.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${t.length} 条,点击在新标签打开)`),t.map((e,t)=>(0,r.createElement)(`a`,{key:t,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:o.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:o.tertiary,flex:`none`}},w(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url))))))}function O({base:e,dark:t,palette:n,onClose:i}){return(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&i()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[i]),(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&i()},style:{position:`fixed`,inset:0,zIndex:2147483002,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1000px, 94vw)`,height:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${n.border}`,borderRadius:`12px`,background:t?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:n.primary,fontSize:`14px`,fontWeight:600}},`回归测试报告 (最新运行)`),(0,r.createElement)(`a`,{href:`${e}/runs/report.html`,target:`_blank`,rel:`noopener noreferrer`,style:{color:n.secondary,fontSize:`12px`,textDecoration:`none`,padding:`4px 8px`,border:`1px solid ${n.border}`,borderRadius:`6px`}},`新标签页打开`),(0,r.createElement)(`button`,{type:`button`,onClick:i,style:{width:`28px`,height:`28px`,border:`1px solid ${n.border}`,borderRadius:`6px`,background:`transparent`,color:n.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${e}/runs/report.html`,title:`回归测试报告`,style:{flex:1,width:`100%`,border:`1px solid ${n.border}`,borderRadius:`8px`,background:`#ffffff`}})))}function k(){let e=C(),t=e?S:x,[n,i]=(0,r.useState)(`/browser`),[o,c]=(0,r.useState)(!1),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[m,h]=(0,r.useState)([]);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&i(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{status:g,ops:_,shotVersion:w,lastRun:T,agentBusy:k,rerunMsg:A,rerunLast:j}=v(n),{pendingConfirm:M,resolveConfirm:N}=y(n),{pendingPick:P}=b(n);(0,r.useEffect)(()=>{if(!l)return;let e=!0,t=async()=>{let t=await s(n);e&&h(t)};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[l,n]);let F=`${n}/screenshot?v=${w}`,I=`${n}/stream`;return(0,r.createElement)(`div`,{id:`dsh-browser-client-overlay`,style:{position:`fixed`,inset:0,pointerEvents:`none`,zIndex:2147483e3}},(0,r.createElement)(`button`,{type:`button`,title:`浏览器自动化与实时画面`,onClick:()=>c(e=>!e),style:{position:`fixed`,right:`16px`,bottom:`16px`,width:`42px`,height:`42px`,borderRadius:`999px`,border:`1px solid ${t.border}`,background:e?`#1e2430`:`#ffffff`,color:t.primary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`18px`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.2)`,transition:`transform 0.15s ease, background 0.15s ease`,pointerEvents:`auto`}},`🌐`),o&&(0,r.createElement)(E,{status:g,ops:_,lastRun:T,agentBusy:k,rerunMsg:A,pendingConfirm:M,pendingPick:P,shotSrc:F,streamSrc:I,dark:e,palette:t,onOpenModal:()=>u(!0),onOpenReport:()=>f(!0),onConfirmDecision:e=>{M&&N(M.id,e)},onRerunLast:j}),l&&(0,r.createElement)(D,{status:g,history:m,shotSrc:F,streamSrc:I,dark:e,palette:t,onClose:()=>u(!1),pendingPick:P,onPick:(e,t,r,i)=>{P&&p(n,P.id,e,t,r,i)}}),d&&(0,r.createElement)(O,{base:n,dark:e,palette:t,onClose:()=>f(!1)}))}function A(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)(``),[o,s]=(0,r.useState)({});return(0,r.useEffect)(()=>{let t=!0;return n(null),a(``),h(e).then(e=>{t&&n(e)}).catch(e=>{t&&a(e instanceof Error?e.message:String(e))}),()=>{t=!1}},[e]),{list:t,error:i,expanded:o,toggleDetail:t=>{s(n=>{let r=n[t];if(r!==void 0&&r!==`loading`){let e={...n};return delete e[t],e}return g(e,t).then(e=>{s(n=>({...n,[t]:e??`failed`}))}).catch(()=>s(e=>({...e,[t]:`failed`}))),{...n,[t]:`loading`}})},remove:async t=>{await _(e,t)&&(n(e=>e===null?e:e.filter(e=>e.name!==t)),s(e=>{let n={...e};return delete n[t],n}))}}}function j(e){return{row:{display:`flex`,alignItems:`center`,gap:`8px`,minWidth:0},label:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,fontSize:`13px`,color:e.primary,cursor:`pointer`},meta:{color:e.tertiary,fontSize:`12px`,whiteSpace:`nowrap`},btn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.secondary,font:`inherit`,fontSize:`12px`,cursor:`pointer`},delBtn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.danger,font:`inherit`,fontSize:`12px`,cursor:`pointer`},itemBox:{display:`flex`,flexDirection:`column`,gap:`6px`,border:`1px solid ${e.border}`,borderRadius:`8px`,padding:`8px 10px`},stepsBox:{display:`flex`,flexDirection:`column`,gap:`2px`,borderTop:`1px solid ${e.border}`,paddingTop:`6px`},stepLine:{display:`flex`,gap:`8px`,fontSize:`13px`,lineHeight:`1.7`,color:e.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`},toolName:{color:e.primary,flex:`none`,fontWeight:500},stepArgs:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,color:e.secondary},empty:{color:e.tertiary,fontSize:`12px`,textAlign:`center`,padding:`12px 0`},root:{display:`flex`,flexDirection:`column`,gap:`8px`,maxWidth:`720px`},heading:{color:e.tertiary,fontSize:`12px`,lineHeight:`1.6`}}}function M(e){let t=C(),n=t?S:x,i=j(n),[o,s]=(0,r.useState)(`/browser`);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&s(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{list:c,error:l,expanded:u,toggleDetail:d,remove:f}=A(o);return(0,r.createElement)(`div`,{style:i.root},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`12px`,paddingBottom:`12px`,marginBottom:`6px`,borderBottom:`1px solid ${n.border}`}},(0,r.createElement)(`div`,{style:{width:`36px`,height:`36px`,borderRadius:`8px`,background:t?`rgba(99, 102, 241, 0.15)`:`rgba(99, 102, 241, 0.1)`,border:`1px solid ${t?`rgba(99, 102, 241, 0.3)`:`rgba(99, 102, 241, 0.2)`}`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`20px`,flex:`none`}},`🌐`),(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`2px`}},(0,r.createElement)(`div`,{style:{fontSize:`15px`,fontWeight:600,color:n.primary}},`浏览器自动化与录制管理`),(0,r.createElement)(`div`,{style:{fontSize:`12px`,color:n.tertiary}},`管理已保存的浏览器回归脚本,查看执行序列与历史数据`))),(0,r.createElement)(`div`,{style:i.heading},`已保存的录制(由 browser_record save 生成,供 browser_replay 一键回放):`),l?(0,r.createElement)(`div`,{style:{...i.empty,color:n.danger}},`加载失败: ${l}`):c===null?(0,r.createElement)(`div`,{style:i.empty},`加载中…`):c.length===0?(0,r.createElement)(`div`,{style:i.empty},`暂无已保存录制。在与 Agent 对话中完成一轮浏览器操作后,输入「保存录制为 xxx」即可沉淀为回归用例。`):c.map(e=>{let t=u[e.name],n=t!==void 0&&t!==`loading`;return(0,r.createElement)(`div`,{key:e.name,style:i.itemBox},(0,r.createElement)(`div`,{style:i.row},(0,r.createElement)(`span`,{style:i.label,title:`点击展开 / 折叠步骤详情`,onClick:()=>d(e.name)},(n?`▾ `:`▸ `)+e.name),(0,r.createElement)(`span`,{style:i.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleDateString(`zh-CN`)}`),(0,r.createElement)(`button`,{type:`button`,style:i.btn,onClick:()=>d(e.name)},n?`收起`:`步骤`),(0,r.createElement)(`button`,{type:`button`,style:i.delBtn,onClick:()=>f(e.name)},`删除`)),n&&t!==`failed`&&(0,r.createElement)(`div`,{style:i.stepsBox},t.steps.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:i.stepLine},(0,r.createElement)(`span`,{style:i.meta},`#${t+1}`),(0,r.createElement)(`span`,{style:i.toolName},e.tool),(0,r.createElement)(`span`,{style:i.stepArgs},JSON.stringify(e.args))))))}))}let N=[`slots`];function P(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`🌐 浏览器`},M)),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},k))}return n.apply=P,n.inject=N,t.exports}});
1
+ window.__ModuleLoader__.load({id:`dsh-browser-view`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=null;async function a(){if(i!==null)return i;try{let e=await fetch(`/__dsh-browser-view__/config`,{cache:`no-store`});if(e.ok){let t=await e.json();if(typeof t?.basePath==`string`&&t.basePath)return i=t.basePath,t.basePath}}catch{}return i=`/browser`,i}async function o(e){try{let t=await fetch(`${e}/status`,{cache:`no-store`});return t.ok?await t.json():null}catch{return null}}async function s(e){try{let t=await fetch(`${e}/history`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.history)?n.history:[]}catch{return[]}}async function c(e){try{let t=await fetch(`${e}/log`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.calls)?n.calls:[]}catch{return[]}}async function l(e){try{let t=await fetch(`${e}/runs/latest`,{cache:`no-store`});if(!t.ok)return{run:null,busy:!1};let n=await t.json();return{run:n?.run??null,busy:n?.busy===!0}}catch{return{run:null,busy:!1}}}async function u(e){try{let t=await fetch(`${e}/confirm`,{cache:`no-store`});if(!t.ok)return null;let n=await t.json();return n?.confirm??n?.pending??null}catch{return null}}async function d(e,t,n){try{return(await fetch(`${e}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:n})})).ok}catch{return!1}}async function f(e){try{let t=await fetch(`${e}/pick`,{cache:`no-store`});return t.ok?(await t.json())?.pick??null:null}catch{return null}}async function p(e,t,n,r,i,a){try{return(await fetch(`${e}/pick`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,x:n,y:r,imgW:i,imgH:a})})).ok}catch{return!1}}async function m(e,t,n){try{let r=await fetch(`${e}/runs/rerun`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({source:t,...n?{retryOf:n}:{}})});if(!r.ok){let e=await r.text().catch(()=>``);return{ok:!1,status:r.status,error:e}}return{ok:!0}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}async function h(e){try{let t=await fetch(`${e}/recordings`,{cache:`no-store`});if(!t.ok)return[];let n=await t.json();return Array.isArray(n?.recordings)?n.recordings:[]}catch{return[]}}async function g(e,t){try{let n=await fetch(`${e}/recordings/detail?name=${encodeURIComponent(t)}`,{cache:`no-store`});return n.ok?await n.json():null}catch{return null}}async function _(e,t){try{return(await fetch(`${e}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:t})})).ok}catch{return!1}}function v(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)([]),[s,u]=(0,r.useState)(Date.now()),[d,f]=(0,r.useState)(null),[p,h]=(0,r.useState)(!1),[g,_]=(0,r.useState)(``);return(0,r.useEffect)(()=>{let t=!0,r=async()=>{let[r,i,s]=await Promise.all([o(e),c(e),l(e)]);t&&(n(r),r?.open&&u(Date.now()),a(i.slice(0,8)),f(s.run),h(s.busy))};r();let i=setInterval(r,3e3);return()=>{t=!1,clearInterval(i)}},[e]),{status:t,ops:i,shotVersion:s,lastRun:d,agentBusy:p,rerunMsg:g,rerunLast:async()=>{let t=d;if(!t||p)return;let n=t.source.replace(/\(已从磁盘重载\)$/,``);if(!n||[`steps`,`recording`,`recent-log`].includes(n)){_(`该来源不是已保存录制,无法一键重跑`);return}_(`重跑中…`),h(!0);let r=await m(e,n,t.runId);r.ok?_(`重跑完成,报告稍后刷新`):(h(!1),_(r.error??`重跑失败`))}}}function y(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await u(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingConfirm:t,resolveConfirm:async(t,r)=>{await d(e,t,r)&&n(null)}}}function b(e){let[t,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let t=!0,r=0,i,a=async()=>{try{let i=await f(e);if(!t)return;r=0,n(i)}catch{r++,t&&n(null)}finally{if(!t)return;i=setTimeout(a,r>=3?15e3:1e3)}};return a(),()=>{t=!1,i!==void 0&&clearTimeout(i)}},[e]),{pendingPick:t}}let x={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},S={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function C(){let[e,t]=(0,r.useState)(()=>typeof document<`u`&&document.body?.getAttribute(`data-ds-dark-theme`)!==null);return(0,r.useEffect)(()=>{if(typeof document>`u`)return;let e=new MutationObserver(()=>t(document.body?.getAttribute(`data-ds-dark-theme`)!==null));return e.observe(document.body,{attributes:!0,attributeFilter:[`data-ds-dark-theme`]}),()=>e.disconnect()},[]),e}function w(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function T({confirm:e,dark:t,onDecision:n}){return(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:t?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:t?`#fca5a5`:`#b91c1c`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 请求执行敏感操作,请审批`},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`⚠`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`,fontWeight:600}},`敏感操作待审批`)),(0,r.createElement)(`div`,{style:{marginTop:`4px`,wordBreak:`break-all`}},e.reason,e.tool?` (${e.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>n(`allowed`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#16a34a`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`允许`),(0,r.createElement)(`button`,{onClick:()=>n(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`)))}function E({streamSrc:e,shotSrc:t,alt:n,title:i,style:a,onClick:o}){let[s,c]=(0,r.useState)(!0),[l,u]=(0,r.useState)(0);(0,r.useEffect)(()=>{s||(u(e=>e+1),c(!0))},[t,s]);let d=s&&e!==void 0?l>0?`${e}${e.includes(`?`)?`&`:`?`}probe=${l}`:e:t;return(0,r.createElement)(`img`,{src:d,onError:()=>c(!1),alt:n,title:i,onClick:o,style:a})}function D({status:e,ops:t,lastRun:n,agentBusy:i,rerunMsg:a,pendingConfirm:o,pendingPick:s,shotSrc:c,streamSrc:l,dark:u,palette:d,onOpenModal:f,onOpenReport:p,onConfirmDecision:m,onRerunLast:h}){return(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`68px`,width:`320px`,maxHeight:`78vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${d.border}`,borderRadius:`12px`,background:u?`#181e29`:`#ffffff`,boxShadow:`0 8px 32px rgba(0, 0, 0, 0.3)`,padding:`12px`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`4px 0`}},(0,r.createElement)(`span`,{style:{width:`8px`,height:`8px`,borderRadius:`999px`,flex:`none`,background:e?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:d.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:d.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},e===null?`未启用`:e.open?`${e.tabs??1} 标签 · ${(e.url||``).slice(0,20)}`:`未打开`)),o?(0,r.createElement)(T,{confirm:o,dark:u,onDecision:m}):null,e?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`},title:`Agent 正在等待人工操作,请在浏览器窗口中完成`},(0,r.createElement)(`span`,{style:{flex:`none`}},`⏸`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待人工操作: ${e.pause.instruction}`)):null,s?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:u?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:u?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`,cursor:`pointer`},title:`点击放大实时画面,在画面上点选目标元素`,onClick:f},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${s.instruction}(点此放大画面)`)):null,e?.open?(0,r.createElement)(E,{streamSrc:l,shotSrc:c,alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:f,style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${d.border}`,cursor:`pointer`,background:u?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:u?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:d.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${t.length} 条)`),t.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{flex:`none`,color:e.ok?`#10b981`:`#ef4444`,fontWeight:600}},e.ok?`✓`:`✗`),(0,r.createElement)(`span`,{style:{color:d.tertiary,fontSize:`11px`,flex:`none`}},w(e.ts)),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`},title:`${e.tool}${e.error?`: ${e.error}`:``}`},`${e.tool}${e.error?`: ${e.error}`:``}`)))),n!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${d.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:d.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${n.runId} · 点击查看报告`,onClick:p},(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:n.failed===0?`#10b981`:`#ef4444`}},n.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${n.source}${n.row===void 0?``:` 行${n.row}`} ${n.passed}/${n.total}${n.healedCount>0?` · 自愈${n.healedCount}`:``}${n.retries!==void 0&&n.retries.recovered>0?` · 重试挽回${n.retries.recovered}`:``} · ${new Date(n.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`),a!==``&&(0,r.createElement)(`span`,{style:{flex:`none`,color:d.tertiary,fontSize:`11px`}},a),(0,r.createElement)(`button`,{type:`button`,title:i?`agent 忙碌,稍后可重跑`:`重跑 ${n.source}(结果进运行历史,标 retryOf)`,disabled:i,onClick:e=>{e.stopPropagation(),h()},style:{flex:`none`,height:`20px`,padding:`0 8px`,border:`1px solid ${d.border}`,borderRadius:`6px`,background:`transparent`,color:i?d.tertiary:d.secondary,fontSize:`11px`,cursor:i?`not-allowed`:`pointer`,opacity:i?.5:1}},i?`重跑(忙碌)`:`↻ 重跑`)))}function O({status:e,history:t,shotSrc:n,streamSrc:i,dark:a,palette:o,onClose:s,pendingPick:c,onPick:l}){(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&s()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[s]);let u=e=>{if(c==null||l===void 0)return;let t=e.currentTarget,n=t.getBoundingClientRect(),r=t.naturalWidth||n.width,i=t.naturalHeight||n.height;if(r<=0||i<=0)return;let a=Math.min(n.width/r,n.height/i),o=(n.width-r*a)/2,s=(n.height-i*a)/2,u=Math.min(Math.max((e.clientX-n.left-o)/a,0),r),d=Math.min(Math.max((e.clientY-n.top-s)/a,0),i);l(Math.round(u),Math.round(d),Math.round(r),Math.round(i))},d=c!=null;return(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&s()},style:{position:`fixed`,inset:0,zIndex:2147483001,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1100px, 94vw)`,maxHeight:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${o.border}`,borderRadius:`12px`,background:a?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,color:o.primary,fontSize:`14px`,fontWeight:600}},e?.open?`${e.title||``} (${e.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,onClick:s,style:{width:`28px`,height:`28px`,border:`1px solid ${o.border}`,borderRadius:`6px`,background:`transparent`,color:o.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),d?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 10px`,borderRadius:`8px`,border:`1px dashed #f59e0b`,background:a?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:a?`#fbbf24`:`#b45309`,fontSize:`12px`,lineHeight:`1.5`}},(0,r.createElement)(`span`,{style:{flex:`none`}},`🎯`),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,wordBreak:`break-all`}},`等待点选教学: ${c.instruction}(在下方画面上点击目标元素)`)):null,e?.open?(0,r.createElement)(E,{streamSrc:i,shotSrc:n,alt:`浏览器大图`,...d?{onClick:u}:{},style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:d?`2px dashed #f59e0b`:`1px solid ${o.border}`,background:a?`#10141c`:`#f8fafc`,cursor:d?`crosshair`:`default`}}):(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),t.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${o.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:o.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${t.length} 条,点击在新标签打开)`),t.map((e,t)=>(0,r.createElement)(`a`,{key:t,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:o.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:o.tertiary,flex:`none`}},w(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url))))))}function k({base:e,dark:t,palette:n,onClose:i}){return(0,r.useEffect)(()=>{let e=e=>{e.key===`Escape`&&i()};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[i]),(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&i()},style:{position:`fixed`,inset:0,zIndex:2147483002,display:`flex`,alignItems:`center`,justifyContent:`center`,padding:`24px`,background:`rgba(0, 0, 0, 0.65)`,pointerEvents:`auto`}},(0,r.createElement)(`div`,{style:{width:`min(1000px, 94vw)`,height:`90vh`,display:`flex`,flexDirection:`column`,gap:`10px`,border:`1px solid ${n.border}`,borderRadius:`12px`,background:t?`#181e29`:`#ffffff`,padding:`16px`,boxShadow:`0 16px 64px rgba(0, 0, 0, 0.4)`}},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`}},(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:n.primary,fontSize:`14px`,fontWeight:600}},`回归测试报告 (最新运行)`),(0,r.createElement)(`a`,{href:`${e}/runs/report.html`,target:`_blank`,rel:`noopener noreferrer`,style:{color:n.secondary,fontSize:`12px`,textDecoration:`none`,padding:`4px 8px`,border:`1px solid ${n.border}`,borderRadius:`6px`}},`新标签页打开`),(0,r.createElement)(`button`,{type:`button`,onClick:i,style:{width:`28px`,height:`28px`,border:`1px solid ${n.border}`,borderRadius:`6px`,background:`transparent`,color:n.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${e}/runs/report.html`,title:`回归测试报告`,style:{flex:1,width:`100%`,border:`1px solid ${n.border}`,borderRadius:`8px`,background:`#ffffff`}})))}function A(){let e=C(),t=e?S:x,[n,i]=(0,r.useState)(`/browser`),[o,c]=(0,r.useState)(!1),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[m,h]=(0,r.useState)([]);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&i(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{status:g,ops:_,shotVersion:w,lastRun:T,agentBusy:E,rerunMsg:A,rerunLast:j}=v(n),{pendingConfirm:M,resolveConfirm:N}=y(n),{pendingPick:P}=b(n);(0,r.useEffect)(()=>{if(!l)return;let e=!0,t=async()=>{let t=await s(n);e&&h(t)};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[l,n]);let F=`${n}/screenshot?v=${w}`,I=`${n}/stream`;return(0,r.createElement)(`div`,{id:`dsh-browser-client-overlay`,style:{position:`fixed`,inset:0,pointerEvents:`none`,zIndex:2147483e3}},(0,r.createElement)(`button`,{type:`button`,title:`浏览器自动化与实时画面`,onClick:()=>c(e=>!e),style:{position:`fixed`,right:`16px`,bottom:`16px`,width:`42px`,height:`42px`,borderRadius:`999px`,border:`1px solid ${t.border}`,background:e?`#1e2430`:`#ffffff`,color:t.primary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`18px`,boxShadow:`0 4px 16px rgba(0, 0, 0, 0.2)`,transition:`transform 0.15s ease, background 0.15s ease`,pointerEvents:`auto`}},`🌐`),o&&(0,r.createElement)(D,{status:g,ops:_,lastRun:T,agentBusy:E,rerunMsg:A,pendingConfirm:M,pendingPick:P,shotSrc:F,streamSrc:I,dark:e,palette:t,onOpenModal:()=>u(!0),onOpenReport:()=>f(!0),onConfirmDecision:e=>{M&&N(M.id,e)},onRerunLast:j}),l&&(0,r.createElement)(O,{status:g,history:m,shotSrc:F,streamSrc:I,dark:e,palette:t,onClose:()=>u(!1),pendingPick:P,onPick:(e,t,r,i)=>{P&&p(n,P.id,e,t,r,i)}}),d&&(0,r.createElement)(k,{base:n,dark:e,palette:t,onClose:()=>f(!1)}))}function j(e){let[t,n]=(0,r.useState)(null),[i,a]=(0,r.useState)(``),[o,s]=(0,r.useState)({});return(0,r.useEffect)(()=>{let t=!0;return n(null),a(``),h(e).then(e=>{t&&n(e)}).catch(e=>{t&&a(e instanceof Error?e.message:String(e))}),()=>{t=!1}},[e]),{list:t,error:i,expanded:o,toggleDetail:t=>{s(n=>{let r=n[t];if(r!==void 0&&r!==`loading`){let e={...n};return delete e[t],e}return g(e,t).then(e=>{s(n=>({...n,[t]:e??`failed`}))}).catch(()=>s(e=>({...e,[t]:`failed`}))),{...n,[t]:`loading`}})},remove:async t=>{await _(e,t)&&(n(e=>e===null?e:e.filter(e=>e.name!==t)),s(e=>{let n={...e};return delete n[t],n}))}}}function M(e){return{row:{display:`flex`,alignItems:`center`,gap:`8px`,minWidth:0},label:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,whiteSpace:`nowrap`,fontSize:`13px`,color:e.primary,cursor:`pointer`},meta:{color:e.tertiary,fontSize:`12px`,whiteSpace:`nowrap`},btn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.secondary,font:`inherit`,fontSize:`12px`,cursor:`pointer`},delBtn:{height:`24px`,padding:`0 10px`,border:`1px solid ${e.border}`,borderRadius:`6px`,background:`transparent`,color:e.danger,font:`inherit`,fontSize:`12px`,cursor:`pointer`},itemBox:{display:`flex`,flexDirection:`column`,gap:`6px`,border:`1px solid ${e.border}`,borderRadius:`8px`,padding:`8px 10px`},stepsBox:{display:`flex`,flexDirection:`column`,gap:`2px`,borderTop:`1px solid ${e.border}`,paddingTop:`6px`},stepLine:{display:`flex`,gap:`8px`,fontSize:`13px`,lineHeight:`1.7`,color:e.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`},toolName:{color:e.primary,flex:`none`,fontWeight:500},stepArgs:{flex:1,minWidth:0,overflow:`hidden`,textOverflow:`ellipsis`,color:e.secondary},empty:{color:e.tertiary,fontSize:`12px`,textAlign:`center`,padding:`12px 0`},root:{display:`flex`,flexDirection:`column`,gap:`8px`,maxWidth:`720px`},heading:{color:e.tertiary,fontSize:`12px`,lineHeight:`1.6`}}}function N(e){let t=C(),n=t?S:x,i=M(n),[o,s]=(0,r.useState)(`/browser`);(0,r.useEffect)(()=>{let e=!0;return a().then(t=>{e&&s(t.replace(/\/+$/,``))}),()=>{e=!1}},[]);let{list:c,error:l,expanded:u,toggleDetail:d,remove:f}=j(o);return(0,r.createElement)(`div`,{style:i.root},(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`12px`,paddingBottom:`12px`,marginBottom:`6px`,borderBottom:`1px solid ${n.border}`}},(0,r.createElement)(`div`,{style:{width:`36px`,height:`36px`,borderRadius:`8px`,background:t?`rgba(99, 102, 241, 0.15)`:`rgba(99, 102, 241, 0.1)`,border:`1px solid ${t?`rgba(99, 102, 241, 0.3)`:`rgba(99, 102, 241, 0.2)`}`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`20px`,flex:`none`}},`🌐`),(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`2px`}},(0,r.createElement)(`div`,{style:{fontSize:`15px`,fontWeight:600,color:n.primary}},`浏览器自动化与录制管理`),(0,r.createElement)(`div`,{style:{fontSize:`12px`,color:n.tertiary}},`管理已保存的浏览器回归脚本,查看执行序列与历史数据`))),(0,r.createElement)(`div`,{style:i.heading},`已保存的录制(由 browser_record save 生成,供 browser_replay 一键回放):`),l?(0,r.createElement)(`div`,{style:{...i.empty,color:n.danger}},`加载失败: ${l}`):c===null?(0,r.createElement)(`div`,{style:i.empty},`加载中…`):c.length===0?(0,r.createElement)(`div`,{style:i.empty},`暂无已保存录制。在与 Agent 对话中完成一轮浏览器操作后,输入「保存录制为 xxx」即可沉淀为回归用例。`):c.map(e=>{let t=u[e.name],n=t!==void 0&&t!==`loading`;return(0,r.createElement)(`div`,{key:e.name,style:i.itemBox},(0,r.createElement)(`div`,{style:i.row},(0,r.createElement)(`span`,{style:i.label,title:`点击展开 / 折叠步骤详情`,onClick:()=>d(e.name)},(n?`▾ `:`▸ `)+e.name),(0,r.createElement)(`span`,{style:i.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleDateString(`zh-CN`)}`),(0,r.createElement)(`button`,{type:`button`,style:i.btn,onClick:()=>d(e.name)},n?`收起`:`步骤`),(0,r.createElement)(`button`,{type:`button`,style:i.delBtn,onClick:()=>f(e.name)},`删除`)),n&&t!==`failed`&&(0,r.createElement)(`div`,{style:i.stepsBox},t.steps.map((e,t)=>(0,r.createElement)(`div`,{key:t,style:i.stepLine},(0,r.createElement)(`span`,{style:i.meta},`#${t+1}`),(0,r.createElement)(`span`,{style:i.toolName},e.tool),(0,r.createElement)(`span`,{style:i.stepArgs},JSON.stringify(e.args))))))}))}let P=[`slots`];function F(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`🌐 浏览器`},N)),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},A))}return n.apply=F,n.inject=P,t.exports}});
2
2
  //# sourceMappingURL=client.js.map