mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-25 23:07:46 +00:00
Compare commits
4 Commits
qpo-progre
...
fix/empty-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
def709cd7d | ||
|
|
e6a423c36e | ||
|
|
1b6c2f0add | ||
|
|
68c6a9d7e2 |
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<tr
|
||||
class="border-y border-solid border-neutral-700"
|
||||
class="border-neutral-700 border-solid border-y"
|
||||
:class="{
|
||||
'opacity-50': runner.resolved,
|
||||
'opacity-75': isLoading && runner.resolved
|
||||
}"
|
||||
>
|
||||
<td class="w-16 text-center">
|
||||
<td class="text-center w-16">
|
||||
<TaskListStatusIcon :state="runner.state" :loading="isLoading" />
|
||||
</td>
|
||||
<td>
|
||||
@@ -14,7 +14,7 @@
|
||||
{{ task.name }}
|
||||
</p>
|
||||
<Button
|
||||
class="mx-2 inline-block"
|
||||
class="inline-block mx-2"
|
||||
type="button"
|
||||
:icon="PrimeIcons.INFO_CIRCLE"
|
||||
severity="secondary"
|
||||
@@ -22,11 +22,11 @@
|
||||
@click="toggle"
|
||||
/>
|
||||
|
||||
<Popover ref="infoPopover" class="m-1 block max-w-64 min-w-32">
|
||||
<Popover ref="infoPopover" class="block m-1 max-w-64 min-w-32">
|
||||
<span class="whitespace-pre-line">{{ task.description }}</span>
|
||||
</Popover>
|
||||
</td>
|
||||
<td class="px-4 text-right">
|
||||
<td class="text-right px-4">
|
||||
<Button
|
||||
:icon="task.button?.icon"
|
||||
:label="task.button?.text"
|
||||
|
||||
@@ -14,18 +14,25 @@ vi.mock('@/i18n', () => ({
|
||||
const executionStore = reactive<{
|
||||
isIdle: boolean
|
||||
executionProgress: number
|
||||
executingNode: null | {
|
||||
title?: string
|
||||
type?: string
|
||||
}
|
||||
executingNode: unknown
|
||||
executingNodeProgress: number
|
||||
nodeProgressStates: Record<string, unknown>
|
||||
activeJob: {
|
||||
workflow: {
|
||||
changeTracker: {
|
||||
activeState: {
|
||||
nodes: { id: number; type: string }[]
|
||||
}
|
||||
}
|
||||
}
|
||||
} | null
|
||||
}>({
|
||||
isIdle: true,
|
||||
executionProgress: 0,
|
||||
executingNode: null,
|
||||
executingNodeProgress: 0,
|
||||
nodeProgressStates: {}
|
||||
nodeProgressStates: {},
|
||||
activeJob: null
|
||||
})
|
||||
vi.mock('@/stores/executionStore', () => ({
|
||||
useExecutionStore: () => executionStore
|
||||
@@ -69,6 +76,7 @@ describe('useBrowserTabTitle', () => {
|
||||
executionStore.executingNode = null
|
||||
executionStore.executingNodeProgress = 0
|
||||
executionStore.nodeProgressStates = {}
|
||||
executionStore.activeJob = null
|
||||
|
||||
// reset setting and workflow stores
|
||||
vi.mocked(settingStore.get).mockReturnValue('Enabled')
|
||||
@@ -176,12 +184,18 @@ describe('useBrowserTabTitle', () => {
|
||||
it('shows node execution title when executing a node using nodeProgressStates', async () => {
|
||||
executionStore.isIdle = false
|
||||
executionStore.executionProgress = 0.4
|
||||
executionStore.executingNode = {
|
||||
type: 'Foo'
|
||||
}
|
||||
executionStore.nodeProgressStates = {
|
||||
'1': { state: 'running', value: 5, max: 10, node: '1', prompt_id: 'test' }
|
||||
}
|
||||
executionStore.activeJob = {
|
||||
workflow: {
|
||||
changeTracker: {
|
||||
activeState: {
|
||||
nodes: [{ id: 1, type: 'Foo' }]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const scope: EffectScope = effectScope()
|
||||
scope.run(() => useBrowserTabTitle())
|
||||
await nextTick()
|
||||
|
||||
@@ -74,14 +74,14 @@ export const useBrowserTabTitle = () => {
|
||||
}
|
||||
|
||||
// If only one node is running
|
||||
const [, state] = runningNodes[0]
|
||||
const [nodeId, state] = runningNodes[0]
|
||||
const progress = Math.round((state.value / state.max) * 100)
|
||||
const nodeLabel =
|
||||
executionStore.executingNode?.type?.trim() ||
|
||||
executionStore.executingNode?.title?.trim() ||
|
||||
'Node'
|
||||
const nodeType =
|
||||
executionStore.activeJob?.workflow?.changeTracker?.activeState.nodes.find(
|
||||
(n) => String(n.id) === nodeId
|
||||
)?.type || 'Node'
|
||||
|
||||
return `${executionText.value}[${progress}%] ${nodeLabel}`
|
||||
return `${executionText.value}[${progress}%] ${nodeType}`
|
||||
})
|
||||
|
||||
const workflowTitle = computed(
|
||||
|
||||
@@ -1889,7 +1889,9 @@
|
||||
"extensionFileHint": "This may be due to the following script",
|
||||
"promptExecutionError": "Prompt execution failed",
|
||||
"accessRestrictedTitle": "Access Restricted",
|
||||
"accessRestrictedMessage": "Your account is not authorized for this feature."
|
||||
"accessRestrictedMessage": "Your account is not authorized for this feature.",
|
||||
"emptyFileInputTitle": "Missing File Inputs",
|
||||
"emptyFileInputMessage": "The following nodes require a file to be selected: {nodeList}. Please upload or select files before running."
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"title": "Sign In Required to Use API Nodes",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<BaseModalLayout content-title="" data-testid="settings-dialog" size="sm">
|
||||
<BaseModalLayout content-title="" data-testid="settings-dialog" size="md">
|
||||
<template #leftPanelHeaderTitle>
|
||||
<i class="icon-[lucide--settings]" />
|
||||
<h2 class="text-neutral text-base">{{ $t('g.settings') }}</h2>
|
||||
@@ -12,7 +12,6 @@
|
||||
size="md"
|
||||
:placeholder="$t('g.searchSettings') + '...'"
|
||||
:debounce-time="128"
|
||||
autofocus
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -146,6 +146,45 @@ import {
|
||||
|
||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||
|
||||
const FILE_INPUT_FIELDS = {
|
||||
LoadImage: 'image',
|
||||
LoadAudio: 'audio',
|
||||
Load3D: 'model_file',
|
||||
LoadVideo: 'video'
|
||||
} as const
|
||||
|
||||
function isEmptyFileValue(value: unknown): boolean {
|
||||
if (Array.isArray(value)) return false // linked input from another node
|
||||
if (typeof value === 'string') return value.trim() === ''
|
||||
return value == null
|
||||
}
|
||||
|
||||
interface EmptyFileInputNode {
|
||||
nodeId: string
|
||||
classType: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export function findEmptyFileInputNodes(
|
||||
output: ComfyApiWorkflow,
|
||||
nodeIds?: Set<string>
|
||||
): EmptyFileInputNode[] {
|
||||
const result: EmptyFileInputNode[] = []
|
||||
for (const [nodeId, node] of Object.entries(output)) {
|
||||
if (nodeIds && !nodeIds.has(nodeId)) continue
|
||||
const field =
|
||||
FILE_INPUT_FIELDS[node.class_type as keyof typeof FILE_INPUT_FIELDS]
|
||||
if (field && isEmptyFileValue(node.inputs[field])) {
|
||||
result.push({
|
||||
nodeId,
|
||||
classType: node.class_type,
|
||||
title: node._meta?.title ?? node.class_type
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function sanitizeNodeName(string: string) {
|
||||
let entityMap = {
|
||||
'&': '',
|
||||
@@ -1615,6 +1654,25 @@ export class ComfyApp {
|
||||
const queuedWorkflow = useWorkspaceStore().workflow
|
||||
.activeWorkflow as ComfyWorkflow
|
||||
const p = await this.graphToPrompt(this.rootGraph)
|
||||
|
||||
const targetNodeIds = isPartialExecution
|
||||
? new Set(queueNodeIds!)
|
||||
: undefined
|
||||
const emptyFileInputNodes = findEmptyFileInputNodes(
|
||||
p.output,
|
||||
targetNodeIds
|
||||
)
|
||||
if (emptyFileInputNodes.length) {
|
||||
const nodeList = emptyFileInputNodes
|
||||
.map((n) => `#${n.nodeId} ${n.title}`)
|
||||
.join(', ')
|
||||
useDialogService().showErrorDialog(
|
||||
new Error(t('errorDialog.emptyFileInputMessage', { nodeList })),
|
||||
{ title: t('errorDialog.emptyFileInputTitle') }
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
const queuedNodes = collectAllNodes(this.rootGraph)
|
||||
try {
|
||||
api.authToken = comfyOrgAuthToken
|
||||
@@ -1634,7 +1692,6 @@ export class ComfyApp {
|
||||
executionStore.storeJob({
|
||||
id: res.prompt_id,
|
||||
nodes: Object.keys(p.output),
|
||||
promptOutput: p.output,
|
||||
workflow: queuedWorkflow
|
||||
})
|
||||
}
|
||||
|
||||
120
src/scripts/findEmptyFileInputNodes.test.ts
Normal file
120
src/scripts/findEmptyFileInputNodes.test.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ComfyApiWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { findEmptyFileInputNodes } from './app'
|
||||
|
||||
function makeNode(
|
||||
classType: string,
|
||||
inputs: Record<string, unknown>,
|
||||
title?: string
|
||||
) {
|
||||
return {
|
||||
class_type: classType,
|
||||
inputs,
|
||||
_meta: { title: title ?? classType }
|
||||
}
|
||||
}
|
||||
|
||||
describe('findEmptyFileInputNodes', () => {
|
||||
it('detects LoadImage with empty image field', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: '' }),
|
||||
'2': makeNode('KSampler', { seed: 42 })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([
|
||||
{ nodeId: '1', classType: 'LoadImage', title: 'LoadImage' }
|
||||
])
|
||||
})
|
||||
|
||||
it('detects multiple empty file input nodes', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: '' }, 'My Image'),
|
||||
'2': makeNode('LoadAudio', { audio: '' }),
|
||||
'3': makeNode('LoadVideo', { video: 'file.mp4' })
|
||||
}
|
||||
const result = findEmptyFileInputNodes(output)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual({
|
||||
nodeId: '1',
|
||||
classType: 'LoadImage',
|
||||
title: 'My Image'
|
||||
})
|
||||
expect(result[1]).toEqual({
|
||||
nodeId: '2',
|
||||
classType: 'LoadAudio',
|
||||
title: 'LoadAudio'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns empty array when all file inputs are populated', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: 'photo.png' }),
|
||||
'2': makeNode('Load3D', { model_file: 'model.glb' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty array when no file input nodes exist', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('KSampler', { seed: 42 }),
|
||||
'2': makeNode('CLIPTextEncode', { text: 'hello' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([])
|
||||
})
|
||||
|
||||
it('detects Load3D with empty model_file', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'5': makeNode('Load3D', { model_file: '' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([
|
||||
{ nodeId: '5', classType: 'Load3D', title: 'Load3D' }
|
||||
])
|
||||
})
|
||||
|
||||
it('detects null file input values', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: null })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('detects undefined file input values', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: undefined })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('detects whitespace-only file input values', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: ' ' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('skips linked inputs (array references to other nodes)', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: ['5', 0] })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([])
|
||||
})
|
||||
|
||||
it('filters to only specified node IDs when provided', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: '' }),
|
||||
'2': makeNode('LoadAudio', { audio: '' }),
|
||||
'3': makeNode('KSampler', { seed: 42 })
|
||||
}
|
||||
const result = findEmptyFileInputNodes(output, new Set(['2', '3']))
|
||||
expect(result).toEqual([
|
||||
{ nodeId: '2', classType: 'LoadAudio', title: 'LoadAudio' }
|
||||
])
|
||||
})
|
||||
|
||||
it('detects missing file input field entirely', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', {})
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
@@ -1,22 +1,17 @@
|
||||
import { setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import { app } from '@/scripts/app'
|
||||
import { MAX_PROGRESS_JOBS, useExecutionStore } from '@/stores/executionStore'
|
||||
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
|
||||
import { executionIdToNodeLocatorId } from '@/utils/graphTraversalUtil'
|
||||
import type * as WorkflowStoreModule from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { NodeProgressState } from '@/schemas/apiSchema'
|
||||
|
||||
type WorkflowConstructor = typeof WorkflowStoreModule.ComfyWorkflow
|
||||
|
||||
// Create mock functions that will be shared
|
||||
const mockNodeExecutionIdToNodeLocatorId = vi.fn()
|
||||
const mockNodeIdToNodeLocatorId = vi.fn()
|
||||
const mockNodeLocatorIdToNodeExecutionId = vi.fn()
|
||||
const workflowModuleState = vi.hoisted(() => ({
|
||||
WorkflowClass: undefined as WorkflowConstructor | undefined
|
||||
}))
|
||||
|
||||
import type * as WorkflowStoreModule from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type { NodeProgressState } from '@/schemas/apiSchema'
|
||||
import { createMockLGraphNode } from '@/utils/__tests__/litegraphTestUtils'
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
|
||||
@@ -25,7 +20,6 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', async () => {
|
||||
const { ComfyWorkflow } = await vi.importActual<typeof WorkflowStoreModule>(
|
||||
'@/platform/workflow/management/stores/workflowStore'
|
||||
)
|
||||
workflowModuleState.WorkflowClass = ComfyWorkflow
|
||||
return {
|
||||
ComfyWorkflow,
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
@@ -66,7 +60,7 @@ vi.mock('@/scripts/api', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/nodeOutputStore', () => ({
|
||||
vi.mock('@/stores/imagePreviewStore', () => ({
|
||||
useNodeOutputStore: () => ({
|
||||
revokePreviewsByExecutionId: vi.fn()
|
||||
})
|
||||
@@ -90,29 +84,6 @@ vi.mock('@/scripts/app', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
function createQueuedWorkflow(path: string = 'workflows/test.json') {
|
||||
const { WorkflowClass } = workflowModuleState
|
||||
if (!WorkflowClass) {
|
||||
throw new Error('ComfyWorkflow mock class is not available')
|
||||
}
|
||||
|
||||
return new WorkflowClass({
|
||||
path,
|
||||
modified: 0,
|
||||
size: 0
|
||||
})
|
||||
}
|
||||
|
||||
function createPromptNode(title: string, classType: string) {
|
||||
return {
|
||||
inputs: {},
|
||||
class_type: classType,
|
||||
_meta: {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('useExecutionStore - NodeLocatorId conversions', () => {
|
||||
let store: ReturnType<typeof useExecutionStore>
|
||||
|
||||
@@ -627,103 +598,6 @@ describe('useExecutionErrorStore - Node Error Lookups', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('useExecutionStore - executingNode with subgraphs', () => {
|
||||
let store: ReturnType<typeof useExecutionStore>
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setActivePinia(createTestingPinia({ stubActions: false }))
|
||||
store = useExecutionStore()
|
||||
})
|
||||
|
||||
it('should find executing node info in root graph from queued prompt data', () => {
|
||||
store.storeJob({
|
||||
id: 'test-prompt',
|
||||
nodes: ['123'],
|
||||
promptOutput: {
|
||||
'123': createPromptNode('Test Node', 'TestNode')
|
||||
},
|
||||
workflow: createQueuedWorkflow()
|
||||
})
|
||||
store.activeJobId = 'test-prompt'
|
||||
|
||||
store.nodeProgressStates = {
|
||||
'123': {
|
||||
state: 'running',
|
||||
value: 0,
|
||||
max: 100,
|
||||
display_node_id: '123',
|
||||
prompt_id: 'test-prompt',
|
||||
node_id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
expect(store.executingNode).toEqual({
|
||||
title: 'Test Node',
|
||||
type: 'TestNode'
|
||||
})
|
||||
})
|
||||
|
||||
it('should find executing node info in subgraph using execution ID', () => {
|
||||
store.storeJob({
|
||||
id: 'test-prompt',
|
||||
nodes: ['456:789'],
|
||||
promptOutput: {
|
||||
'456:789': createPromptNode('Nested Node', 'NestedNode')
|
||||
},
|
||||
workflow: createQueuedWorkflow()
|
||||
})
|
||||
store.activeJobId = 'test-prompt'
|
||||
|
||||
store.nodeProgressStates = {
|
||||
'456:789': {
|
||||
state: 'running',
|
||||
value: 0,
|
||||
max: 100,
|
||||
display_node_id: '456:789',
|
||||
prompt_id: 'test-prompt',
|
||||
node_id: '456:789'
|
||||
}
|
||||
}
|
||||
|
||||
expect(store.executingNode).toEqual({
|
||||
title: 'Nested Node',
|
||||
type: 'NestedNode'
|
||||
})
|
||||
})
|
||||
|
||||
it('should return null when no node is executing', () => {
|
||||
store.nodeProgressStates = {}
|
||||
|
||||
expect(store.executingNode).toBeNull()
|
||||
})
|
||||
|
||||
it('should return null when executing node metadata cannot be found', () => {
|
||||
store.storeJob({
|
||||
id: 'test-prompt',
|
||||
nodes: ['123'],
|
||||
promptOutput: {
|
||||
'123': createPromptNode('Test Node', 'TestNode')
|
||||
},
|
||||
workflow: createQueuedWorkflow()
|
||||
})
|
||||
store.activeJobId = 'test-prompt'
|
||||
|
||||
store.nodeProgressStates = {
|
||||
'999': {
|
||||
state: 'running',
|
||||
value: 0,
|
||||
max: 100,
|
||||
display_node_id: '999',
|
||||
prompt_id: 'test-prompt',
|
||||
node_id: '999'
|
||||
}
|
||||
}
|
||||
|
||||
expect(store.executingNode).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('useExecutionErrorStore - setMissingNodeTypes', () => {
|
||||
let store: ReturnType<typeof useExecutionErrorStore>
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import { useTelemetry } from '@/platform/telemetry'
|
||||
import type { ComfyWorkflow } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import type {
|
||||
ComfyApiWorkflow,
|
||||
ComfyNode,
|
||||
ComfyWorkflowJSON,
|
||||
NodeId
|
||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
@@ -33,11 +34,6 @@ import type { NodeLocatorId } from '@/types/nodeIdentification'
|
||||
import { classifyCloudValidationError } from '@/utils/executionErrorUtil'
|
||||
import { executionIdToNodeLocatorId } from '@/utils/graphTraversalUtil'
|
||||
|
||||
interface ExecutionNodeInfo {
|
||||
title?: string | null
|
||||
type?: string | null
|
||||
}
|
||||
|
||||
interface QueuedJob {
|
||||
/**
|
||||
* The nodes that are queued to be executed. The key is the node id and the
|
||||
@@ -48,25 +44,6 @@ interface QueuedJob {
|
||||
* The workflow that is queued to be executed
|
||||
*/
|
||||
workflow?: ComfyWorkflow
|
||||
/**
|
||||
* Queue-time node metadata keyed by execution ID.
|
||||
* This stays stable even if the user switches workflows or edits the canvas.
|
||||
*/
|
||||
nodeLookup?: Record<string, ExecutionNodeInfo>
|
||||
}
|
||||
|
||||
function buildExecutionNodeLookup(
|
||||
promptOutput: ComfyApiWorkflow
|
||||
): Record<string, ExecutionNodeInfo> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(promptOutput).map(([executionId, node]) => [
|
||||
executionId,
|
||||
{
|
||||
title: node._meta.title,
|
||||
type: node.class_type
|
||||
}
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,11 +166,21 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
() => new Set(executingNodeIds.value.map(String))
|
||||
)
|
||||
|
||||
// For backward compatibility - returns the primary executing node info
|
||||
const executingNode = computed<ExecutionNodeInfo | null>(() => {
|
||||
// For backward compatibility - returns the primary executing node
|
||||
const executingNode = computed<ComfyNode | null>(() => {
|
||||
if (!executingNodeId.value) return null
|
||||
|
||||
return activeJob.value?.nodeLookup?.[String(executingNodeId.value)] ?? null
|
||||
const workflow: ComfyWorkflow | undefined = activeJob.value?.workflow
|
||||
if (!workflow) return null
|
||||
|
||||
const canvasState: ComfyWorkflowJSON | null =
|
||||
workflow.changeTracker?.activeState ?? null
|
||||
if (!canvasState) return null
|
||||
|
||||
return (
|
||||
canvasState.nodes.find((n) => String(n.id) === executingNodeId.value) ??
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
// This is the progress of the currently executing node (for backward compatibility)
|
||||
@@ -549,12 +536,10 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
function storeJob({
|
||||
nodes,
|
||||
id,
|
||||
promptOutput,
|
||||
workflow
|
||||
}: {
|
||||
nodes: string[]
|
||||
id: string
|
||||
promptOutput: ComfyApiWorkflow
|
||||
workflow: ComfyWorkflow
|
||||
}) {
|
||||
queuedJobs.value[id] ??= { nodes: {} }
|
||||
@@ -566,7 +551,6 @@ export const useExecutionStore = defineStore('execution', () => {
|
||||
}, {}),
|
||||
...queuedJob.nodes
|
||||
}
|
||||
queuedJob.nodeLookup = buildExecutionNodeLookup(promptOutput)
|
||||
queuedJob.workflow = workflow
|
||||
const wid = workflow?.activeState?.id ?? workflow?.initialState?.id
|
||||
if (wid) {
|
||||
|
||||
Reference in New Issue
Block a user