From 2b64692196137ae62c0c942eaa71cecf5322680e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 1 Apr 2026 10:43:24 -0700 Subject: [PATCH 1/3] ci: use internal registry (bypass Cloudflare upload limit) Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 4ca1a86..23c58ac 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -8,8 +8,8 @@ on: branches: [main] env: - REGISTRY: gitea.jeffemmett.com - IMAGE: gitea.jeffemmett.com/jeffemmett/canvas-website + REGISTRY: localhost:3000 + IMAGE: localhost:3000/jeffemmett/canvas-website jobs: test: From 4d42db4bb4ccc9eac69dcc4e5fb3372f06e8134e Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 1 Apr 2026 12:21:56 -0700 Subject: [PATCH 2/3] feat: add Mermaid diagram generator tool to canvas AI-powered diagram generation via Ollama (llama3.1:8b), client-side SVG preview using mermaid.js, and animated GIF export via mermaid-animator API. Supports progressive/template/sequence animation modes, theme selection, adjustable timing, code editor toggle, and diagram history. Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 508 +++++++++++++++-- package.json | 1 + src/routes/Board.tsx | 4 + src/shapes/MermaidGenShapeUtil.tsx | 860 +++++++++++++++++++++++++++++ src/tools/MermaidGenTool.ts | 11 + src/ui/CustomToolbar.tsx | 9 + src/ui/overrides.tsx | 9 + 7 files changed, 1341 insertions(+), 61 deletions(-) create mode 100644 src/shapes/MermaidGenShapeUtil.tsx create mode 100644 src/tools/MermaidGenTool.ts diff --git a/package-lock.json b/package-lock.json index 20e9789..cb9c2ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,7 @@ "lodash.throttle": "^4.1.1", "maplibre-gl": "^5.14.0", "marked": "^15.0.4", + "mermaid": "^11.14.0", "one-webcrypto": "^1.0.3", "openai": "^4.79.3", "qrcode.react": "^4.2.0", @@ -261,6 +262,19 @@ "zod": "^3.23.8" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@anthropic-ai/sdk": { "version": "0.33.1", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.33.1.tgz", @@ -1962,11 +1976,10 @@ } }, "node_modules/@braintree/sanitize-url": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", - "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", - "license": "MIT", - "optional": true + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" }, "node_modules/@cbor-extract/cbor-extract-darwin-arm64": { "version": "2.2.0", @@ -2072,6 +2085,45 @@ "react": "*" } }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==", + "license": "Apache-2.0" + }, "node_modules/@cloudflare/intl-types": { "version": "1.5.7", "resolved": "https://registry.npmjs.org/@cloudflare/intl-types/-/intl-types-1.5.7.tgz", @@ -4046,6 +4098,23 @@ "node": ">=18" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, "node_modules/@img/colour": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", @@ -5473,6 +5542,15 @@ "integrity": "sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==", "license": "Apache-2.0" }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz", + "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", + "license": "MIT", + "dependencies": { + "langium": "^4.0.0" + } + }, "node_modules/@monogrid/gainmap-js": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.4.0.tgz", @@ -10197,6 +10275,16 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@use-gesture/core": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", @@ -11309,9 +11397,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -12313,6 +12401,13 @@ "mermaid": "9.4.3" } }, + "node_modules/cherry-markdown/node_modules/@braintree/sanitize-url": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-6.0.4.tgz", + "integrity": "sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==", + "license": "MIT", + "optional": true + }, "node_modules/cherry-markdown/node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -12343,6 +12438,17 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", "license": "MIT" }, + "node_modules/cherry-markdown/node_modules/dagre-d3-es": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", + "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "license": "MIT", + "optional": true, + "dependencies": { + "d3": "^7.8.2", + "lodash-es": "^4.17.21" + } + }, "node_modules/cherry-markdown/node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", @@ -12370,6 +12476,13 @@ "node": ">=12" } }, + "node_modules/cherry-markdown/node_modules/dompurify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", + "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true + }, "node_modules/cherry-markdown/node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -12467,6 +12580,31 @@ } } }, + "node_modules/cherry-markdown/node_modules/mermaid": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", + "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.9", + "dayjs": "^1.11.7", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", + "khroma": "^2.0.0", + "lodash-es": "^4.17.21", + "non-layered-tidy-tree-layout": "^2.0.2", + "stylis": "^4.1.2", + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" + } + }, "node_modules/cherry-markdown/node_modules/parse5": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", @@ -12576,6 +12714,32 @@ "node": ">=12" } }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -12832,6 +12996,12 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -12924,7 +13094,6 @@ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", "license": "MIT", - "optional": true, "dependencies": { "layout-base": "^1.0.0" } @@ -13107,7 +13276,6 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=0.10" } @@ -13117,7 +13285,6 @@ "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", "license": "MIT", - "optional": true, "dependencies": { "cose-base": "^1.0.0" }, @@ -13130,7 +13297,6 @@ "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", "license": "MIT", - "optional": true, "dependencies": { "cose-base": "^2.2.0" }, @@ -13143,7 +13309,6 @@ "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", "license": "MIT", - "optional": true, "dependencies": { "layout-base": "^2.0.0" } @@ -13152,8 +13317,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/d": { "version": "1.0.2", @@ -13463,6 +13627,46 @@ "node": ">=12" } }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -13582,13 +13786,12 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.9.tgz", - "integrity": "sha512-rYR4QfVmy+sR44IBDvVtcAmOReGBvRCWDpO2QjYwqgh9yijw6eSHBqaPG/LIOEy7aBsniLvtMW6pg19qJhq60w==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", - "optional": true, "dependencies": { - "d3": "^7.8.2", + "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, @@ -13664,8 +13867,7 @@ "version": "1.11.19", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/debounce": { "version": "1.2.1", @@ -15422,6 +15624,12 @@ "yarn": ">=1.3.0" } }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/hamt_plus": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/hamt_plus/-/hamt_plus-1.0.2.tgz", @@ -17117,6 +17325,31 @@ "html2canvas": "^1.0.0-rc.5" } }, + "node_modules/katex": { + "version": "0.16.44", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.44.tgz", + "integrity": "sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", @@ -17132,8 +17365,7 @@ "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==", - "optional": true + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" }, "node_modules/kind-of": { "version": "6.0.3", @@ -17161,12 +17393,28 @@ "use-strict": "1.0.1" } }, + "node_modules/langium": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/leven": { "version": "3.1.0", @@ -17258,11 +17506,10 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT", - "optional": true + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -17907,36 +18154,67 @@ } }, "node_modules/mermaid": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.4.3.tgz", - "integrity": "sha512-TLkQEtqhRSuEHSE34lh5bCa94KATCyluAXmFnNI2PRZwOpXFeqiJWwZl+d2CcemE1RS6QbbueSSq9QIg8Uxcyw==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz", + "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", "license": "MIT", - "optional": true, "dependencies": { - "@braintree/sanitize-url": "^6.0.0", - "cytoscape": "^3.23.0", + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.0", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", - "d3": "^7.4.0", - "dagre-d3-es": "7.0.9", - "dayjs": "^1.11.7", - "dompurify": "2.4.3", - "elkjs": "^0.8.2", - "khroma": "^2.0.0", - "lodash-es": "^4.17.21", - "non-layered-tidy-tree-layout": "^2.0.2", - "stylis": "^4.1.2", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", "ts-dedent": "^2.2.0", - "uuid": "^9.0.0", - "web-worker": "^1.2.0" + "uuid": "^11.1.0" } }, "node_modules/mermaid/node_modules/dompurify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz", - "integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } }, "node_modules/meshline": { "version": "3.3.1", @@ -18939,6 +19217,18 @@ } } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/motion": { "version": "10.16.2", "resolved": "https://registry.npmjs.org/motion/-/motion-10.16.2.tgz", @@ -19613,6 +19903,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -19699,6 +19995,12 @@ "tslib": "^2.0.3" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19761,7 +20063,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pbf": { @@ -19841,6 +20142,17 @@ "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", "license": "MIT" }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/platform": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", @@ -19903,6 +20215,22 @@ "node": ">=10.13.0" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -21600,6 +21928,18 @@ "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", "license": "MIT" }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/rrweb-cssom": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", @@ -22555,8 +22895,7 @@ "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/supercluster": { "version": "8.0.1", @@ -22848,7 +23187,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -23052,7 +23390,6 @@ "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", "license": "MIT", - "optional": true, "engines": { "node": ">=6.10" } @@ -23254,9 +23591,9 @@ "license": "MIT" }, "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", "license": "MIT" }, "node_modules/uint8arrays": { @@ -24181,6 +24518,55 @@ } } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "license": "MIT" + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", diff --git a/package.json b/package.json index 9ae1e4e..2623189 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "lodash.throttle": "^4.1.1", "maplibre-gl": "^5.14.0", "marked": "^15.0.4", + "mermaid": "^11.14.0", "one-webcrypto": "^1.0.3", "openai": "^4.79.3", "qrcode.react": "^4.2.0", diff --git a/src/routes/Board.tsx b/src/routes/Board.tsx index d6fac09..591b9c5 100644 --- a/src/routes/Board.tsx +++ b/src/routes/Board.tsx @@ -47,6 +47,8 @@ import { MeetingIntelligenceBrowserShape } from "@/shapes/MeetingIntelligenceBro import { MeetingIntelligenceTool } from "@/tools/MeetingIntelligenceTool" import { ImageGenShape } from "@/shapes/ImageGenShapeUtil" import { ImageGenTool } from "@/tools/ImageGenTool" +import { MermaidGenShape } from "@/shapes/MermaidGenShapeUtil" +import { MermaidGenTool } from "@/tools/MermaidGenTool" import { VideoGenShape } from "@/shapes/VideoGenShapeUtil" import { VideoGenTool } from "@/tools/VideoGenTool" // Blender 3D generation @@ -166,6 +168,7 @@ const customShapeUtils = [ FathomNoteShape, // Individual Fathom meeting notes created from FathomMeetingsBrowser MeetingIntelligenceBrowserShape, // Self-hosted meeting intelligence browser ImageGenShape, + MermaidGenShape, VideoGenShape, BlenderGenShape, // Blender 3D procedural generation ...(ENABLE_DRAWFAST ? [DrawfastShape] : []), // Drawfast - dev only @@ -195,6 +198,7 @@ const customTools = [ FathomMeetingsTool, MeetingIntelligenceTool, // Self-hosted meeting intelligence tool ImageGenTool, + MermaidGenTool, VideoGenTool, BlenderGenTool, // Blender 3D procedural generation ...(ENABLE_DRAWFAST ? [DrawfastTool] : []), // Drawfast - dev only diff --git a/src/shapes/MermaidGenShapeUtil.tsx b/src/shapes/MermaidGenShapeUtil.tsx new file mode 100644 index 0000000..1176035 --- /dev/null +++ b/src/shapes/MermaidGenShapeUtil.tsx @@ -0,0 +1,860 @@ +import { + BaseBoxShapeUtil, + Geometry2d, + HTMLContainer, + Rectangle2d, + TLBaseShape, +} from "tldraw" +import React, { useState, useEffect, useRef, useCallback } from "react" +import { getOllamaConfig } from "@/lib/clientConfig" +import { StandardizedToolWrapper } from "@/components/StandardizedToolWrapper" +import { usePinnedToView } from "@/hooks/usePinnedToView" +import { useMaximize } from "@/hooks/useMaximize" + +const MERMAID_ANIMATOR_URL = "https://mermaid.jeffemmett.com" + +interface MermaidDiagram { + id: string + prompt: string + source: string + svgHtml: string | null + gifBase64: string | null + timestamp: number +} + +type IMermaidGen = TLBaseShape< + "MermaidGen", + { + w: number + h: number + prompt: string + diagramSource: string + diagramHistory: MermaidDiagram[] + isLoading: boolean + loadingPrompt: string | null + error: string | null + animationMode: "progressive" | "template" | "sequence" + animationDelay: number + theme: "default" | "dark" | "forest" | "neutral" + showCode: boolean + tags: string[] + pinnedToView: boolean + } +> + +/** + * Render mermaid code to SVG using the mermaid library (client-side). + * Dynamically imports mermaid to avoid SSR issues. + */ +async function renderMermaidSvg(code: string, theme: string = "default"): Promise { + const mermaid = (await import("mermaid")).default + mermaid.initialize({ startOnLoad: false, theme: theme as any, securityLevel: "loose" }) + const id = `mermaid-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + const { svg } = await mermaid.render(id, code) + return svg +} + +export class MermaidGenShape extends BaseBoxShapeUtil { + static override type = "MermaidGen" as const + + static readonly PRIMARY_COLOR = "#7C3AED" + + MIN_WIDTH = 400 as const + MIN_HEIGHT = 350 as const + DEFAULT_WIDTH = 600 as const + DEFAULT_HEIGHT = 500 as const + + getDefaultProps(): IMermaidGen["props"] { + return { + w: this.DEFAULT_WIDTH, + h: this.DEFAULT_HEIGHT, + prompt: "", + diagramSource: "", + diagramHistory: [], + isLoading: false, + loadingPrompt: null, + error: null, + animationMode: "progressive", + animationDelay: 800, + theme: "default", + showCode: false, + tags: ["diagram", "mermaid", "ai-generated"], + pinnedToView: false, + } + } + + getGeometry(shape: IMermaidGen): Geometry2d { + return new Rectangle2d({ + width: Math.max(shape.props.w, 1), + height: Math.max(shape.props.h, 1), + isFilled: true, + }) + } + + component(shape: IMermaidGen) { + const editor = this.editor + const isSelected = editor.getSelectedShapeIds().includes(shape.id) + + usePinnedToView(editor, shape.id, shape.props.pinnedToView) + + const { isMaximized, toggleMaximize } = useMaximize({ + editor, + shapeId: shape.id, + currentW: shape.props.w, + currentH: shape.props.h, + shapeType: "MermaidGen", + }) + + const handlePinToggle = () => { + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { pinnedToView: !shape.props.pinnedToView }, + }) + } + + const [isMinimized, setIsMinimized] = useState(false) + const [svgPreview, setSvgPreview] = useState(null) + const [isAnimating, setIsAnimating] = useState(false) + const svgContainerRef = useRef(null) + + const handleClose = () => editor.deleteShape(shape.id) + const handleMinimize = () => setIsMinimized(!isMinimized) + + const handleTagsChange = (newTags: string[]) => { + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { tags: newTags }, + }) + } + + // Re-render SVG when diagramSource or theme changes + useEffect(() => { + if (!shape.props.diagramSource) { + setSvgPreview(null) + return + } + let cancelled = false + renderMermaidSvg(shape.props.diagramSource, shape.props.theme) + .then((svg) => { + if (!cancelled) setSvgPreview(svg) + }) + .catch((err) => { + if (!cancelled) setSvgPreview(null) + console.error("Mermaid render error:", err) + }) + return () => { cancelled = true } + }, [shape.props.diagramSource, shape.props.theme]) + + // Generate diagram from prompt via Ollama + const generateDiagram = useCallback(async (prompt: string) => { + const ollamaConfig = getOllamaConfig() + if (!ollamaConfig) { + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { error: "Ollama not configured" }, + }) + return + } + + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { isLoading: true, loadingPrompt: prompt, error: null }, + }) + + try { + const response = await fetch(`${ollamaConfig.url}/api/generate`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "llama3.1:8b", + prompt: `Generate a Mermaid diagram for: ${prompt}\n\nRules:\n- Output ONLY valid mermaid diagram code\n- No explanation, no markdown fences, no comments\n- Use graph TD, sequenceDiagram, classDiagram, flowchart, etc. as appropriate\n- Keep it clear and well-structured`, + system: "You are a Mermaid diagram generator. Output ONLY valid mermaid diagram code, no explanation, no markdown code fences.", + stream: false, + }), + }) + + if (!response.ok) { + throw new Error(`Ollama error: ${response.status}`) + } + + const data = (await response.json()) as { response?: string } + let mermaidCode = (data.response || "").trim() + + // Strip markdown fences if the model included them + mermaidCode = mermaidCode + .replace(/^```mermaid\s*/i, "") + .replace(/^```\s*/m, "") + .replace(/\s*```$/m, "") + .trim() + + if (!mermaidCode) { + throw new Error("No diagram code returned from LLM") + } + + // Render SVG to validate + const svg = await renderMermaidSvg(mermaidCode, shape.props.theme) + + const currentShape = editor.getShape(shape.id) + const currentHistory = currentShape?.props.diagramHistory || [] + + const newDiagram: MermaidDiagram = { + id: `dia-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + prompt, + source: mermaidCode, + svgHtml: svg, + gifBase64: null, + timestamp: Date.now(), + } + + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { + diagramSource: mermaidCode, + diagramHistory: [newDiagram, ...currentHistory], + isLoading: false, + loadingPrompt: null, + error: null, + }, + }) + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { isLoading: false, loadingPrompt: null, error: `Error: ${msg}` }, + }) + } + }, [editor, shape.id, shape.props.theme]) + + // Animate GIF via mermaid-animator API + const animateGif = useCallback(async () => { + if (!shape.props.diagramSource) return + + setIsAnimating(true) + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { error: null }, + }) + + try { + const response = await fetch(`${MERMAID_ANIMATOR_URL}/api/render`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + code: shape.props.diagramSource, + mode: shape.props.animationMode, + delay: shape.props.animationDelay, + theme: shape.props.theme, + }), + }) + + if (!response.ok) { + const errText = await response.text() + throw new Error(`Animation API error: ${response.status} - ${errText}`) + } + + const data = (await response.json()) as { gif: string; frames: number; width: number; height: number } + + // Update the latest history entry with the GIF + const currentShape = editor.getShape(shape.id) + const history = [...(currentShape?.props.diagramHistory || [])] + if (history.length > 0) { + history[0] = { ...history[0], gifBase64: data.gif } + } + + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { diagramHistory: history }, + }) + } catch (error) { + const msg = error instanceof Error ? error.message : String(error) + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { error: `Animation error: ${msg}` }, + }) + } finally { + setIsAnimating(false) + } + }, [editor, shape.id, shape.props.diagramSource, shape.props.animationMode, shape.props.animationDelay, shape.props.theme]) + + const handleGenerate = () => { + if (shape.props.prompt.trim() && !shape.props.isLoading) { + generateDiagram(shape.props.prompt) + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { prompt: "" }, + }) + } + } + + // Handle code edit and live re-render + const handleCodeChange = useCallback(async (newCode: string) => { + editor.updateShape({ + id: shape.id, + type: "MermaidGen", + props: { diagramSource: newCode }, + }) + }, [editor, shape.id]) + + const latestGif = shape.props.diagramHistory[0]?.gifBase64 + + return ( + + + Mermaid Generator + + Generating... + + + ) : undefined + } + > +
+ {/* Preview Area */} +
+ {/* Loading spinner */} + {shape.props.isLoading && ( +
+
+ + {shape.props.loadingPrompt ? `Generating: ${shape.props.loadingPrompt.slice(0, 40)}...` : "Generating diagram..."} + +
+ )} + + {/* Animated GIF (takes priority over SVG when available) */} + {!shape.props.isLoading && latestGif && ( + Animated diagram + )} + + {/* SVG preview */} + {!shape.props.isLoading && !latestGif && svgPreview && ( +
+ )} + + {/* Animating overlay */} + {isAnimating && ( +
+
+ Creating animation... +
+ )} + + {/* Empty state */} + {!shape.props.isLoading && !svgPreview && !latestGif && ( + + Describe a diagram or write mermaid code below + + )} +
+ + {/* Controls Bar */} +
+ {/* Mode selector */} + + + {/* Delay slider */} + + + {/* Theme picker */} + + + {/* Toggle code/prompt button */} + +
+ + {/* Input Section — prompt mode or code editor mode */} + {shape.props.showCode ? ( + /* Code editor */ +