dsh-codex-subscription 2.5.7 → 2.5.9

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/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,47 @@ 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$8 = /*#__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$7 = /*#__PURE__*/ react.forwardRef(ArrowsPointingOutIcon);
920
+ //#endregion
921
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ChatBubbleLeftEllipsisIcon.js
922
+ function ChatBubbleLeftEllipsisIcon({ title, titleId, ...props }, svgRef) {
923
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
924
+ xmlns: "http://www.w3.org/2000/svg",
925
+ fill: "none",
926
+ viewBox: "0 0 24 24",
927
+ strokeWidth: 1.5,
928
+ stroke: "currentColor",
929
+ "aria-hidden": "true",
930
+ "data-slot": "icon",
931
+ ref: svgRef,
932
+ "aria-labelledby": titleId
933
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
934
+ strokeLinecap: "round",
935
+ strokeLinejoin: "round",
936
+ d: "M8.625 9.75a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375m-13.5 3.01c0 1.6 1.123 2.994 2.707 3.227 1.087.16 2.185.283 3.293.369V21l4.184-4.183a1.14 1.14 0 0 1 .778-.332 48.294 48.294 0 0 0 5.83-.498c1.585-.233 2.708-1.626 2.708-3.228V6.741c0-1.602-1.123-2.995-2.707-3.228A48.394 48.394 0 0 0 12 3c-2.392 0-4.744.175-7.043.513C3.373 3.746 2.25 5.14 2.25 6.741v6.018Z"
937
+ }));
938
+ }
939
+ const ForwardRef$6 = /*#__PURE__*/ react.forwardRef(ChatBubbleLeftEllipsisIcon);
808
940
  //#endregion
809
941
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
810
942
  function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
@@ -824,7 +956,47 @@ window.__ModuleLoader__.load({
824
956
  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
957
  }));
826
958
  }
827
- const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
959
+ const ForwardRef$5 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
960
+ //#endregion
961
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PaintBrushIcon.js
962
+ function PaintBrushIcon({ title, titleId, ...props }, svgRef) {
963
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
964
+ xmlns: "http://www.w3.org/2000/svg",
965
+ fill: "none",
966
+ viewBox: "0 0 24 24",
967
+ strokeWidth: 1.5,
968
+ stroke: "currentColor",
969
+ "aria-hidden": "true",
970
+ "data-slot": "icon",
971
+ ref: svgRef,
972
+ "aria-labelledby": titleId
973
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
974
+ strokeLinecap: "round",
975
+ strokeLinejoin: "round",
976
+ 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"
977
+ }));
978
+ }
979
+ const ForwardRef$4 = /*#__PURE__*/ react.forwardRef(PaintBrushIcon);
980
+ //#endregion
981
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PencilIcon.js
982
+ function PencilIcon({ 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: "m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125"
997
+ }));
998
+ }
999
+ const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(PencilIcon);
828
1000
  //#endregion
829
1001
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PhotoIcon.js
830
1002
  function PhotoIcon({ title, titleId, ...props }, svgRef) {
@@ -844,7 +1016,27 @@ window.__ModuleLoader__.load({
844
1016
  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
1017
  }));
846
1018
  }
847
- const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
1019
+ const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
1020
+ //#endregion
1021
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ScissorsIcon.js
1022
+ function ScissorsIcon({ title, titleId, ...props }, svgRef) {
1023
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
1024
+ xmlns: "http://www.w3.org/2000/svg",
1025
+ fill: "none",
1026
+ viewBox: "0 0 24 24",
1027
+ strokeWidth: 1.5,
1028
+ stroke: "currentColor",
1029
+ "aria-hidden": "true",
1030
+ "data-slot": "icon",
1031
+ ref: svgRef,
1032
+ "aria-labelledby": titleId
1033
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
1034
+ strokeLinecap: "round",
1035
+ strokeLinejoin: "round",
1036
+ 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"
1037
+ }));
1038
+ }
1039
+ const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(ScissorsIcon);
848
1040
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
849
1041
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
850
1042
  function decodeOriginalImageRef(value) {
@@ -1032,32 +1224,41 @@ window.__ModuleLoader__.load({
1032
1224
  errorLabel: t("imageDownloadFailed"),
1033
1225
  onInvoke: downloadOriginal
1034
1226
  },
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
- }] : []]
1227
+ actions: !features.imageEditing ? [] : [
1228
+ {
1229
+ id: "continue-editing",
1230
+ label: t("imageEdit"),
1231
+ pendingLabel: t("imageEditPreparing"),
1232
+ errorLabel: t("imageEditFailed"),
1233
+ closeOnSuccess: true,
1234
+ onInvoke: ({ annotations = [] }) => {
1235
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1236
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1237
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
1238
+ return attachForEdit(src, sourceName, buildImageEditDraft({
1239
+ annotations,
1240
+ translate: t,
1241
+ width: attachment.width,
1242
+ height: attachment.height,
1243
+ sourceName,
1244
+ referenceName
1245
+ }), annotations, referenceName);
1246
+ }
1247
+ },
1248
+ ...officialImageTools({
1249
+ src,
1250
+ name: downloadName,
1251
+ t,
1252
+ attachForEdit
1253
+ }),
1254
+ ...features.imageSketch && openSketchImage ? [{
1255
+ id: "sketch",
1256
+ label: t("imageToSketch"),
1257
+ pendingLabel: t("imageEditPreparing"),
1258
+ errorLabel: t("imageEditFailed"),
1259
+ onInvoke: () => openSketchImage(src, downloadName)
1260
+ }] : []
1261
+ ]
1061
1262
  }],
1062
1263
  opener: triggerRef.current,
1063
1264
  source: "codex-generated",
@@ -1107,7 +1308,7 @@ window.__ModuleLoader__.load({
1107
1308
  showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1108
1309
  className: "codexImageToolRow",
1109
1310
  children: [
1110
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
1311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$8 : failed ? ForwardRef$5 : ForwardRef$2, {
1111
1312
  className: "codexImageToolIcon",
1112
1313
  "aria-hidden": "true"
1113
1314
  }),
@@ -5273,7 +5474,7 @@ window.__ModuleLoader__.load({
5273
5474
  const IconCopyOutline16 = _deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconCopyOutlineRegular;
5274
5475
  const IconDownloadOutline16 = _deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconDownloadOutlineRegular;
5275
5476
  const IconEditOutline16 = _deepseek_ai_dsh_client_ui_primitives.IconEditOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconEditOutlineRegular;
5276
- const IconFullscreenOutline16 = _deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutlineRegular;
5477
+ _deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16 ?? _deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutlineRegular;
5277
5478
  //#endregion
5278
5479
  //#region src/image-workspace.jsx
5279
5480
  function ImageWorkspace(props) {
@@ -5525,6 +5726,21 @@ window.__ModuleLoader__.load({
5525
5726
  sketchEditableFile: "可编辑草稿",
5526
5727
  sketchFormatHint: "PNG 为合并图片;可编辑草稿保留原生笔画,可重新导入(32 MB 内)。",
5527
5728
  imageToSketch: "进入草图",
5729
+ imageRemoveBackground: "移除背景",
5730
+ imageRemoveBackgroundPrompt: "移除此图像的背景。保持所有前景主体不变且完整,边缘干净平滑。将背景设为透明。",
5731
+ imageErase: "擦除",
5732
+ imageEraseHint: "在要移除的内容上涂抹",
5733
+ imageErasePrompt: "从第一张图像中移除第二张图像中标记的区域",
5734
+ imageBrushSize: "画笔大小",
5735
+ imageUndo: "撤销",
5736
+ imageSend: "发送",
5737
+ imageResize: "调整大小",
5738
+ imageResizePrompt: "将宽高比设为 {aspectRatio}",
5739
+ imageRatio_square: "正方形",
5740
+ imageRatio_portrait: "竖向",
5741
+ imageRatio_story: "故事版式",
5742
+ imageRatio_landscape: "横向",
5743
+ imageRatio_widescreen: "宽屏",
5528
5744
  sketchPictures: "图片",
5529
5745
  sketchPictureAdd: "添加",
5530
5746
  sketchPictureSelect: "选择图片",
@@ -5591,7 +5807,7 @@ window.__ModuleLoader__.load({
5591
5807
  imageEntryPoints_on: "显示",
5592
5808
  imageEntryPoints_off: "隐藏",
5593
5809
  imageBrowsing: "图片浏览",
5594
- imageBrowsingHint: "预览粘贴图片与会话图片,标注后回到输入框编辑。",
5810
+ imageBrowsingHint: "预览粘贴图片与会话图片,可标注、移除背景、擦除、调整大小。",
5595
5811
  imageBrowsing_on: "增强",
5596
5812
  imageBrowsing_off: "DSH 默认",
5597
5813
  imageGroupMixed: "保留原设置",
@@ -5811,7 +6027,7 @@ window.__ModuleLoader__.load({
5811
6027
  resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
5812
6028
  resetAccountChanged: "登录账号已变更,请重新开始。",
5813
6029
  resetUncertain: "服务端返回结果不确定。请再次确认,插件会复用同一个请求,不会另外发起一次重置。",
5814
- creditsNote: "额外 Credits、消费上限、重置次数分别显示。",
6030
+ creditsNote: "下方的额外 Credits 和额度重置与套餐额度分开计算。",
5815
6031
  creditsExpires: "预计 {date} 到期,还剩 {days} 天",
5816
6032
  creditsExpiredToday: "今天 {date} 到期",
5817
6033
  creditsExpiryEstimate: "(估计:官方公布的 Pro 200 补偿 Credits 到期日,接口未返回,以 ChatGPT 使用情况页为准)",
@@ -5890,7 +6106,7 @@ window.__ModuleLoader__.load({
5890
6106
  modelMenuAria: "模型、推理等级、速度与输出详略",
5891
6107
  modelLabel: "模型",
5892
6108
  effortLabel: "推理等级",
5893
- providerDefault: "Default",
6109
+ providerDefault: "默认",
5894
6110
  selectModel: "选择模型",
5895
6111
  modelsLoading: "正在读取模型…",
5896
6112
  modelsEmpty: "没有可用模型;请先登录,并确认网络可用。",
@@ -6097,6 +6313,21 @@ window.__ModuleLoader__.load({
6097
6313
  sketchEditableFile: "Editable draft",
6098
6314
  sketchFormatHint: "PNG is flattened; editable drafts retain native strokes and can be imported again (up to 32 MB).",
6099
6315
  imageToSketch: "Open in sketch",
6316
+ imageRemoveBackground: "Remove background",
6317
+ imageRemoveBackgroundPrompt: "Remove the background from this image. Keep all foreground subjects unchanged and fully intact, with clean, smooth edges. Make the background transparent.",
6318
+ imageErase: "Erase",
6319
+ imageEraseHint: "Brush over what you want to remove",
6320
+ imageErasePrompt: "Remove the area marked in the second image from the first image",
6321
+ imageBrushSize: "Brush size",
6322
+ imageUndo: "Undo",
6323
+ imageSend: "Send",
6324
+ imageResize: "Resize",
6325
+ imageResizePrompt: "Make the aspect ratio {aspectRatio}",
6326
+ imageRatio_square: "Square",
6327
+ imageRatio_portrait: "Portrait",
6328
+ imageRatio_story: "Story",
6329
+ imageRatio_landscape: "Landscape",
6330
+ imageRatio_widescreen: "Widescreen",
6100
6331
  sketchPictures: "Images",
6101
6332
  sketchPictureAdd: "Add",
6102
6333
  sketchPictureSelect: "Select image",
@@ -6163,7 +6394,7 @@ window.__ModuleLoader__.load({
6163
6394
  imageEntryPoints_on: "Show",
6164
6395
  imageEntryPoints_off: "Hide",
6165
6396
  imageBrowsing: "Image browsing",
6166
- imageBrowsingHint: "Preview pasted and conversation images, annotate and return to the composer.",
6397
+ imageBrowsingHint: "Preview pasted and conversation images; annotate, remove the background, erase or resize.",
6167
6398
  imageBrowsing_on: "Enhanced",
6168
6399
  imageBrowsing_off: "DSH default",
6169
6400
  imageGroupMixed: "Keep existing",
@@ -6383,7 +6614,7 @@ window.__ModuleLoader__.load({
6383
6614
  resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
6384
6615
  resetAccountChanged: "The signed-in account changed. Start again.",
6385
6616
  resetUncertain: "The server result is uncertain. Confirm again to check the same request; the plugin will not start a separate reset.",
6386
- creditsNote: "Extra Credits, spending caps, and resets are separate items.",
6617
+ creditsNote: "Extra Credits and quota resets below are counted separately from the plan quota.",
6387
6618
  creditsExpires: "Expires {date} ({days} days left)",
6388
6619
  creditsExpiredToday: "Expires today ({date})",
6389
6620
  creditsExpiryEstimate: "(Estimate: the published expiry of the Pro 200 compensation Credits; the API does not report it, so check ChatGPT usage settings)",
@@ -6597,7 +6828,7 @@ window.__ModuleLoader__.load({
6597
6828
  .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}
6598
6829
  .codexSubscriptionRecover .codexSubscriptionError{flex:1}
6599
6830
  .codexSubscriptionRecover button{flex:0 0 auto}
6600
- .codexSubscriptionDiagnostics{color:var(--dsw-alias-label-secondary)}
6831
+ .codexSubscriptionDiagnostics{color:var(--dsw-alias-label-secondary)}.codexSubscriptionDiagnostics h3{color:var(--dsw-alias-label-primary)}
6601
6832
  .codexSubscriptionDiagnostics ul{margin:0;padding-left:18px;font-size:13px;line-height:20px}
6602
6833
  .codexSubscriptionDiagnostics li+li{margin-top:6px}
6603
6834
  .codexSubscriptionDiagnostics li strong{font-weight:600;color:var(--dsw-alias-label-primary)}
@@ -6919,10 +7150,13 @@ window.__ModuleLoader__.load({
6919
7150
  .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
7151
  .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
7152
  .dcsiv-hint{display:none}
7153
+ .dcsiv-mask{position:absolute;inset:0;width:100%;height:100%;border-radius:12px;cursor:crosshair;touch-action:none}
7154
+ .dcsiv-tool-icon{width:16px;height:16px;flex:none}.dcsiv-icon-button,.dcsiv-download[data-compact="true"]{width:34px;padding:0!important;justify-content:center}.dcsiv-zoom{min-width:52px;font-variant-numeric:tabular-nums}.dcsiv-divider{width:1px;height:18px;margin:0 4px;background:rgba(255,255,255,.14)}.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{outline:none;background:rgba(255,255,255,.1)}
7155
+ .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}.dcsiv-primary[data-state="failed"]{color:#c62828!important}.dcsiv-brush-cursor{position:fixed;z-index:9;pointer-events:none;border:1.5px solid #fff;border-radius:50%;corner-shape:round;box-shadow:0 0 0 1px rgba(0,0,0,.45),inset 0 0 0 1px rgba(0,0,0,.25);transform:translate(-50%,-50%)}@media(pointer:fine){.dcsiv-mask{cursor:none}}.dcsiv-button[data-state="pending"] .dcsiv-tool-icon{animation:dcsiv-pulse 1s ease-in-out infinite}.dcsiv-button[data-state="failed"]{color:#ff8a80!important}@keyframes dcsiv-pulse{50%{opacity:.3}}
6922
7156
  .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
7157
  .dcsiv-edit-short{display:none}
6924
7158
  @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}}
6925
- @media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}}
7159
+ @media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}.dcsiv-tool-icon{animation:none!important}}
6926
7160
  `;
6927
7161
  //#endregion
6928
7162
  //#region src/subscription-image-transform.js
@@ -7102,6 +7336,20 @@ window.__ModuleLoader__.load({
7102
7336
  }
7103
7337
  //#endregion
7104
7338
  //#region src/subscription-image-viewer.jsx
7339
+ const TOOL_ICONS = {
7340
+ "remove-background": ForwardRef$1,
7341
+ erase: ForwardRef$4,
7342
+ resize: ForwardRef$7,
7343
+ sketch: ForwardRef$3,
7344
+ annotate: ForwardRef$6
7345
+ };
7346
+ const ToolIcon = ({ id }) => {
7347
+ const Icon = TOOL_ICONS[id];
7348
+ return Icon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
7349
+ className: "dcsiv-tool-icon",
7350
+ "aria-hidden": "true"
7351
+ }) : null;
7352
+ };
7105
7353
  const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
7106
7354
  const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
7107
7355
  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,23 +7360,54 @@ window.__ModuleLoader__.load({
7112
7360
  const noteText = (annotations, t) => annotations.map((annotation, index) => {
7113
7361
  return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
7114
7362
  }).filter((line) => !line.endsWith(": ")).join("\n");
7115
- function ViewerAction({ action, annotations, item, service, revision, t }) {
7363
+ function ViewerAction({ action, annotations, item, service, revision, t, onStartMask }) {
7116
7364
  const [state, setState] = (0, react.useState)("idle");
7365
+ const [menu, setMenu] = (0, react.useState)();
7117
7366
  const active = (0, react.useRef)(true);
7367
+ const menuRef = (0, react.useRef)(null);
7368
+ const menuButtonRef = (0, react.useRef)(null);
7118
7369
  (0, react.useEffect)(() => {
7119
7370
  active.current = true;
7120
7371
  return () => {
7121
7372
  active.current = false;
7122
7373
  };
7123
7374
  }, []);
7124
- const invoke = async () => {
7375
+ (0, react.useEffect)(() => {
7376
+ if (menu === void 0) return void 0;
7377
+ menuRef.current?.querySelector("[role=\"menuitem\"]")?.focus();
7378
+ const onKeyDown = (event) => {
7379
+ const items = [...menuRef.current?.querySelectorAll("[role=\"menuitem\"]") ?? []];
7380
+ if (event.key === "Escape") {
7381
+ event.preventDefault();
7382
+ event.stopPropagation();
7383
+ setMenu(void 0);
7384
+ menuButtonRef.current?.focus();
7385
+ } else if ((event.key === "ArrowDown" || event.key === "ArrowUp") && items.length > 0) {
7386
+ event.preventDefault();
7387
+ event.stopPropagation();
7388
+ const step = event.key === "ArrowDown" ? 1 : -1;
7389
+ items[(items.indexOf(document.activeElement) + step + items.length) % items.length].focus();
7390
+ } else if (event.key === "Tab") setMenu(void 0);
7391
+ };
7392
+ const onPointerDown = (event) => {
7393
+ if (!menuRef.current?.contains(event.target) && !menuButtonRef.current?.contains(event.target)) setMenu(void 0);
7394
+ };
7395
+ window.addEventListener("keydown", onKeyDown, true);
7396
+ window.addEventListener("pointerdown", onPointerDown, true);
7397
+ return () => {
7398
+ window.removeEventListener("keydown", onKeyDown, true);
7399
+ window.removeEventListener("pointerdown", onPointerDown, true);
7400
+ };
7401
+ }, [menu]);
7402
+ const invoke = async (extra = {}) => {
7125
7403
  if (state === "pending") return;
7126
7404
  setState("pending");
7127
7405
  try {
7128
7406
  await action.onInvoke({
7129
7407
  annotations,
7130
7408
  item,
7131
- src: item.src
7409
+ src: item.src,
7410
+ ...extra
7132
7411
  });
7133
7412
  if (!active.current || service.getSnapshot()?.revision !== revision) return;
7134
7413
  setState("idle");
@@ -7138,6 +7417,74 @@ window.__ModuleLoader__.load({
7138
7417
  }
7139
7418
  };
7140
7419
  const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
7420
+ if (action.kind === "mask") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7421
+ type: "button",
7422
+ className: "dcsiv-button dcsiv-icon-button",
7423
+ title: action.label,
7424
+ "aria-label": action.label,
7425
+ onClick: () => onStartMask(action),
7426
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id })
7427
+ });
7428
+ if (action.kind === "tool") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7429
+ type: "button",
7430
+ className: "dcsiv-button dcsiv-icon-button",
7431
+ title: label,
7432
+ "aria-label": label,
7433
+ "data-state": state,
7434
+ disabled: state === "pending",
7435
+ onClick: () => {
7436
+ invoke();
7437
+ },
7438
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id })
7439
+ });
7440
+ if (action.id === "sketch" && state === "idle") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7441
+ type: "button",
7442
+ className: "dcsiv-button dcsiv-icon-button",
7443
+ title: action.label,
7444
+ "aria-label": action.label,
7445
+ onClick: () => {
7446
+ invoke();
7447
+ },
7448
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: "sketch" })
7449
+ });
7450
+ if (action.kind === "choice") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7451
+ ref: menuButtonRef,
7452
+ type: "button",
7453
+ "aria-haspopup": "menu",
7454
+ "aria-expanded": menu !== void 0,
7455
+ "data-state": state,
7456
+ disabled: state === "pending",
7457
+ onClick: (event) => {
7458
+ const box = event.currentTarget.getBoundingClientRect();
7459
+ const host = event.currentTarget.closest(".dcsiv-root");
7460
+ setMenu((value) => value === void 0 && host ? {
7461
+ host,
7462
+ style: {
7463
+ left: box.left,
7464
+ bottom: window.innerHeight - box.top + 8
7465
+ }
7466
+ } : void 0);
7467
+ },
7468
+ className: "dcsiv-button dcsiv-icon-button",
7469
+ title: label,
7470
+ "aria-label": label,
7471
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id })
7472
+ }), menu ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7473
+ ref: menuRef,
7474
+ className: "dcsiv-menu",
7475
+ role: "menu",
7476
+ "aria-label": action.label,
7477
+ style: menu.style,
7478
+ children: action.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7479
+ type: "button",
7480
+ role: "menuitem",
7481
+ onClick: () => {
7482
+ setMenu(void 0);
7483
+ invoke({ option: option.value });
7484
+ },
7485
+ children: option.label
7486
+ }, option.value))
7487
+ }), menu.host) : null] });
7141
7488
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7142
7489
  type: "button",
7143
7490
  className: "dcsiv-button dcsiv-edit-action",
@@ -7198,10 +7545,13 @@ window.__ModuleLoader__.load({
7198
7545
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7199
7546
  type: "button",
7200
7547
  className: "dcsiv-download",
7548
+ "data-compact": state === "idle",
7549
+ title: label,
7550
+ "aria-label": label,
7201
7551
  onClick: () => {
7202
7552
  invoke();
7203
7553
  },
7204
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconDownloadOutline16, {}), state === "idle" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7205
7555
  className: "dcsiv-label",
7206
7556
  children: state === "pending" ? `${label} ${progress}% · ${t("cancel")}` : label
7207
7557
  })]
@@ -7224,6 +7574,22 @@ window.__ModuleLoader__.load({
7224
7574
  const [selected, setSelected] = (0, react.useState)();
7225
7575
  const [focusNote, setFocusNote] = (0, react.useState)();
7226
7576
  const [copied, setCopied] = (0, react.useState)(false);
7577
+ const [erasing, setErasing] = (0, react.useState)();
7578
+ const [strokes, setStrokes] = (0, react.useState)([]);
7579
+ const [brush, setBrush] = (0, react.useState)(.05);
7580
+ const [sending, setSending] = (0, react.useState)(false);
7581
+ const [sendFailed, setSendFailed] = (0, react.useState)(false);
7582
+ const [brushCursor, setBrushCursor] = (0, react.useState)();
7583
+ const eraseRef = (0, react.useRef)({
7584
+ erasing: void 0,
7585
+ sending: false
7586
+ });
7587
+ eraseRef.current = {
7588
+ erasing,
7589
+ sending
7590
+ };
7591
+ const drawingRef = (0, react.useRef)(false);
7592
+ const maskRef = (0, react.useRef)(null);
7227
7593
  const rootRef = (0, react.useRef)(null);
7228
7594
  annotationsByImageRef.current = annotationsByImage;
7229
7595
  (0, react.useEffect)(() => {
@@ -7253,6 +7619,28 @@ window.__ModuleLoader__.load({
7253
7619
  document.body.style.overflow = "hidden";
7254
7620
  rootRef.current?.focus();
7255
7621
  const onKeyDown = (event) => {
7622
+ if (eraseRef.current.erasing !== void 0) {
7623
+ const key = event.key.toLowerCase();
7624
+ if (event.key === "Escape") {
7625
+ event.preventDefault();
7626
+ if (!eraseRef.current.sending) {
7627
+ setErasing(void 0);
7628
+ setStrokes([]);
7629
+ }
7630
+ return;
7631
+ }
7632
+ if ((event.ctrlKey || event.metaKey) && key === "z") {
7633
+ event.preventDefault();
7634
+ if (!eraseRef.current.sending) setStrokes((current) => current.slice(0, -1));
7635
+ return;
7636
+ }
7637
+ if (event.key === "[" || event.key === "]") {
7638
+ event.preventDefault();
7639
+ setBrush((value) => Math.round(clamp(value + (event.key === "]" ? .01 : -.01), .01, .15) * 100) / 100);
7640
+ return;
7641
+ }
7642
+ if (event.key === "ArrowLeft" || event.key === "ArrowRight") return;
7643
+ }
7256
7644
  if (event.key === "Escape") {
7257
7645
  event.preventDefault();
7258
7646
  if (event.target instanceof Element && event.target.closest(".dcsiv-inline-note") !== null) {
@@ -7316,11 +7704,99 @@ window.__ModuleLoader__.load({
7316
7704
  (0, react.useEffect)(() => {
7317
7705
  setAnnotating(false);
7318
7706
  setSelected(void 0);
7707
+ setErasing(void 0);
7708
+ setStrokes([]);
7319
7709
  }, [
7320
7710
  item?.id,
7321
7711
  item?.src,
7322
7712
  request?.revision
7323
7713
  ]);
7714
+ (0, react.useEffect)(() => {
7715
+ const canvas = maskRef.current;
7716
+ if (!erasing || canvas === null) return;
7717
+ const context = canvas.getContext("2d");
7718
+ context.clearRect(0, 0, canvas.width, canvas.height);
7719
+ paintEraseStrokes(context, strokes, canvas.width, canvas.height, "rgba(255,59,48,.5)");
7720
+ }, [erasing, strokes]);
7721
+ const strokePoint = (event) => {
7722
+ const bounds = maskRef.current?.getBoundingClientRect();
7723
+ if (bounds === void 0) return void 0;
7724
+ return {
7725
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
7726
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1)
7727
+ };
7728
+ };
7729
+ const maskHandlers = {
7730
+ onPointerDown: (event) => {
7731
+ const point = strokePoint(event);
7732
+ if (point === void 0) return;
7733
+ event.stopPropagation();
7734
+ event.currentTarget.setPointerCapture?.(event.pointerId);
7735
+ drawingRef.current = true;
7736
+ setStrokes((current) => [...current, {
7737
+ size: brush / transformRef.current.zoom,
7738
+ points: [point]
7739
+ }]);
7740
+ },
7741
+ onPointerMove: (event) => {
7742
+ const bounds = maskRef.current?.getBoundingClientRect();
7743
+ if (bounds !== void 0) setBrushCursor({
7744
+ left: event.clientX,
7745
+ top: event.clientY,
7746
+ span: Math.min(bounds.width, bounds.height) / transformRef.current.zoom
7747
+ });
7748
+ if (!drawingRef.current) return;
7749
+ const point = strokePoint(event);
7750
+ if (point === void 0) return;
7751
+ setStrokes((current) => current.length === 0 ? current : [...current.slice(0, -1), {
7752
+ ...current.at(-1),
7753
+ points: [...current.at(-1).points, point]
7754
+ }]);
7755
+ },
7756
+ onPointerUp: () => {
7757
+ drawingRef.current = false;
7758
+ },
7759
+ onPointerCancel: () => {
7760
+ drawingRef.current = false;
7761
+ },
7762
+ onPointerLeave: () => {
7763
+ setBrushCursor(void 0);
7764
+ }
7765
+ };
7766
+ const startMask = (action) => {
7767
+ resetGesture();
7768
+ setAnnotating(false);
7769
+ setStrokes([]);
7770
+ setSendFailed(false);
7771
+ setBrushCursor(void 0);
7772
+ setErasing(action);
7773
+ };
7774
+ const sendMask = async () => {
7775
+ const image = imageRef.current;
7776
+ if (!erasing || strokes.length === 0 || image === null || sending) return;
7777
+ setSending(true);
7778
+ setSendFailed(false);
7779
+ try {
7780
+ const canvas = document.createElement("canvas");
7781
+ canvas.width = image.naturalWidth;
7782
+ canvas.height = image.naturalHeight;
7783
+ paintEraseMask(canvas.getContext("2d"), strokes, canvas.width, canvas.height);
7784
+ const mask = await new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(/* @__PURE__ */ new Error("Mask")), "image/png"));
7785
+ await erasing.onInvoke({
7786
+ mask,
7787
+ item,
7788
+ src: item.src,
7789
+ annotations: []
7790
+ });
7791
+ setErasing(void 0);
7792
+ setStrokes([]);
7793
+ if (erasing.closeOnSuccess) service.close();
7794
+ } catch {
7795
+ setSendFailed(true);
7796
+ } finally {
7797
+ setSending(false);
7798
+ }
7799
+ };
7324
7800
  const addAnnotation = (event) => {
7325
7801
  if (!annotating || event.target.closest(".dcsiv-annotation")) return;
7326
7802
  const bounds = surfaceRef.current?.getBoundingClientRect();
@@ -7382,18 +7858,19 @@ window.__ModuleLoader__.load({
7382
7858
  children: [
7383
7859
  request.annotations ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7384
7860
  type: "button",
7385
- className: "dcsiv-button",
7861
+ className: annotating ? "dcsiv-button" : "dcsiv-button dcsiv-icon-button",
7386
7862
  "data-active": annotating,
7863
+ title: annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
7387
7864
  "aria-label": annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
7388
7865
  "aria-pressed": annotating,
7389
7866
  onClick: () => {
7390
7867
  resetGesture();
7391
7868
  setAnnotating((value) => !value);
7392
7869
  },
7393
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEditOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7870
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: "annotate" }), annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7394
7871
  className: "dcsiv-label",
7395
- children: annotating ? t("imageAnnotateCancel") : t("imageAnnotate")
7396
- })]
7872
+ children: t("imageAnnotateCancel")
7873
+ }) : null]
7397
7874
  }) : null,
7398
7875
  annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7399
7876
  type: "button",
@@ -7415,32 +7892,27 @@ window.__ModuleLoader__.load({
7415
7892
  }) : null,
7416
7893
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7417
7894
  type: "button",
7418
- className: "dcsiv-button",
7419
- "aria-label": t("imageFit"),
7420
- onClick: fit,
7421
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconFullscreenOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7422
- className: "dcsiv-label",
7423
- children: t("imageFit")
7424
- })]
7425
- }),
7426
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7427
- type: "button",
7428
- className: "dcsiv-button",
7429
- onClick: actual,
7430
- children: t("imageActual")
7431
- }),
7432
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7433
- className: "dcsiv-zoom",
7895
+ className: "dcsiv-button dcsiv-zoom",
7896
+ title: transform.zoom === 1 ? t("imageActual") : t("imageFit"),
7897
+ "aria-label": transform.zoom === 1 ? t("imageActual") : t("imageFit"),
7898
+ onClick: () => {
7899
+ if (transform.zoom === 1) actual();
7900
+ else fit();
7901
+ },
7434
7902
  children: [Math.round(transform.zoom * pixelScale * 100), "%"]
7435
7903
  }),
7436
- item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
7904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7905
+ className: "dcsiv-divider",
7906
+ "aria-hidden": "true"
7907
+ }),
7908
+ item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
7437
7909
  className: "dcsiv-download",
7910
+ "data-compact": "true",
7438
7911
  href: item.src,
7439
7912
  download: downloadName(item.name),
7440
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconDownloadOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7441
- className: "dcsiv-label",
7442
- children: t("imageDownload")
7443
- })]
7913
+ title: t("imageDownload"),
7914
+ "aria-label": t("imageDownload"),
7915
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconDownloadOutline16, {})
7444
7916
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewerDownload, {
7445
7917
  download: item.download,
7446
7918
  item,
@@ -7452,7 +7924,8 @@ window.__ModuleLoader__.load({
7452
7924
  item,
7453
7925
  service,
7454
7926
  revision: request.revision,
7455
- t
7927
+ t,
7928
+ onStartMask: startMask
7456
7929
  }, `${request.revision}:${index}:${action.id}`))
7457
7930
  ]
7458
7931
  })
@@ -7466,142 +7939,205 @@ window.__ModuleLoader__.load({
7466
7939
  }),
7467
7940
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7468
7941
  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();
7942
+ children: [
7943
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
7944
+ ref: stageRef,
7945
+ className: "dcsiv-stage",
7946
+ "data-dragging": dragging,
7947
+ "data-annotating": annotating || erasing !== void 0,
7948
+ onClick: (event) => {
7949
+ if (event.target === event.currentTarget && !annotating && !erasing && transform.zoom === 1) service.close();
7950
+ },
7951
+ ...!annotating && !erasing ? pointerHandlers : {},
7952
+ onDoubleClick: () => {
7953
+ if (transform.zoom === 1) actual();
7954
+ else fit();
7955
+ },
7956
+ children: [
7957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7958
+ ref: surfaceRef,
7959
+ className: "dcsiv-surface",
7960
+ onClick: addAnnotation,
7961
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
7962
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
7963
+ ref: imageRef,
7964
+ className: "dcsiv-image",
7965
+ src: item.src,
7966
+ alt: item.name,
7967
+ draggable: "false",
7968
+ onLoad: () => {
7969
+ measure();
7970
+ }
7971
+ }, `${request.revision}:${item.id}`), erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
7972
+ ref: maskRef,
7973
+ className: "dcsiv-mask",
7974
+ width: imageRef.current?.naturalWidth || 1,
7975
+ height: imageRef.current?.naturalHeight || 1,
7976
+ "aria-label": t("imageEraseHint"),
7977
+ ...maskHandlers
7978
+ }) : null]
7979
+ }),
7980
+ annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7981
+ className: "dcsiv-annotation",
7982
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
7983
+ "data-y": annotation.y < .28 ? "down" : "up",
7984
+ style: annotationPosition(annotation),
7985
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7986
+ type: "button",
7987
+ className: "dcsiv-pin",
7988
+ "data-active": selected === annotation.id,
7989
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7990
+ onClick: (event) => {
7991
+ event.stopPropagation();
7992
+ const opening = selected !== annotation.id;
7993
+ setSelected(opening ? annotation.id : void 0);
7994
+ if (opening) setFocusNote(annotation.id);
7995
+ },
7996
+ children: position + 1
7997
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7998
+ className: "dcsiv-inline-note",
7999
+ "data-note-id": annotation.id,
8000
+ onClick: (event) => event.stopPropagation(),
8001
+ children: [
8002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8003
+ className: "dcsiv-inline-index",
8004
+ children: position + 1
8005
+ }),
8006
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
8007
+ value: annotation.note,
8008
+ rows: 1,
8009
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
8010
+ placeholder: t("imageAnnotationPlaceholder"),
8011
+ onChange: (event) => {
8012
+ const note = event.target.value;
8013
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
8014
+ ...entry,
8015
+ note
8016
+ } : entry));
8017
+ },
8018
+ onKeyDown: (event) => {
8019
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
8020
+ event.preventDefault();
8021
+ event.stopPropagation();
8022
+ event.nativeEvent?.stopImmediatePropagation?.();
8023
+ setSelected(void 0);
8024
+ }
8025
+ }
8026
+ }),
8027
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8028
+ type: "button",
8029
+ className: "dcsiv-note-remove",
8030
+ "aria-label": t("imageRemoveAnnotation"),
8031
+ onClick: (event) => {
7540
8032
  event.stopPropagation();
7541
- event.nativeEvent?.stopImmediatePropagation?.();
8033
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
7542
8034
  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: [
8035
+ },
8036
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, {})
8037
+ })
8038
+ ]
8039
+ }) : null]
8040
+ }, annotation.id)),
8041
+ showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8042
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8043
+ type: "button",
8044
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
8045
+ "aria-label": t("imagePrevious"),
8046
+ onClick: (event) => {
8047
+ event.stopPropagation();
8048
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
8049
+ },
8050
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronLeftOutline14, {})
8051
+ }),
8052
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8053
+ type: "button",
8054
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
8055
+ "aria-label": t("imageNext"),
8056
+ onClick: (event) => {
8057
+ event.stopPropagation();
8058
+ setIndex((value) => (value + 1) % request.items.length);
8059
+ },
8060
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
8061
+ }),
8062
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8063
+ className: "dcsiv-counter",
8064
+ children: [
8065
+ index + 1,
8066
+ " / ",
8067
+ request.items.length
8068
+ ]
8069
+ })
8070
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8071
+ className: "dcsiv-hint",
8072
+ children: t("imageAnnotateHint")
8073
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8074
+ className: "dcsiv-hint",
8075
+ children: t("imageZoomHint")
8076
+ }) : null
8077
+ ]
8078
+ }),
8079
+ erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8080
+ className: "dcsiv-erase-bar",
8081
+ role: "toolbar",
8082
+ "aria-label": t("imageErase"),
8083
+ children: [
8084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageEraseHint") }),
8085
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("imageBrushSize"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
8086
+ type: "range",
8087
+ min: "0.01",
8088
+ max: "0.15",
8089
+ step: "0.01",
8090
+ value: brush,
8091
+ onChange: (event) => setBrush(Number(event.target.value))
8092
+ })] }),
7561
8093
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7562
8094
  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, {})
8095
+ className: "dcsiv-button",
8096
+ disabled: strokes.length === 0 || sending,
8097
+ onClick: () => setStrokes((current) => current.slice(0, -1)),
8098
+ children: t("imageUndo")
7570
8099
  }),
7571
8100
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7572
8101
  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);
8102
+ className: "dcsiv-button",
8103
+ disabled: sending,
8104
+ onClick: () => {
8105
+ setErasing(void 0);
8106
+ setStrokes([]);
7578
8107
  },
7579
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
8108
+ children: t("cancel")
7580
8109
  }),
7581
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7582
- className: "dcsiv-counter",
7583
- children: [
7584
- index + 1,
7585
- " / ",
7586
- request.items.length
7587
- ]
8110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8111
+ type: "button",
8112
+ className: "dcsiv-button dcsiv-primary",
8113
+ "data-state": sendFailed ? "failed" : void 0,
8114
+ disabled: strokes.length === 0 || sending,
8115
+ onClick: () => {
8116
+ sendMask();
8117
+ },
8118
+ children: sending ? t("imageEditPreparing") : sendFailed ? t("imageEditFailed") : t("imageSend")
7588
8119
  })
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]
8120
+ ]
8121
+ }) : null,
8122
+ erasing && brushCursor ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8123
+ className: "dcsiv-brush-cursor",
8124
+ "aria-hidden": "true",
8125
+ style: {
8126
+ left: brushCursor.left,
8127
+ top: brushCursor.top,
8128
+ width: brush * brushCursor.span,
8129
+ height: brush * brushCursor.span
8130
+ }
8131
+ }) : null,
8132
+ annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8133
+ type: "button",
8134
+ className: "dcsiv-copy-notes",
8135
+ onClick: () => {
8136
+ copyNotes();
8137
+ },
8138
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
8139
+ }) : null
8140
+ ]
7605
8141
  })
7606
8142
  ]
7607
8143
  });
@@ -7622,6 +8158,12 @@ window.__ModuleLoader__.load({
7622
8158
  pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
7623
8159
  errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
7624
8160
  closeOnSuccess: action.closeOnSuccess === true,
8161
+ kind: [
8162
+ "tool",
8163
+ "mask",
8164
+ "choice"
8165
+ ].includes(action.kind) ? action.kind : "edit",
8166
+ options: Array.isArray(action.options) ? action.options.filter((option) => typeof option?.value === "string" && typeof option?.label === "string") : [],
7625
8167
  onInvoke: action.onInvoke
7626
8168
  }];
7627
8169
  }) : [];
@@ -8549,7 +9091,7 @@ window.__ModuleLoader__.load({
8549
9091
  }
8550
9092
  //#endregion
8551
9093
  //#region src/version.js
8552
- const PACKAGE_VERSION = "2.5.7";
9094
+ const PACKAGE_VERSION = "2.5.9";
8553
9095
  //#endregion
8554
9096
  //#region src/client-recovery.js
8555
9097
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
@@ -11228,6 +11770,7 @@ window.__ModuleLoader__.load({
11228
11770
  //#endregion
11229
11771
  //#region src/client-usage.jsx
11230
11772
  const count = (value) => Number(value).toLocaleString();
11773
+ const creditAmount = (value) => Number.isFinite(Number(value)) && String(value).trim() !== "" ? Number(value).toLocaleString(void 0, { maximumFractionDigits: 2 }) : value;
11231
11774
  function CreditExpiry({ credits, t }) {
11232
11775
  const date = validDate(credits.expiresAt);
11233
11776
  if (date === void 0 || credits.unlimited) return null;
@@ -11630,7 +12173,7 @@ window.__ModuleLoader__.load({
11630
12173
  children: [
11631
12174
  visibleUsage?.credits ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11632
12175
  className: "codexSubscriptionCreditBalance",
11633
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("creditsBalance") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: visibleUsage.credits.unlimited ? t("unlimited") : `${visibleUsage.credits.balance ?? t("unavailable")} ${t("creditsUnit")}` })]
12176
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("creditsBalance") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: visibleUsage.credits.unlimited ? t("unlimited") : visibleUsage.credits.balance == null ? t("unavailable") : `${creditAmount(visibleUsage.credits.balance)} ${t("creditsUnit")}` })]
11634
12177
  }) : null,
11635
12178
  visibleUsage?.credits ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CreditExpiry, {
11636
12179
  credits: visibleUsage.credits,
@@ -12161,7 +12704,8 @@ window.__ModuleLoader__.load({
12161
12704
  imageViewer.close();
12162
12705
  liveOpen("sketch", document.activeElement, new File([blob], name || "image.png", { type: blob.type || "image/png" }));
12163
12706
  };
12164
- const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false) => {
12707
+ const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false, extraFiles) => {
12708
+ const tool = Array.isArray(extraFiles);
12165
12709
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12166
12710
  return withComposerSession(sessions, sessionId, async (actx) => {
12167
12711
  if (conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
@@ -12173,14 +12717,15 @@ window.__ModuleLoader__.load({
12173
12717
  const reference = await createAnnotatedImageReference(blob, annotations);
12174
12718
  files.push(new File([reference], referenceName, { type: "image/png" }));
12175
12719
  }
12720
+ if (tool) files.push(...extraFiles);
12176
12721
  lifetime.signal.throwIfAborted();
12177
12722
  const input = conversation.input.for(actx);
12178
12723
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12179
12724
  if (files.length) attachImageFiles(conversation, input, files, sessionId);
12180
12725
  openComposerSession(sessions, ctx.get("uiWorkspace"), sessionId);
12181
- if (sourceInDraft && !annotations.length) return;
12726
+ if (sourceInDraft && !annotations.length && !tool) return;
12182
12727
  if (!input.state.getSnapshot().draft.trim()) input.setDraft(draft);
12183
- else if (annotations.length) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12728
+ else if (annotations.length || tool) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12184
12729
  else input.notify("info", draft);
12185
12730
  });
12186
12731
  };