Compare commits

...

380 Commits

Author SHA1 Message Date
christian-byrne
c1f30b96f9 Add key function param in node tree build 2025-01-14 18:19:15 -07:00
Gremlation
c13190cd07 Fix execution_interrupted (#2244) 2025-01-14 11:46:42 -05:00
filtered
00f031e382 [Refactor] Remove old workarounds (#2242) 2025-01-14 10:52:34 -05:00
filtered
04153caaf5 Fix prettier output in CI does not match IDE (#2243) 2025-01-14 10:51:32 -05:00
Chenlei Hu
210bfdeb7d 1.7.10 (#2241) 2025-01-13 20:25:36 -05:00
bymyself
ce0726d85e Restore all open workflows on load (#2238) 2025-01-13 20:24:40 -05:00
bymyself
dd69f9dc30 [Style] Update workflow template cards style (#2239) 2025-01-13 20:21:03 -05:00
Chenlei Hu
3f261f0e53 [Desktop] Add user journey events to the metrics collection list (UI) (#2237)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-12 17:16:16 -05:00
Chenlei Hu
3b2cc23f65 [Desktop] Mark window style setting as experimental (#2236) 2025-01-12 16:14:51 -05:00
bymyself
c50a86b258 [CI] Fix vite config condition (#2235) 2025-01-12 16:09:46 -05:00
Chenlei Hu
1a8c2bba42 1.7.9 (#2234) 2025-01-12 13:43:05 -05:00
Benjamin Lu
fc09951b3e [Style] Visual improvements to WorkflowTabs (#2232)
Co-authored-by: Benjamin Lu <templu1107@proton.me>
2025-01-12 13:41:55 -05:00
Terry Jia
76d5f39607 [3d] use threejs native viewHelper (#2230) 2025-01-12 13:23:23 -05:00
bymyself
9d3bc0f173 Add optional report feature to error dialog (#2229)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-12 13:23:02 -05:00
bymyself
d9b350e159 Add bookmark option in workflow tab context menu (#2231)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-12 13:22:22 -05:00
Chenlei Hu
44610674ee [Desktop] Fix server start view layout (#2226) 2025-01-10 22:14:01 -05:00
Chenlei Hu
9bfce5b8d0 Disable i18n on PRs from forked repo (#2225) 2025-01-10 19:23:20 -05:00
Chenlei Hu
8986fa356a 1.7.8 (#2223) 2025-01-10 19:06:03 -05:00
Chenlei Hu
0c4fd4af1c [Style] Set topbar height to 2.5rem (40px) (#2224) 2025-01-10 19:05:44 -05:00
Chenlei Hu
30cd46ce1f [Desktop] Allow dragging window on empty titlebar (#2222) 2025-01-10 13:41:26 -05:00
filtered
3122c33310 Allow Ctrl + C to send interrupt in terminal (#2221) 2025-01-10 13:30:11 -05:00
Chenlei Hu
91d8d04dc6 [Style] Reduce opacity on workflow tabs scrollbar (#2220) 2025-01-10 10:59:58 -05:00
Chenlei Hu
8f5aa1ff08 [Desktop] Native window in graph view (#2216) 2025-01-09 23:00:28 -05:00
Chenlei Hu
e076783b89 [Desktop] Native window virtual menu bar (#2215) 2025-01-09 20:21:03 -05:00
Chenlei Hu
04c23001fc [Desktop] Set window action buttons style (#2214) 2025-01-09 19:35:22 -05:00
Chenlei Hu
cb265fb0bf 1.7.7 (#2213) 2025-01-09 19:03:43 -05:00
Chenlei Hu
e9211fe377 [Desktop] Add window style setting (#2212)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-09 17:33:57 -05:00
Chenlei Hu
ffc7febeac [BrowserTest] Disable flaky test (#2211) 2025-01-09 14:46:09 -05:00
Chenlei Hu
b15e626607 [Style] Replace px with rem in sidebar width (#2210) 2025-01-09 14:25:34 -05:00
Chenlei Hu
906b5e35a3 [Style] Set fixed top menu bar height (#2209) 2025-01-09 14:14:14 -05:00
Chenlei Hu
e8cd9c7642 [BrowserTest] Replace ComfyPage.reload with ComfyPage.setup (#2208) 2025-01-09 12:54:05 -05:00
Chenlei Hu
93e184e379 Explicitly add zod-to-json-schema as dev dependency (#2207) 2025-01-09 11:41:40 -05:00
Chenlei Hu
1d02cd3c47 [Docs] Extract comfy workflow zod schema as json schema (#2206) 2025-01-09 11:30:47 -05:00
Chenlei Hu
b86e3f71cb 1.7.6 (#2202) 2025-01-08 11:24:59 -05:00
bymyself
1ece2462bd Use more conventional import/export icons in color palette settings (#2199)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-08 10:58:21 -05:00
bymyself
73ecacfa2d Fix searchbox dismissed when closing filter panel (#2196) 2025-01-08 10:57:55 -05:00
bymyself
67e6df7c72 Fix search result category text on light theme (#2198) 2025-01-08 10:57:10 -05:00
Jarvis
7e8510028d fix: Make sure use the correct graph parameter (#2200) 2025-01-08 10:56:27 -05:00
bymyself
dd4dd8b68a [Doc] Add anchors to sections in README (#2197) 2025-01-08 10:55:59 -05:00
bymyself
7111022617 Fix node text highlight color on light theme (#2195) 2025-01-08 09:16:57 -05:00
Chenlei Hu
daee073045 [Style] Fix second row workflow tabs overflow (#2194) 2025-01-07 16:28:49 -05:00
Chenlei Hu
a1a834a76d Support 2nd row location for workflow tabs (#2193)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-07 16:05:37 -05:00
Chenlei Hu
c437d32691 Truncate sidebar title first to avoid tool buttons wrap (#2192) 2025-01-07 15:14:09 -05:00
Chenlei Hu
0130d41be5 Try fix workflow tabs vertical scrollbar (#2191) 2025-01-07 13:39:14 -05:00
Chenlei Hu
527561d148 1.7.5 (#2189) 2025-01-07 10:47:34 -05:00
Chenlei Hu
1c4481c342 Horizontal scroll workflow tabs on overflow (#2188) 2025-01-07 10:45:58 -05:00
Chenlei Hu
07000a23d4 Wrap each init task with error handling (#2187) 2025-01-07 10:40:32 -05:00
Chenlei Hu
ea6c9e7ca5 Replace unset unknown keybinding error with warn (#2186) 2025-01-07 10:33:23 -05:00
Chenlei Hu
90698fced6 [BugFix] Migrate deprecated Comfy.Keybinding.UnsetBindings values (#2185) 2025-01-07 10:23:25 -05:00
iola1999
9716aea10d fix: Fix infinite recursion in mask upload retry logic (#2181) 2025-01-07 16:19:13 +01:00
Chenlei Hu
077ded2cce [Refactor] Simplify keybindingStore with _.groupBy (#2180) 2025-01-06 19:46:20 -05:00
Chenlei Hu
ffb20b8789 1.7.4 (#2179) 2025-01-06 19:24:40 -05:00
Chenlei Hu
549a2fdc92 Remove main_repo_release.py (#2147) 2025-01-06 17:01:41 -05:00
bymyself
e123295423 Fix saved keybinding persistence (#2176) 2025-01-06 15:54:35 -05:00
Chenlei Hu
613b44610a 1.7.3 (#2175) 2025-01-06 11:04:54 -05:00
Chenlei Hu
e82d795ff9 Trigger node def locale update manually (#2170) 2025-01-06 10:54:59 -05:00
bymyself
ba31f8fa68 Add Close Workflow to commands (#2171)
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Chenlei Hu <huchenlei@proton.me>
2025-01-06 10:50:44 -05:00
Alexander Piskun
f2eb4e1519 (fix): added missing slash to "loadWorkflow" templates endpoint (#2174) 2025-01-06 10:49:07 -05:00
Chenlei Hu
975c2248c5 Rename Keybinding.targetSelector to targetElementId (#2169)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-05 16:03:38 -05:00
bymyself
477f4b275d Fix node bookmark color customization (#2168) 2025-01-05 13:05:09 -05:00
bymyself
179b8c22a9 [BrowserTest] Refactor test fixtures (#2165) 2025-01-05 13:03:07 -05:00
bymyself
6525389273 Update Settings schema (#2167) 2025-01-05 12:56:29 -05:00
Chenlei Hu
59fc5ac77e Update litegraph 0.8.60 (#2164) 2025-01-04 20:46:38 -05:00
bymyself
ed844e04b8 Fix text wrapping in dropdown menu (#2162) 2025-01-04 20:30:58 -05:00
bymyself
972af1977d Improve menu accessibility (#2163) 2025-01-04 20:30:27 -05:00
Chenlei Hu
5ac0b7b181 1.7.2 (#2161) 2025-01-04 19:42:15 -05:00
Chenlei Hu
31ea39e44c Prevent reference sharing on settingStore content (#2160) 2025-01-04 19:31:32 -05:00
Chenlei Hu
e65653c107 Add support for custom light color palette (#2156)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-04 18:53:47 -05:00
Chenlei Hu
e46706777c [BrowserTest] Disable flaky queue sidebar tests (#2158) 2025-01-04 16:33:36 -05:00
Chenlei Hu
1a817a48cb 1.7.1 (#2152) 2025-01-03 20:19:11 -05:00
Chenlei Hu
871967349f 1.7.0 (#2151)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-03 20:14:51 -05:00
filtered
11258f4a95 Revert markdown note node, reland as new node (#2148) 2025-01-03 16:05:23 -05:00
bymyself
c3e05c2a10 Add alt text to images in gallery and queue (#2136) 2025-01-03 13:49:06 -05:00
Chenlei Hu
ea1e776dcc Update litegraph 0.8.59 (#2140) 2025-01-03 11:15:16 -05:00
Dr.Lt.Data
f0c273f845 elaborate korean translation (#2137) 2025-01-03 11:14:56 -05:00
Chenlei Hu
ea489851ed [BugFix] Fix open workflow after insert (#2138) 2025-01-03 11:09:28 -05:00
Chenlei Hu
5717c33a0b [BrowserTest] Add test on paste with link (#2130)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-02 20:38:36 -05:00
Chenlei Hu
c31919c418 Remove invertMenuScrolling extension (#2131)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-02 20:19:00 -05:00
bymyself
2e7de4701e Reduce debounce delay in settings search (#2126) 2025-01-02 19:51:31 -05:00
Chenlei Hu
f53723da0f 1.6.15 (#2129) 2025-01-02 17:54:31 -05:00
bymyself
5c7cbe968e Fix gallery changing image every time a task finishes (#2125) 2025-01-02 17:51:28 -05:00
Chenlei Hu
cb607ee070 Update litegraph 0.8.58 (#2128) 2025-01-02 17:47:32 -05:00
Terry Jia
f4b5677901 [3d] support for fov and mask (#2116) 2025-01-02 15:45:37 -05:00
Chenlei Hu
9c1eacf0af Fix duplication of persisted workflow (#2124)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-02 12:16:39 -05:00
Chenlei Hu
bb988edf9f 1.6.14 (#2120) 2025-01-01 19:29:47 -05:00
Chenlei Hu
39e9f421f4 Fix double trigger of loadColorPalette effect (#2118)
Co-authored-by: github-actions <github-actions@github.com>
2025-01-01 19:27:22 -05:00
Chenlei Hu
3d2b9a8d9d Call api.storeSetting only when setting value changes (#2119) 2025-01-01 17:34:58 -05:00
pythongosssss
c77a5cab5b Add support for nested dynamic prompts (#2117) 2025-01-01 15:29:17 -05:00
Chenlei Hu
e525a3ad98 1.6.13 (#2115) 2024-12-31 19:05:26 -05:00
bymyself
f507142e6a Fix error when adding custom setting with no category (#2112)
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-31 19:04:58 -05:00
Chenlei Hu
3189e310a8 Add default confirm dialog (#2114) 2024-12-31 18:18:48 -05:00
Chenlei Hu
174a9a114a Expose dialogService to extensionManager (#2113) 2024-12-31 17:01:37 -05:00
Chenlei Hu
0b3c0cc0c9 Fix i18n-custom-nodes action (#1991)
Co-authored-by: snomiao <snomiao@gmail.com>
2024-12-31 16:03:33 -05:00
bymyself
74b02237db Add virtual scroll to Queue Tab for improved performance (#2108) 2024-12-31 12:35:29 -05:00
bymyself
6dc5c6958f Compress test assets (#2109) 2024-12-31 12:33:29 -05:00
bymyself
70bece2d48 Update history task item schema (#2110) 2024-12-31 12:33:11 -05:00
bymyself
7eea2e5aa0 Increase width of settings dialog on mobile (#2111) 2024-12-31 12:30:40 -05:00
Chenlei Hu
eb94337c43 1.6.12 (#2107) 2024-12-30 21:30:07 -05:00
Chenlei Hu
98b96d2b1a Fix install view stepper layout shift (#2106) 2024-12-30 19:19:40 -05:00
Chenlei Hu
1b10e16b2d Adjust styles on various views (#2105) 2024-12-30 19:09:55 -05:00
Chenlei Hu
d8f074fea0 [Lint] Sort imports (#2104) 2024-12-30 17:26:37 -05:00
Chenlei Hu
e88817ea36 Lint/format vite.config.mts (#2103) 2024-12-30 16:48:57 -05:00
Chenlei Hu
4ae77e22d4 Add eslint to presubmit hook (#2102) 2024-12-30 16:47:12 -05:00
Chenlei Hu
9f23f81879 [Refactor] Move goToNode to litegraphService (#2101) 2024-12-30 16:38:10 -05:00
Chenlei Hu
3bf0dfd218 [Refactor] Move addNodeOnGraph to litegraphService (#2100) 2024-12-30 16:09:44 -05:00
Yuki Shindo
be7e2c6505 Add category-based filtering in settings search (#2086)
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-30 11:23:02 -05:00
Chenlei Hu
6dce285210 Fix litegraph grab cursor style (#2097) 2024-12-30 10:50:53 -05:00
Zoltán Dócs
5218024395 Show workflow templates from custom nodes (#2032) 2024-12-29 22:26:03 -05:00
Chenlei Hu
92a5a8057f 1.6.11 (#2096) 2024-12-29 21:04:37 -05:00
Chenlei Hu
b19f7ca228 Add BaseViewTemplate (#2094) 2024-12-29 18:07:22 -05:00
Chenlei Hu
94f964cf17 [Cleanup] Remove legacy slow jest test utils (#2093) 2024-12-29 17:05:05 -05:00
Chenlei Hu
a7fd9bf0d9 Remove life on default error handler toast (#2091) 2024-12-29 16:42:09 -05:00
Chenlei Hu
642094ead1 Remove legacy slow jest tests (#2092) 2024-12-29 16:25:06 -05:00
Chenlei Hu
95a4623c37 Re-enable keybinding jest test (#2090) 2024-12-29 14:55:18 -05:00
Chenlei Hu
773059da93 1.6.10 (#2087) 2024-12-28 21:37:03 -05:00
Chenlei Hu
a48ad1cb41 Move setting impl from ComfySettingsDialog to settingStore (#2085) 2024-12-28 21:31:09 -05:00
Chenlei Hu
5152985656 [Refactor] Rename settingStore.settings to settingsById (#2081) 2024-12-28 17:39:40 -05:00
Chenlei Hu
ab33eb6262 Remove legacy menu setting Comfy.MenuPosition (#2080)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-27 23:15:43 -05:00
Chenlei Hu
af1a5d9dbc Fix Comfy.WidgetControlMode watcher (#2079) 2024-12-27 22:42:41 -05:00
Chenlei Hu
feabd3f11d [Refactor] Move Comfy.WidgetControlMode to coreSettings (#2078) 2024-12-27 21:06:23 -05:00
Chenlei Hu
74361eebe0 1.6.9 (#2077) 2024-12-27 18:56:46 -05:00
Yuki Shindo
20412f2c74 Enable category-based narrowing and reset to default on search clear (#2071)
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-27 18:06:50 -05:00
Chenlei Hu
861d559f19 Update litegraph 0.8.50 (#2076) 2024-12-27 14:13:36 -05:00
Chenlei Hu
7cb0c230df [Refactor] Move app.registerNodeDef to litegraphService (#2075) 2024-12-27 13:08:42 -05:00
Chenlei Hu
9d94802e26 Activate French translation (#2074) 2024-12-27 10:35:21 -05:00
filtered
34492aed17 Fix context menu text blurred by CSS filter (#2069) 2024-12-27 10:31:17 -05:00
Gremlation
a3be889872 Add Markdown table support to the Note node (#2072) 2024-12-27 10:30:47 -05:00
Chenlei Hu
9acb5abf58 1.6.8 (#2070) 2024-12-26 20:33:03 -05:00
Chenlei Hu
b9edd05623 [i18n] Add French translation (#2064)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-26 20:31:31 -05:00
Chenlei Hu
56a243f958 [Refactor] Deprecate ComfyApp.progress (#2065) 2024-12-26 15:21:58 -05:00
Chenlei Hu
c311806249 [Refactor] Move keybinds extension to keybindingService (#2063) 2024-12-26 13:19:04 -05:00
gremlation
7990491c58 Add Markdown support to the Note node (#2044) 2024-12-26 13:16:00 -05:00
Chenlei Hu
66ada54587 [Refactor] Move vintageClipboard to utils/ (#2062) 2024-12-26 12:55:58 -05:00
DorotaL
a5f9fa006c [i18n] Update zh translations (#2061)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-26 10:55:40 -05:00
Chenlei Hu
f69e88bf41 [Refactor] Convert dialogService to composable (#2058) 2024-12-26 00:26:01 -05:00
Chenlei Hu
4e554503c0 [Refactor] Convert workflowService to composable (#2057) 2024-12-26 00:11:27 -05:00
Chenlei Hu
c00d665aa5 Update litegraph 0.8.49 (#2056) 2024-12-25 23:20:28 -05:00
Chenlei Hu
cce11bb639 Move extension related functions from app to extensionService (#2055) 2024-12-25 23:19:25 -05:00
Chenlei Hu
7771802038 1.6.7 (#2054) 2024-12-25 22:47:42 -05:00
Chenlei Hu
9d7a7dae62 [Theme] Loosen color palette schema (#2053) 2024-12-25 22:47:10 -05:00
Chenlei Hu
e7528f13a2 [Theme] Fallback secondary menu bg to menu bg if not set (#2052) 2024-12-25 21:59:37 -05:00
Chenlei Hu
f851e356f2 Fix flaky reroute insert playwright test (#2051)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-25 21:57:52 -05:00
Chenlei Hu
db572a4085 Implement color palette in Vue (#2047)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-25 21:41:48 -05:00
Chenlei Hu
f1eee96ebc Ignore hidden settings for translation (#2050)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-25 21:26:52 -05:00
Dr.Lt.Data
d11fad8a06 Fix Korean translation (#2049)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-25 19:44:00 -05:00
Chenlei Hu
e961e64497 1.6.6 (#2046) 2024-12-24 17:20:25 -05:00
filtered
006d544ac5 Improve install path validation and i18n (#2042)
Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2024-12-24 14:36:32 -05:00
filtered
467b35f98d Add i18n for next/back/install buttons (#2045)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-24 14:01:54 -05:00
Chenlei Hu
eb43234a3a Fix extension panel search/sort (#2041)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-24 10:51:41 -05:00
bymyself
5da26e917a Fix render issue when removing search filter chips (#2038) 2024-12-24 10:13:14 -05:00
bymyself
96b2987590 Fix batch count limit (#2039) 2024-12-24 10:12:38 -05:00
filtered
653ed6f096 Fix reroutes not included in Insert Workflow (#2037) 2024-12-23 23:24:54 -05:00
Chenlei Hu
3cb303ff05 1.6.5 (#2036) 2024-12-23 21:43:46 -05:00
Chenlei Hu
af6cb31214 Move color palettes to constants/coreColorPalettes.ts (#2035) 2024-12-23 21:43:30 -05:00
Chenlei Hu
54ca111c7c Fix FormItem structure (#2034) 2024-12-23 21:33:03 -05:00
Chenlei Hu
e2bbfe7d80 Fix NODE_DEFAULT_SHAPE in color palette (#2033)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-23 20:44:52 -05:00
Chenlei Hu
3b9a5a61b2 Add color picker and image upload form items (#2031)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-23 16:10:15 -05:00
Chenlei Hu
27ec6139a8 Translate keybinding panel table header (#2030)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-23 14:54:12 -05:00
Chenlei Hu
fa06d6ea99 [Refactor] Split color palettes into separate json files (#2029) 2024-12-23 14:42:03 -05:00
Chenlei Hu
47435cdfed Fix docked actionbar expanding menu height (#2027) 2024-12-23 10:15:05 -05:00
Fahd Arafat
cfb884b118 Rearrange workflow tabs (#2026) 2024-12-23 09:51:17 -05:00
pythongosssss
1af0b4f992 [Electron] Migrate custom nodes (#1892)
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-23 09:33:48 -05:00
bymyself
c36e55f0b7 Auto-focus node filter search (#2024) 2024-12-23 09:27:32 -05:00
bymyself
9f6c828cfe Fix search input failing to focus after adding filter (#2023) 2024-12-23 09:27:17 -05:00
bymyself
cf51254d24 Remove defineProps import (#2025) 2024-12-23 09:25:47 -05:00
Chenlei Hu
b0cc5eac95 1.6.4 (#2021) 2024-12-22 19:52:40 -05:00
bymyself
feeb1d19c8 Fix workflow-insert with reroute nodes (#2008)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-22 19:51:32 -05:00
Chenlei Hu
920266e1ff [ExtensionPanel] Distinguish core and 3rd party extensions (#2020) 2024-12-22 19:41:28 -05:00
Chenlei Hu
b81ccc0507 [ExtensionPanel] Add disable all / enable all actions (#2017)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-22 18:57:58 -05:00
shinich39
f4c523f188 Bypass selected nodes (#2013)
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2024-12-22 16:51:28 -05:00
AustinMroz
cce5ade578 Fix memory leak in preview code (#2012)
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2024-12-22 16:18:31 -05:00
filtered
3767749398 Add About to Help menu (#2010)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-22 16:17:25 -05:00
bymyself
c3ceef0461 Update mobile settings visibility test (#2009) 2024-12-22 16:16:11 -05:00
Terry Jia
ac952fbee3 [3d] fix file path issue for preview 3d node (#2005) 2024-12-22 16:13:15 -05:00
Chenlei Hu
72f7c3101d Always show extension panel in settings dialog (#2002)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-20 16:30:11 -08:00
Chenlei Hu
ca9627cada Move widget link drop conversion to widgetInputs (#2001)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-20 16:10:18 -08:00
Chenlei Hu
c8f62cd963 1.6.3 (#1997) 2024-12-19 18:12:59 -08:00
Chenlei Hu
9900e46b95 Unify title edit handling (#1995) 2024-12-19 16:56:26 -08:00
Chenlei Hu
c433b38ba8 [CNR] Support module sytax of <node name>@<version> (#1994) 2024-12-19 14:48:33 -08:00
Chenlei Hu
0b501d7a1c Add DorotaLuna to translation codeowner (#1990) 2024-12-19 09:46:17 -08:00
Yuki Shindo
0e197261b0 Add Clear Functionality in the SearchBox (#1989)
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-19 09:40:08 -08:00
Chenlei Hu
89bf5c182d Add AIGODLIKE team to translation codeowner (#1983) 2024-12-18 19:26:19 -08:00
Chenlei Hu
e80c75482e 1.6.2 (#1988) 2024-12-18 17:45:38 -08:00
Chenlei Hu
569a131624 [i18n] Add gihtub action to translate custom nodes (#1987) 2024-12-18 17:04:35 -08:00
Chenlei Hu
b1d02c6a7b [i18n] Split settings locale to separate json files (#1986)
Co-authored-by: github-actions <github-actions@github.com>
2024-12-18 14:57:05 -08:00
Zoltán Dócs
abeafd5625 Save sidebar width per tab (#1985) 2024-12-18 14:56:27 -08:00
Chenlei Hu
0906d7873a Update locales (Sync core node defs)(#1984) 2024-12-18 13:24:11 -08:00
Chenlei Hu
0ce3f1ecb0 Update README.md (#1982) 2024-12-18 12:23:00 -08:00
Chenlei Hu
ec6919c5ab 1.6.1 (#1979) 2024-12-17 17:20:50 -08:00
Laurent Erignoux
10de690ccf Updating workflow template to use relative url and ensure resources can be accessed behind a url rewriting proxy. (#1922)
Co-authored-by: Erignoux Laurent <laurent.erignoux@ubisoft.com>
2024-12-17 13:09:38 -08:00
filtered
cb3e4b5ed7 [Refactor] Clean up image nodes, add TS types (#1927)
* [Refactor] Clean up image nodes, add TS types

Should be no functional changes.

* Remove unused code
2024-12-17 13:09:24 -08:00
filtered
2b4ac582d4 Add manual configuration screen (#1926)
* Add manual configuration screen

* nit

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-17 09:52:30 -08:00
filtered
7f60811ebf Fix cannot open groupNodes menu from canvas (#1925)
* Fix cannot open groupNode menu from canvas

* Fix cannot open groupNode menu from canvas

Fixes root cause of issue

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-17 09:47:09 -08:00
Chenlei Hu
90e94df1e4 1.6.0 (#1921) 2024-12-16 18:52:35 -08:00
Hayden
5b72fc7fdc Optimize model library (#1739)
* Adapt experiment models api

* Add model preview
2024-12-16 15:27:30 -08:00
filtered
601b739f75 Add GPU selection to desktop installer (#1888)
* Remove redundant code

* Add GPU picker screen to installer

* Permit jumping installation steps when visited

* Prevent sad girl from covering buttons

* Update gpu options to match availability

* Ensure install screen renders inside bounds

* Pre-select GPU detected by desktop native

* Redesign GPU selection screen

* Prevent text highlight on installer stepper

* Add translations for GPU picker

* Rename for clarity

* Remove unused code

* Update electron types

* Update locales [skip ci]

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2024-12-16 15:26:40 -08:00
Fahd Arafat
a441908db3 display cancelled worklflow images (#1920) 2024-12-16 14:45:49 -08:00
Terry Jia
a376503a7b [3d] improve Preview 3d node (#1918) 2024-12-16 11:56:20 -08:00
Chenlei Hu
69a4a17848 [Multi-User] Press enter in new username textbox trigger login (#1919)
* [Multi-User] Press enter in new username textbox trigger login

* nit
2024-12-16 10:34:33 -08:00
Chenlei Hu
22f9ad31dc 1.5.18 (#1916) 2024-12-15 16:39:43 -08:00
Fahd Arafat
2a02159355 fix settings not showing on small screens (#1914) 2024-12-15 15:40:40 -08:00
Chenlei Hu
9258098e0c [litegraph] Fix slider value display when custom widget label is present (#1915)
* Update litegraph

* Add playwright test

* Update locales [skip ci]

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-15 13:54:03 -08:00
Fahd Arafat
fa48fe587d forward mouse events from textareas to canvas (#1911)
* forward mouse events from textareas to canvas

* use arrow functions

* Apply suggestions from code review

Use pointer events instead of mouse to get extended properties
Remove workaround (only required due to missing extended properties)

* Apply code review - Adjust UX

- Do not prevent middle click from hitting DOM widgets
- Allow middle click and drag to continue functioning if another button is held down

---------

Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
2024-12-15 12:50:32 -08:00
Terry Jia
7b64bfa173 [3d] Set view only when object exists (#1912)
* [3d] Set view only when object exists

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-15 10:57:34 -08:00
Chenlei Hu
59b62eadf1 [i18n] Translate save/export workflow dialog (#1906)
* [i18n] Translate save/export workflow dialog

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-13 16:56:37 -08:00
Chenlei Hu
98912efeb7 1.5.17 (#1907) 2024-12-13 16:55:52 -08:00
Fahd Arafat
c7303ea465 Add new workflow button in topbar for tab view (#1903) 2024-12-13 14:14:13 -08:00
Chenlei Hu
618b451f1e [test] Stricter locator matching on workflows sidebar elements (#1905)
* [test] Stricter locator matching on workflows sidebar elements

* nit
2024-12-13 14:13:56 -08:00
Chenlei Hu
a99f63784d Fix io slot label overwrite by i18n (#1904)
* Add test on export with different locales

* Fix node slot rename
2024-12-13 12:27:16 -08:00
Yoland Yan
6f7c7f3c61 Update default value for 'Opened workflows position' setting from 'Sidebar' to 'Topbar' in coreSettings.ts (#1902) 2024-12-13 09:45:52 -08:00
Chenlei Hu
4f049b66a2 1.5.16 (#1901) 2024-12-13 08:50:18 -08:00
Chenlei Hu
458ff350c8 1.5.15 (#1900)
* 1.5.15

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-13 08:24:01 -08:00
Chenlei Hu
fc8c10bca4 Add Comfy-Desktop.OpenUserGuide in help menu (#1896)
* Add Comfy-Desktop.OpenUserGuide in help menu

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-12 16:39:54 -08:00
filtered
28ab86cd4a Fix #1769 - Keep log on screen after installation error (#1891)
* Hide terminal on server start error

* nit - Doc

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-12 14:41:07 -08:00
Chenlei Hu
efb6f8afe3 Proper fix on locale message type (#1894) 2024-12-12 14:26:43 -08:00
Chenlei Hu
a2549f23c7 Fix circular dependency on updating missing locale (#1893) 2024-12-12 12:26:18 -08:00
oto-ciulis-tt
7877c96ffe feat: Add more server side arguments (#1846)
* feat: Add more server side arguments

* nit

* Update locales [skip ci]

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2024-12-12 11:18:21 -08:00
Chenlei Hu
4997d13219 [i18n] Add commands label translation (#1890)
* [i18n] Add commands label translation

* Use translation
2024-12-12 09:28:47 -08:00
Chenlei Hu
c5135afd29 [i18n] Fix some unintuitive Chinese translations (#1889) 2024-12-12 08:32:53 -08:00
Chenlei Hu
8663340067 1.5.14 (#1886) 2024-12-11 20:03:31 -08:00
Chenlei Hu
a1ffc521a4 [i18n] Update i18n on node defs (#1885)
* [i18n] Update i18n on node defs

* Update test expectations [skip ci]

* Downgrade i18n-cli

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-11 19:59:51 -08:00
Chenlei Hu
f7dce3116a 1.5.13 (#1884) 2024-12-11 18:05:11 -08:00
Chenlei Hu
f23530afcf [i18n] Translate widget label (#1883)
* [i18n] Translate widget label

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-11 18:03:56 -08:00
Chenlei Hu
c5f77de1bf [CSS] Use brighter secondary menu color in light mode (#1882) 2024-12-11 16:11:12 -08:00
Chenlei Hu
c60645d9f4 [i18n] Translate node input label (#1880) 2024-12-11 14:57:49 -08:00
Chenlei Hu
ebbff358a0 [i18n] Fix untranslated dataTypes in ru (#1879) 2024-12-11 12:13:39 -08:00
Chenlei Hu
a39c04d56e [i18n] Split nodeDefs translations to a separate json file (#1878)
* [i18n] Split nodeDefs translations to a separate json file

* build locale
2024-12-11 11:51:54 -08:00
Chenlei Hu
9e61d1509f [i18n] Fix ko translation schema (#1877) 2024-12-11 11:38:00 -08:00
Chenlei Hu
0c511d08e6 [i18n] Use folder mode for locale (#1876)
* [i18n] Use folder mode for locale

* try pipe
2024-12-11 11:29:52 -08:00
filtered
929e3a5733 Add native context menu when using desktop (#1875)
* Add system menu to elements

* Add desktop text context menu control

* Update electron types

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-11 11:29:14 -08:00
Chenlei Hu
75c98d00fc [i18n] Fix node output label translation (#1871) 2024-12-11 09:36:44 -08:00
Chenlei Hu
7bce5ac4d0 1.5.12 (#1868) 2024-12-11 08:33:16 -08:00
Chenlei Hu
e7733d94f9 [i18n] Supress expected translation/fallback warnings (#1867) 2024-12-11 08:32:50 -08:00
Chenlei Hu
20a3a95df2 Fix output label on untranslated output names (#1866)
* Fix output label on untranslated output names

* nit

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-11 08:09:37 -08:00
Yuki Shindo
d2bbf41fb5 Add translated setting names to the search index for improved localization support (#1865)
* feat: add translated setting names to the search index for improved localization support

* nit

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-11 08:07:41 -08:00
Chenlei Hu
97ccc6a854 1.5.11 (#1862) 2024-12-10 17:10:36 -08:00
Chenlei Hu
b48619878f Replace deprecated InputSwitch with ToggleSwitch (#1861) 2024-12-10 17:08:58 -08:00
Chenlei Hu
ac6130a556 [i18n] Translate node input/output label and tooltip (#1860)
* [i18n] Translate node input/output labels

* nit

* Impl actual translation

* Remove outputs where name is the type

* Pick outputs key

* Translate outputs

* Properly persist label

* Translate input tooltips

* Translate single node

* Translate first node

* Translate 10 nodes

* Exclude input name translation

* Update test expectations [skip ci]

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-10 17:08:45 -08:00
Chenlei Hu
86797d2492 [i18n] Collect node data types (#1859)
* [i18n] Collect node data types

* nit

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-10 11:29:54 -08:00
Chenlei Hu
112a693f3d [i18n] Translate setting options (#1858)
* Collect options

* Translate setting options

* Update locales [skip ci]

* Normalize key

* Revert change of logic

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-10 09:31:13 -08:00
oto-ciulis-tt
57701f6145 feat: Adding indicator for completed download (#1852)
* feat: Adding indicator for completed download

* Reverting changes

* Inline color style

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-09 15:09:33 -08:00
filtered
c2145b6f45 Adjust UI theme slightly (#1853)
* Format CSS - prettier

* Cast Minor Shadows

Adds faint shadows to the top and side bars

* Darken the sidebar slightly

* Add theme config for bar shadows

* Fix menu item alignment and spacing

* [Refactor] Prefer canvas setDirty over draw

- nits
- types
2024-12-09 15:03:14 -08:00
Chenlei Hu
0b91d53c9f 1.5.10 (#1850) 2024-12-08 20:42:27 -05:00
Chenlei Hu
b4ccaf4fec Rename EditAttention to EditTokenWeight (#1848)
* Rename EditAttention to EditTokenWeight

* Update locales [skip ci]

* Remove EditAttention

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-08 20:22:09 -05:00
Chenlei Hu
2d1b2cb1cc Fix group node playwright tests (#1849)
* Fix group node playwright tests

* nit

* nit
2024-12-08 20:21:59 -05:00
Chenlei Hu
aa04ab78c1 Replace window.prompt with custom prompt impl (#1847)
* Replace window.prompt with custom prompt impl

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-08 16:55:32 -05:00
Chenlei Hu
1671437fb3 Fix korea translation activation (#1845)
* Fix korea translation activation

* nit
2024-12-08 11:48:05 -05:00
Chenlei Hu
9d49cb0e4b Add support for korea translation (#1830)
* Add support for korea translation

* Update locales [skip ci]

* Update ko.json

* Update locales [skip ci]

* Update ko.json

---------

Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Dr.Lt.Data <128333288+ltdrdata@users.noreply.github.com>
2024-12-08 11:44:36 -05:00
Chenlei Hu
43548785b5 Fix Comfy-Desktop.Reinstall command label (#1844) 2024-12-07 22:13:03 -05:00
Chenlei Hu
b6038128cb [i18n] g global namespace (#1843)
* Batch move global scope i18n to g. namespace

* Minor fix

* Update locale

* Update locales [skip ci]

* More moves

* Regroup icon/color

* nit

* Fix component test

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 22:07:03 -05:00
Chenlei Hu
a76159e9a0 [i18n] Translate node categories (#1842)
* [i18n] Translate node categories

* nit

* Update locales [skip ci]

* Fix some translations

* Do translate

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 21:25:40 -05:00
Chenlei Hu
a0ff78dbdb 1.5.9 (#1840) 2024-12-07 18:54:26 -05:00
Yuki Shindo
059dfcbebb Use i18n keys for workflow tree type labels (#1839)
* fix: use i18n keys for workflow tree type labels

* Inline translations

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-07 18:52:52 -05:00
Chenlei Hu
c17d44638e [i18n] Safe fallback to original string (#1838)
* [i18n] Safe fallback to original string

* nit
2024-12-07 18:48:14 -05:00
filtered
0e385c4262 Require confirmation for desktop reinstall (#1835)
* Add generic default button for unknown dialogs

* Add reinstall confirmation dialog

* Prevent removal of newlines in i18n dialogs

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-07 10:24:00 -05:00
Chenlei Hu
da98b1c4cc Update CODEOWNER matching rule (#1836) 2024-12-07 10:23:43 -05:00
Chenlei Hu
26d2e5de31 Add code owners (#1832)
* Add code owners

* nit
2024-12-06 22:29:21 -05:00
filtered
b0e37036d5 Fix missing titles during desktop installation (#1831)
* Add missing desktop translation keys

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-06 22:18:19 -05:00
Chenlei Hu
b7ddb47d2a 1.5.8 (#1829) 2024-12-06 21:29:11 -05:00
Yuki Shindo
6eb5ee99d8 feat: use friendly language names for Comfy.Locale setting options (#1828) 2024-12-06 20:19:01 -05:00
Chenlei Hu
6409e17d4a Hint for empty canvas when invoking Comfy.Canvas.FitView (#1826) 2024-12-06 18:07:01 -05:00
Yuki Shindo
a6549fb41e docs: Localize menu paths in firstTimeUIMessage (#1825) 2024-12-06 18:06:43 -05:00
Yuki Shindo
06d5064b7c fix: updated About label translation to a more appropriate Japanese term (#1827) 2024-12-06 18:06:20 -05:00
Chenlei Hu
fd621f485e Reload current workflow when locale changes (#1824) 2024-12-06 17:37:25 -05:00
Chenlei Hu
517ae56763 Translate node title and description (#1822)
* Collect node def i18n

* Add collected en locale

* Sort by node id

* Add translations

* Show translated node def

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-06 16:56:54 -05:00
Chenlei Hu
ae26390776 Properly implement ComfyNodeDef interface (#1821)
* nit

* Properly implement ComfyNodeDef interface

* nit

* Mark readonly
2024-12-06 12:00:15 -05:00
Chenlei Hu
aa68422e0f 1.5.7 (#1818) 2024-12-05 20:00:18 -05:00
Chenlei Hu
dac2a2ec86 [Electron] Translate server config panel (#1817)
* Update i18 collection to include server config items

* Use translation

* Update locales [skip ci]

* nit

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 19:56:51 -05:00
Chenlei Hu
5cee4d828f [Electron] Use shortname for server config disable-smart-memory (#1816) 2024-12-05 17:17:24 -05:00
Chenlei Hu
d6247d69ce [Electron] Hide auto-launch in server config (#1815) 2024-12-05 17:02:17 -05:00
Chenlei Hu
e2fa1e65d1 Add Workspace.SearchBox.Toggle command (#1814)
* Add Workspace.SearchBox.Toggle command

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 15:30:02 -05:00
Chenlei Hu
55c04b5533 [Electron] Allow force install on not supported device page (#1812)
* [Electron] Allow force install on not supported device page

* Update locales [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-12-05 12:06:51 -05:00
Chenlei Hu
540e785424 Reland "Replace magic numbers with LGraphEventMode (#1809)" (#1811)
* Revert "Revert "Replace magic numbers with LGraphEventMode (#1809)" (#1810)"

This reverts commit 30a92d40eb.

* Update litegraph
2024-12-05 11:48:07 -05:00
Chenlei Hu
30a92d40eb Revert "Replace magic numbers with LGraphEventMode (#1809)" (#1810)
This reverts commit 679405e0a2.
2024-12-05 11:29:41 -05:00
Chenlei Hu
679405e0a2 Replace magic numbers with LGraphEventMode (#1809) 2024-12-05 11:27:43 -05:00
Chenlei Hu
6cdc524174 Align desktop version badge format with other badges (#1808) 2024-12-05 11:11:28 -05:00
Chenlei Hu
80a42ebf10 Fix fallback of unrecognized extension categories (#1807) 2024-12-05 11:06:06 -05:00
Chenlei Hu
c45f03f5e7 1.5.6 (#1805) 2024-12-04 22:35:06 -05:00
Chenlei Hu
36c2604639 [Electron] Use electronAPI to get ComfyUI core version (#1804) 2024-12-04 22:34:55 -05:00
Chenlei Hu
73f50e7e0b Replace locale presubmit hook with Github action (#1802)
* Replace locale presubmit hook with Github action

* nit

* Make sure electronAPI is defined on window object

* Set correct env var
2024-12-04 21:43:02 -05:00
Chenlei Hu
418fddd669 Revert "Fix setting list nav separator (#1799)" (#1803)
This reverts commit 30465f17e0.
2024-12-04 17:26:44 -08:00
Chenlei Hu
e3cda0e749 Translate queue buttom modes (#1800)
* Translate queue button

* Update label

* nit
2024-12-04 17:43:06 -05:00
Chenlei Hu
30465f17e0 Fix setting list nav separator (#1799) 2024-12-04 16:37:38 -05:00
Chenlei Hu
5206939c78 Translate setting categories (#1797)
* Add i18n for setting categories

* Update locales

* Translate about

* nit

* nit
2024-12-04 16:32:28 -05:00
Chenlei Hu
73396784a8 Keep shim of several legacy components (#1796) 2024-12-04 15:45:38 -05:00
Chenlei Hu
3745d8d791 Remove logging module (#1795)
* Remove logging module

* Remove translation
2024-12-04 14:16:43 -05:00
filtered
735153886f Confirm delete workflow (#1772)
* Add confirm delete workflow prompt

* Add confirm delete workflow setting

* Add delete workflow tests

* Change dialog to modal, set default cancel

* Fix setting version key

* Rename for clarity

* Fix tests

- Move into correct section
- Add confirm control

* Export type: ShowDialogOptions

* Replace workflow overwrite with new dialog

* Add delete workflow confirmation dialog

* Update i18n

* Add item list support to confirmation dialog

* Prevent multiple dialogs for same action

* Add confirm close file when dirty

* Add i18n for overwrite dialog

* Fix regression: confirm dialog setting ignored

* Fix delete last workflow does not open replacement

* Update tests
2024-12-04 14:11:49 -05:00
Chenlei Hu
d04cc4e272 Translate dynamically added settings (#1794)
* Collect settings i18n at runtime

* Translation

* Remove unused file
2024-12-04 13:51:37 -05:00
Chenlei Hu
473aa120eb Fix extra_model_paths_yaml i18n key (#1793) 2024-12-04 13:25:04 -05:00
Chenlei Hu
7986aebf27 Translate command label on top command dropdown menu (#1792)
* collect i18n with playwright

* Add command label translation

* Normalize i18n object key
2024-12-04 13:19:53 -05:00
filtered
2caa87d35d Prevent interface crash from dialog abuse (#1789)
- Adds hard-coded limit of 10 open dialogs
- Discards the oldest item on the dialog stack to maintain size
2024-12-04 10:09:06 -05:00
Chenlei Hu
dabcd4741b [Refactor] Extract command definitions to coreCommandHooks (#1785)
* [Refactor] Extract command definitions to coreCommandHooks

* Register core commands

* Fix rebase issue
2024-12-03 23:06:19 -05:00
Hayden
e893f3ed03 Add a unique key to each showDialog (#1786) 2024-12-03 23:04:27 -05:00
Chenlei Hu
1856479de9 Translate topbar command menu (#1784)
* Rename script

* Translate menu labels

* Update translations

* Display translated menu items
2024-12-03 22:51:15 -05:00
Chenlei Hu
fc69129a2f 1.5.5 (#1783) 2024-12-03 22:05:35 -05:00
Chenlei Hu
d953b8fa46 Merge core settings with existing translations (#1782) 2024-12-03 22:04:19 -05:00
Yuki Shindo
1847db7a47 chore: add locale keys for settings dialog (i18n) (#1779) 2024-12-03 17:27:13 -08:00
Chenlei Hu
9cbcda20a7 [Refactor] Extract top menu bar command definitions (#1778) 2024-12-03 17:17:15 -08:00
filtered
0fe0aea242 Fix keybinds invalidated by capslock state (#1776)
* [Refactor] Simplify keybinds code

* [Refactor] Type safety

* Fix capslock inverts undo/redo shortcuts

* [Refactor] Type safety

* Fix capslock state changes keybinds

* Deprecate keybind deserialize

* Remove keybind deserialize
2024-12-03 19:12:09 -05:00
Chenlei Hu
08ae36818a Fix litegraph types in NodeSearchBoxPopover (#1777) 2024-12-03 19:07:15 -05:00
Chenlei Hu
da6b4d2872 Disable install button when there is error (#1775) 2024-12-03 14:34:03 -05:00
Chenlei Hu
8e293b41f5 [Refactor] Remove abstract inheritance of node search filter (#1774) 2024-12-03 11:39:29 -05:00
Chenlei Hu
d2771a7a1d Always include node title metadata for API export (#1771) 2024-12-02 22:49:42 -05:00
Chenlei Hu
13869da300 1.5.4 (#1770) 2024-12-02 22:09:49 -05:00
filtered
6ba53f3af1 Keep log on screen after installation error (#1769) 2024-12-02 21:57:30 -05:00
Tristan Sommer
b23cebcba4 maskeditor: massive improvements to brush opacity in brush strokes and lines (#1768)
* massive improvements to brush opacity in brush strokes and lines, improved save button visibility

* prettier formatting fixed
2024-12-02 18:16:18 -08:00
pythongosssss
30dfe76577 Allow copy/paste in terminal (#1760) 2024-12-02 19:55:14 -05:00
Chenlei Hu
c1f0cfe366 Move Comfy.EnableWorkflowViewRestore to coreSettings (#1767) 2024-12-02 17:50:46 -05:00
Chenlei Hu
d1279fa474 Move Comfy.TreeExplorer.ItemPadding to Appearance category (#1766) 2024-12-02 17:38:46 -05:00
Chenlei Hu
a56462fc7c Translate core setting name & tooltip (#1765)
* lazy eval default value

* Add setting translation

* Adjust hooks

* Add all translations

* nit

* Normalized setting id

* Update locales

* Fallback

* Locale => Language

* Locale => Language

* Update translations
2024-12-02 17:34:21 -05:00
Chenlei Hu
646bcf595b Revert "Bump @intlify/core-base and vue-i18n (#1762)" (#1764)
This reverts commit 01ac9d9336.
2024-12-02 15:22:49 -05:00
Chenlei Hu
4796677a0a Create presubmit hooks for i18n (#1763)
* Add locale scripts to package.json

* Add i18n hooks
2024-12-02 15:10:53 -05:00
dependabot[bot]
01ac9d9336 Bump @intlify/core-base and vue-i18n (#1762)
Bumps [@intlify/core-base](https://github.com/intlify/vue-i18n/tree/HEAD/packages/core) to 9.14.2 and updates ancestor dependency [vue-i18n](https://github.com/intlify/vue-i18n/tree/HEAD/packages/vue-i18n). These dependencies need to be updated together.


Updates `@intlify/core-base` from 9.13.1 to 9.14.2
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v9.14.2/packages/core)

Updates `vue-i18n` from 9.13.1 to 9.14.2
- [Release notes](https://github.com/intlify/vue-i18n/releases)
- [Changelog](https://github.com/intlify/vue-i18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/intlify/vue-i18n/commits/v9.14.2/packages/vue-i18n)

---
updated-dependencies:
- dependency-name: "@intlify/core-base"
  dependency-type: indirect
- dependency-name: vue-i18n
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 15:10:03 -05:00
pythongosssss
89a9288154 Support closing tab with middle click (#1761) 2024-12-02 15:09:46 -05:00
pythongosssss
17b7ef18d6 Add context menu to tabs (#1759)
* Add context menu to tabs

* Flatten menu
Translate

* Add translations

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-02 13:19:39 -05:00
Yuki Shindo
1afc584393 fix: remove unnecessary $t calls for column headers (#1758) 2024-12-02 09:47:08 -05:00
Chenlei Hu
9d71cdf8ef 1.5.3 (#1756) 2024-12-01 22:48:49 -05:00
pythongosssss
1c7f3e865a Add test for cloning pinned node (#1753)
* Add test for cloning pinned node

* Remove only

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-12-01 22:38:09 -05:00
Yuki Shindo
9ef40189f9 Add lobe-i18n setup and translation scripts and update translation files for consistency (#1751)
* refactor: convert translation files from TS to JSON format

* feat: add lobe-i18n setup and translation scripts

* chore: update translation files for consistency

* chore: refine translations in ja_JP.json for natural phrasing

* refactor: revert locale file names to original simpler format (e.g., en_US → en)
2024-12-01 20:03:17 -05:00
pythongosssss
5c6eecd660 Fix group node manage opening to wrong node type (#1754)
Remove dialog from DOM when closed
Add test
2024-12-01 19:52:08 -05:00
filtered
9b07993e1a Clear console error - missed API type (#1749) 2024-11-30 09:47:15 -05:00
Chenlei Hu
c35d29f31c 1.5.2 (#1745) 2024-11-29 17:31:10 -05:00
Chenlei Hu
e50d7c5eef Update litegraph 0.8.41 (#1744)
* chore: update litegraph to 0.8.41

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-29 17:30:04 -05:00
filtered
7c2cce40de Add default setting for node error colour (#1738) 2024-11-29 13:20:54 -05:00
filtered
2017b9016b Add TS types - API (#1736)
* nit

* Add TS types - API events

* Replace all API event emits with type-safe variants

* Add missing API type

* nit

* Remove test code, nit
2024-11-29 13:15:25 -05:00
filtered
0bf30e7621 Add git download page (#1742) 2024-11-29 13:12:09 -05:00
Chenlei Hu
7f5b685c9f Add current user message in settings dialog (#1740) 2024-11-29 11:24:21 -05:00
Yuki Shindo
ec824579d6 docs: add i18n section to README with instructions for adding new languages (#1735) 2024-11-29 10:57:37 -05:00
Chenlei Hu
9e565154a9 Rework user selection (#1728)
* Move to new route

* Convert to tailwind

* Basic style

* Add userStore

* nit

* nit

* nit

* Remove app.#setUser

* Route to user-select view

* Mock login

* Use primevue UI components

* handle create new user

* Remove legacy user selection

* Add logout button on side toolbar

* Add username to logout button tooltip

* Add playwright tests

* hide logout button in single user server

* nit
2024-11-28 23:36:41 -05:00
Chenlei Hu
541335bb31 1.5.1 (#1734) 2024-11-28 17:25:52 -05:00
Chenlei Hu
814c4b8ef0 Remove node bookmark migration (#1733)
* Remove node bookmarks setting migration

* nit

* nit
2024-11-28 14:28:53 -05:00
Chenlei Hu
df3fff5dbb Remove migration logic (settings & templates storage location) (#1732)
* Remove isNewUserSession handling

* Remove writing of setting and templates to localStorage
2024-11-28 14:24:06 -05:00
Chenlei Hu
b0085114d7 Remove app.storageLocation handling (#1731) 2024-11-28 13:35:08 -05:00
Chenlei Hu
7f9c70386f Update package.json (Fix repo name) (#1729) 2024-11-28 12:41:28 -05:00
filtered
578870d345 Add canvas maximum FPS setting (#1727)
* Add canvas maximum FPS setting

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-28 11:46:54 -05:00
Chenlei Hu
05fab91bda Build comfyui-frontend-types library (#1725)
* Install vite-plugin-dts

* Explicitly type workflowStore

* Working rollup

* Hide diff type

* Inline primevue toast mesage types

* Add prepare-types script to generate package.json for type package

* Add global declaration

* Add publish types Github action

* Update litegraph

* Add @comfyorg to package name
2024-11-28 11:21:19 -05:00
Yuki Shindo
5191e11650 Add Japanese (ja) locale files and update type definition for supported locales (#1726)
* feat: add locale files for Japanese (ja)

* fix: update locale type definition to include all supported languages
2024-11-28 09:45:00 -05:00
filtered
92079a653e Update unsupproted card help link (#1724) 2024-11-27 22:43:03 -05:00
Chenlei Hu
a7d14eb815 1.5.0 (#1723) 2024-11-27 19:25:18 -05:00
Chenlei Hu
9aea6eae70 chore: update litegraph to 0.8.38 (#1722) 2024-11-27 19:23:40 -05:00
Chenlei Hu
88a42172c5 Typecheck vue components (#1721)
* Fix various type issues in vue components

* Add vue tsc

* Add to hooks

* nit
2024-11-27 19:18:16 -05:00
Chenlei Hu
e79013dcfe Remove deprecated type def on ComfyNodeDef (#1720) 2024-11-27 16:16:50 -05:00
Chenlei Hu
08f3370828 Use auto inferred type on electronAPI.Terminal (#1719) 2024-11-27 16:14:30 -05:00
Chenlei Hu
c4d3c672ad Enforce ComfyExtension types (#1718)
* Enforce extension types

* nit
2024-11-27 15:35:18 -05:00
Chenlei Hu
39eaa2e850 [Electron] Add not supported hardware page (#1717) 2024-11-27 10:46:24 -05:00
oto-ciulis-tt
2d022e4e49 feat: Remove successful model downloads (#1710)
* feat: Remove successful model downloads

* PR comments

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
2024-11-27 10:40:43 -05:00
Chenlei Hu
1ac6d6529f Add empty workflows placeholder (#1712) 2024-11-26 17:22:05 -05:00
Chenlei Hu
86fec820ac Remove app.multiUserServer flag (#1711) 2024-11-26 16:06:58 -05:00
Chenlei Hu
030d5845db 1.4.13 (#1709) 2024-11-26 14:27:55 -05:00
Chenlei Hu
dd1c878fdf [Electron] Fix path validation on typing in input box (#1708) 2024-11-26 14:26:46 -05:00
Chenlei Hu
3942603a38 [Electron] Add version number to error state (#1707) 2024-11-26 14:21:37 -05:00
Chenlei Hu
244578db96 1.4.12 (#1706) 2024-11-26 14:02:43 -05:00
Chenlei Hu
6b6edfde9f chore: update litegraph to 0.8.37 (#1704) 2024-11-26 13:20:21 -05:00
Chenlei Hu
c54b675a48 Revert Filter cached/canceled results (#1586) (#1703)
* Revert "Filter cached/canceled results (#1586)"

This reverts commit 6fbf1248f4.

* nit
2024-11-26 13:17:26 -05:00
Chenlei Hu
b7008dfc5c Revert "nit: Fix import of OutputFilters in queue sidebar (#1680)" (#1702)
This reverts commit f97b673481.
2024-11-26 13:11:28 -05:00
Chenlei Hu
d0ad4af51c Revert "Move queueStore update to GraphView (#1679)" (#1701)
This reverts commit c8d5a6f154.
2024-11-26 13:08:42 -05:00
Chenlei Hu
4a182014e1 Revert "Fix queue sidebar tab task filter (#1682)" (#1700)
This reverts commit c1c5573e7f.
2024-11-26 13:08:19 -05:00
Chenlei Hu
46cd522384 Fix save temporary workflow loop on overwrite (#1699) 2024-11-26 10:44:25 -05:00
Hayden
c977667a15 Change dialog to multi-window mode (#1695)
Fixed Dropdown's z-index being below the dialog
2024-11-26 10:11:15 -05:00
Chenlei Hu
d531bc34c4 Make ChangeTracker detect changes in workflow.extra (Except ds) (#1692)
* Compare workflow.extra content

* Add tests
2024-11-25 21:59:27 -05:00
Chenlei Hu
adfbec2744 Add setting to adjust queue MaxHistoryItems (#1689)
* Add MaxHistoryItems

* nit
2024-11-25 18:49:40 -05:00
Chenlei Hu
23521559bf 1.4.11 (#1688) 2024-11-25 18:43:17 -05:00
Chenlei Hu
51f57aba17 Revert "Change dialog to multi-window mode (#1672)" (#1686)
This reverts commit 43c23e526c.
2024-11-25 13:29:47 -05:00
Chenlei Hu
97bab053df Split i18n locales to multiple files (#1683) 2024-11-25 13:18:14 -05:00
Chenlei Hu
c1c5573e7f Fix queue sidebar tab task filter (#1682)
* Fix queue sidebar tab task filter

* nit
2024-11-25 12:59:10 -05:00
Chenlei Hu
16d2a95760 chore: update litegraph to 0.8.36 (#1681) 2024-11-25 11:55:42 -05:00
Chenlei Hu
f97b673481 nit: Fix import of OutputFilters in queue sidebar (#1680) 2024-11-25 11:49:49 -05:00
Chenlei Hu
c8d5a6f154 Move queueStore update to GraphView (#1679) 2024-11-25 11:47:35 -05:00
Chenlei Hu
3708afaf21 [Electron] Add fp32 and fp64 to unet inference precision options (#1678) 2024-11-25 11:20:49 -05:00
Hayden
43c23e526c Change dialog to multi-window mode (#1672) 2024-11-24 21:36:30 -05:00
Chenlei Hu
a80eb84df1 1.4.10 (#1676) 2024-11-24 21:36:00 -05:00
Chenlei Hu
f89898b3d0 Add searchbox for extensions panel (#1675) 2024-11-24 21:33:43 -05:00
Chenlei Hu
af21142602 Use setting panel template (#1674)
* PanelTemplate

* Use panel template
2024-11-24 21:24:13 -05:00
Chenlei Hu
4b91860227 [Refactor] Extract SettingsPanel (#1673)
* [Refactor] Extract SettingsPanel

* nit
2024-11-24 20:48:35 -05:00
Chenlei Hu
e53bafbca6 [Electron] Add custom node migration placeholder (#1670) 2024-11-24 20:16:39 -05:00
Chenlei Hu
e01c8f06c7 [Electron] Show server launch args in server config panel (#1669)
* Move revertChanges

* Show launch args

* Explicit ServerConfigValue type

* nit

* nit

* Add tests
2024-11-24 18:14:05 -05:00
Chenlei Hu
c61ed4da37 Add server config modified message to prompt restart (#1668)
* Server config changed message

* Write to settings on unmount

* nit

* Highlight modified config

* Move modified logic to store

* Add jest test

* nit
2024-11-24 16:13:37 -05:00
Terry Jia
4a4d6d070a restore camera state (#1666) 2024-11-24 16:09:58 -05:00
Tristan Sommer
4bedd873a1 improved mouse brush adjustment, added zoom level indicator with reset, added invert button, bug fixes (#1664) 2024-11-24 11:32:13 -05:00
filtered
f8bd910e63 Fix terminal resizes incorrectly in flex parent (#1663)
Pushes siblings or self off-screen
2024-11-24 11:31:29 -05:00
453 changed files with 60263 additions and 10738 deletions

View File

@@ -20,4 +20,4 @@ jobs:
run: npm ci
- name: Run Prettier check
run: npx prettier --check './**/*.{js,ts,tsx,vue}'
run: npm run format:check

162
.github/workflows/i18n-custom-nodes.yaml vendored Normal file
View File

@@ -0,0 +1,162 @@
name: Update Locales for given custom node repository
on:
workflow_dispatch:
inputs:
owner:
description: 'Owner of the repository to update locales for'
required: true
type: string
repository:
description: 'Repository to update locales for'
required: true
type: string
fork_owner:
description: 'Owner of the forked repository'
required: false
type: string
default: 'Comfy-Org'
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- name: Checkout ComfyUI
uses: actions/checkout@v4
with:
repository: 'comfyanonymous/ComfyUI'
path: 'ComfyUI'
ref: master
- name: Checkout ComfyUI_frontend
uses: actions/checkout@v4
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: Checkout custom node repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.owner }}/${{ inputs.repository }}
path: 'ComfyUI/custom_nodes/${{ inputs.repository }}'
- uses: actions/setup-node@v3
with:
node-version: lts/*
- uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install ComfyUI requirements
run: |
python -m pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements.txt
pip install wait-for-it
shell: bash
working-directory: ComfyUI
- name: Install custom node requirements
run: |
if [ -f "requirements.txt" ]; then
pip install -r requirements.txt
fi
shell: bash
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
- name: Build & Install ComfyUI_frontend
run: |
npm ci
npm run build
rm -rf ../ComfyUI/web/*
mv dist/* ../ComfyUI/web/
shell: bash
working-directory: ComfyUI_frontend
- name: Start ComfyUI server
run: |
python main.py --cpu --multi-user &
wait-for-it --service 127.0.0.1:8188 -t 600
working-directory: ComfyUI
shell: bash
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Capture base i18n
run: npx tsx scripts/diff-i18n capture
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Diff base vs updated i18n
run: npx tsx scripts/diff-i18n diff
working-directory: ComfyUI_frontend
- name: Update i18n in custom node repository
run: |
LOCALE_DIR=ComfyUI/custom_nodes/${{ inputs.repository }}/locales/
install -d "$LOCALE_DIR"
cp -rf ComfyUI_frontend/temp/diff/* "$LOCALE_DIR"
- name: Check and create fork of custom node repository
run: |
# Try to fork the repository
gh repo fork ${{ inputs.owner }}/${{ inputs.repository }} --clone=false || {
echo "Fork failed - repository might already be forked"
# Exit 0 to prevent the workflow from failing
exit 0
}
# Enable workflows on the forked repository
gh api \
--method PUT \
-H "Accept: application/vnd.github+json" \
"/repos/${{ inputs.fork_owner }}/${{ inputs.repository }}/actions/permissions/workflow" \
-F can_approve_pull_request_reviews=true \
-F default_workflow_permissions="write" \
-F enabled=true
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}
- name: Commit changes
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
# Create and switch to new branch
git checkout -b update-locales
# Stage and commit changes
git add -A
git commit -m "Update locales"
- name: Install SSH key For PUSH
uses: shimataro/ssh-key-action@v2
with:
# PR private key from action server
key: ${{ secrets.PR_SSH_PRIVATE_KEY }}
# github public key to confirm it's github server
known_hosts: github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==
- name: Push changes
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
# Force push to create the branch
echo "Pushing changes to ${{ inputs.fork_owner }}/${{ inputs.repository }}"
git push -f git@github.com:${{ inputs.fork_owner }}/${{ inputs.repository }}.git update-locales
- name: Create PR
working-directory: ComfyUI/custom_nodes/${{ inputs.repository }}
run: |
# Create PR using gh cli
gh pr create --title "Update locales for ${{ inputs.repository }}" --repo ${{ inputs.owner }}/${{ inputs.repository }} --head ${{ inputs.fork_owner }}:update-locales --body "Update locales for ${{ inputs.repository }}"
env:
GH_TOKEN: ${{ secrets.PR_GH_TOKEN }}

47
.github/workflows/i18n-node-defs.yaml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Update Node Definitions Locales
on:
workflow_dispatch:
inputs:
trigger_type:
description: 'Type of trigger (manual or automatic)'
required: false
type: string
default: 'manual'
jobs:
update-locales:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n -- scripts/collect-i18n-node-defs.ts
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.PR_GH_TOKEN }}
commit-message: "Update locales for node definitions"
title: "Update locales for node definitions"
body: |
Automated PR to update locales for node definitions
This PR was created automatically by the frontend update workflow.
branch: update-locales-node-defs-{{ github.event.inputs.trigger_type }}-{{ github.run_id }}
base: main
labels: dependencies

40
.github/workflows/i18n.yaml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Update Locales
on:
pull_request:
branches: [ main, master, dev* ]
jobs:
update-locales:
# Don't run on fork PRs
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
- name: Start dev server
# Run electron dev server as it is a superset of the web dev server
# We do want electron specific UIs to be translated.
run: npm run dev:electron &
working-directory: ComfyUI_frontend
- name: Update en.json
run: npm run collect-i18n -- scripts/collect-i18n-general.ts
env:
PLAYWRIGHT_TEST_URL: http://localhost:5173
working-directory: ComfyUI_frontend
- name: Update translations
run: npm run locale
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
working-directory: ComfyUI_frontend
- name: Commit updated locales
run: |
git config --global user.name 'github-actions'
git config --global user.email 'github-actions@github.com'
git fetch origin ${{ github.head_ref }}
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
git add src/locales/
git diff --staged --quiet || git commit -m "Update locales [skip ci]"
git push origin HEAD:${{ github.head_ref }}
working-directory: ComfyUI_frontend

View File

@@ -25,6 +25,8 @@ jobs:
id: current_version
run: echo ::set-output name=version::$(node -p "require('./package.json').version")
- name: Build project
env:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
npm ci
npm run build
@@ -41,3 +43,21 @@ jobs:
draft: true
prerelease: false
make_latest: "true"
publish_types:
runs-on: ubuntu-latest
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'Release')
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: lts/*
registry-url: "https://registry.npmjs.org"
- run: npm ci
- run: npm run build:types
- name: Publish package
run: npm publish --access public
working-directory: ./dist
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -19,8 +19,7 @@ jobs:
- name: Run Jest tests
run: |
npm run test:generate
npm run test:generate:examples
npm run test:jest:fast -- --verbose
npm run test:jest -- --verbose
working-directory: ComfyUI_frontend
playwright-tests-chromium:

7
.gitignore vendored
View File

@@ -39,4 +39,9 @@ browser_tests/*/*-win32.png
.env
dist.zip
dist.zip
/temp/
# Generated JSON Schemas
/schemas/

17
.i18nrc.cjs Normal file
View File

@@ -0,0 +1,17 @@
// This file is intentionally kept in CommonJS format (.cjs)
// to resolve compatibility issues with dependencies that require CommonJS.
// Do not convert this file to ESModule format unless all dependencies support it.
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
modelName: 'gpt-4',
splitToken: 1024,
entry: 'src/locales/en',
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'ru', 'ja', 'ko', 'fr'],
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade.
'latent' is the short form of 'latent space'.
'mask' is in the context of image processing.
`
});

View File

@@ -3,5 +3,16 @@
"tabWidth": 2,
"semi": false,
"trailingComma": "none",
"printWidth": 80
}
"printWidth": 80,
"importOrder": ["^@core/(.*)$", "<THIRD_PARTY_MODULES>", "^@/(.*)$", "^[./]"],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"overrides": [
{
"files": "*.{js,cjs,mjs,ts,cts,mts,tsx,vue}",
"options": {
"plugins": ["@trivago/prettier-plugin-sort-imports"]
}
}
]
}

17
CODEOWNERS Normal file
View File

@@ -0,0 +1,17 @@
# Admins
* @Comfy-Org/comfy_frontend_devs
# Maintainers
*.md @Comfy-Org/comfy_maintainer
/tests-ui/ @Comfy-Org/comfy_maintainer
/browser_tests/ @Comfy-Org/comfy_maintainer
/.env_example @Comfy-Org/comfy_maintainer
# Translations (AIGODLIKE team + shinshin86)
/src/locales/ @Yorha4D @KarryCharon @DorotaLuna @shinshin86 @Comfy-Org/comfy_maintainer
# Load 3D extension
/src/extensions/core/load3d.ts @jtydhr88 @Comfy-Org/comfy_frontend_devs
# Mask Editor extension
/src/extensions/core/maskeditor.ts @trsommer @Comfy-Org/comfy_frontend_devs

211
README.md
View File

@@ -52,17 +52,31 @@ 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.
Stable releases are published bi-weekly in the ComfyUI main repository.
## Release Summary
### Major features
<details>
<details id='feature-native-translation'>
<summary>v1.5: Native translation (i18n)</summary>
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
in `Comfy > Locale > Language` to translate the interface into English, Chinese (Simplified), Russian, Japanese, or Korean. This native
implementation offers better performance, reliability, and maintainability compared to previous solutions.<br>
More details available here: https://blog.comfy.org/p/native-localization-support-i18n
</details>
<details id='feature-mask-editor'>
<summary>v1.4: New mask editor</summary>
https://github.com/Comfy-Org/ComfyUI_frontend/pull/1284 implements a new mask editor.
![image](https://github.com/user-attachments/assets/f0ea6ee5-00ee-4e5d-a09c-6938e86a1f17)
</details>
<details id='feature-integrated-server-terminal'>
<summary>v1.3.22: Integrated server terminal</summary>
Press Ctrl + ` to toggle integrated terminal.
@@ -70,7 +84,7 @@ Press Ctrl + ` to toggle integrated terminal.
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details>
<details id='feature-keybinding-customization'>
<summary>v1.3.7: Keybinding customization</summary>
## Basic UI
@@ -87,7 +101,7 @@ https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details>
<details id='feature-node-library-sidebar'>
<summary>v1.2.4: Node library sidebar tab</summary>
#### Drag & Drop
@@ -97,13 +111,13 @@ https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
</details>
<details>
<details id='feature-queue-sidebar'>
<summary>v1.2.0: Queue/History sidebar tab</summary>
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
</details>
<details>
<details id='feature-node-search'>
<summary>v1.1.0: Node search box</summary>
#### Fuzzy search & Node preview
@@ -115,26 +129,26 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
### QoL changes
<details>
<details id='feature-nested-group'>
<summary>v1.3.32: **Litegraph** Nested group</summary>
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
</details>
<details>
<details id='feature-group-selection'>
<summary>v1.3.24: **Litegraph** Group selection</summary>
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
</details>
<details>
<details id='feature-toggle-link-visibility'>
<summary>v1.3.6: **Litegraph** Toggle link visibility</summary>
[rec.webm](https://github.com/user-attachments/assets/34e460ac-fbbc-44ef-bfbb-99a84c2ae2be)
</details>
<details>
<details id='feature-auto-widget-conversion'>
<summary>v1.3.4: **Litegraph** Auto widget to input conversion</summary>
Dropping a link of correct type on node widget will automatically convert the widget to input.
@@ -143,7 +157,7 @@ Dropping a link of correct type on node widget will automatically convert the wi
</details>
<details>
<details id='feature-pan-mode'>
<summary>v1.3.4: **Litegraph** Canvas pan mode</summary>
The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu
@@ -153,42 +167,42 @@ or by holding the space key.
</details>
<details>
<details id='feature-shift-drag-link-creation'>
<summary>v1.3.1: **Litegraph** Shift drag link to create a new link</summary>
[rec.webm](https://github.com/user-attachments/assets/7e73aaf9-79e2-4c3c-a26a-911cba3b85e4)
</details>
<details>
<details id='feature-optional-input-donuts'>
<summary>v1.2.62: **Litegraph** Show optional input slots as donuts</summary>
![GYEIRidb0AYGO-v](https://github.com/user-attachments/assets/e6cde0b6-654b-4afd-a117-133657a410b1)
</details>
<details>
<details id='feature-group-title-edit'>
<summary>v1.2.44: **Litegraph** Double click group title to edit</summary>
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
</details>
<details>
<details id='feature-group-selection-shortcut'>
<summary>v1.2.39: **Litegraph** Group selected nodes with Ctrl + G</summary>
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
</details>
<details>
<details id='feature-node-title-edit'>
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
</details>
<details>
<details id='feature-drag-multi-link'>
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
@@ -197,7 +211,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
</details>
<details>
<details id='feature-auto-connect-link'>
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
#### Before
@@ -207,7 +221,7 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
</details>
<details>
<details id='feature-hide-text-overflow'>
<summary>v1.1.8: **Litegraph** hides text overflow on widget value</summary>
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
@@ -215,6 +229,55 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Developer APIs
<details>
<summary>v1.6.13: prompt/confirm/alert replacements for ComfyUI desktop</summary>
Several browser-only APIs are not available in ComfyUI desktop's electron environment.
- `window.prompt`
- `window.confirm`
- `window.alert`
Please use the following APIs as replacements.
```js
// window.prompt
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
})
.then((value: string) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/c73f74d0-9bb4-4555-8d56-83f1be4a1d7e)
```js
// window.confirm
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
})
.then((value: boolean) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/8dec7a42-7443-4245-85be-ceefb1116e96)
```js
// window.alert
window['app'].extensionManager.toast
.addAlert("Test Alert")
```
![image](https://github.com/user-attachments/assets/9b18bdca-76ef-4432-95de-5cd2369684f2)
</details>
<details>
<summary>v1.3.34: Register about panel badges</summary>
@@ -235,7 +298,7 @@ app.registerExtension({
</details>
<details>
<details id='extension-api-bottom-panel-tabs'>
<summary>v1.3.22: Register bottom panel tabs</summary>
```js
@@ -258,7 +321,7 @@ app.registerExtension({
</details>
<details>
<details id='extension-api-settings'>
<summary>v1.3.22: New settings API</summary>
Legacy settings API.
@@ -304,7 +367,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
</details>
<details>
<details id='extension-api-commands-keybindings'>
<summary>v1.3.7: Register commands and keybindings</summary>
Extensions can call the following API to register commands and keybindings. Do
@@ -333,7 +396,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
</details>
<details>
<details id='extension-api-topbar-menu'>
<summary>v1.3.1: Extension API to register custom topbar menu items</summary>
Extensions can call the following API to register custom topbar menu items.
@@ -362,7 +425,7 @@ app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
![image](https://github.com/user-attachments/assets/ae7b082f-7ce9-4549-a446-4563567102fe)
</details>
<details>
<details id='extension-api-toast'>
<summary>v1.2.27: Extension API to add toast message</summary>i
Extensions can call the following API to add toast messages.
@@ -380,7 +443,7 @@ Documentation of all supported options can be found here: <https://primevue.org/
![image](https://github.com/user-attachments/assets/de02cd7e-cd81-43d1-a0b0-bccef92ff487)
</details>
<details>
<details id='extension-api-sidebar-tab'>
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
Extensions now can call the following API to register a sidebar tab.
@@ -414,6 +477,7 @@ We will support custom icons later.
- [PrimeVue](https://primevue.org/) with [TailwindCSS](https://tailwindcss.com/) for UI
- [Litegraph](https://github.com/Comfy-Org/litegraph.js) for node editor
- [zod](https://zod.dev/) for schema validation
- [vue-i18n](https://github.com/intlify/vue-i18n) for internationalization
### Git pre-commit hooks
@@ -456,8 +520,7 @@ navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to
- `git clone https://github.com/comfyanonymous/ComfyUI_examples.git` to `tests-ui/ComfyUI_examples` or the EXAMPLE_REPO_PATH location specified in .env
- `npm i` to install all dependencies
- `npm run test:generate` to fetch `tests-ui/data/object_info.json`
- `npm run test:generate:examples` to extract the example workflows
- `npm run test` to execute all unit tests.
- `npm run test:jest` to execute all unit tests.
### Component Test
@@ -480,14 +543,86 @@ This repo is using litegraph package hosted on <https://github.com/Comfy-Org/lit
This will replace the litegraph package in this repo with the local litegraph repo.
## Internationalization (i18n)
Our project supports multiple languages using `vue-i18n`. This allows users around the world to use the application in their preferred language.
### Supported Languages
- en (English)
- zh (中文)
- ru (Русский)
- ja (日本語)
- ko (한국어)
- fr (Français)
### How to Add a New Language
We welcome the addition of new languages. You can add a new language by following these steps:
#### 1. Generate language files
We use [lobe-i18n](https://github.com/lobehub/lobe-cli-toolbox/blob/master/packages/lobe-i18n/README.md) as our translation tool, which integrates with LLM for efficient localization.
Update the configuration file to include the new language(s) you wish to add:
```javascript
const { defineConfig } = require('@lobehub/i18n-cli');
module.exports = defineConfig({
entry: 'src/locales/en.json', // Base language file
entryLocale: 'en',
output: 'src/locales',
outputLocales: ['zh', 'ru', 'ja'], // Add the new language(s) here
});
```
Set your OpenAI API Key by running the following command:
```sh
npx lobe-i18n --option
```
Once configured, generate the translation files with:
```sh
npx lobe-i18n locale
```
This will create the language files for the specified languages in the configuration.
#### 2. Update i18n Configuration
Import the newly generated locale file(s) in the `src/i18n.ts` file to include them in the application's i18n setup.
#### 3. Enable Selection of the New Language
Add the newly added language to the following item in `src/constants/coreSettings.ts`:
```typescript
{
id: 'Comfy.Locale',
name: 'Locale',
type: 'combo',
// Add the new language(s) here
options: [
{ value: 'en', text: 'English' },
{ value: 'zh', text: '中文' },
{ value: 'ru', text: 'Русский' },
{ value: 'ja', text: '日本語' }
],
defaultValue: navigator.language.split('-')[0] || 'en'
},
```
This will make the new language selectable in the application's settings.
#### 4. Test the Translations
Start the development server, switch to the new language, and verify the translations.
You can switch languages by opening the ComfyUI Settings and selecting from the `ComfyUI > Locale` dropdown box.
## 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.
## Publish release to ComfyUI main repo
Run following command to publish a release to ComfyUI main repo. The script will create a new branch and do a commit to `web/` folder by checkout `dist.zip`
from GitHub release.
- `python scripts/main_repo_release.py <path_to_comfyui_main_repo> <version>`

View File

@@ -34,6 +34,8 @@ There are two ways to run the tests:
```
This opens a user interface where you can select specific tests to run and inspect the test execution timeline.
To run the same test multiple times in Playwright's UI mode, you must launch the main ComfyUI process with the `--multi-user` flag.
![Playwright UI Mode](https://github.com/user-attachments/assets/6a1ebef0-90eb-4157-8694-f5ee94d03755)
## Screenshot Expectations

View File

@@ -1,6 +1,7 @@
import type { Response } from '@playwright/test'
import type { StatusWsMessage } from '../src/types/apiTypes.ts'
import { expect, mergeTests } from '@playwright/test'
import type { StatusWsMessage } from '../src/types/apiTypes.ts'
import { comfyPageFixture } from './fixtures/ComfyPage'
import { webSocketFixture } from './fixtures/ws.ts'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 KiB

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 152 B

View File

@@ -0,0 +1,49 @@
{
"last_node_id": 12,
"last_link_id": 9,
"nodes": [
{
"id": 12,
"type": "DevToolsSimpleSlider",
"pos": [
50,
50
],
"size": [
315,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": null,
"label": "FLOAT"
}
],
"properties": {
"Node name for S&R": "DevToolsSimpleSlider"
},
"widgets_values": [
0.5
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

View File

@@ -0,0 +1,116 @@
{
"last_node_id": 16,
"last_link_id": 18,
"nodes": [
{
"id": 12,
"type": "VAEDecode",
"pos": [620, 260],
"size": [210, 46],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": null
},
{
"name": "vae",
"type": "VAE",
"link": 18
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null
}
],
"properties": {
"Node name for S&R": "VAEDecode"
},
"widgets_values": []
},
{
"id": 13,
"type": "Reroute",
"pos": [510, 280],
"size": [75, 26],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 13
}
],
"outputs": [
{
"name": "",
"type": "VAE",
"links": [18],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [160, 240],
"size": [315, 98],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [13],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": ["v1-5-pruned-emaonly.safetensors"]
}
],
"links": [
[13, 4, 2, 13, 0, "*"],
[18, 13, 0, 12, 1, "VAE"]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": {
"0": 0,
"1": 0
}
}
},
"version": 0.4
}

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Browser tab title', () => {

View File

@@ -1,11 +1,8 @@
import {
ComfyPage,
comfyPageFixture as test,
comfyExpect as expect
comfyExpect as expect,
comfyPageFixture as test
} from './fixtures/ComfyPage'
import type { useWorkspaceStore } from '../src/stores/workspaceStore'
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
async function beforeChange(comfyPage: ComfyPage) {
await comfyPage.page.evaluate(() => {
@@ -26,64 +23,41 @@ test.describe('Change Tracker', () => {
})
test('Can undo multiple operations', async ({ comfyPage }) => {
function isModified() {
return comfyPage.page.evaluate(async () => {
return !!(window['app'].extensionManager as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
function getUndoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.undoQueue.length
})
}
function getRedoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.redoQueue.length
})
}
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
// Save, confirm no errors & workflow modified flag removed
await comfyPage.menu.topbar.saveWorkflow('undo-redo-test')
expect(await comfyPage.getToastErrorCount()).toBe(0)
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
const node = (await comfyPage.getFirstNodeRef())!
await node.click('title')
await node.click('collapse')
await expect(node).toBeCollapsed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(1)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
await comfyPage.ctrlB()
await expect(node).toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(2)
expect(await getRedoQueueSize()).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(2)
expect(await comfyPage.getRedoQueueSize()).toBe(0)
await comfyPage.ctrlZ()
await expect(node).not.toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(1)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
expect(await comfyPage.getUndoQueueSize()).toBe(1)
expect(await comfyPage.getRedoQueueSize()).toBe(1)
await comfyPage.ctrlZ()
await expect(node).not.toBeCollapsed()
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(2)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
expect(await comfyPage.getUndoQueueSize()).toBe(0)
expect(await comfyPage.getRedoQueueSize()).toBe(2)
})
})
@@ -174,4 +148,20 @@ test.describe('Change Tracker', () => {
await expect(node).toBePinned()
await expect(node).toBeCollapsed()
})
test('Can detect changes in workflow.extra', async ({ comfyPage }) => {
expect(await comfyPage.getUndoQueueSize()).toBe(0)
await comfyPage.page.evaluate(() => {
window['app'].graph.extra.foo = 'bar'
})
// Click empty space to trigger a change detection.
await comfyPage.clickEmptySpace()
expect(await comfyPage.getUndoQueueSize()).toBe(1)
})
test('Ignores changes in workflow.ds', async ({ comfyPage }) => {
expect(await comfyPage.getUndoQueueSize()).toBe(0)
await comfyPage.pan({ x: 10, y: 10 })
expect(await comfyPage.getUndoQueueSize()).toBe(0)
})
})

View File

@@ -1,7 +1,9 @@
import { expect } from '@playwright/test'
import type { Palette } from '../src/types/colorPaletteTypes'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
const customColorPalettes = {
const customColorPalettes: Record<string, Palette> = {
obsidian: {
version: 102,
id: 'obsidian',
@@ -127,23 +129,61 @@ const customColorPalettes = {
'tr-odd-bg-color': 'rgba(19,19,19,.9)'
}
}
},
// A custom light theme with fg color red
light_red: {
id: 'light_red',
name: 'Light Red',
light_theme: true,
colors: {
node_slot: {},
litegraph_base: {},
comfy_base: {
'fg-color': '#ff0000'
}
}
}
}
test.describe('Color Palette', () => {
test.beforeEach(async ({ comfyPage }) => {
test('Can show custom color palette', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
// doesn't update the store immediately.
await comfyPage.setup()
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-obsidian-dark.png'
)
await comfyPage.setSetting('Comfy.ColorPalette', 'light_red')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-light-red.png'
)
await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')
})
test('Can show custom color palette', async ({ comfyPage }) => {
test('Can add custom color palette', async ({ comfyPage }) => {
await comfyPage.page.evaluate((p) => {
window['app'].extensionManager.colorPalette.addCustomColorPalette(p)
}, customColorPalettes.obsidian_dark)
expect(await comfyPage.getToastErrorCount()).toBe(0)
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-obsidian-dark.png'
)
// Legacy `custom_` prefix is still supported
await comfyPage.setSetting('Comfy.ColorPalette', 'custom_obsidian_dark')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'custom-color-palette-obsidian-dark.png'
)
await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Keybindings', () => {

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Copy Paste', () => {
@@ -10,6 +11,14 @@ test.describe('Copy Paste', () => {
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
})
test('Can copy and paste node with link', async ({ comfyPage }) => {
await comfyPage.clickTextEncodeNode1()
await comfyPage.page.mouse.move(10, 10)
await comfyPage.ctrlC()
await comfyPage.page.keyboard.press('Control+Shift+V')
await expect(comfyPage.canvas).toHaveScreenshot('copied-node-with-link.png')
})
test('Can copy and paste text', async ({ comfyPage }) => {
const textBox = comfyPage.widgetTextBox
await textBox.click()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 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: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -1,4 +1,6 @@
import { expect } from '@playwright/test'
import { Keybinding } from '../src/types/keyBindingTypes'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Load workflow warning', () => {
@@ -42,6 +44,18 @@ test.describe('Execution error', () => {
const executionError = comfyPage.page.locator('.comfy-error-report')
await expect(executionError).toBeVisible()
})
test('Can display Issue Report form', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('execution_error')
await comfyPage.queueButton.click()
await comfyPage.nextFrame()
await comfyPage.page.getByLabel('Help Fix This').click()
const issueReportForm = comfyPage.page.getByText(
'Submit Error Report (Optional)'
)
await expect(issueReportForm).toBeVisible()
})
})
test.describe('Missing models warning', () => {
@@ -77,8 +91,12 @@ test.describe('Missing models warning', () => {
test.describe('Settings', () => {
test('@mobile Should be visible on mobile', async ({ comfyPage }) => {
await comfyPage.page.keyboard.press('Control+,')
const searchBox = comfyPage.page.locator('.settings-content')
await expect(searchBox).toBeVisible()
const settingsContent = comfyPage.page.locator('.settings-content')
await expect(settingsContent).toBeVisible()
const isUsableHeight = await settingsContent.evaluate(
(el) => el.clientHeight > 30
)
expect(isUsableHeight).toBeTruthy()
})
test('Can open settings with hotkey', async ({ comfyPage }) => {
@@ -94,8 +112,56 @@ test.describe('Settings', () => {
test('Can change canvas zoom speed setting', async ({ comfyPage }) => {
const maxSpeed = 2.5
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', maxSpeed)
test.step('Setting should persist', async () => {
await test.step('Setting should persist', async () => {
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
})
})
test('Should persist keybinding setting', async ({ comfyPage }) => {
// Open the settings dialog
await comfyPage.page.keyboard.press('Control+,')
await comfyPage.page.waitForSelector('.settings-container')
// Open the keybinding tab
await comfyPage.page.getByLabel('Keybinding').click()
await comfyPage.page.waitForSelector(
'[placeholder="Search Keybindings..."]'
)
// Focus the 'New Blank Workflow' row
const newBlankWorkflowRow = comfyPage.page.locator('tr', {
has: comfyPage.page.getByRole('cell', { name: 'New Blank Workflow' })
})
await newBlankWorkflowRow.click()
// Click edit button
const editKeybindingButton = newBlankWorkflowRow.locator('.pi-pencil')
await editKeybindingButton.click()
// Set new keybinding
const input = comfyPage.page.getByPlaceholder('Press keys for new binding')
await input.press('Alt+n')
const requestPromise = comfyPage.page.waitForRequest(
'**/api/settings/Comfy.Keybinding.NewBindings'
)
// Save keybinding
const saveButton = comfyPage.page
.getByLabel('Comfy.NewBlankWorkflow')
.getByLabel('Save')
await saveButton.click()
const request = await requestPromise
const expectedSetting: Keybinding = {
commandId: 'Comfy.NewBlankWorkflow',
combo: {
key: 'n',
ctrl: false,
alt: true,
shift: false
}
}
expect(request.postData()).toContain(JSON.stringify(expectedSetting))
})
})

View File

@@ -1,4 +1,5 @@
import { expect, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Topbar commands', () => {
@@ -157,4 +158,42 @@ test.describe('Topbar commands', () => {
expect(await badge.textContent()).toContain('Test Badge')
})
})
test.describe('Dialog', () => {
test('Should allow showing a prompt dialog', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
})
.then((value: string) => {
window['value'] = value
})
})
await comfyPage.fillPromptDialog('Hello, world!')
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(
'Hello, world!'
)
})
test('Should allow showing a confirmation dialog', async ({
comfyPage
}) => {
await comfyPage.page.evaluate(() => {
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
})
.then((value: boolean) => {
window['value'] = value
})
})
await comfyPage.confirmDialog.click('confirm')
expect(await comfyPage.page.evaluate(() => window['value'])).toBe(true)
})
})
})

View File

@@ -1,25 +1,37 @@
import type { Page, Locator, APIRequestContext } from '@playwright/test'
import type { APIRequestContext, Locator, Page } from '@playwright/test'
import { expect } from '@playwright/test'
import { test as base } from '@playwright/test'
import { ComfyActionbar } from '../helpers/actionbar'
import dotenv from 'dotenv'
dotenv.config()
import * as fs from 'fs'
import { NodeBadgeMode } from '../../src/types/nodeSource'
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
import type { NodeId } from '../../src/types/comfyWorkflow'
import type { KeyCombo } from '../../src/types/keyBindingTypes'
import { NodeBadgeMode } from '../../src/types/nodeSource'
import { ComfyActionbar } from '../helpers/actionbar'
import { ComfyTemplates } from '../helpers/templates'
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
import { SettingDialog } from './components/SettingDialog'
import {
NodeLibrarySidebarTab,
QueueSidebarTab,
WorkflowsSidebarTab
} from './components/SidebarTab'
import { Topbar } from './components/Topbar'
import { NodeReference } from './utils/litegraphUtils'
import type { Position, Size } from './types'
import { SettingDialog } from './components/SettingDialog'
import { NodeReference } from './utils/litegraphUtils'
import TaskHistory from './utils/taskHistory'
dotenv.config()
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
class ComfyMenu {
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
private _workflowsTab: WorkflowsSidebarTab | null = null
private _queueTab: QueueSidebarTab | null = null
private _topbar: Topbar | null = null
public readonly sideToolbar: Locator
public readonly themeToggleButton: Locator
public readonly saveButton: Locator
@@ -33,15 +45,23 @@ class ComfyMenu {
}
get nodeLibraryTab() {
return new NodeLibrarySidebarTab(this.page)
this._nodeLibraryTab ??= new NodeLibrarySidebarTab(this.page)
return this._nodeLibraryTab
}
get workflowsTab() {
return new WorkflowsSidebarTab(this.page)
this._workflowsTab ??= new WorkflowsSidebarTab(this.page)
return this._workflowsTab
}
get queueTab() {
this._queueTab ??= new QueueSidebarTab(this.page)
return this._queueTab
}
get topbar() {
return new Topbar(this.page)
this._topbar ??= new Topbar(this.page)
return this._topbar
}
async toggleTheme() {
@@ -72,7 +92,33 @@ type FolderStructure = {
[key: string]: FolderStructure | string
}
type KeysOfType<T, Match> = {
[K in keyof T]: T[K] extends Match ? K : never
}[keyof T]
class ConfirmDialog {
public readonly delete: Locator
public readonly overwrite: Locator
public readonly reject: Locator
public readonly confirm: Locator
constructor(public readonly page: Page) {
this.delete = page.locator('button.p-button[aria-label="Delete"]')
this.overwrite = page.locator('button.p-button[aria-label="Overwrite"]')
this.reject = page.locator('button.p-button[aria-label="Cancel"]')
this.confirm = page.locator('button.p-button[aria-label="Confirm"]')
}
async click(locator: KeysOfType<ConfirmDialog, Locator>) {
const loc = this[locator]
await expect(loc).toBeVisible()
await loc.click()
}
}
export class ComfyPage {
private _history: TaskHistory | null = null
public readonly url: string
// All canvas position operations are based on default view of canvas.
public readonly canvas: Locator
@@ -91,6 +137,7 @@ export class ComfyPage {
public readonly actionbar: ComfyActionbar
public readonly templates: ComfyTemplates
public readonly settingDialog: SettingDialog
public readonly confirmDialog: ConfirmDialog
/** Worker index to test user ID */
public readonly userIds: string[] = []
@@ -115,6 +162,7 @@ export class ComfyPage {
this.actionbar = new ComfyActionbar(page)
this.templates = new ComfyTemplates(page)
this.settingDialog = new SettingDialog(page)
this.confirmDialog = new ConfirmDialog(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {
@@ -204,6 +252,11 @@ export class ComfyPage {
}
}
setupHistory(): TaskHistory {
this._history ??= new TaskHistory(this)
return this._history
}
async setup({ clearStorage = true }: { clearStorage?: boolean } = {}) {
await this.goto()
if (clearStorage) {
@@ -316,11 +369,6 @@ export class ComfyPage {
}, settingId)
}
async reload({ clearStorage = true }: { clearStorage?: boolean } = {}) {
await this.page.reload({ timeout: 15000 })
await this.setup({ clearStorage })
}
async goto() {
await this.page.goto(this.url)
}
@@ -487,6 +535,17 @@ export class ComfyPage {
return { x: 427, y: 98 }
}
get promptDialogInput() {
return this.page.locator('.p-dialog-content input[type="text"]')
}
async fillPromptDialog(value: string) {
await this.promptDialogInput.fill(value)
await this.page.keyboard.press('Enter')
await this.promptDialogInput.waitFor({ state: 'hidden' })
await this.nextFrame()
}
async disconnectEdge() {
await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace)
}
@@ -737,28 +796,27 @@ export class ComfyPage {
)
}
async confirmDialog(prompt: string, text: string = 'Yes') {
async clickDialogButton(prompt: string, buttonText: string = 'Yes') {
const modal = this.page.locator(
`.comfy-modal-content:has-text("${prompt}")`
)
await expect(modal).toBeVisible()
await modal
.locator('.comfyui-button', {
hasText: text
hasText: buttonText
})
.click()
await expect(modal).toBeHidden()
}
async convertAllNodesToGroupNode(groupNodeName: string) {
this.page.on('dialog', async (dialog) => {
await dialog.accept(groupNodeName)
})
await this.canvas.press('Control+a')
const node = await this.getFirstNodeRef()
await node!.clickContextMenuOption('Convert to Group Node')
await this.fillPromptDialog(groupNodeName)
await this.nextFrame()
}
async convertOffsetToCanvas(pos: [number, number]) {
return this.page.evaluate((pos) => {
return window['app'].canvas.ds.convertOffsetToCanvas(pos)
@@ -767,6 +825,11 @@ export class ComfyPage {
async getNodeRefById(id: NodeId) {
return new NodeReference(id, this)
}
async getNodes() {
return await this.page.evaluate(() => {
return window['app'].graph.nodes
})
}
async getNodeRefsByType(type: string): Promise<NodeReference[]> {
return Promise.all(
(
@@ -788,6 +851,37 @@ export class ComfyPage {
async moveMouseToEmptyArea() {
await this.page.mouse.move(10, 10)
}
async getUndoQueueSize() {
return this.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.undoQueue.length
})
}
async getRedoQueueSize() {
return this.page.evaluate(() => {
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.redoQueue.length
})
}
async isCurrentWorkflowModified() {
return this.page.evaluate(() => {
return (window['app'].extensionManager as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
async getExportedWorkflow({ api = false }: { api?: boolean } = {}) {
return this.page.evaluate(async (api) => {
return (await window['app'].graphToPrompt())[api ? 'output' : 'workflow']
}, api)
}
async setFocusMode(focusMode: boolean) {
await this.page.evaluate((focusMode) => {
window['app'].extensionManager.focusMode = focusMode
}, focusMode)
await this.nextFrame()
}
}
export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
@@ -799,18 +893,23 @@ export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
const userId = await comfyPage.setupUser(username)
comfyPage.userIds[parallelIndex] = userId
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId
})
try {
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
// Hide all badges by default.
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false,
'Comfy.userId': userId
})
} catch (e) {
console.error(e)
}
await comfyPage.setup()
await use(comfyPage)
}
@@ -845,5 +944,19 @@ const makeMatcher = function <T>(
export const comfyExpect = expect.extend({
toBePinned: makeMatcher((n) => n.isPinned(), 'pinned'),
toBeBypassed: makeMatcher((n) => n.isBypassed(), 'bypassed'),
toBeCollapsed: makeMatcher((n) => n.isCollapsed(), 'collapsed')
toBeCollapsed: makeMatcher((n) => n.isCollapsed(), 'collapsed'),
async toHaveFocus(locator: Locator, options = { timeout: 256 }) {
const isFocused = await locator.evaluate(
(el) => el === document.activeElement
)
await expect(async () => {
expect(isFocused).toBe(!this.isNot)
}).toPass(options)
return {
pass: isFocused,
message: () => `Expected element to ${isFocused ? 'not ' : ''}be focused.`
}
}
})

View File

@@ -0,0 +1,41 @@
import { test as base } from '@playwright/test'
import { Page } from 'playwright'
export class UserSelectPage {
constructor(
public readonly url: string,
public readonly page: Page
) {}
get selectionUrl() {
return this.url + '/user-select'
}
get container() {
return this.page.locator('#comfy-user-selection')
}
get newUserInput() {
return this.container.locator('#new-user-input')
}
get existingUserSelect() {
return this.container.locator('#existing-user-select')
}
get nextButton() {
return this.container.getByText('Next')
}
}
export const userSelectPageFixture = base.extend<{
userSelectPage: UserSelectPage
}>({
userSelectPage: async ({ page }, use) => {
const userSelectPage = new UserSelectPage(
process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188',
page
)
await use(userSelectPage)
}
})

View File

@@ -3,6 +3,13 @@ import { Locator, Page } from '@playwright/test'
export class ComfyNodeSearchFilterSelectionPanel {
constructor(public readonly page: Page) {}
get header() {
return this.page
.getByRole('dialog')
.locator('div')
.filter({ hasText: 'Add node filter condition' })
}
async selectFilterType(filterType: string) {
await this.page
.locator(

View File

@@ -22,6 +22,12 @@ class SidebarTab {
}
await this.tabButton.click()
}
async close() {
if (!this.tabButton.isVisible()) {
return
}
await this.tabButton.click()
}
}
export class NodeLibrarySidebarTab extends SidebarTab {
@@ -85,32 +91,36 @@ export class WorkflowsSidebarTab extends SidebarTab {
super(page, 'workflows')
}
get root() {
return this.page.locator('.workflows-sidebar-tab')
}
get browseGalleryButton() {
return this.page.locator('.browse-templates-button')
return this.root.locator('.browse-templates-button')
}
get newBlankWorkflowButton() {
return this.page.locator('.new-blank-workflow-button')
return this.root.locator('.new-blank-workflow-button')
}
get openWorkflowButton() {
return this.page.locator('.open-workflow-button')
return this.root.locator('.open-workflow-button')
}
async getOpenedWorkflowNames() {
return await this.page
return await this.root
.locator('.comfyui-workflows-open .node-label')
.allInnerTexts()
}
async getActiveWorkflowName() {
return await this.page
return await this.root
.locator('.comfyui-workflows-open .p-tree-node-selected .node-label')
.innerText()
}
async getTopLevelSavedWorkflowNames() {
return await this.page
return await this.root
.locator('.comfyui-workflows-browse .node-label')
.allInnerTexts()
}
@@ -122,13 +132,13 @@ export class WorkflowsSidebarTab extends SidebarTab {
}
getOpenedItem(name: string) {
return this.page.locator('.comfyui-workflows-open .node-label', {
return this.root.locator('.comfyui-workflows-open .node-label', {
hasText: name
})
}
getPersistedItem(name: string) {
return this.page.locator('.comfyui-workflows-browse .node-label', {
return this.root.locator('.comfyui-workflows-browse .node-label', {
hasText: name
})
}
@@ -142,4 +152,118 @@ export class WorkflowsSidebarTab extends SidebarTab {
await this.page.keyboard.press('Enter')
await this.page.waitForTimeout(300)
}
async insertWorkflow(locator: Locator) {
await locator.click({ button: 'right' })
await this.page
.locator('.p-contextmenu-item-content', { hasText: 'Insert' })
.click()
}
}
export class QueueSidebarTab extends SidebarTab {
constructor(public readonly page: Page) {
super(page, 'queue')
}
get root() {
return this.page.locator('.sidebar-content-container', { hasText: 'Queue' })
}
get tasks() {
return this.root.locator('[data-virtual-grid-item]')
}
get visibleTasks() {
return this.tasks.locator('visible=true')
}
get clearButton() {
return this.root.locator('.clear-all-button')
}
get collapseTasksButton() {
return this.getToggleExpandButton(false)
}
get expandTasksButton() {
return this.getToggleExpandButton(true)
}
get noResultsPlaceholder() {
return this.root.locator('.no-results-placeholder')
}
private getToggleExpandButton(isExpanded: boolean) {
const iconSelector = isExpanded ? '.pi-image' : '.pi-images'
return this.root.locator(`.toggle-expanded-button ${iconSelector}`)
}
async open() {
await super.open()
return this.root.waitFor({ state: 'visible' })
}
async close() {
await super.close()
await this.root.waitFor({ state: 'hidden' })
}
async expandTasks() {
await this.expandTasksButton.click()
await this.collapseTasksButton.waitFor({ state: 'visible' })
}
async collapseTasks() {
await this.collapseTasksButton.click()
await this.expandTasksButton.waitFor({ state: 'visible' })
}
async waitForTasks() {
return Promise.all([
this.tasks.first().waitFor({ state: 'visible' }),
this.tasks.last().waitFor({ state: 'visible' })
])
}
async scrollTasks(direction: 'up' | 'down') {
const scrollToEl =
direction === 'up' ? this.tasks.last() : this.tasks.first()
await scrollToEl.scrollIntoViewIfNeeded()
await this.waitForTasks()
}
async clearTasks() {
await this.clearButton.click()
const confirmButton = this.page.getByLabel('Delete')
await confirmButton.click()
await this.noResultsPlaceholder.waitFor({ state: 'visible' })
}
/** Set the width of the tab (out of 100). Must call before opening the tab */
async setTabWidth(width: number) {
if (width < 0 || width > 100) {
throw new Error('Width must be between 0 and 100')
}
return this.page.evaluate((width) => {
localStorage.setItem('queue', JSON.stringify([width, 100 - width]))
}, width)
}
getTaskPreviewButton(taskIndex: number) {
return this.tasks.nth(taskIndex).getByRole('button')
}
async openTaskPreview(taskIndex: number) {
const previewButton = this.getTaskPreviewButton(taskIndex)
await previewButton.hover()
await previewButton.click()
return this.getGalleryImage(taskIndex).waitFor({ state: 'visible' })
}
getGalleryImage(galleryItemIndex: number) {
// Aria labels of Galleria items are 1-based indices
const galleryLabel = `${galleryItemIndex + 1}`
return this.page.getByLabel(galleryLabel).locator('.galleria-image')
}
}

View File

@@ -9,6 +9,12 @@ export class Topbar {
.allInnerTexts()
}
async getActiveTabName(): Promise<string> {
return this.page
.locator('.workflow-tabs .p-togglebutton-checked')
.innerText()
}
async openSubmenuMobile() {
await this.page.locator('.p-menubar-mobile .p-menubar-button').click()
}
@@ -40,7 +46,14 @@ export class Topbar {
return this._saveWorkflow(workflowName, 'Save As')
}
async _saveWorkflow(workflowName: string, command: 'Save' | 'Save As') {
exportWorkflow(workflowName: string): Promise<void> {
return this._saveWorkflow(workflowName, 'Export')
}
async _saveWorkflow(
workflowName: string,
command: 'Save' | 'Save As' | 'Export'
) {
await this.triggerTopbarCommand(['Workflow', command])
await this.getSaveDialog().fill(workflowName)
await this.page.keyboard.press('Enter')

View File

@@ -1,6 +1,7 @@
import { ManageGroupNode } from '../../helpers/manageGroupNode'
import type { NodeId } from '../../../src/types/comfyWorkflow'
import type { Page } from '@playwright/test'
import type { NodeId } from '../../../src/types/comfyWorkflow'
import { ManageGroupNode } from '../../helpers/manageGroupNode'
import type { ComfyPage } from '../ComfyPage'
import type { Position, Size } from '../types'
@@ -233,10 +234,8 @@ export class NodeReference {
await ctx.getByText(optionText).click()
}
async convertToGroupNode(groupNodeName: string = 'GroupNode') {
this.comfyPage.page.once('dialog', async (dialog) => {
await dialog.accept(groupNodeName)
})
await this.clickContextMenuOption('Convert to Group Node')
await this.comfyPage.fillPromptDialog(groupNodeName)
await this.comfyPage.nextFrame()
const nodes = await this.comfyPage.getNodeRefsByType(
`workflow>${groupNodeName}`

View File

@@ -0,0 +1,161 @@
import fs from 'fs'
import _ from 'lodash'
import path from 'path'
import type { Request, Route } from 'playwright'
import { v4 as uuidv4 } from 'uuid'
import type {
HistoryTaskItem,
TaskItem,
TaskOutput
} from '../../../src/types/apiTypes'
import type { ComfyPage } from '../ComfyPage'
/** keyof TaskOutput[string] */
type OutputFileType = 'images' | 'audio' | 'animated'
const DEFAULT_IMAGE = 'example.webp'
const getFilenameParam = (request: Request) => {
const url = new URL(request.url())
return url.searchParams.get('filename') || DEFAULT_IMAGE
}
const getContentType = (filename: string, fileType: OutputFileType) => {
const subtype = path.extname(filename).slice(1)
switch (fileType) {
case 'images':
return `image/${subtype}`
case 'audio':
return `audio/${subtype}`
case 'animated':
return `video/${subtype}`
}
}
const setQueueIndex = (task: TaskItem) => {
task.prompt[0] = TaskHistory.queueIndex++
}
const setPromptId = (task: TaskItem) => {
task.prompt[1] = uuidv4()
}
export default class TaskHistory {
static queueIndex = 0
static readonly defaultTask: Readonly<HistoryTaskItem> = {
prompt: [0, 'prompt-id', {}, { client_id: uuidv4() }, []],
outputs: {},
status: {
status_str: 'success',
completed: true,
messages: []
},
taskType: 'History'
}
private tasks: HistoryTaskItem[] = []
private outputContentTypes: Map<string, string> = new Map()
constructor(readonly comfyPage: ComfyPage) {}
private loadAsset: (filename: string) => Buffer = _.memoize(
(filename: string) => {
const filePath = this.comfyPage.assetPath(filename)
return fs.readFileSync(filePath)
}
)
private async handleGetHistory(route: Route) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(this.tasks)
})
}
private async handleGetView(route: Route) {
const fileName = getFilenameParam(route.request())
if (!this.outputContentTypes.has(fileName)) route.continue()
const asset = this.loadAsset(fileName)
return route.fulfill({
status: 200,
contentType: this.outputContentTypes.get(fileName),
body: asset,
headers: {
'Cache-Control': 'public, max-age=31536000',
'Content-Length': asset.byteLength.toString()
}
})
}
async setupRoutes() {
return this.comfyPage.page.route(
/.*\/api\/(view|history)(\?.*)?$/,
async (route) => {
const request = route.request()
const method = request.method()
const isViewReq = request.url().includes('view') && method === 'GET'
if (isViewReq) return this.handleGetView(route)
const isHistoryPath = request.url().includes('history')
const isGetHistoryReq = isHistoryPath && method === 'GET'
if (isGetHistoryReq) return this.handleGetHistory(route)
const isClearReq =
method === 'POST' &&
isHistoryPath &&
request.postDataJSON()?.clear === true
if (isClearReq) return this.clearTasks()
return route.continue()
}
)
}
private createOutputs(
filenames: string[],
filetype: OutputFileType
): TaskOutput {
return filenames.reduce((outputs, filename, i) => {
const nodeId = `${i + 1}`
outputs[nodeId] = {
[filetype]: [{ filename, subfolder: '', type: 'output' }]
}
const contentType = getContentType(filename, filetype)
this.outputContentTypes.set(filename, contentType)
return outputs
}, {})
}
private addTask(task: HistoryTaskItem) {
setPromptId(task)
setQueueIndex(task)
this.tasks.push(task)
}
clearTasks() {
this.tasks = []
}
withTask(
outputFilenames: string[],
outputFiletype: OutputFileType = 'images',
overrides: Partial<HistoryTaskItem> = {}
): this {
this.addTask({
...TaskHistory.defaultTask,
outputs: this.createOutputs(outputFilenames, outputFiletype),
...overrides
})
return this
}
/** Repeats the last task in the task history a specified number of times. */
repeat(n: number): this {
for (let i = 0; i < n; i++)
this.addTask(structuredClone(this.tasks.at(-1)) as HistoryTaskItem)
return this
}
}

View File

@@ -1,7 +1,8 @@
import { FullConfig } from '@playwright/test'
import { backupPath } from './utils/backupUtils'
import dotenv from 'dotenv'
import { backupPath } from './utils/backupUtils'
dotenv.config()
export default function globalSetup(config: FullConfig) {

View File

@@ -1,7 +1,8 @@
import { FullConfig } from '@playwright/test'
import { restorePath } from './utils/backupUtils'
import dotenv from 'dotenv'
import { restorePath } from './utils/backupUtils'
dotenv.config()
export default function globalTeardown(config: FullConfig) {

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Graph Canvas Menu', () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 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: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { ComfyPage, comfyPageFixture as test } from './fixtures/ComfyPage'
import type { NodeReference } from './fixtures/utils/litegraphUtils'
@@ -103,6 +104,36 @@ test.describe('Group Node', () => {
await expect(comfyPage.page.locator('.node-tooltip')).toBeVisible()
})
test('Manage group opens with the correct group selected', async ({
comfyPage
}) => {
const makeGroup = async (name, type1, type2) => {
const node1 = (await comfyPage.getNodeRefsByType(type1))[0]
const node2 = (await comfyPage.getNodeRefsByType(type2))[0]
await node1.click('title')
await node2.click('title', {
modifiers: ['Shift']
})
return await node2.convertToGroupNode(name)
}
const group1 = await makeGroup(
'g1',
'CLIPTextEncode',
'CheckpointLoaderSimple'
)
const group2 = await makeGroup('g2', 'EmptyLatentImage', 'KSampler')
const manage1 = await group1.manageGroupNode()
await comfyPage.nextFrame()
expect(await manage1.getSelectedNodeType()).toBe('g1')
await manage1.close()
await expect(manage1.root).not.toBeVisible()
const manage2 = await group2.manageGroupNode()
expect(await manage2.getSelectedNodeType()).toBe('g2')
})
test('Reconnects inputs after configuration changed via manage dialog save', async ({
comfyPage
}) => {

View File

@@ -1,4 +1,5 @@
import type { Page, Locator } from '@playwright/test'
import type { Locator, Page } from '@playwright/test'
import type { AutoQueueMode } from '../../src/stores/queueStore'
export class ComfyActionbar {

View File

@@ -1,12 +1,15 @@
import { Locator, Page } from '@playwright/test'
export class ManageGroupNode {
footer: Locator
header: Locator
constructor(
readonly page: Page,
readonly root: Locator
) {
this.footer = root.locator('footer')
this.header = root.locator('header')
}
async setLabel(name: string, label: string) {
@@ -23,6 +26,11 @@ export class ManageGroupNode {
await this.footer.getByText('Close').click()
}
async getSelectedNodeType() {
const select = this.header.locator('select').first()
return await select.inputValue()
}
async selectNode(name: string) {
const list = this.root.locator('.comfy-group-manage-list-items')
const item = list.getByText(name)

View File

@@ -1,4 +1,5 @@
import { Locator, Page } from '@playwright/test'
export class ComfyTemplates {
readonly content: Locator

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Item Interaction', () => {
@@ -452,6 +453,9 @@ test.describe('Canvas Interaction', () => {
expect(await getCursorStyle()).toBe('default')
await comfyPage.page.mouse.down()
expect(await getCursorStyle()).toBe('grabbing')
// Move mouse should not alter cursor style.
await comfyPage.page.mouse.move(10, 20)
expect(await getCursorStyle()).toBe('grabbing')
await comfyPage.page.mouse.up()
expect(await getCursorStyle()).toBe('default')
@@ -465,6 +469,43 @@ test.describe('Canvas Interaction', () => {
expect(await getCursorStyle()).toBe('default')
})
// https://github.com/Comfy-Org/litegraph.js/pull/424
test('Properly resets dragging state after pan mode sequence', async ({
comfyPage
}) => {
const getCursorStyle = async () => {
return await comfyPage.page.evaluate(() => {
return (
document.getElementById('graph-canvas')!.style.cursor || 'default'
)
})
}
// Initial state check
await comfyPage.page.mouse.move(10, 10)
expect(await getCursorStyle()).toBe('default')
// Click and hold
await comfyPage.page.mouse.down()
expect(await getCursorStyle()).toBe('grabbing')
// Press space while holding click
await comfyPage.page.keyboard.down('Space')
expect(await getCursorStyle()).toBe('grabbing')
// Release click while space is still down
await comfyPage.page.mouse.up()
expect(await getCursorStyle()).toBe('grab')
// Release space
await comfyPage.page.keyboard.up('Space')
expect(await getCursorStyle()).toBe('default')
// Move mouse - cursor should remain default
await comfyPage.page.mouse.move(20, 20)
expect(await getCursorStyle()).toBe('default')
})
test('Can pan when dragging a link', async ({ comfyPage }) => {
const posSlot1 = comfyPage.clipTextEncodeNode1InputSlot
await comfyPage.page.mouse.move(posSlot1.x, posSlot1.y)
@@ -552,7 +593,7 @@ test.describe('Load workflow', () => {
}) => {
await comfyPage.loadWorkflow('single_ksampler')
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
await comfyPage.reload({ clearStorage: false })
await comfyPage.setup({ clearStorage: false })
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
})
@@ -569,11 +610,72 @@ test.describe('Load workflow', () => {
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
)
await comfyPage.reload({ clearStorage: false })
await comfyPage.setup({ clearStorage: false })
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
)
})
test.describe('Restore all open workflows on reload', () => {
let workflowA: string
let workflowB: string
const generateUniqueFilename = (extension = '') =>
`${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}${extension}`
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
workflowA = generateUniqueFilename()
await comfyPage.menu.topbar.saveWorkflow(workflowA)
workflowB = generateUniqueFilename()
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
await comfyPage.menu.topbar.saveWorkflow(workflowB)
// Wait for localStorage to persist the workflow paths before reloading
await comfyPage.page.waitForFunction(
() => !!window.localStorage.getItem('Comfy.OpenWorkflowsPaths')
)
await comfyPage.setup({ clearStorage: false })
})
test('Restores topbar workflow tabs after reload', async ({
comfyPage
}) => {
await comfyPage.setSetting(
'Comfy.Workflow.WorkflowTabsPosition',
'Topbar'
)
const tabs = await comfyPage.menu.topbar.getTabNames()
const activeWorkflowName = await comfyPage.menu.topbar.getActiveTabName()
expect(tabs).toEqual(expect.arrayContaining([workflowA, workflowB]))
expect(tabs.indexOf(workflowA)).toBeLessThan(tabs.indexOf(workflowB))
expect(activeWorkflowName).toEqual(workflowB)
})
test('Restores sidebar workflows after reload', async ({ comfyPage }) => {
await comfyPage.setSetting(
'Comfy.Workflow.WorkflowTabsPosition',
'Sidebar'
)
await comfyPage.menu.workflowsTab.open()
const openWorkflows =
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
const activeWorkflowName =
await comfyPage.menu.workflowsTab.getActiveWorkflowName()
const workflowPathA = `${workflowA}.json`
const workflowPathB = `${workflowB}.json`
expect(openWorkflows).toEqual(
expect.arrayContaining([workflowPathA, workflowPathB])
)
expect(openWorkflows.indexOf(workflowPathA)).toBeLessThan(
openWorkflows.indexOf(workflowPathB)
)
expect(activeWorkflowName).toEqual(workflowPathB)
})
})
})
test.describe('Load duplicate workflow', () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 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: 65 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 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: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

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

After

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

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Keybindings', () => {

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
function listenForEvent(): Promise<Event> {

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Load Workflow in Media', () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -1,5 +1,9 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
import { expect, mergeTests } from '@playwright/test'
import { comfyPageFixture } from './fixtures/ComfyPage'
import { webSocketFixture } from './fixtures/ws'
const test = mergeTests(comfyPageFixture, webSocketFixture)
test.describe('Menu', () => {
test.beforeEach(async ({ comfyPage }) => {
@@ -17,7 +21,7 @@ test.describe('Menu', () => {
expect(await comfyPage.menu.getThemeId()).toBe('light')
// Theme id should persist after reload.
await comfyPage.reload()
await comfyPage.setup()
expect(await comfyPage.menu.getThemeId()).toBe('light')
await comfyPage.menu.toggleTheme()
@@ -288,6 +292,47 @@ test.describe('Menu', () => {
}
})
})
test('Can customize bookmark color after interacting with color options', async ({
comfyPage
}) => {
// Open customization dialog
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
const tab = comfyPage.menu.nodeLibraryTab
await tab.getFolder('foo').click({ button: 'right' })
await comfyPage.page.getByLabel('Customize').click()
// Click a color option multiple times
const customColorOption = comfyPage.page.locator(
'.p-togglebutton-content > .pi-palette'
)
await customColorOption.click()
await customColorOption.click()
// Use the color picker
await comfyPage.page
.getByLabel('Customize Folder')
.getByRole('textbox')
.click()
await comfyPage.page.locator('.p-colorpicker-color-background').click()
// Finalize the customization
await comfyPage.page
.locator('.icon-field .p-selectbutton > *:nth-child(2)')
.click()
await comfyPage.page.getByLabel('Confirm').click()
await comfyPage.nextFrame()
// Verify the color selection is saved
const setting = await comfyPage.getSetting(
'Comfy.NodeLibrary.BookmarksCustomization'
)
await expect(setting).toHaveProperty(['foo/', 'color'])
await expect(setting['foo/'].color).not.toBeNull()
await expect(setting['foo/'].color).not.toBeUndefined()
await expect(setting['foo/'].color).not.toBe('')
})
test('Can rename customized bookmark folder', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', ['foo/'])
await comfyPage.setSetting('Comfy.NodeLibrary.BookmarksCustomization', {
@@ -350,23 +395,6 @@ test.describe('Menu', () => {
await comfyPage.page.waitForTimeout(1000)
expect(await tab.getNode('KSampler (Advanced)').count()).toBe(2)
})
test('Can migrate legacy bookmarks', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks', [
'foo/',
'foo/KSampler (Advanced)',
'UNKNOWN',
'KSampler'
])
await comfyPage.setSetting('Comfy.NodeLibrary.Bookmarks.V2', [])
await comfyPage.reload()
expect(await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks')).toEqual(
[]
)
expect(
await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks.V2')
).toEqual(['foo/', 'foo/KSamplerAdvanced', 'KSampler'])
})
})
test.describe('Workflows sidebar', () => {
@@ -412,6 +440,56 @@ test.describe('Menu', () => {
)
})
test('Can duplicate workflow', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
expect(await tab.getTopLevelSavedWorkflowNames()).toEqual(
expect.arrayContaining(['workflow1.json'])
)
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json'
])
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json',
'*workflow1 (Copy) (2).json'
])
await comfyPage.executeCommand('Comfy.DuplicateWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'workflow1.json',
'*workflow1 (Copy).json',
'*workflow1 (Copy) (2).json',
'*workflow1 (Copy) (3).json'
])
})
test('Can open workflow after insert', async ({ comfyPage }) => {
await comfyPage.setupWorkflowsDirectory({
'workflow1.json': 'single_ksampler.json'
})
await comfyPage.setup()
const tab = comfyPage.menu.workflowsTab
await tab.open()
await comfyPage.executeCommand('Comfy.LoadDefaultWorkflow')
const originalNodeCount = (await comfyPage.getNodes()).length
await tab.insertWorkflow(tab.getPersistedItem('workflow1.json'))
await comfyPage.nextFrame()
expect((await comfyPage.getNodes()).length).toEqual(originalNodeCount + 1)
await tab.getPersistedItem('workflow1.json').click()
await comfyPage.nextFrame()
expect((await comfyPage.getNodes()).length).toEqual(1)
})
test('Can rename nested workflow from opened workflow item', async ({
comfyPage
}) => {
@@ -452,6 +530,56 @@ test.describe('Menu', () => {
).toEqual(['*Unsaved Workflow.json', 'workflow3.json', 'workflow4.json'])
})
test('Exported workflow does not contain localized slot names', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('default')
const exportedWorkflow = await comfyPage.getExportedWorkflow({
api: false
})
expect(exportedWorkflow).toBeDefined()
for (const node of exportedWorkflow.nodes) {
for (const slot of node.inputs) {
expect(slot.localized_name).toBeUndefined()
expect(slot.label).toBeUndefined()
}
for (const slot of node.outputs) {
expect(slot.localized_name).toBeUndefined()
expect(slot.label).toBeUndefined()
}
}
})
test('Can export same workflow with different locales', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('default')
// Setup download listener before triggering the export
const downloadPromise = comfyPage.page.waitForEvent('download')
await comfyPage.menu.topbar.exportWorkflow('exported_default.json')
// Wait for the download and get the file content
const download = await downloadPromise
expect(download.suggestedFilename()).toBe('exported_default.json')
// Get the exported workflow content
const downloadedContent = await comfyPage.getExportedWorkflow({
api: false
})
await comfyPage.setSetting('Comfy.Locale', 'zh')
await comfyPage.setup()
const downloadedContentZh = await comfyPage.getExportedWorkflow({
api: false
})
// Compare the exported workflow with the original
expect(downloadedContent).toBeDefined()
expect(downloadedContent).toEqual(downloadedContentZh)
})
test('Can save workflow as with same name', async ({ comfyPage }) => {
await comfyPage.menu.topbar.saveWorkflow('workflow5.json')
expect(
@@ -459,12 +587,26 @@ test.describe('Menu', () => {
).toEqual(['workflow5.json'])
await comfyPage.menu.topbar.saveWorkflowAs('workflow5.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['workflow5.json'])
})
test('Can save temporary workflow with unmodified name', async ({
comfyPage
}) => {
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
await comfyPage.menu.topbar.saveWorkflow('Unsaved Workflow')
// Should not trigger the overwrite dialog
expect(
await comfyPage.page.locator('.comfy-modal-content:visible').count()
).toBe(0)
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
})
test('Can overwrite other workflows with save as', async ({
comfyPage
}) => {
@@ -480,7 +622,7 @@ test.describe('Menu', () => {
)
await topbar.saveWorkflowAs('workflow1.json')
await comfyPage.confirmDialog('Overwrite existing file?', 'Yes')
await comfyPage.confirmDialog.click('overwrite')
// The old workflow1.json should be deleted and the new one should be saved.
expect(
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
@@ -522,6 +664,52 @@ test.describe('Menu', () => {
await comfyPage.menu.workflowsTab.getOpenedWorkflowNames()
).toEqual(['*Unsaved Workflow.json'])
})
test('Can close saved workflow with command', async ({ comfyPage }) => {
const tab = comfyPage.menu.workflowsTab
await comfyPage.menu.topbar.saveWorkflow('workflow1.json')
await comfyPage.executeCommand('Workspace.CloseWorkflow')
expect(await tab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
test('Can delete workflows (confirm disabled)', async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.Workflow.ConfirmDelete', false)
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.nextFrame()
await comfyPage.clickContextMenuItem('Delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
test('Can delete workflows', async ({ comfyPage }) => {
const { topbar, workflowsTab } = comfyPage.menu
const filename = 'workflow18.json'
await topbar.saveWorkflow(filename)
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([filename])
await workflowsTab.getOpenedItem(filename).click({ button: 'right' })
await comfyPage.clickContextMenuItem('Delete')
await comfyPage.confirmDialog.click('delete')
await expect(workflowsTab.getOpenedItem(filename)).not.toBeVisible()
expect(await workflowsTab.getOpenedWorkflowNames()).toEqual([
'*Unsaved Workflow.json'
])
})
})
test.describe('Workflows topbar tabs', () => {
@@ -619,3 +807,209 @@ test.describe('Menu', () => {
})
})
})
test.describe.skip('Queue sidebar', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
test('can display tasks', async ({ comfyPage }) => {
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
test('can display tasks after closing then opening', async ({
comfyPage
}) => {
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.close()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
test.describe('Virtual scroll', () => {
const layouts = [
{ description: 'Five columns layout', width: 95, rows: 3, cols: 5 },
{ description: 'Three columns layout', width: 55, rows: 3, cols: 3 },
{ description: 'Two columns layout', width: 40, rows: 3, cols: 2 }
]
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(50)
.setupRoutes()
})
layouts.forEach(({ description, width, rows, cols }) => {
const preRenderedRows = 1
const preRenderedTasks = preRenderedRows * cols * 2
const visibleTasks = rows * cols
const expectRenderLimit = visibleTasks + preRenderedTasks
test.describe(description, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.menu.queueTab.setTabWidth(width)
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
})
test('should not render items outside of view', async ({
comfyPage
}) => {
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
test('should teardown items after scrolling away', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.scrollTasks('down')
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
test('should re-render items after scrolling away then back', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.scrollTasks('down')
await comfyPage.menu.queueTab.scrollTasks('up')
const renderedCount =
await comfyPage.menu.queueTab.visibleTasks.count()
expect(renderedCount).toBeLessThanOrEqual(expectRenderLimit)
})
})
})
})
test.describe('Expand tasks', () => {
test.beforeEach(async ({ comfyPage }) => {
// 2-item batch and 3-item batch -> 3 additional items when expanded
await comfyPage
.setupHistory()
.withTask(['example.webp', 'example.webp', 'example.webp'])
.withTask(['example.webp', 'example.webp'])
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
})
test('can expand tasks with multiple outputs', async ({ comfyPage }) => {
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
await comfyPage.menu.queueTab.expandTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
initialCount + 3
)
})
test('can collapse flat tasks', async ({ comfyPage }) => {
const initialCount = await comfyPage.menu.queueTab.visibleTasks.count()
await comfyPage.menu.queueTab.expandTasks()
await comfyPage.menu.queueTab.collapseTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(
initialCount
)
})
})
test.describe('Clear tasks', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(6)
.setupRoutes()
await comfyPage.menu.queueTab.open()
})
test('can clear all tasks', async ({ comfyPage }) => {
await comfyPage.menu.queueTab.clearTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(0)
expect(
await comfyPage.menu.queueTab.noResultsPlaceholder.isVisible()
).toBe(true)
})
test('can load new tasks after clearing all', async ({ comfyPage }) => {
await comfyPage.menu.queueTab.clearTasks()
await comfyPage.menu.queueTab.close()
await comfyPage.setupHistory().withTask(['example.webp']).setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
expect(await comfyPage.menu.queueTab.visibleTasks.count()).toBe(1)
})
})
test.describe('Gallery', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask(['example.webp'])
.repeat(1)
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
})
test('displays gallery image after opening task preview', async ({
comfyPage
}) => {
await comfyPage.menu.queueTab.openTaskPreview(0)
expect(comfyPage.menu.queueTab.getGalleryImage(0)).toBeVisible()
})
test('should maintain active gallery item when new tasks are added', async ({
comfyPage,
ws
}) => {
const initialIndex = 0
await comfyPage.menu.queueTab.openTaskPreview(initialIndex)
// Add a new task while the gallery is still open
comfyPage.setupHistory().withTask(['example.webp'])
await ws.trigger({
type: 'status',
data: {
status: { exec_info: { queue_remaining: 0 } }
}
})
await comfyPage.menu.queueTab.waitForTasks()
// The index of all tasks increments when a new task is prepended
const expectIndex = initialIndex + 1
expect(comfyPage.menu.queueTab.getGalleryImage(expectIndex)).toBeVisible()
})
test.describe('Gallery navigation', () => {
const paths: {
description: string
path: ('Right' | 'Left')[]
expectIndex: number
}[] = [
{ description: 'Right', path: ['Right'], expectIndex: 1 },
{ description: 'Left', path: ['Right', 'Left'], expectIndex: 0 },
{ description: 'Right wrap', path: ['Right', 'Right'], expectIndex: 0 },
{ description: 'Left wrap', path: ['Left'], expectIndex: 1 }
]
paths.forEach(({ description, path, expectIndex }) => {
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
await comfyPage.menu.queueTab.openTaskPreview(0)
for (const direction of path)
await comfyPage.page.keyboard.press(`Arrow${direction}`)
expect(
comfyPage.menu.queueTab.getGalleryImage(expectIndex)
).toBeVisible()
})
})
})
})
})

View File

@@ -1,7 +1,8 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
import type { ComfyApp } from '../src/scripts/app'
import { NodeBadgeMode } from '../src/types/nodeSource'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Node Badge', () => {
test('Can add badge', async ({ comfyPage }) => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
// If an input is optional by node definition, it should be shown as
@@ -48,4 +49,8 @@ test.describe('Optional input', () => {
expect(vaeInput.link).toBeNull()
expect(convertedInput.link).not.toBeNull()
})
test('slider', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('simple_slider')
await expect(comfyPage.canvas).toHaveScreenshot('simple_slider.png')
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -1,5 +1,7 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
import {
comfyExpect as expect,
comfyPageFixture as test
} from './fixtures/ComfyPage'
test.describe('Node search box', () => {
test.beforeEach(async ({ comfyPage }) => {
@@ -105,26 +107,127 @@ test.describe('Node search box', () => {
})
test.describe('Filtering', () => {
const expectFilterChips = async (comfyPage, expectedTexts: string[]) => {
const chips = comfyPage.searchBox.filterChips
// Check that the number of chips matches the expected count
await expect(chips).toHaveCount(expectedTexts.length)
// Verify the text and visibility of each filter chip
await Promise.all(
expectedTexts.map(async (text, index) => {
const chip = chips.nth(index)
await expect(chip).toContainText(text)
await expect(chip).toBeVisible()
})
)
}
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.doubleClickCanvas()
})
test('Can add filter', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await expect(comfyPage.searchBox.filterChips).toHaveCount(1)
await expectFilterChips(comfyPage, ['MODEL'])
})
// Flaky test.
// Sample test failure:
// https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/12696912248/job/35391990861?pr=2210
/*
1) [chromium-2x] nodeSearchBox.spec.ts:135:5 Node search box Filtering Outer click dismisses filter panel but keeps search box visible
Error: expect(locator).not.toBeVisible()
Locator: getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })
Expected: not visible
Received: visible
Call log:
- expect.not.toBeVisible with timeout 5000ms
- waiting for getByRole('dialog').locator('div').filter({ hasText: 'Add node filter condition' })
143 |
144 | // Verify the filter selection panel is hidden
> 145 | expect(panel.header).not.toBeVisible()
| ^
146 |
147 | // Verify the node search dialog is still visible
148 | expect(comfyPage.searchBox.input).toBeVisible()
at /home/runner/work/ComfyUI_frontend/ComfyUI_frontend/ComfyUI_frontend/browser_tests/nodeSearchBox.spec.ts:145:32
*/
test.skip('Outer click dismisses filter panel but keeps search box visible', async ({
comfyPage
}) => {
await comfyPage.searchBox.filterButton.click()
const panel = comfyPage.searchBox.filterSelectionPanel
await panel.header.waitFor({ state: 'visible' })
const panelBounds = await panel.header.boundingBox()
await comfyPage.page.mouse.click(panelBounds!.x - 10, panelBounds!.y - 10)
// Verify the filter selection panel is hidden
expect(panel.header).not.toBeVisible()
// Verify the node search dialog is still visible
expect(comfyPage.searchBox.input).toBeVisible()
})
test('Can add multiple filters', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
await expect(comfyPage.searchBox.filterChips).toHaveCount(2)
await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
})
test('Can remove filter', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
await comfyPage.searchBox.removeFilter(0)
await expect(comfyPage.searchBox.filterChips).toHaveCount(1)
await expectFilterChips(comfyPage, [])
})
test.describe('Removing from multiple filters', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.addFilter('CLIP', 'Output Type')
await comfyPage.searchBox.addFilter('utils', 'Category')
})
test('Can remove first filter', async ({ comfyPage }) => {
await comfyPage.searchBox.removeFilter(0)
await expectFilterChips(comfyPage, ['CLIP', 'utils'])
await comfyPage.searchBox.removeFilter(0)
await expectFilterChips(comfyPage, ['utils'])
await comfyPage.searchBox.removeFilter(0)
await expectFilterChips(comfyPage, [])
})
test('Can remove middle filter', async ({ comfyPage }) => {
await comfyPage.searchBox.removeFilter(1)
await expectFilterChips(comfyPage, ['MODEL', 'utils'])
})
test('Can remove last filter', async ({ comfyPage }) => {
await comfyPage.searchBox.removeFilter(2)
await expectFilterChips(comfyPage, ['MODEL', 'CLIP'])
})
})
})
test.describe('Input focus behavior', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.doubleClickCanvas()
})
test('focuses input after adding a filter', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await expect(comfyPage.searchBox.input).toHaveFocus()
})
test('focuses input after removing a filter', async ({ comfyPage }) => {
await comfyPage.searchBox.addFilter('MODEL', 'Input Type')
await comfyPage.searchBox.removeFilter(0)
await expect(comfyPage.searchBox.input).toHaveFocus()
})
})
})

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
import type { NodeReference } from './fixtures/utils/litegraphUtils'

View File

@@ -1,4 +1,5 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Properties Panel', () => {

View File

@@ -0,0 +1,38 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Reroute Node', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
})
test.afterEach(async ({ comfyPage }) => {
await comfyPage.setupWorkflowsDirectory({})
})
test('loads from inserted workflow', async ({ comfyPage }) => {
const workflowName = 'single_connected_reroute_node.json'
await comfyPage.setupWorkflowsDirectory({
[workflowName]: workflowName
})
await comfyPage.setup()
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
// Insert the workflow
const workflowsTab = comfyPage.menu.workflowsTab
await workflowsTab.open()
await workflowsTab.getPersistedItem(workflowName).click({ button: 'right' })
const insertButton = comfyPage.page.locator('.p-contextmenu-item-link', {
hasText: 'Insert'
})
await insertButton.click()
// Close the sidebar tab
await workflowsTab.tabButton.click()
await workflowsTab.root.waitFor({ state: 'hidden' })
await comfyPage.setFocusMode(true)
await expect(comfyPage.canvas).toHaveScreenshot('reroute_inserted.png')
})
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

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