dsh-browser-view 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +134 -2
- package/cordis.patch.yml +31 -0
- package/lib/client.js +2 -0
- package/lib/index.js +2949 -0
- package/lib/types/browser/recordings.d.ts +31 -0
- package/lib/types/browser/register.d.ts +4 -0
- package/lib/types/browser/session.d.ts +103 -0
- package/lib/types/browser/tools.d.ts +339 -0
- package/lib/types/browser/types.d.ts +55 -0
- package/lib/types/browser/ui.d.ts +4 -0
- package/lib/types/client/index.d.ts +26 -0
- package/lib/types/embed.d.ts +7 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/lib/config.d.ts +17 -0
- package/lib/types/lib/http.d.ts +12 -0
- package/lib/types/lib/tools.d.ts +26 -0
- package/lib/types/lib/types.d.ts +59 -0
- package/package.json +76 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,135 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-browser-view
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
DSH 插件:共享 Playwright 浏览器自动化 + 原生界面实时画面面板。一次安装,两件事:
|
|
4
|
+
|
|
5
|
+
| 模块 | 做什么 |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| 🧭 **浏览器自动化** | 共享 Playwright 浏览器:多标签、多 profile、下载/上传、Cookie、表单回放,22 个 `browser_*` 工具 |
|
|
8
|
+
| 🖥️ **实时画面** | 注入 DSH 界面右下角悬浮面板:浏览器状态 + 实时缩略图,点开大屏模态框(2 秒轮询画面 + 访问历史) |
|
|
9
|
+
|
|
10
|
+
设计原则:能做的一次性操作交给 agent 用 bash 完成,插件只做 bash 做不到的常驻浏览器自动化。唯一第三方运行时依赖是 `playwright-core`(复用系统已装的 Edge/Chrome,无需下载 Chromium),不依赖任何 `@deepseek-ai` 运行时包。
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
dsh plugin --profile web add dsh-browser-view
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
重启 DSH 生效。不需要时在 `$DSH_HOME/profiles/web/cordis.patch.yml` 里删除 `browser` 行或加 `config: { enabled: false }`。
|
|
19
|
+
|
|
20
|
+
## 配置(全在 cordis.patch.yml)
|
|
21
|
+
|
|
22
|
+
```yaml
|
|
23
|
+
- insert:
|
|
24
|
+
- id: browser
|
|
25
|
+
name: 'dsh-browser-view'
|
|
26
|
+
config:
|
|
27
|
+
channel: auto # auto | msedge | chrome | chromium | ""(playwright 默认)
|
|
28
|
+
executablePath: "" # 显式指定浏览器可执行文件(优先于 channel)
|
|
29
|
+
headless: false # 默认有头(本机调试/人工登录);服务器场景可改 true
|
|
30
|
+
userDataDir: "" # 设置后登录态跨 DSH 重启保留
|
|
31
|
+
profiles: {} # { work: { userDataDir, channel, headless }, ... }
|
|
32
|
+
screenshotDir: .dsh-browser/screenshots
|
|
33
|
+
downloadDir: .dsh-browser/downloads
|
|
34
|
+
screenshotMaxAgeDays: 7 # 截图保留天数(0 = 不按时间清理)
|
|
35
|
+
screenshotMaxCount: 200 # 截图保留数量上限(0 = 不按数量清理)
|
|
36
|
+
basePath: /browser # 面板嵌入 + 实时画面 API 基路径(重启生效)
|
|
37
|
+
maxTextChars: 20000
|
|
38
|
+
maxLinks: 50
|
|
39
|
+
timeoutMs: 30000
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 浏览器工具
|
|
43
|
+
|
|
44
|
+
| 工具 | 说明 |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| `browser_open` | 打开 URL |
|
|
47
|
+
| `browser_tabs` | 标签管理:list / new / switch / close(按 id 或 index) |
|
|
48
|
+
| `browser_snapshot` | 读取页面文本/链接/输入框(纯文本模型"看"网页的核心) |
|
|
49
|
+
| `browser_elements` | 可交互元素结构化清单(input/button/select/链接 + 现成 CSS 选择器) |
|
|
50
|
+
| `browser_click` / `browser_type` / `browser_press` | 点击 / 输入(可清空、可回车提交)/ 按键 |
|
|
51
|
+
| `browser_eval` | 在页面上下文执行 JS(结果自动净化成 lossless JSON) |
|
|
52
|
+
| `browser_screenshot` | 截图存为 PNG(自动清理,见下) |
|
|
53
|
+
| `browser_wait` / `browser_back` / `browser_reload` | 等待 / 后退 / 刷新 |
|
|
54
|
+
| `browser_wait_for_login` | 等待人工登录完成(配 `headless: false` 使用,见下) |
|
|
55
|
+
| `browser_wait_for` | 通用显式等待(元素状态 / URL / 文本 / 数量 / eval,超时抛错) |
|
|
56
|
+
| `browser_assert` | 结构化断言(失败自动截图存证据目录并报错) |
|
|
57
|
+
| `browser_network` | 接口记录查询:list / failed(4xx/5xx)/ wait / clear,含请求/响应体(截断) |
|
|
58
|
+
| `browser_record` | 操作录制:start / stop / save / list / delete |
|
|
59
|
+
| `browser_replay` | 回放录制或步骤序列(回归测试,遇错可停) |
|
|
60
|
+
| `browser_status` / `browser_close` | 状态 / 关闭会话 |
|
|
61
|
+
| `browser_download` / `browser_upload` | 下载(保存到工作区)/ 上传本地文件 |
|
|
62
|
+
| `browser_cookies` | Cookie 管理(list / set / clear,登录态处理) |
|
|
63
|
+
| `browser_form` / `browser_form_save` / `browser_forms` | 批量填表 / 保存回放 / 管理已存表单 |
|
|
64
|
+
| `browser_profile` | 多配置文件:work/personal 等独立会话与登录态(userDataDir 持久化) |
|
|
65
|
+
|
|
66
|
+
**截图自动清理**:`browser_screenshot` 保存的 `.png` 自动修剪——每次截图后即时清理 + 每小时定时清扫;默认保留最近 7 天、最多 200 张(`screenshotMaxAgeDays` / `screenshotMaxCount`,设为 0 关闭对应规则),只清理截图目录直属文件。
|
|
67
|
+
|
|
68
|
+
> 首次调用浏览器工具时自动启动浏览器;启动失败会提示安装 Chromium(`npx playwright install chromium`)或配置 `executablePath`。
|
|
69
|
+
|
|
70
|
+
## 自动化测试
|
|
71
|
+
|
|
72
|
+
插件提供完整的测试原语,agent 可以写出「等待 + 断言 + 网络校验 + 回放」的测试流程:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
# 典型流程
|
|
76
|
+
browser_open 打开目标页
|
|
77
|
+
browser_wait_for { selector: "#app", state: "visible" } # 等页面稳定
|
|
78
|
+
browser_assert { text: "登录成功" } # 断言文本
|
|
79
|
+
browser_assert { count: { selector: ".row", op: "gte", value: 10 } } # 断言数量
|
|
80
|
+
browser_network { action: "failed" } # 检查 4xx/5xx
|
|
81
|
+
browser_network { action: "wait", url: "api/orders", status: 200 } # 等接口返回
|
|
82
|
+
browser_screenshot # 证据
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**接口出入参**:`browser_network` 自动记录每个 XHR/fetch 的请求体(`postData`)与响应体(`body`,均截断:请求体 2000 字符、响应体 4000 字符;SSE 流与 >500KB 响应跳过;`config.recordBodies: false` 可关闭)。`wait` 匹配到的记录同样带出入参,可直接断言接口返回结构。
|
|
86
|
+
|
|
87
|
+
**条件格式**(`browser_wait_for` / `browser_assert` 共用,五选一):
|
|
88
|
+
|
|
89
|
+
| 字段 | 示例 | 说明 |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `selector` | `{ selector: "#btn", state: "visible" }` | 元素状态:visible / hidden / attached / detached |
|
|
92
|
+
| `url` | `{ url: "https://.*\\.example\\.com" }` | 当前 URL 正则匹配 |
|
|
93
|
+
| `text` | `{ text: "保存成功" }` | 页面可见文本包含 |
|
|
94
|
+
| `count` | `{ count: { selector: ".row", op: "gte", value: 10 } }` | 元素数量比较(eq/gt/gte/lt/lte) |
|
|
95
|
+
| `eval` | `{ eval: "location.hash === '#done'" }` | 任意 JS 表达式求值 truthy |
|
|
96
|
+
|
|
97
|
+
`browser_assert` 失败时自动截图(`assert-fail-*.png`,存截图目录,受清理策略约束)并抛错,错误消息含原因与截图路径。
|
|
98
|
+
|
|
99
|
+
**回归测试**:agent 跑通一轮流程后 `browser_record save <name>`,以后用 `browser_replay { name }` 一键回放(默认遇错即停,`failFast: false` 可继续);也可直接传 `steps` 数组。已保存录制写入 `$DSH_HOME/.dsh-browser/recordings/<name>.json`(原子替换,名字自动安全化防路径穿越),重启 DSH 后自动加载,可跨会话复用;`browser_record list / delete` 管理。**录制管理界面**:DSH 设置弹窗 → 左侧导航「浏览器」页,可查看列表、展开步骤详情、删除录制。所有工具调用自动记录操作轨迹(面板显示最近 8 条,`/browser/log` 可取全量)。
|
|
100
|
+
|
|
101
|
+
## 人工登录(验证码 / 扫码 / 双因素)
|
|
102
|
+
|
|
103
|
+
很多网站需要登录。插件支持"agent 打开登录页 → 人工在浏览器窗口完成登录 → agent 继续"的协作流程:
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
config:
|
|
107
|
+
userDataDir: ~/.dsh-browser/profile-work # 登录态持久化,重启/切回无头后仍有效
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> `headless` 默认已是 `false`(有头窗口);服务器等无 GUI 场景请显式覆盖为 `true`。
|
|
111
|
+
|
|
112
|
+
1. agent 用 `browser_open` 打开登录页(此时弹出真实浏览器窗口);
|
|
113
|
+
2. agent 告知用户完成登录,并调用 `browser_wait_for_login`(可选 `successSelector` / `successUrl` 指定完成条件);
|
|
114
|
+
3. 用户在窗口中输入账号 / 扫码 / 完成双因素;
|
|
115
|
+
4. `browser_wait_for_login` 检测到完成条件(默认 URL 跳转)后返回,agent 继续后续操作。
|
|
116
|
+
|
|
117
|
+
配置 `userDataDir` 后,登录态(Cookie / localStorage)会保存到该目录,后续即使切回 `headless: true` 或重启 DSH,已登录的网站仍保持登录态。
|
|
118
|
+
|
|
119
|
+
## 实时画面面板
|
|
120
|
+
|
|
121
|
+
安装后 DSH 界面右下角出现 🌐 悬浮按钮(跟随 DSH 亮/暗主题):
|
|
122
|
+
|
|
123
|
+
- **状态行**:浏览器开关、标签数、当前 URL;
|
|
124
|
+
- **实时缩略图**:浏览器打开时显示,点击弹出大屏模态框——2 秒轮询实时画面,下方列出**访问历史**(最近 50 条,点击可在新标签打开)。
|
|
125
|
+
|
|
126
|
+
## 开发
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
pnpm install
|
|
130
|
+
pnpm verify # typecheck + test + build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# dsh-browser bundle layer. 一个插件行:浏览器自动化工具 + 原生界面实时画面面板。
|
|
2
|
+
# 配置可通过用户 profile 的 cordis.patch.yml 覆盖,不需要时删除本行。
|
|
3
|
+
#
|
|
4
|
+
# config 键:
|
|
5
|
+
# channel 浏览器通道('auto'|'msedge'|'chrome'|'chromium'|'' = playwright 默认)
|
|
6
|
+
# executablePath 显式浏览器可执行文件(优先于 channel)
|
|
7
|
+
# headless 无头模式(默认 false = 有头窗口,适合本机调试/人工登录)
|
|
8
|
+
# userDataDir 登录态持久化目录(设置后 Cookie/localStorage 跨 DSH 重启保留)
|
|
9
|
+
# profiles 多配置文件:{ work: { userDataDir, channel, headless }, ... }
|
|
10
|
+
# screenshotDir 截图目录(相对工作区,默认 .dsh-browser/screenshots)
|
|
11
|
+
# downloadDir 下载目录(默认 .dsh-browser/downloads)
|
|
12
|
+
# screenshotMaxAgeDays 截图保留天数(0 = 不按时间清理)
|
|
13
|
+
# screenshotMaxCount 截图保留数量(0 = 不按数量清理)
|
|
14
|
+
# basePath 面板嵌入 + 实时画面 API 基路径(重启生效,默认 /browser)
|
|
15
|
+
# recordBodies 网络记录是否保存请求/响应体(默认 true,截断存储)
|
|
16
|
+
# maxTextChars browser_snapshot 文本截断上限
|
|
17
|
+
# maxLinks browser_snapshot 链接数量上限
|
|
18
|
+
# timeoutMs 浏览器操作超时(毫秒)
|
|
19
|
+
- insert:
|
|
20
|
+
- id: browser
|
|
21
|
+
name: 'dsh-browser-view'
|
|
22
|
+
config:
|
|
23
|
+
channel: auto
|
|
24
|
+
headless: false
|
|
25
|
+
screenshotDir: .dsh-browser/screenshots
|
|
26
|
+
screenshotMaxAgeDays: 7
|
|
27
|
+
screenshotMaxCount: 200
|
|
28
|
+
basePath: /browser
|
|
29
|
+
maxTextChars: 20000
|
|
30
|
+
maxLinks: 50
|
|
31
|
+
timeoutMs: 30000
|
package/lib/client.js
ADDED
|
@@ -0,0 +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());(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(()=>{})};t();let r=setInterval(t,3e3);return()=>{e=!1,clearInterval(r)}},[n]),(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 x=`${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)}`:`未打开`)),p?.open?(0,r.createElement)(`img`,{src:x,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}`:``}`))))),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:x,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)))))))}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
|
+
//# sourceMappingURL=client.js.map
|