mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 00:20:07 +00:00
139 lines
3.8 KiB
TypeScript
139 lines
3.8 KiB
TypeScript
/**
|
|
* @fileoverview Job output cache for caching and managing job data
|
|
* @module services/jobOutputCache
|
|
*
|
|
* Centralizes job output and detail caching with LRU eviction.
|
|
* Provides helpers for working with previewable outputs and workflows.
|
|
*/
|
|
|
|
import QuickLRU from '@alloc/quick-lru'
|
|
|
|
import type { JobDetail } from '@/platform/remote/comfyui/jobs/jobTypes'
|
|
import { extractWorkflow } from '@/platform/remote/comfyui/jobs/fetchJobs'
|
|
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
import type { ResultItem, TaskOutput } from '@/schemas/apiSchema'
|
|
import { api } from '@/scripts/api'
|
|
import { ResultItemImpl } from '@/stores/queueStore'
|
|
import type { TaskItemImpl } from '@/stores/queueStore'
|
|
|
|
const MAX_TASK_CACHE_SIZE = 50
|
|
const MAX_JOB_DETAIL_CACHE_SIZE = 50
|
|
|
|
const taskCache = new QuickLRU<string, TaskItemImpl>({
|
|
maxSize: MAX_TASK_CACHE_SIZE
|
|
})
|
|
const jobDetailCache = new QuickLRU<string, JobDetail>({
|
|
maxSize: MAX_JOB_DETAIL_CACHE_SIZE
|
|
})
|
|
|
|
// Track latest request to dedupe stale responses
|
|
let latestTaskRequestId: string | null = null
|
|
|
|
// ===== Task Output Caching =====
|
|
|
|
export function findActiveIndex(
|
|
items: readonly ResultItemImpl[],
|
|
url?: string
|
|
): number {
|
|
return ResultItemImpl.findByUrl(items, url)
|
|
}
|
|
|
|
/**
|
|
* Gets previewable outputs for a task, with lazy loading, caching, and request deduping.
|
|
* Returns null if a newer request superseded this one while loading.
|
|
*/
|
|
export async function getOutputsForTask(
|
|
task: TaskItemImpl
|
|
): Promise<ResultItemImpl[] | null> {
|
|
const requestId = String(task.promptId)
|
|
latestTaskRequestId = requestId
|
|
|
|
const outputsCount = task.outputsCount ?? 0
|
|
const needsLazyLoad = outputsCount > 1
|
|
|
|
if (!needsLazyLoad) {
|
|
return [...task.previewableOutputs]
|
|
}
|
|
|
|
const cached = taskCache.get(requestId)
|
|
if (cached) {
|
|
return [...cached.previewableOutputs]
|
|
}
|
|
|
|
try {
|
|
const loadedTask = await task.loadFullOutputs()
|
|
|
|
// Check if request was superseded while loading
|
|
if (latestTaskRequestId !== requestId) {
|
|
return null
|
|
}
|
|
|
|
taskCache.set(requestId, loadedTask)
|
|
return [...loadedTask.previewableOutputs]
|
|
} catch (error) {
|
|
console.warn('Failed to load full outputs, using preview:', error)
|
|
return [...task.previewableOutputs]
|
|
}
|
|
}
|
|
|
|
function getPreviewableOutputs(outputs?: TaskOutput): ResultItemImpl[] {
|
|
if (!outputs) return []
|
|
const resultItems = Object.entries(outputs).flatMap(([nodeId, nodeOutputs]) =>
|
|
Object.entries(nodeOutputs)
|
|
.filter(([mediaType, items]) => mediaType !== 'animated' && items)
|
|
.flatMap(([mediaType, items]) => {
|
|
if (!Array.isArray(items)) {
|
|
return []
|
|
}
|
|
|
|
return items.filter(isResultItem).map(
|
|
(item) =>
|
|
new ResultItemImpl({
|
|
...item,
|
|
nodeId,
|
|
mediaType
|
|
})
|
|
)
|
|
})
|
|
)
|
|
|
|
return ResultItemImpl.filterPreviewable(resultItems)
|
|
}
|
|
|
|
function isResultItem(item: unknown): item is ResultItem {
|
|
return typeof item === 'object' && item !== null
|
|
}
|
|
|
|
export function getPreviewableOutputsFromJobDetail(
|
|
jobDetail?: JobDetail
|
|
): ResultItemImpl[] {
|
|
return getPreviewableOutputs(jobDetail?.outputs)
|
|
}
|
|
|
|
// ===== Job Detail Caching =====
|
|
|
|
export async function getJobDetail(
|
|
jobId: string
|
|
): Promise<JobDetail | undefined> {
|
|
const cached = jobDetailCache.get(jobId)
|
|
if (cached) return cached
|
|
|
|
try {
|
|
const detail = await api.getJobDetail(jobId)
|
|
if (detail) {
|
|
jobDetailCache.set(jobId, detail)
|
|
}
|
|
return detail
|
|
} catch (error) {
|
|
console.warn('Failed to fetch job detail:', error)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
export async function getJobWorkflow(
|
|
jobId: string
|
|
): Promise<ComfyWorkflowJSON | undefined> {
|
|
const detail = await getJobDetail(jobId)
|
|
return await extractWorkflow(detail)
|
|
}
|