dsh-codex-subscription 2.0.0 → 2.1.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +46 -15
- package/README.md +45 -13
- package/THIRD_PARTY_NOTICES.md +80 -1
- package/compatibility.json +9 -3
- package/lib/client.js +1000 -243
- package/lib/index.js +262 -8
- package/lib/sketch-psd-worker.js +11 -0
- package/package.json +34 -34
- package/README.zh-CN.md +0 -16
package/lib/client.js
CHANGED
|
@@ -279,6 +279,66 @@ window.__ModuleLoader__.load({
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
};
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ArrowPathIcon.js
|
|
284
|
+
function ArrowPathIcon({ title, titleId, ...props }, svgRef) {
|
|
285
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
286
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
287
|
+
fill: "none",
|
|
288
|
+
viewBox: "0 0 24 24",
|
|
289
|
+
strokeWidth: 1.5,
|
|
290
|
+
stroke: "currentColor",
|
|
291
|
+
"aria-hidden": "true",
|
|
292
|
+
"data-slot": "icon",
|
|
293
|
+
ref: svgRef,
|
|
294
|
+
"aria-labelledby": titleId
|
|
295
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
296
|
+
strokeLinecap: "round",
|
|
297
|
+
strokeLinejoin: "round",
|
|
298
|
+
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"
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
|
|
304
|
+
function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
|
|
305
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
306
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
307
|
+
fill: "none",
|
|
308
|
+
viewBox: "0 0 24 24",
|
|
309
|
+
strokeWidth: 1.5,
|
|
310
|
+
stroke: "currentColor",
|
|
311
|
+
"aria-hidden": "true",
|
|
312
|
+
"data-slot": "icon",
|
|
313
|
+
ref: svgRef,
|
|
314
|
+
"aria-labelledby": titleId
|
|
315
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
316
|
+
strokeLinecap: "round",
|
|
317
|
+
strokeLinejoin: "round",
|
|
318
|
+
d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PhotoIcon.js
|
|
324
|
+
function PhotoIcon({ title, titleId, ...props }, svgRef) {
|
|
325
|
+
return /*#__PURE__*/ react.createElement("svg", Object.assign({
|
|
326
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
327
|
+
fill: "none",
|
|
328
|
+
viewBox: "0 0 24 24",
|
|
329
|
+
strokeWidth: 1.5,
|
|
330
|
+
stroke: "currentColor",
|
|
331
|
+
"aria-hidden": "true",
|
|
332
|
+
"data-slot": "icon",
|
|
333
|
+
ref: svgRef,
|
|
334
|
+
"aria-labelledby": titleId
|
|
335
|
+
}, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
|
|
336
|
+
strokeLinecap: "round",
|
|
337
|
+
strokeLinejoin: "round",
|
|
338
|
+
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"
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
|
|
282
342
|
const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
|
|
283
343
|
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
284
344
|
function decodeOriginalImageRef(value) {
|
|
@@ -323,7 +383,11 @@ window.__ModuleLoader__.load({
|
|
|
323
383
|
"reset-credit/inspect",
|
|
324
384
|
"reset-credit/prepare",
|
|
325
385
|
"reset-credit/consume",
|
|
326
|
-
"image/original/chunk"
|
|
386
|
+
"image/original/chunk",
|
|
387
|
+
"sketch/connect",
|
|
388
|
+
"sketch/poll",
|
|
389
|
+
"sketch/result",
|
|
390
|
+
"sketch/disconnect"
|
|
327
391
|
]);
|
|
328
392
|
function createSubscriptionRpcClient(transport) {
|
|
329
393
|
return Object.freeze({ call(channel, endpoint, payload, signal) {
|
|
@@ -511,7 +575,7 @@ window.__ModuleLoader__.load({
|
|
|
511
575
|
})
|
|
512
576
|
});
|
|
513
577
|
}
|
|
514
|
-
function CodexImageToolRow({ block, sessionId, rpc, loadImage, openSketchImage, attachForEdit, getImageViewer, getInternalImageViewer, t, preference }) {
|
|
578
|
+
function CodexImageToolRow({ presentation = "tool", block, sessionId, rpc, loadImage, openSketchImage, attachForEdit, getImageViewer, getInternalImageViewer, t, preference }) {
|
|
515
579
|
const features = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
516
580
|
const settled = block?.kind === "tool-result";
|
|
517
581
|
const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
|
|
@@ -520,14 +584,15 @@ window.__ModuleLoader__.load({
|
|
|
520
584
|
const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
|
|
521
585
|
const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
|
|
522
586
|
const original = decodeImagePresentation(block?.meta)?.original;
|
|
587
|
+
const showOutput = presentation === "output";
|
|
523
588
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
524
589
|
className: "codexImageTool",
|
|
525
590
|
"data-state": state,
|
|
526
591
|
children: [
|
|
527
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
592
|
+
showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
528
593
|
className: "codexImageToolRow",
|
|
529
594
|
children: [
|
|
530
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
595
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
|
|
531
596
|
className: "codexImageToolIcon",
|
|
532
597
|
"aria-hidden": "true"
|
|
533
598
|
}),
|
|
@@ -545,7 +610,7 @@ window.__ModuleLoader__.load({
|
|
|
545
610
|
})
|
|
546
611
|
]
|
|
547
612
|
}),
|
|
548
|
-
image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
613
|
+
!showOutput || image === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
549
614
|
className: "codexImageToolGallery",
|
|
550
615
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
|
|
551
616
|
features,
|
|
@@ -561,7 +626,7 @@ window.__ModuleLoader__.load({
|
|
|
561
626
|
t
|
|
562
627
|
})
|
|
563
628
|
}),
|
|
564
|
-
typeof block?.meta?.requestedModel === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
629
|
+
!showOutput && typeof block?.meta?.requestedModel === "string" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
565
630
|
className: "codexImageDetails",
|
|
566
631
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: t("imageDetails") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [
|
|
567
632
|
t("imageRequestedModel"),
|
|
@@ -595,13 +660,16 @@ window.__ModuleLoader__.load({
|
|
|
595
660
|
className: "codexImageOutput",
|
|
596
661
|
children: node.data.blocks.map((block) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexImageToolRow, {
|
|
597
662
|
block,
|
|
598
|
-
...props
|
|
663
|
+
...props,
|
|
664
|
+
presentation: "output"
|
|
599
665
|
}, block.toolCallId))
|
|
600
666
|
});
|
|
601
667
|
}
|
|
602
668
|
//#endregion
|
|
603
669
|
//#region src/sketch-document.js
|
|
604
670
|
const SKETCH_SIZE = 1024;
|
|
671
|
+
const MAX_SKETCH_STROKES = 2e3;
|
|
672
|
+
const MAX_STROKE_POINTS = 2e3;
|
|
605
673
|
function sketchPoint(clientX, clientY, rect) {
|
|
606
674
|
if (!(rect.width > 0 && rect.height > 0)) return void 0;
|
|
607
675
|
return {
|
|
@@ -635,10 +703,18 @@ window.__ModuleLoader__.load({
|
|
|
635
703
|
context.stroke();
|
|
636
704
|
} else if (stroke.shape === "rectangle") {
|
|
637
705
|
context.rect(first.x * size, first.y * height, (last.x - first.x) * size, (last.y - first.y) * height);
|
|
638
|
-
context.
|
|
706
|
+
if (stroke.fill) context.fill();
|
|
707
|
+
else context.stroke();
|
|
639
708
|
} else if (stroke.shape === "circle") {
|
|
640
709
|
context.ellipse((first.x + last.x) * size / 2, (first.y + last.y) * height / 2, Math.abs(last.x - first.x) * size / 2, Math.abs(last.y - first.y) * height / 2, 0, 0, Math.PI * 2);
|
|
641
|
-
context.
|
|
710
|
+
if (stroke.fill) context.fill();
|
|
711
|
+
else context.stroke();
|
|
712
|
+
} else if (stroke.shape === "polygon") {
|
|
713
|
+
context.moveTo(first.x * size, first.y * height);
|
|
714
|
+
for (const point of stroke.points.slice(1)) context.lineTo(point.x * size, point.y * height);
|
|
715
|
+
context.closePath();
|
|
716
|
+
if (stroke.fill) context.fill();
|
|
717
|
+
else context.stroke();
|
|
642
718
|
} else if (stroke.points.length === 1) {
|
|
643
719
|
context.arc(first.x * size, first.y * height, context.lineWidth / 2, 0, Math.PI * 2);
|
|
644
720
|
context.fill();
|
|
@@ -676,7 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
676
752
|
active: id
|
|
677
753
|
};
|
|
678
754
|
if (action === "add" || action === "duplicate") {
|
|
679
|
-
if (layers.length >= 8 || action === "duplicate" && strokeCount(doc) + layer.strokes.length >
|
|
755
|
+
if (layers.length >= 8 || action === "duplicate" && strokeCount(doc) + layer.strokes.length > 2e3) return doc;
|
|
680
756
|
const next = action === "add" ? {
|
|
681
757
|
id: doc.nextId,
|
|
682
758
|
name: "",
|
|
@@ -736,6 +812,22 @@ window.__ModuleLoader__.load({
|
|
|
736
812
|
let points = stroke.points;
|
|
737
813
|
if (!points.length) return false;
|
|
738
814
|
const a = points[0], b = points.at(-1);
|
|
815
|
+
if (stroke.fill && stroke.shape === "rectangle" && point.x >= Math.min(a.x, b.x) && point.x <= Math.max(a.x, b.x) && point.y >= Math.min(a.y, b.y) && point.y <= Math.max(a.y, b.y)) return true;
|
|
816
|
+
if (stroke.fill && stroke.shape === "circle") {
|
|
817
|
+
const rx = Math.abs(b.x - a.x) / 2, ry = Math.abs(b.y - a.y) / 2;
|
|
818
|
+
if (rx && ry && ((point.x - (a.x + b.x) / 2) / rx) ** 2 + ((point.y - (a.y + b.y) / 2) / ry) ** 2 <= 1) return true;
|
|
819
|
+
}
|
|
820
|
+
if (stroke.shape === "polygon") {
|
|
821
|
+
if (stroke.fill) {
|
|
822
|
+
let inside = false;
|
|
823
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
|
|
824
|
+
const p = points[i], q = points[j];
|
|
825
|
+
if (p.y > point.y !== q.y > point.y && point.x < (q.x - p.x) * (point.y - p.y) / (q.y - p.y) + p.x) inside = !inside;
|
|
826
|
+
}
|
|
827
|
+
if (inside) return true;
|
|
828
|
+
}
|
|
829
|
+
points = [...points, points[0]];
|
|
830
|
+
}
|
|
739
831
|
if (stroke.shape === "rectangle") points = [
|
|
740
832
|
a,
|
|
741
833
|
{
|
|
@@ -1253,10 +1345,320 @@ window.__ModuleLoader__.load({
|
|
|
1253
1345
|
]
|
|
1254
1346
|
});
|
|
1255
1347
|
}
|
|
1348
|
+
const SKETCH_COMMAND_HELP = {
|
|
1349
|
+
coordinates: "Normalized x/y in [0,1]; width is canvas pixels. Read documentId and revision before editing.",
|
|
1350
|
+
shapes: "line: exactly two endpoints; rectangle/circle: exactly two opposite bounding-box corners (circle draws an ellipse within that box); polygon: three or more vertices, closed automatically; pen: ordered path points. fill:true fills rectangle/circle/polygon. Layers and strokes paint in list order, later ones on top. All commands needed for drawing are described here; no source-code search is required.",
|
|
1351
|
+
commands: {
|
|
1352
|
+
stroke: "{op:\"stroke\",layer:1,shape:\"pen|line|rectangle|circle|polygon\",color:\"#rrggbb\",width:2,opacity:1,fill:false,points:[{x:0.1,y:0.1},...]}",
|
|
1353
|
+
layer: "{op:\"layer\",action:\"add|select|rename|visible|duplicate|up|down|delete|clear\",id:1,value:\"name\"}",
|
|
1354
|
+
resize: "{op:\"resize\",ratio:\"1:1|4:3|3:4|16:9|9:16\"}"
|
|
1355
|
+
},
|
|
1356
|
+
limits: {
|
|
1357
|
+
strokes: MAX_SKETCH_STROKES,
|
|
1358
|
+
pointsPerStroke: MAX_STROKE_POINTS,
|
|
1359
|
+
pointsTotal: 2e5,
|
|
1360
|
+
commandsPerBatch: 256
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
const finite = (value, min, max) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max;
|
|
1364
|
+
function applySketchCommands(source, commands) {
|
|
1365
|
+
if (!Array.isArray(commands) || !commands.length || commands.length > 256) throw Error("Expected 1–256 commands");
|
|
1366
|
+
let doc = source;
|
|
1367
|
+
for (const command of commands) {
|
|
1368
|
+
if (!command || typeof command !== "object") throw Error("Invalid command");
|
|
1369
|
+
if (command.op === "resize") {
|
|
1370
|
+
doc = resizeSketch(doc, command.ratio);
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
if (command.op === "layer") {
|
|
1374
|
+
if (![
|
|
1375
|
+
"add",
|
|
1376
|
+
"select",
|
|
1377
|
+
"rename",
|
|
1378
|
+
"visible",
|
|
1379
|
+
"duplicate",
|
|
1380
|
+
"up",
|
|
1381
|
+
"down",
|
|
1382
|
+
"delete",
|
|
1383
|
+
"clear"
|
|
1384
|
+
].includes(command.action)) throw Error("Unknown layer action");
|
|
1385
|
+
const next = changeSketchLayer(doc, command.action, command.id ?? doc.active, command.value);
|
|
1386
|
+
if (next === doc) throw Error("Layer action unavailable; inspect the document first");
|
|
1387
|
+
doc = next;
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (command.op !== "stroke") throw Error("Unknown command");
|
|
1391
|
+
const { points, color, shape = "pen", width = 2, opacity = 1, fill = false } = command;
|
|
1392
|
+
if (![
|
|
1393
|
+
"pen",
|
|
1394
|
+
"line",
|
|
1395
|
+
"rectangle",
|
|
1396
|
+
"circle",
|
|
1397
|
+
"polygon",
|
|
1398
|
+
"eraser"
|
|
1399
|
+
].includes(shape) || !/^#[0-9a-f]{6}$/i.test(color ?? "") || !finite(width, 1, 256) || !finite(opacity, 0, 1) || typeof fill !== "boolean" || !Array.isArray(points) || !points.length || points.length > 2e3 || points.some((p) => !p || !finite(p.x, 0, 1) || !finite(p.y, 0, 1))) throw Error("Invalid stroke");
|
|
1400
|
+
if ([
|
|
1401
|
+
"line",
|
|
1402
|
+
"rectangle",
|
|
1403
|
+
"circle"
|
|
1404
|
+
].includes(shape) && points.length !== 2 || shape === "polygon" && points.length < 3) throw Error("Invalid shape points");
|
|
1405
|
+
if (fill && ![
|
|
1406
|
+
"rectangle",
|
|
1407
|
+
"circle",
|
|
1408
|
+
"polygon"
|
|
1409
|
+
].includes(shape)) throw Error("Fill requires a closed shape");
|
|
1410
|
+
const layer = doc.layers.find((layer) => layer.id === (command.layer ?? doc.active));
|
|
1411
|
+
if (!layer?.visible) throw Error("Target layer is missing or hidden");
|
|
1412
|
+
const stroke = {
|
|
1413
|
+
shape,
|
|
1414
|
+
color,
|
|
1415
|
+
width,
|
|
1416
|
+
opacity,
|
|
1417
|
+
fill,
|
|
1418
|
+
brush: "pen",
|
|
1419
|
+
points: points.map((p) => ({
|
|
1420
|
+
x: p.x,
|
|
1421
|
+
y: p.y
|
|
1422
|
+
}))
|
|
1423
|
+
};
|
|
1424
|
+
doc = {
|
|
1425
|
+
...doc,
|
|
1426
|
+
layers: doc.layers.map((item) => item === layer ? {
|
|
1427
|
+
...item,
|
|
1428
|
+
strokes: [...item.strokes, stroke]
|
|
1429
|
+
} : item)
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
if (strokeCount(doc) > 2e3 || doc.layers.reduce((n, l) => n + l.strokes.reduce((m, s) => m + s.points.length, 0), 0) > 2e5) throw Error("Sketch resource budget exceeded");
|
|
1433
|
+
return doc;
|
|
1434
|
+
}
|
|
1435
|
+
function createSketchCommandSession(adapter) {
|
|
1436
|
+
const completed = /* @__PURE__ */ new Map();
|
|
1437
|
+
let pending = false;
|
|
1438
|
+
return async (request) => {
|
|
1439
|
+
if (!request || typeof request !== "object") throw Error("Invalid sketch request");
|
|
1440
|
+
if (!adapter.available()) throw Error("Open the sketch board for this session first");
|
|
1441
|
+
const current = adapter.snapshot();
|
|
1442
|
+
if (request.action === "inspect") return {
|
|
1443
|
+
...current,
|
|
1444
|
+
help: SKETCH_COMMAND_HELP
|
|
1445
|
+
};
|
|
1446
|
+
if (request.documentId !== current.documentId) throw Error("Document changed; inspect again");
|
|
1447
|
+
if (pending || adapter.busy()) throw Error("Sketch is being edited; retry after it settles");
|
|
1448
|
+
if (request.action === "preview") return {
|
|
1449
|
+
...current,
|
|
1450
|
+
png: await adapter.preview()
|
|
1451
|
+
};
|
|
1452
|
+
if (!["apply", "save"].includes(request.action)) throw Error("Unknown sketch action");
|
|
1453
|
+
if (typeof request.requestId !== "string" || !request.requestId.length || request.requestId.length > 100) throw Error("A unique requestId is required");
|
|
1454
|
+
const key = `${current.documentId}:${request.requestId}`, fingerprint = JSON.stringify(request);
|
|
1455
|
+
const cached = completed.get(key);
|
|
1456
|
+
if (cached) {
|
|
1457
|
+
if (cached.fingerprint !== fingerprint) throw Error("requestId reused with different content");
|
|
1458
|
+
return cached.result;
|
|
1459
|
+
}
|
|
1460
|
+
if (request.revision !== current.revision || adapter.busy()) throw Error("Sketch changed or is being edited; inspect again");
|
|
1461
|
+
if (request.action === "apply") {
|
|
1462
|
+
const next = applySketchCommands(adapter.document(), request.commands);
|
|
1463
|
+
adapter.commit(next);
|
|
1464
|
+
} else {
|
|
1465
|
+
if (request.name !== void 0 && (typeof request.name !== "string" || request.name.length > 60)) throw Error("Invalid draft name");
|
|
1466
|
+
pending = true;
|
|
1467
|
+
try {
|
|
1468
|
+
await adapter.save(request.name);
|
|
1469
|
+
} finally {
|
|
1470
|
+
pending = false;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const result = adapter.snapshot();
|
|
1474
|
+
completed.set(key, {
|
|
1475
|
+
fingerprint,
|
|
1476
|
+
result
|
|
1477
|
+
});
|
|
1478
|
+
if (completed.size > 128) completed.delete(completed.keys().next().value);
|
|
1479
|
+
return result;
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
//#endregion
|
|
1483
|
+
//#region src/sketch-formats.js
|
|
1484
|
+
const SKETCH_FILE_ACCEPT = ".psd,.dsh-sketch.json,image/png,image/jpeg,image/webp";
|
|
1485
|
+
const canvas = (w, h) => {
|
|
1486
|
+
const c = document.createElement("canvas");
|
|
1487
|
+
c.width = w;
|
|
1488
|
+
c.height = h;
|
|
1489
|
+
return c;
|
|
1490
|
+
};
|
|
1491
|
+
function runPsdCodec(action, payload) {
|
|
1492
|
+
return new Promise((resolve, reject) => {
|
|
1493
|
+
const worker = new Worker("/api/codex-subscription/sketch-psd-worker", { type: "module" });
|
|
1494
|
+
const finish = (callback, value) => {
|
|
1495
|
+
clearTimeout(timer);
|
|
1496
|
+
worker.terminate();
|
|
1497
|
+
callback(value);
|
|
1498
|
+
};
|
|
1499
|
+
const timer = setTimeout(() => finish(reject, Error("PSD operation timed out")), 3e4);
|
|
1500
|
+
worker.onerror = () => finish(reject, Error("PSD codec could not be loaded"));
|
|
1501
|
+
worker.onmessage = (event) => event.data.ok ? finish(resolve, event.data.value) : finish(reject, Error(event.data.error));
|
|
1502
|
+
worker.postMessage({
|
|
1503
|
+
action,
|
|
1504
|
+
payload
|
|
1505
|
+
});
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function encodeSketchDocument(doc) {
|
|
1509
|
+
return JSON.stringify({
|
|
1510
|
+
format: "dsh-sketch",
|
|
1511
|
+
version: 1,
|
|
1512
|
+
doc
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
function decodeSketchDocument(text) {
|
|
1516
|
+
if (text.length > 32 * 1024 * 1024) throw Error("Draft exceeds 32 MB");
|
|
1517
|
+
const file = JSON.parse(text), source = file.doc;
|
|
1518
|
+
if (file.format !== "dsh-sketch" || file.version !== 1 || !source || !Array.isArray(source.layers) || !source.layers.length || source.layers.length > 8) throw Error("Invalid sketch file");
|
|
1519
|
+
const w = source.width ?? 1024, h = source.height ?? 1024;
|
|
1520
|
+
if (!Number.isInteger(w) || !Number.isInteger(h) || w < 1 || h < 1 || w > 2048 || h > 2048) throw Error("Invalid canvas size");
|
|
1521
|
+
let doc = {
|
|
1522
|
+
...createSketchLayers(),
|
|
1523
|
+
width: w,
|
|
1524
|
+
height: h,
|
|
1525
|
+
ratio: Object.keys(SKETCH_RATIOS).find((k) => SKETCH_RATIOS[k][0] === w && SKETCH_RATIOS[k][1] === h) ?? "custom"
|
|
1526
|
+
};
|
|
1527
|
+
for (let i = 0; i < source.layers.length; i++) {
|
|
1528
|
+
const layer = source.layers[i];
|
|
1529
|
+
if (i) doc = applySketchCommands(doc, [{
|
|
1530
|
+
op: "layer",
|
|
1531
|
+
action: "add"
|
|
1532
|
+
}]);
|
|
1533
|
+
if (!Array.isArray(layer.strokes)) throw Error("Invalid strokes");
|
|
1534
|
+
for (let j = 0; j < layer.strokes.length; j += 256) {
|
|
1535
|
+
const strokes = layer.strokes.slice(j, j + 256);
|
|
1536
|
+
doc = applySketchCommands(doc, strokes.map((s) => ({
|
|
1537
|
+
...s,
|
|
1538
|
+
op: "stroke",
|
|
1539
|
+
layer: doc.active,
|
|
1540
|
+
fill: s.fill ?? false
|
|
1541
|
+
})));
|
|
1542
|
+
const added = doc.layers.at(-1).strokes;
|
|
1543
|
+
for (let k = 0; k < strokes.length; k++) {
|
|
1544
|
+
const s = strokes[k];
|
|
1545
|
+
if (s.brush !== void 0 && ![
|
|
1546
|
+
"pen",
|
|
1547
|
+
"pencil",
|
|
1548
|
+
"marker"
|
|
1549
|
+
].includes(s.brush)) throw Error("Invalid brush");
|
|
1550
|
+
if (s.pressure !== void 0 && (!Number.isFinite(s.pressure) || s.pressure < .2 || s.pressure > 1)) throw Error("Invalid pressure");
|
|
1551
|
+
Object.assign(added[added.length - strokes.length + k], {
|
|
1552
|
+
brush: s.brush ?? "pen",
|
|
1553
|
+
pressure: s.pressure ?? 1
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
const target = doc.layers.at(-1);
|
|
1558
|
+
target.name = String(layer.name ?? "").slice(0, 40);
|
|
1559
|
+
target.visible = layer.visible !== false;
|
|
1560
|
+
if (layer.image) {
|
|
1561
|
+
const image = layer.image;
|
|
1562
|
+
if (typeof image.src !== "string" || !/^data:image\/png;base64,/.test(image.src) || image.src.length > 8 * 1024 * 1024 || [
|
|
1563
|
+
"x",
|
|
1564
|
+
"y",
|
|
1565
|
+
"width",
|
|
1566
|
+
"height"
|
|
1567
|
+
].some((k) => !Number.isFinite(image[k]) || image[k] < 0 || image[k] > 1)) throw Error("Invalid draft image");
|
|
1568
|
+
const bytes = Uint8Array.from(atob(image.src.slice(image.src.indexOf(",") + 1)), (c) => c.charCodeAt(0));
|
|
1569
|
+
if (bytes.length < 24) throw Error("Invalid draft image");
|
|
1570
|
+
const header = new DataView(bytes.buffer);
|
|
1571
|
+
if (header.getUint32(0) !== 2303741511 || header.getUint32(4) !== 218765834 || header.getUint32(16) < 1 || header.getUint32(20) < 1 || header.getUint32(16) > 4096 || header.getUint32(20) > 4096) throw Error("Invalid draft image size");
|
|
1572
|
+
target.image = {
|
|
1573
|
+
src: image.src,
|
|
1574
|
+
x: image.x,
|
|
1575
|
+
y: image.y,
|
|
1576
|
+
width: image.width,
|
|
1577
|
+
height: image.height
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const activeIndex = source.layers.findIndex((layer) => layer.id === source.active);
|
|
1582
|
+
doc.active = doc.layers[Math.max(0, activeIndex)].id;
|
|
1583
|
+
return doc;
|
|
1584
|
+
}
|
|
1585
|
+
async function exportSketchPsd(doc, images, composite) {
|
|
1586
|
+
const width = doc.width ?? 1024, height = doc.height ?? 1024;
|
|
1587
|
+
const children = doc.layers.map((layer, i) => {
|
|
1588
|
+
const ctx = canvas(width, height).getContext("2d"), ref = layer.image;
|
|
1589
|
+
if (ref) ctx.drawImage(images.get(ref.src), ref.x * width, ref.y * height, ref.width * width, ref.height * height);
|
|
1590
|
+
paintSketch(ctx, layer.strokes, width, true, height);
|
|
1591
|
+
return {
|
|
1592
|
+
name: layer.name || `Layer ${i + 1}`,
|
|
1593
|
+
hidden: !layer.visible,
|
|
1594
|
+
opacity: 1,
|
|
1595
|
+
blendMode: "normal",
|
|
1596
|
+
imageData: ctx.getImageData(0, 0, width, height)
|
|
1597
|
+
};
|
|
1598
|
+
});
|
|
1599
|
+
const context = canvas(width, height).getContext("2d");
|
|
1600
|
+
context.fillStyle = "#fff";
|
|
1601
|
+
context.fillRect(0, 0, width, height);
|
|
1602
|
+
if (children[0] && !children[0].hidden) {
|
|
1603
|
+
const bottom = canvas(width, height);
|
|
1604
|
+
bottom.getContext("2d").putImageData(children[0].imageData, 0, 0);
|
|
1605
|
+
context.drawImage(bottom, 0, 0);
|
|
1606
|
+
children[0].imageData = context.getImageData(0, 0, width, height);
|
|
1607
|
+
} else {
|
|
1608
|
+
if (children.length >= 8) throw Error("Show the bottom layer before exporting this eight-layer drawing");
|
|
1609
|
+
children.unshift({
|
|
1610
|
+
name: "Paper",
|
|
1611
|
+
opacity: 1,
|
|
1612
|
+
blendMode: "normal",
|
|
1613
|
+
imageData: context.getImageData(0, 0, width, height)
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
return runPsdCodec("write", {
|
|
1617
|
+
width,
|
|
1618
|
+
height,
|
|
1619
|
+
children,
|
|
1620
|
+
imageData: composite.getContext("2d").getImageData(0, 0, width, height)
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
async function importSketchPsd(file) {
|
|
1624
|
+
if (file.size > 32 * 1024 * 1024) throw Error("PSD exceeds 32 MB");
|
|
1625
|
+
const psd = await runPsdCodec("read", await file.arrayBuffer());
|
|
1626
|
+
const scale = Math.min(1, 1024 / Math.max(psd.width, psd.height)), width = Math.max(1, Math.round(psd.width * scale)), height = Math.max(1, Math.round(psd.height * scale));
|
|
1627
|
+
const doc = {
|
|
1628
|
+
...createSketchLayers(),
|
|
1629
|
+
width,
|
|
1630
|
+
height,
|
|
1631
|
+
ratio: Object.keys(SKETCH_RATIOS).find((k) => SKETCH_RATIOS[k][0] === width && SKETCH_RATIOS[k][1] === height) ?? "custom",
|
|
1632
|
+
layers: [],
|
|
1633
|
+
nextId: psd.layers.length + 1
|
|
1634
|
+
};
|
|
1635
|
+
for (const [i, layer] of psd.layers.entries()) {
|
|
1636
|
+
const src = canvas(layer.imageData.width, layer.imageData.height);
|
|
1637
|
+
src.getContext("2d").putImageData(new ImageData(new Uint8ClampedArray(layer.imageData.data), layer.imageData.width, layer.imageData.height), 0, 0);
|
|
1638
|
+
const out = canvas(width, height), ctx = out.getContext("2d");
|
|
1639
|
+
ctx.globalAlpha = layer.opacity;
|
|
1640
|
+
ctx.drawImage(src, layer.left * scale, layer.top * scale, src.width * scale, src.height * scale);
|
|
1641
|
+
doc.layers.push({
|
|
1642
|
+
id: i + 1,
|
|
1643
|
+
name: layer.name,
|
|
1644
|
+
visible: !layer.hidden,
|
|
1645
|
+
strokes: [],
|
|
1646
|
+
image: {
|
|
1647
|
+
src: out.toDataURL("image/png"),
|
|
1648
|
+
x: 0,
|
|
1649
|
+
y: 0,
|
|
1650
|
+
width: 1,
|
|
1651
|
+
height: 1
|
|
1652
|
+
}
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
return doc;
|
|
1656
|
+
}
|
|
1256
1657
|
//#endregion
|
|
1257
1658
|
//#region src/sketch-files.jsx
|
|
1258
|
-
function SketchFiles({ save, load, fresh, importImage, disabled, t, report, onWorking }) {
|
|
1659
|
+
function SketchFiles({ save, load, fresh, importImage, download, hasContent, disabled, t, report, onWorking }) {
|
|
1259
1660
|
const [open, setOpen] = (0, react.useState)(false), [rows, setRows] = (0, react.useState)([]), [name, setName] = (0, react.useState)(""), [remove, setRemove] = (0, react.useState)(null), [working, setWorking] = (0, react.useState)(false);
|
|
1661
|
+
const [format, setFormat] = (0, react.useState)("png");
|
|
1260
1662
|
const input = (0, react.useRef)(null), host = (0, react.useRef)(null);
|
|
1261
1663
|
(0, react.useEffect)(() => {
|
|
1262
1664
|
if (open && !working) host.current?.querySelector("input:not([type=file])")?.focus({ preventScroll: true });
|
|
@@ -1271,8 +1673,8 @@ window.__ModuleLoader__.load({
|
|
|
1271
1673
|
onWorking(true);
|
|
1272
1674
|
try {
|
|
1273
1675
|
await operation();
|
|
1274
|
-
} catch {
|
|
1275
|
-
report(t("sketchStorageFailed"));
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
report(error?.message || t("sketchStorageFailed"));
|
|
1276
1678
|
} finally {
|
|
1277
1679
|
setWorking(false);
|
|
1278
1680
|
onWorking(false);
|
|
@@ -1303,7 +1705,7 @@ window.__ModuleLoader__.load({
|
|
|
1303
1705
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1304
1706
|
ref: input,
|
|
1305
1707
|
type: "file",
|
|
1306
|
-
accept:
|
|
1708
|
+
accept: SKETCH_FILE_ACCEPT,
|
|
1307
1709
|
hidden: true,
|
|
1308
1710
|
onChange: (e) => {
|
|
1309
1711
|
const file = e.target.files?.[0];
|
|
@@ -1352,6 +1754,32 @@ window.__ModuleLoader__.load({
|
|
|
1352
1754
|
}),
|
|
1353
1755
|
children: t("sketchSave")
|
|
1354
1756
|
}),
|
|
1757
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1758
|
+
className: "codexSketchExportFormats codexSketchSegment",
|
|
1759
|
+
role: "group",
|
|
1760
|
+
"aria-label": t("sketchExportFormat"),
|
|
1761
|
+
children: [
|
|
1762
|
+
["png", "PNG"],
|
|
1763
|
+
["psd", "PSD"],
|
|
1764
|
+
["draft", t("sketchEditableFile")]
|
|
1765
|
+
].map(([value, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1766
|
+
type: "button",
|
|
1767
|
+
"aria-pressed": format === value,
|
|
1768
|
+
disabled: working,
|
|
1769
|
+
onClick: () => setFormat(value),
|
|
1770
|
+
children: label
|
|
1771
|
+
}, value))
|
|
1772
|
+
}),
|
|
1773
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1774
|
+
type: "button",
|
|
1775
|
+
disabled: working || !hasContent,
|
|
1776
|
+
onClick: () => void run(async () => {
|
|
1777
|
+
await download(format);
|
|
1778
|
+
setOpen(false);
|
|
1779
|
+
}),
|
|
1780
|
+
children: t("sketchDownload")
|
|
1781
|
+
}),
|
|
1782
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("sketchFormatHint") }),
|
|
1355
1783
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("sketchLocalDrafts") }),
|
|
1356
1784
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1357
1785
|
className: "codexSketchDraftList",
|
|
@@ -1430,6 +1858,62 @@ window.__ModuleLoader__.load({
|
|
|
1430
1858
|
});
|
|
1431
1859
|
}
|
|
1432
1860
|
//#endregion
|
|
1861
|
+
//#region src/sketch-agent-client.js
|
|
1862
|
+
function connectSketchAgent(rpc, sessionId, execute, report, pollDelay = () => 350) {
|
|
1863
|
+
let stopped = false, token, timer;
|
|
1864
|
+
const call = (endpoint, payload) => rpc.call(CHANNEL, `sketch/${endpoint}`, {
|
|
1865
|
+
sessionId,
|
|
1866
|
+
token,
|
|
1867
|
+
...payload
|
|
1868
|
+
}).then(unwrap);
|
|
1869
|
+
const poll = async () => {
|
|
1870
|
+
try {
|
|
1871
|
+
const tasks = await call("poll");
|
|
1872
|
+
for (const task of tasks) {
|
|
1873
|
+
if (stopped) break;
|
|
1874
|
+
let value, error;
|
|
1875
|
+
try {
|
|
1876
|
+
if (task.expiresAt < Date.now()) throw Error("Sketch command expired; inspect before retrying");
|
|
1877
|
+
value = await execute(task.request);
|
|
1878
|
+
} catch (cause) {
|
|
1879
|
+
error = cause.message;
|
|
1880
|
+
}
|
|
1881
|
+
await call("result", {
|
|
1882
|
+
id: task.id,
|
|
1883
|
+
value,
|
|
1884
|
+
error
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
if (!stopped) report(error.message);
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (!stopped) timer = setTimeout(poll, pollDelay());
|
|
1892
|
+
};
|
|
1893
|
+
call("connect").then((value) => {
|
|
1894
|
+
token = value.token;
|
|
1895
|
+
if (stopped) call("disconnect").catch(() => {});
|
|
1896
|
+
else poll();
|
|
1897
|
+
}, (error) => {
|
|
1898
|
+
if (!stopped) report(error.message);
|
|
1899
|
+
});
|
|
1900
|
+
return () => {
|
|
1901
|
+
stopped = true;
|
|
1902
|
+
clearTimeout(timer);
|
|
1903
|
+
if (token) call("disconnect").catch(() => {});
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
async function executeSketchFromAgent(request, { available, open, execute, live = () => true, wait = () => new Promise((resolve) => setTimeout(resolve, 20)) }) {
|
|
1907
|
+
if (!available()) {
|
|
1908
|
+
if (request.action !== "inspect") throw Error("Sketch board closed; inspect before editing");
|
|
1909
|
+
if (!live()) throw Error("Sketch session disconnected");
|
|
1910
|
+
open();
|
|
1911
|
+
for (let i = 0; i < 100 && live() && !available(); i++) await wait();
|
|
1912
|
+
}
|
|
1913
|
+
if (!live() || !available()) throw Error("Sketch board could not open in the current session");
|
|
1914
|
+
return execute(request);
|
|
1915
|
+
}
|
|
1916
|
+
//#endregion
|
|
1433
1917
|
//#region src/sketch-studio.jsx
|
|
1434
1918
|
const PALETTE = [
|
|
1435
1919
|
"#18181b",
|
|
@@ -1440,15 +1924,17 @@ window.__ModuleLoader__.load({
|
|
|
1440
1924
|
"#34c759",
|
|
1441
1925
|
"#0088ff"
|
|
1442
1926
|
];
|
|
1443
|
-
function SketchStudio({ open, onClose, attachSketch, enabled, t, incoming }) {
|
|
1927
|
+
function SketchStudio({ open, agentEnabled, onOpen, onClose, attachSketch, enabled, t, incoming, sessionId, rpc }) {
|
|
1444
1928
|
const dialog = (0, react.useRef)(null), canvas = (0, react.useRef)(null), doc = (0, react.useRef)(createSketchLayers()), cache = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
1445
1929
|
const undo = (0, react.useRef)([]), redo = (0, react.useRef)([]), active = (0, react.useRef)(null), frame = (0, react.useRef)(null);
|
|
1446
1930
|
const images = (0, react.useRef)(/* @__PURE__ */ new Map()), saved = (0, react.useRef)(null), dirty = (0, react.useRef)(false), updateUi = (0, react.useRef)(false);
|
|
1931
|
+
const documentId = (0, react.useRef)(crypto.randomUUID()), documentRevision = (0, react.useRef)(0), agentAdapter = (0, react.useRef)({}), agentSession = (0, react.useRef)(null);
|
|
1447
1932
|
const [stability, setStability] = (0, react.useState)(0), [flow, setFlow] = (0, react.useState)(100), [picturesOpen, setPicturesOpen] = (0, react.useState)(false);
|
|
1448
1933
|
const pictureInput = (0, react.useRef)(null), received = (0, react.useRef)(null);
|
|
1449
1934
|
const navigation = useSketchView(canvas, open);
|
|
1450
1935
|
const [revision, redraw] = (0, react.useState)(0), [tool, setTool] = (0, react.useState)("pen"), [brush, setBrush] = (0, react.useState)("pen");
|
|
1451
1936
|
const [eraser, setEraser] = (0, react.useState)("pixel"), [color, setColor] = (0, react.useState)("#0088ff"), [width, setWidth] = (0, react.useState)(12);
|
|
1937
|
+
const [fillShape, setFillShape] = (0, react.useState)(false);
|
|
1452
1938
|
const [layersOpen, setLayersOpen] = (0, react.useState)(false), [busy, setBusy] = (0, react.useState)(false), [error, setError] = (0, react.useState)("");
|
|
1453
1939
|
const cursorRing = (0, react.useRef)(null);
|
|
1454
1940
|
const cursor = useSketchCursor(canvas, cursorRing, width, tool === "pen" ? brush : "pen", navigation.view.scale, !open || navigation.space || busy);
|
|
@@ -1474,6 +1960,7 @@ window.__ModuleLoader__.load({
|
|
|
1474
1960
|
});
|
|
1475
1961
|
};
|
|
1476
1962
|
const checkpoint = () => {
|
|
1963
|
+
documentRevision.current++;
|
|
1477
1964
|
dirty.current = true;
|
|
1478
1965
|
undo.current.push(doc.current);
|
|
1479
1966
|
if (undo.current.length > 30) undo.current.shift();
|
|
@@ -1484,6 +1971,7 @@ window.__ModuleLoader__.load({
|
|
|
1484
1971
|
const next = changeSketchLayer(doc.current, action, id, value);
|
|
1485
1972
|
if (next === doc.current) return;
|
|
1486
1973
|
if (action !== "select") checkpoint();
|
|
1974
|
+
else documentRevision.current++;
|
|
1487
1975
|
doc.current = next;
|
|
1488
1976
|
setError("");
|
|
1489
1977
|
schedule();
|
|
@@ -1517,6 +2005,8 @@ window.__ModuleLoader__.load({
|
|
|
1517
2005
|
if (dirty.current && (hasContent() || saved.current)) await save();
|
|
1518
2006
|
};
|
|
1519
2007
|
const replace = (next, decoded, identity) => {
|
|
2008
|
+
documentId.current = crypto.randomUUID();
|
|
2009
|
+
documentRevision.current++;
|
|
1520
2010
|
doc.current = structuredClone(next);
|
|
1521
2011
|
images.current = decoded;
|
|
1522
2012
|
cache.current.clear();
|
|
@@ -1541,6 +2031,16 @@ window.__ModuleLoader__.load({
|
|
|
1541
2031
|
});
|
|
1542
2032
|
};
|
|
1543
2033
|
const importImage = async (file) => {
|
|
2034
|
+
if (file.name?.toLowerCase().endsWith(".psd") || file.name?.toLowerCase().endsWith(".dsh-sketch.json")) {
|
|
2035
|
+
if (file.size > 32 * 1024 * 1024) throw Error("File exceeds 32 MB");
|
|
2036
|
+
const next = file.name.toLowerCase().endsWith(".psd") ? await importSketchPsd(file) : decodeSketchDocument(await file.text());
|
|
2037
|
+
const decoded = /* @__PURE__ */ new Map();
|
|
2038
|
+
await decodeSketchImages(next, decoded);
|
|
2039
|
+
await saveChanges();
|
|
2040
|
+
replace(next, decoded, null);
|
|
2041
|
+
dirty.current = true;
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1544
2044
|
if (doc.current.layers.length >= 8) throw Error("Layer limit");
|
|
1545
2045
|
const image = await importSketchImage(file), w = doc.current.width ?? 1024, h = doc.current.height ?? 1024;
|
|
1546
2046
|
const scale = Math.min(w / image.width, h / image.height), width = image.width * scale / w, height = image.height * scale / h;
|
|
@@ -1703,11 +2203,101 @@ window.__ModuleLoader__.load({
|
|
|
1703
2203
|
if (busy || active.current) return;
|
|
1704
2204
|
const source = direction === "undo" ? undo : redo, target = direction === "undo" ? redo : undo;
|
|
1705
2205
|
if (!source.current.length) return;
|
|
2206
|
+
documentRevision.current++;
|
|
1706
2207
|
dirty.current = true;
|
|
1707
2208
|
target.current.push(doc.current);
|
|
1708
2209
|
doc.current = source.current.pop();
|
|
1709
2210
|
schedule();
|
|
1710
2211
|
};
|
|
2212
|
+
Object.assign(agentAdapter.current, {
|
|
2213
|
+
available: () => open && enabled,
|
|
2214
|
+
open: () => onOpen(),
|
|
2215
|
+
busy: () => busy || Boolean(active.current),
|
|
2216
|
+
document: () => doc.current,
|
|
2217
|
+
snapshot: () => ({
|
|
2218
|
+
documentId: documentId.current,
|
|
2219
|
+
revision: documentRevision.current,
|
|
2220
|
+
width: doc.current.width ?? 1024,
|
|
2221
|
+
height: doc.current.height ?? 1024,
|
|
2222
|
+
active: doc.current.active,
|
|
2223
|
+
layers: doc.current.layers.map((layer) => ({
|
|
2224
|
+
id: layer.id,
|
|
2225
|
+
name: layer.name,
|
|
2226
|
+
visible: layer.visible,
|
|
2227
|
+
strokes: layer.strokes.length,
|
|
2228
|
+
image: Boolean(layer.image)
|
|
2229
|
+
})),
|
|
2230
|
+
strokeCount: strokeCount(doc.current)
|
|
2231
|
+
}),
|
|
2232
|
+
commit: (next) => {
|
|
2233
|
+
checkpoint();
|
|
2234
|
+
doc.current = next;
|
|
2235
|
+
setError("");
|
|
2236
|
+
schedule();
|
|
2237
|
+
},
|
|
2238
|
+
preview: async () => {
|
|
2239
|
+
paint();
|
|
2240
|
+
return canvas.current.toDataURL("image/png");
|
|
2241
|
+
},
|
|
2242
|
+
save: async (name) => {
|
|
2243
|
+
setBusy(true);
|
|
2244
|
+
try {
|
|
2245
|
+
await save(name);
|
|
2246
|
+
} finally {
|
|
2247
|
+
setBusy(false);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
agentSession.current ??= createSketchCommandSession(agentAdapter.current);
|
|
2252
|
+
(0, react.useEffect)(() => {
|
|
2253
|
+
if (!open || !enabled || !agentEnabled) return;
|
|
2254
|
+
const api = Object.freeze({
|
|
2255
|
+
version: 1,
|
|
2256
|
+
sessionId,
|
|
2257
|
+
execute: (request) => agentSession.current(request),
|
|
2258
|
+
export: async (format) => {
|
|
2259
|
+
if (agentAdapter.current.busy()) throw Error("Sketch is being edited");
|
|
2260
|
+
const { blob, extension } = await agentAdapter.current.export(format);
|
|
2261
|
+
const data = new Uint8Array(await blob.arrayBuffer());
|
|
2262
|
+
let raw = "";
|
|
2263
|
+
for (let i = 0; i < data.length; i += 8192) raw += String.fromCharCode(...data.subarray(i, i + 8192));
|
|
2264
|
+
return {
|
|
2265
|
+
extension,
|
|
2266
|
+
mediaType: blob.type,
|
|
2267
|
+
base64: btoa(raw)
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
window.dshSketchAgent = api;
|
|
2272
|
+
return () => {
|
|
2273
|
+
if (window.dshSketchAgent === api) delete window.dshSketchAgent;
|
|
2274
|
+
};
|
|
2275
|
+
}, [
|
|
2276
|
+
open,
|
|
2277
|
+
enabled,
|
|
2278
|
+
agentEnabled,
|
|
2279
|
+
rpc,
|
|
2280
|
+
sessionId
|
|
2281
|
+
]);
|
|
2282
|
+
(0, react.useEffect)(() => {
|
|
2283
|
+
if (!enabled || !agentEnabled || !rpc || !sessionId) return;
|
|
2284
|
+
let live = true;
|
|
2285
|
+
const disconnect = connectSketchAgent(rpc, sessionId, (request) => executeSketchFromAgent(request, {
|
|
2286
|
+
available: () => agentAdapter.current.available(),
|
|
2287
|
+
open: () => agentAdapter.current.open(),
|
|
2288
|
+
execute: (request) => agentSession.current(request),
|
|
2289
|
+
live: () => live
|
|
2290
|
+
}), (message) => setError(message), () => agentAdapter.current.available() ? 350 : 1500);
|
|
2291
|
+
return () => {
|
|
2292
|
+
live = false;
|
|
2293
|
+
disconnect();
|
|
2294
|
+
};
|
|
2295
|
+
}, [
|
|
2296
|
+
enabled,
|
|
2297
|
+
agentEnabled,
|
|
2298
|
+
rpc,
|
|
2299
|
+
sessionId
|
|
2300
|
+
]);
|
|
1711
2301
|
const attach = async () => {
|
|
1712
2302
|
if (!enabled || busy) return;
|
|
1713
2303
|
setBusy(true);
|
|
@@ -1724,6 +2314,34 @@ window.__ModuleLoader__.load({
|
|
|
1724
2314
|
setBusy(false);
|
|
1725
2315
|
}
|
|
1726
2316
|
};
|
|
2317
|
+
const exportFile = async (format = "png") => {
|
|
2318
|
+
paint();
|
|
2319
|
+
if (format === "draft") return {
|
|
2320
|
+
blob: new Blob([encodeSketchDocument(doc.current)], { type: "application/json" }),
|
|
2321
|
+
extension: "dsh-sketch.json"
|
|
2322
|
+
};
|
|
2323
|
+
if (format === "psd") return {
|
|
2324
|
+
blob: new Blob([await exportSketchPsd(doc.current, images.current, canvas.current)], { type: "image/vnd.adobe.photoshop" }),
|
|
2325
|
+
extension: "psd"
|
|
2326
|
+
};
|
|
2327
|
+
if (format !== "png") throw Error("Unsupported format");
|
|
2328
|
+
return {
|
|
2329
|
+
blob: await new Promise((resolve, reject) => canvas.current.toBlob((blob) => blob ? resolve(blob) : reject(Error("PNG")), "image/png")),
|
|
2330
|
+
extension: "png"
|
|
2331
|
+
};
|
|
2332
|
+
};
|
|
2333
|
+
agentAdapter.current.export = exportFile;
|
|
2334
|
+
const download = async (format) => {
|
|
2335
|
+
const { blob, extension } = await exportFile(format);
|
|
2336
|
+
await saveChanges();
|
|
2337
|
+
const url = URL.createObjectURL(blob), link = document.createElement("a");
|
|
2338
|
+
link.href = url;
|
|
2339
|
+
link.download = `${(saved.current?.name || "sketch").replace(/[\\/:*?"<>|\u0000-\u001f]/g, "-").slice(0, 80)}.${extension}`;
|
|
2340
|
+
document.body.append(link);
|
|
2341
|
+
link.click();
|
|
2342
|
+
link.remove();
|
|
2343
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e4);
|
|
2344
|
+
};
|
|
1727
2345
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
|
|
1728
2346
|
ref: dialog,
|
|
1729
2347
|
className: "codexSketchDialog codexSketchStudio codexLayerStudio",
|
|
@@ -1774,6 +2392,8 @@ window.__ModuleLoader__.load({
|
|
|
1774
2392
|
load,
|
|
1775
2393
|
fresh,
|
|
1776
2394
|
importImage,
|
|
2395
|
+
download,
|
|
2396
|
+
hasContent: doc.current.layers.some((l) => l.visible && (l.image || l.strokes.length)),
|
|
1777
2397
|
disabled: busy,
|
|
1778
2398
|
t,
|
|
1779
2399
|
report: setError,
|
|
@@ -1801,7 +2421,7 @@ window.__ModuleLoader__.load({
|
|
|
1801
2421
|
})
|
|
1802
2422
|
}, name))
|
|
1803
2423
|
}),
|
|
1804
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2424
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
1805
2425
|
className: "codexSketchRatio",
|
|
1806
2426
|
"aria-label": t("sketchRatio"),
|
|
1807
2427
|
title: t("sketchRatioHint"),
|
|
@@ -1815,10 +2435,18 @@ window.__ModuleLoader__.load({
|
|
|
1815
2435
|
doc.current = next;
|
|
1816
2436
|
schedule();
|
|
1817
2437
|
},
|
|
1818
|
-
children:
|
|
2438
|
+
children: [doc.current.ratio === "custom" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("option", {
|
|
2439
|
+
value: "custom",
|
|
2440
|
+
disabled: true,
|
|
2441
|
+
children: [
|
|
2442
|
+
doc.current.width,
|
|
2443
|
+
"×",
|
|
2444
|
+
doc.current.height
|
|
2445
|
+
]
|
|
2446
|
+
}) : null, Object.keys(SKETCH_RATIOS).map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1819
2447
|
value: ratio,
|
|
1820
2448
|
children: ratio
|
|
1821
|
-
}, ratio))
|
|
2449
|
+
}, ratio))]
|
|
1822
2450
|
}),
|
|
1823
2451
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
1824
2452
|
type: "button",
|
|
@@ -1873,7 +2501,11 @@ window.__ModuleLoader__.load({
|
|
|
1873
2501
|
setError(t("sketchHiddenLayer"));
|
|
1874
2502
|
return;
|
|
1875
2503
|
}
|
|
1876
|
-
if (!(tool === "eraser" && eraser === "stroke") && strokeCount(doc.current) >=
|
|
2504
|
+
if (!(tool === "eraser" && eraser === "stroke") && strokeCount(doc.current) >= 2e3) {
|
|
2505
|
+
setError(t("sketchLimit"));
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
if (tool !== "eraser" && doc.current.layers.reduce((n, l) => n + l.strokes.reduce((m, s) => m + s.points.length, 0), 0) >= 2e5) {
|
|
1877
2509
|
setError(t("sketchLimit"));
|
|
1878
2510
|
return;
|
|
1879
2511
|
}
|
|
@@ -1895,6 +2527,7 @@ window.__ModuleLoader__.load({
|
|
|
1895
2527
|
opacity: flow / 100,
|
|
1896
2528
|
shape: tool,
|
|
1897
2529
|
width,
|
|
2530
|
+
fill: fillShape && ["rectangle", "circle"].includes(tool),
|
|
1898
2531
|
brush: tool === "pen" ? brush : "pen",
|
|
1899
2532
|
pressure: event.pointerType === "pen" ? Math.max(.2, event.pressure) : 1,
|
|
1900
2533
|
points: [start]
|
|
@@ -2021,7 +2654,7 @@ window.__ModuleLoader__.load({
|
|
|
2021
2654
|
type: "button",
|
|
2022
2655
|
title: t(`sketchLayer_${action}`),
|
|
2023
2656
|
"aria-label": t(`sketchLayer_${action}`),
|
|
2024
|
-
disabled: busy || action === "delete" && doc.current.layers.length === 1 || action === "duplicate" && (doc.current.layers.length >= 8 || strokeCount(doc.current) + current.strokes.length >
|
|
2657
|
+
disabled: busy || action === "delete" && doc.current.layers.length === 1 || action === "duplicate" && (doc.current.layers.length >= 8 || strokeCount(doc.current) + current.strokes.length > 2e3) || action === "up" && current === doc.current.layers.at(-1) || action === "down" && current === doc.current.layers[0],
|
|
2025
2658
|
onClick: () => change(action),
|
|
2026
2659
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceIcon, {
|
|
2027
2660
|
name: action === "delete" ? "clear" : action,
|
|
@@ -2097,6 +2730,11 @@ window.__ModuleLoader__.load({
|
|
|
2097
2730
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2098
2731
|
className: "codexLayerBrush",
|
|
2099
2732
|
children: [
|
|
2733
|
+
["rectangle", "circle"].includes(tool) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
2734
|
+
type: "checkbox",
|
|
2735
|
+
checked: fillShape,
|
|
2736
|
+
onChange: (e) => setFillShape(e.target.checked)
|
|
2737
|
+
}), t("sketchFill")] }) : null,
|
|
2100
2738
|
tool === "pen" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2101
2739
|
className: "codexSketchStability",
|
|
2102
2740
|
children: [t("sketchStability"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
@@ -2241,6 +2879,7 @@ window.__ModuleLoader__.load({
|
|
|
2241
2879
|
.codexSketchFilePanel button{min-height:36px;border-radius:8px;padding:4px 10px;background:color-mix(in srgb,var(--sketch-fg) 6%,transparent)}
|
|
2242
2880
|
.codexSketchFilePanel input{min-width:0;min-height:36px;border:1px solid var(--sketch-line);border-radius:8px;padding:6px 10px;background:var(--sketch-bg);color:inherit;font:inherit}
|
|
2243
2881
|
.codexSketchFilePanel small{color:var(--sketch-muted);line-height:1.5}.codexSketchFileActions{display:flex;gap:8px}.codexSketchFileActions>button{flex:1}
|
|
2882
|
+
.codexSketchFilePanel .codexSketchExportFormats{gap:2px;border-radius:10px}.codexSketchFilePanel .codexSketchExportFormats button{flex:1;min-width:0;min-height:34px;padding:4px 8px;background:transparent;font-size:12px}.codexSketchFilePanel .codexSketchExportFormats button[aria-pressed=true]{background:var(--sketch-bg);box-shadow:0 1px 4px #0002,inset 0 0 0 1px var(--sketch-line)}
|
|
2244
2883
|
.codexSketchDraftList{max-height:220px;overflow:auto}.codexSketchDraftList>div{display:flex;gap:6px;margin-bottom:6px}.codexSketchDraftList button:first-child{flex:1;min-width:0;justify-content:flex-start;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
2245
2884
|
.codexSketchStability{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--sketch-muted)}.codexSketchStability select{height:32px;border:1px solid var(--sketch-line);border-radius:8px;background:var(--sketch-bg);color:var(--sketch-fg);font:inherit}
|
|
2246
2885
|
@media(max-width:760px){.codexSketchTop{gap:2px}.codexSketchUtility{gap:2px}.codexSketchRound{width:32px}.codexSketchLayersToggle{padding:0 6px}.codexSketchFiles>button{padding:0 6px}.codexSketchRatio{max-width:65px}.codexSketchPill button{min-width:42px}.codexSketchDialog .codexSketchConfirm{padding:0 8px}.codexSketchTop{flex-shrink:0}}
|
|
@@ -2254,7 +2893,7 @@ window.__ModuleLoader__.load({
|
|
|
2254
2893
|
`;
|
|
2255
2894
|
//#endregion
|
|
2256
2895
|
//#region src/sketch-workspace.jsx
|
|
2257
|
-
function SketchWorkspace({ preference, attachSketch, registerOpen, t }) {
|
|
2896
|
+
function SketchWorkspace({ preference, attachSketch, registerOpen, t, sessionId, rpc }) {
|
|
2258
2897
|
const settings = (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
|
|
2259
2898
|
const [open, setOpen] = (0, react.useState)(false);
|
|
2260
2899
|
const [incoming, setIncoming] = (0, react.useState)(null);
|
|
@@ -2267,6 +2906,13 @@ window.__ModuleLoader__.load({
|
|
|
2267
2906
|
}
|
|
2268
2907
|
}), [registerOpen]);
|
|
2269
2908
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SketchStudio, {
|
|
2909
|
+
agentEnabled: settings.imageSketchAgent,
|
|
2910
|
+
onOpen: () => {
|
|
2911
|
+
opener.current = document.activeElement;
|
|
2912
|
+
setOpen(true);
|
|
2913
|
+
},
|
|
2914
|
+
sessionId,
|
|
2915
|
+
rpc,
|
|
2270
2916
|
incoming,
|
|
2271
2917
|
open,
|
|
2272
2918
|
onClose: () => {
|
|
@@ -2311,12 +2957,17 @@ window.__ModuleLoader__.load({
|
|
|
2311
2957
|
}
|
|
2312
2958
|
//#endregion
|
|
2313
2959
|
//#region src/image-composer.js
|
|
2314
|
-
function attachImageFiles(conversation, input, files) {
|
|
2315
|
-
const
|
|
2960
|
+
function attachImageFiles(conversation, input, files, sessionId) {
|
|
2961
|
+
const modern = typeof conversation.createDrafts === "function";
|
|
2962
|
+
const create = modern ? () => conversation.createDrafts(sessionId, files) : () => conversation.createDraftImages(files);
|
|
2963
|
+
const release = modern ? (items) => conversation.releaseDraftAttachments(items) : (items) => conversation.releaseDraftImages(items);
|
|
2964
|
+
const add = modern ? input.addAttachments : input.addImages;
|
|
2965
|
+
if (typeof add !== "function" || modern && !sessionId) throw new Error("Image composer is unavailable");
|
|
2966
|
+
const created = create();
|
|
2316
2967
|
try {
|
|
2317
|
-
if (!
|
|
2968
|
+
if (!add.call(input, created.map((item) => item.id))) throw new Error("The composer is busy");
|
|
2318
2969
|
} catch (error) {
|
|
2319
|
-
|
|
2970
|
+
release(created);
|
|
2320
2971
|
throw error;
|
|
2321
2972
|
}
|
|
2322
2973
|
return created;
|
|
@@ -2362,6 +3013,11 @@ window.__ModuleLoader__.load({
|
|
|
2362
3013
|
//#endregion
|
|
2363
3014
|
//#region src/client-locales.js
|
|
2364
3015
|
const zh = {
|
|
3016
|
+
sketchFill: "填色",
|
|
3017
|
+
sketchDownload: "下载",
|
|
3018
|
+
sketchExportFormat: "导出格式",
|
|
3019
|
+
sketchEditableFile: "可编辑草稿",
|
|
3020
|
+
sketchFormatHint: "PNG 为合并图片;PSD 交换像素图层;可编辑草稿保留原生笔画。可导入 PSD 或草稿文件(32 MB 内);PSD 仅支持普通像素图层,长边适配至 1024。",
|
|
2365
3021
|
imageToSketch: "进入草图",
|
|
2366
3022
|
sketchPictures: "图片",
|
|
2367
3023
|
sketchPictureAdd: "添加",
|
|
@@ -2386,7 +3042,7 @@ window.__ModuleLoader__.load({
|
|
|
2386
3042
|
sketchKey_fit: "重置视图",
|
|
2387
3043
|
sketchFiles: "草稿",
|
|
2388
3044
|
sketchNew: "新建草图",
|
|
2389
|
-
sketchImport: "
|
|
3045
|
+
sketchImport: "导入文件",
|
|
2390
3046
|
sketchDraftName: "草稿名称",
|
|
2391
3047
|
sketchSave: "保存草稿",
|
|
2392
3048
|
sketchDeleteDraft: "删除",
|
|
@@ -2425,7 +3081,7 @@ window.__ModuleLoader__.load({
|
|
|
2425
3081
|
imageCapability_on: "启用",
|
|
2426
3082
|
imageCapability_off: "关闭",
|
|
2427
3083
|
imageEntryPoints: "创作入口",
|
|
2428
|
-
imageEntryPointsHint: "
|
|
3084
|
+
imageEntryPointsHint: "@生图入口;隐藏后仍可用自然语言生图。",
|
|
2429
3085
|
imageEntryPoints_on: "显示",
|
|
2430
3086
|
imageEntryPoints_off: "隐藏",
|
|
2431
3087
|
imageBrowsing: "图片浏览",
|
|
@@ -2461,6 +3117,14 @@ window.__ModuleLoader__.load({
|
|
|
2461
3117
|
sketchTool_rectangle: "矩形",
|
|
2462
3118
|
sketchTool_circle: "椭圆",
|
|
2463
3119
|
sketchTool_eraser: "橡皮",
|
|
3120
|
+
sketchCanvas: "草图画板 · Beta",
|
|
3121
|
+
sketchCanvasHint: "默认关闭;开启后显示手动画板入口。",
|
|
3122
|
+
sketchCanvas_on: "开启",
|
|
3123
|
+
sketchCanvas_off: "关闭",
|
|
3124
|
+
sketchAgent: "Agent 绘图 · Beta",
|
|
3125
|
+
sketchAgentHint: "默认关闭;开启后向模型提供绘图工具,需要同时开启草图画板。",
|
|
3126
|
+
sketchAgent_on: "开启",
|
|
3127
|
+
sketchAgent_off: "关闭",
|
|
2464
3128
|
imageSettings: "图片",
|
|
2465
3129
|
imageGeneration: "生成新图片",
|
|
2466
3130
|
imageEditing: "参考图编辑",
|
|
@@ -2734,6 +3398,11 @@ window.__ModuleLoader__.load({
|
|
|
2734
3398
|
imageRemoveAnnotation: "删除标注"
|
|
2735
3399
|
};
|
|
2736
3400
|
const en = {
|
|
3401
|
+
sketchFill: "Fill",
|
|
3402
|
+
sketchDownload: "Download",
|
|
3403
|
+
sketchExportFormat: "Export format",
|
|
3404
|
+
sketchEditableFile: "Editable draft",
|
|
3405
|
+
sketchFormatHint: "PNG is flattened; PSD exchanges pixel layers; editable drafts retain native strokes. Import PSD or draft files up to 32 MB. PSD supports normal pixel layers, fitted to a 1024 px long edge.",
|
|
2737
3406
|
imageToSketch: "Open in sketch",
|
|
2738
3407
|
sketchPictures: "Images",
|
|
2739
3408
|
sketchPictureAdd: "Add",
|
|
@@ -2758,7 +3427,7 @@ window.__ModuleLoader__.load({
|
|
|
2758
3427
|
sketchKey_fit: "Reset view",
|
|
2759
3428
|
sketchFiles: "Drafts",
|
|
2760
3429
|
sketchNew: "New sketch",
|
|
2761
|
-
sketchImport: "Import
|
|
3430
|
+
sketchImport: "Import file",
|
|
2762
3431
|
sketchDraftName: "Draft name",
|
|
2763
3432
|
sketchSave: "Save draft",
|
|
2764
3433
|
sketchDeleteDraft: "Delete",
|
|
@@ -2797,7 +3466,7 @@ window.__ModuleLoader__.load({
|
|
|
2797
3466
|
imageCapability_on: "Enabled",
|
|
2798
3467
|
imageCapability_off: "Off",
|
|
2799
3468
|
imageEntryPoints: "Creative shortcuts",
|
|
2800
|
-
imageEntryPointsHint: "@Image
|
|
3469
|
+
imageEntryPointsHint: "@Image shortcut. Natural-language image requests still work when hidden.",
|
|
2801
3470
|
imageEntryPoints_on: "Show",
|
|
2802
3471
|
imageEntryPoints_off: "Hide",
|
|
2803
3472
|
imageBrowsing: "Image browsing",
|
|
@@ -2833,6 +3502,14 @@ window.__ModuleLoader__.load({
|
|
|
2833
3502
|
sketchTool_rectangle: "Rectangle",
|
|
2834
3503
|
sketchTool_circle: "Ellipse",
|
|
2835
3504
|
sketchTool_eraser: "Eraser",
|
|
3505
|
+
sketchCanvas: "Sketch canvas · Beta",
|
|
3506
|
+
sketchCanvasHint: "Off by default. Enables the manual sketch board.",
|
|
3507
|
+
sketchCanvas_on: "On",
|
|
3508
|
+
sketchCanvas_off: "Off",
|
|
3509
|
+
sketchAgent: "Agent drawing · Beta",
|
|
3510
|
+
sketchAgentHint: "Off by default. Exposes drawing tools to the model; requires the sketch canvas.",
|
|
3511
|
+
sketchAgent_on: "On",
|
|
3512
|
+
sketchAgent_off: "Off",
|
|
2836
3513
|
imageSettings: "Images",
|
|
2837
3514
|
imageGeneration: "Generate new images",
|
|
2838
3515
|
imageEditing: "Reference image editing",
|
|
@@ -3308,14 +3985,14 @@ window.__ModuleLoader__.load({
|
|
|
3308
3985
|
.codexModelSelectMenu{overflow:visible}
|
|
3309
3986
|
.codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}
|
|
3310
3987
|
.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}
|
|
3311
|
-
.codexImageToolIcon{display:
|
|
3312
|
-
.codexImageToolIcon
|
|
3313
|
-
.codexImageTool[data-state=
|
|
3314
|
-
.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}
|
|
3988
|
+
.codexImageToolIcon{display:block;flex-shrink:0;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}
|
|
3989
|
+
.codexImageTool[data-state=running] .codexImageToolIcon{animation:codexImageSpin 800ms linear infinite}
|
|
3990
|
+
.codexImageTool[data-state=error] .codexImageToolIcon{color:var(--dsw-alias-state-error-primary)}
|
|
3315
3991
|
.codexImageToolTitle{font-weight:500}
|
|
3316
3992
|
.codexImageToolState{color:var(--dsw-alias-label-tertiary)}
|
|
3317
3993
|
.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
|
|
3318
|
-
.codexImageToolGallery{margin-left:
|
|
3994
|
+
.codexImageToolGallery{margin-left:0}
|
|
3995
|
+
.codexImageDetails{margin-left:24px;font-size:12px;color:var(--dsw-alias-label-secondary)}.codexImageDetails summary{cursor:pointer}.codexImageDetails p{margin:6px 0;line-height:1.6;overflow-wrap:anywhere}
|
|
3319
3996
|
.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);height:240px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}
|
|
3320
3997
|
.codexGeneratedImageFrame img{display:block;width:100%;height:100%;object-fit:cover}
|
|
3321
3998
|
.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
@@ -3335,10 +4012,10 @@ window.__ModuleLoader__.load({
|
|
|
3335
4012
|
const PIN_OUTER_STROKE = "#111827";
|
|
3336
4013
|
const PIN_INNER_STROKE = "#ffffff";
|
|
3337
4014
|
const finiteDimension = (value) => Number.isSafeInteger(value) && value > 0;
|
|
3338
|
-
const clamp$
|
|
4015
|
+
const clamp$2 = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
3339
4016
|
const clampCenter = (value, size, edge) => {
|
|
3340
4017
|
const margin = Math.min(edge, size / 2);
|
|
3341
|
-
return clamp$
|
|
4018
|
+
return clamp$2(value, margin, size - margin);
|
|
3342
4019
|
};
|
|
3343
4020
|
function getBitmapFactory(options) {
|
|
3344
4021
|
const factory = options?.createImageBitmap ?? options?.bitmapFactory ?? globalThis.createImageBitmap;
|
|
@@ -3470,6 +4147,182 @@ window.__ModuleLoader__.load({
|
|
|
3470
4147
|
@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}}
|
|
3471
4148
|
@media(prefers-reduced-motion:reduce){.dcsiv-surface{transition:none}}
|
|
3472
4149
|
`;
|
|
4150
|
+
//#endregion
|
|
4151
|
+
//#region src/subscription-image-transform.js
|
|
4152
|
+
const clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
4153
|
+
const fitted = () => ({
|
|
4154
|
+
zoom: 1,
|
|
4155
|
+
x: 0,
|
|
4156
|
+
y: 0
|
|
4157
|
+
});
|
|
4158
|
+
function useImageTransform(identity) {
|
|
4159
|
+
const [transform, setTransform] = (0, react.useState)(fitted);
|
|
4160
|
+
const [dragging, setDragging] = (0, react.useState)(false);
|
|
4161
|
+
const [pixelScale, setPixelScale] = (0, react.useState)(1);
|
|
4162
|
+
const [geometry, setGeometry] = (0, react.useState)({
|
|
4163
|
+
width: 0,
|
|
4164
|
+
height: 0,
|
|
4165
|
+
stageWidth: 0,
|
|
4166
|
+
stageHeight: 0
|
|
4167
|
+
});
|
|
4168
|
+
const stageRef = (0, react.useRef)(null), surfaceRef = (0, react.useRef)(null), imageRef = (0, react.useRef)(null);
|
|
4169
|
+
const transformRef = (0, react.useRef)(transform);
|
|
4170
|
+
const pointers = (0, react.useRef)(/* @__PURE__ */ new Map()), gesture = (0, react.useRef)();
|
|
4171
|
+
transformRef.current = transform;
|
|
4172
|
+
const resetGesture = (0, react.useCallback)(() => {
|
|
4173
|
+
pointers.current.clear();
|
|
4174
|
+
gesture.current = void 0;
|
|
4175
|
+
setDragging(false);
|
|
4176
|
+
}, []);
|
|
4177
|
+
const fit = (0, react.useCallback)(() => {
|
|
4178
|
+
resetGesture();
|
|
4179
|
+
setTransform(fitted());
|
|
4180
|
+
}, [resetGesture]);
|
|
4181
|
+
(0, react.useEffect)(() => {
|
|
4182
|
+
fit();
|
|
4183
|
+
}, [identity, fit]);
|
|
4184
|
+
const actualScale = () => imageRef.current?.naturalWidth / Math.max(1, surfaceRef.current?.offsetWidth || 1) || 1;
|
|
4185
|
+
const boundedPan = (0, react.useCallback)((zoom, x, y) => {
|
|
4186
|
+
const stage = stageRef.current, surface = surfaceRef.current;
|
|
4187
|
+
if (!stage || !surface || zoom <= 1) return {
|
|
4188
|
+
x: 0,
|
|
4189
|
+
y: 0
|
|
4190
|
+
};
|
|
4191
|
+
const limitX = Math.max(0, (surface.offsetWidth * zoom - stage.clientWidth) / 2);
|
|
4192
|
+
const limitY = Math.max(0, (surface.offsetHeight * zoom - stage.clientHeight) / 2);
|
|
4193
|
+
return {
|
|
4194
|
+
x: clamp$1(x, -limitX, limitX),
|
|
4195
|
+
y: clamp$1(y, -limitY, limitY)
|
|
4196
|
+
};
|
|
4197
|
+
}, []);
|
|
4198
|
+
const setZoomAt = (0, react.useCallback)((zoom, clientX, clientY) => {
|
|
4199
|
+
const stage = stageRef.current;
|
|
4200
|
+
if (!stage) return;
|
|
4201
|
+
setTransform((current) => {
|
|
4202
|
+
const next = clamp$1(zoom, .5, Math.max(8, actualScale()));
|
|
4203
|
+
const box = stage.getBoundingClientRect();
|
|
4204
|
+
const px = clientX - box.left - box.width / 2, py = clientY - box.top - box.height / 2;
|
|
4205
|
+
const ratio = next / current.zoom;
|
|
4206
|
+
return {
|
|
4207
|
+
zoom: next,
|
|
4208
|
+
...boundedPan(next, px - (px - current.x) * ratio, py - (py - current.y) * ratio)
|
|
4209
|
+
};
|
|
4210
|
+
});
|
|
4211
|
+
}, [boundedPan]);
|
|
4212
|
+
const actual = (0, react.useCallback)(() => {
|
|
4213
|
+
if (!imageRef.current?.naturalWidth) return;
|
|
4214
|
+
resetGesture();
|
|
4215
|
+
setTransform({
|
|
4216
|
+
zoom: actualScale(),
|
|
4217
|
+
x: 0,
|
|
4218
|
+
y: 0
|
|
4219
|
+
});
|
|
4220
|
+
}, [resetGesture]);
|
|
4221
|
+
const measure = (0, react.useCallback)(() => {
|
|
4222
|
+
const next = {
|
|
4223
|
+
width: surfaceRef.current?.offsetWidth ?? 0,
|
|
4224
|
+
height: surfaceRef.current?.offsetHeight ?? 0,
|
|
4225
|
+
stageWidth: stageRef.current?.clientWidth ?? 0,
|
|
4226
|
+
stageHeight: stageRef.current?.clientHeight ?? 0
|
|
4227
|
+
};
|
|
4228
|
+
setGeometry((current) => Object.keys(next).every((key) => current[key] === next[key]) ? current : next);
|
|
4229
|
+
setPixelScale(1 / actualScale());
|
|
4230
|
+
setTransform((current) => ({
|
|
4231
|
+
...current,
|
|
4232
|
+
...boundedPan(current.zoom, current.x, current.y)
|
|
4233
|
+
}));
|
|
4234
|
+
}, [boundedPan]);
|
|
4235
|
+
(0, react.useEffect)(() => {
|
|
4236
|
+
if (!stageRef.current || !surfaceRef.current) return;
|
|
4237
|
+
const observer = new ResizeObserver(measure);
|
|
4238
|
+
observer.observe(stageRef.current);
|
|
4239
|
+
observer.observe(surfaceRef.current);
|
|
4240
|
+
measure();
|
|
4241
|
+
return () => observer.disconnect();
|
|
4242
|
+
}, [identity, measure]);
|
|
4243
|
+
(0, react.useEffect)(() => {
|
|
4244
|
+
const stage = stageRef.current;
|
|
4245
|
+
if (!stage) return;
|
|
4246
|
+
const wheel = (event) => {
|
|
4247
|
+
event.preventDefault();
|
|
4248
|
+
const delta = event.deltaY * (event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? stage.clientHeight : 1);
|
|
4249
|
+
setZoomAt(transformRef.current.zoom * Math.exp(-delta * .0015), event.clientX, event.clientY);
|
|
4250
|
+
};
|
|
4251
|
+
stage.addEventListener("wheel", wheel, { passive: false });
|
|
4252
|
+
return () => stage.removeEventListener("wheel", wheel);
|
|
4253
|
+
}, [identity, setZoomAt]);
|
|
4254
|
+
const onPointerDown = (event) => {
|
|
4255
|
+
if (event.button !== 0 || event.target.closest("button,a,input,textarea,select")) return;
|
|
4256
|
+
const current = transformRef.current;
|
|
4257
|
+
if (event.pointerType === "mouse" && current.zoom <= 1) return;
|
|
4258
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
4259
|
+
pointers.current.set(event.pointerId, {
|
|
4260
|
+
x: event.clientX,
|
|
4261
|
+
y: event.clientY
|
|
4262
|
+
});
|
|
4263
|
+
if (pointers.current.size === 2) {
|
|
4264
|
+
const [a, b] = [...pointers.current.values()];
|
|
4265
|
+
gesture.current = {
|
|
4266
|
+
kind: "pinch",
|
|
4267
|
+
distance: Math.hypot(a.x - b.x, a.y - b.y),
|
|
4268
|
+
transform: current
|
|
4269
|
+
};
|
|
4270
|
+
} else gesture.current = {
|
|
4271
|
+
kind: "pan",
|
|
4272
|
+
x: event.clientX,
|
|
4273
|
+
y: event.clientY,
|
|
4274
|
+
transform: current
|
|
4275
|
+
};
|
|
4276
|
+
setDragging(true);
|
|
4277
|
+
};
|
|
4278
|
+
const onPointerMove = (event) => {
|
|
4279
|
+
if (!pointers.current.has(event.pointerId)) return;
|
|
4280
|
+
pointers.current.set(event.pointerId, {
|
|
4281
|
+
x: event.clientX,
|
|
4282
|
+
y: event.clientY
|
|
4283
|
+
});
|
|
4284
|
+
const start = gesture.current;
|
|
4285
|
+
if (start?.kind === "pinch" && pointers.current.size >= 2) {
|
|
4286
|
+
const [a, b] = [...pointers.current.values()];
|
|
4287
|
+
setZoomAt(start.transform.zoom * Math.hypot(a.x - b.x, a.y - b.y) / Math.max(1, start.distance), (a.x + b.x) / 2, (a.y + b.y) / 2);
|
|
4288
|
+
} else if (start?.kind === "pan") setTransform({
|
|
4289
|
+
zoom: start.transform.zoom,
|
|
4290
|
+
...boundedPan(start.transform.zoom, start.transform.x + event.clientX - start.x, start.transform.y + event.clientY - start.y)
|
|
4291
|
+
});
|
|
4292
|
+
};
|
|
4293
|
+
const endPointer = (event) => {
|
|
4294
|
+
pointers.current.delete(event.pointerId);
|
|
4295
|
+
const remaining = [...pointers.current.values()][0];
|
|
4296
|
+
if (remaining) gesture.current = {
|
|
4297
|
+
kind: "pan",
|
|
4298
|
+
...remaining,
|
|
4299
|
+
transform: transformRef.current
|
|
4300
|
+
};
|
|
4301
|
+
else resetGesture();
|
|
4302
|
+
};
|
|
4303
|
+
return {
|
|
4304
|
+
transform,
|
|
4305
|
+
transformRef,
|
|
4306
|
+
dragging,
|
|
4307
|
+
pixelScale,
|
|
4308
|
+
geometry,
|
|
4309
|
+
stageRef,
|
|
4310
|
+
surfaceRef,
|
|
4311
|
+
imageRef,
|
|
4312
|
+
fit,
|
|
4313
|
+
actual,
|
|
4314
|
+
measure,
|
|
4315
|
+
setZoomAt,
|
|
4316
|
+
resetGesture,
|
|
4317
|
+
pointerHandlers: {
|
|
4318
|
+
onPointerDown,
|
|
4319
|
+
onPointerMove,
|
|
4320
|
+
onPointerUp: endPointer,
|
|
4321
|
+
onPointerCancel: endPointer,
|
|
4322
|
+
onLostPointerCapture: endPointer
|
|
4323
|
+
}
|
|
4324
|
+
};
|
|
4325
|
+
}
|
|
3473
4326
|
//#endregion
|
|
3474
4327
|
//#region src/subscription-image-viewer.jsx
|
|
3475
4328
|
const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
|
|
@@ -3482,8 +4335,15 @@ window.__ModuleLoader__.load({
|
|
|
3482
4335
|
const noteText = (annotations, t) => annotations.map((annotation, index) => {
|
|
3483
4336
|
return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
|
|
3484
4337
|
}).filter((line) => !line.endsWith(": ")).join("\n");
|
|
3485
|
-
function ViewerAction({ action, annotations, item, service, t }) {
|
|
4338
|
+
function ViewerAction({ action, annotations, item, service, revision, t }) {
|
|
3486
4339
|
const [state, setState] = (0, react.useState)("idle");
|
|
4340
|
+
const active = (0, react.useRef)(true);
|
|
4341
|
+
(0, react.useEffect)(() => {
|
|
4342
|
+
active.current = true;
|
|
4343
|
+
return () => {
|
|
4344
|
+
active.current = false;
|
|
4345
|
+
};
|
|
4346
|
+
}, []);
|
|
3487
4347
|
const invoke = async () => {
|
|
3488
4348
|
if (state === "pending") return;
|
|
3489
4349
|
setState("pending");
|
|
@@ -3493,10 +4353,11 @@ window.__ModuleLoader__.load({
|
|
|
3493
4353
|
item,
|
|
3494
4354
|
src: item.src
|
|
3495
4355
|
});
|
|
4356
|
+
if (!active.current || service.getSnapshot()?.revision !== revision) return;
|
|
3496
4357
|
setState("idle");
|
|
3497
4358
|
if (action.closeOnSuccess) service.close();
|
|
3498
4359
|
} catch {
|
|
3499
|
-
setState("failed");
|
|
4360
|
+
if (active.current) setState("failed");
|
|
3500
4361
|
}
|
|
3501
4362
|
};
|
|
3502
4363
|
const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
|
|
@@ -3571,13 +4432,15 @@ window.__ModuleLoader__.load({
|
|
|
3571
4432
|
}
|
|
3572
4433
|
function SubscriptionImageViewerOverlay({ service, t }) {
|
|
3573
4434
|
const request = (0, react.useSyncExternalStore)(service.subscribe, service.getSnapshot);
|
|
3574
|
-
const [
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
x: 0,
|
|
3578
|
-
y: 0
|
|
4435
|
+
const [cursor, setCursor] = (0, react.useState)({
|
|
4436
|
+
revision: 0,
|
|
4437
|
+
index: 0
|
|
3579
4438
|
});
|
|
3580
|
-
const
|
|
4439
|
+
const index = cursor.revision === request?.revision ? cursor.index : request?.index ?? 0;
|
|
4440
|
+
const setIndex = (update) => setCursor((current) => ({
|
|
4441
|
+
revision: request.revision,
|
|
4442
|
+
index: typeof update === "function" ? update(current.revision === request.revision ? current.index : request.index) : update
|
|
4443
|
+
}));
|
|
3581
4444
|
const [annotating, setAnnotating] = (0, react.useState)(false);
|
|
3582
4445
|
const [annotationsByImage, setAnnotationsByImage] = (0, react.useState)(service.getAnnotationsSnapshot);
|
|
3583
4446
|
const annotationsByImageRef = (0, react.useRef)(annotationsByImage);
|
|
@@ -3585,28 +4448,15 @@ window.__ModuleLoader__.load({
|
|
|
3585
4448
|
const [focusNote, setFocusNote] = (0, react.useState)();
|
|
3586
4449
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
3587
4450
|
const rootRef = (0, react.useRef)(null);
|
|
3588
|
-
const stageRef = (0, react.useRef)(null);
|
|
3589
|
-
const surfaceRef = (0, react.useRef)(null);
|
|
3590
|
-
const imageRef = (0, react.useRef)(null);
|
|
3591
|
-
const pointersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
3592
|
-
const gestureRef = (0, react.useRef)();
|
|
3593
|
-
const transformRef = (0, react.useRef)(transform);
|
|
3594
|
-
transformRef.current = transform;
|
|
3595
4451
|
annotationsByImageRef.current = annotationsByImage;
|
|
3596
4452
|
(0, react.useEffect)(() => {
|
|
3597
4453
|
if (request === void 0) return;
|
|
3598
|
-
setIndex(request.index);
|
|
3599
|
-
setTransform({
|
|
3600
|
-
zoom: 1,
|
|
3601
|
-
x: 0,
|
|
3602
|
-
y: 0
|
|
3603
|
-
});
|
|
3604
|
-
setDragging(false);
|
|
3605
4454
|
setAnnotating(false);
|
|
3606
4455
|
setSelected(void 0);
|
|
3607
4456
|
setCopied(false);
|
|
3608
4457
|
}, [request?.revision]);
|
|
3609
4458
|
const item = request?.items[index];
|
|
4459
|
+
const { transform, transformRef, dragging, pixelScale, geometry, stageRef, surfaceRef, imageRef, fit, actual, measure, setZoomAt, resetGesture, pointerHandlers } = useImageTransform(`${request?.revision}:${item?.id}:${item?.src}`);
|
|
3610
4460
|
const annotations = item === void 0 ? [] : annotationsByImage[item.id] ?? [];
|
|
3611
4461
|
const setAnnotations = (0, react.useCallback)((update) => {
|
|
3612
4462
|
if (item === void 0) return;
|
|
@@ -3620,53 +4470,6 @@ window.__ModuleLoader__.load({
|
|
|
3620
4470
|
service.setAnnotations(item.id, next);
|
|
3621
4471
|
setAnnotationsByImage(snapshot);
|
|
3622
4472
|
}, [item?.id, service]);
|
|
3623
|
-
const boundedPan = (0, react.useCallback)((zoom, x, y) => {
|
|
3624
|
-
const stage = stageRef.current;
|
|
3625
|
-
const surface = surfaceRef.current;
|
|
3626
|
-
if (stage === null || surface === null || zoom <= 1) return {
|
|
3627
|
-
x: 0,
|
|
3628
|
-
y: 0
|
|
3629
|
-
};
|
|
3630
|
-
const limitX = Math.max(0, (surface.offsetWidth * zoom - stage.clientWidth) / 2) + 28;
|
|
3631
|
-
const limitY = Math.max(0, (surface.offsetHeight * zoom - stage.clientHeight) / 2) + 28;
|
|
3632
|
-
return {
|
|
3633
|
-
x: clamp(x, -limitX, limitX),
|
|
3634
|
-
y: clamp(y, -limitY, limitY)
|
|
3635
|
-
};
|
|
3636
|
-
}, []);
|
|
3637
|
-
const setZoomAt = (0, react.useCallback)((nextZoom, clientX, clientY) => {
|
|
3638
|
-
const stage = stageRef.current;
|
|
3639
|
-
if (stage === null) return;
|
|
3640
|
-
setTransform((current) => {
|
|
3641
|
-
const next = clamp(nextZoom, .5, 8);
|
|
3642
|
-
const box = stage.getBoundingClientRect();
|
|
3643
|
-
const px = clientX - box.left - box.width / 2;
|
|
3644
|
-
const py = clientY - box.top - box.height / 2;
|
|
3645
|
-
const ratio = next / current.zoom;
|
|
3646
|
-
return {
|
|
3647
|
-
zoom: next,
|
|
3648
|
-
...boundedPan(next, px - (px - current.x) * ratio, py - (py - current.y) * ratio)
|
|
3649
|
-
};
|
|
3650
|
-
});
|
|
3651
|
-
}, [boundedPan]);
|
|
3652
|
-
const fit = (0, react.useCallback)(() => {
|
|
3653
|
-
setTransform({
|
|
3654
|
-
zoom: 1,
|
|
3655
|
-
x: 0,
|
|
3656
|
-
y: 0
|
|
3657
|
-
});
|
|
3658
|
-
}, []);
|
|
3659
|
-
const actual = (0, react.useCallback)(() => {
|
|
3660
|
-
const image = imageRef.current;
|
|
3661
|
-
const surface = surfaceRef.current;
|
|
3662
|
-
if (image === null || surface === null || image.naturalWidth === 0) return;
|
|
3663
|
-
const zoom = clamp(image.naturalWidth / Math.max(1, surface.offsetWidth), 1, 8);
|
|
3664
|
-
setTransform({
|
|
3665
|
-
zoom,
|
|
3666
|
-
x: 0,
|
|
3667
|
-
y: 0
|
|
3668
|
-
});
|
|
3669
|
-
}, []);
|
|
3670
4473
|
(0, react.useEffect)(() => {
|
|
3671
4474
|
if (request === void 0) return void 0;
|
|
3672
4475
|
const previousOverflow = document.body.style.overflow;
|
|
@@ -3734,77 +4537,13 @@ window.__ModuleLoader__.load({
|
|
|
3734
4537
|
annotations.length
|
|
3735
4538
|
]);
|
|
3736
4539
|
(0, react.useEffect)(() => {
|
|
3737
|
-
setTransform({
|
|
3738
|
-
zoom: 1,
|
|
3739
|
-
x: 0,
|
|
3740
|
-
y: 0
|
|
3741
|
-
});
|
|
3742
|
-
setDragging(false);
|
|
3743
4540
|
setAnnotating(false);
|
|
3744
4541
|
setSelected(void 0);
|
|
3745
|
-
}, [
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
(0, react.useEffect)(() => {
|
|
3751
|
-
const stage = stageRef.current;
|
|
3752
|
-
if (stage === null || request === void 0) return void 0;
|
|
3753
|
-
stage.addEventListener("wheel", onWheel, { passive: false });
|
|
3754
|
-
return () => stage.removeEventListener("wheel", onWheel);
|
|
3755
|
-
}, [onWheel, request]);
|
|
3756
|
-
const onPointerDown = (event) => {
|
|
3757
|
-
const target = event.target;
|
|
3758
|
-
if (event.button !== 0 || annotating || target instanceof Element && target.closest("button,textarea,input,a,select,[contenteditable=true]") !== null) return;
|
|
3759
|
-
pointersRef.current.set(event.pointerId, {
|
|
3760
|
-
x: event.clientX,
|
|
3761
|
-
y: event.clientY
|
|
3762
|
-
});
|
|
3763
|
-
if (pointersRef.current.size === 2) {
|
|
3764
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
3765
|
-
const [a, b] = [...pointersRef.current.values()];
|
|
3766
|
-
gestureRef.current = {
|
|
3767
|
-
kind: "pinch",
|
|
3768
|
-
distance: Math.hypot(a.x - b.x, a.y - b.y),
|
|
3769
|
-
transform
|
|
3770
|
-
};
|
|
3771
|
-
} else if (!annotating && transform.zoom > 1) {
|
|
3772
|
-
event.currentTarget.setPointerCapture(event.pointerId);
|
|
3773
|
-
gestureRef.current = {
|
|
3774
|
-
kind: "pan",
|
|
3775
|
-
x: event.clientX,
|
|
3776
|
-
y: event.clientY,
|
|
3777
|
-
transform
|
|
3778
|
-
};
|
|
3779
|
-
setDragging(true);
|
|
3780
|
-
}
|
|
3781
|
-
};
|
|
3782
|
-
const onPointerMove = (event) => {
|
|
3783
|
-
if (!pointersRef.current.has(event.pointerId)) return;
|
|
3784
|
-
pointersRef.current.set(event.pointerId, {
|
|
3785
|
-
x: event.clientX,
|
|
3786
|
-
y: event.clientY
|
|
3787
|
-
});
|
|
3788
|
-
const gesture = gestureRef.current;
|
|
3789
|
-
if (gesture?.kind === "pinch" && pointersRef.current.size >= 2) {
|
|
3790
|
-
const [a, b] = [...pointersRef.current.values()];
|
|
3791
|
-
const distance = Math.max(1, Math.hypot(a.x - b.x, a.y - b.y));
|
|
3792
|
-
setZoomAt(gesture.transform.zoom * distance / Math.max(1, gesture.distance), (a.x + b.x) / 2, (a.y + b.y) / 2);
|
|
3793
|
-
} else if (gesture?.kind === "pan") {
|
|
3794
|
-
const pan = boundedPan(gesture.transform.zoom, gesture.transform.x + event.clientX - gesture.x, gesture.transform.y + event.clientY - gesture.y);
|
|
3795
|
-
setTransform({
|
|
3796
|
-
zoom: gesture.transform.zoom,
|
|
3797
|
-
...pan
|
|
3798
|
-
});
|
|
3799
|
-
}
|
|
3800
|
-
};
|
|
3801
|
-
const endPointer = (event) => {
|
|
3802
|
-
pointersRef.current.delete(event.pointerId);
|
|
3803
|
-
if (pointersRef.current.size === 0) {
|
|
3804
|
-
gestureRef.current = void 0;
|
|
3805
|
-
setDragging(false);
|
|
3806
|
-
}
|
|
3807
|
-
};
|
|
4542
|
+
}, [
|
|
4543
|
+
item?.id,
|
|
4544
|
+
item?.src,
|
|
4545
|
+
request?.revision
|
|
4546
|
+
]);
|
|
3808
4547
|
const addAnnotation = (event) => {
|
|
3809
4548
|
if (!annotating || event.target.closest(".dcsiv-annotation")) return;
|
|
3810
4549
|
const bounds = surfaceRef.current?.getBoundingClientRect();
|
|
@@ -3836,6 +4575,15 @@ window.__ModuleLoader__.load({
|
|
|
3836
4575
|
if (request === void 0 || item === void 0) return null;
|
|
3837
4576
|
const meta = [item.width && item.height ? `${item.width} × ${item.height}` : void 0, bytesLabel(item.bytes)].filter(Boolean).join(" · ");
|
|
3838
4577
|
const showCounter = request.items.length > 1;
|
|
4578
|
+
const annotationPosition = (annotation) => {
|
|
4579
|
+
const ratio = globalThis.devicePixelRatio || 1;
|
|
4580
|
+
const align = (value) => Math.round(value * ratio) / ratio;
|
|
4581
|
+
return {
|
|
4582
|
+
left: align(geometry.stageWidth / 2 + transform.x + (annotation.x - .5) * geometry.width * transform.zoom - 12),
|
|
4583
|
+
top: align(geometry.stageHeight / 2 + transform.y + (annotation.y - .5) * geometry.height * transform.zoom - 12),
|
|
4584
|
+
visibility: geometry.width > 0 ? "visible" : "hidden"
|
|
4585
|
+
};
|
|
4586
|
+
};
|
|
3839
4587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3840
4588
|
ref: rootRef,
|
|
3841
4589
|
className: "dcsiv-root",
|
|
@@ -3861,7 +4609,10 @@ window.__ModuleLoader__.load({
|
|
|
3861
4609
|
"data-active": annotating,
|
|
3862
4610
|
"aria-label": annotating ? t("imageAnnotateCancel") : t("imageAnnotate"),
|
|
3863
4611
|
"aria-pressed": annotating,
|
|
3864
|
-
onClick: () =>
|
|
4612
|
+
onClick: () => {
|
|
4613
|
+
resetGesture();
|
|
4614
|
+
setAnnotating((value) => !value);
|
|
4615
|
+
},
|
|
3865
4616
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
3866
4617
|
className: "dcsiv-label",
|
|
3867
4618
|
children: annotating ? t("imageAnnotateCancel") : t("imageAnnotate")
|
|
@@ -3903,7 +4654,7 @@ window.__ModuleLoader__.load({
|
|
|
3903
4654
|
}),
|
|
3904
4655
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
3905
4656
|
className: "dcsiv-zoom",
|
|
3906
|
-
children: [Math.round(transform.zoom * 100), "%"]
|
|
4657
|
+
children: [Math.round(transform.zoom * pixelScale * 100), "%"]
|
|
3907
4658
|
}),
|
|
3908
4659
|
item.download === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
|
|
3909
4660
|
className: "dcsiv-download",
|
|
@@ -3923,8 +4674,9 @@ window.__ModuleLoader__.load({
|
|
|
3923
4674
|
annotations,
|
|
3924
4675
|
item,
|
|
3925
4676
|
service,
|
|
4677
|
+
revision: request.revision,
|
|
3926
4678
|
t
|
|
3927
|
-
}, action.id))
|
|
4679
|
+
}, `${request.revision}:${index}:${action.id}`))
|
|
3928
4680
|
]
|
|
3929
4681
|
})
|
|
3930
4682
|
}),
|
|
@@ -3945,34 +4697,33 @@ window.__ModuleLoader__.load({
|
|
|
3945
4697
|
onClick: (event) => {
|
|
3946
4698
|
if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
|
|
3947
4699
|
},
|
|
3948
|
-
|
|
3949
|
-
onPointerMove,
|
|
3950
|
-
onPointerUp: endPointer,
|
|
3951
|
-
onPointerCancel: endPointer,
|
|
4700
|
+
...!annotating ? pointerHandlers : {},
|
|
3952
4701
|
onDoubleClick: () => {
|
|
3953
4702
|
if (transform.zoom === 1) actual();
|
|
3954
4703
|
else fit();
|
|
3955
4704
|
},
|
|
3956
|
-
children: [
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
4705
|
+
children: [
|
|
4706
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
4707
|
+
ref: surfaceRef,
|
|
4708
|
+
className: "dcsiv-surface",
|
|
4709
|
+
onClick: addAnnotation,
|
|
4710
|
+
style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
|
|
4711
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
4712
|
+
ref: imageRef,
|
|
4713
|
+
className: "dcsiv-image",
|
|
4714
|
+
src: item.src,
|
|
4715
|
+
alt: item.name,
|
|
4716
|
+
draggable: "false",
|
|
4717
|
+
onLoad: () => {
|
|
4718
|
+
measure();
|
|
4719
|
+
}
|
|
4720
|
+
}, `${request.revision}:${item.id}`)
|
|
4721
|
+
}),
|
|
4722
|
+
annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3968
4723
|
className: "dcsiv-annotation",
|
|
3969
4724
|
"data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
|
|
3970
4725
|
"data-y": annotation.y < .28 ? "down" : "up",
|
|
3971
|
-
style:
|
|
3972
|
-
left: `${annotation.x * 100}%`,
|
|
3973
|
-
top: `${annotation.y * 100}%`,
|
|
3974
|
-
transform: `translate(-50%,-50%) scale(${1 / transform.zoom})`
|
|
3975
|
-
},
|
|
4726
|
+
style: annotationPosition(annotation),
|
|
3976
4727
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3977
4728
|
type: "button",
|
|
3978
4729
|
className: "dcsiv-pin",
|
|
@@ -4028,43 +4779,44 @@ window.__ModuleLoader__.load({
|
|
|
4028
4779
|
})
|
|
4029
4780
|
]
|
|
4030
4781
|
}) : null]
|
|
4031
|
-
}, annotation.id))
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4782
|
+
}, annotation.id)),
|
|
4783
|
+
showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4784
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4785
|
+
type: "button",
|
|
4786
|
+
className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
|
|
4787
|
+
"aria-label": t("imagePrevious"),
|
|
4788
|
+
onClick: (event) => {
|
|
4789
|
+
event.stopPropagation();
|
|
4790
|
+
setIndex((value) => (value - 1 + request.items.length) % request.items.length);
|
|
4791
|
+
},
|
|
4792
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
|
|
4793
|
+
}),
|
|
4794
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4795
|
+
type: "button",
|
|
4796
|
+
className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
|
|
4797
|
+
"aria-label": t("imageNext"),
|
|
4798
|
+
onClick: (event) => {
|
|
4799
|
+
event.stopPropagation();
|
|
4800
|
+
setIndex((value) => (value + 1) % request.items.length);
|
|
4801
|
+
},
|
|
4802
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
|
|
4803
|
+
}),
|
|
4804
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4805
|
+
className: "dcsiv-counter",
|
|
4806
|
+
children: [
|
|
4807
|
+
index + 1,
|
|
4808
|
+
" / ",
|
|
4809
|
+
request.items.length
|
|
4810
|
+
]
|
|
4811
|
+
})
|
|
4812
|
+
] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4813
|
+
className: "dcsiv-hint",
|
|
4814
|
+
children: t("imageAnnotateHint")
|
|
4815
|
+
}) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4816
|
+
className: "dcsiv-hint",
|
|
4817
|
+
children: t("imageZoomHint")
|
|
4818
|
+
}) : null
|
|
4819
|
+
]
|
|
4068
4820
|
}), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
4069
4821
|
type: "button",
|
|
4070
4822
|
className: "dcsiv-copy-notes",
|
|
@@ -4100,7 +4852,7 @@ window.__ModuleLoader__.load({
|
|
|
4100
4852
|
const items = (Array.isArray(request?.items) ? request.items : []).flatMap((item, position) => {
|
|
4101
4853
|
if (typeof item?.src !== "string" || item.src === "") return [];
|
|
4102
4854
|
return [{
|
|
4103
|
-
id: typeof item.id === "string" && item.id !== "" ? item.id :
|
|
4855
|
+
id: typeof item.id === "string" && item.id !== "" ? item.id : item.src,
|
|
4104
4856
|
src: item.src,
|
|
4105
4857
|
name: typeof item.name === "string" && item.name !== "" ? item.name : `Image ${position + 1}`,
|
|
4106
4858
|
width: boundedNumber(item.width, void 0),
|
|
@@ -4211,7 +4963,8 @@ window.__ModuleLoader__.load({
|
|
|
4211
4963
|
imageEditing: true,
|
|
4212
4964
|
imageViewer: true,
|
|
4213
4965
|
imageAnnotations: true,
|
|
4214
|
-
imageSketch:
|
|
4966
|
+
imageSketch: false,
|
|
4967
|
+
imageSketchAgent: false
|
|
4215
4968
|
});
|
|
4216
4969
|
function readImageFeatures(value = {}) {
|
|
4217
4970
|
return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
|
|
@@ -4849,7 +5602,7 @@ window.__ModuleLoader__.load({
|
|
|
4849
5602
|
}
|
|
4850
5603
|
//#endregion
|
|
4851
5604
|
//#region src/version.js
|
|
4852
|
-
const PACKAGE_VERSION = "2.0.
|
|
5605
|
+
const PACKAGE_VERSION = "2.1.0-beta.1";
|
|
4853
5606
|
//#endregion
|
|
4854
5607
|
//#region src/client-recovery.js
|
|
4855
5608
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -5416,7 +6169,9 @@ window.__ModuleLoader__.load({
|
|
|
5416
6169
|
//#region src/image-setting-groups.js
|
|
5417
6170
|
const IMAGE_SETTING_GROUPS = Object.freeze({
|
|
5418
6171
|
imageCapability: ["imageGeneration", "imageEditing"],
|
|
5419
|
-
imageEntryPoints: ["imageShortcut"
|
|
6172
|
+
imageEntryPoints: ["imageShortcut"],
|
|
6173
|
+
sketchCanvas: ["imageSketch"],
|
|
6174
|
+
sketchAgent: ["imageSketchAgent"],
|
|
5420
6175
|
imageBrowsing: ["imageViewer", "imageAnnotations"]
|
|
5421
6176
|
});
|
|
5422
6177
|
function imageGroupValue(snapshot, group) {
|
|
@@ -7193,7 +7948,7 @@ window.__ModuleLoader__.load({
|
|
|
7193
7948
|
})), "codex-subscription: Image trigger"));
|
|
7194
7949
|
const sessionInput = (sessionId) => {
|
|
7195
7950
|
const actx = sessions.scope(sessionId);
|
|
7196
|
-
if (!actx ||
|
|
7951
|
+
if (!actx || !conversation.input?.for) throw new Error("Image composer is unavailable");
|
|
7197
7952
|
return conversation.input.for(actx);
|
|
7198
7953
|
};
|
|
7199
7954
|
const openSketchImage = (sessionId) => async (src, name) => {
|
|
@@ -7209,7 +7964,7 @@ window.__ModuleLoader__.load({
|
|
|
7209
7964
|
const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false) => {
|
|
7210
7965
|
if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
|
|
7211
7966
|
const actx = sessions.scope(sessionId);
|
|
7212
|
-
if (actx === void 0 ||
|
|
7967
|
+
if (actx === void 0 || conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
|
|
7213
7968
|
const response = await fetch(src);
|
|
7214
7969
|
if (!response.ok) throw new Error("Could not read generated image");
|
|
7215
7970
|
const blob = await response.blob();
|
|
@@ -7220,7 +7975,7 @@ window.__ModuleLoader__.load({
|
|
|
7220
7975
|
}
|
|
7221
7976
|
const input = conversation.input.for(actx);
|
|
7222
7977
|
if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
|
|
7223
|
-
if (files.length) attachImageFiles(conversation, input, files);
|
|
7978
|
+
if (files.length) attachImageFiles(conversation, input, files, sessionId);
|
|
7224
7979
|
sessions.open(sessionId);
|
|
7225
7980
|
if (sourceInDraft && !annotations.length) return;
|
|
7226
7981
|
if (!input.state.getSnapshot().draft.trim()) input.setDraft(draft);
|
|
@@ -7270,6 +8025,8 @@ window.__ModuleLoader__.load({
|
|
|
7270
8025
|
inject: (sessionId) => ({
|
|
7271
8026
|
preference,
|
|
7272
8027
|
t,
|
|
8028
|
+
sessionId,
|
|
8029
|
+
rpc,
|
|
7273
8030
|
registerOpen: (callback) => {
|
|
7274
8031
|
sketchOpeners.set(sessionId, callback);
|
|
7275
8032
|
return () => {
|
|
@@ -7279,7 +8036,7 @@ window.__ModuleLoader__.load({
|
|
|
7279
8036
|
attachSketch: (blob) => {
|
|
7280
8037
|
const current = preference.getSnapshot();
|
|
7281
8038
|
if (!current.imageSketch || !current.imageEditing) throw new Error("Sketch editing is disabled");
|
|
7282
|
-
attachImageFiles(conversation, sessionInput(sessionId), [new File([blob], "sketch-reference.png", { type: "image/png" })]);
|
|
8039
|
+
attachImageFiles(conversation, sessionInput(sessionId), [new File([blob], "sketch-reference.png", { type: "image/png" })], sessionId);
|
|
7283
8040
|
}
|
|
7284
8041
|
})
|
|
7285
8042
|
}, ImageWorkspace));
|