Extract useOutputStacks

This commit is contained in:
Benjamin Lu
2026-01-23 15:53:23 -08:00
parent 1e5e7f2282
commit 82bc727739
2 changed files with 162 additions and 138 deletions

View File

@@ -120,18 +120,10 @@ import { useJobActions } from '@/composables/queue/useJobActions'
import type { JobListItem } from '@/composables/queue/useJobList'
import { useJobList } from '@/composables/queue/useJobList'
import AssetsListItem from '@/platform/assets/components/AssetsListItem.vue'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema';
import type { OutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema';
import { useOutputStacks } from '@/platform/assets/composables/useOutputStacks'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import {
mapOutputsToAssetItems,
shouldLoadFullOutputs
} from '@/platform/assets/utils/outputAssetUtil'
import { iconForMediaType } from '@/platform/assets/utils/mediaIconUtil'
import {
getJobDetail,
getPreviewableOutputsFromJobDetail
} from '@/services/jobOutputCache'
import { isActiveJobState } from '@/utils/queueUtil'
import {
formatDuration,
@@ -163,15 +155,10 @@ const { t } = useI18n()
const { jobItems } = useJobList()
const hoveredJobId = ref<string | null>(null)
const hoveredAssetId = ref<string | null>(null)
const expandedStackPromptIds = ref<Set<string>>(new Set())
const stackChildrenByPromptId = ref<Record<string, AssetItem[]>>({})
const loadingStackPromptIds = ref<Set<string>>(new Set())
type AssetListItem = {
key: string
asset: AssetItem
isChild?: boolean
}
const { assetItems, selectableAssets, isStackExpanded, toggleStack } =
useOutputStacks({
assets: computed(() => assets)
})
const activeJobItems = computed(() =>
jobItems.value.filter((item) => isActiveJobState(item.state))
@@ -184,37 +171,6 @@ const hoveredJob = computed(() =>
)
const { cancelAction, canCancelJob, runCancelJob } = useJobActions(hoveredJob)
const assetItems = computed<AssetListItem[]>(() => {
const items: AssetListItem[] = []
for (const asset of assets) {
const promptId = getStackPromptId(asset)
items.push({
key: `asset-${asset.id}`,
asset
})
if (!promptId || !expandedStackPromptIds.value.has(promptId)) {
continue
}
const children = stackChildrenByPromptId.value[promptId] ?? []
for (const child of children) {
items.push({
key: `asset-${child.id}`,
asset: child,
isChild: true
})
}
}
return items
})
const selectableAssets = computed(() =>
assetItems.value.map((item) => item.asset)
)
watch(
selectableAssets,
(nextAssets) => {
@@ -252,11 +208,6 @@ function getAssetSecondaryText(asset: AssetItem): string {
return ''
}
function getStackPromptId(asset: AssetItem): string | null {
const metadata = getOutputAssetMetadata(asset.user_metadata)
return metadata?.promptId ?? null
}
function getStackCount(asset: AssetItem): number | undefined {
const metadata = getOutputAssetMetadata(asset.user_metadata)
if (typeof metadata?.outputCount === 'number') {
@@ -270,89 +221,6 @@ function getStackCount(asset: AssetItem): number | undefined {
return undefined
}
function isStackExpanded(asset: AssetItem): boolean {
const promptId = getStackPromptId(asset)
if (!promptId) return false
return expandedStackPromptIds.value.has(promptId)
}
async function toggleStack(asset: AssetItem) {
const promptId = getStackPromptId(asset)
if (!promptId) return
if (expandedStackPromptIds.value.has(promptId)) {
const next = new Set(expandedStackPromptIds.value)
next.delete(promptId)
expandedStackPromptIds.value = next
return
}
if (!stackChildrenByPromptId.value[promptId]?.length) {
if (loadingStackPromptIds.value.has(promptId)) {
return
}
const nextLoading = new Set(loadingStackPromptIds.value)
nextLoading.add(promptId)
loadingStackPromptIds.value = nextLoading
const children = await resolveStackChildren(asset)
const afterLoading = new Set(loadingStackPromptIds.value)
afterLoading.delete(promptId)
loadingStackPromptIds.value = afterLoading
if (!children.length) {
return
}
stackChildrenByPromptId.value = {
...stackChildrenByPromptId.value,
[promptId]: children
}
}
const nextExpanded = new Set(expandedStackPromptIds.value)
nextExpanded.add(promptId)
expandedStackPromptIds.value = nextExpanded
}
async function resolveStackOutputs(metadata: OutputAssetMetadata) {
const outputsToDisplay = metadata.allOutputs ?? []
if (!shouldLoadFullOutputs(metadata.outputCount, outputsToDisplay.length)) {
return outputsToDisplay
}
try {
const jobDetail = await getJobDetail(metadata.promptId)
const previewableOutputs = getPreviewableOutputsFromJobDetail(jobDetail)
return previewableOutputs.length ? previewableOutputs : outputsToDisplay
} catch (error) {
console.error('Failed to fetch job detail for stack children:', error)
return outputsToDisplay
}
}
async function resolveStackChildren(asset: AssetItem): Promise<AssetItem[]> {
const metadata = getOutputAssetMetadata(asset.user_metadata)
if (!metadata) {
return []
}
const outputsToDisplay = await resolveStackOutputs(metadata)
if (!outputsToDisplay.length) {
return []
}
return mapOutputsToAssetItems({
promptId: metadata.promptId,
outputs: outputsToDisplay,
createdAt: asset.created_at,
executionTimeInSeconds: metadata.executionTimeInSeconds,
workflow: metadata.workflow,
excludeFilename: asset.name
})
}
function getAssetCardClass(selected: boolean): string {
return cn(
'w-full text-text-primary transition-colors hover:bg-secondary-background-hover',

View File

@@ -0,0 +1,156 @@
import { computed, ref } from 'vue'
import type { Ref } from 'vue'
import { getOutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema';
import type { OutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema';
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
import {
mapOutputsToAssetItems,
shouldLoadFullOutputs
} from '@/platform/assets/utils/outputAssetUtil'
import {
getJobDetail,
getPreviewableOutputsFromJobDetail
} from '@/services/jobOutputCache'
export type OutputStackListItem = {
key: string
asset: AssetItem
isChild?: boolean
}
type UseOutputStacksOptions = {
assets: Ref<AssetItem[]>
}
export function useOutputStacks({ assets }: UseOutputStacksOptions) {
const expandedStackPromptIds = ref<Set<string>>(new Set())
const stackChildrenByPromptId = ref<Record<string, AssetItem[]>>({})
const loadingStackPromptIds = ref<Set<string>>(new Set())
const assetItems = computed<OutputStackListItem[]>(() => {
const items: OutputStackListItem[] = []
for (const asset of assets.value) {
const promptId = getStackPromptId(asset)
items.push({
key: `asset-${asset.id}`,
asset
})
if (!promptId || !expandedStackPromptIds.value.has(promptId)) {
continue
}
const children = stackChildrenByPromptId.value[promptId] ?? []
for (const child of children) {
items.push({
key: `asset-${child.id}`,
asset: child,
isChild: true
})
}
}
return items
})
const selectableAssets = computed(() =>
assetItems.value.map((item) => item.asset)
)
function getStackPromptId(asset: AssetItem): string | null {
const metadata = getOutputAssetMetadata(asset.user_metadata)
return metadata?.promptId ?? null
}
function isStackExpanded(asset: AssetItem): boolean {
const promptId = getStackPromptId(asset)
if (!promptId) return false
return expandedStackPromptIds.value.has(promptId)
}
async function toggleStack(asset: AssetItem) {
const promptId = getStackPromptId(asset)
if (!promptId) return
if (expandedStackPromptIds.value.has(promptId)) {
const next = new Set(expandedStackPromptIds.value)
next.delete(promptId)
expandedStackPromptIds.value = next
return
}
if (!stackChildrenByPromptId.value[promptId]?.length) {
if (loadingStackPromptIds.value.has(promptId)) {
return
}
const nextLoading = new Set(loadingStackPromptIds.value)
nextLoading.add(promptId)
loadingStackPromptIds.value = nextLoading
const children = await resolveStackChildren(asset)
const afterLoading = new Set(loadingStackPromptIds.value)
afterLoading.delete(promptId)
loadingStackPromptIds.value = afterLoading
if (!children.length) {
return
}
stackChildrenByPromptId.value = {
...stackChildrenByPromptId.value,
[promptId]: children
}
}
const nextExpanded = new Set(expandedStackPromptIds.value)
nextExpanded.add(promptId)
expandedStackPromptIds.value = nextExpanded
}
async function resolveStackOutputs(metadata: OutputAssetMetadata) {
const outputsToDisplay = metadata.allOutputs ?? []
if (!shouldLoadFullOutputs(metadata.outputCount, outputsToDisplay.length)) {
return outputsToDisplay
}
try {
const jobDetail = await getJobDetail(metadata.promptId)
const previewableOutputs = getPreviewableOutputsFromJobDetail(jobDetail)
return previewableOutputs.length ? previewableOutputs : outputsToDisplay
} catch (error) {
console.error('Failed to fetch job detail for stack children:', error)
return outputsToDisplay
}
}
async function resolveStackChildren(asset: AssetItem): Promise<AssetItem[]> {
const metadata = getOutputAssetMetadata(asset.user_metadata)
if (!metadata) {
return []
}
const outputsToDisplay = await resolveStackOutputs(metadata)
if (!outputsToDisplay.length) {
return []
}
return mapOutputsToAssetItems({
promptId: metadata.promptId,
outputs: outputsToDisplay,
createdAt: asset.created_at,
executionTimeInSeconds: metadata.executionTimeInSeconds,
workflow: metadata.workflow,
excludeFilename: asset.name
})
}
return {
assetItems,
selectableAssets,
isStackExpanded,
toggleStack
}
}