feat(historyV2): reconcile completed workflows (#6340)

## Summary

Running + Finished + History tasks now all work and reconcile correctly
in the queue.

## Changes

1. Reconcile complete workflows so they show up in history.
2. Do the above in a way that minimizes recreation of `TaskItemImpls`
3. Address some CR feedback on #6336 

## Review Focus

I tried to optimize `TaskItemImpls` so we aren't recreating ones for
history items tat already exist. Please give me feedback on if I did
this correctly, or if it was even necessary.

## Screenshots 🎃 



https://github.com/user-attachments/assets/afc08f31-cc09-4082-8e9d-cee977bc1e22

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6340-feat-historyV2-reconcile-completed-workflows-29a6d73d36508145a56aeb99cfa0e6ba)
by [Unito](https://www.unito.io)
This commit is contained in:
Arjan Singh
2025-10-28 17:40:55 -07:00
committed by GitHub
parent 32688b8e34
commit 32a803c31e
7 changed files with 666 additions and 201 deletions

View File

@@ -1,12 +1,14 @@
import _ from 'es-toolkit/compat'
import { defineStore } from 'pinia'
import { computed, ref, toRaw } from 'vue'
import { computed, ref, shallowRef, toRaw, toValue } from 'vue'
import { reconcileHistory } from '@/platform/remote/comfyui/history/reconciliation'
import type {
ComfyWorkflowJSON,
NodeId
} from '@/platform/workflow/validation/schemas/workflowSchema'
import type {
HistoryTaskItem,
ResultItem,
StatusWsMessageStatus,
TaskItem,
@@ -423,14 +425,18 @@ export class TaskItemImpl {
)
)
}
public toTaskItem(): TaskItem {
const item: HistoryTaskItem = {
taskType: 'History',
prompt: this.prompt,
status: this.status!,
outputs: this.outputs
}
return item
}
}
const extractPromptIds = (tasks: TaskItem[]): Set<string> =>
new Set(tasks.map((task) => task.prompt[1]))
const isAddedAfter = (queueIndex: number) => (task: TaskItem) =>
task.prompt[0] > queueIndex
const sortNewestFirst = (a: TaskItemImpl, b: TaskItemImpl) =>
b.queueIndex - a.queueIndex
@@ -445,31 +451,12 @@ const toTaskItemImpls = (tasks: TaskItem[]): TaskItemImpl[] =>
)
)
const reconcileHistoryWithServer = (
serverHistory: TaskItem[],
clientHistory: TaskItemImpl[],
lastKnownQueueIndex: number,
maxItems: number
): TaskItemImpl[] => {
const serverPromptIds = extractPromptIds(serverHistory)
const itemsAddedSinceLastSync = toTaskItemImpls(
serverHistory.filter(isAddedAfter(lastKnownQueueIndex))
)
const itemsStillOnServer = clientHistory.filter((item) =>
serverPromptIds.has(item.promptId)
)
return [...itemsAddedSinceLastSync, ...itemsStillOnServer]
.sort(sortNewestFirst)
.slice(0, maxItems)
}
export const useQueueStore = defineStore('queue', () => {
const runningTasks = ref<TaskItemImpl[]>([])
const pendingTasks = ref<TaskItemImpl[]>([])
const historyTasks = ref<TaskItemImpl[]>([])
// Use shallowRef because TaskItemImpl instances are immutable and arrays are
// replaced entirely (not mutated), so deep reactivity would waste performance
const runningTasks = shallowRef<TaskItemImpl[]>([])
const pendingTasks = shallowRef<TaskItemImpl[]>([])
const historyTasks = shallowRef<TaskItemImpl[]>([])
const maxHistoryItems = ref(64)
const isLoading = ref(false)
@@ -503,11 +490,23 @@ export const useQueueStore = defineStore('queue', () => {
runningTasks.value = toTaskItemImpls(queue.Running).sort(sortNewestFirst)
pendingTasks.value = toTaskItemImpls(queue.Pending).sort(sortNewestFirst)
historyTasks.value = reconcileHistoryWithServer(
const currentHistory = toValue(historyTasks)
const { items } = reconcileHistory(
history.History,
historyTasks.value,
lastHistoryQueueIndex.value,
maxHistoryItems.value
currentHistory.map((impl) => impl.toTaskItem()),
toValue(maxHistoryItems),
toValue(lastHistoryQueueIndex)
)
// Reuse existing TaskItemImpl instances or create new
const existingByPromptId = new Map(
currentHistory.map((impl) => [impl.promptId, impl])
)
historyTasks.value = items.map(
(item) =>
existingByPromptId.get(item.prompt[1]) ?? toTaskItemImpls([item])[0]
)
} finally {
isLoading.value = false