dsh-browser-view 0.3.0 → 0.3.1
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 +1 -1
- package/lib/client.js +1 -1
- package/lib/index.js +21 -100
- package/lib/types/browser/tools.d.ts +0 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ DSH 插件:共享 Playwright 浏览器自动化 + 原生界面实时画面面板
|
|
|
5
5
|
| 模块 | 做什么 |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| 🧭 **浏览器自动化** | 共享 Playwright 浏览器:多标签、多 profile、隔离 context、下载/上传、Cookie、表单回放,36 个 `browser_*` 工具 |
|
|
8
|
-
| 🖥️ **实时画面** | 注入 DSH 界面右下角悬浮面板:浏览器状态 + 实时缩略图,点开大屏模态框(2 秒轮询画面 + 访问历史);**双向协作**——agent
|
|
8
|
+
| 🖥️ **实时画面** | 注入 DSH 界面右下角悬浮面板:浏览器状态 + 实时缩略图,点开大屏模态框(2 秒轮询画面 + 访问历史);**双向协作**——agent 等待人工操作/敏感审批时显示交互横幅、执行点击/输入前目标元素琥珀色高亮预告,「最近回归」行点击直接内嵌查看 HTML 回归报告 |
|
|
9
9
|
| ✅ **回归测试闭环** | 录制回放 + 选择器自愈 + 参数化/数据驱动 + 视觉回归 + 运行历史与报告导出 |
|
|
10
10
|
|
|
11
11
|
设计原则:能做的一次性操作交给 agent 用 bash 完成,插件只做 bash 做不到的常驻浏览器自动化。唯一第三方运行时依赖是 `playwright-core`(复用系统已装的 Edge/Chrome,无需下载 Chromium),不依赖任何 `@deepseek-ai` 运行时包。
|
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={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},a={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function o(){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 s(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function c(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 l(){let e=o(),t=e?a:i,[n,c]=(0,r.useState)(`/browser`),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[p,m]=(0,r.useState)(null),[h,g]=(0,r.useState)([]),[_,v]=(0,r.useState)([]),[y,b]=(0,r.useState)(Date.now()),[x,S]=(0,r.useState)(null),[C,w]=(0,r.useState)(null),[T,E]=(0,r.useState)(!1),[D,O]=(0,r.useState)(!1),[k,A]=(0,r.useState)(null);(0,r.useEffect)(()=>{let e=!0;return fetch(`/__dsh-browser-view__/config`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&typeof t.basePath==`string`&&t.basePath&&c(t.basePath.replace(/\/+$/,``))}).catch(()=>{}),()=>{e=!1}},[]),(0,r.useEffect)(()=>{let e=!0,t=()=>{fetch(`${n}/status`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&(m(t),t?.open&&b(Date.now()))}).catch(()=>{e&&m(null)}),fetch(`${n}/log`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&t&&Array.isArray(t.calls)&&g(t.calls.slice(0,8))}).catch(()=>{}),fetch(`${n}/runs/latest`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&S(t?.run??null)}).catch(()=>{})};t();let r=setInterval(t,3e3);return()=>{e=!1,clearInterval(r)}},[n]),(0,r.useEffect)(()=>{let e=!0,t=()=>{fetch(`${n}/confirm`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&w(t?.confirm??null)}).catch(()=>{})};t();let r=setInterval(t,1e3);return()=>{e=!1,clearInterval(r)}},[n]);let j=e=>{let t=C?.id;t&&(w(null),fetch(`${n}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:e})}).catch(()=>{}))},M=()=>{if(D)return;A(null),O(!0),fetch(`${n}/pick`,{method:`POST`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).catch(()=>{});let e=Date.now()+6e4,t=setInterval(()=>{if(Date.now()>e){clearInterval(t),O(!1);return}fetch(`${n}/pick`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(e=>{e?.pick&&(clearInterval(t),O(!1),A(e.pick))}).catch(()=>{})},500)},N=()=>{let e=k?.selector;e&&navigator.clipboard?.writeText(e).catch(()=>{})};(0,r.useEffect)(()=>{if(!d)return;let e=!0,t=()=>{fetch(`${n}/history`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&t&&Array.isArray(t.history)&&v(t.history)}).catch(()=>{}),b(Date.now())};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[d,n]),(0,r.useEffect)(()=>{if(!d)return;let e=e=>{e.key===`Escape`&&f(!1)};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[d]);let P=`${n}/screenshot?v=${y}`;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:()=>u(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.25)`,pointerEvents:`auto`,userSelect:`none`}},`🌐`),l&&(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`66px`,width:`320px`,maxHeight:`72vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${t.border}`,borderRadius:`12px`,background:e?`#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:p?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:t.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:t.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},p===null?`未启用`:p.open?`${p.tabs??1} 标签 · ${(p.url||``).slice(0,20)}`:`未打开`)),C?(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:e?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:e?`#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`}},C.reason,C.tool?` (${C.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>j(`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:()=>j(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`))):null,p?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:e?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:e?`#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`}},`等待人工操作: ${p.pause.instruction}`)):null,p?.open?(0,r.createElement)(`img`,{src:P,alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:()=>f(!0),style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${t.border}`,cursor:`pointer`,background:e?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:e?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),h.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${t.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${h.length} 条)`),h.map((e,n)=>(0,r.createElement)(`div`,{key:n,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:t.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:t.tertiary,fontSize:`11px`,flex:`none`}},s(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}`:``}`)))),x!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${t.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:t.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${x.runId} · 点击查看报告`,onClick:()=>E(!0)},(0,r.createElement)(`span`,{style:{flex:`none`,color:t.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:x.failed===0?`#10b981`:`#ef4444`}},x.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${x.source}${x.row===void 0?``:` 行${x.row}`} ${x.passed}/${x.total}${x.healedCount>0?` · 自愈${x.healedCount}`:``} · ${new Date(x.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`))),d&&(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&f(!1)},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 ${t.border}`,borderRadius:`12px`,background:e?`#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:t.primary,fontSize:`14px`,fontWeight:600}},p?.open?`${p.title||``} (${p.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,title:D?`拾取中:在浏览器窗口点击任意元素`:`点选元素生成 selector`,onClick:M,style:{flex:`none`,padding:`2px 10px`,border:`1px solid ${D?`#f59e0b`:t.border}`,borderRadius:`6px`,background:D?`rgba(245, 158, 11, 0.12)`:`transparent`,color:D?`#f59e0b`:t.secondary,cursor:`pointer`,fontSize:`12px`}},D?`◎ 拾取中…`:`🎯 点选元素`),(0,r.createElement)(`button`,{type:`button`,onClick:()=>f(!1),style:{width:`28px`,height:`28px`,border:`1px solid ${t.border}`,borderRadius:`6px`,background:`transparent`,color:t.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),k!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`8px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #10b981`,background:e?`rgba(16, 185, 129, 0.1)`:`rgba(16, 185, 129, 0.06)`,fontSize:`12px`}},(0,r.createElement)(`div`,{style:{flex:1,minWidth:0}},(0,r.createElement)(`div`,{style:{color:t.primary,fontFamily:`monospace`,wordBreak:`break-all`}},k.selector),(0,r.createElement)(`div`,{style:{color:t.tertiary,marginTop:`2px`}},`${k.tag}${k.role?` · role=${k.role}`:``}${k.name?` · "${k.name}"`:``} — 可作为 browser_click/type/select 的 selector 告诉 agent`)),(0,r.createElement)(`button`,{type:`button`,onClick:N,style:{flex:`none`,padding:`4px 10px`,border:`none`,borderRadius:`6px`,background:`#10b981`,color:`#fff`,cursor:`pointer`,fontSize:`12px`,fontWeight:600}},`复制 selector`)),p?.open?(0,r.createElement)(`img`,{src:P,alt:`浏览器大图`,style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:`1px solid ${t.border}`,background:e?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),_.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${t.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${_.length} 条,点击在新标签打开)`),_.map((e,n)=>(0,r.createElement)(`a`,{key:n,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:t.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:t.tertiary,flex:`none`}},s(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url)))))),T&&(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&E(!1)},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 ${t.border}`,borderRadius:`12px`,background:e?`#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,color:t.primary,fontSize:`14px`,fontWeight:600}},`回归报告${x===null?``:` — ${x.source}`}(运行趋势 / 失败明细 / 自愈清单)`),(0,r.createElement)(`button`,{type:`button`,onClick:()=>E(!1),style:{width:`28px`,height:`28px`,border:`1px solid ${t.border}`,borderRadius:`6px`,background:`transparent`,color:t.secondary,cursor:`pointer`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${n}/runs/report`,title:`回归报告`,style:{flex:1,width:`100%`,border:`1px solid ${t.border}`,borderRadius:`8px`,background:`#fff`}}))))}function u(e){let t=o(),n=t?a:i,s=c(n),[l,u]=(0,r.useState)(`/browser`),[d,f]=(0,r.useState)(null),[p,m]=(0,r.useState)(``),[h,g]=(0,r.useState)({});(0,r.useEffect)(()=>{let e=!0;return fetch(`/__dsh-browser-view__/config`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&typeof t.basePath==`string`&&t.basePath&&u(t.basePath.replace(/\/+$/,``))}).catch(()=>{}),()=>{e=!1}},[]),(0,r.useEffect)(()=>{let e=!0;return f(null),m(``),fetch(`${l}/recordings`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&f(t.recordings??[])}).catch(t=>{e&&m(t instanceof Error?t.message:String(t))}),()=>{e=!1}},[l]);let _=e=>{g(t=>{let n=t[e];if(n!==void 0&&n!==`loading`){let n={...t};return delete n[e],n}return fetch(`${l}/recordings/detail?name=${encodeURIComponent(e)}`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>g(n=>({...n,[e]:t}))).catch(()=>g(t=>({...t,[e]:`failed`}))),{...t,[e]:`loading`}})},v=e=>{fetch(`${l}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:e})}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(()=>{f(t=>t===null?t:t.filter(t=>t.name!==e)),g(t=>{let n={...t};return delete n[e],n})}).catch(()=>{})};return(0,r.createElement)(`div`,{style:s.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:`38px`,height:`38px`,borderRadius:`10px`,display:`flex`,alignItems:`center`,justifyContent:`center`,background:t?`#222938`:`#eef2f6`,border:`1px solid ${n.border}`,fontSize:`20px`,flexShrink:0}},`🌐`),(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}},`共享 Playwright 浏览器自动化、测试原语、操作轨迹与回归录制管理`))),(0,r.createElement)(`div`,{style:s.heading},`已保存的操作录制 (agent 在对话中用 browser_record save <name> 创建,存储于 $DSH_HOME/.dsh-browser/recordings)`),p?(0,r.createElement)(`div`,{style:s.empty},`加载失败: ${p}`):d===null?(0,r.createElement)(`div`,{style:s.empty},`加载中…`):d.length===0?(0,r.createElement)(`div`,{style:s.empty},`暂无录制。让 agent 跑完一轮流程后 browser_record save <name> 创建。`):d.map(e=>{let t=h[e.name];return(0,r.createElement)(`div`,{key:e.name,style:s.itemBox},(0,r.createElement)(`div`,{style:s.row},(0,r.createElement)(`span`,{style:s.label,title:`点击展开步骤`,onClick:()=>_(e.name)},e.name),(0,r.createElement)(`span`,{style:s.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleString()}`),(0,r.createElement)(`button`,{style:s.btn,onClick:()=>_(e.name)},t===void 0?`展开`:`收起`),(0,r.createElement)(`button`,{style:s.delBtn,onClick:()=>v(e.name)},`删除`)),t===void 0?null:t===`loading`?(0,r.createElement)(`div`,{style:s.empty},`加载中…`):(0,r.createElement)(`div`,{style:s.stepsBox},t===`failed`?(0,r.createElement)(`div`,{style:s.empty},`加载失败`):t.steps.length===0?(0,r.createElement)(`div`,{style:s.empty},`(空步骤)`):t.steps.map((t,n)=>(0,r.createElement)(`div`,{key:`${e.name}-${n}`,style:s.stepLine,title:`${t.tool} ${JSON.stringify(t.args??{})}`},(0,r.createElement)(`span`,{style:s.meta},`#${n}`),(0,r.createElement)(`span`,{style:s.toolName},t.tool),(0,r.createElement)(`span`,{style:s.stepArgs},JSON.stringify(t.args??{}))))))}),d!==null&&(0,r.createElement)(`div`,{style:s.heading},`提示:agent 在对话中用 browser_record save <name> 创建录制。`))}let d=[`slots`];function f(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`🌐 浏览器`},u)),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},l))}return n.apply=f,n.inject=d,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={primary:`#1f2937`,secondary:`#4b5563`,tertiary:`#6b7280`,border:`#d8dde3`,hoverBg:`#f1f4f7`,danger:`#dc2626`},a={primary:`#f3f4f6`,secondary:`#cbd5e1`,tertiary:`#94a3b8`,border:`#3f4a58`,hoverBg:`#2a3240`,danger:`#f87171`};function o(){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 s(e){let t=new Date(e);return`${(`0`+t.getHours()).slice(-2)}:${(`0`+t.getMinutes()).slice(-2)}`}function c(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 l(){let e=o(),t=e?a:i,[n,c]=(0,r.useState)(`/browser`),[l,u]=(0,r.useState)(!1),[d,f]=(0,r.useState)(!1),[p,m]=(0,r.useState)(null),[h,g]=(0,r.useState)([]),[_,v]=(0,r.useState)([]),[y,b]=(0,r.useState)(Date.now()),[x,S]=(0,r.useState)(null),[C,w]=(0,r.useState)(null),[T,E]=(0,r.useState)(!1);(0,r.useEffect)(()=>{let e=!0;return fetch(`/__dsh-browser-view__/config`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&typeof t.basePath==`string`&&t.basePath&&c(t.basePath.replace(/\/+$/,``))}).catch(()=>{}),()=>{e=!1}},[]),(0,r.useEffect)(()=>{let e=!0,t=()=>{fetch(`${n}/status`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&(m(t),t?.open&&b(Date.now()))}).catch(()=>{e&&m(null)}),fetch(`${n}/log`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&t&&Array.isArray(t.calls)&&g(t.calls.slice(0,8))}).catch(()=>{}),fetch(`${n}/runs/latest`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&S(t?.run??null)}).catch(()=>{})};t();let r=setInterval(t,3e3);return()=>{e=!1,clearInterval(r)}},[n]),(0,r.useEffect)(()=>{let e=!0,t=()=>{fetch(`${n}/confirm`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&w(t?.confirm??null)}).catch(()=>{})};t();let r=setInterval(t,1e3);return()=>{e=!1,clearInterval(r)}},[n]);let D=e=>{let t=C?.id;t&&(w(null),fetch(`${n}/confirm`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t,decision:e})}).catch(()=>{}))};(0,r.useEffect)(()=>{if(!d)return;let e=!0,t=()=>{fetch(`${n}/history`,{cache:`no-store`}).then(e=>e.ok?e.json():null).then(t=>{e&&t&&Array.isArray(t.history)&&v(t.history)}).catch(()=>{}),b(Date.now())};t();let r=setInterval(t,2e3);return()=>{e=!1,clearInterval(r)}},[d,n]),(0,r.useEffect)(()=>{if(!d)return;let e=e=>{e.key===`Escape`&&f(!1)};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[d]);let O=`${n}/screenshot?v=${y}`;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:()=>u(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.25)`,pointerEvents:`auto`,userSelect:`none`}},`🌐`),l&&(0,r.createElement)(`div`,{style:{position:`fixed`,right:`16px`,bottom:`66px`,width:`320px`,maxHeight:`72vh`,overflowY:`auto`,overflowX:`hidden`,display:`flex`,flexDirection:`column`,gap:`8px`,border:`1px solid ${t.border}`,borderRadius:`12px`,background:e?`#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:p?.open?`#10b981`:`#ef4444`}}),(0,r.createElement)(`span`,{style:{flex:1,minWidth:0,color:t.primary,fontSize:`13px`,fontWeight:600,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},`浏览器实时画面`),(0,r.createElement)(`span`,{style:{color:t.secondary,fontSize:`12px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,maxWidth:`160px`}},p===null?`未启用`:p.open?`${p.tabs??1} 标签 · ${(p.url||``).slice(0,20)}`:`未打开`)),C?(0,r.createElement)(`div`,{style:{padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #ef4444`,background:e?`rgba(239, 68, 68, 0.12)`:`rgba(239, 68, 68, 0.07)`,color:e?`#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`}},C.reason,C.tool?` (${C.tool})`:``),(0,r.createElement)(`div`,{style:{display:`flex`,gap:`8px`,marginTop:`6px`}},(0,r.createElement)(`button`,{onClick:()=>D(`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:()=>D(`denied`),style:{flex:1,padding:`4px 0`,borderRadius:`6px`,border:`none`,cursor:`pointer`,background:`#dc2626`,color:`#fff`,fontSize:`12px`,fontWeight:600}},`拒绝`))):null,p?.pause?(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,padding:`6px 8px`,borderRadius:`8px`,border:`1px solid #f59e0b`,background:e?`rgba(245, 158, 11, 0.12)`:`rgba(245, 158, 11, 0.08)`,color:e?`#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`}},`等待人工操作: ${p.pause.instruction}`)):null,p?.open?(0,r.createElement)(`img`,{src:O,alt:`实时画面`,title:`点击放大查看实时大屏与访问历史`,onClick:()=>f(!0),style:{width:`100%`,maxHeight:`160px`,objectFit:`cover`,borderRadius:`8px`,border:`1px solid ${t.border}`,cursor:`pointer`,background:e?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`11px`,lineHeight:`1.5`,padding:`8px`,background:e?`#121620`:`#f8fafc`,borderRadius:`6px`}},`浏览器尚未启动。在对话中让 Agent 调用 browser_open 即可呈现画面。`),h.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`3px`,borderTop:`1px solid ${t.border}`,paddingTop:`6px`,marginTop:`2px`}},(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`11px`,fontWeight:500,marginBottom:`2px`}},`最近操作 (${h.length} 条)`),h.map((e,n)=>(0,r.createElement)(`div`,{key:n,style:{display:`flex`,alignItems:`center`,gap:`6px`,fontSize:`12px`,color:t.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:t.tertiary,fontSize:`11px`,flex:`none`}},s(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}`:``}`)))),x!==null&&(0,r.createElement)(`div`,{style:{display:`flex`,alignItems:`center`,gap:`6px`,borderTop:`1px solid ${t.border}`,paddingTop:`6px`,marginTop:`2px`,fontSize:`12px`,color:t.secondary,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`,cursor:`pointer`},title:`runId=${x.runId} · 点击查看报告`,onClick:()=>E(!0)},(0,r.createElement)(`span`,{style:{flex:`none`,color:t.tertiary,fontSize:`11px`}},`最近回归`),(0,r.createElement)(`span`,{style:{flex:`none`,fontWeight:600,color:x.failed===0?`#10b981`:`#ef4444`}},x.failed===0?`✓`:`✗`),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},`${x.source}${x.row===void 0?``:` 行${x.row}`} ${x.passed}/${x.total}${x.healedCount>0?` · 自愈${x.healedCount}`:``} · ${new Date(x.ts).toLocaleTimeString(`zh-CN`,{hour12:!1})}`))),d&&(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&f(!1)},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 ${t.border}`,borderRadius:`12px`,background:e?`#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:t.primary,fontSize:`14px`,fontWeight:600}},p?.open?`${p.title||``} (${p.url||``})`:`浏览器实时大屏`),(0,r.createElement)(`button`,{type:`button`,onClick:()=>f(!1),style:{width:`28px`,height:`28px`,border:`1px solid ${t.border}`,borderRadius:`6px`,background:`transparent`,color:t.secondary,cursor:`pointer`,display:`flex`,alignItems:`center`,justifyContent:`center`,fontSize:`14px`}},`✕`)),p?.open?(0,r.createElement)(`img`,{src:O,alt:`浏览器大图`,style:{width:`100%`,maxHeight:`55vh`,objectFit:`contain`,borderRadius:`8px`,border:`1px solid ${t.border}`,background:e?`#10141c`:`#f8fafc`}}):(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`13px`,textAlign:`center`,padding:`32px 0`}},`浏览器尚未打开`),_.length>0&&(0,r.createElement)(`div`,{style:{display:`flex`,flexDirection:`column`,gap:`4px`,maxHeight:`22vh`,overflowY:`auto`,borderTop:`1px solid ${t.border}`,paddingTop:`8px`}},(0,r.createElement)(`div`,{style:{color:t.tertiary,fontSize:`12px`,fontWeight:500}},`访问历史 (${_.length} 条,点击在新标签打开)`),_.map((e,n)=>(0,r.createElement)(`a`,{key:n,href:e.url,target:`_blank`,rel:`noopener noreferrer`,style:{display:`flex`,gap:`8px`,alignItems:`center`,color:t.secondary,fontSize:`12px`,textDecoration:`none`,borderRadius:`6px`,padding:`4px 6px`,whiteSpace:`nowrap`,overflow:`hidden`,textOverflow:`ellipsis`}},(0,r.createElement)(`span`,{style:{color:t.tertiary,flex:`none`}},s(e.ts)),(0,r.createElement)(`span`,{style:{overflow:`hidden`,textOverflow:`ellipsis`}},e.title||e.url)))))),T&&(0,r.createElement)(`div`,{onClick:e=>{e.target===e.currentTarget&&E(!1)},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 ${t.border}`,borderRadius:`12px`,background:e?`#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,color:t.primary,fontSize:`14px`,fontWeight:600}},`回归报告${x===null?``:` — ${x.source}`}(运行趋势 / 失败明细 / 自愈清单)`),(0,r.createElement)(`button`,{type:`button`,onClick:()=>E(!1),style:{width:`28px`,height:`28px`,border:`1px solid ${t.border}`,borderRadius:`6px`,background:`transparent`,color:t.secondary,cursor:`pointer`}},`✕`)),(0,r.createElement)(`iframe`,{src:`${n}/runs/report`,title:`回归报告`,style:{flex:1,width:`100%`,border:`1px solid ${t.border}`,borderRadius:`8px`,background:`#fff`}}))))}function u(e){let t=o(),n=t?a:i,s=c(n),[l,u]=(0,r.useState)(`/browser`),[d,f]=(0,r.useState)(null),[p,m]=(0,r.useState)(``),[h,g]=(0,r.useState)({});(0,r.useEffect)(()=>{let e=!0;return fetch(`/__dsh-browser-view__/config`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&typeof t.basePath==`string`&&t.basePath&&u(t.basePath.replace(/\/+$/,``))}).catch(()=>{}),()=>{e=!1}},[]),(0,r.useEffect)(()=>{let e=!0;return f(null),m(``),fetch(`${l}/recordings`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>{e&&f(t.recordings??[])}).catch(t=>{e&&m(t instanceof Error?t.message:String(t))}),()=>{e=!1}},[l]);let _=e=>{g(t=>{let n=t[e];if(n!==void 0&&n!==`loading`){let n={...t};return delete n[e],n}return fetch(`${l}/recordings/detail?name=${encodeURIComponent(e)}`,{cache:`no-store`}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(t=>g(n=>({...n,[e]:t}))).catch(()=>g(t=>({...t,[e]:`failed`}))),{...t,[e]:`loading`}})},v=e=>{fetch(`${l}/recordings`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({name:e})}).then(e=>e.ok?e.json():Promise.reject(Error(`HTTP ${e.status}`))).then(()=>{f(t=>t===null?t:t.filter(t=>t.name!==e)),g(t=>{let n={...t};return delete n[e],n})}).catch(()=>{})};return(0,r.createElement)(`div`,{style:s.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:`38px`,height:`38px`,borderRadius:`10px`,display:`flex`,alignItems:`center`,justifyContent:`center`,background:t?`#222938`:`#eef2f6`,border:`1px solid ${n.border}`,fontSize:`20px`,flexShrink:0}},`🌐`),(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}},`共享 Playwright 浏览器自动化、测试原语、操作轨迹与回归录制管理`))),(0,r.createElement)(`div`,{style:s.heading},`已保存的操作录制 (agent 在对话中用 browser_record save <name> 创建,存储于 $DSH_HOME/.dsh-browser/recordings)`),p?(0,r.createElement)(`div`,{style:s.empty},`加载失败: ${p}`):d===null?(0,r.createElement)(`div`,{style:s.empty},`加载中…`):d.length===0?(0,r.createElement)(`div`,{style:s.empty},`暂无录制。让 agent 跑完一轮流程后 browser_record save <name> 创建。`):d.map(e=>{let t=h[e.name];return(0,r.createElement)(`div`,{key:e.name,style:s.itemBox},(0,r.createElement)(`div`,{style:s.row},(0,r.createElement)(`span`,{style:s.label,title:`点击展开步骤`,onClick:()=>_(e.name)},e.name),(0,r.createElement)(`span`,{style:s.meta},`${e.steps} 步 · ${new Date(e.savedAt).toLocaleString()}`),(0,r.createElement)(`button`,{style:s.btn,onClick:()=>_(e.name)},t===void 0?`展开`:`收起`),(0,r.createElement)(`button`,{style:s.delBtn,onClick:()=>v(e.name)},`删除`)),t===void 0?null:t===`loading`?(0,r.createElement)(`div`,{style:s.empty},`加载中…`):(0,r.createElement)(`div`,{style:s.stepsBox},t===`failed`?(0,r.createElement)(`div`,{style:s.empty},`加载失败`):t.steps.length===0?(0,r.createElement)(`div`,{style:s.empty},`(空步骤)`):t.steps.map((t,n)=>(0,r.createElement)(`div`,{key:`${e.name}-${n}`,style:s.stepLine,title:`${t.tool} ${JSON.stringify(t.args??{})}`},(0,r.createElement)(`span`,{style:s.meta},`#${n}`),(0,r.createElement)(`span`,{style:s.toolName},t.tool),(0,r.createElement)(`span`,{style:s.stepArgs},JSON.stringify(t.args??{}))))))}),d!==null&&(0,r.createElement)(`div`,{style:s.heading},`提示:agent 在对话中用 browser_record save <name> 创建录制。`))}let d=[`slots`];function f(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`browser`,order:40,label:`🌐 浏览器`},u)),e.slots.inject(`shell.overlay`,()=>e.slots.register({name:`shell.overlay`,id:`dsh-browser-overlay`,order:100},l))}return n.apply=f,n.inject=d,t.exports}});
|
|
2
2
|
//# sourceMappingURL=client.js.map
|
package/lib/index.js
CHANGED
|
@@ -2260,69 +2260,6 @@ async function contextTool(config, args) {
|
|
|
2260
2260
|
throw new Error(`未知 action: ${action}(list | new | switch | close)`);
|
|
2261
2261
|
}
|
|
2262
2262
|
/**
|
|
2263
|
-
* 页内拾取器(evaluate 注入):一次性 click 捕获 —— 点击任意元素时阻止默认行为,
|
|
2264
|
-
* 计算稳定 CSS 路径 + 元素信息存 window.__dshPickResult(面板轮询读取后自清)。
|
|
2265
|
-
* 与 aria walker 的 cssPath 同一套语义(#id → [data-testid] → nth-of-type 链)。
|
|
2266
|
-
*/
|
|
2267
|
-
function pickElementInjector() {
|
|
2268
|
-
const w = window;
|
|
2269
|
-
if (w.__dshPicking === true) return;
|
|
2270
|
-
w.__dshPicking = true;
|
|
2271
|
-
function cssPath(el) {
|
|
2272
|
-
if (el.id) return `#${CSS.escape(el.id)}`;
|
|
2273
|
-
const testid = el.getAttribute("data-testid") ?? el.getAttribute("data-test-id");
|
|
2274
|
-
if (testid) return `[data-testid="${testid.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"]`;
|
|
2275
|
-
const parts = [];
|
|
2276
|
-
let cur = el;
|
|
2277
|
-
while (cur && cur !== document.body && parts.length < 6) {
|
|
2278
|
-
const tag = cur.tagName.toLowerCase();
|
|
2279
|
-
const parent = cur.parentElement;
|
|
2280
|
-
if (!parent) {
|
|
2281
|
-
parts.unshift(tag);
|
|
2282
|
-
break;
|
|
2283
|
-
}
|
|
2284
|
-
const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
|
|
2285
|
-
const idx = sameTag.indexOf(cur) + 1;
|
|
2286
|
-
parts.unshift(sameTag.length > 1 ? `${tag}:nth-of-type(${idx})` : tag);
|
|
2287
|
-
cur = parent;
|
|
2288
|
-
}
|
|
2289
|
-
return parts.join(" > ");
|
|
2290
|
-
}
|
|
2291
|
-
const handler = (e) => {
|
|
2292
|
-
const el = e.target instanceof Element ? e.target : null;
|
|
2293
|
-
if (el === null) return;
|
|
2294
|
-
e.preventDefault();
|
|
2295
|
-
e.stopPropagation();
|
|
2296
|
-
const aria = el.getAttribute("aria-label");
|
|
2297
|
-
w.__dshPickResult = {
|
|
2298
|
-
selector: cssPath(el),
|
|
2299
|
-
tag: el.tagName.toLowerCase(),
|
|
2300
|
-
id: el.id ?? "",
|
|
2301
|
-
testid: el.getAttribute("data-testid") ?? el.getAttribute("data-test-id") ?? "",
|
|
2302
|
-
role: el.getAttribute("role") ?? "",
|
|
2303
|
-
name: aria && aria.trim() ? aria.trim().slice(0, 60) : (el.innerText || el.textContent || "").trim().slice(0, 60),
|
|
2304
|
-
url: location.href
|
|
2305
|
-
};
|
|
2306
|
-
w.__dshPicking = false;
|
|
2307
|
-
document.removeEventListener("click", handler, true);
|
|
2308
|
-
};
|
|
2309
|
-
document.addEventListener("click", handler, true);
|
|
2310
|
-
}
|
|
2311
|
-
/** 读取并清空拾取结果(ui.ts /pick GET 调用;无结果返回 null)。 */
|
|
2312
|
-
async function takePickResult(page) {
|
|
2313
|
-
try {
|
|
2314
|
-
return await page.evaluate(() => {
|
|
2315
|
-
const w = window;
|
|
2316
|
-
const out = w.__dshPickResult ?? null;
|
|
2317
|
-
w.__dshPickResult = null;
|
|
2318
|
-
w.__dshPicking = false;
|
|
2319
|
-
return out;
|
|
2320
|
-
});
|
|
2321
|
-
} catch {
|
|
2322
|
-
return null;
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
/**
|
|
2326
2263
|
* 操作预告高亮:agent 即将 click/type/select/press 的元素在页面上琥珀色描边
|
|
2327
2264
|
* ~1.5 秒。异步不阻塞(fire-and-forget,任何失败静默),不影响操作时序。
|
|
2328
2265
|
*/
|
|
@@ -3934,16 +3871,11 @@ function registerTools(ctx, config) {
|
|
|
3934
3871
|
if (tools === void 0) return;
|
|
3935
3872
|
setToolsLogger((m) => ctx.logger?.info?.(m));
|
|
3936
3873
|
const timeout = (ms) => ms ?? config.timeoutMs;
|
|
3937
|
-
/**
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
const s = JSON.stringify(v);
|
|
3943
|
-
out[k] = s && s.length > 300 ? `${s.slice(0, 300)}…(截断)` : v;
|
|
3944
|
-
}
|
|
3945
|
-
return out;
|
|
3946
|
-
}
|
|
3874
|
+
/**
|
|
3875
|
+
* 打点包装:每次工具调用记录操作轨迹(测试报告 / 录制数据源)。
|
|
3876
|
+
* 注意:记录必须保留完整原始 args —— 录制/回放依赖脚本、steps 数组等大字段的
|
|
3877
|
+
* 完整性(截断会让回放得到损坏的 script / 空的 steps)。展示层截断在 ui.ts 做。
|
|
3878
|
+
*/
|
|
3947
3879
|
/** 会触发真实 DOM 事件的 agent 工具:执行前后打点,混合录制时用于区分人手与工具回声。 */
|
|
3948
3880
|
const AGENT_DOM_TOOLS = /* @__PURE__ */ new Set([
|
|
3949
3881
|
"browser_click",
|
|
@@ -3997,7 +3929,6 @@ function registerTools(ctx, config) {
|
|
|
3997
3929
|
const original = def.execute;
|
|
3998
3930
|
def.execute = async (args, exec) => {
|
|
3999
3931
|
const t0 = Date.now();
|
|
4000
|
-
const sanitized = sanitizeArgs(args);
|
|
4001
3932
|
const domTool = AGENT_DOM_TOOLS.has(def.name);
|
|
4002
3933
|
if (domTool) noteAgentAction();
|
|
4003
3934
|
if (PREVIEW_TOOLS.has(def.name)) {
|
|
@@ -4009,7 +3940,7 @@ function registerTools(ctx, config) {
|
|
|
4009
3940
|
if (domTool) noteAgentAction();
|
|
4010
3941
|
const call = recordCall({
|
|
4011
3942
|
tool: def.name,
|
|
4012
|
-
args
|
|
3943
|
+
args,
|
|
4013
3944
|
ok: true,
|
|
4014
3945
|
ms: Date.now() - t0,
|
|
4015
3946
|
ts: Date.now()
|
|
@@ -4025,7 +3956,7 @@ function registerTools(ctx, config) {
|
|
|
4025
3956
|
} catch (error) {
|
|
4026
3957
|
recordCall({
|
|
4027
3958
|
tool: def.name,
|
|
4028
|
-
args
|
|
3959
|
+
args,
|
|
4029
3960
|
ok: false,
|
|
4030
3961
|
ms: Date.now() - t0,
|
|
4031
3962
|
ts: Date.now(),
|
|
@@ -5787,6 +5718,16 @@ function registerTools(ctx, config) {
|
|
|
5787
5718
|
}
|
|
5788
5719
|
//#endregion
|
|
5789
5720
|
//#region lib/types/browser/ui.js
|
|
5721
|
+
/** 展示层参数截断:单字段 JSON 超过 300 字符截断(仅用于面板轨迹显示,不进录制)。 */
|
|
5722
|
+
function truncateForDisplay(args) {
|
|
5723
|
+
if (typeof args !== "object" || args === null) return args;
|
|
5724
|
+
const out = {};
|
|
5725
|
+
for (const [k, v] of Object.entries(args)) {
|
|
5726
|
+
const s = JSON.stringify(v);
|
|
5727
|
+
out[k] = s && s.length > 300 ? `${s.slice(0, 300)}…(截断)` : v;
|
|
5728
|
+
}
|
|
5729
|
+
return out;
|
|
5730
|
+
}
|
|
5790
5731
|
function registerBrowserApi(ctx, config, base) {
|
|
5791
5732
|
const webServer = ctx.get("webServer");
|
|
5792
5733
|
if (webServer === void 0) return;
|
|
@@ -5813,7 +5754,10 @@ function registerBrowserApi(ctx, config, base) {
|
|
|
5813
5754
|
if (path === `${base}/log`) {
|
|
5814
5755
|
sendJson(res, 200, {
|
|
5815
5756
|
profile: currentProfile,
|
|
5816
|
-
calls: testLogOf().slice(0, 50)
|
|
5757
|
+
calls: testLogOf().slice(0, 50).map((c) => ({
|
|
5758
|
+
...c,
|
|
5759
|
+
args: truncateForDisplay(c.args)
|
|
5760
|
+
}))
|
|
5817
5761
|
});
|
|
5818
5762
|
return;
|
|
5819
5763
|
}
|
|
@@ -5836,29 +5780,6 @@ function registerBrowserApi(ctx, config, base) {
|
|
|
5836
5780
|
res.end(html);
|
|
5837
5781
|
return;
|
|
5838
5782
|
}
|
|
5839
|
-
if (path === `${base}/pick`) {
|
|
5840
|
-
if ((req.method ?? "GET") === "POST") {
|
|
5841
|
-
const page = activePage();
|
|
5842
|
-
if (page === null || page.isClosed()) {
|
|
5843
|
-
sendText(res, 409, "browser not open");
|
|
5844
|
-
return;
|
|
5845
|
-
}
|
|
5846
|
-
try {
|
|
5847
|
-
await page.evaluate(pickElementInjector);
|
|
5848
|
-
sendJson(res, 200, { started: true });
|
|
5849
|
-
} catch (error) {
|
|
5850
|
-
sendText(res, 500, `pick start failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
5851
|
-
}
|
|
5852
|
-
return;
|
|
5853
|
-
}
|
|
5854
|
-
const page = activePage();
|
|
5855
|
-
if (page === null || page.isClosed()) {
|
|
5856
|
-
sendJson(res, 200, { pick: null });
|
|
5857
|
-
return;
|
|
5858
|
-
}
|
|
5859
|
-
sendJson(res, 200, { pick: await takePickResult(page) });
|
|
5860
|
-
return;
|
|
5861
|
-
}
|
|
5862
5783
|
if (path === `${base}/recordings`) {
|
|
5863
5784
|
if ((req.method ?? "GET") === "POST") {
|
|
5864
5785
|
const body = await readJsonBody(req).catch(() => void 0);
|
|
@@ -251,14 +251,6 @@ export declare function contextTool(config: BrowserConfig, args: Record<string,
|
|
|
251
251
|
contexts?: undefined;
|
|
252
252
|
profile?: undefined;
|
|
253
253
|
}>;
|
|
254
|
-
/**
|
|
255
|
-
* 页内拾取器(evaluate 注入):一次性 click 捕获 —— 点击任意元素时阻止默认行为,
|
|
256
|
-
* 计算稳定 CSS 路径 + 元素信息存 window.__dshPickResult(面板轮询读取后自清)。
|
|
257
|
-
* 与 aria walker 的 cssPath 同一套语义(#id → [data-testid] → nth-of-type 链)。
|
|
258
|
-
*/
|
|
259
|
-
export declare function pickElementInjector(): void;
|
|
260
|
-
/** 读取并清空拾取结果(ui.ts /pick GET 调用;无结果返回 null)。 */
|
|
261
|
-
export declare function takePickResult(page: Page): Promise<Record<string, unknown> | null>;
|
|
262
254
|
/**
|
|
263
255
|
* 操作预告高亮:agent 即将 click/type/select/press 的元素在页面上琥珀色描边
|
|
264
256
|
* ~1.5 秒。异步不阻塞(fire-and-forget,任何失败静默),不影响操作时序。
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-browser-view",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "DSH 插件:共享 Playwright 浏览器自动化(36 个 browser_* 工具,含语义元素查找、批处理、视觉回归、选择器自愈、参数化回放、结构化抽取、回归报告与隔离 context)+ 人机协作(
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "DSH 插件:共享 Playwright 浏览器自动化(36 个 browser_* 工具,含语义元素查找、批处理、视觉回归、选择器自愈、参数化回放、结构化抽取、回归报告与隔离 context)+ 人机协作(人工接管等待、敏感操作审批、人机混合录制、面板双向化:操作预告高亮/内嵌报告)+ 实时画面面板与录制管理。仅依赖 playwright-core,不依赖任何 @deepseek-ai 运行时包。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
7
7
|
"types": "./lib/types/index.d.ts",
|