Compare commits

..

2 Commits

Author SHA1 Message Date
filtered
782d93a7a0 Add awaits to various tests 2024-11-15 01:28:48 +11:00
filtered
7be14c5189 Add nodeTemplate tests
Test failure confirmed when links are not connected
2024-11-15 01:28:18 +11:00
64 changed files with 1426 additions and 7273 deletions

View File

@@ -6,11 +6,6 @@ PLAYWRIGHT_TEST_URL=http://localhost:5173
# Note: localhost:8188 does not work.
DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
# Allow dev server access from remote IP addresses.
# If true, the vite dev server will listen on all addresses, including LAN
# and public addresses.
VITE_REMOTE_DEV=false
# The target ComfyUI checkout directory to deploy the frontend code to.
# The dist directory will be copied to {DEPLOY_COMFYUI_DIR}/custom_web_versions/main/dev
# Add `--front-end-root {DEPLOY_COMFYUI_DIR}/custom_web_versions/main/dev`

View File

@@ -1,7 +1,5 @@
if [[ "$OS" == "Windows_NT" ]]; then
npx.cmd lint-staged
npm.cmd run typecheck
else
npx lint-staged
npm run typecheck
fi

View File

@@ -431,8 +431,6 @@ core extensions will be loaded.
#### Access dev server on touch devices
Enable remote access to the dev server by setting `VITE_REMOTE_DEV` in `.env` to `true`.
After you start the dev server, you should see following logs:
```

View File

@@ -0,0 +1,10 @@
[
{
"name": "Three Nodes Template",
"data": "{\"nodes\":[{\"id\":7,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]},{\"id\":6,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":4,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]}],\"groups\":[],\"reroutes\":[],\"links\":[{\"id\":5,\"origin_id\":4,\"origin_slot\":1,\"target_id\":7,\"target_slot\":0,\"type\":\"CLIP\"},{\"id\":3,\"origin_id\":4,\"origin_slot\":1,\"target_id\":6,\"target_slot\":0,\"type\":\"CLIP\"}]}"
},
{
"name": "Completely empty template",
"data": "{\"nodes\":[],\"groups\":[],\"reroutes\":[],\"links\":[]}"
}
]

View File

@@ -0,0 +1,6 @@
[
{
"name": "vintageClipboard Template",
"data": "{\"nodes\":[{\"id\":-1,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]}],\"links\":[[0,1,1,0,4],[0,1,2,0,4]]}"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 152 KiB

View File

@@ -94,7 +94,7 @@ test.describe('Settings', () => {
test('Can change canvas zoom speed setting', async ({ comfyPage }) => {
const maxSpeed = 2.5
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', maxSpeed)
test.step('Setting should persist', async () => {
await test.step('Setting should persist', async () => {
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
})
})

View File

@@ -77,6 +77,7 @@ export class ComfyPage {
// All canvas position operations are based on default view of canvas.
public readonly canvas: Locator
public readonly widgetTextBox: Locator
public readonly contextMenu: Locator
// Buttons
public readonly resetViewButton: Locator
@@ -107,6 +108,7 @@ export class ComfyPage {
this.url = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'
this.canvas = page.locator('#graph-canvas')
this.widgetTextBox = page.getByPlaceholder('text').nth(1)
this.contextMenu = page.locator('.litegraph.litecontextmenu')
this.resetViewButton = page.getByRole('button', { name: 'Reset View' })
this.queueButton = page.getByRole('button', { name: 'Queue Prompt' })
this.workflowUploadInput = page.locator('#comfy-file-input')
@@ -148,6 +150,12 @@ export class ComfyPage {
})
}
async getGraphSelectedItemsCount(): Promise<number | undefined> {
return await this.page.evaluate(() => {
return window['app']?.canvas?.selectedItems?.size
})
}
async setupWorkflowsDirectory(structure: FolderStructure) {
const resp = await this.request.post(
`${this.url}/api/devtools/setup_folder_structure`,
@@ -191,6 +199,39 @@ export class ComfyPage {
return await resp.json()
}
async clearNodeTemplates() {
const resp = await this.request.delete(
`${this.url}/api/userdata/comfy.templates.json`,
{
headers: { 'Comfy-User': this.id }
}
)
const status = resp.status()
if (status !== 204 && status !== 404)
throw new Error(`Failed to delete node templates: ${await resp.text()}`)
}
async setNodeTemplates(fileName: string) {
const path = this.assetPath(fileName)
const data = fs.readFileSync(path, 'utf-8')
const resp = await this.request.post(
`${this.url}/api/userdata/comfy.templates.json`,
{
headers: {
'Comfy-User': this.id,
overwrite: 'true',
full_info: 'true'
},
data
}
)
if (resp.status() !== 200)
throw new Error(`Failed to upload node templates: ${await resp.text()}`)
}
async setupSettings(settings: Record<string, any>) {
const resp = await this.request.post(
`${this.url}/api/devtools/set_settings`,
@@ -204,15 +245,13 @@ export class ComfyPage {
}
}
async setup({ clearStorage = true }: { clearStorage?: boolean } = {}) {
async setup() {
await this.goto()
if (clearStorage) {
await this.page.evaluate((id) => {
localStorage.clear()
sessionStorage.clear()
localStorage.setItem('Comfy.userId', id)
}, this.id)
}
await this.page.evaluate((id) => {
localStorage.clear()
sessionStorage.clear()
localStorage.setItem('Comfy.userId', id)
}, this.id)
await this.goto()
// Unify font for consistent screenshots.
@@ -316,9 +355,9 @@ export class ComfyPage {
}, settingId)
}
async reload({ clearStorage = true }: { clearStorage?: boolean } = {}) {
async reload() {
await this.page.reload({ timeout: 15000 })
await this.setup({ clearStorage })
await this.setup()
}
async goto() {
@@ -401,11 +440,17 @@ export class ComfyPage {
await this.nextFrame()
}
async dragAndDrop(source: Position, target: Position) {
async dragAndDrop(
source: Position,
target: Position,
modifierKey?: 'ControlOrMeta' | 'Control' | 'Alt' | 'Shift'
) {
if (modifierKey) await this.page.keyboard.down(modifierKey)
await this.page.mouse.move(source.x, source.y)
await this.page.mouse.down()
await this.page.mouse.move(target.x, target.y)
await this.page.mouse.up()
if (modifierKey) await this.page.keyboard.up(modifierKey)
await this.nextFrame()
}
@@ -553,8 +598,11 @@ export class ComfyPage {
}
async rightClickCanvas() {
await this.page.mouse.click(10, 10, { button: 'right' })
await this.nextFrame()
await this.canvas.click({
position: { x: 10, y: 10 },
button: 'right'
})
await expect(this.contextMenu).toBeVisible()
}
async doubleClickCanvas() {
@@ -569,7 +617,7 @@ export class ComfyPage {
y: 625
}
})
this.page.mouse.move(10, 10)
await this.page.mouse.move(10, 10)
await this.nextFrame()
}
@@ -581,10 +629,14 @@ export class ComfyPage {
},
button: 'right'
})
this.page.mouse.move(10, 10)
await this.page.mouse.move(10, 10)
await this.nextFrame()
}
async clickContextMenuItem(name: string): Promise<void> {
await this.page.getByRole('menuitem', { name }).click()
}
async select2Nodes() {
// Select 2 CLIP nodes.
await this.page.keyboard.down('Control')

View File

@@ -43,7 +43,7 @@ export class ComfyNodeSearchBox {
}
get filterButton() {
return this.page.locator('.comfy-vue-node-search-container .filter-button')
return this.page.locator('.comfy-vue-node-search-container ._filter-button')
}
async fillAndSelectFirstNode(

View File

@@ -77,13 +77,8 @@ test.describe('Group Node', () => {
.click()
})
})
// The 500ms fixed delay on the search results is causing flakiness
// Potential solution: add a spinner state when the search is in progress,
// and observe that state from the test. Blocker: the PrimeVue AutoComplete
// does not have a v-model on the query, so we cannot observe the raw
// query update, and thus cannot set the spinning state between the raw query
// update and the debounced search update.
test.skip('Can be added to canvas using search', async ({ comfyPage }) => {
test('Can be added to canvas using search', async ({ comfyPage }) => {
const groupNodeName = 'DefautWorkflowGroupNode'
await comfyPage.convertAllNodesToGroupNode(groupNodeName)
await comfyPage.doubleClickCanvas()

View File

@@ -537,34 +537,6 @@ test.describe('Load workflow', () => {
await comfyPage.loadWorkflow('string_input')
await expect(comfyPage.canvas).toHaveScreenshot('string_input.png')
})
test('Restore workflow on reload (switch workflow)', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('single_ksampler')
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
await comfyPage.reload({ clearStorage: false })
await expect(comfyPage.canvas).toHaveScreenshot('single_ksampler.png')
})
test('Restore workflow on reload (modify workflow)', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('single_ksampler')
const node = (await comfyPage.getFirstNodeRef())!
await node.click('collapse')
// Wait 300ms between 2 clicks so that it is not treated as a double click
// by litegraph.
await comfyPage.page.waitForTimeout(300)
await comfyPage.clickEmptySpace()
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
)
await comfyPage.reload({ clearStorage: false })
await expect(comfyPage.canvas).toHaveScreenshot(
'single_ksampler_modified.png'
)
})
})
test.describe('Load duplicate workflow', () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,70 @@
import {
comfyPageFixture as test,
comfyExpect as expect
} from './fixtures/ComfyPage'
// Old `nodeTemplate.ts` system
test.describe('Node Template', () => {
test.afterEach(async ({ comfyPage }) => {
await comfyPage.clearNodeTemplates()
})
test('Can create and use node template', async ({ comfyPage }) => {
const templateName = 'Can create node template template'
await comfyPage.clearNodeTemplates()
await comfyPage.reload()
// TODO: Flaky test. Right click requires delay after reload, but other interactions do not.
await comfyPage.page.waitForTimeout(500)
// Enter filename when prompt dialog shown
comfyPage.page.on('dialog', (dialog) => dialog.accept(templateName))
// Ctrl + drag over 3 nodes
await comfyPage.dragAndDrop(
{ x: 175, y: 252 },
{ x: 483, y: 564 },
'ControlOrMeta'
)
expect(await comfyPage.getGraphSelectedItemsCount()).toEqual(3)
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Save Selected as Template')
await comfyPage.nextFrame()
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Node Templates >')
await comfyPage.clickContextMenuItem(templateName)
await expect(comfyPage.canvas).toHaveScreenshot()
})
test('Can load old format template', async ({ comfyPage }) => {
await comfyPage.setNodeTemplates('vintage_clipboard_template.json')
await comfyPage.reload()
// TODO: Flaky test. Right click requires delay after reload, but other interactions do not.
await comfyPage.page.waitForTimeout(500)
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Node Templates >')
await comfyPage.clickContextMenuItem('vintageClipboard Template')
await expect(comfyPage.canvas).toHaveScreenshot()
})
test('Can load new format template', async ({ comfyPage }) => {
await comfyPage.setNodeTemplates('node_template_templates.json')
await comfyPage.reload()
// TODO: Flaky test. Right click requires delay after reload, but other interactions do not.
await comfyPage.page.waitForTimeout(500)
await comfyPage.rightClickCanvas()
await comfyPage.clickContextMenuItem('Node Templates >')
await comfyPage.clickContextMenuItem('Three Nodes Template')
await expect(comfyPage.canvas).toHaveScreenshot()
})
})

View File

@@ -14,7 +14,6 @@ const jestConfig: JestConfigWithTsJest = {
}
]
},
transformIgnorePatterns: ['/node_modules/(?!(three|@three)/)'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy'

91
package-lock.json generated
View File

@@ -1,16 +1,16 @@
{
"name": "comfyui-frontend",
"version": "1.4.2",
"version": "1.3.43",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "comfyui-frontend",
"version": "1.4.2",
"version": "1.3.43",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.2.16",
"@comfyorg/litegraph": "^0.8.27",
"@comfyorg/litegraph": "^0.8.26",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
"@xterm/addon-fit": "^0.10.0",
@@ -24,7 +24,6 @@
"pinia": "^2.1.7",
"primeicons": "^7.0.0",
"primevue": "^4.0.5",
"three": "^0.170.0",
"vue": "^3.4.31",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.3",
@@ -41,7 +40,6 @@
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.6",
"@types/node": "^20.14.8",
"@types/three": "^0.169.0",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/test-utils": "^2.4.6",
"@vue/vue3-jest": "^29.2.6",
@@ -65,6 +63,7 @@
"tailwindcss": "^3.4.4",
"ts-jest": "^29.1.4",
"ts-node": "^10.9.2",
"tsc-files": "^1.1.4",
"tsx": "^4.15.6",
"typescript": "^5.4.5",
"typescript-eslint": "^8.0.0",
@@ -1923,9 +1922,9 @@
"license": "GPL-3.0-only"
},
"node_modules/@comfyorg/litegraph": {
"version": "0.8.27",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.27.tgz",
"integrity": "sha512-EMQ3jsny+3gUQL4+vSVwJAFxrLq4IpuyjCvAiErLY4wLZZu2Mi+7cELmhrNS0MajhZqfN1M0GPmdcBRwSWbarw==",
"version": "0.8.26",
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.26.tgz",
"integrity": "sha512-q0Vcd5usphR5nghfyFksVx+VM+eSB1MyX8Ne304KFDnr214KQMA6DAjrEQJlGBUUCybLiOtPCvd3dxPecEQiSQ==",
"license": "MIT"
},
"node_modules/@cspotcode/source-map-support": {
@@ -3851,13 +3850,6 @@
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
"node_modules/@tweenjs/tween.js": {
"version": "23.1.3",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
"integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -3993,13 +3985,6 @@
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"dev": true
},
"node_modules/@types/stats.js": {
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
"integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -4014,21 +3999,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/three": {
"version": "0.169.0",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.169.0.tgz",
"integrity": "sha512-oan7qCgJBt03wIaK+4xPWclYRPG9wzcg7Z2f5T8xYTNEF95kh0t0lklxLLYBDo7gQiGLYzE6iF4ta7nXF2bcsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tweenjs/tween.js": "~23.1.3",
"@types/stats.js": "*",
"@types/webxr": "*",
"@webgpu/types": "*",
"fflate": "~0.8.2",
"meshoptimizer": "~0.18.1"
}
},
"node_modules/@types/tough-cookie": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
@@ -4041,13 +4011,6 @@
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"license": "MIT"
},
"node_modules/@types/webxr": {
"version": "0.5.20",
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.20.tgz",
"integrity": "sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/yargs": {
"version": "17.0.32",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
@@ -4673,13 +4636,6 @@
}
}
},
"node_modules/@webgpu/types": {
"version": "0.1.51",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.51.tgz",
"integrity": "sha512-ktR3u64NPjwIViNCck+z9QeyN0iPkQCUOQ07ZCV1RzlkfP+olLTeEZ95O1QHS+v4w9vJeY9xj/uJuSphsHy5rQ==",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@xterm/addon-fit": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
@@ -6822,13 +6778,6 @@
"bser": "2.1.1"
}
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"dev": true,
"license": "MIT"
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -10118,13 +10067,6 @@
"node": ">= 8"
}
},
"node_modules/meshoptimizer": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
"dev": true,
"license": "MIT"
},
"node_modules/micromatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz",
@@ -12010,12 +11952,6 @@
"node": ">=0.8"
}
},
"node_modules/three": {
"version": "0.170.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz",
"integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==",
"license": "MIT"
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -12234,6 +12170,19 @@
}
}
},
"node_modules/tsc-files": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/tsc-files/-/tsc-files-1.1.4.tgz",
"integrity": "sha512-RePsRsOLru3BPpnf237y1Xe1oCGta8rmSYzM76kYo5tLGsv5R2r3s64yapYorGTPuuLyfS9NVbh9ydzmvNie2w==",
"dev": true,
"license": "MIT",
"bin": {
"tsc-files": "cli.js"
},
"peerDependencies": {
"typescript": ">=3"
}
},
"node_modules/tsconfig": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "comfyui-frontend",
"private": true,
"version": "1.4.2",
"version": "1.3.43",
"type": "module",
"scripts": {
"dev": "vite",
@@ -35,7 +35,6 @@
"@types/jest": "^29.5.12",
"@types/lodash": "^4.17.6",
"@types/node": "^20.14.8",
"@types/three": "^0.169.0",
"@vitejs/plugin-vue": "^5.1.4",
"@vue/test-utils": "^2.4.6",
"@vue/vue3-jest": "^29.2.6",
@@ -59,6 +58,7 @@
"tailwindcss": "^3.4.4",
"ts-jest": "^29.1.4",
"ts-node": "^10.9.2",
"tsc-files": "^1.1.4",
"tsx": "^4.15.6",
"typescript": "^5.4.5",
"typescript-eslint": "^8.0.0",
@@ -73,7 +73,7 @@
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
"@comfyorg/comfyui-electron-types": "^0.2.16",
"@comfyorg/litegraph": "^0.8.27",
"@comfyorg/litegraph": "^0.8.26",
"@primevue/themes": "^4.0.5",
"@vueuse/core": "^11.0.0",
"@xterm/addon-fit": "^0.10.0",
@@ -87,7 +87,6 @@
"pinia": "^2.1.7",
"primeicons": "^7.0.0",
"primevue": "^4.0.5",
"three": "^0.170.0",
"vue": "^3.4.31",
"vue-i18n": "^9.13.1",
"vue-router": "^4.4.3",
@@ -95,8 +94,7 @@
"zod-validation-error": "^3.3.0"
},
"lint-staged": {
"./**/*.{js,ts,tsx,vue}": [
"prettier --write"
]
"./**/*.{js,ts,tsx,vue}": "prettier --write",
"**/*.ts": "tsc-files --noEmit"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 B

View File

@@ -125,45 +125,30 @@ const adjustMenuPosition = () => {
const menuWidth = panelRef.value.offsetWidth
const menuHeight = panelRef.value.offsetHeight
// Calculate distances to all edges
const distanceLeft = lastDragState.value.x
// Calculate the distance from each edge
const distanceRight =
lastDragState.value.windowWidth - (lastDragState.value.x + menuWidth)
const distanceTop = lastDragState.value.y
const distanceBottom =
lastDragState.value.windowHeight - (lastDragState.value.y + menuHeight)
// Find the smallest distance to determine which edge to anchor to
const distances = [
{ edge: 'left', distance: distanceLeft },
{ edge: 'right', distance: distanceRight },
{ edge: 'top', distance: distanceTop },
{ edge: 'bottom', distance: distanceBottom }
]
const closestEdge = distances.reduce((min, curr) =>
curr.distance < min.distance ? curr : min
)
// Determine if the menu is closer to right/bottom or left/top
const anchorRight = distanceRight < lastDragState.value.x
const anchorBottom = distanceBottom < lastDragState.value.y
// Calculate vertical position as a percentage of screen height
const verticalRatio =
lastDragState.value.y / lastDragState.value.windowHeight
const horizontalRatio =
lastDragState.value.x / lastDragState.value.windowWidth
// Apply positioning based on closest edge
if (closestEdge.edge === 'left') {
x.value = closestEdge.distance // Maintain exact distance from left
y.value = verticalRatio * screenHeight
} else if (closestEdge.edge === 'right') {
x.value = screenWidth - menuWidth - closestEdge.distance // Maintain exact distance from right
y.value = verticalRatio * screenHeight
} else if (closestEdge.edge === 'top') {
x.value = horizontalRatio * screenWidth
y.value = closestEdge.distance // Maintain exact distance from top
// Calculate new position
if (anchorRight) {
x.value =
screenWidth - (lastDragState.value.windowWidth - lastDragState.value.x)
} else {
// bottom
x.value = horizontalRatio * screenWidth
y.value = screenHeight - menuHeight - closestEdge.distance // Maintain exact distance from bottom
x.value = lastDragState.value.x
}
if (anchorBottom) {
y.value =
screenHeight -
(lastDragState.value.windowHeight - lastDragState.value.y)
} else {
y.value = lastDragState.value.y
}
// Ensure the menu stays within the screen bounds

View File

@@ -0,0 +1,104 @@
<template>
<div class="relative h-full w-full bg-black">
<p v-if="errorMessage" class="p-4 text-center">{{ errorMessage }}</p>
<ProgressSpinner
v-else-if="loading"
class="absolute inset-0 flex justify-center items-center h-full z-10"
/>
<div v-show="!loading" class="p-terminal rounded-none h-full w-full p-2">
<div class="h-full" ref="terminalEl"></div>
</div>
</div>
</template>
<script setup lang="ts">
import '@xterm/xterm/css/xterm.css'
import { Terminal } from '@xterm/xterm'
import { FitAddon } from '@xterm/addon-fit'
import { api } from '@/scripts/api'
import { onMounted, onUnmounted, ref } from 'vue'
import { debounce } from 'lodash'
import ProgressSpinner from 'primevue/progressspinner'
import { useExecutionStore } from '@/stores/executionStore'
import { storeToRefs } from 'pinia'
import { until } from '@vueuse/core'
import { LogEntry, LogsWsMessage, TerminalSize } from '@/types/apiTypes'
const errorMessage = ref('')
const loading = ref(true)
const terminalEl = ref<HTMLDivElement>()
const fitAddon = new FitAddon()
const terminal = new Terminal({
convertEol: true
})
terminal.loadAddon(fitAddon)
const resizeTerminal = () =>
terminal.resize(terminal.cols, fitAddon.proposeDimensions().rows)
const resizeObserver = new ResizeObserver(debounce(resizeTerminal, 50))
const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
if (size) {
terminal.resize(size.cols, fitAddon.proposeDimensions().rows)
}
terminal.write(entries.map((e) => e.m).join(''))
}
const logReceived = (e: CustomEvent<LogsWsMessage>) => {
update(e.detail.entries, e.detail.size)
}
const loadLogEntries = async () => {
const logs = await api.getRawLogs()
update(logs.entries, logs.size)
}
const watchLogs = async () => {
const { clientId } = storeToRefs(useExecutionStore())
if (!clientId.value) {
await until(clientId).not.toBeNull()
}
api.subscribeLogs(true)
api.addEventListener('logs', logReceived)
}
onMounted(async () => {
terminal.open(terminalEl.value)
try {
await loadLogEntries()
} catch (err) {
console.error('Error loading logs', err)
// On older backends the endpoints wont exist
errorMessage.value =
'Unable to load logs, please ensure you have updated your ComfyUI backend.'
return
}
loading.value = false
resizeObserver.observe(terminalEl.value)
await watchLogs()
})
onUnmounted(() => {
if (api.clientId) {
api.subscribeLogs(false)
}
api.removeEventListener('logs', logReceived)
resizeObserver.disconnect()
})
</script>
<style scoped>
:deep(.p-terminal) .xterm {
overflow-x: auto;
}
:deep(.p-terminal) .xterm-screen {
background-color: black;
overflow-y: hidden;
}
</style>

View File

@@ -1,30 +0,0 @@
<template>
<div class="relative h-full w-full bg-black" ref="rootEl">
<div class="p-terminal rounded-none h-full w-full p-2">
<div class="h-full terminal-host" ref="terminalEl"></div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineEmits, Ref } from 'vue'
import { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
const emit = defineEmits<{
created: [ReturnType<typeof useTerminal>, Ref<HTMLElement>]
}>()
const terminalEl = ref<HTMLElement>()
const rootEl = ref<HTMLElement>()
emit('created', useTerminal(terminalEl), rootEl)
</script>
<style scoped>
:deep(.p-terminal) .xterm {
overflow-x: auto;
}
:deep(.p-terminal) .xterm-screen {
background-color: black;
overflow-y: hidden;
}
</style>

View File

@@ -1,73 +0,0 @@
<template>
<BaseTerminal @created="terminalCreated" />
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, Ref } from 'vue'
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
import { electronAPI } from '@/utils/envUtil'
import { IDisposable } from '@xterm/xterm'
import BaseTerminal from './BaseTerminal.vue'
const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement>
) => {
// TODO: use types from electron package
const terminalApi = electronAPI()['Terminal'] as {
onOutput(cb: (message: string) => void): () => void
resize(cols: number, rows: number): void
restore(): Promise<{
buffer: string[]
pos: { x: number; y: number }
size: { cols: number; rows: number }
}>
storePos(x: number, y: number): void
write(data: string): void
}
let offData: IDisposable
let offOutput: () => void
useAutoSize(root, true, true, () => {
// If we aren't visible, don't resize
if (!terminal.element?.offsetParent) return
terminalApi.resize(terminal.cols, terminal.rows)
})
onMounted(async () => {
offData = terminal.onData(async (message: string) => {
terminalApi.write(message)
})
offOutput = terminalApi.onOutput((message) => {
terminal.write(message)
})
const restore = await terminalApi.restore()
setTimeout(() => {
if (restore.buffer.length) {
terminal.resize(restore.size.cols, restore.size.rows)
terminal.write(restore.buffer.join(''))
}
}, 500)
})
onUnmounted(() => {
offData?.dispose()
offOutput?.()
})
}
</script>
<style scoped>
:deep(.p-terminal) .xterm {
overflow-x: auto;
}
:deep(.p-terminal) .xterm-screen {
background-color: black;
overflow-y: hidden;
}
</style>

View File

@@ -1,90 +0,0 @@
<template>
<div class="bg-black h-full w-full">
<p v-if="errorMessage" class="p-4 text-center">{{ errorMessage }}</p>
<ProgressSpinner
v-else-if="loading"
class="relative inset-0 flex justify-center items-center h-full z-10"
/>
<BaseTerminal v-show="!loading" @created="terminalCreated" />
</div>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted, Ref, ref } from 'vue'
import type { useTerminal } from '@/hooks/bottomPanelTabs/useTerminal'
import { LogEntry, LogsWsMessage, TerminalSize } from '@/types/apiTypes'
import { api } from '@/scripts/api'
import { useExecutionStore } from '@/stores/executionStore'
import { until } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import BaseTerminal from './BaseTerminal.vue'
import ProgressSpinner from 'primevue/progressspinner'
const errorMessage = ref('')
const loading = ref(true)
const terminalCreated = (
{ terminal, useAutoSize }: ReturnType<typeof useTerminal>,
root: Ref<HTMLElement>
) => {
useAutoSize(root, true, false)
const update = (entries: Array<LogEntry>, size?: TerminalSize) => {
if (size) {
terminal.resize(size.cols, terminal.rows)
}
terminal.write(entries.map((e) => e.m).join(''))
}
const logReceived = (e: CustomEvent<LogsWsMessage>) => {
update(e.detail.entries, e.detail.size)
}
const loadLogEntries = async () => {
const logs = await api.getRawLogs()
update(logs.entries, logs.size)
}
const watchLogs = async () => {
const { clientId } = storeToRefs(useExecutionStore())
if (!clientId.value) {
await until(clientId).not.toBeNull()
}
api.subscribeLogs(true)
api.addEventListener('logs', logReceived)
}
onMounted(async () => {
try {
await loadLogEntries()
} catch (err) {
console.error('Error loading logs', err)
// On older backends the endpoints wont exist
errorMessage.value =
'Unable to load logs, please ensure you have updated your ComfyUI backend.'
return
}
await watchLogs()
loading.value = false
})
onUnmounted(() => {
if (api.clientId) {
api.subscribeLogs(false)
}
api.removeEventListener('logs', logReceived)
})
}
</script>
<style scoped>
:deep(.p-terminal) .xterm {
overflow-x: auto;
}
:deep(.p-terminal) .xterm-screen {
background-color: black;
overflow-y: hidden;
}
</style>

View File

@@ -28,14 +28,7 @@
class="flex flex-row items-center gap-2"
v-if="status === 'in_progress' || status === 'paused'"
>
<!-- Temporary fix for issue when % only comes into view only if the progress bar is large enough
https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1731551013385499
-->
<ProgressBar
class="flex-1"
:value="downloadProgress"
:show-value="downloadProgress > 10"
/>
<ProgressBar class="flex-1" :value="downloadProgress" />
<Button
class="file-action-button"

View File

@@ -13,7 +13,6 @@
:modelValue="modelValue"
@update:modelValue="updateValue"
class="input-part"
:max-fraction-digits="3"
:class="inputClass"
:min="min"
:max="max"

View File

@@ -1,6 +1,6 @@
<template>
<Tree
class="tree-explorer py-0 px-2 2xl:px-4"
class="tree-explorer p-2 2xl:p-4"
:class="props.class"
v-model:expandedKeys="expandedKeys"
v-model:selectionKeys="selectionKeys"

View File

@@ -61,7 +61,6 @@ import { usePragmaticDroppable } from '@/hooks/dndHooks'
import { useWorkflowStore } from '@/stores/workflowStore'
import { setStorageValue } from '@/scripts/utils'
import { ChangeTracker } from '@/scripts/changeTracker'
import { api } from '@/scripts/api'
const emit = defineEmits(['ready'])
const canvasRef = ref<HTMLCanvasElement | null>(null)
@@ -179,26 +178,13 @@ watchEffect(() => {
})
const workflowStore = useWorkflowStore()
const persistCurrentWorkflow = () => {
const workflow = JSON.stringify(comfyApp.serializeGraph())
localStorage.setItem('workflow', workflow)
if (api.clientId) {
sessionStorage.setItem(`workflow:${api.clientId}`, workflow)
}
}
watchEffect(() => {
if (workflowStore.activeWorkflow) {
const workflow = workflowStore.activeWorkflow
setStorageValue('Comfy.PreviousWorkflow', workflow.key)
// When the activeWorkflow changes, the graph has already been loaded.
// Saving the current state of the graph to the localStorage.
persistCurrentWorkflow()
}
})
api.addEventListener('graphChanged', persistCurrentWorkflow)
usePragmaticDroppable(() => canvasRef.value, {
onDrop: (event) => {
const loc = event.location.current.input
@@ -276,7 +262,6 @@ onMounted(async () => {
ChangeTracker.init(comfyApp)
await comfyApp.setup(canvasRef.value)
canvasStore.canvas = comfyApp.canvas
canvasStore.canvas.render_canvas_border = false
workspaceStore.spinner = false
window['app'] = comfyApp

View File

@@ -87,10 +87,10 @@ onMounted(async () => {
installPath.value = paths.defaultInstallPath
})
const validatePath = async (path: string) => {
const validatePath = async () => {
try {
pathError.value = ''
const validation = await electron.validateInstallPath(path)
const validation = await electron.validateInstallPath(installPath.value)
if (!validation.isValid) {
pathError.value = validation.error
@@ -105,7 +105,7 @@ const browsePath = async () => {
const result = await electron.showDirectoryPicker()
if (result) {
installPath.value = result
await validatePath(result)
await validatePath()
}
} catch (error) {
pathError.value = t('install.failedToSelectDirectory')

View File

@@ -1,11 +1,6 @@
<template>
<div
class="comfy-vue-node-search-container flex justify-center items-center w-full min-w-96 pointer-events-auto"
>
<div
class="comfy-vue-node-preview-container absolute left-[-350px] top-[50px]"
v-if="enableNodePreview"
>
<div class="comfy-vue-node-search-container">
<div class="comfy-vue-node-preview-container" v-if="enableNodePreview">
<NodePreview
:nodeDef="hoveredSuggestion"
:key="hoveredSuggestion?.name || ''"
@@ -16,10 +11,10 @@
<Button
icon="pi pi-filter"
severity="secondary"
class="filter-button z-10"
class="_filter-button"
@click="nodeSearchFilterVisible = true"
/>
<Dialog v-model:visible="nodeSearchFilterVisible" class="min-w-96">
<Dialog v-model:visible="nodeSearchFilterVisible" class="_dialog">
<template #header>
<h3>Add node filter condition</h3>
</template>
@@ -30,7 +25,7 @@
<AutoCompletePlus
:model-value="props.filters"
class="comfy-vue-node-search-box z-10 flex-grow"
class="comfy-vue-node-search-box"
scrollHeight="40vh"
:placeholder="placeholder"
:input-id="inputId"
@@ -153,3 +148,31 @@ const setHoverSuggestion = (index: number) => {
hoveredSuggestion.value = value
}
</script>
<style scoped>
.comfy-vue-node-search-container {
@apply flex justify-center items-center w-full min-w-96;
}
.comfy-vue-node-search-container * {
pointer-events: auto;
}
.comfy-vue-node-preview-container {
position: absolute;
left: -350px;
top: 50px;
}
.comfy-vue-node-search-box {
@apply z-10 flex-grow;
}
._filter-button {
z-index: 10;
}
._dialog {
@apply min-w-96;
}
</style>

View File

@@ -31,7 +31,7 @@
<ElectronDownloadItems v-if="isElectron()" />
<TreeExplorer
class="model-lib-tree-explorer"
class="model-lib-tree-explorer py-0"
:roots="renderedRoot.children"
v-model:expandedKeys="expandedKeys"
>

View File

@@ -48,7 +48,7 @@
class="m-2"
/>
<TreeExplorer
class="node-lib-tree-explorer"
class="node-lib-tree-explorer py-0"
:roots="renderedRoot.children"
v-model:expandedKeys="expandedKeys"
>

View File

@@ -16,13 +16,9 @@
class="mt-2 flex flex-row items-center gap-2"
v-if="['in_progress', 'paused', 'completed'].includes(download.status)"
>
<!-- Temporary fix for issue when % only comes into view only if the progress bar is large enough
https://comfy-organization.slack.com/archives/C07H3GLKDPF/p1731551013385499
-->
<ProgressBar
class="flex-1"
:value="Number((download.progress * 100).toFixed(1))"
:show-value="download.progress > 0.1"
/>
<Button

View File

@@ -1,6 +1,6 @@
<template>
<TreeExplorer
class="node-lib-bookmark-tree-explorer"
class="node-lib-bookmark-tree-explorer py-0"
ref="treeExplorerRef"
:roots="renderedBookmarkedRoot.children"
:expandedKeys="expandedKeys"

View File

@@ -733,7 +733,7 @@ app.registerExtension({
app.ui.settings.addSetting({
id,
category: ['Appearance', 'ColorPalette'],
category: ['Comfy', 'ColorPalette'],
name: 'Color Palette',
type: (name, setter, value) => {
const options = [

View File

@@ -1,138 +0,0 @@
import { app } from '@/scripts/app'
import { electronAPI as getElectronAPI, isElectron } from '@/utils/envUtil'
;(async () => {
if (!isElectron()) return
const electronAPI = getElectronAPI()
const desktopAppVersion = await electronAPI.getElectronVersion()
app.registerExtension({
name: 'Comfy.ElectronAdapter',
settings: [
{
id: 'Comfy-Desktop.AutoUpdate',
category: ['Comfy-Desktop', 'General', 'AutoUpdate'],
name: 'Automatically check for updates',
type: 'boolean',
defaultValue: true,
onChange(newValue, oldValue) {
if (oldValue !== undefined && newValue !== oldValue) {
electronAPI.restartApp(
'Restart ComfyUI to apply changes.',
1500 // add delay to allow changes to take effect before restarting.
)
}
}
}
],
commands: [
{
id: 'Comfy-Desktop.Folders.OpenLogsFolder',
label: 'Open Logs Folder',
icon: 'pi pi-folder-open',
function() {
electronAPI.openLogsFolder()
}
},
{
id: 'Comfy-Desktop.Folders.OpenModelsFolder',
label: 'Open Models Folder',
icon: 'pi pi-folder-open',
function() {
electronAPI.openModelsFolder()
}
},
{
id: 'Comfy-Desktop.Folders.OpenOutputsFolder',
label: 'Open Outputs Folder',
icon: 'pi pi-folder-open',
function() {
electronAPI.openOutputsFolder()
}
},
{
id: 'Comfy-Desktop.Folders.OpenInputsFolder',
label: 'Open Inputs Folder',
icon: 'pi pi-folder-open',
function() {
electronAPI.openInputsFolder()
}
},
{
id: 'Comfy-Desktop.Folders.OpenCustomNodesFolder',
label: 'Open Custom Nodes Folder',
icon: 'pi pi-folder-open',
function() {
electronAPI.openCustomNodesFolder()
}
},
{
id: 'Comfy-Desktop.Folders.OpenModelConfig',
label: 'Open extra_model_paths.yaml',
icon: 'pi pi-file',
function() {
electronAPI.openModelConfig()
}
},
{
id: 'Comfy-Desktop.OpenDevTools',
label: 'Open DevTools',
icon: 'pi pi-code',
function() {
electronAPI.openDevTools()
}
},
{
id: 'Comfy-Desktop.OpenFeedbackPage',
label: 'Feedback',
icon: 'pi pi-envelope',
function() {
window.open('https://forum.comfy.org/c/v1-feedback/', '_blank')
}
},
{
id: 'Comfy-Desktop.Reinstall',
label: 'Reinstall',
icon: 'pi pi-refresh',
function() {
// TODO(huchenlei): Add a confirmation dialog.
electronAPI.reinstall()
}
}
],
menuCommands: [
{
path: ['Help'],
commands: ['Comfy-Desktop.OpenFeedbackPage']
},
{
path: ['Help'],
commands: ['Comfy-Desktop.OpenDevTools']
},
{
path: ['Help', 'Open Folder'],
commands: [
'Comfy-Desktop.Folders.OpenLogsFolder',
'Comfy-Desktop.Folders.OpenModelsFolder',
'Comfy-Desktop.Folders.OpenOutputsFolder',
'Comfy-Desktop.Folders.OpenInputsFolder',
'Comfy-Desktop.Folders.OpenCustomNodesFolder',
'Comfy-Desktop.Folders.OpenModelConfig'
]
},
{
path: ['Help'],
commands: ['Comfy-Desktop.Reinstall']
}
],
aboutPageBadges: [
{
label: 'ComfyUI_Desktop ' + desktopAppVersion,
url: 'https://github.com/Comfy-Org/electron',
icon: 'pi pi-github'
}
]
})
})()

View File

@@ -13,7 +13,6 @@ import {
deserialiseAndCreate,
serialise
} from '@/extensions/core/vintageClipboard'
import type { ComfyNodeDef } from '@/types/apiTypes'
type GroupNodeWorkflowData = {
external: ComfyLink[]
@@ -57,7 +56,7 @@ const Workflow = {
class GroupNodeBuilder {
nodes: LGraphNode[]
nodeData: GroupNodeWorkflowData
nodeData: any
constructor(nodes: LGraphNode[]) {
this.nodes = nodes
@@ -176,7 +175,7 @@ export class GroupNodeConfig {
primitiveToWidget: {}
nodeInputs: {}
outputVisibility: any[]
nodeDef: ComfyNodeDef
nodeDef: any
inputs: any[]
linksFrom: {}
linksTo: {}
@@ -205,7 +204,6 @@ export class GroupNodeConfig {
output: [],
output_name: [],
output_is_list: [],
// @ts-expect-error Unused, doesn't exist
output_is_hidden: [],
name: source + SEPARATOR + this.name,
display_name: this.name,
@@ -697,11 +695,11 @@ export class GroupNodeConfig {
}
export class GroupNodeHandler {
node: LGraphNode
node
groupData
innerNodes: any
constructor(node: LGraphNode) {
constructor(node) {
this.node = node
this.groupData = node.constructor?.nodeData?.[GROUP]
@@ -776,7 +774,6 @@ export class GroupNodeHandler {
this.node.updateLink = (link) => {
// Replace the group node reference with the internal node
// @ts-expect-error Can this be removed? Or replaced with: LLink.create(link.asSerialisable())
link = { ...link }
const output = this.groupData.newToOldOutputMap[link.origin_slot]
let innerNode = this.innerNodes[output.node.index]
@@ -968,20 +965,17 @@ export class GroupNodeHandler {
app.canvas.emitBeforeChange()
try {
const { newNodes, selectedIds } = addInnerNodes()
reconnectInputs(selectedIds)
reconnectOutputs(selectedIds)
app.graph.remove(this.node)
const { newNodes, selectedIds } = addInnerNodes()
reconnectInputs(selectedIds)
reconnectOutputs(selectedIds)
app.graph.remove(this.node)
return newNodes
} finally {
app.canvas.emitAfterChange()
}
app.canvas.emitAfterChange()
return newNodes
}
const getExtraMenuOptions = this.node.getExtraMenuOptions
// @ts-expect-error Should pass patched return value getExtraMenuOptions
this.node.getExtraMenuOptions = function (_, options) {
getExtraMenuOptions?.apply(this, arguments)
@@ -994,7 +988,6 @@ export class GroupNodeHandler {
null,
{
content: 'Convert to nodes',
// @ts-expect-error
callback: () => {
return this.convertToNodes()
}
@@ -1155,7 +1148,6 @@ export class GroupNodeHandler {
if (
old.inputName !== 'image' &&
// @ts-expect-error Widget values
!widget.options.values.includes(widget.value)
) {
widget.value = widget.options.values[0]
@@ -1362,7 +1354,6 @@ export class GroupNodeHandler {
if (!originNode) continue // this node is in the group
originNode.connect(
originSlot,
// @ts-expect-error Valid - uses deprecated interface. Required check: if (graph.getNodeById(this.node.id) !== this.node) report()
this.node.id,
this.groupData.oldToNewInputMap[targetId][targetSlot]
)
@@ -1484,7 +1475,7 @@ function ungroupSelectedGroupNodes() {
const nodes = Object.values(app.canvas.selected_nodes ?? {})
for (const node of nodes) {
if (GroupNodeHandler.isGroupNode(node)) {
node.convertToNodes?.()
node['convertToNodes']?.()
}
}
}

View File

@@ -4,7 +4,6 @@ import { app } from '../../scripts/app'
import { LGraphCanvas } from '@comfyorg/litegraph'
import type { Positionable } from '@comfyorg/litegraph/dist/interfaces'
import type { LGraphNode } from '@comfyorg/litegraph'
import { useSettingStore } from '@/stores/settingStore'
function setNodeMode(node: LGraphNode, mode: number) {
node.mode = mode
@@ -12,8 +11,7 @@ function setNodeMode(node: LGraphNode, mode: number) {
}
function addNodesToGroup(group: LGraphGroup, items: Iterable<Positionable>) {
const padding = useSettingStore().get('Comfy.GroupSelectedNodes.Padding')
group.resizeTo([...group.children, ...items], padding)
group.resizeTo([...group.children, ...items])
}
app.registerExtension({
@@ -78,10 +76,7 @@ app.registerExtension({
content: 'Fit Group To Nodes',
callback: () => {
group.recomputeInsideNodes()
const padding = useSettingStore().get(
'Comfy.GroupSelectedNodes.Padding'
)
group.resizeTo(group.children, padding)
group.resizeTo(group.children)
this.graph.change()
}
})

View File

@@ -20,5 +20,3 @@ import './uploadImage'
import './webcamCapture'
import './widgetInputs'
import './uploadAudio'
import './electronAdapter'
import './load3d'

View File

@@ -24,7 +24,7 @@ app.registerExtension({
}
app.ui.settings.addSetting({
id,
category: ['LiteGraph', 'Menu', 'InvertMenuScrolling'],
category: ['Comfy', 'Graph', 'InvertMenuScrolling'],
name: 'Invert Context Menu Scrolling',
type: 'boolean',
defaultValue: false,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,7 @@ app.registerExtension({
// Add setting to control grid size
app.ui.settings.addSetting({
id: 'Comfy.SnapToGrid.GridSize',
category: ['LiteGraph', 'Canvas', 'GridSize'],
category: ['Comfy', 'Graph', 'GridSize'],
name: 'Snap to grid size',
type: 'slider',
attrs: {
@@ -43,7 +43,7 @@ app.registerExtension({
// Using a new setting id can cause existing users to lose their existing settings.
const alwaysSnapToGrid = app.ui.settings.addSetting({
id: 'pysssss.SnapToGrid',
category: ['LiteGraph', 'Canvas', 'AlwaysSnapToGrid'],
category: ['Comfy', 'Graph', 'AlwaysSnapToGrid'],
name: 'Always snap to grid',
type: 'boolean',
defaultValue: false,

View File

@@ -69,54 +69,51 @@ export function deserialiseAndCreate(data: string, canvas: LGraphCanvas): void {
const { graph, graph_mouse } = canvas
canvas.emitBeforeChange()
try {
graph.beforeChange()
graph.beforeChange()
const deserialised = JSON.parse(data)
const deserialised = JSON.parse(data)
// Find the top left point of the boundary of all pasted nodes
const topLeft = [Infinity, Infinity]
for (const { pos } of deserialised.nodes) {
if (topLeft[0] > pos[0]) topLeft[0] = pos[0]
if (topLeft[1] > pos[1]) topLeft[1] = pos[1]
}
// Silent default instead of throw
if (!Number.isFinite(topLeft[0]) || !Number.isFinite(topLeft[1])) {
topLeft[0] = graph_mouse[0]
topLeft[1] = graph_mouse[1]
}
// Create nodes
const nodes: LGraphNode[] = []
for (const info of deserialised.nodes) {
const node = LiteGraph.createNode(info.type)
if (!node) continue
node.configure(info)
// Paste to the bottom right of pointer
node.pos[0] += graph_mouse[0] - topLeft[0]
node.pos[1] += graph_mouse[1] - topLeft[1]
graph.add(node, true)
nodes.push(node)
}
// Create links
for (const info of deserialised.links) {
const relativeId = info[0]
const outNode = relativeId != null ? nodes[relativeId] : undefined
const inNode = nodes[info[2]]
if (outNode && inNode) outNode.connect(info[1], inNode, info[3])
else console.warn('Warning, nodes missing on pasting')
}
canvas.selectNodes(nodes)
graph.afterChange()
} finally {
canvas.emitAfterChange()
// Find the top left point of the boundary of all pasted nodes
const topLeft = [Infinity, Infinity]
for (const { pos } of deserialised.nodes) {
if (topLeft[0] > pos[0]) topLeft[0] = pos[0]
if (topLeft[1] > pos[1]) topLeft[1] = pos[1]
}
// Silent default instead of throw
if (!Number.isFinite(topLeft[0]) || !Number.isFinite(topLeft[1])) {
topLeft[0] = graph_mouse[0]
topLeft[1] = graph_mouse[1]
}
// Create nodes
const nodes: LGraphNode[] = []
for (const info of deserialised.nodes) {
const node = LiteGraph.createNode(info.type)
if (!node) continue
node.configure(info)
// Paste to the bottom right of pointer
node.pos[0] += graph_mouse[0] - topLeft[0]
node.pos[1] += graph_mouse[1] - topLeft[1]
graph.add(node, true)
nodes.push(node)
}
// Create links
for (const info of deserialised.links) {
const relativeId = info[0]
const outNode = relativeId != null ? nodes[relativeId] : undefined
const inNode = nodes[info[2]]
if (outNode && inNode) outNode.connect(info[1], inNode, info[3])
else console.warn('Warning, nodes missing on pasting')
}
canvas.selectNodes(nodes)
graph.afterChange()
canvas.emitAfterChange()
}

View File

@@ -0,0 +1,14 @@
import { useI18n } from 'vue-i18n'
import { markRaw } from 'vue'
import IntegratedTerminal from '@/components/bottomPanel/tabs/IntegratedTerminal.vue'
import { BottomPanelExtension } from '@/types/extensionTypes'
export const useIntegratedTerminalTab = (): BottomPanelExtension => {
const { t } = useI18n()
return {
id: 'integrated-terminal',
title: t('terminal'),
component: markRaw(IntegratedTerminal),
type: 'vue'
}
}

View File

@@ -1,25 +0,0 @@
import { useI18n } from 'vue-i18n'
import { markRaw } from 'vue'
import { BottomPanelExtension } from '@/types/extensionTypes'
import LogsTerminal from '@/components/bottomPanel/tabs/terminal/LogsTerminal.vue'
import CommandTerminal from '@/components/bottomPanel/tabs/terminal/CommandTerminal.vue'
export const useLogsTerminalTab = (): BottomPanelExtension => {
const { t } = useI18n()
return {
id: 'logs-terminal',
title: t('logs'),
component: markRaw(LogsTerminal),
type: 'vue'
}
}
export const useCommandTerminalTab = (): BottomPanelExtension => {
const { t } = useI18n()
return {
id: 'command-terminal',
title: t('terminal'),
component: markRaw(CommandTerminal),
type: 'vue'
}
}

View File

@@ -1,69 +0,0 @@
import { FitAddon } from '@xterm/addon-fit'
import { Terminal } from '@xterm/xterm'
import { debounce } from 'lodash'
import { onMounted, onUnmounted, Ref } from 'vue'
import '@xterm/xterm/css/xterm.css'
export function useTerminal(element: Ref<HTMLElement>) {
const fitAddon = new FitAddon()
const terminal = new Terminal({
convertEol: true
})
terminal.loadAddon(fitAddon)
onMounted(async () => {
terminal.open(element.value)
})
onUnmounted(() => {
terminal.dispose()
})
return {
terminal,
useAutoSize(
root: Ref<HTMLElement>,
autoRows: boolean = true,
autoCols: boolean = true,
onResize?: () => void
) {
const ensureValidRows = (rows: number | undefined) => {
if (rows == null || isNaN(rows)) {
return root.value?.clientHeight / 20
}
return rows
}
const ensureValidCols = (cols: number | undefined): number => {
if (cols == null || isNaN(cols)) {
// Sometimes this is NaN if so, estimate.
return root.value?.clientWidth / 8
}
return cols
}
const resize = () => {
const dims = fitAddon.proposeDimensions()
// Sometimes propose returns NaN, so we may need to estimate.
terminal.resize(
autoCols ? ensureValidCols(dims?.cols) : terminal.cols,
autoRows ? ensureValidRows(dims?.rows) : terminal.rows
)
onResize?.()
}
const resizeObserver = new ResizeObserver(debounce(resize, 25))
onMounted(async () => {
resizeObserver.observe(root.value)
resize()
})
onUnmounted(() => {
resizeObserver.disconnect()
})
return { resize }
}
}
}

View File

@@ -2,28 +2,15 @@ import { markRaw } from 'vue'
import { useI18n } from 'vue-i18n'
import ModelLibrarySidebarTab from '@/components/sidebar/tabs/ModelLibrarySidebarTab.vue'
import type { SidebarTabExtension } from '@/types/extensionTypes'
import { useElectronDownloadStore } from '@/stores/electronDownloadStore'
import { isElectron } from '@/utils/envUtil'
export const useModelLibrarySidebarTab = (): SidebarTabExtension => {
const { t } = useI18n()
return {
id: 'model-library',
icon: 'pi pi-box',
title: t('sideToolbar.modelLibrary'),
tooltip: t('sideToolbar.modelLibrary'),
component: markRaw(ModelLibrarySidebarTab),
type: 'vue',
iconBadge: () => {
if (isElectron()) {
const electronDownloadStore = useElectronDownloadStore()
if (electronDownloadStore.downloads.length > 0) {
return electronDownloadStore.downloads.length.toString()
}
}
return null
}
type: 'vue'
}
}

View File

@@ -57,7 +57,6 @@ const messages = {
loadAllFolders: 'Load All Folders',
refresh: 'Refresh',
terminal: 'Terminal',
logs: 'Logs',
videoFailedToLoad: 'Video failed to load',
extensionName: 'Extension Name',
reloadToApplyChanges: 'Reload to apply changes',

View File

@@ -1227,8 +1227,7 @@ export class ComfyApp {
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown
LGraphCanvas.prototype.processMouseDown = function (e) {
// prepare for ctrl+shift drag: zoom start
const useFastZoom = useSettingStore().get('Comfy.Graph.CtrlShiftZoom')
if (useFastZoom && e.ctrlKey && e.shiftKey && !e.altKey && e.buttons) {
if (e.ctrlKey && e.shiftKey && e.buttons) {
self.zoom_drag_start = [e.x, e.y, this.ds.scale]
return
}
@@ -1573,7 +1572,10 @@ export class ComfyApp {
api.addEventListener('execution_start', ({ detail }) => {
this.lastExecutionError = null
this.graph.nodes.forEach((node) => {
if (node.onExecutionStart) node.onExecutionStart()
// @ts-expect-error
if (node.onExecutionStart)
// @ts-expect-error
node.onExecutionStart()
})
})
@@ -1865,6 +1867,15 @@ export class ComfyApp {
await this.loadGraphData()
}
// Save current workflow automatically
setInterval(() => {
const workflow = JSON.stringify(this.serializeGraph())
localStorage.setItem('workflow', workflow)
if (api.clientId) {
sessionStorage.setItem(`workflow:${api.clientId}`, workflow)
}
}, 1000)
this.#addDrawNodeHandler()
this.#addDrawGroupsHandler()
this.#addDropHandler()
@@ -2396,8 +2407,8 @@ export class ComfyApp {
}
}
const innerNodes = outerNode.getInnerNodes
? outerNode.getInnerNodes()
const innerNodes = outerNode['getInnerNodes']
? outerNode['getInnerNodes']()
: [outerNode]
for (const node of innerNodes) {
if (node.isVirtualNode) {
@@ -2415,8 +2426,8 @@ export class ComfyApp {
for (const outerNode of graph.computeExecutionOrder(false)) {
const skipNode = outerNode.mode === 2 || outerNode.mode === 4
const innerNodes =
!skipNode && outerNode.getInnerNodes
? outerNode.getInnerNodes()
!skipNode && outerNode['getInnerNodes']
? outerNode['getInnerNodes']()
: [outerNode]
for (const node of innerNodes) {
if (node.isVirtualNode) {
@@ -2882,6 +2893,7 @@ export class ComfyApp {
for (let nodeNum in this.graph.nodes) {
const node = this.graph.nodes[nodeNum]
const def = defs[node.type]
// @ts-expect-error
// Allow primitive nodes to handle refresh
node.refreshComboInNode?.(defs)

View File

@@ -99,7 +99,7 @@ export class ChangeTracker {
this.initialState,
this.activeState
)
if (logger.getLevel() <= logger.levels.DEBUG && workflow.isModified) {
if (workflow.isModified) {
const diff = ChangeTracker.graphDiff(
this.initialState,
this.activeState

View File

@@ -1,6 +1,9 @@
import type { Keybinding } from '@/types/keyBindingTypes'
import { NodeBadgeMode } from '@/types/nodeSource'
import { LinkReleaseTriggerAction } from '@/types/searchBoxTypes'
import {
LinkReleaseTriggerAction,
LinkReleaseTriggerMode
} from '@/types/searchBoxTypes'
import type { SettingParams } from '@/types/settingTypes'
import { LinkMarkerShape } from '@comfyorg/litegraph'
import { LiteGraph } from '@comfyorg/litegraph'
@@ -21,9 +24,17 @@ export const CORE_SETTINGS: SettingParams[] = [
options: ['default', 'litegraph (legacy)'],
defaultValue: 'default'
},
{
id: 'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger',
category: ['Comfy', 'Node Search Box', 'LinkReleaseTrigger'],
name: 'Trigger on link release',
type: 'hidden',
options: Object.values(LinkReleaseTriggerMode),
defaultValue: LinkReleaseTriggerMode.ALWAYS,
deprecated: true
},
{
id: 'Comfy.LinkRelease.Action',
category: ['LiteGraph', 'LinkRelease', 'Action'],
name: 'Action on link release (No modifier)',
type: 'combo',
options: Object.values(LinkReleaseTriggerAction),
@@ -31,7 +42,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.LinkRelease.ActionShift',
category: ['LiteGraph', 'LinkRelease', 'ActionShift'],
name: 'Action on link release (Shift)',
type: 'combo',
options: Object.values(LinkReleaseTriggerAction),
@@ -71,7 +81,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Sidebar.Location',
category: ['Appearance', 'Sidebar', 'Location'],
category: ['Comfy', 'Sidebar', 'Location'],
name: 'Sidebar location',
type: 'combo',
options: ['left', 'right'],
@@ -79,7 +89,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Sidebar.Size',
category: ['Appearance', 'Sidebar', 'Size'],
category: ['Comfy', 'Sidebar', 'Size'],
name: 'Sidebar size',
type: 'combo',
options: ['normal', 'small'],
@@ -87,7 +97,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.TextareaWidget.FontSize',
category: ['Appearance', 'Node Widget', 'TextareaWidget', 'FontSize'],
category: ['Comfy', 'Node Widget', 'TextareaWidget', 'FontSize'],
name: 'Textarea widget font size',
type: 'slider',
defaultValue: 10,
@@ -111,8 +121,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Graph.CanvasInfo',
category: ['LiteGraph', 'Canvas', 'CanvasInfo'],
name: 'Show canvas info on bottom left corner (fps, etc.)',
name: 'Show canvas info (fps, etc.)',
type: 'boolean',
defaultValue: true
},
@@ -134,7 +143,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.Opacity',
category: ['Appearance', 'Node', 'Opacity'],
name: 'Node opacity',
type: 'slider',
defaultValue: 1,
@@ -159,7 +167,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Graph.ZoomSpeed',
category: ['LiteGraph', 'Canvas', 'ZoomSpeed'],
name: 'Canvas zoom speed',
type: 'slider',
defaultValue: 1.1,
@@ -200,7 +207,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.GroupSelectedNodes.Padding',
category: ['LiteGraph', 'Group', 'Padding'],
name: 'Group selected nodes padding',
type: 'slider',
defaultValue: 10,
@@ -211,14 +217,12 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.DoubleClickTitleToEdit',
category: ['LiteGraph', 'Node', 'DoubleClickTitleToEdit'],
name: 'Double click node title to edit',
type: 'boolean',
defaultValue: true
},
{
id: 'Comfy.Group.DoubleClickTitleToEdit',
category: ['LiteGraph', 'Group', 'DoubleClickTitleToEdit'],
name: 'Double click group title to edit',
type: 'boolean',
defaultValue: true
@@ -266,7 +270,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.NodeBadge.NodeSourceBadgeMode',
category: ['LiteGraph', 'Node', 'NodeSourceBadgeMode'],
name: 'Node source badge mode',
type: 'combo',
options: Object.values(NodeBadgeMode),
@@ -274,7 +277,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.NodeBadge.NodeIdBadgeMode',
category: ['LiteGraph', 'Node', 'NodeIdBadgeMode'],
name: 'Node ID badge mode',
type: 'combo',
options: [NodeBadgeMode.None, NodeBadgeMode.ShowAll],
@@ -282,7 +284,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.NodeBadge.NodeLifeCycleBadgeMode',
category: ['LiteGraph', 'Node', 'NodeLifeCycleBadgeMode'],
name: 'Node life cycle badge mode',
type: 'combo',
options: [NodeBadgeMode.None, NodeBadgeMode.ShowAll],
@@ -315,7 +316,7 @@ export const CORE_SETTINGS: SettingParams[] = [
*/
{
id: 'Comfy.PreviewFormat',
category: ['LiteGraph', 'Node Widget', 'PreviewFormat'],
category: ['Comfy', 'Node Widget', 'PreviewFormat'],
name: 'Preview image format',
tooltip:
'When displaying a preview in the image widget, convert it to a lightweight image, e.g. webp, jpeg, webp;50, etc.',
@@ -324,14 +325,14 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.DisableSliders',
category: ['LiteGraph', 'Node Widget', 'DisableSliders'],
category: ['Comfy', 'Node Widget', 'DisableSliders'],
name: 'Disable node widget sliders',
type: 'boolean',
defaultValue: false
},
{
id: 'Comfy.DisableFloatRounding',
category: ['LiteGraph', 'Node Widget', 'DisableFloatRounding'],
category: ['Comfy', 'Node Widget', 'DisableFloatRounding'],
name: 'Disable default float widget rounding.',
tooltip:
'(requires page reload) Cannot disable round when round is set by the node in the backend.',
@@ -340,7 +341,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.FloatRoundingPrecision',
category: ['LiteGraph', 'Node Widget', 'FloatRoundingPrecision'],
category: ['Comfy', 'Node Widget', 'FloatRoundingPrecision'],
name: 'Float widget rounding decimal places [0 = auto].',
tooltip: '(requires page reload)',
type: 'slider',
@@ -353,7 +354,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.EnableTooltips',
category: ['LiteGraph', 'Node', 'EnableTooltips'],
category: ['Comfy', 'Node', 'EnableTooltips'],
name: 'Enable Tooltips',
type: 'boolean',
defaultValue: true
@@ -394,7 +395,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Graph.CanvasMenu',
category: ['LiteGraph', 'Canvas', 'CanvasMenu'],
name: 'Show graph canvas menu',
type: 'boolean',
defaultValue: true
@@ -448,7 +448,7 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.LinkRenderMode',
category: ['LiteGraph', 'Graph', 'LinkRenderMode'],
category: ['Comfy', 'Graph', 'LinkRenderMode'],
name: 'Link Render Mode',
defaultValue: 2,
type: 'combo',
@@ -461,7 +461,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.AutoSnapLinkToSlot',
category: ['LiteGraph', 'Node', 'AutoSnapLinkToSlot'],
name: 'Auto snap link to node slot',
tooltip:
'When dragging a link over a node, the link automatically snap to a viable input slot on the node',
@@ -471,7 +470,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.SnapHighlightsNode',
category: ['LiteGraph', 'Node', 'SnapHighlightsNode'],
name: 'Snap highlights node',
tooltip:
'When dragging a link over a node with viable input slot, highlight the node',
@@ -481,7 +479,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.BypassAllLinksOnDelete',
category: ['LiteGraph', 'Node', 'BypassAllLinksOnDelete'],
name: 'Keep all links when deleting nodes',
tooltip:
'When deleting a node, attempt to reconnect all of its input and output links (bypassing the deleted node)',
@@ -491,7 +488,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Node.MiddleClickRerouteNode',
category: ['LiteGraph', 'Node', 'MiddleClickRerouteNode'],
name: 'Middle-click creates a new Reroute node',
type: 'boolean',
defaultValue: true,
@@ -499,7 +495,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.RerouteBeta',
category: ['LiteGraph', 'RerouteBeta'],
name: 'Opt-in to the reroute beta test',
tooltip:
'Enables the new native reroutes.\n\nReroutes can be added by holding alt and dragging from a link line, or on the link menu.\n\nDisabling this option is non-destructive - reroutes are hidden.',
@@ -510,7 +505,6 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.Graph.LinkMarkers',
category: ['LiteGraph', 'Link', 'LinkMarkers'],
name: 'Link midpoint markers',
defaultValue: LinkMarkerShape.Circle,
type: 'combo',
@@ -523,17 +517,9 @@ export const CORE_SETTINGS: SettingParams[] = [
},
{
id: 'Comfy.DOMClippingEnabled',
category: ['LiteGraph', 'Node', 'DOMClippingEnabled'],
category: ['Comfy', 'Node', 'DOMClippingEnabled'],
name: 'Enable DOM element clipping (enabling may reduce performance)',
type: 'boolean',
defaultValue: true
},
{
id: 'Comfy.Graph.CtrlShiftZoom',
category: ['LiteGraph', 'Canvas', 'CtrlShiftZoom'],
name: 'Enable fast-zoom shortcut (Ctrl + Shift + Drag)',
type: 'boolean',
defaultValue: true,
versionAdded: '1.4.0'
}
]

View File

@@ -2,12 +2,8 @@ import type { BottomPanelExtension } from '@/types/extensionTypes'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useCommandStore } from '@/stores/commandStore'
import {
useLogsTerminalTab,
useCommandTerminalTab
} from '@/hooks/bottomPanelTabs/terminalTabs'
import { useIntegratedTerminalTab } from '@/hooks/bottomPanelTabs/integratedTerminalTab'
import { ComfyExtension } from '@/types/comfy'
import { isElectron } from '@/utils/envUtil'
export const useBottomPanelStore = defineStore('bottomPanel', () => {
const bottomPanelVisible = ref(false)
@@ -53,10 +49,7 @@ export const useBottomPanelStore = defineStore('bottomPanel', () => {
}
const registerCoreBottomPanelTabs = () => {
registerBottomPanelTab(useLogsTerminalTab())
if (isElectron()) {
registerBottomPanelTab(useCommandTerminalTab())
}
registerBottomPanelTab(useIntegratedTerminalTab())
}
const registerExtensionBottomPanelTabs = (extension: ComfyExtension) => {

View File

@@ -484,6 +484,11 @@ const zSettings = z.record(z.any()).and(
zBookmarkCustomization
),
'Comfy.NodeInputConversionSubmenus': z.boolean(),
'Comfy.NodeSearchBoxImpl.LinkReleaseTrigger': z.enum([
'always',
'hold shift',
'NOT hold shift'
]),
'Comfy.LinkRelease.Action': zLinkReleaseTriggerAction,
'Comfy.LinkRelease.ActionShift': zLinkReleaseTriggerAction,
'Comfy.NodeSearchBoxImpl.NodePreview': z.boolean(),

View File

@@ -1,7 +1,6 @@
import '@comfyorg/litegraph'
import type { ComfyNodeDef } from '@/types/apiTypes'
import type { LLink } from '@comfyorg/litegraph'
import type { NodeId } from './comfyWorkflow'
/**
* ComfyUI extensions of litegraph
@@ -27,17 +26,8 @@ declare module '@comfyorg/litegraph' {
onExecuted?(output: any): void
onNodeCreated?(this: LGraphNode): void
setInnerNodes?(nodes: LGraphNode[]): void
// TODO: Requires several coercion changes to runtime code.
getInnerNodes?() // : LGraphNode[]
convertToNodes?(): LGraphNode[]
recreate?(): Promise<LGraphNode>
refreshComboInNode?(defs: Record<string, ComfyNodeDef>)
applyToGraph?(extraLinks?: LLink[]): void
updateLink?(link: LLink): LLink | null
onExecutionStart?(): unknown
index?: number
runningInternalNodeId?: NodeId
comfyClass?: string

View File

@@ -11,8 +11,6 @@ dotenv.config()
const IS_DEV = process.env.NODE_ENV === 'development'
const SHOULD_MINIFY = process.env.ENABLE_MINIFY === 'true'
// vite dev server will listen on all addresses, including LAN and public addresses
const VITE_REMOTE_DEV = process.env.VITE_REMOTE_DEV === 'true'
interface ShimResult {
code: string
@@ -96,7 +94,7 @@ const DEV_SERVER_COMFYUI_URL = process.env.DEV_SERVER_COMFYUI_URL || 'http://127
export default defineConfig({
base: '',
server: {
host: VITE_REMOTE_DEV ? '0.0.0.0' : undefined,
host: '0.0.0.0',
proxy: {
'/internal': {
target: DEV_SERVER_COMFYUI_URL,