Compare commits
2 Commits
v1.4.2
...
node-templ
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
782d93a7a0 | ||
|
|
7be14c5189 |
@@ -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`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
```
|
||||
|
||||
10
browser_tests/assets/node_template_templates.json
Normal 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\":[]}"
|
||||
}
|
||||
]
|
||||
6
browser_tests/assets/vintage_clipboard_template.json
Normal 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]]}"
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 152 KiB |
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 38 KiB |
70
browser_tests/nodeTemplate.spec.ts
Normal 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()
|
||||
})
|
||||
})
|
||||
@@ -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
@@ -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",
|
||||
|
||||
12
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 373 B |
|
Before Width: | Height: | Size: 410 B |
@@ -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
|
||||
|
||||
104
src/components/bottomPanel/tabs/IntegratedTerminal.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
:modelValue="modelValue"
|
||||
@update:modelValue="updateValue"
|
||||
class="input-part"
|
||||
:max-fraction-digits="3"
|
||||
:class="inputClass"
|
||||
:min="min"
|
||||
:max="max"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
})
|
||||
})()
|
||||
@@ -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']?.()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -20,5 +20,3 @@ import './uploadImage'
|
||||
import './webcamCapture'
|
||||
import './widgetInputs'
|
||||
import './uploadAudio'
|
||||
import './electronAdapter'
|
||||
import './load3d'
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
14
src/hooks/bottomPanelTabs/integratedTerminalTab.ts
Normal 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'
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
10
src/types/litegraph-augmentation.d.ts
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||