mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-03 20:51:58 +00:00
Compare commits
2 Commits
version-bu
...
graphMutat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ea615fb8c | ||
|
|
ceac8f3741 |
568
package-lock.json
generated
568
package-lock.json
generated
@@ -975,32 +975,6 @@
|
||||
"integrity": "sha512-o6WFbYn9yAkGbkOwvhPF7pbKDvN0occZ21Tfyhya8CIsIqKpTHLft0aOqo4yhSh+kTxN16FYjsfrTH5Olk4WuA==",
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz",
|
||||
@@ -4717,17 +4691,6 @@
|
||||
"url": "https://github.com/sponsors/ueberdosis"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
|
||||
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@trivago/prettier-plugin-sort-imports": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.0.tgz",
|
||||
@@ -4763,38 +4726,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
|
||||
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
|
||||
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@tweenjs/tween.js": {
|
||||
"version": "23.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
|
||||
@@ -5720,15 +5651,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="
|
||||
},
|
||||
"node_modules/abab": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
|
||||
"deprecated": "Use your platform's native atob() and btoa() methods instead",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
|
||||
@@ -5798,18 +5720,6 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-globals": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz",
|
||||
"integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.1.0",
|
||||
"acorn-walk": "^8.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-jsx": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||
@@ -5820,34 +5730,6 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn-walk": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz",
|
||||
"integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"acorn": "^8.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
|
||||
@@ -6058,14 +5940,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
@@ -7227,14 +7101,6 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
@@ -7305,58 +7171,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/cssom": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz",
|
||||
"integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cssstyle": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
|
||||
"integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cssom": "~0.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle/node_modules/cssom": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
|
||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz",
|
||||
"integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"abab": "^2.0.6",
|
||||
"whatwg-mimetype": "^3.0.0",
|
||||
"whatwg-url": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/de-indent": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||
@@ -7420,14 +7240,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decimal.js": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz",
|
||||
"integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/decode-named-character-reference": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz",
|
||||
@@ -7583,17 +7395,6 @@
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
@@ -7689,21 +7490,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domexception": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz",
|
||||
"integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
|
||||
@@ -8065,29 +7851,6 @@
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/escodegen": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
|
||||
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^5.2.0",
|
||||
"esutils": "^2.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"escodegen": "bin/escodegen.js",
|
||||
"esgenerate": "bin/esgenerate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "9.12.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.12.0.tgz",
|
||||
@@ -9638,20 +9401,6 @@
|
||||
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
|
||||
"integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"whatwg-encoding": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/html-minifier-terser": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
|
||||
@@ -9703,37 +9452,6 @@
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
|
||||
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA=="
|
||||
},
|
||||
"node_modules/http-proxy-agent": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
|
||||
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tootallnate/once": "2",
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
@@ -10353,14 +10071,6 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-potential-custom-element-name": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
|
||||
"integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/is-promise": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
|
||||
@@ -10592,53 +10302,6 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/jsdom": {
|
||||
"version": "20.0.3",
|
||||
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz",
|
||||
"integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"abab": "^2.0.6",
|
||||
"acorn": "^8.8.1",
|
||||
"acorn-globals": "^7.0.0",
|
||||
"cssom": "^0.5.0",
|
||||
"cssstyle": "^2.3.0",
|
||||
"data-urls": "^3.0.2",
|
||||
"decimal.js": "^10.4.2",
|
||||
"domexception": "^4.0.0",
|
||||
"escodegen": "^2.0.0",
|
||||
"form-data": "^4.0.0",
|
||||
"html-encoding-sniffer": "^3.0.0",
|
||||
"http-proxy-agent": "^5.0.0",
|
||||
"https-proxy-agent": "^5.0.1",
|
||||
"is-potential-custom-element-name": "^1.0.1",
|
||||
"nwsapi": "^2.2.2",
|
||||
"parse5": "^7.1.1",
|
||||
"saxes": "^6.0.0",
|
||||
"symbol-tree": "^3.2.4",
|
||||
"tough-cookie": "^4.1.2",
|
||||
"w3c-xmlserializer": "^4.0.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-encoding": "^2.0.0",
|
||||
"whatwg-mimetype": "^3.0.0",
|
||||
"whatwg-url": "^11.0.0",
|
||||
"ws": "^8.11.0",
|
||||
"xml-name-validator": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"canvas": "^2.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"canvas": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
|
||||
@@ -11651,14 +11314,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
@@ -13048,14 +12703,6 @@
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/nwsapi": {
|
||||
"version": "2.2.10",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz",
|
||||
"integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -14215,14 +13862,6 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -14288,14 +13927,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -14801,14 +14432,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
@@ -15016,20 +14639,6 @@
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"xmlchars": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
@@ -15681,14 +15290,6 @@
|
||||
"react": "^16.11.0 || ^17.0.0 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/symbol-tree": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
|
||||
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.11.3",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.3.tgz",
|
||||
@@ -15943,37 +15544,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
|
||||
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"punycode": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/trough": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
|
||||
@@ -16004,51 +15574,6 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.9.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@swc/core": ">=1.2.50",
|
||||
"@swc/wasm": ">=1.2.50",
|
||||
"@types/node": "*",
|
||||
"typescript": ">=2.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@swc/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@swc/wasm": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
@@ -16762,17 +16287,6 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -17014,18 +16528,6 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
|
||||
@@ -17102,14 +16604,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@@ -17911,20 +17405,6 @@
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
|
||||
"integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"xml-name-validator": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/walk-up-path": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz",
|
||||
@@ -17986,20 +17466,6 @@
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
|
||||
"integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
|
||||
@@ -18009,21 +17475,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
|
||||
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tr46": "^3.0.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/when-exit": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.3.tgz",
|
||||
@@ -18320,14 +17771,6 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/xmlchars": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
@@ -18433,17 +17876,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
15
src/components/custom/button/IconButton.vue
Normal file
15
src/components/custom/button/IconButton.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<button
|
||||
class="flex justify-center items-center outline-none border-none p-0 bg-white text-neutral-950 dark-theme:bg-zinc-700 dark-theme:text-white w-8 h-8 rounded-lg cursor-pointer"
|
||||
role="button"
|
||||
@click="onClick"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { onClick } = defineProps<{
|
||||
onClick: () => void
|
||||
}>()
|
||||
</script>
|
||||
67
src/components/custom/widget/ModelSelector.vue
Normal file
67
src/components/custom/widget/ModelSelector.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<BaseWidgetLayout>
|
||||
<template #leftPanel>
|
||||
<LeftSidePanel v-model="selectedNavItem" :nav-items="tempNavigation">
|
||||
<template #header-icon>
|
||||
<i-lucide:puzzle class="text-neutral" />
|
||||
</template>
|
||||
<template #header-title>
|
||||
<span class="text-neutral text-base">{{ t('g.title') }}</span>
|
||||
</template>
|
||||
</LeftSidePanel>
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
<!-- here -->
|
||||
</template>
|
||||
|
||||
<template #content>
|
||||
<!-- here -->
|
||||
</template>
|
||||
|
||||
<template #rightPanel>
|
||||
<RightSidePanel></RightSidePanel>
|
||||
</template>
|
||||
</BaseWidgetLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { provide, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { NavGroupData, NavItemData } from '@/types/custom_components/navTypes'
|
||||
import { OnCloseKey } from '@/types/custom_components/widgetTypes'
|
||||
|
||||
import BaseWidgetLayout from './layout/BaseWidgetLayout.vue'
|
||||
import LeftSidePanel from './panel/LeftSidePanel.vue'
|
||||
import RightSidePanel from './panel/RightSidePanel.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { onClose } = defineProps<{
|
||||
onClose: () => void
|
||||
}>()
|
||||
|
||||
provide(OnCloseKey, onClose)
|
||||
|
||||
const tempNavigation = ref<(NavItemData | NavGroupData)[]>([
|
||||
{ id: 'installed', label: 'Installed' },
|
||||
{
|
||||
title: 'TAGS',
|
||||
items: [
|
||||
{ id: 'tag-sd15', label: 'SD 1.5' },
|
||||
{ id: 'tag-sdxl', label: 'SDXL' },
|
||||
{ id: 'tag-utility', label: 'Utility' }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'CATEGORIES',
|
||||
items: [
|
||||
{ id: 'cat-models', label: 'Models' },
|
||||
{ id: 'cat-nodes', label: 'Nodes' }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
const selectedNavItem = ref<string | null>('installed')
|
||||
</script>
|
||||
176
src/components/custom/widget/layout/BaseWidgetLayout.vue
Normal file
176
src/components/custom/widget/layout/BaseWidgetLayout.vue
Normal file
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div
|
||||
class="base-widget-layout rounded-2xl overflow-hidden relative bg-zinc-100 dark-theme:bg-zinc-800"
|
||||
>
|
||||
<IconButton
|
||||
v-show="!isRightPanelOpen && hasRightPanel"
|
||||
class="absolute top-4 right-16 z-10 transition-opacity duration-200"
|
||||
:class="{
|
||||
'opacity-0 pointer-events-none': isRightPanelOpen || !hasRightPanel
|
||||
}"
|
||||
@click="toggleRightPanel"
|
||||
>
|
||||
<i-lucide:panel-right class="text-sm" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
class="absolute top-4 right-6 z-10 transition-opacity duration-200"
|
||||
@click="closeDialog"
|
||||
>
|
||||
<i class="pi pi-times text-sm"></i>
|
||||
</IconButton>
|
||||
<div class="flex w-full h-full">
|
||||
<Transition name="slide-panel">
|
||||
<nav
|
||||
v-if="$slots.leftPanel && showLeftPanel"
|
||||
:class="[
|
||||
PANEL_SIZES.width,
|
||||
PANEL_SIZES.minWidth,
|
||||
PANEL_SIZES.maxWidth
|
||||
]"
|
||||
>
|
||||
<slot name="leftPanel"></slot>
|
||||
</nav>
|
||||
</Transition>
|
||||
|
||||
<div class="flex-1 flex bg-zinc-100 dark-theme:bg-neutral-900">
|
||||
<div class="w-full h-full flex flex-col">
|
||||
<header
|
||||
v-if="$slots.header"
|
||||
class="w-full h-16 px-6 py-4 flex justify-between gap-2"
|
||||
>
|
||||
<div class="flex-1 flex gap-2 flex-shrink-0">
|
||||
<IconButton v-if="!notMobile" @click="toggleLeftPanel">
|
||||
<i-lucide:panel-left v-if="!showLeftPanel" class="text-sm" />
|
||||
<i-lucide:panel-left-close v-else class="text-sm" />
|
||||
</IconButton>
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 min-w-20">
|
||||
<slot name="header-right-area"></slot>
|
||||
<IconButton
|
||||
v-if="isRightPanelOpen && hasRightPanel"
|
||||
@click="toggleRightPanel"
|
||||
>
|
||||
<i-lucide:panel-right-close class="text-sm" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</header>
|
||||
<main class="flex-1">
|
||||
<slot name="content"></slot>
|
||||
</main>
|
||||
</div>
|
||||
<Transition name="slide-panel-right">
|
||||
<aside
|
||||
v-if="hasRightPanel && isRightPanelOpen"
|
||||
class="w-1/4 min-w-40 max-w-80"
|
||||
>
|
||||
<slot name="rightPanel"></slot>
|
||||
</aside>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useBreakpoints } from '@vueuse/core'
|
||||
import { computed, inject, ref, useSlots, watch } from 'vue'
|
||||
|
||||
import IconButton from '@/components/custom/button/IconButton.vue'
|
||||
import { OnCloseKey } from '@/types/custom_components/widgetTypes'
|
||||
|
||||
const BREAKPOINTS = { sm: 480 }
|
||||
const PANEL_SIZES = {
|
||||
width: 'w-1/3',
|
||||
minWidth: 'min-w-40',
|
||||
maxWidth: 'max-w-56'
|
||||
}
|
||||
|
||||
const slots = useSlots()
|
||||
const closeDialog = inject(OnCloseKey, () => {})
|
||||
|
||||
const breakpoints = useBreakpoints(BREAKPOINTS)
|
||||
const notMobile = breakpoints.greater('sm')
|
||||
|
||||
const isLeftPanelOpen = ref<boolean>(true)
|
||||
const isRightPanelOpen = ref<boolean>(false)
|
||||
const mobileMenuOpen = ref<boolean>(false)
|
||||
|
||||
const hasRightPanel = computed(() => !!slots.rightPanel)
|
||||
|
||||
watch(notMobile, (isDesktop) => {
|
||||
if (!isDesktop) {
|
||||
mobileMenuOpen.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const showLeftPanel = computed(() => {
|
||||
const shouldShow = notMobile.value
|
||||
? isLeftPanelOpen.value
|
||||
: mobileMenuOpen.value
|
||||
return shouldShow
|
||||
})
|
||||
|
||||
const toggleLeftPanel = () => {
|
||||
if (notMobile.value) {
|
||||
isLeftPanelOpen.value = !isLeftPanelOpen.value
|
||||
} else {
|
||||
mobileMenuOpen.value = !mobileMenuOpen.value
|
||||
}
|
||||
}
|
||||
|
||||
const toggleRightPanel = () => {
|
||||
isRightPanelOpen.value = !isRightPanelOpen.value
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.base-widget-layout {
|
||||
height: 80vh;
|
||||
width: 90vw;
|
||||
max-width: 1280px;
|
||||
aspect-ratio: 20/13;
|
||||
}
|
||||
|
||||
@media (min-width: 1450px) {
|
||||
.base-widget-layout {
|
||||
max-width: 1724px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fade transition for buttons */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Slide transition for left panel */
|
||||
.slide-panel-enter-active,
|
||||
.slide-panel-leave-active {
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: transform;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.slide-panel-enter-from,
|
||||
.slide-panel-leave-to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
/* Slide transition for right panel */
|
||||
.slide-panel-right-enter-active,
|
||||
.slide-panel-right-leave-active {
|
||||
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: transform;
|
||||
backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.slide-panel-right-enter-from,
|
||||
.slide-panel-right-leave-to {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
</style>
|
||||
24
src/components/custom/widget/nav/NavItem.vue
Normal file
24
src/components/custom/widget/nav/NavItem.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex items-center gap-2 px-4 py-2 text-xs rounded-md transition-colors cursor-pointer"
|
||||
:class="
|
||||
active
|
||||
? 'bg-neutral-100 dark-theme:bg-zinc-700 text-neutral'
|
||||
: 'text-neutral hover:bg-zinc-100 hover:dark-theme:bg-zinc-700/50'
|
||||
"
|
||||
role="button"
|
||||
@click="onClick"
|
||||
>
|
||||
<i-lucide:folder class="text-xs text-neutral" />
|
||||
<span>
|
||||
<slot></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { active, onClick } = defineProps<{
|
||||
active?: boolean
|
||||
onClick: () => void
|
||||
}>()
|
||||
</script>
|
||||
13
src/components/custom/widget/nav/NavTitle.vue
Normal file
13
src/components/custom/widget/nav/NavTitle.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<h3
|
||||
class="m-0 px-3 py-0 pt-5 text-sm font-bold uppercase text-neutral-400 dark-theme:text-neutral-400"
|
||||
>
|
||||
{{ title }}
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { title } = defineProps<{
|
||||
title: string
|
||||
}>()
|
||||
</script>
|
||||
75
src/components/custom/widget/panel/LeftSidePanel.vue
Normal file
75
src/components/custom/widget/panel/LeftSidePanel.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="flex flex-col h-full w-full bg-white dark-theme:bg-zinc-800">
|
||||
<PanelHeader>
|
||||
<template #icon>
|
||||
<slot name="header-icon"></slot>
|
||||
</template>
|
||||
<slot name="header-title"></slot>
|
||||
</PanelHeader>
|
||||
|
||||
<nav class="flex-1 px-3 py-4 flex flex-col gap-2">
|
||||
<template v-for="(item, index) in navItems" :key="index">
|
||||
<div v-if="'items' in item" class="flex flex-col gap-2">
|
||||
<NavTitle :title="item.title" />
|
||||
<NavItem
|
||||
v-for="subItem in item.items"
|
||||
:key="subItem.id"
|
||||
:active="activeItem === subItem.id"
|
||||
@click="activeItem = subItem.id"
|
||||
>
|
||||
{{ subItem.label }}
|
||||
</NavItem>
|
||||
</div>
|
||||
<div v-else class="flex flex-col gap-2">
|
||||
<NavItem
|
||||
:active="activeItem === item.id"
|
||||
@click="activeItem = item.id"
|
||||
>
|
||||
{{ item.label }}
|
||||
</NavItem>
|
||||
</div>
|
||||
</template>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { NavGroupData, NavItemData } from '@/types/custom_components/navTypes'
|
||||
|
||||
import NavItem from '../nav/NavItem.vue'
|
||||
import NavTitle from '../nav/NavTitle.vue'
|
||||
import PanelHeader from './PanelHeader.vue'
|
||||
|
||||
const { navItems = [], modelValue } = defineProps<{
|
||||
navItems?: (NavItemData | NavGroupData)[]
|
||||
modelValue?: string | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: string | null]
|
||||
}>()
|
||||
|
||||
const getFirstItemId = () => {
|
||||
if (!navItems || navItems.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
const firstEntry = navItems[0]
|
||||
|
||||
if ('items' in firstEntry && firstEntry.items.length > 0) {
|
||||
return firstEntry.items[0].id
|
||||
}
|
||||
if ('id' in firstEntry) {
|
||||
return firstEntry.id
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const activeItem = computed({
|
||||
get: () => modelValue ?? getFirstItemId(),
|
||||
set: (value: string | null) => emit('update:modelValue', value)
|
||||
})
|
||||
</script>
|
||||
12
src/components/custom/widget/panel/PanelHeader.vue
Normal file
12
src/components/custom/widget/panel/PanelHeader.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<header class="flex items-center justify-between h-16 px-6">
|
||||
<div class="flex items-center gap-2 pl-1">
|
||||
<slot name="icon">
|
||||
<i-lucide:puzzle class="text-neutral text-base" />
|
||||
</slot>
|
||||
<h2 class="font-bold text-base text-neutral">
|
||||
<slot></slot>
|
||||
</h2>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
5
src/components/custom/widget/panel/RightSidePanel.vue
Normal file
5
src/components/custom/widget/panel/RightSidePanel.vue
Normal file
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div class="w-full h-full pl-4 pr-6 pb-8 bg-white dark-theme:bg-zinc-800">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
@@ -10,14 +10,16 @@
|
||||
:aria-labelledby="item.key"
|
||||
>
|
||||
<template #header>
|
||||
<component
|
||||
:is="item.headerComponent"
|
||||
v-if="item.headerComponent"
|
||||
:id="item.key"
|
||||
/>
|
||||
<h3 v-else :id="item.key">
|
||||
{{ item.title || ' ' }}
|
||||
</h3>
|
||||
<div v-if="!item.dialogComponentProps?.headless">
|
||||
<component
|
||||
:is="item.headerComponent"
|
||||
v-if="item.headerComponent"
|
||||
:id="item.key"
|
||||
/>
|
||||
<h3 v-else :id="item.key">
|
||||
{{ item.title || ' ' }}
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<component
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
LiteGraph
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import type { LiteGraphCanvasEvent } from '@/lib/litegraph/src/litegraph'
|
||||
import { app } from '@/scripts/app'
|
||||
import { useGraphMutationService } from '@/services/graphMutationService'
|
||||
import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore'
|
||||
import { useSettingStore } from '@/stores/settingStore'
|
||||
|
||||
@@ -42,19 +42,23 @@ const inputStyle = computed<CSSProperties>(() => ({
|
||||
const titleEditorStore = useTitleEditorStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
const previousCanvasDraggable = ref(true)
|
||||
const graphMutationService = useGraphMutationService()
|
||||
|
||||
const onEdit = (newValue: string) => {
|
||||
const onEdit = async (newValue: string) => {
|
||||
if (titleEditorStore.titleEditorTarget && newValue.trim() !== '') {
|
||||
const trimmedTitle = newValue.trim()
|
||||
titleEditorStore.titleEditorTarget.title = trimmedTitle
|
||||
|
||||
// If this is a subgraph node, sync the runtime subgraph name for breadcrumb reactivity
|
||||
const target = titleEditorStore.titleEditorTarget
|
||||
if (target instanceof LGraphNode && target.isSubgraphNode?.()) {
|
||||
target.subgraph.name = trimmedTitle
|
||||
}
|
||||
|
||||
app.graph.setDirtyCanvas(true, true)
|
||||
if (target instanceof LGraphNode) {
|
||||
await graphMutationService.updateNodeTitle(target.id, trimmedTitle)
|
||||
|
||||
// If this is a subgraph node, sync the runtime subgraph name for breadcrumb reactivity
|
||||
if (target.isSubgraphNode?.()) {
|
||||
target.subgraph.name = trimmedTitle
|
||||
}
|
||||
} else if (target instanceof LGraphGroup) {
|
||||
await graphMutationService.updateGroupTitle(target.id, trimmedTitle)
|
||||
}
|
||||
}
|
||||
showInput.value = false
|
||||
titleEditorStore.titleEditorTarget = null
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
|
||||
import { useModelSelectorDialog } from '@/composables/useModelSelectorDialog'
|
||||
import {
|
||||
DEFAULT_DARK_COLOR_PALETTE,
|
||||
DEFAULT_LIGHT_COLOR_PALETTE
|
||||
@@ -878,6 +879,17 @@ export function useCoreCommands(): ComfyCommand[] {
|
||||
navigationStore.navigationStack.at(-2) ?? canvas.graph.rootGraph
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Dev.ShowModelSelector',
|
||||
icon: 'pi pi-box',
|
||||
label: 'Show Model Selector (Dev)',
|
||||
versionAdded: '1.26.2',
|
||||
category: 'view-controls' as const,
|
||||
function: () => {
|
||||
const modelSelectorDialog = useModelSelectorDialog()
|
||||
modelSelectorDialog.show()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
29
src/composables/useModelSelectorDialog.ts
Normal file
29
src/composables/useModelSelectorDialog.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import ModelSelector from '@/components/custom/widget/ModelSelector.vue'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
const DIALOG_KEY = 'global-model-selector'
|
||||
|
||||
export const useModelSelectorDialog = () => {
|
||||
const dialogService = useDialogService()
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
function hide() {
|
||||
dialogStore.closeDialog({ key: DIALOG_KEY })
|
||||
}
|
||||
|
||||
function show() {
|
||||
dialogService.showLayoutDialog({
|
||||
key: DIALOG_KEY,
|
||||
component: ModelSelector,
|
||||
props: {
|
||||
onClose: hide
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
show,
|
||||
hide
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
} from './measure'
|
||||
import type { ISerialisedGroup } from './types/serialisation'
|
||||
|
||||
export type GroupId = number
|
||||
|
||||
export interface IGraphGroupFlags extends Record<string, unknown> {
|
||||
pinned?: true
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
} from './ExecutableNodeDTO'
|
||||
import type { SubgraphInput } from './SubgraphInput'
|
||||
|
||||
export type SubgraphId = string
|
||||
|
||||
/**
|
||||
* An instance of a {@link Subgraph}, displayed as a node on the containing (parent) graph.
|
||||
*/
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "الاتصال بالدعم"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "إظهار منتقي النماذج (للمطورين)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "تكرار سير العمل الحالي"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "حفظ",
|
||||
"Save As": "حفظ باسم",
|
||||
"Show Keybindings Dialog": "عرض مربع حوار اختصارات لوحة المفاتيح",
|
||||
"Show Model Selector (Dev)": "إظهار منتقي النماذج (للمطورين)",
|
||||
"Show Settings Dialog": "عرض نافذة الإعدادات",
|
||||
"Sign Out": "تسجيل خروج",
|
||||
"Toggle Essential Bottom Panel": "تبديل اللوحة السفلية الأساسية",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "Contact Support"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "Show Model Selector (Dev)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "Duplicate Current Workflow"
|
||||
},
|
||||
|
||||
@@ -980,6 +980,7 @@
|
||||
"Clear Pending Tasks": "Clear Pending Tasks",
|
||||
"Clear Workflow": "Clear Workflow",
|
||||
"Contact Support": "Contact Support",
|
||||
"Show Model Selector (Dev)": "Show Model Selector (Dev)",
|
||||
"Duplicate Current Workflow": "Duplicate Current Workflow",
|
||||
"Export": "Export",
|
||||
"Export (API)": "Export (API)",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "Contactar soporte"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "Mostrar selector de modelo (Dev)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "Duplicar flujo de trabajo actual"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "Guardar",
|
||||
"Save As": "Guardar como",
|
||||
"Show Keybindings Dialog": "Mostrar diálogo de combinaciones de teclas",
|
||||
"Show Model Selector (Dev)": "Mostrar selector de modelo (Desarrollo)",
|
||||
"Show Settings Dialog": "Mostrar diálogo de configuración",
|
||||
"Sign Out": "Cerrar sesión",
|
||||
"Toggle Essential Bottom Panel": "Alternar panel inferior esencial",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "Contacter le support"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "Afficher le sélecteur de modèle (Dev)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "Dupliquer le flux de travail actuel"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "Enregistrer",
|
||||
"Save As": "Enregistrer sous",
|
||||
"Show Keybindings Dialog": "Afficher la boîte de dialogue des raccourcis clavier",
|
||||
"Show Model Selector (Dev)": "Afficher le sélecteur de modèle (Dev)",
|
||||
"Show Settings Dialog": "Afficher la boîte de dialogue des paramètres",
|
||||
"Sign Out": "Se déconnecter",
|
||||
"Toggle Essential Bottom Panel": "Afficher/Masquer le panneau inférieur essentiel",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "サポートに連絡"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "モデルセレクターを表示(開発用)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "現在のワークフローを複製"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "保存",
|
||||
"Save As": "名前を付けて保存",
|
||||
"Show Keybindings Dialog": "キーバインドダイアログを表示",
|
||||
"Show Model Selector (Dev)": "モデルセレクターを表示(開発用)",
|
||||
"Show Settings Dialog": "設定ダイアログを表示",
|
||||
"Sign Out": "サインアウト",
|
||||
"Toggle Essential Bottom Panel": "エッセンシャル下部パネルの切り替え",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "지원팀에 문의하기"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "모델 선택기 표시 (개발자용)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "현재 워크플로 복제"
|
||||
},
|
||||
|
||||
@@ -839,7 +839,8 @@
|
||||
"Restart": "재시작",
|
||||
"Save": "저장",
|
||||
"Save As": "다른 이름으로 저장",
|
||||
"Show Keybindings Dialog": "키 바인딩 대화상자 표시",
|
||||
"Show Keybindings Dialog": "단축키 대화상자 표시",
|
||||
"Show Model Selector (Dev)": "모델 선택기 표시 (개발자용)",
|
||||
"Show Settings Dialog": "설정 대화상자 표시",
|
||||
"Sign Out": "로그아웃",
|
||||
"Toggle Essential Bottom Panel": "필수 하단 패널 전환",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "Связаться с поддержкой"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "Показать выбор модели (Dev)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "Дублировать текущий рабочий процесс"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "Сохранить",
|
||||
"Save As": "Сохранить как",
|
||||
"Show Keybindings Dialog": "Показать диалог клавиш быстрого доступа",
|
||||
"Show Model Selector (Dev)": "Показать выбор модели (Dev)",
|
||||
"Show Settings Dialog": "Показать диалог настроек",
|
||||
"Sign Out": "Выйти",
|
||||
"Toggle Essential Bottom Panel": "Показать/скрыть основную нижнюю панель",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "聯絡支援"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "顯示模型選擇器(開發)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "複製目前工作流程"
|
||||
},
|
||||
|
||||
@@ -840,6 +840,7 @@
|
||||
"Save": "儲存",
|
||||
"Save As": "另存新檔",
|
||||
"Show Keybindings Dialog": "顯示快捷鍵對話框",
|
||||
"Show Model Selector (Dev)": "顯示模型選擇器(開發用)",
|
||||
"Show Settings Dialog": "顯示設定對話框",
|
||||
"Sign Out": "登出",
|
||||
"Toggle Essential Bottom Panel": "切換基本下方面板",
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
"Comfy_ContactSupport": {
|
||||
"label": "联系支持"
|
||||
},
|
||||
"Comfy_Dev_ShowModelSelector": {
|
||||
"label": "顯示模型選擇器(開發)"
|
||||
},
|
||||
"Comfy_DuplicateWorkflow": {
|
||||
"label": "复制当前工作流"
|
||||
},
|
||||
|
||||
@@ -839,7 +839,8 @@
|
||||
"Restart": "重启",
|
||||
"Save": "保存",
|
||||
"Save As": "另存为",
|
||||
"Show Keybindings Dialog": "显示快捷键对话框",
|
||||
"Show Keybindings Dialog": "顯示快捷鍵對話框",
|
||||
"Show Model Selector (Dev)": "顯示模型選擇器(開發用)",
|
||||
"Show Settings Dialog": "显示设置对话框",
|
||||
"Sign Out": "退出登录",
|
||||
"Toggle Essential Bottom Panel": "切换基础底部面板",
|
||||
|
||||
462
src/services/GRAPH_MUTATION_SERVICE_DESIGN.md
Normal file
462
src/services/GRAPH_MUTATION_SERVICE_DESIGN.md
Normal file
@@ -0,0 +1,462 @@
|
||||
# GraphMutationService Design and Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
GraphMutationService is the centralized service layer for all graph modification operations in ComfyUI Frontend. It provides a unified and validated API for graph mutations, serving as the single entry point for all graph modification operations.
|
||||
|
||||
## Project Background
|
||||
|
||||
### Current System Analysis
|
||||
|
||||
ComfyUI Frontend uses the LiteGraph library for graph operations, with main components including:
|
||||
|
||||
1. **LGraph** (`src/lib/litegraph/src/LGraph.ts`)
|
||||
- Core graph management class
|
||||
- Provides basic operations like `add()`, `remove()`
|
||||
- Supports `beforeChange()`/`afterChange()` transaction mechanism
|
||||
|
||||
2. **LGraphNode** (`src/lib/litegraph/src/LGraphNode.ts`)
|
||||
- Node class containing position, connections, and other properties
|
||||
- Provides methods like `connect()`, `disconnectInput()`, `disconnectOutput()`
|
||||
|
||||
3. **ChangeTracker** (`src/scripts/changeTracker.ts`)
|
||||
- Existing undo/redo system
|
||||
- Snapshot-based history tracking
|
||||
- Supports up to 50 history states
|
||||
|
||||
**Primary Goals:**
|
||||
- Single entry point for all graph modifications
|
||||
- Built-in validation and error handling
|
||||
- Transaction support for atomic operations
|
||||
- Natural undo/redo through existing ChangeTracker
|
||||
- Clean architecture for future extensibility
|
||||
|
||||
### Interface-Based Architecture
|
||||
|
||||
The GraphMutationService follows an **interface-based design pattern** with singleton state management:
|
||||
|
||||
- **IGraphMutationService Interface**: Defines the complete contract for all graph operations
|
||||
- **GraphMutationService Class**: Implements the interface with LiteGraph integration
|
||||
- **Singleton State**: Shared clipboard and transaction state across components
|
||||
|
||||
```typescript
|
||||
interface IGraphMutationService {
|
||||
// Node operations
|
||||
addNode(params: AddNodeParams): Promise<NodeId>
|
||||
removeNode(nodeId: NodeId): Promise<void>
|
||||
updateNodeProperty(nodeId: NodeId, property: string, value: any): Promise<void>
|
||||
// ... 50+ total operations
|
||||
|
||||
// Transaction support
|
||||
transaction<T>(fn: () => Promise<T>): Promise<T>
|
||||
|
||||
// Undo/Redo
|
||||
undo(): Promise<void>
|
||||
redo(): Promise<void>
|
||||
}
|
||||
|
||||
class GraphMutationService implements IGraphMutationService {
|
||||
// Implementation details...
|
||||
}
|
||||
```
|
||||
|
||||
The `useGraphMutationService()` hook returns the interface type, maintaining backward compatibility while enabling new architectural benefits.
|
||||
|
||||
### Core Components
|
||||
|
||||
```typescript
|
||||
// Interface Definition
|
||||
interface IGraphMutationService {
|
||||
// Complete method signatures for all 50+ operations
|
||||
// Organized by functional categories
|
||||
}
|
||||
|
||||
// Implementation Class
|
||||
class GraphMutationService implements IGraphMutationService {
|
||||
private workflowStore = useWorkflowStore()
|
||||
private transactionDepth = 0
|
||||
private clipboard: ClipboardData | null = null
|
||||
|
||||
// All interface methods implemented
|
||||
}
|
||||
|
||||
// Singleton Hook
|
||||
export const useGraphMutationService = (): IGraphMutationService => {
|
||||
if (!graphMutationServiceInstance) {
|
||||
graphMutationServiceInstance = new GraphMutationService()
|
||||
}
|
||||
return graphMutationServiceInstance
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Validation Framework
|
||||
|
||||
Each operation includes validation to ensure data integrity:
|
||||
|
||||
```typescript
|
||||
interface ValidationResult {
|
||||
isValid: boolean
|
||||
errors: ValidationError[]
|
||||
warnings: ValidationWarning[]
|
||||
}
|
||||
```
|
||||
|
||||
## Implemented Operations
|
||||
|
||||
### Node Operations (6 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `addNode` | Add a new node to the graph | src/scripts/app.ts:1589-1593, src/lib/litegraph/src/LGraph.ts:823-893 |
|
||||
| `removeNode` | Remove a node from the graph | src/lib/litegraph/src/LGraph.ts:899-986 |
|
||||
| `updateNodeProperty` | Update a custom node property | src/lib/litegraph/src/LGraphNode.ts:974-984 |
|
||||
| `updateNodeTitle` | Change the node's title | src/services/litegraphService.ts:369 (direct assignment) |
|
||||
| `changeNodeMode` | Change execution mode (ALWAYS/ON_TRIGGER/NEVER/ON_REQUEST/ON_EVENT) | src/lib/litegraph/src/LGraphNode.ts:1295-1320 |
|
||||
| `cloneNode` | Create a copy of a node | src/lib/litegraph/src/LGraphNode.ts:923-950 |
|
||||
|
||||
### Connection Operations (6 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `connect` | Create a connection between nodes | src/lib/litegraph/src/LGraphNode.ts:2641-2743, :2753-2870 (connectSlots) |
|
||||
| `disconnect` | Generic disconnect (auto-detects input/output) | Wrapper combining disconnectInput/disconnectOutput |
|
||||
| `disconnectInput` | Disconnect a specific input slot | src/lib/litegraph/src/LGraphNode.ts:3050-3144 |
|
||||
| `disconnectOutput` | Disconnect all connections from an output slot | src/lib/litegraph/src/LGraphNode.ts:2931-3043 |
|
||||
| `disconnectOutputTo` | Disconnect output to a specific target node | src/lib/litegraph/src/LGraphNode.ts:2931 (with target_node param) |
|
||||
| `disconnectLink` | Disconnect by link ID | src/lib/litegraph/src/LGraph.ts:1433-1441 |
|
||||
|
||||
### Group Operations (6 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `createGroup` | Create a new node group | src/composables/useCoreCommands.ts:425-430, src/lib/litegraph/src/LGraph.ts:823-848 (add method for groups) |
|
||||
| `removeGroup` | Delete a group (nodes remain) | src/lib/litegraph/src/LGraph.ts:899-913 (remove method for groups) |
|
||||
| `updateGroupTitle` | Change group title | Direct assignment (group.title = value) |
|
||||
| `moveGroup` | Move group and its contents | src/lib/litegraph/src/LGraphGroup.ts:230-240 |
|
||||
| `addNodesToGroup` | Add nodes to group and auto-resize | src/lib/litegraph/src/LGraphGroup.ts:303-306 |
|
||||
| `recomputeGroupNodes` | Recalculate which nodes are in group | src/lib/litegraph/src/LGraphGroup.ts:247-273 |
|
||||
|
||||
### Subgraph Node Slot Operations (4 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `addSubgraphNodeInput` | Add an input slot to a subgraph node | src/lib/litegraph/src/LGraphNode.ts:1606-1627 |
|
||||
| `addSubgraphNodeOutput` | Add an output slot to a subgraph node | src/lib/litegraph/src/LGraphNode.ts:1551-1571 |
|
||||
| `removeSubgraphNodeInput` | Remove an input slot from a subgraph node | src/lib/litegraph/src/LGraphNode.ts:1632-1652 |
|
||||
| `removeSubgraphNodeOutput` | Remove an output slot from a subgraph node | src/lib/litegraph/src/LGraphNode.ts:1576-1599 |
|
||||
|
||||
### Batch Operations (3 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `addNodes` | Add multiple nodes in one operation | Custom implementation based on single addNode logic |
|
||||
| `removeNodes` | Remove multiple nodes in one operation | src/composables/useCoreCommands.ts:180 (forEach pattern) |
|
||||
| `duplicateNodes` | Duplicate selected nodes with their connections | src/utils/vintageClipboard.ts:32 (node.clone pattern) |
|
||||
|
||||
### Clipboard Operations (6 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `copyNodes` | Copy nodes to clipboard | src/lib/litegraph/src/LGraphCanvas.ts:3602-3687 |
|
||||
| `cutNodes` | Cut nodes to clipboard | Custom implementation (copy + mark for deletion) |
|
||||
| `pasteNodes` | Paste nodes from clipboard | src/lib/litegraph/src/LGraphCanvas.ts:3693-3871 |
|
||||
| `getClipboard` | Get current clipboard content | Custom implementation (returns internal clipboard) |
|
||||
| `clearClipboard` | Clear clipboard content | Custom implementation (sets clipboard to null) |
|
||||
| `hasClipboardContent` | Check if clipboard has content | Custom implementation (checks clipboard state) |
|
||||
|
||||
### Reroute Operations (2 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `addReroute` | Add a reroute point on a connection | src/lib/litegraph/src/LGraph.ts:1338-1361 (createReroute) |
|
||||
| `removeReroute` | Remove a reroute point | src/lib/litegraph/src/LGraph.ts:1381-1407 |
|
||||
|
||||
### Subgraph Operations (6 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `createSubgraph` | Create a subgraph from selected nodes | src/lib/litegraph/src/LGraph.ts:1459-1566 (convertToSubgraph) |
|
||||
| `unpackSubgraph` | Unpack a subgraph node back into regular nodes | src/lib/litegraph/src/LGraph.ts:1672-1841 |
|
||||
| `addSubgraphInput` | Add an input to a subgraph | src/lib/litegraph/src/LGraph.ts:2440-2456 |
|
||||
| `addSubgraphOutput` | Add an output to a subgraph | src/lib/litegraph/src/LGraph.ts:2458-2474 |
|
||||
| `removeSubgraphInput` | Remove a subgraph input | src/lib/litegraph/src/LGraph.ts:2520-2535 |
|
||||
| `removeSubgraphOutput` | Remove a subgraph output | src/lib/litegraph/src/LGraph.ts:2541-2559 |
|
||||
|
||||
### Graph-level Operations (1 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `clearGraph` | Clear all nodes and connections | src/lib/litegraph/src/LGraph.ts:293-362 |
|
||||
|
||||
### Execution Control Operations (2 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `bypassNode` | Set node to bypass mode (never execute) | Direct mode assignment (node.mode = LGraphEventMode.BYPASS) |
|
||||
| `unbypassNode` | Set node to normal mode (always execute) | Direct mode assignment (node.mode = LGraphEventMode.ALWAYS) |
|
||||
|
||||
### Transaction and History Operations (3 operations)
|
||||
|
||||
| Operation | Description | Original Implementation Reference |
|
||||
|-----------|-------------|-----------------------------------|
|
||||
| `transaction` | Execute multiple operations atomically | Custom implementation using beforeChange/afterChange |
|
||||
| `undo` | Undo the last operation | src/scripts/changeTracker.ts (uses changeTracker.undo) |
|
||||
| `redo` | Redo the previously undone operation | src/scripts/changeTracker.ts (uses changeTracker.redo) |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Node Operations
|
||||
|
||||
```typescript
|
||||
import { useGraphMutationService } from '@/services/graphMutationService'
|
||||
|
||||
const service = useGraphMutationService()
|
||||
|
||||
// Add a node
|
||||
const nodeId = await service.addNode({
|
||||
type: 'LoadImage',
|
||||
pos: [100, 100],
|
||||
title: 'Image Loader'
|
||||
})
|
||||
|
||||
// Update node properties
|
||||
await service.updateNodeTitle(nodeId, 'My Image')
|
||||
await service.updateNodeProperty(nodeId, 'seed', 12345)
|
||||
|
||||
// Clone a node
|
||||
const clonedId = await service.cloneNode(nodeId, [300, 200])
|
||||
```
|
||||
|
||||
### Connection Management
|
||||
|
||||
```typescript
|
||||
// Create a connection
|
||||
const linkId = await service.connect({
|
||||
sourceNodeId: node1Id,
|
||||
sourceSlot: 0,
|
||||
targetNodeId: node2Id,
|
||||
targetSlot: 0
|
||||
})
|
||||
|
||||
// Various disconnect methods
|
||||
await service.disconnectInput(node2Id, 0)
|
||||
await service.disconnectOutput(node1Id, 0)
|
||||
await service.disconnectLink(linkId)
|
||||
```
|
||||
|
||||
### Group Management
|
||||
|
||||
```typescript
|
||||
// Create a group
|
||||
const groupId = await service.createGroup({
|
||||
title: 'Image Processing',
|
||||
pos: [100, 100],
|
||||
size: [400, 300],
|
||||
color: '#335577'
|
||||
})
|
||||
|
||||
// Manage group content
|
||||
await service.addNodesToGroup(groupId, [node1Id, node2Id])
|
||||
await service.moveGroup(groupId, 50, 100) // deltaX, deltaY
|
||||
await service.resizeGroup(groupId, [500, 400])
|
||||
```
|
||||
|
||||
|
||||
### Batch Operations
|
||||
|
||||
```typescript
|
||||
// Add multiple nodes
|
||||
const nodeIds = await service.addNodes([
|
||||
{ type: 'LoadImage', pos: [100, 100] },
|
||||
{ type: 'VAEEncode', pos: [300, 100] },
|
||||
{ type: 'KSampler', pos: [500, 100] }
|
||||
])
|
||||
|
||||
// Duplicate with connections preserved
|
||||
const duplicatedIds = await service.duplicateNodes(
|
||||
[node1Id, node2Id, node3Id],
|
||||
[100, 100] // offset
|
||||
)
|
||||
|
||||
// Batch delete
|
||||
await service.removeNodes([node1Id, node2Id, node3Id])
|
||||
```
|
||||
|
||||
### Clipboard Operations
|
||||
|
||||
```typescript
|
||||
// Copy/Cut/Paste workflow
|
||||
await service.copyNodes([node1Id, node2Id])
|
||||
await service.cutNodes([node3Id, node4Id])
|
||||
|
||||
const pastedNodes = await service.pasteNodes([200, 200])
|
||||
|
||||
// Check clipboard
|
||||
if (service.hasClipboardContent()) {
|
||||
const clipboard = service.getClipboard()
|
||||
console.log(`${clipboard.nodes.length} nodes in clipboard`)
|
||||
}
|
||||
```
|
||||
|
||||
### Transactions
|
||||
|
||||
```typescript
|
||||
// Atomic operations
|
||||
await service.transaction(async () => {
|
||||
const node1 = await service.addNode({ type: 'LoadImage' })
|
||||
const node2 = await service.addNode({ type: 'SaveImage' })
|
||||
await service.connect({
|
||||
sourceNodeId: node1,
|
||||
sourceSlot: 0,
|
||||
targetNodeId: node2,
|
||||
targetSlot: 0
|
||||
})
|
||||
})
|
||||
|
||||
// Entire transaction can be undone as one operation
|
||||
await service.undo()
|
||||
```
|
||||
|
||||
### Graph-level Operations
|
||||
|
||||
```typescript
|
||||
// Clear entire graph
|
||||
await service.clearGraph()
|
||||
|
||||
// Distribute nodes evenly
|
||||
await service.distributeNodes([node1Id, node2Id, node3Id], 'horizontal')
|
||||
```
|
||||
|
||||
### Execution Control
|
||||
|
||||
```typescript
|
||||
// Bypass node (set to never execute)
|
||||
await service.bypassNode(nodeId)
|
||||
|
||||
// Re-enable node execution
|
||||
await service.unbypassNode(nodeId)
|
||||
```
|
||||
|
||||
### Subgraph Operations
|
||||
|
||||
```typescript
|
||||
// Create subgraph from selected nodes
|
||||
const subgraphId = await service.createSubgraph({
|
||||
name: 'Image Processing',
|
||||
nodeIds: [node1Id, node2Id, node3Id]
|
||||
})
|
||||
|
||||
// Configure subgraph I/O
|
||||
await service.addSubgraphInput(subgraphId, 'image', 'IMAGE')
|
||||
await service.addSubgraphOutput(subgraphId, 'result', 'IMAGE')
|
||||
|
||||
// Add dynamic slots to subgraph nodes
|
||||
await service.addSubgraphNodeInput({
|
||||
nodeId: subgraphNodeId,
|
||||
name: 'extra_input',
|
||||
type: 'LATENT'
|
||||
})
|
||||
```
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Integration Points
|
||||
|
||||
1. **LiteGraph Integration**
|
||||
- Uses `app.graph` for graph access
|
||||
- Calls `beforeChange()`/`afterChange()` for transactions
|
||||
- Integrates with existing LiteGraph node/connection APIs
|
||||
|
||||
2. **ChangeTracker Integration**
|
||||
- Maintains compatibility with existing undo/redo system
|
||||
- Calls `checkState()` after operations
|
||||
- Provides undo/redo through existing tracker
|
||||
|
||||
## Validation System
|
||||
|
||||
### Current Validations (Placeholder)
|
||||
|
||||
- `validateAddNode()` - Check node type exists
|
||||
- `validateRemoveNode()` - Check node can be removed
|
||||
- `validateConnect()` - Check connection compatibility
|
||||
- `validateUpdateNodePosition()` - Check position bounds
|
||||
|
||||
### Future Validations
|
||||
|
||||
- Type compatibility checking
|
||||
- Circular dependency detection
|
||||
- Resource limit enforcement
|
||||
- Permission validation
|
||||
- Business rule enforcement
|
||||
|
||||
## Technical Decisions
|
||||
|
||||
### Why Validation Layer?
|
||||
- **Data Integrity**: Prevent invalid graph states
|
||||
- **User Experience**: Early error detection
|
||||
- **Security**: Prevent malicious operations
|
||||
- **Extensibility**: Easy to add new rules
|
||||
|
||||
### Why Transaction Support?
|
||||
- **Atomicity**: Multiple operations succeed or fail together
|
||||
- **Consistency**: Graph remains valid throughout
|
||||
- **User Experience**: Natural undo/redo boundaries
|
||||
|
||||
## Related Files
|
||||
|
||||
- **Interface Definition**: `src/services/IGraphMutationService.ts`
|
||||
- **Implementation**: `src/services/GraphMutationService.ts`
|
||||
- **LiteGraph Core**: `src/lib/litegraph/src/LGraph.ts`
|
||||
- **Node Implementation**: `src/lib/litegraph/src/LGraphNode.ts`
|
||||
- **Change Tracking**: `src/scripts/changeTracker.ts`
|
||||
|
||||
## Implementation Compatibility Notes
|
||||
|
||||
### Critical Implementation Details to Maintain:
|
||||
|
||||
1. **beforeChange/afterChange Pattern**
|
||||
- All mutations MUST be wrapped with `graph.beforeChange()` and `graph.afterChange()`
|
||||
- This enables undo/redo functionality through ChangeTracker
|
||||
- Reference: `src/scripts/changeTracker.ts:200-208`
|
||||
|
||||
2. **Node ID Management**
|
||||
- Node IDs can be numbers or strings (for API compatibility)
|
||||
- Reference: `src/scripts/app.ts:1591` - `node.id = isNaN(+id) ? id : +id`
|
||||
|
||||
3. **Clipboard Implementation**
|
||||
- Current implementation uses localStorage for persistence
|
||||
- Must maintain compatibility with existing clipboard format
|
||||
- Reference: `src/lib/litegraph/src/LGraphCanvas.ts:3602-3857`
|
||||
|
||||
4. **Group Resizing**
|
||||
- Groups should auto-resize when adding nodes using `recomputeInsideNodes()`
|
||||
- Reference: `src/composables/useCoreCommands.ts:430` - `group.resizeTo()`
|
||||
|
||||
5. **Canvas Dirty Flag**
|
||||
- Visual operations (groups, reroutes) must call `graph.setDirtyCanvas(true, false)`
|
||||
- This triggers canvas redraw
|
||||
|
||||
6. **Error Handling**
|
||||
- Node creation can return null (for invalid types)
|
||||
- Connection operations return null/false on failure
|
||||
- Must validate before operations
|
||||
|
||||
7. **Subgraph Support**
|
||||
- Subgraph operations use specialized Subgraph and SubgraphNode classes
|
||||
- Reference: `src/lib/litegraph/src/subgraph/`
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. Start by replacing direct `app.graph.add()` calls with `graphMutationService.addNode()`
|
||||
2. Replace `graph.remove()` calls with `graphMutationService.removeNode()`
|
||||
3. Update connection operations to use service methods
|
||||
4. Migrate clipboard operations to use centralized service
|
||||
5. Ensure all operations maintain existing beforeChange/afterChange patterns
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Always use GraphMutationService** - Never call graph methods directly
|
||||
2. **Backward Compatibility** - Service maintains compatibility with existing code
|
||||
3. **Gradual Migration** - Existing code can be migrated incrementally
|
||||
4. **Performance** - Command recording has minimal overhead
|
||||
161
src/services/IGraphMutationService.ts
Normal file
161
src/services/IGraphMutationService.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { GroupId } from '@/lib/litegraph/src/LGraphGroup'
|
||||
import { LinkId } from '@/lib/litegraph/src/LLink'
|
||||
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
|
||||
import { SubgraphId } from '@/lib/litegraph/src/subgraph/SubgraphNode'
|
||||
import { NodeId } from '@/schemas/comfyWorkflowSchema'
|
||||
|
||||
export interface AddNodeParams {
|
||||
type: string
|
||||
pos?: [number, number]
|
||||
properties?: Record<string, any>
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface ConnectParams {
|
||||
sourceNodeId: NodeId
|
||||
sourceSlot: number | string
|
||||
targetNodeId: NodeId
|
||||
targetSlot: number | string
|
||||
}
|
||||
|
||||
export interface CreateGroupParams {
|
||||
title?: string
|
||||
pos?: [number, number]
|
||||
size?: [number, number]
|
||||
color?: string
|
||||
fontSize?: number
|
||||
}
|
||||
|
||||
export interface AddRerouteParams {
|
||||
pos: [number, number]
|
||||
linkId?: LinkId
|
||||
parentRerouteId?: RerouteId
|
||||
}
|
||||
|
||||
export interface AddNodeInputParams {
|
||||
nodeId: NodeId
|
||||
name: string
|
||||
type: string
|
||||
extra_info?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface AddNodeOutputParams {
|
||||
nodeId: NodeId
|
||||
name: string
|
||||
type: string
|
||||
extra_info?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface CreateSubgraphParams {
|
||||
selectedItems: Set<any>
|
||||
}
|
||||
|
||||
export interface ClipboardData {
|
||||
nodes: any[]
|
||||
connections: any[]
|
||||
isCut: boolean
|
||||
}
|
||||
|
||||
export interface ValidationResult {
|
||||
valid: boolean
|
||||
errors?: ValidationError[]
|
||||
warnings?: ValidationWarning[]
|
||||
}
|
||||
|
||||
export interface ValidationError {
|
||||
code: string
|
||||
message: string
|
||||
field?: string
|
||||
}
|
||||
|
||||
export interface ValidationWarning {
|
||||
code: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface IGraphMutationService {
|
||||
// Node operations
|
||||
addNode(params: AddNodeParams): Promise<NodeId>
|
||||
removeNode(nodeId: NodeId): Promise<void>
|
||||
updateNodeProperty(
|
||||
nodeId: NodeId,
|
||||
property: string,
|
||||
value: any
|
||||
): Promise<void>
|
||||
updateNodeTitle(nodeId: NodeId, title: string): Promise<void>
|
||||
changeNodeMode(nodeId: NodeId, mode: number): Promise<void>
|
||||
cloneNode(nodeId: NodeId, pos?: [number, number]): Promise<NodeId>
|
||||
|
||||
connect(params: ConnectParams): Promise<LinkId>
|
||||
disconnect(
|
||||
nodeId: NodeId,
|
||||
slot: number | string,
|
||||
slotType: 'input' | 'output',
|
||||
targetNodeId?: NodeId
|
||||
): Promise<boolean>
|
||||
disconnectInput(nodeId: NodeId, slot: number | string): Promise<boolean>
|
||||
disconnectOutput(nodeId: NodeId, slot: number | string): Promise<boolean>
|
||||
disconnectOutputTo(
|
||||
nodeId: NodeId,
|
||||
slot: number | string,
|
||||
targetNodeId: NodeId
|
||||
): Promise<boolean>
|
||||
disconnectLink(linkId: LinkId): Promise<void>
|
||||
|
||||
createGroup(params: CreateGroupParams): Promise<GroupId>
|
||||
removeGroup(groupId: GroupId): Promise<void>
|
||||
updateGroupTitle(groupId: GroupId, title: string): Promise<void>
|
||||
moveGroup(groupId: GroupId, deltaX: number, deltaY: number): Promise<void>
|
||||
addNodesToGroup(groupId: GroupId, nodeIds: NodeId[]): Promise<void>
|
||||
recomputeGroupNodes(groupId: GroupId): Promise<void>
|
||||
|
||||
addReroute(params: AddRerouteParams): Promise<RerouteId>
|
||||
removeReroute(rerouteId: RerouteId): Promise<void>
|
||||
|
||||
addNodes(nodes: AddNodeParams[]): Promise<NodeId[]>
|
||||
removeNodes(nodeIds: NodeId[]): Promise<void>
|
||||
duplicateNodes(
|
||||
nodeIds: NodeId[],
|
||||
offset?: [number, number]
|
||||
): Promise<NodeId[]>
|
||||
|
||||
copyNodes(nodeIds: NodeId[]): Promise<void>
|
||||
cutNodes(nodeIds: NodeId[]): Promise<void>
|
||||
pasteNodes(position?: [number, number]): Promise<NodeId[]>
|
||||
getClipboard(): ClipboardData | null
|
||||
clearClipboard(): void
|
||||
hasClipboardContent(): boolean
|
||||
|
||||
addSubgraphNodeInput(params: AddNodeInputParams): Promise<number>
|
||||
addSubgraphNodeOutput(params: AddNodeOutputParams): Promise<number>
|
||||
removeSubgraphNodeInput(nodeId: NodeId, slot: number): Promise<void>
|
||||
removeSubgraphNodeOutput(nodeId: NodeId, slot: number): Promise<void>
|
||||
|
||||
createSubgraph(params: CreateSubgraphParams): Promise<{
|
||||
subgraph: any
|
||||
node: any
|
||||
}>
|
||||
unpackSubgraph(subgraphNodeId: NodeId): Promise<void>
|
||||
addSubgraphInput(
|
||||
subgraphId: SubgraphId,
|
||||
name: string,
|
||||
type: string
|
||||
): Promise<void>
|
||||
addSubgraphOutput(
|
||||
subgraphId: SubgraphId,
|
||||
name: string,
|
||||
type: string
|
||||
): Promise<void>
|
||||
removeSubgraphInput(subgraphId: SubgraphId, index: number): Promise<void>
|
||||
removeSubgraphOutput(subgraphId: SubgraphId, index: number): Promise<void>
|
||||
|
||||
clearGraph(): Promise<void>
|
||||
|
||||
bypassNode(nodeId: NodeId): Promise<void>
|
||||
unbypassNode(nodeId: NodeId): Promise<void>
|
||||
|
||||
transaction<T>(fn: () => Promise<T>): Promise<T>
|
||||
|
||||
undo(): Promise<void>
|
||||
redo(): Promise<void>
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
import { merge } from 'es-toolkit/compat'
|
||||
import { Component } from 'vue'
|
||||
|
||||
import ApiNodesSignInContent from '@/components/dialog/content/ApiNodesSignInContent.vue'
|
||||
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
|
||||
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
|
||||
@@ -20,7 +23,11 @@ import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsCo
|
||||
import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkflowsDialogHeader.vue'
|
||||
import { t } from '@/i18n'
|
||||
import type { ExecutionErrorWsMessage } from '@/schemas/apiSchema'
|
||||
import { type ShowDialogOptions, useDialogStore } from '@/stores/dialogStore'
|
||||
import {
|
||||
type DialogComponentProps,
|
||||
type ShowDialogOptions,
|
||||
useDialogStore
|
||||
} from '@/stores/dialogStore'
|
||||
|
||||
export type ConfirmationDialogType =
|
||||
| 'default'
|
||||
@@ -424,6 +431,33 @@ export const useDialogService = () => {
|
||||
}
|
||||
}
|
||||
|
||||
function showLayoutDialog(options: {
|
||||
key: string
|
||||
component: Component
|
||||
props: { onClose: () => void }
|
||||
dialogComponentProps?: DialogComponentProps
|
||||
}) {
|
||||
const layoutDefaultProps: DialogComponentProps = {
|
||||
headless: true,
|
||||
unstyled: true,
|
||||
modal: true,
|
||||
closable: false,
|
||||
pt: {
|
||||
mask: {
|
||||
class: 'bg-black bg-opacity-40'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialogStore.showDialog({
|
||||
...options,
|
||||
dialogComponentProps: merge(
|
||||
layoutDefaultProps,
|
||||
options.dialogComponentProps || {}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
showLoadWorkflowWarning,
|
||||
showMissingModelsWarning,
|
||||
@@ -443,6 +477,7 @@ export const useDialogService = () => {
|
||||
prompt,
|
||||
confirm,
|
||||
toggleManagerDialog,
|
||||
toggleManagerProgressDialog
|
||||
toggleManagerProgressDialog,
|
||||
showLayoutDialog
|
||||
}
|
||||
}
|
||||
|
||||
1152
src/services/graphMutationService.ts
Normal file
1152
src/services/graphMutationService.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,9 +28,11 @@ interface CustomDialogComponentProps {
|
||||
pt?: DialogPassThroughOptions
|
||||
closeOnEscape?: boolean
|
||||
dismissableMask?: boolean
|
||||
unstyled?: boolean
|
||||
headless?: boolean
|
||||
}
|
||||
|
||||
type DialogComponentProps = InstanceType<typeof GlobalDialog>['$props'] &
|
||||
export type DialogComponentProps = InstanceType<typeof GlobalDialog>['$props'] &
|
||||
CustomDialogComponentProps
|
||||
|
||||
interface DialogInstance {
|
||||
|
||||
2
src/types/custom_components/index.d.ts
vendored
Normal file
2
src/types/custom_components/index.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './navTypes'
|
||||
export * from './widgetTypes'
|
||||
9
src/types/custom_components/navTypes.ts
Normal file
9
src/types/custom_components/navTypes.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface NavItemData {
|
||||
id: string
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface NavGroupData {
|
||||
title: string
|
||||
items: NavItemData[]
|
||||
}
|
||||
3
src/types/custom_components/widgetTypes.ts
Normal file
3
src/types/custom_components/widgetTypes.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { InjectionKey } from 'vue'
|
||||
|
||||
export const OnCloseKey: InjectionKey<() => void> = Symbol()
|
||||
@@ -79,14 +79,14 @@ export default {
|
||||
colors: {
|
||||
zinc: {
|
||||
50: '#fafafa',
|
||||
100: '#f4f4f5',
|
||||
100: '#8282821a',
|
||||
200: '#e4e4e7',
|
||||
300: '#d4d4d8',
|
||||
400: '#a1a1aa',
|
||||
500: '#71717a',
|
||||
600: '#52525b',
|
||||
700: '#3f3f46',
|
||||
800: '#27272a',
|
||||
700: '#38393b',
|
||||
800: '#262729',
|
||||
900: '#18181b',
|
||||
950: '#09090b'
|
||||
},
|
||||
|
||||
1049
tests-ui/tests/services/graphMutationService.test.ts
Normal file
1049
tests-ui/tests/services/graphMutationService.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user