dsh-codex-subscription 2.0.1 → 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: () => {
@@ -2367,6 +3013,11 @@ window.__ModuleLoader__.load({
2367
3013
  //#endregion
2368
3014
  //#region src/client-locales.js
2369
3015
  const zh = {
3016
+ sketchFill: "填色",
3017
+ sketchDownload: "下载",
3018
+ sketchExportFormat: "导出格式",
3019
+ sketchEditableFile: "可编辑草稿",
3020
+ sketchFormatHint: "PNG 为合并图片;PSD 交换像素图层;可编辑草稿保留原生笔画。可导入 PSD 或草稿文件(32 MB 内);PSD 仅支持普通像素图层,长边适配至 1024。",
2370
3021
  imageToSketch: "进入草图",
2371
3022
  sketchPictures: "图片",
2372
3023
  sketchPictureAdd: "添加",
@@ -2391,7 +3042,7 @@ window.__ModuleLoader__.load({
2391
3042
  sketchKey_fit: "重置视图",
2392
3043
  sketchFiles: "草稿",
2393
3044
  sketchNew: "新建草图",
2394
- sketchImport: "导入图片",
3045
+ sketchImport: "导入文件",
2395
3046
  sketchDraftName: "草稿名称",
2396
3047
  sketchSave: "保存草稿",
2397
3048
  sketchDeleteDraft: "删除",
@@ -2430,7 +3081,7 @@ window.__ModuleLoader__.load({
2430
3081
  imageCapability_on: "启用",
2431
3082
  imageCapability_off: "关闭",
2432
3083
  imageEntryPoints: "创作入口",
2433
- imageEntryPointsHint: "@生图与草图 Beta;隐藏后仍可用自然语言生图。",
3084
+ imageEntryPointsHint: "@生图入口;隐藏后仍可用自然语言生图。",
2434
3085
  imageEntryPoints_on: "显示",
2435
3086
  imageEntryPoints_off: "隐藏",
2436
3087
  imageBrowsing: "图片浏览",
@@ -2466,6 +3117,14 @@ window.__ModuleLoader__.load({
2466
3117
  sketchTool_rectangle: "矩形",
2467
3118
  sketchTool_circle: "椭圆",
2468
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: "关闭",
2469
3128
  imageSettings: "图片",
2470
3129
  imageGeneration: "生成新图片",
2471
3130
  imageEditing: "参考图编辑",
@@ -2739,6 +3398,11 @@ window.__ModuleLoader__.load({
2739
3398
  imageRemoveAnnotation: "删除标注"
2740
3399
  };
2741
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.",
2742
3406
  imageToSketch: "Open in sketch",
2743
3407
  sketchPictures: "Images",
2744
3408
  sketchPictureAdd: "Add",
@@ -2763,7 +3427,7 @@ window.__ModuleLoader__.load({
2763
3427
  sketchKey_fit: "Reset view",
2764
3428
  sketchFiles: "Drafts",
2765
3429
  sketchNew: "New sketch",
2766
- sketchImport: "Import image",
3430
+ sketchImport: "Import file",
2767
3431
  sketchDraftName: "Draft name",
2768
3432
  sketchSave: "Save draft",
2769
3433
  sketchDeleteDraft: "Delete",
@@ -2802,7 +3466,7 @@ window.__ModuleLoader__.load({
2802
3466
  imageCapability_on: "Enabled",
2803
3467
  imageCapability_off: "Off",
2804
3468
  imageEntryPoints: "Creative shortcuts",
2805
- 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.",
2806
3470
  imageEntryPoints_on: "Show",
2807
3471
  imageEntryPoints_off: "Hide",
2808
3472
  imageBrowsing: "Image browsing",
@@ -2838,6 +3502,14 @@ window.__ModuleLoader__.load({
2838
3502
  sketchTool_rectangle: "Rectangle",
2839
3503
  sketchTool_circle: "Ellipse",
2840
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",
2841
3513
  imageSettings: "Images",
2842
3514
  imageGeneration: "Generate new images",
2843
3515
  imageEditing: "Reference image editing",
@@ -3313,14 +3985,14 @@ window.__ModuleLoader__.load({
3313
3985
  .codexModelSelectMenu{overflow:visible}
3314
3986
  .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}
3315
3987
  .codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}
3316
- .codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}
3317
- .codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}
3318
- .codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}
3319
- .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)}
3320
3991
  .codexImageToolTitle{font-weight:500}
3321
3992
  .codexImageToolState{color:var(--dsw-alias-label-tertiary)}
3322
3993
  .codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}
3323
- .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}
3324
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}
3325
3997
  .codexGeneratedImageFrame img{display:block;width:100%;height:100%;object-fit:cover}
3326
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}
@@ -4291,7 +4963,8 @@ window.__ModuleLoader__.load({
4291
4963
  imageEditing: true,
4292
4964
  imageViewer: true,
4293
4965
  imageAnnotations: true,
4294
- imageSketch: true
4966
+ imageSketch: false,
4967
+ imageSketchAgent: false
4295
4968
  });
4296
4969
  function readImageFeatures(value = {}) {
4297
4970
  return Object.fromEntries(Object.entries(IMAGE_FEATURE_DEFAULTS).map(([key, fallback]) => [key, typeof value?.[key] === "boolean" ? value[key] : fallback]));
@@ -4929,7 +5602,7 @@ window.__ModuleLoader__.load({
4929
5602
  }
4930
5603
  //#endregion
4931
5604
  //#region src/version.js
4932
- const PACKAGE_VERSION = "2.0.1";
5605
+ const PACKAGE_VERSION = "2.1.0-beta.1";
4933
5606
  //#endregion
4934
5607
  //#region src/client-recovery.js
4935
5608
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
@@ -5496,7 +6169,9 @@ window.__ModuleLoader__.load({
5496
6169
  //#region src/image-setting-groups.js
5497
6170
  const IMAGE_SETTING_GROUPS = Object.freeze({
5498
6171
  imageCapability: ["imageGeneration", "imageEditing"],
5499
- imageEntryPoints: ["imageShortcut", "imageSketch"],
6172
+ imageEntryPoints: ["imageShortcut"],
6173
+ sketchCanvas: ["imageSketch"],
6174
+ sketchAgent: ["imageSketchAgent"],
5500
6175
  imageBrowsing: ["imageViewer", "imageAnnotations"]
5501
6176
  });
5502
6177
  function imageGroupValue(snapshot, group) {
@@ -7350,6 +8025,8 @@ window.__ModuleLoader__.load({
7350
8025
  inject: (sessionId) => ({
7351
8026
  preference,
7352
8027
  t,
8028
+ sessionId,
8029
+ rpc,
7353
8030
  registerOpen: (callback) => {
7354
8031
  sketchOpeners.set(sessionId, callback);
7355
8032
  return () => {