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/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)("span", {
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.stroke();
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.stroke();
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 > 200) return doc;
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: "image/png,image/jpeg,image/webp",
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.jsx)("select", {
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: Object.keys(SKETCH_RATIOS).map((ratio) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
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) >= 200) {
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 > 200) || action === "up" && current === doc.current.layers.at(-1) || action === "down" && current === doc.current.layers[0],
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 created = conversation.createDraftImages(files);
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 (!input.addImages(created.map((item) => item.id))) throw new Error("The composer is busy");
2968
+ if (!add.call(input, created.map((item) => item.id))) throw new Error("The composer is busy");
2318
2969
  } catch (error) {
2319
- conversation.releaseDraftImages(created);
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: "@生图与草图 Beta;隐藏后仍可用自然语言生图。",
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 image",
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 and Sketch Beta. Natural-language image requests still work when hidden.",
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:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}
3312
- .codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}
3313
- .codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}
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:24px}
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$1 = (value, min, max) => Math.min(max, Math.max(min, value));
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$1(value, margin, size - margin);
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 [index, setIndex] = (0, react.useState)(0);
3575
- const [transform, setTransform] = (0, react.useState)({
3576
- zoom: 1,
3577
- x: 0,
3578
- y: 0
4435
+ const [cursor, setCursor] = (0, react.useState)({
4436
+ revision: 0,
4437
+ index: 0
3579
4438
  });
3580
- const [dragging, setDragging] = (0, react.useState)(false);
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
- }, [item?.id]);
3746
- const onWheel = (0, react.useCallback)((event) => {
3747
- event.preventDefault();
3748
- setZoomAt(transformRef.current.zoom * Math.exp(-event.deltaY * .0015), event.clientX, event.clientY);
3749
- }, [setZoomAt]);
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: () => setAnnotating((value) => !value),
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
- onPointerDown,
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: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3957
- ref: surfaceRef,
3958
- className: "dcsiv-surface",
3959
- onClick: addAnnotation,
3960
- style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
3961
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
3962
- ref: imageRef,
3963
- className: "dcsiv-image",
3964
- src: item.src,
3965
- alt: item.name,
3966
- draggable: "false"
3967
- }), annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
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
- }), showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4033
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4034
- type: "button",
4035
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
4036
- "aria-label": t("imagePrevious"),
4037
- onClick: (event) => {
4038
- event.stopPropagation();
4039
- setIndex((value) => (value - 1 + request.items.length) % request.items.length);
4040
- },
4041
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
4042
- }),
4043
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4044
- type: "button",
4045
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
4046
- "aria-label": t("imageNext"),
4047
- onClick: (event) => {
4048
- event.stopPropagation();
4049
- setIndex((value) => (value + 1) % request.items.length);
4050
- },
4051
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
4052
- }),
4053
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4054
- className: "dcsiv-counter",
4055
- children: [
4056
- index + 1,
4057
- " / ",
4058
- request.items.length
4059
- ]
4060
- })
4061
- ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4062
- className: "dcsiv-hint",
4063
- children: t("imageAnnotateHint")
4064
- }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4065
- className: "dcsiv-hint",
4066
- children: t("imageZoomHint")
4067
- }) : null]
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 : `image-${position + 1}`,
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: true
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.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", "imageSketch"],
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 || typeof conversation.createDraftImages !== "function" || !conversation.input?.for) throw new Error("Image composer is unavailable");
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 || typeof conversation.createDraftImages !== "function" || conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
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));