diff --git a/04_test/probe-screenshots.cjs b/04_test/probe-screenshots.cjs new file mode 100644 index 0000000..c468d54 --- /dev/null +++ b/04_test/probe-screenshots.cjs @@ -0,0 +1,63 @@ +// probe-screenshots.cjs — 真实界面截图:侧栏工作台 / 控制室 / 分栏工作区 → docs/assets/*.png +const http = require('http'); +const { spawn } = require('child_process'); +const WebSocket = require('ws'); +const fs = require('fs'); +const path = require('path'); +const PORT = 9382; +const OUT_DIR = path.join(__dirname, '..', 'docs', 'assets'); +fs.mkdirSync(OUT_DIR, { recursive: true }); +const proc = spawn('C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', [ + '--headless=new', '--disable-gpu', '--no-first-run', '--no-default-browser-check', + '--window-size=1600,1000', '--force-device-scale-factor=1', + '--remote-debugging-port=' + PORT, + '--user-data-dir=C:\\Users\\SJL\\AppData\\Local\\Temp\\wt-chrome-shots', + 'about:blank', +], { stdio: 'ignore' }); +const getJSON = (p) => new Promise((res, rej) => { + http.get({ host: '127.0.0.1', port: PORT, path: p }, (r) => { let d=''; r.on('data',c=>d+=c); r.on('end',()=>{try{res(JSON.parse(d))}catch(e){rej(e)}}) }).on('error', rej); +}); +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); +(async () => { + let list = null; + for (let i = 0; i < 40; i++) { try { list = await getJSON('/json/list'); if (list && list.length) break; } catch {} await sleep(500); } + const target = list.find((t) => t.type === 'page') || list[0]; + const ws = new WebSocket(target.webSocketDebuggerUrl); + let id = 0; const pending = new Map(); + const send = (method, params) => new Promise((res) => { const mid = ++id; pending.set(mid, res); ws.send(JSON.stringify({ id: mid, method, params })); }); + ws.on('message', (raw) => { const m = JSON.parse(String(raw)); if (m.id && pending.has(m.id)) { pending.get(m.id)(m); pending.delete(m.id); } }); + await new Promise((r) => ws.on('open', r)); + await send('Runtime.enable'); + await send('Page.enable'); + await send('Page.addScriptToEvaluateOnNewDocument', { + source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:['t-shot'],lastUsed:{},hidden:[],nameOverrides:{},iconOverrides:{},shortcuts:[],layouts:[{id:'t-shot',title:'Demo Workspace',icon:'🧱',top:null,left:null,main:[{id:'p1',title:'Pane 1',min:200,content:null,tabs:[],active:0}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}],bindings:{},folders:{},views:{},removed:[]})); } }catch(e){}", + }); + await send('Page.navigate', { url: 'http://127.0.0.1:3080/' }); + const evaluate = async (expr) => { + const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); + if (r.result && r.result.exceptionDetails) return { __error: (r.result.exceptionDetails.exception || {}).description || r.result.exceptionDetails.text }; + return r.result && r.result.result ? r.result.result.value : undefined; + }; + let ready = null; + for (let i = 0; i < 30; i++) { await sleep(1000); ready = await evaluate("!!window.__dshWorktable"); if (ready === true) break; } + await sleep(1800); + const shot = async (file) => { + const r = await send('Page.captureScreenshot', { format: 'png' }); + fs.writeFileSync(path.join(OUT_DIR, file), Buffer.from(r.result.data, 'base64')); + console.log('SHOT', file); + }; + // 1) 展开侧栏 → 工作台区块 + await evaluate("(function(){ var b=document.querySelector('button[title=\"Open sidebar\"],button[aria-label=\"Open sidebar\"]'); if(b){b.click(); return true} return false })()"); + await sleep(1600); + await shot('shot-1-sidebar.png'); + // 2) 控制室(未绑定 → 绑定第一个会话 → 打开面板) + const r2 = await evaluate("(async function(){ var sleep=function(ms){return new Promise(function(r){setTimeout(r,ms)})}; var vis=function(sel){return [].slice.call(document.querySelectorAll(sel)).find(function(el){return (getComputedStyle(el).visibility!=='hidden'&&el.getBoundingClientRect().height>0)})}; var card=vis('.dsh-wt_projects .dsh-wt_consoleEntry'); if(!card) return 'no-card'; card.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); await sleep(600); var bpop=vis('.dsh-wt_consoleBindPop'); var item=bpop&&bpop.querySelector('.dsh-wt_consoleBindList .dsh-wt_selectItem'); if(item){ item.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true})); } await sleep(3000); return 'ok'; })()"); + console.log('CONSOLE:', JSON.stringify(r2)); + await shot('shot-2-console.png'); + // 3) 分栏工作区(浏览器 + 终端) + await evaluate("(function(){ var st=window.__dshWorktable.splitStore; st.open({id:'shot-demo',title:'Worktable',top:[{id:'tp',title:'Browser',min:120,content:{kind:'builtin',type:'browser'}}],main:[{id:'mp',title:'Terminal',min:120,content:{kind:'builtin',type:'terminal'}}],chatWidth:{default:360,min:240,max:600},topHeight:{default:220,min:120,max:480},chatSide:'right'}); return 'ok'; })()"); + await sleep(2500); + await shot('shot-3-workspace.png'); + try { ws.close(); } catch {} + proc.kill(); +})().catch((e) => { console.error('PROBE FAIL', e); try { proc.kill(); } catch {} }); diff --git a/README.md b/README.md index 8c6a8a0..d5893a8 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,107 @@ -# dsh-worktable · 工作台 +# dsh-worktable 🖥️ -> DeepSeek Harness 侧边栏「工作台」容器插件:在「工作区」下方开辟一个收纳 agent 级项目(如 TravelAtlas 旅行图鉴)的应用抽屉。 -> A DeepSeek Harness sidebar "worktable" container: a project drawer below the official workspace section, hosting agent-level projects (e.g. TravelAtlas). +
English · 简体中文
-## 目录结构(仿 dsh-reminder) +**An agent-project workbench for DeepSeek Harness** — a sidebar app drawer that turns every project into dockable windows, plus a built-in control room that watches them all in real time. -``` -dsh-worktable/ -├── 00_index/ # 项目导航 -├── 01_content/ # 插件包(dsh bundle:cordis 服务端 + 客户端 CJS) -├── 02_process/ # PRD.md / drafts / research / worklogs -├── 03_local/ # 本机脚本与日志(不入库) -├── 04_test/ # 测试与验证产物 -├── README.md # 本文件 -└── AGENTS.md # 项目规则 -``` +
-## 当前状态
+
-- v2 稳定版:侧边栏区块(拖动/停靠/折叠态项目图标框)+ 排序(手动/最近)+ 管理项目(改名/隐藏/拖拽排序)+ 添加面板(接入指引 + 本地快捷方式)+ zh/en 词典 + 使用埋点 ✅
-- 项目协议 v2:`sidebar.worktable.project` 子座位 + 渐进上报协议(reportMeta/reportUsed/order/hidden/nameOverrides,全部可选,v1 卡片兼容)✅
-- 悬浮窗几何与 sidebar 联动(ResizeObserver)、底部悬浮面板避让 ✅
-- 首个入驻项目:dsh-travelatlas(图标 🌏,协议 v2 参考实现)✅
+---
-## 构建与安装
+## ✨ Feature tour
-```powershell
+### 🧩 Sidebar app drawer
+
+- Collects your self-hosted projects (and resident plugins like dsh-travelatlas) in one place
+- Rename / icon / reorder / hide each project; per-project folder; **project ↔ conversation binding** — opening a project switches the chat pane to its bound conversation
+- Collapse the sidebar and every project becomes a tappable square tile (icon only)
+
+### 🪟 Dockable split workspace
+
+- Declarative layout presets (left column / top row / main grid + right chat pane)
+- Draggable dividers, per-pane tabs, per-layout width persistence
+- Built-in panes: **file explorer, terminal, browser, animation site, custom window**
+- Custom window: send a requirement to a new or existing conversation; the agent builds it and the result auto-mounts into the window (locked)
+
+### 🖥️ Control room (built-in default project)
+
+- A pinned, undeletable first project — bind one management conversation on first open
+- 3-column card grid mirrors **every** project: working / needs you / done with live runtime, subagent counts and a cleaned message preview
+- Event-driven host snapshot mirroring — **zero polling, zero tokens**
+- Glassmorphism cards, dark / light / system theme, neon status glows and a rotating comet on busy cards
+
+---
+
+## At a glance
+
+| | |
+|---|------|
+| 🧩 Plugin type | Cordis plugin — host routes + web client, pure additive (no official plugin replaced) |
+| 🪟 Workspace engine | Self-built split engine rendered into the host shell overlay seat |
+| 💬 Chat pane | Reuses the host conversation — the plugin only selects sessions (`sessions.open`) |
+| 📡 Status data | Mirror of the host session runtime snapshots (subscription-driven) |
+| 💾 State | localStorage only (`dsh.worktable.*`); no workspace files touched |
+| 🎨 UI | TypeScript + React (host externals) + vanilla CSS, dark-first with light theme |
+
+---
+
+## Quick start
+
+1. **Install**: `dsh plugin --profile web add "link:English · 简体中文
+ +**DeepSeek Harness 的 agent 项目工作台**——侧边栏「应用抽屉」把每个项目变成可停靠的窗口,再加一个实时监控所有项目的内置「控制室」。 + +
+
+
+
+---
+
+## ✨ 功能导览
+
+### 🧩 侧边栏应用抽屉
+
+- 收纳你的自建项目(以及 dsh-travelatlas 等入驻插件项目)
+- 项目支持改名 / 图标 / 排序 / 隐藏;每个项目有专属文件夹;**项目 ↔ 对话绑定**——打开项目时右侧对话窗自动切到其绑定对话
+- 收起侧边栏后,每个项目变成可点击的方形小贴片(只留 emoji)
+
+### 🪟 可停靠的分栏工作区
+
+- 声明式布局预设(左栏 / 顶行 / 主网格 + 右侧对话窗)
+- 可拖拽分割线、窗格标签页、按布局持久化的宽度记忆
+- 内置窗格:**文件资源管理器、终端、浏览器、动画站点、自定义窗口**
+- 自定义窗口:把需求发给新建或已有对话,agent 完成后产物自动挂载进对应窗口(锁死)
+
+### 🖥️ 控制室(内置默认项目)
+
+- 固定在首位的不可删除项目——首次打开绑定一条管理对话即可
+- 三列卡片网格实时镜像**每一个**项目的状态:工作中 / 待你决定 / 已完成,附带运行时长与清洗后的最近消息预览
+- 宿主会话快照的事件订阅镜像——**零轮询、零 Token**
+- 玻璃拟态卡片、深色/白色/跟随系统主题、霓虹状态光效与工作中卡片的旋转彗星光点
+
+---
+
+## 一览
+
+| | |
+|---|------|
+| 🧩 插件类型 | Cordis 插件——服务端路由 + Web 客户端,纯增量(不替换任何官方插件) |
+| 🪟 工作区引擎 | 自研分栏引擎,渲染进宿主的 shell overlay 座位 |
+| 💬 对话窗 | 复用宿主对话——插件只做会话选择(sessions.open) |
+| 📡 状态数据 | 宿主会话运行时快照的镜像(订阅驱动) |
+| 💾 状态存储 | 仅 localStorage(dsh.worktable.*),不碰工作区文件 |
+| 🎨 界面 | TypeScript + React(宿主 external)+ 原生 CSS,暗色优先 + 浅色主题 |
+
+---
+
+## 快速开始
+
+1. **安装**:`dsh plugin --profile web add "link: