Revert "feat: add Mermaid diagram generator tool to canvas"
CI/CD / test (push) Successful in 1m40s Details
CI/CD / deploy (push) Has been skipped Details

This reverts commit 4d42db4bb4.
This commit is contained in:
Jeff Emmett 2026-04-01 12:57:49 -07:00
parent 4d42db4bb4
commit 3f0b6c7d6c
7 changed files with 61 additions and 1341 deletions

508
package-lock.json generated
View File

@ -51,7 +51,6 @@
"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",
@ -262,19 +261,6 @@
"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",
@ -1976,10 +1962,11 @@
}
},
"node_modules/@braintree/sanitize-url": {
"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"
"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/@cbor-extract/cbor-extract-darwin-arm64": {
"version": "2.2.0",
@ -2085,45 +2072,6 @@
"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",
@ -4098,23 +4046,6 @@
"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",
@ -5542,15 +5473,6 @@
"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",
@ -10275,16 +10197,6 @@
"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",
@ -11397,9 +11309,9 @@
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
@ -12401,13 +12313,6 @@
"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",
@ -12438,17 +12343,6 @@
"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",
@ -12476,13 +12370,6 @@
"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",
@ -12580,31 +12467,6 @@
}
}
},
"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",
@ -12714,32 +12576,6 @@
"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",
@ -12996,12 +12832,6 @@
"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",
@ -13094,6 +12924,7 @@
"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"
}
@ -13276,6 +13107,7 @@
"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"
}
@ -13285,6 +13117,7 @@
"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"
},
@ -13297,6 +13130,7 @@
"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"
},
@ -13309,6 +13143,7 @@
"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"
}
@ -13317,7 +13152,8 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz",
"integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/d": {
"version": "1.0.2",
@ -13627,46 +13463,6 @@
"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",
@ -13786,12 +13582,13 @@
}
},
"node_modules/dagre-d3-es": {
"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==",
"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.9.0",
"d3": "^7.8.2",
"lodash-es": "^4.17.21"
}
},
@ -13867,7 +13664,8 @@
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/debounce": {
"version": "1.2.1",
@ -15624,12 +15422,6 @@
"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",
@ -17325,31 +17117,6 @@
"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",
@ -17365,7 +17132,8 @@
"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=="
"integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==",
"optional": true
},
"node_modules/kind-of": {
"version": "6.0.3",
@ -17393,28 +17161,12 @@
"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"
"license": "MIT",
"optional": true
},
"node_modules/leven": {
"version": "3.1.0",
@ -17506,10 +17258,11 @@
"license": "MIT"
},
"node_modules/lodash-es": {
"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"
"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
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
@ -18154,67 +17907,36 @@
}
},
"node_modules/mermaid": {
"version": "11.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz",
"integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==",
"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": "^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",
"@braintree/sanitize-url": "^6.0.0",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"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",
"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": "^11.1.0"
"uuid": "^9.0.0",
"web-worker": "^1.2.0"
}
},
"node_modules/mermaid/node_modules/dompurify": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
"integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
"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)",
"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"
}
"optional": true
},
"node_modules/meshline": {
"version": "3.3.1",
@ -19217,18 +18939,6 @@
}
}
},
"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",
@ -19903,12 +19613,6 @@
"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",
@ -19995,12 +19699,6 @@
"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",
@ -20063,6 +19761,7 @@
"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": {
@ -20142,17 +19841,6 @@
"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",
@ -20215,22 +19903,6 @@
"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",
@ -21928,18 +21600,6 @@
"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",
@ -22895,7 +22555,8 @@
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/supercluster": {
"version": "8.0.1",
@ -23187,6 +22848,7 @@
"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"
@ -23390,6 +23052,7 @@
"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"
}
@ -23591,9 +23254,9 @@
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz",
"integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
"license": "MIT"
},
"node_modules/uint8arrays": {
@ -24518,55 +24181,6 @@
}
}
},
"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",

View File

@ -77,7 +77,6 @@
"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",

View File

@ -47,8 +47,6 @@ 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
@ -168,7 +166,6 @@ 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
@ -198,7 +195,6 @@ const customTools = [
FathomMeetingsTool,
MeetingIntelligenceTool, // Self-hosted meeting intelligence tool
ImageGenTool,
MermaidGenTool,
VideoGenTool,
BlenderGenTool, // Blender 3D procedural generation
...(ENABLE_DRAWFAST ? [DrawfastTool] : []), // Drawfast - dev only

View File

@ -1,860 +0,0 @@
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<string> {
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<IMermaidGen> {
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<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { pinnedToView: !shape.props.pinnedToView },
})
}
const [isMinimized, setIsMinimized] = useState(false)
const [svgPreview, setSvgPreview] = useState<string | null>(null)
const [isAnimating, setIsAnimating] = useState(false)
const svgContainerRef = useRef<HTMLDivElement>(null)
const handleClose = () => editor.deleteShape(shape.id)
const handleMinimize = () => setIsMinimized(!isMinimized)
const handleTagsChange = (newTags: string[]) => {
editor.updateShape<IMermaidGen>({
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<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { error: "Ollama not configured" },
})
return
}
editor.updateShape<IMermaidGen>({
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<IMermaidGen>(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<IMermaidGen>({
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<IMermaidGen>({
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<IMermaidGen>({
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<IMermaidGen>(shape.id)
const history = [...(currentShape?.props.diagramHistory || [])]
if (history.length > 0) {
history[0] = { ...history[0], gifBase64: data.gif }
}
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { diagramHistory: history },
})
} catch (error) {
const msg = error instanceof Error ? error.message : String(error)
editor.updateShape<IMermaidGen>({
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<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { prompt: "" },
})
}
}
// Handle code edit and live re-render
const handleCodeChange = useCallback(async (newCode: string) => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { diagramSource: newCode },
})
}, [editor, shape.id])
const latestGif = shape.props.diagramHistory[0]?.gifBase64
return (
<HTMLContainer id={shape.id}>
<StandardizedToolWrapper
title="Mermaid Generator"
primaryColor={MermaidGenShape.PRIMARY_COLOR}
isSelected={isSelected}
width={shape.props.w}
height={shape.props.h}
onClose={handleClose}
onMinimize={handleMinimize}
isMinimized={isMinimized}
onMaximize={toggleMaximize}
isMaximized={isMaximized}
editor={editor}
shapeId={shape.id}
tags={shape.props.tags || []}
onTagsChange={handleTagsChange}
tagsEditable={true}
isPinnedToView={shape.props.pinnedToView}
onPinToggle={handlePinToggle}
headerContent={
shape.props.isLoading ? (
<span style={{ display: "flex", alignItems: "center", gap: "8px" }}>
Mermaid Generator
<span style={{
marginLeft: "auto",
fontSize: "11px",
color: MermaidGenShape.PRIMARY_COLOR,
animation: "pulse 1.5s ease-in-out infinite",
}}>
Generating...
</span>
</span>
) : undefined
}
>
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
padding: "12px",
gap: "10px",
overflow: "auto",
backgroundColor: "#fafafa",
}}
>
{/* Preview Area */}
<div
ref={svgContainerRef}
style={{
flex: 1,
minHeight: "120px",
backgroundColor: "#fff",
borderRadius: "6px",
border: "1px solid #e0e0e0",
overflow: "auto",
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "relative",
}}
>
{/* Loading spinner */}
{shape.props.isLoading && (
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 12 }}>
<div style={{
width: 40, height: 40,
border: "4px solid #f3f3f3",
borderTop: `4px solid ${MermaidGenShape.PRIMARY_COLOR}`,
borderRadius: "50%",
animation: "spin 1s linear infinite",
}} />
<span style={{ color: "#666", fontSize: "14px" }}>
{shape.props.loadingPrompt ? `Generating: ${shape.props.loadingPrompt.slice(0, 40)}...` : "Generating diagram..."}
</span>
</div>
)}
{/* Animated GIF (takes priority over SVG when available) */}
{!shape.props.isLoading && latestGif && (
<img
src={`data:image/gif;base64,${latestGif}`}
alt="Animated diagram"
style={{ maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }}
/>
)}
{/* SVG preview */}
{!shape.props.isLoading && !latestGif && svgPreview && (
<div
dangerouslySetInnerHTML={{ __html: svgPreview }}
style={{ maxWidth: "100%", maxHeight: "100%", overflow: "auto", padding: "8px" }}
/>
)}
{/* Animating overlay */}
{isAnimating && (
<div style={{
position: "absolute", inset: 0,
backgroundColor: "rgba(255,255,255,0.85)",
display: "flex", flexDirection: "column",
alignItems: "center", justifyContent: "center", gap: 8,
}}>
<div style={{
width: 32, height: 32,
border: "3px solid #f3f3f3",
borderTop: `3px solid ${MermaidGenShape.PRIMARY_COLOR}`,
borderRadius: "50%",
animation: "spin 1s linear infinite",
}} />
<span style={{ color: "#666", fontSize: "12px" }}>Creating animation...</span>
</div>
)}
{/* Empty state */}
{!shape.props.isLoading && !svgPreview && !latestGif && (
<span style={{ color: "#999", fontSize: "14px" }}>
Describe a diagram or write mermaid code below
</span>
)}
</div>
{/* Controls Bar */}
<div
style={{
display: "flex",
gap: "8px",
alignItems: "center",
flexWrap: "wrap",
flexShrink: 0,
}}
>
{/* Mode selector */}
<select
value={shape.props.animationMode}
onChange={(e) => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { animationMode: e.target.value as any },
})
}}
onPointerDown={(e) => e.stopPropagation()}
style={{
padding: "4px 8px", fontSize: "12px",
border: "1px solid #ddd", borderRadius: "4px",
backgroundColor: "#fff", cursor: "pointer",
}}
>
<option value="progressive">Progressive</option>
<option value="template">Template</option>
<option value="sequence">Sequence</option>
</select>
{/* Delay slider */}
<label style={{ display: "flex", alignItems: "center", gap: "4px", fontSize: "12px", color: "#666" }}>
Delay:
<input
type="range"
min={100}
max={2000}
step={100}
value={shape.props.animationDelay}
onChange={(e) => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { animationDelay: Number(e.target.value) },
})
}}
onPointerDown={(e) => e.stopPropagation()}
style={{ width: "80px", cursor: "pointer" }}
/>
<span style={{ minWidth: "36px" }}>{shape.props.animationDelay}ms</span>
</label>
{/* Theme picker */}
<select
value={shape.props.theme}
onChange={(e) => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { theme: e.target.value as any },
})
}}
onPointerDown={(e) => e.stopPropagation()}
style={{
padding: "4px 8px", fontSize: "12px",
border: "1px solid #ddd", borderRadius: "4px",
backgroundColor: "#fff", cursor: "pointer",
}}
>
<option value="default">Default</option>
<option value="dark">Dark</option>
<option value="forest">Forest</option>
<option value="neutral">Neutral</option>
</select>
{/* Toggle code/prompt button */}
<button
onClick={(e) => {
e.stopPropagation()
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { showCode: !shape.props.showCode },
})
}}
onPointerDown={(e) => e.stopPropagation()}
style={{
padding: "4px 10px", fontSize: "12px",
border: "1px solid #ddd", borderRadius: "4px",
backgroundColor: shape.props.showCode ? MermaidGenShape.PRIMARY_COLOR : "#fff",
color: shape.props.showCode ? "#fff" : "#666",
cursor: "pointer", fontWeight: 500,
marginLeft: "auto",
}}
>
{shape.props.showCode ? "Prompt" : "Code"}
</button>
</div>
{/* Input Section — prompt mode or code editor mode */}
{shape.props.showCode ? (
/* Code editor */
<textarea
style={{
minHeight: "80px",
height: "100px",
backgroundColor: "#1e1e2e",
color: "#cdd6f4",
border: "1px solid #45475a",
borderRadius: "6px",
fontSize: 13,
padding: "10px",
fontFamily: "monospace",
resize: "vertical",
lineHeight: "1.5",
flexShrink: 0,
}}
value={shape.props.diagramSource}
onChange={(e) => handleCodeChange(e.target.value)}
onKeyDown={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
placeholder="graph TD&#10; A[Start] --> B[End]"
spellCheck={false}
/>
) : (
/* Prompt input */
<div
style={{
display: "flex",
flexDirection: shape.props.w < 400 ? "column" : "row",
gap: 8,
flexShrink: 0,
}}
>
<textarea
style={{
flex: 1,
minHeight: "48px",
height: "48px",
backgroundColor: "#fff",
border: "1px solid #ddd",
borderRadius: "8px",
fontSize: 14,
padding: "12px",
touchAction: "manipulation",
resize: "none",
fontFamily: "inherit",
lineHeight: "1.4",
WebkitAppearance: "none",
}}
placeholder="Describe the diagram you want..."
value={shape.props.prompt}
onChange={(e) => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { prompt: e.target.value },
})
}}
onKeyDown={(e) => {
e.stopPropagation()
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleGenerate()
}
}}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
onClick={(e) => e.stopPropagation()}
disabled={shape.props.isLoading}
/>
<button
style={{
height: "48px",
padding: "0 16px",
cursor: shape.props.prompt.trim() && !shape.props.isLoading ? "pointer" : "not-allowed",
backgroundColor: shape.props.prompt.trim() && !shape.props.isLoading ? MermaidGenShape.PRIMARY_COLOR : "#ccc",
color: "white",
border: "none",
borderRadius: "8px",
fontWeight: "600",
fontSize: "14px",
opacity: shape.props.prompt.trim() && !shape.props.isLoading ? 1 : 0.6,
touchAction: "manipulation",
minWidth: shape.props.w < 400 ? "100%" : "100px",
minHeight: "48px",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "6px",
}}
onPointerDown={(e) => {
e.stopPropagation()
e.preventDefault()
handleGenerate()
}}
onTouchStart={(e) => e.stopPropagation()}
onTouchEnd={(e) => {
e.stopPropagation()
e.preventDefault()
handleGenerate()
}}
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
handleGenerate()
}}
disabled={shape.props.isLoading || !shape.props.prompt.trim()}
>
Generate
</button>
</div>
)}
{/* Action Buttons — Animate + Download */}
{shape.props.diagramSource && (
<div style={{ display: "flex", gap: "8px", flexShrink: 0 }}>
<button
onClick={(e) => { e.stopPropagation(); animateGif() }}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
disabled={isAnimating || !shape.props.diagramSource}
style={{
flex: 1,
padding: "8px 12px",
backgroundColor: isAnimating ? "#ccc" : "#fff",
border: `1px solid ${MermaidGenShape.PRIMARY_COLOR}`,
borderRadius: "6px",
cursor: isAnimating ? "not-allowed" : "pointer",
fontSize: "13px",
fontWeight: 500,
color: MermaidGenShape.PRIMARY_COLOR,
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "6px",
minHeight: "40px",
}}
>
{isAnimating ? "Animating..." : "Animate GIF"}
</button>
{latestGif && (
<button
onClick={(e) => {
e.stopPropagation()
const link = document.createElement("a")
link.href = `data:image/gif;base64,${latestGif}`
const slug = (shape.props.diagramHistory[0]?.prompt || "diagram")
.slice(0, 30).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
link.download = `${slug}-${new Date().toISOString().slice(0, 10)}.gif`
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
style={{
flex: 1,
padding: "8px 12px",
backgroundColor: MermaidGenShape.PRIMARY_COLOR,
border: "none",
borderRadius: "6px",
cursor: "pointer",
fontSize: "13px",
fontWeight: 500,
color: "#fff",
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: "6px",
minHeight: "40px",
}}
>
Download GIF
</button>
)}
</div>
)}
{/* History */}
{shape.props.diagramHistory.length > 1 && (
<div style={{ borderTop: "1px solid #e0e0e0", paddingTop: "8px" }}>
<div style={{ fontSize: "12px", fontWeight: 600, color: "#666", marginBottom: "6px" }}>
History ({shape.props.diagramHistory.length})
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "6px", maxHeight: "150px", overflow: "auto" }}>
{shape.props.diagramHistory.slice(1).map((item) => (
<div
key={item.id}
style={{
display: "flex",
alignItems: "center",
gap: "8px",
padding: "6px 8px",
backgroundColor: "#fff",
borderRadius: "4px",
border: "1px solid #e8e8e8",
cursor: "pointer",
fontSize: "12px",
}}
onClick={(e) => {
e.stopPropagation()
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { diagramSource: item.source },
})
}}
onPointerDown={(e) => e.stopPropagation()}
>
{item.gifBase64 ? (
<img
src={`data:image/gif;base64,${item.gifBase64}`}
alt=""
style={{ width: 40, height: 30, objectFit: "cover", borderRadius: 3 }}
/>
) : (
<div style={{ width: 40, height: 30, backgroundColor: "#f0f0f0", borderRadius: 3, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "10px", color: "#aaa" }}>
SVG
</div>
)}
<div style={{ flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: "#555" }}>
{item.prompt || item.source.slice(0, 50)}
</div>
<button
onClick={(e) => {
e.stopPropagation()
const newHistory = shape.props.diagramHistory.filter((d) => d.id !== item.id)
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { diagramHistory: newHistory },
})
}}
onPointerDown={(e) => e.stopPropagation()}
style={{
padding: "2px 6px", backgroundColor: "transparent",
border: "none", cursor: "pointer", color: "#bbb", fontSize: "14px",
}}
title="Remove"
>
x
</button>
</div>
))}
</div>
</div>
)}
{/* Error Display */}
{shape.props.error && (
<div
style={{
padding: "8px 12px",
backgroundColor: "#fee",
border: "1px solid #fcc",
borderRadius: "6px",
color: "#c33",
fontSize: "12px",
display: "flex",
alignItems: "flex-start",
gap: "8px",
whiteSpace: "pre-wrap",
wordBreak: "break-word",
maxHeight: "80px",
overflowY: "auto",
flexShrink: 0,
}}
>
<span style={{ flex: 1, lineHeight: "1.4" }}>{shape.props.error}</span>
<button
onClick={() => {
editor.updateShape<IMermaidGen>({
id: shape.id,
type: "MermaidGen",
props: { error: null },
})
}}
onPointerDown={(e) => e.stopPropagation()}
onTouchStart={(e) => e.stopPropagation()}
style={{
padding: "2px 6px",
backgroundColor: "#fcc",
border: "1px solid #c99",
borderRadius: "4px",
cursor: "pointer",
fontSize: "10px",
flexShrink: 0,
minWidth: "32px",
minHeight: "32px",
}}
>
x
</button>
</div>
)}
</div>
<style>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
`}</style>
</StandardizedToolWrapper>
</HTMLContainer>
)
}
override indicator(shape: IMermaidGen) {
return (
<rect
width={shape.props.w}
height={shape.props.h}
rx={6}
/>
)
}
}

View File

@ -1,11 +0,0 @@
import { BaseBoxShapeTool, TLEventHandlers } from 'tldraw'
export class MermaidGenTool extends BaseBoxShapeTool {
static override id = 'MermaidGen'
static override initial = 'idle'
override shapeType = 'MermaidGen'
override onComplete: TLEventHandlers["onComplete"] = () => {
this.editor.setCurrentTool('select')
}
}

View File

@ -37,7 +37,6 @@ const AI_TOOLS = [
{ id: 'video-gen', name: 'Video Gen', icon: '🎬', model: 'Wan2.1', provider: 'RunPod', type: 'gpu' },
{ id: 'transcription', name: 'Transcribe', icon: '🎤', model: 'Web Speech', provider: 'Browser', type: 'local' },
{ id: 'mycelial', name: 'Mycelial', icon: '🍄', model: 'llama3.1:70b', provider: 'Ollama', type: 'local' },
{ id: 'mermaid-gen', name: 'Diagrams', icon: '🔀', model: 'llama3.1:8b', provider: 'Ollama', type: 'local' },
]
// Dark mode utilities
@ -766,14 +765,6 @@ export function CustomToolbar() {
isSelected={tools["ImageGen"].id === editor.getCurrentToolId()}
/>
)}
{tools["MermaidGen"] && (
<TldrawUiMenuItem
{...tools["MermaidGen"]}
icon="code"
label="Mermaid Diagram"
isSelected={tools["MermaidGen"].id === editor.getCurrentToolId()}
/>
)}
{tools["VideoGen"] && (
<TldrawUiMenuItem
{...tools["VideoGen"]}

View File

@ -227,15 +227,6 @@ export const overrides: TLUiOverrides = {
type: "ImageGen",
onSelect: () => editor.setCurrentTool("ImageGen"),
},
MermaidGen: {
id: "MermaidGen",
icon: "code",
label: "Mermaid Diagram",
kbd: "ctrl+shift+r",
readonlyOk: true,
type: "MermaidGen",
onSelect: () => editor.setCurrentTool("MermaidGen"),
},
VideoGen: {
id: "VideoGen",
icon: "video",