Compare commits

..

156 Commits

Author SHA1 Message Date
Chenlei Hu
7a7188aeb0 1.2.34 (#623) 2024-08-24 19:43:53 -04:00
Veikka
2079a54ffa multi-level node search ranking (#622) 2024-08-24 19:42:56 -04:00
Chenlei Hu
74baf2af12 Rename onNonLeafClick to toggleNodeOnEvent (#621) 2024-08-24 18:09:55 -04:00
Chenlei Hu
5dec86861b Replace ToggleButton with Button to be consistent in style (#620) 2024-08-24 17:54:03 -04:00
Chenlei Hu
fc05029f4e Cleanup dragSelector prop (#619) 2024-08-24 17:48:36 -04:00
Chenlei Hu
98064f301d Extract tree expand/collapse logic as hook (#618) 2024-08-24 17:43:26 -04:00
Chenlei Hu
bff1dc91fa Refactor node library drag and drop (#617) 2024-08-24 17:40:56 -04:00
bymyself
f4242f8a66 Explicitly invoke setting.options when it's a function (#616) 2024-08-24 16:22:08 -04:00
Chenlei Hu
845ab88d55 Update string input spec to include extra multiline args (#615) 2024-08-24 15:21:58 -04:00
Chenlei Hu
6c557eaa58 Fix unrecognized bookmark node crash the node library sidebar (#614)
* Add playwright test

* nit
2024-08-24 11:58:14 -04:00
Chenlei Hu
2fdaabd2c9 Ctrl + Click recursively expand/collapse node library folder (#613) 2024-08-24 11:01:10 -04:00
Chenlei Hu
a2143d9120 Bookmark nodes in node library (#612)
* Basic bookmark

* Extract node leaf as component

* bigger hitbox
2024-08-24 10:39:18 -04:00
Chenlei Hu
f2b02dd10b Add userFileStore (#611)
* WIP

* Refactor

* Add userFileStore test
2024-08-23 21:39:22 -04:00
Chenlei Hu
b831a82360 Target ES2022 in vite config to align with tsconfig target (#610) 2024-08-23 21:11:45 -04:00
Chenlei Hu
d232e38c33 1.2.33 (#609) 2024-08-23 18:53:48 -04:00
Chenlei Hu
1a3cf4c3f3 Show node name and node id on flattened task outputs (#608)
* wip

* Show node name and node id
2024-08-23 18:52:45 -04:00
Chenlei Hu
31d172d4d9 Fix hotkeys triggering while editing properties panel values (#606) (#607)
* Fix hotkeys triggering while editing properties panel values (#606)

* Add properties panel inputs to key handler ignore

* Add properties panel test

* Update test expectations [skip ci]

---------

Co-authored-by: bymyself <abolkonsky.rem@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
2024-08-23 16:46:40 -04:00
Chenlei Hu
92ce064ebf Fix github request too long issue (#605)
* Fix github request too long issue

* Remove life on error message
2024-08-23 13:52:53 -04:00
bymyself
3d041dd742 Fix settings dialog on mobile (#602) 2024-08-23 09:43:45 -04:00
Alex "mcmonkey" Goodwin
af378262f4 Model downloader dialog (#569)
* API core for model downloader

* initial basic dialog for missing models

* app.ts handling for missing models

* don't explode if getModels is a 404

* actually track downloads in progress

* overall pile of improvements to the missing models view

* minor fixes

* add setting to disable missing models warning

* temporarily remove 'models' entry from default graph

to avoid missing model dialog causing issues. Also because ckpt autodownloading shouldn't be allowed

* swap the url to a title

* add model directory to display

* match settingStore commit

* check setting before scanning models list

ie avoid redundant calcs when setting is disabled anyway
2024-08-23 09:43:20 -04:00
Chenlei Hu
57c5a78af3 Fix signature of listUserData (#601) 2024-08-22 20:26:23 -04:00
Chenlei Hu
233fd1347e Move error handling out of api.ts to workflows.ts (#600) 2024-08-22 20:21:31 -04:00
Chenlei Hu
60221254d9 1.2.32 (#599) 2024-08-22 19:48:04 -04:00
Chenlei Hu
7434691bed Disable flaky test (#598) 2024-08-22 19:42:38 -04:00
Chenlei Hu
fbdc9d430b Improve node search matching algorithm (#597) 2024-08-22 19:40:58 -04:00
Chenlei Hu
3e457f812d Execution Error Dialog Revamp (One click issue searching and filing) (#595)
* Add basic error dialog

* 2 level error report

* Add find issue button

* nit

* Add file issue button

* Single dialog

* nit

* Fix long text wrapping

* Merge component

* Test execution error dialog
2024-08-22 15:55:38 -04:00
bymyself
0466c79725 Fix right-click save image in gallery (firefox) (#588) 2024-08-21 21:28:12 -04:00
Chenlei Hu
8b989c6415 Add a wait to prevent searchbox popup (#589)
* Add a wait to prevent searchbox popup

* nit
2024-08-21 21:27:54 -04:00
Chenlei Hu
2e51122778 Add browser tests for litegraph changes (#580) (#585) (#587)
* Add browser tests for litegraph changes (#580) (#585)

* Zoom speed tests

* Merge

* Prompt dialog test

* Update test expectations [skip ci]

---------

Co-authored-by: bymyself <abolkonsky.rem@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
2024-08-21 20:39:30 -04:00
Chenlei Hu
8f8eac038a 1.2.31 (#586) 2024-08-21 19:03:40 -04:00
Tobias Menzi
4b3bad4bc5 Change broken relative font paths (#584) 2024-08-21 19:01:02 -04:00
Chenlei Hu
17c7f57d8f Update litegraph (Zoom speed adjustment) (#580) 2024-08-21 17:16:06 -04:00
Chenlei Hu
5542845710 Task output folder view (#579)
* Change to button

* Folder view
2024-08-21 16:47:30 -04:00
Chenlei Hu
f2de9b0d3c Revert "Add custom sort function to improve search matching (#574)" (#578)
This reverts commit 77ba201367.
2024-08-21 14:25:20 -04:00
Chenlei Hu
71fa71e82c Prefer saved media over preview media as task cover media (#576) 2024-08-21 14:19:27 -04:00
Chenlei Hu
77ba201367 Add custom sort function to improve search matching (#574) 2024-08-21 13:54:29 -04:00
Chenlei Hu
6edfc9bc1b Use settingStore to access setting (#572) 2024-08-21 11:42:12 -04:00
Chenlei Hu
743dc4879a Add setting to disable missing nodes dialog (#571)
* Add setting to disable missing nodes dialog

* nit

* nit
2024-08-21 10:31:07 -04:00
Alex "mcmonkey" Goodwin
2c1bd662e1 minor typo fix (#567) 2024-08-21 09:51:15 -04:00
Björn Söderqvist
7051d86ba7 Fix incorrect type paths (#568) 2024-08-21 09:41:06 -04:00
Chenlei Hu
7487c565c8 1.2.30 (#566) 2024-08-20 17:48:31 -04:00
Chenlei Hu
a86d10b02d Fix node library searchbox background color (#565) 2024-08-20 17:41:59 -04:00
Chenlei Hu
3d89c245e5 Add experimental/deprecated tags to search result / node library (#564) 2024-08-20 17:35:23 -04:00
Chenlei Hu
9dd6da3dc2 Support node deprecated/experimental flag (#563)
* Add deprecated field

* Hide deprecated nodes

* Add experimental node show/hide

* Add setting tooltips

* nit

* nit

* nit
2024-08-20 17:00:47 -04:00
Chenlei Hu
269e468425 i18n node searchbox placeholder (#561) 2024-08-20 15:20:00 -04:00
Chenlei Hu
c3ef716d53 Reduce search highlight padding (#560) 2024-08-20 15:12:30 -04:00
Chenlei Hu
bd7bbd9e95 Reduce debounce delay in node searchbox (#559) 2024-08-20 15:10:06 -04:00
ruucm
447d1c95ef enable cmd shortcuts for mac (mute & bypass) (#557) 2024-08-20 11:01:17 -04:00
Chenlei Hu
c4bc0e8430 Auto expand tree on search in node library tab (#558)
* Add custom nodelib searchbox

* Auto expand on search

* Support alphabetical sort in filtered tree
2024-08-20 11:01:05 -04:00
Chenlei Hu
f3ab9cfb8e Fix multiuser selection screen (#554) 2024-08-19 22:59:14 -04:00
Chenlei Hu
52c8c8194e Add browser_tests README (#553) 2024-08-19 22:45:06 -04:00
Chenlei Hu
9dbc114ae9 Remove win32 expectations (#551) 2024-08-19 21:59:05 -04:00
Chenlei Hu
556edea299 1.2.29 (#550) 2024-08-19 21:49:58 -04:00
Chenlei Hu
d5584a1d39 Fix node source chip alignment (#549) 2024-08-19 21:45:49 -04:00
Chenlei Hu
628f2afc34 Remove double reroute pin (#548) 2024-08-19 21:38:41 -04:00
Alex "mcmonkey" Goodwin
ea01fde607 [Experimental] hide/show logic improvement (#475)
* experimental hide/show logic improvement

for #470

* minor early out fix

not sure this is strictly needed (doesn't seem to be from a test, seems draw stops being called when hidden?) but better safe than sorry

* use null

* persist collapsed state
2024-08-19 21:22:48 -04:00
Chenlei Hu
b8a3e6b1ad Reduce padding on searchbox result item (#547)
* Reduce search result padding

* nit
2024-08-19 21:19:34 -04:00
Chenlei Hu
cfad3cd918 Add setting to hide node category in search result (#546) 2024-08-19 21:00:15 -04:00
Alex "mcmonkey" Goodwin
339e201920 Dom widget playwright tests (#540)
* gitignore win32 browser_test files

just so i can local dev with playwright without git cluttering up

* experimental add test for dom widget node toggle open/closed

* Update test expectations [skip ci]

* vs code extension lied to me, manually fix loc

* Update test expectations [skip ci]

* okay that time was my fault

* Update test expectations [skip ci]

* yknow what dont expect exactly default after actually

* Update test expectations [skip ci]

* test for multiple far panning

* Update test expectations [skip ci]

* oops, flip that

* Update test expectations [skip ci]

* more stable pan coords

* Update test expectations [skip ci]

* 'move' is not strictly relative, so compensate accordingly

* Update test expectations [skip ci]

* test to zoom very far out

* Update test expectations [skip ci]

* add hackaround for node search menu popup

* Update test expectations [skip ci]

* make zoom work better

* Update test expectations [skip ci]

* fix preexisting typo

this function is never called so it's fine

* dom widget toggle needs a delay

otherwise it's a double click

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-08-19 20:54:07 -04:00
bymyself
c227a8af9a Use DPI to calculate screen center (#544) 2024-08-19 20:51:24 -04:00
Chenlei Hu
6b47162606 Update README.md (#545) 2024-08-19 18:18:28 -04:00
Chenlei Hu
a4c5a2a3d1 Update README.md (#543) 2024-08-19 17:26:12 -04:00
Chenlei Hu
45a47be7c0 Ctrl+Shift+Drag zoom on mouse position (#538) (#541)
* Ctrl+Shift+Drag zoom on mouse position (#538)

* Update test expectations [skip ci]

---------

Co-authored-by: bymyself <abolkonsky.rem@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
2024-08-19 17:08:50 -04:00
Chenlei Hu
2252f0a134 Resize large image to fit into window for Image Gallery (#539) 2024-08-19 16:50:40 -04:00
pythongosssss
0dfbcfb2d6 Support clicking on library entries to toggle expand/insert node (#511)
* Support clicking on library entries to toggle expand/insert node

* Fix type
2024-08-19 14:09:34 -04:00
bymyself
b46036f25d Fix escape not resetting activeIndex in gallery (#535)
* Fix escape not resetting activeIndex in gallery

* Use handleVisibilityChange for consistency
2024-08-19 14:00:47 -04:00
Chenlei Hu
f5ce42d5d5 Add setting to show/hide canvas info (#533)
* Add setting to show/hide canvas info

* nit
2024-08-19 12:12:37 -04:00
Chenlei Hu
ce75a29202 Update README.md (Sheild badges) (#532) 2024-08-19 11:36:14 -04:00
Chenlei Hu
6a8a68a240 Image failed to load placeholder (#531)
* Image failed to load placeholder

* Use broken image placeholder in gallery

* nit
2024-08-19 11:16:35 -04:00
Chenlei Hu
f9adaadc7d Use toast on reconnection message (#530) 2024-08-19 10:18:09 -04:00
Chenlei Hu
727992048e Fix type check on TaskItemImpl (#529) 2024-08-19 09:47:33 -04:00
Chenlei Hu
dd1e3f087d Update README.md (#523) 2024-08-18 22:16:46 -04:00
Chenlei Hu
f1c1a3dab7 1.2.28 (#522) 2024-08-18 21:59:00 -04:00
Chenlei Hu
4a43dfe6b9 Performance optimization on queueStore (#521)
* Eager calculate flat outputs

* Optimize flat tasks performance
2024-08-18 21:48:48 -04:00
Chenlei Hu
8576d3797b Prefer image for queue task preview (#520) 2024-08-18 20:59:43 -04:00
Chenlei Hu
22e2628479 Queue preview gallery (#519)
* Custom preview event

* Plub event

* Basic gallery

* Gallery nits

* Navigate with keyboard keys
2024-08-18 20:42:42 -04:00
Chenlei Hu
4e1f14139b Fix queue tab update issue (#518) 2024-08-18 18:11:33 -04:00
Chenlei Hu
0c53ab9177 Fix context menu on node with only optional input (#514) 2024-08-18 16:25:45 -04:00
Chenlei Hu
eb5f4d9bc7 Support view of gifs in queue media preview (#513) 2024-08-18 16:20:56 -04:00
Chenlei Hu
19681fd43d Experimental settings BETA tag (#509)
* Add BETA tag for experimental setting

* Mark node searchbox impl as experimental

* nit
2024-08-18 13:04:37 -04:00
Chenlei Hu
add2f9baa0 Group comfy core settings (#508)
* Add category overwrite

* Group settings
2024-08-18 12:49:23 -04:00
Chenlei Hu
ec5f1152da Organize setting display for new settings dialog (#507) 2024-08-18 11:31:50 -04:00
Chenlei Hu
17aa44d9f6 Activate new settings dialog from default UI (#506) 2024-08-18 10:23:03 -04:00
Chenlei Hu
d8887a434d Add Comfy.Workflow.SortNodeIdOnSave setting (#502)
* Update litegraph (Sort order)

* nit

* Add sort node on save setting

* nit
2024-08-17 23:23:40 -04:00
Chenlei Hu
9d3ca763d0 Queue tab infinite scroll (#501)
* Add vueuse

* Infinite scroll queue tab

* Set item per page to 8

* Handle sidebar resize

* nit
2024-08-17 22:48:31 -04:00
Chenlei Hu
a45851d7a6 1.2.27 (#500) 2024-08-17 21:42:02 -04:00
Chenlei Hu
266336104a Fix frontend only node category (#499) 2024-08-17 21:31:56 -04:00
Chenlei Hu
8c40f83b35 Properly update vue app's node defs on refresh (#493) 2024-08-17 13:23:47 -04:00
Chenlei Hu
5ba524fd94 Add toast message on refresh button click (#492) 2024-08-17 13:11:47 -04:00
Chenlei Hu
966b1dd057 Extension API to add toast message (#491)
* Extension API to add toast message

* Update readme
2024-08-17 12:44:55 -04:00
Chenlei Hu
069766337a Add toast message on execution interrupted (#490)
* Move toast to top level

* Toast store
2024-08-17 12:29:48 -04:00
Chenlei Hu
a1a6eeed0f Remove unused var mainCanvas (#488) 2024-08-17 11:33:17 -04:00
Chenlei Hu
b33874db2b Fix queue cancelled status (#487) 2024-08-17 11:31:46 -04:00
bymyself
05c6193a1d Fix loadGraphData call when loading from json (#484) 2024-08-17 10:47:39 -04:00
bymyself
73f7889f81 Add touch-action event back to canvas (#483) 2024-08-17 09:05:23 -04:00
Alex "mcmonkey" Goodwin
c73915e31e EditAttention: allow negatives (#476)
for #474
2024-08-16 21:56:03 -04:00
Alex "mcmonkey" Goodwin
7626d08c98 vite relative paths fix (#472) 2024-08-16 21:15:03 -04:00
Alex "mcmonkey" Goodwin
f10554d914 fix minor typing error (#473)
introduced at e6d29656fa
2024-08-16 21:14:15 -04:00
Chenlei Hu
238225dd12 1.2.26 (#471) 2024-08-16 14:07:32 -04:00
Chenlei Hu
9cea54686a Pin reroute on link release searchbox (#469)
* Correctly handle wildcard type

* Add test

* nit

* Pin reroute on link release search

* Connect wildcard

* nit
2024-08-16 13:22:17 -04:00
Chenlei Hu
d1715972e3 Register frontend only node defs (#468)
* Register frontend only node defs

* nit

* nit
2024-08-16 12:16:41 -04:00
Chenlei Hu
49a910d7b0 Update litegraph (Sort order) (#467)
* Update litegraph (Sort order)

* Update

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-08-16 11:36:18 -04:00
Chenlei Hu
479ca63e3c Add setting to adjust textarea font size (#463) 2024-08-16 10:17:40 -04:00
Chenlei Hu
7468555c06 1.2.25 (#456) 2024-08-16 01:04:26 -04:00
Chenlei Hu
14a395a0ce Fix queue item context menu (#455) 2024-08-16 01:02:51 -04:00
Chenlei Hu
0a99a2bd53 1.2.24 (#454) 2024-08-16 00:02:15 -04:00
Chenlei Hu
955c703fde Still load workflow even validation fails (#453) 2024-08-15 23:55:20 -04:00
Chenlei Hu
ef8b952d79 Fix undefined spec (#452) 2024-08-15 23:37:42 -04:00
Chenlei Hu
e6d29656fa Queue media preview (#449)
* output url

* Basic image previews

* Split out task item component

* Move task actions to context menu

* simplify

* Move spinner

* Lift context menu to tab scope

* Better tag

* Fix placeholder style

* nit

* Correctly handle cancelled

* nit

* Split out result item as separate component

* nit

* Fix center crop

* nit

* Simplify task item

* Flat list

* Show prompt id

* Make image draggable

* Disable preview for dragging

* Fix key

* Correctly handle task in expanded view

* Add preview
2024-08-15 23:26:38 -04:00
Alex "mcmonkey" Goodwin
9e3dffd7fd Fix relative paths (#447)
ComfyUI is not necessarily the webroot, paths should be relative rather than absolute
2024-08-15 20:27:06 -04:00
Chenlei Hu
429e44f74d Control minify with env var (#445) 2024-08-15 14:41:36 -04:00
Alex "mcmonkey" Goodwin
73b0ecc8cb fix preview nodes clipping under the bottom (#444)
for #411 - moves code to main typescript section (to avoid vue weirdness) and finds the preview node element, adjust top by height
2024-08-15 13:33:02 -04:00
Björn Söderqvist
775f536d30 Add more Zod types to api.ts (#440) 2024-08-15 10:45:40 -04:00
Chenlei Hu
1ff7a70579 1.2.23 (#441) 2024-08-15 09:54:10 -04:00
Chenlei Hu
51233b4be3 Fix node preview location when sidebar location is right (#435) 2024-08-14 22:46:05 -04:00
bymyself
5c4d1c2cec Fix copy/paste on firefox (#432) 2024-08-14 22:05:15 -04:00
Chenlei Hu
711205b499 1.2.22 (#429) 2024-08-14 15:11:45 -04:00
Chenlei Hu
00fe3515b9 Fix delete queue item (#428) 2024-08-14 15:10:17 -04:00
Chenlei Hu
d8ee0b4584 Relax schema on node I/O link (#427) 2024-08-14 14:17:18 -04:00
Chenlei Hu
fc1b0b3e53 Fix searchbox trigger on non-always link release setting (#426)
* Fix searchbox trigger on non-always link release setting

* Add browsertest
2024-08-14 13:44:22 -04:00
Chenlei Hu
a68f7c680b Rename SideBar to Sidebar (#422)
* Rename SideBar to Sidebar

* rename files

* rename files
2024-08-14 11:27:23 -04:00
Chenlei Hu
c6b6bdcb67 Add setting to disable node preview in searchbox (#421) 2024-08-14 10:29:32 -04:00
Chenlei Hu
6993a56c2d Highlight query in search result in node searchbox (#420)
* min match set to 2

* Highlight query in search result

* nit
2024-08-14 10:28:17 -04:00
Chenlei Hu
4eb56a19ba Fix queue/history keybinding (#419) 2024-08-14 09:53:49 -04:00
Chenlei Hu
784947fdea 1.2.21 (#416) 2024-08-13 21:45:23 -04:00
Alex "mcmonkey" Goodwin
64ee131e64 Don't cache API requests (#415)
API requests should by default never be cached
2024-08-13 20:22:22 -04:00
Chenlei Hu
1b59e3ab6d Fast searchbox dialog activation (#410) 2024-08-13 16:25:11 -04:00
Chenlei Hu
37b414d1b2 Use fuse.js extended search (#405) 2024-08-13 16:01:52 -04:00
Yuta Hayashibe
7245eee85b Perform text replacement for SaveAnimatedWEBP in addition to SaveImage (#403) 2024-08-13 13:57:26 -04:00
Yuta Hayashibe
9f3696e70f Fix typos (#404)
* Fix typos: Interupt -> Interrupt

* Fix typos: tempateManagerRow -> templateManagerRow

* Fix some typos

* Fix typos: Convertable -> Convertible

* Fix some typos
2024-08-13 13:57:02 -04:00
Chenlei Hu
d5deb6d2a0 Only report each unknown message type once (#402) 2024-08-13 11:18:30 -04:00
Chenlei Hu
41889c3cfe Add GitHub action to automatically submit release PR to main repo (#400) 2024-08-13 10:14:38 -04:00
Chenlei Hu
e8602e88fa 1.2.20 (#398) 2024-08-12 22:28:17 -04:00
Chenlei Hu
b5f2d334d9 Add sidebar size setting (normal/small) (#397)
* Add sidebar size setting (normal/small)

* Set default small sidebar size for small window
2024-08-12 22:27:35 -04:00
Chenlei Hu
c459698956 Add side bar location in settings (left/right) (#396)
* Allow side bar on right side

* Panel on the right
2024-08-12 21:49:07 -04:00
Chenlei Hu
f91e335ca7 Only shim legacy directories (#395)
* Only shim legacy directories

* nit
2024-08-12 21:11:30 -04:00
Chenlei Hu
1fc173b48f 1.2.19 (#394) 2024-08-12 20:55:06 -04:00
Chenlei Hu
4fe44339fd Only validate new history items (#393) 2024-08-12 20:30:11 -04:00
Chenlei Hu
f987f4f5f3 Configure minify (#392) 2024-08-12 17:12:47 -04:00
Chenlei Hu
91e21b1387 Update ws message schema on reconnecting (#390)
* Update ws message schema on reconnecting

* nit
2024-08-12 13:21:48 -04:00
Chenlei Hu
a5be1f6072 Revert "Minify build (#373)" (#391)
This reverts commit 9385014799.
2024-08-12 13:12:51 -04:00
Chenlei Hu
d5f30be06d 1.2.18 (#387) 2024-08-12 09:21:53 -04:00
Chenlei Hu
d607f6c7f7 Revert "Add support for LiteGraph to convert to classes (#334)" (#386)
This reverts commit e2141a81e2.
2024-08-12 09:19:10 -04:00
Chenlei Hu
d9df0328c5 Revert "Update litegraph (ES6 LLink & LGraphNode) (#372)" (#385)
This reverts commit cfce1c6037.
2024-08-12 09:18:37 -04:00
Chenlei Hu
aff059b98b 1.2.17 (#380) 2024-08-11 19:46:42 -04:00
Chenlei Hu
cf53e8df6a Revert "Update primevue/themes to 4.0.0 (#378)" (#379)
This reverts commit d1d4324e57.
2024-08-11 19:39:55 -04:00
Chenlei Hu
d1d4324e57 Update primevue/themes to 4.0.0 (#378) 2024-08-11 19:38:27 -04:00
Chenlei Hu
c86abd3dc6 Remove pending task count on queue button (#377) 2024-08-11 19:33:43 -04:00
Chenlei Hu
281ed0c5d1 Show pending task count on side bar queue icon (#376)
* remove listener

* Store pending task count

* Add iconBadge to queue icon
2024-08-11 19:15:21 -04:00
Chenlei Hu
edf0396619 Type WS messages (#375) 2024-08-11 18:05:14 -04:00
Chenlei Hu
2a5b2e8d12 Reduce fusejs threshold (#374) 2024-08-11 12:39:23 -04:00
Chenlei Hu
9385014799 Minify build (#373) 2024-08-11 10:59:39 -04:00
Chenlei Hu
cfce1c6037 Update litegraph (ES6 LLink & LGraphNode) (#372) 2024-08-11 10:14:00 -04:00
Chenlei Hu
480679aa32 Relax restriction on data type in schema (#371) 2024-08-11 10:12:16 -04:00
filtered
e2141a81e2 Add support for LiteGraph to convert to classes (#334)
* Add support for LiteGraph to convert to classes

* Fix large context menu search regression

* Remove debug code

* Fix regression from rename & prototype change

* Fix super() calls to match LGraphNode
2024-08-11 09:46:54 -04:00
Chenlei Hu
0f3b58b610 Add space to setting group label (#370)
* Add space to setting group label

* handle acronym
2024-08-11 09:36:52 -04:00
178 changed files with 4606 additions and 1016 deletions

View File

@@ -13,4 +13,7 @@ DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
DEPLOY_COMFYUI_DIR=/home/ComfyUI/web
# The directory containing the ComfyUI_examples repo used to extract test workflows.
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
# Whether to enable minification of the frontend code.
ENABLE_MINIFY=true

View File

@@ -27,6 +27,11 @@ jobs:
with:
repository: "Comfy-Org/ComfyUI_frontend"
path: "ComfyUI_frontend"
- name: Checkout ComfyUI_devtools
uses: actions/checkout@v4
with:
repository: "Comfy-Org/ComfyUI_devtools"
path: "ComfyUI/custom_nodes/ComfyUI_devtools"
- name: Get commit message
id: commit-message
run: echo "::set-output name=message::$(git log -1 --pretty=%B)"

53
.github/workflows/update-main.yaml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Update Main Repo from PR
on:
pull_request:
types: [labeled]
jobs:
update-main-repo:
if: github.event.label.name == 'Update Main Repo'
runs-on: ubuntu-latest
steps:
- name: Checkout frontend repo PR
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Build project
run: npm run build
- name: Checkout ComfyUI
uses: actions/checkout@v4
with:
repository: "comfyanonymous/ComfyUI"
path: ComfyUI
ref: master
- name: Copy compiled assets
run: |
rm -rf ./ComfyUI/web/*
cp -R dist/* ./ComfyUI/web/
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
token: ${{ secrets.PAT }}
commit-message: 'Update frontend assets from PR #${{ github.event.pull_request.number }}'
title: 'Update frontend assets from PR #${{ github.event.pull_request.number }}'
body: |
This PR updates the compiled frontend assets from PR #${{ github.event.pull_request.number }} in the frontend repo.
Frontend PR: ${{ github.event.pull_request.html_url }}
branch: update-frontend-assets-pr-${{ github.event.pull_request.number }}
base: main
path: ComfyUI

1
.gitignore vendored
View File

@@ -34,6 +34,7 @@ tests-ui/workflows/examples
/playwright-report/
/blob-report/
/playwright/.cache/
browser_tests/*/*-win32.png
.env

View File

@@ -1,37 +1,62 @@
<div align="center">
# ComfyUI_frontend
Front-end of [ComfyUI](https://github.com/comfyanonymous/ComfyUI) modernized. This repo is fully compatible with the existing extension system.
**Official front-end implementation of [ComfyUI](https://github.com/comfyanonymous/ComfyUI).**
## How To Use
[![Website][website-shield]][website-url]
[![Discord][discord-shield]][discord-url]
[![Matrix][matrix-shield]][matrix-url]
<br>
[![][github-release-shield]][github-release-link]
[![][github-release-date-shield]][github-release-link]
[![][github-downloads-shield]][github-downloads-link]
[![][github-downloads-latest-shield]][github-downloads-link]
Add command line argument `--front-end-version Comfy-Org/ComfyUI_frontend@latest` to your
ComfyUI launch script.
For Windows stand-alone build users, please edit the `run_cpu.bat` / `run_nvidia_gpu.bat` file as following
[github-release-shield]: https://img.shields.io/github/v/release/Comfy-Org/ComfyUI_frontend?style=flat&sort=semver
[github-release-link]: https://github.com/Comfy-Org/ComfyUI_frontend/releases
[github-release-date-shield]: https://img.shields.io/github/release-date/Comfy-Org/ComfyUI_frontend?style=flat
[github-downloads-shield]: https://img.shields.io/github/downloads/Comfy-Org/ComfyUI_frontend/total?style=flat
[github-downloads-latest-shield]: https://img.shields.io/github/downloads/Comfy-Org/ComfyUI_frontend/latest/total?style=flat&label=downloads%40latest
[github-downloads-link]: https://github.com/Comfy-Org/ComfyUI_frontend/releases
[matrix-shield]: https://img.shields.io/badge/Matrix-000000?style=flat&logo=matrix&logoColor=white
[matrix-url]: https://app.element.io/#/room/%23comfyui_space%3Amatrix.org
[website-shield]: https://img.shields.io/badge/ComfyOrg-4285F4?style=flat
[website-url]: https://www.comfy.org/
[discord-shield]: https://img.shields.io/discord/1218270712402415686?style=flat&logo=discord&logoColor=white&label=Discord
[discord-url]: https://www.comfy.org/discord
</div>
## Release Schedule
### Nightly Release
Nightly releases are published daily at [https://github.com/Comfy-Org/ComfyUI_frontend/releases](https://github.com/Comfy-Org/ComfyUI_frontend/releases).
To use the latest nightly release, add the following command line argument to your ComfyUI launch script:
```
--front-end-version Comfy-Org/ComfyUI_frontend@latest
```
#### For Windows Stand-alone Build Users
Edit your `run_cpu.bat` or `run_nvidia_gpu.bat` file as follows:
```bat
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --front-end-version Comfy-Org/ComfyUI_frontend@latest
pause
```
## Trouble Shooting
<details>
<summary>Empty white screen (Fixed by https://github.com/comfyanonymous/ComfyUI/pull/4211)</summary>
### Stable Release
### Behavior
After you enable the new frontend in commandline, and open ComfyUI in the browser, you see a blank screen. If you toggle dev tools with F12, you can observe `litegraph.core.js:1` 404 in console messages.
Stable releases are published weekly in the ComfyUI main repository, aligned with ComfyUI backend's stable release schedule.
### Cause
The browser is caching the `index.html` file previously served from `localhost:8188`.
#### Feature Freeze
### How to fix
Step 1: Disable cache in devtools
![image](https://github.com/user-attachments/assets/c0cec519-93b7-49f8-aea1-7adb0aa5b073)
Step 2: Refresh your browser
</details>
There will be a 2-day feature freeze before each stable release. During this period, no new major features will be merged.
## Release Summary
@@ -67,7 +92,7 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
<details>
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
@@ -92,9 +117,9 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Node developers API
<details>
<summary>v1.2.4: Extension API to register custom side bar tab</summary>
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
Extensions now can call following API to register a sidebar tab.
Extensions now can call the following API to register a sidebar tab.
```js
app.extensionManager.registerSidebarTab({
@@ -109,20 +134,35 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
});
```
The list of supported icons can be find here: https://primevue.org/icons/#list
The list of supported icons can be found here: <https://primevue.org/icons/#list>
We will support custom icon later.
We will support custom icons later.
![image](https://github.com/user-attachments/assets/7bff028a-bf91-4cab-bf97-55c243b3f5e0)
</details>
<details>
<summary>v1.2.27: Extension API to add toast message</summary>
Extensions can call the following API to add toast messages.
```js
app.extensionManager.toast.add({
severity: 'info',
summary: 'Loaded!',
detail: 'Extension loaded!'
})
```
![image](https://github.com/user-attachments/assets/de02cd7e-cd81-43d1-a0b0-bccef92ff487)
</details>
## Road Map
### What has been done
- Migrate all code to TypeScript with minimal change modification to the original logic.
- Bundle all code with vite's rollup build.
- Bundle all code with Vite's rollup build.
- Added a shim layer to be backward compatible with the existing extension system. <https://github.com/huchenlei/ComfyUI_frontend/pull/15>
- Front-end dev server.
- Zod schema for input validation on ComfyUI workflow.
@@ -175,7 +215,3 @@ This repo is using litegraph package hosted on https://github.com/Comfy-Org/lite
- Option 1: Set `DEPLOY_COMFYUI_DIR` in `.env` and run `npm run deploy`.
- Option 2: Copy everything under `dist/` to `ComfyUI/web/` in your ComfyUI checkout manually.
## Breaking changes
- api.api_url now adds a prefix `api/` to every url going through the method. If the custom node registers a new api endpoint but does not offer the `api/` prefixed alt endpoint, it will have issue. Luckily there aren't many extensions that do that. We can perform an audit before launching to resolve this issue.

View File

@@ -26,18 +26,24 @@ class ComfyNodeSearchBox {
)
}
async fillAndSelectFirstNode(nodeName: string) {
async fillAndSelectFirstNode(
nodeName: string,
options?: { suggestionIndex: number }
) {
await this.input.waitFor({ state: 'visible' })
await this.input.fill(nodeName)
await this.dropdown.waitFor({ state: 'visible' })
// Wait for some time for the auto complete list to update.
// The auto complete list is debounced and may take some time to update.
await this.page.waitForTimeout(500)
await this.dropdown.locator('li').nth(0).click()
await this.dropdown
.locator('li')
.nth(options?.suggestionIndex || 0)
.click()
}
}
class NodeLibrarySideBarTab {
class NodeLibrarySidebarTab {
public readonly tabId: string = 'node-library'
constructor(public readonly page: Page) {}
@@ -68,22 +74,30 @@ class NodeLibrarySideBarTab {
await this.nodeLibraryTree.waitFor({ state: 'visible' })
}
async toggleFirstFolder() {
await this.page.locator('.p-tree-node-toggle-button').nth(0).click()
getFolder(folderName: string) {
return this.page.locator(
`.p-tree-node-content:has(> .node-lib-tree-node-label:has(.folder-label:has-text("${folderName}")))`
)
}
getNode(nodeName: string) {
return this.page.locator(
`.p-tree-node-content:has(> .node-lib-tree-node-label:has(.node-label:has-text("${nodeName}")))`
)
}
}
class ComfyMenu {
public readonly sideToolBar: Locator
public readonly sideToolbar: Locator
public readonly themeToggleButton: Locator
constructor(public readonly page: Page) {
this.sideToolBar = page.locator('.side-tool-bar-container')
this.sideToolbar = page.locator('.side-tool-bar-container')
this.themeToggleButton = page.locator('.comfy-vue-theme-toggle')
}
get nodeLibraryTab() {
return new NodeLibrarySideBarTab(this.page)
return new NodeLibrarySidebarTab(this.page)
}
async toggleTheme() {
@@ -118,6 +132,7 @@ export class ComfyPage {
// Buttons
public readonly resetViewButton: Locator
public readonly queueButton: Locator
// Inputs
public readonly workflowUploadInput: Locator
@@ -131,6 +146,7 @@ export class ComfyPage {
this.canvas = page.locator('#graph-canvas')
this.widgetTextBox = page.getByPlaceholder('text').nth(1)
this.resetViewButton = page.getByRole('button', { name: 'Reset View' })
this.queueButton = page.getByRole('button', { name: 'Queue Prompt' })
this.workflowUploadInput = page.locator('#comfy-file-input')
this.searchBox = new ComfyNodeSearchBox(page)
this.menu = new ComfyMenu(page)
@@ -170,7 +186,22 @@ export class ComfyPage {
await this.resetView()
}
async realod() {
async setSetting(settingId: string, settingValue: any) {
return await this.page.evaluate(
async ({ id, value }) => {
await window['app'].ui.settings.setSettingValueAsync(id, value)
},
{ id: settingId, value: settingValue }
)
}
async getSetting(settingId: string) {
return await this.page.evaluate(async (id) => {
return await window['app'].ui.settings.getSettingValue(id)
}, settingId)
}
async reload() {
await this.page.reload({ timeout: 15000 })
await this.setup()
}
@@ -185,6 +216,10 @@ export class ComfyPage {
})
}
async delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
async loadWorkflow(workflowName: string) {
await this.workflowUploadInput.setInputFiles(
`./browser_tests/assets/${workflowName}.json`
@@ -211,6 +246,16 @@ export class ComfyPage {
await this.nextFrame()
}
async clickTextEncodeNodeToggler() {
await this.canvas.click({
position: {
x: 430,
y: 171
}
})
await this.nextFrame()
}
async clickTextEncodeNode2() {
await this.canvas.click({
position: {
@@ -280,16 +325,22 @@ export class ComfyPage {
await this.nextFrame()
}
async zoom(deltaY: number) {
async zoom(deltaY: number, steps: number = 1) {
await this.page.mouse.move(10, 10)
await this.page.mouse.wheel(0, deltaY)
for (let i = 0; i < steps; i++) {
await this.page.mouse.wheel(0, deltaY)
}
await this.nextFrame()
}
async pan(offset: Position) {
await this.page.mouse.move(10, 10)
async pan(offset: Position, safeSpot?: Position) {
safeSpot = safeSpot || { x: 10, y: 10 }
await this.page.mouse.move(safeSpot.x, safeSpot.y)
await this.page.mouse.down()
await this.page.mouse.move(offset.x, offset.y)
// TEMPORARY HACK: Multiple pans open the search menu, so cheat and keep it closed.
// TODO: Fix that (double-click at not-the-same-coordinations should not open the menu)
await this.page.keyboard.press('Escape')
await this.page.mouse.move(offset.x + safeSpot.x, offset.y + safeSpot.y)
await this.page.mouse.up()
await this.nextFrame()
}

41
browser_tests/README.md Normal file
View File

@@ -0,0 +1,41 @@
# Playwright Testing for ComfyUI_frontend
This document outlines the setup and usage of Playwright for testing the ComfyUI_frontend project.
## Setup
Ensure you have Node.js v20 or later installed. Then, set up the Chromium test driver:
```bash
npx playwright install chromium --with-deps
```
## Running Tests
There are two ways to run the tests:
1. **Headless mode with report generation:**
```bash
npx playwright test
```
This runs all tests without a visible browser and generates a comprehensive test report.
2. **UI mode for interactive testing:**
```bash
npx playwright test --ui
```
This opens a user interface where you can select specific tests to run and inspect the test execution timeline.
![Playwright UI Mode](https://github.com/user-attachments/assets/6a1ebef0-90eb-4157-8694-f5ee94d03755)
## Screenshot Expectations
Due to variations in system font rendering, screenshot expectations are platform-specific. Please note:
- We maintain Linux screenshot expectations as our GitHub Action runner operates in a Linux environment.
- To set new test expectations:
1. Create a pull request from a `Comfy-Org/ComfyUI_frontend` branch.
2. Add the `New Browser Test Expectation` tag to your pull request.
3. This will trigger a GitHub action to update the screenshot expectations automatically.
> **Note:** If you're making a pull request from a forked repository, the GitHub action won't be able to commit updated screenshot expectations directly to your PR branch.

View File

@@ -0,0 +1,82 @@
{
"last_node_id": 17,
"last_link_id": 15,
"nodes": [
{
"id": 14,
"type": "PreviewImage",
"pos": [
858,
-41
],
"size": {
"0": 213.8594970703125,
"1": 50.65289306640625
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 15
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 17,
"type": "DevToolsErrorRaiseNode",
"pos": [
477,
-40
],
"size": {
"0": 210,
"1": 26
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
15
],
"slot_index": 0,
"shape": 3
}
],
"properties": {
"Node name for S&R": "DevToolsErrorRaiseNode"
}
}
],
"links": [
[
15,
17,
0,
14,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.2100000000000006,
"offset": [
-266.1038310281165,
337.94335447664554
]
}
},
"version": 0.4
}

View File

@@ -0,0 +1,61 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "UNKNOWN NODE",
"pos": [
48,
86
],
"size": {
"0": 358.80780029296875,
"1": 314.7989501953125
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null,
"slot_index": 0
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [],
"slot_index": 0,
"shape": 6
}
],
"properties": {
"Node name for S&R": "UNKNOWN NODE"
},
"widgets_values": [
"wd-v1-4-moat-tagger-v2",
0.35,
0.85,
false,
false,
""
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0, 0
]
}
},
"version": 0.4
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -0,0 +1,27 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Load workflow warning', () => {
test('Should display a warning when loading a workflow with missing nodes', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('missing_nodes')
// Wait for the element with the .comfy-missing-nodes selector to be visible
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
await expect(missingNodesWarning).toBeVisible()
})
})
test.describe('Execution error', () => {
test('Should display an error message when an execution error occurs', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('execution_error')
await comfyPage.queueButton.click()
// Wait for the element with the .comfy-execution-error selector to be visible
const executionError = comfyPage.page.locator('.comfy-error-report')
await expect(executionError).toBeVisible()
})
})

View File

@@ -1,5 +1,5 @@
import { expect } from '@playwright/test'
import { ComfyPage, comfyPageFixture as test } from './ComfyPage'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Node Interaction', () => {
test('Can enter prompt', async ({ comfyPage }) => {
@@ -27,7 +27,7 @@ test.describe('Node Interaction', () => {
test('Can disconnect/connect edge', async ({ comfyPage }) => {
await comfyPage.disconnectEdge()
// Close search menu poped up.
// Close search menu popped up.
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
@@ -100,6 +100,38 @@ test.describe('Node Interaction', () => {
'batch-disconnect-links-disconnected.png'
)
})
test('Can toggle dom widget node open/closed', async ({ comfyPage }) => {
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
await comfyPage.clickTextEncodeNodeToggler()
await expect(comfyPage.canvas).toHaveScreenshot(
'text-encode-toggled-off.png'
)
await comfyPage.delay(1000)
await comfyPage.clickTextEncodeNodeToggler()
await expect(comfyPage.canvas).toHaveScreenshot(
'text-encode-toggled-back-open.png'
)
})
test('Can close prompt dialog with canvas click', async ({ comfyPage }) => {
await comfyPage.canvas.click({
position: {
x: 724,
y: 645
}
})
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-opened.png')
// Wait for 1s so that it does not trigger the search box by double click.
await comfyPage.page.waitForTimeout(1000)
await comfyPage.canvas.click({
position: {
x: 10,
y: 10
}
})
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-closed.png')
})
})
test.describe('Canvas Interaction', () => {
@@ -110,8 +142,78 @@ test.describe('Canvas Interaction', () => {
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-out.png')
})
test('Can zoom very far out', async ({ comfyPage }) => {
await comfyPage.zoom(100, 12)
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-very-far-out.png')
await comfyPage.zoom(-100, 12)
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-back-in.png')
})
test('Can zoom in/out with ctrl+shift+vertical-drag', async ({
comfyPage
}) => {
await comfyPage.page.keyboard.down('Control')
await comfyPage.page.keyboard.down('Shift')
await comfyPage.dragAndDrop({ x: 10, y: 100 }, { x: 10, y: 40 })
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in-ctrl-shift.png')
await comfyPage.dragAndDrop({ x: 10, y: 40 }, { x: 10, y: 160 })
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-out-ctrl-shift.png')
await comfyPage.dragAndDrop({ x: 10, y: 280 }, { x: 10, y: 220 })
await expect(comfyPage.canvas).toHaveScreenshot(
'zoomed-default-ctrl-shift.png'
)
await comfyPage.page.keyboard.up('Control')
await comfyPage.page.keyboard.up('Shift')
})
test('Can zoom in/out after decreasing canvas zoom speed setting', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.05)
await comfyPage.zoom(-100, 4)
await expect(comfyPage.canvas).toHaveScreenshot(
'zoomed-in-low-zoom-speed.png'
)
await comfyPage.zoom(100, 8)
await expect(comfyPage.canvas).toHaveScreenshot(
'zoomed-out-low-zoom-speed.png'
)
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
})
test('Can zoom in/out after increasing canvas zoom speed', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.5)
await comfyPage.zoom(-100, 4)
await expect(comfyPage.canvas).toHaveScreenshot(
'zoomed-in-high-zoom-speed.png'
)
await comfyPage.zoom(100, 8)
await expect(comfyPage.canvas).toHaveScreenshot(
'zoomed-out-high-zoom-speed.png'
)
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
})
test('Can pan', async ({ comfyPage }) => {
await comfyPage.pan({ x: 200, y: 200 })
await expect(comfyPage.canvas).toHaveScreenshot('panned.png')
})
test('Can pan very far and back', async ({ comfyPage }) => {
// intentionally slice the edge of where the clip text encode dom widgets are
await comfyPage.pan({ x: -800, y: -300 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-step-one.png')
await comfyPage.pan({ x: -200, y: 0 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-step-two.png')
await comfyPage.pan({ x: -2200, y: -2200 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-far-away.png')
await comfyPage.pan({ x: 2200, y: 2200 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-from-far.png')
await comfyPage.pan({ x: 200, y: 0 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-to-two.png')
await comfyPage.pan({ x: 800, y: 300 }, { x: 1000, y: 10 })
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-to-one.png')
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -3,12 +3,7 @@ import { comfyPageFixture as test } from './ComfyPage'
test.describe('Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.page.evaluate(async () => {
await window['app'].ui.settings.setSettingValueAsync(
'Comfy.UseNewMenu',
'Top'
)
})
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
test.afterEach(async ({ comfyPage }) => {
@@ -16,15 +11,11 @@ test.describe('Menu', () => {
if (currentThemeId !== 'dark') {
await comfyPage.menu.toggleTheme()
}
await comfyPage.page.evaluate(async () => {
await window['app'].ui.settings.setSettingValueAsync(
'Comfy.UseNewMenu',
'Disabled'
)
})
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test('Toggle theme', async ({ comfyPage }) => {
// Skip reason: Flaky.
test.skip('Toggle theme', async ({ comfyPage }) => {
test.setTimeout(30000)
expect(await comfyPage.menu.getThemeId()).toBe('dark')
@@ -44,7 +35,7 @@ test.describe('Menu', () => {
})
test('Can register sidebar tab', async ({ comfyPage }) => {
const initialChildrenCount = await comfyPage.menu.sideToolBar.evaluate(
const initialChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
(el) => el.children.length
)
@@ -62,34 +53,83 @@ test.describe('Menu', () => {
})
await comfyPage.nextFrame()
const newChildrenCount = await comfyPage.menu.sideToolBar.evaluate(
const newChildrenCount = await comfyPage.menu.sideToolbar.evaluate(
(el) => el.children.length
)
expect(newChildrenCount).toBe(initialChildrenCount + 1)
})
test('Sidebar node preview and drag to canvas', async ({ comfyPage }) => {
// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
await tab.toggleFirstFolder()
test.describe('Node library sidebar', () => {
test('Node preview and drag to canvas', async ({ comfyPage }) => {
// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
await tab.getFolder('sampling').click()
// Hover over a node to display the preview
const nodeSelector = '.p-tree-node-leaf'
await comfyPage.page.hover(nodeSelector)
// Hover over a node to display the preview
const nodeSelector = '.p-tree-node-leaf'
await comfyPage.page.hover(nodeSelector)
// Verify the preview is displayed
const previewVisible = await comfyPage.page.isVisible(
'.node-lib-node-preview'
// Verify the preview is displayed
const previewVisible = await comfyPage.page.isVisible(
'.node-lib-node-preview'
)
expect(previewVisible).toBe(true)
const count = await comfyPage.getGraphNodesCount()
// Drag the node onto the canvas
const canvasSelector = '#graph-canvas'
await comfyPage.page.dragAndDrop(nodeSelector, canvasSelector)
// Verify the node is added to the canvas
expect(await comfyPage.getGraphNodesCount()).toBe(count + 1)
})
test('Bookmark node', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [])
// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
await tab.getFolder('sampling').click()
// Bookmark the node
await tab
.getNode('KSampler (Advanced)')
.locator('.bookmark-button')
.click()
// Verify the bookmark is added to the bookmarks tab
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
['KSampler (Advanced)']
)
// Verify the bookmark node with the same name is added to the tree.
expect(await tab.getNode('KSampler (Advanced)').count()).toBe(2)
})
test('Ignores unrecognized node', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', ['foo'])
// Open the sidebar
const tab = comfyPage.menu.nodeLibraryTab
await tab.open()
expect(await tab.getFolder('sampling').count()).toBe(1)
expect(await tab.getNode('foo').count()).toBe(0)
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [])
})
})
test('Can change canvas zoom speed setting', async ({ comfyPage }) => {
const [defaultSpeed, maxSpeed] = [1.1, 2.5]
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(
defaultSpeed
)
expect(previewVisible).toBe(true)
const count = await comfyPage.getGraphNodesCount()
// Drag the node onto the canvas
const canvasSelector = '#graph-canvas'
await comfyPage.page.dragAndDrop(nodeSelector, canvasSelector)
// Verify the node is added to the canvas
expect(await comfyPage.getGraphNodesCount()).toBe(count + 1)
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', maxSpeed)
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
await comfyPage.page.reload()
await comfyPage.setup()
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', defaultSpeed)
})
})

View File

@@ -2,9 +2,23 @@ import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Node search box', () => {
test('Can trigger on empty canvas double click', async ({ comfyPage }) => {
await comfyPage.doubleClickCanvas()
await expect(comfyPage.searchBox.input).toHaveCount(1)
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting(
'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
'always'
)
})
;['always', 'hold shift', 'NOT hold shift'].forEach((triggerMode) => {
test(`Can trigger on empty canvas double click (${triggerMode})`, async ({
comfyPage
}) => {
await comfyPage.setSetting(
'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
triggerMode
)
await comfyPage.doubleClickCanvas()
await expect(comfyPage.searchBox.input).toHaveCount(1)
})
})
test('Can trigger on link release', async ({ comfyPage }) => {
@@ -21,7 +35,10 @@ test.describe('Node search box', () => {
test('Can auto link node', async ({ comfyPage }) => {
await comfyPage.disconnectEdge()
await comfyPage.searchBox.fillAndSelectFirstNode('CLIPTextEncode')
// Select the second item as the first item is always reroute
await comfyPage.searchBox.fillAndSelectFirstNode('CLIPTextEncode', {
suggestionIndex: 0
})
await expect(comfyPage.canvas).toHaveScreenshot('auto-linked-node.png')
})
@@ -40,7 +57,10 @@ test.describe('Node search box', () => {
await comfyPage.dragAndDrop(outputSlot1Pos, emptySpacePos)
await comfyPage.page.keyboard.up('Shift')
await comfyPage.searchBox.fillAndSelectFirstNode('Load Checkpoint')
// Select the second item as the first item is always reroute
await comfyPage.searchBox.fillAndSelectFirstNode('Load Checkpoint', {
suggestionIndex: 0
})
await expect(comfyPage.canvas).toHaveScreenshot(
'auto-linked-node-batch.png'
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,24 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './ComfyPage'
test.describe('Properties Panel', () => {
test('Can change property value', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.rightClickEmptyLatentNode()
await comfyPage.page.getByText('Properties Panel').click()
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-node-properties-panel.png'
)
const propertyInput = comfyPage.page.locator('span.property_value').first()
// Ensure no keybinds are triggered while typing
await propertyInput.pressSequentially('abcdefghijklmnopqrstuvwxyz')
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-node-properties-panel-property-changed.png'
)
await propertyInput.fill('Empty Latent Image')
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -18,9 +18,9 @@
window["__COMFYUI_FRONTEND_VERSION__"] = __COMFYUI_FRONTEND_VERSION__;
console.log("ComfyUI Front-end version:", __COMFYUI_FRONTEND_VERSION__);
</script>
<script type="module" src="/src/main.ts"></script>
<link rel="stylesheet" type="text/css" href="/user.css" />
<link rel="stylesheet" type="text/css" href="/materialdesignicons.min.css" />
<script type="module" src="src/main.ts"></script>
<link rel="stylesheet" type="text/css" href="user.css" />
<link rel="stylesheet" type="text/css" href="materialdesignicons.min.css" />
</head>
<body class="litegraph">
<div id="vue-app"></div>

209
package-lock.json generated
View File

@@ -1,24 +1,26 @@
{
"name": "comfyui-frontend",
"version": "1.2.16",
"version": "1.2.34",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "comfyui-frontend",
"version": "1.2.16",
"version": "1.2.34",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@comfyorg/litegraph": "^0.7.43",
"@comfyorg/litegraph": "^0.7.48",
"@primevue/themes": "^4.0.0-rc.2",
"@vitejs/plugin-vue": "^5.0.5",
"@vueuse/core": "^11.0.0",
"axios": "^1.7.4",
"class-transformer": "^0.5.1",
"dotenv": "^16.4.5",
"fuse.js": "^7.0.0",
"lodash": "^4.17.21",
"pinia": "^2.1.7",
"primeicons": "^7.0.0",
"primevue": "^4.0.0-rc.2",
"primevue": "^4.0.0",
"reflect-metadata": "^0.2.2",
"vue": "^3.4.31",
"vue-i18n": "^9.13.1",
@@ -1879,9 +1881,9 @@
"dev": true
},
"node_modules/@comfyorg/litegraph": {
"version": "0.7.43",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.43.tgz",
"integrity": "sha512-IX+A/cqscFkSUyon/RgyYg2OU1Af2dfW2QSFr068huf+BcHZz8YMqzNjpSPH/SwZNw98EcUuDtYBajfqeWu38Q==",
"version": "0.7.48",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.7.48.tgz",
"integrity": "sha512-BmR91huOjoMvVvdQ8Pw+L/Iez+ZIxHXA/ApfLwUTOVFa+SvwlFY76qD6C0Hw64jOx9fous1jIQUp35X0xF0RGw==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
@@ -3318,30 +3320,55 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.0.1.tgz",
"integrity": "sha512-0psCSZr3906UwC4mTl2ol4aDoLvdbM0ekJFLKvCvC2oQ9z2YZhmUOVZQNNxBW34mChDzZYcAcRWXADQz3z5lBg==",
"peer": true,
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@primeuix/utils": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@primeuix/utils/-/utils-0.0.5.tgz",
"integrity": "sha512-ntUiUgtRtkF8KuaxHffzhYxQxoXk6LAPHm7CVlFjdqS8Rx8xRkLkZVyo84E+pO2hcNFkOGVP/GxHhQ2s94O8zA==",
"license": "MIT",
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@primevue/core": {
"version": "4.0.0-rc.2",
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.0.0-rc.2.tgz",
"integrity": "sha512-S0RGGdW/M/ogIKeif6JwkJrPLC8pyuBYy2zZlloXXj6Fpvk416sMpO308sx89fqvba2d5poT9a+AbkGob4TDtQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@primevue/core/-/core-4.0.0.tgz",
"integrity": "sha512-M+GF1HYnl/x5J6uevXh1k42J0XnFhp0XHce+cHddWg7v3bVwgsn7LD3AKKcf0A/iQQPXVKX9nY/4/9eFVct67w==",
"license": "MIT",
"dependencies": {
"@primeuix/styled": "^0.0.1"
"@primeuix/styled": "^0.0.5"
},
"engines": {
"node": ">=12.11.0"
},
"peerDependencies": {
"@primeuix/utils": "^0.0.5",
"vue": "^3.0.0"
}
},
"node_modules/@primevue/icons": {
"version": "4.0.0-rc.2",
"resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.0.0-rc.2.tgz",
"integrity": "sha512-VZFETBtmpAduGxXhxcYoqiuGriidyz4XR/4NcrR87CQpUWmT13PXfRoqZ1hLXwNZp1XferT1F5GtY1mHcYxJdw==",
"node_modules/@primevue/core/node_modules/@primeuix/styled": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@primeuix/styled/-/styled-0.0.5.tgz",
"integrity": "sha512-pVoGn/uPkVm/DyF3TR3EmH/pL/dP4nR42FcYbVduFq9VfO3KVeOEqvcCULHXos66RZO9MCbCFUoLy6ctf9GUGQ==",
"license": "MIT",
"dependencies": {
"@primevue/core": "4.0.0-rc.2"
"@primeuix/utils": "^0.0.5"
},
"engines": {
"node": ">=12.11.0"
}
},
"node_modules/@primevue/icons": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@primevue/icons/-/icons-4.0.0.tgz",
"integrity": "sha512-gv9pbj7JjCuW59tW2csIJgg6btTJpkr/mjlfqscEIrYzDGqzCrbfxLur48gA2dyhYsiQPPTbIHFwL944piFgIg==",
"license": "MIT",
"dependencies": {
"@primevue/core": "4.0.0"
},
"engines": {
"node": ">=12.11.0"
@@ -3748,6 +3775,12 @@
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"dev": true
},
"node_modules/@types/web-bluetooth": {
"version": "0.0.20",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"license": "MIT"
},
"node_modules/@types/yargs": {
"version": "17.0.32",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
@@ -4186,6 +4219,94 @@
"node": ">=0.10.0"
}
},
"node_modules/@vueuse/core": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-11.0.0.tgz",
"integrity": "sha512-shibzNGjmRjZucEm97B8V0NO5J3vPHMCE/mltxQ3vHezbDoFQBMtK11XsfwfPionxSbo+buqPmsCljtYuXIBpw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.20",
"@vueuse/metadata": "11.0.0",
"@vueuse/shared": "11.0.0",
"vue-demi": ">=0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-11.0.0.tgz",
"integrity": "sha512-0TKsAVT0iUOAPWyc9N79xWYfovJVPATiOPVKByG6jmAYdDiwvMVm9xXJ5hp4I8nZDxpCcYlLq/Rg9w1Z/jrGcg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-11.0.0.tgz",
"integrity": "sha512-i4ZmOrIEjSsL94uAEt3hz88UCz93fMyP/fba9S+vypX90fKg3uYX9cThqvWc9aXxuTzR0UGhOKOTQd//Goh1nQ==",
"license": "MIT",
"dependencies": {
"vue-demi": ">=0.14.10"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
"hasInstallScript": true,
"license": "MIT",
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/abab": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
@@ -4365,8 +4486,7 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": {
"version": "10.4.19",
@@ -4405,6 +4525,17 @@
"postcss": "^8.1.0"
}
},
"node_modules/axios": {
"version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -5035,7 +5166,6 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -5331,7 +5461,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -6151,6 +6280,26 @@
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
@@ -6183,7 +6332,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dev": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
@@ -9165,7 +9313,6 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -9174,7 +9321,6 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -9909,12 +10055,13 @@
"integrity": "sha512-jK3Et9UzwzTsd6tzl2RmwrVY/b8raJ3QZLzoDACj+oTJ0oX7L9Hy+XnVwgo4QVKlKpnP/Ur13SXV/pVh4LzaDw=="
},
"node_modules/primevue": {
"version": "4.0.0-rc.2",
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.0.0-rc.2.tgz",
"integrity": "sha512-1SICPga4FA5sx27h5FBnMHWidPJRn58mPZj0LNlJygp+9P5ksrtRRNdfDPzmRB+fWcmhvbIxk46meyHeMw1wTQ==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/primevue/-/primevue-4.0.0.tgz",
"integrity": "sha512-2PFmmJqyXpOcKOdF+gbps5fpSXfoXZp2LwX+hya/b5SDseMt3UNboyEgVI6B+DNbJRrib35EbDiMw+7RIANQ1w==",
"license": "MIT",
"dependencies": {
"@primevue/core": "4.0.0-rc.2",
"@primevue/icons": "4.0.0-rc.2"
"@primevue/core": "4.0.0",
"@primevue/icons": "4.0.0"
},
"engines": {
"node": ">=12.11.0"
@@ -9946,6 +10093,12 @@
"dev": true,
"license": "ISC"
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "comfyui-frontend",
"private": true,
"version": "1.2.16",
"version": "1.2.34",
"type": "module",
"scripts": {
"dev": "vite",
@@ -56,16 +56,18 @@
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@comfyorg/litegraph": "^0.7.43",
"@comfyorg/litegraph": "^0.7.48",
"@primevue/themes": "^4.0.0-rc.2",
"@vitejs/plugin-vue": "^5.0.5",
"@vueuse/core": "^11.0.0",
"axios": "^1.7.4",
"class-transformer": "^0.5.1",
"dotenv": "^16.4.5",
"fuse.js": "^7.0.0",
"lodash": "^4.17.21",
"pinia": "^2.1.7",
"primeicons": "^7.0.0",
"primevue": "^4.0.0-rc.2",
"primevue": "^4.0.0",
"reflect-metadata": "^0.2.2",
"vue": "^3.4.31",
"vue-i18n": "^9.13.1",

File diff suppressed because one or more lines are too long

View File

@@ -2,21 +2,35 @@
<ProgressSpinner v-if="isLoading" class="spinner"></ProgressSpinner>
<BlockUI full-screen :blocked="isLoading" />
<GlobalDialog />
<GlobalToast />
<GraphCanvas />
</template>
<script setup lang="ts">
import { computed, markRaw, onMounted, watch } from 'vue'
import {
computed,
markRaw,
onMounted,
onUnmounted,
watch,
watchEffect
} from 'vue'
import BlockUI from 'primevue/blockui'
import ProgressSpinner from 'primevue/progressspinner'
import GraphCanvas from '@/components/graph/GraphCanvas.vue'
import QueueSideBarTab from '@/components/sidebar/tabs/QueueSideBarTab.vue'
import QueueSidebarTab from '@/components/sidebar/tabs/QueueSidebarTab.vue'
import { app } from './scripts/app'
import { useSettingStore } from './stores/settingStore'
import { useI18n } from 'vue-i18n'
import { useWorkspaceStore } from './stores/workspaceStateStore'
import NodeLibrarySideBarTab from './components/sidebar/tabs/NodeLibrarySideBarTab.vue'
import NodeLibrarySidebarTab from './components/sidebar/tabs/NodeLibrarySidebarTab.vue'
import GlobalDialog from './components/dialog/GlobalDialog.vue'
import GlobalToast from './components/toast/GlobalToast.vue'
import { api } from './scripts/api'
import { StatusWsMessageStatus } from './types/apiTypes'
import { useQueuePendingTaskCountStore } from './stores/queueStore'
import type { ToastMessageOptions } from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
const isLoading = computed<boolean>(() => useWorkspaceStore().spinner)
const theme = computed<string>(() =>
@@ -36,6 +50,14 @@ watch(
{ immediate: true }
)
watchEffect(() => {
const fontSize = useSettingStore().get('Comfy.TextareaWidget.FontSize')
document.documentElement.style.setProperty(
'--comfy-textarea-font-size',
`${fontSize}px`
)
})
const { t } = useI18n()
const init = () => {
useSettingStore().addSettings(app.ui.settings)
@@ -43,28 +65,63 @@ const init = () => {
app.extensionManager.registerSidebarTab({
id: 'queue',
icon: 'pi pi-history',
title: t('sideToolBar.queue'),
tooltip: t('sideToolBar.queue'),
component: markRaw(QueueSideBarTab),
iconBadge: () => {
const value = useQueuePendingTaskCountStore().count.toString()
return value === '0' ? null : value
},
title: t('sideToolbar.queue'),
tooltip: t('sideToolbar.queue'),
component: markRaw(QueueSidebarTab),
type: 'vue'
})
app.extensionManager.registerSidebarTab({
id: 'node-library',
icon: 'pi pi-book',
title: t('sideToolBar.nodeLibrary'),
tooltip: t('sideToolBar.nodeLibrary'),
component: markRaw(NodeLibrarySideBarTab),
title: t('sideToolbar.nodeLibrary'),
tooltip: t('sideToolbar.nodeLibrary'),
component: markRaw(NodeLibrarySidebarTab),
type: 'vue'
})
}
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
const onStatus = (e: CustomEvent<StatusWsMessageStatus>) =>
queuePendingTaskCountStore.update(e)
const toast = useToast()
const reconnectingMessage: ToastMessageOptions = {
severity: 'error',
summary: t('reconnecting')
}
const onReconnecting = () => {
toast.remove(reconnectingMessage)
toast.add(reconnectingMessage)
}
const onReconnected = () => {
toast.remove(reconnectingMessage)
toast.add({
severity: 'success',
summary: t('reconnected'),
life: 2000
})
}
onMounted(() => {
api.addEventListener('status', onStatus)
api.addEventListener('reconnecting', onReconnecting)
api.addEventListener('reconnected', onReconnected)
try {
init()
} catch (e) {
console.error('Failed to init Vue app', e)
}
})
onUnmounted(() => {
api.removeEventListener('status', onStatus)
api.removeEventListener('reconnecting', onReconnecting)
api.removeEventListener('reconnected', onReconnected)
})
</script>
<style scoped>

View File

@@ -103,6 +103,7 @@ body {
#graph-canvas {
width: 100%;
height: 100%;
touch-action: none;
}
.comfyui-body-right {
@@ -131,7 +132,7 @@ body {
resize: none;
border: none;
box-sizing: border-box;
font-size: 10px;
font-size: var(--comfy-textarea-font-size);
}
.comfy-modal {

View File

@@ -4,27 +4,43 @@
class="side-bar-panel"
:minSize="10"
:size="20"
v-show="sideBarPanelVisible"
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'left'"
>
<slot name="side-bar-panel"></slot>
</SplitterPanel>
<SplitterPanel class="graph-canvas-panel" :size="100">
<div></div>
</SplitterPanel>
<SplitterPanel
class="side-bar-panel"
:minSize="10"
:size="20"
v-show="sidebarPanelVisible"
v-if="sidebarLocation === 'right'"
>
<slot name="side-bar-panel"></slot>
</SplitterPanel>
</Splitter>
</template>
<script setup lang="ts">
import { useSettingStore } from '@/stores/settingStore'
import { useWorkspaceStore } from '@/stores/workspaceStateStore'
import Splitter from 'primevue/splitter'
import SplitterPanel from 'primevue/splitterpanel'
import { computed } from 'vue'
const sideBarPanelVisible = computed(
const settingStore = useSettingStore()
const sidebarLocation = computed<'left' | 'right'>(() =>
settingStore.get('Comfy.Sidebar.Location')
)
const sidebarPanelVisible = computed(
() => useWorkspaceStore().activeSidebarTab !== null
)
const gutterClass = computed(() => {
return sideBarPanelVisible.value ? '' : 'gutter-hidden'
return sidebarPanelVisible.value ? '' : 'gutter-hidden'
})
</script>

View File

@@ -0,0 +1,58 @@
<!-- A image with placeholder fallback on error -->
<template>
<img
:src="src"
@error="handleImageError"
:class="[{ 'broken-image': imageBroken }, ...classArray]"
/>
<div v-if="imageBroken" class="broken-image-placeholder">
<i class="pi pi-image"></i>
<span>{{ $t('imageFailedToLoad') }}</span>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
const props = defineProps<{
src: string
class?: string | string[] | object
}>()
const imageBroken = ref(false)
const handleImageError = (e: Event) => {
imageBroken.value = true
}
const classArray = computed(() => {
if (Array.isArray(props.class)) {
return props.class
} else if (typeof props.class === 'string') {
return props.class.split(' ')
} else if (typeof props.class === 'object') {
return Object.keys(props.class).filter((key) => props.class[key])
}
return []
})
</script>
<style scoped>
.broken-image {
display: none;
}
.broken-image-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
margin: 2rem;
}
.broken-image-placeholder i {
font-size: 3rem;
margin-bottom: 0.5rem;
}
</style>

View File

@@ -44,6 +44,7 @@ defineEmits(['action'])
.no-results-placeholder :deep(.p-card) {
background-color: var(--surface-ground);
text-align: center;
box-shadow: unset;
}
.no-results-placeholder h3 {

View File

@@ -1,35 +1,45 @@
<template>
<IconField :class="props.class">
<InputIcon class="pi pi-search" />
<InputIcon :class="props.icon" />
<InputText
class="search-box-input"
@input="handleInput"
:modelValue="props.modelValue"
:placeholder="$t('searchSettings') + '...'"
:placeholder="props.placeholder"
/>
</IconField>
</template>
<script setup lang="ts">
import { debounce } from 'lodash'
import IconField from 'primevue/iconfield'
import InputIcon from 'primevue/inputicon'
import InputText from 'primevue/inputtext'
import { debounce } from 'lodash'
const props = defineProps<{
interface Props {
class?: string
modelValue: string
}>()
const emit = defineEmits(['update:modelValue', 'search'])
const emitSearch = debounce((event: KeyboardEvent) => {
const target = event.target as HTMLInputElement
emit('search', target.value)
}, 300)
placeholder?: string
icon?: string
debounceTime?: number
}
const handleInput = (event: KeyboardEvent) => {
const props = withDefaults(defineProps<Props>(), {
placeholder: 'Search...',
icon: 'pi pi-search',
debounceTime: 300
})
const emit = defineEmits(['update:modelValue', 'search'])
const emitSearch = debounce((value: string) => {
emit('search', value)
}, props.debounceTime)
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement
emit('update:modelValue', target.value)
emitSearch(event)
emitSearch(target.value)
}
</script>

View File

@@ -6,7 +6,7 @@
closable
closeOnEscape
dismissableMask
:maximizable="dialogStore.props.maximizable ?? false"
:maximizable="maximizable"
@hide="dialogStore.closeDialog"
@maximize="maximized = true"
@unmaximize="maximized = false"
@@ -19,20 +19,20 @@
<h3 v-else>{{ dialogStore.title || ' ' }}</h3>
</template>
<component
:is="dialogStore.component"
v-bind="dialogStore.props"
:maximized="maximized"
/>
<component :is="dialogStore.component" v-bind="contentProps" />
</Dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { useDialogStore } from '@/stores/dialogStore'
import Dialog from 'primevue/dialog'
const dialogStore = useDialogStore()
const maximizable = dialogStore.props.maximizable ?? false
const maximized = ref(false)
const contentProps = computed(() => ({
...dialogStore.props,
...(dialogStore.props.maximizable ? { maximized } : {})
}))
</script>

View File

@@ -0,0 +1,184 @@
<template>
<NoResultsPlaceholder
icon="pi pi-exclamation-circle"
:title="props.error.node_type"
:message="props.error.exception_message"
/>
<div class="comfy-error-report">
<Button
v-show="!reportOpen"
:label="$t('showReport')"
@click="showReport"
text
/>
<template v-if="reportOpen">
<Divider />
<ScrollPanel style="width: 100%; height: 400px; max-width: 80vw">
<pre class="wrapper-pre">{{ reportContent }}</pre>
</ScrollPanel>
<Divider />
</template>
<div class="action-container">
<FindIssueButton
:errorMessage="props.error.exception_message"
:repoOwner="repoOwner"
:repoName="repoName"
/>
<Button
:label="$t('openNewIssue')"
icon="pi pi-github"
@click="openNewGithubIssue"
class="p-button-secondary"
/>
<Button
v-if="reportOpen"
:label="$t('copyToClipboard')"
icon="pi pi-copy"
@click="copyReportToClipboard"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useClipboard } from '@vueuse/core'
import { useToast } from 'primevue/usetoast'
import Button from 'primevue/button'
import Divider from 'primevue/divider'
import ScrollPanel from 'primevue/scrollpanel'
import FindIssueButton from '@/components/dialog/content/error/FindIssueButton.vue'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import type { ExecutionErrorWsMessage, SystemStats } from '@/types/apiTypes'
import { api } from '@/scripts/api'
import { app } from '@/scripts/app'
const props = defineProps<{
error: ExecutionErrorWsMessage
}>()
const repoOwner = 'comfyanonymous'
const repoName = 'ComfyUI'
const reportContent = ref('')
const reportOpen = ref(false)
const showReport = () => {
reportOpen.value = true
}
const toast = useToast()
const { copy, isSupported } = useClipboard()
onMounted(async () => {
generateReport(await api.getSystemStats())
})
const generateReport = (systemStats: SystemStats) => {
// The default JSON workflow has about 3000 characters.
const MAX_JSON_LENGTH = 20000
const workflowJSONString = JSON.stringify(app.graph.serialize())
const workflowText =
workflowJSONString.length > MAX_JSON_LENGTH
? 'Workflow too large. Please manually upload the workflow from local file system.'
: workflowJSONString
reportContent.value = `
# ComfyUI Error Report
## Error Details
- **Node Type:** ${props.error.node_type}
- **Exception Type:** ${props.error.exception_type}
- **Exception Message:** ${props.error.exception_message}
## Stack Trace
\`\`\`
${props.error.traceback.join('\n')}
\`\`\`
## System Information
- **OS:** ${systemStats.system.os}
- **Python Version:** ${systemStats.system.python_version}
- **Embedded Python:** ${systemStats.system.embedded_python}
## Devices
${systemStats.devices
.map(
(device) => `
- **Name:** ${device.name}
- **Type:** ${device.type}
- **VRAM Total:** ${device.vram_total}
- **VRAM Free:** ${device.vram_free}
- **Torch VRAM Total:** ${device.torch_vram_total}
- **Torch VRAM Free:** ${device.torch_vram_free}
`
)
.join('\n')}
## Attached Workflow
Please make sure that workflow does not contain any sensitive information such as API keys or passwords.
\`\`\`
${workflowText}
\`\`\`
## Additional Context
(Please add any additional context or steps to reproduce the error here)
`
}
const copyReportToClipboard = async () => {
if (isSupported) {
try {
await copy(reportContent.value)
toast.add({
severity: 'success',
summary: 'Success',
detail: 'Report copied to clipboard',
life: 3000
})
} catch (err) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Failed to copy report'
})
}
} else {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'Clipboard API not supported in your browser'
})
}
}
const openNewGithubIssue = async () => {
await copyReportToClipboard()
const issueTitle = encodeURIComponent(
`[Bug]: ${props.error.exception_type} in ${props.error.node_type}`
)
const issueBody = encodeURIComponent(
'The report has been copied to the clipboard. Please paste it here.'
)
const url = `https://github.com/${repoOwner}/${repoName}/issues/new?title=${issueTitle}&body=${issueBody}`
window.open(url, '_blank')
}
</script>
<style scoped>
.comfy-error-report {
display: flex;
flex-direction: column;
gap: 1rem;
}
.action-container {
display: flex;
gap: 1rem;
justify-content: flex-end;
}
.wrapper-pre {
white-space: pre-wrap;
word-wrap: break-word;
}
.no-results-placeholder {
padding-top: 0;
}
</style>

Some files were not shown because too many files have changed in this diff Show More