Compare commits

..

194 Commits

Author SHA1 Message Date
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
Chenlei Hu
7ef8e56c25 1.2.16 (#366) 2024-08-10 21:06:51 -04:00
Chenlei Hu
9ffdf768b1 Better no task placeholder (#365) 2024-08-10 21:05:40 -04:00
Chenlei Hu
23fcdd3e44 Attach DOM widgets to canvas container instead of document body (#364) 2024-08-10 18:38:56 -04:00
Chenlei Hu
7ce7490bc3 Add search settings feature (#362)
* Add setting searchbox ui

* Basic search

* Remove first divider

* Keep group label on search result

* No result placeholder

* Prevent no result flash

* i18n

* Disable category nav when searching
2024-08-10 17:26:57 -04:00
pythongosssss
3e7b0a4907 Test using latest examples (#361) 2024-08-10 16:46:33 -04:00
Chenlei Hu
a095e7ecae Show category in node search box instead of description (#360) 2024-08-10 14:52:37 -04:00
Chenlei Hu
fe0d63e16c Use consistent NodeId and SlotIndex in schema (#359)
* Use consistent NodeId and SlotIndex in schema

* Add test
2024-08-10 14:15:00 -04:00
Chenlei Hu
3c76554bec 1.2.15 (#358) 2024-08-10 11:55:13 -04:00
Chenlei Hu
ce2a2dd2b6 Add link release searchbox trigger mode (#356) 2024-08-10 10:30:29 -04:00
Chenlei Hu
d6c304690c Allow skipping workflow validation (#355) 2024-08-10 09:49:12 -04:00
Chenlei Hu
e1bc2708d3 Make workflow group color optional (#354) 2024-08-10 08:55:48 -04:00
Chenlei Hu
95dc6ff5de Refactor nodeDefStore.nodeTree (#351) 2024-08-09 17:53:42 -04:00
Chenlei Hu
4b83ee3918 link release pops up searchbox by default (#348) (#350)
* link release pops up searchbox by default (#348)

* link release pops up searchbox by default

* Update browser test

* Fix tests

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-08-09 16:33:30 -04:00
Chenlei Hu
078df413b5 Update lg (ES6 LGraph) (#347) 2024-08-09 11:36:52 -04:00
Chenlei Hu
3fc85f1fb6 Update README.md (#346) 2024-08-09 11:11:43 -04:00
Chenlei Hu
ce14c1c071 i18n for setting dialog header (#343) 2024-08-09 09:30:08 -04:00
Chenlei Hu
0d31dc5b4c Reduce setting content height to avoid dialog level scroll (#341) 2024-08-08 20:31:00 -04:00
Chenlei Hu
42c1e4b5e1 1.2.14 (#340) 2024-08-08 20:15:38 -04:00
Chenlei Hu
5490ccf4f0 Assign default category Others (#339)
* Assign default category Others

* nit
2024-08-08 20:09:49 -04:00
Chenlei Hu
a5f0d2b201 Categorize setting items (#338)
* Basic setting panel rework

* refactor

* Style the setting item

* Reject invalid value

* nit

* nit

* Sort settings by label

* info chip as icon

* nit
2024-08-08 17:52:41 -04:00
Chenlei Hu
02d7f91e9e Migrate settings dialog to Vue (#335)
* Basic setting dialog

* Add custom setting value render

* handle combo options

* Add input slider

* 100% width for select dropdown
2024-08-07 14:01:43 -04:00
Chenlei Hu
eb1c66c90a Revert "Revert "Update litegraph (Vite build) (#320)" (#329)" (#332)
This reverts commit 3249bbf4ab.
2024-08-06 21:44:44 -04:00
Chenlei Hu
6b1776450b Explicitly bind litegraph names to global scope (#331) 2024-08-06 21:40:05 -04:00
Chenlei Hu
7804b25d5f 1.2.13 (#330) 2024-08-06 21:06:34 -04:00
Chenlei Hu
3249bbf4ab Revert "Update litegraph (Vite build) (#320)" (#329)
This reverts commit e162d0007c.
2024-08-06 21:04:48 -04:00
Chenlei Hu
baa1e54fc0 1.2.12 (#328) 2024-08-06 20:26:40 -04:00
Chenlei Hu
968a1da227 Add more litegraph change browser tests (#326)
* Add tests on litegraph batch disconnect shortcut (#323)

* Add tests on right click node pin/unpin (#324)

* Update test expectations [skip ci]

---------

Co-authored-by: bymyself <abolkonsky.rem@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
2024-08-06 20:24:40 -04:00
Chenlei Hu
a013d83fc0 Shift dialog close to right (#327) 2024-08-06 20:23:57 -04:00
Chenlei Hu
564ec887f2 Fix script type (#325) 2024-08-06 20:18:34 -04:00
Chenlei Hu
79469bd2b1 Missing node dialog revamp (#322)
* Basic rework of load workflow warning dialog

* Better style

* Add vue jest support

* Mock vue component in jest test

* nit

* Make dialog maximizable
2024-08-06 20:11:05 -04:00
Robin Huang
6fe2297cc1 Add models information to default workflow. (#321)
* Add models information to default workflow.

* Add models to zod schema.

* Fix zod schema.

* Update schema name.

* Add z prefix to modelfile schema name.
2024-08-06 13:45:07 -04:00
Chenlei Hu
e162d0007c Update litegraph (Vite build) (#320) 2024-08-06 10:46:19 -04:00
Chenlei Hu
5419865740 1.2.11 (#318) 2024-08-05 20:32:46 -04:00
Chenlei Hu
b90b1194d6 Organize searchbox files (#315) 2024-08-05 18:29:06 -04:00
Chenlei Hu
1eb45ddc55 Update README.md (#314) 2024-08-05 17:47:42 -04:00
Chenlei Hu
2f1df2c6ce Proper truncate of long content in node preview (#313) 2024-08-05 16:57:36 -04:00
Chenlei Hu
3269b54aae Sync 2666 Execution Model Inversion (#312)
* Sync 2666 Execution Model Inversion

* Fix unittest

* Fallback compatible
2024-08-05 16:42:37 -04:00
Chenlei Hu
50c4c87e62 Add error boundary on node.DrawBackground (#311) 2024-08-05 15:41:12 -04:00
Chenlei Hu
1b96c16cca Sync commit fixing flac metadata (#307) 2024-08-05 13:59:44 -04:00
Chenlei Hu
31ca016055 1.2.10 (#301) 2024-08-04 20:15:29 -04:00
Chenlei Hu
cf9d95aa97 Fix PrimeVue ref error on adding node via searchbox (#298) 2024-08-04 12:27:02 -04:00
pythongosssss
7a980f46c9 Add support for node/input/output tooltips (#287)
* Add support for node/input/output tooltips

* pr feedback

* Remove
2024-08-04 11:54:46 -04:00
Chenlei Hu
c48f68e53e Revert to sync validation on node def for better performance (#297) 2024-08-04 11:02:54 -04:00
Chenlei Hu
0210c7f438 Update README.md (#296) 2024-08-04 10:15:07 -04:00
Chenlei Hu
0384cf96c4 Reduce loglevel on node def validation (#295)
* Lower the loglevel of node def validation messages

* Fix tests
2024-08-04 10:08:55 -04:00
Chenlei Hu
4bebcb408e Add eslint (#294)
* Add eslint

* Add eslint github action
2024-08-04 09:35:49 -04:00
余腾靖
b5a919e8b2 fix: remove useless @ts-ignore and migrate to @ts-expect-error (#293)
* fix: vite primevue/treenode import error

* refactor: remove useless @ts-ignore and replace with @ts-expect-error

* build(tsconfig): enable incremental to speed up secondary time type check
2024-08-04 07:22:24 -04:00
Chenlei Hu
7e669b0f68 Add issue templates (#290) 2024-08-03 21:30:25 -04:00
Chenlei Hu
244cd3c920 1.2.9 (#285) 2024-08-03 14:25:16 -04:00
Chenlei Hu
0cf5e647af Fix copy paste of widget value (#284)
* Fix copy paste of widget value

* Fix ui tests

* Allow undefined group font size

* Update test expectations [skip ci]

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-08-03 14:22:43 -04:00
Chenlei Hu
f0f867481d Fix canvas not init issue (#283) 2024-08-03 10:48:54 -04:00
Chenlei Hu
d02b074fa3 Manage searchbox imp setting in Vue app (#282)
* Manage searchbox setting in vue

* nit
2024-08-03 10:31:10 -04:00
Chenlei Hu
e14d84526a 1.2.8 (#279) 2024-08-01 21:19:41 -04:00
Chenlei Hu
2aa9166079 Disable flux example workflow test (#278) 2024-08-01 21:17:28 -04:00
Chenlei Hu
3baa07e0a9 Update litegraph (Font size fix / Perf improvement) (#275) 2024-07-31 11:06:04 -04:00
Chenlei Hu
c494cd211e Allow INT/FLOAT represent list of numbers (#274) 2024-07-31 10:45:46 -04:00
Chenlei Hu
c00e2fd208 Allow input spec with extra values passthrough (#273)
* Allow input spec extra values passthrough

* Refine custom input spec

* nit

* nit
2024-07-31 09:51:32 -04:00
bymyself
d77343da83 Sync #4090 (#272) 2024-07-31 08:45:44 -04:00
Chenlei Hu
c611c15d40 Update README.md (#271) 2024-07-30 19:12:44 -04:00
Chenlei Hu
269686eebb 1.2.7 (#270) 2024-07-30 19:09:22 -04:00
Chenlei Hu
0e3590d017 Update litegraph (Batch link move with shift + drag) (#268)
* Refactor based on new event data format

* nit

* Add playwright tests

* Update test expectations [skip ci]

* nit

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-07-30 19:06:58 -04:00
Alistor
7d2d6df57b Add spellcheck option to Multiline widget, add Interrupt Queue keybind (#267)
* Add spellcheck option to Multiline widget and set to false by default

* Add Queue Interrupt Keybind

* Update keybinds.ts

Fixed indentation
2024-07-30 17:34:54 -04:00
Chenlei Hu
4462dabc63 Truncate JSON default value in node preview (#264) 2024-07-30 10:12:47 -04:00
Chenlei Hu
53bfc0c95a Block UI interaction when loading (#263) 2024-07-30 09:56:40 -04:00
Chenlei Hu
b78682689e Update litegraph (#262) 2024-07-30 09:25:27 -04:00
Chenlei Hu
6d1dce8255 1.2.6 (#261) 2024-07-29 18:38:51 -04:00
Chenlei Hu
73f4e5143d Attach isLeaf info (#260) 2024-07-29 17:49:57 -04:00
Chenlei Hu
7d75cc99ba Add sort button in node library sidebar tab (#259)
* Add sort button on node library

* tab template
2024-07-29 12:39:54 -04:00
Chenlei Hu
0aa7d0b99a Store spinner state in workspace state store (#256) 2024-07-29 10:54:22 -04:00
Chenlei Hu
66b690e5c8 Manage canvas element in Vue (#255)
* Manage canvas element in Vue

* nit

* Fix unittest
2024-07-29 10:29:29 -04:00
Chenlei Hu
6e27b884fc Fix node preview widget overflow (#254)
* Fix node preview widget overflow

* nit
2024-07-28 21:51:14 -04:00
Chenlei Hu
561162fb3e Update litegraph (Fix drag + alt copy node) (#253)
* Update litegraph

* Update github action

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-07-28 13:39:45 -04:00
Chenlei Hu
7c6bd7ed71 1.2.5 (#252) 2024-07-28 09:41:53 -04:00
Chenlei Hu
fc5bdf80b3 Fix no task message display (#251) 2024-07-28 09:39:17 -04:00
Chenlei Hu
033f242e43 Float workflow selection on top of sidebar (#250) 2024-07-28 09:37:00 -04:00
Chenlei Hu
304429b967 Update README.md (#249) 2024-07-28 08:20:41 -04:00
Chenlei Hu
6dbdb9baa6 Fix clientX/Y offset calculation (#248) 2024-07-28 07:50:00 -04:00
filtered
3e3e909e36 Fix "undo" incorrectly undoing text input (#247)
Fixes an issue where under certain conditions, the ComfyUI custom undo / redo functions would not run when intended to.

When trying to undo an action like deleting several nodes, instead the native browser undo runs - e.g. a textarea gets focus and the last typed text is undone.  Clicking outside the textarea and hitting ctrl + z again just keeps doing the same thing.
2024-07-28 06:57:05 -04:00
202 changed files with 6788 additions and 1290 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

72
.github/ISSUE_TEMPLATE/bug-report.yaml vendored Normal file
View File

@@ -0,0 +1,72 @@
name: Bug Report
description: "Something is not behaving as expected."
title: "[Bug]: "
labels: ['Potential Bug']
body:
- type: markdown
attributes:
value: |
Before submitting a **Bug Report**, please ensure the following:
- **1:** You are running the latest version of ComfyUI.
- **2:** You have looked at the existing bug reports and made sure this isn't already reported.
- **3:** You confirmed that the bug is not caused by a custom node. You can disable all custom nodes by passing
`--disable-all-custom-nodes` command line argument.
- type: textarea
attributes:
label: Frontend Version
description: 'What is the frontend version you are using? You can check this in the settings dialog'
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: 'What you expected to happen.'
validations:
required: true
- type: textarea
attributes:
label: Actual Behavior
description: 'What actually happened. Please include a screenshot / video clip of the issue if possible.'
validations:
required: true
- type: textarea
attributes:
label: Steps to Reproduce
description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than ComfyUI, in which case it should be reported to the node's author."
validations:
required: true
- type: textarea
attributes:
label: Debug Logs
description: 'Please copy the output from your terminal logs here.'
render: powershell
validations:
required: true
- type: textarea
attributes:
label: Browser Logs
description: 'Please copy the output from your browser logs here. You can access this by pressing F12 to toggle the developer tools, then navigating to the Console tab.'
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers do you use to access the UI ?
multiple: true
options:
- Mozilla Firefox
- Google Chrome
- Brave
- Apple Safari
- Microsoft Edge
- Android
- iOS
- Other
- type: textarea
attributes:
label: Other
description: 'Any other additional information you think might be helpful.'
validations:
required: false

View File

@@ -0,0 +1,40 @@
name: Feature Request
description: Suggest an idea for this project
title: "[Feature Request]: "
labels: ["enhancement"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.
options:
- label: I have searched the existing issues and checked the recent builds/commits
required: true
- type: markdown
attributes:
value: |
*Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*
- type: textarea
id: feature
attributes:
label: What would your feature do ?
description: Tell us about your feature in a very clear and simple way, and what problem it would solve
validations:
required: true
- type: textarea
id: workflow
attributes:
label: Proposed workflow
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
value: |
1. Go to ....
2. Press ....
3. ...
validations:
required: true
- type: textarea
id: misc
attributes:
label: Additional information
description: Add any other context or screenshots about the feature request here.

25
.github/workflows/eslint.yaml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: ESLint
on:
push:
branches:
- main
- master
- 'dev*'
pull_request:
branches:
- main
- master
- 'dev*'
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: lts/*
- run: npm ci
- run: npm run lint

View File

@@ -21,7 +21,7 @@ jobs:
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v4
with:
repository: "huchenlei/ComfyUI_frontend"
repository: "Comfy-Org/ComfyUI_frontend"
path: "ComfyUI_frontend"
ref: ${{ github.head_ref }}
- uses: actions/setup-node@v3

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

170
README.md
View File

@@ -1,34 +1,175 @@
<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
```
### Stable Release
Stable releases are published weekly in the ComfyUI main repository, aligned with ComfyUI backend's stable release schedule.
#### Feature Freeze
There will be a 2-day feature freeze before each stable release. During this period, no new major features will be merged.
## Release Summary
### Major features
<details>
<summary>v1.2.4: Node library sidebar tab</summary>
#### Drag & Drop
https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
#### Filter
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
</details>
<details>
<summary>v1.2.0: Queue/History sidebar tab</summary>
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
</details>
<details>
<summary>v1.1.0: Node search box</summary>
#### Fuzzy search & Node preview
![image](https://github.com/user-attachments/assets/94733e32-ea4e-4a9c-b321-c1a05db48709)
#### Release link with shift
https://github.com/user-attachments/assets/a1b2b5c3-10d1-4256-b620-345de6858f25
</details>
### QoL changes
<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
</details>
<details>
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
#### Before
https://github.com/user-attachments/assets/c253f778-82d5-4e6f-aec0-ea2ccf421651
#### After
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
</details>
<details>
<summary>v1.1.8: **Litegraph** hides text overflow on widget value</summary>
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
</details>
### Node developers API
<details>
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
Extensions now can call the following API to register a sidebar tab.
```js
app.extensionManager.registerSidebarTab({
id: "search",
icon: "pi pi-search",
title: "search",
tooltip: "search",
type: "custom",
render: (el) => {
el.innerHTML = "<div>Custom search tab</div>";
},
});
```
The list of supported icons can be found here: <https://primevue.org/icons/#list>
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.
- Make litegraph a npm dependency. <https://github.com/Comfy-Org/ComfyUI_frontend/pull/89>
- Introduce Vue to start managing part of the UI.
- Starting with node search box revamp ![image](https://github.com/user-attachments/assets/ef6ce019-5194-4e55-9f1e-91440e473920)
- Easy install and version management (<https://github.com/comfyanonymous/ComfyUI/pull/3897>).
- Better node management. Sherlock <https://github.com/Nuked88/ComfyUI-N-Sidebar>.
### What to be done
@@ -36,10 +177,9 @@ pause
- Replace the existing ComfyUI front-end impl
- Remove `@ts-ignore`s.
- Turn on `strict` on `tsconfig.json`.
- Introduce a UI library to add more widget types for node developers.
- Add more widget types for node developers.
- LLM streaming node.
- Linear mode (Similar to InvokeAI's linear mode).
- Better node management. Sherlock https://github.com/Nuked88/ComfyUI-N-Sidebar.
- Keybinding settings management. Register keybindings API for custom nodes.
- New extensions API for adding UI-related features.
@@ -67,11 +207,11 @@ core extensions will be loaded.
- `npm run test:generate:examples` to extract the example workflows
- `npm run test` to execute all unit tests.
### LiteGraph
This repo is using litegraph package hosted on https://github.com/Comfy-Org/litegraph.js. Any changes to litegraph should be submitted in that repo instead.
## Deploy
- 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) {}
@@ -74,16 +80,16 @@ class NodeLibrarySideBarTab {
}
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() {
@@ -170,7 +176,16 @@ 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 reload() {
await this.page.reload({ timeout: 15000 })
await this.setup()
}
@@ -185,6 +200,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 +230,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 +309,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,193 @@
{
"last_node_id": 10,
"last_link_id": 9,
"nodes": [
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
0,
92
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
3,
5
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"3Guofeng3_v32Light.safetensors"
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
460,
92
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 3
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
460,
368
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 10,
"type": "CheckpointLoaderSimple",
"pos": [
0,
276
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"3Guofeng3_v32Light.safetensors"
]
}
],
"links": [
[
3,
4,
1,
6,
0,
"CLIP"
],
[
5,
4,
1,
7,
0,
"CLIP"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -22,6 +22,28 @@ test.describe('Copy Paste', () => {
expect(resultString).toBe(originalString + originalString)
})
test('Can copy and paste widget value', async ({ comfyPage }) => {
// Copy width value (512) from empty latent node to KSampler's seed.
// Empty latent node's width
await comfyPage.canvas.click({
position: {
x: 718,
y: 643
}
})
await comfyPage.ctrlC()
// KSampler's seed
await comfyPage.canvas.click({
position: {
x: 1005,
y: 281
}
})
await comfyPage.ctrlV()
await comfyPage.page.keyboard.press('Enter')
await expect(comfyPage.canvas).toHaveScreenshot('copied-widget-value.png')
})
/**
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/98
*/
@@ -53,4 +75,15 @@ test.describe('Copy Paste', () => {
await comfyPage.ctrlV()
await expect(comfyPage.canvas).toHaveScreenshot('no-node-copied.png')
})
test('Copy node by dragging + alt', async ({ comfyPage }) => {
// TextEncodeNode1
await comfyPage.page.mouse.move(618, 191)
await comfyPage.page.keyboard.down('Alt')
await comfyPage.page.mouse.down()
await comfyPage.page.mouse.move(100, 100)
await comfyPage.page.mouse.up()
await comfyPage.page.keyboard.up('Alt')
await expect(comfyPage.canvas).toHaveScreenshot('drag-copy-copied-node.png')
})
})

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.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

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

@@ -27,6 +27,9 @@ test.describe('Node Interaction', () => {
test('Can disconnect/connect edge', async ({ comfyPage }) => {
await comfyPage.disconnectEdge()
// Close search menu popped up.
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'disconnected-edge-with-menu.png'
)
@@ -58,6 +61,54 @@ test.describe('Node Interaction', () => {
await expect(comfyPage.canvas).toHaveScreenshot('snap_to_slot_linked.png')
})
test('Can batch move links by drag with shift', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('batch_move_links')
await expect(comfyPage.canvas).toHaveScreenshot('batch_move_links.png')
const outputSlot1Pos = {
x: 304,
y: 127
}
const outputSlot2Pos = {
x: 307,
y: 310
}
await comfyPage.page.keyboard.down('Shift')
await comfyPage.dragAndDrop(outputSlot1Pos, outputSlot2Pos)
await comfyPage.page.keyboard.up('Shift')
await expect(comfyPage.canvas).toHaveScreenshot(
'batch_move_links_moved.png'
)
})
test('Can batch disconnect links with ctrl+alt+click', async ({
comfyPage
}) => {
const loadCheckpointClipSlotPos = {
x: 332,
y: 508
}
await comfyPage.canvas.click({
modifiers: ['Control', 'Alt'],
position: loadCheckpointClipSlotPos
})
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'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.describe('Canvas Interaction', () => {
@@ -68,8 +119,48 @@ 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 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.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 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: 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.

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.

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: 35 KiB

View File

@@ -44,7 +44,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,7 +62,7 @@ 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)

View File

@@ -2,22 +2,30 @@ 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 }) => {
await comfyPage.page.keyboard.down('Shift')
await comfyPage.disconnectEdge()
await expect(comfyPage.searchBox.input).toHaveCount(1)
})
test('Does not trigger on link release (no shift)', async ({ comfyPage }) => {
await comfyPage.disconnectEdge()
await expect(comfyPage.searchBox.input).toHaveCount(0)
})
test('Can add node', async ({ comfyPage }) => {
await comfyPage.doubleClickCanvas()
await expect(comfyPage.searchBox.input).toHaveCount(1)
@@ -26,10 +34,35 @@ test.describe('Node search box', () => {
})
test('Can auto link node', async ({ comfyPage }) => {
await comfyPage.page.keyboard.down('Shift')
await comfyPage.disconnectEdge()
await comfyPage.page.keyboard.up('Shift')
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')
})
test('Can auto link batch moved node', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('batch_move_links')
const outputSlot1Pos = {
x: 304,
y: 127
}
const emptySpacePos = {
x: 5,
y: 5
}
await comfyPage.page.keyboard.down('Shift')
await comfyPage.dragAndDrop(outputSlot1Pos, emptySpacePos)
await comfyPage.page.keyboard.up('Shift')
// 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.

After

Width:  |  Height:  |  Size: 81 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

@@ -85,4 +85,36 @@ test.describe('Node Right Click Menu', () => {
'right-click-node-widget-converted.png'
)
})
test('Can pin and unpin', async ({ comfyPage }) => {
await comfyPage.rightClickEmptyLatentNode()
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
await comfyPage.page.click('.litemenu-entry:has-text("Pin")')
await comfyPage.nextFrame()
await comfyPage.dragAndDrop({ x: 621, y: 617 }, { x: 16, y: 16 })
await comfyPage.rightClickEmptyLatentNode()
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-pinned-node.png'
)
await comfyPage.page.click('.litemenu-entry:has-text("Unpin")')
await comfyPage.nextFrame()
await comfyPage.rightClickEmptyLatentNode()
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-unpinned-node.png'
)
})
test('Can move after unpin', async ({ comfyPage }) => {
await comfyPage.rightClickEmptyLatentNode()
await comfyPage.page.click('.litemenu-entry:has-text("Pin")')
await comfyPage.nextFrame()
await comfyPage.rightClickEmptyLatentNode()
await comfyPage.page.click('.litemenu-entry:has-text("Unpin")')
await comfyPage.nextFrame()
await comfyPage.page.waitForTimeout(256)
await comfyPage.dragAndDrop({ x: 496, y: 618 }, { x: 200, y: 590 })
await expect(comfyPage.canvas).toHaveScreenshot(
'right-click-unpinned-node-moved.png'
)
})
})

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.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

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

32
eslint.config.js Normal file
View File

@@ -0,0 +1,32 @@
import globals from 'globals'
import pluginJs from '@eslint/js'
import tseslint from 'typescript-eslint'
import pluginVue from 'eslint-plugin-vue'
export default [
{
files: ['src/**/*.{js,mjs,cjs,ts,vue}']
},
{
ignores: [
'src/scripts/*',
'src/extensions/core/*',
'src/types/vue-shim.d.ts'
]
},
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
...pluginVue.configs['flat/essential'],
{
files: ['src/**/*.vue'],
languageOptions: { parserOptions: { parser: tseslint.parser } }
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/prefer-as-const': 'off'
}
}
]

View File

@@ -12,15 +12,15 @@
font-family: 'Roboto Mono', 'Noto Color Emoji';
}
</style> -->
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script type="module" src="node_modules/reflect-metadata/Reflect.js"></script>
<script type="module">
import 'reflect-metadata';
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>

View File

@@ -3,7 +3,9 @@ import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
testMatch: ['**/tests-ui/**/*.test.ts'],
testEnvironment: 'jsdom',
moduleFileExtensions: ['js', 'jsx', 'json', 'vue', 'ts', 'tsx'],
transform: {
'^.+\\.vue$': '@vue/vue3-jest',
'^.+\\.m?[tj]sx?$': [
'ts-jest',
{

1787
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "comfyui-frontend",
"private": true,
"version": "1.2.4",
"version": "1.2.30",
"type": "module",
"scripts": {
"dev": "vite",
@@ -15,20 +15,28 @@
"test:generate": "npx tsx tests-ui/setup",
"test:browser": "npx playwright test",
"prepare": "husky || true",
"preview": "vite preview"
"preview": "vite preview",
"lint": "eslint src",
"lint:fix": "eslint src --fix"
},
"devDependencies": {
"@babel/core": "^7.24.7",
"@babel/preset-env": "^7.22.20",
"@eslint/js": "^9.8.0",
"@playwright/test": "^1.44.1",
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.6",
"@types/node": "^20.14.8",
"@vue/test-utils": "^2.4.6",
"@vue/vue3-jest": "^29.2.6",
"autoprefixer": "^10.4.19",
"babel-plugin-transform-import-meta": "^2.2.1",
"babel-plugin-transform-rename-import": "^2.3.0",
"chalk": "^5.3.0",
"eslint": "^9.8.0",
"eslint-plugin-vue": "^9.27.0",
"fs-extra": "^11.2.0",
"globals": "^15.9.0",
"husky": "^9.0.11",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
@@ -41,22 +49,24 @@
"ts-node": "^10.9.2",
"tsx": "^4.15.6",
"typescript": "^5.4.5",
"typescript-eslint": "^8.0.0",
"vite": "^5.2.0",
"vite-plugin-static-copy": "^1.0.5",
"zip-dir": "^2.0.0"
},
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@comfyorg/litegraph": "^0.7.29",
"@comfyorg/litegraph": "^0.7.47",
"@primevue/themes": "^4.0.0-rc.2",
"@vitejs/plugin-vue": "^5.0.5",
"@vueuse/core": "^11.0.0",
"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",

View File

@@ -36,7 +36,7 @@ export default defineConfig({
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
timeout: 5000
timeout: 15000
}
// {

View File

@@ -1,36 +1,38 @@
<template>
<ProgressSpinner v-if="isLoading" class="spinner"></ProgressSpinner>
<div v-else>
<NodeSearchboxPopover v-if="nodeSearchEnabled" />
<teleport to=".graph-canvas-container">
<LiteGraphCanvasSplitterOverlay v-if="betaMenuEnabled">
<template #side-bar-panel>
<SideToolBar />
</template>
</LiteGraphCanvasSplitterOverlay>
</teleport>
</div>
<BlockUI full-screen :blocked="isLoading" />
<GlobalDialog />
<GlobalToast />
<GraphCanvas />
</template>
<script setup lang="ts">
import { computed, markRaw, onMounted, onUnmounted, ref, watch } from 'vue'
import NodeSearchboxPopover from '@/components/NodeSearchBoxPopover.vue'
import SideToolBar from '@/components/sidebar/SideToolBar.vue'
import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitterOverlay.vue'
import QueueSideBarTab from '@/components/sidebar/tabs/QueueSideBarTab.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 { 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 { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'
import { useNodeDefStore } from './stores/nodeDefStore'
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 = ref(true)
const nodeSearchEnabled = computed<boolean>(
() => useSettingStore().get('Comfy.NodeSearchBoxImpl') === 'default'
)
const isLoading = computed<boolean>(() => useWorkspaceStore().spinner)
const theme = computed<string>(() =>
useSettingStore().get('Comfy.ColorPalette')
)
@@ -47,61 +49,78 @@ watch(
},
{ immediate: true }
)
const betaMenuEnabled = computed(
() => useSettingStore().get('Comfy.UseNewMenu') !== 'Disabled'
)
watchEffect(() => {
const fontSize = useSettingStore().get('Comfy.TextareaWidget.FontSize')
document.documentElement.style.setProperty(
'--comfy-textarea-font-size',
`${fontSize}px`
)
})
const { t } = useI18n()
let dropTargetCleanup = () => {}
const init = () => {
useSettingStore().addSettings(app.ui.settings)
app.vueAppReady = true
app.extensionManager = useWorkspaceStore()
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'
})
}
dropTargetCleanup = dropTargetForElements({
element: document.querySelector('.graph-canvas-container'),
onDrop: (event) => {
const loc = event.location.current.input
// Add an offset on x to make sure after adding the node, the cursor
// is on the node (top left corner)
const pos = app.clientPosToCanvasPos([loc.clientX - 20, loc.clientY])
const comfyNodeName = event.source.element.getAttribute(
'data-comfy-node-name'
)
const nodeDef = useNodeDefStore().nodeDefsByName[comfyNodeName]
app.addNodeOnGraph(nodeDef, { pos })
}
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)
} finally {
isLoading.value = false
}
})
onUnmounted(() => {
dropTargetCleanup()
api.removeEventListener('status', onStatus)
api.removeEventListener('reconnecting', onReconnecting)
api.removeEventListener('reconnected', onReconnected)
})
</script>

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

@@ -0,0 +1,58 @@
<template>
<div class="no-results-placeholder">
<Card>
<template #content>
<div class="flex flex-column align-items-center">
<i :class="icon" style="font-size: 3rem; margin-bottom: 1rem"></i>
<h3>{{ title }}</h3>
<p>{{ message }}</p>
<Button
v-if="buttonLabel"
:label="buttonLabel"
@click="$emit('action')"
class="p-button-text"
/>
</div>
</template>
</Card>
</div>
</template>
<script setup lang="ts">
import Card from 'primevue/card'
import Button from 'primevue/button'
defineProps<{
icon?: string
title: string
message: string
buttonLabel?: string
}>()
defineEmits(['action'])
</script>
<style scoped>
.no-results-placeholder {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 2rem;
}
.no-results-placeholder :deep(.p-card) {
background-color: var(--surface-ground);
text-align: center;
}
.no-results-placeholder h3 {
color: var(--text-color);
margin-bottom: 0.5rem;
}
.no-results-placeholder p {
color: var(--text-color-secondary);
margin-bottom: 1rem;
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<IconField :class="props.class">
<InputIcon :class="props.icon" />
<InputText
class="search-box-input"
@input="handleInput"
:modelValue="props.modelValue"
: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'
interface Props {
class?: string
modelValue: string
placeholder?: string
icon?: string
debounceTime?: number
}
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(target.value)
}
</script>
<style scoped>
.search-box-input {
width: 100%;
}
</style>

View File

@@ -0,0 +1,38 @@
<!-- The main global dialog to show various things -->
<template>
<Dialog
v-model:visible="dialogStore.isVisible"
modal
closable
closeOnEscape
dismissableMask
:maximizable="dialogStore.props.maximizable ?? false"
@hide="dialogStore.closeDialog"
@maximize="maximized = true"
@unmaximize="maximized = false"
>
<template #header>
<component
v-if="dialogStore.headerComponent"
:is="dialogStore.headerComponent"
/>
<h3 v-else>{{ dialogStore.title || ' ' }}</h3>
</template>
<component
:is="dialogStore.component"
v-bind="dialogStore.props"
:maximized="maximized"
/>
</Dialog>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useDialogStore } from '@/stores/dialogStore'
import Dialog from 'primevue/dialog'
const dialogStore = useDialogStore()
const maximized = ref(false)
</script>

View File

@@ -0,0 +1,138 @@
<template>
<div class="comfy-missing-nodes">
<h4 class="warning-title">Warning: Missing Node Types</h4>
<p class="warning-description">
When loading the graph, the following node types were not found:
</p>
<ListBox
:options="uniqueNodes"
optionLabel="label"
scrollHeight="100%"
:class="'missing-nodes-list' + (props.maximized ? ' maximized' : '')"
:pt="{
list: { class: 'border-none' }
}"
>
<template #option="slotProps">
<div class="missing-node-item">
<span class="node-type">{{ slotProps.option.label }}</span>
<span v-if="slotProps.option.hint" class="node-hint">{{
slotProps.option.hint
}}</span>
<Button
v-if="slotProps.option.action"
@click="slotProps.option.action.callback"
:label="slotProps.option.action.text"
class="p-button-sm p-button-outlined"
/>
</div>
</template>
</ListBox>
<p v-if="hasAddedNodes" class="added-nodes-warning">
Nodes that have failed to load will show as red on the graph.
</p>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import ListBox from 'primevue/listbox'
import Button from 'primevue/button'
interface NodeType {
type: string
hint?: string
action?: {
text: string
callback: () => void
}
}
const props = defineProps<{
missingNodeTypes: (string | NodeType)[]
hasAddedNodes: boolean
maximized: boolean
}>()
const uniqueNodes = computed(() => {
const seenTypes = new Set()
return props.missingNodeTypes
.filter((node) => {
const type = typeof node === 'object' ? node.type : node
if (seenTypes.has(type)) return false
seenTypes.add(type)
return true
})
.map((node) => {
if (typeof node === 'object') {
return {
label: node.type,
hint: node.hint,
action: node.action
}
}
return { label: node }
})
})
</script>
<style>
:root {
--red-600: #dc3545;
}
</style>
<style scoped>
.comfy-missing-nodes {
font-family: monospace;
color: var(--red-600);
padding: 1.5rem;
background-color: var(--surface-ground);
border-radius: var(--border-radius);
box-shadow: var(--card-shadow);
}
.warning-title {
margin-top: 0;
margin-bottom: 1rem;
}
.warning-description {
margin-bottom: 1rem;
}
.missing-nodes-list {
max-height: 300px;
overflow-y: auto;
}
.missing-nodes-list.maximized {
max-height: unset;
}
.missing-node-item {
display: flex;
align-items: center;
padding: 0.5rem;
}
.node-type {
font-weight: 600;
color: var(--text-color);
}
.node-hint {
margin-left: 0.5rem;
font-style: italic;
color: var(--text-color-secondary);
}
:deep(.p-button) {
margin-left: auto;
}
.added-nodes-warning {
margin-top: 1rem;
font-style: italic;
}
</style>

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