From 5af1e1475073cc9348475ee3eb3e948d51cd8578 Mon Sep 17 00:00:00 2001 From: SysVis AI Date: Mon, 5 Jan 2026 18:39:08 +0700 Subject: [PATCH] feat: Add ComfyUI import, static view mode, and dashboard controls --- package-lock.json | 235 +++++++++------- package.json | 1 + src/components/FlowCanvas.tsx | 310 ++++++++++++++------- src/components/InputPanel.tsx | 19 +- src/components/InteractiveLegend.tsx | 95 ++++++- src/components/MermaidStaticViewer.tsx | 168 +++++++++++ src/components/editor/ComfyImportPanel.tsx | 116 ++++++++ src/lib/comfy2mermaid.test.ts | 50 ++++ src/lib/comfy2mermaid.ts | 276 ++++++++++++++++++ src/pages/Editor.tsx | 9 +- src/store/index.ts | 4 + src/store/uiStore.ts | 10 +- 12 files changed, 1073 insertions(+), 220 deletions(-) create mode 100644 src/components/MermaidStaticViewer.tsx create mode 100644 src/components/editor/ComfyImportPanel.tsx create mode 100644 src/lib/comfy2mermaid.test.ts create mode 100644 src/lib/comfy2mermaid.ts diff --git a/package-lock.json b/package-lock.json index 8624fac..e1d63ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@rollup/rollup-darwin-arm64": "^4.55.0", "@tailwindcss/vite": "^4.1.18", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", @@ -626,9 +627,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "license": "MIT", "optional": true, "dependencies": { @@ -1078,9 +1079,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1235,9 +1236,9 @@ } }, "node_modules/@exodus/bytes": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.7.0.tgz", - "integrity": "sha512-5i+BtvujK/vM07YCGDyz4C4AyDzLmhxHMtM5HpUyPRtJPBdFPsj290ffXW+UXY21/G7GtXeHD2nRmq0T1ShyQQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.8.0.tgz", + "integrity": "sha512-8JPn18Bcp8Uo1T82gR8lh2guEOa5KKU/IEKvvdp0sgmi7coPBWf1Doi1EXsGZb2ehc8ym/StJCjffYV+ne7sXQ==", "dev": true, "license": "MIT", "engines": { @@ -2089,15 +2090,14 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "version": "4.55.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.0.tgz", + "integrity": "sha512-iiSGJu03Vsi2+Zz9PRbJ18icTAte/Geh/3f5T94DGDwuCa2GBY0MwIyvgZNV6Hur5fBgEBsUUqIZ/cPC8r9B/g==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", - "optional": true, "os": [ "darwin" ] @@ -2369,9 +2369,9 @@ ] }, "node_modules/@sinclair/typebox": { - "version": "0.34.45", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.45.tgz", - "integrity": "sha512-qJcFVfCa5jxBFSuv7S5WYbA8XdeCPmhnaVVfX/2Y6L8WYg8sk3XY2+6W0zH+3mq1Cz+YC7Ki66HfqX6IHAwnkg==", + "version": "0.34.46", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.46.tgz", + "integrity": "sha512-kiW7CtS/NkdvTUjkjUJo7d5JsFfbJ14YjdhDk9KoEgK6nFjKNXZPrX0jfLA8ZlET4cFLHxOZ/0vFKOP+bOxIOQ==", "dev": true, "license": "MIT" }, @@ -3226,20 +3226,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.1.tgz", - "integrity": "sha512-PKhLGDq3JAg0Jk/aK890knnqduuI/Qj+udH7wCf0217IGi4gt+acgCyPVe79qoT+qKUvHMDQkwJeKW9fwl8Cyw==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", + "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/type-utils": "8.50.1", - "@typescript-eslint/utils": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/type-utils": "8.51.0", + "@typescript-eslint/utils": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3249,7 +3249,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.50.1", + "@typescript-eslint/parser": "^8.51.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -3265,16 +3265,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.1.tgz", - "integrity": "sha512-hM5faZwg7aVNa819m/5r7D0h0c9yC4DUlWAOvHAtISdFTc8xB86VmX5Xqabrama3wIPJ/q9RbGS1worb6JfnMg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", + "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4" }, "engines": { @@ -3290,14 +3290,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.1.tgz", - "integrity": "sha512-E1ur1MCVf+YiP89+o4Les/oBAVzmSbeRB0MQLfSlYtbWU17HPxZ6Bhs5iYmKZRALvEuBoXIZMOIRRc/P++Ortg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", + "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.50.1", - "@typescript-eslint/types": "^8.50.1", + "@typescript-eslint/tsconfig-utils": "^8.51.0", + "@typescript-eslint/types": "^8.51.0", "debug": "^4.3.4" }, "engines": { @@ -3312,14 +3312,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.1.tgz", - "integrity": "sha512-mfRx06Myt3T4vuoHaKi8ZWNTPdzKPNBhiblze5N50//TSHOAQQevl/aolqA/BcqqbJ88GUnLqjjcBc8EWdBcVw==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", + "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1" + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3330,9 +3330,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.1.tgz", - "integrity": "sha512-ooHmotT/lCWLXi55G4mvaUF60aJa012QzvLK0Y+Mp4WdSt17QhMhWOaBWeGTFVkb2gDgBe19Cxy1elPXylslDw==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", + "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "dev": true, "license": "MIT", "engines": { @@ -3347,17 +3347,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.1.tgz", - "integrity": "sha512-7J3bf022QZE42tYMO6SL+6lTPKFk/WphhRPe9Tw/el+cEwzLz1Jjz2PX3GtGQVxooLDKeMVmMt7fWpYRdG5Etg==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", + "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/utils": "8.50.1", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0", "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3372,9 +3372,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.1.tgz", - "integrity": "sha512-v5lFIS2feTkNyMhd7AucE/9j/4V9v5iIbpVRncjk/K0sQ6Sb+Np9fgYS/63n6nwqahHQvbmujeBL7mp07Q9mlA==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", + "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "dev": true, "license": "MIT", "engines": { @@ -3386,21 +3386,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.1.tgz", - "integrity": "sha512-woHPdW+0gj53aM+cxchymJCrh0cyS7BTIdcDxWUNsclr9VDkOSbqC13juHzxOmQ22dDkMZEpZB+3X1WpUvzgVQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", + "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.50.1", - "@typescript-eslint/tsconfig-utils": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/visitor-keys": "8.50.1", + "@typescript-eslint/project-service": "8.51.0", + "@typescript-eslint/tsconfig-utils": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/visitor-keys": "8.51.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3453,16 +3453,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.1.tgz", - "integrity": "sha512-lCLp8H1T9T7gPbEuJSnHwnSuO9mDf8mfK/Nion5mZmiEaQD9sWf9W4dfeFqRyqRjF06/kBuTmAqcs9sewM2NbQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", + "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.50.1", - "@typescript-eslint/types": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1" + "@typescript-eslint/scope-manager": "8.51.0", + "@typescript-eslint/types": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3477,13 +3477,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.1.tgz", - "integrity": "sha512-IrDKrw7pCRUR94zeuCSUWQ+w8JEf5ZX5jl/e6AHGSLi1/zIr0lgutfn/7JpfCey+urpgQEdrZVYzCaVVKiTwhQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", + "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.50.1", + "@typescript-eslint/types": "8.51.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3936,9 +3936,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001761", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", - "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", "dev": true, "funding": [ { @@ -4163,20 +4163,31 @@ "license": "MIT" }, "node_modules/cssstyle": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.5.tgz", - "integrity": "sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.6.tgz", + "integrity": "sha512-legscpSpgSAeGEe0TNcai97DKt9Vd9AsAdOL7Uoetb52Ar/8eJm3LIa39qpv8wWzLFlNG4vVvppQM+teaMPj3A==", "dev": true, "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", - "css-tree": "^3.1.0" + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" }, "engines": { "node": ">=20" } }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -5103,9 +5114,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6081,9 +6092,9 @@ } }, "node_modules/lib0": { - "version": "0.2.116", - "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.116.tgz", - "integrity": "sha512-4zsosjzmt33rx5XjmFVYUAeLNh+BTeDTiwGdLt4muxiir2btsc60Nal0EvkvDRizg+pnlK1q+BtYi7M+d4eStw==", + "version": "0.2.117", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", "license": "MIT", "dependencies": { "isomorphic.js": "^0.2.4" @@ -7332,6 +7343,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, "node_modules/roughjs": { "version": "4.6.6", "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", @@ -7826,9 +7851,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", - "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -7894,16 +7919,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.50.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.1.tgz", - "integrity": "sha512-ytTHO+SoYSbhAH9CrYnMhiLx8To6PSSvqnvXyPUgPETCvB6eBKmTI9w6XMPS3HsBRGkwTVBX+urA8dYQx6bHfQ==", + "version": "8.51.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", + "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.50.1", - "@typescript-eslint/parser": "8.50.1", - "@typescript-eslint/typescript-estree": "8.50.1", - "@typescript-eslint/utils": "8.50.1" + "@typescript-eslint/eslint-plugin": "8.51.0", + "@typescript-eslint/parser": "8.51.0", + "@typescript-eslint/typescript-estree": "8.51.0", + "@typescript-eslint/utils": "8.51.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8214,9 +8239,9 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -8404,9 +8429,9 @@ "license": "ISC" }, "node_modules/yjs": { - "version": "13.6.28", - "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.28.tgz", - "integrity": "sha512-EgnDOXs8+hBVm6mq3/S89Kiwzh5JRbn7w2wXwbrMRyKy/8dOFsLvuIfC+x19ZdtaDc0tA9rQmdZzbqqNHG44wA==", + "version": "13.6.29", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.29.tgz", + "integrity": "sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==", "license": "MIT", "dependencies": { "lib0": "^0.2.99" @@ -8434,9 +8459,9 @@ } }, "node_modules/zod": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", - "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", "dev": true, "license": "MIT", "funding": { diff --git a/package.json b/package.json index 553c097..43e80f9 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@rollup/rollup-darwin-arm64": "^4.55.0", "@tailwindcss/vite": "^4.1.18", "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", diff --git a/src/components/FlowCanvas.tsx b/src/components/FlowCanvas.tsx index 1a9229a..c719486 100644 --- a/src/components/FlowCanvas.tsx +++ b/src/components/FlowCanvas.tsx @@ -14,7 +14,7 @@ import { nodeTypes } from './nodes/CustomNodes'; import { edgeTypes, EdgeDefs } from './edges/AnimatedEdge'; import { Spline, Minus, Plus, Maximize, Map, Wand2, - Hand, MousePointer2, Settings2, ChevronDown + Hand, MousePointer2, Settings2, ChevronDown, FileImage, Trash2 } from 'lucide-react'; import { getLayoutedElements } from '../lib/layoutEngine'; @@ -25,7 +25,7 @@ export function FlowCanvas() { const { nodes, edges, onNodesChange, onEdgesChange, onConnect, setSelectedNode, edgeStyle, setEdgeStyle, theme, activeFilters, - setNodes, setEdges, focusMode + setNodes, setEdges, focusMode, viewMode, setViewMode } = useFlowStore(); const { isMobile } = useMobileDetect(); const { zoomIn, zoomOut, fitView } = useReactFlow(); @@ -110,9 +110,104 @@ export function FlowCanvas() { } } + // Check for Dynamic Label Filter + const label = (node.data?.label as string || '').trim(); + const dynamicFilterId = `dyn-${label}`; + + // Logic: + // 1. If a dynamic filter active state exists for this label (meaning the label is "known" effectively), check it. + // However, activeFilters is a list of IDs. + // Wait, we don't know if the filter 'exists' here easily without scanning all nodes or passing `dynamicFilters` prop. + // But we know if `dyn-` version is in `activeFilters`, it is explicitly ON. + // If `dyn-` version is NOT in `activeFilters`, is it implicitly OFF? + // In `InteractiveLegend`, we auto-add new dynamic labels to `activeFilters`. + // So if it IS a known dynamic label, it SHOULD be in `activeFilters` to be visible. + // But `FlowCanvas` doesn't know if it's a "known" dynamic label or just some random text. + // Heuristic: If the label is short enough to trigger a dynamic filter (length < 30), + // then we assume it is governed by the dynamic filter system. + + let isVisible = false; + + if (label.length < 30 && label.length > 0) { + // It is a dynamic filter candidate. + // Visibility is determined by presence in activeFilters. + // Note: InteractiveLegend adds them asynchronously. There might be a split second where it's hidden before appearing. + // To prevent flickering: maybe default to TRUE if activeFilters doesn't contain ANY dynamic filters yet? No. + // We will trust the store. + + // If the user has disabled the category (e.g. 'Server'), should 'KSampler' still show? + // User request imply: "specific node that appear". + // Usually specific overrides general. + + const isDynamicActive = activeFilters.includes(dynamicFilterId); + if (isDynamicActive) { + isVisible = true; + } else { + // CAUTION: If it's NOT in activeFilters, it could mean: + // A) InteractiveLegend hasn't added it yet (it's new) -> Should show? + // B) User explicitly turned it off -> Should hide. + + // Problem: We can't distinguish A from B easily here. + // But we know InteractiveLegend adds them immediately on mount/update. + // A flash of invisibility is possible. + // BUT, we can fallback to Category visibility if Dynamic visibility is "off" (missing)? + // No, if I turn off "KSampler", I want it gone. + + // Let's assume if the label is "Dynamic-able", strict filtering applies. + // But if activeFilters doesn't have it, it's hidden. + + // Fallback check: + // Is the CATEGORY also required? + // If I have "KSampler" (Other), and I turn off "Other", "KSampler" should probably hide too? + // Composite logic: Visible if (Category is Active) AND (Dynamic is Active or Not Applicable). + + // BUT, dynamic filters are added to the list. + // If I toggle "KSampler", it removes from list. + // So we must check dynamic ID. + + // If we enforce BOTH, then unchecking "Other" hides everything. + // If we enforce EITHER, then unchecking "Other" keeps KSampler. + + // Let's go with: Dynamic Filter OVERRIDES Category if present? + // Or Dynamic Filter is an AND condition? + // Usually: Visible = CategoryActive && (DynamicActive if exists). + + // Let's try: + // If `activeFilters` has ANY `dyn-`... implied system is active. + } + + // REVISED LOGIC: + // We check if `dyn-${label}` is in activeFilters. + // If we find ANY `dyn-` filters in `activeFilters` at all, we assume system is initialized. + // If so, we stick to strict checking. + + const anyDynamicActive = activeFilters.some(f => f.startsWith('dyn-')); + + if (!anyDynamicActive) { + // System maybe not ready or no dynamic filters active. + // Fallback to category. + isVisible = activeFilters.includes(category); + } else { + // System has dynamic filters. + // If this specific dynamic filter is present, show. + // Also check category? + // Let's prioritize Dynamic Filter visibility. + if (activeFilters.includes(dynamicFilterId)) { + isVisible = true; + } else { + // Dynamic filter is OFF. + isVisible = false; + } + } + + } else { + // Not a dynamic label situation, standard category + isVisible = activeFilters.includes(category); + } + return { ...node, - hidden: !activeFilters.includes(category) + hidden: !isVisible }; }); }, [nodes, activeFilters]); @@ -146,14 +241,18 @@ export function FlowCanvas() { }); }, [edges, edgeStyle]); - // Filter edges to only show connections between visible nodes + // Filter edges to only show connections between visible nodes AND if edges are enabled const filteredEdges = useMemo(() => { + // Check if edges are globally enabled via filter + const edgesEnabled = activeFilters.includes('filter-edge'); + if (!edgesEnabled) return []; + const visibleNodeIds = new Set(filteredNodes.filter(n => !n.hidden).map(n => n.id)); return styledEdges.map(edge => ({ ...edge, hidden: !visibleNodeIds.has(edge.source) || !visibleNodeIds.has(edge.target) })); - }, [styledEdges, filteredNodes]); + }, [styledEdges, filteredNodes, activeFilters]); // Node click handler - bidirectional highlighting const onNodeClick = useCallback((_event: React.MouseEvent, node: any) => { @@ -234,96 +333,121 @@ export function FlowCanvas() { {/* Control Panel - Top Right (Unified Toolkit) - Desktop Only */} {!isMobile && ( -
- - - {/* Dropdown Menu */} - {showToolkit && ( -
- - {/* Section: Interaction Mode */} -
- Mode -
- - -
-
- -
- - {/* Section: View Controls */} -
- View -
- zoomOut()} label="Out" /> - zoomIn()} label="In" /> - -
-
- -
- - {/* Section: Layout & Overlays */} -
- Actions - - - - setEdgeStyle(edgeStyle === 'curved' ? 'straight' : 'curved')} - /> - - setShowMiniMap(!showMiniMap)} - /> -
-
+
+ {/* Clear Dashboard Button */} + {nodes.length > 0 && ( + )} -
+
+ + + {/* Dropdown Menu */} + {showToolkit && ( +
+ + {/* Section: Interaction Mode */} +
+ Mode +
+ + +
+
+ +
+ + {/* Section: View Controls */} +
+ View +
+ zoomOut()} label="Out" /> + zoomIn()} label="In" /> + +
+
+ +
+ + {/* Section: Layout & Overlays */} +
+ Actions + + + + setEdgeStyle(edgeStyle === 'curved' ? 'straight' : 'curved')} + /> + + setShowMiniMap(!showMiniMap)} + /> + + setViewMode('static')} + /> +
+
+ )} +
+
)} diff --git a/src/components/InputPanel.tsx b/src/components/InputPanel.tsx index b49342f..3bbc674 100644 --- a/src/components/InputPanel.tsx +++ b/src/components/InputPanel.tsx @@ -1,13 +1,14 @@ import { useCallback } from 'react'; import { ImageUpload } from './ImageUpload'; import { CodeEditor } from './CodeEditor'; -import { Image, Code, MessageSquare, Loader2, Zap } from 'lucide-react'; +import { ComfyImportPanel } from './editor/ComfyImportPanel'; // Import new panel +import { Image, Code, MessageSquare, Loader2, Zap, Workflow } from 'lucide-react'; // Add Workflow icon import { useFlowStore } from '../store'; import { interpretText } from '../lib/aiService'; import { parseMermaid } from '../lib/mermaidParser'; import { getLayoutedElements } from '../lib/layoutEngine'; -type Tab = 'image' | 'code' | 'describe'; +type Tab = 'image' | 'code' | 'describe' | 'comfy'; // Add 'comfy' tab type export function InputPanel() { const { @@ -95,16 +96,17 @@ export function InputPanel() { }, [description, ollamaUrl, modelName, aiMode, onlineProvider, apiKey, generationComplexity, setNodes, setEdges, setLoading, setError, setSourceCode, setMermaidCode, saveDiagram]); const tabs = [ - { id: 'image' as Tab, icon: Image, label: 'Upload' }, + { id: 'describe' as Tab, icon: MessageSquare, label: 'Describe' }, // Moved Describe first as primary { id: 'code' as Tab, icon: Code, label: 'Code' }, - { id: 'describe' as Tab, icon: MessageSquare, label: 'Describe' }, + { id: 'comfy' as Tab, icon: Workflow, label: 'ComfyUI' }, // New Tab + { id: 'image' as Tab, icon: Image, label: 'Upload' }, ]; return (
{/* Floating Tabs */}
-
+
{tabs.map(tab => { const Icon = tab.icon; const isActive = activeTab === tab.id; @@ -121,7 +123,7 @@ export function InputPanel() { )}
- {tab.label} + {tab.label}
); @@ -129,7 +131,7 @@ export function InputPanel() {
- {/* Complexity Toggle */} + {/* Complexity Toggle - Only for text intent flow */} {(activeTab === 'image' || activeTab === 'describe') && (
@@ -164,13 +166,14 @@ export function InputPanel() {
{activeTab === 'image' && } {activeTab === 'code' && } + {activeTab === 'comfy' && } + {activeTab === 'describe' && (