mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-23 06:10:32 +00:00
Compare commits
1 Commits
v1.19.5
...
branding_m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47e3330baa |
443
README.md
443
README.md
@@ -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.
|
||||
|
||||

|
||||
</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
|
||||

|
||||
|
||||
## Reset button
|
||||

|
||||
|
||||
## Edit Keybinding
|
||||

|
||||

|
||||
|
||||
[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
|
||||

|
||||
|
||||
#### 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>
|
||||
|
||||

|
||||
|
||||
</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
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
```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
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
```js
|
||||
// window.alert
|
||||
window['app'].extensionManager.toast
|
||||
.addAlert("Test Alert")
|
||||
```
|
||||
|
||||

|
||||
|
||||
</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'
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
</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>'
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
|
||||
</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']
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||

|
||||
</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>
|
||||
|
||||

|
||||
</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.
|
||||
|
||||

|
||||
</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:
|
||||

|
||||
|
||||
</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
12
package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
49
src/components/topbar/ComfyLogo.vue
Normal file
49
src/components/topbar/ComfyLogo.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
@@ -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('\\', '/')
|
||||
|
||||
|
||||
@@ -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
|
||||
) {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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)
|
||||
|
||||
13
src/types/litegraph-augmentation.d.ts
vendored
13
src/types/litegraph-augmentation.d.ts
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user