diff --git a/src/renderer/extensions/minimap/composables/useMinimap.test.ts b/src/renderer/extensions/minimap/composables/useMinimap.test.ts index db050ad1f6..5d15bccc2e 100644 --- a/src/renderer/extensions/minimap/composables/useMinimap.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimap.test.ts @@ -200,6 +200,12 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ })) })) +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: vi.fn().mockReturnValue({ + nodeProgressStates: {} + }) +})) + import { useMinimap } from '@/renderer/extensions/minimap/composables/useMinimap' import { api } from '@/scripts/api' diff --git a/src/renderer/extensions/minimap/composables/useMinimap.ts b/src/renderer/extensions/minimap/composables/useMinimap.ts index 0529737ed6..e7525dda32 100644 --- a/src/renderer/extensions/minimap/composables/useMinimap.ts +++ b/src/renderer/extensions/minimap/composables/useMinimap.ts @@ -5,6 +5,7 @@ import type { ShallowRef } from 'vue' import type { LGraph } from '@/lib/litegraph/src/litegraph' import { useSettingStore } from '@/platform/settings/settingStore' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' +import { useExecutionStore } from '@/stores/executionStore' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import type { MinimapCanvas, MinimapSettingsKey } from '../types' @@ -201,6 +202,18 @@ export function useMinimap({ } }) + const executionStore = useExecutionStore() + watch( + () => executionStore.nodeProgressStates, + () => { + if (visible.value) { + renderer.forceFullRedraw() + renderer.updateMinimap(viewport.updateBounds, viewport.updateViewport) + } + }, + { deep: true } + ) + const toggle = async () => { visible.value = !visible.value await settingStore.set('Comfy.Minimap.Visible', visible.value) diff --git a/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts b/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts index 9e9e760530..10f9931e1a 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapGraph.test.ts @@ -17,6 +17,12 @@ vi.mock('@vueuse/core', () => ({ useThrottleFn: vi.fn((fn) => fn) })) +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: vi.fn().mockReturnValue({ + nodeProgressStates: {} + }) +})) + vi.mock('@/scripts/api', () => ({ api: { addEventListener: vi.fn(), diff --git a/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts b/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts index fbdff5142c..0546d395b1 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapRenderer.test.ts @@ -20,7 +20,9 @@ describe('useMinimapRenderer', () => { vi.clearAllMocks() mockContext = { - clearRect: vi.fn() + clearRect: vi.fn(), + save: vi.fn(), + restore: vi.fn() } as Partial as CanvasRenderingContext2D mockCanvas = { diff --git a/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts b/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts index c2416edc3f..60437cc641 100644 --- a/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts +++ b/src/renderer/extensions/minimap/composables/useMinimapViewport.test.ts @@ -13,6 +13,15 @@ import { useMinimapViewport } from '@/renderer/extensions/minimap/composables/us import type { MinimapCanvas } from '@/renderer/extensions/minimap/types' vi.mock('@vueuse/core') +vi.mock('@/renderer/core/canvas/canvasStore', () => ({ + useCanvasStore: vi.fn() +})) + +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: vi.fn().mockReturnValue({ + nodeProgressStates: {} + }) +})) vi.mock('@/renderer/core/spatial/boundsCalculator', () => ({ calculateNodeBounds: vi.fn(), calculateMinimapScale: vi.fn(), diff --git a/src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts b/src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts index c0daf7030f..725c268132 100644 --- a/src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts +++ b/src/renderer/extensions/minimap/data/LayoutStoreDataSource.ts @@ -1,8 +1,11 @@ import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import { useExecutionStore } from '@/stores/executionStore' import type { MinimapNodeData } from '../types' import { AbstractMinimapDataSource } from './AbstractMinimapDataSource' +let executionStore: ReturnType | null = null + /** * Layout Store data source implementation */ @@ -11,12 +14,19 @@ export class LayoutStoreDataSource extends AbstractMinimapDataSource { const allNodes = layoutStore.getAllNodes().value if (allNodes.size === 0) return [] + if (!executionStore) { + executionStore = useExecutionStore() + } + const nodeProgressStates = executionStore.nodeLocationProgressStates + const nodes: MinimapNodeData[] = [] for (const [nodeId, layout] of allNodes) { // Find corresponding LiteGraph node for additional properties const graphNode = this.graph?._nodes?.find((n) => String(n.id) === nodeId) + const executionState = nodeProgressStates[nodeId]?.state ?? null + nodes.push({ id: nodeId, x: layout.position.x, @@ -25,7 +35,8 @@ export class LayoutStoreDataSource extends AbstractMinimapDataSource { height: layout.size.height, bgcolor: graphNode?.bgcolor, mode: graphNode?.mode, - hasErrors: graphNode?.has_errors + hasErrors: graphNode?.has_errors, + executionState }) } diff --git a/src/renderer/extensions/minimap/data/LiteGraphDataSource.ts b/src/renderer/extensions/minimap/data/LiteGraphDataSource.ts index 8e1048e750..8374f86a55 100644 --- a/src/renderer/extensions/minimap/data/LiteGraphDataSource.ts +++ b/src/renderer/extensions/minimap/data/LiteGraphDataSource.ts @@ -1,3 +1,5 @@ +import { useExecutionStore } from '@/stores/executionStore' + import type { MinimapNodeData } from '../types' import { AbstractMinimapDataSource } from './AbstractMinimapDataSource' @@ -8,16 +10,25 @@ export class LiteGraphDataSource extends AbstractMinimapDataSource { getNodes(): MinimapNodeData[] { if (!this.graph?._nodes) return [] - return this.graph._nodes.map((node) => ({ - id: String(node.id), - x: node.pos[0], - y: node.pos[1], - width: node.size[0], - height: node.size[1], - bgcolor: node.bgcolor, - mode: node.mode, - hasErrors: node.has_errors - })) + const executionStore = useExecutionStore() + const nodeProgressStates = executionStore.nodeProgressStates + + return this.graph._nodes.map((node) => { + const nodeId = String(node.id) + const executionState = nodeProgressStates[nodeId]?.state ?? null + + return { + id: nodeId, + x: node.pos[0], + y: node.pos[1], + width: node.size[0], + height: node.size[1], + bgcolor: node.bgcolor, + mode: node.mode, + hasErrors: node.has_errors, + executionState + } + }) } getNodeCount(): number { diff --git a/src/renderer/extensions/minimap/data/MinimapDataSource.test.ts b/src/renderer/extensions/minimap/data/MinimapDataSource.test.ts index 50f9ebba0e..51ab1c7383 100644 --- a/src/renderer/extensions/minimap/data/MinimapDataSource.test.ts +++ b/src/renderer/extensions/minimap/data/MinimapDataSource.test.ts @@ -15,6 +15,13 @@ vi.mock('@/renderer/core/layout/store/layoutStore', () => ({ } })) +// Mock useExecutionStore +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: vi.fn().mockReturnValue({ + nodeProgressStates: {} + }) +})) + // Helper to create mock links that satisfy LGraph['links'] type function createMockLinks(): LGraph['links'] { const map = new Map() diff --git a/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts b/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts index 81fcfceb88..9141a3ba3d 100644 --- a/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts +++ b/src/renderer/extensions/minimap/minimapCanvasRenderer.test.ts @@ -22,6 +22,12 @@ vi.mock('@/utils/colorUtil', () => ({ adjustColor: vi.fn((color: string) => color + '_adjusted') })) +vi.mock('@/stores/executionStore', () => ({ + useExecutionStore: vi.fn().mockReturnValue({ + nodeProgressStates: {} + }) +})) + describe('minimapCanvasRenderer', () => { let mockCanvas: HTMLCanvasElement let mockContext: CanvasRenderingContext2D @@ -42,7 +48,9 @@ describe('minimapCanvasRenderer', () => { fill: vi.fn(), fillStyle: '', strokeStyle: '', - lineWidth: 1 + lineWidth: 1, + save: vi.fn(), + restore: vi.fn() } as Partial as CanvasRenderingContext2D mockCanvas = { diff --git a/src/renderer/extensions/minimap/minimapCanvasRenderer.ts b/src/renderer/extensions/minimap/minimapCanvasRenderer.ts index 3e547ce689..ee1ff83180 100644 --- a/src/renderer/extensions/minimap/minimapCanvasRenderer.ts +++ b/src/renderer/extensions/minimap/minimapCanvasRenderer.ts @@ -26,6 +26,8 @@ function getMinimapColors() { groupColorDefault: isLightTheme ? '#283640' : '#B3C1CB', bypassColor: isLightTheme ? '#DBDBDB' : '#4B184B', errorColor: '#FF0000', + runningColor: '#00FF00', + successColor: '#239B23', isLightTheme } } @@ -103,10 +105,19 @@ function renderNodes( const nodes = dataSource.getNodes() if (nodes.length === 0) return + ctx.save() + // Group nodes by color for batch rendering (performance optimization) const nodesByColor = new Map< string, - Array<{ x: number; y: number; w: number; h: number; hasErrors?: boolean }> + Array<{ + x: number + y: number + w: number + h: number + hasErrors?: boolean + executionState?: 'pending' | 'running' | 'finished' | 'error' | null + }> >() for (const node of nodes) { @@ -121,7 +132,14 @@ function renderNodes( nodesByColor.set(color, []) } - nodesByColor.get(color)!.push({ x, y, w, h, hasErrors: node.hasErrors }) + nodesByColor.get(color)!.push({ + x, + y, + w, + h, + hasErrors: node.hasErrors, + executionState: node.executionState + }) } // Batch render nodes by color @@ -132,18 +150,29 @@ function renderNodes( } } - // Render error outlines if needed - if (context.settings.renderError) { - ctx.strokeStyle = colors.errorColor - ctx.lineWidth = 0.3 - for (const nodes of nodesByColor.values()) { - for (const node of nodes) { - if (node.hasErrors) { - ctx.strokeRect(node.x, node.y, node.w, node.h) - } + ctx.lineWidth = 0.3 + for (const nodes of nodesByColor.values()) { + for (const node of nodes) { + if (node.hasErrors && context.settings.renderError) { + ctx.strokeStyle = colors.errorColor + ctx.strokeRect(node.x, node.y, node.w, node.h) + } else if (node.executionState === 'running') { + ctx.strokeStyle = colors.runningColor + ctx.strokeRect(node.x, node.y, node.w, node.h) + } else if (node.executionState === 'finished') { + ctx.strokeStyle = colors.successColor + ctx.strokeRect(node.x, node.y, node.w, node.h) + } else if ( + node.executionState === 'error' && + context.settings.renderError + ) { + ctx.strokeStyle = colors.errorColor + ctx.strokeRect(node.x, node.y, node.w, node.h) } } } + + ctx.restore() } /** diff --git a/src/renderer/extensions/minimap/types.ts b/src/renderer/extensions/minimap/types.ts index b458718ea8..d23200a2e9 100644 --- a/src/renderer/extensions/minimap/types.ts +++ b/src/renderer/extensions/minimap/types.ts @@ -80,6 +80,7 @@ export interface MinimapNodeData { bgcolor?: string mode?: number hasErrors?: boolean + executionState?: 'pending' | 'running' | 'finished' | 'error' | null } /** diff --git a/src/utils/__tests__/litegraphTestUtils.ts b/src/utils/__tests__/litegraphTestUtils.ts index 3f0a0cea65..0603d0908b 100644 --- a/src/utils/__tests__/litegraphTestUtils.ts +++ b/src/utils/__tests__/litegraphTestUtils.ts @@ -276,6 +276,8 @@ export function createMockCanvas2DContext( fillStyle: '', strokeStyle: '', lineWidth: 1, + save: vi.fn(), + restore: vi.fn(), ...overrides } return partial as CanvasRenderingContext2D