Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
import { Topbar } from './components/Topbar'
import type { Position, Size } from './types'
import { NodeReference, SubgraphSlotReference } from './utils/litegraphUtils'
import TaskHistory from './utils/taskHistory'

dotenv.config()

Expand Down Expand Up @@ -116,8 +115,6 @@ class ConfirmDialog {
}

export class ComfyPage {
private _history: TaskHistory | null = null

public readonly url: string
// All canvas position operations are based on default view of canvas.
public readonly canvas: Locator
Expand Down Expand Up @@ -268,11 +265,6 @@ export class ComfyPage {
}
}

setupHistory(): TaskHistory {
this._history ??= new TaskHistory(this)
return this._history
}

async setup({
clearStorage = true,
mockReleases = true
Expand Down
8 changes: 6 additions & 2 deletions src/components/queue/QueueProgressOverlay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,11 @@ const {
galleryActiveIndex,
galleryItems,
onViewItem: openResultGallery
} = useResultGallery(() => filteredTasks.value)
} = useResultGallery(
() => filteredTasks.value,
// Lazy load full outputs for history items
(url) => api.fetchApi(url)
)

const setExpanded = (expanded: boolean) => {
isExpanded.value = expanded
Expand Down Expand Up @@ -252,7 +256,7 @@ const focusAssetInSidebar = async (item: JobListItem) => {

const inspectJobAsset = wrapWithErrorHandlingAsync(
async (item: JobListItem) => {
openResultGallery(item)
await openResultGallery(item)
await focusAssetInSidebar(item)
}
)
Expand Down
131 changes: 66 additions & 65 deletions src/components/queue/job/JobDetailsPopover.stories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Meta, StoryObj } from '@storybook/vue3-vite'

import type { TaskStatus } from '@/schemas/apiSchema'
import type { JobListItem } from '@/platform/remote/comfyui/jobs/types/jobTypes'
import { useExecutionStore } from '@/stores/executionStore'
import { TaskItemImpl, useQueueStore } from '@/stores/queueStore'

Expand Down Expand Up @@ -37,91 +37,88 @@ function resetStores() {
exec.nodeProgressStatesByPrompt = {}
}

function makePendingTask(
function makeTask(
id: string,
index: number,
createTimeMs?: number
priority: number,
overrides: Omit<Partial<JobListItem>, 'id' | 'priority'> &
Pick<JobListItem, 'status' | 'create_time' | 'update_time'>
): TaskItemImpl {
const extraData = {
client_id: 'c1',
...(typeof createTimeMs === 'number' ? { create_time: createTimeMs } : {})
const job: JobListItem = {
id,
priority,
last_state_update: null,
...overrides
}
return new TaskItemImpl('Pending', [index, id, {}, extraData, []])
return new TaskItemImpl(job)
}

function makePendingTask(
id: string,
priority: number,
createTimeMs: number
): TaskItemImpl {
return makeTask(id, priority, {
status: 'pending',
create_time: createTimeMs,
update_time: createTimeMs
})
}

function makeRunningTask(
id: string,
index: number,
createTimeMs?: number
priority: number,
createTimeMs: number
): TaskItemImpl {
const extraData = {
client_id: 'c1',
...(typeof createTimeMs === 'number' ? { create_time: createTimeMs } : {})
}
return new TaskItemImpl('Running', [index, id, {}, extraData, []])
return makeTask(id, priority, {
status: 'in_progress',
create_time: createTimeMs,
update_time: createTimeMs
})
}

function makeRunningTaskWithStart(
id: string,
index: number,
priority: number,
startedSecondsAgo: number
): TaskItemImpl {
const start = Date.now() - startedSecondsAgo * 1000
const status: TaskStatus = {
status_str: 'success',
completed: false,
messages: [['execution_start', { prompt_id: id, timestamp: start } as any]]
}
return new TaskItemImpl(
'Running',
[index, id, {}, { client_id: 'c1', create_time: start - 5000 }, []],
status
)
return makeTask(id, priority, {
status: 'in_progress',
create_time: start - 5000,
update_time: start
})
}

function makeHistoryTask(
id: string,
index: number,
priority: number,
durationSec: number,
ok: boolean,
errorMessage?: string
): TaskItemImpl {
const start = Date.now() - durationSec * 1000 - 1000
const end = start + durationSec * 1000
const messages: TaskStatus['messages'] = ok
? [
['execution_start', { prompt_id: id, timestamp: start } as any],
['execution_success', { prompt_id: id, timestamp: end } as any]
]
: [
['execution_start', { prompt_id: id, timestamp: start } as any],
[
'execution_error',
{
prompt_id: id,
timestamp: end,
node_id: '1',
node_type: 'Node',
executed: [],
exception_message:
errorMessage || 'Demo error: Node failed during execution',
exception_type: 'RuntimeError',
traceback: [],
current_inputs: {},
current_outputs: {}
} as any
]
]
const status: TaskStatus = {
status_str: ok ? 'success' : 'error',
completed: true,
messages
}
return new TaskItemImpl(
'History',
[index, id, {}, { client_id: 'c1', create_time: start }, []],
status
)
const now = Date.now()
const executionEndTime = now
const executionStartTime = now - durationSec * 1000
return makeTask(id, priority, {
status: ok ? 'completed' : 'failed',
create_time: executionStartTime - 5000,
update_time: now,
execution_start_time: executionStartTime,
execution_end_time: executionEndTime,
execution_error: errorMessage
? {
prompt_id: id,
timestamp: now,
node_id: '1',
node_type: 'ExampleNode',
exception_message: errorMessage,
exception_type: 'RuntimeError',
traceback: [],
current_inputs: {},
current_outputs: {}
}
: undefined
})
}

export const Queued: Story = {
Expand All @@ -140,8 +137,12 @@ export const Queued: Story = {
makePendingTask(jobId, queueIndex, Date.now() - 90_000)
]
// Add some other pending jobs to give context
queue.pendingTasks.push(makePendingTask('job-older-1', 100))
queue.pendingTasks.push(makePendingTask('job-older-2', 101))
queue.pendingTasks.push(
makePendingTask('job-older-1', 100, Date.now() - 60_000)
)
queue.pendingTasks.push(
makePendingTask('job-older-2', 101, Date.now() - 30_000)
)

// Queued at (in metadata on prompt[4])

Expand Down
2 changes: 1 addition & 1 deletion src/components/queue/job/JobGroupsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
v-for="ji in group.items"
:key="ji.id"
:job-id="ji.id"
:workflow-id="ji.taskRef?.workflow?.id"
:workflow-id="ji.taskRef?.workflowId"
:state="ji.state"
:title="ji.title"
:right-text="ji.meta"
Expand Down
41 changes: 10 additions & 31 deletions src/components/queue/job/useJobErrorReporting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { TaskItemImpl } from '@/stores/queueStore'
type CopyHandler = (value: string) => void | Promise<void>

export type JobErrorDialogService = {
showExecutionErrorDialog: (error: ExecutionErrorWsMessage) => void
showExecutionErrorDialog: (executionError: ExecutionErrorWsMessage) => void
showErrorDialog: (
error: Error,
options?: {
Expand All @@ -17,29 +17,6 @@ export type JobErrorDialogService = {
) => void
}

type JobExecutionError = {
detail?: ExecutionErrorWsMessage
message: string
}

export const extractExecutionError = (
task: TaskItemImpl | null
): JobExecutionError | null => {
const status = (task as TaskItemImpl | null)?.status
const messages = (status as { messages?: unknown[] } | undefined)?.messages
if (!Array.isArray(messages) || !messages.length) return null
const record = messages.find((entry: unknown) => {
return Array.isArray(entry) && entry[0] === 'execution_error'
}) as [string, ExecutionErrorWsMessage?] | undefined
if (!record) return null
const detail = record[1]
const message = String(detail?.exception_message ?? '')
return {
detail,
message
}
}

type UseJobErrorReportingOptions = {
taskForJob: ComputedRef<TaskItemImpl | null>
copyToClipboard: CopyHandler
Expand All @@ -51,10 +28,7 @@ export const useJobErrorReporting = ({
copyToClipboard,
dialog
}: UseJobErrorReportingOptions) => {
const errorMessageValue = computed(() => {
const error = extractExecutionError(taskForJob.value)
return error?.message ?? ''
})
const errorMessageValue = computed(() => taskForJob.value?.errorMessage ?? '')

const copyErrorMessage = () => {
if (errorMessageValue.value) {
Expand All @@ -63,11 +37,16 @@ export const useJobErrorReporting = ({
}

const reportJobError = () => {
const error = extractExecutionError(taskForJob.value)
if (error?.detail) {
dialog.showExecutionErrorDialog(error.detail)
const task = taskForJob.value

// Use execution_error from list response if available (includes prompt_id, timestamp)
const executionError = task?.executionError
if (executionError) {
dialog.showExecutionErrorDialog(executionError as ExecutionErrorWsMessage)
return
}

// Fall back to simple error dialog
if (errorMessageValue.value) {
dialog.showErrorDialog(new Error(errorMessageValue.value), {
reportType: 'queueJobError'
Expand Down
2 changes: 1 addition & 1 deletion src/composables/queue/useJobList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export function useJobList() {
const activeId = workflowStore.activeWorkflow?.activeState?.id
if (!activeId) return []
entries = entries.filter(({ task }) => {
const wid = task.workflow?.id
const wid = task.workflowId
return !!wid && wid === activeId
})
}
Expand Down
Loading