Compare commits
15 Commits
node-templ
...
v1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
227db065f3 | ||
|
|
b4352bcd8d | ||
|
|
39bab9d9e2 | ||
|
|
c71644f02f | ||
|
|
6aad7ee8b6 | ||
|
|
2b96d831fc | ||
|
|
dde0291add | ||
|
|
8af016ffc1 | ||
|
|
82b4547d7d | ||
|
|
791a25637f | ||
|
|
b922aa5c7c | ||
|
|
cbaebbc9c2 | ||
|
|
86b2e1aa6c | ||
|
|
61c5f05126 | ||
|
|
dde9c3dad5 |
@@ -6,6 +6,11 @@ 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`
|
||||
|
||||
@@ -3,3 +3,4 @@ if [[ "$OS" == "Windows_NT" ]]; then
|
||||
else
|
||||
npx lint-staged
|
||||
fi
|
||||
npm run typecheck
|
||||
|
||||
@@ -431,6 +431,8 @@ 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:
|
||||
|
||||
```
|
||||
|
||||
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 138 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: 158 KiB After Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 151 KiB |
@@ -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,8 +77,13 @@ test.describe('Group Node', () => {
|
||||
.click()
|
||||
})
|
||||
})
|
||||
|
||||
test('Can be added to canvas using search', async ({ comfyPage }) => {
|
||||
// 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 }) => {
|
||||
const groupNodeName = 'DefautWorkflowGroupNode'
|
||||
await comfyPage.convertAllNodesToGroupNode(groupNodeName)
|
||||
await comfyPage.doubleClickCanvas()
|
||||
|
||||
26
package-lock.json
generated
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "comfyui-frontend",
|
||||
"version": "1.3.43",
|
||||
"version": "1.4.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "comfyui-frontend",
|
||||
"version": "1.3.43",
|
||||
"version": "1.4.0",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.2.16",
|
||||
"@comfyorg/litegraph": "^0.8.26",
|
||||
"@comfyorg/litegraph": "^0.8.27",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
@@ -63,7 +63,6 @@
|
||||
"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",
|
||||
@@ -1922,9 +1921,9 @@
|
||||
"license": "GPL-3.0-only"
|
||||
},
|
||||
"node_modules/@comfyorg/litegraph": {
|
||||
"version": "0.8.26",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.26.tgz",
|
||||
"integrity": "sha512-q0Vcd5usphR5nghfyFksVx+VM+eSB1MyX8Ne304KFDnr214KQMA6DAjrEQJlGBUUCybLiOtPCvd3dxPecEQiSQ==",
|
||||
"version": "0.8.27",
|
||||
"resolved": "https://registry.npmjs.org/@comfyorg/litegraph/-/litegraph-0.8.27.tgz",
|
||||
"integrity": "sha512-EMQ3jsny+3gUQL4+vSVwJAFxrLq4IpuyjCvAiErLY4wLZZu2Mi+7cELmhrNS0MajhZqfN1M0GPmdcBRwSWbarw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
@@ -12170,19 +12169,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
||||
10
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "comfyui-frontend",
|
||||
"private": true,
|
||||
"version": "1.3.43",
|
||||
"version": "1.4.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -58,7 +58,6 @@
|
||||
"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 +72,7 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.3.1",
|
||||
"@comfyorg/comfyui-electron-types": "^0.2.16",
|
||||
"@comfyorg/litegraph": "^0.8.26",
|
||||
"@comfyorg/litegraph": "^0.8.27",
|
||||
"@primevue/themes": "^4.0.5",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
@@ -94,7 +93,8 @@
|
||||
"zod-validation-error": "^3.3.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"./**/*.{js,ts,tsx,vue}": "prettier --write",
|
||||
"**/*.ts": "tsc-files --noEmit"
|
||||
"./**/*.{js,ts,tsx,vue}": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Tree
|
||||
class="tree-explorer p-2 2xl:p-4"
|
||||
class="tree-explorer py-0 px-2 2xl:px-4"
|
||||
:class="props.class"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectionKeys="selectionKeys"
|
||||
|
||||
@@ -262,6 +262,7 @@ 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 () => {
|
||||
const validatePath = async (path: string) => {
|
||||
try {
|
||||
pathError.value = ''
|
||||
const validation = await electron.validateInstallPath(installPath.value)
|
||||
const validation = await electron.validateInstallPath(path)
|
||||
|
||||
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()
|
||||
await validatePath(result)
|
||||
}
|
||||
} catch (error) {
|
||||
pathError.value = t('install.failedToSelectDirectory')
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template>
|
||||
<div class="comfy-vue-node-search-container">
|
||||
<div class="comfy-vue-node-preview-container" v-if="enableNodePreview">
|
||||
<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"
|
||||
>
|
||||
<NodePreview
|
||||
:nodeDef="hoveredSuggestion"
|
||||
:key="hoveredSuggestion?.name || ''"
|
||||
@@ -11,10 +16,10 @@
|
||||
<Button
|
||||
icon="pi pi-filter"
|
||||
severity="secondary"
|
||||
class="_filter-button"
|
||||
class="filter-button z-10"
|
||||
@click="nodeSearchFilterVisible = true"
|
||||
/>
|
||||
<Dialog v-model:visible="nodeSearchFilterVisible" class="_dialog">
|
||||
<Dialog v-model:visible="nodeSearchFilterVisible" class="min-w-96">
|
||||
<template #header>
|
||||
<h3>Add node filter condition</h3>
|
||||
</template>
|
||||
@@ -25,7 +30,7 @@
|
||||
|
||||
<AutoCompletePlus
|
||||
:model-value="props.filters"
|
||||
class="comfy-vue-node-search-box"
|
||||
class="comfy-vue-node-search-box z-10 flex-grow"
|
||||
scrollHeight="40vh"
|
||||
:placeholder="placeholder"
|
||||
:input-id="inputId"
|
||||
@@ -148,31 +153,3 @@ 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 py-0"
|
||||
class="model-lib-tree-explorer"
|
||||
:roots="renderedRoot.children"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
class="m-2"
|
||||
/>
|
||||
<TreeExplorer
|
||||
class="node-lib-tree-explorer py-0"
|
||||
class="node-lib-tree-explorer"
|
||||
:roots="renderedRoot.children"
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<TreeExplorer
|
||||
class="node-lib-bookmark-tree-explorer py-0"
|
||||
class="node-lib-bookmark-tree-explorer"
|
||||
ref="treeExplorerRef"
|
||||
:roots="renderedBookmarkedRoot.children"
|
||||
:expandedKeys="expandedKeys"
|
||||
|
||||
138
src/extensions/core/electronAdapter.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
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,6 +13,7 @@ import {
|
||||
deserialiseAndCreate,
|
||||
serialise
|
||||
} from '@/extensions/core/vintageClipboard'
|
||||
import type { ComfyNodeDef } from '@/types/apiTypes'
|
||||
|
||||
type GroupNodeWorkflowData = {
|
||||
external: ComfyLink[]
|
||||
@@ -56,7 +57,7 @@ const Workflow = {
|
||||
|
||||
class GroupNodeBuilder {
|
||||
nodes: LGraphNode[]
|
||||
nodeData: any
|
||||
nodeData: GroupNodeWorkflowData
|
||||
|
||||
constructor(nodes: LGraphNode[]) {
|
||||
this.nodes = nodes
|
||||
@@ -175,7 +176,7 @@ export class GroupNodeConfig {
|
||||
primitiveToWidget: {}
|
||||
nodeInputs: {}
|
||||
outputVisibility: any[]
|
||||
nodeDef: any
|
||||
nodeDef: ComfyNodeDef
|
||||
inputs: any[]
|
||||
linksFrom: {}
|
||||
linksTo: {}
|
||||
@@ -204,6 +205,7 @@ 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,
|
||||
@@ -695,11 +697,11 @@ export class GroupNodeConfig {
|
||||
}
|
||||
|
||||
export class GroupNodeHandler {
|
||||
node
|
||||
node: LGraphNode
|
||||
groupData
|
||||
innerNodes: any
|
||||
|
||||
constructor(node) {
|
||||
constructor(node: LGraphNode) {
|
||||
this.node = node
|
||||
this.groupData = node.constructor?.nodeData?.[GROUP]
|
||||
|
||||
@@ -774,6 +776,7 @@ 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]
|
||||
@@ -965,17 +968,20 @@ export class GroupNodeHandler {
|
||||
|
||||
app.canvas.emitBeforeChange()
|
||||
|
||||
const { newNodes, selectedIds } = addInnerNodes()
|
||||
reconnectInputs(selectedIds)
|
||||
reconnectOutputs(selectedIds)
|
||||
app.graph.remove(this.node)
|
||||
try {
|
||||
const { newNodes, selectedIds } = addInnerNodes()
|
||||
reconnectInputs(selectedIds)
|
||||
reconnectOutputs(selectedIds)
|
||||
app.graph.remove(this.node)
|
||||
|
||||
app.canvas.emitAfterChange()
|
||||
|
||||
return newNodes
|
||||
return newNodes
|
||||
} finally {
|
||||
app.canvas.emitAfterChange()
|
||||
}
|
||||
}
|
||||
|
||||
const getExtraMenuOptions = this.node.getExtraMenuOptions
|
||||
// @ts-expect-error Should pass patched return value getExtraMenuOptions
|
||||
this.node.getExtraMenuOptions = function (_, options) {
|
||||
getExtraMenuOptions?.apply(this, arguments)
|
||||
|
||||
@@ -988,6 +994,7 @@ export class GroupNodeHandler {
|
||||
null,
|
||||
{
|
||||
content: 'Convert to nodes',
|
||||
// @ts-expect-error
|
||||
callback: () => {
|
||||
return this.convertToNodes()
|
||||
}
|
||||
@@ -1148,6 +1155,7 @@ export class GroupNodeHandler {
|
||||
|
||||
if (
|
||||
old.inputName !== 'image' &&
|
||||
// @ts-expect-error Widget values
|
||||
!widget.options.values.includes(widget.value)
|
||||
) {
|
||||
widget.value = widget.options.values[0]
|
||||
@@ -1354,6 +1362,7 @@ 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]
|
||||
)
|
||||
@@ -1475,7 +1484,7 @@ function ungroupSelectedGroupNodes() {
|
||||
const nodes = Object.values(app.canvas.selected_nodes ?? {})
|
||||
for (const node of nodes) {
|
||||
if (GroupNodeHandler.isGroupNode(node)) {
|
||||
node['convertToNodes']?.()
|
||||
node.convertToNodes?.()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,3 +20,4 @@ import './uploadImage'
|
||||
import './webcamCapture'
|
||||
import './widgetInputs'
|
||||
import './uploadAudio'
|
||||
import './electronAdapter'
|
||||
|
||||
@@ -69,51 +69,54 @@ export function deserialiseAndCreate(data: string, canvas: LGraphCanvas): void {
|
||||
|
||||
const { graph, graph_mouse } = canvas
|
||||
canvas.emitBeforeChange()
|
||||
graph.beforeChange()
|
||||
try {
|
||||
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]
|
||||
// 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()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@@ -1227,7 +1227,8 @@ export class ComfyApp {
|
||||
const origProcessMouseDown = LGraphCanvas.prototype.processMouseDown
|
||||
LGraphCanvas.prototype.processMouseDown = function (e) {
|
||||
// prepare for ctrl+shift drag: zoom start
|
||||
if (e.ctrlKey && e.shiftKey && e.buttons) {
|
||||
const useFastZoom = useSettingStore().get('Comfy.Graph.CtrlShiftZoom')
|
||||
if (useFastZoom && e.ctrlKey && e.shiftKey && !e.altKey && e.buttons) {
|
||||
self.zoom_drag_start = [e.x, e.y, this.ds.scale]
|
||||
return
|
||||
}
|
||||
@@ -1572,10 +1573,7 @@ export class ComfyApp {
|
||||
api.addEventListener('execution_start', ({ detail }) => {
|
||||
this.lastExecutionError = null
|
||||
this.graph.nodes.forEach((node) => {
|
||||
// @ts-expect-error
|
||||
if (node.onExecutionStart)
|
||||
// @ts-expect-error
|
||||
node.onExecutionStart()
|
||||
if (node.onExecutionStart) node.onExecutionStart()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2407,8 +2405,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) {
|
||||
@@ -2426,8 +2424,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) {
|
||||
@@ -2893,7 +2891,6 @@ 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)
|
||||
|
||||
|
||||
@@ -521,5 +521,12 @@ export const CORE_SETTINGS: SettingParams[] = [
|
||||
name: 'Enable DOM element clipping (enabling may reduce performance)',
|
||||
type: 'boolean',
|
||||
defaultValue: true
|
||||
},
|
||||
{
|
||||
id: 'Comfy.Graph.CtrlShiftZoom',
|
||||
name: 'Enable fast-zoom shortcut (Ctrl + Shift + Drag)',
|
||||
type: 'boolean',
|
||||
defaultValue: true,
|
||||
versionAdded: '1.4.0'
|
||||
}
|
||||
]
|
||||
|
||||
10
src/types/litegraph-augmentation.d.ts
vendored
@@ -1,6 +1,7 @@
|
||||
import '@comfyorg/litegraph'
|
||||
import type { ComfyNodeDef } from '@/types/apiTypes'
|
||||
import type { LLink } from '@comfyorg/litegraph'
|
||||
import type { NodeId } from './comfyWorkflow'
|
||||
|
||||
/**
|
||||
* ComfyUI extensions of litegraph
|
||||
@@ -26,8 +27,17 @@ 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,6 +11,8 @@ 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
|
||||
@@ -94,7 +96,7 @@ const DEV_SERVER_COMFYUI_URL = process.env.DEV_SERVER_COMFYUI_URL || 'http://127
|
||||
export default defineConfig({
|
||||
base: '',
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
host: VITE_REMOTE_DEV ? '0.0.0.0' : undefined,
|
||||
proxy: {
|
||||
'/internal': {
|
||||
target: DEV_SERVER_COMFYUI_URL,
|
||||
|
||||