dsh-codex-subscription 2.5.7 → 2.5.8

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
@@ -106,7 +106,7 @@ This plugin supports the latest DeepSeek Harness release recorded in its package
106
106
  2. Paste this package name, with its version, into the **Package name or address** field:
107
107
 
108
108
  ```text
109
- dsh-codex-subscription@2.5.7
109
+ dsh-codex-subscription@2.5.8
110
110
  ```
111
111
 
112
112
  3. Click **Install** and wait for completion. Follow the page instructions; save your work before restarting if requested.
@@ -139,6 +139,12 @@ dsh --profile headless "Reply with only the word: ok"
139
139
 
140
140
  ## Inside the plugin
141
141
 
142
+ <details>
143
+ <summary><b>Interface demo (animation, click to expand)</b>: models, prompts, images, annotation, sketch board, quota and resets</summary>
144
+ <br>
145
+ <p align="center"><img src="docs/assets/readme-demo-en.webp" width="1100" alt="Demo: sign in with ChatGPT, pick a subscription model, ask, generate an image, view and annotate it, continue on the sketch board, and check quota and resets"></p>
146
+ </details>
147
+
142
148
  ### Everyday controls, right in the composer
143
149
 
144
150
  Choose a model, adjust reasoning, switch speed tiers, and check remaining quota without opening settings for every request.
package/README.md CHANGED
@@ -106,7 +106,7 @@
106
106
  2. 在 **包名或地址** 输入框中粘贴下面的包名(已带版本号):
107
107
 
108
108
  ```text
109
- dsh-codex-subscription@2.5.7
109
+ dsh-codex-subscription@2.5.8
110
110
  ```
111
111
 
112
112
  3. 点击 **安装**,等待安装完成;按页面提示操作,需要重启时先保存工作。
@@ -139,6 +139,12 @@ dsh --profile headless "只回复:ok"
139
139
 
140
140
  ## 界面一览
141
141
 
142
+ <details>
143
+ <summary><b>界面演示(动图,点击展开)</b>:选模型、提问、出图、标注、草图、额度与重置</summary>
144
+ <br>
145
+ <p align="center"><img src="docs/assets/readme-demo.webp" width="1100" alt="演示:登录 ChatGPT 选订阅模型、提问、用订阅出图、查看与标注、进入草图、查看额度与重置"></p>
146
+ </details>
147
+
142
148
  ### 日常操作,留在输入框
143
149
 
144
150
  选模型、调推理档位、切换速度档位,同时查看剩余额度。无需为每次请求打开设置。
package/lib/client.js CHANGED
@@ -587,6 +587,88 @@ window.__ModuleLoader__.load({
587
587
  ].join("\n");
588
588
  }
589
589
  //#endregion
590
+ //#region src/image-official-tools.js
591
+ const IMAGE_ASPECT_RATIOS = Object.freeze([
592
+ ["square", "1:1"],
593
+ ["portrait", "3:4"],
594
+ ["story", "9:16"],
595
+ ["landscape", "4:3"],
596
+ ["widescreen", "16:9"]
597
+ ]);
598
+ /**
599
+ * Build viewer actions for one image.
600
+ * @param {{ src: string, name: string, t: (key: string) => string,
601
+ * attachForEdit: (src: string, name: string, draft: string, annotations?: unknown[], referenceName?: string, sourceInDraft?: boolean, extraFiles?: File[]) => Promise<unknown>,
602
+ * sourceInDraft?: boolean }} options
603
+ */
604
+ function officialImageTools({ src, name, t, attachForEdit, sourceInDraft = false }) {
605
+ const send = (draft, extraFiles = []) => attachForEdit(src, name, draft, [], void 0, sourceInDraft, extraFiles);
606
+ const base = {
607
+ pendingLabel: t("imageEditPreparing"),
608
+ errorLabel: t("imageEditFailed"),
609
+ closeOnSuccess: true
610
+ };
611
+ return [
612
+ {
613
+ ...base,
614
+ id: "remove-background",
615
+ kind: "tool",
616
+ label: t("imageRemoveBackground"),
617
+ onInvoke: () => send(t("imageRemoveBackgroundPrompt"))
618
+ },
619
+ {
620
+ ...base,
621
+ id: "erase",
622
+ kind: "mask",
623
+ label: t("imageErase"),
624
+ onInvoke: async ({ mask }) => {
625
+ if (!(mask instanceof Blob)) throw new Error("Mark the area to erase first");
626
+ const maskName = `${String(name).replace(/\.[a-z0-9]+$/iu, "")}-erase-mask.png`;
627
+ return send(t("imageErasePrompt"), [new File([mask], maskName, { type: "image/png" })]);
628
+ }
629
+ },
630
+ {
631
+ ...base,
632
+ id: "resize",
633
+ kind: "choice",
634
+ label: t("imageResize"),
635
+ options: IMAGE_ASPECT_RATIOS.map(([id, ratio]) => ({
636
+ value: ratio,
637
+ label: `${t(`imageRatio_${id}`)} ${ratio}`
638
+ })),
639
+ onInvoke: ({ option }) => send(t("imageResizePrompt").replaceAll("{aspectRatio}", option))
640
+ }
641
+ ];
642
+ }
643
+ /** Draw relative erase strokes in one color onto a canvas of the given size. */
644
+ function paintEraseStrokes(context, strokes, width, height, color) {
645
+ context.strokeStyle = color;
646
+ context.fillStyle = color;
647
+ context.lineCap = "round";
648
+ context.lineJoin = "round";
649
+ const scale = Math.min(width, height);
650
+ for (const stroke of strokes) {
651
+ const points = stroke.points;
652
+ if (points.length === 0) continue;
653
+ context.lineWidth = stroke.size * scale;
654
+ context.beginPath();
655
+ if (points.length === 1) {
656
+ context.arc(points[0].x * width, points[0].y * height, stroke.size * scale / 2, 0, Math.PI * 2);
657
+ context.fill();
658
+ continue;
659
+ }
660
+ context.moveTo(points[0].x * width, points[0].y * height);
661
+ for (const point of points.slice(1)) context.lineTo(point.x * width, point.y * height);
662
+ context.stroke();
663
+ }
664
+ }
665
+ /** Codex's erase mask: the marked area white on black, at the image's own resolution. */
666
+ function paintEraseMask(context, strokes, width, height) {
667
+ context.fillStyle = "#000";
668
+ context.fillRect(0, 0, width, height);
669
+ paintEraseStrokes(context, strokes, width, height, "#fff");
670
+ }
671
+ //#endregion
590
672
  //#region src/client-image-previews.jsx
591
673
  function openPreview(props, item, opener, sourceInDraft = false) {
592
674
  const { service, preference, t, attachForEdit } = props;
@@ -595,25 +677,35 @@ window.__ModuleLoader__.load({
595
677
  service.open({
596
678
  items: [{
597
679
  ...item,
598
- actions: settings.imageEditing ? [{
599
- id: "edit",
600
- label: t("imageEdit"),
601
- pendingLabel: t("imageEditPreparing"),
602
- errorLabel: t("imageEditFailed"),
603
- closeOnSuccess: true,
604
- onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
605
- annotations,
606
- translate: t,
607
- sourceName: item.name,
608
- referenceName
609
- }), annotations, referenceName, sourceInDraft)
610
- }, ...settings.imageSketch && props.openSketchImage ? [{
611
- id: "sketch",
612
- label: t("imageToSketch"),
613
- pendingLabel: t("imageEditPreparing"),
614
- errorLabel: t("imageEditFailed"),
615
- onInvoke: () => props.openSketchImage(item.src, item.name)
616
- }] : []] : []
680
+ actions: settings.imageEditing ? [
681
+ {
682
+ id: "edit",
683
+ label: t("imageEdit"),
684
+ pendingLabel: t("imageEditPreparing"),
685
+ errorLabel: t("imageEditFailed"),
686
+ closeOnSuccess: true,
687
+ onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
688
+ annotations,
689
+ translate: t,
690
+ sourceName: item.name,
691
+ referenceName
692
+ }), annotations, referenceName, sourceInDraft)
693
+ },
694
+ ...officialImageTools({
695
+ src: item.src,
696
+ name: item.name,
697
+ t,
698
+ attachForEdit,
699
+ sourceInDraft
700
+ }),
701
+ ...settings.imageSketch && props.openSketchImage ? [{
702
+ id: "sketch",
703
+ label: t("imageToSketch"),
704
+ pendingLabel: t("imageEditPreparing"),
705
+ errorLabel: t("imageEditFailed"),
706
+ onInvoke: () => props.openSketchImage(item.src, item.name)
707
+ }] : []
708
+ ] : []
617
709
  }],
618
710
  opener,
619
711
  source: sourceInDraft ? "codex-draft" : "codex-message",
@@ -804,7 +896,27 @@ window.__ModuleLoader__.load({
804
896
  d: "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
805
897
  }));
806
898
  }
807
- const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
899
+ const ForwardRef$6 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
900
+ //#endregion
901
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ArrowsPointingOutIcon.js
902
+ function ArrowsPointingOutIcon({ title, titleId, ...props }, svgRef) {
903
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
904
+ xmlns: "http://www.w3.org/2000/svg",
905
+ fill: "none",
906
+ viewBox: "0 0 24 24",
907
+ strokeWidth: 1.5,
908
+ stroke: "currentColor",
909
+ "aria-hidden": "true",
910
+ "data-slot": "icon",
911
+ ref: svgRef,
912
+ "aria-labelledby": titleId
913
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
914
+ strokeLinecap: "round",
915
+ strokeLinejoin: "round",
916
+ d: "M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
917
+ }));
918
+ }
919
+ const ForwardRef$5 = /*#__PURE__*/ react.forwardRef(ArrowsPointingOutIcon);
808
920
  //#endregion
809
921
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
810
922
  function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
@@ -824,7 +936,27 @@ window.__ModuleLoader__.load({
824
936
  d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
825
937
  }));
826
938
  }
827
- const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
939
+ const ForwardRef$4 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
940
+ //#endregion
941
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PaintBrushIcon.js
942
+ function PaintBrushIcon({ title, titleId, ...props }, svgRef) {
943
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
944
+ xmlns: "http://www.w3.org/2000/svg",
945
+ fill: "none",
946
+ viewBox: "0 0 24 24",
947
+ strokeWidth: 1.5,
948
+ stroke: "currentColor",
949
+ "aria-hidden": "true",
950
+ "data-slot": "icon",
951
+ ref: svgRef,
952
+ "aria-labelledby": titleId
953
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
954
+ strokeLinecap: "round",
955
+ strokeLinejoin: "round",
956
+ d: "M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42"
957
+ }));
958
+ }
959
+ const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(PaintBrushIcon);
828
960
  //#endregion
829
961
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PhotoIcon.js
830
962
  function PhotoIcon({ title, titleId, ...props }, svgRef) {
@@ -844,7 +976,27 @@ window.__ModuleLoader__.load({
844
976
  d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
845
977
  }));
846
978
  }
847
- const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
979
+ const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
980
+ //#endregion
981
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ScissorsIcon.js
982
+ function ScissorsIcon({ title, titleId, ...props }, svgRef) {
983
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
984
+ xmlns: "http://www.w3.org/2000/svg",
985
+ fill: "none",
986
+ viewBox: "0 0 24 24",
987
+ strokeWidth: 1.5,
988
+ stroke: "currentColor",
989
+ "aria-hidden": "true",
990
+ "data-slot": "icon",
991
+ ref: svgRef,
992
+ "aria-labelledby": titleId
993
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
994
+ strokeLinecap: "round",
995
+ strokeLinejoin: "round",
996
+ d: "m7.848 8.25 1.536.887M7.848 8.25a3 3 0 1 1-5.196-3 3 3 0 0 1 5.196 3Zm1.536.887a2.165 2.165 0 0 1 1.083 1.839c.005.351.054.695.14 1.024M9.384 9.137l2.077 1.199M7.848 15.75l1.536-.887m-1.536.887a3 3 0 1 1-5.196 3 3 3 0 0 1 5.196-3Zm1.536-.887a2.165 2.165 0 0 0 1.083-1.838c.005-.352.054-.695.14-1.025m-1.223 2.863 2.077-1.199m0-3.328a4.323 4.323 0 0 1 2.068-1.379l5.325-1.628a4.5 4.5 0 0 1 2.48-.044l.803.215-7.794 4.5m-2.882-1.664A4.33 4.33 0 0 0 10.607 12m3.736 0 7.794 4.5-.802.215a4.5 4.5 0 0 1-2.48-.043l-5.326-1.629a4.324 4.324 0 0 1-2.068-1.379M14.343 12l-2.882 1.664"
997
+ }));
998
+ }
999
+ const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(ScissorsIcon);
848
1000
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
849
1001
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
850
1002
  function decodeOriginalImageRef(value) {
@@ -1032,32 +1184,41 @@ window.__ModuleLoader__.load({
1032
1184
  errorLabel: t("imageDownloadFailed"),
1033
1185
  onInvoke: downloadOriginal
1034
1186
  },
1035
- actions: !features.imageEditing ? [] : [{
1036
- id: "continue-editing",
1037
- label: t("imageEdit"),
1038
- pendingLabel: t("imageEditPreparing"),
1039
- errorLabel: t("imageEditFailed"),
1040
- closeOnSuccess: true,
1041
- onInvoke: ({ annotations = [] }) => {
1042
- const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1043
- const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1044
- const referenceName = `codex-edit-${imageKey}-annotations.png`;
1045
- return attachForEdit(src, sourceName, buildImageEditDraft({
1046
- annotations,
1047
- translate: t,
1048
- width: attachment.width,
1049
- height: attachment.height,
1050
- sourceName,
1051
- referenceName
1052
- }), annotations, referenceName);
1053
- }
1054
- }, ...features.imageSketch && openSketchImage ? [{
1055
- id: "sketch",
1056
- label: t("imageToSketch"),
1057
- pendingLabel: t("imageEditPreparing"),
1058
- errorLabel: t("imageEditFailed"),
1059
- onInvoke: () => openSketchImage(src, downloadName)
1060
- }] : []]
1187
+ actions: !features.imageEditing ? [] : [
1188
+ {
1189
+ id: "continue-editing",
1190
+ label: t("imageEdit"),
1191
+ pendingLabel: t("imageEditPreparing"),
1192
+ errorLabel: t("imageEditFailed"),
1193
+ closeOnSuccess: true,
1194
+ onInvoke: ({ annotations = [] }) => {
1195
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1196
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1197
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
1198
+ return attachForEdit(src, sourceName, buildImageEditDraft({
1199
+ annotations,
1200
+ translate: t,
1201
+ width: attachment.width,
1202
+ height: attachment.height,
1203
+ sourceName,
1204
+ referenceName
1205
+ }), annotations, referenceName);
1206
+ }
1207
+ },
1208
+ ...officialImageTools({
1209
+ src,
1210
+ name: downloadName,
1211
+ t,
1212
+ attachForEdit
1213
+ }),
1214
+ ...features.imageSketch && openSketchImage ? [{
1215
+ id: "sketch",
1216
+ label: t("imageToSketch"),
1217
+ pendingLabel: t("imageEditPreparing"),
1218
+ errorLabel: t("imageEditFailed"),
1219
+ onInvoke: () => openSketchImage(src, downloadName)
1220
+ }] : []
1221
+ ]
1061
1222
  }],
1062
1223
  opener: triggerRef.current,
1063
1224
  source: "codex-generated",
@@ -1107,7 +1268,7 @@ window.__ModuleLoader__.load({
1107
1268
  showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1108
1269
  className: "codexImageToolRow",
1109
1270
  children: [
1110
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$6 : failed ? ForwardRef$4 : ForwardRef$2, {
1111
1272
  className: "codexImageToolIcon",
1112
1273
  "aria-hidden": "true"
1113
1274
  }),
@@ -5525,6 +5686,21 @@ window.__ModuleLoader__.load({
5525
5686
  sketchEditableFile: "可编辑草稿",
5526
5687
  sketchFormatHint: "PNG 为合并图片;可编辑草稿保留原生笔画,可重新导入(32 MB 内)。",
5527
5688
  imageToSketch: "进入草图",
5689
+ imageRemoveBackground: "移除背景",
5690
+ imageRemoveBackgroundPrompt: "移除此图像的背景。保持所有前景主体不变且完整,边缘干净平滑。将背景设为透明。",
5691
+ imageErase: "擦除",
5692
+ imageEraseHint: "在要移除的内容上涂抹",
5693
+ imageErasePrompt: "从第一张图像中移除第二张图像中标记的区域",
5694
+ imageBrushSize: "画笔大小",
5695
+ imageUndo: "撤销",
5696
+ imageSend: "发送",
5697
+ imageResize: "调整大小",
5698
+ imageResizePrompt: "将宽高比设为 {aspectRatio}",
5699
+ imageRatio_square: "正方形",
5700
+ imageRatio_portrait: "竖向",
5701
+ imageRatio_story: "故事版式",
5702
+ imageRatio_landscape: "横向",
5703
+ imageRatio_widescreen: "宽屏",
5528
5704
  sketchPictures: "图片",
5529
5705
  sketchPictureAdd: "添加",
5530
5706
  sketchPictureSelect: "选择图片",
@@ -6097,6 +6273,21 @@ window.__ModuleLoader__.load({
6097
6273
  sketchEditableFile: "Editable draft",
6098
6274
  sketchFormatHint: "PNG is flattened; editable drafts retain native strokes and can be imported again (up to 32 MB).",
6099
6275
  imageToSketch: "Open in sketch",
6276
+ imageRemoveBackground: "Remove background",
6277
+ imageRemoveBackgroundPrompt: "Remove the background from this image. Keep all foreground subjects unchanged and fully intact, with clean, smooth edges. Make the background transparent.",
6278
+ imageErase: "Erase",
6279
+ imageEraseHint: "Brush over what you want to remove",
6280
+ imageErasePrompt: "Remove the area marked in the second image from the first image",
6281
+ imageBrushSize: "Brush size",
6282
+ imageUndo: "Undo",
6283
+ imageSend: "Send",
6284
+ imageResize: "Resize",
6285
+ imageResizePrompt: "Make the aspect ratio {aspectRatio}",
6286
+ imageRatio_square: "Square",
6287
+ imageRatio_portrait: "Portrait",
6288
+ imageRatio_story: "Story",
6289
+ imageRatio_landscape: "Landscape",
6290
+ imageRatio_widescreen: "Widescreen",
6100
6291
  sketchPictures: "Images",
6101
6292
  sketchPictureAdd: "Add",
6102
6293
  sketchPictureSelect: "Select image",
@@ -6919,6 +7110,9 @@ window.__ModuleLoader__.load({
6919
7110
  .dcsiv-inline-note{position:absolute;bottom:34px;box-sizing:border-box;display:grid;width:min(300px,calc(100vw - 40px));grid-template-columns:24px minmax(0,1fr) 24px;align-items:center;gap:7px;padding:7px 8px;border:1px solid rgba(255,255,255,.12);border-radius:18px;background:rgba(32,33,37,.94);box-shadow:0 14px 38px rgba(0,0,0,.38);color:#fff;pointer-events:auto;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.dcsiv-annotation[data-x=right] .dcsiv-inline-note{left:-8px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note{right:-8px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note{left:50%;transform:translateX(-50%)}.dcsiv-annotation[data-y=down] .dcsiv-inline-note{top:34px;bottom:auto}.dcsiv-inline-note::after{position:absolute;width:9px;height:9px;background:rgba(32,33,37,.94);content:'';transform:rotate(45deg)}.dcsiv-annotation[data-y=up] .dcsiv-inline-note::after{bottom:-5px}.dcsiv-annotation[data-y=down] .dcsiv-inline-note::after{top:-5px}.dcsiv-annotation[data-x=right] .dcsiv-inline-note::after{left:13px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note::after{right:13px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note::after{left:calc(50% - 4px)}.dcsiv-inline-index{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--dsw-alias-state-business-primary,#3964fe);color:#fff;font-size:10px;font-weight:700}.dcsiv-inline-note textarea{box-sizing:border-box;width:100%;min-height:24px;max-height:92px;resize:none;overflow:auto;border:0;outline:0;background:transparent;color:#fff;font:inherit;font-size:12px;line-height:18px}.dcsiv-inline-note textarea::placeholder{color:rgba(255,255,255,.44)}.dcsiv-note-remove{display:grid;place-items:center;width:24px;height:24px;padding:0;border:0;border-radius:50%;background:transparent;color:rgba(255,255,255,.68);cursor:pointer}.dcsiv-note-remove:hover{background:rgba(255,255,255,.1);color:#fff}
6920
7111
  .dcsiv-nav{position:absolute;top:50%;z-index:4;width:42px;height:42px;padding:0;transform:translateY(-50%);background:rgba(48,49,53,.82);box-shadow:0 8px 24px rgba(0,0,0,.28);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.dcsiv-prev{left:6px}.dcsiv-next{right:6px}.dcsiv-counter{position:absolute;bottom:8px;left:50%;padding:5px 10px;border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.72);font-size:11px;transform:translateX(-50%);backdrop-filter:blur(16px)}
6921
7112
  .dcsiv-hint{display:none}
7113
+ .dcsiv-mask{position:absolute;inset:0;width:100%;height:100%;border-radius:12px;cursor:crosshair;touch-action:none}
7114
+ .dcsiv-tool-icon{width:16px;height:16px;flex:none}.dcsiv-menu{position:fixed;z-index:12;display:flex;flex-direction:column;min-width:150px;padding:6px;border:1px solid rgba(255,255,255,.12);border-radius:12px;background:rgba(32,33,37,.97);box-shadow:0 12px 32px rgba(0,0,0,.4)}.dcsiv-menu button{height:32px;padding:0 10px;border:0;border-radius:8px;background:none;color:rgba(255,255,255,.9);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dcsiv-menu button:hover,.dcsiv-menu button:focus-visible{background:rgba(255,255,255,.1)}
7115
+ .dcsiv-erase-bar{position:absolute;left:50%;top:22px;z-index:7;display:flex;align-items:center;gap:10px;padding:6px 8px 6px 14px;transform:translateX(-50%);border:1px solid rgba(255,255,255,.1);border-radius:999px;background:rgba(38,39,43,.92);color:rgba(255,255,255,.86);font-size:12px;white-space:nowrap}.dcsiv-erase-bar label{display:inline-flex;align-items:center;gap:6px}.dcsiv-erase-bar input{width:90px}.dcsiv-primary{background:#fff!important;color:#111!important}
6922
7116
  .dcsiv-copy-notes{position:absolute;right:20px;bottom:22px;z-index:6;display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 11px;border:1px solid rgba(255,255,255,.1);border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.82);font:inherit;font-size:12px;cursor:pointer;backdrop-filter:blur(16px)}
6923
7117
  .dcsiv-edit-short{display:none}
6924
7118
  @media(max-width:760px){.dcsiv-actions{flex-wrap:wrap;justify-content:center}.dcsiv-edit-short{display:inline;white-space:normal;max-width:min(280px,calc(100vw - 80px))}.dcsiv-edit-action{height:auto;min-height:32px}.dcsiv-close-floating{top:12px;right:12px;width:40px;height:40px}.dcsiv-workspace{padding:60px 12px 68px}.dcsiv-image{max-width:calc(100vw - 24px);max-height:calc(100vh - 136px)}.dcsiv-topbar{bottom:12px;max-width:calc(100vw - 24px)}.dcsiv-button{padding:0 8px}.dcsiv-button span.dcsiv-label{display:none}.dcsiv-inline-note{width:min(260px,calc(100vw - 40px))}.dcsiv-copy-notes{display:none}}
@@ -7102,6 +7296,18 @@ window.__ModuleLoader__.load({
7102
7296
  }
7103
7297
  //#endregion
7104
7298
  //#region src/subscription-image-viewer.jsx
7299
+ const TOOL_ICONS = {
7300
+ "remove-background": ForwardRef$1,
7301
+ erase: ForwardRef$3,
7302
+ resize: ForwardRef$5
7303
+ };
7304
+ const ToolIcon = ({ id }) => {
7305
+ const Icon = TOOL_ICONS[id];
7306
+ return Icon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
7307
+ className: "dcsiv-tool-icon",
7308
+ "aria-hidden": "true"
7309
+ }) : null;
7310
+ };
7105
7311
  const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
7106
7312
  const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
7107
7313
  const bytesLabel = (bytes) => bytes === void 0 ? void 0 : bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
@@ -7112,8 +7318,9 @@ window.__ModuleLoader__.load({
7112
7318
  const noteText = (annotations, t) => annotations.map((annotation, index) => {
7113
7319
  return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
7114
7320
  }).filter((line) => !line.endsWith(": ")).join("\n");
7115
- function ViewerAction({ action, annotations, item, service, revision, t }) {
7321
+ function ViewerAction({ action, annotations, item, service, revision, t, onStartMask }) {
7116
7322
  const [state, setState] = (0, react.useState)("idle");
7323
+ const [menu, setMenu] = (0, react.useState)();
7117
7324
  const active = (0, react.useRef)(true);
7118
7325
  (0, react.useEffect)(() => {
7119
7326
  active.current = true;
@@ -7121,14 +7328,15 @@ window.__ModuleLoader__.load({
7121
7328
  active.current = false;
7122
7329
  };
7123
7330
  }, []);
7124
- const invoke = async () => {
7331
+ const invoke = async (extra = {}) => {
7125
7332
  if (state === "pending") return;
7126
7333
  setState("pending");
7127
7334
  try {
7128
7335
  await action.onInvoke({
7129
7336
  annotations,
7130
7337
  item,
7131
- src: item.src
7338
+ src: item.src,
7339
+ ...extra
7132
7340
  });
7133
7341
  if (!active.current || service.getSnapshot()?.revision !== revision) return;
7134
7342
  setState("idle");
@@ -7138,6 +7346,62 @@ window.__ModuleLoader__.load({
7138
7346
  }
7139
7347
  };
7140
7348
  const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
7349
+ if (action.kind === "mask") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7350
+ type: "button",
7351
+ className: "dcsiv-button",
7352
+ onClick: () => onStartMask(action),
7353
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7354
+ className: "dcsiv-label",
7355
+ children: action.label
7356
+ })]
7357
+ });
7358
+ if (action.kind === "tool") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7359
+ type: "button",
7360
+ className: "dcsiv-button",
7361
+ disabled: state === "pending",
7362
+ onClick: () => {
7363
+ invoke();
7364
+ },
7365
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7366
+ className: "dcsiv-label",
7367
+ children: label
7368
+ })]
7369
+ });
7370
+ if (action.kind === "choice") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7371
+ type: "button",
7372
+ className: "dcsiv-button",
7373
+ "aria-haspopup": "menu",
7374
+ "aria-expanded": menu !== void 0,
7375
+ disabled: state === "pending",
7376
+ onClick: (event) => {
7377
+ const box = event.currentTarget.getBoundingClientRect();
7378
+ const host = event.currentTarget.closest(".dcsiv-root");
7379
+ setMenu((value) => value === void 0 && host ? {
7380
+ host,
7381
+ style: {
7382
+ left: box.left,
7383
+ bottom: window.innerHeight - box.top + 8
7384
+ }
7385
+ } : void 0);
7386
+ },
7387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7388
+ className: "dcsiv-label",
7389
+ children: label
7390
+ })]
7391
+ }), menu ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7392
+ className: "dcsiv-menu",
7393
+ role: "menu",
7394
+ style: menu.style,
7395
+ children: action.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7396
+ type: "button",
7397
+ role: "menuitem",
7398
+ onClick: () => {
7399
+ setMenu(void 0);
7400
+ invoke({ option: option.value });
7401
+ },
7402
+ children: option.label
7403
+ }, option.value))
7404
+ }), menu.host) : null] });
7141
7405
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7142
7406
  type: "button",
7143
7407
  className: "dcsiv-button dcsiv-edit-action",
@@ -7224,6 +7488,12 @@ window.__ModuleLoader__.load({
7224
7488
  const [selected, setSelected] = (0, react.useState)();
7225
7489
  const [focusNote, setFocusNote] = (0, react.useState)();
7226
7490
  const [copied, setCopied] = (0, react.useState)(false);
7491
+ const [erasing, setErasing] = (0, react.useState)();
7492
+ const [strokes, setStrokes] = (0, react.useState)([]);
7493
+ const [brush, setBrush] = (0, react.useState)(.05);
7494
+ const [sending, setSending] = (0, react.useState)(false);
7495
+ const drawingRef = (0, react.useRef)(false);
7496
+ const maskRef = (0, react.useRef)(null);
7227
7497
  const rootRef = (0, react.useRef)(null);
7228
7498
  annotationsByImageRef.current = annotationsByImage;
7229
7499
  (0, react.useEffect)(() => {
@@ -7316,11 +7586,85 @@ window.__ModuleLoader__.load({
7316
7586
  (0, react.useEffect)(() => {
7317
7587
  setAnnotating(false);
7318
7588
  setSelected(void 0);
7589
+ setErasing(void 0);
7590
+ setStrokes([]);
7319
7591
  }, [
7320
7592
  item?.id,
7321
7593
  item?.src,
7322
7594
  request?.revision
7323
7595
  ]);
7596
+ (0, react.useEffect)(() => {
7597
+ const canvas = maskRef.current;
7598
+ if (!erasing || canvas === null) return;
7599
+ const context = canvas.getContext("2d");
7600
+ context.clearRect(0, 0, canvas.width, canvas.height);
7601
+ paintEraseStrokes(context, strokes, canvas.width, canvas.height, "rgba(255,59,48,.5)");
7602
+ }, [erasing, strokes]);
7603
+ const strokePoint = (event) => {
7604
+ const bounds = maskRef.current?.getBoundingClientRect();
7605
+ if (bounds === void 0) return void 0;
7606
+ return {
7607
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
7608
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1)
7609
+ };
7610
+ };
7611
+ const maskHandlers = {
7612
+ onPointerDown: (event) => {
7613
+ const point = strokePoint(event);
7614
+ if (point === void 0) return;
7615
+ event.stopPropagation();
7616
+ event.currentTarget.setPointerCapture?.(event.pointerId);
7617
+ drawingRef.current = true;
7618
+ setStrokes((current) => [...current, {
7619
+ size: brush / transformRef.current.zoom,
7620
+ points: [point]
7621
+ }]);
7622
+ },
7623
+ onPointerMove: (event) => {
7624
+ if (!drawingRef.current) return;
7625
+ const point = strokePoint(event);
7626
+ if (point === void 0) return;
7627
+ setStrokes((current) => current.length === 0 ? current : [...current.slice(0, -1), {
7628
+ ...current.at(-1),
7629
+ points: [...current.at(-1).points, point]
7630
+ }]);
7631
+ },
7632
+ onPointerUp: () => {
7633
+ drawingRef.current = false;
7634
+ },
7635
+ onPointerCancel: () => {
7636
+ drawingRef.current = false;
7637
+ }
7638
+ };
7639
+ const startMask = (action) => {
7640
+ resetGesture();
7641
+ setAnnotating(false);
7642
+ setStrokes([]);
7643
+ setErasing(action);
7644
+ };
7645
+ const sendMask = async () => {
7646
+ const image = imageRef.current;
7647
+ if (!erasing || strokes.length === 0 || image === null || sending) return;
7648
+ setSending(true);
7649
+ try {
7650
+ const canvas = document.createElement("canvas");
7651
+ canvas.width = image.naturalWidth;
7652
+ canvas.height = image.naturalHeight;
7653
+ paintEraseMask(canvas.getContext("2d"), strokes, canvas.width, canvas.height);
7654
+ const mask = await new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(/* @__PURE__ */ new Error("Mask")), "image/png"));
7655
+ await erasing.onInvoke({
7656
+ mask,
7657
+ item,
7658
+ src: item.src,
7659
+ annotations: []
7660
+ });
7661
+ setErasing(void 0);
7662
+ setStrokes([]);
7663
+ if (erasing.closeOnSuccess) service.close();
7664
+ } finally {
7665
+ setSending(false);
7666
+ }
7667
+ };
7324
7668
  const addAnnotation = (event) => {
7325
7669
  if (!annotating || event.target.closest(".dcsiv-annotation")) return;
7326
7670
  const bounds = surfaceRef.current?.getBoundingClientRect();
@@ -7452,7 +7796,8 @@ window.__ModuleLoader__.load({
7452
7796
  item,
7453
7797
  service,
7454
7798
  revision: request.revision,
7455
- t
7799
+ t,
7800
+ onStartMask: startMask
7456
7801
  }, `${request.revision}:${index}:${action.id}`))
7457
7802
  ]
7458
7803
  })
@@ -7466,142 +7811,194 @@ window.__ModuleLoader__.load({
7466
7811
  }),
7467
7812
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7468
7813
  className: "dcsiv-workspace",
7469
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
7470
- ref: stageRef,
7471
- className: "dcsiv-stage",
7472
- "data-dragging": dragging,
7473
- "data-annotating": annotating,
7474
- onClick: (event) => {
7475
- if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
7476
- },
7477
- ...!annotating ? pointerHandlers : {},
7478
- onDoubleClick: () => {
7479
- if (transform.zoom === 1) actual();
7480
- else fit();
7481
- },
7482
- children: [
7483
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7484
- ref: surfaceRef,
7485
- className: "dcsiv-surface",
7486
- onClick: addAnnotation,
7487
- style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
7488
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
7489
- ref: imageRef,
7490
- className: "dcsiv-image",
7491
- src: item.src,
7492
- alt: item.name,
7493
- draggable: "false",
7494
- onLoad: () => {
7495
- measure();
7496
- }
7497
- }, `${request.revision}:${item.id}`)
7498
- }),
7499
- annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7500
- className: "dcsiv-annotation",
7501
- "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
7502
- "data-y": annotation.y < .28 ? "down" : "up",
7503
- style: annotationPosition(annotation),
7504
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7505
- type: "button",
7506
- className: "dcsiv-pin",
7507
- "data-active": selected === annotation.id,
7508
- "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7509
- onClick: (event) => {
7510
- event.stopPropagation();
7511
- const opening = selected !== annotation.id;
7512
- setSelected(opening ? annotation.id : void 0);
7513
- if (opening) setFocusNote(annotation.id);
7514
- },
7515
- children: position + 1
7516
- }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7517
- className: "dcsiv-inline-note",
7518
- "data-note-id": annotation.id,
7519
- onClick: (event) => event.stopPropagation(),
7520
- children: [
7521
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7522
- className: "dcsiv-inline-index",
7523
- children: position + 1
7524
- }),
7525
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7526
- value: annotation.note,
7527
- rows: 1,
7528
- "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7529
- placeholder: t("imageAnnotationPlaceholder"),
7530
- onChange: (event) => {
7531
- const note = event.target.value;
7532
- setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
7533
- ...entry,
7534
- note
7535
- } : entry));
7536
- },
7537
- onKeyDown: (event) => {
7538
- if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
7539
- event.preventDefault();
7814
+ children: [
7815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
7816
+ ref: stageRef,
7817
+ className: "dcsiv-stage",
7818
+ "data-dragging": dragging,
7819
+ "data-annotating": annotating || erasing !== void 0,
7820
+ onClick: (event) => {
7821
+ if (event.target === event.currentTarget && !annotating && !erasing && transform.zoom === 1) service.close();
7822
+ },
7823
+ ...!annotating && !erasing ? pointerHandlers : {},
7824
+ onDoubleClick: () => {
7825
+ if (transform.zoom === 1) actual();
7826
+ else fit();
7827
+ },
7828
+ children: [
7829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7830
+ ref: surfaceRef,
7831
+ className: "dcsiv-surface",
7832
+ onClick: addAnnotation,
7833
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
7834
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
7835
+ ref: imageRef,
7836
+ className: "dcsiv-image",
7837
+ src: item.src,
7838
+ alt: item.name,
7839
+ draggable: "false",
7840
+ onLoad: () => {
7841
+ measure();
7842
+ }
7843
+ }, `${request.revision}:${item.id}`), erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
7844
+ ref: maskRef,
7845
+ className: "dcsiv-mask",
7846
+ width: imageRef.current?.naturalWidth || 1,
7847
+ height: imageRef.current?.naturalHeight || 1,
7848
+ "aria-label": t("imageEraseHint"),
7849
+ ...maskHandlers
7850
+ }) : null]
7851
+ }),
7852
+ annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7853
+ className: "dcsiv-annotation",
7854
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
7855
+ "data-y": annotation.y < .28 ? "down" : "up",
7856
+ style: annotationPosition(annotation),
7857
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7858
+ type: "button",
7859
+ className: "dcsiv-pin",
7860
+ "data-active": selected === annotation.id,
7861
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7862
+ onClick: (event) => {
7863
+ event.stopPropagation();
7864
+ const opening = selected !== annotation.id;
7865
+ setSelected(opening ? annotation.id : void 0);
7866
+ if (opening) setFocusNote(annotation.id);
7867
+ },
7868
+ children: position + 1
7869
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7870
+ className: "dcsiv-inline-note",
7871
+ "data-note-id": annotation.id,
7872
+ onClick: (event) => event.stopPropagation(),
7873
+ children: [
7874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7875
+ className: "dcsiv-inline-index",
7876
+ children: position + 1
7877
+ }),
7878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7879
+ value: annotation.note,
7880
+ rows: 1,
7881
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7882
+ placeholder: t("imageAnnotationPlaceholder"),
7883
+ onChange: (event) => {
7884
+ const note = event.target.value;
7885
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
7886
+ ...entry,
7887
+ note
7888
+ } : entry));
7889
+ },
7890
+ onKeyDown: (event) => {
7891
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
7892
+ event.preventDefault();
7893
+ event.stopPropagation();
7894
+ event.nativeEvent?.stopImmediatePropagation?.();
7895
+ setSelected(void 0);
7896
+ }
7897
+ }
7898
+ }),
7899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7900
+ type: "button",
7901
+ className: "dcsiv-note-remove",
7902
+ "aria-label": t("imageRemoveAnnotation"),
7903
+ onClick: (event) => {
7540
7904
  event.stopPropagation();
7541
- event.nativeEvent?.stopImmediatePropagation?.();
7905
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
7542
7906
  setSelected(void 0);
7543
- }
7544
- }
7545
- }),
7546
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7547
- type: "button",
7548
- className: "dcsiv-note-remove",
7549
- "aria-label": t("imageRemoveAnnotation"),
7550
- onClick: (event) => {
7551
- event.stopPropagation();
7552
- setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
7553
- setSelected(void 0);
7554
- },
7555
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, {})
7556
- })
7557
- ]
7558
- }) : null]
7559
- }, annotation.id)),
7560
- showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7907
+ },
7908
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, {})
7909
+ })
7910
+ ]
7911
+ }) : null]
7912
+ }, annotation.id)),
7913
+ showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7914
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7915
+ type: "button",
7916
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
7917
+ "aria-label": t("imagePrevious"),
7918
+ onClick: (event) => {
7919
+ event.stopPropagation();
7920
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
7921
+ },
7922
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronLeftOutline14, {})
7923
+ }),
7924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7925
+ type: "button",
7926
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
7927
+ "aria-label": t("imageNext"),
7928
+ onClick: (event) => {
7929
+ event.stopPropagation();
7930
+ setIndex((value) => (value + 1) % request.items.length);
7931
+ },
7932
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
7933
+ }),
7934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7935
+ className: "dcsiv-counter",
7936
+ children: [
7937
+ index + 1,
7938
+ " / ",
7939
+ request.items.length
7940
+ ]
7941
+ })
7942
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7943
+ className: "dcsiv-hint",
7944
+ children: t("imageAnnotateHint")
7945
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7946
+ className: "dcsiv-hint",
7947
+ children: t("imageZoomHint")
7948
+ }) : null
7949
+ ]
7950
+ }),
7951
+ erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7952
+ className: "dcsiv-erase-bar",
7953
+ role: "toolbar",
7954
+ "aria-label": t("imageErase"),
7955
+ children: [
7956
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageEraseHint") }),
7957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("imageBrushSize"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7958
+ type: "range",
7959
+ min: "0.01",
7960
+ max: "0.15",
7961
+ step: "0.01",
7962
+ value: brush,
7963
+ onChange: (event) => setBrush(Number(event.target.value))
7964
+ })] }),
7561
7965
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7562
7966
  type: "button",
7563
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
7564
- "aria-label": t("imagePrevious"),
7565
- onClick: (event) => {
7566
- event.stopPropagation();
7567
- setIndex((value) => (value - 1 + request.items.length) % request.items.length);
7568
- },
7569
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronLeftOutline14, {})
7967
+ className: "dcsiv-button",
7968
+ disabled: strokes.length === 0 || sending,
7969
+ onClick: () => setStrokes((current) => current.slice(0, -1)),
7970
+ children: t("imageUndo")
7570
7971
  }),
7571
7972
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7572
7973
  type: "button",
7573
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
7574
- "aria-label": t("imageNext"),
7575
- onClick: (event) => {
7576
- event.stopPropagation();
7577
- setIndex((value) => (value + 1) % request.items.length);
7974
+ className: "dcsiv-button",
7975
+ disabled: sending,
7976
+ onClick: () => {
7977
+ setErasing(void 0);
7978
+ setStrokes([]);
7578
7979
  },
7579
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
7980
+ children: t("cancel")
7580
7981
  }),
7581
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7582
- className: "dcsiv-counter",
7583
- children: [
7584
- index + 1,
7585
- " / ",
7586
- request.items.length
7587
- ]
7982
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7983
+ type: "button",
7984
+ className: "dcsiv-button dcsiv-primary",
7985
+ disabled: strokes.length === 0 || sending,
7986
+ onClick: () => {
7987
+ sendMask();
7988
+ },
7989
+ children: sending ? t("imageEditPreparing") : t("imageSend")
7588
7990
  })
7589
- ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7590
- className: "dcsiv-hint",
7591
- children: t("imageAnnotateHint")
7592
- }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7593
- className: "dcsiv-hint",
7594
- children: t("imageZoomHint")
7595
- }) : null
7596
- ]
7597
- }), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7598
- type: "button",
7599
- className: "dcsiv-copy-notes",
7600
- onClick: () => {
7601
- copyNotes();
7602
- },
7603
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
7604
- }) : null]
7991
+ ]
7992
+ }) : null,
7993
+ annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7994
+ type: "button",
7995
+ className: "dcsiv-copy-notes",
7996
+ onClick: () => {
7997
+ copyNotes();
7998
+ },
7999
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
8000
+ }) : null
8001
+ ]
7605
8002
  })
7606
8003
  ]
7607
8004
  });
@@ -7622,6 +8019,12 @@ window.__ModuleLoader__.load({
7622
8019
  pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
7623
8020
  errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
7624
8021
  closeOnSuccess: action.closeOnSuccess === true,
8022
+ kind: [
8023
+ "tool",
8024
+ "mask",
8025
+ "choice"
8026
+ ].includes(action.kind) ? action.kind : "edit",
8027
+ options: Array.isArray(action.options) ? action.options.filter((option) => typeof option?.value === "string" && typeof option?.label === "string") : [],
7625
8028
  onInvoke: action.onInvoke
7626
8029
  }];
7627
8030
  }) : [];
@@ -8549,7 +8952,7 @@ window.__ModuleLoader__.load({
8549
8952
  }
8550
8953
  //#endregion
8551
8954
  //#region src/version.js
8552
- const PACKAGE_VERSION = "2.5.7";
8955
+ const PACKAGE_VERSION = "2.5.8";
8553
8956
  //#endregion
8554
8957
  //#region src/client-recovery.js
8555
8958
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
@@ -12161,7 +12564,8 @@ window.__ModuleLoader__.load({
12161
12564
  imageViewer.close();
12162
12565
  liveOpen("sketch", document.activeElement, new File([blob], name || "image.png", { type: blob.type || "image/png" }));
12163
12566
  };
12164
- const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false) => {
12567
+ const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false, extraFiles) => {
12568
+ const tool = Array.isArray(extraFiles);
12165
12569
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12166
12570
  return withComposerSession(sessions, sessionId, async (actx) => {
12167
12571
  if (conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
@@ -12173,14 +12577,15 @@ window.__ModuleLoader__.load({
12173
12577
  const reference = await createAnnotatedImageReference(blob, annotations);
12174
12578
  files.push(new File([reference], referenceName, { type: "image/png" }));
12175
12579
  }
12580
+ if (tool) files.push(...extraFiles);
12176
12581
  lifetime.signal.throwIfAborted();
12177
12582
  const input = conversation.input.for(actx);
12178
12583
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12179
12584
  if (files.length) attachImageFiles(conversation, input, files, sessionId);
12180
12585
  openComposerSession(sessions, ctx.get("uiWorkspace"), sessionId);
12181
- if (sourceInDraft && !annotations.length) return;
12586
+ if (sourceInDraft && !annotations.length && !tool) return;
12182
12587
  if (!input.state.getSnapshot().draft.trim()) input.setDraft(draft);
12183
- else if (annotations.length) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12588
+ else if (annotations.length || tool) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12184
12589
  else input.notify("info", draft);
12185
12590
  });
12186
12591
  };
package/lib/index.js CHANGED
@@ -2049,7 +2049,7 @@ function inheritedOriginalImageRef(session, assetId) {
2049
2049
  }
2050
2050
  //#endregion
2051
2051
  //#region src/version.js
2052
- const PACKAGE_VERSION = "2.5.7";
2052
+ const PACKAGE_VERSION = "2.5.8";
2053
2053
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2054
2054
  //#endregion
2055
2055
  //#region src/codex-images.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.5.7",
3
+ "version": "2.5.8",
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",