Skip to content

Commit 9a6a1c1

Browse files
committed
use new history_v2 prompt response - lazy load workflows with /history_v2/:prompt_id
1 parent 19eaf6e commit 9a6a1c1

File tree

9 files changed

+283
-61
lines changed

9 files changed

+283
-61
lines changed

browser_tests/fixtures/utils/taskHistory.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,21 @@ const getContentType = (filename: string, fileType: OutputFileType) => {
3434
}
3535

3636
const setQueueIndex = (task: TaskItem) => {
37-
task.prompt[0] = TaskHistory.queueIndex++
37+
task.prompt.priority = TaskHistory.queueIndex++
3838
}
3939

4040
const setPromptId = (task: TaskItem) => {
41-
task.prompt[1] = uuidv4()
41+
task.prompt.prompt_id = uuidv4()
4242
}
4343

4444
export default class TaskHistory {
4545
static queueIndex = 0
4646
static readonly defaultTask: Readonly<HistoryTaskItem> = {
47-
prompt: [0, 'prompt-id', {}, { client_id: uuidv4() }, []],
47+
prompt: {
48+
priority: 0,
49+
prompt_id: 'prompt-id',
50+
extra_data: { client_id: uuidv4() }
51+
},
4852
outputs: {},
4953
status: {
5054
status_str: 'success',
@@ -75,7 +79,7 @@ export default class TaskHistory {
7579

7680
private async handleGetView(route: Route) {
7781
const fileName = getFilenameParam(route.request())
78-
if (!this.outputContentTypes.has(fileName)) route.continue()
82+
if (!this.outputContentTypes.has(fileName)) return route.continue()
7983

8084
const asset = this.loadAsset(fileName)
8185
return route.fulfill({

src/components/sidebar/tabs/QueueSidebarTab.vue

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
106106
import VirtualGrid from '@/components/common/VirtualGrid.vue'
107107
import { ComfyNode } from '@/schemas/comfyWorkflowSchema'
108108
import { api } from '@/scripts/api'
109-
import { app } from '@/scripts/app'
110109
import { useLitegraphService } from '@/services/litegraphService'
110+
import { useWorkflowService } from '@/services/workflowService'
111111
import { useCommandStore } from '@/stores/commandStore'
112112
import {
113113
ResultItemImpl,
@@ -126,6 +126,7 @@ const toast = useToast()
126126
const queueStore = useQueueStore()
127127
const settingStore = useSettingStore()
128128
const commandStore = useCommandStore()
129+
const workflowService = useWorkflowService()
129130
const { t } = useI18n()
130131
131132
// Expanded view: show all outputs in a flat list.
@@ -208,8 +209,17 @@ const menuItems = computed<MenuItem[]>(() => {
208209
{
209210
label: t('g.loadWorkflow'),
210211
icon: 'pi pi-file-export',
211-
command: () => menuTargetTask.value?.loadWorkflow(app),
212-
disabled: !menuTargetTask.value?.workflow
212+
command: () => {
213+
if (menuTargetTask.value) {
214+
void workflowService.loadTaskWorkflow(menuTargetTask.value)
215+
}
216+
},
217+
disabled:
218+
!menuTargetTask.value?.workflow &&
219+
!(
220+
menuTargetTask.value?.isHistory &&
221+
menuTargetTask.value?.prompt.prompt_id
222+
)
213223
},
214224
{
215225
label: t('g.goToNode'),

src/schemas/apiSchema.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,6 @@ export type DisplayComponentWsMessage = z.infer<
134134
>
135135
// End of ws messages
136136

137-
const zPromptInputItem = z.object({
138-
inputs: z.record(z.string(), z.any()),
139-
class_type: zNodeType
140-
})
141-
142-
const zPromptInputs = z.record(zPromptInputItem)
143-
144137
const zExtraPngInfo = z
145138
.object({
146139
workflow: zComfyWorkflow
@@ -152,7 +145,6 @@ const zExtraData = z.object({
152145
extra_pnginfo: zExtraPngInfo.optional(),
153146
client_id: z.string()
154147
})
155-
const zOutputsToExecute = z.array(zNodeId)
156148

157149
const zExecutionStartMessage = z.tuple([
158150
z.literal('execution_start'),
@@ -193,13 +185,11 @@ const zStatus = z.object({
193185
messages: z.array(zStatusMessage)
194186
})
195187

196-
const zTaskPrompt = z.tuple([
197-
zQueueIndex,
198-
zPromptId,
199-
zPromptInputs,
200-
zExtraData,
201-
zOutputsToExecute
202-
])
188+
const zTaskPrompt = z.object({
189+
priority: zQueueIndex,
190+
prompt_id: zPromptId,
191+
extra_data: zExtraData
192+
})
203193

204194
const zRunningTaskItem = z.object({
205195
taskType: z.literal('Running'),
@@ -235,6 +225,20 @@ const zHistoryTaskItem = z.object({
235225
meta: zTaskMeta.optional()
236226
})
237227

228+
// Raw history item from backend (without taskType)
229+
const zRawHistoryItem = z.object({
230+
prompt_id: zPromptId,
231+
prompt: zTaskPrompt,
232+
status: zStatus.optional(),
233+
outputs: zTaskOutput,
234+
meta: zTaskMeta.optional()
235+
})
236+
237+
// New API response format: { history: [{prompt_id: "...", ...}, ...] }
238+
const zHistoryResponse = z.object({
239+
history: z.array(zRawHistoryItem)
240+
})
241+
238242
const zTaskItem = z.union([
239243
zRunningTaskItem,
240244
zPendingTaskItem,
@@ -257,6 +261,8 @@ export type RunningTaskItem = z.infer<typeof zRunningTaskItem>
257261
export type PendingTaskItem = z.infer<typeof zPendingTaskItem>
258262
// `/history`
259263
export type HistoryTaskItem = z.infer<typeof zHistoryTaskItem>
264+
export type RawHistoryItem = z.infer<typeof zRawHistoryItem>
265+
export type HistoryResponse = z.infer<typeof zHistoryResponse>
260266
export type TaskItem = z.infer<typeof zTaskItem>
261267

262268
export function validateTaskItem(taskItem: unknown) {

src/scripts/api.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
ExecutionStartWsMessage,
1212
ExecutionSuccessWsMessage,
1313
ExtensionsResponse,
14+
HistoryResponse,
1415
HistoryTaskItem,
1516
LogsRawResponse,
1617
LogsWsMessage,
@@ -23,6 +24,7 @@ import type {
2324
StatusWsMessage,
2425
StatusWsMessageStatus,
2526
SystemStats,
27+
TaskPrompt,
2628
User,
2729
UserDataFullInfo
2830
} from '@/schemas/apiSchema'
@@ -686,13 +688,12 @@ export class ComfyApi extends EventTarget {
686688
const data = await res.json()
687689
return {
688690
// Running action uses a different endpoint for cancelling
689-
Running: data.queue_running.map((prompt: Record<number, any>) => ({
691+
Running: data.queue_running.map((prompt: TaskPrompt) => ({
690692
taskType: 'Running',
691693
prompt,
692-
// prompt[1] is the prompt id
693-
remove: { name: 'Cancel', cb: () => api.interrupt(prompt[1]) }
694+
remove: { name: 'Cancel', cb: () => api.interrupt(prompt.prompt_id) }
694695
})),
695-
Pending: data.queue_pending.map((prompt: Record<number, any>) => ({
696+
Pending: data.queue_pending.map((prompt: TaskPrompt) => ({
696697
taskType: 'Pending',
697698
prompt
698699
}))
@@ -711,20 +712,51 @@ export class ComfyApi extends EventTarget {
711712
max_items: number = 200
712713
): Promise<{ History: HistoryTaskItem[] }> {
713714
try {
714-
const res = await this.fetchApi(`/history?max_items=${max_items}`)
715-
const json: Promise<HistoryTaskItem[]> = await res.json()
715+
const res = await this.fetchApi(`/history_v2?max_items=${max_items}`)
716+
const json: HistoryResponse = await res.json()
717+
718+
// Extract history data from new format: { history: [{prompt_id: "...", ...}, ...] }
716719
return {
717-
History: Object.values(json).map((item) => ({
718-
...item,
719-
taskType: 'History'
720-
}))
720+
History: json.history.map(
721+
(item): HistoryTaskItem => ({
722+
...item,
723+
taskType: 'History'
724+
})
725+
)
721726
}
722727
} catch (error) {
723728
console.error(error)
724729
return { History: [] }
725730
}
726731
}
727732

733+
/**
734+
* Gets workflow data for a specific prompt from history
735+
* @param prompt_id The prompt ID to fetch workflow for
736+
* @returns Workflow data for the specific prompt
737+
*/
738+
async getWorkflowFromHistory(
739+
prompt_id: string
740+
): Promise<ComfyWorkflowJSON | null> {
741+
try {
742+
const res = await this.fetchApi(`/history_v2/${prompt_id}`)
743+
const json = await res.json()
744+
745+
// The /history_v2/{prompt_id} endpoint returns data for a specific prompt
746+
// The response format is: { prompt_id: { prompt: [...], outputs: {...}, status: {...} } }
747+
const historyItem = json[prompt_id]
748+
if (!historyItem) return null
749+
750+
// Extract workflow from the prompt array
751+
// prompt[3] contains extra_data which has extra_pnginfo.workflow
752+
const workflow = historyItem.prompt?.[3]?.extra_pnginfo?.workflow
753+
return workflow || null
754+
} catch (error) {
755+
console.error(`Failed to fetch workflow for prompt ${prompt_id}:`, error)
756+
return null
757+
}
758+
}
759+
728760
/**
729761
* Gets system & device stats
730762
* @returns System stats such as python version, OS, per device info

src/scripts/ui.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,16 @@ class ComfyList {
264264
? item.remove
265265
: {
266266
name: 'Delete',
267-
cb: () => api.deleteItem(this.#type, item.prompt[1])
267+
cb: () =>
268+
api.deleteItem(this.#type, item.prompt.prompt_id)
268269
}
269-
return $el('div', { textContent: item.prompt[0] + ': ' }, [
270+
return $el('div', { textContent: item.prompt.priority + ': ' }, [
270271
$el('button', {
271272
textContent: 'Load',
272273
onclick: async () => {
273274
await app.loadGraphData(
274275
// @ts-expect-error fixme ts strict error
275-
item.prompt[3].extra_pnginfo.workflow,
276+
item.prompt.extra_data.extra_pnginfo.workflow,
276277
true,
277278
false
278279
)

src/services/workflowService.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import { toRaw } from 'vue'
44

55
import { t } from '@/i18n'
66
import { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema'
7+
import { api } from '@/scripts/api'
78
import { app } from '@/scripts/app'
89
import { blankGraph, defaultGraph } from '@/scripts/defaultGraph'
910
import { downloadBlob } from '@/scripts/utils'
1011
import { useDomWidgetStore } from '@/stores/domWidgetStore'
12+
import { TaskItemImpl } from '@/stores/queueStore'
1113
import { useSettingStore } from '@/stores/settingStore'
1214
import { useToastStore } from '@/stores/toastStore'
1315
import { ComfyWorkflow, useWorkflowStore } from '@/stores/workflowStore'
@@ -152,6 +154,32 @@ export const useWorkflowService = () => {
152154
await app.loadGraphData(blankGraph)
153155
}
154156

157+
/**
158+
* Load a workflow from a task item (queue/history)
159+
* For history items, fetches workflow data from /history_v2/{prompt_id}
160+
* @param task The task item to load the workflow from
161+
*/
162+
const loadTaskWorkflow = async (task: TaskItemImpl) => {
163+
let workflowData = task.workflow
164+
165+
// History items don't include workflow data - fetch from API
166+
if (task.isHistory) {
167+
const promptId = task.prompt.prompt_id
168+
if (promptId) {
169+
workflowData = (await api.getWorkflowFromHistory(promptId)) || undefined
170+
}
171+
}
172+
173+
if (!workflowData) {
174+
return
175+
}
176+
177+
await app.loadGraphData(toRaw(workflowData))
178+
if (task.outputs) {
179+
app.nodeOutputs = toRaw(task.outputs)
180+
}
181+
}
182+
155183
/**
156184
* Reload the current workflow
157185
* This is used to refresh the node definitions update, e.g. when the locale changes.
@@ -394,6 +422,7 @@ export const useWorkflowService = () => {
394422
saveWorkflow,
395423
loadDefaultWorkflow,
396424
loadBlankWorkflow,
425+
loadTaskWorkflow,
397426
reloadCurrentWorkflow,
398427
openWorkflow,
399428
closeWorkflow,

src/stores/queueStore.ts

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -269,23 +269,15 @@ export class TaskItemImpl {
269269
}
270270

271271
get queueIndex() {
272-
return this.prompt[0]
272+
return this.prompt.priority
273273
}
274274

275275
get promptId() {
276-
return this.prompt[1]
277-
}
278-
279-
get promptInputs() {
280-
return this.prompt[2]
276+
return this.prompt.prompt_id
281277
}
282278

283279
get extraData() {
284-
return this.prompt[3]
285-
}
286-
287-
get outputsToExecute() {
288-
return this.prompt[4]
280+
return this.prompt.extra_data
289281
}
290282

291283
get extraPngInfo() {
@@ -390,13 +382,11 @@ export class TaskItemImpl {
390382
(output: ResultItemImpl, i: number) =>
391383
new TaskItemImpl(
392384
this.taskType,
393-
[
394-
this.queueIndex,
395-
`${this.promptId}-${i}`,
396-
this.promptInputs,
397-
this.extraData,
398-
this.outputsToExecute
399-
],
385+
{
386+
priority: this.queueIndex,
387+
prompt_id: `${this.promptId}-${i}`,
388+
extra_data: this.extraData
389+
},
400390
this.status,
401391
{
402392
[output.nodeId]: {
@@ -461,11 +451,11 @@ export const useQueueStore = defineStore('queue', () => {
461451
pendingTasks.value = toClassAll(queue.Pending)
462452

463453
const allIndex = new Set<number>(
464-
history.History.map((item: TaskItem) => item.prompt[0])
454+
history.History.map((item: TaskItem) => item.prompt.priority)
465455
)
466456
const newHistoryItems = toClassAll(
467457
history.History.filter(
468-
(item) => item.prompt[0] > lastHistoryQueueIndex.value
458+
(item) => item.prompt.priority > lastHistoryQueueIndex.value
469459
)
470460
)
471461
const existingHistoryItems = historyTasks.value.filter((item) =>

0 commit comments

Comments
 (0)