Compare commits

..

1 Commits

Author SHA1 Message Date
Chenlei Hu
47e3330baa [Branding] Match menu logo with branding spec 2025-05-07 13:48:29 -04:00
27 changed files with 238 additions and 633 deletions

443
README.md
View File

@@ -67,449 +67,6 @@ The development of successive minor versions overlaps. For example, while versio
| 3 | Mar 15-21 | Released | Feature Freeze | Development | 1.1.7 through 1.1.13 (daily)<br>1.2.0 through 1.2.6 (daily) |
| 4 | Mar 22-28 | - | Released | Feature Freeze | 1.2.7 through 1.2.13 (daily)<br>1.3.0 through 1.3.6 (daily) |
## Release Summary
### Major features
<details id='feature-native-translation'>
<summary>v1.5: Native translation (i18n)</summary>
ComfyUI now includes built-in translation support, replacing the need for third-party translation extensions. Select your language
in `Comfy > Locale > Language` to translate the interface into English, Chinese (Simplified), Russian, Japanese, or Korean. This native
implementation offers better performance, reliability, and maintainability compared to previous solutions.<br>
More details available here: https://blog.comfy.org/p/native-localization-support-i18n
</details>
<details id='feature-mask-editor'>
<summary>v1.4: New mask editor</summary>
https://github.com/Comfy-Org/ComfyUI_frontend/pull/1284 implements a new mask editor.
![image](https://github.com/user-attachments/assets/f0ea6ee5-00ee-4e5d-a09c-6938e86a1f17)
</details>
<details id='feature-integrated-server-terminal'>
<summary>v1.3.22: Integrated server terminal</summary>
Press Ctrl + ` to toggle integrated terminal.
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details id='feature-keybinding-customization'>
<summary>v1.3.7: Keybinding customization</summary>
## Basic UI
![image](https://github.com/user-attachments/assets/c84a1609-3880-48e0-a746-011f36beda68)
## Reset button
![image](https://github.com/user-attachments/assets/4d2922da-bb4f-4f90-8017-a8e4a0db07c7)
## Edit Keybinding
![image](https://github.com/user-attachments/assets/77626b7a-cb46-48f8-9465-e03120aac66a)
![image](https://github.com/user-attachments/assets/79131a4e-75c6-4715-bd11-c6aaed887779)
[rec.webm](https://github.com/user-attachments/assets/a3984ed9-eb28-4d47-86c0-7fc3efc2b5d0)
</details>
<details id='feature-node-library-sidebar'>
<summary>v1.2.4: Node library sidebar tab</summary>
#### Drag & Drop
https://github.com/user-attachments/assets/853e20b7-bc0e-49c9-bbce-a2ba7566f92f
#### Filter
https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
</details>
<details id='feature-queue-sidebar'>
<summary>v1.2.0: Queue/History sidebar tab</summary>
https://github.com/user-attachments/assets/86e264fe-4d26-4f07-aa9a-83bdd2d02b8f
</details>
<details id='feature-node-search'>
<summary>v1.1.0: Node search box</summary>
#### Fuzzy search & Node preview
![image](https://github.com/user-attachments/assets/94733e32-ea4e-4a9c-b321-c1a05db48709)
#### Release link with shift
https://github.com/user-attachments/assets/a1b2b5c3-10d1-4256-b620-345de6858f25
</details>
### QoL changes
<details id='feature-nested-group'>
<summary>v1.3.32: **Litegraph** Nested group</summary>
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
</details>
<details id='feature-group-selection'>
<summary>v1.3.24: **Litegraph** Group selection</summary>
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
</details>
<details id='feature-toggle-link-visibility'>
<summary>v1.3.6: **Litegraph** Toggle link visibility</summary>
[rec.webm](https://github.com/user-attachments/assets/34e460ac-fbbc-44ef-bfbb-99a84c2ae2be)
</details>
<details id='feature-auto-widget-conversion'>
<summary>v1.3.4: **Litegraph** Auto widget to input conversion</summary>
Dropping a link of correct type on node widget will automatically convert the widget to input.
[rec.webm](https://github.com/user-attachments/assets/15cea0b0-b225-4bec-af50-2cdb16dc46bf)
</details>
<details id='feature-pan-mode'>
<summary>v1.3.4: **Litegraph** Canvas pan mode</summary>
The canvas becomes readonly in pan mode. Pan mode is activated by clicking the pan mode button on the canvas menu
or by holding the space key.
[rec.webm](https://github.com/user-attachments/assets/c7872532-a2ac-44c1-9e7d-9e03b5d1a80b)
</details>
<details id='feature-shift-drag-link-creation'>
<summary>v1.3.1: **Litegraph** Shift drag link to create a new link</summary>
[rec.webm](https://github.com/user-attachments/assets/7e73aaf9-79e2-4c3c-a26a-911cba3b85e4)
</details>
<details id='feature-optional-input-donuts'>
<summary>v1.2.62: **Litegraph** Show optional input slots as donuts</summary>
![GYEIRidb0AYGO-v](https://github.com/user-attachments/assets/e6cde0b6-654b-4afd-a117-133657a410b1)
</details>
<details id='feature-group-title-edit'>
<summary>v1.2.44: **Litegraph** Double click group title to edit</summary>
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
</details>
<details id='feature-group-selection-shortcut'>
<summary>v1.2.39: **Litegraph** Group selected nodes with Ctrl + G</summary>
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
</details>
<details id='feature-node-title-edit'>
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
</details>
<details id='feature-drag-multi-link'>
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
</details>
<details id='feature-auto-connect-link'>
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
#### Before
https://github.com/user-attachments/assets/c253f778-82d5-4e6f-aec0-ea2ccf421651
#### After
https://github.com/user-attachments/assets/b6360ac0-f0d2-447c-9daa-8a2e20c0dc1d
</details>
<details id='feature-hide-text-overflow'>
<summary>v1.1.8: **Litegraph** hides text overflow on widget value</summary>
https://github.com/user-attachments/assets/5696a89d-4a47-4fcc-9e8c-71e1264943f2
</details>
### Developer APIs
<details>
<summary>v1.6.13: prompt/confirm/alert replacements for ComfyUI desktop</summary>
Several browser-only APIs are not available in ComfyUI desktop's electron environment.
- `window.prompt`
- `window.confirm`
- `window.alert`
Please use the following APIs as replacements.
```js
// window.prompt
window['app'].extensionManager.dialog
.prompt({
title: 'Test Prompt',
message: 'Test Prompt Message'
})
.then((value: string) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/c73f74d0-9bb4-4555-8d56-83f1be4a1d7e)
```js
// window.confirm
window['app'].extensionManager.dialog
.confirm({
title: 'Test Confirm',
message: 'Test Confirm Message'
})
.then((value: boolean) => {
// Do something with the value user entered
})
```
![image](https://github.com/user-attachments/assets/8dec7a42-7443-4245-85be-ceefb1116e96)
```js
// window.alert
window['app'].extensionManager.toast
.addAlert("Test Alert")
```
![image](https://github.com/user-attachments/assets/9b18bdca-76ef-4432-95de-5cd2369684f2)
</details>
<details>
<summary>v1.3.34: Register about panel badges</summary>
```js
app.registerExtension({
name: 'TestExtension1',
aboutPageBadges: [
{
label: 'Test Badge',
url: 'https://example.com',
icon: 'pi pi-box'
}
]
})
```
![image](https://github.com/user-attachments/assets/099e77ee-16ad-4141-b2fc-5e9d5075188b)
</details>
<details id='extension-api-bottom-panel-tabs'>
<summary>v1.3.22: Register bottom panel tabs</summary>
```js
app.registerExtension({
name: 'TestExtension',
bottomPanelTabs: [
{
id: 'TestTab',
title: 'Test Tab',
type: 'custom',
render: (el) => {
el.innerHTML = '<div>Custom tab</div>'
}
}
]
})
```
![image](https://github.com/user-attachments/assets/2114f8b8-2f55-414b-b027-78e61c870b64)
</details>
<details id='extension-api-settings'>
<summary>v1.3.22: New settings API</summary>
Legacy settings API.
```js
// Register a new setting
app.ui.settings.addSetting({
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!'
})
// Get the value of a setting
const value = app.ui.settings.getSettingValue('TestSetting')
// Set the value of a setting
app.ui.settings.setSettingValue('TestSetting', 'Hello, universe!')
```
New settings API.
```js
// Register a new setting
app.registerExtension({
name: 'TestExtension1',
settings: [
{
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!'
}
]
})
// Get the value of a setting
const value = app.extensionManager.setting.get('TestSetting')
// Set the value of a setting
app.extensionManager.setting.set('TestSetting', 'Hello, universe!')
```
</details>
<details id='extension-api-commands-keybindings'>
<summary>v1.3.7: Register commands and keybindings</summary>
Extensions can call the following API to register commands and keybindings. Do
note that keybindings defined in core cannot be overwritten, and some keybindings
are reserved by the browser.
```js
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'TestCommand',
function: () => {
alert('TestCommand')
}
}
],
keybindings: [
{
combo: { key: 'k' },
commandId: 'TestCommand'
}
]
})
```
</details>
<details id='extension-api-topbar-menu'>
<summary>v1.3.1: Extension API to register custom topbar menu items</summary>
Extensions can call the following API to register custom topbar menu items.
```js
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'foo-id',
label: 'foo',
function: () => {
alert(1)
}
}
],
menuCommands: [
{
path: ['ext', 'ext2'],
commands: ['foo-id']
}
]
})
```
![image](https://github.com/user-attachments/assets/ae7b082f-7ce9-4549-a446-4563567102fe)
</details>
<details id='extension-api-toast'>
<summary>v1.2.27: Extension API to add toast message</summary>i
Extensions can call the following API to add toast messages.
```js
app.extensionManager.toast.add({
severity: 'info',
summary: 'Loaded!',
detail: 'Extension loaded!',
life: 3000
})
```
Documentation of all supported options can be found here: <https://primevue.org/toast/#api.toast.interfaces.ToastMessageOptions>
![image](https://github.com/user-attachments/assets/de02cd7e-cd81-43d1-a0b0-bccef92ff487)
</details>
<details id='extension-api-sidebar-tab'>
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
Extensions now can call the following API to register a sidebar tab.
```js
app.extensionManager.registerSidebarTab({
id: "search",
icon: "pi pi-search",
title: "search",
tooltip: "search",
type: "custom",
render: (el) => {
el.innerHTML = "<div>Custom search tab</div>";
},
});
```
The list of supported icons can be found here: <https://primevue.org/icons/#list>
We will support custom icons later.
![image](https://github.com/user-attachments/assets/7bff028a-bf91-4cab-bf97-55c243b3f5e0)
</details>
<details id='extension-api-selection-toolbox'>
<summary>v1.10.9: Selection Toolbox API</summary>
Extensions can register commands that appear in the selection toolbox when specific items are selected on the canvas.
```js
app.registerExtension({
name: 'TestExtension1',
commands: [
{
id: 'test.selection.command',
label: 'Test Command',
icon: 'pi pi-star',
function: () => {
// Command logic here
}
}
],
// Return an array of command IDs to show in the selection toolbox
// when an item is selected
getSelectionToolboxCommands: (selectedItem) => ['test.selection.command']
})
```
The selection toolbox will display the command button when items are selected:
![Image](https://github.com/user-attachments/assets/28d91267-c0a9-4bd5-a7c4-36e8ec44c9bd)
</details>
## Contributing
We're building this frontend together and would love your help — no matter how you'd like to pitch in! You don't need to write code to make a difference.

12
package-lock.json generated
View File

@@ -1,18 +1,18 @@
{
"name": "@comfyorg/comfyui-frontend",
"version": "1.19.5",
"version": "1.19.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@comfyorg/comfyui-frontend",
"version": "1.19.5",
"version": "1.19.4",
"license": "GPL-3.0-only",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
"@comfyorg/litegraph": "^0.15.6",
"@comfyorg/litegraph": "^0.15.3",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",
@@ -482,9 +482,9 @@
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
"version": "0.15.6",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.6.tgz",
"integrity": "sha512-ZOHBctjY4pu7FUQibO1z8HD+1JKhNy/tKCMKds9CJK3XVbEcA1+GiRfvp5lAhpkxJStmvD1WLcDgkb/uMAWKWQ==",
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.15.3.tgz",
"integrity": "sha512-YpHhNgV6a1bsi/NCuK/u5m9bgI3HK5qLXCLR2aKUJ4MPVfdILodqcbkKVBOb4li6KKUyg7plQ9GJpml6F2/fYQ==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {

View File

@@ -1,7 +1,7 @@
{
"name": "@comfyorg/comfyui-frontend",
"private": true,
"version": "1.19.5",
"version": "1.19.4",
"type": "module",
"repository": "https://github.com/Comfy-Org/ComfyUI_frontend",
"homepage": "https://comfy.org",
@@ -72,7 +72,7 @@
"@alloc/quick-lru": "^5.2.0",
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.4.43",
"@comfyorg/litegraph": "^0.15.6",
"@comfyorg/litegraph": "^0.15.3",
"@primevue/forms": "^4.2.5",
"@primevue/themes": "^4.2.5",
"@sentry/vue": "^8.48.0",

View File

@@ -1,9 +0,0 @@
<svg width="520" height="520" viewBox="0 0 520 520" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_227_285" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="520" height="520">
<path d="M0 184.335C0 119.812 0 87.5502 12.5571 62.9055C23.6026 41.2274 41.2274 23.6026 62.9055 12.5571C87.5502 0 119.812 0 184.335 0H335.665C400.188 0 432.45 0 457.094 12.5571C478.773 23.6026 496.397 41.2274 507.443 62.9055C520 87.5502 520 119.812 520 184.335V335.665C520 400.188 520 432.45 507.443 457.094C496.397 478.773 478.773 496.397 457.094 507.443C432.45 520 400.188 520 335.665 520H184.335C119.812 520 87.5502 520 62.9055 507.443C41.2274 496.397 23.6026 478.773 12.5571 457.094C0 432.45 0 400.188 0 335.665V184.335Z" fill="#FFFFFF"/>
</mask>
<g mask="url(#mask0_227_285)">
<rect y="0.751831" width="520" height="520" fill="#000000"/>
<path d="M176.484 428.831C168.649 428.831 162.327 425.919 158.204 420.412C153.966 414.755 152.861 406.857 155.171 398.749L164.447 366.178C165.187 363.585 164.672 360.794 163.059 358.636C161.446 356.483 158.921 355.216 156.241 355.216H129.571C121.731 355.216 115.409 352.308 111.289 346.802C107.051 341.14 105.946 333.242 108.258 325.134L140.124 213.748L143.642 201.51C148.371 184.904 165.62 171.407 182.097 171.407H214.009C217.817 171.407 221.167 168.868 222.215 165.183L232.769 128.135C237.494 111.545 254.742 98.048 271.219 98.048L339.468 97.9264L389.431 97.9221C397.268 97.9221 403.59 100.831 407.711 106.337C411.949 111.994 413.054 119.892 410.744 128L396.457 178.164C391.734 194.75 374.485 208.242 358.009 208.242L289.607 208.372H257.706C253.902 208.372 250.557 210.907 249.502 214.588L222.903 307.495C222.159 310.093 222.673 312.892 224.291 315.049C225.904 317.202 228.428 318.469 231.107 318.469C231.113 318.469 276.307 318.381 276.307 318.381H326.122C333.959 318.381 340.281 321.29 344.402 326.796C348.639 332.457 349.744 340.355 347.433 348.463L333.146 398.619C328.423 415.209 311.174 428.701 294.698 428.701L226.299 428.831H176.484Z" fill="#FFFFFF"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -27,6 +27,9 @@
--content-fg: #000;
--content-hover-bg: #adadad;
--content-hover-fg: #000;
/* Colors for Comfy branding. */
--comfy-yellow: #f0ff41;
--comfy-blue: #172dd7;
}
@media (prefers-color-scheme: dark) {

View File

@@ -75,7 +75,7 @@
</template>
<script setup lang="ts">
import { LGraphNode } from '@comfyorg/litegraph'
import { IWidget, LGraphNode } from '@comfyorg/litegraph'
import { Tooltip } from 'primevue'
import Button from 'primevue/button'
@@ -101,13 +101,13 @@ const emit = defineEmits<{
const resizeNodeMatchOutput = () => {
console.log('resizeNodeMatchOutput')
const outputWidth = node.widgets?.find((w) => w.name === 'width')
const outputHeight = node.widgets?.find((w) => w.name === 'height')
const outputWidth = node.widgets?.find((w: IWidget) => w.name === 'width')
const outputHeight = node.widgets?.find((w: IWidget) => w.name === 'height')
if (outputWidth && outputHeight && outputHeight.value && outputWidth.value) {
const [oldWidth, oldHeight] = node.size
const scene = node.widgets?.find((w) => w.name === 'image')
const scene = node.widgets?.find((w: IWidget) => w.name === 'image')
const sceneHeight = scene?.computedHeight

View File

@@ -0,0 +1,49 @@
<template>
<svg
width="520"
height="520"
viewBox="0 0 520 520"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<mask
id="mask0_227_285"
style="mask-type: alpha"
maskUnits="userSpaceOnUse"
x="0"
y="0"
width="520"
height="520"
>
<path
d="M0 184.335C0 119.812 0 87.5502 12.5571 62.9055C23.6026 41.2274 41.2274 23.6026 62.9055 12.5571C87.5502 0 119.812 0 184.335 0H335.665C400.188 0 432.45 0 457.094 12.5571C478.773 23.6026 496.397 41.2274 507.443 62.9055C520 87.5502 520 119.812 520 184.335V335.665C520 400.188 520 432.45 507.443 457.094C496.397 478.773 478.773 496.397 457.094 507.443C432.45 520 400.188 520 335.665 520H184.335C119.812 520 87.5502 520 62.9055 507.443C41.2274 496.397 23.6026 478.773 12.5571 457.094C0 432.45 0 400.188 0 335.665V184.335Z"
fill="#EEFF30"
/>
</mask>
<g mask="url(#mask0_227_285)">
<rect y="0.751831" width="520" height="520" fill="#172DD7" />
<path
d="M176.484 428.831C168.649 428.831 162.327 425.919 158.204 420.412C153.966 414.755 152.861 406.857 155.171 398.749L164.447 366.178C165.187 363.585 164.672 360.794 163.059 358.636C161.446 356.483 158.921 355.216 156.241 355.216H129.571C121.731 355.216 115.409 352.308 111.289 346.802C107.051 341.14 105.946 333.242 108.258 325.134L140.124 213.748L143.642 201.51C148.371 184.904 165.62 171.407 182.097 171.407H214.009C217.817 171.407 221.167 168.868 222.215 165.183L232.769 128.135C237.494 111.545 254.742 98.048 271.219 98.048L339.468 97.9264L389.431 97.9221C397.268 97.9221 403.59 100.831 407.711 106.337C411.949 111.994 413.054 119.892 410.744 128L396.457 178.164C391.734 194.75 374.485 208.242 358.009 208.242L289.607 208.372H257.706C253.902 208.372 250.557 210.907 249.502 214.588L222.903 307.495C222.159 310.093 222.673 312.892 224.291 315.049C225.904 317.202 228.428 318.469 231.107 318.469C231.113 318.469 276.307 318.381 276.307 318.381H326.122C333.959 318.381 340.281 321.29 344.402 326.796C348.639 332.457 349.744 340.355 347.433 348.463L333.146 398.619C328.423 415.209 311.174 428.701 294.698 428.701L226.299 428.831H176.484Z"
fill="#F0FF41"
/>
</g>
</svg>
</template>
<style scoped>
svg > g > rect {
fill: black;
}
.dark-theme svg > g > rect {
fill: white;
}
svg > g > path {
fill: var(--comfy-yellow);
}
.dark-theme svg > g > path {
fill: var(--comfy-blue);
}
</style>

View File

@@ -5,11 +5,7 @@
class="comfyui-menu flex items-center"
:class="{ dropzone: isDropZone, 'dropzone-active': isDroppable }"
>
<img
src="/assets/images/comfy-logo-mono.svg"
alt="ComfyUI Logo"
class="comfyui-logo ml-2 app-drag h-6"
/>
<ComfyLogo class="user-select-none cursor-default ml-2 app-drag h-6 w-6" />
<CommandMenubar />
<div class="flex-grow min-w-0 app-drag h-full">
<WorkflowTabs v-if="workflowTabsPosition === 'Topbar'" />
@@ -48,6 +44,7 @@ import { computed, onMounted, provide, ref } from 'vue'
import Actionbar from '@/components/actionbar/ComfyActionbar.vue'
import BottomPanelToggleButton from '@/components/topbar/BottomPanelToggleButton.vue'
import ComfyLogo from '@/components/topbar/ComfyLogo.vue'
import CommandMenubar from '@/components/topbar/CommandMenubar.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import WorkflowTabs from '@/components/topbar/WorkflowTabs.vue'
@@ -128,14 +125,4 @@ onMounted(() => {
:deep(.p-menubar-item-label) {
line-height: revert;
}
.comfyui-logo {
user-select: none;
cursor: default;
filter: invert(0);
}
.dark-theme .comfyui-logo {
filter: invert(1);
}
</style>

View File

@@ -1,5 +1,4 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import type { IWidget, LGraphNode } from '@comfyorg/litegraph'
import { computed, ref, watchEffect } from 'vue'
import { useCanvasStore } from '@/stores/graphStore'
@@ -9,11 +8,9 @@ interface RefreshableItem {
refresh: () => Promise<void> | void
}
type RefreshableWidget = IBaseWidget & RefreshableItem
type RefreshableWidget = IWidget & RefreshableItem
const isRefreshableWidget = (
widget: IBaseWidget
): widget is RefreshableWidget =>
const isRefreshableWidget = (widget: IWidget): widget is RefreshableWidget =>
'refresh' in widget && typeof widget.refresh === 'function'
/**

View File

@@ -7,55 +7,15 @@ import { fixBadLinks } from '@/utils/linkFixer'
export interface ValidationResult {
graphData: ComfyWorkflowJSON | null
linksFixes?: {
patched: number
deleted: number
}
}
export function useWorkflowValidation() {
const toastStore = useToastStore()
function tryFixLinks(
graphData: ComfyWorkflowJSON,
options: { silent?: boolean } = {}
) {
const { silent = false } = options
// Collect all logs in an array
const logs: string[] = []
// Then validate and fix links if schema validation passed
const linkValidation = fixBadLinks(
graphData as unknown as ISerialisedGraph,
{
fix: true,
silent,
logger: {
log: (message: string) => {
logs.push(message)
}
}
}
)
if (!silent && logs.length > 0) {
toastStore.add({
severity: 'warn',
summary: 'Workflow Validation',
detail: logs.join('\n')
})
}
// If links were fixed, notify the user
if (linkValidation.fixed) {
if (!silent) {
toastStore.add({
severity: 'success',
summary: 'Workflow Links Fixed',
detail: `Fixed ${linkValidation.patched} node connections and removed ${linkValidation.deleted} invalid links.`
})
}
}
return linkValidation.graph as unknown as ComfyWorkflowJSON
}
/**
* Validates a workflow, including link validation and schema validation
*/
@@ -67,6 +27,7 @@ export function useWorkflowValidation() {
): Promise<ValidationResult> {
const { silent = false } = options
let linksFixes
let validatedData: ComfyWorkflowJSON | null = null
// First do schema validation
@@ -80,16 +41,51 @@ export function useWorkflowValidation() {
)
if (validatedGraphData) {
try {
validatedData = tryFixLinks(validatedGraphData, { silent })
} catch (err) {
// Link fixer itself is throwing an error
console.error(err)
// Collect all logs in an array
const logs: string[] = []
// Then validate and fix links if schema validation passed
const linkValidation = fixBadLinks(
validatedGraphData as unknown as ISerialisedGraph,
{
fix: true,
silent,
logger: {
log: (message: string) => {
logs.push(message)
}
}
}
)
if (!silent && logs.length > 0) {
toastStore.add({
severity: 'warn',
summary: 'Workflow Validation',
detail: logs.join('\n')
})
}
// If links were fixed, notify the user
if (linkValidation.fixed) {
if (!silent) {
toastStore.add({
severity: 'success',
summary: 'Workflow Links Fixed',
detail: `Fixed ${linkValidation.patched} node connections and removed ${linkValidation.deleted} invalid links.`
})
}
}
validatedData = linkValidation.graph as unknown as ComfyWorkflowJSON
linksFixes = {
patched: linkValidation.patched,
deleted: linkValidation.deleted
}
}
return {
graphData: validatedData
graphData: validatedData,
linksFixes
}
}

View File

@@ -50,8 +50,7 @@ export const useFloatWidget = () => {
Math.max(0, -Math.floor(Math.log10(step)))
const enableRounding = !settingStore.get('Comfy.DisableFloatRounding')
/** Assertion {@link inputSpec.default} */
const defaultValue = (inputSpec.default as number | undefined) ?? 0
const defaultValue = inputSpec.default ?? 0
return node.addWidget(
widgetType,
inputSpec.name,

View File

@@ -55,8 +55,7 @@ export const useIntWidget = () => {
: 'number'
const step = inputSpec.step ?? 1
/** Assertion {@link inputSpec.default} */
const defaultValue = (inputSpec.default as number | undefined) ?? 0
const defaultValue = inputSpec.default ?? 0
const widget = node.addWidget(
widgetType,
inputSpec.name,

View File

@@ -1,4 +1,4 @@
import { LGraphCanvas, LiteGraph, isComboWidget } from '@comfyorg/litegraph'
import { LGraphCanvas, LiteGraph } from '@comfyorg/litegraph'
import { app } from '../../scripts/app'
@@ -33,10 +33,10 @@ const ext = {
const clickedComboValue = currentNode?.widgets
?.filter(
(w) =>
isComboWidget(w) && w.options.values?.length === values.length
w.type === 'combo' && w.options.values?.length === values.length
)
.find((w) =>
// @ts-expect-error Poorly typed; filter above "should" mitigate exceptions
// @ts-ignore Poorly typed; filter above "should" mitigate exceptions
w.options.values?.every((v, i) => v === values[i])
)?.value

View File

@@ -1231,6 +1231,7 @@ export class GroupNodeHandler {
const widgetName = self.groupData.oldToNewWidgetMap[n][w]
const widget = this.widgets.find((w) => w.name === widgetName)
if (widget) {
// @ts-expect-error fixme ts strict error
widget.type = 'hidden'
widget.computeSize = () => [0, -4]
}

View File

@@ -1,7 +1,5 @@
import type {
IComboWidget,
IStringWidget
} from '@comfyorg/litegraph/dist/types/widgets'
import { IWidget } from '@comfyorg/litegraph'
import { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { nextTick } from 'vue'
import Load3D from '@/components/load3d/Load3D.vue'
@@ -118,8 +116,8 @@ useExtensionService().registerExtension({
fileInput.onchange = async () => {
if (fileInput.files?.length) {
const modelWidget = node.widgets?.find(
(w) => w.name === 'model_file'
) as IComboWidget & { options: { values: string[] } }
(w: IWidget) => w.name === 'model_file'
) as IStringWidget
node.properties['Texture'] = undefined
@@ -141,6 +139,7 @@ useExtensionService().registerExtension({
if (uploadPath && modelWidget) {
if (!modelWidget.options?.values?.includes(uploadPath)) {
// @ts-ignore Fails due to earlier type-assertion of IStringWidget
modelWidget.options?.values?.push(uploadPath)
}
@@ -156,7 +155,9 @@ useExtensionService().registerExtension({
node.addWidget('button', 'clear', 'clear', () => {
useLoad3dService().getLoad3d(node)?.clearModel()
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
if (modelWidget) {
modelWidget.value = ''
@@ -202,10 +203,12 @@ useExtensionService().registerExtension({
const config = new Load3DConfiguration(load3d)
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const width = node.widgets?.find((w) => w.name === 'width')
const height = node.widgets?.find((w) => w.name === 'height')
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
const sceneWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
if (modelWidget && width && height && sceneWidget) {
config.configure('input', modelWidget, cameraState, width, height)
@@ -273,7 +276,7 @@ useExtensionService().registerExtension({
fileInput.onchange = async () => {
if (fileInput.files?.length) {
const modelWidget = node.widgets?.find(
(w) => w.name === 'model_file'
(w: IWidget) => w.name === 'model_file'
) as IStringWidget
const uploadPath = await Load3dUtils.uploadFile(
@@ -294,6 +297,7 @@ useExtensionService().registerExtension({
if (uploadPath && modelWidget) {
if (!modelWidget.options?.values?.includes(uploadPath)) {
// @ts-ignore Fails due to earlier type-assertion of IStringWidget
modelWidget.options?.values?.push(uploadPath)
}
@@ -309,7 +313,9 @@ useExtensionService().registerExtension({
node.addWidget('button', 'clear', 'clear', () => {
useLoad3dService().getLoad3d(node)?.clearModel()
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
if (modelWidget) {
modelWidget.value = ''
}
@@ -346,16 +352,18 @@ useExtensionService().registerExtension({
await nextTick()
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
const sceneWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
const load3d = useLoad3dService().getLoad3d(node) as Load3dAnimation
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
let cameraState = node.properties['Camera Info']
const width = node.widgets?.find((w) => w.name === 'width')
const height = node.widgets?.find((w) => w.name === 'height')
const width = node.widgets?.find((w: IWidget) => w.name === 'width')
const height = node.widgets?.find((w: IWidget) => w.name === 'height')
if (modelWidget && width && height && sceneWidget && load3d) {
const config = new Load3DConfiguration(load3d)
@@ -471,7 +479,9 @@ useExtensionService().registerExtension({
let cameraState = message.result[1]
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
if (load3d && modelWidget) {
modelWidget.value = filePath.replaceAll('\\', '/')
@@ -545,7 +555,9 @@ useExtensionService().registerExtension({
const load3d = useLoad3dService().getLoad3d(node)
const modelWidget = node.widgets?.find((w) => w.name === 'model_file')
const modelWidget = node.widgets?.find(
(w: IWidget) => w.name === 'model_file'
)
if (load3d && modelWidget) {
modelWidget.value = filePath.replaceAll('\\', '/')

View File

@@ -1,4 +1,4 @@
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import type { IWidget } from '@comfyorg/litegraph'
import Load3d from '@/extensions/core/load3d/Load3d'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
@@ -15,20 +15,17 @@ class Load3DConfiguration {
configure(
loadFolder: 'input' | 'output',
modelWidget: IBaseWidget,
modelWidget: IWidget,
cameraState?: any,
width: IBaseWidget | null = null,
height: IBaseWidget | null = null
width: IWidget | null = null,
height: IWidget | null = null
) {
this.setupModelHandling(modelWidget, loadFolder, cameraState)
this.setupTargetSize(width, height)
this.setupDefaultProperties()
}
private setupTargetSize(
width: IBaseWidget | null,
height: IBaseWidget | null
) {
private setupTargetSize(width: IWidget | null, height: IWidget | null) {
if (width && height) {
this.load3d.setTargetSize(width.value as number, height.value as number)
@@ -54,7 +51,7 @@ class Load3DConfiguration {
}
private setupModelHandling(
modelWidget: IBaseWidget,
modelWidget: IWidget,
loadFolder: 'input' | 'output',
cameraState?: any
) {

View File

@@ -3,6 +3,8 @@ Preview Any - original implement from
https://github.com/rgthree/rgthree-comfy/blob/main/py/display_any.py
upstream requested in https://github.com/Kosinkadink/rfcs/blob/main/rfcs/0000-corenodes.md#preview-nodes
*/
import { IWidget } from '@comfyorg/litegraph'
import { DOMWidget } from '@/scripts/domWidget'
import { ComfyWidgets } from '@/scripts/widgets'
import { useExtensionService } from '@/services/extensionService'
@@ -35,7 +37,9 @@ useExtensionService().registerExtension({
? void 0
: onExecuted.apply(this, [message])
const previewWidget = this.widgets?.find((w) => w.name === 'preview')
const previewWidget = this.widgets?.find(
(w: IWidget) => w.name === 'preview'
)
if (previewWidget) {
previewWidget.value = message.text[0]

View File

@@ -1,3 +1,4 @@
import { IWidget } from '@comfyorg/litegraph'
import { nextTick } from 'vue'
import Load3D from '@/components/load3d/Load3D.vue'
@@ -60,7 +61,7 @@ useExtensionService().registerExtension({
const load3d = useLoad3dService().getLoad3d(node)
const modelWidget = node.widgets?.find((w) => w.name === 'image')
const modelWidget = node.widgets?.find((w: IWidget) => w.name === 'image')
if (load3d && modelWidget) {
const filePath = fileInfo['subfolder'] + '/' + fileInfo['filename']

View File

@@ -1,4 +1,4 @@
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IWidget, LGraphNode } from '@comfyorg/litegraph'
import type { IStringWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { useNodeDragAndDrop } from '@/composables/node/useNodeDragAndDrop'
@@ -164,11 +164,11 @@ app.registerExtension({
// The widget that allows user to select file.
// @ts-expect-error fixme ts strict error
const audioWidget = node.widgets.find(
(w) => w.name === 'audio'
(w: IWidget) => w.name === 'audio'
) as IStringWidget
// @ts-expect-error fixme ts strict error
const audioUIWidget = node.widgets.find(
(w) => w.name === 'audioUI'
(w: IWidget) => w.name === 'audioUI'
) as unknown as DOMWidget<HTMLAudioElement, string>
const onAudioWidgetUpdate = () => {

View File

@@ -3,11 +3,11 @@ import type {
INodeInputSlot,
INodeOutputSlot,
ISlotType,
IWidget,
LLink,
Vector2
} from '@comfyorg/litegraph'
import type { CanvasMouseEvent } from '@comfyorg/litegraph/dist/types/events'
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import {
type CallbackParams,
@@ -226,7 +226,7 @@ export class PrimitiveNode extends LGraphNode {
// Store current size as addWidget resizes the node
const [oldWidth, oldHeight] = this.size
let widget: IBaseWidget | undefined
let widget: IWidget | undefined
if (type in ComfyWidgets) {
widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget
} else {
@@ -426,7 +426,7 @@ function getConfig(this: LGraphNode, widgetName: string) {
*/
export function convertToInput(
node: LGraphNode,
widget: IBaseWidget
widget: IWidget
): INodeInputSlot | undefined {
console.warn(
'Please remove call to convertToInput. Widget to socket conversion is no longer necessary, as they co-exist now.'
@@ -506,7 +506,8 @@ export function mergeIfValid(
}
}
return { customConfig: customSpec?.[1] ?? {} }
// @ts-expect-error fixme ts strict error
return { customConfig: customSpec[1] }
}
app.registerExtension({

View File

@@ -36,7 +36,7 @@ export const zNumericInputOptions = zBaseInputOptions.extend({
min: z.number().optional(),
max: z.number().optional(),
step: z.number().optional(),
/** Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT. */
// Note: Many node authors are using INT/FLOAT to pass list of INT/FLOAT.
default: z.union([z.number(), z.array(z.number())]).optional(),
display: z.enum(['slider', 'number', 'knob']).optional()
})

View File

@@ -5,8 +5,7 @@ import {
LGraphNode,
LiteGraph
} from '@comfyorg/litegraph'
import type { Vector2 } from '@comfyorg/litegraph'
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import type { IWidget, Vector2 } from '@comfyorg/litegraph'
import _ from 'lodash'
import type { ToastMessageOptions } from 'primevue/toast'
import { reactive } from 'vue'
@@ -95,7 +94,7 @@ function sanitizeNodeName(string: string) {
}
type Clipspace = {
widgets?: Pick<IBaseWidget, 'type' | 'name' | 'value'>[] | null
widgets?: Pick<IWidget, 'type' | 'name' | 'value'>[] | null
imgs?: HTMLImageElement[] | null
original_imgs?: HTMLImageElement[] | null
images?: any[] | null
@@ -388,6 +387,7 @@ export class ComfyApp {
const index = node.widgets.findIndex((obj) => obj.name === 'image')
if (index >= 0) {
if (
// @ts-expect-error custom widget type
node.widgets[index].type != 'image' &&
typeof node.widgets[index].value == 'string' &&
clip_image.filename
@@ -409,6 +409,7 @@ export class ComfyApp {
)
if (prop && prop.type != 'button') {
if (
// @ts-expect-error Custom widget type
prop.type != 'image' &&
typeof prop.value == 'string' &&
// @ts-expect-error Custom widget value
@@ -420,6 +421,7 @@ export class ComfyApp {
resultItem.filename +
(resultItem.type ? ` [${resultItem.type}]` : '')
} else {
// @ts-expect-error fixme ts strict error
prop.value = value
prop.callback?.(value)
}
@@ -1099,8 +1101,10 @@ export class ComfyApp {
) {
if (widget.name == 'control_after_generate') {
if (widget.value === true) {
// @ts-expect-error string is not assignable to boolean
widget.value = 'randomize'
} else if (widget.value === false) {
// @ts-expect-error string is not assignable to boolean
widget.value = 'fixed'
}
}
@@ -1531,8 +1535,10 @@ export class ComfyApp {
for (const widget of node.widgets) {
if (widget.type === 'combo') {
if (def['input'].required?.[widget.name] !== undefined) {
// @ts-expect-error Requires discriminated union
widget.options.values = def['input'].required[widget.name][0]
} else if (def['input'].optional?.[widget.name] !== undefined) {
// @ts-expect-error Requires discriminated union
widget.options.values = def['input'].optional[widget.name][0]
}
}

View File

@@ -1,6 +1,7 @@
import { LGraphNode, LegacyWidget, LiteGraph } from '@comfyorg/litegraph'
import { LGraphNode, LiteGraph } from '@comfyorg/litegraph'
import type {
IBaseWidget,
ICustomWidget,
IWidget,
IWidgetOptions
} from '@comfyorg/litegraph/dist/types/widgets'
import _ from 'lodash'
@@ -12,9 +13,9 @@ import { useDomWidgetStore } from '@/stores/domWidgetStore'
import { generateUUID } from '@/utils/formatUtil'
export interface BaseDOMWidget<V extends object | string>
extends IBaseWidget<V, string, DOMWidgetOptions<V>> {
extends ICustomWidget {
// ICustomWidget properties
type: string
type: 'custom'
options: DOMWidgetOptions<V>
value: V
callback?: (value: V) => void
@@ -80,23 +81,26 @@ export interface DOMWidgetOptions<V extends object | string>
}
export const isDOMWidget = <T extends HTMLElement, V extends object | string>(
widget: IBaseWidget
widget: IWidget
): widget is DOMWidget<T, V> => 'element' in widget && !!widget.element
export const isComponentWidget = <V extends object | string>(
widget: IBaseWidget
widget: IWidget
): widget is ComponentWidget<V> => 'component' in widget && !!widget.component
abstract class BaseDOMWidgetImpl<V extends object | string>
extends LegacyWidget<IBaseWidget<V, string, DOMWidgetOptions<V>>>
implements BaseDOMWidget<V>
{
static readonly DEFAULT_MARGIN = 10
declare readonly name: string
declare readonly options: DOMWidgetOptions<V>
declare callback?: (value: V) => void
readonly type: 'custom'
readonly name: string
readonly options: DOMWidgetOptions<V>
computedHeight?: number
y: number = 0
callback?: (value: V) => void
readonly id: string
readonly node: LGraphNode
constructor(obj: {
node: LGraphNode
@@ -104,17 +108,20 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
type: string
options: DOMWidgetOptions<V>
}) {
const { node, name, type, options } = obj
super({ y: 0, name, type, options }, node)
// @ts-expect-error custom widget type
this.type = obj.type
this.name = obj.name
this.options = obj.options
this.id = generateUUID()
this.node = obj.node
}
override get value(): V {
get value(): V {
return this.options.getValue?.() ?? ('' as V)
}
override set value(v: V) {
set value(v: V) {
this.options.setValue?.(v)
this.callback?.(this.value)
}
@@ -127,7 +134,7 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
return !['hidden'].includes(this.type) && this.node.isWidgetVisible(this)
}
override draw(
draw(
ctx: CanvasRenderingContext2D,
_node: LGraphNode,
widget_width: number,
@@ -161,7 +168,7 @@ export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
extends BaseDOMWidgetImpl<V>
implements DOMWidget<T, V>
{
override readonly element: T
readonly element: T
constructor(obj: {
node: LGraphNode
@@ -176,6 +183,7 @@ export class DOMWidgetImpl<T extends HTMLElement, V extends object | string>
/** Extract DOM widget size info */
computeLayoutSize(node: LGraphNode) {
// @ts-expect-error custom widget type
if (this.type === 'hidden') {
return {
minHeight: 0,
@@ -249,7 +257,7 @@ export class ComponentWidgetImpl<V extends object | string>
}
}
override serializeValue(): V {
serializeValue(): V {
return toRaw(this.value)
}
}

View File

@@ -1,5 +1,4 @@
import { LGraphNode } from '@comfyorg/litegraph'
import type { IBaseWidget } from '@comfyorg/litegraph/dist/types/widgets'
import { IWidget, LGraphNode } from '@comfyorg/litegraph'
import { useChainCallback } from '@/composables/functional/useChainCallback'
import { useBooleanWidget } from '@/composables/widgets/useBooleanWidget'
@@ -11,7 +10,7 @@ const FloatWidget = useFloatWidget()
const BooleanWidget = useBooleanWidget()
function addWidgetFromValue(node: LGraphNode, value: unknown) {
let widget: IBaseWidget
let widget: IWidget
if (typeof value === 'string') {
widget = StringWidget(node, {

View File

@@ -1,6 +1,6 @@
import { type LGraphNode, isComboWidget } from '@comfyorg/litegraph'
import type { LGraphNode } from '@comfyorg/litegraph'
import type { IWidget } from '@comfyorg/litegraph'
import type {
IBaseWidget,
IComboWidget,
IStringWidget
} from '@comfyorg/litegraph/dist/types/widgets'
@@ -25,7 +25,7 @@ import './errorNodeWidgets'
export type ComfyWidgetConstructorV2 = (
node: LGraphNode,
inputSpec: InputSpecV2
) => IBaseWidget
) => IWidget
export type ComfyWidgetConstructor = (
node: LGraphNode,
@@ -33,7 +33,7 @@ export type ComfyWidgetConstructor = (
inputData: InputSpec,
app: ComfyApp,
widgetName?: string
) => { widget: IBaseWidget; minWidth?: number; minHeight?: number }
) => { widget: IWidget; minWidth?: number; minHeight?: number }
/**
* Transforms a V2 widget constructor to a V1 widget constructor.
@@ -60,7 +60,7 @@ function controlValueRunBefore() {
return useSettingStore().get('Comfy.WidgetControlMode') === 'before'
}
export function updateControlWidgetLabel(widget: IBaseWidget) {
export function updateControlWidgetLabel(widget: IWidget) {
if (controlValueRunBefore()) {
widget.label = t('g.control_before_generate')
} else {
@@ -73,12 +73,12 @@ const HAS_EXECUTED = Symbol()
export function addValueControlWidget(
node: LGraphNode,
targetWidget: IBaseWidget,
targetWidget: IWidget,
defaultValue?: string,
_values?: unknown,
widgetName?: string,
inputData?: InputSpec
): IComboWidget {
): IWidget {
let name = inputData?.[1]?.control_after_generate
if (typeof name !== 'string') {
name = widgetName
@@ -98,11 +98,11 @@ export function addValueControlWidget(
export function addValueControlWidgets(
node: LGraphNode,
targetWidget: IBaseWidget,
targetWidget: IWidget,
defaultValue?: string,
options?: Record<string, any>,
inputData?: InputSpec
): [IComboWidget, ...IStringWidget[]] {
): IWidget[] {
if (!defaultValue) defaultValue = 'randomize'
if (!options) options = {}
@@ -118,6 +118,7 @@ export function addValueControlWidgets(
return name
}
const widgets: IWidget[] = []
const valueControl = node.addWidget(
'combo',
getName('control_after_generate', 'controlAfterGenerateName'),
@@ -134,12 +135,12 @@ export function addValueControlWidgets(
// @ts-ignore index with symbol
valueControl[IS_CONTROL_WIDGET] = true
updateControlWidgetLabel(valueControl)
const widgets: [IComboWidget, ...IStringWidget[]] = [valueControl]
widgets.push(valueControl)
const isCombo = isComboWidget(targetWidget)
const isCombo = targetWidget.type === 'combo'
let comboFilter: IStringWidget
if (isCombo && valueControl.options.values) {
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// @ts-ignore Combo widget values may be a dictionary or legacy function type
valueControl.options.values.push('increment-wrap')
}
if (isCombo && options.addFilterList !== false) {
@@ -183,7 +184,7 @@ export function addValueControlWidgets(
const lower = filter.toLocaleLowerCase()
check = (item: string) => item.toLocaleLowerCase().includes(lower)
}
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// @ts-ignore Combo widget values may be a dictionary or legacy function type
values = values.filter((item: string) => check(item))
if (!values.length && targetWidget.options.values?.length) {
console.warn(
@@ -210,17 +211,17 @@ export function addValueControlWidgets(
current_index -= 1
break
case 'randomize':
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// @ts-ignore Combo widget values may be a dictionary or legacy function type
current_index = Math.floor(Math.random() * current_length)
break
default:
break
}
current_index = Math.max(0, current_index)
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// @ts-ignore Combo widget values may be a dictionary or legacy function type
current_index = Math.min(current_length - 1, current_index)
if (current_index >= 0) {
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// @ts-ignore Combo widget values may be a dictionary or legacy function type
let value = values[current_index]
targetWidget.value = value
targetWidget.callback?.(value)

View File

@@ -35,7 +35,10 @@ declare module '@comfyorg/litegraph/dist/types/widgets' {
onRemove?: () => void
beforeQueued?: () => unknown
afterQueued?: () => unknown
serializeValue?(node: LGraphNode, index: number): Promise<unknown> | unknown
serializeValue?: (
node: LGraphNode,
index: number
) => Promise<unknown> | unknown
/**
* Refreshes the widget's value or options from its remote source.
@@ -63,14 +66,6 @@ declare module '@comfyorg/litegraph' {
new (): T
}
interface TextWidget {
dynamicPrompts?: boolean
}
interface BaseWidget {
serializeValue?(node: LGraphNode, index: number): Promise<unknown> | unknown
}
interface LGraphNode {
constructor: LGraphNodeConstructor

View File

@@ -2,8 +2,8 @@ import type { ColorOption, LGraph } from '@comfyorg/litegraph'
import { LGraphGroup, LGraphNode, isColorable } from '@comfyorg/litegraph'
import type { ISerialisedGraph } from '@comfyorg/litegraph/dist/types/serialisation'
import type {
IBaseWidget,
IComboWidget
IComboWidget,
IWidget
} from '@comfyorg/litegraph/dist/types/widgets'
import _ from 'lodash'
@@ -35,9 +35,11 @@ export function isAudioNode(node: LGraphNode | undefined): boolean {
export function addToComboValues(widget: IComboWidget, value: string) {
if (!widget.options) widget.options = { values: [] }
if (!widget.options.values) widget.options.values = []
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Combo widget values may be a dictionary or legacy function type
if (!widget.options.values.includes(value)) {
// @ts-expect-error Combo widget values may be a dictionary or legacy function type
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore Combo widget values may be a dictionary or legacy function type
widget.options.values.push(value)
}
}
@@ -92,7 +94,7 @@ export function executeWidgetsCallback(
*/
export function migrateWidgetsValues<TWidgetValue>(
inputDefs: Record<string, InputSpec>,
widgets: IBaseWidget[],
widgets: IWidget[],
widgetsValues: TWidgetValue[]
): TWidgetValue[] {
const widgetNames = new Set(widgets.map((w) => w.name))