Progress Bars for ComfyUI
Introduction
This is a patch to GraphCanvasMenu
and an implementation of ProgressBars
for the ComfyUI frontend which adds two progress bars to the UI.
Features
The implementation adds two distinct progress bars to the ComfyUI interface. The first is a Workflow Progress Bar that shows the overall workflow execution progress, while the second is a Node Progress Bar that displays the progress of the currently executing node.
These progress bars are carefully designed with user experience in mind - they maintain a non-intrusive presence with minimal heights of 2px and 1px respectively, adapt seamlessly to both light and dark themes, and automatically hide themselves when the system is idle to keep the interface clean and uncluttered.
Implementation Details
Component Structure
The progress bars are implemented as a Vue component (GraphProgressBars.vue
) that gets mounted alongside the main canvas. This component provides the core structure needed to display and manage the progress indicators in the UI.
The component is built with a container div that houses both progress bars, along with their respective fill indicators, and utilizes computed properties that derive from the execution store to enable real-time progress tracking during workflow execution.
Dynamic Positioning
The progress bars are dynamically positioned beneath the ComfyUI menu, ensuring they stay in a consistent and visible location regardless of UI changes. This positioning is:
- Updated on initial component mount
- Refreshed when the window resizes
- Periodically checked to handle dynamic UI changes
- Automatically cleaned up when the component unmounts
Progress Bar States
The progress bars respond to three main states from the execution store:
executionProgress
: Overall workflow progress (0-1) converted to percentage (0-100%)executingNodeProgress
: Current node progress (0-1) converted to percentage (0-100%)isIdle
: Whether any processing is happening
Visual Design
The progress bars incorporate smooth transitions with a 0.2s ease-out animation, and feature a visual hierarchy through different heights - the workflow bar at 2px and the node bar at 1px. They also utilize distinct colors to differentiate their purposes, with the workflow progress shown in blue (#3498db) and node progress displayed in green (#2ecc71).
The progress bars are designed to be theme-sensitive, with backgrounds that adapt based on the UI theme - using semi-transparent black in light theme and semi-transparent white in dark theme. This ensures optimal visibility regardless of the user’s theme preference.
Installation
To add the progress bars to your ComfyUI installation:
Apply the patch to your
GraphCanvas.vue
:patch -p1 < GraphCanvasMenu_progressbars.patch
Add the
GraphProgressBars.vue
component to your project’s components directory:src/components/graph/GraphProgressBars.vue
Rebuild the frontend.
Usage
The progress bars will automatically appear when:
- A workflow is executing (top bar)
- Individual nodes are processing (bottom bar)
They automatically hide when the system returns to an idle state, keeping your interface clean when not needed.
Technical Notes
The component leverages Vue’s Composition API with computed properties to interact with the execution store. To ensure the progress bars remain in a consistent position, they utilize a fixed position with dynamic adjustments based on the ComfyUI menu’s location. The implementation uses pointer-events-none to prevent the progress bars from interfering with any UI elements positioned underneath them.
The Code
GraphCanvasMenu_progressbars.patch
diff --git a/src/components/graph/GraphCanvas.vue b/src/components/graph/GraphCanvas.vue index 127a6b20..7e6a6840 100644 --- a/src/components/graph/GraphCanvas.vue +++ b/src/components/graph/GraphCanvas.vue @@ -18,6 +18,7 @@ </LiteGraphCanvasSplitterOverlay> <TitleEditor /> <GraphCanvasMenu v-if="!betaMenuEnabled && canvasMenuEnabled" /> + <GraphProgressBars /> <canvas ref="canvasRef" id="graph-canvas" tabindex="1" /> </teleport> <NodeSearchboxPopover /> @@ -33,6 +34,7 @@ import LiteGraphCanvasSplitterOverlay from '@/components/LiteGraphCanvasSplitter import NodeSearchboxPopover from '@/components/searchbox/NodeSearchBoxPopover.vue' import NodeTooltip from '@/components/graph/NodeTooltip.vue' import NodeBadge from '@/components/graph/NodeBadge.vue' +import GraphProgressBars from '@/components/graph/GraphProgressBars.vue' import { ref, computed, onMounted, watchEffect, watch } from 'vue' import { app as comfyApp } from '@/scripts/app' import { useSettingStore } from '@/stores/settingStore'
/src/components/graph/GraphProgressBars.vue
<template> <div v-if="!isIdle" ref="progressBarsRef" class="graph-progress-bars"> <div class="bars-container"> <!-- Workflow progress bar --> <div class="progress-bar workflow-progress"> <div class="progress-fill" :style="{ width: `${progress}%` }" /> </div> <!-- Node progress bar --> <div v-if="nodeProgress !== null" class="progress-bar node-progress"> <div class="progress-fill" :style="{ width: `${nodeProgress}%` }" /> </div> </div> </div> </template> <script setup lang="ts"> import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import { useExecutionStore } from '@/stores/executionStore' const executionStore = useExecutionStore() const progressBarsRef = ref<HTMLElement | null>(null) // Use computed properties to unwrap the refs and convert from fractions (0-1) to percentages (0-100) const progress = computed(() => (executionStore.executionProgress || 0) * 100) const nodeProgress = computed(() => executionStore.executingNodeProgress !== null ? executionStore.executingNodeProgress * 100 : null ) const isIdle = computed(() => executionStore.isIdle) function updatePosition() { if (!progressBarsRef.value) return // Find the menu element const menuElement = document.querySelector('.comfyui-menu') as HTMLElement if (!menuElement) return // Get the menu's position and dimensions const menuRect = menuElement.getBoundingClientRect() // Place our progress bars at the bottom of the menu with same width progressBarsRef.value.style.top = `${menuRect.bottom}px` progressBarsRef.value.style.left = `${menuRect.left}px` progressBarsRef.value.style.width = `${menuRect.width}px` } // Update position when any of these events happen onMounted(() => { // Initial positioning setTimeout(updatePosition, 0) // Update position when window resizes window.addEventListener('resize', updatePosition) // Update position periodically to handle dynamic UI changes const interval = setInterval(updatePosition, 1000) // Clean up interval on unmount onBeforeUnmount(() => { clearInterval(interval) window.removeEventListener('resize', updatePosition) }) }) </script> <style scoped> .graph-progress-bars { position: fixed; z-index: 999; pointer-events: none; overflow: visible; width: 100%; } .bars-container { width: 100%; display: block; } .progress-bar { width: 100%; height: 2px; background: rgba(0, 0, 0, 0.1); overflow: hidden; } .progress-bar.node-progress { height: 1px; background: rgba(0, 0, 0, 0.05); } .progress-fill { height: 100%; background: #3498db; transition: width 0.2s ease-out; } .node-progress .progress-fill { background: #2ecc71; } /* Dark theme support */ :root[data-theme='dark'] .progress-bar { background: rgba(255, 255, 255, 0.1); } :root[data-theme='dark'] .progress-bar.node-progress { background: rgba(255, 255, 255, 0.05); } </style>