dsh-pictor 0.2.0 → 0.2.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/NOTICE.md +18 -0
- package/README.md +10 -1
- package/README.zh-cn.md +10 -1
- package/lib/client.js +67 -6
- package/lib/index.js +46 -0
- package/package.json +6 -2
package/NOTICE.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# NOTICE — 第三方内容声明
|
|
2
|
+
|
|
3
|
+
本仓库包含以下源自第三方的内容,请在分发或再使用时留意相应许可:
|
|
4
|
+
|
|
5
|
+
## references/paper/
|
|
6
|
+
|
|
7
|
+
- `2601.23265v1.md`:arXiv 预印本 2601.23265v1 的正文提取(仅文本,不含 PDF),
|
|
8
|
+
作为研究背景资料随源码仓库分发。
|
|
9
|
+
- `paperbanana-prompts.md`:同一 arXiv 预印本附录 G、H 中提示词原文的忠实提取与中文翻译。
|
|
10
|
+
|
|
11
|
+
两者均**不随发布包分发**(package.json 的 files 仅含 `references/domain`),
|
|
12
|
+
只存在于源码仓库。再分发请以 arXiv 原文及其作者声明的许可为准。
|
|
13
|
+
|
|
14
|
+
## 免责声明
|
|
15
|
+
|
|
16
|
+
除上述文件外,本仓库其余内容(agents/ 蓝图、references/domain/ 模板与规范、
|
|
17
|
+
src/、fixtures/ 示例文档)均为作者原创。样式与布局模板的早期版本
|
|
18
|
+
曾参考 PaperBanana(arXiv:2601.23265v1)的设计思路,但经重写已不包含其原文。
|
package/README.md
CHANGED
|
@@ -20,6 +20,10 @@ Replace `<profile>` with the target profile name (e.g. `desktop`, `web`) and sta
|
|
|
20
20
|
|
|
21
21
|
> Install the Pictor plugin from the npm package `dsh-pictor` into the current profile, restart if needed, and confirm when a Pictor button appears at the bottom left.
|
|
22
22
|
|
|
23
|
+
## Supported dsh version
|
|
24
|
+
|
|
25
|
+
Pictor is built and tested against **DSH Desktop 0.7.2** (bundled Harness **0.1.2-alpha.1**). The DeepSeek Harness client API moves quickly — the workspace/session services it exposes keep changing — so this plugin intentionally tracks one harness generation. Other versions (older or newer) may work or may break without notice, and are outside our support scope. If something stops working after a DSH update, update dsh to the version above rather than debugging the mismatch.
|
|
26
|
+
|
|
23
27
|
## Usage
|
|
24
28
|
|
|
25
29
|
1. Open the workbench via the **Pictor** footer button and click **New project**: upload a file (`md` / `txt` / `docx` / `pdf` / image) or paste content (from Word or the web; formatting is kept, stored as sanitised HTML).
|
|
@@ -47,10 +51,15 @@ Replace `<profile>` with the target profile name (e.g. `desktop`, `web`) and sta
|
|
|
47
51
|
```bash
|
|
48
52
|
npm run build # esbuild build of host + bundled client
|
|
49
53
|
npm run verify # L1 unit + L2 host integration (mock ctx, 15 items)
|
|
50
|
-
npm run verify:integration # L3 transport smoke against a real dsh web
|
|
54
|
+
npm run verify:integration # L3 transport smoke against a real dsh web (hermetic temp home)
|
|
55
|
+
npm run test:pack # package integrity: tarball carries lib/, agents/, references/, assets/
|
|
56
|
+
npm run test:install # L5: README install path (dsh plugin add) → real dsh web boot
|
|
51
57
|
npm run test:e2e # L4a browser e2e (fixture data, deterministic)
|
|
58
|
+
npm run hooks:install # one-time: enable the pre-push hook (git config core.hooksPath .githooks)
|
|
52
59
|
```
|
|
53
60
|
|
|
61
|
+
The pre-push hook runs `npm run verify` + `npm run test:install` before every push; all smoke layers are hermetic (temp `DSH_HOME`, never the live `~/.dsh`).
|
|
62
|
+
|
|
54
63
|
Test-layer definitions and rationale: [docs/DESIGN.zh.md](docs/DESIGN.zh.md) §7.
|
|
55
64
|
|
|
56
65
|
### Layout
|
package/README.zh-cn.md
CHANGED
|
@@ -20,6 +20,10 @@ dsh plugin --profile <profile> add dsh-pictor
|
|
|
20
20
|
|
|
21
21
|
> 帮我把 Pictor 插件装上,从 npm 包 dsh-pictor 装到当前 profile,需要的话重启,装好告诉我左下角有没有 Pictor 入口。
|
|
22
22
|
|
|
23
|
+
## 适配的 DSH 版本
|
|
24
|
+
|
|
25
|
+
Pictor 以 **DSH Desktop 0.7.2**(内置 Harness **0.1.2-alpha.1**)为适配与测试基准。DeepSeek Harness 的客户端 API(工作区/会话服务)演进很快,本月两个版本之间可能就面目全非,所以本插件只跟住一个 harness 世代。其他版本——无论更旧更新——可能能跑、也可能无预告地坏掉,都不在我们的支持范围内。DSH 升级后插件不工作,请先回到上面这个版本,而不是去排查版本差异。
|
|
26
|
+
|
|
23
27
|
## 使用
|
|
24
28
|
|
|
25
29
|
1. 打开工作台,点「新建项目」:上传文件(md/txt/docx/pdf/图片)或粘贴内容(从 Word/网页粘贴,格式保留,落盘为消毒 HTML)。
|
|
@@ -47,10 +51,15 @@ dsh plugin --profile <profile> add dsh-pictor
|
|
|
47
51
|
```bash
|
|
48
52
|
npm run build # esbuild 构建 host + 拼接 client
|
|
49
53
|
npm run verify # L1 单元 + L2 host 集成(mock ctx,15 项)
|
|
50
|
-
npm run verify:integration # L3 transport 冒烟(真实 dsh web)
|
|
54
|
+
npm run verify:integration # L3 transport 冒烟(真实 dsh web,封闭临时 DSH_HOME)
|
|
55
|
+
npm run test:pack # 打包完整性:tarball 必带 lib/、agents/、references/、assets/
|
|
56
|
+
npm run test:install # L5:README 安装路径(dsh plugin add)→ 真实 dsh web 启动
|
|
51
57
|
npm run test:e2e # L4a 浏览器 e2e(fixture 数据,确定性)
|
|
58
|
+
npm run hooks:install # 一次性启用 pre-push 钩子(git config core.hooksPath .githooks)
|
|
52
59
|
```
|
|
53
60
|
|
|
61
|
+
pre-push 钩子在每次 push 前依次跑 `npm run verify` 与 `npm run test:install`;所有冒烟层都是封闭式(临时 `DSH_HOME`,不碰真实 `~/.dsh`)。
|
|
62
|
+
|
|
54
63
|
测试分层的定义与理由见 [docs/DESIGN.zh.md](docs/DESIGN.zh.md) 第 7 节。
|
|
55
64
|
|
|
56
65
|
## 目录
|
package/lib/client.js
CHANGED
|
@@ -487,10 +487,23 @@ function sanitizePaste(html) {
|
|
|
487
487
|
// 会话创建与驱动走 dsh 的 workspace 流程(照 pomasa 的 driveSession):
|
|
488
488
|
// 宿主只出 prompt,客户端经 workspaces.connectWorkspace 建会话(自然归入
|
|
489
489
|
// 「Pictor」工作区、继承 profile 默认模型),再经 sessions.prompt 驱动。
|
|
490
|
+
// dsh ≥ 0.1.2-alpha.1(DSH Desktop 0.7.2)把 connectWorkspace 从
|
|
491
|
+
// `workspaces` 服务移到了 `uiWorkspace` 服务上:这里两个形态都尝试,哪个
|
|
492
|
+
// 在就调哪个,老版本前端不受影响。
|
|
493
|
+
function sessionConnectFn(ctx, ws) {
|
|
494
|
+
let uiWs = null
|
|
495
|
+
try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
|
|
496
|
+
return (uiWs && typeof uiWs.connectWorkspace === 'function')
|
|
497
|
+
? uiWs.connectWorkspace.bind(uiWs)
|
|
498
|
+
: (ws && typeof ws.connectWorkspace === 'function') ? ws.connectWorkspace.bind(ws) : null
|
|
499
|
+
}
|
|
500
|
+
|
|
490
501
|
async function pictorWorkspace(ctx) {
|
|
491
502
|
const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
|
|
492
503
|
const base = await rpc(ctx, 'config.get').then((c) => (c && c.dataRoot) || '')
|
|
493
|
-
|
|
504
|
+
// 这里只用 list/create/delete/rename,新旧 services 都有;connectWorkspace
|
|
505
|
+
// 的可用性由 driveProjectSession 检查。
|
|
506
|
+
if (!ws) throw new Error('会话服务不可用(workspaces)')
|
|
494
507
|
const items = () => {
|
|
495
508
|
try {
|
|
496
509
|
const snap = ws.list && typeof ws.list.getSnapshot === 'function' ? ws.list.getSnapshot() : null
|
|
@@ -531,14 +544,29 @@ async function pictorWorkspace(ctx) {
|
|
|
531
544
|
async function driveProjectSession(ctx, prompt) {
|
|
532
545
|
const ws = ctx.workspaces || (ctx.get && ctx.get('workspaces'))
|
|
533
546
|
const sessionsSvc = ctx.sessions || (ctx.get && ctx.get('sessions'))
|
|
547
|
+
const connect = sessionConnectFn(ctx, ws)
|
|
548
|
+
const canCreate = !!(sessionsSvc && typeof sessionsSvc.create === 'function')
|
|
549
|
+
if (!(connect || canCreate) || !(sessionsSvc && typeof sessionsSvc.binding === 'function')) {
|
|
550
|
+
let uiWs = null
|
|
551
|
+
try { uiWs = ctx.uiWorkspace || ((ctx.get && ctx.get('uiWorkspace')) || null) } catch { /* ignore */ }
|
|
552
|
+
const diag = { ws: !!ws, ses: !!sessionsSvc && typeof sessionsSvc.binding === 'function', uiws: !!uiWs, conn: !!connect, create: canCreate }
|
|
553
|
+
console.warn('[pictor] session services unavailable', diag)
|
|
554
|
+
try { window.__pictor_diag = diag } catch { /* ignore */ }
|
|
555
|
+
try { pictorDiag(ctx, 'drive:fail') } catch { /* ignore */ }
|
|
556
|
+
throw new Error(`会话服务不可用(workspaces) [ws:${diag.ws},ses:${diag.ses},uiws:${diag.uiws},conn:${diag.conn},create:${diag.create}]`)
|
|
557
|
+
}
|
|
534
558
|
const wid = await pictorWorkspace(ctx)
|
|
535
559
|
let sessionId
|
|
536
|
-
try {
|
|
560
|
+
try {
|
|
561
|
+
const created = connect ? await connect(wid) : await sessionsSvc.create({ workspaceId: wid })
|
|
562
|
+
sessionId = created && typeof created === 'object' && created.id ? created.id : created
|
|
563
|
+
}
|
|
537
564
|
catch (e) { throw new Error('创建会话失败:' + String((e && e.message) || e)) }
|
|
538
565
|
const bound = sessionsSvc && typeof sessionsSvc.binding === 'function' ? sessionsSvc.binding(sessionId) : null
|
|
539
566
|
const sess = bound && bound.session
|
|
540
567
|
if (!sess || typeof sess.prompt !== 'function') throw new Error('会话无 prompt 通道')
|
|
541
568
|
await sess.prompt([{ type: 'text', text: String(prompt || '') }], 'queue')
|
|
569
|
+
try { pictorDiag(ctx, 'drive:ok') } catch { /* ignore */ }
|
|
542
570
|
return sessionId
|
|
543
571
|
}
|
|
544
572
|
|
|
@@ -945,7 +973,17 @@ function fileSlug(s) {
|
|
|
945
973
|
return String(s || '').toLowerCase().trim().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40) || 'infographic'
|
|
946
974
|
}
|
|
947
975
|
|
|
948
|
-
|
|
976
|
+
// 结果图直出走同源 HTTP(data URL 在新版 DSH Desktop WebView 可能被拦),
|
|
977
|
+
// 失败时回退到 RPC 返回的 data URL。
|
|
978
|
+
const imgUrlFor = (projectId, name) => {
|
|
979
|
+
const base = '/pictor/asset/project/' + encodeURIComponent(projectId) + '/' + encodeURIComponent(name)
|
|
980
|
+
try {
|
|
981
|
+
const tok = new URLSearchParams(window.location.search || '').get('token')
|
|
982
|
+
return tok ? base + '?token=' + encodeURIComponent(tok) : base
|
|
983
|
+
} catch { return base }
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function ResultsPanel({ projectId, outputs, images, renderErrors, proposals, lay, renderBatches }) {
|
|
949
987
|
const [big, setBig] = React.useState(null)
|
|
950
988
|
const [collapsed, setCollapsed] = React.useState({})
|
|
951
989
|
if (!outputs.length && !renderErrors.length) {
|
|
@@ -967,7 +1005,7 @@ function ResultsPanel({ outputs, images, renderErrors, proposals, lay, renderBat
|
|
|
967
1005
|
h('div', null, h('h3', { className: 'pt-option-title', style: { margin: 0 } }, bigInfo.title)),
|
|
968
1006
|
h('button', { className: 'pt-modal-close', onClick: () => setBig(null), title: t('close'), 'aria-label': t('close') }, '×')),
|
|
969
1007
|
h('div', { className: 'pt-modal-body', style: { textAlign: 'center' } },
|
|
970
|
-
h('img', { src:
|
|
1008
|
+
h('img', { src: imgUrlFor(projectId, big), alt: bigInfo.title, style: { maxWidth: '100%', maxHeight: '70vh', borderRadius: 10 }, onError: (e) => { if (e && e.currentTarget) e.currentTarget.src = images[big] } }),
|
|
971
1009
|
bigInfo.actualLayout ? h('p', { className: 'pt-meta', style: { marginTop: 10 } }, t('layoutLabel') + ': ' + layoutLabel(bigInfo.actualLayout)) : null)))
|
|
972
1010
|
: null
|
|
973
1011
|
|
|
@@ -991,7 +1029,7 @@ function ResultsPanel({ outputs, images, renderErrors, proposals, lay, renderBat
|
|
|
991
1029
|
const dlName = info.prop ? (fileSlug(info.prop.title || layoutLabel(info.actualLayout || info.prop.suggested_layout)) + '-' + info.pid + '.png') : name
|
|
992
1030
|
return h('figure', { key: name, className: 'pt-img-card' },
|
|
993
1031
|
images[name]
|
|
994
|
-
? h('img', { className: 'pt-img', src:
|
|
1032
|
+
? h('img', { className: 'pt-img', src: imgUrlFor(projectId, name), alt: info.title, style: { cursor: 'zoom-in' }, onClick: () => setBig(name), onError: (e) => { if (e && e.currentTarget) e.currentTarget.src = images[name] } })
|
|
995
1033
|
: h('div', { className: 'thumb', style: { width: '100%', height: 140 } }),
|
|
996
1034
|
h('figcaption', null,
|
|
997
1035
|
h('div', { className: 'pt-fig-title' }, info.title),
|
|
@@ -1405,7 +1443,7 @@ function DetailPane(props) {
|
|
|
1405
1443
|
ratios, setRatio, onRender: renderSelected, renderBusy,
|
|
1406
1444
|
hasImageKey: Boolean(imageCfg && imageCfg.hasKey), running: Boolean(data.running),
|
|
1407
1445
|
renderState: data.renderState, renderRunning: Boolean(data.renderRunning) }),
|
|
1408
|
-
render: h(ResultsPanel, { outputs: data.outputs, images, renderErrors, proposals, lay: layOv, renderBatches: data.renderBatches }),
|
|
1446
|
+
render: h(ResultsPanel, { projectId, outputs: data.outputs, images, renderErrors, proposals, lay: layOv, renderBatches: data.renderBatches }),
|
|
1409
1447
|
}[step]
|
|
1410
1448
|
|
|
1411
1449
|
return h('div', null,
|
|
@@ -1670,7 +1708,30 @@ function selectedLabel(ids) {
|
|
|
1670
1708
|
return ids.join('、')
|
|
1671
1709
|
}
|
|
1672
1710
|
|
|
1711
|
+
// 最佳努力诊断:上报此 ctx 上工作区/会话服务的暴露情况(DSH Desktop 0.7.2
|
|
1712
|
+
// 调整了客户端 API),host 落盘 ~/.pictor/diag.jsonl,无需 DevTools 即可排查。
|
|
1713
|
+
function pictorDiag(ctx, phase) {
|
|
1714
|
+
let ws = null, ses = null, ui = null
|
|
1715
|
+
try { ws = ctx && (ctx.workspaces || (ctx.get && ctx.get('workspaces'))) } catch { /* ignore */ }
|
|
1716
|
+
try { ses = ctx && (ctx.sessions || (ctx.get && ctx.get('sessions'))) } catch { /* ignore */ }
|
|
1717
|
+
try { ui = ctx && (ctx.uiWorkspace || (ctx.get && ctx.get('uiWorkspace'))) } catch { /* ignore */ }
|
|
1718
|
+
const body = {
|
|
1719
|
+
phase,
|
|
1720
|
+
ws: !!ws, ses: !!ses, ui: !!ui,
|
|
1721
|
+
wsMethods: ws ? Object.keys(ws).slice(0, 40) : null,
|
|
1722
|
+
sesMethods: ses ? Object.keys(ses).slice(0, 40) : null,
|
|
1723
|
+
uiMethods: ui ? Object.keys(ui).slice(0, 40) : null,
|
|
1724
|
+
connectWs: !!(ws && typeof ws.connectWorkspace === 'function'),
|
|
1725
|
+
connectUi: !!(ui && typeof ui.connectWorkspace === 'function'),
|
|
1726
|
+
bind: !!(ses && typeof ses.binding === 'function'),
|
|
1727
|
+
href: typeof location !== 'undefined' ? location.href.slice(0, 80) : '',
|
|
1728
|
+
}
|
|
1729
|
+
try { fetch('/pictor/diag', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }).catch(() => {}) } catch { /* ignore */ }
|
|
1730
|
+
console.warn('[pictor] diag', body)
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1673
1733
|
function apply(ctx) {
|
|
1734
|
+
try { pictorDiag(ctx, 'apply') } catch { /* ignore */ }
|
|
1674
1735
|
console.info('[dsh-pictor] apply 被调用', { hasSlots: Boolean(ctx && ctx.slots) })
|
|
1675
1736
|
if (typeof document !== 'undefined') {
|
|
1676
1737
|
const styleId = 'dsh-pictor-style'
|
package/lib/index.js
CHANGED
|
@@ -702,6 +702,52 @@ function apply(ctx, config = {}) {
|
|
|
702
702
|
} });
|
|
703
703
|
}
|
|
704
704
|
}
|
|
705
|
+
const PREFIX = "/pictor/asset/project";
|
|
706
|
+
wsA.register({ kind: "prefix", path: PREFIX, handler: (req, res) => {
|
|
707
|
+
try {
|
|
708
|
+
const pathname = new URL(req.url || "/", "http://x").pathname;
|
|
709
|
+
const rest = pathname.slice(PREFIX.length + 1);
|
|
710
|
+
const slash = rest.indexOf("/");
|
|
711
|
+
if (slash <= 0) {
|
|
712
|
+
res.writeHead(404);
|
|
713
|
+
res.end("not found");
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const id = rest.slice(0, slash);
|
|
717
|
+
const file = rest.slice(slash + 1);
|
|
718
|
+
if (id.includes("..") || /[\\/]/.test(id) || !/^[0-9a-zA-Z_.-]+$/.test(file) || !file.endsWith(".png")) {
|
|
719
|
+
res.writeHead(400);
|
|
720
|
+
res.end("bad request");
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const buf = readFileSync(join(projectDir(id), "output", file));
|
|
724
|
+
res.writeHead(200, { "content-type": "image/png", "cache-control": "public, max-age=300" });
|
|
725
|
+
res.end(buf);
|
|
726
|
+
} catch {
|
|
727
|
+
res.writeHead(404);
|
|
728
|
+
res.end("not found");
|
|
729
|
+
}
|
|
730
|
+
} });
|
|
731
|
+
wsA.register({ kind: "exact", path: "/pictor/diag", handler: (req, res) => {
|
|
732
|
+
let body = "";
|
|
733
|
+
try {
|
|
734
|
+
req.on("data", (c) => {
|
|
735
|
+
body += c;
|
|
736
|
+
});
|
|
737
|
+
req.on("end", () => {
|
|
738
|
+
try {
|
|
739
|
+
mkdirSync(base, { recursive: true });
|
|
740
|
+
appendFileSync(join(base, "diag.jsonl"), JSON.stringify({ t: Date.now(), ...JSON.parse(body || "{}") }) + "\n");
|
|
741
|
+
} catch {
|
|
742
|
+
}
|
|
743
|
+
res.writeHead(204);
|
|
744
|
+
res.end();
|
|
745
|
+
});
|
|
746
|
+
} catch {
|
|
747
|
+
res.writeHead(204);
|
|
748
|
+
res.end();
|
|
749
|
+
}
|
|
750
|
+
} });
|
|
705
751
|
}
|
|
706
752
|
return { dataRoot: base };
|
|
707
753
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-pictor",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Pictor:文档转信息图工作台。每项目一个连贯 dsh 会话,GUI 为 shell.overlay 工作台(左栏项目、右栏三步:提取/方案/渲染),行为全部交给 DSH 智能运行时。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -46,12 +46,16 @@
|
|
|
46
46
|
"docs",
|
|
47
47
|
"README.md",
|
|
48
48
|
"LICENSE",
|
|
49
|
-
"assets"
|
|
49
|
+
"assets",
|
|
50
|
+
"NOTICE.md"
|
|
50
51
|
],
|
|
51
52
|
"scripts": {
|
|
52
53
|
"build": "node scripts/build.mjs",
|
|
53
54
|
"verify": "node scripts/build.mjs && node verify.mjs",
|
|
54
55
|
"verify:integration": "bash scripts/transport-smoke.sh",
|
|
56
|
+
"test:pack": "bash scripts/package-integrity.sh",
|
|
57
|
+
"test:install": "bash scripts/install-smoke.sh",
|
|
58
|
+
"hooks:install": "git config core.hooksPath .githooks",
|
|
55
59
|
"test:e2e": "playwright test --config e2e/playwright.config.ts --project chromium",
|
|
56
60
|
"verify:live": "playwright test --config e2e/playwright.config.ts --project live",
|
|
57
61
|
"prepare": "node scripts/build.mjs"
|