agentic-canvas 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/dist/{chunk-JQCUVXXD.js → chunk-5II2WZIL.js} +2 -2
- package/dist/{chunk-NLTN7J22.js → chunk-A4YK2SWM.js} +3 -5
- package/dist/chunk-EDQNA2SU.js +290 -0
- package/dist/{chunk-ZSPZMGHG.js → chunk-MVWUIBGV.js} +50 -320
- package/dist/{chunk-M74ENUWR.js → chunk-Z6KLGAFP.js} +6 -6
- package/dist/cli.js +4 -4
- package/dist/embed.d.ts +29 -4
- package/dist/embed.js +114 -35
- package/dist/index.js +11 -11
- package/dist/live-worker.js +4 -4
- package/dist/live.js +6 -5
- package/dist/ui.css +17 -9
- package/dist/worker.js +4 -4
- package/llms.txt +1 -1
- package/package.json +4 -2
- package/dist/chunk-JOBTIAFJ.js +0 -19
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// src/core/principal.ts
|
|
2
|
+
import { PRINCIPAL_KINDS, isMachineMade, parsePrincipal } from "agentic-identity";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
var principalSchema = z.object({ kind: z.string(), id: z.string() }).strict().transform((value, ctx) => {
|
|
5
|
+
try {
|
|
6
|
+
return parsePrincipal(value);
|
|
7
|
+
} catch (error) {
|
|
8
|
+
ctx.addIssue({ code: "custom", message: error instanceof Error ? error.message : "invalid principal" });
|
|
9
|
+
return z.NEVER;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var storedPrincipalSchema = z.object({ kind: z.enum(PRINCIPAL_KINDS), id: z.string().min(1).max(200) }).strict();
|
|
6
13
|
|
|
7
14
|
// src/core/board.ts
|
|
8
|
-
import { randomUUID } from "crypto";
|
|
9
15
|
import * as Y from "yjs";
|
|
10
|
-
import { z } from "zod";
|
|
16
|
+
import { z as z2 } from "zod";
|
|
11
17
|
var CanvasError = class extends Error {
|
|
12
18
|
constructor(code, message) {
|
|
13
19
|
super(message);
|
|
@@ -19,18 +25,18 @@ var CanvasError = class extends Error {
|
|
|
19
25
|
var SHAPE_TYPES = ["rect", "ellipse", "diamond", "text", "sticky", "frame", "image"];
|
|
20
26
|
var MAX_ELEMENTS = 1e4;
|
|
21
27
|
var MAX_TEXT = 1e4;
|
|
22
|
-
var id =
|
|
23
|
-
var coord =
|
|
24
|
-
var size =
|
|
25
|
-
var text =
|
|
28
|
+
var id = z2.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
|
|
29
|
+
var coord = z2.number().finite().min(-1e7).max(1e7);
|
|
30
|
+
var size = z2.number().finite().gt(0).max(1e7);
|
|
31
|
+
var text = z2.string().max(MAX_TEXT);
|
|
26
32
|
var COLOR_PATTERN = /^(#[0-9a-fA-F]{3,8}|[a-z]{3,20})$/;
|
|
27
33
|
var IMAGE_PATTERN = /^(data:image\/(png|jpeg|gif|webp);base64,[A-Za-z0-9+/]+=*|blob:[A-Za-z0-9._-]{1,200})$/;
|
|
28
|
-
var color =
|
|
29
|
-
var imageSrc =
|
|
30
|
-
var endpoint =
|
|
31
|
-
var shapeInput =
|
|
34
|
+
var color = z2.string().regex(COLOR_PATTERN);
|
|
35
|
+
var imageSrc = z2.string().max(2e6).regex(IMAGE_PATTERN);
|
|
36
|
+
var endpoint = z2.union([z2.object({ id }).strict(), z2.object({ x: coord, y: coord }).strict()]);
|
|
37
|
+
var shapeInput = z2.object({
|
|
32
38
|
id: id.optional(),
|
|
33
|
-
type:
|
|
39
|
+
type: z2.enum(SHAPE_TYPES),
|
|
34
40
|
x: coord,
|
|
35
41
|
y: coord,
|
|
36
42
|
w: size.default(120),
|
|
@@ -42,40 +48,40 @@ var shapeInput = z.object({
|
|
|
42
48
|
}).strict().refine((s) => s.type === "image" === (s.src !== void 0), {
|
|
43
49
|
message: "src is required for image shapes and allowed on no other type"
|
|
44
50
|
});
|
|
45
|
-
var connectorInput =
|
|
51
|
+
var connectorInput = z2.object({
|
|
46
52
|
id: id.optional(),
|
|
47
53
|
from: endpoint,
|
|
48
54
|
to: endpoint,
|
|
49
55
|
text: text.default(""),
|
|
50
|
-
startArrow:
|
|
51
|
-
endArrow:
|
|
56
|
+
startArrow: z2.boolean().default(false),
|
|
57
|
+
endArrow: z2.boolean().default(true),
|
|
52
58
|
stroke: color.optional()
|
|
53
59
|
}).strict();
|
|
54
|
-
var shapePatch =
|
|
55
|
-
var connectorPatch =
|
|
56
|
-
var delta =
|
|
60
|
+
var shapePatch = z2.object({ x: coord, y: coord, w: size, h: size, text, fill: color, stroke: color, src: imageSrc }).partial().strict();
|
|
61
|
+
var connectorPatch = z2.object({ from: endpoint, to: endpoint, text, startArrow: z2.boolean(), endArrow: z2.boolean(), stroke: color }).partial().strict();
|
|
62
|
+
var delta = z2.number().finite().min(-2e7).max(2e7);
|
|
57
63
|
var MAX_Z = 1e9;
|
|
58
64
|
var stampFields = {
|
|
59
65
|
id,
|
|
60
|
-
z:
|
|
66
|
+
z: z2.number().finite().min(-MAX_Z).max(MAX_Z),
|
|
61
67
|
groupId: id.optional(),
|
|
62
68
|
author: storedPrincipalSchema,
|
|
63
69
|
editedBy: storedPrincipalSchema
|
|
64
70
|
};
|
|
65
|
-
var storedShape =
|
|
66
|
-
var storedConnector =
|
|
71
|
+
var storedShape = z2.object({ ...stampFields, type: z2.enum(SHAPE_TYPES), x: coord, y: coord, w: size, h: size, text, fill: color.optional(), stroke: color.optional(), src: imageSrc.optional() }).refine((s) => s.type === "image" === (s.src !== void 0), { message: "src is required for image shapes and allowed on no other type" });
|
|
72
|
+
var storedConnector = z2.object({
|
|
67
73
|
...stampFields,
|
|
68
|
-
type:
|
|
74
|
+
type: z2.literal("connector"),
|
|
69
75
|
from: endpoint,
|
|
70
76
|
to: endpoint,
|
|
71
77
|
text,
|
|
72
|
-
startArrow:
|
|
73
|
-
endArrow:
|
|
78
|
+
startArrow: z2.boolean(),
|
|
79
|
+
endArrow: z2.boolean(),
|
|
74
80
|
stroke: color.optional()
|
|
75
81
|
});
|
|
76
|
-
var storedGroup =
|
|
77
|
-
var storedElement =
|
|
78
|
-
var titleSchema =
|
|
82
|
+
var storedGroup = z2.object({ ...stampFields, type: z2.literal("group") });
|
|
83
|
+
var storedElement = z2.union([storedShape, storedConnector, storedGroup]);
|
|
84
|
+
var titleSchema = z2.string().max(200);
|
|
79
85
|
function parse(schema, value) {
|
|
80
86
|
const result = schema.safeParse(value);
|
|
81
87
|
if (!result.success) {
|
|
@@ -134,7 +140,7 @@ var Board = class _Board {
|
|
|
134
140
|
return value.success ? value.data : void 0;
|
|
135
141
|
}
|
|
136
142
|
setTitle(title, actor) {
|
|
137
|
-
const t = parse(
|
|
143
|
+
const t = parse(z2.string().max(200), title);
|
|
138
144
|
const by = parse(principalSchema, actor);
|
|
139
145
|
this.doc.transact(() => {
|
|
140
146
|
this.meta.set("title", t);
|
|
@@ -325,7 +331,7 @@ var Board = class _Board {
|
|
|
325
331
|
/** Shift an element, or a whole group. Returns the ids that moved. Bound connector ends follow their shapes. */
|
|
326
332
|
move(elementId, dx, dy, actor) {
|
|
327
333
|
const by = parse(principalSchema, actor);
|
|
328
|
-
const [mx, my] = parse(
|
|
334
|
+
const [mx, my] = parse(z2.tuple([delta, delta]), [dx, dy]);
|
|
329
335
|
this.require(elementId);
|
|
330
336
|
const free = (e) => hasId(e) ? e : { x: e.x + mx, y: e.y + my };
|
|
331
337
|
const plan = [];
|
|
@@ -354,7 +360,7 @@ var Board = class _Board {
|
|
|
354
360
|
}
|
|
355
361
|
group(ids, actor, groupId) {
|
|
356
362
|
const by = parse(principalSchema, actor);
|
|
357
|
-
const members = parse(
|
|
363
|
+
const members = parse(z2.array(id).min(2).max(MAX_ELEMENTS), ids);
|
|
358
364
|
const gid = parse(id.optional(), groupId);
|
|
359
365
|
if (new Set(members).size !== members.length) throw new CanvasError("INVALID", "member ids repeat");
|
|
360
366
|
const parents = new Set(members.map((m) => this.require(m).groupId));
|
|
@@ -424,12 +430,12 @@ var Board = class _Board {
|
|
|
424
430
|
}
|
|
425
431
|
/** The z value just past the top or the bottom. Not clamped: the caller checks the range. */
|
|
426
432
|
edgeZ(direction) {
|
|
427
|
-
let
|
|
433
|
+
let z3 = 0;
|
|
428
434
|
for (const m of this.elements.values()) {
|
|
429
435
|
const value = m instanceof Y.Map ? m.get("z") : void 0;
|
|
430
|
-
if (isValidZ(value))
|
|
436
|
+
if (isValidZ(value)) z3 = direction === 1 ? Math.max(z3, value) : Math.min(z3, value);
|
|
431
437
|
}
|
|
432
|
-
return
|
|
438
|
+
return z3 + direction;
|
|
433
439
|
}
|
|
434
440
|
/** The element and, for a group, everything below it. Cycle-safe. */
|
|
435
441
|
subtree(elementId) {
|
|
@@ -446,7 +452,7 @@ var Board = class _Board {
|
|
|
446
452
|
}
|
|
447
453
|
create(fields, actor) {
|
|
448
454
|
const by = parse(principalSchema, actor);
|
|
449
|
-
const elementId = fields.id ?? randomUUID();
|
|
455
|
+
const elementId = fields.id ?? crypto.randomUUID();
|
|
450
456
|
if (this.elements.has(elementId)) throw new CanvasError("DUPLICATE_ID", `element "${elementId}" already exists`);
|
|
451
457
|
if (this.elements.size >= this.maxElements) throw new CanvasError("LIMIT", `a board holds at most ${this.maxElements} elements`);
|
|
452
458
|
const record = { ...fields, id: elementId, z: clampZ(this.edgeZ(1)), author: by, editedBy: by };
|
|
@@ -469,116 +475,6 @@ var Board = class _Board {
|
|
|
469
475
|
}
|
|
470
476
|
};
|
|
471
477
|
|
|
472
|
-
// src/core/flow.ts
|
|
473
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
474
|
-
import dagre from "@dagrejs/dagre";
|
|
475
|
-
import * as Y2 from "yjs";
|
|
476
|
-
var MAX_FLOW_NODES = 500;
|
|
477
|
-
var MAX_FLOW_EDGES = 2e3;
|
|
478
|
-
var MAX_FLOW_CHARS = 2e5;
|
|
479
|
-
var NODE = String.raw`([A-Za-z0-9_-]{1,48})(?:\[([^\]]*)\]|\(([^)]*)\)|\{([^}]*)\})?`;
|
|
480
|
-
var EDGE = new RegExp(String.raw`^${NODE}\s*->\s*${NODE}\s*(?::(.*))?$`);
|
|
481
|
-
var ALONE = new RegExp(String.raw`^${NODE}$`);
|
|
482
|
-
var SHAPES = ["rect", "ellipse", "diamond"];
|
|
483
|
-
function parseFlow(source) {
|
|
484
|
-
if (source.length > MAX_FLOW_CHARS) throw new CanvasError("LIMIT", `flow text is over ${MAX_FLOW_CHARS} characters`);
|
|
485
|
-
const graph = { direction: "TB", nodes: [], edges: [] };
|
|
486
|
-
const byId = /* @__PURE__ */ new Map();
|
|
487
|
-
const fail = (line, message) => {
|
|
488
|
-
throw new CanvasError("INVALID", `line ${line}: ${message}`);
|
|
489
|
-
};
|
|
490
|
-
const node = (line, m, at) => {
|
|
491
|
-
const id2 = m[at];
|
|
492
|
-
if (/^edge_\d+$/.test(id2)) fail(line, `"${id2}" is reserved for connectors. Pick another node id.`);
|
|
493
|
-
const brackets = m.slice(at + 1, at + 4);
|
|
494
|
-
const open = brackets.findIndex((b) => b !== void 0);
|
|
495
|
-
const shape = SHAPES[Math.max(open, 0)];
|
|
496
|
-
const text2 = (open < 0 ? "" : brackets[open].trim()) || id2;
|
|
497
|
-
const known = byId.get(id2);
|
|
498
|
-
if (!known) {
|
|
499
|
-
const created = { id: id2, text: text2, shape, explicit: open >= 0 };
|
|
500
|
-
byId.set(id2, created);
|
|
501
|
-
graph.nodes.push(created);
|
|
502
|
-
} else if (open >= 0) {
|
|
503
|
-
if (known.explicit && (known.text !== text2 || known.shape !== shape)) fail(line, `"${id2}" is defined twice with different text`);
|
|
504
|
-
Object.assign(known, { text: text2, shape, explicit: true });
|
|
505
|
-
}
|
|
506
|
-
return id2;
|
|
507
|
-
};
|
|
508
|
-
source.replace(/^/, "").split(/\r?\n/).forEach((raw, index) => {
|
|
509
|
-
const line = index + 1;
|
|
510
|
-
const text2 = raw.trim();
|
|
511
|
-
if (text2 === "" || text2.startsWith("#") || text2.startsWith("//")) return;
|
|
512
|
-
if (/[\u0000-\u0008\u000a-\u001f\u007f]/.test(text2)) fail(line, "control characters are not allowed");
|
|
513
|
-
const direction = /^direction\s+(\S+)$/.exec(text2);
|
|
514
|
-
if (direction) {
|
|
515
|
-
if (direction[1] !== "TB" && direction[1] !== "LR") fail(line, `direction must be TB or LR, got "${direction[1]}"`);
|
|
516
|
-
graph.direction = direction[1];
|
|
517
|
-
return;
|
|
518
|
-
}
|
|
519
|
-
const edge = EDGE.exec(text2);
|
|
520
|
-
if (edge) {
|
|
521
|
-
const from = node(line, edge, 1);
|
|
522
|
-
const to = node(line, edge, 5);
|
|
523
|
-
graph.edges.push({ from, to, text: (edge[9] ?? "").trim() });
|
|
524
|
-
} else {
|
|
525
|
-
const alone = ALONE.exec(text2);
|
|
526
|
-
if (!alone) fail(line, `cannot read "${text2.slice(0, 40)}". Use "A -> B: label".`);
|
|
527
|
-
else node(line, alone, 1);
|
|
528
|
-
}
|
|
529
|
-
if (graph.nodes.length > MAX_FLOW_NODES) throw new CanvasError("LIMIT", `a flow holds at most ${MAX_FLOW_NODES} nodes`);
|
|
530
|
-
if (graph.edges.length > MAX_FLOW_EDGES) throw new CanvasError("LIMIT", `a flow holds at most ${MAX_FLOW_EDGES} edges`);
|
|
531
|
-
});
|
|
532
|
-
if (graph.nodes.length === 0) throw new CanvasError("INVALID", "the flow has no nodes");
|
|
533
|
-
return { ...graph, nodes: graph.nodes.map(({ id: id2, text: text2, shape }) => ({ id: id2, text: text2, shape })) };
|
|
534
|
-
}
|
|
535
|
-
function sizeOf(n) {
|
|
536
|
-
const w = Math.min(360, Math.max(120, 24 + 8 * n.text.length));
|
|
537
|
-
return n.shape === "diamond" ? { w: Math.min(360, Math.round(w * 1.5)), h: 90 } : { w, h: 60 };
|
|
538
|
-
}
|
|
539
|
-
function layoutFlow(graph) {
|
|
540
|
-
const g = new dagre.graphlib.Graph({ multigraph: true });
|
|
541
|
-
g.setGraph({ rankdir: graph.direction, nodesep: 40, ranksep: 70, marginx: 0, marginy: 0 });
|
|
542
|
-
g.setDefaultEdgeLabel(() => ({}));
|
|
543
|
-
const index = new Map(graph.nodes.map((n, i) => [n.id, `n${i}`]));
|
|
544
|
-
const sizes = graph.nodes.map(sizeOf);
|
|
545
|
-
sizes.forEach(({ w, h }, i) => g.setNode(`n${i}`, { width: w, height: h }));
|
|
546
|
-
graph.edges.forEach((e, i) => g.setEdge(index.get(e.from), index.get(e.to), {}, String(i)));
|
|
547
|
-
dagre.layout(g);
|
|
548
|
-
const boxes = graph.nodes.map((n, i) => {
|
|
549
|
-
const { w, h } = sizes[i];
|
|
550
|
-
const c = g.node(`n${i}`);
|
|
551
|
-
return { id: n.id, x: Math.round(c.x - w / 2), y: Math.round(c.y - h / 2), w, h };
|
|
552
|
-
});
|
|
553
|
-
const minX = Math.min(...boxes.map((b) => b.x));
|
|
554
|
-
const minY = Math.min(...boxes.map((b) => b.y));
|
|
555
|
-
return boxes.map((b) => ({ ...b, x: b.x - minX, y: b.y - minY }));
|
|
556
|
-
}
|
|
557
|
-
function drawFlow(board, source, actor, options = {}) {
|
|
558
|
-
const prefix = options.idPrefix ?? `${randomUUID2().slice(0, 8)}-`;
|
|
559
|
-
if (!/^[A-Za-z0-9_-]{0,16}$/.test(prefix)) throw new CanvasError("INVALID", "idPrefix must be up to 16 of A-Z, a-z, 0-9, _ and -");
|
|
560
|
-
const graph = parseFlow(source);
|
|
561
|
-
const placed = layoutFlow(graph);
|
|
562
|
-
const shapes = board.list().flatMap((e) => e.type === "connector" || e.type === "group" ? [] : [e]);
|
|
563
|
-
const origin = options.origin ?? {
|
|
564
|
-
x: shapes.length ? Math.min(...shapes.map((s) => s.x)) : 0,
|
|
565
|
-
y: shapes.length ? Math.max(...shapes.map((s) => s.y + s.h)) + 80 : 0
|
|
566
|
-
};
|
|
567
|
-
const work = board.clone();
|
|
568
|
-
const text2 = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
569
|
-
for (const p of placed) {
|
|
570
|
-
const n = text2.get(p.id);
|
|
571
|
-
work.addShape({ id: prefix + p.id, type: n.shape, text: n.text, x: p.x + origin.x, y: p.y + origin.y, w: p.w, h: p.h }, actor);
|
|
572
|
-
}
|
|
573
|
-
const edges = graph.edges.map((e, i) => {
|
|
574
|
-
const id2 = `${prefix}edge_${i + 1}`;
|
|
575
|
-
work.addConnector({ id: id2, from: { id: prefix + e.from }, to: { id: prefix + e.to }, text: e.text }, actor);
|
|
576
|
-
return id2;
|
|
577
|
-
});
|
|
578
|
-
Y2.applyUpdate(board.doc, Y2.encodeStateAsUpdate(work.doc, Y2.encodeStateVector(board.doc)));
|
|
579
|
-
return { nodes: placed.map((p) => prefix + p.id), edges };
|
|
580
|
-
}
|
|
581
|
-
|
|
582
478
|
// src/core/read.ts
|
|
583
479
|
function readBoard(board) {
|
|
584
480
|
const data = { title: board.title, nodes: [], edges: [], groups: [], texts: [] };
|
|
@@ -599,182 +495,16 @@ function readBoard(board) {
|
|
|
599
495
|
return data;
|
|
600
496
|
}
|
|
601
497
|
|
|
602
|
-
// src/core/svg.ts
|
|
603
|
-
var LINE = 17;
|
|
604
|
-
var num = (n) => String(Math.round(n * 100) / 100);
|
|
605
|
-
var esc = (value) => (typeof value === "string" ? value : "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]|[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g, "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
606
|
-
var paint = (value, fallback) => typeof value === "string" && COLOR_PATTERN.test(value) ? value : fallback;
|
|
607
|
-
var str = (value) => typeof value === "string" ? value : "";
|
|
608
|
-
var within = (n, low, high) => typeof n === "number" && Number.isFinite(n) && n >= low && n <= high;
|
|
609
|
-
var LIMIT_COORD = 1e7;
|
|
610
|
-
var TYPES = /* @__PURE__ */ new Set([...SHAPE_TYPES, "connector", "group"]);
|
|
611
|
-
var sane = (s) => within(s.x, -LIMIT_COORD, LIMIT_COORD) && within(s.y, -LIMIT_COORD, LIMIT_COORD) && within(s.w, 1e-6, LIMIT_COORD) && within(s.h, 1e-6, LIMIT_COORD);
|
|
612
|
-
var isShape = (v) => "w" in v;
|
|
613
|
-
var center = (s) => ({ x: s.x + s.w / 2, y: s.y + s.h / 2 });
|
|
614
|
-
function edgePoint(s, toward) {
|
|
615
|
-
const c = center(s);
|
|
616
|
-
const dx = toward.x - c.x;
|
|
617
|
-
const dy = toward.y - c.y;
|
|
618
|
-
if (dx === 0 && dy === 0) return c;
|
|
619
|
-
const a = s.w / 2;
|
|
620
|
-
const b = s.h / 2;
|
|
621
|
-
const k = s.type === "ellipse" ? 1 / Math.hypot(dx / a, dy / b) : s.type === "diamond" ? 1 / (Math.abs(dx) / a + Math.abs(dy) / b) : Math.min(a / Math.abs(dx), b / Math.abs(dy));
|
|
622
|
-
return { x: c.x + dx * k, y: c.y + dy * k };
|
|
623
|
-
}
|
|
624
|
-
function wrap(text2, width) {
|
|
625
|
-
const max = Math.max(4, Math.floor((width - 16) / 7.5));
|
|
626
|
-
const out = [];
|
|
627
|
-
for (const paragraph of text2.split(/\r?\n/)) {
|
|
628
|
-
let line = "";
|
|
629
|
-
for (const word of paragraph.split(/\s+/).filter(Boolean)) {
|
|
630
|
-
const chars = [...word];
|
|
631
|
-
while (chars.length > max) {
|
|
632
|
-
if (line) out.push(line);
|
|
633
|
-
line = "";
|
|
634
|
-
out.push(chars.splice(0, max).join(""));
|
|
635
|
-
}
|
|
636
|
-
const rest = chars.join("");
|
|
637
|
-
if (line && [...line].length + 1 + chars.length > max) {
|
|
638
|
-
out.push(line);
|
|
639
|
-
line = rest;
|
|
640
|
-
} else line = line ? `${line} ${rest}` : rest;
|
|
641
|
-
}
|
|
642
|
-
out.push(line);
|
|
643
|
-
}
|
|
644
|
-
return out.slice(0, 40);
|
|
645
|
-
}
|
|
646
|
-
function label(text2, cx, cy, width, extra = "") {
|
|
647
|
-
if (text2 === "") return "";
|
|
648
|
-
const lines = wrap(text2, width);
|
|
649
|
-
const top = cy - (lines.length - 1) * LINE / 2;
|
|
650
|
-
const spans = lines.map((l, i) => `<tspan x="${num(cx)}" y="${num(top + i * LINE)}">${esc(l)}</tspan>`);
|
|
651
|
-
return `<text text-anchor="middle" dominant-baseline="central"${extra}>${spans.join("")}</text>`;
|
|
652
|
-
}
|
|
653
|
-
function shapeBody(s) {
|
|
654
|
-
const { x, y, w, h } = s;
|
|
655
|
-
const stroke = paint(s.stroke, "#1f2937");
|
|
656
|
-
const look = (fill, more = "") => `fill="${paint(s.fill, fill)}" stroke="${stroke}" stroke-width="2"${more}`;
|
|
657
|
-
const text2 = label(str(s.text), x + w / 2, y + h / 2, w);
|
|
658
|
-
switch (s.type) {
|
|
659
|
-
case "ellipse":
|
|
660
|
-
return `<ellipse cx="${num(x + w / 2)}" cy="${num(y + h / 2)}" rx="${num(w / 2)}" ry="${num(h / 2)}" ${look("#ffffff")}/>${text2}`;
|
|
661
|
-
case "diamond": {
|
|
662
|
-
const pts = [[x + w / 2, y], [x + w, y + h / 2], [x + w / 2, y + h], [x, y + h / 2]];
|
|
663
|
-
return `<polygon points="${pts.map(([px, py]) => `${num(px)},${num(py)}`).join(" ")}" ${look("#ffffff")}/>${text2}`;
|
|
664
|
-
}
|
|
665
|
-
case "text":
|
|
666
|
-
return text2;
|
|
667
|
-
case "frame":
|
|
668
|
-
return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" ${look("none", ' stroke-dasharray="8 6"')}/>${label(str(s.text), x + w / 2, y - 12, w)}`;
|
|
669
|
-
case "image":
|
|
670
|
-
if (typeof s.src === "string" && s.src.startsWith("data:") && IMAGE_PATTERN.test(s.src)) {
|
|
671
|
-
return `<image href="${s.src}" x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" preserveAspectRatio="xMidYMid meet"/>`;
|
|
672
|
-
}
|
|
673
|
-
return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" ${look("#f3f4f6", ' stroke-dasharray="4 4"')}/>${label("image", x + w / 2, y + h / 2, w)}`;
|
|
674
|
-
default:
|
|
675
|
-
return `<rect x="${num(x)}" y="${num(y)}" width="${num(w)}" height="${num(h)}" rx="${s.type === "sticky" ? 2 : 6}" ${look(s.type === "sticky" ? "#fde68a" : "#ffffff")}/>${text2}`;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
var endKey = (e) => "id" in e ? e.id : `${e.x},${e.y}`;
|
|
679
|
-
var wellFormed = (e) => typeof e === "object" && e !== null && (typeof e.id === "string" || within(e.x, -LIMIT_COORD, LIMIT_COORD) && within(e.y, -LIMIT_COORD, LIMIT_COORD));
|
|
680
|
-
function connectorBody(c, shapes, lane) {
|
|
681
|
-
const resolve = (e) => "id" in e ? shapes.get(e.id) : e;
|
|
682
|
-
const a = resolve(c.from);
|
|
683
|
-
const b = resolve(c.to);
|
|
684
|
-
if (!a || !b) return "";
|
|
685
|
-
let d;
|
|
686
|
-
let at;
|
|
687
|
-
if (isShape(a) && a === b) {
|
|
688
|
-
const x0 = a.x + a.w * 0.35;
|
|
689
|
-
const x1 = a.x + a.w * 0.65;
|
|
690
|
-
const lift = 45 + 32 * lane.index;
|
|
691
|
-
d = `M${num(x0)} ${num(a.y)}C${num(x0)} ${num(a.y - lift)} ${num(x1)} ${num(a.y - lift)} ${num(x1)} ${num(a.y)}`;
|
|
692
|
-
at = { x: a.x + a.w / 2, y: a.y - lift * 0.75 - 6 };
|
|
693
|
-
} else {
|
|
694
|
-
const ca = isShape(a) ? center(a) : a;
|
|
695
|
-
const cb = isShape(b) ? center(b) : b;
|
|
696
|
-
const flip = endKey(c.from) > endKey(c.to);
|
|
697
|
-
const dx = (flip ? ca.x - cb.x : cb.x - ca.x) || 0;
|
|
698
|
-
const dy = (flip ? ca.y - cb.y : cb.y - ca.y) || 0;
|
|
699
|
-
const len = Math.hypot(dx, dy) || 1;
|
|
700
|
-
const offset = (lane.index - (lane.count - 1) / 2) * 44;
|
|
701
|
-
const mid = { x: (ca.x + cb.x) / 2, y: (ca.y + cb.y) / 2 };
|
|
702
|
-
const ctrl = { x: mid.x - dy / len * 2 * offset, y: mid.y + dx / len * 2 * offset };
|
|
703
|
-
const p = isShape(a) ? edgePoint(a, ctrl) : a;
|
|
704
|
-
const q = isShape(b) ? edgePoint(b, ctrl) : b;
|
|
705
|
-
d = offset === 0 ? `M${num(p.x)} ${num(p.y)}L${num(q.x)} ${num(q.y)}` : `M${num(p.x)} ${num(p.y)}Q${num(ctrl.x)} ${num(ctrl.y)} ${num(q.x)} ${num(q.y)}`;
|
|
706
|
-
at = offset === 0 ? { x: (p.x + q.x) / 2, y: (p.y + q.y) / 2 } : { x: (p.x + q.x) / 4 + ctrl.x / 2, y: (p.y + q.y) / 4 + ctrl.y / 2 };
|
|
707
|
-
}
|
|
708
|
-
const marks = `${c.startArrow ? ' marker-start="url(#arrow)"' : ""}${c.endArrow ? ' marker-end="url(#arrow)"' : ""}`;
|
|
709
|
-
const halo = ' stroke="#ffffff" stroke-width="4" paint-order="stroke"';
|
|
710
|
-
return `<path d="${d}" fill="none" stroke="${paint(c.stroke, "#1f2937")}" stroke-width="2"${marks}/>${label(str(c.text), at.x, at.y, 160, halo)}`;
|
|
711
|
-
}
|
|
712
|
-
var drawable = (el) => TYPES.has(el.type) && (el.type === "connector" ? wellFormed(el.from) && wellFormed(el.to) : el.type === "group" || sane(el));
|
|
713
|
-
function toSvg(board) {
|
|
714
|
-
const listed = board.list();
|
|
715
|
-
if (listed.length > MAX_ELEMENTS) throw new CanvasError("LIMIT", `a board holds at most ${MAX_ELEMENTS} elements`);
|
|
716
|
-
const all = listed.filter((el) => drawable(el));
|
|
717
|
-
const shapes = /* @__PURE__ */ new Map();
|
|
718
|
-
const box = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
|
|
719
|
-
const grow = (x, y) => {
|
|
720
|
-
box.minX = Math.min(box.minX, x);
|
|
721
|
-
box.minY = Math.min(box.minY, y);
|
|
722
|
-
box.maxX = Math.max(box.maxX, x);
|
|
723
|
-
box.maxY = Math.max(box.maxY, y);
|
|
724
|
-
};
|
|
725
|
-
for (const el of all) {
|
|
726
|
-
if (el.type === "connector") {
|
|
727
|
-
for (const e of [el.from, el.to]) if (!("id" in e)) grow(e.x, e.y);
|
|
728
|
-
} else if (el.type !== "group") {
|
|
729
|
-
shapes.set(el.id, el);
|
|
730
|
-
grow(el.x, el.y);
|
|
731
|
-
grow(el.x + el.w, el.y + el.h);
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
const pad = 80;
|
|
735
|
-
const any = box.minX !== Infinity;
|
|
736
|
-
const minX = any ? box.minX - pad : 0;
|
|
737
|
-
const minY = any ? box.minY - pad : 0;
|
|
738
|
-
const width = any ? Math.ceil(box.maxX + pad - minX) : 240;
|
|
739
|
-
const height = any ? Math.ceil(box.maxY + pad - minY) : 120;
|
|
740
|
-
const lanes = /* @__PURE__ */ new Map();
|
|
741
|
-
const groups = /* @__PURE__ */ new Map();
|
|
742
|
-
for (const el of all) {
|
|
743
|
-
if (el.type !== "connector") continue;
|
|
744
|
-
const key = [endKey(el.from), endKey(el.to)].sort().join("|");
|
|
745
|
-
groups.set(key, [...groups.get(key) ?? [], el.id]);
|
|
746
|
-
}
|
|
747
|
-
for (const ids of groups.values()) ids.forEach((id2, index) => lanes.set(id2, { index, count: ids.length }));
|
|
748
|
-
const body = all.map((el) => {
|
|
749
|
-
if (el.type === "group") return "";
|
|
750
|
-
const inner = el.type === "connector" ? connectorBody(el, shapes, lanes.get(el.id)) : shapeBody(el);
|
|
751
|
-
if (inner === "") return "";
|
|
752
|
-
const kind = el.author.kind;
|
|
753
|
-
const mark = `data-id="${esc(el.id)}" data-type="${el.type}" data-author-kind="${kind}" data-machine-made="${kind !== "human"}"`;
|
|
754
|
-
return `<g ${mark}>${inner}</g>`;
|
|
755
|
-
});
|
|
756
|
-
const title = esc(board.title) || "Whiteboard";
|
|
757
|
-
return [
|
|
758
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="${num(minX)} ${num(minY)} ${width} ${height}" font-family="sans-serif" font-size="14" fill="#111827" role="img" aria-label="${title}">`,
|
|
759
|
-
`<title>${title}</title>`,
|
|
760
|
-
'<defs><marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="#1f2937"/></marker></defs>',
|
|
761
|
-
`<rect x="${num(minX)}" y="${num(minY)}" width="${width}" height="${height}" fill="#ffffff"/>`,
|
|
762
|
-
...body.filter(Boolean),
|
|
763
|
-
"</svg>"
|
|
764
|
-
].join("\n");
|
|
765
|
-
}
|
|
766
|
-
|
|
767
498
|
export {
|
|
499
|
+
PRINCIPAL_KINDS,
|
|
500
|
+
isMachineMade,
|
|
501
|
+
principalSchema,
|
|
768
502
|
CanvasError,
|
|
769
503
|
SHAPE_TYPES,
|
|
770
504
|
MAX_ELEMENTS,
|
|
771
505
|
MAX_TEXT,
|
|
506
|
+
COLOR_PATTERN,
|
|
507
|
+
IMAGE_PATTERN,
|
|
772
508
|
Board,
|
|
773
|
-
|
|
774
|
-
MAX_FLOW_EDGES,
|
|
775
|
-
parseFlow,
|
|
776
|
-
layoutFlow,
|
|
777
|
-
drawFlow,
|
|
778
|
-
readBoard,
|
|
779
|
-
toSvg
|
|
509
|
+
readBoard
|
|
780
510
|
};
|
|
@@ -2,15 +2,15 @@ import {
|
|
|
2
2
|
FileStoreError
|
|
3
3
|
} from "./chunk-NWQED42L.js";
|
|
4
4
|
import {
|
|
5
|
-
Board,
|
|
6
|
-
CanvasError,
|
|
7
5
|
drawFlow,
|
|
8
|
-
readBoard,
|
|
9
6
|
toSvg
|
|
10
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-EDQNA2SU.js";
|
|
11
8
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
Board,
|
|
10
|
+
CanvasError,
|
|
11
|
+
principalSchema,
|
|
12
|
+
readBoard
|
|
13
|
+
} from "./chunk-MVWUIBGV.js";
|
|
14
14
|
|
|
15
15
|
// src/service.ts
|
|
16
16
|
import * as Y from "yjs";
|
package/dist/cli.js
CHANGED
|
@@ -5,18 +5,18 @@ import {
|
|
|
5
5
|
operatorActor,
|
|
6
6
|
runMcp,
|
|
7
7
|
svgToPng
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-A4YK2SWM.js";
|
|
9
9
|
import {
|
|
10
10
|
openLocalStore
|
|
11
11
|
} from "./chunk-NRLF6MGR.js";
|
|
12
12
|
import {
|
|
13
13
|
CanvasService
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-Z6KLGAFP.js";
|
|
15
15
|
import "./chunk-NWQED42L.js";
|
|
16
|
+
import "./chunk-EDQNA2SU.js";
|
|
16
17
|
import {
|
|
17
18
|
CanvasError
|
|
18
|
-
} from "./chunk-
|
|
19
|
-
import "./chunk-JOBTIAFJ.js";
|
|
19
|
+
} from "./chunk-MVWUIBGV.js";
|
|
20
20
|
|
|
21
21
|
// src/cli.ts
|
|
22
22
|
import { readFileSync, writeFileSync } from "fs";
|
package/dist/embed.d.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { C as CanvasClient } from './client-Dyd30StF.js';
|
|
3
3
|
import { PrincipalKind } from 'agentic-identity';
|
|
4
|
+
import { SyncClient, Status } from 'agentic-realtime/client';
|
|
5
|
+
import * as Y from 'yjs';
|
|
4
6
|
import './read-UIJlrkwx.js';
|
|
5
|
-
import 'yjs';
|
|
6
7
|
import 'zod';
|
|
7
8
|
|
|
9
|
+
/** The presence of a room: who is here. A `SyncClient` of agentic-realtime has these. */
|
|
10
|
+
type RoomPresence = Pick<SyncClient, "peers" | "onPeers">;
|
|
11
|
+
/**
|
|
12
|
+
* A board that the app keeps itself, in its own Yjs room. The UI reads and writes it, and does not call a client, poll, or
|
|
13
|
+
* open a live room. The app keeps the document and its room.
|
|
14
|
+
*/
|
|
15
|
+
interface HostBoard {
|
|
16
|
+
/** The Yjs document. The UI opens it as a `Board`: its `elements` and `meta` maps, as this package stores them. */
|
|
17
|
+
doc: Y.Doc;
|
|
18
|
+
/** The presence of the app's room. With it, the UI shows who else is here. */
|
|
19
|
+
presence?: RoomPresence;
|
|
20
|
+
/** The connection state now. With neither `status` nor `onStatus`, the UI shows the board as live. With only `onStatus`, it shows not live until the room reports "connected". */
|
|
21
|
+
status?: () => Status;
|
|
22
|
+
/** A way to hear when the connection state changes. It returns a function that stops the listener. The UI reads it once each time it opens `doc`. */
|
|
23
|
+
onStatus?: (listener: (status: Status) => void) => () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
8
26
|
interface RoomOptions {
|
|
9
27
|
/** The API root, for example `https://canvas.example.com/api/v1/canvas`. */
|
|
10
28
|
baseUrl: string;
|
|
@@ -16,14 +34,21 @@ interface RoomOptions {
|
|
|
16
34
|
interface BoardPrincipal {
|
|
17
35
|
kind: PrincipalKind;
|
|
18
36
|
name: string;
|
|
37
|
+
/** Stamped on each element the principal writes in host mode. The default is `name`, which must then be a valid id. A client mode board takes the actor from its token. */
|
|
38
|
+
id?: string;
|
|
19
39
|
/** Only `editor` can change the board. Any other value, or no value, opens it read only. */
|
|
20
40
|
role?: "editor" | "viewer";
|
|
21
41
|
}
|
|
22
42
|
interface BoardEditorOptions {
|
|
23
43
|
/** The board id. */
|
|
24
44
|
boardId: string;
|
|
25
|
-
/** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. */
|
|
26
|
-
client
|
|
45
|
+
/** Loads and changes the board. A client from `createClient` in `agentic-canvas/client` works. Needed unless `host` is given. */
|
|
46
|
+
client?: CanvasClient;
|
|
47
|
+
/**
|
|
48
|
+
* The board in your own Yjs document. The UI then reads and writes that document, and calls no client, does not poll, and
|
|
49
|
+
* opens no live room. `client` or `room` together with `host` throws a `TypeError`.
|
|
50
|
+
*/
|
|
51
|
+
host?: HostBoard;
|
|
27
52
|
principal: BoardPrincipal;
|
|
28
53
|
/** Open the board read only, even for an editor. */
|
|
29
54
|
readOnly?: boolean;
|
|
@@ -43,4 +68,4 @@ interface MountedBoard {
|
|
|
43
68
|
/** Render the board into an element, for an app that does not use React itself. */
|
|
44
69
|
declare function mount(element: HTMLElement, options: BoardEditorOptions): MountedBoard;
|
|
45
70
|
|
|
46
|
-
export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type MountedBoard, type RoomOptions, mount };
|
|
71
|
+
export { BoardEditor, type BoardEditorOptions, type BoardPrincipal, CanvasClient, type HostBoard, type MountedBoard, type RoomOptions, mount };
|