Skip to content

Commit e832e03

Browse files
committed
feat: Implement centralized AssetsStore for reactive assets updates
- Create AssetsStore following QueueStore pattern for history-based assets - Use useAsyncState for async state management (loading/error handling) - Support both cloud and local environments (via isCloud flag) - Auto-update history assets on status events in GraphView - Refactor useMediaAssets composables to use AssetsStore
1 parent c9aa234 commit e832e03

File tree

4 files changed

+194
-126
lines changed

4 files changed

+194
-126
lines changed

src/platform/assets/composables/useMediaAssets/useAssetsApi.ts

Lines changed: 22 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,39 @@
1-
import { useAsyncState } from '@vueuse/core'
1+
import { computed } from 'vue'
22

33
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
4-
import { assetService } from '@/platform/assets/services/assetService'
5-
import { useQueueStore } from '@/stores/queueStore'
6-
7-
import { mapTaskOutputToAssetItem } from './assetMappers'
8-
9-
/**
10-
* Fetch input assets from cloud service
11-
*/
12-
async function fetchInputAssets(directory: string): Promise<AssetItem[]> {
13-
const assets = await assetService.getAssetsByTag(directory, false)
14-
return assets
15-
}
4+
import { useAssetsStore } from '@/stores/assetsStore'
165

176
/**
18-
* Fetch output assets from queue store
7+
* Composable for fetching media assets from cloud environment
8+
* Uses AssetsStore for centralized state management
199
*/
20-
function fetchOutputAssets(): AssetItem[] {
21-
const queueStore = useQueueStore()
22-
23-
const assetItems: AssetItem[] = queueStore.tasks
24-
.filter((task) => task.previewOutput && task.displayStatus === 'Completed')
25-
.map((task) => {
26-
const output = task.previewOutput!
27-
const assetItem = mapTaskOutputToAssetItem(task, output)
10+
export function useAssetsApi(directory: 'input' | 'output') {
11+
const assetsStore = useAssetsStore()
2812

29-
// Add output count and all outputs for folder view
30-
assetItem.user_metadata = {
31-
...assetItem.user_metadata,
32-
outputCount: task.flatOutputs.filter((o) => o.supportsPreview).length,
33-
allOutputs: task.flatOutputs.filter((o) => o.supportsPreview)
34-
}
13+
const media = computed(() =>
14+
directory === 'input' ? assetsStore.inputAssets : assetsStore.historyAssets
15+
)
3516

36-
return assetItem
37-
})
17+
const loading = computed(() =>
18+
directory === 'input'
19+
? assetsStore.inputLoading
20+
: assetsStore.historyLoading
21+
)
3822

39-
return assetItems
40-
}
23+
const error = computed(() =>
24+
directory === 'input' ? assetsStore.inputError : assetsStore.historyError
25+
)
4126

42-
/**
43-
* Composable for fetching media assets from cloud environment
44-
* Creates an independent instance for each directory
45-
*/
46-
export function useAssetsApi(directory: 'input' | 'output') {
47-
const fetchAssets = async (): Promise<AssetItem[]> => {
27+
const fetchMediaList = async (): Promise<AssetItem[]> => {
4828
if (directory === 'input') {
49-
return fetchInputAssets(directory)
29+
await assetsStore.updateInputs()
30+
return assetsStore.inputAssets
5031
} else {
51-
return fetchOutputAssets()
32+
await assetsStore.updateHistory()
33+
return assetsStore.historyAssets
5234
}
5335
}
5436

55-
const {
56-
state: media,
57-
isLoading: loading,
58-
error,
59-
execute: fetchMediaList
60-
} = useAsyncState(fetchAssets, [], {
61-
immediate: false,
62-
resetOnExecute: false,
63-
onError: (err) => {
64-
console.error(`Error fetching ${directory} cloud assets:`, err)
65-
}
66-
})
67-
6837
const refresh = () => fetchMediaList()
6938

7039
return {

src/platform/assets/composables/useMediaAssets/useInternalFilesApi.ts

Lines changed: 20 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,39 @@
1-
import { useAsyncState } from '@vueuse/core'
1+
import { computed } from 'vue'
22

33
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
4-
import { api } from '@/scripts/api'
5-
import { useQueueStore } from '@/stores/queueStore'
6-
7-
import {
8-
mapInputFileToAssetItem,
9-
mapTaskOutputToAssetItem
10-
} from './assetMappers'
4+
import { useAssetsStore } from '@/stores/assetsStore'
115

126
/**
13-
* Fetch input directory files from the internal API
7+
* Composable for fetching media assets from local environment
8+
* Uses AssetsStore for centralized state management
149
*/
15-
async function fetchInputFiles(directory: string): Promise<AssetItem[]> {
16-
const response = await fetch(api.internalURL(`/files/${directory}`), {
17-
headers: {
18-
'Comfy-User': api.user
19-
}
20-
})
21-
22-
if (!response.ok) {
23-
throw new Error(`Failed to fetch ${directory} files`)
24-
}
10+
export function useInternalFilesApi(directory: 'input' | 'output') {
11+
const assetsStore = useAssetsStore()
2512

26-
const filenames: string[] = await response.json()
27-
return filenames.map((name, index) =>
28-
mapInputFileToAssetItem(name, index, directory as 'input')
13+
const media = computed(() =>
14+
directory === 'input' ? assetsStore.inputAssets : assetsStore.historyAssets
2915
)
30-
}
31-
32-
/**
33-
* Fetch output files from the queue store
34-
*/
35-
function fetchOutputFiles(): AssetItem[] {
36-
const queueStore = useQueueStore()
37-
38-
// Use tasks (already grouped by promptId) instead of flatTasks
39-
const assetItems: AssetItem[] = queueStore.tasks
40-
.filter((task) => task.previewOutput && task.displayStatus === 'Completed')
41-
.map((task) => {
42-
const output = task.previewOutput!
43-
const assetItem = mapTaskOutputToAssetItem(task, output)
4416

45-
// Add output count and all outputs for folder view
46-
assetItem.user_metadata = {
47-
...assetItem.user_metadata,
48-
outputCount: task.flatOutputs.filter((o) => o.supportsPreview).length,
49-
allOutputs: task.flatOutputs.filter((o) => o.supportsPreview)
50-
}
51-
52-
return assetItem
53-
})
17+
const loading = computed(() =>
18+
directory === 'input'
19+
? assetsStore.inputLoading
20+
: assetsStore.historyLoading
21+
)
5422

55-
// Sort by creation date (newest first)
56-
return assetItems.sort(
57-
(a, b) =>
58-
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
23+
const error = computed(() =>
24+
directory === 'input' ? assetsStore.inputError : assetsStore.historyError
5925
)
60-
}
6126

62-
/**
63-
* Composable for fetching media assets from local environment
64-
* Creates an independent instance for each directory
65-
*/
66-
export function useInternalFilesApi(directory: 'input' | 'output') {
67-
const fetchAssets = async (): Promise<AssetItem[]> => {
27+
const fetchMediaList = async (): Promise<AssetItem[]> => {
6828
if (directory === 'input') {
69-
return fetchInputFiles(directory)
29+
await assetsStore.updateInputs()
30+
return assetsStore.inputAssets
7031
} else {
71-
return fetchOutputFiles()
32+
await assetsStore.updateHistory()
33+
return assetsStore.historyAssets
7234
}
7335
}
7436

75-
const {
76-
state: media,
77-
isLoading: loading,
78-
error,
79-
execute: fetchMediaList
80-
} = useAsyncState(fetchAssets, [], {
81-
immediate: false,
82-
resetOnExecute: false,
83-
onError: (err) => {
84-
console.error(`Error fetching ${directory} assets:`, err)
85-
}
86-
})
87-
8837
const refresh = () => fetchMediaList()
8938

9039
return {

src/stores/assetsStore.ts

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import { useAsyncState } from '@vueuse/core'
2+
import { defineStore } from 'pinia'
3+
import { computed } from 'vue'
4+
5+
import {
6+
mapInputFileToAssetItem,
7+
mapTaskOutputToAssetItem
8+
} from '@/platform/assets/composables/useMediaAssets/assetMappers'
9+
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
10+
import { assetService } from '@/platform/assets/services/assetService'
11+
import { isCloud } from '@/platform/distribution/types'
12+
import { api } from '@/scripts/api'
13+
14+
import { TaskItemImpl } from './queueStore'
15+
16+
/**
17+
* Fetch input files from the internal API (OSS version)
18+
*/
19+
async function fetchInputFilesFromAPI(): Promise<AssetItem[]> {
20+
const response = await fetch(api.internalURL('/files/input'), {
21+
headers: {
22+
'Comfy-User': api.user
23+
}
24+
})
25+
26+
if (!response.ok) {
27+
throw new Error('Failed to fetch input files')
28+
}
29+
30+
const filenames: string[] = await response.json()
31+
return filenames.map((name, index) =>
32+
mapInputFileToAssetItem(name, index, 'input')
33+
)
34+
}
35+
36+
/**
37+
* Fetch input files from cloud service
38+
*/
39+
async function fetchInputFilesFromCloud(): Promise<AssetItem[]> {
40+
return await assetService.getAssetsByTag('input', false)
41+
}
42+
43+
/**
44+
* Convert history task items to asset items
45+
*/
46+
function mapHistoryToAssets(historyItems: any[]): AssetItem[] {
47+
const assetItems: AssetItem[] = []
48+
49+
for (const item of historyItems) {
50+
if (!item.outputs || !item.status || item.status?.status_str === 'error') {
51+
continue
52+
}
53+
54+
const task = new TaskItemImpl(
55+
'History',
56+
item.prompt,
57+
item.status,
58+
item.outputs
59+
)
60+
61+
if (!task.previewOutput) {
62+
continue
63+
}
64+
65+
const assetItem = mapTaskOutputToAssetItem(task, task.previewOutput)
66+
67+
const supportedOutputs = task.flatOutputs.filter((o) => o.supportsPreview)
68+
assetItem.user_metadata = {
69+
...assetItem.user_metadata,
70+
outputCount: supportedOutputs.length,
71+
allOutputs: supportedOutputs
72+
}
73+
74+
assetItems.push(assetItem)
75+
}
76+
77+
return assetItems.sort(
78+
(a, b) =>
79+
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
80+
)
81+
}
82+
83+
export const useAssetsStore = defineStore('assets', () => {
84+
const maxHistoryItems = 200
85+
86+
const fetchInputFiles = isCloud
87+
? fetchInputFilesFromCloud
88+
: fetchInputFilesFromAPI
89+
90+
const {
91+
state: inputAssets,
92+
isLoading: inputLoading,
93+
error: inputError,
94+
execute: updateInputs
95+
} = useAsyncState(fetchInputFiles, [], {
96+
immediate: false,
97+
resetOnExecute: false,
98+
onError: (err) => {
99+
console.error('Error fetching input assets:', err)
100+
}
101+
})
102+
103+
const fetchHistoryAssets = async (): Promise<AssetItem[]> => {
104+
const history = await api.getHistory(maxHistoryItems)
105+
return mapHistoryToAssets(history.History)
106+
}
107+
108+
const {
109+
state: historyAssets,
110+
isLoading: historyLoading,
111+
error: historyError,
112+
execute: updateHistory
113+
} = useAsyncState(fetchHistoryAssets, [], {
114+
immediate: false,
115+
resetOnExecute: false,
116+
onError: (err) => {
117+
console.error('Error fetching history assets:', err)
118+
}
119+
})
120+
121+
const isLoading = computed(() => inputLoading.value || historyLoading.value)
122+
123+
const update = async () => {
124+
await Promise.all([updateInputs(), updateHistory()])
125+
}
126+
127+
return {
128+
// States
129+
inputAssets,
130+
historyAssets,
131+
inputLoading,
132+
historyLoading,
133+
inputError,
134+
historyError,
135+
isLoading,
136+
137+
// Actions
138+
updateInputs,
139+
updateHistory,
140+
update
141+
}
142+
})

src/views/GraphView.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { api } from '@/scripts/api'
5454
import { app } from '@/scripts/app'
5555
import { setupAutoQueueHandler } from '@/services/autoQueueService'
5656
import { useKeybindingService } from '@/services/keybindingService'
57+
import { useAssetsStore } from '@/stores/assetsStore'
5758
import { useCommandStore } from '@/stores/commandStore'
5859
import { useExecutionStore } from '@/stores/executionStore'
5960
import { useMenuItemStore } from '@/stores/menuItemStore'
@@ -80,6 +81,7 @@ const settingStore = useSettingStore()
8081
const executionStore = useExecutionStore()
8182
const colorPaletteStore = useColorPaletteStore()
8283
const queueStore = useQueueStore()
84+
const assetsStore = useAssetsStore()
8385
const versionCompatibilityStore = useVersionCompatibilityStore()
8486
const graphCanvasContainerRef = ref<HTMLDivElement | null>(null)
8587
@@ -188,11 +190,17 @@ const init = () => {
188190
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
189191
const onStatus = async (e: CustomEvent<StatusWsMessageStatus>) => {
190192
queuePendingTaskCountStore.update(e)
191-
await queueStore.update()
193+
await Promise.all([
194+
queueStore.update(),
195+
assetsStore.updateHistory() // Update history assets when status changes
196+
])
192197
}
193198
194199
const onExecutionSuccess = async () => {
195-
await queueStore.update()
200+
await Promise.all([
201+
queueStore.update(),
202+
assetsStore.updateHistory() // Update history assets on execution success
203+
])
196204
}
197205
198206
const reconnectingMessage: ToastMessageOptions = {

0 commit comments

Comments
 (0)