dsh-codex-subscription 2.1.0-beta.5 → 2.1.0

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.en.md CHANGED
@@ -180,11 +180,11 @@ The screenshot above illustrates image viewing and on-image notes; available but
180
180
 
181
181
  ![Sketch canvas in the Chinese UI: aspect ratio, brushes, shapes, layers and zoom](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-canvas.png)
182
182
 
183
- Use the composer pen button for manual drawing. In Beta.3, selecting `@sketch` only inserts the Agent entry into the composer; the Agent opens the board after you send your drawing request. You can also choose Open in sketch from an enhanced image preview. Attachment intake and removal use the native DSH component.
183
+ Use the composer pen button for manual drawing. Selecting `@sketch` only inserts the Agent entry into the composer; the Agent opens the board after you send your drawing request. You can also choose Open in sketch from an enhanced image preview. Attachment intake and removal use the native DSH component.
184
184
 
185
- Sketch supports local drafts, image layers, aspect ratios, brushes, lines and shapes, two erasers, undo/redo, pan/zoom and configurable shortcuts. Smoothing processes a completed stroke only after release. Up to 20 drafts stay in the current browser; attaching a sketch never sends it automatically. Its image panel manages only the current sketch, not the conversation library.
185
+ Sketch supports local drafts, image layers, aspect ratios, three brushes (solid ink, grainy pencil and translucent highlighter), lines and shapes, two erasers, undo/redo, pan/zoom and configurable shortcuts. Smoothing processes a completed stroke only after release. Up to 20 drafts stay in the current browser; attaching a sketch never sends it automatically. Its image panel manages only the current sketch, not the conversation library.
186
186
 
187
- Beta.3 adds editable shapes and text, native curves, and a side control for size/opacity. During Agent drawing, you can view, zoom, close the panel or stop drawing; manual edits unlock when it finishes. Automatic completion previews are off by default and can be enabled in Advanced settings. The board requires the current session page; drawing does not continue after closing the page or switching sessions.
187
+ The board supports editable shapes and text, native curves, and a side control for size/opacity. During Agent drawing, you can view, zoom, close the panel or stop drawing; manual edits unlock when it finishes. Automatic completion previews are off by default and can be enabled in Advanced settings. The document, history and run state are retained when switching away and back. Background drawing while viewing another session is not guaranteed. Save before a full-page reload or exit; unsaved recovery is not guaranteed.
188
188
 
189
189
  **Sketch-to-image example**: draw, click Attach, describe the desired result in the composer, then send.
190
190
 
@@ -195,7 +195,9 @@ Beta.3 adds editable shapes and text, native curves, and a side control for size
195
195
  The request preserves the mountain and cabin composition while creating a warm watercolor travel illustration with green peaks, an orange roof, a meadow stream and morning light, without the blue outlines. GPT-5.6-Luna made one image-tool call requesting low quality. Luna is the conversation model; the subscription backend determines the actual image model.
196
196
 
197
197
  <details>
198
- <summary>Advanced example · 2.1.0-beta.2</summary>
198
+ <summary>Advanced examples</summary>
199
+
200
+ **Someone Behind the Canvas**
199
201
 
200
202
  **Astra draws the sketch; GPT Image 2 generates the illustration.** Astra draws 427 strokes across six layers through the native `codex_sketch` interface; Luna then calls the subscription image tool. The request uses `gpt-image-2` at low quality; the server does not report the executing model. This Beta adds PNG export, layered PSD import/export and editable draft files. PSD retains pixel layers; native drafts retain strokes. Sketch canvas and Agent drawing are separate Beta options, both off by default in Advanced settings. Drawing tools are exposed only when Agent drawing is enabled; the agent then automatically opens the current session’s board.
201
203
 
@@ -203,7 +205,35 @@ The request preserves the mountain and cabin composition while creating a warm w
203
205
  | --- | --- |
204
206
  | ![Sketch](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-advanced-source.png) | ![Result](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-advanced-result.png) |
205
207
 
206
- [Beta v2.1.0-beta.2](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.2)
208
+ **Sketch reproduction prompt (reconstructed from the artwork, not the original conversation)**
209
+
210
+ Astra drew the original in stages. This Chinese prompt provides a starting point for the same concept, not a guarantee of an identical result.
211
+
212
+ ```text
213
+ @sketch 用 4:3 横版画板绘制《画布背面有人》:中央偏上是一处撕开的纸洞,洞内是深蓝星空和一位拿颜料桶的小画师;蓝色颜料从桶中流出,形成 S 形河流,流向下方城市。左侧城市保持未上色线稿,右侧城市被暖色点亮,加入纸船与飞鸟。按纸面、洞内世界、颜料河流、城市、画师和细节分层绘制,保留原生可编辑笔画。
214
+ ```
215
+
216
+ **Actual image-generation prompt (original Chinese)**
217
+
218
+ ```text
219
+ 请基于本条附加草图实际调用订阅图片工具一次,生成成品插画。quality=low,模型使用当前默认,不切换型号,不额外生成。主题《画布背面有人》:保留4:3横构图、中央偏上的撕纸洞口、洞内拿颜料桶的小画师、流出成为S形河流的蓝色颜料、下方左侧未上色城市与右侧被点亮城市、纸船飞鸟。精修为惊艳的立体纸艺与精细手绘结合的编辑插画,纸张纤维、真实撕边及柔和投影,深靛蓝洞内星月,丰富青蓝颜料层次和流动质感,赭橙画师与暖色建筑,微小清晰的叙事细节。不重构为风景,不添加文字水印。必须使用本条参考图片编辑,不能仅凭文字生成。生成后简短说明完成即可。
220
+ ```
221
+
222
+ Original example released in: [Beta v2.1.0-beta.2](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.2)
223
+
224
+ **Mona Lisa: Astra sketch → GPT image generation**
225
+
226
+ Example version: [2.1.0-beta.5](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.5)
227
+
228
+ Actual results supplied by the user from another computer: Astra draws on a portrait canvas, then GPT image generation turns the sketch into an oil painting.
229
+
230
+ | Native Astra sketch | GPT-generated oil painting |
231
+ | --- | --- |
232
+ | ![Mona Lisa sketch drawn by Astra](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-mona-lisa-source.png) | ![Mona Lisa oil painting generated from the sketch](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-mona-lisa-result.png) |
233
+
234
+ Original sketch prompt: `@sketch 用竖版画板画一幅《蒙娜丽莎》` (Draw the Mona Lisa on a portrait canvas.)
235
+
236
+ Original image prompt: `帮我变成油画` (Turn it into an oil painting.)
207
237
 
208
238
  </details>
209
239
 
package/README.md CHANGED
@@ -179,11 +179,11 @@ ChatGPT 返回可用重置卡时,设置页会把每张卡分别显示为紧凑
179
179
 
180
180
  ![草图画板实机界面:画布比例、笔刷、形状、图层与缩放](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-canvas.png)
181
181
 
182
- 点击输入框的草图按钮手动画图。Beta.3 中,`@sketch` 是 Agent 绘图入口:选择时只填入输入框,发送绘图请求后才由 Agent 打开画板。图片预览中可选择“进入草图”;上传、粘贴和移除附件仍由 DSH 原生组件处理。
182
+ 点击输入框的草图按钮手动画图。`@sketch` 是 Agent 绘图入口:选择时只填入输入框,发送绘图请求后才由 Agent 打开画板。图片预览中可选择“进入草图”;上传、粘贴和移除附件仍由 DSH 原生组件处理。
183
183
 
184
- 草图支持多份本地草稿、图片图层、画布比例、三种笔刷、直线和形状、两种橡皮、撤销重做、拖动缩放与可配置快捷键。平滑仅在抬笔后处理整笔路径,不拖慢绘画光标。草稿最多 20 份,仅保存在当前浏览器;附加草图不会自动发送。左侧图片面板只管理当前草图的图片,不是会话图片库。
184
+ 草图支持多份本地草稿、图片图层、画布比例、三种笔刷(实色钢笔、颗粒铅笔、半透明荧光笔)、直线和形状、两种橡皮、撤销重做、拖动缩放与可配置快捷键。平滑仅在抬笔后处理整笔路径,不拖慢绘画光标。草稿最多 20 份,仅保存在当前浏览器;附加草图不会自动发送。左侧图片面板只管理当前草图的图片,不是会话图片库。
185
185
 
186
- Beta.3 增加可选中修改的形状与文字、原生曲线,以及侧边粗细/浓度控件。Agent 绘制时可查看、缩放和关闭面板,编辑暂时锁定;顶部可停止绘制,完成后恢复编辑。自动完成预览默认关闭,可在高级设置开启。画板依赖当前会话页面,关闭页面或切换会话不支持继续后台绘制。
186
+ 支持可选中修改的形状与文字、原生曲线,以及侧边粗细/浓度控件。Agent 绘制时可查看、缩放和关闭面板,编辑暂时锁定;顶部可停止绘制,完成后恢复编辑。自动完成预览默认关闭,可在高级设置开启。切换会话后再返回会保留画板、历史和运行状态。切到其他会话期间不保证后台绘制;整页刷新或退出前请保存草稿,未保存内容不保证恢复。
187
187
 
188
188
  **草图生成图片实测**:画好后点击“附加”,在输入框说明想要的效果,再发送。
189
189
 
@@ -194,7 +194,9 @@ Beta.3 增加可选中修改的形状与文字、原生曲线,以及侧边粗
194
194
  示例要求:保留山峰与小屋的构图,生成温暖的水彩旅行插画,青绿山峰、橙色屋顶、草地小溪与柔和晨光,不保留蓝色线条。此例通过 GPT-5.6-Luna 发起一次图片工具调用,请求低质量;Luna 是对话模型,实际出图型号由订阅后端决定。
195
195
 
196
196
  <details>
197
- <summary>进阶展示 · 2.1.0-beta.2</summary>
197
+ <summary>进阶展示</summary>
198
+
199
+ **《画布背面有人》**
198
200
 
199
201
  **Astra 绘制草图,GPT Image 2 生成成图。** Astra 通过原生 `codex_sketch` 接口完成 6 层、427 笔绘制,再由 Luna 调用订阅生图工具精修。生图请求使用 `gpt-image-2`、低质量档;服务端未报告实际执行型号。Beta 新增 PNG 导出、分层 PSD 导入导出和可编辑草稿文件;PSD 保留像素图层,原生草稿保留笔画。草图画板与 Agent 绘图均默认关闭,可在高级设置中分别开启。仅开启 Agent 绘图后才向模型提供绘图工具。
200
202
 
@@ -202,7 +204,35 @@ Beta.3 增加可选中修改的形状与文字、原生曲线,以及侧边粗
202
204
  | --- | --- |
203
205
  | ![Sketch](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-advanced-source.png) | ![Result](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-advanced-result.png) |
204
206
 
205
- [Beta v2.1.0-beta.2](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.2)
207
+ **草图复现提示词(按原画面整理,非完整原始对话)**
208
+
209
+ 原案例由 Astra 分阶段绘制,下面提供同主题的复现起点,不保证得到完全相同的画面。
210
+
211
+ ```text
212
+ @sketch 用 4:3 横版画板绘制《画布背面有人》:中央偏上是一处撕开的纸洞,洞内是深蓝星空和一位拿颜料桶的小画师;蓝色颜料从桶中流出,形成 S 形河流,流向下方城市。左侧城市保持未上色线稿,右侧城市被暖色点亮,加入纸船与飞鸟。按纸面、洞内世界、颜料河流、城市、画师和细节分层绘制,保留原生可编辑笔画。
213
+ ```
214
+
215
+ **实际生图提示词**
216
+
217
+ ```text
218
+ 请基于本条附加草图实际调用订阅图片工具一次,生成成品插画。quality=low,模型使用当前默认,不切换型号,不额外生成。主题《画布背面有人》:保留4:3横构图、中央偏上的撕纸洞口、洞内拿颜料桶的小画师、流出成为S形河流的蓝色颜料、下方左侧未上色城市与右侧被点亮城市、纸船飞鸟。精修为惊艳的立体纸艺与精细手绘结合的编辑插画,纸张纤维、真实撕边及柔和投影,深靛蓝洞内星月,丰富青蓝颜料层次和流动质感,赭橙画师与暖色建筑,微小清晰的叙事细节。不重构为风景,不添加文字水印。必须使用本条参考图片编辑,不能仅凭文字生成。生成后简短说明完成即可。
219
+ ```
220
+
221
+ 原案例首发:[Beta v2.1.0-beta.2](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.2)
222
+
223
+ **《蒙娜丽莎》:Astra 草图 → GPT 生图**
224
+
225
+ 案例版本:[2.1.0-beta.5](https://github.com/WSL043/dsh-codex-subscription/releases/tag/v2.1.0-beta.5)
226
+
227
+ 用户在另一台电脑上的实际效果:先让 Astra 在竖版画板上绘制,再通过 GPT 生图转成油画。
228
+
229
+ | Astra 原生草图 | GPT 生图:油画效果 |
230
+ | --- | --- |
231
+ | ![Astra 绘制的蒙娜丽莎草图](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-mona-lisa-source.png) | ![草图转换后的蒙娜丽莎油画](https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/sketch-mona-lisa-result.png) |
232
+
233
+ 草图提示词:`@sketch 用竖版画板画一幅《蒙娜丽莎》`
234
+
235
+ 生图提示词:`帮我变成油画`
206
236
 
207
237
  </details>
208
238
 
package/lib/client.js CHANGED
@@ -71,6 +71,40 @@ window.__ModuleLoader__.load({
71
71
  return normalized;
72
72
  }
73
73
  //#endregion
74
+ //#region src/sketch-brushes.js
75
+ const grains = /* @__PURE__ */ new Map();
76
+ function pencilGrain(context, color) {
77
+ if (!context.createPattern || typeof document === "undefined") return color;
78
+ if (!grains.has(color)) {
79
+ const canvas = document.createElement("canvas");
80
+ canvas.width = 64;
81
+ canvas.height = 64;
82
+ const ctx = canvas.getContext("2d");
83
+ ctx.fillStyle = color;
84
+ let seed = 173;
85
+ for (let y = 0; y < 64; y++) for (let x = 0; x < 64; x++) {
86
+ seed = Math.imul(seed, 1664525) + 1013904223 >>> 0;
87
+ const value = seed / 4294967296;
88
+ if (value < .28) continue;
89
+ ctx.globalAlpha = .2 + value * .8;
90
+ ctx.fillRect(x, y, 1, 1);
91
+ }
92
+ if (grains.size >= 16) grains.delete(grains.keys().next().value);
93
+ grains.set(color, canvas);
94
+ }
95
+ return context.createPattern(grains.get(color), "repeat") ?? color;
96
+ }
97
+ function configureSketchBrush(context, stroke) {
98
+ const modern = stroke.brushVersion === 2 && stroke.shape === "pen";
99
+ const brush = stroke.brush ?? "pen";
100
+ context.lineCap = modern && brush === "marker" ? "butt" : "round";
101
+ context.lineJoin = "round";
102
+ context.globalAlpha = (stroke.opacity ?? 1) * (brush === "marker" ? .28 : brush === "pencil" ? modern ? .85 : .65 : 1);
103
+ context.lineWidth = stroke.width * (brush === "pencil" && !modern ? .55 : 1) * (stroke.pressure ?? 1);
104
+ context.strokeStyle = modern && brush === "pencil" ? pencilGrain(context, stroke.color) : stroke.color;
105
+ context.fillStyle = context.strokeStyle;
106
+ }
107
+ //#endregion
74
108
  //#region src/sketch-document.js
75
109
  const SKETCH_SIZE = 1024;
76
110
  const MAX_SKETCH_STROKES = 2e3;
@@ -96,10 +130,7 @@ window.__ModuleLoader__.load({
96
130
  const first = stroke.points[0];
97
131
  if (!first) continue;
98
132
  context.globalCompositeOperation = stroke.shape === "eraser" ? "destination-out" : "source-over";
99
- context.globalAlpha = (stroke.opacity ?? 1) * (stroke.brush === "marker" ? .28 : stroke.brush === "pencil" ? .65 : 1);
100
- context.strokeStyle = stroke.color;
101
- context.fillStyle = stroke.color;
102
- context.lineWidth = stroke.width * (stroke.brush === "pencil" ? .55 : 1) * (stroke.pressure ?? 1);
133
+ configureSketchBrush(context, stroke);
103
134
  context.beginPath();
104
135
  const last = stroke.points.at(-1);
105
136
  if (stroke.shape === "text") {
@@ -148,7 +179,8 @@ window.__ModuleLoader__.load({
148
179
  if (stroke.fill) context.fill();
149
180
  else context.stroke();
150
181
  } else if (stroke.points.length === 1) {
151
- context.arc(first.x * size, first.y * height, context.lineWidth / 2, 0, Math.PI * 2);
182
+ if (stroke.brushVersion === 2 && stroke.brush === "marker") context.rect(first.x * size - context.lineWidth / 2, first.y * height - context.lineWidth / 4, context.lineWidth, context.lineWidth / 2);
183
+ else context.arc(first.x * size, first.y * height, context.lineWidth / 2, 0, Math.PI * 2);
152
184
  context.fill();
153
185
  } else {
154
186
  context.moveTo(first.x * size, first.y * height);
@@ -995,6 +1027,92 @@ window.__ModuleLoader__.load({
995
1027
  });
996
1028
  }
997
1029
  //#endregion
1030
+ //#region src/workspace-icons.jsx
1031
+ function WorkspaceIcon({ name, size = 24 }) {
1032
+ const paths = {
1033
+ select: "M5 3l14 9-7 2-3 7-4-18z",
1034
+ text: "M4 5h16M12 5v15M8 20h8M4 5v3M20 5v3",
1035
+ arrow: "M4 20L20 4M10 4h10v10",
1036
+ line: "M4 20L20 4",
1037
+ layers: "M12 3L2 8l10 5 10-5-10-5zM2 12l10 5 10-5M2 16l10 5 10-5",
1038
+ eye: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12zM15 12a3 3 0 11-6 0 3 3 0 016 0",
1039
+ eyeOff: "M3 3l18 18M9 5a10 10 0 013 0c6 0 10 7 10 7l-3 4M6 6C3 8 2 12 2 12s4 7 10 7c2 0 4-1 5-2",
1040
+ duplicate: "M8 8h13v13H8zM16 8V3H3v13h5",
1041
+ up: "M12 20V4M5 11l7-7 7 7",
1042
+ down: "M12 4v16M5 13l7 7 7-7",
1043
+ pencil: "M4 20l2-6L17 3l4 4L10 18l-6 2zM14 6l4 4",
1044
+ marker: "M5 16l9-12 7 5-9 12-7-5zM5 16l-3 4 6 1M12 7l7 5",
1045
+ image: "M4 4h16v16H4zM4 16l5-5 4 4 3-3 4 4M15 8h.01",
1046
+ close: "M6 6l12 12M18 6L6 18",
1047
+ stop: "M6 6h12v12H6z",
1048
+ pen: "M4 17c3-7 12-15 12-11S4 19 8 19s10-10 10-6-6 8-2 7l4-3",
1049
+ eraser: "M4 14l9-10 7 7-9 10H9l-5-5zM8 10l7 7M11 21h10",
1050
+ undo: "M9 5L4 10l5 5M5 10h9a6 6 0 010 12",
1051
+ redo: "M15 5l5 5-5 5M19 10h-9a6 6 0 000 12",
1052
+ check: "M5 12l5 5 9-11",
1053
+ clear: "M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13",
1054
+ rectangle: "M5 5h14v14H5z",
1055
+ circle: "M20 12a8 8 0 11-16 0 8 8 0 0116 0"
1056
+ };
1057
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1058
+ width: size,
1059
+ height: size,
1060
+ viewBox: "0 0 24 24",
1061
+ fill: "none",
1062
+ stroke: "currentColor",
1063
+ strokeWidth: "1.8",
1064
+ strokeLinecap: "round",
1065
+ strokeLinejoin: "round",
1066
+ "aria-hidden": "true",
1067
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: paths[name] ?? paths.pen })
1068
+ });
1069
+ }
1070
+ //#endregion
1071
+ //#region src/sketch-run-status.jsx
1072
+ function SketchRunStatus({ state, t, floating = false, onOpen, onStop, onResume, onDismiss }) {
1073
+ if (state === "idle") return null;
1074
+ const drawing = state === "drawing", recover = state === "stopped" || state === "failed";
1075
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1076
+ className: floating ? "codexSketchBackgroundStatus" : "codexSketchAgentStatus",
1077
+ role: "status",
1078
+ "aria-live": "polite",
1079
+ children: [
1080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
1081
+ name: drawing ? "pen" : state === "finished" ? "check" : "rectangle",
1082
+ size: 15
1083
+ }),
1084
+ floating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1085
+ type: "button",
1086
+ onClick: onOpen,
1087
+ children: t(`sketchRun_${state}`)
1088
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(`sketchRun_${state}`) }),
1089
+ drawing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1090
+ type: "button",
1091
+ className: "codexSketchStop",
1092
+ onClick: onStop,
1093
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
1094
+ name: "stop",
1095
+ size: 12
1096
+ }), t("sketchRunStop")]
1097
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [recover ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1098
+ type: "button",
1099
+ title: t("sketchRunResumeHint"),
1100
+ onClick: onResume,
1101
+ children: t("sketchRunResume")
1102
+ }) : null, !recover ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1103
+ type: "button",
1104
+ "aria-label": t("sketchDismissStatus"),
1105
+ title: t("sketchDismissStatus"),
1106
+ onClick: onDismiss,
1107
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
1108
+ name: "close",
1109
+ size: 14
1110
+ })
1111
+ }) : null] })
1112
+ ]
1113
+ });
1114
+ }
1115
+ //#endregion
998
1116
  //#region src/sketch-layer-renderer.js
999
1117
  const NO_IMAGES = /* @__PURE__ */ new Map();
1000
1118
  const surface = (width, height) => {
@@ -1200,8 +1318,7 @@ window.__ModuleLoader__.load({
1200
1318
  cursor.hidden = true;
1201
1319
  return;
1202
1320
  }
1203
- const pressure = node.hasPointerCapture(pointer.pointerId) && pointer.pointerType === "pen" ? heldPressure.current : 1;
1204
- const diameter = width * (brush === "pencil" ? .55 : 1) * pressure * rect.width / node.width;
1321
+ const diameter = width * (node.hasPointerCapture(pointer.pointerId) && pointer.pointerType === "pen" ? heldPressure.current : 1) * rect.width / node.width;
1205
1322
  cursor.hidden = false;
1206
1323
  cursor.style.width = `${diameter}px`;
1207
1324
  cursor.style.height = `${diameter}px`;
@@ -1642,7 +1759,8 @@ window.__ModuleLoader__.load({
1642
1759
  layer: layer.id
1643
1760
  }]).layers[0].strokes[0],
1644
1761
  brush: original.brush ?? "pen",
1645
- ...original.pressure !== void 0 ? { pressure: original.pressure } : {}
1762
+ ...original.pressure !== void 0 ? { pressure: original.pressure } : {},
1763
+ ...original.brushVersion === 2 ? { brushVersion: 2 } : {}
1646
1764
  };
1647
1765
  } else throw Error("Unknown object action");
1648
1766
  doc = {
@@ -1880,9 +1998,11 @@ window.__ModuleLoader__.load({
1880
1998
  "marker"
1881
1999
  ].includes(s.brush)) throw Error("Invalid brush");
1882
2000
  if (s.pressure !== void 0 && (!Number.isFinite(s.pressure) || s.pressure < .2 || s.pressure > 1)) throw Error("Invalid pressure");
2001
+ if (s.brushVersion !== void 0 && s.brushVersion !== 2) throw Error("Unsupported brush version");
1883
2002
  Object.assign(added[added.length - strokes.length + k], {
1884
2003
  brush: s.brush ?? "pen",
1885
- pressure: s.pressure ?? 1
2004
+ pressure: s.pressure ?? 1,
2005
+ ...s.brushVersion === 2 ? { brushVersion: 2 } : {}
1886
2006
  });
1887
2007
  }
1888
2008
  }
@@ -2154,46 +2274,6 @@ window.__ModuleLoader__.load({
2154
2274
  });
2155
2275
  }
2156
2276
  //#endregion
2157
- //#region src/workspace-icons.jsx
2158
- function WorkspaceIcon({ name, size = 24 }) {
2159
- const paths = {
2160
- select: "M5 3l14 9-7 2-3 7-4-18z",
2161
- text: "M4 5h16M12 5v15M8 20h8M4 5v3M20 5v3",
2162
- arrow: "M4 20L20 4M10 4h10v10",
2163
- line: "M4 20L20 4",
2164
- layers: "M12 3L2 8l10 5 10-5-10-5zM2 12l10 5 10-5M2 16l10 5 10-5",
2165
- eye: "M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12zM15 12a3 3 0 11-6 0 3 3 0 016 0",
2166
- eyeOff: "M3 3l18 18M9 5a10 10 0 013 0c6 0 10 7 10 7l-3 4M6 6C3 8 2 12 2 12s4 7 10 7c2 0 4-1 5-2",
2167
- duplicate: "M8 8h13v13H8zM16 8V3H3v13h5",
2168
- up: "M12 20V4M5 11l7-7 7 7",
2169
- down: "M12 4v16M5 13l7 7 7-7",
2170
- pencil: "M4 20l2-6L17 3l4 4L10 18l-6 2zM14 6l4 4",
2171
- marker: "M5 16l9-12 7 5-9 12-7-5zM5 16l-3 4 6 1M12 7l7 5",
2172
- image: "M4 4h16v16H4zM4 16l5-5 4 4 3-3 4 4M15 8h.01",
2173
- close: "M6 6l12 12M18 6L6 18",
2174
- pen: "M4 17c3-7 12-15 12-11S4 19 8 19s10-10 10-6-6 8-2 7l4-3",
2175
- eraser: "M4 14l9-10 7 7-9 10H9l-5-5zM8 10l7 7M11 21h10",
2176
- undo: "M9 5L4 10l5 5M5 10h9a6 6 0 010 12",
2177
- redo: "M15 5l5 5-5 5M19 10h-9a6 6 0 000 12",
2178
- check: "M5 12l5 5 9-11",
2179
- clear: "M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13",
2180
- rectangle: "M5 5h14v14H5z",
2181
- circle: "M20 12a8 8 0 11-16 0 8 8 0 0116 0"
2182
- };
2183
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
2184
- width: size,
2185
- height: size,
2186
- viewBox: "0 0 24 24",
2187
- fill: "none",
2188
- stroke: "currentColor",
2189
- strokeWidth: "1.8",
2190
- strokeLinecap: "round",
2191
- strokeLinejoin: "round",
2192
- "aria-hidden": "true",
2193
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: paths[name] ?? paths.pen })
2194
- });
2195
- }
2196
- //#endregion
2197
2277
  //#region src/sketch-size-control.jsx
2198
2278
  function SketchSizeControl({ value, onChange, onStart, onEnd, label, disabled, min = 2, max = 128, mode, modes, onModeChange, suffix = "" }) {
2199
2279
  const active = (0, react.useRef)(false);
@@ -2435,6 +2515,11 @@ window.__ModuleLoader__.load({
2435
2515
  const [stability, setStability] = (0, react.useState)(0), [flow, setFlow] = (0, react.useState)(100), [picturesOpen, setPicturesOpen] = (0, react.useState)(false);
2436
2516
  const pictureInput = (0, react.useRef)(null), received = (0, react.useRef)(null);
2437
2517
  const navigation = useSketchView(canvas, open);
2518
+ const brushWidths = (0, react.useRef)({
2519
+ pen: 12,
2520
+ pencil: 6,
2521
+ marker: 28
2522
+ });
2438
2523
  const [revision, redraw] = (0, react.useState)(0), [tool, setTool] = (0, react.useState)("pen"), [brush, setBrush] = (0, react.useState)("pen");
2439
2524
  const [eraser, setEraser] = (0, react.useState)("pixel"), [color, setColor] = (0, react.useState)("#0088ff"), [width, setWidth] = (0, react.useState)(12);
2440
2525
  const [selection, setSelection] = (0, react.useState)(null), [textEdit, setTextEdit] = (0, react.useState)(null), [shapesOpen, setShapesOpen] = (0, react.useState)(false);
@@ -2464,6 +2549,13 @@ window.__ModuleLoader__.load({
2464
2549
  setColor(value);
2465
2550
  if (selected) editObject({ color: value });
2466
2551
  };
2552
+ const chooseBrush = (name) => {
2553
+ brushWidths.current[brush] = width;
2554
+ setWidth(brushWidths.current[name]);
2555
+ setBrush(name);
2556
+ setTool("pen");
2557
+ setSelection(null);
2558
+ };
2467
2559
  const [fillShape, setFillShape] = (0, react.useState)(false);
2468
2560
  const [layersOpen, setLayersOpen] = (0, react.useState)(false), [busy, setBusy] = (0, react.useState)(false), [error, setError] = (0, react.useState)("");
2469
2561
  const cursorRing = (0, react.useRef)(null);
@@ -2652,11 +2744,11 @@ window.__ModuleLoader__.load({
2652
2744
  editObject({}, "delete");
2653
2745
  return;
2654
2746
  }
2655
- if (event.key.toLowerCase() === "v") {
2747
+ if (!event.ctrlKey && !event.metaKey && !event.altKey && event.key.toLowerCase() === "v") {
2656
2748
  setTool("select");
2657
2749
  return;
2658
2750
  }
2659
- if (event.key.toLowerCase() === "t") {
2751
+ if (!event.ctrlKey && !event.metaKey && !event.altKey && event.key.toLowerCase() === "t") {
2660
2752
  setTool("text");
2661
2753
  return;
2662
2754
  }
@@ -2683,7 +2775,6 @@ window.__ModuleLoader__.load({
2683
2775
  if (tools[key]) {
2684
2776
  event.preventDefault();
2685
2777
  setTool(tools[key]);
2686
- if (tools[key] === "pen") setBrush("pen");
2687
2778
  }
2688
2779
  if (key === "[" || key === "]") {
2689
2780
  event.preventDefault();
@@ -2962,24 +3053,17 @@ window.__ModuleLoader__.load({
2962
3053
  link.remove();
2963
3054
  setTimeout(() => URL.revokeObjectURL(url), 1e4);
2964
3055
  };
2965
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [enabled && agentEnabled && !open && !noticeHidden && agentState !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2966
- className: "codexSketchBackgroundStatus",
2967
- role: "status",
2968
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2969
- type: "button",
2970
- onClick: onOpen,
2971
- children: t(`sketchRun_${agentState}`)
2972
- }), agentLocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2973
- type: "button",
2974
- "aria-label": t("sketchRunStop"),
2975
- onClick: () => agentRun.current.stop(),
2976
- children: "×"
2977
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2978
- type: "button",
2979
- "aria-label": t("sketchCancel"),
2980
- onClick: () => setNoticeHidden(true),
2981
- children: "×"
2982
- })]
3056
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [enabled && agentEnabled && !open && !noticeHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchRunStatus, {
3057
+ state: agentState,
3058
+ floating: true,
3059
+ t,
3060
+ onOpen,
3061
+ onStop: () => agentRun.current.stop(),
3062
+ onResume: () => {
3063
+ setError("");
3064
+ agentRun.current.resume();
3065
+ },
3066
+ onDismiss: () => setNoticeHidden(true)
2983
3067
  }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
2984
3068
  ref: dialog,
2985
3069
  className: "codexSketchDialog codexSketchStudio codexLayerStudio",
@@ -3121,24 +3205,15 @@ window.__ModuleLoader__.load({
3121
3205
  })
3122
3206
  ]
3123
3207
  }),
3124
- enabled && agentEnabled && agentState !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3125
- className: "codexSketchAgentStatus",
3126
- role: "status",
3127
- "aria-live": "polite",
3128
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(`sketchRun_${agentState}`) }), agentLocked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3129
- type: "button",
3130
- "aria-label": t("sketchRunStop"),
3131
- title: t("sketchRunStop"),
3132
- onClick: () => agentRun.current.stop(),
3133
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
3134
- name: "close",
3135
- size: 16
3136
- })
3137
- }) : agentState === "stopped" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3138
- type: "button",
3139
- onClick: () => agentRun.current.resume(),
3140
- children: t("sketchRunResume")
3141
- }) : null]
3208
+ enabled && agentEnabled && !noticeHidden ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchRunStatus, {
3209
+ state: agentState,
3210
+ t,
3211
+ onStop: () => agentRun.current.stop(),
3212
+ onResume: () => {
3213
+ setError("");
3214
+ agentRun.current.resume();
3215
+ },
3216
+ onDismiss: () => setNoticeHidden(true)
3142
3217
  }) : null,
3143
3218
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3144
3219
  className: `codexLayerBody ${layersOpen ? "withLayers" : ""}`,
@@ -3254,6 +3329,7 @@ window.__ModuleLoader__.load({
3254
3329
  width,
3255
3330
  fill: fillShape && ["rectangle", "circle"].includes(tool),
3256
3331
  brush: tool === "pen" ? brush : "pen",
3332
+ ...tool === "pen" ? { brushVersion: 2 } : {},
3257
3333
  pressure: event.pointerType === "pen" ? Math.max(.2, event.pressure) : 1,
3258
3334
  points: [start]
3259
3335
  });
@@ -3549,6 +3625,8 @@ window.__ModuleLoader__.load({
3549
3625
  [
3550
3626
  "select",
3551
3627
  "pen",
3628
+ "pencil",
3629
+ "marker",
3552
3630
  "text",
3553
3631
  "eraser"
3554
3632
  ].map((name) => {
@@ -3561,13 +3639,15 @@ window.__ModuleLoader__.load({
3561
3639
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3562
3640
  type: "button",
3563
3641
  "aria-label": label,
3564
- title: label,
3642
+ title: drawing ? `${label} · ${t(`sketchBrushHint_${name}`)}` : label,
3565
3643
  "aria-pressed": drawing ? tool === "pen" && brush === name : tool === name,
3566
3644
  disabled: agentLocked || busy,
3567
3645
  onClick: () => {
3568
- setTool(drawing ? "pen" : name);
3569
- if (name !== "select") setSelection(null);
3570
- if (drawing) setBrush(name);
3646
+ if (drawing) chooseBrush(name);
3647
+ else {
3648
+ setTool(name);
3649
+ if (name !== "select") setSelection(null);
3650
+ }
3571
3651
  },
3572
3652
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
3573
3653
  name,
@@ -3578,13 +3658,30 @@ window.__ModuleLoader__.load({
3578
3658
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3579
3659
  className: "codexSketchShapeToggle",
3580
3660
  type: "button",
3661
+ "aria-label": t("sketchShapes"),
3581
3662
  "aria-expanded": shapesOpen,
3663
+ "aria-pressed": [
3664
+ "line",
3665
+ "arrow",
3666
+ "rectangle",
3667
+ "circle"
3668
+ ].includes(tool),
3582
3669
  disabled: agentLocked || busy,
3583
3670
  onClick: () => setShapesOpen((v) => !v),
3584
3671
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
3585
- name: "rectangle",
3672
+ name: [
3673
+ "line",
3674
+ "arrow",
3675
+ "rectangle",
3676
+ "circle"
3677
+ ].includes(tool) ? tool : "rectangle",
3586
3678
  size: 23
3587
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("sketchShapes") })]
3679
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t([
3680
+ "line",
3681
+ "arrow",
3682
+ "rectangle",
3683
+ "circle"
3684
+ ].includes(tool) ? `sketchTool_${tool}` : "sketchShapes") })]
3588
3685
  }),
3589
3686
  shapesOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3590
3687
  className: "codexSketchShapeMenu",
@@ -3595,6 +3692,7 @@ window.__ModuleLoader__.load({
3595
3692
  "circle"
3596
3693
  ].map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3597
3694
  type: "button",
3695
+ "aria-pressed": tool === name,
3598
3696
  onClick: () => {
3599
3697
  setTool(name);
3600
3698
  setSelection(null);
@@ -3614,20 +3712,6 @@ window.__ModuleLoader__.load({
3614
3712
  checked: fillShape,
3615
3713
  onChange: (e) => setFillShape(e.target.checked)
3616
3714
  }), t("sketchFill")] }) : null,
3617
- tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
3618
- "aria-label": t("sketchBrush"),
3619
- value: brush,
3620
- onChange: (e) => setBrush(e.target.value),
3621
- disabled: agentLocked || busy,
3622
- children: [
3623
- "pen",
3624
- "pencil",
3625
- "marker"
3626
- ].map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
3627
- value: b,
3628
- children: t(`sketchBrush_${b}`)
3629
- }, b))
3630
- }) : null,
3631
3715
  selected ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3632
3716
  className: "codexSketchObjectActions",
3633
3717
  children: [
@@ -3734,7 +3818,7 @@ window.__ModuleLoader__.load({
3734
3818
  .codexLayerBrush>select{background:var(--sketch-bg);color:inherit;border:1px solid var(--sketch-line);border-radius:8px;padding:6px}
3735
3819
 
3736
3820
  .codexSketchBackgroundStatus{position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:1000;display:flex;gap:8px;align-items:center;padding:6px 10px;border-radius:12px;background:var(--dsw-alias-bg-layer-1,#f5f5f7);color:var(--dsw-alias-label-primary,#202124);border:1px solid #8883;box-shadow:0 4px 16px #0002;font:12px system-ui}.codexSketchBackgroundStatus button{color:inherit;background:none;border:0;cursor:pointer}
3737
- .codexSketchAgentStatus{display:flex;align-items:center;justify-content:center;gap:8px;min-height:24px;color:var(--sketch-muted);font-size:12px}.codexSketchAgentStatus button{padding:3px 6px;border-radius:6px;background:var(--sketch-line)}
3821
+ .codexSketchAgentStatus{display:flex;align-items:center;justify-content:center;gap:8px;min-height:24px;color:var(--sketch-muted);font-size:12px}.codexSketchAgentStatus button{min-height:30px;padding:4px 10px;border-radius:16px;background:var(--sketch-line)}.codexSketchAgentStatus{align-self:center;max-width:100%;min-height:34px;padding:0 6px;flex-wrap:wrap}.codexSketchBackgroundStatus button{min-height:30px;display:inline-flex;align-items:center;gap:5px}.codexSketchAgentStatus .codexSketchStop,.codexSketchBackgroundStatus .codexSketchStop{font-weight:500;background:var(--sketch-line,#8882);border-radius:16px;padding:4px 10px}
3738
3822
  .codexSketchDialog{--sketch-bg:var(--dsw-alias-bg-layer-1,#f5f5f7);--sketch-fg:var(--dsw-alias-label-primary,#202124);--sketch-muted:var(--dsw-alias-label-secondary,#727279);--sketch-line:var(--dsw-alias-border-l2,#8883);--sketch-glass:color-mix(in srgb,var(--sketch-bg) 90%,transparent);box-sizing:border-box;width:min(1280px,calc(100vw - 24px));height:94dvh;max-height:94dvh;margin:auto;padding:10px;border:1px solid var(--sketch-line);border-radius:18px;background:var(--sketch-bg);color:var(--sketch-fg);box-shadow:0 24px 90px #0004;overflow:hidden;font:13px/1.4 system-ui}
3739
3823
  .codexSketchDialog[open]{display:flex;flex-direction:column;gap:8px}
3740
3824
  .codexSketchDialog::backdrop{background:#0005;backdrop-filter:blur(12px)}
@@ -3918,6 +4002,11 @@ window.__ModuleLoader__.load({
3918
4002
  sketchSizeShort: "粗细",
3919
4003
  sketchObjectDuplicate: "复制对象",
3920
4004
  sketchObjectDelete: "删除对象",
4005
+ sketchBrushHint_pen: "实色圆头墨线",
4006
+ sketchBrushHint_pencil: "细腻颗粒,叠画加深",
4007
+ sketchBrushHint_marker: "半透明平头,适合高亮",
4008
+ sketchDismissStatus: "收起提示",
4009
+ sketchRunResumeHint: "允许 Agent 接收后续绘图请求,不会自动重发消息",
3921
4010
  sketchTool_select: "选择",
3922
4011
  sketchTool_text: "文字",
3923
4012
  sketchTool_arrow: "箭头",
@@ -4324,6 +4413,11 @@ window.__ModuleLoader__.load({
4324
4413
  sketchSizeShort: "Size",
4325
4414
  sketchObjectDuplicate: "Duplicate object",
4326
4415
  sketchObjectDelete: "Delete object",
4416
+ sketchBrushHint_pen: "Solid round ink",
4417
+ sketchBrushHint_pencil: "Grain builds with repeated strokes",
4418
+ sketchBrushHint_marker: "Translucent flat tip for highlights",
4419
+ sketchDismissStatus: "Dismiss status",
4420
+ sketchRunResumeHint: "Allow subsequent drawing requests; does not resend a message",
4327
4421
  sketchTool_select: "Select",
4328
4422
  sketchTool_text: "Text",
4329
4423
  sketchTool_arrow: "Arrow",
@@ -6547,7 +6641,7 @@ window.__ModuleLoader__.load({
6547
6641
  }
6548
6642
  //#endregion
6549
6643
  //#region src/version.js
6550
- const PACKAGE_VERSION = "2.1.0-beta.5";
6644
+ const PACKAGE_VERSION = "2.1.0";
6551
6645
  //#endregion
6552
6646
  //#region src/client-recovery.js
6553
6647
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
package/lib/index.js CHANGED
@@ -2004,7 +2004,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
2004
2004
  Object.freeze(["0.82.1", "0.85.1"]);
2005
2005
  //#endregion
2006
2006
  //#region src/version.js
2007
- const PACKAGE_VERSION = "2.1.0-beta.5";
2007
+ const PACKAGE_VERSION = "2.1.0";
2008
2008
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2009
2009
  //#endregion
2010
2010
  //#region src/model-catalog.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.1.0-beta.5",
3
+ "version": "2.1.0",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",