@braincrew-lab/langchain-canvas 0.1.2 → 0.1.4
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/dist/index.js +30 -64
- package/dist/styles.css +6 -3
- package/package.json +22 -16
package/dist/index.js
CHANGED
|
@@ -392,6 +392,14 @@ var INSPECTOR_SCRIPT = `
|
|
|
392
392
|
}, "*");
|
|
393
393
|
}, true);
|
|
394
394
|
|
|
395
|
+
// Block the browser's native drag (images and links start an HTML5 drag on
|
|
396
|
+
// mousemove by default, which hijacks our free-drag \u2014 the image "ghost" gets
|
|
397
|
+
// dragged instead of the element moving). Suppress it for any editable node.
|
|
398
|
+
document.addEventListener("dragstart", function (e) {
|
|
399
|
+
var t = e.target;
|
|
400
|
+
if (t instanceof Element && t.closest("[data-cid]") && !t.hasAttribute("data-lcx")) e.preventDefault();
|
|
401
|
+
}, true);
|
|
402
|
+
|
|
395
403
|
// --- drag: freely move an element anywhere, or marquee-select empty space ----
|
|
396
404
|
document.addEventListener("mousedown", function (e) {
|
|
397
405
|
suppressClick = false; // a fresh press: never carry a stale suppress
|
|
@@ -1545,10 +1553,11 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1545
1553
|
const lastSelfHtml = useRef(null);
|
|
1546
1554
|
const srcDocRef = useRef("");
|
|
1547
1555
|
const srcDoc = useMemo(() => {
|
|
1548
|
-
if (artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
|
|
1556
|
+
if (mode === "design" && artifact2.data.html === lastSelfHtml.current) return srcDocRef.current;
|
|
1549
1557
|
srcDocRef.current = withInspector(artifact2.data.html);
|
|
1558
|
+
lastSelfHtml.current = null;
|
|
1550
1559
|
return srcDocRef.current;
|
|
1551
|
-
}, [artifact2.data.html]);
|
|
1560
|
+
}, [artifact2.data.html, mode]);
|
|
1552
1561
|
const selected = selections.filter((s) => s.artifactId === artifact2.id);
|
|
1553
1562
|
const single = selected.length === 1 ? selected[0] : null;
|
|
1554
1563
|
useEffect(() => {
|
|
@@ -1768,8 +1777,7 @@ var dataExporters = {
|
|
|
1768
1777
|
{ label: "JSON", extension: "json", mime: MIME.json, build: (a) => JSON.stringify(a.data, null, 2) }
|
|
1769
1778
|
],
|
|
1770
1779
|
slides: [
|
|
1771
|
-
{ label: "PowerPoint", extension: "pptx", mime: MIME.pptx, build: (a) => slidesToPptx(a.data, a.title) }
|
|
1772
|
-
{ label: "Figma (JSON)", extension: "json", mime: MIME.json, build: (a) => slidesToFigmaJson(a.data) }
|
|
1780
|
+
{ label: "PowerPoint", extension: "pptx", mime: MIME.pptx, build: (a) => slidesToPptx(a.data, a.title) }
|
|
1773
1781
|
]
|
|
1774
1782
|
};
|
|
1775
1783
|
function toStandaloneHtml(title, renderedHtml) {
|
|
@@ -1876,52 +1884,6 @@ async function slidesToPptx(data, _title) {
|
|
|
1876
1884
|
}
|
|
1877
1885
|
return await pptx.write({ outputType: "blob" });
|
|
1878
1886
|
}
|
|
1879
|
-
function slidesToFigmaJson(data) {
|
|
1880
|
-
const W = 1280;
|
|
1881
|
-
const H = 720;
|
|
1882
|
-
const GAP = 64;
|
|
1883
|
-
const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
|
|
1884
|
-
const textNode = (x, y, width, characters, fontSize, fontWeight, align, color = "#1F2328") => ({ type: "text", x, y, width, characters, fontSize, fontWeight, align, color });
|
|
1885
|
-
const frames = slides2.map((slide, i) => {
|
|
1886
|
-
const nodes = [];
|
|
1887
|
-
for (const el of resolveElements(slide)) {
|
|
1888
|
-
if (el.type !== "text") continue;
|
|
1889
|
-
nodes.push(
|
|
1890
|
-
textNode(el.x / 100 * W, el.y / 100 * H, el.w / 100 * W, el.text ?? "", el.fontSize ?? 24, el.bold ? 700 : 400, el.align === "center" ? "CENTER" : "LEFT", el.color ?? slide.textColor ?? "#1F2328")
|
|
1891
|
-
);
|
|
1892
|
-
}
|
|
1893
|
-
return { name: `Slide ${i + 1}`, x: 0, y: i * (H + GAP), width: W, height: H, fill: slide.background ?? "#FFFFFF", nodes };
|
|
1894
|
-
});
|
|
1895
|
-
return JSON.stringify({ type: "langchain-canvas/figma-deck", version: 1, frames }, null, 2);
|
|
1896
|
-
}
|
|
1897
|
-
function slidesToSvg(data) {
|
|
1898
|
-
const W = 1280;
|
|
1899
|
-
const H = 720;
|
|
1900
|
-
const GAP = 64;
|
|
1901
|
-
const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
|
|
1902
|
-
const totalH = slides2.length * H + (slides2.length - 1) * GAP;
|
|
1903
|
-
const text = (x, y, value, size, anchor, weight, fill = "#1f2328") => `<text x="${x}" y="${y}" font-family="Inter, Arial, sans-serif" font-size="${size}" font-weight="${weight}" text-anchor="${anchor}" fill="${fill}">${escapeXml(value)}</text>`;
|
|
1904
|
-
const frames = slides2.map((slide, i) => {
|
|
1905
|
-
const y = i * (H + GAP);
|
|
1906
|
-
const bg = slide.background ?? "#ffffff";
|
|
1907
|
-
let body = `<rect width="${W}" height="${H}" rx="8" fill="${bg}" stroke="#e5e7eb"/>`;
|
|
1908
|
-
for (const el of resolveElements(slide)) {
|
|
1909
|
-
const ex = el.x / 100 * W;
|
|
1910
|
-
const ey = el.y / 100 * H;
|
|
1911
|
-
const ew = el.w / 100 * W;
|
|
1912
|
-
if (el.type === "text") {
|
|
1913
|
-
const anchor = el.align === "center" ? "middle" : el.align === "right" ? "end" : "start";
|
|
1914
|
-
const ax = el.align === "center" ? ex + ew / 2 : el.align === "right" ? ex + ew : ex;
|
|
1915
|
-
const fs = el.fontSize ?? 24;
|
|
1916
|
-
body += text(ax, ey + fs, el.text ?? "", fs, anchor, el.bold ? 700 : 400, el.color ?? slide.textColor ?? "#1f2328");
|
|
1917
|
-
} else if (el.src) {
|
|
1918
|
-
body += `<image href="${el.src}" x="${ex}" y="${ey}" width="${ew}" height="${el.h / 100 * H}" preserveAspectRatio="xMidYMid meet"/>`;
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
return `<g transform="translate(0 ${y})">${body}</g>`;
|
|
1922
|
-
}).join("");
|
|
1923
|
-
return `<svg xmlns="http://www.w3.org/2000/svg" width="${W}" height="${totalH}" viewBox="0 0 ${W} ${totalH}">${frames}</svg>`;
|
|
1924
|
-
}
|
|
1925
1887
|
function slidesToPrintHtml(data, title) {
|
|
1926
1888
|
const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
|
|
1927
1889
|
const pages = slides2.map((slide) => {
|
|
@@ -2035,14 +1997,6 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2035
1997
|
}
|
|
2036
1998
|
setOpen(false);
|
|
2037
1999
|
};
|
|
2038
|
-
const copyToFigma = async () => {
|
|
2039
|
-
const svg = slidesToSvg(artifact2.data);
|
|
2040
|
-
try {
|
|
2041
|
-
await navigator.clipboard.writeText(svg);
|
|
2042
|
-
} catch {
|
|
2043
|
-
}
|
|
2044
|
-
setOpen(false);
|
|
2045
|
-
};
|
|
2046
2000
|
return /* @__PURE__ */ jsxs("div", { className: "cv-export", children: [
|
|
2047
2001
|
/* @__PURE__ */ jsx(
|
|
2048
2002
|
"button",
|
|
@@ -2065,10 +2019,6 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2065
2019
|
"PDF ",
|
|
2066
2020
|
/* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: ".pdf" })
|
|
2067
2021
|
] }),
|
|
2068
|
-
artifact2.type === "slides" && /* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: copyToFigma, children: [
|
|
2069
|
-
"Copy to Figma ",
|
|
2070
|
-
/* @__PURE__ */ jsx("span", { className: "cv-export__ext", children: "paste \u2318V" })
|
|
2071
|
-
] }),
|
|
2072
2022
|
dataOptions.map((option) => /* @__PURE__ */ jsxs("button", { role: "menuitem", onClick: () => exportData(option), children: [
|
|
2073
2023
|
option.label,
|
|
2074
2024
|
" ",
|
|
@@ -2579,18 +2529,34 @@ function EmptyState({ onOpenFiles }) {
|
|
|
2579
2529
|
] })
|
|
2580
2530
|
] });
|
|
2581
2531
|
}
|
|
2582
|
-
var
|
|
2532
|
+
var TYPE_META = {
|
|
2583
2533
|
html: { icon: "\u{1F310}", label: "Web page" },
|
|
2584
2534
|
document: { icon: "\u{1F4C4}", label: "Word document" },
|
|
2585
2535
|
chart: { icon: "\u{1F4CA}", label: "Chart" },
|
|
2586
2536
|
table: { icon: "\u{1F522}", label: "Excel sheet" },
|
|
2587
2537
|
slides: { icon: "\u{1F4FD}\uFE0F", label: "PowerPoint deck" }
|
|
2588
2538
|
};
|
|
2539
|
+
var KIND_META = {
|
|
2540
|
+
web: TYPE_META.html,
|
|
2541
|
+
html: TYPE_META.html,
|
|
2542
|
+
document: TYPE_META.document,
|
|
2543
|
+
doc: TYPE_META.document,
|
|
2544
|
+
chart: TYPE_META.chart,
|
|
2545
|
+
table: TYPE_META.table,
|
|
2546
|
+
sheet: TYPE_META.table,
|
|
2547
|
+
slide: TYPE_META.slides,
|
|
2548
|
+
slides: TYPE_META.slides
|
|
2549
|
+
};
|
|
2550
|
+
function resolveCardMeta(artifact2) {
|
|
2551
|
+
const kind = typeof artifact2.meta?.kind === "string" ? artifact2.meta.kind : void 0;
|
|
2552
|
+
const byKind = kind ? KIND_META[kind] : void 0;
|
|
2553
|
+
return byKind ?? TYPE_META[artifact2.type] ?? { icon: "\u{1F4CE}", label: artifact2.type };
|
|
2554
|
+
}
|
|
2589
2555
|
function ArtifactCard({ artifactId }) {
|
|
2590
2556
|
const artifact2 = useCanvasStore((s) => s.canvas.artifacts[artifactId]);
|
|
2591
2557
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
2592
2558
|
if (!artifact2) return null;
|
|
2593
|
-
const meta =
|
|
2559
|
+
const meta = resolveCardMeta(artifact2);
|
|
2594
2560
|
return /* @__PURE__ */ jsxs("button", { className: "cv-card", onClick: () => setActive(artifact2.id), title: "Open on the canvas", children: [
|
|
2595
2561
|
/* @__PURE__ */ jsx("span", { className: "cv-card__icon", children: meta.icon }),
|
|
2596
2562
|
/* @__PURE__ */ jsxs("span", { className: "cv-card__meta", children: [
|
package/dist/styles.css
CHANGED
|
@@ -208,7 +208,7 @@
|
|
|
208
208
|
|
|
209
209
|
/* --- body --------------------------------------------------------------------- */
|
|
210
210
|
|
|
211
|
-
.cv-body { flex: 1; overflow: auto; padding: 24px; }
|
|
211
|
+
.cv-body { flex: 1; min-height: 0; overflow: auto; padding: 24px; }
|
|
212
212
|
.cv-body--flush { padding: 0; overflow: hidden; }
|
|
213
213
|
.cv-fallback { color: var(--cv-muted); font-size: 14px; }
|
|
214
214
|
|
|
@@ -750,7 +750,7 @@
|
|
|
750
750
|
|
|
751
751
|
/* --- html renderer (base substrate) ------------------------------------------- */
|
|
752
752
|
|
|
753
|
-
.cv-html-wrap { display: flex; flex-direction: column; height: 100%; gap: 10px; }
|
|
753
|
+
.cv-html-wrap { display: flex; flex-direction: column; height: 100%; min-height: 0; gap: 10px; }
|
|
754
754
|
.cv-html-bar {
|
|
755
755
|
display: flex; align-items: center; flex-wrap: wrap; gap: 5px;
|
|
756
756
|
padding: 7px 9px; border: 1px solid var(--cv-border); border-radius: 9px; background: var(--cv-surface);
|
|
@@ -807,7 +807,10 @@
|
|
|
807
807
|
.cv-html {
|
|
808
808
|
width: 100%;
|
|
809
809
|
height: 100%;
|
|
810
|
-
|
|
810
|
+
/* Fill the (now height-bounded) stage exactly so the sandboxed page scrolls
|
|
811
|
+
inside the iframe. A forced min-height would push the iframe taller than the
|
|
812
|
+
stage and defeat the stage's overflow, so the panel couldn't scroll at all. */
|
|
813
|
+
min-height: 0;
|
|
811
814
|
border: 0;
|
|
812
815
|
border-radius: 10px;
|
|
813
816
|
background: #fff;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braincrew-lab/langchain-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,17 +18,29 @@
|
|
|
18
18
|
"LICENSE"
|
|
19
19
|
],
|
|
20
20
|
"comment": "Dev resolves to source (no build needed; Next transpiles via transpilePackages). publishConfig swaps to dist for npm.",
|
|
21
|
-
"main": "./
|
|
22
|
-
"types": "./
|
|
21
|
+
"main": "./src/index.ts",
|
|
22
|
+
"types": "./src/index.ts",
|
|
23
23
|
"exports": {
|
|
24
|
-
".":
|
|
25
|
-
|
|
26
|
-
"import": "./dist/index.js"
|
|
27
|
-
},
|
|
28
|
-
"./styles.css": "./dist/styles.css"
|
|
24
|
+
".": "./src/index.ts",
|
|
25
|
+
"./styles.css": "./src/styles/canvas.css"
|
|
29
26
|
},
|
|
30
27
|
"publishConfig": {
|
|
31
|
-
"access": "public"
|
|
28
|
+
"access": "public",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"import": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./styles.css": "./dist/styles.css"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run"
|
|
32
44
|
},
|
|
33
45
|
"peerDependencies": {
|
|
34
46
|
"react": "^18 || ^19",
|
|
@@ -53,11 +65,5 @@
|
|
|
53
65
|
"tsup": "^8.2.4",
|
|
54
66
|
"typescript": "^5.5.4",
|
|
55
67
|
"vitest": "^2"
|
|
56
|
-
},
|
|
57
|
-
"scripts": {
|
|
58
|
-
"build": "tsup",
|
|
59
|
-
"dev": "tsup --watch",
|
|
60
|
-
"typecheck": "tsc --noEmit",
|
|
61
|
-
"test": "vitest run"
|
|
62
68
|
}
|
|
63
|
-
}
|
|
69
|
+
}
|