feat: add KSampler live previews to assets sidebar jobs (#8723)

## Summary
Show live KSampler previews on active job cards/list items in the Assets
sidebar, while preserving existing fallback behavior.

## Changes
- **What**:
- Added a prompt-scoped job preview store (`jobPreviewStore`) gated by
`Comfy.Execution.PreviewMethod`.
- Wired `b_preview_with_metadata` handling to map previews by
`promptId`.
- Extended queue job view model with `livePreviewUrl` and consumed it in
both sidebar list and grid active job UIs.
  - Cleared prompt previews on execution reset.
- Added ref-counted shared blob URL lifecycle utility (`objectUrlUtil`)
and updated preview stores to retain/release shared URLs so each preview
event creates one object URL.
- Added/updated unit coverage in `useJobList.test.ts` for preview
enable/disable mapping.

## Review Focus
- Object URL lifecycle correctness across node previews and job previews
(retain/release behavior).
- Preview gating behavior when `Comfy.Execution.PreviewMethod` is
`none`.
- Active job UI fallback behavior (`livePreviewUrl` -> `iconImageUrl`).

## Screenshots (if applicable)
<img width="808" height="614" alt="image"
src="https://github.com/user-attachments/assets/37c66eb2-8c28-4eb4-bb86-5679cb77d740"
/>
<img width="775" height="345" alt="image"
src="https://github.com/user-attachments/assets/aa420642-b0d4-4ae6-b94a-e7934b5df9d6"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8723-feat-add-KSampler-live-previews-to-assets-sidebar-jobs-3006d73d365081aeb81dd8279bf99f94)
by [Unito](https://www.unito.io)
This commit is contained in:
Benjamin Lu
2026-02-09 10:49:27 -08:00
committed by GitHub
parent 815be49112
commit 9209badd37
7 changed files with 199 additions and 5 deletions

View File

@@ -7,6 +7,7 @@ import { st } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useJobPreviewStore } from '@/stores/jobPreviewStore'
import { useQueueStore } from '@/stores/queueStore'
import type { TaskItemImpl } from '@/stores/queueStore'
import type { JobState } from '@/types/queue'
@@ -96,6 +97,7 @@ export function useJobList() {
const { t, locale } = useI18n()
const queueStore = useQueueStore()
const executionStore = useExecutionStore()
const jobPreviewStore = useJobPreviewStore()
const workflowStore = useWorkflowStore()
const seenPendingIds = ref<Set<string>>(new Set())
@@ -256,6 +258,11 @@ export function useJobList() {
String(task.promptId ?? '') ===
String(executionStore.activePromptId ?? '')
const showAddedHint = shouldShowAddedHint(task, state)
const promptKey = taskIdToKey(task.promptId)
const promptPreviewUrl =
state === 'running' && jobPreviewStore.isPreviewEnabled && promptKey
? jobPreviewStore.previewsByPromptId[promptKey]
: undefined
const display = buildJobDisplay(task, state, {
t,
@@ -275,7 +282,7 @@ export function useJobList() {
meta: display.secondary,
state,
iconName: display.iconName,
iconImageUrl: display.iconImageUrl,
iconImageUrl: promptPreviewUrl ?? display.iconImageUrl,
showClear: display.showClear,
taskRef: task,
progressTotalPercent: