Compare commits

...

131 Commits

Author SHA1 Message Date
Comfy Org PR Bot
01e379ce95 1.33.14 (#7300)
Patch version increment to 1.33.14

**Base branch:** `core/1.33`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7300-1-33-14-2c46d73d365081c19d1cd1f8591c8dd0)
by [Unito](https://www.unito.io)

Co-authored-by: benceruleanlu <162923238+benceruleanlu@users.noreply.github.com>
2025-12-09 16:32:14 -08:00
Comfy Org PR Bot
77051cabf5 [backport core/1.33] Fix desktop menu docs links regression (#7278)
Backport of #7181 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7278-backport-core-1-33-Fix-desktop-menu-docs-links-regression-2c46d73d36508115ab76e36fa53cd5d8)
by [Unito](https://www.unito.io)

Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
2025-12-09 00:36:09 -07:00
Comfy Org PR Bot
88d92f53f0 1.33.13 (#7272)
Patch version increment to 1.33.13

**Base branch:** `core/1.33`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7272-1-33-13-2c46d73d36508182a967ce4bd4c39816)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-08 20:40:19 -07:00
Christian Byrne
9ad36303f6 [backport core/1.33] Fix copy not working when text is selected in dialogs (#7269)
## Summary
Backport of #7166 to core/1.33 branch.

- Fixes copy not working when text is selected in dialogs
- Also includes workflow priority fix (workflow checked before
parameters)

## Conflicts Resolved
- `src/scripts/app.ts`: Accepted incoming changes for workflow priority
logic

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7269-backport-core-1-33-Fix-copy-not-working-when-text-is-selected-in-dialogs-2c46d73d365081b690bfc9dc1548618e)
by [Unito](https://www.unito.io)

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-12-08 19:52:31 -07:00
Comfy Org PR Bot
815e2d843d [backport core/1.33] Fix: Toolbox position desync (#7240)
Backport of #6962 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7240-backport-core-1-33-Fix-Toolbox-position-desync-2c36d73d365081c5b340e6e90b56fd0c)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 19:42:09 -07:00
Benjamin Lu
baf8fb918a [backport core/1.33] Restore Cancel Button (#7266)
Backport of 259e9563c8 to core/1.33.
Resolved conflicts in TopMenuSection.vue and reverted binary snapshots.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7266-Backport-Restore-Cancel-Button-to-core-1-33-2c46d73d3650812f996cd959e5e29cfa)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-12-08 19:29:40 -07:00
Comfy Org PR Bot
cb6d33007c [backport core/1.33] Fix workflow loading from PNG images with both workflow and parameter… (#7265)
Backport of #7154 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7265-backport-core-1-33-Fix-workflow-loading-from-PNG-images-with-both-workflow-and-paramete-2c46d73d365081e2b6f2dcbec3f2ba04)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Rvage <skentler@protonmail.com>
Co-authored-by: Alexander Brown <DrJKL0424@gmail.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2025-12-08 19:21:44 -07:00
Comfy Org PR Bot
386e10516e [backport core/1.33] Fix zombie linkIds on node deletion, add safety check (#7264)
Backport of #7153 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7264-backport-core-1-33-Fix-zombie-linkIds-on-node-deletion-add-safety-check-2c46d73d365081ae9161f6b464a8e446)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 19:21:32 -07:00
Comfy Org PR Bot
03a99e2eed [backport core/1.33] On adding output matchType, initialize type (#7263)
Backport of #7161 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7263-backport-core-1-33-On-adding-output-matchType-initialize-type-2c46d73d365081d8bb8bc55cf1ef9a6e)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 19:21:26 -07:00
Comfy Org PR Bot
8b2620ee0f [backport core/1.33] fix: preserve node selection on right-click (#7262)
Backport of #7162 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7262-backport-core-1-33-fix-preserve-node-selection-on-right-click-2c46d73d36508198a959dd8b2cd99fd8)
by [Unito](https://www.unito.io)

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
2025-12-08 19:21:18 -07:00
Comfy Org PR Bot
5537877f49 [backport core/1.33] Fix issue No#7155: Nodes 2.0: Load Audio node is broken (#7261)
Backport of #7175 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7261-backport-core-1-33-Fix-issue-No-7155-Nodes-2-0-Load-Audio-node-is-broken-2c46d73d36508149b7d4eeddbc05919f)
by [Unito](https://www.unito.io)

Co-authored-by: Kelly Yang <124ykl@gmail.com>
2025-12-08 19:21:10 -07:00
Comfy Org PR Bot
2ab1a0cf2b [backport core/1.33] feat(api-nodes): add pricing for seedream4.5 (#7260)
Backport of #7189 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7260-backport-core-1-33-feat-api-nodes-add-pricing-for-seedream4-5-2c46d73d36508130bf7bd755f6eed66c)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
2025-12-08 17:55:47 -07:00
Comfy Org PR Bot
8192bbcae8 [backport core/1.33] fix: preserve custom nodes i18n data when locales are lazily loaded (#7259)
Backport of #7214 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7259-backport-core-1-33-fix-preserve-custom-nodes-i18n-data-when-locales-are-lazily-loaded-2c46d73d3650813ea30bff1b39e78bd2)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-08 17:55:40 -07:00
Comfy Org PR Bot
b17ab15e90 [backport core/1.33] fix: preserve Vue node reactivity during undo/redo operations (#7257)
Backport of #7222 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7257-backport-core-1-33-fix-preserve-Vue-node-reactivity-during-undo-redo-operations-2c46d73d36508184aff1c7c0e8e82408)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-08 17:55:29 -07:00
Comfy Org PR Bot
195d779035 [backport core/1.33] When moving subgraphInput link, properly disconnect old link (#7255)
Backport of #7229 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7255-backport-core-1-33-When-moving-subgraphInput-link-properly-disconnect-old-link-2c46d73d365081cda606e81ec4cbfeec)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2025-12-08 17:55:22 -07:00
Comfy Org PR Bot
c814db1861 [backport core/1.33] Fix job details popover sticking after cancel/delete (#7256)
Backport of #6930 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7256-backport-core-1-33-Fix-job-details-popover-sticking-after-cancel-delete-2c46d73d3650819ba0ffccb9e41e8783)
by [Unito](https://www.unito.io)

Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
2025-12-08 17:32:11 -07:00
Comfy Org PR Bot
87f8de7759 [backport core/1.33] Hotfix: Templates spec (#7254)
Backport of #7235 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7254-backport-core-1-33-Hotfix-Templates-spec-2c46d73d36508168802bef2990c14d4d)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 17:32:05 -07:00
Comfy Org PR Bot
623954e582 [backport core/1.33] fix: should allow autoCols=true when server doesn't provide size (#7249)
Backport of #7132 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7249-backport-core-1-33-fix-should-allow-autoCols-true-when-server-doesn-t-provide-size-2c36d73d365081679b89fdf5ea4083d2)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-08 17:15:08 -07:00
Comfy Org PR Bot
6e541b7c46 [backport core/1.33] Fix skeleton loaders for Image/Video Previews (#7248)
Backport of #7094 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7248-backport-core-1-33-Fix-skeleton-loaders-for-Image-Video-Previews-2c36d73d3650814c9187f6321af27b96)
by [Unito](https://www.unito.io)

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
2025-12-08 17:14:54 -07:00
Comfy Org PR Bot
2561dd9ac0 [backport core/1.33] fix: should only trigger asset panel when it opening (#7247)
Backport of #7098 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7247-backport-core-1-33-fix-should-only-trigger-asset-panel-when-it-opening-2c36d73d365081caacf1c68bb8020b45)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-08 17:14:49 -07:00
Comfy Org PR Bot
4ed7c29f9a [backport core/1.33] fix: handle Unicode characters in clipboard copy/paste and add Paste menu option (#7246)
Backport of #7103 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7246-backport-core-1-33-fix-handle-Unicode-characters-in-clipboard-copy-paste-and-add-Paste-2c36d73d365081d79d8de04456bdf9d8)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-08 17:13:24 -07:00
Comfy Org PR Bot
4231514baf [backport core/1.33] fix: layout scale to handle downscale correctly (#7245)
Backport of #7109 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7245-backport-core-1-33-fix-layout-scale-to-handle-downscale-correctly-2c36d73d365081b5a4cbd6e1215aa92e)
by [Unito](https://www.unito.io)

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
2025-12-08 17:13:17 -07:00
Christian Byrne
79cdd0c06a [backport core/1.33] fix: Add fraction digits props to number input widget (backport #7033) (#7250)
## Summary
Backport of #7033 to core/1.33 branch.

- Adds fraction digits props to number input widget

## Conflicts Resolved
- Screenshot snapshots updated to match the incoming version

Fixes the manual backport requested after auto-backport failed due to
binary file conflicts.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7250-backport-core-1-33-fix-Add-fraction-digits-props-to-number-input-widget-backport-703-2c36d73d365081ce8b8ad86f1fc63df7)
by [Unito](https://www.unito.io)

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-08 17:12:56 -07:00
Comfy Org PR Bot
f7278ddbee [backport core/1.33] A11y/style: Make properties panel use theme colors. Not comprehensive. (#7243)
Backport of #7036 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7243-backport-core-1-33-A11y-style-Make-properties-panel-use-theme-colors-Not-comprehensiv-2c36d73d365081d988c0f360b3a26872)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
2025-12-08 16:27:10 -07:00
Comfy Org PR Bot
5b3401b8dd [backport core/1.33] Fix/vue nodes banner text (#7242)
Backport of #7007 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7242-backport-core-1-33-Fix-vue-nodes-banner-text-2c36d73d3650816d8356c5956102f4d9)
by [Unito](https://www.unito.io)

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
2025-12-08 16:27:02 -07:00
Comfy Org PR Bot
0e757b88fa [backport core/1.33] Fix: Toolbox position desync (#7241)
Backport of #6962 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7241-backport-core-1-33-Fix-Toolbox-position-desync-2c36d73d3650810bb77bdd720976a119)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 16:26:50 -07:00
Comfy Org PR Bot
4ae12b8006 [backport core/1.33] Feat: Double Click on Image Assets to open the lightbox (#7239)
Backport of #6955 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7239-backport-core-1-33-Feat-Double-Click-on-Image-Assets-to-open-the-lightbox-2c36d73d36508107a168c49a5dad0bf7)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 16:26:42 -07:00
Comfy Org PR Bot
0c265922dc [backport core/1.33] Change node-component-header-surface color variable (#7238)
Backport of #6990 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7238-backport-core-1-33-Change-node-component-header-surface-color-variable-2c36d73d36508117b86aca4802a9c9a2)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-12-08 16:26:30 -07:00
Comfy Org PR Bot
7fb3d2b122 [backport core/1.33] feat(api-nodes-pricing): add prices for Kling-v2-5-turbo for node KlingStartEndFrame (#7237)
Backport of #6996 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7237-backport-core-1-33-feat-api-nodes-pricing-add-prices-for-Kling-v2-5-turbo-for-node-Kl-2c36d73d365081f09245e6bac055dc2b)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
2025-12-08 16:26:25 -07:00
Comfy Org PR Bot
1df90b0b5f 1.33.12 (#7120)
Patch version increment to 1.33.12

**Base branch:** `core/1.33`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7120-1-33-12-2be6d73d365081ef95c0fcaea6332cbd)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-02 20:29:43 -07:00
Comfy Org PR Bot
b24d155bcd [backport core/1.33] fix: subpath routing for reverse proxy, embedded frontends, nginx/apache subpath hosting, etc. (like SwarmUI) (#7116)
Backport of #7115 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7116-backport-core-1-33-fix-subpath-routing-for-reverse-proxy-embedded-frontends-nginx-ap-2be6d73d36508125a7cdf11672203e88)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2025-12-02 19:28:12 -07:00
Comfy Org PR Bot
cb50a45a80 1.33.11 (#7093)
Patch version increment to 1.33.11

**Base branch:** `core/1.33`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7093-1-33-11-2bd6d73d365081548ec5fc7bb02a1685)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-02 19:26:26 -07:00
Comfy Org PR Bot
3d7df5762b [backport core/1.33] fix: normalize path separators in comfyAPIPlugin for Windows compatibility (#7089)
Backport of #7087 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7089-backport-core-1-33-fix-normalize-path-separators-in-comfyAPIPlugin-for-Windows-compati-2bd6d73d3650817e83d5d2cc101dc9a1)
by [Unito](https://www.unito.io)

Co-authored-by: Terry Jia <terryjia88@gmail.com>
2025-12-01 21:48:49 -08:00
Comfy Org PR Bot
792554d6dd 1.33.10 (#7080)
Patch version increment to 1.33.10

**Base branch:** `core/1.33`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7080-1-33-10-2bc6d73d365081af8d5ac2009068ee43)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-12-01 14:39:26 -07:00
Comfy Org PR Bot
8df8149bd6 [backport core/1.33] feat(api-nodes-pricing): add prices for Kling O1 video model (#7078)
Backport of #7077 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7078-backport-core-1-33-feat-api-nodes-pricing-add-prices-for-Kling-O1-video-model-2bc6d73d365081429431cbe6c4645fba)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
2025-12-01 14:18:19 -07:00
Comfy Org PR Bot
5e23ae318c [backport core/1.33] [fix] Prevent drag activation during Vue node resize (#7070)
Backport of #7064 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7070-backport-core-1-33-fix-Prevent-drag-activation-during-Vue-node-resize-2bc6d73d365081d29489d1704477bca4)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: DrJKL <DrJKL0424@gmail.com>
2025-11-30 20:44:29 -08:00
Christian Byrne
5606c977c8 [backport core/1.33] Simplify Vue node resize to bottom-right corner only (#7063) (#7067)
## Summary
- Backport of #7063 to core/1.33
- Simplifies Vue node resize to bottom-right corner only

Cherry-picked from d76c59cb14

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7067-backport-core-1-33-Simplify-Vue-node-resize-to-bottom-right-corner-only-7063-2bc6d73d36508189b25acf94c8639569)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-30 20:20:23 -08:00
Comfy Org PR Bot
5103c8df3f [backport core/1.33] Expose LGraphNode.getSlotPosition (#7058)
Backport of #7042 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7058-backport-core-1-33-Expose-LGraphNode-getSlotPosition-2bb6d73d365081ce9a18f1d4a802276a)
by [Unito](https://www.unito.io)

Co-authored-by: niknah <niknah+github@gmail.com>
2025-11-30 16:42:50 -07:00
Comfy Org PR Bot
0da2d80708 [backport core/1.33] mark vue nodes menu toggle with beta tag (#7051)
Backport of #7047 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7051-backport-core-1-33-mark-vue-nodes-menu-toggle-with-beta-tag-2bb6d73d365081b09428d6f0bb21dc67)
by [Unito](https://www.unito.io)

Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2025-11-29 19:14:17 -07:00
Comfy Org PR Bot
93b06525cc [backport core/1.33] feat(api-nodes-pricing): add prices for ByteDance seedance-1-0-pro-fast model (#7029)
Backport of #7026 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7029-backport-core-1-33-feat-api-nodes-pricing-add-prices-for-ByteDance-seedance-1-0-pro-f-2b96d73d365081e48660fa00f236fae9)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
2025-11-29 15:09:51 -07:00
Comfy Org PR Bot
6867d84ec1 [backport core/1.33] Remove app.graph usage from widgetInput code (#7010)
Backport of #7008 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7010-backport-core-1-33-Remove-app-graph-usage-from-widgetInput-code-2b86d73d3650811f9da1c5869f352e10)
by [Unito](https://www.unito.io)

Co-authored-by: AustinMroz <austin@comfy.org>
2025-11-27 17:57:00 -07:00
Comfy Org PR Bot
d0bd4c26ca [backport core/1.33] fix: add filter for combo widgets (#7002)
Backport of #6999 to `core/1.33`

Automatically created by backport workflow.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7002-backport-core-1-33-fix-add-filter-for-combo-widgets-2b86d73d365081d4ab3cc6d04b85bed1)
by [Unito](https://www.unito.io)

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
2025-11-27 14:20:37 -07:00
Christian Byrne
9f19c8e10e [backport core/1.33] fix: Vue Node <-> Litegraph node height offset normalization (#6979)
## Summary
Backport of #6966 onto core/1.33.

- cherry-picked 29dbfa3f
- resolved the zoomed-in ctrl+shift snapshot conflict by taking upstream
expectations

## Testing
- pnpm typecheck

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6979-backport-core-1-33-fix-Vue-Node-Litegraph-node-height-offset-normalization-2b86d73d365081748ef2d6f47ce7b1d3)
by [Unito](https://www.unito.io)

Co-authored-by: github-actions <github-actions@github.com>
2025-11-26 22:38:09 -07:00
Christian Byrne
36b8972442 [backport core/1.33] fix: remove LOD from vue nodes (#6984)
## Summary
Backport of #6950 onto core/1.33 (clean cherry-pick of 4b87b1fdc).

## Testing
- pnpm typecheck

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6984-backport-core-1-33-fix-remove-LOD-from-vue-nodes-2b86d73d36508151bf1ae4a879016211)
by [Unito](https://www.unito.io)

Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-26 21:40:53 -07:00
Christian Byrne
6f77d274a4 [backport core/1.33] fix: don't use registry when only checking for presence of missing nodes (#6974)
## Summary
Backport of #6965 onto core/1.33 (clean cherry-pick of 83f04490b).

## Testing
- pnpm typecheck

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6974-backport-core-1-33-fix-don-t-use-registry-when-only-checking-for-presence-of-missing-n-2b86d73d3650813dac37e224f857f296)
by [Unito](https://www.unito.io)
2025-11-26 17:50:32 -07:00
Alexander Brown
e6332046b0 BYOM: Model Import Wizard (#6949)
## Summary

Design alignment for the model import wizard.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6949-BYOM-Model-Import-Wizard-2b76d73d365081a48632c40430e05c93)
by [Unito](https://www.unito.io)
2025-11-25 19:19:16 -08:00
AustinMroz
5fa76e23d9 Reduce width of run button dock zone (#6925)
Tiny PR to make the docking area line up with the new size of the
actionbar after #6723
<img width="550" height="106" alt="image"
src="https://github.com/user-attachments/assets/911d510e-351f-484f-807a-17f5428dea79"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6925-Reduce-width-of-run-button-dock-zone-2b66d73d36508137aa0cc18178172264)
by [Unito](https://www.unito.io)
2025-11-25 18:19:33 -07:00
Simula_r
fcfb5437a9 feat: mobile breakpoint for vue nodes banner (#6942)
## Summary

Add mobile breakpoint to vue nodes banner

## Changes

- **What**: main.json and vue nodes banner

## Screenshots (if applicable)

<img width="500" height="615" alt="image"
src="https://github.com/user-attachments/assets/fd8cc621-c335-41c9-bbee-4ec0ae04b226"
/>
<img width="980" height="615" alt="image"
src="https://github.com/user-attachments/assets/30e17fc2-fc91-44a3-b9f0-85d5146e861b"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6942-feat-mobile-breakpoint-for-vue-nodes-banner-2b66d73d36508139b69afd7e7134b72e)
by [Unito](https://www.unito.io)
2025-11-25 18:18:01 -07:00
Comfy Org PR Bot
5ff3a0ed52 1.33.9 (#6941)
Patch version increment to 1.33.9

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6941-1-33-9-2b66d73d365081cc9b0de993e427287d)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-25 16:59:12 -07:00
Christian Byrne
5fa0295ff5 feat: update About panel (system stats and version info) to work on cloud (#6940)
## Summary

Updates the About Panel in the settings to work with the cloud-specific
`GET /system_stats` response schema.

| Before | After |
| ------ | ----- |
| <img width="1922" height="1436" alt="Selection_2392"
src="https://github.com/user-attachments/assets/3b97bf38-7eeb-4f46-9c59-eb681f5d7401"
/> | <img width="1922" height="1436" alt="Selection_2391"
src="https://github.com/user-attachments/assets/1d30e604-654a-4d48-ba05-4cac3b54c2ba"
/> |

## Screenshots (if applicable)

OSS version stays the same:

<img width="1922" height="1436" alt="Selection_2393"
src="https://github.com/user-attachments/assets/40e1eeeb-fc5a-4ad0-b37f-dc5d0374901e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6940-feat-update-About-panel-system-stats-and-version-info-to-work-on-cloud-2b66d73d365081f69b6fedfe9507ba92)
by [Unito](https://www.unito.io)
2025-11-25 16:34:34 -07:00
Christian Byrne
1348a0934a feat: update node search result rankings with 2025 usage data (#6939)
Updates the usage data for nodes to improve the search result algorithm.
Old data was from 2024. New data is from Nov 2025 and comes from the
search result interactions only (rather than workflow membership). Old
data is normalized against new data's scale and kept in fields where it
wouldn't have been otherwise overwritten.

Here's a helpful visualization of the before and after for the ranking:
https://www.diffchecker.com/Aa5XTN7F/

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6939-feat-update-node-search-result-rankings-with-2025-usage-data-2b66d73d3650818f9bb5d8765f7f7eaa)
by [Unito](https://www.unito.io)
2025-11-25 16:23:21 -07:00
Christian Byrne
01f8e77251 fix: backport workflow fails with PR title has double quotes (#6934)
Fixes issue when PR title or merge commit has double quotes (e.g.,
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19684310613/job/56385939600).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6934-fix-backport-workflow-fails-with-PR-title-has-double-quotes-2b66d73d3650815992dadb6142676754)
by [Unito](https://www.unito.io)
2025-11-25 15:04:31 -07:00
Benjamin Lu
31c03b669e Bump desktop-ui to 0.0.4 (#6933)
This contains a change to clarify to what is the base path issue after
restrictions were tightened.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6933-Bump-desktop-ui-to-0-0-4-2b66d73d3650816e9f3bed7202455ddb)
by [Unito](https://www.unito.io)
2025-11-25 13:46:21 -08:00
Alexander Piskun
c9da19b5b5 feat(api-nodes-pricing): add prices for Veo3FirstLastFrameNode (#6920)
## Summary

Price badges for this PR:
https://github.com/comfyanonymous/ComfyUI/pull/10878

If we can include this in an upcoming release, that would be absolutely
great.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6920-feat-api-nodes-pricing-add-prices-for-Veo3FirstLastFrameNode-2b66d73d365081bdb49be98d953a7a0b)
by [Unito](https://www.unito.io)
2025-11-25 11:56:39 -08:00
Alexander Piskun
10222860eb feat(api-nodes-pricing): add prices for Flux2ProImageNode (#6921)
## Summary

Price badges for https://github.com/comfyanonymous/ComfyUI/pull/10880

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6921-feat-api-nodes-pricing-add-prices-for-Flux2ProImageNode-2b66d73d365081f2b269c77df7ef93d6)
by [Unito](https://www.unito.io)
2025-11-25 11:56:10 -08:00
Benjamin Lu
4597b7e600 [feat] Improve queue job item UX based on design feedback (#6893)
## Summary
- Running jobs now show cancel button at all times (always visible, not
just on hover)
- Cancel/delete buttons use destructive red styling by default with
hover state
- Changed pending job icon from clock to loader-circle with spin
animation
- Fixed icon buttons to be square (size-6) instead of rectangular
- Added TODO comment for future declarative button config system
- Pending hint ("Job added to queue") now shows only once per entry and
no longer resets when other jobs update
- Spinner animation now applies only to the pending loader icon;
completed/check icons no longer spin
- Queue overlay hover/active state also triggers when hovering the top
menu bar so controls stay visible

## Design Spec

https://www.notion.so/comfy-org/Design-Queue-Dialog-Job-Ordering-and-Cancel-Button-Visibility-2b46d73d365081748a43d5cc9fbe2639

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6893-feat-Improve-queue-job-item-UX-based-on-design-feedback-2b56d73d365081a2bc7ef6f6fea1c739)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-25 11:23:39 -08:00
AustinMroz
6782d04f00 Support display_name on frontend (#6922)
The v3 schema allows defining a `display_name` on inputs, but this was
previously ignored on the frontend. It is now used to designate a
default value for the label of a widget or input.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6922-Support-display_name-on-frontend-2b66d73d365081d992cbea07abc27a0f)
by [Unito](https://www.unito.io)
2025-11-25 10:36:14 -08:00
Benjamin Lu
4bb5c12fac Add cloud backport tagging workflow (#6896)
## Summary
- add workflow to tag merged backport-labeled PRs into cloud/* with
cloud/v<package.json version>
- validate branch/version alignment and skip when tag already exists
- use .nvmrc (Node 24) for setup-node

## Question
- This workflow expects the version bump to already be in the PR when
you merge, as it fails if the tag already exists to keep immutability.
Should we autobump in this case?
2025-11-24 22:56:33 -08:00
Alexander Brown
8b5cfe7e55 Lint: Adding more checks for non internationalized strings (#5625)
## Summary

Catch more user visible (or audible) text that isn't
internationalizable.

## Changes

- **What**: Linter now checks other attributes for raw text.

## Review Focus

What other properties have leaked English text to non-English locales
that aren't in here?
2025-11-24 21:55:47 -08:00
Alexander Brown
135169003f Devex: Remove Importmap plugin (#6899)
## Summary

See [this
page](https://www.notion.so/comfy-org/Remove-importmap-and-replace-with-better-solution-if-it-exists-2ab6d73d3650801d83afe006fa0d9929?source=copy_link).

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6899-Devex-Remove-Importmap-plugin-2b66d73d365081b28167c9ae70100092)
by [Unito](https://www.unito.io)
2025-11-24 20:39:46 -08:00
Alexander Brown
d58a464c9c Style: Widget spacing and Markdown padding (#6902)
## Summary

Less padding, looks nice.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6902-Style-Widget-spacing-and-Markdown-padding-2b66d73d365081cca409dbabc7b883bb)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 18:31:21 -08:00
Alexander Brown
e54b972550 Chore: Update CODEOWNERS (#6901)
## Summary

Add Global Owners and a team, update/remove references to some cherished
former colleagues.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6901-Chore-Update-CODEOWNERS-2b66d73d365081f8b687ef678567b30a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
2025-11-24 17:34:41 -08:00
Alexander Brown
9bd63dbe6a Cleanup: Consistent use of Nodes 2.0 in user facing strings. (#6898)
https://github.com/Comfy-Org/ComfyUI_frontend/issues/6888

## Summary

Did a quick pass to find user facing strings. Not sure if this gets them
all.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6898-Cleanup-Consistent-use-of-Nodes-2-0-in-user-facing-strings-2b66d73d36508124aee2f5f8373a4b60)
by [Unito](https://www.unito.io)
2025-11-24 17:26:16 -08:00
Johnpaul Chiwetelu
a21c813d11 Style button widgets (#6900)
This pull request introduces improvements to widget customization and UI
consistency in the application. The most notable changes are the
addition of support for icon classes in widget options, updates to
button rendering logic, and enhanced visual consistency for button
components.

Widget customization enhancements:
* Added an optional `iconClass` property to the `IWidgetOptions`
interface in `widgets.ts`, allowing widgets to specify custom icons.

UI and rendering updates:
* Updated `WidgetButton.vue` to render the widget label and, if
provided, an icon using the new `iconClass` option. Also standardized
button styling and label usage.
* Improved button styling in `WidgetRecordAudio.vue` for better visual
consistency with other components.
<img width="662" height="534" alt="Screenshot 2025-11-25 at 01 36 45"
src="https://github.com/user-attachments/assets/43bbe226-07fd-48be-9b98-78b08a726b1b"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6900-Style-button-widgets-2b66d73d3650818ebeadd9315a47ba0f)
by [Unito](https://www.unito.io)
2025-11-25 01:12:30 +00:00
Alexander Brown
df373af987 Feat: Restore settings button to the sidebar. (#6892)
## Summary

Opens settings with a single click!
<img width="247" height="252" alt="image"
src="https://github.com/user-attachments/assets/c571d5e0-4973-4570-a542-fff343364ec0"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6892-Feat-Restore-settings-button-to-the-sidebar-2b56d73d36508102a48aec318d468303)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 12:23:29 -08:00
Alexander Brown
c06a7279e2 Style: Grid for widgets (#6891)
## Summary

Keeps the controls and widgets a consistent width, but lets the size be
more flexible

## Screenshots

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6891-Style-Grid-for-widgets-2b56d73d365081a29c30d337f3be1af6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-24 11:46:24 -08:00
Benjamin Lu
ffc17c054b Proposal: Run Storybook CI on all pull requests (#6880)
## Summary
Allow the Storybook workflow to run on every PR branch instead of only
`main`.

## Changes
- **What**: remove the `branches: [main]` filter from the Storybook CI
workflow so it triggers for all pull_request events.

## Review Focus
- Confirm broader triggering is desired for all branches while Chromatic
deployment remains restricted to `version-bump-*` or manual runs.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6880-Run-Storybook-CI-on-all-pull-requests-2b56d73d36508169a32afcf50f8e8fd8)
by [Unito](https://www.unito.io)

Co-authored-by: sno <snomiao@gmail.com>
2025-11-23 23:29:47 -08:00
Comfy Org PR Bot
ddb00d02d5 1.33.8 (#6885)
Patch version increment to 1.33.8

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6885-1-33-8-2b56d73d3650815c8660c30e7d625bdb)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-23 22:33:34 -08:00
Benjamin Lu
4815d6b14c Add test id for startup status text (#6882)
## Summary
- add `data-testid="startup-status-text"` to the status text in
StartupDisplay
- keep the status element targetable for Playwright masks

## Why
Desktop’s Playwright tests mask the troubleshooting version line; this
test id is needed there to keep screenshots stable across releases.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6882-Add-test-id-for-startup-status-text-2b56d73d365081b6a2e4ddca9aa985a4)
by [Unito](https://www.unito.io)
2025-11-23 22:54:14 -07:00
Benjamin Lu
a9653ba9c7 Remove unsupported workflow description fields (#6881)
## Summary
Remove top-level `description` keys from workflows because they are not
valid in GitHub Actions workflow syntax (see
https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax?utm_source=chatgpt.com).

## Changes
- **What**: delete the unsupported `description:` field from all
workflow YAMLs under `.github/workflows/`.

## Review Focus
- Confirm workflows still show intended names and triggers without the
invalid `description` metadata.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6881-Remove-unsupported-workflow-description-fields-2b56d73d365081ed9f20eb7f57956bc6)
by [Unito](https://www.unito.io)
2025-11-23 22:53:51 -07:00
Christian Byrne
30bafcd019 hide "unload models" and "unload cache" menu entries on cloud (#6879)
Hides these features which the user does not need when on cloud.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6879-hide-unload-models-and-unload-cache-menu-entries-on-cloud-2b46d73d3650816a8e22e913a848e4ac)
by [Unito](https://www.unito.io)
2025-11-23 22:53:18 -07:00
Christian Byrne
b789791fd9 fix: workflow that creates release branch fails (#6878)
Fixes 'create-release-branch' workflow. The script was emitting a
multiline output using `echo "results<<'EOF'" … echo "EOF"`. GitHub
treats the opening delimiter literally (`'EOF'` with quotes). Because
the closing line omits the quotes, the runner never sees a matching
terminator, so it aborts the file-command write and marks the step as
failed even though the preceding git pushes succeeded.

Example of error:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19520246619/job/55881876566

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6878-fix-workflow-that-creates-release-branch-fails-2b46d73d365081549651eacacd5cbfec)
by [Unito](https://www.unito.io)
2025-11-23 22:51:47 -07:00
Christian Byrne
723f53751e fix: duplicate "refresh node definitions" in menu entries (#6876)
The "Refresh node definitions" menu entry was added in the Manager
upstream but while in development was also added in a separate commit,
leading to duplicate menu entries:

<img width="1460" height="324" alt="image"
src="https://github.com/user-attachments/assets/66347cb3-1c52-457e-a4f1-8b32b615a1ca"
/>

This PR removes the second one.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6876-fix-duplicate-refresh-node-definitions-in-menu-entries-2b46d73d365081b98bdfcc60dc9bad36)
by [Unito](https://www.unito.io)
2025-11-23 22:51:36 -07:00
Christian Byrne
2539a7d2ce fix: tabindex prop should be number type in MultiSelect component (#6875)
Change to use number prop to fix warnings:

```
WorkflowTemplateSelectorDialog.vue:7 [Vue warn]: Invalid prop: type check failed for prop "tabindex". Expected Number with value 0, got String with value "0".
  at <MultiSelect modelValue= [] onUpdate:modelValue=fn class="w-[250px]"  ... >
  at <MultiSelect modelValue= [] onUpdate:modelValue=fn search-query=""  ... >
  at <BaseModalLayout content-title="Get Started with a Template" class="workflow-template-selector-dialog" maximized=false >
  at <WorkflowTemplateSelectorDialog ref_for=true onClose=fn<hide> maximized=false >
  at <BaseTransition onEnter=fn onAfterEnter=fn<bound onAfterEnter> onBeforeLeave=fn<bound onBeforeLeave>  ... > 
  at <Transition name="p-dialog" onEnter=fn<bound onEnter> onAfterEnter=fn<bound onAfterEnter>  ... > 
  at <Portal appendTo="body" > 
  at <Dialog key="global-workflow-template-selector" visible=true onUpdate:visible=fn<onUpdate:visible>  ... >
  at <GlobalDialog > 
  at <App>
```

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6875-fix-tabindex-prop-should-be-number-type-in-MultiSelect-component-2b46d73d3650816d8288fec4cc0f7e7f)
by [Unito](https://www.unito.io)
2025-11-23 22:51:26 -07:00
Christian Byrne
c9556d7aff fix: ordering of Vue mode LOD setting initialization in browser tests (#6884)
Fixes test "should toggle LOD based on zoom threshold" failing (example:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19618176782/job/56174006314).

Test execution order:

1. Sets MinFontSizeForLOD = 8
2. Calls setup()
3. App initializes → useVueNodeLifecycle runs → useRenderModeSetting
executes with immediate: true
4. Overrides setting back to 0 (Vue mode value)
5. LOD never activates (threshold = 0)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6884-fix-ordering-of-Vue-mode-LOD-setting-initialization-in-browser-tests-2b56d73d365081209676fb14d2c04d28)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 22:51:07 -07:00
AustinMroz
58b051a473 Share button and Assets Panel in Linear Mode (#6794)
- Re-enables the share button in Linear Mode and have it export the
current workflow
- Not as nice as having it copy an actual URL, but good enough for the
interim and it help with dead space
- Display the Media Assets Panel on the left hand side to replace the
removed Queue Panel

<img width="806" alt="image"
src="https://github.com/user-attachments/assets/93786dfa-8fbb-4368-8594-b9c98bbeb79e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6794-Share-button-and-Assets-Panel-in-Linear-Mode-2b26d73d36508178aef9ededa38d47f1)
by [Unito](https://www.unito.io)
2025-11-23 12:25:30 -07:00
Alexander Brown
0b33470744 Minor: transformState and setting error cleanup (#6841)
## Summary

Fixes the routing of TransformState through the node and the console
error from a setting that ends up being undefined via
useRenderModeSetting.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6841-Minor-transformState-and-setting-error-cleanup-2b46d73d3650817a8da7fca5bc56ea9a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 12:24:37 -07:00
Comfy Org PR Bot
fb3ce74d2f 1.33.7 (#6856)
Patch version increment to 1.33.7

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6856-1-33-7-2b46d73d365081c4b709d125df63c98d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 11:47:49 -07:00
Comfy Org PR Bot
86d3f0ebd5 1.33.6 (#6837)
Patch version increment to 1.33.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6837-1-33-6-2b46d73d3650815194b4fd885b13b574)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-23 11:31:54 -07:00
Jin Yi
09c888e338 Fix: Selected assets count not updating in Imported tab (#6842)
## Summary
- Fix bug where the "Selected assets count" displayed as 0 in the
Imported tab when selecting assets

## Root Cause
The `getOutputCount` function was returning `0` when
`user_metadata.outputCount` was not present.

- **Generated tab**: Works correctly because `outputCount` metadata is
set during generation
- **Imported tab**: `outputCount` metadata is never set, so it always
returns `0` → selected count shows as 0

## Solution
Changed the default return value from `0` to `1` when `outputCount`
metadata is not present, ensuring every asset counts as at least 1.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2025-11-23 00:57:36 -08:00
Alexander Brown
6d41e8b6e4 Feat: Load Image (from Outputs) support in Vue Nodes (#6836)
## Summary

Expose the Auto-refresh and manual refresh controls.
Fixes an issue with the Dropdown where it was index dependent so wasn't
updating correctly as new items came in.

## Screenshots

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6836-Feat-Load-Image-from-Outputs-support-in-Vue-Nodes-2b46d73d365081f1b44fcf2054d653da)
by [Unito](https://www.unito.io)
2025-11-22 23:59:00 -08:00
Terry Jia
274f77869b Fix: Opening mask editor on context menu (#6825)
## Summary

Fix issue of opening mask editor on context menu, reported in
https://github.com/Comfy-Org/ComfyUI_frontend/issues/6824

## Screenshots (if applicable)


https://github.com/user-attachments/assets/666d2769-d848-4b08-b54b-0cf5ed799b35

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6825-Fix-Opening-mask-editor-on-context-menu-2b36d73d3650810781a3c25a23ba488a)
by [Unito](https://www.unito.io)
2025-11-22 20:46:29 -05:00
Alexander Brown
f5c9f69678 Style: Fix the filter/search/sort controls on the Template Select Modal (#6835)
## Summary

Background and text colors.

## Screenshot
<img width="1186" height="148" alt="image"
src="https://github.com/user-attachments/assets/0ff3b0d5-6aae-45c5-9ebf-060a9973489c"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6835-Style-Fix-the-filter-search-sort-controls-on-the-Template-Select-Modal-2b36d73d3650816b9850e1b9f7feb25e)
by [Unito](https://www.unito.io)
2025-11-22 15:30:14 -08:00
Alexander Brown
c1e237255a Fix: TextArea context menu (#6834)
## Summary

Allow the default browser context menu within textareas on Vue Nodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6834-Fix-TextArea-context-menu-2b36d73d3650814e9706e76163dda59a)
by [Unito](https://www.unito.io)
2025-11-22 15:18:04 -08:00
AustinMroz
a91b9f288f When filtering by IO type, put wildcards last (#6829)
| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/628057b2-4844-490d-9899-fce82d8e2d58"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/2825f15f-c084-4e3d-8b22-cc0aa3febdce"
/> |

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6829-When-filtering-by-IO-type-put-wildcards-last-2b36d73d36508196a41fed40b62f5b84)
by [Unito](https://www.unito.io)
2025-11-22 13:43:05 -07:00
Tristan Sommer
4adcf09cca GPU accelerated maskeditor rendering (#6767)
## GPU accelerated brush engine for the mask editor

- Full GPU acceleration using TypeGPU and type-safe shaders
- Catmull-Rom Spline Smoothing
- arc-length equidistant resampling
- much improved performance, even for huge images
- photoshop like opacity clamping for brush strokes
- much improved soft brushes
- fallback to CPU fully implemented, much improved CPU rendering
features as well

### Tested Browsers
- Chrome (fully supported)
- Safari 26 (fully supported, prev versions CPU fallback)
- Firefox (CPU fallback, flags needed for full support)



https://github.com/user-attachments/assets/b7b5cb8a-2290-4a95-ae7d-180e11fccdb0



https://github.com/user-attachments/assets/4297aaa5-f249-499a-9b74-869677f1c73b



https://github.com/user-attachments/assets/602b4783-3e2b-489e-bcb9-70534bcaac5e

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6767-GPU-accelerated-maskeditor-rendering-2b16d73d3650818cb294e1fca03f6169)
by [Unito](https://www.unito.io)
2025-11-22 09:07:16 -05:00
Christian Byrne
1dbb3fc1b9 make vue node settings appear higher in the settings dialog (#6820)
makes setting groups/categories be sorted by highest internal setting
field `sortOrder` and adds high `sortOrder` values to the Vue Nodes
(Nodes 2.0) settings.


<img width="2282" height="1872" alt="Selection_2371"
src="https://github.com/user-attachments/assets/71e7e76b-4637-42b5-9f0c-2617622cda23"
/>
2025-11-21 20:32:18 -08:00
Alexander Brown
d6c5b33fce Fix: Vue <--> Litegraph scaling logic. (#6745)
Consistent names and order of operations. I think this fixes the
resizing too.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6745-refactor-Reorganizing-scaling-logic-2b06d73d365081eebc8ff93c87fa69fb)
by [Unito](https://www.unito.io)
2025-11-22 01:52:20 +00:00
Alexander Brown
f5608435b4 Fix: Clear apiKey on failed auth (#6816)
## Summary

Handles the case where an API key is structurally valid but not in our
DB.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6816-Fix-Clear-apiKey-on-failed-auth-2b26d73d3650817ab34edfa380795178)
by [Unito](https://www.unito.io)
2025-11-21 17:26:39 -07:00
Alexander Brown
9da82f47ef Feat: Alt+Drag to clone - Vue Nodes (#6789)
## Summary

Replicate the alt+drag to clone behavior present in litegraph.

## Changes

- **What**: Simplify the interaction/drag handling, now with less state!
- **What**: Alt+Click+Drag a node to clone it

## Screenshots (if applicable)



https://github.com/user-attachments/assets/469e33c2-de0c-4e64-a344-1e9d9339d528



<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6789-WIP-Alt-Drag-to-clone-Vue-Nodes-2b16d73d36508102a871ffe97ed2831f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-21 14:16:03 -08:00
Alexander Brown
a8d6f7baff Feat: Show Progress Text on Vue Nodes, Markdown for Preview as Text (#6805)
## Summary

Maps the progressText / text preview to a readonly Markdown widget.

## Review Focus

Anything else to tweak about this while I'm in here?

## Screenshots
<img width="1107" height="593" alt="image"
src="https://github.com/user-attachments/assets/a445c07a-2fcb-480c-976e-bda6ff343f14"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6805-Feat-Show-Progress-Text-on-Vue-Nodes-2b26d73d3650814d93c4f2fbf0e00095)
by [Unito](https://www.unito.io)
2025-11-21 13:54:53 -08:00
Christian Byrne
a7daa5071c fix: backport workflow fails when label description has single quote (#6814)
Fixes issue with backporting workflow when the target PR has a label
whose description has quotes
([example](https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19580741998/job/56077744515))

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6814-fix-backport-workflow-fails-when-label-description-has-single-quote-2b26d73d36508143bbefe6c4314fd370)
by [Unito](https://www.unito.io)
2025-11-21 14:23:36 -07:00
Jin Yi
639975b804 fix: Prevent multiple asset card popovers from opening simultaneously (#6808)
## Summary

Ensures only one MediaAssetCard popover is open at a time.

## Changes

- Added `hide()` method exposure in MoreButton component
- Implemented parent-level state management for tracking open popover
- Added VueUse `whenever` to auto-close other popovers when one opens

## Test Plan

- Open multiple asset cards' more menus
- Verify only one popover remains open at a time

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6808-fix-Prevent-multiple-asset-card-popovers-from-opening-simultaneously-2b26d73d365081e1b3f0d97b869cedc5)
by [Unito](https://www.unito.io)
2025-11-21 14:01:19 -07:00
Luke Mino-Altherr
ff0d385db8 [bugfix] Fix double-click required after pasting URL in upload model dialog (#6801)
## Summary
Fixed an issue where users had to click twice to continue after pasting
a URL in the upload model dialog - once to blur the input, then again to
click the button.

## Changes
- **What**: Replaced `UrlInput` with plain `InputText` in
`UploadModelUrlInput` to emit value immediately on input instead of only
on blur
- **Cleanup**: Moved URL cleaning/normalization to the `fetchMetadata`
handler, removed unused `disableValidation` prop from `UrlInput`
component

## Review Focus
- URL normalization logic in `useUploadModelWizard.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6801-bugfix-Fix-double-click-required-after-pasting-URL-in-upload-model-dialog-2b26d73d3650811881aed0cc064efcc7)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-21 11:05:36 -08:00
Luke Mino-Altherr
6e2e591937 refactor: change model button terminology from Upload to Import (#6800)
## Summary
Changes user-facing text from "Upload" to "Import" for the model import
feature, as "Import" better describes importing models from external
sources like Civitai.

## Changes
- Updated button icon from `upload` to `package-plus`
- Changed all user-facing text in English locale from "Upload" to
"Import"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6800-refactor-change-model-button-terminology-from-Upload-to-Import-2b26d73d365081059ce6e4a548f943ce)
by [Unito](https://www.unito.io)

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-20 21:47:02 -08:00
Comfy Org PR Bot
27fcc4554f 1.33.5 (#6798)
Patch version increment to 1.33.5

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6798-1-33-5-2b26d73d3650814fb062c8e2c1602ac6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-20 21:06:15 -07:00
Alexander Brown
e563c1be75 hotfix: Stop clicks on the textarea from propagating to the node itself (#6788)
## Summary

Selecting text shouldn't drag the node.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6788-hotfix-Stop-clicks-on-the-textarea-from-propagating-to-the-node-itself-2b16d73d3650819c8d0dc427d5758580)
by [Unito](https://www.unito.io)
2025-11-20 21:05:24 -07:00
Jin Yi
5a297e520c fix: disabling queue button (#6797)
## Summary
- Removed the `disabled` prop binding from the queue button that was
incorrectly disabling it when there were missing nodes

## Changes
- Removed `:disabled="hasMissingNodes"` from ComfyQueueButton.vue:13

## Test plan
- [x] Verify queue button is no longer incorrectly disabled when there
are missing nodes
- [x] Verify queue functionality works as expected

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6797-fix-disabling-queue-button-2b26d73d3650810783cedd44fce757be)
by [Unito](https://www.unito.io)
2025-11-20 20:52:30 -07:00
Benjamin Lu
85bec5ee47 Use shared button components in queue overlay (#6793)
## Summary
- replace raw button elements in queue progress overlay UI with shared
IconButton/TextButton/IconTextButton components
- remove forced justify-start from IconTextButton base and add explicit
alignment where needed
- keep queue overlay actions consistent with button styling patterns

## Testing
- pnpm typecheck
- pnpm lint:fix

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6793-Use-shared-button-components-in-queue-overlay-2b26d73d3650814d9ebfebba74226036)
by [Unito](https://www.unito.io)
2025-11-20 20:18:33 -07:00
AustinMroz
bdf6d4dea2 Allow updating position and mode on missing nodes (#6792)
When a node is missing, attempts to serialize it will return the "last
known good" serialization to ensure that the node will still be
functional in the future (the node pack is installed/comfyui is
updated). However, this means even small and safe changes (like moving
the node out of the way or bypassing it so the workflow can be run) will
be discarded on reload.

This is resolved by including the updated position and mode when
returning early.

| Before | After |
| ------ | ----- |
| <img width="360" height="360" alt="before"
src="https://github.com/user-attachments/assets/8452682c-9531-4153-a258-158c634df3e8"
/> | <img width="360" height="360" alt="after"
src="https://github.com/user-attachments/assets/8825ce5e-c4a6-4f4a-be20-97e4aca69964"
/> |

Thanks to @Kosinkadink for bringing this up

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6792-Allow-updating-position-and-mode-on-missing-nodes-2b26d73d365081ed8c22fafe5348c49f)
by [Unito](https://www.unito.io)
2025-11-20 17:07:15 -08:00
Comfy Org PR Bot
b8a796212c 1.33.4 (#6791)
Patch version increment to 1.33.4

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6791-1-33-4-2b16d73d365081f4b675e2d44f4935ca)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-20 17:19:34 -07:00
AustinMroz
bc553f12be Add support for dynamic widgets (#6661)
Adds support for "dynamic combo" widgets where selecting a value on a
combo widget can cause other widgets or inputs to be created.


![dynamic-widgets_00001](https://github.com/user-attachments/assets/c797d008-f335-4d4e-9b2e-6fe4a7187ba7)

Includes a fairly large refactoring in litegraphService to remove
`#private` methods and cleanup some duplication in constructors for
subgraphNodes.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6661-Add-support-for-dynamic-widgets-2a96d73d3650817aa570c7babbaca2f3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
2025-11-20 16:53:59 -07:00
Jin Yi
6bb35d46c1 fix: Conditionally hide bottom border in missing nodes modal on non-cloud environments (#6779)
## Summary
- Conditionally hide the bottom border of the missing nodes modal
content when not running in cloud environment
- The footer is not visible in non-cloud environments, so the bottom
border was appearing disconnected

## Changes
- Added conditional `border-b-1` class based on `isCloud` flag in
`MissingNodesContent.vue`
- Keeps top border visible in all environments
- Bottom border only shows in cloud environment where footer is present

## Test plan
- [ ] Open missing nodes dialog in cloud environment - bottom border
should be visible
- [ ] Open missing nodes dialog in non-cloud environment - bottom border
should be hidden
- [ ] Verify top border remains visible in both environments

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6779-fix-Conditionally-hide-bottom-border-in-missing-nodes-modal-on-non-cloud-environments-2b16d73d365081cea1c8c98b11878045)
by [Unito](https://www.unito.io)
2025-11-20 16:52:08 -07:00
Alexander Piskun
68c38f0098 feat(api-nodes-pricing): add Nano-Banana-2 prices (#6781)
## Summary

Change pricing display for Nano Banana 1, and added pricing for Nano
Banana 2.

## Screenshots (if applicable)

<img width="2101" height="963" alt="image"
src="https://github.com/user-attachments/assets/78c922c6-f6d8-47c3-afeb-adf28deb5542"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6781-feat-api-nodes-pricing-add-Nano-Banana-2-prices-2b16d73d3650810a8e8dde8f01ba9f02)
by [Unito](https://www.unito.io)
2025-11-20 09:20:05 -08:00
Comfy Org PR Bot
236247f05f 1.33.3 (#6778)
Patch version increment to 1.33.3

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6778-1-33-3-2b16d73d365081308daaf0a8553c0588)
by [Unito](https://www.unito.io)

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-20 00:56:00 -08:00
AustinMroz
87d6d18c57 Fix linear mode with vue (#6769)
Previously, entering linear mode from vue mode would cause all nodes to
be set to position 0.

This is fixed by ignoring resize updates with a contentRect of all 0s

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6769-Fix-linear-mode-with-vue-2b16d73d36508188964bcfb8b465dcb1)
by [Unito](https://www.unito.io)
2025-11-19 21:39:38 -08:00
Jin Yi
87106ccb95 [bugfix] Fix execute button incorrectly disabled on empty workflows (#6774)
## Summary

Fixes a bug where the queue/execute button was incorrectly disabled with
a warning icon when creating a new empty workflow, due to stale missing
nodes data persisting from a previous workflow.

## Root Cause

When switching from a workflow with missing nodes to an empty workflow,
the `getWorkflowPacks()` function in `useWorkflowPacks.ts` would return
early without clearing the `workflowPacks.value` ref, causing stale
missing node data to persist.

## Changes

- **`useWorkflowPacks.ts`**: Explicitly clear `workflowPacks.value = []`
when switching to empty workflow
- **`useMissingNodes.test.ts`**: Add test case to verify missing nodes
state clears when switching to empty workflow

## Test Plan

- [x] Added unit test covering the empty workflow scenario
- [x] All 20 unit tests pass
- [x] TypeScript type checking passes
- [x] Manual verification: Create workflow with missing nodes → Create
new empty workflow → Button should be enabled

## Before

1. Open workflow with missing nodes → Button disabled  (correct)
2. Create new empty workflow → Button still disabled  (bug)
3. Click valid workflow → Button enabled 

## After

1. Open workflow with missing nodes → Button disabled 
2. Create new empty workflow → Button enabled  (fixed)
3. Click valid workflow → Button enabled 

[screen-capture
(2).webm](https://github.com/user-attachments/assets/833355d6-6b4b-4e77-94b9-d7964454cfce)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6774-bugfix-Fix-execute-button-incorrectly-disabled-on-empty-workflows-2b16d73d365081e3a050c3f7c0a20cc6)
by [Unito](https://www.unito.io)
2025-11-19 21:22:32 -08:00
AustinMroz
a20fb7d260 Allow unsetting widget labels (#6773)
https://github.com/user-attachments/assets/af344318-dac2-4611-b080-910cdfa1e87d

Quick followup to #6752
- Adds support for placeholder values in dialogService.prompt
- When label is unset, initial prompt is empty
- Display original widget name as placeholder
- When prompt returns an empty string (as opposed to null for a canceled
operation), remove widget label

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6773-Allow-unsetting-widget-labels-2b16d73d365081ae9f5dd085d0081733)
by [Unito](https://www.unito.io)
2025-11-19 21:14:32 -08:00
Christian Byrne
836cd7f9ba fix: node preview background color (#6768)
## Summary

Changes node previews to use background color from color palette instead
of the menu color.

| Before | After |
| ------ | ----- |
| <img width="1700" height="1248" alt="Selection_2345"
src="https://github.com/user-attachments/assets/cc1d0e97-9551-4f88-8e92-4fe6bcdd8c21"
/> | <img width="2144" height="1138" alt="Selection_2347"
src="https://github.com/user-attachments/assets/16e64be3-3623-4900-ad18-c599a1aee59b"
/> |

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/6576

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6768-fix-node-preview-background-color-2b16d73d365081868644e1e6b7c7e3be)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2025-11-19 21:41:25 -07:00
Luke Mino-Altherr
acd855601c [feat] Add Civitai model upload wizard (#6694)
## Summary
Adds a complete model upload workflow that allows users to import models
from Civitai URLs directly into their library.

## Changes
- **Multi-step wizard**: URL input → metadata confirmation → upload
progress
- **Components**: UploadModelDialog, UploadModelUrlInput,
UploadModelConfirmation, UploadModelProgress, UploadModelDialogHeader
- **API integration**: New assetService methods for metadata retrieval
and URL-based uploads
- **Model type management**: useModelTypes composable for fetching and
formatting available model types
- **UX improvements**: Optional validation bypass for UrlInput component
- **Localization**: 26 new i18n strings for the upload workflow

## Review Focus
- Error handling for failed uploads and metadata retrieval
- Model type detection and selection logic
- Dialog state management across multi-step workflow

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6694-feat-Add-Civitai-model-upload-wizard-2ab6d73d36508193b3b1dd67c7cc5a09)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-19 20:37:22 -08:00
Comfy Org PR Bot
423a2e76bc 1.33.2 (#6762)
Patch version increment to 1.33.2

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6762-1-33-2-2b16d73d365081faa7dec4ac8660105a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 20:29:36 -07:00
Benjamin Lu
26578981d4 Remove queue sidebar tab (#6724)
## Summary
- drop the queue sidebar entry, its component, and the supporting
composable so only the overlay-based queue UI remains
- clean up the related tests and keybindings so nothing references the
removed tab
- prune the unused queue task card components to keep the repo tidy
- remove unused queue sidebar translations and command strings across
all locales

## Testing
- pnpm typecheck
- pnpm lint:fix

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6724-Remove-queue-sidebar-tab-2ae6d73d3650811db0d4c5ad4c5ffc8d)
by [Unito](https://www.unito.io)

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-19 19:50:24 -07:00
Simula_r
38fb53dca8 feat: LOD setting for LG and Vue (#6755)
## Summary

Create a composable for useRenderModeSetting that lets you set a setting
for either vue or litegraph and once set remembers each state
respectively.

```
  useRenderModeSetting(
    { setting: 'LiteGraph.Canvas.MinFontSizeForLOD', vue: 0, litegraph: 8 },
    shouldRenderVueNodes
  )
```

## Screenshots (if applicable)

<img width="1611" height="997" alt="image"
src="https://github.com/user-attachments/assets/621930f2-2d21-4c86-a46d-e3e292d4e012"
/>
<img width="1611" height="997" alt="chrome_Gr1V3P6sJi"
src="https://github.com/user-attachments/assets/eb63b747-487f-4f5e-8fcf-f0d2ff97b976"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6755-feat-LOD-setting-for-LG-and-Vue-2b16d73d365081cbbf09c292ee3c0e96)
by [Unito](https://www.unito.io)
2025-11-19 18:49:58 -08:00
AustinMroz
a832141a45 Support renaming widgets (#6752)
![widget-rename_00002](https://github.com/user-attachments/assets/65205d3e-2c03-480d-916e-0dae89ddbdd9)

Widget labels are saved by serializing the value on inputs. This
requires minor changes to ensure widgets inputs are serialized when
required.

Currently only exposed by right clicking on widgets directly. Should
probably be added to the subgraph config panel in the future.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6752-Support-renaming-widgets-2b06d73d36508196bff2e511c6e7b89b)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 19:49:03 -07:00
Benjamin Lu
d1f0211b61 Desktop maintenance: unsafe base path warning (#6750)
Surface unsafe base path validation in the desktop maintenance view and
add an installation-fix auto-refresh after successful tasks.

<img width="1080" height="870" alt="image"
src="https://github.com/user-attachments/assets/26fe61be-fed8-47c0-a921-604f0af018f8"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6750-Desktop-maintenance-unsafe-base-path-warning-2b06d73d36508147aeb4d19d02bbf0f0)
by [Unito](https://www.unito.io)
2025-11-19 18:13:32 -08:00
Comfy Org PR Bot
cc42c2967c 1.33.1 (#6756)
Patch version increment to 1.33.1

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6756-1-33-1-2b16d73d36508138a2daf1cc8ba88736)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 17:52:38 -07:00
AustinMroz
bb51a5aa76 Add linear mode (#6670)
![linear-mode](https://github.com/user-attachments/assets/d1aa078a-00a8-4e71-86d5-ee929b269c90)

See also: #6642

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6670-Add-linear-mode-2aa6d73d365081d08887e4a6db3a8fa0)
by [Unito](https://www.unito.io)
2025-11-19 16:59:43 -07:00
Benjamin Lu
674d884e79 Remove queue cancel controls (#6723)
## Summary
- remove the interrupt/clear controls from the run button cluster
- drop the queue pending count dependencies that only fed those controls

This is at the request of product design, because this functionality is
being relocated to the queue progress overlay in bl-execution

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6723-Remove-queue-cancel-controls-2ae6d73d365081ddaa63ea9e3447ad7f)
by [Unito](https://www.unito.io)

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-19 16:54:27 -07:00
Benjamin Lu
6f89d9a9f8 Add errors for install dir edge cases (#6733)
## Summary
- show explicit validation errors when the install path lives inside the
desktop app bundle or updater cache
- include the new locale strings for these error prompts so the UI
surfaces actionable guidance

## Testing
- pnpm typecheck
- pnpm lint:fix

## Notes
Desktop types still need to be updated to include the new validation
flags; see https://github.com/Comfy-Org/desktop/pull/1400

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6733-Add-errors-for-install-dir-edge-cases-2af6d73d3650811bada6fc7dd72ecf68)
by [Unito](https://www.unito.io)
2025-11-19 15:53:35 -08:00
Comfy Org PR Bot
08b206f191 1.33.0 (#6753)
Minor version increment to 1.33.0

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6753-1-33-0-2b06d73d365081658da1ff01bf5e8328)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-19 16:51:48 -07:00
Johnpaul Chiwetelu
14d94da52b Fix Node Event Handlers for Shift Click (#6262)
This pull request refactors the node selection and pointer interaction
logic in the Vue node graph editor to improve multi-selection behavior,
clarify event handling, and enhance test coverage. The main change is to
defer multi-select toggle actions (such as ctrl+click for
selection/deselection) from pointer down to pointer up, preventing
premature selection state changes and making drag interactions more
robust. The drag initiation logic is also refined to only start dragging
after the pointer moves beyond a threshold, and new composable methods
are introduced for granular node selection control.

**Node selection and pointer event handling improvements:**
* Refactored multi-select (ctrl/cmd/shift+click) logic in
`useNodeEventHandlersIndividual`: selection toggling is now deferred to
pointer up, and pointer down only brings the node to front without
changing selection state. The previous `hasMultipleNodesSelected`
function and related logic were removed for clarity.
[[1]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L18-L35)
[[2]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L57-L73)
[[3]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL112-L116)
[[4]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aR127-R143)
* Added new composable methods `deselectNode` and
`toggleNodeSelectionAfterPointerUp` to `useNodeEventHandlersIndividual`
for more granular control over node selection, and exposed them in the
returned API.
[[1]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084R210-R245)
[[2]](diffhunk://#diff-8d3820a1ca9c569bce00671fdd6290af81315ae11b8f3d6f29a5a9d30379d084L251-R259)

**Pointer interaction and drag behavior changes:**
* Updated `useNodePointerInteractions` to track pointer down/up state
and only start dragging after the pointer moves beyond a pixel
threshold. Multi-select toggling is now handled on pointer up, not
pointer down, and selection state is read from the actual node manager
for accuracy.
[[1]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R6-R10)
[[2]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R33-R34)
[[3]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R44-R53)
[[4]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R76-R110)
[[5]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1R122-R123)
[[6]](diffhunk://#diff-b50f38fec4f988dcbee7b7adf2b3425ae1e40a7ff10439ecbcb380dfa0a05ee1L131-R175)

**Test suite enhancements:**
* Improved and expanded tests for pointer interactions and selection
logic, including new cases for ctrl+click selection toggling on pointer
up, drag threshold behavior, and mocking of new composable methods.
[[1]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR9-R11)
[[2]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR35-R56)
[[3]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR100-R102)
[[4]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL144-R181)
[[5]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL155-R196)
[[6]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL196-R247)
[[7]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdL276-R336)
[[8]](diffhunk://#diff-8d94b444c448b346f5863e859c75f67267439a56a02baf44b385af1c6945effdR348-R423)
* Updated test setup and assertions for node event handlers, ensuring
selection changes are only triggered at the correct event phase and that
drag and multi-select logic is covered.
[[1]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aL4-R7)
[[2]](diffhunk://#diff-89bfc2a05201c6ff7116578efa45f96097594eb346f18446c70aa7125ab1811aR92)

These changes make node selection more predictable and user-friendly,
and ensure drag and multi-select actions behave consistently in both the
UI and the test suite.

fix https://github.com/Comfy-Org/ComfyUI_frontend/issues/6128



https://github.com/user-attachments/assets/582804d0-1d21-4ba0-a161-6582fb379352
2025-11-19 11:32:20 -08:00
Jin Yi
a521066b25 [feat] Add missing nodes warning UI to queue button and breadcrumb (#6674) 2025-11-19 12:23:24 -07:00
Jin Yi
ada0993572 feat: Improve MediaAssetCard design and add responsive sidebar footer (#6749)
## Summary
Implements design feedback for the asset panel, improving visual
hierarchy, contrast, and responsiveness based on design tokens update.

## Changes

### 🎨 Design System Updates (style.css)
- **New tokens for MediaAssetCard states:**
  - `--modal-card-background-hovered`: Hover state background
  - `--modal-card-border-highlighted`: Selected state border color
- **Updated tag contrast:**
  - Light mode: `smoke-200` → `smoke-400`
  - Dark mode: `charcoal-200` → `ash-800`
- **Registered tokens in Tailwind** via `@theme inline` for proper class
generation

### 🖼️ MediaAssetCard Improvements
- **Added tooltips** to all interactive buttons:
  - Zoom button: "Inspect"
  - More button: "More options"
  - Output count button: "See more outputs"
- **Fixed tooltip event conflicts** by wrapping buttons in tooltip divs
- **Updated hover/selected states:**
  - Hover: Uses `--modal-card-background-hovered` for subtle highlight
- Selected: Uses `--modal-card-border-highlighted` for border only (no
background)
- **Updated placeholder background** to use
`--modal-card-placeholder-background`
- **Tag styling:** Changed from `variant="light"` to `variant="gray"`
for better contrast

### 📦 SquareChip Component
- **Added `gray` variant** that uses `--modal-card-tag-background` token
- Maintains consistency with design system tokens

### 📱 AssetsSidebarTab Responsive Footer
- **Responsive button display:**
  - Width > 350px: Shows icon + text buttons
  - Width ≤ 350px: Shows icon-only buttons
- **Text alignment:** Left-aligns selection count text in compact mode
- **Uses `useResizeObserver`** for automatic width detection

### 🌐 Internationalization
- Added new i18n keys for tooltips:
  - `mediaAsset.actions.inspect`
  - `mediaAsset.actions.more`
  - `mediaAsset.actions.seeMoreOutputs`

### 🔧 Minor Fixes
- **Media3DTop:** Improved text size and icon color for better visual
hierarchy

## Visual Changes
- **Increased contrast** for asset card tags (more visible in both
themes)
- **Hover state** now provides clear visual feedback
- **Selected state** uses border highlight instead of background fill
- **Sidebar footer** gracefully adapts to narrow widths

## Related
- Addresses feedback from:
https://www.notion.so/comfy-org/Asset-panel-feedback-2aa6d73d3650800baacaf739a49360b3
- Design token updates by @Alex Tov

## Test Plan
- [ ] Verify asset card hover states in both light and dark themes
- [ ] Verify asset card selected states show highlighted border
- [ ] Test tooltips on all MediaAssetCard buttons
- [ ] Resize sidebar to < 350px and verify footer shows icon-only
buttons
- [ ] Resize sidebar to > 350px and verify footer shows icon + text
buttons
- [ ] Verify tag contrast improvement in both themes
- [ ] Test 3D asset placeholder appearance

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6749-feat-Improve-MediaAssetCard-design-and-add-responsive-sidebar-footer-2b06d73d365081019b90e110df2f1ae8)
by [Unito](https://www.unito.io)
2025-11-19 12:13:03 -07:00
Benjamin Lu
e42715086e Implement workflow progress panel (#6092)
Adds a workflow progress panel component underneath the
`actionbar-container`.

I suggest starting a review at the extraneous changes that were needed.
Including but not limited to:

- `get createTime()` in queueStore
- `promptIdToWorkflowId`, `initializingPromptIds`, and
`nodeProgressStatesByPrompt` in executionStore
- `create_time` handling in v2ToV1Adapter
- `pointer-events-auto` on ComfyActionbar.vue

The rest of the changes should be contained under
`QueueProgressOverlay.vue`, and has less of a blast radius in case
something goes wrong.

---------

Co-authored-by: pythongosssss <125205205+pythongosssss@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Jin Yi <jin12cc@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
2025-11-18 22:43:49 -08:00
Comfy Org PR Bot
92968f3f9b 1.32.6 (#6744)
Patch version increment to 1.32.6

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6744-1-32-6-2b06d73d365081948f0ff7c6b359bfd8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 22:14:12 -08:00
Alexander Brown
73692464ef Hotfix: Make the actionbar at least higher than the comfyui-body-bottom (#6743)
## Summary

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/6739

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6743-Hotfix-Make-the-actionbar-at-least-higher-than-the-comfyui-body-bottom-2b06d73d365081adb107e6aa2c4ca813)
by [Unito](https://www.unito.io)
2025-11-19 01:59:59 +00:00
Alexander Brown
61b3ca046a Cleanup: Missed the schema and registration for the removed unused widgets (#6742)
## Summary

Just a cleanup following up on
https://github.com/Comfy-Org/ComfyUI_frontend/pull/6741
2025-11-19 01:47:24 +00:00
Alexander Piskun
f8912ebaf4 feat(api-nodes-pricing): add pricing for gemini-3-pro-preview model (#6735)
## Summary

Pricing is a little bit higher then for `2.5` models:

https://cloud.google.com/vertex-ai/generative-ai/pricing#gemini-models-3

## Screenshots (if applicable)

<img width="1202" height="821" alt="Screenshot From 2025-11-18 19-28-21"
src="https://github.com/user-attachments/assets/c4a279f3-8981-4424-93c5-efa7b68f0578"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6735-feat-api-nodes-pricing-add-pricing-for-gemini-3-pro-preview-model-2af6d73d36508196afaedaf1eeb21c52)
by [Unito](https://www.unito.io)
2025-11-18 14:48:09 -08:00
Christian Byrne
80b87c1277 ci: fix cache step in release version bump workflow (#6740)
Removed the unnecessary `cache: 'pnpm'` configuration from the workflow
since version bumping doesn't require dependency caching.

The workflow configured `cache: 'pnpm'` in the Node.js setup step but
never ran `pnpm install` to create the cache directories. The cache
action expects paths like `~/.pnpm-store` and `node_modules` to exist,
but since this workflow only runs `pnpm version` (which doesn't require
dependencies), those paths are never created.

Issue occurred here:
https://github.com/Comfy-Org/ComfyUI_frontend/actions/runs/19478181571/job/55743250471

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6740-ci-fix-cache-step-in-release-version-bump-workflow-2af6d73d3650813b84a8e9d3272ad8fd)
by [Unito](https://www.unito.io)
2025-11-18 14:42:59 -08:00
Alexander Brown
00fa9b691b Fix: Simplify the widget state logic (#6741)
## Summary

Fixes the case where a value is updated in the graph but the result
doesn't reflect on the widget representation on the relevant node.

## Changes

- **What**: Uses vanilla Vue utilities instead of a special utility
- **What**: Fewer places where state could be desynced.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6741-Fix-WIP-Simplify-the-widget-state-logic-2af6d73d36508160b729db50608a2ea9)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 14:32:22 -08:00
Simula_r
0cff8eb357 fix: arbitrary styles, min size <= content, ensure layout calc, trunc… (#6731)
## Summary

### Problem:
After [vue node compacting
PR](https://github.com/Comfy-Org/ComfyUI_frontend/pull/6687) the white
space within the node has been greatly reduced, lowering the min
intrinsic size, thus allowing us to reduce the amount we need to scale
up via ensureCorrectLayoutScale(), therefore increasing readability of
nodes. Great!

However, a side effect of reducing the scale factor means nodes with
larger min content will not be scaled up enough causing nodes to be too
large in many cases.

For example, if the min intrinsic width is very long due to input
length:
<img width="807" height="519" alt="image"
src="https://github.com/user-attachments/assets/a6ea3852-bed5-49b2-b10e-c2e65c6450b2"
/>

### Solution:
Allow for nodes to be resized less than their intrinsic min width. And
truncate widget inputs like many other node UIs do.

IMPORTANT: when a node is added via search or other, it will still get a
min size based on its intrinsic content it just wont be the min width!
So best of both worlds.

<img width="670" height="551" alt="image"
src="https://github.com/user-attachments/assets/f4f5ec8c-037e-472f-a5a1-d8a59a87c0b0"
/>


this means we choose a default min width and clamp resize to it. This
also means we have to remove the arbitrary min width values that were
sprinkled around the vue node widgets. They are not needed because
instead of min width, they can take up full width and inherit the sizing
from the node min width! This makes nodes like little browser windows
and widgets are just responsive elements with in. Much more natural imo.

### Bonus
- Set ensureCorrectLayouScale() to scale factor of 1.2 which means vue
nodes are now only being set 20% bigger than LG. That covers for the
height difference we cant change!
- Fix ensureCorrectLayouScale() to offset y position for groups / better
alignment
- Get rid of arbitrary inflexible min width like min-[417px] which
shouldnt have been used the first place
- Make Select and Input overlay portals width set to their content


## Changes

**What**: 
- Node resizing behavior
- Node widget min width
- Widget input and slot truncation
- Misc arbitrary styling that should have been fluid

## Screenshots (if applicable)


https://github.com/user-attachments/assets/3ea4b8fe-565a-47f7-b3ab-6cef56cecde5


https://github.com/user-attachments/assets/2fe1e1a0-a9dc-4000-b865-ce2d8c7f3606


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6731-fix-arbitrary-styles-min-size-content-ensure-layout-calc-trunc-2af6d73d365081eab507c2f1638a4194)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
2025-11-18 13:52:23 -07:00
369 changed files with 19061 additions and 12056 deletions

View File

@@ -1,5 +1,5 @@
# Description: When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Electron API Types'
description: 'When upstream electron API is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
workflow_dispatch:

View File

@@ -1,5 +1,5 @@
# Description: When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Manager API Types'
description: 'When upstream ComfyUI-Manager API is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
# Manual trigger

View File

@@ -1,5 +1,5 @@
# Description: When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo
name: 'Api: Update Registry API Types'
description: 'When upstream comfy-api is updated, click dispatch to update the TypeScript type definitions in this repo'
on:
# Manual trigger

View File

@@ -1,5 +1,5 @@
# Description: Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq
name: "CI: JSON Validation"
description: "Validates JSON syntax in all tracked .json files (excluding tsconfig*.json) using jq"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Linting and code formatting validation for pull requests
name: "CI: Lint Format"
description: "Linting and code formatting validation for pull requests"
on:
pull_request:

View File

@@ -1,5 +1,5 @@
# Description: Validates Python code in tools/devtools directory
name: "CI: Python Validation"
description: "Validates Python code in tools/devtools directory"
on:
pull_request:

View File

@@ -1,5 +1,5 @@
# Description: Deploys test results from forked PRs (forks can't access deployment secrets)
name: "CI: Tests E2E (Deploy for Forks)"
description: "Deploys test results from forked PRs (forks can't access deployment secrets)"
on:
workflow_run:

View File

@@ -1,5 +1,5 @@
# Description: End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages
name: "CI: Tests E2E"
description: "End-to-end testing with Playwright across multiple browsers, deploys test reports to Cloudflare Pages"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Deploys Storybook previews from forked PRs (forks can't access deployment secrets)
name: "CI: Tests Storybook (Deploy for Forks)"
description: "Deploys Storybook previews from forked PRs (forks can't access deployment secrets)"
on:
workflow_run:

View File

@@ -1,10 +1,9 @@
# Description: Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages
name: "CI: Tests Storybook"
description: "Builds Storybook and runs visual regression testing via Chromatic, deploys previews to Cloudflare Pages"
on:
workflow_dispatch: # Allow manual triggering
pull_request:
branches: [main]
jobs:
# Post starting comment for non-forked PRs

View File

@@ -1,5 +1,5 @@
# Description: Unit and component testing with Vitest
name: "CI: Tests Unit"
description: "Unit and component testing with Vitest"
on:
push:

View File

@@ -1,5 +1,5 @@
# Description: Validates YAML syntax and style using yamllint with relaxed rules
name: "CI: YAML Validation"
description: "Validates YAML syntax and style using yamllint with relaxed rules"
on:
push:

View File

@@ -0,0 +1,69 @@
---
name: Cloud Backport Tag
on:
pull_request:
types: ['closed']
branches: [cloud/*]
jobs:
create-tag:
if: >
github.event.pull_request.merged == true &&
contains(github.event.pull_request.labels.*.name, 'backport')
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: read
steps:
- name: Checkout merge commit
uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
- name: Create tag for cloud backport
id: tag
run: |
set -euo pipefail
BRANCH="${{ github.event.pull_request.base.ref }}"
if [[ ! "$BRANCH" =~ ^cloud/([0-9]+)\.([0-9]+)$ ]]; then
echo "::error::Base branch '$BRANCH' is not a cloud/x.y branch"
exit 1
fi
MAJOR="${BASH_REMATCH[1]}"
MINOR="${BASH_REMATCH[2]}"
VERSION=$(node -p "require('./package.json').version")
if [[ "$VERSION" =~ ^${MAJOR}\.${MINOR}\.([0-9]+)(-.+)?$ ]]; then
PATCH="${BASH_REMATCH[1]}"
SUFFIX="${BASH_REMATCH[2]:-}"
else
echo "::error::Version '${VERSION}' does not match cloud branch '${BRANCH}'"
exit 1
fi
TAG="cloud/v${VERSION}"
if git ls-remote --tags origin "${TAG}" | grep -Fq "refs/tags/${TAG}"; then
echo "::notice::Tag ${TAG} already exists; skipping"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
exit 0
fi
git tag "${TAG}" "${{ github.event.pull_request.merge_commit_sha }}"
git push origin "${TAG}"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
{
echo "Created tag: ${TAG}"
echo "Branch: ${BRANCH}"
echo "Version: ${VERSION}"
echo "Commit: ${{ github.event.pull_request.merge_commit_sha }}"
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -1,5 +1,5 @@
# Description: Generates and updates translations for core ComfyUI components using OpenAI
name: "i18n: Update Core"
description: "Generates and updates translations for core ComfyUI components using OpenAI"
on:
# Manual dispatch for urgent translation updates

View File

@@ -78,8 +78,7 @@ jobs:
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
LABELS=$(gh pr view ${{ inputs.pr_number }} --json labels | jq -r '.labels[].name')
else
LABELS='${{ toJSON(github.event.pull_request.labels) }}'
LABELS=$(echo "$LABELS" | jq -r '.[].name')
LABELS=$(jq -r '.pull_request.labels[].name' "$GITHUB_EVENT_PATH")
fi
add_target() {
@@ -237,8 +236,8 @@ jobs:
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_TITLE="${{ github.event.pull_request.title }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
fi
for target in ${{ steps.filter-targets.outputs.pending-targets }}; do
@@ -327,8 +326,8 @@ jobs:
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
else
PR_TITLE="${{ github.event.pull_request.title }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
PR_TITLE=$(jq -r '.pull_request.title' "$GITHUB_EVENT_PATH")
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
fi
for backport in ${{ steps.backport.outputs.success }}; do
@@ -365,9 +364,9 @@ jobs:
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login')
MERGE_COMMIT=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid')
else
PR_NUMBER="${{ github.event.pull_request.number }}"
PR_AUTHOR="${{ github.event.pull_request.user.login }}"
MERGE_COMMIT="${{ github.event.pull_request.merge_commit_sha }}"
PR_NUMBER=$(jq -r '.pull_request.number' "$GITHUB_EVENT_PATH")
PR_AUTHOR=$(jq -r '.pull_request.user.login' "$GITHUB_EVENT_PATH")
MERGE_COMMIT=$(jq -r '.pull_request.merge_commit_sha' "$GITHUB_EVENT_PATH")
fi
for failure in ${{ steps.backport.outputs.failed }}; do

View File

@@ -1,5 +1,5 @@
# Description: AI-powered code review triggered by adding the 'claude-review' label to a PR
name: "PR: Claude Review"
description: "AI-powered code review triggered by adding the 'claude-review' label to a PR"
permissions:
contents: read

View File

@@ -148,10 +148,10 @@ jobs:
done
{
echo "results<<'EOF'"
echo "results<<EOF"
cat "$RESULTS_FILE"
echo "EOF"
} >> $GITHUB_OUTPUT
} >> "$GITHUB_OUTPUT"
- name: Ensure release labels
if: steps.check_version.outputs.is_minor_bump == 'true'

View File

@@ -1,5 +1,5 @@
# Description: Manual workflow to increment package version with semantic versioning support
name: "Release: Version Bump"
description: "Manual workflow to increment package version with semantic versioning support"
on:
workflow_dispatch:
@@ -59,7 +59,6 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: 'pnpm'
- name: Bump version
id: bump-version

View File

@@ -1,5 +1,5 @@
# Description: Automated weekly documentation accuracy check and update via Claude
name: "Weekly Documentation Check"
description: "Automated weekly documentation accuracy check and update via Claude"
permissions:
contents: write

View File

@@ -64,7 +64,6 @@ const config: StorybookConfig = {
deep: true,
extensions: ['vue']
})
// Note: Explicitly NOT including generateImportMapPlugin to avoid externalization
],
server: {
allowedHosts: true

View File

@@ -1,8 +1,11 @@
# Global Ownership
* @Comfy-org/comfy_frontend_devs
# Desktop/Electron
/apps/desktop-ui/ @webfiltered
/src/stores/electronDownloadStore.ts @webfiltered
/src/extensions/core/electronAdapter.ts @webfiltered
/vite.electron.config.mts @webfiltered
/apps/desktop-ui/ @benceruleanlu
/src/stores/electronDownloadStore.ts @benceruleanlu
/src/extensions/core/electronAdapter.ts @benceruleanlu
/vite.electron.config.mts @benceruleanlu
# Common UI Components
/src/components/chip/ @viva-jinyi
@@ -31,10 +34,7 @@
/src/components/graph/selectionToolbox/ @Myestery
# Minimap
/src/renderer/extensions/minimap/ @jtydhr88
# Assets
/src/platform/assets/ @arjansingh
/src/renderer/extensions/minimap/ @jtydhr88 @Myestery
# Workflow Templates
/src/platform/workflow/templates/ @Myestery @christian-byrne @comfyui-wiki
@@ -53,7 +53,7 @@
/src/workbench/extensions/manager/ @viva-jinyi @christian-byrne @ltdrdata
# Translations
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer
/src/locales/ @Yorha4D @KarryCharon @shinshin86 @Comfy-Org/comfy_maintainer @Comfy-org/comfy_frontend_devs
# LLM Instructions (blank on purpose)
.claude/

View File

@@ -1,6 +1,6 @@
{
"name": "@comfyorg/desktop-ui",
"version": "0.0.3",
"version": "0.0.4",
"type": "module",
"nx": {
"tags": [
@@ -91,7 +91,7 @@
"build-storybook": "storybook build -o dist/storybook"
},
"dependencies": {
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/comfyui-electron-types": "catalog:",
"@comfyorg/shared-frontend-utils": "workspace:*",
"@primevue/core": "catalog:",
"@primevue/themes": "catalog:",

View File

@@ -22,7 +22,11 @@
<h1 v-if="title" class="font-inter font-bold text-3xl text-neutral-300">
{{ title }}
</h1>
<p v-if="statusText" class="text-lg text-neutral-400">
<p
v-if="statusText"
class="text-lg text-neutral-400"
data-testid="startup-status-text"
>
{{ statusText }}
</p>
</div>

View File

@@ -115,19 +115,18 @@ import Button from 'primevue/button'
import Divider from 'primevue/divider'
import InputText from 'primevue/inputtext'
import Message from 'primevue/message'
import { type ModelRef, computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import type { ModelRef } from 'vue'
import { useI18n } from 'vue-i18n'
import MigrationPicker from '@/components/install/MigrationPicker.vue'
import MirrorItem from '@/components/install/mirror/MirrorItem.vue'
import {
PYPI_MIRROR,
PYTHON_MIRROR,
type UVMirror
} from '@/constants/uvMirrors'
import { PYPI_MIRROR, PYTHON_MIRROR } from '@/constants/uvMirrors'
import type { UVMirror } from '@/constants/uvMirrors'
import { electronAPI } from '@/utils/envUtil'
import { ValidationState } from '@/utils/validationUtil'
import MigrationPicker from './MigrationPicker.vue'
import MirrorItem from './mirror/MirrorItem.vue'
const { t } = useI18n()
const installPath = defineModel<string>('installPath', { required: true })
@@ -229,6 +228,10 @@ const validatePath = async (path: string | undefined) => {
}
if (validation.parentMissing) errors.push(t('install.parentMissing'))
if (validation.isOneDrive) errors.push(t('install.isOneDrive'))
if (validation.isInsideAppInstallDir)
errors.push(t('install.insideAppInstallDir'))
if (validation.isInsideUpdaterCache)
errors.push(t('install.insideUpdaterCache'))
if (validation.error)
errors.push(`${t('install.unhandledError')}: ${validation.error}`)

View File

@@ -16,7 +16,8 @@ export const DESKTOP_MAINTENANCE_TASKS: Readonly<MaintenanceTask>[] = [
execute: async () => await electron.setBasePath(),
name: 'Base path',
shortDescription: 'Change the application base path.',
errorDescription: 'Unable to open the base path. Please select a new one.',
errorDescription:
'The current base path is invalid or unsafe. Please select a new location.',
description:
'The base path is the default location where ComfyUI stores data. It is the location for the python environment, and may also contain models, custom nodes, and other extensions.',
isInstallationFix: true,

View File

@@ -85,6 +85,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
const electron = electronAPI()
// Reactive state
const lastUpdate = ref<InstallValidation | null>(null)
const isRefreshing = ref(false)
const isRunningTerminalCommand = computed(() =>
tasks.value
@@ -97,6 +98,13 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
.some((task) => getRunner(task)?.executing)
)
const unsafeBasePath = computed(
() => lastUpdate.value?.unsafeBasePath === true
)
const unsafeBasePathReason = computed(
() => lastUpdate.value?.unsafeBasePathReason
)
// Task list
const tasks = ref(DESKTOP_MAINTENANCE_TASKS)
@@ -123,6 +131,7 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
* @param validationUpdate Update details passed in by electron
*/
const processUpdate = (validationUpdate: InstallValidation) => {
lastUpdate.value = validationUpdate
const update = validationUpdate as IndexedUpdate
isRefreshing.value = true
@@ -155,7 +164,11 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
}
const execute = async (task: MaintenanceTask) => {
return getRunner(task).execute(task)
const success = await getRunner(task).execute(task)
if (success && task.isInstallationFix) {
await refreshDesktopTasks()
}
return success
}
return {
@@ -163,6 +176,8 @@ export const useMaintenanceTaskStore = defineStore('maintenanceTask', () => {
isRefreshing,
isRunningTerminalCommand,
isRunningInstallationFix,
unsafeBasePath,
unsafeBasePathReason,
execute,
getRunner,
processUpdate,

View File

@@ -0,0 +1,159 @@
// eslint-disable-next-line storybook/no-renderer-packages
import type { Meta, StoryObj } from '@storybook/vue3'
import { defineAsyncComponent } from 'vue'
type UnsafeReason = 'appInstallDir' | 'updaterCache' | 'oneDrive' | null
type ValidationIssueState = 'OK' | 'warning' | 'error' | 'skipped'
type ValidationState = {
inProgress: boolean
installState: string
basePath?: ValidationIssueState
unsafeBasePath: boolean
unsafeBasePathReason: UnsafeReason
venvDirectory?: ValidationIssueState
pythonInterpreter?: ValidationIssueState
pythonPackages?: ValidationIssueState
uv?: ValidationIssueState
git?: ValidationIssueState
vcRedist?: ValidationIssueState
upgradePackages?: ValidationIssueState
}
const validationState: ValidationState = {
inProgress: false,
installState: 'installed',
basePath: 'OK',
unsafeBasePath: false,
unsafeBasePathReason: null,
venvDirectory: 'OK',
pythonInterpreter: 'OK',
pythonPackages: 'OK',
uv: 'OK',
git: 'OK',
vcRedist: 'OK',
upgradePackages: 'OK'
}
const createMockElectronAPI = () => {
const logListeners: Array<(message: string) => void> = []
const getValidationUpdate = () => ({
...validationState
})
return {
getPlatform: () => 'darwin',
changeTheme: (_theme: unknown) => {},
onLogMessage: (listener: (message: string) => void) => {
logListeners.push(listener)
},
showContextMenu: (_options: unknown) => {},
Events: {
trackEvent: (_eventName: string, _data?: unknown) => {}
},
Validation: {
onUpdate: (_callback: (update: unknown) => void) => {},
async getStatus() {
return getValidationUpdate()
},
async validateInstallation(callback: (update: unknown) => void) {
callback(getValidationUpdate())
},
async complete() {
// Only allow completion when the base path is safe
return !validationState.unsafeBasePath
},
dispose: () => {}
},
setBasePath: () => Promise.resolve(true),
reinstall: () => Promise.resolve(),
uv: {
installRequirements: () => Promise.resolve(),
clearCache: () => Promise.resolve(),
resetVenv: () => Promise.resolve()
}
}
}
const ensureElectronAPI = () => {
const globalWindow = window as unknown as { electronAPI?: unknown }
if (!globalWindow.electronAPI) {
globalWindow.electronAPI = createMockElectronAPI()
}
return globalWindow.electronAPI
}
const MaintenanceView = defineAsyncComponent(async () => {
ensureElectronAPI()
const module = await import('./MaintenanceView.vue')
return module.default
})
const meta: Meta<typeof MaintenanceView> = {
title: 'Desktop/Views/MaintenanceView',
component: MaintenanceView,
parameters: {
layout: 'fullscreen',
backgrounds: {
default: 'dark',
values: [
{ name: 'dark', value: '#0a0a0a' },
{ name: 'neutral-900', value: '#171717' },
{ name: 'neutral-950', value: '#0a0a0a' }
]
}
}
}
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
name: 'All tasks OK',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'OK'
validationState.unsafeBasePath = false
validationState.unsafeBasePathReason = null
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}
export const UnsafeBasePathOneDrive: Story = {
name: 'Unsafe base path (OneDrive)',
render: () => ({
components: { MaintenanceView },
setup() {
validationState.inProgress = false
validationState.installState = 'installed'
validationState.basePath = 'error'
validationState.unsafeBasePath = true
validationState.unsafeBasePathReason = 'oneDrive'
validationState.venvDirectory = 'OK'
validationState.pythonInterpreter = 'OK'
validationState.pythonPackages = 'OK'
validationState.uv = 'OK'
validationState.git = 'OK'
validationState.vcRedist = 'OK'
validationState.upgradePackages = 'OK'
ensureElectronAPI()
return {}
},
template: '<MaintenanceView />'
})
}

View File

@@ -47,6 +47,28 @@
</div>
</div>
<!-- Unsafe migration warning -->
<div v-if="taskStore.unsafeBasePath" class="my-4">
<p class="flex items-start gap-3 text-neutral-300">
<Tag
icon="pi pi-exclamation-triangle"
severity="warn"
:value="t('icon.exclamation-triangle')"
/>
<span>
<strong class="block mb-1">
{{ t('maintenance.unsafeMigration.title') }}
</strong>
<span class="block mb-1">
{{ unsafeReasonText }}
</span>
<span class="block text-sm text-neutral-400">
{{ t('maintenance.unsafeMigration.action') }}
</span>
</span>
</p>
</div>
<!-- Tasks -->
<TaskListPanel
class="border-neutral-700 border-solid border-x-0 border-y"
@@ -89,10 +111,10 @@
import { PrimeIcons } from '@primevue/core/api'
import Button from 'primevue/button'
import SelectButton from 'primevue/selectbutton'
import Tag from 'primevue/tag'
import Toast from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import RefreshButton from '@/components/common/RefreshButton.vue'
import StatusTag from '@/components/maintenance/StatusTag.vue'
@@ -139,6 +161,27 @@ const filterOptions = ref([
/** Filter binding; can be set to show all tasks, or only errors. */
const filter = ref<MaintenanceFilter>(filterOptions.value[0])
const unsafeReasonText = computed(() => {
const reason = taskStore.unsafeBasePathReason
if (!reason) {
return t('maintenance.unsafeMigration.generic')
}
if (reason === 'appInstallDir') {
return t('maintenance.unsafeMigration.appInstallDir')
}
if (reason === 'updaterCache') {
return t('maintenance.unsafeMigration.updaterCache')
}
if (reason === 'oneDrive') {
return t('maintenance.unsafeMigration.oneDrive')
}
return t('maintenance.unsafeMigration.generic')
})
/** If valid, leave the validation window. */
const completeValidation = async () => {
const isValid = await electron.Validation.complete()

View File

@@ -85,11 +85,10 @@
</template>
<script setup lang="ts">
import {
InstallStage,
type InstallStageInfo,
type InstallStageName,
ProgressStatus
import { InstallStage, ProgressStatus } from '@comfyorg/comfyui-electron-types'
import type {
InstallStageInfo,
InstallStageName
} from '@comfyorg/comfyui-electron-types'
import type { Terminal } from '@xterm/xterm'
import Button from 'primevue/button'

View File

@@ -16,7 +16,6 @@ import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
import { SettingDialog } from './components/SettingDialog'
import {
NodeLibrarySidebarTab,
QueueSidebarTab,
WorkflowsSidebarTab
} from './components/SidebarTab'
import { Topbar } from './components/Topbar'
@@ -31,7 +30,6 @@ 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
@@ -60,11 +58,6 @@ class ComfyMenu {
return this._workflowsTab
}
get queueTab() {
this._queueTab ??= new QueueSidebarTab(this.page)
return this._queueTab
}
get topbar() {
this._topbar ??= new Topbar(this.page)
return this._topbar
@@ -564,7 +557,7 @@ export class ComfyPage {
async dragAndDrop(source: Position, target: Position) {
await this.page.mouse.move(source.x, source.y)
await this.page.mouse.down()
await this.page.mouse.move(target.x, target.y)
await this.page.mouse.move(target.x, target.y, { steps: 100 })
await this.page.mouse.up()
await this.nextFrame()
}

View File

@@ -65,7 +65,9 @@ export class VueNodeHelpers {
* Select a specific Vue node by ID
*/
async selectNode(nodeId: string): Promise<void> {
await this.page.locator(`[data-node-id="${nodeId}"]`).click()
await this.page
.locator(`[data-node-id="${nodeId}"] .lg-node-header`)
.click()
}
/**
@@ -77,11 +79,13 @@ export class VueNodeHelpers {
// Select first node normally
await this.selectNode(nodeIds[0])
// Add additional nodes with Ctrl+click
// Add additional nodes with Ctrl+click on header
for (let i = 1; i < nodeIds.length; i++) {
await this.page.locator(`[data-node-id="${nodeIds[i]}"]`).click({
modifiers: ['Control']
})
await this.page
.locator(`[data-node-id="${nodeIds[i]}"] .lg-node-header`)
.click({
modifiers: ['Control']
})
}
}

View File

@@ -148,124 +148,3 @@ export class WorkflowsSidebarTab extends SidebarTab {
.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')
}
get galleryImage() {
return this.page.locator('.galleria-image')
}
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.click()
return this.galleryImage.waitFor({ state: 'visible' })
}
getGalleryImage(imageFilename: string) {
return this.galleryImage.and(this.page.getByAltText(imageFilename))
}
getTaskImage(imageFilename: string) {
return this.tasks.getByAltText(imageFilename)
}
/** Trigger the queue store and tasks to update */
async triggerTasksUpdate() {
await this.page.evaluate(() => {
window['app']['api'].dispatchCustomEvent('status', {
exec_info: { queue_remaining: 0 }
})
})
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -2,6 +2,7 @@ import {
comfyExpect as expect,
comfyPageFixture as test
} from '../fixtures/ComfyPage'
import { fitToViewInstant } from '../helpers/fitToView'
// TODO: there might be a better solution for this
// Helper function to pan canvas and select node
@@ -516,6 +517,7 @@ This is English documentation.
)
await comfyPage.loadWorkflow('default')
await fitToViewInstant(comfyPage)
// Select KSampler first
const ksamplerNodes = await comfyPage.getNodeRefsByType('KSampler')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 82 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: 106 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1,210 +0,0 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
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', () => {
const firstImage = 'example.webp'
const secondImage = 'image32x32.webp'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage
.setupHistory()
.withTask([secondImage])
.withTask([firstImage])
.setupRoutes()
await comfyPage.menu.queueTab.open()
await comfyPage.menu.queueTab.waitForTasks()
await comfyPage.menu.queueTab.openTaskPreview(0)
})
test('displays gallery image after opening task preview', async ({
comfyPage
}) => {
await comfyPage.nextFrame()
await expect(
comfyPage.menu.queueTab.getGalleryImage(firstImage)
).toBeVisible()
})
test('maintains active gallery item when new tasks are added', async ({
comfyPage
}) => {
// Add a new task while the gallery is still open
const newImage = 'image64x64.webp'
comfyPage.setupHistory().withTask([newImage])
await comfyPage.menu.queueTab.triggerTasksUpdate()
await comfyPage.page.waitForTimeout(500)
const newTask = comfyPage.menu.queueTab.tasks.getByAltText(newImage)
await newTask.waitFor({ state: 'visible' })
// The active gallery item should still be the initial image
await expect(
comfyPage.menu.queueTab.getGalleryImage(firstImage)
).toBeVisible()
})
test.describe('Gallery navigation', () => {
const paths: {
description: string
path: ('Right' | 'Left')[]
end: string
}[] = [
{ description: 'Right', path: ['Right'], end: secondImage },
{ description: 'Left', path: ['Right', 'Left'], end: firstImage },
{ description: 'Left wrap', path: ['Left'], end: secondImage },
{ description: 'Right wrap', path: ['Right', 'Right'], end: firstImage }
]
paths.forEach(({ description, path, end }) => {
test(`can navigate gallery ${description}`, async ({ comfyPage }) => {
for (const direction of path)
await comfyPage.page.keyboard.press(`Arrow${direction}`, {
delay: 256
})
await comfyPage.nextFrame()
await expect(
comfyPage.menu.queueTab.getGalleryImage(end)
).toBeVisible()
})
})
})
})
})

View File

@@ -170,9 +170,7 @@ test.describe('Templates', () => {
// Verify English titles are shown as fallback
await expect(
comfyPage.templates.content.getByRole('heading', {
name: 'Image Generation'
})
comfyPage.page.getByRole('main').getByText('All Templates')
).toBeVisible()
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -6,6 +6,7 @@ import {
test.describe('Vue Nodes Zoom', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 8)
await comfyPage.vueNodes.waitForNodes()
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,54 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../../../fixtures/ComfyPage'
test.describe('Vue Node Resizing', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.vueNodes.waitForNodes()
})
test('should resize node without position drift after selecting', async ({
comfyPage
}) => {
// Get a Vue node fixture
const node = await comfyPage.vueNodes.getFixtureByTitle('Load Checkpoint')
const initialBox = await node.boundingBox()
if (!initialBox) throw new Error('Node bounding box not found')
// Select the node first (this was causing the bug)
await node.header.click()
await comfyPage.page.waitForTimeout(100) // Brief pause after selection
// Get position after selection
const selectedBox = await node.boundingBox()
if (!selectedBox)
throw new Error('Node bounding box not found after select')
// Verify position unchanged after selection
expect(selectedBox.x).toBeCloseTo(initialBox.x, 1)
expect(selectedBox.y).toBeCloseTo(initialBox.y, 1)
// Now resize from bottom-right corner
const resizeStartX = selectedBox.x + selectedBox.width - 5
const resizeStartY = selectedBox.y + selectedBox.height - 5
await comfyPage.page.mouse.move(resizeStartX, resizeStartY)
await comfyPage.page.mouse.down()
await comfyPage.page.mouse.move(resizeStartX + 50, resizeStartY + 30)
await comfyPage.page.mouse.up()
// Get final position and size
const finalBox = await node.boundingBox()
if (!finalBox) throw new Error('Node bounding box not found after resize')
// Position should NOT have changed (the bug was position drift)
expect(finalBox.x).toBeCloseTo(initialBox.x, 1)
expect(finalBox.y).toBeCloseTo(initialBox.y, 1)
// Size should have increased
expect(finalBox.width).toBeGreaterThan(initialBox.width)
expect(finalBox.height).toBeGreaterThan(initialBox.height)
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 143 KiB

View File

@@ -1,48 +0,0 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from '../../../fixtures/ComfyPage'
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
})
test.describe('Vue Nodes - LOD', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setup()
await comfyPage.loadWorkflow('default')
})
test('should toggle LOD based on zoom threshold', async ({ comfyPage }) => {
await comfyPage.vueNodes.waitForNodes()
const initialNodeCount = await comfyPage.vueNodes.getNodeCount()
expect(initialNodeCount).toBeGreaterThan(0)
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-default.png')
const vueNodesContainer = comfyPage.vueNodes.nodes
const textboxesInNodes = vueNodesContainer.getByRole('textbox')
const comboboxesInNodes = vueNodesContainer.getByRole('combobox')
await expect(textboxesInNodes.first()).toBeVisible()
await expect(comboboxesInNodes.first()).toBeVisible()
await comfyPage.zoom(120, 10)
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('vue-nodes-lod-active.png')
await expect(textboxesInNodes.first()).toBeHidden()
await expect(comboboxesInNodes.first()).toBeHidden()
await comfyPage.zoom(-120, 10)
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot(
'vue-nodes-lod-inactive.png'
)
await expect(textboxesInNodes.first()).toBeVisible()
await expect(comboboxesInNodes.first()).toBeVisible()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -88,12 +88,14 @@ export function comfyAPIPlugin(isDev: boolean): Plugin {
if (result.exports.length > 0) {
const projectRoot = process.cwd()
const relativePath = path.relative(path.join(projectRoot, 'src'), id)
const relativePath = path
.relative(path.join(projectRoot, 'src'), id)
.replace(/\\/g, '/')
const shimFileName = relativePath.replace(/\.ts$/, '.js')
let shimContent = `// Shim for ${relativePath}\n`
const fileKey = relativePath.replace(/\.ts$/, '').replace(/\\/g, '/')
const fileKey = relativePath.replace(/\.ts$/, '')
const warningMessage = getWarningMessage(fileKey, shimFileName)
if (warningMessage) {

View File

@@ -1,154 +0,0 @@
import glob from 'fast-glob'
import fs from 'fs-extra'
import { dirname, join } from 'node:path'
import { type HtmlTagDescriptor, type Plugin, normalizePath } from 'vite'
interface ImportMapSource {
name: string
pattern: string | RegExp
entry: string
recursiveDependence?: boolean
override?: Record<string, Partial<ImportMapSource>>
}
const parseDeps = (root: string, pkg: string) => {
const pkgPath = join(root, 'node_modules', pkg, 'package.json')
if (fs.existsSync(pkgPath)) {
const content = fs.readFileSync(pkgPath, 'utf-8')
const pkg = JSON.parse(content)
return Object.keys(pkg.dependencies || {})
}
return []
}
/**
* Vite plugin that generates an import map for vendor chunks.
*
* This plugin creates a browser-compatible import map that maps module specifiers
* (like 'vue' or 'primevue') to their actual file locations in the build output.
* This improves module loading in modern browsers and enables better caching.
*
* The plugin:
* 1. Tracks vendor chunks during bundle generation
* 2. Creates mappings between module names and their file paths
* 3. Injects an import map script tag into the HTML head
* 4. Configures manual chunk splitting for vendor libraries
*
* @param vendorLibraries - An array of vendor libraries to split into separate chunks
* @returns {Plugin} A Vite plugin that generates and injects an import map
*/
export function generateImportMapPlugin(
importMapSources: ImportMapSource[]
): Plugin {
const importMapEntries: Record<string, string> = {}
const resolvedImportMapSources: Map<string, ImportMapSource> = new Map()
const assetDir = 'assets/lib'
let root: string
return {
name: 'generate-import-map-plugin',
// Configure manual chunks during the build process
configResolved(config) {
root = config.root
if (config.build) {
// Ensure rollupOptions exists
if (!config.build.rollupOptions) {
config.build.rollupOptions = {}
}
for (const source of importMapSources) {
resolvedImportMapSources.set(source.name, source)
if (source.recursiveDependence) {
const deps = parseDeps(root, source.name)
while (deps.length) {
const dep = deps.shift()!
const depSource = Object.assign({}, source, {
name: dep,
pattern: dep,
...source.override?.[dep]
})
resolvedImportMapSources.set(depSource.name, depSource)
const _deps = parseDeps(root, depSource.name)
deps.unshift(..._deps)
}
}
}
const external: (string | RegExp)[] = []
for (const [, source] of resolvedImportMapSources) {
external.push(source.pattern)
}
config.build.rollupOptions.external = external
}
},
generateBundle(_options) {
for (const [, source] of resolvedImportMapSources) {
if (source.entry) {
const moduleFile = join(source.name, source.entry)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
importMapEntries[source.name] =
'./' + normalizePath(join(assetDir, moduleFile))
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
if (source.recursiveDependence) {
const files = glob.sync(['**/*.{js,mjs}'], {
cwd: join(root, 'node_modules', source.name)
})
for (const file of files) {
const moduleFile = join(source.name, file)
const sourceFile = join(root, 'node_modules', moduleFile)
const targetFile = join(root, 'dist', assetDir, moduleFile)
importMapEntries[normalizePath(join(source.name, dirname(file)))] =
'./' + normalizePath(join(assetDir, moduleFile))
const targetDir = dirname(targetFile)
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
fs.copyFileSync(sourceFile, targetFile)
}
}
}
},
transformIndexHtml(html) {
if (Object.keys(importMapEntries).length === 0) {
console.warn(
'[ImportMap Plugin] No vendor chunks found to create import map.'
)
return html
}
const importMap = {
imports: importMapEntries
}
const importMapTag: HtmlTagDescriptor = {
tag: 'script',
attrs: { type: 'importmap' },
children: JSON.stringify(importMap, null, 2),
injectTo: 'head'
}
return {
html,
tags: [importMapTag]
}
}
}
}

View File

@@ -1,2 +1 @@
export { comfyAPIPlugin } from './comfyAPIPlugin'
export { generateImportMapPlugin } from './generateImportMapPlugin'

View File

@@ -191,6 +191,19 @@ export default defineConfig([
'@intlify/vue-i18n/no-raw-text': [
'error',
{
attributes: {
'/.+/': [
'aria-label',
'aria-placeholder',
'aria-roledescription',
'aria-valuetext',
'label',
'placeholder',
'title',
'v-tooltip'
],
img: ['alt']
},
// Ignore strings that are:
// 1. Less than 2 characters
// 2. Only symbols/numbers/whitespace (no letters)
@@ -200,24 +213,27 @@ export default defineConfig([
ignoreNodes: ['md-icon', 'v-icon', 'pre', 'code', 'script', 'style'],
// Brand names and technical terms that shouldn't be translated
ignoreText: [
'ComfyUI',
'GitHub',
'OpenAI',
'API',
'URL',
'JSON',
'YAML',
'GPU',
'CPU',
'RAM',
'GB',
'MB',
'KB',
'ms',
'fps',
'px',
'App Data:',
'App Path:'
'App Path:',
'ComfyUI',
'CPU',
'fps',
'GB',
'GitHub',
'GPU',
'JSON',
'KB',
'LoRA',
'MB',
'ms',
'OpenAI',
'png',
'px',
'RAM',
'URL',
'YAML',
'1.2 MB'
]
}
]

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.32.5",
"version": "1.33.14",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -75,6 +75,7 @@
"@vitest/coverage-v8": "catalog:",
"@vitest/ui": "catalog:",
"@vue/test-utils": "catalog:",
"@webgpu/types": "catalog:",
"cross-env": "catalog:",
"eslint": "catalog:",
"eslint-config-prettier": "catalog:",
@@ -112,6 +113,7 @@
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"unplugin-icons": "catalog:",
"unplugin-typegpu": "catalog:",
"unplugin-vue-components": "catalog:",
"uuid": "^11.1.0",
"vite": "catalog:",
@@ -128,7 +130,7 @@
"dependencies": {
"@alloc/quick-lru": "catalog:",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "0.4.73-0",
"@comfyorg/comfyui-electron-types": "catalog:",
"@comfyorg/design-system": "workspace:*",
"@comfyorg/registry-types": "workspace:*",
"@comfyorg/shared-frontend-utils": "workspace:*",
@@ -162,7 +164,6 @@
"es-toolkit": "^1.39.9",
"extendable-media-recorder": "^9.2.27",
"extendable-media-recorder-wav-encoder": "^7.0.129",
"fast-glob": "^3.3.3",
"firebase": "catalog:",
"fuse.js": "^7.0.0",
"glob": "^11.0.3",
@@ -176,6 +177,7 @@
"semver": "^7.7.2",
"three": "^0.170.0",
"tiptap-markdown": "^0.8.10",
"typegpu": "catalog:",
"vue": "catalog:",
"vue-i18n": "catalog:",
"vue-router": "catalog:",

View File

@@ -98,6 +98,9 @@
--color-comfy-menu-secondary: var(--comfy-menu-secondary-bg);
--color-interface-panel-job-progress-primary: var(--color-azure-300);
--color-interface-panel-job-progress-secondary: var(--color-alpha-azure-600-30);
--color-blue-selection: rgb(from var(--color-azure-600) r g b / 0.3);
--color-node-hover-100: rgb(from var(--color-charcoal-800) r g b/ 0.15);
--color-node-hover-200: rgb(from var(--color-charcoal-800) r g b/ 0.1);
@@ -191,7 +194,7 @@
--node-component-executing: var(--color-blue-500);
--node-component-header: var(--fg-color);
--node-component-header-icon: var(--color-ash-800);
--node-component-header-surface: var(--color-white);
--node-component-header-surface: var(--color-smoke-400);
--node-component-outline: var(--color-black);
--node-component-ring: rgb(from var(--color-smoke-500) r g b / 50%);
--node-component-slot-dot-outline-opacity-mult: 1;
@@ -265,12 +268,13 @@
--palette-interface-button-hover-surface: color-mix(in srgb, var(--interface-panel-surface) 82%, var(--contrast-mix-color));
--modal-card-background: var(--secondary-background);
--modal-card-background-hovered: var(--secondary-background-hover);
--modal-card-border-highlighted: var(--secondary-background-selected);
--modal-card-button-surface: var(--color-smoke-300);
--modal-card-placeholder-background: var(--color-smoke-600);
--modal-card-tag-background: var(--color-smoke-200);
--modal-card-tag-background: var(--color-smoke-400);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-white);
}
.dark-theme {
@@ -330,6 +334,12 @@
--node-stroke-error: var(--color-error);
--node-stroke-executing: var(--color-azure-600);
/* Queue progress (dark theme) */
--color-interface-panel-job-progress-primary: var(--color-cobalt-800);
--color-interface-panel-job-progress-secondary: var(
--color-alpha-azure-600-30
);
--text-secondary: var(--color-slate-100);
--text-primary: var(--color-white);
@@ -368,9 +378,11 @@
--component-node-widget-background-highlighted: var(--color-graphite-400);
--modal-card-background: var(--secondary-background);
--modal-card-background-hovered: var(--secondary-background-hover);
--modal-card-border-highlighted: var(--color-ash-400);
--modal-card-button-surface: var(--color-charcoal-300);
--modal-card-placeholder-background: var(--secondary-background);
--modal-card-tag-background: var(--color-charcoal-200);
--modal-card-tag-background: var(--color-ash-800);
--modal-card-tag-foreground: var(--base-foreground);
--modal-panel-background: var(--color-charcoal-600);
@@ -386,12 +398,14 @@
--color-subscription-button-gradient: var(--subscription-button-gradient);
--color-modal-card-background: var(--modal-card-background);
--color-modal-card-background-hovered: var(--modal-card-background-hovered);
--color-modal-card-border-highlighted: var(--modal-card-border-highlighted);
--color-modal-card-button-surface: var(--modal-card-button-surface);
--color-modal-card-placeholder-background: var(--modal-card-placeholder-background);
--color-modal-card-tag-background: var(--modal-card-tag-background);
--color-modal-card-tag-foreground: var(--modal-card-tag-foreground);
--color-modal-panel-background: var(--modal-panel-background);
--color-dialog-surface: var(--dialog-surface);
--color-interface-menu-component-surface-hovered: var(
--interface-menu-component-surface-hovered
@@ -1176,24 +1190,19 @@ dialog::backdrop {
.litegraph.litecontextmenu,
.litegraph.litecontextmenu.dark {
z-index: 9999 !important;
background-color: var(--comfy-menu-bg) !important;
background-color: var(--comfy-menu-bg);
}
.litegraph.litecontextmenu
.litemenu-entry:hover:not(.disabled):not(.separator) {
background-color: var(--comfy-menu-hover-bg, var(--border-color)) !important;
color: var(--fg-color);
}
.litegraph.litecontextmenu .litemenu-entry.submenu,
.litegraph.litecontextmenu.dark .litemenu-entry.submenu {
background-color: var(--comfy-menu-bg) !important;
color: var(--input-text);
background-color: var(--comfy-menu-bg);
color: var(--fg-color);
}
.litegraph.litecontextmenu input {
background-color: var(--comfy-input-bg) !important;
color: var(--input-text) !important;
background-color: var(--comfy-input-bg);
color: var(--input-text);
}
.comfy-context-menu-filter {
@@ -1234,14 +1243,14 @@ dialog::backdrop {
.litegraph.litesearchbox {
z-index: 9999 !important;
background-color: var(--comfy-menu-bg) !important;
background-color: var(--comfy-menu-bg);
overflow: hidden;
display: block;
}
.litegraph.litesearchbox input,
.litegraph.litesearchbox select {
background-color: var(--comfy-input-bg) !important;
background-color: var(--comfy-input-bg);
color: var(--input-text);
}
@@ -1315,57 +1324,6 @@ audio.comfy-audio.empty-audio-widget {
will-change: transform;
}
/* START LOD specific styles */
/* LOD styles - Custom CSS avoids 100+ Tailwind selectors that would slow style recalculation when .isLOD toggles */
.isLOD .lg-node {
box-shadow: none;
filter: none;
backdrop-filter: none;
text-shadow: none;
mask-image: none;
clip-path: none;
background-image: none;
text-rendering: optimizeSpeed;
border-radius: 0;
contain: layout style;
transition: none;
}
.isLOD .lg-node-header {
border-radius: 0;
pointer-events: none;
}
.isLOD .lg-node-widgets {
pointer-events: none;
}
.lod-toggle {
visibility: visible;
}
.isLOD .lod-toggle {
visibility: hidden;
}
.lod-fallback {
display: none;
}
.isLOD .lod-fallback {
display: block;
}
.isLOD .image-preview img {
image-rendering: pixelated;
}
.isLOD .slot-dot {
border-radius: 0;
}
/* END LOD specific styles */
/* ===================== Mask Editor Styles ===================== */
/* To be migrated to Tailwind later */
#maskEditor_brush {
@@ -1827,4 +1785,4 @@ audio.comfy-audio.empty-audio-widget {
.maskEditor_sidePanelLayerCheckbox {
margin-left: 15px;
}
/* ===================== End of Mask Editor Styles ===================== */
/* ===================== End of Mask Editor Styles ===================== */

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.33398 1.33337V4.00004C9.33398 4.35366 9.47446 4.6928 9.72451 4.94285C9.97456 5.1929 10.3137 5.33337 10.6673 5.33337H13.334M2.66732 4.66671V2.66671C2.66732 2.31309 2.80779 1.97395 3.05784 1.7239C3.30789 1.47385 3.64703 1.33337 4.00065 1.33337H10.0006L13.334 4.66671V13.3334C13.334 13.687 13.1935 14.0261 12.9435 14.2762C12.6934 14.5262 12.3543 14.6667 12.0006 14.6667L4.04264 14.666C3.77927 14.7004 3.51166 14.6552 3.2741 14.5365C3.03655 14.4177 2.83988 14.2307 2.70931 13.9994M3.33398 7.33337L1.33398 9.33337M1.33398 9.33337L3.33398 11.3334M1.33398 9.33337H8.00065" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 771 B

View File

@@ -75,6 +75,17 @@ export function formatSize(value?: number) {
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`
}
/**
* Formats a commit hash by truncating long (40-char) hashes to 7 chars.
* Returns the original string if not a valid full commit hash.
*/
export function formatCommitHash(value: string): string {
if (/^[a-f0-9]{40}$/i.test(value)) {
return value.slice(0, 7)
}
return value
}
/**
* Returns various filename components.
* Example:

150
pnpm-lock.yaml generated
View File

@@ -9,18 +9,15 @@ catalogs:
'@alloc/quick-lru':
specifier: ^5.2.0
version: 5.2.0
'@comfyorg/comfyui-electron-types':
specifier: 0.5.5
version: 0.5.5
'@eslint/js':
specifier: ^9.35.0
version: 9.35.0
'@iconify-json/lucide':
specifier: ^1.1.178
version: 1.2.66
'@iconify/json':
specifier: ^2.2.380
version: 2.2.380
'@iconify/tailwind':
specifier: ^1.1.3
version: 1.2.0
'@intlify/eslint-plugin-vue-i18n':
specifier: ^4.1.0
version: 4.1.0
@@ -123,6 +120,9 @@ catalogs:
'@vueuse/integrations':
specifier: ^13.9.0
version: 13.9.0
'@webgpu/types':
specifier: ^0.1.66
version: 0.1.66
algoliasearch:
specifier: ^5.21.0
version: 5.21.0
@@ -243,6 +243,9 @@ catalogs:
tw-animate-css:
specifier: ^1.3.8
version: 1.3.8
typegpu:
specifier: ^0.8.2
version: 0.8.2
typescript:
specifier: ^5.9.2
version: 5.9.2
@@ -252,6 +255,9 @@ catalogs:
unplugin-icons:
specifier: ^0.22.0
version: 0.22.0
unplugin-typegpu:
specifier: 0.8.0
version: 0.8.0
unplugin-vue-components:
specifier: ^0.28.0
version: 0.28.0
@@ -318,8 +324,8 @@ importers:
specifier: ^1.3.1
version: 1.3.1
'@comfyorg/comfyui-electron-types':
specifier: 0.4.73-0
version: 0.4.73-0
specifier: 'catalog:'
version: 0.5.5
'@comfyorg/design-system':
specifier: workspace:*
version: link:packages/design-system
@@ -419,9 +425,6 @@ importers:
extendable-media-recorder-wav-encoder:
specifier: ^7.0.129
version: 7.0.129
fast-glob:
specifier: ^3.3.3
version: 3.3.3
firebase:
specifier: 'catalog:'
version: 11.6.0
@@ -461,6 +464,9 @@ importers:
tiptap-markdown:
specifier: ^0.8.10
version: 0.8.10(@tiptap/core@2.10.4(@tiptap/pm@2.10.4))
typegpu:
specifier: 'catalog:'
version: 0.8.2
vue:
specifier: 'catalog:'
version: 3.5.13(typescript@5.9.2)
@@ -558,6 +564,9 @@ importers:
'@vue/test-utils':
specifier: 'catalog:'
version: 2.4.6
'@webgpu/types':
specifier: 'catalog:'
version: 0.1.66
cross-env:
specifier: 'catalog:'
version: 10.1.0
@@ -669,6 +678,9 @@ importers:
unplugin-icons:
specifier: 'catalog:'
version: 0.22.0(@vue/compiler-sfc@3.5.13)
unplugin-typegpu:
specifier: 'catalog:'
version: 0.8.0(typegpu@0.8.2)
unplugin-vue-components:
specifier: 'catalog:'
version: 0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2))
@@ -709,8 +721,8 @@ importers:
apps/desktop-ui:
dependencies:
'@comfyorg/comfyui-electron-types':
specifier: 0.4.73-0
version: 0.4.73-0
specifier: 'catalog:'
version: 0.5.5
'@comfyorg/shared-frontend-utils':
specifier: workspace:*
version: link:../../packages/shared-frontend-utils
@@ -1428,6 +1440,10 @@ packages:
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/standalone@7.28.5':
resolution: {integrity: sha512-1DViPYJpRU50irpGMfLBQ9B4kyfQuL6X7SS7pwTeWeZX0mNkjzPi0XFqxCjSdddZXUQy4AhnQnnesA/ZHnvAdw==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -1453,8 +1469,8 @@ packages:
'@cacheable/utils@2.0.3':
resolution: {integrity: sha512-m7Rce68cMHlAUjvWBy9Ru1Nmw5gU0SjGGtQDdhpe6E0xnbcvrIY0Epy//JU1VYYBUTzrG9jvgmTauULGKzOkWA==}
'@comfyorg/comfyui-electron-types@0.4.73-0':
resolution: {integrity: sha512-WlItGJQx9ZWShNG9wypx3kq+19pSig/U+s5sD2SAeEcMph4u8A/TS+lnRgdKhT58VT1uD7cMcj2SJpfdBPNWvw==}
'@comfyorg/comfyui-electron-types@0.5.5':
resolution: {integrity: sha512-f3XOXpMsALIwHakz7FekVPm4/Fh2pvJPEi8tRe8jYGBt8edsd4Mkkq31Yjs2Weem3BP7yNwbdNuSiQdP/pxJyg==}
'@csstools/color-helpers@5.1.0':
resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
@@ -3787,8 +3803,8 @@ packages:
peerDependencies:
vue: ^3.5.0
'@webgpu/types@0.1.51':
resolution: {integrity: sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==}
'@webgpu/types@0.1.66':
resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==}
'@xstate/fsm@1.6.5':
resolution: {integrity: sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==}
@@ -4413,6 +4429,9 @@ packages:
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
data-urls@5.0.0:
resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
engines: {node: '>=18'}
@@ -6032,6 +6051,10 @@ packages:
resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
hasBin: true
magic-string-ast@1.0.3:
resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==}
engines: {node: '>=20.19.0'}
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
@@ -7000,6 +7023,11 @@ packages:
engines: {node: '>= 0.4'}
hasBin: true
resolve@1.22.11:
resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
engines: {node: '>= 0.4'}
hasBin: true
restore-cursor@3.1.0:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
@@ -7095,6 +7123,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
hasBin: true
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -7395,6 +7428,14 @@ packages:
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyest-for-wgsl@0.1.3:
resolution: {integrity: sha512-Wm5ADG1UyDxykf42S1gLYP4U9e1QP/TdtJeovQi6y68zttpiFLKqQGioHmPs9Mjysh7YMSAr/Lpuk0cD2MVdGA==}
engines: {node: '>=12.20.0'}
tinyest@0.1.2:
resolution: {integrity: sha512-aHRmouyowIq1P5jrTF+YK6pGX+WuvFtSCLbqk91yHnU3SWQRIcNIamZLM5XF6lLqB13AWz0PGPXRff2QGDsxIg==}
engines: {node: '>=12.20.0'}
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -7521,6 +7562,13 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
typed-binary@4.3.2:
resolution: {integrity: sha512-HT3pIBM2njCZUmeczDaQUUErGiM6GXFCqMsHegE12HCoBtvHCkfR10JJni0TeGOTnLilTd6YFyj+YhflqQDrDQ==}
typegpu@0.8.2:
resolution: {integrity: sha512-wkMJWhJE0pSkw2G/FesjqjbtHkREyOKu1Zmyj19xfmaX5+65YFwgfQNKSK8CxqN4kJkP7JFelLDJTSYY536TYg==}
engines: {node: '>=12.20.0'}
typescript-eslint@8.44.0:
resolution: {integrity: sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -7625,6 +7673,11 @@ packages:
vue-template-es2015-compiler:
optional: true
unplugin-typegpu@0.8.0:
resolution: {integrity: sha512-VJHdXSXGOkAx0WhwFczhVUjAI6HyDkrQXk20HnwyuzIE3FdqE5l9sJTCYZzoVGo3z8i/IA5TMHCDzzP0Bc97Cw==}
peerDependencies:
typegpu: ^0.8.0
unplugin-vue-components@0.28.0:
resolution: {integrity: sha512-jiTGtJ3JsRFBjgvyilfrX7yUoGKScFgbdNw+6p6kEXU+Spf/rhxzgvdfuMcvhCcLmflB/dY3pGQshYBVGOUx7Q==}
engines: {node: '>=14'}
@@ -7815,8 +7868,8 @@ packages:
vue-component-type-helpers@3.1.1:
resolution: {integrity: sha512-B0kHv7qX6E7+kdc5nsaqjdGZ1KwNKSUQDWGy7XkTYT7wFsOpkEyaJ1Vq79TjwrrtuLRgizrTV7PPuC4rRQo+vw==}
vue-component-type-helpers@3.1.3:
resolution: {integrity: sha512-V1dOD8XYfstOKCnXbWyEJIrhTBMwSyNjv271L1Jlx9ExpNlCSuqOs3OdWrGJ0V544zXufKbcYabi/o+gK8lyfQ==}
vue-component-type-helpers@3.1.5:
resolution: {integrity: sha512-7V3yJuNWW7/1jxCcI1CswnpDsvs02Qcx/N43LkV+ZqhLj2PKj50slUflHAroNkN4UWiYfzMUUUXiNuv9khmSpQ==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
@@ -8953,6 +9006,8 @@ snapshots:
'@babel/runtime@7.28.4': {}
'@babel/standalone@7.28.5': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -8992,7 +9047,7 @@ snapshots:
'@cacheable/utils@2.0.3': {}
'@comfyorg/comfyui-electron-types@0.4.73-0': {}
'@comfyorg/comfyui-electron-types@0.5.5': {}
'@csstools/color-helpers@5.1.0': {}
@@ -10617,7 +10672,7 @@ snapshots:
storybook: 9.1.6(@testing-library/dom@10.4.1)(prettier@3.6.2)(vite@5.4.19(@types/node@20.14.10)(lightningcss@1.30.1)(terser@5.39.2))
type-fest: 2.19.0
vue: 3.5.13(typescript@5.9.2)
vue-component-type-helpers: 3.1.3
vue-component-type-helpers: 3.1.5
'@swc/helpers@0.5.17':
dependencies:
@@ -10989,7 +11044,7 @@ snapshots:
'@types/react@19.1.9':
dependencies:
csstype: 3.1.3
csstype: 3.2.3
'@types/semver@7.7.0': {}
@@ -11000,7 +11055,7 @@ snapshots:
'@tweenjs/tween.js': 23.1.3
'@types/stats.js': 0.17.3
'@types/webxr': 0.5.20
'@webgpu/types': 0.1.51
'@webgpu/types': 0.1.66
fflate: 0.8.2
meshoptimizer: 0.18.1
@@ -11503,7 +11558,7 @@ snapshots:
dependencies:
vue: 3.5.13(typescript@5.9.2)
'@webgpu/types@0.1.51': {}
'@webgpu/types@0.1.66': {}
'@xstate/fsm@1.6.5': {}
@@ -12168,6 +12223,8 @@ snapshots:
csstype@3.1.3: {}
csstype@3.2.3: {}
data-urls@5.0.0:
dependencies:
whatwg-mimetype: 4.0.0
@@ -12594,7 +12651,7 @@ snapshots:
dependencies:
debug: 3.2.7
is-core-module: 2.16.1
resolve: 1.22.10
resolve: 1.22.11
transitivePeerDependencies:
- supports-color
optional: true
@@ -13740,7 +13797,7 @@ snapshots:
acorn: 8.15.0
eslint-visitor-keys: 3.4.3
espree: 9.6.1
semver: 7.7.2
semver: 7.7.3
jsonc-parser@3.2.0: {}
@@ -13982,6 +14039,10 @@ snapshots:
lz-string@1.5.0: {}
magic-string-ast@1.0.3:
dependencies:
magic-string: 0.30.19
magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -15345,6 +15406,13 @@ snapshots:
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
resolve@1.22.11:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
optional: true
restore-cursor@3.1.0:
dependencies:
onetime: 5.1.2
@@ -15449,6 +15517,8 @@ snapshots:
semver@7.7.2: {}
semver@7.7.3: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -15837,6 +15907,12 @@ snapshots:
tinybench@2.9.0: {}
tinyest-for-wgsl@0.1.3:
dependencies:
tinyest: 0.1.2
tinyest@0.1.2: {}
tinyexec@0.3.2: {}
tinyexec@1.0.1: {}
@@ -15968,6 +16044,13 @@ snapshots:
reflect.getprototypeof: 1.0.10
optional: true
typed-binary@4.3.2: {}
typegpu@0.8.2:
dependencies:
tinyest: 0.1.2
typed-binary: 4.3.2
typescript-eslint@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2):
dependencies:
'@typescript-eslint/eslint-plugin': 8.44.0(@typescript-eslint/parser@8.44.0(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2))(eslint@9.35.0(jiti@2.4.2))(typescript@5.9.2)
@@ -16063,6 +16146,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
unplugin-typegpu@0.8.0(typegpu@0.8.2):
dependencies:
'@babel/standalone': 7.28.5
defu: 6.1.4
estree-walker: 3.0.3
magic-string-ast: 1.0.3
pathe: 2.0.3
picomatch: 4.0.3
tinyest: 0.1.2
tinyest-for-wgsl: 0.1.3
typegpu: 0.8.2
unplugin: 2.3.5
unplugin-vue-components@0.28.0(@babel/parser@7.28.4)(rollup@4.22.4)(vue@3.5.13(typescript@5.9.2)):
dependencies:
'@antfu/utils': 0.7.10
@@ -16343,7 +16439,7 @@ snapshots:
vue-component-type-helpers@3.1.1: {}
vue-component-type-helpers@3.1.3: {}
vue-component-type-helpers@3.1.5: {}
vue-demi@0.14.10(vue@3.5.13(typescript@5.9.2)):
dependencies:

View File

@@ -4,6 +4,7 @@ packages:
catalog:
'@alloc/quick-lru': ^5.2.0
'@comfyorg/comfyui-electron-types': 0.5.5
'@eslint/js': ^9.35.0
'@iconify-json/lucide': ^1.1.178
'@iconify/json': ^2.2.380
@@ -42,6 +43,7 @@ catalog:
'@vue/test-utils': ^2.4.6
'@vueuse/core': ^11.0.0
'@vueuse/integrations': ^13.9.0
'@webgpu/types': ^0.1.66
algoliasearch: ^5.21.0
axios: ^1.8.2
cross-env: ^10.1.0
@@ -82,9 +84,11 @@ catalog:
tailwindcss-primeui: ^0.6.1
tsx: ^4.15.6
tw-animate-css: ^1.3.8
typegpu: ^0.8.2
typescript: ^5.9.2
typescript-eslint: ^8.44.0
unplugin-icons: ^0.22.0
unplugin-typegpu: 0.8.0
unplugin-vue-components: ^0.28.0
vite: ^5.4.19
vite-plugin-dts: ^4.5.4

View File

@@ -1 +1,9 @@
Thanks to OpenArt (https://openart.ai) for providing the sorted-custom-node-map data, captured in September 2024.
Node usage data merged from two sources:
- Mixpanel "app:node_search_result_selected" events (Nov 2025): 1,112 nodes, 46,514 selections
Reflects actual user search behavior - what users choose when searching.
- OpenArt workflow data (Sept 2024): 2,600 nodes, 118,676 uses
Reflects overall popularity - what's used in workflows.
Merge strategy: New data overwrites old for 514 overlapping nodes. Old data
normalized by 2.55x scale factor to match new data. Total: 3,198 nodes.
Search-selected nodes prioritized in ranking.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.5 MiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,41 +1,114 @@
<template>
<div v-if="!workspaceStore.focusMode" class="ml-1 flex gap-x-0.5 pt-1">
<div
v-if="!workspaceStore.focusMode"
class="ml-1 flex gap-x-0.5 pt-1"
@mouseenter="isTopMenuHovered = true"
@mouseleave="isTopMenuHovered = false"
>
<div class="min-w-0 flex-1">
<SubgraphBreadcrumb />
</div>
<div
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-[var(--interface-stroke)] px-2 shadow-interface"
>
<ActionBarButtons />
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div class="mx-1 flex flex-col items-end gap-1">
<div
ref="legacyCommandsContainerRef"
class="[&:not(:has(*>*:not(:empty)))]:hidden"
></div>
<ComfyActionbar />
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
<LoginButton v-else-if="isDesktop" />
class="actionbar-container pointer-events-auto flex h-12 items-center rounded-lg border border-interface-stroke px-2 shadow-interface"
>
<ActionBarButtons />
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
<div
ref="legacyCommandsContainerRef"
class="[&:not(:has(*>*:not(:empty)))]:hidden"
></div>
<ComfyActionbar />
<IconButton
v-tooltip.bottom="cancelJobTooltipConfig"
type="transparent"
size="sm"
class="mr-2 bg-destructive-background text-base-foreground transition-colors duration-200 ease-in-out hover:bg-destructive-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-destructive-background"
:disabled="isExecutionIdle"
:aria-label="t('menu.interrupt')"
@click="cancelCurrentJob"
>
<i class="icon-[lucide--x] size-4" />
</IconButton>
<IconButton
v-tooltip.bottom="queueHistoryTooltipConfig"
type="transparent"
size="sm"
class="queue-history-toggle relative mr-2 transition-colors duration-200 ease-in-out hover:bg-secondary-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background"
:class="queueHistoryButtonBackgroundClass"
:aria-pressed="isQueueOverlayExpanded"
:aria-label="
t('sideToolbar.queueProgressOverlay.expandCollapsedQueue')
"
@click="toggleQueueOverlay"
>
<i
class="icon-[lucide--history] block size-4 text-muted-foreground"
/>
<span
v-if="queuedCount > 0"
class="absolute -top-1 -right-1 min-w-[16px] rounded-full bg-primary-background py-0.25 text-[10px] font-medium leading-[14px] text-white"
>
{{ queuedCount }}
</span>
</IconButton>
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
<LoginButton v-else-if="isDesktop" />
</div>
<QueueProgressOverlay
v-model:expanded="isQueueOverlayExpanded"
:menu-hovered="isTopMenuHovered"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
import IconButton from '@/components/button/IconButton.vue'
import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { buildTooltipConfig } from '@/composables/useTooltipConfig'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useQueueStore } from '@/stores/queueStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { isElectron } from '@/utils/envUtil'
const workspaceStore = useWorkspaceStore()
const executionStore = useExecutionStore()
const commandStore = useCommandStore()
const { isLoggedIn } = useCurrentUser()
const isDesktop = isElectron()
const { t } = useI18n()
const isQueueOverlayExpanded = ref(false)
const queueStore = useQueueStore()
const isTopMenuHovered = ref(false)
const queuedCount = computed(() => queueStore.pendingTasks.length)
const queueHistoryTooltipConfig = computed(() =>
buildTooltipConfig(t('sideToolbar.queueProgressOverlay.viewJobHistory'))
)
const queueHistoryButtonBackgroundClass = computed(() =>
isQueueOverlayExpanded.value
? 'bg-secondary-background-selected'
: 'bg-secondary-background'
)
const cancelJobTooltipConfig = computed(() =>
buildTooltipConfig(t('menu.interrupt'))
)
const { isIdle: isExecutionIdle } = storeToRefs(executionStore)
// Maintain support for legacy topbar elements attached by custom scripts
const legacyCommandsContainerRef = ref<HTMLElement>()
@@ -45,6 +118,15 @@ onMounted(() => {
legacyCommandsContainerRef.value.appendChild(app.menu.element)
}
})
const toggleQueueOverlay = () => {
isQueueOverlayExpanded.value = !isQueueOverlayExpanded.value
}
const cancelCurrentJob = async () => {
if (isExecutionIdle.value) return
await commandStore.execute('Comfy.Interrupt')
}
</script>
<style scoped>

View File

@@ -10,7 +10,7 @@
</div>
<Panel
class="pointer-events-auto z-1000"
class="pointer-events-auto z-1010"
:style="style"
:class="panelClass"
:pt="{
@@ -257,9 +257,10 @@ watch(isDragging, (dragging) => {
})
const actionbarClass = computed(() =>
cn(
'w-[265px] border-dashed border-blue-500 opacity-80',
'w-[200px] border-dashed border-blue-500 opacity-80',
'm-1.5 flex items-center justify-center self-stretch',
'rounded-md before:w-50 before:-ml-50 before:h-full',
'pointer-events-auto',
isMouseOverDropZone.value &&
'border-[3px] opacity-100 scale-105 shadow-[0_0_20px] shadow-blue-500'
)

View File

@@ -2,9 +2,7 @@
<div class="queue-button-group flex">
<SplitButton
v-tooltip.bottom="{
value: workspaceStore.shiftDown
? $t('menu.runWorkflowFront')
: $t('menu.runWorkflow'),
value: queueButtonTooltip,
showDelay: 600
}"
class="comfyui-queue-button"
@@ -16,16 +14,7 @@
@click="queuePrompt"
>
<template #icon>
<i v-if="workspaceStore.shiftDown" class="icon-[lucide--list-start]" />
<i v-else-if="queueMode === 'disabled'" class="icon-[lucide--play]" />
<i
v-else-if="queueMode === 'instant'"
class="icon-[lucide--fast-forward]"
/>
<i
v-else-if="queueMode === 'change'"
class="icon-[lucide--step-forward]"
/>
<i :class="iconClass" />
</template>
<template #item="{ item }">
<Button
@@ -42,46 +31,12 @@
</template>
</SplitButton>
<BatchCountEdit />
<ButtonGroup class="execution-actions flex flex-nowrap">
<Button
v-tooltip.bottom="{
value: $t('menu.interrupt'),
showDelay: 600
}"
icon="pi pi-times"
:severity="executingPrompt ? 'danger' : 'secondary'"
:disabled="!executingPrompt"
text
:aria-label="$t('menu.interrupt')"
@click="() => commandStore.execute('Comfy.Interrupt')"
/>
<Button
v-tooltip.bottom="{
value: $t('sideToolbar.queueTab.clearPendingTasks'),
showDelay: 600
}"
icon="pi pi-stop"
:severity="hasPendingTasks ? 'danger' : 'secondary'"
:disabled="!hasPendingTasks"
text
:aria-label="$t('sideToolbar.queueTab.clearPendingTasks')"
@click="
() => {
if (queueCountStore.count.value > 1) {
commandStore.execute('Comfy.ClearPendingTasks')
}
queueMode = 'disabled'
}
"
/>
</ButtonGroup>
</div>
</template>
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import Button from 'primevue/button'
import ButtonGroup from 'primevue/buttongroup'
import type { MenuItem } from 'primevue/menuitem'
import SplitButton from 'primevue/splitbutton'
import { computed } from 'vue'
@@ -89,19 +44,23 @@ import { useI18n } from 'vue-i18n'
import { isCloud } from '@/platform/distribution/types'
import { useTelemetry } from '@/platform/telemetry'
import { app } from '@/scripts/app'
import { useCommandStore } from '@/stores/commandStore'
import {
useQueuePendingTaskCountStore,
useQueueSettingsStore
} from '@/stores/queueStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useQueueSettingsStore } from '@/stores/queueStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
import BatchCountEdit from '../BatchCountEdit.vue'
const workspaceStore = useWorkspaceStore()
const queueCountStore = storeToRefs(useQueuePendingTaskCountStore())
const { mode: queueMode, batchCount } = storeToRefs(useQueueSettingsStore())
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()
const queueModeMenuItemLookup = computed(() => {
const items: Record<string, MenuItem> = {
@@ -152,10 +111,34 @@ const queueModeMenuItems = computed(() =>
Object.values(queueModeMenuItemLookup.value)
)
const executingPrompt = computed(() => !!queueCountStore.count.value)
const hasPendingTasks = computed(
() => queueCountStore.count.value > 1 || queueMode.value !== 'disabled'
)
const iconClass = computed(() => {
if (hasMissingNodes.value) {
return 'icon-[lucide--triangle-alert]'
}
if (workspaceStore.shiftDown) {
return 'icon-[lucide--list-start]'
}
if (queueMode.value === 'disabled') {
return 'icon-[lucide--play]'
}
if (queueMode.value === 'instant') {
return 'icon-[lucide--fast-forward]'
}
if (queueMode.value === 'change') {
return 'icon-[lucide--step-forward]'
}
return 'icon-[lucide--play]'
})
const queueButtonTooltip = computed(() => {
if (hasMissingNodes.value) {
return t('menu.runWorkflowDisabled')
}
if (workspaceStore.shiftDown) {
return t('menu.runWorkflowFront')
}
return t('menu.runWorkflow')
})
const commandStore = useCommandStore()
const queuePrompt = async (e: Event) => {

View File

@@ -19,7 +19,7 @@ import type { Ref } from 'vue'
import { onMounted, onUnmounted, ref } from 'vue'
import type { useTerminal } from '@/composables/bottomPanelTabs/useTerminal'
import type { LogEntry, LogsWsMessage, TerminalSize } from '@/schemas/apiSchema'
import type { LogEntry, LogsWsMessage } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { useExecutionStore } from '@/stores/executionStore'
@@ -32,27 +32,22 @@ const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement | undefined>
) => {
// `autoCols` is false because we don't want the progress bar in the terminal
// to render incorrectly as the progress bar is rendered based on the
// server's terminal size.
// Apply a min cols of 80 for colab environments
// Auto-size terminal to fill container width.
// minCols: 80 ensures minimum width for colab environments.
// See https://github.com/comfyanonymous/ComfyUI/issues/6396
useAutoSize({ root, autoRows: true, autoCols: false, minCols: 80 })
useAutoSize({ root, autoRows: true, autoCols: true, minCols: 80 })
const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
if (size) {
terminal.resize(size.cols, terminal.rows)
}
const update = (entries: Array<LogEntry>) => {
terminal.write(entries.map((e) => e.m).join(''))
}
const logReceived = (e: CustomEvent<LogsWsMessage>) => {
update(e.detail.entries, e.detail.size)
update(e.detail.entries)
}
const loadLogEntries = async () => {
const logs = await api.getRawLogs()
update(logs.entries, logs.size)
update(logs.entries)
}
const watchLogs = async () => {

View File

@@ -2,7 +2,7 @@
<a
ref="wrapperRef"
v-tooltip.bottom="{
value: item.label,
value: tooltipText,
showDelay: 512
}"
draggable="false"
@@ -16,6 +16,10 @@
}"
@click="handleClick"
>
<i
v-if="hasMissingNodes && isRoot"
class="icon-[lucide--triangle-alert] text-warning-background"
/>
<span class="p-breadcrumb-item-label px-2">{{ item.label }}</span>
<Tag v-if="item.isBlueprint" :value="'Blueprint'" severity="primary" />
<i v-if="isActive" class="pi pi-angle-down text-[10px]"></i>
@@ -60,10 +64,13 @@ import {
ComfyWorkflow,
useWorkflowStore
} from '@/platform/workflow/management/stores/workflowStore'
import { app } from '@/scripts/app'
import { useDialogService } from '@/services/dialogService'
import { useCommandStore } from '@/stores/commandStore'
import { useNodeDefStore } from '@/stores/nodeDefStore'
import { useSubgraphNavigationStore } from '@/stores/subgraphNavigationStore'
import { appendJsonExt } from '@/utils/formatUtil'
import { graphHasMissingNodes } from '@/workbench/extensions/manager/utils/graphHasMissingNodes'
interface Props {
item: MenuItem
@@ -74,6 +81,11 @@ const props = withDefaults(defineProps<Props>(), {
isActive: false
})
const nodeDefStore = useNodeDefStore()
const hasMissingNodes = computed(() =>
graphHasMissingNodes(app.graph, nodeDefStore.nodeDefsByName)
)
const { t } = useI18n()
const menu = ref<InstanceType<typeof Menu> & MenuState>()
const dialogService = useDialogService()
@@ -115,6 +127,14 @@ const rename = async (
}
const isRoot = props.item.key === 'root'
const tooltipText = computed(() => {
if (hasMissingNodes.value && isRoot) {
return t('breadcrumbsMenu.missingNodesWarning')
}
return props.item.label
})
const menuItems = computed<MenuItem[]>(() => {
return [
{

View File

@@ -24,7 +24,7 @@ import {
import { cn } from '@/utils/tailwindUtil'
interface IconButtonProps extends BaseButtonProps {
onClick: (event: Event) => void
onClick?: (event: MouseEvent) => void
}
defineOptions({

View File

@@ -47,7 +47,7 @@ const {
} = defineProps<IconTextButtonProps>()
const buttonStyle = computed(() => {
const baseClasses = `${getBaseButtonClasses()} justify-start! gap-2`
const baseClasses = `${getBaseButtonClasses()} justify-start gap-2`
const sizeClasses = getButtonSizeClasses(size)
const typeClasses = border
? getBorderButtonTypeClasses(type)

View File

@@ -68,4 +68,8 @@ const toggle = (event: Event) => {
const hide = () => {
popover.value?.hide()
}
defineExpose({
hide
})
</script>

View File

@@ -11,7 +11,7 @@ import { cn } from '@/utils/tailwindUtil'
const { label, variant = 'dark' } = defineProps<{
label: string
variant?: 'dark' | 'light'
variant?: 'dark' | 'light' | 'gray'
}>()
const baseClasses =
@@ -19,7 +19,10 @@ const baseClasses =
const variantStyles = {
dark: 'bg-zinc-500/40 text-white/90',
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground')
light: cn('backdrop-blur-[2px] bg-base-background/50 text-base-foreground'),
gray: cn(
'backdrop-blur-[2px] bg-modal-card-tag-background text-base-foreground'
)
}
const chipClasses = computed(() => {

View File

@@ -9,29 +9,31 @@
<div class="font-medium">
{{ col.header }}
</div>
<div>{{ formatValue(systemInfo[col.field], col.field) }}</div>
<div>{{ getDisplayValue(col) }}</div>
</template>
</div>
</div>
<Divider />
<template v-if="hasDevices">
<Divider />
<div>
<h2 class="mb-4 text-2xl font-semibold">
{{ $t('g.devices') }}
</h2>
<TabView v-if="props.stats.devices.length > 1">
<TabPanel
v-for="device in props.stats.devices"
:key="device.index"
:header="device.name"
:value="device.index"
>
<DeviceInfo :device="device" />
</TabPanel>
</TabView>
<DeviceInfo v-else :device="props.stats.devices[0]" />
</div>
<div>
<h2 class="mb-4 text-2xl font-semibold">
{{ $t('g.devices') }}
</h2>
<TabView v-if="props.stats.devices.length > 1">
<TabPanel
v-for="device in props.stats.devices"
:key="device.index"
:header="device.name"
:value="device.index"
>
<DeviceInfo :device="device" />
</TabPanel>
</TabView>
<DeviceInfo v-else :device="props.stats.devices[0]" />
</div>
</template>
</div>
</template>
@@ -42,8 +44,9 @@ import TabView from 'primevue/tabview'
import { computed } from 'vue'
import DeviceInfo from '@/components/common/DeviceInfo.vue'
import { isCloud } from '@/platform/distribution/types'
import type { SystemStats } from '@/schemas/apiSchema'
import { formatSize } from '@/utils/formatUtil'
import { formatCommitHash, formatSize } from '@/utils/formatUtil'
const props = defineProps<{
stats: SystemStats
@@ -54,20 +57,53 @@ const systemInfo = computed(() => ({
argv: props.stats.system.argv.join(' ')
}))
const systemColumns: { field: keyof SystemStats['system']; header: string }[] =
[
{ field: 'os', header: 'OS' },
{ field: 'python_version', header: 'Python Version' },
{ field: 'embedded_python', header: 'Embedded Python' },
{ field: 'pytorch_version', header: 'Pytorch Version' },
{ field: 'argv', header: 'Arguments' },
{ field: 'ram_total', header: 'RAM Total' },
{ field: 'ram_free', header: 'RAM Free' }
]
const hasDevices = computed(() => props.stats.devices.length > 0)
const formatValue = (value: any, field: string) => {
if (['ram_total', 'ram_free'].includes(field)) {
return formatSize(value)
type SystemInfoKey = keyof SystemStats['system']
type ColumnDef = {
field: SystemInfoKey
header: string
format?: (value: string) => string
formatNumber?: (value: number) => string
}
/** Columns for local distribution */
const localColumns: ColumnDef[] = [
{ field: 'os', header: 'OS' },
{ field: 'python_version', header: 'Python Version' },
{ field: 'embedded_python', header: 'Embedded Python' },
{ field: 'pytorch_version', header: 'Pytorch Version' },
{ field: 'argv', header: 'Arguments' },
{ field: 'ram_total', header: 'RAM Total', formatNumber: formatSize },
{ field: 'ram_free', header: 'RAM Free', formatNumber: formatSize }
]
/** Columns for cloud distribution */
const cloudColumns: ColumnDef[] = [
{ field: 'cloud_version', header: 'Cloud Version' },
{
field: 'comfyui_version',
header: 'ComfyUI Version',
format: formatCommitHash
},
{
field: 'comfyui_frontend_version',
header: 'Frontend Version',
format: formatCommitHash
},
{ field: 'workflow_templates_version', header: 'Templates Version' }
]
const systemColumns = computed(() => (isCloud ? cloudColumns : localColumns))
const getDisplayValue = (column: ColumnDef) => {
const value = systemInfo.value[column.field]
if (column.formatNumber && typeof value === 'number') {
return column.formatNumber(value)
}
if (column.format && typeof value === 'string') {
return column.format(value)
}
return value
}

View File

@@ -92,7 +92,7 @@
class="w-62.5"
>
<template #icon>
<i class="icon-[lucide--arrow-up-down]" />
<i class="icon-[lucide--arrow-up-down] text-muted-foreground" />
</template>
</SingleSelect>
</div>

View File

@@ -1,6 +1,7 @@
<template>
<div
class="flex w-[490px] flex-col border-t-1 border-b-1 border-border-default"
class="flex w-[490px] flex-col border-t-1 border-border-default"
:class="isCloud ? 'border-b-1' : ''"
>
<div class="flex h-full w-full flex-col gap-4 p-4">
<!-- Description -->

View File

@@ -4,6 +4,7 @@
<InputText
ref="inputRef"
v-model="inputValue"
:placeholder
autofocus
@keyup.enter="onConfirm"
@focus="selectAllText"
@@ -28,6 +29,7 @@ const props = defineProps<{
message: string
defaultValue: string
onConfirm: (value: string) => void
placeholder?: string
}>()
const inputValue = ref<string>(props.defaultValue)

View File

@@ -125,7 +125,7 @@ import { useCopy } from '@/composables/useCopy'
import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'
import { usePaste } from '@/composables/usePaste'
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
import { i18n, t } from '@/i18n'
import { mergeCustomNodesI18n, t } from '@/i18n'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings'
import { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings'
@@ -384,9 +384,7 @@ useEventListener(
const loadCustomNodesI18n = async () => {
try {
const i18nData = await api.getCustomNodesI18n()
Object.entries(i18nData).forEach(([locale, message]) => {
i18n.global.mergeLocaleMessage(locale, message)
})
mergeCustomNodesI18n(i18nData)
} catch (error) {
console.error('Failed to load custom nodes i18n', error)
}

View File

@@ -17,7 +17,7 @@
root: ({ props }: MultiSelectPassThroughMethodOptions) => ({
class: cn(
'h-10 relative inline-flex cursor-pointer select-none',
'rounded-lg bg-base-background text-base-foreground',
'rounded-lg bg-secondary-background text-base-foreground',
'transition-all duration-200 ease-in-out',
'border-[2.5px] border-solid',
selectedCount > 0
@@ -83,7 +83,7 @@
role="combobox"
:aria-expanded="false"
aria-haspopup="listbox"
tabindex="0"
:tabindex="0"
>
<template
v-if="showSearchBox || showSelectedCount || showClearButton"
@@ -127,7 +127,7 @@
<!-- Trigger value (keep text scale identical) -->
<template #value>
<span class="text-sm text-muted-foreground">
<span class="text-sm">
{{ label }}
</span>
<span
@@ -140,7 +140,7 @@
<!-- Chevron size identical to current -->
<template #dropdownicon>
<i class="icon-[lucide--chevron-down] text-lg text-neutral-400" />
<i class="icon-[lucide--chevron-down] text-muted-foreground" />
</template>
<!-- Custom option row: square checkbox + label (unchanged layout/colors) -->

View File

@@ -1,6 +1,6 @@
<template>
<div :class="wrapperStyle" @click="focusInput">
<i class="icon-[lucide--search] text-muted" />
<i class="icon-[lucide--search] text-muted-foreground" />
<InputText
ref="input"
v-model="internalSearchQuery"
@@ -73,7 +73,7 @@ onMounted(() => autofocus && focusInput())
const wrapperStyle = computed(() => {
const baseClasses =
'relative flex w-full items-center gap-2 bg-base-background cursor-text'
'relative flex w-full items-center gap-2 bg-secondary-background cursor-text'
if (showBorder) {
return cn(

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