Submodule litegraph updated: db6a916757...cd4f68ef42
@@ -33,7 +33,7 @@
|
||||
"svelte-check": "^3.2.0",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
"typescript": "^5.0.3",
|
||||
"vite": "^4.3.1",
|
||||
"vite": "^4.3.8",
|
||||
"vite-plugin-glsl": "^1.1.2",
|
||||
"vite-plugin-static-copy": "^0.14.0",
|
||||
"vite-plugin-svelte-console-remover": "^1.0.10",
|
||||
@@ -65,6 +65,7 @@
|
||||
"@litegraph-ts/tsconfig": "workspace:*",
|
||||
"@sveltejs/vite-plugin-svelte": "^2.1.1",
|
||||
"@tsconfig/svelte": "^4.0.1",
|
||||
"@zerodevx/svelte-json-view": "^1.0.5",
|
||||
"events": "^3.3.0",
|
||||
"framework7": "^8.0.3",
|
||||
"framework7-svelte": "^8.0.3",
|
||||
@@ -80,6 +81,7 @@
|
||||
"tailwindcss": "^3.3.1",
|
||||
"typed-emitter": "github:andywer/typed-emitter",
|
||||
"uuid": "^9.0.0",
|
||||
"vite-plugin-full-reload": "^1.0.5"
|
||||
"vite-plugin-full-reload": "^1.0.5",
|
||||
"zod": "^3.21.4"
|
||||
}
|
||||
}
|
||||
|
||||
196
pnpm-lock.yaml
generated
196
pnpm-lock.yaml
generated
@@ -69,10 +69,13 @@ importers:
|
||||
version: link:litegraph/packages/tsconfig
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1(svelte@3.58.0)(vite@4.3.1)
|
||||
version: 2.1.1(svelte@3.58.0)(vite@4.3.8)
|
||||
'@tsconfig/svelte':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
'@zerodevx/svelte-json-view':
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(svelte@3.58.0)
|
||||
events:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
@@ -120,7 +123,10 @@ importers:
|
||||
version: 9.0.0
|
||||
vite-plugin-full-reload:
|
||||
specifier: ^1.0.5
|
||||
version: 1.0.5(vite@4.3.1)
|
||||
version: 1.0.5(vite@4.3.8)
|
||||
zod:
|
||||
specifier: ^3.21.4
|
||||
version: 3.21.4
|
||||
devDependencies:
|
||||
'@floating-ui/core':
|
||||
specifier: ^1.2.6
|
||||
@@ -168,20 +174,20 @@ importers:
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
vite:
|
||||
specifier: ^4.3.1
|
||||
version: 4.3.1(sass@1.61.0)
|
||||
specifier: ^4.3.8
|
||||
version: 4.3.8(sass@1.61.0)
|
||||
vite-plugin-glsl:
|
||||
specifier: ^1.1.2
|
||||
version: 1.1.2(vite@4.3.1)
|
||||
version: 1.1.2(vite@4.3.8)
|
||||
vite-plugin-static-copy:
|
||||
specifier: ^0.14.0
|
||||
version: 0.14.0(vite@4.3.1)
|
||||
version: 0.14.0(vite@4.3.8)
|
||||
vite-plugin-svelte-console-remover:
|
||||
specifier: ^1.0.10
|
||||
version: 1.0.10(sass@1.61.0)
|
||||
vite-tsconfig-paths:
|
||||
specifier: ^4.0.8
|
||||
version: 4.0.8(typescript@5.0.3)(vite@4.3.1)
|
||||
version: 4.0.8(typescript@5.0.3)(vite@4.3.8)
|
||||
vitest:
|
||||
specifier: ^0.27.3
|
||||
version: 0.27.3(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0)
|
||||
@@ -3038,7 +3044,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.1):
|
||||
/@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.8):
|
||||
resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==}
|
||||
engines: {node: ^14.18.0 || >= 16}
|
||||
peerDependencies:
|
||||
@@ -3051,8 +3057,8 @@ packages:
|
||||
magic-string: 0.30.0
|
||||
svelte: 3.58.0
|
||||
svelte-hmr: 0.15.1(svelte@3.58.0)
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vitefu: 0.2.4(vite@4.3.1)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
vitefu: 0.2.4(vite@4.3.8)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@@ -3060,7 +3066,7 @@ packages:
|
||||
/@swc/helpers@0.4.14:
|
||||
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/@tootallnate/once@2.0.0:
|
||||
@@ -3126,10 +3132,14 @@ packages:
|
||||
/@types/chai-subset@1.3.3:
|
||||
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
|
||||
dependencies:
|
||||
'@types/chai': 4.3.4
|
||||
'@types/chai': 4.3.5
|
||||
|
||||
/@types/chai@4.3.4:
|
||||
resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
|
||||
dev: false
|
||||
|
||||
/@types/chai@4.3.5:
|
||||
resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
|
||||
|
||||
/@types/concat-stream@1.6.1:
|
||||
resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==}
|
||||
@@ -3368,6 +3378,14 @@ packages:
|
||||
pretty-format: 27.5.1
|
||||
dev: false
|
||||
|
||||
/@zerodevx/svelte-json-view@1.0.5(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-oQDI9v0dJEte6PYVDVjLOjU58AOoWLYRXjghKggFpZXrglWJJqoMeDe14Jrd0cs6NPcPogT/aR/LtkuW2Z1GkQ==}
|
||||
peerDependencies:
|
||||
svelte: ^3.55.1
|
||||
dependencies:
|
||||
svelte: 3.58.0
|
||||
dev: false
|
||||
|
||||
/@zerodevx/svelte-toast@0.9.3(svelte@3.58.0):
|
||||
resolution: {integrity: sha512-VPKWR4A9y01fyXRscu9HiTj7tV2hFrpRKZvGwMmaPXfHIXR1D9+NNsz0HXcQ7qZ0C5UaHS3n9uNtPtIcAXT7RQ==}
|
||||
peerDependencies:
|
||||
@@ -3510,7 +3528,7 @@ packages:
|
||||
engines: {node: '>=16.1.0'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/autoprefixer@10.4.2(postcss@8.4.21):
|
||||
@@ -3682,7 +3700,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
fast-unique-numbers: 8.0.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
worker-factory: 7.0.0
|
||||
dev: false
|
||||
|
||||
@@ -3691,8 +3709,8 @@ packages:
|
||||
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001481
|
||||
electron-to-chromium: 1.4.371
|
||||
caniuse-lite: 1.0.30001488
|
||||
electron-to-chromium: 1.4.400
|
||||
node-releases: 2.0.10
|
||||
update-browserslist-db: 1.0.11(browserslist@4.21.5)
|
||||
|
||||
@@ -3753,6 +3771,10 @@ packages:
|
||||
|
||||
/caniuse-lite@1.0.30001481:
|
||||
resolution: {integrity: sha512-KCqHwRnaa1InZBtqXzP98LPg0ajCVujMKjqKDhZEthIpAsJl/YEIa3YvXjGXPVqzZVguccuu7ga9KOE1J9rKPQ==}
|
||||
dev: true
|
||||
|
||||
/caniuse-lite@1.0.30001488:
|
||||
resolution: {integrity: sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==}
|
||||
|
||||
/case@1.6.3:
|
||||
resolution: {integrity: sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ==}
|
||||
@@ -3953,7 +3975,7 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
dashify: 2.0.0
|
||||
indefinite-article: 0.0.2
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/compilerr@11.0.0:
|
||||
@@ -3963,7 +3985,7 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
dashify: 2.0.0
|
||||
indefinite-article: 0.0.2
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/concat-map@0.0.1:
|
||||
@@ -4449,8 +4471,8 @@ packages:
|
||||
sigmund: 1.0.1
|
||||
dev: false
|
||||
|
||||
/electron-to-chromium@1.4.371:
|
||||
resolution: {integrity: sha512-jlBzY4tFcJaiUjzhRTCWAqRvTO/fWzjA3Bls0mykzGZ7zvcMP7h05W6UcgzfT9Ca1SW2xyKDOFRyI0pQeRNZGw==}
|
||||
/electron-to-chromium@1.4.400:
|
||||
resolution: {integrity: sha512-Lsvf7cvwbIxCfB8VqbnVtEsjGi3+48ejDiQZfWo5gkT+1vQ2DHQI5pl0nUvPD6z1IQk6JgFeMC5ZQJqVhalEHg==}
|
||||
|
||||
/emittery@0.13.1:
|
||||
resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
|
||||
@@ -4950,14 +4972,14 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
broker-factory: 3.0.76
|
||||
extendable-media-recorder-wav-encoder-worker: 8.0.77
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/extendable-media-recorder-wav-encoder-worker@8.0.77:
|
||||
resolution: {integrity: sha512-9g9Q7fhOxPY7RALHVTK9Wjnc8RPYjJ9XCBP1TaNtDraIAFxvhBRax9QUOmFqHM2MvRM6hQhNav7jn23yy6tcVQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
worker-factory: 7.0.0
|
||||
dev: false
|
||||
|
||||
@@ -5020,7 +5042,7 @@ packages:
|
||||
engines: {node: '>=14.15.4'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/fast-unique-numbers@8.0.0:
|
||||
@@ -5028,7 +5050,7 @@ packages:
|
||||
engines: {node: '>=16.1.0'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/fastq@1.15.0:
|
||||
@@ -6624,7 +6646,7 @@ packages:
|
||||
broker-factory: 3.0.76
|
||||
fast-unique-numbers: 8.0.0
|
||||
media-encoder-host-worker: 9.1.1
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/media-encoder-host-worker@9.1.1:
|
||||
@@ -6632,7 +6654,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
extendable-media-recorder-wav-encoder-broker: 7.0.78
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
worker-factory: 7.0.0
|
||||
dev: false
|
||||
|
||||
@@ -6642,7 +6664,7 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
media-encoder-host-broker: 7.0.79
|
||||
media-encoder-host-worker: 9.1.1
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/merge-stream@2.0.0:
|
||||
@@ -6760,7 +6782,7 @@ packages:
|
||||
engines: {node: '>=12.20.1'}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/murmurhash-js@1.0.0:
|
||||
@@ -7176,6 +7198,14 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/postcss@8.4.23:
|
||||
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
nanoid: 3.3.6
|
||||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/posthtml-parser@0.10.2:
|
||||
resolution: {integrity: sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -7360,7 +7390,7 @@ packages:
|
||||
resolution: {integrity: sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/recorder-audio-worklet@5.1.39:
|
||||
@@ -7372,7 +7402,7 @@ packages:
|
||||
recorder-audio-worklet-processor: 4.2.21
|
||||
standardized-audio-context: 25.3.45
|
||||
subscribable-things: 2.1.14
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
worker-factory: 6.0.76
|
||||
dev: false
|
||||
|
||||
@@ -7513,11 +7543,11 @@ packages:
|
||||
resolution: {integrity: sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==}
|
||||
dev: false
|
||||
|
||||
/rxjs@7.8.0:
|
||||
resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
|
||||
/rxjs@7.8.1:
|
||||
resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
@@ -7755,7 +7785,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
automation-events: 6.0.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/std-env@3.3.3:
|
||||
@@ -7839,7 +7869,7 @@ packages:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.21.0
|
||||
rxjs-interop: 2.0.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/sucrase@3.32.0:
|
||||
@@ -8314,6 +8344,11 @@ packages:
|
||||
|
||||
/tinybench@2.4.0:
|
||||
resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==}
|
||||
dev: false
|
||||
|
||||
/tinybench@2.5.0:
|
||||
resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
|
||||
dev: true
|
||||
|
||||
/tinypool@0.3.1:
|
||||
resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==}
|
||||
@@ -8420,6 +8455,10 @@ packages:
|
||||
/tslib@2.5.0:
|
||||
resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
|
||||
|
||||
/tslib@2.5.1:
|
||||
resolution: {integrity: sha512-KaI6gPil5m9vF7DKaoXxx1ia9fxS4qG5YveErRRVknPDXXriu5M8h48YRjB6h5ZUOKuAKlSJYb0GaDe8I39fRw==}
|
||||
dev: false
|
||||
|
||||
/tsutils@3.21.0(typescript@5.0.3):
|
||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -8597,7 +8636,7 @@ packages:
|
||||
fast-json-patch: 3.1.1
|
||||
json-stringify-pretty-compact: 3.0.0
|
||||
semver: 7.4.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
vega: 5.22.1
|
||||
vega-interpreter: 1.0.5
|
||||
vega-lite: 0.6.7
|
||||
@@ -8975,7 +9014,7 @@ packages:
|
||||
picocolors: 1.0.0
|
||||
source-map: 0.6.1
|
||||
source-map-support: 0.5.21
|
||||
vite: 4.3.1(@types/node@18.16.0)(sass@1.61.0)
|
||||
vite: 4.3.8(@types/node@18.16.0)(sass@1.61.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@@ -8996,7 +9035,7 @@ packages:
|
||||
mlly: 1.2.1
|
||||
pathe: 1.1.0
|
||||
picocolors: 1.0.0
|
||||
vite: 4.3.1(@types/node@18.16.0)
|
||||
vite: 4.3.8(@types/node@18.16.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- less
|
||||
@@ -9082,29 +9121,29 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/vite-plugin-full-reload@1.0.5(vite@4.3.1):
|
||||
/vite-plugin-full-reload@1.0.5(vite@4.3.8):
|
||||
resolution: {integrity: sha512-kVZFDFWr0DxiHn6MuDVTQf7gnWIdETGlZh0hvTiMXzRN80vgF4PKbONSq8U1d0WtHsKaFODTQgJeakLacoPZEQ==}
|
||||
peerDependencies:
|
||||
vite: ^2 || ^3 || ^4
|
||||
dependencies:
|
||||
picocolors: 1.0.0
|
||||
picomatch: 2.3.1
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
dev: false
|
||||
|
||||
/vite-plugin-glsl@1.1.2(vite@4.3.1):
|
||||
/vite-plugin-glsl@1.1.2(vite@4.3.8):
|
||||
resolution: {integrity: sha512-zmXsfc1vn2MlYve9t3FAoWuhLyoCkNS1TuQL+TkXZL7tGmBjRErp10eNYxcse5tK9oUC5MyJpNc4ElpQnx8DoA==}
|
||||
engines: {node: '>= 16.15.1', npm: '>= 8.11.0'}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.0.2
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
dev: true
|
||||
|
||||
/vite-plugin-static-copy@0.14.0(vite@4.3.1):
|
||||
/vite-plugin-static-copy@0.14.0(vite@4.3.8):
|
||||
resolution: {integrity: sha512-RMFmb4czomcrsbQBiUZs9HcDGN3kxGvF+OrtkfTVocp12CuoUCuJQhcY26RK35A6KS4WasGzEwcYZqHMjkAvVw==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
@@ -9114,7 +9153,7 @@ packages:
|
||||
fast-glob: 3.2.12
|
||||
fs-extra: 11.1.1
|
||||
picocolors: 1.0.0
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
dev: true
|
||||
|
||||
/vite-plugin-svelte-console-remover@1.0.10(sass@1.61.0):
|
||||
@@ -9130,7 +9169,7 @@ packages:
|
||||
- supports-color
|
||||
dev: true
|
||||
|
||||
/vite-tsconfig-paths@4.0.8(typescript@5.0.3)(vite@4.3.1):
|
||||
/vite-tsconfig-paths@4.0.8(typescript@5.0.3)(vite@4.3.8):
|
||||
resolution: {integrity: sha512-p04zH+Ey+NT78571x0pdX7nVRIJSlmKVvYryFglSWOK3Hc72eDL0+JJfbyQiugaIBApJkaEqbBQvqpsFZOSVGg==}
|
||||
peerDependencies:
|
||||
vite: '*'
|
||||
@@ -9141,7 +9180,7 @@ packages:
|
||||
debug: 4.3.4
|
||||
globrex: 0.1.2
|
||||
tsconfck: 2.1.1(typescript@5.0.3)
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -9293,8 +9332,8 @@ packages:
|
||||
fsevents: 2.3.2
|
||||
dev: false
|
||||
|
||||
/vite@4.3.1(@types/node@18.16.0)(sass@1.61.0):
|
||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||
/vite@4.3.8(@types/node@18.16.0):
|
||||
resolution: {integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -9320,15 +9359,48 @@ packages:
|
||||
dependencies:
|
||||
'@types/node': 18.16.0
|
||||
esbuild: 0.17.18
|
||||
postcss: 8.4.21
|
||||
postcss: 8.4.23
|
||||
rollup: 3.21.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: false
|
||||
|
||||
/vite@4.3.8(@types/node@18.16.0)(sass@1.61.0):
|
||||
resolution: {integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': '>= 14'
|
||||
less: '*'
|
||||
sass: '*'
|
||||
stylus: '*'
|
||||
sugarss: '*'
|
||||
terser: ^5.4.0
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/node': 18.16.0
|
||||
esbuild: 0.17.18
|
||||
postcss: 8.4.23
|
||||
rollup: 3.21.0
|
||||
sass: 1.61.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/vite@4.3.1(sass@1.61.0):
|
||||
resolution: {integrity: sha512-EPmfPLAI79Z/RofuMvkIS0Yr091T2ReUoXQqc5ppBX/sjFRhHKiPPF/R46cTdoci/XgeQpB23diiJxq5w30vdg==}
|
||||
/vite@4.3.8(sass@1.61.0):
|
||||
resolution: {integrity: sha512-uYB8PwN7hbMrf4j1xzGDk/lqjsZvCDbt/JC5dyfxc19Pg8kRm14LinK/uq+HSLNswZEoKmweGdtpbnxRtrAXiQ==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
@@ -9353,7 +9425,7 @@ packages:
|
||||
optional: true
|
||||
dependencies:
|
||||
esbuild: 0.17.18
|
||||
postcss: 8.4.21
|
||||
postcss: 8.4.23
|
||||
rollup: 3.21.0
|
||||
sass: 1.61.0
|
||||
optionalDependencies:
|
||||
@@ -9368,7 +9440,7 @@ packages:
|
||||
optional: true
|
||||
dev: true
|
||||
|
||||
/vitefu@0.2.4(vite@4.3.1):
|
||||
/vitefu@0.2.4(vite@4.3.8):
|
||||
resolution: {integrity: sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==}
|
||||
peerDependencies:
|
||||
vite: ^3.0.0 || ^4.0.0
|
||||
@@ -9376,7 +9448,7 @@ packages:
|
||||
vite:
|
||||
optional: true
|
||||
dependencies:
|
||||
vite: 4.3.1(sass@1.61.0)
|
||||
vite: 4.3.8(sass@1.61.0)
|
||||
dev: false
|
||||
|
||||
/vitest@0.27.3(happy-dom@9.18.3)(jsdom@22.0.0)(sass@1.61.0):
|
||||
@@ -9401,7 +9473,7 @@ packages:
|
||||
jsdom:
|
||||
optional: true
|
||||
dependencies:
|
||||
'@types/chai': 4.3.4
|
||||
'@types/chai': 4.3.5
|
||||
'@types/chai-subset': 1.3.3
|
||||
'@types/node': 18.16.0
|
||||
acorn: 8.8.2
|
||||
@@ -9416,10 +9488,10 @@ packages:
|
||||
source-map: 0.6.1
|
||||
std-env: 3.3.3
|
||||
strip-literal: 1.0.1
|
||||
tinybench: 2.4.0
|
||||
tinybench: 2.5.0
|
||||
tinypool: 0.3.1
|
||||
tinyspy: 1.1.1
|
||||
vite: 4.3.1(@types/node@18.16.0)(sass@1.61.0)
|
||||
vite: 4.3.8(@types/node@18.16.0)(sass@1.61.0)
|
||||
vite-node: 0.27.3(@types/node@18.16.0)(sass@1.61.0)
|
||||
why-is-node-running: 2.2.2
|
||||
transitivePeerDependencies:
|
||||
@@ -9625,7 +9697,7 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
compilerr: 10.0.2
|
||||
fast-unique-numbers: 7.0.2
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/worker-factory@7.0.0:
|
||||
@@ -9634,7 +9706,7 @@ packages:
|
||||
'@babel/runtime': 7.21.0
|
||||
compilerr: 11.0.0
|
||||
fast-unique-numbers: 8.0.0
|
||||
tslib: 2.5.0
|
||||
tslib: 2.5.1
|
||||
dev: false
|
||||
|
||||
/wrap-ansi@7.0.0:
|
||||
@@ -9752,10 +9824,14 @@ packages:
|
||||
commander: 9.5.0
|
||||
dev: false
|
||||
|
||||
/zod@3.21.4:
|
||||
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
|
||||
dev: false
|
||||
|
||||
github.com/andywer/typed-emitter/9a139b6fa0ec6b0db6141b5b756b784e4f7ef4e4:
|
||||
resolution: {tarball: https://codeload.github.com/andywer/typed-emitter/tar.gz/9a139b6fa0ec6b0db6141b5b756b784e4f7ef4e4}
|
||||
name: typed-emitter
|
||||
version: 2.1.0
|
||||
optionalDependencies:
|
||||
rxjs: 7.8.0
|
||||
rxjs: 7.8.1
|
||||
dev: false
|
||||
|
||||
263
src/lib/ComfyBoxStdPrompt.ts
Normal file
263
src/lib/ComfyBoxStdPrompt.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
import { z, type ZodTypeAny } from "zod"
|
||||
|
||||
/*
|
||||
* This metadata can be attached to each entry in a group to assist in
|
||||
* identifying the correct nodes to apply it to.
|
||||
*
|
||||
* As an example, positive and negative conditioning are deployed as two
|
||||
* separate nodes in ComfyUI. This makes bundling them into a { positive,
|
||||
* negative } entry difficult as either one can be missing. So instead they're
|
||||
* tagged like
|
||||
*
|
||||
* {
|
||||
* conditioning: [
|
||||
* { text: "masterpiece", "$meta": { types: ["positive"] } },
|
||||
* { text: "worst quality", "$meta": { types: ["negative"] } },
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* The reasoning is the "types" information isn't required to reinstantiate
|
||||
* the node, it's only semantic information describing how the node is used in
|
||||
* the encompassing workflow. When the prompt is loaded the workflow can be
|
||||
* searched for a node with the compatible type to attach the information to.
|
||||
*/
|
||||
const GroupMetadata = z.object({
|
||||
types: z.array(z.string()).nonempty().optional()
|
||||
})
|
||||
export type ComfyBoxStdGroupMetadata = z.infer<typeof GroupMetadata>
|
||||
|
||||
const group = (obj: Record<string, any>): ZodTypeAny => {
|
||||
const meta = z.object({ "$meta": GroupMetadata.optional() })
|
||||
return z.object(obj).and(meta)
|
||||
}
|
||||
|
||||
const ModelHashes = z.object({
|
||||
a1111_shorthash: z.string().optional(),
|
||||
sha256: z.string().optional(),
|
||||
}).refine(({ a1111_shorthash, sha256 }) =>
|
||||
a1111_shorthash !== undefined || sha256 !== undefined,
|
||||
{ message: "At least one model hash must be specified" })
|
||||
|
||||
const GroupConditioning = group({
|
||||
text: z.string(),
|
||||
})
|
||||
export type ComfyBoxStdGroupConditioning = z.infer<typeof GroupConditioning>
|
||||
|
||||
const GroupCheckpoint = group({
|
||||
model_name: z.string().optional(),
|
||||
model_hashes: ModelHashes.optional(),
|
||||
}).refine(({ model_name, model_hashes }) =>
|
||||
model_name !== undefined || model_hashes !== undefined,
|
||||
{ message: "Must include either model name or model hash" }
|
||||
)
|
||||
export type ComfyBoxStdGroupCheckpoint = z.infer<typeof GroupCheckpoint>
|
||||
|
||||
const GroupVAE = group({
|
||||
model_name: z.string().optional(),
|
||||
model_hashes: ModelHashes.optional(),
|
||||
type: z.enum(["internal", "external"])
|
||||
}).refine(({ model_name, model_hashes }) =>
|
||||
model_name !== undefined || model_hashes !== undefined,
|
||||
{ message: "Must include either model name or model hashes" }
|
||||
)
|
||||
export type ComfyBoxStdGroupVAE = z.infer<typeof GroupVAE>
|
||||
|
||||
const GroupKSampler = group({
|
||||
cfg_scale: z.number(),
|
||||
seed: z.number(),
|
||||
steps: z.number(),
|
||||
sampler_name: z.string(),
|
||||
scheduler: z.string(),
|
||||
denoise: z.number().default(1.0),
|
||||
type: z.enum(["empty", "image", "upscale"]).optional()
|
||||
})
|
||||
export type ComfyBoxStdGroupKSampler = z.infer<typeof GroupKSampler>
|
||||
|
||||
const GroupLatentImage = group({
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
mask_blur: z.number().optional(),
|
||||
batch_count: z.number().default(1).optional(),
|
||||
batch_pos: z.number().default(0).optional()
|
||||
})
|
||||
export type ComfyBoxStdGroupLatentImage = z.infer<typeof GroupLatentImage>
|
||||
|
||||
const GroupLatentUpscale = group({
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
upscale_method: z.string().optional(),
|
||||
upscale_by: z.number().optional(),
|
||||
crop: z.string().optional()
|
||||
})
|
||||
export type ComfyBoxStdGroupLatentUpscale = z.infer<typeof GroupLatentUpscale>
|
||||
|
||||
const GroupSDUpscale = group({
|
||||
upscaler: z.string(),
|
||||
overlap: z.number(),
|
||||
})
|
||||
export type ComfyBoxStdGroupSDUpscale = z.infer<typeof GroupSDUpscale>
|
||||
|
||||
const GroupSelfAttentionGuidance = group({
|
||||
guidance_scale: z.number(),
|
||||
mask_threshold: z.number(),
|
||||
})
|
||||
export type ComfyBoxStdGroupSelfAttentionGuidance = z.infer<typeof GroupSelfAttentionGuidance>
|
||||
|
||||
const GroupHypernetwork = group({
|
||||
model_name: z.string(),
|
||||
model_hashes: ModelHashes.optional(),
|
||||
strength: z.number()
|
||||
})
|
||||
export type ComfyBoxStdGroupHypernetwork = z.infer<typeof GroupHypernetwork>
|
||||
|
||||
const LoRAModelHashes = z.object({
|
||||
addnet_shorthash: z.string().optional(),
|
||||
addnet_shorthash_legacy: z.string().optional(),
|
||||
sha256: z.string().optional(),
|
||||
}).refine(({ addnet_shorthash, addnet_shorthash_legacy, sha256 }) =>
|
||||
addnet_shorthash !== undefined || addnet_shorthash_legacy !== undefined || sha256 !== undefined,
|
||||
{ message: "At least one model hash must be specified" })
|
||||
|
||||
const GroupLoRA = group({
|
||||
model_name: z.string(),
|
||||
module_name: z.string().optional(),
|
||||
model_hashes: LoRAModelHashes.optional(),
|
||||
strength_unet: z.number(),
|
||||
strength_tenc: z.number()
|
||||
})
|
||||
export type ComfyBoxStdGroupLoRA = z.infer<typeof GroupLoRA>
|
||||
|
||||
const GroupControlNet = group({
|
||||
model: z.string(),
|
||||
model_hashes: ModelHashes.optional(),
|
||||
strength: z.number(),
|
||||
})
|
||||
export type ComfyBoxStdGroupControlNet = z.infer<typeof GroupControlNet>
|
||||
|
||||
const GroupCLIP = group({
|
||||
clip_skip: z.number().optional()
|
||||
})
|
||||
export type ComfyBoxStdGroupCLIP = z.infer<typeof GroupCLIP>
|
||||
|
||||
const GroupDynamicThresholding = group({
|
||||
mimic_scale: z.number(),
|
||||
threshold_percentile: z.number(),
|
||||
mimic_mode: z.string(),
|
||||
mimic_scale_minimum: z.number(),
|
||||
cfg_mode: z.string(),
|
||||
cfg_scale_minimum: z.number()
|
||||
})
|
||||
export type ComfyBoxStdGroupDynamicThresholding = z.infer<typeof GroupDynamicThresholding>
|
||||
|
||||
const GroupAestheticEmbedding = group({
|
||||
model_name: z.string(),
|
||||
lr: z.number(),
|
||||
slerp: z.boolean(),
|
||||
slerp_angle: z.number().optional(),
|
||||
steps: z.number(),
|
||||
text: z.string(),
|
||||
text_negative: z.boolean(),
|
||||
weight: z.number(),
|
||||
})
|
||||
export type ComfyBoxStdGroupAestheticEmbedding = z.infer<typeof GroupAestheticEmbedding>
|
||||
|
||||
const GroupDDetailer = group({
|
||||
positive_prompt: z.string(),
|
||||
negative_prompt: z.string(),
|
||||
bitwise: z.string(),
|
||||
model: z.string().optional(),
|
||||
model_hashes: ModelHashes.optional(),
|
||||
conf: z.number(),
|
||||
mask_blur: z.number(),
|
||||
denoise: z.number(),
|
||||
dilation: z.number(),
|
||||
offset_x: z.number(),
|
||||
offset_y: z.number(),
|
||||
preprocess: z.boolean(),
|
||||
inpaint_full: z.boolean(),
|
||||
inpaint_padding: z.number(),
|
||||
cfg: z.number()
|
||||
})
|
||||
export type ComfyBoxStdGroupDDetailer = z.infer<typeof GroupDDetailer>
|
||||
|
||||
const groupArray = (entry: ZodTypeAny) => {
|
||||
return z.optional(z.array(entry).nonempty());
|
||||
}
|
||||
|
||||
const Parameters = z.object({
|
||||
conditioning: groupArray(GroupConditioning),
|
||||
checkpoint: groupArray(GroupCheckpoint),
|
||||
vae: groupArray(GroupVAE),
|
||||
k_sampler: groupArray(GroupKSampler),
|
||||
clip: groupArray(GroupCLIP),
|
||||
latent_image: groupArray(GroupLatentImage),
|
||||
latent_upscale: groupArray(GroupLatentUpscale),
|
||||
sd_upscale: groupArray(GroupSDUpscale),
|
||||
hypernetwork: groupArray(GroupHypernetwork),
|
||||
lora: groupArray(GroupLoRA),
|
||||
control_net: groupArray(GroupControlNet),
|
||||
dynamic_thresholding: groupArray(GroupDynamicThresholding),
|
||||
aesthetic_embedding: groupArray(GroupAestheticEmbedding),
|
||||
self_attention_guidance: groupArray(GroupSelfAttentionGuidance),
|
||||
ddetailer: groupArray(GroupDDetailer)
|
||||
}).partial()
|
||||
export type ComfyBoxStdParameters = z.infer<typeof Parameters>
|
||||
|
||||
const ComfyBoxExtraData = z.object({
|
||||
workflows: z.array(z.string())
|
||||
})
|
||||
|
||||
const A1111ExtraData = z.object({
|
||||
params: z.any()
|
||||
})
|
||||
|
||||
const ExtraData = z.object({
|
||||
comfybox: ComfyBoxExtraData.optional(),
|
||||
a1111: A1111ExtraData.optional()
|
||||
})
|
||||
|
||||
const Metadata = z.object({
|
||||
created_with: z.string(),
|
||||
author: z.string().optional(),
|
||||
app_version: z.string().optional(),
|
||||
commit_hash: z.string().optional(),
|
||||
extra_data: ExtraData
|
||||
})
|
||||
|
||||
const ComfyBoxStdPrompt = z.object({
|
||||
version: z.number(),
|
||||
metadata: Metadata,
|
||||
parameters: Parameters
|
||||
})
|
||||
|
||||
export default ComfyBoxStdPrompt
|
||||
|
||||
/*
|
||||
* A standardized Stable Diffusion parameter format that should be used with an
|
||||
* encompassing workflow. Aims to encompass an arbitrary number of parameter
|
||||
* counts and types, so that most ComfyUI workflows can have parts of their
|
||||
* prompts transferred between each other.
|
||||
*
|
||||
* This format does *not* describe how the information should be used in the
|
||||
* underlying workflow, i.e. it does not specify the structure of a ComfyUI
|
||||
* execution graph. It only gives hints via tagged input types on each input
|
||||
* entry as to where the data should be inserted. To recreate a ComfyBox
|
||||
* workflow with the exact state of the UI intact, the `SerializedAppState` type
|
||||
* should be used instead. It suffices to embed data of that type in the output
|
||||
* PNGs for recreating their workflows. This type is meant as an interchange
|
||||
* format *between* workflows so their inputs can be copied to and from each
|
||||
* other in a sane-enough manner. (In ComfyBox, copying workflow outputs like
|
||||
* images to other workflows is handled separately, since this type does not
|
||||
* retain the actual image data.)
|
||||
*
|
||||
* In contrast with a serialized workflow, which is concerned with the
|
||||
* connections between nodes and the state of the frontend's UI, this format
|
||||
* concerns itself with the exact values that the execution backend receives,
|
||||
* after the data in the UI have finished processing.
|
||||
*
|
||||
* (Take for example a "scale by" slider that adjusts the width and height of an
|
||||
* img2img input image of 512 x 512 resolution by 2x. The backend will only
|
||||
* "see" width 1024 and height 1024, even though the only parameter exposed from
|
||||
* the frontend was the scale of 2.)
|
||||
*/
|
||||
export type ComfyBoxStdPrompt = z.infer<typeof ComfyBoxStdPrompt>
|
||||
55
src/lib/ComfyBoxStdPromptSerializer.ts
Normal file
55
src/lib/ComfyBoxStdPromptSerializer.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import type { ComfyBoxStdGroupLoRA, ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
import type { SerializedPrompt, SerializedPromptInputs } from "./components/ComfyApp";
|
||||
|
||||
export type ComfyPromptConverter = (stdPrompt: ComfyBoxStdPrompt, inputs: SerializedPromptInputs, nodeID: ComfyNodeID) => void;
|
||||
|
||||
function LoraLoader(stdPrompt: ComfyBoxStdPrompt, inputs: SerializedPromptInputs) {
|
||||
const params = stdPrompt.parameters
|
||||
|
||||
const lora: ComfyBoxStdGroupLoRA = {
|
||||
model_name: inputs["lora_name"],
|
||||
strength_unet: inputs["strength_model"],
|
||||
strength_tenc: inputs["strength_clip"]
|
||||
}
|
||||
|
||||
if (params.lora)
|
||||
params.lora.push(lora)
|
||||
else
|
||||
params.lora = [lora]
|
||||
}
|
||||
|
||||
const ALL_CONVERTERS: Record<string, ComfyPromptConverter> = {
|
||||
LoraLoader
|
||||
}
|
||||
|
||||
const COMMIT_HASH: string = __GIT_COMMIT_HASH__;
|
||||
|
||||
export default class ComfyBoxStdPromptSerializer {
|
||||
serialize(prompt: SerializedPrompt): ComfyBoxStdPrompt {
|
||||
const stdPrompt: ComfyBoxStdPrompt = {
|
||||
version: 1,
|
||||
metadata: {
|
||||
created_with: "ComfyBox",
|
||||
commit_hash: COMMIT_HASH,
|
||||
extra_data: {
|
||||
comfybox: {
|
||||
}
|
||||
}
|
||||
},
|
||||
parameters: {}
|
||||
}
|
||||
|
||||
for (const [nodeID, inputs] of Object.entries(prompt.output)) {
|
||||
const classType = inputs.class_type
|
||||
const converter = ALL_CONVERTERS[classType]
|
||||
if (converter) {
|
||||
converter(stdPrompt, inputs.inputs, nodeID)
|
||||
}
|
||||
else {
|
||||
console.warn("No StdPrompt type converter for comfy class!", classType)
|
||||
}
|
||||
}
|
||||
|
||||
return stdPrompt
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
}
|
||||
|
||||
if (color) {
|
||||
this.drawNodeOutline(node, ctx, size, fgColor, bgColor, color, thickness)
|
||||
this.drawNodeOutline(node, ctx, size, mouseOver, fgColor, bgColor, color, thickness)
|
||||
}
|
||||
|
||||
if (isRunningNode && state.progress) {
|
||||
@@ -108,27 +108,36 @@ export default class ComfyGraphCanvas extends LGraphCanvas {
|
||||
}
|
||||
}
|
||||
|
||||
private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, size: Vector2, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) {
|
||||
private drawNodeOutline(node: LGraphNode, ctx: CanvasRenderingContext2D, size: Vector2, mouseOver: boolean, fgColor: string, bgColor: string, outlineColor: string, outlineThickness: number) {
|
||||
const shape = node.shape || BuiltInSlotShape.ROUND_SHAPE;
|
||||
|
||||
var render_title = true;
|
||||
if (node.titleMode == TitleMode.TRANSPARENT_TITLE || node.titleMode == TitleMode.NO_TITLE) {
|
||||
render_title = false;
|
||||
} else if (node.titleMode == TitleMode.AUTOHIDE_TITLE && mouseOver) {
|
||||
render_title = true;
|
||||
}
|
||||
const titleHeight = render_title ? LiteGraph.NODE_TITLE_HEIGHT : 0;
|
||||
|
||||
ctx.lineWidth = outlineThickness;
|
||||
ctx.globalAlpha = 0.8;
|
||||
ctx.beginPath();
|
||||
if (shape == BuiltInSlotShape.BOX_SHAPE)
|
||||
ctx.rect(-6, -6 + LiteGraph.NODE_TITLE_HEIGHT, 12 + size[0] + 1, 12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT);
|
||||
ctx.rect(-6, -6 + titleHeight, 12 + size[0] + 1, 12 + size[1] + titleHeight);
|
||||
else if (shape == BuiltInSlotShape.ROUND_SHAPE || (shape == BuiltInSlotShape.CARD_SHAPE && node.flags.collapsed))
|
||||
ctx.roundRect(
|
||||
-6,
|
||||
-6 - LiteGraph.NODE_TITLE_HEIGHT,
|
||||
-6 - titleHeight,
|
||||
12 + size[0] + 1,
|
||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||
12 + size[1] + titleHeight,
|
||||
this.round_radius * 2
|
||||
);
|
||||
else if (shape == BuiltInSlotShape.CARD_SHAPE)
|
||||
ctx.roundRect(
|
||||
-6,
|
||||
-6 + LiteGraph.NODE_TITLE_HEIGHT,
|
||||
-6 + titleHeight,
|
||||
12 + size[0] + 1,
|
||||
12 + size[1] + LiteGraph.NODE_TITLE_HEIGHT,
|
||||
12 + size[1] + titleHeight,
|
||||
this.round_radius * 2,
|
||||
2
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Progress, SerializedPrompt, SerializedPromptInputs, SerializedPromptInputsAll, SerializedPromptOutputs } from "./components/ComfyApp";
|
||||
import type { Progress, SerializedPrompt, SerializedPromptInputsForNode, SerializedPromptInputsAll, SerializedPromptOutputs } from "./components/ComfyApp";
|
||||
import type TypedEmitter from "typed-emitter";
|
||||
import EventEmitter from "events";
|
||||
import type { ComfyImageLocation } from "$lib/utils";
|
||||
|
||||
114
src/lib/components/A1111PromptDisplay.svelte
Normal file
114
src/lib/components/A1111PromptDisplay.svelte
Normal file
@@ -0,0 +1,114 @@
|
||||
<script lang="ts">
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import { Block, BlockTitle } from "@gradio/atoms";
|
||||
import { TextBox } from "@gradio/form";
|
||||
import { JsonView } from '@zerodevx/svelte-json-view'
|
||||
import type { A1111PromptAndInfo } from "./ComfyApp";
|
||||
import { StaticImage } from "./gradio/image";
|
||||
|
||||
export let prompt: A1111PromptAndInfo | null = null;
|
||||
|
||||
let json: any = {}
|
||||
let a1111: A1111ParsedInfotext | null = null
|
||||
let infotext: string = ""
|
||||
let image: string | null = null;
|
||||
|
||||
$: if (prompt) {
|
||||
infotext = prompt.infotext;
|
||||
a1111 = prompt.parsedInfotext;
|
||||
json = prompt.stdPrompt;
|
||||
image = URL.createObjectURL(prompt.imageFile);
|
||||
}
|
||||
else {
|
||||
infotext = ""
|
||||
a1111 = null;
|
||||
json = {}
|
||||
image = null;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
{#if prompt != null}
|
||||
<div class="a1111-prompt-display">
|
||||
<div class="prompt-container">
|
||||
<Block>
|
||||
<TextBox label="Infotext" show_label={true} value={infotext} lines={5} max_lines={20}/>
|
||||
</Block>
|
||||
<div class="scroll-container">
|
||||
{#if a1111}
|
||||
{#if Object.keys(a1111.extraParams).length > 0}
|
||||
<Block>
|
||||
<BlockTitle>Unused Parameters</BlockTitle>
|
||||
<div class="json">
|
||||
<JsonView json={a1111.extraParams} />
|
||||
</div>
|
||||
</Block>
|
||||
{/if}
|
||||
{/if}
|
||||
<Block>
|
||||
<BlockTitle>Converted Prompt</BlockTitle>
|
||||
<div class="json">
|
||||
<JsonView {json} />
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
</div>
|
||||
<Block>
|
||||
<StaticImage show_label={false} label="Image" value={image} />
|
||||
</Block>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.a1111-prompt-display {
|
||||
width: 70vw;
|
||||
height: 70vh;
|
||||
color: none;
|
||||
|
||||
--jsonPaddingLeft: 1rem;
|
||||
--jsonBorderLeft: 1px dotted var(--neutral-600);
|
||||
--jsonBracketColor: currentcolor;
|
||||
--jsonBracketHoverBackground: var(--neutral-100);
|
||||
--jsonSeparatorColor: currentcolor;
|
||||
--jsonKeyColor: var(--body-text-color);
|
||||
--jsonValColor: var(--body-text-color-subdued);
|
||||
--jsonValStringColor: var(--color-green-500);
|
||||
--jsonValNumberColor: var(--color-blue-500);
|
||||
--jsonValBooleanColor: var(--color-red-500);
|
||||
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-y: none;
|
||||
|
||||
flex-direction: row;
|
||||
|
||||
.prompt-container {
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
||||
.json {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.scroll-container {
|
||||
position: relative;
|
||||
flex: 1 1 0%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
:global(> .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
|
||||
.accordion {
|
||||
background: var(--panel-background-fill);
|
||||
|
||||
|
||||
:global(> .block .block) {
|
||||
background: var(--panel-background-fill);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@
|
||||
export let isMobile: boolean = false;
|
||||
let isOpen: Writable<boolean> | null = null;
|
||||
|
||||
let children: IDragItem[] | null = null;
|
||||
let children: IDragItem[] = [];
|
||||
const flipDurationMs = 100;
|
||||
|
||||
let selectedIndex: number = 0;
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if container && Array.isArray(children)}
|
||||
{#if container}
|
||||
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<number> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
let children: IDragItem[] = [];
|
||||
const flipDurationMs = 100;
|
||||
|
||||
$: if (container) {
|
||||
@@ -35,12 +35,12 @@
|
||||
}
|
||||
else {
|
||||
container = null;
|
||||
children = null;
|
||||
children = [];
|
||||
attrsChanged = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
children = null;
|
||||
children = [];
|
||||
attrsChanged = null
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if container && Array.isArray(children)}
|
||||
{#if container}
|
||||
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import { BlockTitle } from "@gradio/atoms";
|
||||
import ComfyUIPane from "./ComfyUIPane.svelte";
|
||||
import ComfyApp, { type SerializedAppState } from "./ComfyApp";
|
||||
import ComfyApp, { type A1111PromptAndInfo, type SerializedAppState } from "./ComfyApp";
|
||||
import { Checkbox, TextBox } from "@gradio/form"
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import layoutState from "$lib/stores/layoutState";
|
||||
import selectionState from "$lib/stores/selectionState";
|
||||
import { ImageViewer } from "$lib/ImageViewer";
|
||||
import type { ComfyAPIStatus } from "$lib/api";
|
||||
import { SvelteToast, toast } from '@zerodevx/svelte-toast'
|
||||
|
||||
import { LGraph } from "@litegraph-ts/core";
|
||||
@@ -20,14 +19,18 @@
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import { download, jsonToJsObject } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
import { download, jsonToJsObject } from "$lib/utils";
|
||||
import notify from "$lib/notify";
|
||||
import Modal from "./Modal.svelte";
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
import A1111PromptDisplay from "./A1111PromptDisplay.svelte";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
|
||||
export let app: ComfyApp = undefined;
|
||||
let queue: ComfyQueue = undefined;
|
||||
let alreadySetup: Writable<boolean> = writable(false);
|
||||
let a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
||||
let mainElem: HTMLDivElement;
|
||||
let uiPane: ComfyUIPane = undefined;
|
||||
let props: ComfyProperties = undefined;
|
||||
let containerElem: HTMLDivElement;
|
||||
let resizeTimeout: NodeJS.Timeout | null;
|
||||
@@ -44,6 +47,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: if(app) {
|
||||
alreadySetup = app.alreadySetup;
|
||||
a1111Prompt = app.a1111Prompt;
|
||||
}
|
||||
|
||||
function refreshView(event?: Event) {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(app.resizeCanvas.bind(app), 250);
|
||||
@@ -188,6 +196,10 @@
|
||||
else {
|
||||
document.getElementById("app-root").classList.remove("dark")
|
||||
}
|
||||
|
||||
let showModal: boolean = false;
|
||||
|
||||
$: showModal = $a1111Prompt != null
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -196,8 +208,19 @@
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<Modal bind:showModal on:close={() => ($a1111Prompt = null)}>
|
||||
<div slot="header" class="prompt-modal-header">
|
||||
<h1 style="padding-bottom: 1rem;">A1111 Prompt Details</h1>
|
||||
</div>
|
||||
<A1111PromptDisplay prompt={$a1111Prompt} />
|
||||
<div slot="buttons" let:closeDialog>
|
||||
<Button variant="secondary" on:click={closeDialog}>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<div id="main" class:dark={uiTheme === "gradio-dark"}>
|
||||
<div id="dropzone" class="dropzone"></div>
|
||||
<div id="container" bind:this={containerElem}>
|
||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||
<Pane bind:size={propsSidebarSize}>
|
||||
@@ -208,7 +231,7 @@
|
||||
<Pane>
|
||||
<Splitpanes theme="comfy" on:resize={refreshView} horizontal="{true}">
|
||||
<Pane>
|
||||
<ComfyUIPane bind:this={uiPane} {app} />
|
||||
<ComfyUIPane {app} />
|
||||
</Pane>
|
||||
<Pane bind:size={graphSize}>
|
||||
<ComfyGraphView {app} transitioning={graphTransitioning} />
|
||||
@@ -217,7 +240,7 @@
|
||||
</Pane>
|
||||
<Pane bind:size={queueSidebarSize}>
|
||||
<div class="sidebar-wrapper pane-wrapper">
|
||||
<ComfyQueue bind:this={queue} />
|
||||
<ComfyQueue {app} />
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
@@ -225,35 +248,35 @@
|
||||
<div id="bottombar">
|
||||
<div class="left">
|
||||
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||
<Button variant="primary" on:click={queuePrompt}>
|
||||
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
||||
{$layoutState.attrs.queuePromptButtonName}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button variant="secondary" on:click={toggleGraph}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleGraph}>
|
||||
Toggle Graph
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleProps}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleProps}>
|
||||
Toggle Props
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={toggleQueue}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={toggleQueue}>
|
||||
Toggle Queue
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doSave}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSave}>
|
||||
Save
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doSaveLocal}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doSaveLocal}>
|
||||
Save Local
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoad}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoad}>
|
||||
Load
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doClear}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doClear}>
|
||||
Clear
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doLoadDefault}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doLoadDefault}>
|
||||
Load Default
|
||||
</Button>
|
||||
<Button variant="secondary" on:click={doRefreshCombos}>
|
||||
<Button variant="secondary" disabled={!$alreadySetup} on:click={doRefreshCombos}>
|
||||
🔄
|
||||
</Button>
|
||||
<!-- <Checkbox label="Lock Nodes" bind:value={$uiState.nodesLocked}/>
|
||||
@@ -325,19 +348,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
background: #60a7dc80;
|
||||
border: 4px dashed #60a7dc;
|
||||
}
|
||||
|
||||
:global(html, body) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -26,7 +26,7 @@ import layoutState from "$lib/stores/layoutState";
|
||||
import { toast } from '@zerodevx/svelte-toast'
|
||||
import ComfyGraph from "$lib/ComfyGraph";
|
||||
import { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import { get } from "svelte/store";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { tick } from "svelte";
|
||||
import uiState from "$lib/stores/uiState";
|
||||
import { download, graphToGraphVis, jsonToJsObject, promptToGraphVis, range, workflowToGraphVis } from "$lib/utils";
|
||||
@@ -37,6 +37,10 @@ import type { ComfyExecutionResult } from "$lib/utils";
|
||||
import ComfyPromptSerializer, { UpstreamNodeLocator, isActiveBackendNode } from "./ComfyPromptSerializer";
|
||||
import { iterateNodeDefInputs, type ComfyNodeDef, isBackendNodeDefInputType, iterateNodeDefOutputs } from "$lib/ComfyNodeDef";
|
||||
import { ComfyComboNode } from "$lib/nodes/widgets";
|
||||
import parseA1111, { type A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import type { ComfyBoxStdPrompt } from "$lib/ComfyBoxStdPrompt";
|
||||
import ComfyBoxStdPromptSerializer from "$lib/ComfyBoxStdPromptSerializer";
|
||||
|
||||
export const COMFYBOX_SERIAL_VERSION = 1;
|
||||
|
||||
@@ -45,32 +49,68 @@ if (typeof window !== "undefined") {
|
||||
nodes.ComfyReroute.setDefaultTextVisibility(!!localStorage["Comfy.ComfyReroute.DefaultVisibility"]);
|
||||
}
|
||||
|
||||
type QueueItem = { num: number, batchCount: number }
|
||||
/*
|
||||
* Queued prompt that hasn't been sent to the backend yet.
|
||||
* TODO: Assumes the currently active graph will be serialized, needs to change
|
||||
* for multiple loaded workflow support
|
||||
*/
|
||||
type QueueItem = {
|
||||
num: number,
|
||||
batchCount: number
|
||||
}
|
||||
|
||||
export type A1111PromptAndInfo = {
|
||||
infotext: string,
|
||||
parsedInfotext: A1111ParsedInfotext,
|
||||
stdPrompt: ComfyBoxStdPrompt,
|
||||
imageFile: File
|
||||
}
|
||||
|
||||
/*
|
||||
* Represents a single workflow that can be loaded into the program from JSON.
|
||||
*/
|
||||
export type SerializedAppState = {
|
||||
/** Program identifier, should always be "ComfyBox" */
|
||||
createdBy: "ComfyBox",
|
||||
/** Serial version, should be incremented on breaking changes */
|
||||
version: number,
|
||||
/** Commit hash if found */
|
||||
commitHash?: string,
|
||||
/** Graph state */
|
||||
workflow: SerializedLGraph,
|
||||
/** UI state */
|
||||
layout: SerializedLayoutState,
|
||||
/** Position/offset of the canvas at the time of saving */
|
||||
canvas: SerializedGraphCanvasState
|
||||
}
|
||||
|
||||
/** [link origin, link index] | value */
|
||||
/** [link_origin, link_slot_index] | input_value */
|
||||
export type SerializedPromptInput = [ComfyNodeID, number] | any
|
||||
|
||||
export type SerializedPromptInputs = {
|
||||
export type SerializedPromptInputs = Record<string, SerializedPromptInput>;
|
||||
|
||||
/*
|
||||
* A single node in the prompt and its input values.
|
||||
*/
|
||||
export type SerializedPromptInputsForNode = {
|
||||
/* property name -> value or link */
|
||||
inputs: Record<string, SerializedPromptInput>,
|
||||
inputs: SerializedPromptInputs,
|
||||
class_type: string
|
||||
}
|
||||
|
||||
export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputs>
|
||||
/*
|
||||
* All nodes in the graph and their input values.
|
||||
*/
|
||||
export type SerializedPromptInputsAll = Record<ComfyNodeID, SerializedPromptInputsForNode>
|
||||
|
||||
export type SerializedPrompt = {
|
||||
workflow: SerializedLGraph,
|
||||
output: SerializedPromptInputsAll
|
||||
}
|
||||
|
||||
/*
|
||||
* Outputs for each node.
|
||||
*/
|
||||
export type SerializedPromptOutputs = Record<ComfyNodeID, ComfyExecutionResult>
|
||||
|
||||
export type Progress = {
|
||||
@@ -78,6 +118,10 @@ export type Progress = {
|
||||
max: number
|
||||
}
|
||||
|
||||
/*
|
||||
* A combo node and the backend node that will send an updated config over, for
|
||||
* refreshing lists of model files
|
||||
*/
|
||||
type BackendComboNode = {
|
||||
comboNode: ComfyComboNode,
|
||||
comfyInput: IComfyInputSlot,
|
||||
@@ -95,20 +139,24 @@ export default class ComfyApp {
|
||||
nodeOutputs: Record<string, any> = {};
|
||||
|
||||
shiftDown: boolean = false;
|
||||
ctrlDown: boolean = false;
|
||||
selectedGroupMoving: boolean = false;
|
||||
alreadySetup: Writable<boolean> = writable(false);
|
||||
a1111Prompt: Writable<A1111PromptAndInfo | null> = writable(null);
|
||||
|
||||
private queueItems: QueueItem[] = [];
|
||||
private processingQueue: boolean = false;
|
||||
private alreadySetup = false;
|
||||
private promptSerializer: ComfyPromptSerializer;
|
||||
private stdPromptSerializer: ComfyBoxStdPromptSerializer;
|
||||
|
||||
constructor() {
|
||||
this.api = new ComfyAPI();
|
||||
this.promptSerializer = new ComfyPromptSerializer();
|
||||
this.stdPromptSerializer = new ComfyBoxStdPromptSerializer();
|
||||
}
|
||||
|
||||
async setup(): Promise<void> {
|
||||
if (this.alreadySetup) {
|
||||
if (get(this.alreadySetup)) {
|
||||
console.error("Already setup")
|
||||
return;
|
||||
}
|
||||
@@ -151,7 +199,6 @@ export default class ComfyApp {
|
||||
// setInterval(this.saveStateToLocalStorage.bind(this), 1000);
|
||||
|
||||
this.addApiUpdateHandlers();
|
||||
this.addDropHandler();
|
||||
this.addPasteHandler();
|
||||
this.addKeyboardHandler();
|
||||
|
||||
@@ -165,7 +212,7 @@ export default class ComfyApp {
|
||||
|
||||
this.requestPermissions();
|
||||
|
||||
this.alreadySetup = true;
|
||||
this.alreadySetup.set(true);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
@@ -286,70 +333,28 @@ export default class ComfyApp {
|
||||
}
|
||||
}
|
||||
|
||||
private showDropZone() {
|
||||
if (this.dropZone)
|
||||
this.dropZone.style.display = "block";
|
||||
}
|
||||
|
||||
private hideDropZone() {
|
||||
if (this.dropZone)
|
||||
this.dropZone.style.display = "none";
|
||||
}
|
||||
|
||||
private allowDrag(event: DragEvent) {
|
||||
if (event.dataTransfer.items?.length > 0) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
this.showDropZone();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private async handleDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.hideDropZone();
|
||||
|
||||
if (event.dataTransfer.files.length > 0) {
|
||||
await this.handleFile(event.dataTransfer.files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private addDropHandler() {
|
||||
// this.dropZone = document.getElementById("dropzone");
|
||||
|
||||
// if (this.dropZone) {
|
||||
// window.addEventListener('dragenter', this.allowDrag.bind(this));
|
||||
// this.dropZone.addEventListener('dragover', this.allowDrag.bind(this));
|
||||
// this.dropZone.addEventListener('dragleave', this.hideDropZone.bind(this));
|
||||
// this.dropZone.addEventListener('drop', this.handleDrop.bind(this));
|
||||
// }
|
||||
// else {
|
||||
// console.warn("No dropzone detected (probably on mobile).")
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a handler on paste that extracts and loads workflows from pasted JSON data
|
||||
*/
|
||||
private addPasteHandler() {
|
||||
// document.addEventListener("paste", (e) => {
|
||||
// let data = (e.clipboardData || (window as any).clipboardData).getData("text/plain");
|
||||
// let workflow;
|
||||
// try {
|
||||
// data = data.slice(data.indexOf("{"));
|
||||
// workflow = JSON.parse(data);
|
||||
// } catch (err) {
|
||||
// try {
|
||||
// data = data.slice(data.indexOf("workflow\n"));
|
||||
// data = data.slice(data.indexOf("{"));
|
||||
// workflow = JSON.parse(data);
|
||||
// } catch (error) { }
|
||||
// }
|
||||
document.addEventListener("paste", (e) => {
|
||||
let data = (e.clipboardData || (window as any).clipboardData).getData("text/plain");
|
||||
let workflow;
|
||||
try {
|
||||
data = data.slice(data.indexOf("{"));
|
||||
workflow = JSON.parse(data);
|
||||
} catch (err) {
|
||||
try {
|
||||
data = data.slice(data.indexOf("workflow\n"));
|
||||
data = data.slice(data.indexOf("{"));
|
||||
workflow = JSON.parse(data);
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// if (workflow && workflow.version && workflow.nodes && workflow.extra) {
|
||||
// this.loadGraphData(workflow);
|
||||
// }
|
||||
// });
|
||||
if (workflow && workflow.version && workflow.nodes && workflow.extra) {
|
||||
this.loadGraphData(workflow);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,6 +411,7 @@ export default class ComfyApp {
|
||||
private addKeyboardHandler() {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
this.shiftDown = e.shiftKey;
|
||||
this.ctrlDown = e.ctrlKey;
|
||||
|
||||
// Queue prompt using ctrl or command + enter
|
||||
if ((e.ctrlKey || e.metaKey) && (e.key === "Enter" || e.keyCode === 13 || e.keyCode === 10)) {
|
||||
@@ -414,6 +420,7 @@ export default class ComfyApp {
|
||||
});
|
||||
window.addEventListener("keyup", (e) => {
|
||||
this.shiftDown = e.shiftKey;
|
||||
this.ctrlDown = e.ctrlKey;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -561,7 +568,9 @@ export default class ComfyApp {
|
||||
}
|
||||
|
||||
if (get(layoutState).attrs.queuePromptButtonRunWorkflow) {
|
||||
this.queuePrompt(0, 1);
|
||||
// Hold control to queue at the front
|
||||
const num = this.ctrlDown ? -1 : 0;
|
||||
this.queuePrompt(num, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -645,6 +654,9 @@ export default class ComfyApp {
|
||||
console.debug(graphToGraphVis(this.lGraph))
|
||||
console.debug(promptToGraphVis(p))
|
||||
|
||||
const stdPrompt = this.stdPromptSerializer.serialize(p);
|
||||
console.warn("STD", stdPrompt);
|
||||
|
||||
const extraData: ComfyBoxPromptExtraData = {
|
||||
extra_pnginfo: {
|
||||
workflow: p.workflow,
|
||||
@@ -709,8 +721,18 @@ export default class ComfyApp {
|
||||
if (pngInfo.comfyBoxConfig) {
|
||||
this.deserialize(JSON.parse(pngInfo.comfyBoxConfig));
|
||||
} else if (pngInfo.parameters) {
|
||||
throw "TODO A111 import!"
|
||||
// importA1111(this.lGraph, pngInfo.parameters, this.api);
|
||||
const parsed = parseA1111(pngInfo.parameters)
|
||||
if ("error" in parsed) {
|
||||
notify(`Couldn't parse webui prompt: ${parsed.error}`, { type: "error" })
|
||||
return;
|
||||
}
|
||||
const converted = convertA1111ToStdPrompt(parsed)
|
||||
this.a1111Prompt.set({
|
||||
infotext: pngInfo.parameters,
|
||||
parsedInfotext: parsed,
|
||||
stdPrompt: converted,
|
||||
imageFile: file
|
||||
})
|
||||
}
|
||||
else {
|
||||
console.error("No metadata found in image file.", pngInfo)
|
||||
@@ -854,5 +876,6 @@ export default class ComfyApp {
|
||||
*/
|
||||
clean() {
|
||||
this.nodeOutputs = {};
|
||||
this.a1111Prompt.set(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type ComfyGraph from "$lib/ComfyGraph";
|
||||
import type { ComfyBackendNode } from "$lib/nodes/ComfyBackendNode";
|
||||
import type ComfyGraphNode from "$lib/nodes/ComfyGraphNode";
|
||||
import { GraphInput, GraphOutput, LGraph, LGraphNode, LLink, NodeMode, Subgraph, type SlotIndex } from "@litegraph-ts/core";
|
||||
import type { SerializedPrompt, SerializedPromptInput, SerializedPromptInputs, SerializedPromptInputsAll } from "./ComfyApp";
|
||||
import type { SerializedPrompt, SerializedPromptInput, SerializedPromptInputsForNode, SerializedPromptInputsAll, SerializedPromptInputs } from "./ComfyApp";
|
||||
import type IComfyInputSlot from "$lib/IComfyInputSlot";
|
||||
|
||||
function hasTag(node: LGraphNode, tag: string): boolean {
|
||||
@@ -113,7 +113,7 @@ export class UpstreamNodeLocator {
|
||||
}
|
||||
|
||||
// If there are non-target nodes between us and another
|
||||
// backend node, we have to traverse them first. This
|
||||
// target node, we have to traverse them first. This
|
||||
// behavior is dependent on the type of node. Reroute nodes
|
||||
// will simply follow their single input, while branching
|
||||
// nodes have conditional logic that determines which link
|
||||
@@ -150,7 +150,7 @@ export class UpstreamNodeLocator {
|
||||
}
|
||||
|
||||
export default class ComfyPromptSerializer {
|
||||
serializeInputValues(node: ComfyBackendNode): Record<string, SerializedPromptInput> {
|
||||
serializeInputValues(node: ComfyBackendNode): SerializedPromptInputs {
|
||||
// Store input values passed by frontend-only nodes
|
||||
if (!node.inputs) {
|
||||
return {}
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
import { Button } from "@gradio/button";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
import { tick } from "svelte";
|
||||
import Modal from "./Modal.svelte";
|
||||
import Modal from "./Modal.svelte";
|
||||
import DropZone from "./DropZone.svelte";
|
||||
|
||||
export let app: ComfyApp;
|
||||
|
||||
let queuePending: Writable<QueueEntry[]> | null = null;
|
||||
let queueRunning: Writable<QueueEntry[]> | null = null;
|
||||
@@ -197,6 +200,7 @@
|
||||
</Modal>
|
||||
|
||||
<div class="queue">
|
||||
<DropZone {app} />
|
||||
<div class="queue-entries {mode}-mode" bind:this={queueList}>
|
||||
{#if _entries.length > 0}
|
||||
{#each _entries as entry}
|
||||
|
||||
78
src/lib/components/DropZone.svelte
Normal file
78
src/lib/components/DropZone.svelte
Normal file
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { writable, type Writable } from "svelte/store";
|
||||
import type ComfyApp from "./ComfyApp";
|
||||
|
||||
export let app: ComfyApp;
|
||||
let a1111Prompt: Writable<any | null> = writable(null);
|
||||
let dropZone: HTMLDivElement | null = null;
|
||||
let disabled = false;
|
||||
|
||||
$: a1111Prompt = app.a1111Prompt;
|
||||
|
||||
$: disabled = a1111Prompt && $a1111Prompt;
|
||||
|
||||
$: if (disabled) {
|
||||
hideDropZone();
|
||||
}
|
||||
|
||||
function showDropZone() {
|
||||
if (dropZone && !disabled)
|
||||
dropZone.style.display = "block";
|
||||
}
|
||||
|
||||
function hideDropZone() {
|
||||
if (dropZone)
|
||||
dropZone.style.display = "none";
|
||||
}
|
||||
|
||||
function allowDrag(event: DragEvent) {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
if (event.dataTransfer != null && event.dataTransfer.items?.length > 0) {
|
||||
event.dataTransfer.dropEffect = 'copy';
|
||||
showDropZone();
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDrop(event: DragEvent) {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
hideDropZone();
|
||||
|
||||
if (event.dataTransfer != null && event.dataTransfer.files.length > 0) {
|
||||
await app.handleFile(event.dataTransfer.files[0]);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:dragenter={showDropZone} />
|
||||
|
||||
{#if !disabled}
|
||||
<div id="dropzone"
|
||||
class="dropzone"
|
||||
bind:this={dropZone}
|
||||
on:dragover={allowDrag}
|
||||
on:dragleave={hideDropZone}
|
||||
on:drop={handleDrop}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
.dropzone {
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
background: #60a7dc80;
|
||||
border: 4px dashed #60a7dc;
|
||||
}
|
||||
</style>
|
||||
@@ -94,5 +94,6 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--spacing-sm);
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
export let isMobile: boolean = false;
|
||||
|
||||
let attrsChanged: Writable<boolean> | null = null;
|
||||
let children: IDragItem[] | null = null;
|
||||
let children: IDragItem[] = [];
|
||||
const flipDurationMs = 100;
|
||||
|
||||
let selectedIndex: number = 0;
|
||||
@@ -35,7 +35,7 @@
|
||||
attrsChanged = container.attrsChanged
|
||||
}
|
||||
else {
|
||||
children = null;
|
||||
children = [];
|
||||
attrsChanged = null
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if container && Array.isArray(children)}
|
||||
{#if container}
|
||||
{@const selected = $uiState.uiUnlocked && $selectionState.currentSelection.includes(container.id)}
|
||||
<div class="container {container.attrs.direction} {container.attrs.classes} {classes.join(' ')} z-index{zIndex}"
|
||||
class:hide-block={container.attrs.containerVariant === "hidden"}
|
||||
|
||||
377
src/lib/convertA1111ToStdPrompt.ts
Normal file
377
src/lib/convertA1111ToStdPrompt.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
import type { ComfyBoxStdGroupAestheticEmbedding, ComfyBoxStdGroupCheckpoint, ComfyBoxStdGroupDDetailer, ComfyBoxStdGroupDynamicThresholding, ComfyBoxStdGroupHypernetwork, ComfyBoxStdGroupKSampler, ComfyBoxStdGroupLatentImage, ComfyBoxStdGroupLatentUpscale, ComfyBoxStdGroupLoRA, ComfyBoxStdGroupSelfAttentionGuidance, ComfyBoxStdParameters, ComfyBoxStdPrompt } from "./ComfyBoxStdPrompt";
|
||||
import type { A1111ParsedInfotext } from "./parseA1111";
|
||||
|
||||
function getSamplerAndScheduler(a1111Sampler: string): [string, string] {
|
||||
let name = a1111Sampler.toLowerCase().replace("++", "pp").replaceAll(" ", "_");
|
||||
let scheduler = "normal";
|
||||
if (name.includes("karras")) {
|
||||
name = name.replace("karras", "").replace(/_+$/, "");
|
||||
scheduler = "karras";
|
||||
} else {
|
||||
scheduler = "normal"
|
||||
}
|
||||
return [name, scheduler]
|
||||
}
|
||||
|
||||
const reAddNetModelName = /^([^(]+)\((.+)\)$/;
|
||||
|
||||
function parseAddNetModelNameAndHash(name: string | null): [string | undefined, string | undefined] {
|
||||
if (!name)
|
||||
return [undefined, undefined]
|
||||
|
||||
const match = name.match(reAddNetModelName);
|
||||
if (match != null) {
|
||||
return [match[1], match[2]]
|
||||
}
|
||||
return [undefined, undefined]
|
||||
}
|
||||
|
||||
const reDDetailerModelName = /(.+)\s\[(.+)\]/;
|
||||
|
||||
function parseDDetailerModelNameAndHash(name: string | null): [string | undefined, string | undefined] {
|
||||
if (!name || name === "None")
|
||||
return [undefined, undefined]
|
||||
|
||||
// bbox\mmdet_anime-face_yolov3.pth [51e1af4a]
|
||||
const match = name.match(reDDetailerModelName);
|
||||
if (match != null) {
|
||||
return [match[1], match[2]]
|
||||
}
|
||||
return [undefined, undefined]
|
||||
}
|
||||
|
||||
export default function convertA1111ToStdPrompt(infotext: A1111ParsedInfotext): ComfyBoxStdPrompt {
|
||||
const popOpt = (name: string): string | undefined => {
|
||||
const v = infotext.extraParams[name];
|
||||
delete infotext.extraParams[name];
|
||||
return v;
|
||||
}
|
||||
|
||||
const parameters: ComfyBoxStdParameters = {}
|
||||
|
||||
parameters.conditioning = [
|
||||
{
|
||||
"$meta": {
|
||||
types: ["positive"]
|
||||
},
|
||||
text: infotext.positive,
|
||||
},
|
||||
{
|
||||
"$meta": {
|
||||
types: ["negative"]
|
||||
},
|
||||
text: infotext.negative,
|
||||
}
|
||||
]
|
||||
|
||||
const hrUp = popOpt("hires upscale");
|
||||
const hrSz = popOpt("hires resize");
|
||||
let hrScaleBy = hrUp ? parseFloat(hrUp) : undefined;
|
||||
let hrMethod = popOpt("hires upscaler");
|
||||
let hrSteps = popOpt("hires steps");
|
||||
let hrWidth = undefined
|
||||
let hrHeight = undefined
|
||||
if (hrSz) {
|
||||
[hrWidth, hrHeight] = hrSz.split(hrSz).map(parseInt);
|
||||
}
|
||||
|
||||
const latent_image: ComfyBoxStdGroupLatentImage = {
|
||||
width: infotext.width,
|
||||
height: infotext.height,
|
||||
// type: "empty", // detect txt2img???
|
||||
batch_count: infotext.batchSize,
|
||||
batch_pos: infotext.batchPos,
|
||||
}
|
||||
|
||||
const maskBlur = popOpt("mask blur")
|
||||
if (maskBlur != null)
|
||||
latent_image.mask_blur = parseFloat(maskBlur)
|
||||
|
||||
parameters.latent_image = [latent_image];
|
||||
|
||||
if (hrMethod != null) {
|
||||
let uw, uh;
|
||||
if (hrScaleBy) {
|
||||
uw = infotext.width * hrScaleBy;
|
||||
uh = infotext.height * hrScaleBy;
|
||||
} else {
|
||||
if (hrWidth == null || hrHeight == null)
|
||||
throw new Error("Highres prompt didn't have width/height!")
|
||||
uw = +hrWidth;
|
||||
uh = +hrHeight;
|
||||
}
|
||||
const hr: ComfyBoxStdGroupLatentUpscale = {
|
||||
width: uw,
|
||||
height: uh,
|
||||
upscale_by: hrScaleBy,
|
||||
upscale_method: hrMethod
|
||||
}
|
||||
parameters.latent_upscale = [hr];
|
||||
}
|
||||
|
||||
const [sampler_name, scheduler] = getSamplerAndScheduler(infotext.sampler)
|
||||
|
||||
const k_sampler: ComfyBoxStdGroupKSampler = {
|
||||
steps: infotext.steps,
|
||||
seed: infotext.seed,
|
||||
cfg_scale: infotext.cfgScale,
|
||||
denoise: hrMethod != null ? 1.0 : infotext.denoise || 1.0, // detect img2img???
|
||||
sampler_name,
|
||||
scheduler,
|
||||
}
|
||||
parameters.k_sampler = [k_sampler];
|
||||
|
||||
if (hrMethod != null) {
|
||||
const k_sampler_hr: ComfyBoxStdGroupKSampler = {
|
||||
"$meta": {
|
||||
types: ["upscale"]
|
||||
},
|
||||
steps: hrSteps != null ? parseInt(hrSteps) : infotext.steps,
|
||||
seed: infotext.seed,
|
||||
cfg_scale: infotext.cfgScale,
|
||||
denoise: infotext.denoise || 1.0,
|
||||
sampler_name,
|
||||
scheduler,
|
||||
}
|
||||
parameters.k_sampler.push(k_sampler_hr)
|
||||
}
|
||||
|
||||
if (infotext.modelHash || infotext.modelName) {
|
||||
const checkpoint: ComfyBoxStdGroupCheckpoint = {
|
||||
model_name: infotext.modelName,
|
||||
model_hashes: {
|
||||
a1111_shorthash: infotext.modelHash
|
||||
}
|
||||
}
|
||||
parameters.checkpoint = [checkpoint]
|
||||
}
|
||||
|
||||
if ("clip skip" in infotext.extraParams) {
|
||||
const clipSkip = popOpt("clip skip")
|
||||
parameters.clip = [{
|
||||
clip_skip: parseInt(clipSkip)
|
||||
}]
|
||||
}
|
||||
|
||||
if ("sd upscale upscaler" in infotext.extraParams) {
|
||||
const sdUpscaleUpscaler = popOpt("sd upscale upscaler")
|
||||
const sdUpscaleOverlap = popOpt("sd upscale overlap") || "64"
|
||||
parameters.sd_upscale = [{
|
||||
upscaler: sdUpscaleUpscaler,
|
||||
overlap: parseInt(sdUpscaleOverlap)
|
||||
}]
|
||||
}
|
||||
|
||||
if ("aesthetic embedding" in infotext.extraParams) {
|
||||
const slerp = popOpt("aesthetic slerp") === "True"
|
||||
const aesthetic_embedding: ComfyBoxStdGroupAestheticEmbedding = {
|
||||
model_name: popOpt("aesthetic embedding"),
|
||||
lr: parseFloat(popOpt("aesthetic lr")),
|
||||
slerp,
|
||||
slerp_angle: parseFloat(popOpt("aesthetic slerp angle")),
|
||||
steps: parseInt(popOpt("aesthetic steps")),
|
||||
text: popOpt("aesthetic text"),
|
||||
text_negative: popOpt("aesthetic text negative") === "True",
|
||||
weight: parseFloat(popOpt("aesthetic weight")),
|
||||
}
|
||||
parameters.aesthetic_embedding = [aesthetic_embedding]
|
||||
}
|
||||
|
||||
if ("dynamic thresholding enabled" in infotext.extraParams) {
|
||||
const dtEnabled = popOpt("dynamic thresholding enabled")
|
||||
if (dtEnabled === "True") {
|
||||
const dynamic_thresholding: ComfyBoxStdGroupDynamicThresholding = {
|
||||
mimic_scale: parseInt(popOpt("mimic scale")),
|
||||
threshold_percentile: parseFloat(popOpt("threshold percentile")),
|
||||
mimic_mode: popOpt("mimic mode"),
|
||||
mimic_scale_minimum: parseFloat(popOpt("mimic scale minimum")),
|
||||
cfg_mode: popOpt("cfg mode"),
|
||||
cfg_scale_minimum: parseFloat(popOpt("cfg scale minimum")),
|
||||
}
|
||||
parameters.dynamic_thresholding = [dynamic_thresholding]
|
||||
}
|
||||
}
|
||||
|
||||
if ("sag guidance scale" in infotext.extraParams) {
|
||||
const self_attention_guidance: ComfyBoxStdGroupSelfAttentionGuidance = {
|
||||
guidance_scale: parseFloat(popOpt("sag guidance scale")),
|
||||
mask_threshold: parseFloat(popOpt("sag mask threshold")),
|
||||
}
|
||||
parameters.self_attention_guidance = [self_attention_guidance]
|
||||
}
|
||||
|
||||
if ("ddetailer prompt" in infotext.extraParams) {
|
||||
const positive_prompt = popOpt("ddetailer prompt")
|
||||
const negative_prompt = popOpt("ddetailer neg prompt")
|
||||
const bitwise = popOpt("ddetailer bitwise")
|
||||
const denoise = parseFloat(popOpt("ddetailer denoising"))
|
||||
const inpaint_full = popOpt("ddetailer inpaint full") === "True"
|
||||
const inpaint_padding = parseInt(popOpt("ddetailer inpaint padding"))
|
||||
const mask_blur = parseFloat(popOpt("ddetailer mask blur"))
|
||||
const cfg = parseFloat(popOpt("ddetailer cfg"))
|
||||
|
||||
const preprocess_b = popOpt("ddetailer preprocess b") === "True"
|
||||
|
||||
const [model_a, model_a_shorthash] = parseDDetailerModelNameAndHash(popOpt("ddetailer model a"))
|
||||
const [model_b, model_b_shorthash] = parseDDetailerModelNameAndHash(popOpt("ddetailer model b"))
|
||||
|
||||
const ddetailer_a: ComfyBoxStdGroupDDetailer = {
|
||||
positive_prompt,
|
||||
negative_prompt,
|
||||
bitwise,
|
||||
denoise,
|
||||
inpaint_full,
|
||||
inpaint_padding,
|
||||
mask_blur,
|
||||
cfg,
|
||||
model: model_a,
|
||||
model_hashes: model_a_shorthash ? {
|
||||
a1111_shorthash: model_a_shorthash
|
||||
} : undefined,
|
||||
preprocess: !preprocess_b,
|
||||
conf: parseFloat(popOpt("ddetailer conf a")),
|
||||
dilation: parseFloat(popOpt("ddetailer dilation a")),
|
||||
offset_x: parseFloat(popOpt("ddetailer offset x a")),
|
||||
offset_y: parseFloat(popOpt("ddetailer offset y a")),
|
||||
}
|
||||
const ddetailer_b: ComfyBoxStdGroupDDetailer = {
|
||||
positive_prompt,
|
||||
negative_prompt,
|
||||
bitwise,
|
||||
denoise,
|
||||
inpaint_full,
|
||||
inpaint_padding,
|
||||
mask_blur,
|
||||
cfg,
|
||||
model: model_b,
|
||||
model_hashes: model_b_shorthash ? {
|
||||
a1111_shorthash: model_b_shorthash
|
||||
} : undefined,
|
||||
preprocess: preprocess_b,
|
||||
conf: parseFloat(popOpt("ddetailer conf b")),
|
||||
dilation: parseFloat(popOpt("ddetailer dilation b")),
|
||||
offset_x: parseFloat(popOpt("ddetailer offset x b")),
|
||||
offset_y: parseFloat(popOpt("ddetailer offset y b")),
|
||||
}
|
||||
parameters.ddetailer = [ddetailer_a, ddetailer_b]
|
||||
}
|
||||
|
||||
// TODO ControlNet
|
||||
|
||||
for (const [extraNetworkType, extraNetworks] of Object.entries(infotext.extraNetworks)) {
|
||||
for (const extraNetworkParams of extraNetworks) {
|
||||
let strength;
|
||||
switch (extraNetworkType.toLowerCase()) {
|
||||
case "lora":
|
||||
case "locon":
|
||||
case "lyco":
|
||||
strength = parseFloat(extraNetworkParams.items[1]);
|
||||
const lora: ComfyBoxStdGroupLoRA = {
|
||||
module_name: extraNetworkType.toLowerCase(),
|
||||
model_name: extraNetworkParams.items[0],
|
||||
strength_unet: strength,
|
||||
strength_tenc: strength,
|
||||
}
|
||||
if (parameters.lora)
|
||||
parameters.lora.push(lora)
|
||||
else
|
||||
parameters.lora = [lora]
|
||||
break;
|
||||
case "hypernet":
|
||||
strength = parseFloat(extraNetworkParams.items[1]);
|
||||
const hypernetwork: ComfyBoxStdGroupHypernetwork = {
|
||||
model_name: extraNetworkParams.items[0],
|
||||
strength
|
||||
}
|
||||
if (parameters.hypernetwork)
|
||||
parameters.hypernetwork.push(hypernetwork)
|
||||
else
|
||||
parameters.hypernetwork = [hypernetwork]
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete infotext.extraNetworks[extraNetworkType]
|
||||
}
|
||||
|
||||
let index = 1;
|
||||
let found = infotext.extraParams[`addnet module ${index}`]
|
||||
while (`addnet module ${index}` in infotext.extraParams) {
|
||||
popOpt("addnet enabled")
|
||||
const moduleName = popOpt(`addnet module ${index}`)
|
||||
const modelName = popOpt(`addnet model ${index}`);
|
||||
const weight = popOpt(`addnet weight ${index}`);
|
||||
let weightA = popOpt(`addnet weight a ${index}`);
|
||||
let weightB = popOpt(`addnet weight b ${index}`);
|
||||
|
||||
if (weightA == null || weightB == null) {
|
||||
// linked weights before addnet version update
|
||||
weightA = weight;
|
||||
weightB = weight;
|
||||
}
|
||||
|
||||
if (moduleName == null || modelName == null || weightA == null || weightB == null) {
|
||||
throw new Error(`Error parsing addnet model params: ${moduleName} ${modelName} ${weightA} ${weightB}`)
|
||||
}
|
||||
|
||||
if (moduleName !== "LoRA") {
|
||||
throw new Error("Unknown AddNet model type " + moduleName)
|
||||
}
|
||||
|
||||
const [name, hash] = parseAddNetModelNameAndHash(modelName);
|
||||
if (name == null || hash == null) {
|
||||
throw new Error("Error parsing addnet model name: " + JSON.stringify(modelName));
|
||||
}
|
||||
|
||||
let shorthash = undefined
|
||||
let shorthash_legacy = undefined
|
||||
if (hash.length > 8) {
|
||||
// new method using safetensors hash
|
||||
shorthash = hash
|
||||
}
|
||||
else {
|
||||
// old hash using webui's 0x10000 hashing method
|
||||
shorthash_legacy = hash
|
||||
}
|
||||
|
||||
const lora: ComfyBoxStdGroupLoRA = {
|
||||
model_name: name,
|
||||
module_name: moduleName,
|
||||
model_hashes: {
|
||||
addnet_shorthash: shorthash,
|
||||
addnet_shorthash_legacy: shorthash_legacy
|
||||
},
|
||||
strength_unet: parseFloat(weightA),
|
||||
strength_tenc: parseFloat(weightB),
|
||||
}
|
||||
if (parameters.lora)
|
||||
parameters.lora.push(lora)
|
||||
else
|
||||
parameters.lora = [lora]
|
||||
|
||||
index += 1;
|
||||
found = infotext.extraParams[`addnet model ${index}`]
|
||||
}
|
||||
|
||||
let app_version = popOpt("version")
|
||||
|
||||
const extra_data: Record<string, any> = {};
|
||||
if (Object.keys(infotext.extraParams).length > 0) {
|
||||
extra_data.a1111 = { params: infotext.extraParams }
|
||||
}
|
||||
|
||||
const prompt: ComfyBoxStdPrompt = {
|
||||
version: 1,
|
||||
metadata: {
|
||||
created_with: "stable-diffusion-webui",
|
||||
app_version,
|
||||
extra_data
|
||||
},
|
||||
parameters
|
||||
}
|
||||
|
||||
console.warn("Unhandled A1111 parameters:", infotext.extraParams, infotext.extraNetworks)
|
||||
|
||||
return prompt
|
||||
}
|
||||
@@ -312,7 +312,9 @@ export class ComfyExecuteSubgraphAction extends ComfyGraphNode {
|
||||
if (!app)
|
||||
return;
|
||||
|
||||
app.queuePrompt(0, 1, tag);
|
||||
// Hold control to queue at the front
|
||||
const num = app.ctrlDown ? -1 : 0;
|
||||
app.queuePrompt(num, 1, tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@ export default class ComfyComboNode extends ComfyWidgetNode<string> {
|
||||
// Wait until the initial graph load for combo to be valid.
|
||||
firstLoad: Writable<boolean>;
|
||||
lightUp: Writable<boolean>;
|
||||
valuesForCombo: Writable<any[] | null>; // Changed when the combo box has values.
|
||||
valuesForCombo: Writable<any[]>; // Changed when the combo box has values.
|
||||
|
||||
constructor(name?: string) {
|
||||
super(name, "A")
|
||||
this.firstLoad = writable(false)
|
||||
this.lightUp = writable(true)
|
||||
this.valuesForCombo = writable(null)
|
||||
this.valuesForCombo = writable([])
|
||||
}
|
||||
|
||||
override onPropertyChanged(property: any, value: any) {
|
||||
|
||||
166
src/lib/parseA1111.ts
Normal file
166
src/lib/parseA1111.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
interface ExtraNetworkParams {
|
||||
items: string[];
|
||||
}
|
||||
|
||||
export type A1111ParsedInfotext = {
|
||||
positive: string,
|
||||
negative: string,
|
||||
|
||||
steps: number,
|
||||
cfgScale: number,
|
||||
width: number,
|
||||
height: number,
|
||||
modelHash?: string,
|
||||
modelName?: string,
|
||||
batchSize?: number,
|
||||
batchPos?: number,
|
||||
sampler: string,
|
||||
seed: number,
|
||||
denoise?: number,
|
||||
|
||||
extraNetworks: Record<string, ExtraNetworkParams[]>
|
||||
extraParams: Record<string, any>
|
||||
}
|
||||
|
||||
export type A1111ParsingError = {
|
||||
error: string
|
||||
}
|
||||
|
||||
const reExtraNetworks = /<(\w+):([^>]+)>/g;
|
||||
const reParam = /\s*([\w ]+):\s*("(?:\\"[^,]|\\"|\\|[^\"])+"|[^,]*)(?:,|$)/g;
|
||||
|
||||
function parseExtraNetworks(prompt: string): [string, Record<string, ExtraNetworkParams[]>] {
|
||||
const res: Record<string, ExtraNetworkParams[]> = {};
|
||||
|
||||
function found(_match: string, modelType: string, args: string): string {
|
||||
if (!res[modelType]) {
|
||||
res[modelType] = [];
|
||||
}
|
||||
|
||||
res[modelType].push({ items: args.split(":") });
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
prompt = prompt.replace(reExtraNetworks, found);
|
||||
|
||||
return [prompt, res];
|
||||
}
|
||||
|
||||
type A1111ParamHandler = string | ((prompt: A1111ParsedInfotext, value: string) => void);
|
||||
|
||||
const wrapFloat = (name: string): ((p: A1111ParsedInfotext, v: string) => void) => {
|
||||
return (p, v) => {
|
||||
p[name] = parseFloat(v);
|
||||
}
|
||||
}
|
||||
|
||||
const wrapInt = (name: string): A1111ParamHandler => {
|
||||
return (p, v) => {
|
||||
p[name] = parseInt(v);
|
||||
}
|
||||
}
|
||||
|
||||
const handlers: Record<string, A1111ParamHandler> = {
|
||||
steps: wrapInt("steps"),
|
||||
"cfg scale": wrapFloat("cfgScale"),
|
||||
"size": (p, v) => {
|
||||
const [widthStr, heightStr] = v.split("x")
|
||||
p.width = parseInt(widthStr);
|
||||
p.height = parseInt(heightStr);
|
||||
},
|
||||
"model hash": "modelHash",
|
||||
model: "modelName",
|
||||
"batch size": wrapInt("batchSize"),
|
||||
"batch pos": wrapInt("batchPos"),
|
||||
sampler: "sampler",
|
||||
seed: wrapInt("seed"),
|
||||
"denoising strength": wrapFloat("denoise")
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses AUTOMATIC1111/stable-diffusion-webui format infotext into their raw parameters.
|
||||
*
|
||||
* Format is as follows:
|
||||
* - Prompt text immediately starts at the start of the file, ending
|
||||
* on the first line starting with "Negative prompt:" or "Steps:"
|
||||
* - "Negative prompt:" is optional and might be omitted
|
||||
* - Following "Steps:" are various sort-of-comma-separated values.
|
||||
* Random characters can completely break parsing. Here be dragons.
|
||||
*/
|
||||
export default function parseA1111(infotext: string): A1111ParsedInfotext | A1111ParsingError {
|
||||
let doneWithPrompt = false;
|
||||
|
||||
let positive_ = ""
|
||||
let negative = ""
|
||||
|
||||
const lines = infotext.trim().split("\n")
|
||||
let lastLineIdx = lines.findIndex(l => l.trim().indexOf("Steps: ") !== -1)
|
||||
if (lastLineIdx === -1) {
|
||||
return { error: "Steps: line not found" }
|
||||
}
|
||||
|
||||
for (let index = 0; index < lastLineIdx; index++) {
|
||||
let line = lines[index].trim()
|
||||
if (line.startsWith("Negative prompt:")) {
|
||||
doneWithPrompt = true;
|
||||
line = line.substring(16).trim();
|
||||
}
|
||||
|
||||
if (doneWithPrompt) {
|
||||
const addNewLine = negative != ""
|
||||
negative += (addNewLine ? "\n" : "") + line
|
||||
}
|
||||
else {
|
||||
const addNewLine = positive_ != ""
|
||||
positive_ += (addNewLine ? "\n" : "") + line
|
||||
}
|
||||
}
|
||||
|
||||
// webui doesn't apply extra networks in the negative prompt
|
||||
let [positive, extraNetworks] = parseExtraNetworks(positive_)
|
||||
|
||||
const extraParams: Record<string, string> = {}
|
||||
|
||||
let result: A1111ParsedInfotext = {
|
||||
positive,
|
||||
negative,
|
||||
|
||||
// defaults taken from webui
|
||||
width: 512,
|
||||
height: 512,
|
||||
steps: 20,
|
||||
cfgScale: 7.0,
|
||||
seed: -1,
|
||||
sampler: "Euler",
|
||||
|
||||
extraNetworks,
|
||||
extraParams
|
||||
}
|
||||
|
||||
for (let index = lastLineIdx; index < lines.length; index++) {
|
||||
const line = lines[index];
|
||||
for (let [_, key, value] of line.matchAll(reParam)) {
|
||||
key = key.toLowerCase()
|
||||
if (value[0] === '"' && value[value.length - 1] === '""')
|
||||
value = value.substring(1, value.length - 1)
|
||||
|
||||
const handler = handlers[key]
|
||||
if (handler != null) {
|
||||
if (value != null) {
|
||||
if (typeof handler === "function") {
|
||||
handler(result, value)
|
||||
}
|
||||
else {
|
||||
(result as any)[handler] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
extraParams[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -39,8 +39,10 @@ export type LayoutAttributes = {
|
||||
queuePromptButtonName: string,
|
||||
|
||||
/*
|
||||
* If true, clicking the "Queue Prompt" button will run the default subgraph.
|
||||
* Set this to false if you need special behavior before running any subgraphs.
|
||||
* If true, clicking the "Queue Prompt" button will run the default
|
||||
* subgraph. Set this to false if you need special behavior before running
|
||||
* any subgraphs, and instead use the `onDefaultQueueAction` event of the
|
||||
* Comfy.QueueEvents node.
|
||||
*/
|
||||
queuePromptButtonRunWorkflow: boolean,
|
||||
}
|
||||
@@ -84,6 +86,9 @@ export type LayoutState = {
|
||||
*/
|
||||
attrs: LayoutAttributes
|
||||
|
||||
/*
|
||||
* Increment to force Svelte to re-render the props panel
|
||||
*/
|
||||
refreshPropsPanel: Writable<number>
|
||||
}
|
||||
|
||||
@@ -102,7 +107,7 @@ export type Attributes = {
|
||||
title: string,
|
||||
|
||||
/*
|
||||
* List of classes to apply to the component.
|
||||
* List of CSS classes to apply to the component.
|
||||
*/
|
||||
classes: string,
|
||||
|
||||
@@ -204,22 +209,22 @@ export type AttributesSpec = {
|
||||
values?: string[],
|
||||
|
||||
/*
|
||||
* If `type` is "number", step for the slider
|
||||
* If `type` is "number", step for the slider that edits this attribute
|
||||
*/
|
||||
step?: number,
|
||||
|
||||
/*
|
||||
* If `type` is "number", min for the slider
|
||||
* If `type` is "number", min for the slider that edits this attribute
|
||||
*/
|
||||
min?: number,
|
||||
|
||||
/*
|
||||
* If `type` is "number", max for the slider
|
||||
* If `type` is "number", max for the slider that edits this attribute
|
||||
*/
|
||||
max?: number,
|
||||
|
||||
/*
|
||||
* If `type` is "string", display as a textarea.
|
||||
* If `type` is "string", display as a textarea instead of an input.
|
||||
*/
|
||||
multiline?: boolean,
|
||||
|
||||
@@ -863,8 +868,9 @@ function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
||||
let prevWidget = state.allItemsByNode[node.id]
|
||||
if (prevWidget == null) {
|
||||
// If a subgraph was cloned, try looking for the original widget node corresponding to the new widget node being added.
|
||||
// `node` is the new ComfyWidgetNode instance to copy attrs to.
|
||||
// `options.cloneData` should contain the results of Subgraph.clone(), called "subgraphNewIDMapping".
|
||||
// `node` is the new ComfyWidgetNode instance to copy layout attrs to.
|
||||
// `options.cloneData` should contain the results of Subgraph.clone(), which is named "subgraphNewIDMapping" in an
|
||||
// entry of the `forNode` Record.
|
||||
// `options.cloneData` is attached to the onNodeAdded options if a node is added to a graph after being
|
||||
// selection-cloned or pasted, as they both call clone() internally.
|
||||
const cloneData = options.cloneData.forNode[options.prevNodeID]
|
||||
@@ -879,7 +885,7 @@ function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
|
||||
if (nodeIDInLayoutState) {
|
||||
// Gottem.
|
||||
prevWidget = state.allItemsByNode[nodeIDInLayoutState]
|
||||
console.warn("FOUND CLONED SUBGRAPH NODE", node.id, "=>", nodeIDInLayoutState, prevWidget)
|
||||
// console.warn("FOUND CLONED SUBGRAPH NODE", node.id, "=>", nodeIDInLayoutState, prevWidget)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@ import type { ComfyExecutionResult } from "$lib/nodes/ComfyWidgetNodes";
|
||||
import notify from "$lib/notify";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
|
||||
export type QueueItem = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown";
|
||||
|
||||
type QueueStateOps = {
|
||||
@@ -23,8 +19,13 @@ type QueueStateOps = {
|
||||
onExecuted: (promptID: PromptID, nodeID: ComfyNodeID, output: ComfyExecutionResult) => void
|
||||
}
|
||||
|
||||
/*
|
||||
* Single job that the backend keeps track of.
|
||||
*/
|
||||
export type QueueEntry = {
|
||||
/* Data preserved on page refresh */
|
||||
/*** Data preserved on page refresh ***/
|
||||
|
||||
/** Priority of the prompt. -1 means to queue at the front. */
|
||||
number: number,
|
||||
queuedAt?: Date,
|
||||
finishedAt?: Date,
|
||||
@@ -33,23 +34,34 @@ export type QueueEntry = {
|
||||
extraData: ComfyBoxPromptExtraData,
|
||||
goodOutputs: ComfyNodeID[],
|
||||
|
||||
/* Data not sent by ComfyUI's API, lost on page refresh */
|
||||
/*** Data not sent by ComfyUI's API, lost on page refresh ***/
|
||||
|
||||
/* Prompt outputs, collected while the prompt is still executing */
|
||||
outputs: SerializedPromptOutputs,
|
||||
|
||||
/* Nodes in of the workflow that have finished running so far. */
|
||||
/* Nodes of the workflow that have finished running so far. */
|
||||
nodesRan: Set<ComfyNodeID>,
|
||||
/* Nodes of the workflow the backend reported as cached. */
|
||||
cachedNodes: Set<ComfyNodeID>
|
||||
}
|
||||
|
||||
/*
|
||||
* Represents a queue entry that has finished executing (suceeded or failed) and
|
||||
* has been moved to the history.
|
||||
*/
|
||||
export type CompletedQueueEntry = {
|
||||
/** Corresponding entry in the queue, for the prompt/extra data */
|
||||
entry: QueueEntry,
|
||||
/** The result of this prompt, success/failed/cached */
|
||||
status: QueueEntryStatus,
|
||||
/** Message to display in the frontend */
|
||||
message?: string,
|
||||
/** Detailed error/stacktrace, perhaps inspectible with a popup */
|
||||
error?: string,
|
||||
}
|
||||
|
||||
/*
|
||||
* Keeps track of queued and completed (history) prompts.
|
||||
*/
|
||||
export type QueueState = {
|
||||
queueRunning: Writable<QueueEntry[]>,
|
||||
queuePending: Writable<QueueEntry[]>,
|
||||
@@ -57,6 +69,11 @@ export type QueueState = {
|
||||
queueRemaining: number | "X" | null;
|
||||
runningNodeID: ComfyNodeID | null;
|
||||
progress: Progress | null,
|
||||
/**
|
||||
* If true, user pressed the "Interrupt" button in the frontend. Disable the
|
||||
* button and wait until the next prompt starts running to re-enable it
|
||||
* again
|
||||
*/
|
||||
isInterrupting: boolean
|
||||
}
|
||||
type WritableQueueStateStore = Writable<QueueState> & QueueStateOps;
|
||||
@@ -159,6 +176,7 @@ function moveToCompleted(index: number, queue: Writable<QueueEntry[]>, status: Q
|
||||
return qc
|
||||
})
|
||||
|
||||
state.isInterrupting = false;
|
||||
store.set(state)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
export let widget: WidgetLayout | null = null;
|
||||
export let isMobile: boolean = false;
|
||||
let node: ComfyComboNode | null = null;
|
||||
let nodeValue: Writable<string> | null = null;
|
||||
let nodeValue: Writable<string> = writable("");
|
||||
let propsChanged: Writable<number> | null = null;
|
||||
let lightUp: Writable<boolean> = writable(false);
|
||||
let valuesForCombo: Writable<any[]> | null = null;
|
||||
let valuesForCombo: Writable<any[]> = writable([])
|
||||
let lastConfigured: any = null;
|
||||
let option: any = null;
|
||||
|
||||
@@ -132,77 +132,69 @@
|
||||
</script>
|
||||
|
||||
<div class="wrapper comfy-combo" class:mobile={isMobile} class:updated={$lightUp}>
|
||||
{#key $valuesForCombo}
|
||||
{#if node !== null && nodeValue !== null}
|
||||
{#if $valuesForCombo == null}
|
||||
<span>Loading...</span>
|
||||
{:else}
|
||||
<label>
|
||||
{#if widget.attrs.title !== ""}
|
||||
<BlockTitle show_label={true}>
|
||||
{widget.attrs.title}
|
||||
<span class="count-text">({$valuesForCombo.length})</span>
|
||||
</BlockTitle>
|
||||
{/if}
|
||||
<Select
|
||||
value={$nodeValue}
|
||||
bind:justValue={option}
|
||||
bind:hoverItemIndex
|
||||
bind:filterText
|
||||
bind:listOpen
|
||||
bind:input
|
||||
items={$valuesForCombo}
|
||||
disabled={isDisabled(widget)}
|
||||
clearable={false}
|
||||
showChevron={true}
|
||||
listAutoWidth={true}
|
||||
inputAttributes={{ autocomplete: 'off' }}
|
||||
on:change
|
||||
on:focus={onFocus}
|
||||
on:hoverItem={(e) => handleHover(e.detail)}
|
||||
on:select={(e) => handleSelect(e.detail.index)}
|
||||
on:blur
|
||||
on:filter={onFilter}>
|
||||
<div class="comfy-select-list" slot="list" let:filteredItems>
|
||||
{#if filteredItems.length > 0}
|
||||
{@const itemSize = isMobile ? 50 : 25}
|
||||
{@const itemsToShow = isMobile ? 10 : 30}
|
||||
<VirtualList
|
||||
items={filteredItems}
|
||||
width="100%"
|
||||
height={Math.min(filteredItems.length, itemsToShow) * itemSize}
|
||||
itemCount={filteredItems.length}
|
||||
{itemSize}
|
||||
overscanCount={5}
|
||||
scrollToIndex={hoverItemIndex}>
|
||||
<div slot="item"
|
||||
class="comfy-select-item"
|
||||
class:mobile={isMobile}
|
||||
let:index={i}
|
||||
let:style
|
||||
{style}
|
||||
class:active={activeIndex === filteredItems[i].index}
|
||||
class:hover={hoverItemIndex === i}
|
||||
on:click={() => handleSelect(filteredItems[i].index)}
|
||||
on:focus={() => handleHover(i)}
|
||||
on:mouseover={() => handleHover(i)}>
|
||||
{@const item = filteredItems[i]}
|
||||
<span class="comfy-select-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
</VirtualList>
|
||||
{:else}
|
||||
<div class="comfy-empty-list">
|
||||
<span>(No items)</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Select>
|
||||
</label>
|
||||
{/if}
|
||||
<label>
|
||||
{#if widget.attrs.title !== ""}
|
||||
<BlockTitle show_label={true}>
|
||||
{widget.attrs.title}
|
||||
<span class="count-text">({$valuesForCombo.length})</span>
|
||||
</BlockTitle>
|
||||
{/if}
|
||||
{/key}
|
||||
<Select
|
||||
value={$valuesForCombo.length === 0 ? "(Loading...)" : $nodeValue}
|
||||
bind:justValue={option}
|
||||
bind:hoverItemIndex
|
||||
bind:filterText
|
||||
bind:listOpen
|
||||
bind:input
|
||||
items={$valuesForCombo}
|
||||
disabled={$valuesForCombo.length === 0 || isDisabled(widget)}
|
||||
clearable={false}
|
||||
showChevron={true}
|
||||
listAutoWidth={true}
|
||||
inputAttributes={{ autocomplete: 'off' }}
|
||||
on:change
|
||||
on:focus={onFocus}
|
||||
on:hoverItem={(e) => handleHover(e.detail)}
|
||||
on:select={(e) => handleSelect(e.detail.index)}
|
||||
on:blur
|
||||
on:filter={onFilter}>
|
||||
<div class="comfy-select-list" slot="list" let:filteredItems>
|
||||
{#if filteredItems.length > 0}
|
||||
{@const itemSize = isMobile ? 50 : 25}
|
||||
{@const itemsToShow = isMobile ? 10 : 30}
|
||||
<VirtualList
|
||||
items={filteredItems}
|
||||
width="100%"
|
||||
height={Math.min(filteredItems.length, itemsToShow) * itemSize}
|
||||
itemCount={filteredItems.length}
|
||||
{itemSize}
|
||||
overscanCount={5}
|
||||
scrollToIndex={hoverItemIndex}>
|
||||
<div slot="item"
|
||||
class="comfy-select-item"
|
||||
class:mobile={isMobile}
|
||||
let:index={i}
|
||||
let:style
|
||||
{style}
|
||||
class:active={activeIndex === filteredItems[i].index}
|
||||
class:hover={hoverItemIndex === i}
|
||||
on:click={() => handleSelect(filteredItems[i].index)}
|
||||
on:focus={() => handleHover(i)}
|
||||
on:mouseover={() => handleHover(i)}>
|
||||
{@const item = filteredItems[i]}
|
||||
<span class="comfy-select-label">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
</VirtualList>
|
||||
{:else}
|
||||
<div class="comfy-empty-list">
|
||||
<span>(No items)</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -257,6 +249,15 @@
|
||||
--item-background-hover: var(--comfy-dropdown-item-background-hover);
|
||||
--item-color-active: var(--comfy-dropdown-item-color-active);
|
||||
--item-background-active: var(--comfy-dropdown-item-background-active);
|
||||
--disabled-color: var(--comfy-disabled-textbox-text-color);
|
||||
--disabled-border-color: var(--comfy-disabled-textbox-border-color);
|
||||
--disabled-background: var(--comfy-disabled-textbox-background-fill);
|
||||
}
|
||||
|
||||
|
||||
:global(.svelte-select.disabled) {
|
||||
--input-color: var(--comfy-disabled-textbox-text-color) !important;
|
||||
--selected-item-color: var(--comfy-disabled-textbox-text-color) !important;
|
||||
}
|
||||
|
||||
:global(.svelte-select-list) {
|
||||
|
||||
308
src/tests/convertA1111ToStdPromptTests.ts
Normal file
308
src/tests/convertA1111ToStdPromptTests.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import convertA1111ToStdPrompt from "$lib/convertA1111ToStdPrompt";
|
||||
import { expect } from 'vitest';
|
||||
import UnitTest from "./UnitTest";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
|
||||
export default class convertA1111ToStdPromptTests extends UnitTest {
|
||||
test__convertsBasic() {
|
||||
const infotext: A1111ParsedInfotext = {
|
||||
positive: "highest quality, masterpiece, best quality, masterpiece, asuka langley sitting cross legged on a chair",
|
||||
negative: "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts,signature, watermark, username, blurry, artist name",
|
||||
height: 512,
|
||||
width: 512,
|
||||
modelHash: "925997e9",
|
||||
cfgScale: 12,
|
||||
sampler: "Euler",
|
||||
seed: 2870305590,
|
||||
steps: 28,
|
||||
extraNetworks: {},
|
||||
extraParams: {
|
||||
"clip skip": "2",
|
||||
"aesthetic embedding": "Belle",
|
||||
"aesthetic lr": "0.0005",
|
||||
"aesthetic slerp": "False",
|
||||
"aesthetic slerp angle": "0.1",
|
||||
"aesthetic steps": "15",
|
||||
"aesthetic text": "",
|
||||
"aesthetic text negative": "False",
|
||||
"aesthetic weight": "0.9",
|
||||
},
|
||||
}
|
||||
|
||||
const converted = convertA1111ToStdPrompt(infotext);
|
||||
|
||||
expect(converted).toEqual({
|
||||
version: 1,
|
||||
metadata: {
|
||||
created_with: "stable-diffusion-webui",
|
||||
extra_data: {}
|
||||
},
|
||||
parameters: {
|
||||
checkpoint: [{
|
||||
model_hashes: {
|
||||
a1111_shorthash: "925997e9",
|
||||
}
|
||||
}],
|
||||
conditioning: [{
|
||||
"$meta": {
|
||||
types: ["positive"]
|
||||
},
|
||||
text: "highest quality, masterpiece, best quality, masterpiece, asuka langley sitting cross legged on a chair",
|
||||
}, {
|
||||
"$meta": {
|
||||
types: ["negative"]
|
||||
},
|
||||
text: "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts,signature, watermark, username, blurry, artist name"
|
||||
}],
|
||||
clip: [{
|
||||
clip_skip: 2,
|
||||
}],
|
||||
k_sampler: [{
|
||||
cfg_scale: 12,
|
||||
denoise: 1,
|
||||
sampler_name: "euler",
|
||||
scheduler: "normal",
|
||||
seed: 2870305590,
|
||||
steps: 28
|
||||
}],
|
||||
latent_image: [{
|
||||
width: 512,
|
||||
height: 512,
|
||||
}],
|
||||
aesthetic_embedding: [{
|
||||
lr: 0.0005,
|
||||
model_name: "Belle",
|
||||
text: "",
|
||||
text_negative: false,
|
||||
slerp: false,
|
||||
slerp_angle: 0.1,
|
||||
steps: 15,
|
||||
weight: 0.9
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => ComfyBoxStdPrompt.parse(converted)).not.toThrow();
|
||||
}
|
||||
|
||||
test__convertsExtraNetworks() {
|
||||
const infotext: A1111ParsedInfotext = {
|
||||
positive: "dreamlike fantasy landscape where everything is a shade of pink,\n dog ",
|
||||
negative: "(worst quality:1.4), (low quality:1.4) , (monochrome:1.1)",
|
||||
width: 640,
|
||||
height: 512,
|
||||
modelHash: "0f0eaaa61e",
|
||||
modelName: "pastelmix-better-vae-fp16",
|
||||
cfgScale: 12,
|
||||
sampler: "DPM++ 2M Karras",
|
||||
seed: 2416682767,
|
||||
steps: 40,
|
||||
denoise: 0.55,
|
||||
extraNetworks: {
|
||||
hypernet: [
|
||||
{ items: ["zxcfc", "0.5", "baz", "quux"], },
|
||||
],
|
||||
lora: [
|
||||
{ items: ["asdfg", "0.8", "foo", "bar"] },
|
||||
],
|
||||
},
|
||||
extraParams: {
|
||||
"clip skip": "2",
|
||||
"ensd": "31337",
|
||||
"hires steps": "20",
|
||||
"hires upscale": "2",
|
||||
"hires upscaler": "Latent",
|
||||
},
|
||||
}
|
||||
|
||||
const converted = convertA1111ToStdPrompt(infotext);
|
||||
|
||||
expect(converted).toEqual({
|
||||
version: 1,
|
||||
metadata: {
|
||||
created_with: "stable-diffusion-webui",
|
||||
extra_data: {
|
||||
a1111: {
|
||||
params: {
|
||||
"ensd": "31337"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
checkpoint: [{
|
||||
model_name: "pastelmix-better-vae-fp16",
|
||||
model_hashes: {
|
||||
a1111_shorthash: "0f0eaaa61e",
|
||||
}
|
||||
}],
|
||||
conditioning: [{
|
||||
"$meta": {
|
||||
types: ["positive"]
|
||||
},
|
||||
text: "dreamlike fantasy landscape where everything is a shade of pink,\n dog ",
|
||||
}, {
|
||||
"$meta": {
|
||||
types: ["negative"]
|
||||
},
|
||||
text: "(worst quality:1.4), (low quality:1.4) , (monochrome:1.1)"
|
||||
}],
|
||||
clip: [{
|
||||
clip_skip: 2,
|
||||
}],
|
||||
hypernetwork: [{
|
||||
model_name: "zxcfc",
|
||||
strength: 0.5,
|
||||
}],
|
||||
lora: [{
|
||||
model_name: "asdfg",
|
||||
module_name: "lora",
|
||||
strength_unet: 0.8,
|
||||
strength_tenc: 0.8,
|
||||
}],
|
||||
k_sampler: [{
|
||||
cfg_scale: 12,
|
||||
denoise: 1,
|
||||
sampler_name: "dpmpp_2m",
|
||||
scheduler: "karras",
|
||||
seed: 2416682767,
|
||||
steps: 40
|
||||
}, {
|
||||
"$meta": {
|
||||
types: ["upscale"]
|
||||
},
|
||||
cfg_scale: 12,
|
||||
denoise: 0.55,
|
||||
sampler_name: "dpmpp_2m",
|
||||
scheduler: "karras",
|
||||
seed: 2416682767,
|
||||
steps: 20
|
||||
}],
|
||||
latent_image: [{
|
||||
width: 640,
|
||||
height: 512,
|
||||
}],
|
||||
latent_upscale: [{
|
||||
width: 1280,
|
||||
height: 1024,
|
||||
upscale_by: 2,
|
||||
upscale_method: "Latent"
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => ComfyBoxStdPrompt.parse(converted)).not.toThrow();
|
||||
}
|
||||
|
||||
test__convertsAdditionalNetworks() {
|
||||
const infotext: A1111ParsedInfotext = {
|
||||
positive: "1girl, pink hair",
|
||||
negative: "(worst quality, low quality:1.4)",
|
||||
width: 512,
|
||||
height: 768,
|
||||
modelHash: "0873291ac5",
|
||||
modelName: "AbyssOrangeMix2_nsfw",
|
||||
cfgScale: 6,
|
||||
sampler: "DPM++ SDE Karras",
|
||||
seed: 780207036,
|
||||
steps: 20,
|
||||
denoise: 0.2,
|
||||
extraNetworks: {},
|
||||
extraParams: {
|
||||
"addnet enabled": "True",
|
||||
"addnet model 1": "ElysiaV3-000002(6d3eb064dcc1)",
|
||||
"addnet model 2": "elfmorie2(a34cd9a8c3cc)",
|
||||
"addnet module 1": "LoRA",
|
||||
"addnet module 2": "LoRA",
|
||||
"addnet weight a 1": "0.9",
|
||||
"addnet weight a 2": "1",
|
||||
"addnet weight b 1": "0.7",
|
||||
"addnet weight b 2": "0.8",
|
||||
"ensd": "31337",
|
||||
"mask blur": "1",
|
||||
"sd upscale overlap": "64",
|
||||
"sd upscale upscaler": "4x_Valar_v1",
|
||||
// XXX: just make sure it doesn't fall over for now
|
||||
// this prompt format I swear...
|
||||
"template": "1girl",
|
||||
"negative template": "(worst quality",
|
||||
}
|
||||
}
|
||||
|
||||
const converted = convertA1111ToStdPrompt(infotext)
|
||||
|
||||
expect(converted).toEqual({
|
||||
version: 1,
|
||||
metadata: {
|
||||
created_with: "stable-diffusion-webui",
|
||||
extra_data: {
|
||||
a1111: {
|
||||
params: {
|
||||
"ensd": "31337",
|
||||
// TODO
|
||||
"template": "1girl",
|
||||
"negative template": "(worst quality",
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
checkpoint: [{
|
||||
model_name: "AbyssOrangeMix2_nsfw",
|
||||
model_hashes: {
|
||||
a1111_shorthash: "0873291ac5",
|
||||
}
|
||||
}],
|
||||
conditioning: [{
|
||||
"$meta": {
|
||||
types: ["positive"]
|
||||
},
|
||||
text: "1girl, pink hair",
|
||||
}, {
|
||||
"$meta": {
|
||||
types: ["negative"]
|
||||
},
|
||||
text: "(worst quality, low quality:1.4)",
|
||||
}],
|
||||
lora: [{
|
||||
module_name: "LoRA",
|
||||
model_name: "ElysiaV3-000002",
|
||||
model_hashes: {
|
||||
addnet_shorthash: "6d3eb064dcc1"
|
||||
},
|
||||
strength_unet: 0.9,
|
||||
strength_tenc: 0.7,
|
||||
},
|
||||
{
|
||||
module_name: "LoRA",
|
||||
model_name: "elfmorie2",
|
||||
model_hashes: {
|
||||
addnet_shorthash: "a34cd9a8c3cc"
|
||||
},
|
||||
strength_unet: 1,
|
||||
strength_tenc: 0.8,
|
||||
}],
|
||||
k_sampler: [{
|
||||
cfg_scale: 6,
|
||||
denoise: 0.2,
|
||||
sampler_name: "dpmpp_sde",
|
||||
scheduler: "karras",
|
||||
seed: 780207036,
|
||||
steps: 20
|
||||
}],
|
||||
latent_image: [{
|
||||
width: 512,
|
||||
height: 768,
|
||||
mask_blur: 1
|
||||
}],
|
||||
sd_upscale: [{
|
||||
upscaler: "4x_Valar_v1",
|
||||
overlap: 64
|
||||
}]
|
||||
}
|
||||
})
|
||||
|
||||
expect(() => ComfyBoxStdPrompt.parse(converted)).not.toThrow();
|
||||
}
|
||||
}
|
||||
158
src/tests/parseA1111Tests.ts
Normal file
158
src/tests/parseA1111Tests.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import parseA1111 from "$lib/parseA1111";
|
||||
import { expect } from 'vitest';
|
||||
import UnitTest from "./UnitTest";
|
||||
|
||||
export default class parseA1111Tests extends UnitTest {
|
||||
test__parsesBasic() {
|
||||
const infotext = `
|
||||
highest quality, masterpiece, best quality, masterpiece, asuka langley sitting cross legged on a chair
|
||||
Negative prompt: lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts,signature, watermark, username, blurry, artist name
|
||||
Size: 512x512, Seed: 2870305590, Steps: 28, Sampler: Euler, CFG scale: 12, Clip skip: 2, Model hash: 925997e9, Aesthetic LR: 0.0005, Aesthetic text: , Aesthetic slerp: False, Aesthetic steps: 15, Aesthetic weight: 0.9, Aesthetic embedding: Belle, Aesthetic slerp angle: 0.1, Aesthetic text negative: False
|
||||
`
|
||||
|
||||
const parsed = parseA1111(infotext);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
positive: "highest quality, masterpiece, best quality, masterpiece, asuka langley sitting cross legged on a chair",
|
||||
negative: "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts,signature, watermark, username, blurry, artist name",
|
||||
height: 512,
|
||||
width: 512,
|
||||
modelHash: "925997e9",
|
||||
cfgScale: 12,
|
||||
sampler: "Euler",
|
||||
seed: 2870305590,
|
||||
steps: 28,
|
||||
extraNetworks: {},
|
||||
extraParams: {
|
||||
"clip skip": "2",
|
||||
"aesthetic embedding": "Belle",
|
||||
"aesthetic lr": "0.0005",
|
||||
"aesthetic slerp": "False",
|
||||
"aesthetic slerp angle": "0.1",
|
||||
"aesthetic steps": "15",
|
||||
"aesthetic text": "",
|
||||
"aesthetic text negative": "False",
|
||||
"aesthetic weight": "0.9",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
test__parsesExtraNetworks() {
|
||||
const infotext = `
|
||||
dreamlike fantasy landscape where everything is a shade of pink,
|
||||
<lora:asdfg:1:foo:bar> dog <hypernet:0.5:baz:quux>
|
||||
Negative prompt: (worst quality:1.4), (low quality:1.4) , (monochrome:1.1)
|
||||
Steps: 40, Sampler: DPM++ 2M Karras, CFG scale: 12, Seed: 2416682767, Size: 640x512, Model hash: 0f0eaaa61e, Model: pastelmix-better-vae-fp16, Denoising strength: 0.55, Clip skip: 2, ENSD: 31337, Hires upscale: 2, Hires steps: 20, Hires upscaler: Latent
|
||||
`
|
||||
const parsed = parseA1111(infotext);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
positive: "dreamlike fantasy landscape where everything is a shade of pink,\n dog ",
|
||||
negative: "(worst quality:1.4), (low quality:1.4) , (monochrome:1.1)",
|
||||
width: 640,
|
||||
height: 512,
|
||||
modelHash: "0f0eaaa61e",
|
||||
modelName: "pastelmix-better-vae-fp16",
|
||||
cfgScale: 12,
|
||||
sampler: "DPM++ 2M Karras",
|
||||
seed: 2416682767,
|
||||
steps: 40,
|
||||
denoise: 0.55,
|
||||
extraNetworks: {
|
||||
hypernet: [
|
||||
{ items: ["0.5", "baz", "quux"], },
|
||||
],
|
||||
lora: [
|
||||
{ items: ["asdfg", "1", "foo", "bar"] },
|
||||
],
|
||||
},
|
||||
extraParams: {
|
||||
"clip skip": "2",
|
||||
"ensd": "31337",
|
||||
"hires steps": "20",
|
||||
"hires upscale": "2",
|
||||
"hires upscaler": "Latent",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
test__parsesXYZGrid() {
|
||||
const infotext = `
|
||||
1girl
|
||||
Negative prompt: (worst quality, low quality:1.4)
|
||||
Steps: 20, Sampler: DPM++ SDE Karras, CFG scale: 5, Seed: 1964718363, Size: 512x512, Model hash: 736a6f43c2, Denoising strength: 0.5, Clip skip: 2, Hires upscale: 1.75, Hires steps: 14, Hires upscaler: Latent (nearest-exact), Script: X/Y/Z plot, X Type: Prompt S/R, X Values: "<lora:cru5rb:0.5> , <lora:cru5rb:0.6>,<lora:cru5rb:0.7>, <lora:cru5rb:0.8> ,<lora:cru5rb:0.9> , <lora:cru5rb:1>,"
|
||||
`
|
||||
|
||||
const parsed = parseA1111(infotext);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
positive: "1girl",
|
||||
negative: "(worst quality, low quality:1.4)",
|
||||
width: 512,
|
||||
height: 512,
|
||||
modelHash: "736a6f43c2",
|
||||
cfgScale: 5,
|
||||
sampler: "DPM++ SDE Karras",
|
||||
seed: 1964718363,
|
||||
steps: 20,
|
||||
denoise: 0.5,
|
||||
extraNetworks: {},
|
||||
extraParams: {
|
||||
"clip skip": "2",
|
||||
"hires steps": "14",
|
||||
"hires upscale": "1.75",
|
||||
"hires upscaler": "Latent (nearest-exact)",
|
||||
"script": "X/Y/Z plot",
|
||||
"x type": "Prompt S/R",
|
||||
"x values": '"<lora:cru5rb:0.5> , <lora:cru5rb:0.6>,<lora:cru5rb:0.7>, <lora:cru5rb:0.8> ,<lora:cru5rb:0.9> , <lora:cru5rb:1>,"',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
test__parsesDynamicPromptsTemplates() {
|
||||
const infotext = `
|
||||
1girl, pink hair
|
||||
Negative prompt: (worst quality, low quality:1.4)
|
||||
Steps: 20, Sampler: DPM++ SDE Karras, CFG scale: 6, Seed: 780207036, Size: 512x768, Model hash: 0873291ac5, Model: AbyssOrangeMix2_nsfw, Denoising strength: 0.2, ENSD: 31337, Mask blur: 1, SD upscale overlap: 64, SD upscale upscaler: 4x_Valar_v1, AddNet Enabled: True, AddNet Module 1: LoRA, AddNet Model 1: ElysiaV3-000002(6d3eb064dcc1), AddNet Weight A 1: 0.9, AddNet Weight B 1: 0.9, AddNet Module 2: LoRA, AddNet Model 2: elfmorie2(a34cd9a8c3cc), AddNet Weight A 2: 1, AddNet Weight B 2: 1
|
||||
Template: 1girl, __haircolor__
|
||||
Negative Template: (worst quality, low quality:1.4), __badprompt__
|
||||
`
|
||||
|
||||
const parsed = parseA1111(infotext);
|
||||
|
||||
expect(parsed).toEqual({
|
||||
positive: "1girl, pink hair",
|
||||
negative: "(worst quality, low quality:1.4)",
|
||||
width: 512,
|
||||
height: 768,
|
||||
modelHash: "0873291ac5",
|
||||
modelName: "AbyssOrangeMix2_nsfw",
|
||||
cfgScale: 6,
|
||||
sampler: "DPM++ SDE Karras",
|
||||
seed: 780207036,
|
||||
steps: 20,
|
||||
denoise: 0.2,
|
||||
extraNetworks: {},
|
||||
extraParams: {
|
||||
"addnet enabled": "True",
|
||||
"addnet model 1": "ElysiaV3-000002(6d3eb064dcc1)",
|
||||
"addnet model 2": "elfmorie2(a34cd9a8c3cc)",
|
||||
"addnet module 1": "LoRA",
|
||||
"addnet module 2": "LoRA",
|
||||
"addnet weight a 1": "0.9",
|
||||
"addnet weight a 2": "1",
|
||||
"addnet weight b 1": "0.9",
|
||||
"addnet weight b 2": "1",
|
||||
"ensd": "31337",
|
||||
"low quality": "1.4)",
|
||||
"mask blur": "1",
|
||||
"sd upscale overlap": "64",
|
||||
"sd upscale upscaler": "4x_Valar_v1",
|
||||
// XXX: just make sure it doesn't fall over for now
|
||||
// this prompt format I swear...
|
||||
"template": "1girl",
|
||||
"negative template": "(worst quality",
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
export { default as ComfyPromptSerializerTests } from "./ComfyPromptSerializerTests"
|
||||
export { default as ComfyGraphTests } from "./ComfyGraphTests"
|
||||
export { default as parseA1111Tests } from "./parseA1111Tests"
|
||||
export { default as convertA1111ToStdPromptTests } from "./convertA1111ToStdPromptTests"
|
||||
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
declare const __GIT_COMMIT_HASH__: string
|
||||
@@ -7,6 +7,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"strict": false,
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"$lib": ["lib"],
|
||||
|
||||
@@ -11,6 +11,9 @@ const isProduction = process.env.NODE_ENV === "production";
|
||||
console.log("Production build: " + isProduction)
|
||||
|
||||
export default defineConfig({
|
||||
define: {
|
||||
"__GIT_COMMIT_HASH__": '"test"'
|
||||
},
|
||||
clearScreen: false,
|
||||
base: "./",
|
||||
plugins: [
|
||||
|
||||
Reference in New Issue
Block a user