Merge main (as of 10-06-2025) into rh-test (#5965)

## Summary

Merges latest changes from `main` as of 10-06-2025.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5965-Merge-main-as-of-10-06-2025-into-rh-test-2856d73d3650812cb95fd8917278a770)
by [Unito](https://www.unito.io)

---------

Signed-off-by: Marcel Petrick <mail@marcelpetrick.it>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Co-authored-by: Benjamin Lu <benceruleanlu@proton.me>
Co-authored-by: Terry Jia <terryjia88@gmail.com>
Co-authored-by: snomiao <snomiao@gmail.com>
Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com>
Co-authored-by: Jake Schroeder <jake.schroeder@isophex.com>
Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com>
Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com>
Co-authored-by: Marcel Petrick <mail@marcelpetrick.it>
Co-authored-by: Alexander Brown <DrJKL0424@gmail.com>
Co-authored-by: Benjamin Lu <benjaminlu1107@gmail.com>
Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe>
Co-authored-by: JakeSchroeder <jake@axiom.co>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: DrJKL <DrJKL@users.noreply.github.com>
Co-authored-by: ComfyUI Wiki <contact@comfyui-wiki.com>
This commit is contained in:
Arjan Singh
2025-10-08 19:06:40 -07:00
committed by GitHub
parent 529a4de583
commit 5869b04e57
454 changed files with 32333 additions and 37002 deletions

View File

@@ -675,7 +675,7 @@ export class ComfyApi extends EventTarget {
if (msg.data.sid) {
const clientId = msg.data.sid
this.clientId = clientId
window.name = clientId // use window name so it isnt reused when duplicating tabs
window.name = clientId // use window name so it isn't reused when duplicating tabs
sessionStorage.setItem('clientId', clientId) // store in session storage so duplicate tab can load correct workflow
}
this.dispatchCustomEvent('status', msg.data.status ?? null)

View File

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

View File

@@ -173,6 +173,18 @@ abstract class BaseDOMWidgetImpl<V extends object | string>
)
ctx.fill()
ctx.fillStyle = originalFillStyle
} else if (this.promoted && this.isVisible()) {
ctx.save()
const adjustedMargin = this.margin - 1
ctx.beginPath()
ctx.strokeStyle = LiteGraph.WIDGET_PROMOTED_OUTLINE_COLOR
ctx.strokeRect(
adjustedMargin,
y + adjustedMargin,
widget_width - adjustedMargin * 2,
(this.computedHeight ?? widget_height) - 2 * adjustedMargin
)
ctx.restore()
}
this.options.onDraw?.(this)
}

View File

@@ -23,11 +23,8 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
import { $el } from '../ui'
$el('style', {
parent: document.head,
textContent: `
const styleElement = document.createElement('style')
styleElement.textContent = `
.draggable-item {
position: relative;
will-change: transform;
@@ -40,7 +37,7 @@ $el('style', {
z-index: 10;
}
`
})
document.head.append(styleElement)
export class DraggableList extends EventTarget {
listContainer

View File

@@ -1,4 +1,3 @@
import '@/core/graph/subgraph/proxyWidget'
import { t } from '@/i18n'
import { type LGraphNode, isComboWidget } from '@/lib/litegraph/src/litegraph'
import type {
@@ -134,7 +133,8 @@ export function addValueControlWidgets(
function () {},
{
values: ['fixed', 'increment', 'decrement', 'randomize'],
serialize: false // Don't include this in prompt.
serialize: false, // Don't include this in prompt.
canvasOnly: true
}
) as IComboWidget