mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-01 13:59:54 +00:00
refactor: move workflow loading state from bootstrapStore to workflowStore
- Add useAsyncState in workflowStore for syncWorkflows with loading state - Add loadWorkflows action that guards against double-loading - Simplify bootstrapStore by delegating workflow loading to workflowStore - Update test mocks for workflowStore Amp-Thread-ID: https://ampcode.com/threads/T-019bfcce-cce0-70b0-abc6-742669ac86e3 Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import _ from 'es-toolkit/compat'
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, markRaw, ref, shallowRef, watch } from 'vue'
|
||||
import type { Raw } from 'vue'
|
||||
@@ -500,48 +501,67 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
workflow.isPersisted && !workflow.path.startsWith('subgraphs/')
|
||||
)
|
||||
)
|
||||
const syncWorkflows = async (dir: string = '') => {
|
||||
await syncEntities(
|
||||
dir ? 'workflows/' + dir : 'workflows',
|
||||
workflowLookup.value,
|
||||
(file) =>
|
||||
new ComfyWorkflow({
|
||||
path: file.path,
|
||||
modified: file.modified,
|
||||
size: file.size
|
||||
}),
|
||||
(existingWorkflow, file) => {
|
||||
const isActiveWorkflow =
|
||||
activeWorkflow.value?.path === existingWorkflow.path
|
||||
|
||||
const nextLastModified = Math.max(
|
||||
existingWorkflow.lastModified,
|
||||
file.modified
|
||||
)
|
||||
const {
|
||||
isReady: isSyncReady,
|
||||
isLoading: isSyncLoading,
|
||||
execute: executeSyncWorkflows
|
||||
} = useAsyncState(
|
||||
async (dir: string = '') => {
|
||||
await syncEntities(
|
||||
dir ? 'workflows/' + dir : 'workflows',
|
||||
workflowLookup.value,
|
||||
(file) =>
|
||||
new ComfyWorkflow({
|
||||
path: file.path,
|
||||
modified: file.modified,
|
||||
size: file.size
|
||||
}),
|
||||
(existingWorkflow, file) => {
|
||||
const isActiveWorkflow =
|
||||
activeWorkflow.value?.path === existingWorkflow.path
|
||||
|
||||
const isMetadataUnchanged =
|
||||
nextLastModified === existingWorkflow.lastModified &&
|
||||
file.size === existingWorkflow.size
|
||||
const nextLastModified = Math.max(
|
||||
existingWorkflow.lastModified,
|
||||
file.modified
|
||||
)
|
||||
|
||||
if (!isMetadataUnchanged) {
|
||||
existingWorkflow.lastModified = nextLastModified
|
||||
existingWorkflow.size = file.size
|
||||
}
|
||||
const isMetadataUnchanged =
|
||||
nextLastModified === existingWorkflow.lastModified &&
|
||||
file.size === existingWorkflow.size
|
||||
|
||||
// Never unload the active workflow - it may contain unsaved in-memory edits.
|
||||
if (isActiveWorkflow) {
|
||||
return
|
||||
}
|
||||
if (!isMetadataUnchanged) {
|
||||
existingWorkflow.lastModified = nextLastModified
|
||||
existingWorkflow.size = file.size
|
||||
}
|
||||
|
||||
// If nothing changed, keep any loaded content cached.
|
||||
if (isMetadataUnchanged) {
|
||||
return
|
||||
}
|
||||
// Never unload the active workflow - it may contain unsaved in-memory edits.
|
||||
if (isActiveWorkflow) {
|
||||
return
|
||||
}
|
||||
|
||||
existingWorkflow.unload()
|
||||
},
|
||||
/* exclude */ (workflow) => workflow.isTemporary
|
||||
)
|
||||
// If nothing changed, keep any loaded content cached.
|
||||
if (isMetadataUnchanged) {
|
||||
return
|
||||
}
|
||||
|
||||
existingWorkflow.unload()
|
||||
},
|
||||
/* exclude */ (workflow) => workflow.isTemporary
|
||||
)
|
||||
},
|
||||
undefined,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
async function syncWorkflows(dir: string = '') {
|
||||
return executeSyncWorkflows(0, dir)
|
||||
}
|
||||
|
||||
async function loadWorkflows() {
|
||||
if (!isSyncReady.value && !isSyncLoading.value) {
|
||||
return syncWorkflows()
|
||||
}
|
||||
}
|
||||
|
||||
const bookmarkStore = useWorkflowBookmarkStore()
|
||||
@@ -849,6 +869,7 @@ export const useWorkflowStore = defineStore('workflow', () => {
|
||||
modifiedWorkflows,
|
||||
getWorkflowByPath,
|
||||
syncWorkflows,
|
||||
loadWorkflows,
|
||||
|
||||
isSubgraphActive,
|
||||
activeSubgraph,
|
||||
|
||||
@@ -33,11 +33,10 @@ vi.mock('@/platform/settings/settingStore', () => ({
|
||||
}))
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/workspaceStore', () => ({
|
||||
useWorkspaceStore: vi.fn(() => ({
|
||||
workflow: {
|
||||
syncWorkflows: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({
|
||||
useWorkflowStore: vi.fn(() => ({
|
||||
loadWorkflows: vi.fn(),
|
||||
syncWorkflows: vi.fn().mockResolvedValue(undefined)
|
||||
}))
|
||||
}))
|
||||
|
||||
@@ -53,23 +52,10 @@ describe('bootstrapStore', () => {
|
||||
|
||||
it('initializes with all flags false', () => {
|
||||
const settingStore = useSettingStore()
|
||||
expect(store.isNodeDefsReady).toBe(false)
|
||||
expect(settingStore.isReady).toBe(false)
|
||||
expect(store.isI18nReady).toBe(false)
|
||||
})
|
||||
|
||||
it('starts early bootstrap (node defs)', async () => {
|
||||
const { api } = await import('@/scripts/api')
|
||||
|
||||
store.startEarlyBootstrap()
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(store.isNodeDefsReady).toBe(true)
|
||||
})
|
||||
|
||||
expect(api.getNodeDefs).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('starts store bootstrap (settings, i18n)', async () => {
|
||||
const settingStore = useSettingStore()
|
||||
void store.startStoreBootstrap()
|
||||
|
||||
@@ -2,26 +2,13 @@ import { useAsyncState } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
|
||||
export const useBootstrapStore = defineStore('bootstrap', () => {
|
||||
const settingStore = useSettingStore()
|
||||
|
||||
const {
|
||||
state: nodeDefs,
|
||||
isReady: isNodeDefsReady,
|
||||
error: nodeDefsError,
|
||||
execute: fetchNodeDefs
|
||||
} = useAsyncState<Record<string, ComfyNodeDef>>(
|
||||
async () => {
|
||||
const defs = await api.getNodeDefs()
|
||||
return defs
|
||||
},
|
||||
{},
|
||||
{ immediate: false }
|
||||
)
|
||||
const workflowStore = useWorkflowStore()
|
||||
|
||||
const {
|
||||
isReady: isI18nReady,
|
||||
@@ -37,28 +24,7 @@ export const useBootstrapStore = defineStore('bootstrap', () => {
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
const {
|
||||
isReady: isWorkflowsReady,
|
||||
isLoading: isWorkflowsLoading,
|
||||
execute: executeSyncWorkflows
|
||||
} = useAsyncState(
|
||||
async () => {
|
||||
const { useWorkspaceStore } = await import('@/stores/workspaceStore')
|
||||
await useWorkspaceStore().workflow.syncWorkflows()
|
||||
},
|
||||
undefined,
|
||||
{ immediate: false }
|
||||
)
|
||||
|
||||
function syncWorkflows() {
|
||||
if (!isWorkflowsReady.value && !isWorkflowsLoading.value) {
|
||||
void executeSyncWorkflows()
|
||||
}
|
||||
}
|
||||
|
||||
function startEarlyBootstrap() {
|
||||
void fetchNodeDefs()
|
||||
}
|
||||
function startEarlyBootstrap() {}
|
||||
|
||||
async function startStoreBootstrap() {
|
||||
// Defer settings and workflows if multi-user login is required
|
||||
@@ -71,14 +37,11 @@ export const useBootstrapStore = defineStore('bootstrap', () => {
|
||||
|
||||
if (!userStore.needsLogin) {
|
||||
await settingStore.load()
|
||||
syncWorkflows()
|
||||
await workflowStore.loadWorkflows()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodeDefs,
|
||||
isNodeDefsReady,
|
||||
nodeDefsError,
|
||||
isI18nReady,
|
||||
i18nError,
|
||||
startEarlyBootstrap,
|
||||
|
||||
Reference in New Issue
Block a user