Skip to content

Commit eb0ca33

Browse files
ric-yuclaude
andcommitted
[feat] Migrate to Jobs API (PR 2 of 3)
This PR switches the frontend from legacy /history and /queue endpoints to the unified /jobs API. Key changes: - Rewrite TaskItemImpl in queueStore.ts to wrap JobListItem - Update api.ts getQueue()/getHistory() to use Jobs API - Update all queue composables (useJobList, useJobMenu, useResultGallery) - Update useJobErrorReporting to use execution_error.exception_message - Update JobGroupsList.vue workflowId access - Update reconciliation.ts to work with JobListItem - Update all related tests Breaking changes: - getQueue() now returns JobListItem[] instead of legacy tuple format - getHistory() now returns JobListItem[] instead of HistoryTaskItem[] - TaskItemImpl.outputs now lazily loads via loadFullOutputs() Part of Jobs API migration. Depends on PR 1 (jobs-api-pr1-infrastructure). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 5689cca commit eb0ca33

File tree

26 files changed

+1041
-1507
lines changed

26 files changed

+1041
-1507
lines changed

browser_tests/fixtures/ComfyPage.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
import { Topbar } from './components/Topbar'
2222
import type { Position, Size } from './types'
2323
import { NodeReference, SubgraphSlotReference } from './utils/litegraphUtils'
24-
import TaskHistory from './utils/taskHistory'
2524

2625
dotenv.config()
2726

@@ -116,8 +115,6 @@ class ConfirmDialog {
116115
}
117116

118117
export class ComfyPage {
119-
private _history: TaskHistory | null = null
120-
121118
public readonly url: string
122119
// All canvas position operations are based on default view of canvas.
123120
public readonly canvas: Locator
@@ -268,11 +265,6 @@ export class ComfyPage {
268265
}
269266
}
270267

271-
setupHistory(): TaskHistory {
272-
this._history ??= new TaskHistory(this)
273-
return this._history
274-
}
275-
276268
async setup({
277269
clearStorage = true,
278270
mockReleases = true

src/components/queue/QueueProgressOverlay.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,11 @@ const {
208208
galleryActiveIndex,
209209
galleryItems,
210210
onViewItem: openResultGallery
211-
} = useResultGallery(() => filteredTasks.value)
211+
} = useResultGallery(
212+
() => filteredTasks.value,
213+
// Lazy load full outputs for history items
214+
(url) => api.fetchApi(url)
215+
)
212216
213217
const setExpanded = (expanded: boolean) => {
214218
isExpanded.value = expanded
@@ -252,7 +256,7 @@ const focusAssetInSidebar = async (item: JobListItem) => {
252256
253257
const inspectJobAsset = wrapWithErrorHandlingAsync(
254258
async (item: JobListItem) => {
255-
openResultGallery(item)
259+
await openResultGallery(item)
256260
await focusAssetInSidebar(item)
257261
}
258262
)

src/components/queue/job/JobDetailsPopover.stories.ts

Lines changed: 66 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from '@storybook/vue3-vite'
22

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

@@ -37,91 +37,88 @@ function resetStores() {
3737
exec.nodeProgressStatesByPrompt = {}
3838
}
3939

40-
function makePendingTask(
40+
function makeTask(
4141
id: string,
42-
index: number,
43-
createTimeMs?: number
42+
priority: number,
43+
overrides: Omit<Partial<JobListItem>, 'id' | 'priority'> &
44+
Pick<JobListItem, 'status' | 'create_time' | 'update_time'>
4445
): TaskItemImpl {
45-
const extraData = {
46-
client_id: 'c1',
47-
...(typeof createTimeMs === 'number' ? { create_time: createTimeMs } : {})
46+
const job: JobListItem = {
47+
id,
48+
priority,
49+
last_state_update: null,
50+
...overrides
4851
}
49-
return new TaskItemImpl('Pending', [index, id, {}, extraData, []])
52+
return new TaskItemImpl(job)
53+
}
54+
55+
function makePendingTask(
56+
id: string,
57+
priority: number,
58+
createTimeMs: number
59+
): TaskItemImpl {
60+
return makeTask(id, priority, {
61+
status: 'pending',
62+
create_time: createTimeMs,
63+
update_time: createTimeMs
64+
})
5065
}
5166

5267
function makeRunningTask(
5368
id: string,
54-
index: number,
55-
createTimeMs?: number
69+
priority: number,
70+
createTimeMs: number
5671
): TaskItemImpl {
57-
const extraData = {
58-
client_id: 'c1',
59-
...(typeof createTimeMs === 'number' ? { create_time: createTimeMs } : {})
60-
}
61-
return new TaskItemImpl('Running', [index, id, {}, extraData, []])
72+
return makeTask(id, priority, {
73+
status: 'in_progress',
74+
create_time: createTimeMs,
75+
update_time: createTimeMs
76+
})
6277
}
6378

6479
function makeRunningTaskWithStart(
6580
id: string,
66-
index: number,
81+
priority: number,
6782
startedSecondsAgo: number
6883
): TaskItemImpl {
6984
const start = Date.now() - startedSecondsAgo * 1000
70-
const status: TaskStatus = {
71-
status_str: 'success',
72-
completed: false,
73-
messages: [['execution_start', { prompt_id: id, timestamp: start } as any]]
74-
}
75-
return new TaskItemImpl(
76-
'Running',
77-
[index, id, {}, { client_id: 'c1', create_time: start - 5000 }, []],
78-
status
79-
)
85+
return makeTask(id, priority, {
86+
status: 'in_progress',
87+
create_time: start - 5000,
88+
update_time: start
89+
})
8090
}
8191

8292
function makeHistoryTask(
8393
id: string,
84-
index: number,
94+
priority: number,
8595
durationSec: number,
8696
ok: boolean,
8797
errorMessage?: string
8898
): TaskItemImpl {
89-
const start = Date.now() - durationSec * 1000 - 1000
90-
const end = start + durationSec * 1000
91-
const messages: TaskStatus['messages'] = ok
92-
? [
93-
['execution_start', { prompt_id: id, timestamp: start } as any],
94-
['execution_success', { prompt_id: id, timestamp: end } as any]
95-
]
96-
: [
97-
['execution_start', { prompt_id: id, timestamp: start } as any],
98-
[
99-
'execution_error',
100-
{
101-
prompt_id: id,
102-
timestamp: end,
103-
node_id: '1',
104-
node_type: 'Node',
105-
executed: [],
106-
exception_message:
107-
errorMessage || 'Demo error: Node failed during execution',
108-
exception_type: 'RuntimeError',
109-
traceback: [],
110-
current_inputs: {},
111-
current_outputs: {}
112-
} as any
113-
]
114-
]
115-
const status: TaskStatus = {
116-
status_str: ok ? 'success' : 'error',
117-
completed: true,
118-
messages
119-
}
120-
return new TaskItemImpl(
121-
'History',
122-
[index, id, {}, { client_id: 'c1', create_time: start }, []],
123-
status
124-
)
99+
const now = Date.now()
100+
const executionEndTime = now
101+
const executionStartTime = now - durationSec * 1000
102+
return makeTask(id, priority, {
103+
status: ok ? 'completed' : 'failed',
104+
create_time: executionStartTime - 5000,
105+
update_time: now,
106+
execution_start_time: executionStartTime,
107+
execution_end_time: executionEndTime,
108+
execution_error: errorMessage
109+
? {
110+
prompt_id: id,
111+
timestamp: now,
112+
node_id: '1',
113+
node_type: 'ExampleNode',
114+
exception_message: errorMessage,
115+
exception_type: 'RuntimeError',
116+
traceback: [],
117+
current_inputs: {},
118+
current_outputs: {}
119+
}
120+
: undefined
121+
})
125122
}
126123

127124
export const Queued: Story = {
@@ -140,8 +137,12 @@ export const Queued: Story = {
140137
makePendingTask(jobId, queueIndex, Date.now() - 90_000)
141138
]
142139
// Add some other pending jobs to give context
143-
queue.pendingTasks.push(makePendingTask('job-older-1', 100))
144-
queue.pendingTasks.push(makePendingTask('job-older-2', 101))
140+
queue.pendingTasks.push(
141+
makePendingTask('job-older-1', 100, Date.now() - 60_000)
142+
)
143+
queue.pendingTasks.push(
144+
makePendingTask('job-older-2', 101, Date.now() - 30_000)
145+
)
145146

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

src/components/queue/job/JobGroupsList.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
v-for="ji in group.items"
1313
:key="ji.id"
1414
:job-id="ji.id"
15-
:workflow-id="ji.taskRef?.workflow?.id"
15+
:workflow-id="ji.taskRef?.workflowId"
1616
:state="ji.state"
1717
:title="ji.title"
1818
:right-text="ji.meta"

src/components/queue/job/useJobErrorReporting.ts

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { TaskItemImpl } from '@/stores/queueStore'
77
type CopyHandler = (value: string) => void | Promise<void>
88

99
export type JobErrorDialogService = {
10-
showExecutionErrorDialog: (error: ExecutionErrorWsMessage) => void
10+
showExecutionErrorDialog: (executionError: ExecutionErrorWsMessage) => void
1111
showErrorDialog: (
1212
error: Error,
1313
options?: {
@@ -17,29 +17,6 @@ export type JobErrorDialogService = {
1717
) => void
1818
}
1919

20-
type JobExecutionError = {
21-
detail?: ExecutionErrorWsMessage
22-
message: string
23-
}
24-
25-
export const extractExecutionError = (
26-
task: TaskItemImpl | null
27-
): JobExecutionError | null => {
28-
const status = (task as TaskItemImpl | null)?.status
29-
const messages = (status as { messages?: unknown[] } | undefined)?.messages
30-
if (!Array.isArray(messages) || !messages.length) return null
31-
const record = messages.find((entry: unknown) => {
32-
return Array.isArray(entry) && entry[0] === 'execution_error'
33-
}) as [string, ExecutionErrorWsMessage?] | undefined
34-
if (!record) return null
35-
const detail = record[1]
36-
const message = String(detail?.exception_message ?? '')
37-
return {
38-
detail,
39-
message
40-
}
41-
}
42-
4320
type UseJobErrorReportingOptions = {
4421
taskForJob: ComputedRef<TaskItemImpl | null>
4522
copyToClipboard: CopyHandler
@@ -51,10 +28,7 @@ export const useJobErrorReporting = ({
5128
copyToClipboard,
5229
dialog
5330
}: UseJobErrorReportingOptions) => {
54-
const errorMessageValue = computed(() => {
55-
const error = extractExecutionError(taskForJob.value)
56-
return error?.message ?? ''
57-
})
31+
const errorMessageValue = computed(() => taskForJob.value?.errorMessage ?? '')
5832

5933
const copyErrorMessage = () => {
6034
if (errorMessageValue.value) {
@@ -63,11 +37,16 @@ export const useJobErrorReporting = ({
6337
}
6438

6539
const reportJobError = () => {
66-
const error = extractExecutionError(taskForJob.value)
67-
if (error?.detail) {
68-
dialog.showExecutionErrorDialog(error.detail)
40+
const task = taskForJob.value
41+
42+
// Use execution_error from list response if available (includes prompt_id, timestamp)
43+
const executionError = task?.executionError
44+
if (executionError) {
45+
dialog.showExecutionErrorDialog(executionError as ExecutionErrorWsMessage)
6946
return
7047
}
48+
49+
// Fall back to simple error dialog
7150
if (errorMessageValue.value) {
7251
dialog.showErrorDialog(new Error(errorMessageValue.value), {
7352
reportType: 'queueJobError'

src/composables/queue/useJobList.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export function useJobList() {
238238
const activeId = workflowStore.activeWorkflow?.activeState?.id
239239
if (!activeId) return []
240240
entries = entries.filter(({ task }) => {
241-
const wid = task.workflow?.id
241+
const wid = task.workflowId
242242
return !!wid && wid === activeId
243243
})
244244
}

0 commit comments

Comments
 (0)