dsh-drawai 0.2.0 → 0.2.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/README.md CHANGED
@@ -6,23 +6,24 @@
6
6
 
7
7
  [![GitHub stars](https://img.shields.io/github/stars/fourzkw/dsh-drawai?style=social)](https://github.com/fourzkw/dsh-drawai) [![MIT License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
8
8
 
9
- **DSH 右侧栏里的可编辑画布 —— 加上让模型直接改图的两个工具。**
9
+ ## 功能一览
10
10
 
11
- > *An editable diagram canvas in the DSH right sidebar, plus two agent tools (`diagram_read` / `diagram_apply`) that read and edit the workspace's native `.drawio` files in place.*
11
+ `dsh-drawai` 把可编辑画布放进 DeepSeek Harness 右侧栏,并给模型两个改图工具;人和 AI 读写的是同一份原生 `.drawio`。
12
12
 
13
- `dsh-drawai` 把一块**可编辑的画布**放进 DeepSeek Harness 的右侧栏,再给模型**两个工具**;两者读写的是同一份文件。
13
+ 1. **右侧栏可编辑画布** —— 打开 / 新建工作区 `.drawio`:画节点、连边、就地改字、分层、排序、对齐、导出;双击即可改标签。
14
+ 2. **自然语言改图** —— 对话里说需求,模型用 `diagram_read` / `diagram_apply` 读现状、结构化改图、自动布局并原子写回。
15
+ 3. **选区感知与回退** —— 你在画布上选中什么,模型就知道什么;模型改动最多可退 8 步。
16
+ 4. **SVG 图形节点** —— 图标 / logo 可直接当节点内容,拼流程图与示意图。
17
+ 5. **位图矢量化(`traceImage`)** —— 照着工作区图片逐像素描成 SVG 节点,不是凭印象手写近似图。
18
+ 6. **原生 `.drawio` 载体** —— 无需导入导出;画布读不懂的单元逐字节保留,文件可直接用 drawio / diagrams.net 继续编辑。
14
19
 
15
- - **画布(右侧栏面板)** —— 打开工作区里的 `.drawio`:画节点、连边、就地改字、分层、排序、对齐、导出,双击节点就能改标签。
16
- - **`diagram_apply`** —— 模型改图的入口:15 个结构化 ops,改完自动布局并**原子写回**。
17
- - **`diagram_read`** —— 模型的眼睛:结构、样式键、图层、父级、页面尺寸、**你此刻的选区**、文件指纹 `revision`。
18
- - **选中与回退** —— 你在画布上选中什么,模型就知道什么;模型的改动最多可以退 8 步。
19
- - **载体是原生 `.drawio` 文件** —— 不需要导入导出,存出来的文件可以直接用 drawio / diagrams.net 打开继续编辑。
20
+ ![整体界面:右侧栏画布与多标签](docs/picture/整体界面图.png)
20
21
 
21
- ```
22
- 人(侧边栏画布) ┐
23
- AI(diagram_apply) ├──→ 工作区的 .drawio(mxfile) ──→ 用 drawio / diagrams.net 直接打开继续编辑
24
- drawio 本体 ┘ ↑ 无损写回:画布读不懂的单元逐字节保留
25
- ```
22
+ ![从对话到改图的逻辑流程](docs/picture/drawai逻辑图.png)
23
+
24
+ ![用 SVG 节点拼的流程图与图标](docs/picture/图标绘制.png)
25
+
26
+ ![位图矢量化复刻对比](docs/picture/大肥鱼复刻图.png)
26
27
 
27
28
  ## 安装 / Install
28
29
 
@@ -199,7 +200,7 @@ id / layer / parent、选区能上报给模型、写回是逐单元的、画布
199
200
 
200
201
  ## 须知 / Good to know
201
202
 
202
- - **兼容性**:DSH(DeepSeek Harness)Web;官方包以 `peerDependencies` 声明(`@deepseek-ai/dsh-tools`),Node `>= 20`。
203
+ - **兼容性**:DSH(DeepSeek Harness)Web `0.1.x` 与 `0.2.x`(含 `0.2.0-rc.*`);官方包以 `peerDependencies` 声明(`@deepseek-ai/dsh-tools`),Node `>= 20`。
203
204
  - **生效方式**:宿主侧只在 `dsh web` 启动时加载,换 `lib/index.js` 必须**重启**;客户端是独立 bundle,刷新页面即可。
204
205
  - **界面语言**:目前只有简体中文,还没有 i18n。
205
206
  - **大图性能**:几百个单元时的重渲染与路由开销还没测过。
package/lib/client.js CHANGED
@@ -1135,6 +1135,38 @@ window.__ModuleLoader__.load({
1135
1135
  }
1136
1136
 
1137
1137
  const POLL_MS = 3000
1138
+ /**
1139
+ * 打开 workspaceFiles 变更流,兼容 DSH 0.1 与 0.2 的不同签名:
1140
+ * 0.1:changes(sessionId, signal) —— 会话级观测写入
1141
+ * 0.2:changes(sessionId, path, signal) —— 按路径订阅
1142
+ *
1143
+ * `changes.length < 3` 时走 0.1;否则(含 length 为 0 / 不可靠的 Remote 代理)在
1144
+ * 有 path 时先试三参,迭代启动失败再退回两参。外层仍有轮询兜底。
1145
+ */
1146
+ async function* openWorkspaceFileChanges(workspaceFiles, sessionId, path, signal) {
1147
+ const changes = workspaceFiles.changes
1148
+ const watchPath = typeof path === 'string' ? path : ''
1149
+ const arity = typeof changes.length === 'number' ? changes.length : 0
1150
+ if (arity > 0 && arity < 3) {
1151
+ yield* changes.call(workspaceFiles, sessionId, signal)
1152
+ return
1153
+ }
1154
+ if (watchPath.length > 0) {
1155
+ try {
1156
+ const stream = changes.call(workspaceFiles, sessionId, watchPath, signal)
1157
+ const iterator = stream[Symbol.asyncIterator]()
1158
+ let first = await iterator.next()
1159
+ while (true) {
1160
+ if (first.done) return
1161
+ yield first.value
1162
+ first = await iterator.next()
1163
+ }
1164
+ } catch {
1165
+ // 三参被拒(老宿主 / 代理 length 说谎)→ 退回 0.1 两参
1166
+ }
1167
+ }
1168
+ yield* changes.call(workspaceFiles, sessionId, signal)
1169
+ }
1138
1170
  /**
1139
1171
  * 选区上报的防抖时间(ms)。
1140
1172
  *
@@ -9357,10 +9389,11 @@ window.__ModuleLoader__.load({
9357
9389
 
9358
9390
  // 主通道:宿主变更流(只报告经 ctx.fs 的"被观测"写入)。
9359
9391
  // 失败不致命 —— 下面的兜底轮询继续工作。
9392
+ // 0.2 要按路径订阅(传当前标签的 target);0.1 仍是会话级两参,见 openWorkspaceFileChanges。
9360
9393
  if (client.remote !== undefined && client.remote.workspaceFiles !== undefined && typeof client.remote.workspaceFiles.changes === 'function') {
9361
9394
  ;(async () => {
9362
9395
  try {
9363
- for await (const rawFrame of client.remote.workspaceFiles.changes(sessionId, signal)) {
9396
+ for await (const rawFrame of openWorkspaceFileChanges(client.remote.workspaceFiles, sessionId, target, signal)) {
9364
9397
  if (!alive) break
9365
9398
  // 变更流的帧同样可能被信封包着,防御性拆一次。
9366
9399
  let frame = rawFrame
package/lib/index.js CHANGED
@@ -809,6 +809,9 @@ async function applyOps(doc, ops, out, opctx) {
809
809
  if (typeof op.label === 'string') synthetic.label = op.label
810
810
  if (has(op, 'x')) synthetic.x = op.x
811
811
  if (has(op, 'y')) synthetic.y = op.y
812
+ // 就地插一条 addNode 紧随其后(读像素是 async 的,所以这条 op 不能在解析参数时先展开)。
813
+ // 注意:这里改的必须是**我们自己那份** ops —— 调用方(execute)已经 slice 过,
814
+ // 因为宿主给的 arguments 是深冻结的,直接 splice 会抛 "object is not extensible"。
812
815
  ops.splice(i + 1, 0, synthetic)
813
816
  notes.push(
814
817
  '🖼 ' + rawImage + ' → 逐像素矢量化:' + metrics.palette.length + ' 色 / ' + metrics.paths + ' 条 path / ' +
@@ -2900,7 +2903,13 @@ export function apply(ctx) {
2900
2903
  const focused = focusedPathFor(sessionIdOf(exec))
2901
2904
  const path =
2902
2905
  typeof args.path === 'string' && args.path.length > 0 ? args.path : focused !== undefined && focused.length > 0 ? focused : DEFAULT_PATH
2903
- const ops = Array.isArray(args.ops) ? args.ops : []
2906
+ // **入参是宿主深冻结的**:dsh-tools 在 execute 之前把整份 arguments 深冻结
2907
+ // (`arguments: deepFreeze(snapshotJsonValue(exec.arguments))`,见 dsh-tools 的 tool 执行器),
2908
+ // 数组和里面的 op 对象都是 non-extensible。所以这里必须**复制成一份可扩展的数组**再用:
2909
+ // traceImage 会往 ops 里插一条合成的 addNode(见下面 applyOps 里的 `ops.splice`),
2910
+ // 就地改冻结数组会抛 `Cannot add property N, object is not extensible`。
2911
+ // op 落盘语义不受影响 —— 复制出来的数组里元素仍是原来那些(本文件只读它们,从不写回)。
2912
+ const ops = Array.isArray(args.ops) ? args.ops.slice() : []
2904
2913
  if (ops.length === 0) throw new Error('ops must contain at least one operation')
2905
2914
 
2906
2915
  const sessionId = sessionIdOf(exec)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-drawai",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "DSH 右侧栏的可编辑画布 + AI 语义绘图工具(diagram_apply / diagram_read),直接读写原生 .drawio 文件",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "node": ">=20"
40
40
  },
41
41
  "peerDependencies": {
42
- "@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.1.0 || >=0.1.5-rc.1 <0.2.0-0"
42
+ "@deepseek-ai/dsh-tools": ">=0.1.0-rc.1 <0.1.0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.2.0-rc.1 <0.3.0-0"
43
43
  },
44
44
  "dsh": {
45
45
  "bundle": {