|
|
|
|
@@ -1,13 +1,15 @@
|
|
|
|
|
import { useResizeObserver } from '@vueuse/core'
|
|
|
|
|
import _ from 'es-toolkit/compat'
|
|
|
|
|
import type { ToastMessageOptions } from 'primevue/toast'
|
|
|
|
|
import { reactive } from 'vue'
|
|
|
|
|
import { reactive, unref } from 'vue'
|
|
|
|
|
import { shallowRef } from 'vue'
|
|
|
|
|
|
|
|
|
|
import { useCanvasPositionConversion } from '@/composables/element/useCanvasPositionConversion'
|
|
|
|
|
import { registerProxyWidgets } from '@/core/graph/subgraph/proxyWidget'
|
|
|
|
|
import { st, t } from '@/i18n'
|
|
|
|
|
import {
|
|
|
|
|
LGraph,
|
|
|
|
|
LGraphCanvas,
|
|
|
|
|
LGraphEventMode,
|
|
|
|
|
LGraphNode,
|
|
|
|
|
LiteGraph
|
|
|
|
|
} from '@/lib/litegraph/src/litegraph'
|
|
|
|
|
@@ -58,12 +60,10 @@ import { useModelStore } from '@/stores/modelStore'
|
|
|
|
|
import { SYSTEM_NODE_DEFS, useNodeDefStore } from '@/stores/nodeDefStore'
|
|
|
|
|
import { useSubgraphStore } from '@/stores/subgraphStore'
|
|
|
|
|
import { useWidgetStore } from '@/stores/widgetStore'
|
|
|
|
|
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
|
|
|
|
|
import { useWorkspaceStore } from '@/stores/workspaceStore'
|
|
|
|
|
import type { ComfyExtension, MissingNodeType } from '@/types/comfy'
|
|
|
|
|
import { type ExtensionManager } from '@/types/extensionTypes'
|
|
|
|
|
import type { NodeExecutionId } from '@/types/nodeIdentification'
|
|
|
|
|
import { type ColorAdjustOptions, adjustColor } from '@/utils/colorUtil'
|
|
|
|
|
import { graphToPrompt } from '@/utils/executionUtil'
|
|
|
|
|
import { forEachNode } from '@/utils/graphTraversalUtil'
|
|
|
|
|
import {
|
|
|
|
|
@@ -129,7 +129,7 @@ export class ComfyApp {
|
|
|
|
|
/**
|
|
|
|
|
* List of entries to queue
|
|
|
|
|
*/
|
|
|
|
|
#queueItems: {
|
|
|
|
|
private queueItems: {
|
|
|
|
|
number: number
|
|
|
|
|
batchCount: number
|
|
|
|
|
queueNodeIds?: NodeExecutionId[]
|
|
|
|
|
@@ -137,7 +137,7 @@ export class ComfyApp {
|
|
|
|
|
/**
|
|
|
|
|
* If the queue is currently being processed
|
|
|
|
|
*/
|
|
|
|
|
#processingQueue: boolean = false
|
|
|
|
|
private processingQueue: boolean = false
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Content Clipboard
|
|
|
|
|
@@ -176,12 +176,15 @@ export class ComfyApp {
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
canvas: LGraphCanvas
|
|
|
|
|
dragOverNode: LGraphNode | null = null
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
canvasEl: HTMLCanvasElement
|
|
|
|
|
readonly canvasElRef = shallowRef<HTMLCanvasElement>()
|
|
|
|
|
get canvasEl() {
|
|
|
|
|
// TODO: Fix possibly undefined reference
|
|
|
|
|
return unref(this.canvasElRef)!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#configuringGraphLevel: number = 0
|
|
|
|
|
private configuringGraphLevel: number = 0
|
|
|
|
|
get configuringGraph() {
|
|
|
|
|
return this.#configuringGraphLevel > 0
|
|
|
|
|
return this.configuringGraphLevel > 0
|
|
|
|
|
}
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
ctx: CanvasRenderingContext2D
|
|
|
|
|
@@ -191,11 +194,10 @@ export class ComfyApp {
|
|
|
|
|
bodyBottom: HTMLElement
|
|
|
|
|
canvasContainer: HTMLElement
|
|
|
|
|
menu: ComfyAppMenu
|
|
|
|
|
bypassBgColor: string
|
|
|
|
|
// Set by Comfy.Clipspace extension
|
|
|
|
|
openClipspace: () => void = () => {}
|
|
|
|
|
|
|
|
|
|
#positionConversion?: {
|
|
|
|
|
private positionConversion?: {
|
|
|
|
|
clientPosToCanvasPos: (pos: Vector2) => Vector2
|
|
|
|
|
canvasPosToClientPos: (pos: Vector2) => Vector2
|
|
|
|
|
}
|
|
|
|
|
@@ -296,7 +298,6 @@ export class ComfyApp {
|
|
|
|
|
this.canvasContainer = $el('div.graph-canvas-container')
|
|
|
|
|
|
|
|
|
|
this.menu = new ComfyAppMenu(this)
|
|
|
|
|
this.bypassBgColor = '#FF00FF'
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Stores the execution output data for each node
|
|
|
|
|
@@ -525,7 +526,7 @@ export class ComfyApp {
|
|
|
|
|
/**
|
|
|
|
|
* Adds a handler allowing drag+drop of files onto the window to load workflows
|
|
|
|
|
*/
|
|
|
|
|
#addDropHandler() {
|
|
|
|
|
private addDropHandler() {
|
|
|
|
|
// Get prompt from dropped PNG or json
|
|
|
|
|
document.addEventListener('drop', async (event) => {
|
|
|
|
|
try {
|
|
|
|
|
@@ -601,7 +602,7 @@ export class ComfyApp {
|
|
|
|
|
/**
|
|
|
|
|
* Handle keypress
|
|
|
|
|
*/
|
|
|
|
|
#addProcessKeyHandler() {
|
|
|
|
|
private addProcessKeyHandler() {
|
|
|
|
|
const origProcessKey = LGraphCanvas.prototype.processKey
|
|
|
|
|
LGraphCanvas.prototype.processKey = function (e: KeyboardEvent) {
|
|
|
|
|
if (!this.graph) return
|
|
|
|
|
@@ -643,61 +644,14 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall through to Litegraph defaults
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
return origProcessKey.apply(this, arguments)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#addDrawNodeHandler() {
|
|
|
|
|
const origDrawNode = LGraphCanvas.prototype.drawNode
|
|
|
|
|
LGraphCanvas.prototype.drawNode = function (node) {
|
|
|
|
|
const editor_alpha = this.editor_alpha
|
|
|
|
|
const old_color = node.color
|
|
|
|
|
const old_bgcolor = node.bgcolor
|
|
|
|
|
|
|
|
|
|
if (node.mode === LGraphEventMode.NEVER) {
|
|
|
|
|
this.editor_alpha = 0.4
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let bgColor: string
|
|
|
|
|
if (node.mode === LGraphEventMode.BYPASS) {
|
|
|
|
|
bgColor = app.bypassBgColor
|
|
|
|
|
this.editor_alpha = 0.2
|
|
|
|
|
} else {
|
|
|
|
|
bgColor = old_bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const adjustments: ColorAdjustOptions = {}
|
|
|
|
|
|
|
|
|
|
const opacity = useSettingStore().get('Comfy.Node.Opacity')
|
|
|
|
|
if (opacity) adjustments.opacity = opacity
|
|
|
|
|
|
|
|
|
|
if (useColorPaletteStore().completedActivePalette.light_theme) {
|
|
|
|
|
if (old_bgcolor) adjustments.lightness = 0.5
|
|
|
|
|
|
|
|
|
|
// Lighten title bar of colored nodes on light theme
|
|
|
|
|
if (old_color) {
|
|
|
|
|
node.color = adjustColor(old_color, { lightness: 0.5 })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node.bgcolor = adjustColor(bgColor, adjustments)
|
|
|
|
|
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
const res = origDrawNode.apply(this, arguments)
|
|
|
|
|
|
|
|
|
|
this.editor_alpha = editor_alpha
|
|
|
|
|
node.color = old_color
|
|
|
|
|
node.bgcolor = old_bgcolor
|
|
|
|
|
|
|
|
|
|
return res
|
|
|
|
|
return origProcessKey.apply(this, [e])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Handles updates from the API socket
|
|
|
|
|
*/
|
|
|
|
|
#addApiUpdateHandlers() {
|
|
|
|
|
private addApiUpdateHandlers() {
|
|
|
|
|
api.addEventListener('status', ({ detail }) => {
|
|
|
|
|
this.ui.setStatus(detail)
|
|
|
|
|
})
|
|
|
|
|
@@ -771,15 +725,15 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Flag that the graph is configuring to prevent nodes from running checks while its still loading */
|
|
|
|
|
#addConfigureHandler() {
|
|
|
|
|
private addConfigureHandler() {
|
|
|
|
|
const app = this
|
|
|
|
|
const configure = LGraph.prototype.configure
|
|
|
|
|
LGraph.prototype.configure = function (...args) {
|
|
|
|
|
app.#configuringGraphLevel++
|
|
|
|
|
app.configuringGraphLevel++
|
|
|
|
|
try {
|
|
|
|
|
return configure.apply(this, args)
|
|
|
|
|
} finally {
|
|
|
|
|
app.#configuringGraphLevel--
|
|
|
|
|
app.configuringGraphLevel--
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -816,16 +770,15 @@ export class ComfyApp {
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
this.canvasContainer = document.getElementById('graph-canvas-container')
|
|
|
|
|
|
|
|
|
|
this.canvasEl = canvasEl
|
|
|
|
|
this.resizeCanvas()
|
|
|
|
|
this.canvasElRef.value = canvasEl
|
|
|
|
|
|
|
|
|
|
await useWorkspaceStore().workflow.syncWorkflows()
|
|
|
|
|
await useSubgraphStore().fetchSubgraphs()
|
|
|
|
|
await useExtensionService().loadExtensions()
|
|
|
|
|
|
|
|
|
|
this.#addProcessKeyHandler()
|
|
|
|
|
this.#addConfigureHandler()
|
|
|
|
|
this.#addApiUpdateHandlers()
|
|
|
|
|
this.addProcessKeyHandler()
|
|
|
|
|
this.addConfigureHandler()
|
|
|
|
|
this.addApiUpdateHandlers()
|
|
|
|
|
|
|
|
|
|
const graph = new LGraph()
|
|
|
|
|
|
|
|
|
|
@@ -887,43 +840,41 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
registerProxyWidgets(this.canvas)
|
|
|
|
|
|
|
|
|
|
this.graph.start()
|
|
|
|
|
|
|
|
|
|
// Ensure the canvas fills the window
|
|
|
|
|
this.resizeCanvas()
|
|
|
|
|
window.addEventListener('resize', () => this.resizeCanvas())
|
|
|
|
|
const ro = new ResizeObserver(() => this.resizeCanvas())
|
|
|
|
|
ro.observe(this.bodyTop)
|
|
|
|
|
ro.observe(this.bodyLeft)
|
|
|
|
|
ro.observe(this.bodyRight)
|
|
|
|
|
ro.observe(this.bodyBottom)
|
|
|
|
|
useResizeObserver(this.canvasElRef, ([canvasEl]) => {
|
|
|
|
|
if (canvasEl.target instanceof HTMLCanvasElement) {
|
|
|
|
|
this.resizeCanvas(canvasEl.target)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await useExtensionService().invokeExtensionsAsync('init')
|
|
|
|
|
await this.registerNodes()
|
|
|
|
|
|
|
|
|
|
this.#addDrawNodeHandler()
|
|
|
|
|
this.#addDropHandler()
|
|
|
|
|
this.addDropHandler()
|
|
|
|
|
|
|
|
|
|
await useExtensionService().invokeExtensionsAsync('setup')
|
|
|
|
|
|
|
|
|
|
this.#positionConversion = useCanvasPositionConversion(
|
|
|
|
|
this.positionConversion = useCanvasPositionConversion(
|
|
|
|
|
this.canvasContainer,
|
|
|
|
|
this.canvas
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resizeCanvas() {
|
|
|
|
|
private resizeCanvas(canvas: HTMLCanvasElement) {
|
|
|
|
|
// Limit minimal scale to 1, see https://github.com/comfyanonymous/ComfyUI/pull/845
|
|
|
|
|
const scale = Math.max(window.devicePixelRatio, 1)
|
|
|
|
|
|
|
|
|
|
// Clear fixed width and height while calculating rect so it uses 100% instead
|
|
|
|
|
this.canvasEl.height = this.canvasEl.width = NaN
|
|
|
|
|
const { width, height } = this.canvasEl.getBoundingClientRect()
|
|
|
|
|
this.canvasEl.width = Math.round(width * scale)
|
|
|
|
|
this.canvasEl.height = Math.round(height * scale)
|
|
|
|
|
canvas.height = canvas.width = NaN
|
|
|
|
|
const { width, height } = canvas.getBoundingClientRect()
|
|
|
|
|
canvas.width = Math.round(width * scale)
|
|
|
|
|
canvas.height = Math.round(height * scale)
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
this.canvasEl.getContext('2d').scale(scale, scale)
|
|
|
|
|
canvas.getContext('2d').scale(scale, scale)
|
|
|
|
|
this.canvas?.draw(true, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -971,7 +922,7 @@ export class ComfyApp {
|
|
|
|
|
nodeDefStore.updateNodeDefs(nodeDefArray)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async #getNodeDefs(): Promise<Record<string, ComfyNodeDefV1>> {
|
|
|
|
|
async getNodeDefs(): Promise<Record<string, ComfyNodeDefV1>> {
|
|
|
|
|
const translateNodeDef = (def: ComfyNodeDefV1): ComfyNodeDefV1 => ({
|
|
|
|
|
...def,
|
|
|
|
|
display_name: st(
|
|
|
|
|
@@ -995,7 +946,7 @@ export class ComfyApp {
|
|
|
|
|
*/
|
|
|
|
|
async registerNodes() {
|
|
|
|
|
// Load node definitions from the backend
|
|
|
|
|
const defs = await this.#getNodeDefs()
|
|
|
|
|
const defs = await this.getNodeDefs()
|
|
|
|
|
|
|
|
|
|
// Load filename mappings alongside node definitions for better UX
|
|
|
|
|
import('@/services/fileNameMappingService').then(
|
|
|
|
|
@@ -1077,14 +1028,14 @@ export class ComfyApp {
|
|
|
|
|
localStorage.setItem('litegrapheditor_clipboard', old)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#showMissingNodesError(missingNodeTypes: MissingNodeType[]) {
|
|
|
|
|
private showMissingNodesError(missingNodeTypes: MissingNodeType[]) {
|
|
|
|
|
if (useSettingStore().get('Comfy.Workflow.ShowMissingNodesWarning')) {
|
|
|
|
|
useDialogService().showLoadWorkflowWarning({ missingNodeTypes })
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @ts-expect-error fixme ts strict error
|
|
|
|
|
#showMissingModelsError(missingModels, paths) {
|
|
|
|
|
private showMissingModelsError(missingModels, paths) {
|
|
|
|
|
if (useSettingStore().get('Comfy.Workflow.ShowMissingModelsWarning')) {
|
|
|
|
|
useDialogService().showMissingModelsWarning({
|
|
|
|
|
missingModels,
|
|
|
|
|
@@ -1317,11 +1268,11 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (missingNodeTypes.length && showMissingNodesDialog) {
|
|
|
|
|
this.#showMissingNodesError(missingNodeTypes)
|
|
|
|
|
this.showMissingNodesError(missingNodeTypes)
|
|
|
|
|
}
|
|
|
|
|
if (missingModels.length && showMissingModelsDialog) {
|
|
|
|
|
const paths = await api.getFolderPaths()
|
|
|
|
|
this.#showMissingModelsError(missingModels, paths)
|
|
|
|
|
this.showMissingModelsError(missingModels, paths)
|
|
|
|
|
}
|
|
|
|
|
api.postCloudAnalytics('load_workflow', {
|
|
|
|
|
source: 'graph',
|
|
|
|
|
@@ -1353,14 +1304,14 @@ export class ComfyApp {
|
|
|
|
|
batchCount: number = 1,
|
|
|
|
|
queueNodeIds?: NodeExecutionId[]
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
this.#queueItems.push({ number, batchCount, queueNodeIds })
|
|
|
|
|
this.queueItems.push({ number, batchCount, queueNodeIds })
|
|
|
|
|
|
|
|
|
|
// Only have one action process the items so each one gets a unique seed correctly
|
|
|
|
|
if (this.#processingQueue) {
|
|
|
|
|
if (this.processingQueue) {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.#processingQueue = true
|
|
|
|
|
this.processingQueue = true
|
|
|
|
|
const executionStore = useExecutionStore()
|
|
|
|
|
executionStore.lastNodeErrors = null
|
|
|
|
|
|
|
|
|
|
@@ -1368,8 +1319,8 @@ export class ComfyApp {
|
|
|
|
|
let comfyOrgApiKey = useApiKeyAuthStore().getApiKey()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
while (this.#queueItems.length) {
|
|
|
|
|
const { number, batchCount, queueNodeIds } = this.#queueItems.pop()!
|
|
|
|
|
while (this.queueItems.length) {
|
|
|
|
|
const { number, batchCount, queueNodeIds } = this.queueItems.pop()!
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < batchCount; i++) {
|
|
|
|
|
// Allow widgets to run callbacks before a prompt has been queued
|
|
|
|
|
@@ -1434,7 +1385,7 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
this.#processingQueue = false
|
|
|
|
|
this.processingQueue = false
|
|
|
|
|
}
|
|
|
|
|
api.dispatchCustomEvent('promptQueued', { number, batchCount })
|
|
|
|
|
return !executionStore.lastNodeErrors
|
|
|
|
|
@@ -1643,7 +1594,7 @@ export class ComfyApp {
|
|
|
|
|
apiJson: apiData
|
|
|
|
|
})
|
|
|
|
|
if (missingNodeTypes.length) {
|
|
|
|
|
this.#showMissingNodesError(missingNodeTypes.map((t) => t.class_type))
|
|
|
|
|
this.showMissingNodesError(missingNodeTypes.map((t) => t.class_type))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1762,7 +1713,7 @@ export class ComfyApp {
|
|
|
|
|
useToastStore().add(requestToastMessage)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defs = await this.#getNodeDefs()
|
|
|
|
|
const defs = await this.getNodeDefs()
|
|
|
|
|
for (const nodeId in defs) {
|
|
|
|
|
this.registerNodeDef(nodeId, defs[nodeId])
|
|
|
|
|
}
|
|
|
|
|
@@ -1838,17 +1789,17 @@ export class ComfyApp {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clientPosToCanvasPos(pos: Vector2): Vector2 {
|
|
|
|
|
if (!this.#positionConversion) {
|
|
|
|
|
if (!this.positionConversion) {
|
|
|
|
|
throw new Error('clientPosToCanvasPos called before setup')
|
|
|
|
|
}
|
|
|
|
|
return this.#positionConversion.clientPosToCanvasPos(pos)
|
|
|
|
|
return this.positionConversion.clientPosToCanvasPos(pos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
canvasPosToClientPos(pos: Vector2): Vector2 {
|
|
|
|
|
if (!this.#positionConversion) {
|
|
|
|
|
if (!this.positionConversion) {
|
|
|
|
|
throw new Error('canvasPosToClientPos called before setup')
|
|
|
|
|
}
|
|
|
|
|
return this.#positionConversion.canvasPosToClientPos(pos)
|
|
|
|
|
return this.positionConversion.canvasPosToClientPos(pos)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|