diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 5e4cf6125..e3627658b 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -19,6 +19,7 @@ import { useDialogService } from '@/services/dialogService' import { useLitegraphService } from '@/services/litegraphService' import { useWorkflowService } from '@/services/workflowService' import type { ComfyCommand } from '@/stores/commandStore' +import { useCommandStore } from '@/stores/commandStore' import { useExecutionStore } from '@/stores/executionStore' import { useCanvasStore, useTitleEditorStore } from '@/stores/graphStore' import { useQueueSettingsStore, useQueueStore } from '@/stores/queueStore' @@ -660,9 +661,9 @@ export function useCoreCommands(): ComfyCommand[] { } }, { - id: 'Comfy.Manager.CustomNodesManager', - icon: 'pi pi-puzzle', - label: 'Toggle the Custom Nodes Manager', + id: 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu', + icon: 'pi pi-objects-column', + label: 'Custom Nodes (Beta)', versionAdded: '1.12.10', function: () => { dialogService.toggleManagerDialog() @@ -757,6 +758,84 @@ export function useCoreCommands(): ComfyCommand[] { const { node } = res canvas.select(node) } + }, + { + id: 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu', + icon: 'pi pi-bars', + label: 'Custom Nodes (Legacy)', + versionAdded: '1.16.4', + function: () => { + try { + useCommandStore().execute( + 'Comfy.Manager.CustomNodesManager.ToggleVisibility' + ) + } catch (error) { + useToastStore().add({ + severity: 'error', + summary: t('g.error'), + detail: t('manager.legacyMenuNotAvailable'), + life: 3000 + }) + } + } + }, + { + id: 'Comfy.Manager.ShowLegacyManagerMenu', + icon: 'mdi mdi-puzzle', + label: 'Manager Menu (Legacy)', + versionAdded: '1.16.4', + function: () => { + try { + useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility') + } catch (error) { + useToastStore().add({ + severity: 'error', + summary: t('g.error'), + detail: t('manager.legacyMenuNotAvailable'), + life: 3000 + }) + } + } + }, + { + id: 'Comfy.Memory.UnloadModels', + icon: 'mdi mdi-vacuum-outline', + label: 'Unload Models', + versionAdded: '1.16.4', + function: async () => { + if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) { + useToastStore().add({ + severity: 'error', + summary: t('g.error'), + detail: t('g.commandProhibited', { + command: 'Comfy.Memory.UnloadModels' + }), + life: 3000 + }) + return + } + await api.freeMemory({ freeExecutionCache: false }) + } + }, + { + id: 'Comfy.Memory.UnloadModelsAndExecutionCache', + icon: 'mdi mdi-vacuum-outline', + label: 'Unload Models and Execution Cache', + versionAdded: '1.16.4', + function: async () => { + if (!useSettingStore().get('Comfy.Memory.AllowManualUnload')) { + useToastStore().add({ + severity: 'error', + summary: t('g.error'), + detail: t('g.commandProhibited', { + command: 'Comfy.Memory.UnloadModelsAndExecutionCache' + }), + life: 3000 + }) + return + } + await api.freeMemory({ freeExecutionCache: true }) + } } ] diff --git a/src/constants/coreMenuCommands.ts b/src/constants/coreMenuCommands.ts index 9173366a4..9fc0561eb 100644 --- a/src/constants/coreMenuCommands.ts +++ b/src/constants/coreMenuCommands.ts @@ -14,6 +14,16 @@ export const CORE_MENU_COMMANDS = [ [['Edit'], ['Comfy.RefreshNodeDefinitions']], [['Edit'], ['Comfy.ClearWorkflow']], [['Edit'], ['Comfy.OpenClipspace']], + [ + ['Manager'], + [ + 'Comfy.Manager.ShowLegacyManagerMenu', + 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu', + 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu', + 'Comfy.Memory.UnloadModels', + 'Comfy.Memory.UnloadModelsAndExecutionCache' + ] + ], [ ['Help'], [ diff --git a/src/constants/coreSettings.ts b/src/constants/coreSettings.ts index c3a9b30af..72943046c 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -14,6 +14,13 @@ import type { SettingParams } from '@/types/settingTypes' * when they are no longer needed. */ export const CORE_SETTINGS: SettingParams[] = [ + { + id: 'Comfy.Memory.AllowManualUnload', + name: 'Allow manual unload of models and execution cache via user command', + type: 'hidden', + defaultValue: true, + versionAdded: '1.18.0' + }, { id: 'Comfy.Validation.Workflows', name: 'Validate workflows', diff --git a/src/locales/en/main.json b/src/locales/en/main.json index df92e481b..589cbc533 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -133,10 +133,12 @@ "copyURL": "Copy URL", "releaseTitle": "{package} {version} Release", "progressCountOf": "of", - "keybindingAlreadyExists": "Keybinding already exists on" + "keybindingAlreadyExists": "Keybinding already exists on", + "commandProhibited": "Command {command} is prohibited. Contact an administrator for more information." }, "manager": { "title": "Custom Nodes Manager", + "legacyMenuNotAvailable": "Legacy manager menu is not available in this version of ComfyUI. Please use the new manager menu instead.", "failed": "Failed ({count})", "noNodesFound": "No nodes found", "noNodesFoundDescription": "The pack's nodes either could not be parsed, or the pack is a frontend extension only and doesn't have any nodes.", diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 7be323588..66ad0b0e9 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -37,6 +37,7 @@ import { type ComfyNodeDef, validateComfyNodeDef } from '@/schemas/nodeDefSchema' +import { useToastStore } from '@/stores/toastStore' import { WorkflowTemplates } from '@/types/workflowTemplateTypes' interface QueuePromptRequestBody { @@ -987,6 +988,56 @@ export class ComfyApi extends EventTarget { return (await axios.get(this.internalURL('/folder_paths'))).data } + /* Frees memory by unloading models and optionally freeing execution cache + * @param {Object} options - The options object + * @param {boolean} options.freeExecutionCache - If true, also frees execution cache + */ + async freeMemory(options: { freeExecutionCache: boolean }) { + try { + let mode = '' + if (options.freeExecutionCache) { + mode = '{"unload_models": true, "free_memory": true}' + } else { + mode = '{"unload_models": true}' + } + + const res = await this.fetchApi(`/free`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: mode + }) + + if (res.status === 200) { + if (options.freeExecutionCache) { + useToastStore().add({ + severity: 'success', + summary: 'Models and Execution Cache have been cleared.', + life: 3000 + }) + } else { + useToastStore().add({ + severity: 'success', + summary: 'Models have been unloaded.', + life: 3000 + }) + } + } else { + useToastStore().add({ + severity: 'error', + summary: + 'Unloading of models failed. Installed ComfyUI may be an outdated version.', + life: 5000 + }) + } + } catch (error) { + useToastStore().add({ + severity: 'error', + summary: 'An error occurred while trying to unload models.', + life: 5000 + }) + } + } + /** * Gets the custom nodes i18n data from the server. *