From 5846117f4e8364f7aa0e8f997922fcd55d46cc34 Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 9 Apr 2025 09:52:06 -0700 Subject: [PATCH 01/64] migrate manager menu items --- src/composables/useCoreCommands.ts | 85 ++++++++++++++++++++++++++++-- src/constants/coreMenuCommands.ts | 10 ++++ src/constants/coreSettings.ts | 7 +++ src/locales/en/main.json | 4 +- src/scripts/api.ts | 51 ++++++++++++++++++ 5 files changed, 153 insertions(+), 4 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index fe6d112bba..86efaf24c6 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -20,6 +20,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 { useHelpCenterStore } from '@/stores/helpCenterStore' @@ -711,9 +712,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() @@ -880,6 +881,84 @@ export function useCoreCommands(): ComfyCommand[] { const modelSelectorDialog = useModelSelectorDialog() modelSelectorDialog.show() } + }, + { + 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 94668668a9..d1f8bd0323 100644 --- a/src/constants/coreMenuCommands.ts +++ b/src/constants/coreMenuCommands.ts @@ -13,6 +13,16 @@ export const CORE_MENU_COMMANDS = [ ], [['Edit'], ['Comfy.Undo', 'Comfy.Redo']], [['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 d91c03cd56..b293d5e1f3 100644 --- a/src/constants/coreSettings.ts +++ b/src/constants/coreSettings.ts @@ -13,6 +13,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 6e2810b7a6..8411344c12 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -149,10 +149,12 @@ "micPermissionDenied": "Microphone permission denied", "noAudioRecorded": "No audio recorded", "nodesRunning": "nodes running", - "duplicate": "Duplicate" + "duplicate": "Duplicate", + "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 e83eb9f8bf..0b26353f01 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -35,6 +35,7 @@ import type { NodeId } from '@/schemas/comfyWorkflowSchema' import type { ComfyNodeDef } from '@/schemas/nodeDefSchema' +import { useToastStore } from '@/stores/toastStore' import type { NodeExecutionId } from '@/types/nodeIdentification' import { WorkflowTemplates } from '@/types/workflowTemplateTypes' @@ -1020,6 +1021,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. * From d96265a93ba9240968dc097c704ccef73d19ca43 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 9 Apr 2025 17:46:39 +0000 Subject: [PATCH 02/64] Update locales [skip ci] --- src/locales/en/commands.json | 16 +- src/locales/en/main.json | 8 +- src/locales/es/commands.json | 16 +- src/locales/es/main.json | 10 +- src/locales/fr/commands.json | 16 +- src/locales/fr/main.json | 121 ++----------- src/locales/ja/commands.json | 16 +- src/locales/ja/main.json | 120 ++----------- src/locales/ko/commands.json | 16 +- src/locales/ko/main.json | 340 +++++++++++++---------------------- src/locales/ru/commands.json | 16 +- src/locales/ru/main.json | 118 ++---------- src/locales/zh/commands.json | 16 +- src/locales/zh/main.json | 126 +++---------- 14 files changed, 307 insertions(+), 648 deletions(-) diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 8508497e66..4cd9738c62 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "Load Default Workflow" }, - "Comfy_Manager_CustomNodesManager": { - "label": "Toggle the Custom Nodes Manager" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "Custom Nodes (Beta)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "Custom Nodes (Legacy)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "Manager Menu (Legacy)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Toggle the Custom Nodes Manager Progress Bar" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "Open Mask Editor for Selected Node" }, + "Comfy_Memory_UnloadModels": { + "label": "Unload Models" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "Unload Models and Execution Cache" + }, "Comfy_NewBlankWorkflow": { "label": "New Blank Workflow" }, diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 8411344c12..4b67bed068 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -948,6 +948,7 @@ "menuLabels": { "File": "File", "Edit": "Edit", + "Manager": "Manager", "Help": "Help", "Check for Updates": "Check for Updates", "Open Custom Nodes Folder": "Open Custom Nodes Folder", @@ -1009,9 +1010,14 @@ "Decrease Brush Size in MaskEditor": "Decrease Brush Size in MaskEditor", "Increase Brush Size in MaskEditor": "Increase Brush Size in MaskEditor", "Open Mask Editor for Selected Node": "Open Mask Editor for Selected Node", + "Custom Nodes (Beta)": "Custom Nodes (Beta)", + "Custom Nodes (Legacy)": "Custom Nodes (Legacy)", + "Manager Menu (Legacy)": "Manager Menu (Legacy)", + "Toggle Progress Dialog": "Toggle Progress Dialog", + "Unload Models": "Unload Models", + "Unload Models and Execution Cache": "Unload Models and Execution Cache", "New": "New", "Clipspace": "Clipspace", - "Manager": "Manager", "Open": "Open", "Queue Prompt": "Queue Prompt", "Queue Prompt (Front)": "Queue Prompt (Front)", diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index d41962c03b..543a9e3463 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "Cargar flujo de trabajo predeterminado" }, - "Comfy_Manager_CustomNodesManager": { - "label": "Administrador de nodos personalizados" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "Nodos personalizados (Beta)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "Nodos personalizados (Legacy)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "Menú del administrador (Legacy)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Alternar diálogo de progreso del administrador" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "Abrir editor de máscara para el nodo seleccionado" }, + "Comfy_Memory_UnloadModels": { + "label": "Descargar modelos" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "Descargar modelos y caché de ejecución" + }, "Comfy_NewBlankWorkflow": { "label": "Nuevo flujo de trabajo en blanco" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index 3768d07f3b..b523493ba4 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -278,6 +278,7 @@ "color": "Color", "comingSoon": "Próximamente", "command": "Comando", + "commandProhibited": "El comando {command} está prohibido. Contacta a un administrador para más información.", "community": "Comunidad", "completed": "Completado", "confirm": "Confirmar", @@ -670,6 +671,7 @@ "installationQueue": "Cola de Instalación", "lastUpdated": "Última Actualización", "latestVersion": "Última", + "legacyMenuNotAvailable": "El menú del administrador antiguo no está disponible en esta versión de ComfyUI. Por favor, utiliza el nuevo menú del administrador en su lugar.", "license": "Licencia", "loadingVersions": "Cargando versiones...", "nightlyVersion": "Nocturna", @@ -784,6 +786,8 @@ "Convert Selection to Subgraph": "Convertir selección en subgrafo", "Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo", "Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor", + "Custom Nodes (Beta)": "Nodos personalizados (Beta)", + "Custom Nodes (Legacy)": "Nodos personalizados (Antiguo)", "Delete Selected Items": "Eliminar elementos seleccionados", "Desktop User Guide": "Guía de usuario de escritorio", "Duplicate Current Workflow": "Duplicar flujo de trabajo actual", @@ -802,13 +806,14 @@ "Interrupt": "Interrumpir", "Load Default Workflow": "Cargar flujo de trabajo predeterminado", "Manage group nodes": "Gestionar nodos de grupo", - "Manager": "Administrador", "Minimap": "Minimapa", "Model Library": "Biblioteca de modelos", "Move Selected Nodes Down": "Mover nodos seleccionados hacia abajo", "Move Selected Nodes Left": "Mover nodos seleccionados hacia la izquierda", "Move Selected Nodes Right": "Mover nodos seleccionados hacia la derecha", "Move Selected Nodes Up": "Mover nodos seleccionados hacia arriba", + "Manager": "Administrador", + "Manager Menu (Legacy)": "Menú del administrador (Antiguo)", "Mute/Unmute Selected Nodes": "Silenciar/Activar sonido de nodos seleccionados", "New": "Nuevo", "Next Opened Workflow": "Siguiente flujo de trabajo abierto", @@ -857,6 +862,9 @@ "Ungroup selected group nodes": "Desagrupar nodos de grupo seleccionados", "Unpack the selected Subgraph": "Desempaquetar el Subgrafo seleccionado", "Workflows": "Flujos de trabajo", + "Unload Models": "Descargar modelos", + "Unload Models and Execution Cache": "Descargar modelos y caché de ejecución", + "Workflow": "Flujo de trabajo", "Zoom In": "Acercar", "Zoom Out": "Alejar", "Zoom to fit": "Ajustar al tamaño" diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index e04e372b41..7df3dea35d 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "Charger le flux de travail par défaut" }, - "Comfy_Manager_CustomNodesManager": { - "label": "Gestionnaire de Nœuds Personnalisés" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "Nœuds personnalisés (Beta)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "Nœuds personnalisés (Legacy)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "Menu du gestionnaire (Legacy)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Basculer la boîte de dialogue de progression" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "Ouvrir l'éditeur de masque pour le nœud sélectionné" }, + "Comfy_Memory_UnloadModels": { + "label": "Décharger les modèles" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "Décharger les modèles et le cache d'exécution" + }, "Comfy_NewBlankWorkflow": { "label": "Nouveau flux de travail vierge" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 9bc8cfbd51..7dc1300fae 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -82,12 +82,6 @@ "title": "Créer un compte" } }, - "breadcrumbsMenu": { - "clearWorkflow": "Effacer le workflow", - "deleteWorkflow": "Supprimer le workflow", - "duplicate": "Dupliquer", - "enterNewName": "Entrez un nouveau nom" - }, "chatHistory": { "cancelEdit": "Annuler", "cancelEditTooltip": "Annuler la modification", @@ -278,6 +272,7 @@ "color": "Couleur", "comingSoon": "Bientôt disponible", "command": "Commande", + "commandProhibited": "La commande {command} est interdite. Contactez un administrateur pour plus d'informations.", "community": "Communauté", "completed": "Terminé", "confirm": "Confirmer", @@ -298,9 +293,7 @@ "devices": "Appareils", "disableAll": "Désactiver tout", "disabling": "Désactivation", - "dismiss": "Fermer", "download": "Télécharger", - "duplicate": "Dupliquer", "edit": "Modifier", "empty": "Vide", "enableAll": "Activer tout", @@ -314,8 +307,6 @@ "filter": "Filtrer", "findIssues": "Trouver des problèmes", "firstTimeUIMessage": "C'est la première fois que vous utilisez la nouvelle interface utilisateur. Choisissez \"Menu > Utiliser le nouveau menu > Désactivé\" pour restaurer l'ancienne interface utilisateur.", - "frontendNewer": "La version du frontend {frontendVersion} peut ne pas être compatible avec la version du backend {backendVersion}.", - "frontendOutdated": "La version du frontend {frontendVersion} est obsolète. Le backend requiert la version {requiredVersion} ou supérieure.", "goToNode": "Aller au nœud", "help": "Aide", "icon": "Icône", @@ -339,20 +330,17 @@ "loadingPanel": "Chargement du panneau {panel}...", "login": "Connexion", "logs": "Journaux", - "micPermissionDenied": "Permission du microphone refusée", "migrate": "Migrer", "missing": "Manquant", "name": "Nom", "newFolder": "Nouveau dossier", "next": "Suivant", "no": "Non", - "noAudioRecorded": "Aucun audio enregistré", "noResultsFound": "Aucun résultat trouvé", "noTasksFound": "Aucune tâche trouvée", "noTasksFoundMessage": "Il n'y a pas de tâches dans la file d'attente.", "noWorkflowsFound": "Aucun flux de travail trouvé.", "nodes": "Nœuds", - "nodesRunning": "nœuds en cours d’exécution", "ok": "OK", "openNewIssue": "Ouvrir un nouveau problème", "overwrite": "Écraser", @@ -386,9 +374,7 @@ "showReport": "Afficher le rapport", "sort": "Trier", "source": "Source", - "startRecording": "Commencer l’enregistrement", "status": "Statut", - "stopRecording": "Arrêter l’enregistrement", "success": "Succès", "systemInfo": "Informations système", "terminal": "Terminal", @@ -397,14 +383,11 @@ "unknownError": "Erreur inconnue", "update": "Mettre à jour", "updateAvailable": "Mise à jour disponible", - "updateFrontend": "Mettre à jour le frontend", "updated": "Mis à jour", "updating": "Mise à jour", "upload": "Téléverser", "usageHint": "Conseil d'utilisation", "user": "Utilisateur", - "versionMismatchWarning": "Avertissement de compatibilité de version", - "versionMismatchWarningMessage": "{warning} : {detail} Consultez https://docs.comfy.org/installation/update_comfyui#common-update-issues pour les instructions de mise à jour.", "videoFailedToLoad": "Échec du chargement de la vidéo", "workflow": "Flux de travail" }, @@ -414,7 +397,6 @@ "resetView": "Réinitialiser la vue", "selectMode": "Mode sélection", "toggleLinkVisibility": "Basculer la visibilité des liens", - "toggleMinimap": "Afficher/Masquer la mini-carte", "zoomIn": "Zoom avant", "zoomOut": "Zoom arrière" }, @@ -573,10 +555,6 @@ "applyingTexture": "Application de la texture...", "backgroundColor": "Couleur de fond", "camera": "Caméra", - "cameraType": { - "orthographic": "Orthographique", - "perspective": "Perspective" - }, "clearRecording": "Effacer l'enregistrement", "edgeThreshold": "Seuil de Bordure", "export": "Exportation", @@ -597,7 +575,6 @@ "wireframe": "Fil de fer" }, "model": "Modèle", - "openIn3DViewer": "Ouvrir dans la visionneuse 3D", "previewOutput": "Aperçu de la sortie", "removeBackgroundImage": "Supprimer l'image de fond", "resizeNodeMatchOutput": "Redimensionner le nœud pour correspondre à la sortie", @@ -608,22 +585,8 @@ "switchCamera": "Changer de caméra", "switchingMaterialMode": "Changement de mode de matériau...", "upDirection": "Direction Haut", - "upDirections": { - "original": "Original" - }, "uploadBackgroundImage": "Télécharger l'image de fond", - "uploadTexture": "Télécharger Texture", - "viewer": { - "apply": "Appliquer", - "cameraSettings": "Paramètres de la caméra", - "cameraType": "Type de caméra", - "cancel": "Annuler", - "exportSettings": "Paramètres d’exportation", - "lightSettings": "Paramètres de l’éclairage", - "modelSettings": "Paramètres du modèle", - "sceneSettings": "Paramètres de la scène", - "title": "Visionneuse 3D (Bêta)" - } + "uploadTexture": "Télécharger Texture" }, "loadWorkflowWarning": { "coreNodesFromVersion": "Nécessite ComfyUI {version} :", @@ -670,6 +633,7 @@ "installationQueue": "File d'attente d'installation", "lastUpdated": "Dernière mise à jour", "latestVersion": "Dernière", + "legacyMenuNotAvailable": "Le menu du gestionnaire de l'ancienne version n'est pas disponible dans cette version de ComfyUI. Veuillez utiliser le nouveau menu du gestionnaire à la place.", "license": "Licence", "loadingVersions": "Chargement des versions...", "nightlyVersion": "Nocturne", @@ -739,28 +703,21 @@ "batchCountTooltip": "Le nombre de fois que la génération du flux de travail doit être mise en file d'attente", "clear": "Effacer le flux de travail", "clipspace": "Ouvrir Clipspace", - "dark": "Sombre", "disabled": "Désactivé", "disabledTooltip": "Le flux de travail ne sera pas mis en file d'attente automatiquement", "execute": "Exécuter", - "help": "Aide", "hideMenu": "Masquer le menu", "instant": "Instantané", "instantTooltip": "Le flux de travail sera mis en file d'attente immédiatement après la fin d'une génération", "interrupt": "Annuler l'exécution en cours", - "light": "Clair", - "manageExtensions": "Gérer les extensions", "onChange": "Sur modification", "onChangeTooltip": "Le flux de travail sera mis en file d'attente une fois une modification effectuée", - "queue": "Panneau de file d’attente", "refresh": "Actualiser les définitions des nœuds", "resetView": "Réinitialiser la vue du canevas", "run": "Exécuter", "runWorkflow": "Exécuter le workflow (Maj pour mettre en file d'attente en premier)", "runWorkflowFront": "Exécuter le workflow (Mettre en file d'attente en premier)", - "settings": "Paramètres", "showMenu": "Afficher le menu", - "theme": "Thème", "toggleBottomPanel": "Basculer le panneau inférieur" }, "menuLabels": { @@ -768,7 +725,7 @@ "Bottom Panel": "Panneau inférieur", "Browse Templates": "Parcourir les modèles", "Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés", - "Canvas Performance": "Performance du canevas", + "Canvas Toggle Link Visibility": "Basculer la visibilité du lien de la toile", "Canvas Toggle Lock": "Basculer le verrouillage de la toile", "Check for Updates": "Vérifier les mises à jour", "Clear Pending Tasks": "Effacer les tâches en attente", @@ -783,39 +740,32 @@ "Contact Support": "Contacter le support", "Convert Selection to Subgraph": "Convertir la sélection en sous-graphe", "Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe", - "Decrease Brush Size in MaskEditor": "Réduire la taille du pinceau dans MaskEditor", + "Custom Nodes (Beta)": "Nœuds personnalisés (Beta)", + "Custom Nodes (Legacy)": "Nœuds personnalisés (Ancienne version)", "Delete Selected Items": "Supprimer les éléments sélectionnés", "Desktop User Guide": "Guide de l'utilisateur de bureau", "Duplicate Current Workflow": "Dupliquer le flux de travail actuel", "Edit": "Éditer", - "Exit Subgraph": "Quitter le sous-graphe", "Export": "Exporter", "Export (API)": "Exporter (API)", - "File": "Fichier", "Fit Group To Contents": "Ajuster le groupe au contenu", - "Focus Mode": "Mode focus", + "Fit view to selected nodes": "Ajuster la vue aux nœuds sélectionnés", "Give Feedback": "Donnez votre avis", "Group Selected Nodes": "Grouper les nœuds sélectionnés", "Help": "Aide", - "Help Center": "Centre d’aide", - "Increase Brush Size in MaskEditor": "Augmenter la taille du pinceau dans MaskEditor", "Interrupt": "Interrompre", "Load Default Workflow": "Charger le flux de travail par défaut", "Manage group nodes": "Gérer les nœuds de groupe", - "Manager": "Gestionnaire", - "Minimap": "Minicarte", - "Model Library": "Bibliothèque de modèles", "Move Selected Nodes Down": "Déplacer les nœuds sélectionnés vers le bas", "Move Selected Nodes Left": "Déplacer les nœuds sélectionnés vers la gauche", "Move Selected Nodes Right": "Déplacer les nœuds sélectionnés vers la droite", "Move Selected Nodes Up": "Déplacer les nœuds sélectionnés vers le haut", + "Manager": "Gestionnaire", + "Manager Menu (Legacy)": "Menu du gestionnaire (Ancienne version)", "Mute/Unmute Selected Nodes": "Mettre en sourdine/Activer le son des nœuds sélectionnés", "New": "Nouveau", "Next Opened Workflow": "Prochain flux de travail ouvert", - "Node Library": "Bibliothèque de nœuds", - "Node Links": "Liens de nœuds", "Open": "Ouvrir", - "Open 3D Viewer (Beta) for Selected Node": "Ouvrir le visualiseur 3D (bêta) pour le nœud sélectionné", "Open Custom Nodes Folder": "Ouvrir le dossier des nœuds personnalisés", "Open DevTools": "Ouvrir DevTools", "Open Inputs Folder": "Ouvrir le dossier des entrées", @@ -828,7 +778,6 @@ "Pin/Unpin Selected Items": "Épingler/Désépingler les éléments sélectionnés", "Pin/Unpin Selected Nodes": "Épingler/Désépingler les nœuds sélectionnés", "Previous Opened Workflow": "Flux de travail ouvert précédent", - "Queue Panel": "Panneau de file d’attente", "Queue Prompt": "Invite de file d'attente", "Queue Prompt (Front)": "Invite de file d'attente (Front)", "Queue Selected Output Nodes": "Mettre en file d’attente les nœuds de sortie sélectionnés", @@ -847,6 +796,9 @@ "Sign Out": "Se déconnecter", "Toggle Essential Bottom Panel": "Basculer le panneau inférieur essentiel", "Toggle Logs Bottom Panel": "Basculer le panneau inférieur des journaux", + "Toggle Model Library Sidebar": "Afficher/Masquer la barre latérale de la bibliothèque de modèles", + "Toggle Node Library Sidebar": "Afficher/Masquer la barre latérale de la bibliothèque de nœuds", + "Toggle Queue Sidebar": "Afficher/Masquer la barre latérale de la file d’attente", "Toggle Search Box": "Basculer la boîte de recherche", "Toggle Terminal Bottom Panel": "Basculer le panneau inférieur du terminal", "Toggle Theme (Dark/Light)": "Basculer le thème (Sombre/Clair)", @@ -855,18 +807,11 @@ "Toggle the Custom Nodes Manager Progress Bar": "Basculer la barre de progression du gestionnaire de nœuds personnalisés", "Undo": "Annuler", "Ungroup selected group nodes": "Dégrouper les nœuds de groupe sélectionnés", - "Unpack the selected Subgraph": "Décompresser le Subgraph sélectionné", - "Workflows": "Flux de travail", + "Unload Models": "Décharger les modèles", + "Unload Models and Execution Cache": "Décharger les modèles et le cache d'exécution", + "Workflow": "Flux de travail", "Zoom In": "Zoom avant", - "Zoom Out": "Zoom arrière", - "Zoom to fit": "Ajuster à l’écran" - }, - "minimap": { - "nodeColors": "Couleurs des nœuds", - "renderBypassState": "Afficher l’état de contournement", - "renderErrorState": "Afficher l’état d’erreur", - "showGroups": "Afficher les cadres/groupes", - "showLinks": "Afficher les liens" + "Zoom Out": "Zoom arrière" }, "missingModelsDialog": { "doNotAskAgain": "Ne plus afficher ce message", @@ -1134,7 +1079,6 @@ }, "settingsCategories": { "3D": "3D", - "3DViewer": "Visionneuse 3D", "API Nodes": "Nœuds API", "About": "À Propos", "Appearance": "Apparence", @@ -1186,31 +1130,10 @@ "Window": "Fenêtre", "Workflow": "Flux de Travail" }, - "shortcuts": { - "essentials": "Essentiel", - "keyboardShortcuts": "Raccourcis clavier", - "manageShortcuts": "Gérer les raccourcis", - "noKeybinding": "Aucun raccourci", - "subcategories": { - "node": "Nœud", - "panelControls": "Contrôles du panneau", - "queue": "File d’attente", - "view": "Vue", - "workflow": "Flux de travail" - }, - "viewControls": "Contrôles d’affichage" - }, "sideToolbar": { "browseTemplates": "Parcourir les modèles d'exemple", "downloads": "Téléchargements", "helpCenter": "Centre d'aide", - "labels": { - "models": "Modèles", - "nodes": "Nœuds", - "queue": "File d’attente", - "templates": "Modèles", - "workflows": "Flux de travail" - }, "logout": "Déconnexion", "modelLibrary": "Bibliothèque de modèles", "newBlankWorkflow": "Créer un nouveau flux de travail vierge", @@ -1248,7 +1171,7 @@ }, "showFlatList": "Afficher la liste plate" }, - "templates": "Modèles", + "themeToggle": "Changer de thème", "workflowTab": { "confirmDelete": "Êtes-vous sûr de vouloir supprimer ce flux de travail ?", "confirmDeleteTitle": "Supprimer le flux de travail ?", @@ -1295,8 +1218,6 @@ "Video": "Vidéo", "Video API": "API vidéo" }, - "loadingMore": "Chargement de plus de modèles...", - "searchPlaceholder": "Rechercher des modèles...", "template": { "3D": { "3d_hunyuan3d_image_to_model": "Hunyuan3D", @@ -1619,7 +1540,6 @@ "failedToExportModel": "Échec de l'exportation du modèle en {format}", "failedToFetchBalance": "Échec de la récupération du solde : {error}", "failedToFetchLogs": "Échec de la récupération des journaux du serveur", - "failedToInitializeLoad3dViewer": "Échec de l'initialisation du visualiseur 3D", "failedToInitiateCreditPurchase": "Échec de l'initiation de l'achat de crédits : {error}", "failedToPurchaseCredits": "Échec de l'achat de crédits : {error}", "fileLoadError": "Impossible de trouver le flux de travail dans {fileName}", @@ -1675,13 +1595,6 @@ "prefix": "Doit commencer par {prefix}", "required": "Requis" }, - "versionMismatchWarning": { - "dismiss": "Ignorer", - "frontendNewer": "La version du frontend {frontendVersion} peut ne pas être compatible avec la version du backend {backendVersion}.", - "frontendOutdated": "La version du frontend {frontendVersion} est obsolète. Le backend nécessite la version {requiredVersion} ou supérieure.", - "title": "Avertissement de compatibilité de version", - "updateFrontend": "Mettre à jour le frontend" - }, "welcome": { "getStarted": "Commencer", "title": "Bienvenue sur ComfyUI" diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index 307ef72e56..c790972940 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "デフォルトのワークフローを読み込む" }, - "Comfy_Manager_CustomNodesManager": { - "label": "カスタムノードマネージャ" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "カスタムノード(ベータ版)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "カスタムノード(レガシー)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "マネージャーメニュー(レガシー)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "プログレスダイアログの切り替え" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "選択したノードのマスクエディタを開く" }, + "Comfy_Memory_UnloadModels": { + "label": "モデルのアンロード" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "モデルと実行キャッシュのアンロード" + }, "Comfy_NewBlankWorkflow": { "label": "新しい空のワークフロー" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index ff8e82102c..2a3b854aa8 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -82,12 +82,6 @@ "title": "アカウントを作成する" } }, - "breadcrumbsMenu": { - "clearWorkflow": "ワークフローをクリア", - "deleteWorkflow": "ワークフローを削除", - "duplicate": "複製", - "enterNewName": "新しい名前を入力" - }, "chatHistory": { "cancelEdit": "キャンセル", "cancelEditTooltip": "編集をキャンセル", @@ -278,6 +272,7 @@ "color": "色", "comingSoon": "近日公開", "command": "コマンド", + "commandProhibited": "コマンド {command} は禁止されています。詳細は管理者にお問い合わせください。", "community": "コミュニティ", "completed": "完了", "confirm": "確認", @@ -298,9 +293,7 @@ "devices": "デバイス", "disableAll": "すべて無効にする", "disabling": "無効化", - "dismiss": "閉じる", "download": "ダウンロード", - "duplicate": "複製", "edit": "編集", "empty": "空", "enableAll": "すべて有効にする", @@ -314,8 +307,6 @@ "filter": "フィルタ", "findIssues": "問題を見つける", "firstTimeUIMessage": "新しいUIを初めて使用しています。「メニュー > 新しいメニューを使用 > 無効」を選択することで古いUIに戻すことが可能です。", - "frontendNewer": "フロントエンドのバージョン {frontendVersion} はバックエンドのバージョン {backendVersion} と互換性がない可能性があります。", - "frontendOutdated": "フロントエンドのバージョン {frontendVersion} は古くなっています。バックエンドは {requiredVersion} 以上が必要です。", "goToNode": "ノードに移動", "help": "ヘルプ", "icon": "アイコン", @@ -339,20 +330,17 @@ "loadingPanel": "{panel} パネルを読み込み中...", "login": "ログイン", "logs": "ログ", - "micPermissionDenied": "マイクの許可が拒否されました", "migrate": "移行する", "missing": "不足している", "name": "名前", "newFolder": "新しいフォルダー", "next": "次へ", "no": "いいえ", - "noAudioRecorded": "音声が録音されていません", "noResultsFound": "結果が見つかりません", "noTasksFound": "タスクが見つかりません", "noTasksFoundMessage": "キューにタスクがありません。", "noWorkflowsFound": "ワークフローが見つかりません。", "nodes": "ノード", - "nodesRunning": "ノードが実行中", "ok": "OK", "openNewIssue": "新しい問題を開く", "overwrite": "上書き", @@ -386,9 +374,7 @@ "showReport": "レポートを表示", "sort": "並び替え", "source": "ソース", - "startRecording": "録音開始", "status": "ステータス", - "stopRecording": "録音停止", "success": "成功", "systemInfo": "システム情報", "terminal": "ターミナル", @@ -397,14 +383,11 @@ "unknownError": "不明なエラー", "update": "更新", "updateAvailable": "更新が利用可能", - "updateFrontend": "フロントエンドを更新", "updated": "更新済み", "updating": "更新中", "upload": "アップロード", "usageHint": "使用ヒント", "user": "ユーザー", - "versionMismatchWarning": "バージョン互換性の警告", - "versionMismatchWarningMessage": "{warning}: {detail} 更新手順については https://docs.comfy.org/installation/update_comfyui#common-update-issues をご覧ください。", "videoFailedToLoad": "ビデオの読み込みに失敗しました", "workflow": "ワークフロー" }, @@ -414,7 +397,6 @@ "resetView": "ビューをリセット", "selectMode": "選択モード", "toggleLinkVisibility": "リンクの表示切り替え", - "toggleMinimap": "ミニマップの切り替え", "zoomIn": "拡大", "zoomOut": "縮小" }, @@ -573,10 +555,6 @@ "applyingTexture": "テクスチャを適用中...", "backgroundColor": "背景色", "camera": "カメラ", - "cameraType": { - "orthographic": "オルソグラフィック", - "perspective": "パースペクティブ" - }, "clearRecording": "録画をクリア", "edgeThreshold": "エッジ閾値", "export": "エクスポート", @@ -597,7 +575,6 @@ "wireframe": "ワイヤーフレーム" }, "model": "モデル", - "openIn3DViewer": "3Dビューアで開く", "previewOutput": "出力のプレビュー", "removeBackgroundImage": "背景画像を削除", "resizeNodeMatchOutput": "ノードを出力に合わせてリサイズ", @@ -608,22 +585,8 @@ "switchCamera": "カメラを切り替える", "switchingMaterialMode": "マテリアルモードの切り替え中...", "upDirection": "上方向", - "upDirections": { - "original": "オリジナル" - }, "uploadBackgroundImage": "背景画像をアップロード", - "uploadTexture": "テクスチャをアップロード", - "viewer": { - "apply": "適用", - "cameraSettings": "カメラ設定", - "cameraType": "カメラタイプ", - "cancel": "キャンセル", - "exportSettings": "エクスポート設定", - "lightSettings": "ライト設定", - "modelSettings": "モデル設定", - "sceneSettings": "シーン設定", - "title": "3Dビューア(ベータ)" - } + "uploadTexture": "テクスチャをアップロード" }, "loadWorkflowWarning": { "coreNodesFromVersion": "ComfyUI {version} が必要です:", @@ -670,6 +633,7 @@ "installationQueue": "インストールキュー", "lastUpdated": "最終更新日", "latestVersion": "最新", + "legacyMenuNotAvailable": "このバージョンのComfyUIでは、レガシーマネージャーメニューは利用できません。新しいマネージャーメニューを使用してください。", "license": "ライセンス", "loadingVersions": "バージョンを読み込んでいます...", "nightlyVersion": "ナイトリー", @@ -739,28 +703,21 @@ "batchCountTooltip": "ワークフロー生成回数", "clear": "ワークフローをクリア", "clipspace": "クリップスペースを開く", - "dark": "ダーク", "disabled": "無効", "disabledTooltip": "ワークフローは自動的にキューに追加されません", "execute": "実行", - "help": "ヘルプ", "hideMenu": "メニューを隠す", "instant": "即時", "instantTooltip": "生成完了後すぐにキューに追加", "interrupt": "現在の実行を中止", - "light": "ライト", - "manageExtensions": "拡張機能の管理", "onChange": "変更時", "onChangeTooltip": "変更が行われるとワークフローがキューに追加されます", - "queue": "キューパネル", "refresh": "ノードを更新", "resetView": "ビューをリセット", "run": "実行する", "runWorkflow": "ワークフローを実行する (Shiftで先頭にキュー)", "runWorkflowFront": "ワークフローを実行する (先頭にキュー)", - "settings": "設定", "showMenu": "メニューを表示", - "theme": "テーマ", "toggleBottomPanel": "下部パネルを切り替え" }, "menuLabels": { @@ -768,7 +725,7 @@ "Bottom Panel": "下部パネル", "Browse Templates": "テンプレートを参照", "Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除", - "Canvas Performance": "キャンバスパフォーマンス", + "Canvas Toggle Link Visibility": "キャンバスのリンク表示を切り替え", "Canvas Toggle Lock": "キャンバスのロックを切り替え", "Check for Updates": "更新を確認する", "Clear Pending Tasks": "保留中のタスクをクリア", @@ -783,39 +740,32 @@ "Contact Support": "サポートに連絡", "Convert Selection to Subgraph": "選択範囲をサブグラフに変換", "Convert selected nodes to group node": "選択したノードをグループノードに変換", - "Decrease Brush Size in MaskEditor": "マスクエディタでブラシサイズを小さくする", + "Custom Nodes (Beta)": "カスタムノード(ベータ)", + "Custom Nodes (Legacy)": "カスタムノード(レガシー)", "Delete Selected Items": "選択したアイテムを削除", "Desktop User Guide": "デスクトップユーザーガイド", "Duplicate Current Workflow": "現在のワークフローを複製", "Edit": "編集", - "Exit Subgraph": "サブグラフを終了", "Export": "エクスポート", "Export (API)": "エクスポート (API)", - "File": "ファイル", "Fit Group To Contents": "グループを内容に合わせる", - "Focus Mode": "フォーカスモード", + "Fit view to selected nodes": "選択したノードにビューを合わせる", "Give Feedback": "フィードバックを送る", "Group Selected Nodes": "選択したノードをグループ化", "Help": "ヘルプ", - "Help Center": "ヘルプセンター", - "Increase Brush Size in MaskEditor": "マスクエディタでブラシサイズを大きくする", "Interrupt": "中断", "Load Default Workflow": "デフォルトワークフローを読み込む", "Manage group nodes": "グループノードを管理", - "Manager": "マネージャー", - "Minimap": "ミニマップ", - "Model Library": "モデルライブラリ", "Move Selected Nodes Down": "選択したノードを下へ移動", "Move Selected Nodes Left": "選択したノードを左へ移動", "Move Selected Nodes Right": "選択したノードを右へ移動", "Move Selected Nodes Up": "選択したノードを上へ移動", + "Manager": "マネージャー", + "Manager Menu (Legacy)": "マネージャーメニュー(レガシー)", "Mute/Unmute Selected Nodes": "選択したノードのミュート/ミュート解除", "New": "新規", "Next Opened Workflow": "次に開いたワークフロー", - "Node Library": "ノードライブラリ", - "Node Links": "ノードリンク", "Open": "開く", - "Open 3D Viewer (Beta) for Selected Node": "選択したノードの3Dビューアー(ベータ)を開く", "Open Custom Nodes Folder": "カスタムノードフォルダを開く", "Open DevTools": "DevToolsを開く", "Open Inputs Folder": "入力フォルダを開く", @@ -828,7 +778,6 @@ "Pin/Unpin Selected Items": "選択したアイテムのピン留め/ピン留め解除", "Pin/Unpin Selected Nodes": "選択したノードのピン留め/ピン留め解除", "Previous Opened Workflow": "前に開いたワークフロー", - "Queue Panel": "キューパネル", "Queue Prompt": "キューのプロンプト", "Queue Prompt (Front)": "キューのプロンプト (前面)", "Queue Selected Output Nodes": "選択した出力ノードをキューに追加", @@ -850,23 +799,16 @@ "Toggle Search Box": "検索ボックスの切り替え", "Toggle Terminal Bottom Panel": "ターミナル下部パネルの切り替え", "Toggle Theme (Dark/Light)": "テーマを切り替え(ダーク/ライト)", - "Toggle View Controls Bottom Panel": "ビューコントロール下部パネルの切り替え", + "Toggle Workflows Sidebar": "ワークフローサイドバーを切り替え", "Toggle the Custom Nodes Manager": "カスタムノードマネージャーを切り替え", "Toggle the Custom Nodes Manager Progress Bar": "カスタムノードマネージャーの進行状況バーを切り替え", "Undo": "元に戻す", "Ungroup selected group nodes": "選択したグループノードのグループ解除", - "Unpack the selected Subgraph": "選択したサブグラフを展開", - "Workflows": "ワークフロー", + "Unload Models": "モデルのアンロード", + "Unload Models and Execution Cache": "モデルと実行キャッシュのアンロード", + "Workflow": "ワークフロー", "Zoom In": "ズームイン", - "Zoom Out": "ズームアウト", - "Zoom to fit": "全体表示にズーム" - }, - "minimap": { - "nodeColors": "ノードの色", - "renderBypassState": "バイパス状態を表示", - "renderErrorState": "エラー状態を表示", - "showGroups": "フレーム/グループを表示", - "showLinks": "リンクを表示" + "Zoom Out": "ズームアウト" }, "missingModelsDialog": { "doNotAskAgain": "再度表示しない", @@ -1134,7 +1076,6 @@ }, "settingsCategories": { "3D": "3D", - "3DViewer": "3Dビューア", "API Nodes": "APIノード", "About": "情報", "Appearance": "外観", @@ -1186,31 +1127,10 @@ "Window": "ウィンドウ", "Workflow": "ワークフロー" }, - "shortcuts": { - "essentials": "基本", - "keyboardShortcuts": "キーボードショートカット", - "manageShortcuts": "ショートカットの管理", - "noKeybinding": "キー割り当てなし", - "subcategories": { - "node": "ノード", - "panelControls": "パネルコントロール", - "queue": "キュー", - "view": "ビュー", - "workflow": "ワークフロー" - }, - "viewControls": "表示コントロール" - }, "sideToolbar": { "browseTemplates": "サンプルテンプレートを表示", "downloads": "ダウンロード", "helpCenter": "ヘルプセンター", - "labels": { - "models": "モデル", - "nodes": "ノード", - "queue": "キュー", - "templates": "テンプレート", - "workflows": "ワークフロー" - }, "logout": "ログアウト", "modelLibrary": "モデルライブラリ", "newBlankWorkflow": "新しい空のワークフローを作成", @@ -1248,7 +1168,7 @@ }, "showFlatList": "フラットリストを表示" }, - "templates": "テンプレート", + "themeToggle": "テーマの切り替え", "workflowTab": { "confirmDelete": "このワークフローを削除してもよろしいですか?", "confirmDeleteTitle": "ワークフローを削除しますか?", @@ -1295,8 +1215,6 @@ "Video": "ビデオ", "Video API": "動画API" }, - "loadingMore": "さらにテンプレートを読み込み中...", - "searchPlaceholder": "テンプレートを検索...", "template": { "3D": { "3d_hunyuan3d_image_to_model": "Hunyuan3D", @@ -1619,7 +1537,6 @@ "failedToExportModel": "{format}としてモデルのエクスポートに失敗しました", "failedToFetchBalance": "残高の取得に失敗しました: {error}", "failedToFetchLogs": "サーバーログの取得に失敗しました", - "failedToInitializeLoad3dViewer": "3Dビューアの初期化に失敗しました", "failedToInitiateCreditPurchase": "クレジット購入の開始に失敗しました: {error}", "failedToPurchaseCredits": "クレジットの購入に失敗しました: {error}", "fileLoadError": "{fileName}でワークフローが見つかりません", @@ -1675,13 +1592,6 @@ "prefix": "{prefix}で始める必要があります", "required": "必須" }, - "versionMismatchWarning": { - "dismiss": "閉じる", - "frontendNewer": "フロントエンドのバージョン {frontendVersion} は、バックエンドのバージョン {backendVersion} と互換性がない可能性があります。", - "frontendOutdated": "フロントエンドのバージョン {frontendVersion} は古くなっています。バックエンドはバージョン {requiredVersion} 以上が必要です。", - "title": "バージョン互換性の警告", - "updateFrontend": "フロントエンドを更新" - }, "welcome": { "getStarted": "はじめる", "title": "ComfyUIへようこそ" diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 431ae7ae00..23dfa85571 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "기본 워크플로 로드" }, - "Comfy_Manager_CustomNodesManager": { - "label": "사용자 정의 노드 관리자" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "사용자 정의 노드 (베타)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "사용자 정의 노드 (레거시)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "매니저 메뉴 (레거시)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "진행 상황 대화 상자 전환" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "선택한 노드 마스크 편집기 열기" }, + "Comfy_Memory_UnloadModels": { + "label": "모델 언로드" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "모델 및 실행 캐시 언로드" + }, "Comfy_NewBlankWorkflow": { "label": "새로운 빈 워크플로" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 9dfe1220fd..f02344f549 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -5,7 +5,7 @@ "totalCost": "총 비용" }, "apiNodesSignInDialog": { - "message": "이 워크플로에는 API 노드가 포함되어 있으며, 실행하려면 계정에 로그인해야 합니다.", + "message": "이 워크플로우에는 API 노드가 포함되어 있으며, 실행하려면 계정에 로그인해야 합니다.", "title": "API 노드 사용에 필요한 로그인" }, "auth": { @@ -82,12 +82,6 @@ "title": "계정 생성" } }, - "breadcrumbsMenu": { - "clearWorkflow": "워크플로 내용 지우기", - "deleteWorkflow": "워크플로 삭제", - "duplicate": "복제", - "enterNewName": "새 이름 입력" - }, "chatHistory": { "cancelEdit": "취소", "cancelEditTooltip": "편집 취소", @@ -161,7 +155,7 @@ "time": "시간", "topUp": { "buyNow": "지금 구매", - "insufficientMessage": "이 워크플로를 실행하기에 크레딧이 부족합니다.", + "insufficientMessage": "이 워크플로우를 실행하기에 크레딧이 부족합니다.", "insufficientTitle": "크레딧 부족", "maxAmount": "(최대 $1,000 USD)", "quickPurchase": "빠른 구매", @@ -252,7 +246,7 @@ "errorDialog": { "defaultTitle": "오류가 발생했습니다", "extensionFileHint": "다음 스크립트 때문일 수 있습니다", - "loadWorkflowTitle": "워크플로 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다", + "loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다", "noStackTrace": "스택 추적을 사용할 수 없습니다", "promptExecutionError": "프롬프트 실행 실패" }, @@ -278,6 +272,7 @@ "color": "색상", "comingSoon": "곧 출시 예정", "command": "명령", + "commandProhibited": "명령 {command}은 금지되었습니다. 자세한 정보는 관리자에게 문의하십시오.", "community": "커뮤니티", "completed": "완료됨", "confirm": "확인", @@ -298,9 +293,7 @@ "devices": "장치", "disableAll": "모두 비활성화", "disabling": "비활성화 중", - "dismiss": "닫기", "download": "다운로드", - "duplicate": "복제", "edit": "편집", "empty": "비어 있음", "enableAll": "모두 활성화", @@ -314,8 +307,6 @@ "filter": "필터", "findIssues": "문제 찾기", "firstTimeUIMessage": "새 UI를 처음 사용합니다. \"메뉴 > 새 메뉴 사용 > 비활성화\"를 선택하여 이전 UI로 복원하세요.", - "frontendNewer": "프론트엔드 버전 {frontendVersion}이(가) 백엔드 버전 {backendVersion}과(와) 호환되지 않을 수 있습니다.", - "frontendOutdated": "프론트엔드 버전 {frontendVersion}이(가) 오래되었습니다. 백엔드는 {requiredVersion} 이상이 필요합니다.", "goToNode": "노드로 이동", "help": "도움말", "icon": "아이콘", @@ -339,20 +330,17 @@ "loadingPanel": "{panel} 패널 불러오는 중...", "login": "로그인", "logs": "로그", - "micPermissionDenied": "마이크 권한이 거부되었습니다", "migrate": "이전(migrate)", "missing": "누락됨", "name": "이름", "newFolder": "새 폴더", "next": "다음", "no": "아니오", - "noAudioRecorded": "녹음된 오디오가 없습니다", "noResultsFound": "결과를 찾을 수 없습니다.", "noTasksFound": "작업을 찾을 수 없습니다.", "noTasksFoundMessage": "대기열에 작업이 없습니다.", "noWorkflowsFound": "워크플로를 찾을 수 없습니다.", "nodes": "노드", - "nodesRunning": "노드 실행 중", "ok": "확인", "openNewIssue": "새 문제 열기", "overwrite": "덮어쓰기", @@ -386,9 +374,7 @@ "showReport": "보고서 보기", "sort": "정렬", "source": "소스", - "startRecording": "녹음 시작", "status": "상태", - "stopRecording": "녹음 중지", "success": "성공", "systemInfo": "시스템 정보", "terminal": "터미널", @@ -397,14 +383,11 @@ "unknownError": "알 수 없는 오류", "update": "업데이트", "updateAvailable": "업데이트 가능", - "updateFrontend": "프론트엔드 업데이트", "updated": "업데이트 됨", "updating": "업데이트 중", "upload": "업로드", "usageHint": "사용 힌트", "user": "사용자", - "versionMismatchWarning": "버전 호환성 경고", - "versionMismatchWarningMessage": "{warning}: {detail} 업데이트 지침은 https://docs.comfy.org/installation/update_comfyui#common-update-issues 를 방문하세요.", "videoFailedToLoad": "비디오를 로드하지 못했습니다.", "workflow": "워크플로" }, @@ -414,7 +397,6 @@ "resetView": "보기 재설정", "selectMode": "선택 모드", "toggleLinkVisibility": "링크 가시성 전환", - "toggleMinimap": "미니맵 전환", "zoomIn": "확대", "zoomOut": "축소" }, @@ -573,10 +555,6 @@ "applyingTexture": "텍스처 적용 중...", "backgroundColor": "배경색", "camera": "카메라", - "cameraType": { - "orthographic": "직교", - "perspective": "원근" - }, "clearRecording": "녹화 지우기", "edgeThreshold": "엣지 임계값", "export": "내보내기", @@ -597,7 +575,6 @@ "wireframe": "와이어프레임" }, "model": "모델", - "openIn3DViewer": "3D 뷰어에서 열기", "previewOutput": "출력 미리보기", "removeBackgroundImage": "배경 이미지 제거", "resizeNodeMatchOutput": "노드 크기를 출력에 맞추기", @@ -608,22 +585,8 @@ "switchCamera": "카메라 전환", "switchingMaterialMode": "재질 모드 전환 중...", "upDirection": "위 방향", - "upDirections": { - "original": "원본" - }, "uploadBackgroundImage": "배경 이미지 업로드", - "uploadTexture": "텍스처 업로드", - "viewer": { - "apply": "적용", - "cameraSettings": "카메라 설정", - "cameraType": "카메라 유형", - "cancel": "취소", - "exportSettings": "내보내기 설정", - "lightSettings": "조명 설정", - "modelSettings": "모델 설정", - "sceneSettings": "씬 설정", - "title": "3D 뷰어 (베타)" - } + "uploadTexture": "텍스처 업로드" }, "loadWorkflowWarning": { "coreNodesFromVersion": "ComfyUI {version} 이상 필요:", @@ -663,13 +626,14 @@ "enabled": "활성화", "nodePack": "노드 팩" }, - "inWorkflow": "워크플로 내", + "inWorkflow": "워크플로우 내", "infoPanelEmpty": "정보를 보려면 항목을 클릭하세요", "installAllMissingNodes": "모든 누락된 노드 설치", "installSelected": "선택한 항목 설치", "installationQueue": "설치 대기열", "lastUpdated": "마지막 업데이트", "latestVersion": "최신", + "legacyMenuNotAvailable": "이 버전의 ComfyUI에서는 레거시 매니저 메뉴를 사용할 수 없습니다. 대신 새로운 매니저 메뉴를 사용하십시오.", "license": "라이선스", "loadingVersions": "버전 로딩 중...", "nightlyVersion": "최신 테스트 버전(nightly)", @@ -739,28 +703,21 @@ "batchCountTooltip": "워크플로 작업을 실행 대기열에 반복 추가할 횟수", "clear": "워크플로 비우기", "clipspace": "클립스페이스 열기", - "dark": "다크", "disabled": "비활성화됨", "disabledTooltip": "워크플로 작업을 자동으로 실행 대기열에 추가하지 않습니다.", "execute": "실행", - "help": "도움말", "hideMenu": "메뉴 숨기기", "instant": "즉시", "instantTooltip": "워크플로 실행이 완료되면 즉시 실행 대기열에 추가합니다.", "interrupt": "현재 실행 취소", - "light": "라이트", - "manageExtensions": "확장 프로그램 관리", "onChange": "변경 시", "onChangeTooltip": "변경이 있는 경우에만 워크플로를 실행 대기열에 추가합니다.", - "queue": "대기열 패널", "refresh": "노드 정의 새로 고침", "resetView": "캔버스 보기 재설정", "run": "실행", - "runWorkflow": "워크플로 실행 (시프트 키와 함께 클릭시 가장 먼저 실행)", - "runWorkflowFront": "워크플로 실행 (가장 먼저 실행)", - "settings": "설정", + "runWorkflow": "워크플로우 실행 (시프트 키와 함께 클릭시 가장 먼저 실행)", + "runWorkflowFront": "워크플로우 실행 (가장 먼저 실행)", "showMenu": "메뉴 표시", - "theme": "테마", "toggleBottomPanel": "하단 패널 전환" }, "menuLabels": { @@ -768,7 +725,7 @@ "Bottom Panel": "하단 패널", "Browse Templates": "템플릿 탐색", "Bypass/Unbypass Selected Nodes": "선택한 노드 우회/우회 해제", - "Canvas Performance": "캔버스 성능", + "Canvas Toggle Link Visibility": "캔버스 토글 링크 가시성", "Canvas Toggle Lock": "캔버스 토글 잠금", "Check for Updates": "업데이트 확인", "Clear Pending Tasks": "보류 중인 작업 제거하기", @@ -783,39 +740,32 @@ "Contact Support": "고객 지원 문의", "Convert Selection to Subgraph": "선택 영역을 서브그래프로 변환", "Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환", - "Decrease Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 줄이기", + "Custom Nodes (Beta)": "사용자 정의 노드 (베타)", + "Custom Nodes (Legacy)": "사용자 정의 노드 (레거시)", "Delete Selected Items": "선택한 항목 삭제", "Desktop User Guide": "데스크톱 사용자 가이드", "Duplicate Current Workflow": "현재 워크플로 복제", "Edit": "편집", - "Exit Subgraph": "서브그래프 종료", "Export": "내보내기", "Export (API)": "내보내기 (API)", - "File": "파일", "Fit Group To Contents": "그룹을 내용에 맞게 조정", - "Focus Mode": "포커스 모드", + "Fit view to selected nodes": "선택한 노드에 맞게 보기 조정", "Give Feedback": "피드백 제공", "Group Selected Nodes": "선택한 노드 그룹화", "Help": "도움말", - "Help Center": "도움말 센터", - "Increase Brush Size in MaskEditor": "마스크 편집기에서 브러시 크기 늘리기", "Interrupt": "중단", "Load Default Workflow": "기본 워크플로 불러오기", "Manage group nodes": "그룹 노드 관리", - "Manager": "매니저", - "Minimap": "미니맵", - "Model Library": "모델 라이브러리", "Move Selected Nodes Down": "선택한 노드 아래로 이동", "Move Selected Nodes Left": "선택한 노드 왼쪽으로 이동", "Move Selected Nodes Right": "선택한 노드 오른쪽으로 이동", "Move Selected Nodes Up": "선택한 노드 위로 이동", + "Manager": "매니저", + "Manager Menu (Legacy)": "매니저 메뉴 (레거시)", "Mute/Unmute Selected Nodes": "선택한 노드 활성화/비활성화", "New": "새로 만들기", "Next Opened Workflow": "다음 열린 워크플로", - "Node Library": "노드 라이브러리", - "Node Links": "노드 링크", "Open": "열기", - "Open 3D Viewer (Beta) for Selected Node": "선택한 노드에 대해 3D 뷰어(베타) 열기", "Open Custom Nodes Folder": "사용자 정의 노드 폴더 열기", "Open DevTools": "개발자 도구 열기", "Open Inputs Folder": "입력 폴더 열기", @@ -828,7 +778,6 @@ "Pin/Unpin Selected Items": "선택한 항목 고정/고정 해제", "Pin/Unpin Selected Nodes": "선택한 노드 고정/고정 해제", "Previous Opened Workflow": "이전 열린 워크플로", - "Queue Panel": "대기열 패널", "Queue Prompt": "실행 대기열에 프롬프트 추가", "Queue Prompt (Front)": "실행 대기열 맨 앞에 프롬프트 추가", "Queue Selected Output Nodes": "선택한 출력 노드 대기열에 추가", @@ -845,28 +794,25 @@ "Show Model Selector (Dev)": "모델 선택기 표시 (개발자용)", "Show Settings Dialog": "설정 대화상자 표시", "Sign Out": "로그아웃", - "Toggle Essential Bottom Panel": "필수 하단 패널 전환", + "Toggle Bottom Panel": "하단 패널 전환", + "Toggle Focus Mode": "포커스 모드 전환", "Toggle Logs Bottom Panel": "로그 하단 패널 전환", + "Toggle Model Library Sidebar": "모델 라이브러리 사이드바 전환", + "Toggle Node Library Sidebar": "노드 라이브러리 사이드바 전환", + "Toggle Queue Sidebar": "대기열 사이드바 전환", "Toggle Search Box": "검색 상자 전환", "Toggle Terminal Bottom Panel": "터미널 하단 패널 전환", "Toggle Theme (Dark/Light)": "테마 전환 (어두운/밝은)", - "Toggle View Controls Bottom Panel": "뷰 컨트롤 하단 패널 전환", + "Toggle Workflows Sidebar": "워크플로우 사이드바 전환", "Toggle the Custom Nodes Manager": "커스텀 노드 매니저 전환", "Toggle the Custom Nodes Manager Progress Bar": "커스텀 노드 매니저 진행률 표시줄 전환", "Undo": "실행 취소", "Ungroup selected group nodes": "선택한 그룹 노드 그룹 해제", - "Unpack the selected Subgraph": "선택한 서브그래프 풀기", - "Workflows": "워크플로우", + "Unload Models": "모델 언로드", + "Unload Models and Execution Cache": "모델 및 실행 캐시 언로드", + "Workflow": "워크플로", "Zoom In": "확대", - "Zoom Out": "축소", - "Zoom to fit": "화면에 맞추기" - }, - "minimap": { - "nodeColors": "노드 색상", - "renderBypassState": "바이패스 상태 렌더링", - "renderErrorState": "에러 상태 렌더링", - "showGroups": "프레임/그룹 표시", - "showLinks": "링크 표시" + "Zoom Out": "축소" }, "missingModelsDialog": { "doNotAskAgain": "다시 보지 않기", @@ -953,7 +899,7 @@ "documentationPage": "문서 페이지", "inputs": "입력", "loadError": "도움말을 불러오지 못했습니다: {error}", - "moreHelp": "더 자세한 도움말은", + "moreHelp": "더 많은 도움말은", "outputs": "출력", "type": "유형" }, @@ -1134,7 +1080,6 @@ }, "settingsCategories": { "3D": "3D", - "3DViewer": "3D뷰어", "API Nodes": "API 노드", "About": "정보", "Appearance": "모양", @@ -1186,31 +1131,10 @@ "Window": "창", "Workflow": "워크플로" }, - "shortcuts": { - "essentials": "필수", - "keyboardShortcuts": "키보드 단축키", - "manageShortcuts": "단축키 관리", - "noKeybinding": "단축키 없음", - "subcategories": { - "node": "노드", - "panelControls": "패널 컨트롤", - "queue": "대기열", - "view": "보기", - "workflow": "워크플로우" - }, - "viewControls": "보기 컨트롤" - }, "sideToolbar": { "browseTemplates": "예제 템플릿 탐색", "downloads": "다운로드", "helpCenter": "도움말 센터", - "labels": { - "models": "모델", - "nodes": "노드", - "queue": "대기열", - "templates": "템플릿", - "workflows": "워크플로우" - }, "logout": "로그아웃", "modelLibrary": "모델 라이브러리", "newBlankWorkflow": "새 빈 워크플로 만들기", @@ -1248,7 +1172,7 @@ }, "showFlatList": "평면 목록 표시" }, - "templates": "템플릿", + "themeToggle": "테마 전환", "workflowTab": { "confirmDelete": "정말로 이 워크플로를 삭제하시겠습니까?", "confirmDeleteTitle": "워크플로 삭제", @@ -1295,8 +1219,6 @@ "Video": "비디오", "Video API": "비디오 API" }, - "loadingMore": "템플릿을 더 불러오는 중...", - "searchPlaceholder": "템플릿 검색...", "template": { "3D": { "3d_hunyuan3d_image_to_model": "Hunyuan3D 2.0", @@ -1305,11 +1227,11 @@ "stable_zero123_example": "Stable Zero123" }, "3D API": { - "api_rodin_image_to_model": "Rodin: 이미지 → 모델", - "api_rodin_multiview_to_model": "Rodin: 다중뷰 → 모델", - "api_tripo_image_to_model": "Tripo: 이미지 → 모델", - "api_tripo_multiview_to_model": "Tripo: 다중뷰 → 모델", - "api_tripo_text_to_model": "Tripo: 텍스트 → 모델" + "api_rodin_image_to_model": "Rodin: 이미지 투 모델", + "api_rodin_multiview_to_model": "Rodin: 다중뷰 투 모델", + "api_tripo_image_to_model": "Tripo: 이미지 투 모델", + "api_tripo_multiview_to_model": "Tripo: 다중뷰 투 모델", + "api_tripo_text_to_model": "Tripo: 텍스트 투 모델" }, "Area Composition": { "area_composition": "영역 구성", @@ -1317,15 +1239,15 @@ }, "Audio": { "audio_ace_step_1_m2m_editing": "ACE Step v1 M2M 편집", - "audio_ace_step_1_t2a_instrumentals": "ACE-Step v1 텍스트 → 연주곡", - "audio_ace_step_1_t2a_song": "ACE Step v1 텍스트 → 노래", + "audio_ace_step_1_t2a_instrumentals": "ACE-Step v1 텍스트 투 연주곡", + "audio_ace_step_1_t2a_song": "ACE Step v1 텍스트 투 노래", "audio_stable_audio_example": "Stable Audio" }, "Basics": { "default": "이미지 생성", "embedding_example": "임베딩", "gligen_textbox_example": "글리젠 텍스트박스", - "image2image": "이미지 → 이미지", + "image2image": "이미지 투 이미지", "inpaint_example": "인페인트", "inpaint_model_outpainting": "아웃페인팅", "lora": "LoRA", @@ -1339,62 +1261,62 @@ "mixing_controlnets": "컨트롤넷 혼합" }, "Flux": { - "flux_canny_model_example": "FLUX 캐니 모델", - "flux_depth_lora_example": "FLUX 깊이 로라", - "flux_dev_checkpoint_example": "FLUX Dev fp8", - "flux_dev_full_text_to_image": "FLUX Dev 전체 텍스트 투 이미지", - "flux_fill_inpaint_example": "FLUX 인페인트", - "flux_fill_outpaint_example": "FLUX 아웃페인트", - "flux_kontext_dev_basic": "FLUX Kontext Dev(기본)", - "flux_kontext_dev_grouped": "FLUX Kontext Dev(그룹화)", - "flux_redux_model_example": "FLUX Redux 모델", - "flux_schnell": "FLUX Schnell fp8", - "flux_schnell_full_text_to_image": "FLUX Schnell Full 텍스트 → 이미지" + "flux_canny_model_example": "Flux 캐니 모델", + "flux_depth_lora_example": "Flux 깊이 로라", + "flux_dev_checkpoint_example": "Flux Dev fp8", + "flux_dev_full_text_to_image": "Flux Dev 전체 텍스트 투 이미지", + "flux_fill_inpaint_example": "Flux 인페인트", + "flux_fill_outpaint_example": "Flux 아웃페인트", + "flux_kontext_dev_basic": "Flux Kontext Dev(기본)", + "flux_kontext_dev_grouped": "Flux Kontext Dev(그룹화)", + "flux_redux_model_example": "Flux Redux 모델", + "flux_schnell": "Flux Schnell fp8", + "flux_schnell_full_text_to_image": "Flux Schnell 전체 텍스트 투 이미지" }, "Image": { "hidream_e1_full": "HiDream E1 Full", "hidream_i1_dev": "HiDream I1 Dev", "hidream_i1_fast": "HiDream I1 Fast", "hidream_i1_full": "HiDream I1 Full", - "image_chroma_text_to_image": "Chroma 텍스트 → 이미지", + "image_chroma_text_to_image": "Chroma 텍스트 투 이미지", "image_cosmos_predict2_2B_t2i": "Cosmos Predict2 2B T2I", "image_lotus_depth_v1_1": "Lotus Depth", "image_omnigen2_image_edit": "OmniGen2 이미지 편집", - "image_omnigen2_t2i": "OmniGen2 텍스트 → 이미지", - "sd3_5_large_blur": "SD3.5 Large 블러", - "sd3_5_large_canny_controlnet_example": "SD3.5 Large 캐니 컨트롤넷", - "sd3_5_large_depth": "SD3.5 Large 깊이", + "image_omnigen2_t2i": "OmniGen2 텍스트 투 이미지", + "sd3_5_large_blur": "SD3.5 대형 블러", + "sd3_5_large_canny_controlnet_example": "SD3.5 대형 캐니 컨트롤넷", + "sd3_5_large_depth": "SD3.5 대형 깊이", "sd3_5_simple_example": "SD3.5 간단 예제", - "sdxl_refiner_prompt_example": "SDXL Refiner 프롬프트", + "sdxl_refiner_prompt_example": "SDXL 리파이너 프롬프트", "sdxl_revision_text_prompts": "SDXL Revision 텍스트 프롬프트", "sdxl_revision_zero_positive": "SDXL Revision Zero Positive", "sdxl_simple_example": "SDXL 간단 예제", "sdxlturbo_example": "SDXL 터보" }, "Image API": { - "api_bfl_flux_1_kontext_max_image": "BFL FLUX.1 Kontext 맥스", - "api_bfl_flux_1_kontext_multiple_images_input": "BFL FLUX.1 Kontext 다중 이미지 입력", - "api_bfl_flux_1_kontext_pro_image": "BFL FLUX.1 Kontext 프로", - "api_bfl_flux_pro_t2i": "BFL FLUX[Pro]: 텍스트 → 이미지", - "api_ideogram_v3_t2i": "Ideogram V3: 텍스트 → 이미지", - "api_luma_photon_i2i": "Luma Photon: 이미지 → 이미지", + "api_bfl_flux_1_kontext_max_image": "BFL Flux.1 Kontext 맥스", + "api_bfl_flux_1_kontext_multiple_images_input": "BFL Flux.1 Kontext 다중 이미지 입력", + "api_bfl_flux_1_kontext_pro_image": "BFL Flux.1 Kontext 프로", + "api_bfl_flux_pro_t2i": "BFL Flux[Pro]: 텍스트 투 이미지", + "api_ideogram_v3_t2i": "Ideogram V3: 텍스트 투 이미지", + "api_luma_photon_i2i": "Luma Photon: 이미지 투 이미지", "api_luma_photon_style_ref": "Luma Photon: 스타일 참조", "api_openai_dall_e_2_inpaint": "OpenAI: Dall-E 2 인페인트", - "api_openai_dall_e_2_t2i": "OpenAI: Dall-E 2 텍스트 → 이미지", - "api_openai_dall_e_3_t2i": "OpenAI: Dall-E 3 텍스트 → 이미지", - "api_openai_image_1_i2i": "OpenAI: GPT-Image-1 이미지 → 이미지", + "api_openai_dall_e_2_t2i": "OpenAI: Dall-E 2 텍스트 투 이미지", + "api_openai_dall_e_3_t2i": "OpenAI: Dall-E 3 텍스트 투 이미지", + "api_openai_image_1_i2i": "OpenAI: GPT-Image-1 이미지 투 이미지", "api_openai_image_1_inpaint": "OpenAI: GPT-Image-1 인페인트", "api_openai_image_1_multi_inputs": "OpenAI: GPT-Image-1 멀티 입력", - "api_openai_image_1_t2i": "OpenAI: GPT-Image-1 텍스트 → 이미지", + "api_openai_image_1_t2i": "OpenAI: GPT-Image-1 텍스트 투 이미지", "api_recraft_image_gen_with_color_control": "Recraft: 색상 제어 이미지 생성", "api_recraft_image_gen_with_style_control": "Recraft: 스타일 제어 이미지 생성", "api_recraft_vector_gen": "Recraft: 벡터 생성", - "api_runway_reference_to_image": "Runway: 참조 → 이미지", - "api_runway_text_to_image": "Runway: 텍스트 → 이미지", - "api_stability_ai_i2i": "Stability AI: 이미지 → 이미지", - "api_stability_ai_sd3_5_i2i": "Stability AI: SD3.5 이미지 → 이미지", - "api_stability_ai_sd3_5_t2i": "Stability AI: SD3.5 텍스트 → 이미지", - "api_stability_ai_stable_image_ultra_t2i": "Stability AI: Stable Image Ultra 텍스트 → 이미지" + "api_runway_reference_to_image": "Runway: 참조 투 이미지", + "api_runway_text_to_image": "Runway: 텍스트 투 이미지", + "api_stability_ai_i2i": "Stability AI: 이미지 투 이미지", + "api_stability_ai_sd3_5_i2i": "Stability AI: SD3.5 이미지 투 이미지", + "api_stability_ai_sd3_5_t2i": "Stability AI: SD3.5 텍스트 투 이미지", + "api_stability_ai_stable_image_ultra_t2i": "Stability AI: Stable Image Ultra 텍스트 투 이미지" }, "LLM API": { "api_google_gemini": "Google Gemini: 채팅", @@ -1402,24 +1324,24 @@ }, "Upscaling": { "esrgan_example": "ESRGAN", - "hiresfix_esrgan_workflow": "HiresFix ESRGAN 워크플로", - "hiresfix_latent_workflow": "이미지 확대", - "latent_upscale_different_prompt_model": "잠재 이미지 확대 다른 프롬프트 모델" + "hiresfix_esrgan_workflow": "HiresFix ESRGAN 워크플로우", + "hiresfix_latent_workflow": "업스케일", + "latent_upscale_different_prompt_model": "Latent 업스케일 다른 프롬프트 모델" }, "Video": { - "hunyuan_video_text_to_video": "Hunyuan 비디오 텍스트 → 비디오", - "image_to_video": "SVD 이미지 → 비디오", - "image_to_video_wan": "Wan 2.1 이미지 → 비디오", - "ltxv_image_to_video": "LTXV 이미지 → 비디오", - "ltxv_text_to_video": "LTXV 텍스트 → 비디오", - "mochi_text_to_video_example": "Mochi 텍스트 → 비디오", - "text_to_video_wan": "Wan 2.1 텍스트 → 비디오", - "txt_to_image_to_video": "SVD 텍스트 → 이미지 → 비디오", + "hunyuan_video_text_to_video": "Hunyuan 비디오 텍스트 투 비디오", + "image_to_video": "SVD 이미지 투 비디오", + "image_to_video_wan": "Wan 2.1 이미지 투 비디오", + "ltxv_image_to_video": "LTXV 이미지 투 비디오", + "ltxv_text_to_video": "LTXV 텍스트 투 비디오", + "mochi_text_to_video_example": "Mochi 텍스트 투 비디오", + "text_to_video_wan": "Wan 2.1 텍스트 투 비디오", + "txt_to_image_to_video": "SVD 텍스트 투 이미지 투 비디오", "video_cosmos_predict2_2B_video2world_480p_16fps": "Cosmos Predict2 2B Video2World 480p 16fps", "video_wan2_1_fun_camera_v1_1_14B": "Wan 2.1 Fun Camera 14B", "video_wan2_1_fun_camera_v1_1_1_3B": "Wan 2.1 Fun Camera 1.3B", - "video_wan_vace_14B_ref2v": "Wan VACE 참조 → 비디오", - "video_wan_vace_14B_t2v": "Wan VACE 텍스트 → 비디오", + "video_wan_vace_14B_ref2v": "Wan VACE 참조 투 비디오", + "video_wan_vace_14B_t2v": "Wan VACE 텍스트 투 비디오", "video_wan_vace_14B_v2v": "Wan VACE 컨트롤 비디오", "video_wan_vace_flf2v": "Wan VACE 첫-마지막 프레임", "video_wan_vace_inpainting": "Wan VACE 인페인팅", @@ -1429,24 +1351,24 @@ "wan2_1_fun_inp": "Wan 2.1 인페인팅" }, "Video API": { - "api_hailuo_minimax_i2v": "MiniMax: 이미지 → 비디오", - "api_hailuo_minimax_t2v": "MiniMax: 텍스트 → 비디오", + "api_hailuo_minimax_i2v": "MiniMax: 이미지 투 비디오", + "api_hailuo_minimax_t2v": "MiniMax: 텍스트 투 비디오", "api_kling_effects": "Kling: 비디오 효과", "api_kling_flf": "Kling: FLF2V", - "api_kling_i2v": "Kling: 이미지 → 비디오", - "api_luma_i2v": "Luma: 이미지 → 비디오", - "api_luma_t2v": "Luma: 텍스트 → 비디오", - "api_moonvalley_image_to_video": "Moonvalley: 이미지 → 비디오", - "api_moonvalley_text_to_video": "Moonvalley: 텍스트 → 비디오", - "api_pika_i2v": "Pika: 이미지 → 비디오", - "api_pika_scene": "Pika 장면: 이미지 → 비디오", - "api_pixverse_i2v": "PixVerse: 이미지 → 비디오", - "api_pixverse_t2v": "PixVerse: 텍스트 → 비디오", - "api_pixverse_template_i2v": "PixVerse 템플릿: 이미지 → 비디오", - "api_runway_first_last_frame": "Runway: 첫-마지막 프레임 → 비디오", - "api_runway_gen3a_turbo_image_to_video": "Runway: Gen3a Turbo 이미지 → 비디오", - "api_runway_gen4_turo_image_to_video": "Runway: Gen4 Turbo 이미지 → 비디오", - "api_veo2_i2v": "Veo2: 이미지 → 비디오" + "api_kling_i2v": "Kling: 이미지 투 비디오", + "api_luma_i2v": "Luma: 이미지 투 비디오", + "api_luma_t2v": "Luma: 텍스트 투 비디오", + "api_moonvalley_image_to_video": "Moonvalley: 이미지 투 비디오", + "api_moonvalley_text_to_video": "Moonvalley: 텍스트 투 비디오", + "api_pika_i2v": "Pika: 이미지 투 비디오", + "api_pika_scene": "Pika 장면: 이미지 투 비디오", + "api_pixverse_i2v": "PixVerse: 이미지 투 비디오", + "api_pixverse_t2v": "PixVerse: 텍스트 투 비디오", + "api_pixverse_template_i2v": "PixVerse 템플릿: 이미지 투 비디오", + "api_runway_first_last_frame": "Runway: 첫-마지막 프레임 투 비디오", + "api_runway_gen3a_turbo_image_to_video": "Runway: Gen3a Turbo 이미지 투 비디오", + "api_runway_gen4_turo_image_to_video": "Runway: Gen4 Turbo 이미지 투 비디오", + "api_veo2_i2v": "Veo2: 이미지 투 비디오" } }, "templateDescription": { @@ -1461,7 +1383,7 @@ "api_rodin_multiview_to_model": "Rodin의 다각도 재구성으로 종합적인 3D 모델을 만듭니다.", "api_tripo_image_to_model": "Tripo 엔진으로 2D 이미지에서 전문가용 3D 에셋을 생성합니다.", "api_tripo_multiview_to_model": "Tripo의 고급 스캐너로 여러 각도에서 3D 모델을 만듭니다.", - "api_tripo_text_to_model": "Tripo의 텍스트 기반 모델링으로 설명에서 3D 객체를 만듭니다." + "api_tripo_text_to_model": "Tripo의 텍스트 기반 모델링으로 설명에서 3D 오브젝트를 만듭니다." }, "Area Composition": { "area_composition": "정의된 영역으로 구성을 제어하여 이미지를 생성합니다.", @@ -1484,49 +1406,49 @@ "lora_multiple": "여러 LoRA 모델을 결합하여 이미지를 생성합니다." }, "ControlNet": { - "2_pass_pose_worship": "컨트롤넷으로 포즈 참조를 활용해 이미지를 생성합니다.", - "controlnet_example": "컨트롤넷으로 스크리블 참조 이미지를 활용해 이미지를 생성합니다.", - "depth_controlnet": "컨트롤넷으로 깊이 정보를 활용해 이미지를 생성합니다.", + "2_pass_pose_worship": "ControlNet으로 포즈 참조를 활용해 이미지를 생성합니다.", + "controlnet_example": "ControlNet으로 스크리블 참조 이미지를 활용해 이미지를 생성합니다.", + "depth_controlnet": "ControlNet으로 깊이 정보를 활용해 이미지를 생성합니다.", "depth_t2i_adapter": "T2I 어댑터로 깊이 정보를 활용해 이미지를 생성합니다.", - "mixing_controlnets": "여러 컨트롤넷 모델을 결합해 이미지를 생성합니다." + "mixing_controlnets": "여러 ControlNet 모델을 결합해 이미지를 생성합니다." }, "Flux": { - "flux_canny_model_example": "FLUX 캐니 컨트롤넷으로 에지 감지에 따라 이미지를 생성합니다.", - "flux_depth_lora_example": "FLUX LoRA로 깊이 정보를 활용해 이미지를 생성합니다.", - "flux_dev_checkpoint_example": "FLUX Dev fp8 양자화 버전으로 이미지를 생성합니다. VRAM이 제한된 장치에 적합하며, 모델 파일 하나만 필요하지만 화질은 전체 버전보다 약간 낮습니다.", - "flux_dev_full_text_to_image": "FLUX Dev 전체 버전으로 고품질 이미지를 생성합니다. 더 많은 VRAM과 여러 모델 파일이 필요하지만, 최고의 프롬프트 반영력과 화질을 제공합니다.", - "flux_fill_inpaint_example": "FLUX 인페인팅으로 이미지의 누락된 부분을 채웁니다.", - "flux_fill_outpaint_example": "FFLUXlux 아웃페인팅으로 이미지를 경계 너머로 확장합니다.", - "flux_kontext_dev_basic": "FLUX Kontext의 전체 노드 표시로 이미지를 편집합니다. 워크플로 학습에 적합합니다.", - "flux_kontext_dev_grouped": "노드가 그룹화된 FLUX Kontext의 간소화 버전으로 작업 공간이 더 깔끔합니다.", - "flux_redux_model_example": "FLUX Redux로 참조 이미지의 스타일을 전송하여 이미지를 생성합니다.", - "flux_schnell": "FLUX Schnell fp8 양자화 버전으로 이미지를 빠르게 생성합니다. 저사양 하드웨어에 이상적이며, 4단계만으로 이미지를 생성할 수 있습니다.", - "flux_schnell_full_text_to_image": "FLUX Schnell Full 버전을 이용해 이미지를 빠르게 생성합니다. Apache2.0 라이선스를 사용하며, 4단계만으로 좋은 화질을 유지합니다." + "flux_canny_model_example": "Flux Canny로 에지 감지에 따라 이미지를 생성합니다.", + "flux_depth_lora_example": "Flux LoRA로 깊이 정보를 활용해 이미지를 생성합니다.", + "flux_dev_checkpoint_example": "Flux Dev fp8 양자화 버전으로 이미지를 생성합니다. VRAM이 제한된 장치에 적합하며, 모델 파일 하나만 필요하지만 화질은 전체 버전보다 약간 낮습니다.", + "flux_dev_full_text_to_image": "Flux Dev 전체 버전으로 고품질 이미지를 생성합니다. 더 많은 VRAM과 여러 모델 파일이 필요하지만, 최고의 프롬프트 반영력과 화질을 제공합니다.", + "flux_fill_inpaint_example": "Flux 인페인팅으로 이미지의 누락된 부분을 채웁니다.", + "flux_fill_outpaint_example": "Flux 아웃페인팅으로 이미지를 경계 너머로 확장합니다.", + "flux_kontext_dev_basic": "Flux Kontext의 전체 노드 표시로 이미지를 편집합니다. 워크플로우 학습에 적합합니다.", + "flux_kontext_dev_grouped": "노드가 그룹화된 Flux Kontext의 간소화 버전으로 작업 공간이 더 깔끔합니다.", + "flux_redux_model_example": "Flux Redux로 참조 이미지의 스타일을 전송하여 이미지를 생성합니다.", + "flux_schnell": "Flux Schnell fp8 양자화 버전으로 이미지를 빠르게 생성합니다. 저사양 하드웨어에 이상적이며, 4단계만으로 이미지를 생성할 수 있습니다.", + "flux_schnell_full_text_to_image": "Flux Schnell 전체 버전으로 이미지를 빠르게 생성합니다. Apache2.0 라이선스를 사용하며, 4단계만으로 좋은 화질을 유지합니다." }, "Image": { "hidream_e1_full": "HiDream E1 - 전문적인 자연어 이미지 편집 모델로 이미지를 편집합니다.", "hidream_i1_dev": "HiDream I1 Dev - 28 스텝의 균형 잡힌 버전으로, 중간급 하드웨어에 적합합니다.", "hidream_i1_fast": "HiDream I1 Fast - 16 스텝의 경량 버전으로, 저사양 하드웨어에서 빠른 미리보기에 적합합니다.", "hidream_i1_full": "HiDream I1 Full - 50 스텝의 완전 버전으로, 최고의 품질을 제공합니다.", - "image_chroma_text_to_image": "Chroma는 FLUX에서 수정된 모델로, 아키텍처에 일부 변화가 있습니다.", + "image_chroma_text_to_image": "Chroma는 flux에서 수정된 모델로, 아키텍처에 일부 변화가 있습니다.", "image_cosmos_predict2_2B_t2i": "Cosmos-Predict2 2B T2I로 물리적으로 정확하고 고해상도, 디테일이 풍부한 이미지를 생성합니다.", "image_lotus_depth_v1_1": "Lotus Depth로 고효율 단안 깊이 추정 및 디테일 보존이 뛰어난 zero-shot 이미지를 생성합니다.", "image_omnigen2_image_edit": "OmniGen2의 고급 이미지 편집 기능과 텍스트 렌더링 지원으로 자연어 지시로 이미지를 편집합니다.", "image_omnigen2_t2i": "OmniGen2의 통합 7B 멀티모달 모델과 듀얼 패스 아키텍처로 텍스트 프롬프트에서 고품질 이미지를 생성합니다.", "sd3_5_large_blur": "SD 3.5로 흐릿한 참조 이미지를 활용해 이미지를 생성합니다.", - "sd3_5_large_canny_controlnet_example": "SD 3.5 캐니 컨트롤넷으로 에지 감지에 따라 이미지를 생성합니다.", + "sd3_5_large_canny_controlnet_example": "SD 3.5 Canny ControlNet으로 에지 감지에 따라 이미지를 생성합니다.", "sd3_5_large_depth": "SD 3.5로 깊이 정보를 활용해 이미지를 생성합니다.", "sd3_5_simple_example": "SD 3.5로 이미지를 생성합니다.", - "sdxl_refiner_prompt_example": "SDXL Refiner 모델로 이미지를 향상시킵니다.", + "sdxl_refiner_prompt_example": "SDXL 리파이너 모델로 이미지를 향상시킵니다.", "sdxl_revision_text_prompts": "SDXL Revision으로 참조 이미지의 개념을 전송하여 이미지를 생성합니다.", "sdxl_revision_zero_positive": "SDXL Revision으로 텍스트 프롬프트와 참조 이미지를 함께 사용해 이미지를 생성합니다.", "sdxl_simple_example": "SDXL로 고품질 이미지를 생성합니다.", "sdxlturbo_example": "SDXL Turbo로 한 번에 이미지를 생성합니다." }, "Image API": { - "api_bfl_flux_1_kontext_max_image": "FLUX.1 Kontext 맥스 이미지로 이미지를 편집합니다.", - "api_bfl_flux_1_kontext_multiple_images_input": "여러 이미지를 입력하고 FLUX.1 Kontext로 편집합니다.", - "api_bfl_flux_1_kontext_pro_image": "FLUX.1 Kontext 프로 이미지로 이미지를 편집합니다.", + "api_bfl_flux_1_kontext_max_image": "Flux.1 Kontext 맥스 이미지로 이미지를 편집합니다.", + "api_bfl_flux_1_kontext_multiple_images_input": "여러 이미지를 입력하고 Flux.1 Kontext로 편집합니다.", + "api_bfl_flux_1_kontext_pro_image": "Flux.1 Kontext 프로 이미지로 이미지를 편집합니다.", "api_bfl_flux_pro_t2i": "FLUX.1 Pro로 뛰어난 프롬프트 반영과 시각적 품질로 이미지를 생성합니다.", "api_ideogram_v3_t2i": "Ideogram V3로 뛰어난 프롬프트 일치, 포토리얼리즘, 텍스트 렌더링으로 전문가 수준의 이미지를 생성합니다.", "api_luma_photon_i2i": "이미지와 프롬프트를 조합하여 이미지 생성을 가이드합니다.", @@ -1544,9 +1466,9 @@ "api_runway_reference_to_image": "Runway의 AI로 참조 스타일과 구성을 기반으로 새 이미지를 생성합니다.", "api_runway_text_to_image": "Runway의 AI 모델로 텍스트 프롬프트에서 고품질 이미지를 생성합니다.", "api_stability_ai_i2i": "Stability AI로 고품질 이미지 변환 및 스타일 전환을 지원합니다.", - "api_stability_ai_sd3_5_i2i": "1M 픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다.", - "api_stability_ai_sd3_5_t2i": "1M 픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다.", - "api_stability_ai_stable_image_ultra_t2i": "1M 픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다." + "api_stability_ai_sd3_5_i2i": "1메가픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다.", + "api_stability_ai_sd3_5_t2i": "1메가픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다.", + "api_stability_ai_stable_image_ultra_t2i": "1메가픽셀 해상도에서 전문가용 고품질 이미지를 생성합니다. 프롬프트 반영이 우수합니다." }, "LLM API": { "api_google_gemini": "Google Gemini의 멀티모달 AI와 추론 능력을 경험하세요.", @@ -1554,9 +1476,9 @@ }, "Upscaling": { "esrgan_example": "ESRGAN 모델로 이미지 품질을 향상합니다.", - "hiresfix_esrgan_workflow": "중간 생성 단계에서 ESRGAN 모델로 이미지를 확대합니다.", - "hiresfix_latent_workflow": "잠재 이미지의 확대 방식으로 이미지 품질을 향상합니다.", - "latent_upscale_different_prompt_model": "여러 번의 생성 패스에서 프롬프트를 변경하며 이미지를 확대합니다." + "hiresfix_esrgan_workflow": "중간 생성 단계에서 ESRGAN 모델로 업스케일합니다.", + "hiresfix_latent_workflow": "Latent 공간에서 이미지 품질을 향상합니다.", + "latent_upscale_different_prompt_model": "여러 번의 생성 패스에서 프롬프트를 변경하며 업스케일합니다." }, "Video": { "hunyuan_video_text_to_video": "Hunyuan 모델로 텍스트 프롬프트에서 비디오를 생성합니다.", @@ -1577,7 +1499,7 @@ "video_wan_vace_inpainting": "특정 영역을 편집하면서 주변 내용을 보존하는 비디오를 생성합니다. 객체 제거 또는 교체에 적합합니다.", "video_wan_vace_outpainting": "Wan VACE 아웃페인팅으로 비디오 크기를 확장하여 비디오를 생성합니다.", "wan2_1_flf2v_720_f16": "Wan 2.1 FLF2V로 첫 프레임과 마지막 프레임을 제어하여 비디오를 생성합니다.", - "wan2_1_fun_control": "Wan 2.1 컨트롤넷으로 포즈, 깊이, 에지 제어로 적용해 비디오를 생성합니다.", + "wan2_1_fun_control": "Wan 2.1 ControlNet으로 포즈, 깊이, 에지 제어로 비디오를 생성합니다.", "wan2_1_fun_inp": "Wan 2.1 인페인팅으로 시작 및 종료 프레임에서 비디오를 생성합니다." }, "Video API": { @@ -1619,10 +1541,9 @@ "failedToExportModel": "{format} 형식으로 모델 내보내기에 실패했습니다", "failedToFetchBalance": "잔액을 가져오지 못했습니다: {error}", "failedToFetchLogs": "서버 로그를 가져오는 데 실패했습니다", - "failedToInitializeLoad3dViewer": "3D 뷰어 초기화에 실패했습니다", "failedToInitiateCreditPurchase": "크레딧 구매를 시작하지 못했습니다: {error}", "failedToPurchaseCredits": "크레딧 구매에 실패했습니다: {error}", - "fileLoadError": "{fileName}에서 워크플로를 찾을 수 없습니다", + "fileLoadError": "{fileName}에서 워크플로우를 찾을 수 없습니다", "fileUploadFailed": "파일 업로드에 실패했습니다", "interrupted": "실행이 중단되었습니다", "migrateToLitegraphReroute": "향후 버전에서는 Reroute 노드가 제거됩니다. LiteGraph 에서 자체 제공하는 경유점으로 변환하려면 클릭하세요.", @@ -1675,13 +1596,6 @@ "prefix": "{prefix}(으)로 시작해야 합니다", "required": "필수" }, - "versionMismatchWarning": { - "dismiss": "닫기", - "frontendNewer": "프론트엔드 버전 {frontendVersion}이(가) 백엔드 버전 {backendVersion}과(와) 호환되지 않을 수 있습니다.", - "frontendOutdated": "프론트엔드 버전 {frontendVersion}이(가) 오래되었습니다. 백엔드는 {requiredVersion} 이상 버전을 필요로 합니다.", - "title": "버전 호환성 경고", - "updateFrontend": "프론트엔드 업데이트" - }, "welcome": { "getStarted": "시작하기", "title": "ComfyUI에 오신 것을 환영합니다" diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index 86fc88f176..4e55cb7af3 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "Загрузить стандартный рабочий процесс" }, - "Comfy_Manager_CustomNodesManager": { - "label": "Менеджер Пользовательских Узлов" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "Пользовательские узлы (Бета)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "Пользовательские узлы (Устаревшие)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "Меню менеджера (Устаревшее)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Переключить диалоговое окно прогресса" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "Открыть редактор масок для выбранной ноды" }, + "Comfy_Memory_UnloadModels": { + "label": "Выгрузить модели" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "Выгрузить модели и кэш выполнения" + }, "Comfy_NewBlankWorkflow": { "label": "Новый пустой рабочий процесс" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index c7be388c27..1c94d2b535 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -82,12 +82,6 @@ "title": "Создать аккаунт" } }, - "breadcrumbsMenu": { - "clearWorkflow": "Очистить рабочий процесс", - "deleteWorkflow": "Удалить рабочий процесс", - "duplicate": "Дублировать", - "enterNewName": "Введите новое имя" - }, "chatHistory": { "cancelEdit": "Отмена", "cancelEditTooltip": "Отменить редактирование", @@ -278,6 +272,7 @@ "color": "Цвет", "comingSoon": "Скоро будет", "command": "Команда", + "commandProhibited": "Команда {command} запрещена. Свяжитесь с администратором для получения дополнительной информации.", "community": "Сообщество", "completed": "Завершено", "confirm": "Подтвердить", @@ -298,9 +293,7 @@ "devices": "Устройства", "disableAll": "Отключить все", "disabling": "Отключение", - "dismiss": "Закрыть", "download": "Скачать", - "duplicate": "Дублировать", "edit": "Редактировать", "empty": "Пусто", "enableAll": "Включить все", @@ -314,8 +307,6 @@ "filter": "Фильтр", "findIssues": "Найти проблемы", "firstTimeUIMessage": "Вы впервые используете новый интерфейс. Выберите \"Меню > Использовать новое меню > Отключено\", чтобы восстановить старый интерфейс.", - "frontendNewer": "Версия интерфейса {frontendVersion} может быть несовместима с версией сервера {backendVersion}.", - "frontendOutdated": "Версия интерфейса {frontendVersion} устарела. Требуется версия не ниже {requiredVersion} для работы с сервером.", "goToNode": "Перейти к ноде", "help": "Помощь", "icon": "Иконка", @@ -339,20 +330,17 @@ "loadingPanel": "Загрузка панели {panel}...", "login": "Вход", "logs": "Логи", - "micPermissionDenied": "Доступ к микрофону запрещён", "migrate": "Мигрировать", "missing": "Отсутствует", "name": "Имя", "newFolder": "Новая папка", "next": "Далее", "no": "Нет", - "noAudioRecorded": "Аудио не записано", "noResultsFound": "Результатов не найдено", "noTasksFound": "Задачи не найдены", "noTasksFoundMessage": "В очереди нет задач.", "noWorkflowsFound": "Рабочие процессы не найдены.", "nodes": "Узлы", - "nodesRunning": "запущено узлов", "ok": "ОК", "openNewIssue": "Открыть новую проблему", "overwrite": "Перезаписать", @@ -386,9 +374,7 @@ "showReport": "Показать отчёт", "sort": "Сортировать", "source": "Источник", - "startRecording": "Начать запись", "status": "Статус", - "stopRecording": "Остановить запись", "success": "Успех", "systemInfo": "Информация о системе", "terminal": "Терминал", @@ -397,14 +383,11 @@ "unknownError": "Неизвестная ошибка", "update": "Обновить", "updateAvailable": "Доступно обновление", - "updateFrontend": "Обновить интерфейс", "updated": "Обновлено", "updating": "Обновление", "upload": "Загрузить", "usageHint": "Подсказка по использованию", "user": "Пользователь", - "versionMismatchWarning": "Предупреждение о несовместимости версий", - "versionMismatchWarningMessage": "{warning}: {detail} Посетите https://docs.comfy.org/installation/update_comfyui#common-update-issues для инструкций по обновлению.", "videoFailedToLoad": "Не удалось загрузить видео", "workflow": "Рабочий процесс" }, @@ -414,7 +397,6 @@ "resetView": "Сбросить вид", "selectMode": "Выбрать режим", "toggleLinkVisibility": "Переключить видимость ссылок", - "toggleMinimap": "Показать/скрыть миникарту", "zoomIn": "Увеличить", "zoomOut": "Уменьшить" }, @@ -573,10 +555,6 @@ "applyingTexture": "Применение текстуры...", "backgroundColor": "Цвет фона", "camera": "Камера", - "cameraType": { - "orthographic": "Ортографическая", - "perspective": "Перспективная" - }, "clearRecording": "Очистить запись", "edgeThreshold": "Пороговое значение края", "export": "Экспорт", @@ -597,7 +575,6 @@ "wireframe": "Каркас" }, "model": "Модель", - "openIn3DViewer": "Открыть в 3D просмотрщике", "previewOutput": "Предварительный просмотр", "removeBackgroundImage": "Удалить фоновое изображение", "resizeNodeMatchOutput": "Изменить размер узла под вывод", @@ -608,22 +585,8 @@ "switchCamera": "Переключить камеру", "switchingMaterialMode": "Переключение режима материала...", "upDirection": "Направление Вверх", - "upDirections": { - "original": "Оригинал" - }, "uploadBackgroundImage": "Загрузить фоновое изображение", - "uploadTexture": "Загрузить текстуру", - "viewer": { - "apply": "Применить", - "cameraSettings": "Настройки камеры", - "cameraType": "Тип камеры", - "cancel": "Отмена", - "exportSettings": "Настройки экспорта", - "lightSettings": "Настройки освещения", - "modelSettings": "Настройки модели", - "sceneSettings": "Настройки сцены", - "title": "3D Просмотрщик (Бета)" - } + "uploadTexture": "Загрузить текстуру" }, "loadWorkflowWarning": { "coreNodesFromVersion": "Требуется ComfyUI {version}:", @@ -670,6 +633,7 @@ "installationQueue": "Очередь установки", "lastUpdated": "Последнее обновление", "latestVersion": "Последняя", + "legacyMenuNotAvailable": "Устаревшее меню менеджера недоступно в этой версии ComfyUI. Пожалуйста, используйте новое меню менеджера.", "license": "Лицензия", "loadingVersions": "Загрузка версий...", "nightlyVersion": "Ночная", @@ -739,28 +703,21 @@ "batchCountTooltip": "Количество раз, когда генерация рабочего процесса должна быть помещена в очередь", "clear": "Очистить рабочий процесс", "clipspace": "Открыть Clipspace", - "dark": "Тёмная", "disabled": "Отключено", "disabledTooltip": "Рабочий процесс не будет автоматически помещён в очередь", "execute": "Выполнить", - "help": "Справка", "hideMenu": "Скрыть меню", "instant": "Мгновенно", "instantTooltip": "Рабочий процесс будет помещён в очередь сразу же после завершения генерации", "interrupt": "Отменить текущее выполнение", - "light": "Светлая", - "manageExtensions": "Управление расширениями", "onChange": "При изменении", "onChangeTooltip": "Рабочий процесс будет поставлен в очередь после внесения изменений", - "queue": "Панель очереди", "refresh": "Обновить определения нод", "resetView": "Сбросить вид холста", "run": "Запустить", "runWorkflow": "Запустить рабочий процесс (Shift для очереди в начале)", "runWorkflowFront": "Запустить рабочий процесс (Очередь в начале)", - "settings": "Настройки", "showMenu": "Показать меню", - "theme": "Тема", "toggleBottomPanel": "Переключить нижнюю панель" }, "menuLabels": { @@ -768,7 +725,7 @@ "Bottom Panel": "Нижняя панель", "Browse Templates": "Просмотреть шаблоны", "Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные ноды", - "Canvas Performance": "Производительность холста", + "Canvas Toggle Link Visibility": "Переключение видимости ссылки на холст", "Canvas Toggle Lock": "Переключение блокировки холста", "Check for Updates": "Проверить наличие обновлений", "Clear Pending Tasks": "Очистить ожидающие задачи", @@ -783,39 +740,32 @@ "Contact Support": "Связаться с поддержкой", "Convert Selection to Subgraph": "Преобразовать выделенное в подграф", "Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду", - "Decrease Brush Size in MaskEditor": "Уменьшить размер кисти в MaskEditor", + "Custom Nodes (Beta)": "Пользовательские узлы (Бета)", + "Custom Nodes (Legacy)": "Пользовательские узлы (Устаревшие)", "Delete Selected Items": "Удалить выбранные элементы", "Desktop User Guide": "Руководство пользователя для настольных ПК", "Duplicate Current Workflow": "Дублировать текущий рабочий процесс", "Edit": "Редактировать", - "Exit Subgraph": "Выйти из подграфа", "Export": "Экспортировать", "Export (API)": "Экспорт (API)", - "File": "Файл", "Fit Group To Contents": "Подогнать группу под содержимое", - "Focus Mode": "Режим фокуса", + "Fit view to selected nodes": "Подогнать вид под выбранные ноды", "Give Feedback": "Оставить отзыв", "Group Selected Nodes": "Сгруппировать выбранные ноды", "Help": "Помощь", - "Help Center": "Центр поддержки", - "Increase Brush Size in MaskEditor": "Увеличить размер кисти в MaskEditor", "Interrupt": "Прервать", "Load Default Workflow": "Загрузить стандартный рабочий процесс", "Manage group nodes": "Управление групповыми нодами", - "Manager": "Менеджер", - "Minimap": "Мини-карта", - "Model Library": "Библиотека моделей", "Move Selected Nodes Down": "Переместить выбранные узлы вниз", "Move Selected Nodes Left": "Переместить выбранные узлы влево", "Move Selected Nodes Right": "Переместить выбранные узлы вправо", "Move Selected Nodes Up": "Переместить выбранные узлы вверх", + "Manager": "Менеджер", + "Manager Menu (Legacy)": "Меню менеджера (Устаревшее)", "Mute/Unmute Selected Nodes": "Отключить/включить звук для выбранных нод", "New": "Новый", "Next Opened Workflow": "Следующий открытый рабочий процесс", - "Node Library": "Библиотека узлов", - "Node Links": "Связи узлов", "Open": "Открыть", - "Open 3D Viewer (Beta) for Selected Node": "Открыть 3D-просмотрщик (бета) для выбранного узла", "Open Custom Nodes Folder": "Открыть папку пользовательских нод", "Open DevTools": "Открыть инструменты разработчика", "Open Inputs Folder": "Открыть папку входных данных", @@ -828,7 +778,6 @@ "Pin/Unpin Selected Items": "Закрепить/открепить выбранные элементы", "Pin/Unpin Selected Nodes": "Закрепить/открепить выбранные ноды", "Previous Opened Workflow": "Предыдущий открытый рабочий процесс", - "Queue Panel": "Панель очереди", "Queue Prompt": "Запрос в очереди", "Queue Prompt (Front)": "Запрос в очереди (спереди)", "Queue Selected Output Nodes": "Добавить выбранные выходные узлы в очередь", @@ -855,18 +804,11 @@ "Toggle the Custom Nodes Manager Progress Bar": "Переключить индикатор выполнения менеджера пользовательских узлов", "Undo": "Отменить", "Ungroup selected group nodes": "Разгруппировать выбранные групповые ноды", - "Unpack the selected Subgraph": "Распаковать выбранный подграф", - "Workflows": "Рабочие процессы", + "Unload Models": "Выгрузить модели", + "Unload Models and Execution Cache": "Выгрузить модели и кэш выполнения", + "Workflow": "Рабочий процесс", "Zoom In": "Увеличить", - "Zoom Out": "Уменьшить", - "Zoom to fit": "Масштабировать по размеру" - }, - "minimap": { - "nodeColors": "Цвета узлов", - "renderBypassState": "Отображать состояние обхода", - "renderErrorState": "Отображать состояние ошибки", - "showGroups": "Показать фреймы/группы", - "showLinks": "Показать связи" + "Zoom Out": "Уменьшить" }, "missingModelsDialog": { "doNotAskAgain": "Больше не показывать это", @@ -1134,7 +1076,6 @@ }, "settingsCategories": { "3D": "3D", - "3DViewer": "3D-просмотрщик", "API Nodes": "API-узлы", "About": "О программе", "Appearance": "Внешний вид", @@ -1186,31 +1127,10 @@ "Window": "Окно", "Workflow": "Рабочий процесс" }, - "shortcuts": { - "essentials": "Основные", - "keyboardShortcuts": "Горячие клавиши", - "manageShortcuts": "Управление горячими клавишами", - "noKeybinding": "Нет сочетания клавиш", - "subcategories": { - "node": "Узел", - "panelControls": "Управление панелью", - "queue": "Очередь", - "view": "Просмотр", - "workflow": "Рабочий процесс" - }, - "viewControls": "Управление просмотром" - }, "sideToolbar": { "browseTemplates": "Просмотреть примеры шаблонов", "downloads": "Загрузки", "helpCenter": "Центр поддержки", - "labels": { - "models": "Модели", - "nodes": "Узлы", - "queue": "Очередь", - "templates": "Шаблоны", - "workflows": "Воркфлоу" - }, "logout": "Выйти", "modelLibrary": "Библиотека моделей", "newBlankWorkflow": "Создайте новый пустой рабочий процесс", @@ -1248,7 +1168,7 @@ }, "showFlatList": "Показать плоский список" }, - "templates": "Шаблоны", + "themeToggle": "Переключить тему", "workflowTab": { "confirmDelete": "Вы уверены, что хотите удалить этот рабочий процесс?", "confirmDeleteTitle": "Удалить рабочий процесс?", @@ -1295,8 +1215,6 @@ "Video": "Видео", "Video API": "Video API" }, - "loadingMore": "Загрузка дополнительных шаблонов...", - "searchPlaceholder": "Поиск шаблонов...", "template": { "3D": { "3d_hunyuan3d_image_to_model": "Hunyuan3D", @@ -1619,7 +1537,6 @@ "failedToExportModel": "Не удалось экспортировать модель как {format}", "failedToFetchBalance": "Не удалось получить баланс: {error}", "failedToFetchLogs": "Не удалось получить серверные логи", - "failedToInitializeLoad3dViewer": "Не удалось инициализировать 3D просмотрщик", "failedToInitiateCreditPurchase": "Не удалось начать покупку кредитов: {error}", "failedToPurchaseCredits": "Не удалось купить кредиты: {error}", "fileLoadError": "Не удалось найти рабочий процесс в {fileName}", @@ -1675,13 +1592,6 @@ "prefix": "Должно начинаться с {prefix}", "required": "Обязательно" }, - "versionMismatchWarning": { - "dismiss": "Закрыть", - "frontendNewer": "Версия интерфейса {frontendVersion} может быть несовместима с версией сервера {backendVersion}.", - "frontendOutdated": "Версия интерфейса {frontendVersion} устарела. Для работы с сервером требуется версия {requiredVersion} или новее.", - "title": "Предупреждение о несовместимости версий", - "updateFrontend": "Обновить интерфейс" - }, "welcome": { "getStarted": "Начать", "title": "Добро пожаловать в ComfyUI" diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index 5d319aa170..c2a5d89235 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -164,8 +164,14 @@ "Comfy_LoadDefaultWorkflow": { "label": "加载默认工作流" }, - "Comfy_Manager_CustomNodesManager": { - "label": "自定义节点管理器" + "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { + "label": "自定义节点(测试版)" + }, + "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { + "label": "自定义节点(旧版)" + }, + "Comfy_Manager_ShowLegacyManagerMenu": { + "label": "管理菜单(旧版)" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "切换进度对话框" @@ -179,6 +185,12 @@ "Comfy_MaskEditor_OpenMaskEditor": { "label": "打开选中节点的遮罩编辑器" }, + "Comfy_Memory_UnloadModels": { + "label": "卸载模型" + }, + "Comfy_Memory_UnloadModelsAndExecutionCache": { + "label": "卸载模型和执行缓存" + }, "Comfy_NewBlankWorkflow": { "label": "新建空白工作流" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 481fb7712b..efee6fec1e 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -82,12 +82,6 @@ "title": "创建一个账户" } }, - "breadcrumbsMenu": { - "clearWorkflow": "清除工作流程", - "deleteWorkflow": "删除工作流程", - "duplicate": "复制", - "enterNewName": "输入新名称" - }, "chatHistory": { "cancelEdit": "取消", "cancelEditTooltip": "取消编辑", @@ -278,6 +272,7 @@ "color": "颜色", "comingSoon": "即将推出", "command": "指令", + "commandProhibited": "命令 {command} 被禁止。请联系管理员获取更多信息。", "community": "社区", "completed": "已完成", "confirm": "确认", @@ -298,9 +293,7 @@ "devices": "设备", "disableAll": "禁用全部", "disabling": "禁用中", - "dismiss": "关闭", "download": "下载", - "duplicate": "复制", "edit": "编辑", "empty": "空", "enableAll": "启用全部", @@ -314,8 +307,6 @@ "filter": "过滤", "findIssues": "查找问题", "firstTimeUIMessage": "这是您第一次使用新界面。选择 \"菜单 > 使用新菜单 > 禁用\" 来恢复旧界面。", - "frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不相容。", - "frontendOutdated": "前端版本 {frontendVersion} 已过时。后端需要 {requiredVersion} 或更高版本。", "goToNode": "转到节点", "help": "帮助", "icon": "图标", @@ -339,20 +330,17 @@ "loadingPanel": "正在加载{panel}面板...", "login": "登录", "logs": "日志", - "micPermissionDenied": "麦克风权限被拒绝", "migrate": "迁移", "missing": "缺失", "name": "名称", "newFolder": "新文件夹", "next": "下一个", "no": "否", - "noAudioRecorded": "未录制音频", "noResultsFound": "未找到结果", "noTasksFound": "未找到任务", "noTasksFoundMessage": "队列中没有任务。", "noWorkflowsFound": "未找到工作流。", "nodes": "节点", - "nodesRunning": "节点正在运行", "ok": "确定", "openNewIssue": "打开新问题", "overwrite": "覆盖", @@ -386,9 +374,7 @@ "showReport": "显示报告", "sort": "排序", "source": "来源", - "startRecording": "开始录音", "status": "状态", - "stopRecording": "停止录音", "success": "成功", "systemInfo": "系统信息", "terminal": "终端", @@ -397,14 +383,11 @@ "unknownError": "未知错误", "update": "更新", "updateAvailable": "有更新可用", - "updateFrontend": "更新前端", "updated": "已更新", "updating": "更新中", "upload": "上传", "usageHint": "使用提示", "user": "用户", - "versionMismatchWarning": "版本相容性警告", - "versionMismatchWarningMessage": "{warning}:{detail} 请参阅 https://docs.comfy.org/installation/update_comfyui#common-update-issues 以取得更新说明。", "videoFailedToLoad": "视频加载失败", "workflow": "工作流" }, @@ -414,7 +397,6 @@ "resetView": "重置视图", "selectMode": "选择模式", "toggleLinkVisibility": "切换连线可见性", - "toggleMinimap": "切换小地图", "zoomIn": "放大", "zoomOut": "缩小" }, @@ -573,10 +555,6 @@ "applyingTexture": "应用纹理中...", "backgroundColor": "背景颜色", "camera": "摄影机", - "cameraType": { - "orthographic": "正交", - "perspective": "透视" - }, "clearRecording": "清除录制", "edgeThreshold": "边缘阈值", "export": "导出", @@ -597,7 +575,6 @@ "wireframe": "线框" }, "model": "模型", - "openIn3DViewer": "在 3D 查看器中打开", "previewOutput": "预览输出", "removeBackgroundImage": "移除背景图片", "resizeNodeMatchOutput": "调整节点以匹配输出", @@ -608,22 +585,8 @@ "switchCamera": "切换摄影机类型", "switchingMaterialMode": "切换材质模式中...", "upDirection": "上方向", - "upDirections": { - "original": "原始" - }, "uploadBackgroundImage": "上传背景图片", - "uploadTexture": "上传纹理", - "viewer": { - "apply": "应用", - "cameraSettings": "相机设置", - "cameraType": "相机类型", - "cancel": "取消", - "exportSettings": "导出设置", - "lightSettings": "灯光设置", - "modelSettings": "模型设置", - "sceneSettings": "场景设置", - "title": "3D 查看器(测试版)" - } + "uploadTexture": "上传纹理" }, "loadWorkflowWarning": { "coreNodesFromVersion": "需要 ComfyUI {version}:", @@ -670,6 +633,7 @@ "installationQueue": "安装队列", "lastUpdated": "最后更新", "latestVersion": "最新", + "legacyMenuNotAvailable": "在此版本的ComfyUI中,不提供旧版的管理器菜单。请使用新的管理器菜单。", "license": "许可证", "loadingVersions": "正在加载版本...", "nightlyVersion": "每夜", @@ -739,28 +703,21 @@ "batchCountTooltip": "工作流生成次数", "clear": "清空工作流", "clipspace": "打开剪贴板", - "dark": "深色", "disabled": "禁用", "disabledTooltip": "工作流将不会自动执行", "execute": "执行", - "help": "说明", "hideMenu": "隐藏菜单", "instant": "实时", "instantTooltip": "工作流将会在生成完成后立即执行", "interrupt": "取消当前任务", - "light": "淺色", - "manageExtensions": "管理擴充功能", "onChange": "更改时", "onChangeTooltip": "一旦进行更改,工作流将添加到执行队列", - "queue": "队列面板", "refresh": "刷新节点", "resetView": "重置视图", "run": "运行", "runWorkflow": "运行工作流程(Shift排在前面)", "runWorkflowFront": "运行工作流程(排在前面)", - "settings": "设定", "showMenu": "显示菜单", - "theme": "主题", "toggleBottomPanel": "底部面板" }, "menuLabels": { @@ -768,7 +725,7 @@ "Bottom Panel": "底部面板", "Browse Templates": "浏览模板", "Bypass/Unbypass Selected Nodes": "忽略/取消忽略选定节点", - "Canvas Performance": "画布性能", + "Canvas Toggle Link Visibility": "切换连线可见性", "Canvas Toggle Lock": "切换视图锁定", "Check for Updates": "检查更新", "Clear Pending Tasks": "清除待处理任务", @@ -783,39 +740,32 @@ "Contact Support": "联系支持", "Convert Selection to Subgraph": "将选中内容转换为子图", "Convert selected nodes to group node": "将选中节点转换为组节点", - "Decrease Brush Size in MaskEditor": "在 MaskEditor 中减小笔刷大小", + "Custom Nodes (Beta)": "自定义节点(测试版)", + "Custom Nodes (Legacy)": "自定义节点(旧版)", "Delete Selected Items": "删除选定的项目", "Desktop User Guide": "桌面端用户指南", "Duplicate Current Workflow": "复制当前工作流", "Edit": "编辑", - "Exit Subgraph": "退出子图", "Export": "导出", "Export (API)": "导出 (API)", - "File": "文件", "Fit Group To Contents": "适应组内容", - "Focus Mode": "专注模式", + "Fit view to selected nodes": "适应视图到选中节点", "Give Feedback": "提供反馈", "Group Selected Nodes": "将选中节点转换为组节点", "Help": "帮助", - "Help Center": "帮助中心", - "Increase Brush Size in MaskEditor": "在 MaskEditor 中增大笔刷大小", "Interrupt": "中断", "Load Default Workflow": "加载默认工作流", "Manage group nodes": "管理组节点", - "Manager": "管理器", - "Minimap": "小地图", - "Model Library": "模型库", "Move Selected Nodes Down": "下移所选节点", "Move Selected Nodes Left": "左移所选节点", "Move Selected Nodes Right": "右移所选节点", "Move Selected Nodes Up": "上移所选节点", + "Manager": "管理器", + "Manager Menu (Legacy)": "管理器菜单(旧版)", "Mute/Unmute Selected Nodes": "静音/取消静音选定节点", "New": "新建", "Next Opened Workflow": "下一个打开的工作流", - "Node Library": "节点库", - "Node Links": "节点连接", "Open": "打开", - "Open 3D Viewer (Beta) for Selected Node": "为选中节点打开3D查看器(测试版)", "Open Custom Nodes Folder": "打开自定义节点文件夹", "Open DevTools": "打开开发者工具", "Open Inputs Folder": "打开输入文件夹", @@ -828,7 +778,6 @@ "Pin/Unpin Selected Items": "固定/取消固定选定项目", "Pin/Unpin Selected Nodes": "固定/取消固定选定节点", "Previous Opened Workflow": "上一个打开的工作流", - "Queue Panel": "队列面板", "Queue Prompt": "执行提示词", "Queue Prompt (Front)": "执行提示词 (优先执行)", "Queue Selected Output Nodes": "将所选输出节点加入队列", @@ -845,28 +794,25 @@ "Show Model Selector (Dev)": "顯示模型選擇器(開發用)", "Show Settings Dialog": "显示设置对话框", "Sign Out": "退出登录", - "Toggle Essential Bottom Panel": "切换基础底部面板", + "Toggle Bottom Panel": "切换底部面板", + "Toggle Focus Mode": "切换专注模式", "Toggle Logs Bottom Panel": "切换日志底部面板", + "Toggle Model Library Sidebar": "切换模型库侧边栏", + "Toggle Node Library Sidebar": "切换节点库侧边栏", + "Toggle Queue Sidebar": "切换队列侧边栏", "Toggle Search Box": "切换搜索框", "Toggle Terminal Bottom Panel": "切换终端底部面板", "Toggle Theme (Dark/Light)": "切换主题(暗/亮)", - "Toggle View Controls Bottom Panel": "切换视图控制底部面板", + "Toggle Workflows Sidebar": "切换工作流侧边栏", "Toggle the Custom Nodes Manager": "切换自定义节点管理器", "Toggle the Custom Nodes Manager Progress Bar": "切换自定义节点管理器进度条", "Undo": "撤销", "Ungroup selected group nodes": "解散选中组节点", - "Unpack the selected Subgraph": "解包选中子图", - "Workflows": "工作流", + "Unload Models": "卸载模型", + "Unload Models and Execution Cache": "卸载模型和执行缓存", + "Workflow": "工作流", "Zoom In": "放大画面", - "Zoom Out": "缩小画面", - "Zoom to fit": "缩放以适应" - }, - "minimap": { - "nodeColors": "节点颜色", - "renderBypassState": "渲染绕过状态", - "renderErrorState": "渲染错误状态", - "showGroups": "显示框架/分组", - "showLinks": "显示连接" + "Zoom Out": "缩小画面" }, "missingModelsDialog": { "doNotAskAgain": "不再显示此消息", @@ -1134,7 +1080,6 @@ }, "settingsCategories": { "3D": "3D", - "3DViewer": "3D查看器", "API Nodes": "API 节点", "About": "关于", "Appearance": "外观", @@ -1186,31 +1131,10 @@ "Window": "窗口", "Workflow": "工作流" }, - "shortcuts": { - "essentials": "常用", - "keyboardShortcuts": "键盘快捷键", - "manageShortcuts": "管理快捷键", - "noKeybinding": "无快捷键", - "subcategories": { - "node": "节点", - "panelControls": "面板控制", - "queue": "队列", - "view": "视图", - "workflow": "工作流" - }, - "viewControls": "视图控制" - }, "sideToolbar": { "browseTemplates": "浏览示例模板", "downloads": "下载", "helpCenter": "帮助中心", - "labels": { - "models": "模型", - "nodes": "节点", - "queue": "队列", - "templates": "模板", - "workflows": "工作流" - }, "logout": "登出", "modelLibrary": "模型库", "newBlankWorkflow": "创建空白工作流", @@ -1248,7 +1172,7 @@ }, "showFlatList": "平铺结果" }, - "templates": "模板", + "themeToggle": "主题切换", "workflowTab": { "confirmDelete": "您确定要删除此工作流吗?", "confirmDeleteTitle": "删除工作流?", @@ -1295,8 +1219,6 @@ "Video": "视频生成", "Video API": "视频 API" }, - "loadingMore": "正在加载更多模板...", - "searchPlaceholder": "搜索模板...", "template": { "3D": { "3d_hunyuan3d_image_to_model": "混元3D 2.0 图生模型", @@ -1619,7 +1541,6 @@ "failedToExportModel": "无法将模型导出为 {format}", "failedToFetchBalance": "获取余额失败:{error}", "failedToFetchLogs": "无法获取服务器日志", - "failedToInitializeLoad3dViewer": "初始化3D查看器失败", "failedToInitiateCreditPurchase": "发起积分购买失败:{error}", "failedToPurchaseCredits": "购买积分失败:{error}", "fileLoadError": "无法在 {fileName} 中找到工作流", @@ -1675,13 +1596,6 @@ "prefix": "必须以 {prefix} 开头", "required": "必填" }, - "versionMismatchWarning": { - "dismiss": "关闭", - "frontendNewer": "前端版本 {frontendVersion} 可能與後端版本 {backendVersion} 不相容。", - "frontendOutdated": "前端版本 {frontendVersion} 已过时。後端需要 {requiredVersion} 版或更高版本。", - "title": "版本相容性警告", - "updateFrontend": "更新前端" - }, "welcome": { "getStarted": "开始使用", "title": "欢迎使用 ComfyUI" From d2d85650da04040cf3a76b855161d6ee79cf56ae Mon Sep 17 00:00:00 2001 From: bymyself Date: Wed, 9 Apr 2025 14:50:56 -0700 Subject: [PATCH 03/64] switch to v2 manager API endpoints --- src/services/comfyManagerService.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index b98be59583..17fc9eb44c 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -19,20 +19,20 @@ const GENERIC_SECURITY_ERR_MSG = * API routes for ComfyUI Manager */ enum ManagerRoute { - START_QUEUE = 'manager/queue/start', - RESET_QUEUE = 'manager/queue/reset', - QUEUE_STATUS = 'manager/queue/status', - INSTALL = 'manager/queue/install', - UPDATE = 'manager/queue/update', - UPDATE_ALL = 'manager/queue/update_all', - UNINSTALL = 'manager/queue/uninstall', - DISABLE = 'manager/queue/disable', - FIX_NODE = 'manager/queue/fix', - LIST_INSTALLED = 'customnode/installed', - GET_NODES = 'customnode/getmappings', - GET_PACKS = 'customnode/getlist', - IMPORT_FAIL_INFO = 'customnode/import_fail_info', - REBOOT = 'manager/reboot' + START_QUEUE = 'v2/manager/queue/start', + RESET_QUEUE = 'v2/manager/queue/reset', + QUEUE_STATUS = 'v2/manager/queue/status', + INSTALL = 'v2/manager/queue/install', + UPDATE = 'v2/manager/queue/update', + UPDATE_ALL = 'v2/manager/queue/update_all', + UNINSTALL = 'v2/manager/queue/uninstall', + DISABLE = 'v2/manager/queue/disable', + FIX_NODE = 'v2/manager/queue/fix', + LIST_INSTALLED = 'v2/customnode/installed', + GET_NODES = 'v2/customnode/getmappings', + GET_PACKS = 'v2/customnode/getlist', + IMPORT_FAIL_INFO = 'v2/customnode/import_fail_info', + REBOOT = 'v2/manager/reboot' } const managerApiClient = axios.create({ From cd7d64ae5f42885a9c7e5ff9bc81eb008b9c73a3 Mon Sep 17 00:00:00 2001 From: bymyself Date: Thu, 10 Apr 2025 22:09:40 -0700 Subject: [PATCH 04/64] re-arrange menu items --- src/constants/coreMenuCommands.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/constants/coreMenuCommands.ts b/src/constants/coreMenuCommands.ts index d1f8bd0323..4c19c622b4 100644 --- a/src/constants/coreMenuCommands.ts +++ b/src/constants/coreMenuCommands.ts @@ -16,11 +16,9 @@ export const CORE_MENU_COMMANDS = [ [ ['Manager'], [ - 'Comfy.Manager.ShowLegacyManagerMenu', - 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu', 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu', - 'Comfy.Memory.UnloadModels', - 'Comfy.Memory.UnloadModelsAndExecutionCache' + 'Comfy.Manager.ShowLegacyManagerMenu', + 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu' ] ], [ From fd3362ed0d49495cd3869d458349812247b21d97 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 10:07:03 -0700 Subject: [PATCH 05/64] await promises. update settings schema --- src/composables/useCoreCommands.ts | 8 ++++---- src/schemas/apiSchema.ts | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 86efaf24c6..fda6266da3 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -887,9 +887,9 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'pi pi-bars', label: 'Custom Nodes (Legacy)', versionAdded: '1.16.4', - function: () => { + function: async () => { try { - useCommandStore().execute( + await useCommandStore().execute( 'Comfy.Manager.CustomNodesManager.ToggleVisibility' ) } catch (error) { @@ -907,9 +907,9 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'mdi mdi-puzzle', label: 'Manager Menu (Legacy)', versionAdded: '1.16.4', - function: () => { + function: async () => { try { - useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility') + await useCommandStore().execute('Comfy.Manager.Menu.ToggleVisibility') } catch (error) { useToastStore().add({ severity: 'error', diff --git a/src/schemas/apiSchema.ts b/src/schemas/apiSchema.ts index 6e5e2a4d0e..1f30d31ecd 100644 --- a/src/schemas/apiSchema.ts +++ b/src/schemas/apiSchema.ts @@ -495,6 +495,7 @@ const zSettings = z.object({ 'Comfy.Load3D.LightAdjustmentIncrement': z.number(), 'Comfy.Load3D.CameraType': z.enum(['perspective', 'orthographic']), 'Comfy.Load3D.3DViewerEnable': z.boolean(), + 'Comfy.Memory.AllowManualUnload': z.boolean(), 'pysssss.SnapToGrid': z.boolean(), /** VHS setting is used for queue video preview support. */ 'VHS.AdvancedPreviews': z.string(), From 61fa2fd25ad2afa7468394422e26339b29ce4bb5 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 10:19:52 -0700 Subject: [PATCH 06/64] move legacy option to startup arg --- src/composables/useCoreCommands.ts | 23 +++++++++++++++++++++-- src/locales/en/main.json | 2 +- src/services/comfyManagerService.ts | 15 +++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index fda6266da3..d40bbce99b 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -16,6 +16,7 @@ import { import { Point } from '@/lib/litegraph/src/litegraph' import { api } from '@/scripts/api' import { app } from '@/scripts/app' +import { useComfyManagerService } from '@/services/comfyManagerService' import { useDialogService } from '@/services/dialogService' import { useLitegraphService } from '@/services/litegraphService' import { useWorkflowService } from '@/services/workflowService' @@ -716,8 +717,26 @@ export function useCoreCommands(): ComfyCommand[] { icon: 'pi pi-objects-column', label: 'Custom Nodes (Beta)', versionAdded: '1.12.10', - function: () => { - dialogService.toggleManagerDialog() + function: async () => { + const isLegacyManagerUI = + await useComfyManagerService().isLegacyManagerUI() + if (isLegacyManagerUI) { + try { + await useCommandStore().execute( + 'Comfy.Manager.Menu.ToggleVisibility' // This command is registered by legacy manager FE extension + ) + } catch (error) { + useToastStore().add({ + severity: 'error', + summary: t('g.error'), + detail: t('manager.legacyMenuNotAvailable'), + life: 3000 + }) + dialogService.showManagerDialog() + } + } else { + dialogService.showManagerDialog() + } } }, { diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 4b67bed068..21e9e2ef33 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -154,7 +154,7 @@ }, "manager": { "title": "Custom Nodes Manager", - "legacyMenuNotAvailable": "Legacy manager menu is not available in this version of ComfyUI. Please use the new manager menu instead.", + "legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.", "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/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index 17fc9eb44c..0e8f245acb 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -32,7 +32,8 @@ enum ManagerRoute { GET_NODES = 'v2/customnode/getmappings', GET_PACKS = 'v2/customnode/getlist', IMPORT_FAIL_INFO = 'v2/customnode/import_fail_info', - REBOOT = 'v2/manager/reboot' + REBOOT = 'v2/manager/reboot', + IS_LEGACY_MANAGER_UI = 'v2/manager/is_legacy_manager_ui' } const managerApiClient = axios.create({ @@ -247,6 +248,15 @@ export const useComfyManagerService = () => { ) } + const isLegacyManagerUI = async (signal?: AbortSignal) => { + const errorContext = 'Checking if user set Manager to use the legacy UI' + + return executeRequest( + () => managerApiClient.get(ManagerRoute.IS_LEGACY_MANAGER_UI, { signal }), + { errorContext } + ) + } + return { // State isLoading, @@ -268,6 +278,7 @@ export const useComfyManagerService = () => { updateAllPacks, // System operations - rebootComfyUI + rebootComfyUI, + isLegacyManagerUI } } From be7433be465ae03db23532e525bbaa214e71fab0 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 10:33:11 -0700 Subject: [PATCH 07/64] Add banner indicating how to use legacy manager UI --- .../dialog/content/manager/ManagerHeader.vue | 10 ++++++++++ src/locales/en/main.json | 2 ++ 2 files changed, 12 insertions(+) diff --git a/src/components/dialog/content/manager/ManagerHeader.vue b/src/components/dialog/content/manager/ManagerHeader.vue index f6177c87b8..bd438b7048 100644 --- a/src/components/dialog/content/manager/ManagerHeader.vue +++ b/src/components/dialog/content/manager/ManagerHeader.vue @@ -4,6 +4,16 @@

{{ $t('manager.discoverCommunityContent') }}

+ diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 21e9e2ef33..8b32a5e056 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -155,6 +155,8 @@ "manager": { "title": "Custom Nodes Manager", "legacyMenuNotAvailable": "Legacy manager menu is not available, defaulting to the new manager menu.", + "legacyManagerUI": "Use Legacy UI", + "legacyManagerUIDescription": "To use the legacy Manager UI, start ComfyUI with --enable-manager-legacy-ui", "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.", From 29cdd57de26e264ea9c4c366bfbc4c78f3f9a08b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 14 Apr 2025 17:36:52 +0000 Subject: [PATCH 08/64] Update locales [skip ci] --- src/locales/en/commands.json | 6 ------ src/locales/es/commands.json | 6 ------ src/locales/es/main.json | 4 ++-- src/locales/fr/commands.json | 6 ------ src/locales/fr/main.json | 4 ++-- src/locales/ja/commands.json | 6 ------ src/locales/ja/main.json | 4 ++-- src/locales/ko/commands.json | 6 ------ src/locales/ko/main.json | 4 ++-- src/locales/ru/commands.json | 6 ------ src/locales/ru/main.json | 4 ++-- src/locales/zh/commands.json | 6 ------ src/locales/zh/main.json | 4 ++-- 13 files changed, 12 insertions(+), 54 deletions(-) diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 4cd9738c62..0255080249 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Custom Nodes (Beta)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "Custom Nodes (Legacy)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "Manager Menu (Legacy)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Toggle the Custom Nodes Manager Progress Bar" }, diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index 543a9e3463..4989eca344 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Nodos personalizados (Beta)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "Nodos personalizados (Legacy)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "Menú del administrador (Legacy)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Alternar diálogo de progreso del administrador" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index b523493ba4..1a3327390b 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -671,6 +671,8 @@ "installationQueue": "Cola de Instalación", "lastUpdated": "Última Actualización", "latestVersion": "Última", + "legacyManagerUI": "Usar UI antigua", + "legacyManagerUIDescription": "Para usar la UI antigua del Manager, inicia ComfyUI con --enable-manager-legacy-ui", "legacyMenuNotAvailable": "El menú del administrador antiguo no está disponible en esta versión de ComfyUI. Por favor, utiliza el nuevo menú del administrador en su lugar.", "license": "Licencia", "loadingVersions": "Cargando versiones...", @@ -787,7 +789,6 @@ "Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo", "Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor", "Custom Nodes (Beta)": "Nodos personalizados (Beta)", - "Custom Nodes (Legacy)": "Nodos personalizados (Antiguo)", "Delete Selected Items": "Eliminar elementos seleccionados", "Desktop User Guide": "Guía de usuario de escritorio", "Duplicate Current Workflow": "Duplicar flujo de trabajo actual", @@ -813,7 +814,6 @@ "Move Selected Nodes Right": "Mover nodos seleccionados hacia la derecha", "Move Selected Nodes Up": "Mover nodos seleccionados hacia arriba", "Manager": "Administrador", - "Manager Menu (Legacy)": "Menú del administrador (Antiguo)", "Mute/Unmute Selected Nodes": "Silenciar/Activar sonido de nodos seleccionados", "New": "Nuevo", "Next Opened Workflow": "Siguiente flujo de trabajo abierto", diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 7df3dea35d..082bc542fb 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Nœuds personnalisés (Beta)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "Nœuds personnalisés (Legacy)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "Menu du gestionnaire (Legacy)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Basculer la boîte de dialogue de progression" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 7dc1300fae..ae1b450b54 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -633,6 +633,8 @@ "installationQueue": "File d'attente d'installation", "lastUpdated": "Dernière mise à jour", "latestVersion": "Dernière", + "legacyManagerUI": "Utiliser l'interface utilisateur héritée", + "legacyManagerUIDescription": "Pour utiliser l'interface utilisateur de gestion héritée, démarrez ComfyUI avec --enable-manager-legacy-ui", "legacyMenuNotAvailable": "Le menu du gestionnaire de l'ancienne version n'est pas disponible dans cette version de ComfyUI. Veuillez utiliser le nouveau menu du gestionnaire à la place.", "license": "Licence", "loadingVersions": "Chargement des versions...", @@ -741,7 +743,6 @@ "Convert Selection to Subgraph": "Convertir la sélection en sous-graphe", "Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe", "Custom Nodes (Beta)": "Nœuds personnalisés (Beta)", - "Custom Nodes (Legacy)": "Nœuds personnalisés (Ancienne version)", "Delete Selected Items": "Supprimer les éléments sélectionnés", "Desktop User Guide": "Guide de l'utilisateur de bureau", "Duplicate Current Workflow": "Dupliquer le flux de travail actuel", @@ -761,7 +762,6 @@ "Move Selected Nodes Right": "Déplacer les nœuds sélectionnés vers la droite", "Move Selected Nodes Up": "Déplacer les nœuds sélectionnés vers le haut", "Manager": "Gestionnaire", - "Manager Menu (Legacy)": "Menu du gestionnaire (Ancienne version)", "Mute/Unmute Selected Nodes": "Mettre en sourdine/Activer le son des nœuds sélectionnés", "New": "Nouveau", "Next Opened Workflow": "Prochain flux de travail ouvert", diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index c790972940..d7952fa1ad 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "カスタムノード(ベータ版)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "カスタムノード(レガシー)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "マネージャーメニュー(レガシー)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "プログレスダイアログの切り替え" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 2a3b854aa8..afd9ba753e 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -633,6 +633,8 @@ "installationQueue": "インストールキュー", "lastUpdated": "最終更新日", "latestVersion": "最新", + "legacyManagerUI": "レガシーUIを使用する", + "legacyManagerUIDescription": "レガシーManager UIを使用するには、--enable-manager-legacy-uiを付けてComfyUIを起動してください", "legacyMenuNotAvailable": "このバージョンのComfyUIでは、レガシーマネージャーメニューは利用できません。新しいマネージャーメニューを使用してください。", "license": "ライセンス", "loadingVersions": "バージョンを読み込んでいます...", @@ -741,7 +743,6 @@ "Convert Selection to Subgraph": "選択範囲をサブグラフに変換", "Convert selected nodes to group node": "選択したノードをグループノードに変換", "Custom Nodes (Beta)": "カスタムノード(ベータ)", - "Custom Nodes (Legacy)": "カスタムノード(レガシー)", "Delete Selected Items": "選択したアイテムを削除", "Desktop User Guide": "デスクトップユーザーガイド", "Duplicate Current Workflow": "現在のワークフローを複製", @@ -761,7 +762,6 @@ "Move Selected Nodes Right": "選択したノードを右へ移動", "Move Selected Nodes Up": "選択したノードを上へ移動", "Manager": "マネージャー", - "Manager Menu (Legacy)": "マネージャーメニュー(レガシー)", "Mute/Unmute Selected Nodes": "選択したノードのミュート/ミュート解除", "New": "新規", "Next Opened Workflow": "次に開いたワークフロー", diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 23dfa85571..4933da5fb7 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "사용자 정의 노드 (베타)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "사용자 정의 노드 (레거시)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "매니저 메뉴 (레거시)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "진행 상황 대화 상자 전환" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index f02344f549..801abc390c 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -633,6 +633,8 @@ "installationQueue": "설치 대기열", "lastUpdated": "마지막 업데이트", "latestVersion": "최신", + "legacyManagerUI": "레거시 UI 사용", + "legacyManagerUIDescription": "레거시 매니저 UI를 사용하려면, ComfyUI를 --enable-manager-legacy-ui로 시작하세요", "legacyMenuNotAvailable": "이 버전의 ComfyUI에서는 레거시 매니저 메뉴를 사용할 수 없습니다. 대신 새로운 매니저 메뉴를 사용하십시오.", "license": "라이선스", "loadingVersions": "버전 로딩 중...", @@ -741,7 +743,6 @@ "Convert Selection to Subgraph": "선택 영역을 서브그래프로 변환", "Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환", "Custom Nodes (Beta)": "사용자 정의 노드 (베타)", - "Custom Nodes (Legacy)": "사용자 정의 노드 (레거시)", "Delete Selected Items": "선택한 항목 삭제", "Desktop User Guide": "데스크톱 사용자 가이드", "Duplicate Current Workflow": "현재 워크플로 복제", @@ -761,7 +762,6 @@ "Move Selected Nodes Right": "선택한 노드 오른쪽으로 이동", "Move Selected Nodes Up": "선택한 노드 위로 이동", "Manager": "매니저", - "Manager Menu (Legacy)": "매니저 메뉴 (레거시)", "Mute/Unmute Selected Nodes": "선택한 노드 활성화/비활성화", "New": "새로 만들기", "Next Opened Workflow": "다음 열린 워크플로", diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index 4e55cb7af3..d79a2abbb0 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Пользовательские узлы (Бета)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "Пользовательские узлы (Устаревшие)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "Меню менеджера (Устаревшее)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Переключить диалоговое окно прогресса" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index 1c94d2b535..e81b068aa7 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -633,6 +633,8 @@ "installationQueue": "Очередь установки", "lastUpdated": "Последнее обновление", "latestVersion": "Последняя", + "legacyManagerUI": "Использовать устаревший UI", + "legacyManagerUIDescription": "Чтобы использовать устаревший UI менеджера, запустите ComfyUI с --enable-manager-legacy-ui", "legacyMenuNotAvailable": "Устаревшее меню менеджера недоступно в этой версии ComfyUI. Пожалуйста, используйте новое меню менеджера.", "license": "Лицензия", "loadingVersions": "Загрузка версий...", @@ -741,7 +743,6 @@ "Convert Selection to Subgraph": "Преобразовать выделенное в подграф", "Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду", "Custom Nodes (Beta)": "Пользовательские узлы (Бета)", - "Custom Nodes (Legacy)": "Пользовательские узлы (Устаревшие)", "Delete Selected Items": "Удалить выбранные элементы", "Desktop User Guide": "Руководство пользователя для настольных ПК", "Duplicate Current Workflow": "Дублировать текущий рабочий процесс", @@ -761,7 +762,6 @@ "Move Selected Nodes Right": "Переместить выбранные узлы вправо", "Move Selected Nodes Up": "Переместить выбранные узлы вверх", "Manager": "Менеджер", - "Manager Menu (Legacy)": "Меню менеджера (Устаревшее)", "Mute/Unmute Selected Nodes": "Отключить/включить звук для выбранных нод", "New": "Новый", "Next Opened Workflow": "Следующий открытый рабочий процесс", diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index c2a5d89235..4cc8861654 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -167,12 +167,6 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "自定义节点(测试版)" }, - "Comfy_Manager_CustomNodesManager_ShowLegacyCustomNodesMenu": { - "label": "自定义节点(旧版)" - }, - "Comfy_Manager_ShowLegacyManagerMenu": { - "label": "管理菜单(旧版)" - }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "切换进度对话框" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index efee6fec1e..80371d598e 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -633,6 +633,8 @@ "installationQueue": "安装队列", "lastUpdated": "最后更新", "latestVersion": "最新", + "legacyManagerUI": "使用旧版UI", + "legacyManagerUIDescription": "要使用旧版的管理器UI,请启动ComfyUI并使用 --enable-manager-legacy-ui", "legacyMenuNotAvailable": "在此版本的ComfyUI中,不提供旧版的管理器菜单。请使用新的管理器菜单。", "license": "许可证", "loadingVersions": "正在加载版本...", @@ -741,7 +743,6 @@ "Convert Selection to Subgraph": "将选中内容转换为子图", "Convert selected nodes to group node": "将选中节点转换为组节点", "Custom Nodes (Beta)": "自定义节点(测试版)", - "Custom Nodes (Legacy)": "自定义节点(旧版)", "Delete Selected Items": "删除选定的项目", "Desktop User Guide": "桌面端用户指南", "Duplicate Current Workflow": "复制当前工作流", @@ -761,7 +762,6 @@ "Move Selected Nodes Right": "右移所选节点", "Move Selected Nodes Up": "上移所选节点", "Manager": "管理器", - "Manager Menu (Legacy)": "管理器菜单(旧版)", "Mute/Unmute Selected Nodes": "静音/取消静音选定节点", "New": "新建", "Next Opened Workflow": "下一个打开的工作流", From 7ec70e548736be6f4b0c6464ac65ca6eaa7a73c6 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 10:43:24 -0700 Subject: [PATCH 09/64] add "Check for Updates", "Install Missing" menu items --- src/composables/useCoreCommands.ts | 25 ++++++++++++++++++++++++- src/constants/coreMenuCommands.ts | 4 ++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index d40bbce99b..7725feba99 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -35,6 +35,7 @@ import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore' import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { useSearchBoxStore } from '@/stores/workspace/searchBoxStore' import { useWorkspaceStore } from '@/stores/workspaceStore' +import { ManagerTab } from '@/types/comfyManagerTypes' import { getAllNonIoNodesInSubgraph, getExecutionIdsForSelectedNodes @@ -715,7 +716,7 @@ export function useCoreCommands(): ComfyCommand[] { { id: 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu', icon: 'pi pi-objects-column', - label: 'Custom Nodes (Beta)', + label: 'Custom Nodes Manager', versionAdded: '1.12.10', function: async () => { const isLegacyManagerUI = @@ -739,6 +740,28 @@ export function useCoreCommands(): ComfyCommand[] { } } }, + { + id: 'Comfy.Manager.ShowUpdateAvailablePacks', + icon: 'pi pi-sync', + label: 'Check for Updates', + versionAdded: '1.17.0', + function: () => { + dialogService.showManagerDialog({ + initialTab: ManagerTab.UpdateAvailable + }) + } + }, + { + id: 'Comfy.Manager.ShowMissingPacks', + icon: 'pi pi-exclamation-circle', + label: 'Install Missing', + versionAdded: '1.17.0', + function: () => { + dialogService.showManagerDialog({ + initialTab: ManagerTab.Missing + }) + } + }, { id: 'Comfy.Manager.ToggleManagerProgressDialog', icon: 'pi pi-spinner', diff --git a/src/constants/coreMenuCommands.ts b/src/constants/coreMenuCommands.ts index 4c19c622b4..c24acf9c0d 100644 --- a/src/constants/coreMenuCommands.ts +++ b/src/constants/coreMenuCommands.ts @@ -17,8 +17,8 @@ export const CORE_MENU_COMMANDS = [ ['Manager'], [ 'Comfy.Manager.CustomNodesManager.ShowCustomNodesMenu', - 'Comfy.Manager.ShowLegacyManagerMenu', - 'Comfy.Manager.CustomNodesManager.ShowLegacyCustomNodesMenu' + 'Comfy.Manager.ShowMissingPacks', + 'Comfy.Manager.ShowUpdateAvailablePacks' ] ], [ From 226f84eaf180769a046a506a0d61fcb1011f2680 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 14 Apr 2025 17:47:46 +0000 Subject: [PATCH 10/64] Update locales [skip ci] --- src/locales/en/commands.json | 8 +++++++- src/locales/en/main.json | 2 ++ src/locales/es/commands.json | 6 ++++++ src/locales/es/main.json | 2 ++ src/locales/fr/commands.json | 6 ++++++ src/locales/fr/main.json | 5 +++-- src/locales/ja/commands.json | 6 ++++++ src/locales/ja/main.json | 5 +++-- src/locales/ko/commands.json | 6 ++++++ src/locales/ko/main.json | 3 ++- src/locales/ru/commands.json | 6 ++++++ src/locales/ru/main.json | 5 +++-- src/locales/zh/commands.json | 6 ++++++ src/locales/zh/main.json | 3 ++- 14 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 0255080249..48b543702b 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -165,7 +165,13 @@ "label": "Load Default Workflow" }, "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { - "label": "Custom Nodes (Beta)" + "label": "Custom Nodes Manager" + }, + "Comfy_Manager_ShowMissingPacks": { + "label": "Install Missing" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "Check for Updates" }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Toggle the Custom Nodes Manager Progress Bar" diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 8b32a5e056..f06e14485f 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -1015,6 +1015,8 @@ "Custom Nodes (Beta)": "Custom Nodes (Beta)", "Custom Nodes (Legacy)": "Custom Nodes (Legacy)", "Manager Menu (Legacy)": "Manager Menu (Legacy)", + "Custom Nodes Manager": "Custom Nodes Manager", + "Install Missing": "Install Missing", "Toggle Progress Dialog": "Toggle Progress Dialog", "Unload Models": "Unload Models", "Unload Models and Execution Cache": "Unload Models and Execution Cache", diff --git a/src/locales/es/commands.json b/src/locales/es/commands.json index 4989eca344..038f6a2ee0 100644 --- a/src/locales/es/commands.json +++ b/src/locales/es/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Nodos personalizados (Beta)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "Instalar faltantes" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "Buscar actualizaciones" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Alternar diálogo de progreso del administrador" }, diff --git a/src/locales/es/main.json b/src/locales/es/main.json index 1a3327390b..e3fbc9132e 100644 --- a/src/locales/es/main.json +++ b/src/locales/es/main.json @@ -789,6 +789,7 @@ "Convert selected nodes to group node": "Convertir nodos seleccionados en nodo de grupo", "Decrease Brush Size in MaskEditor": "Disminuir tamaño del pincel en MaskEditor", "Custom Nodes (Beta)": "Nodos personalizados (Beta)", + "Custom Nodes Manager": "Administrador de Nodos Personalizados", "Delete Selected Items": "Eliminar elementos seleccionados", "Desktop User Guide": "Guía de usuario de escritorio", "Duplicate Current Workflow": "Duplicar flujo de trabajo actual", @@ -804,6 +805,7 @@ "Help": "Ayuda", "Help Center": "Centro de ayuda", "Increase Brush Size in MaskEditor": "Aumentar tamaño del pincel en MaskEditor", + "Install Missing": "Instalar Faltantes", "Interrupt": "Interrumpir", "Load Default Workflow": "Cargar flujo de trabajo predeterminado", "Manage group nodes": "Gestionar nodos de grupo", diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 082bc542fb..e4fecae147 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Nœuds personnalisés (Beta)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "Installer manquants" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "Vérifier les mises à jour" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Basculer la boîte de dialogue de progression" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index ae1b450b54..5018cbc592 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -729,7 +729,7 @@ "Bypass/Unbypass Selected Nodes": "Contourner/Ne pas contourner les nœuds sélectionnés", "Canvas Toggle Link Visibility": "Basculer la visibilité du lien de la toile", "Canvas Toggle Lock": "Basculer le verrouillage de la toile", - "Check for Updates": "Vérifier les mises à jour", + "Check for Updates": "Vérifier les Mises à Jour", "Clear Pending Tasks": "Effacer les tâches en attente", "Clear Workflow": "Effacer le flux de travail", "Clipspace": "Espace de clip", @@ -742,7 +742,7 @@ "Contact Support": "Contacter le support", "Convert Selection to Subgraph": "Convertir la sélection en sous-graphe", "Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe", - "Custom Nodes (Beta)": "Nœuds personnalisés (Beta)", + "Custom Nodes Manager": "Gestionnaire de Nœuds Personnalisés", "Delete Selected Items": "Supprimer les éléments sélectionnés", "Desktop User Guide": "Guide de l'utilisateur de bureau", "Duplicate Current Workflow": "Dupliquer le flux de travail actuel", @@ -754,6 +754,7 @@ "Give Feedback": "Donnez votre avis", "Group Selected Nodes": "Grouper les nœuds sélectionnés", "Help": "Aide", + "Install Missing": "Installer Manquants", "Interrupt": "Interrompre", "Load Default Workflow": "Charger le flux de travail par défaut", "Manage group nodes": "Gérer les nœuds de groupe", diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index d7952fa1ad..51917d919a 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "カスタムノード(ベータ版)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "不足しているパックをインストール" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "更新を確認" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "プログレスダイアログの切り替え" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index afd9ba753e..20e2bc8b0d 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -729,7 +729,7 @@ "Bypass/Unbypass Selected Nodes": "選択したノードのバイパス/バイパス解除", "Canvas Toggle Link Visibility": "キャンバスのリンク表示を切り替え", "Canvas Toggle Lock": "キャンバスのロックを切り替え", - "Check for Updates": "更新を確認する", + "Check for Updates": "更新を確認", "Clear Pending Tasks": "保留中のタスクをクリア", "Clear Workflow": "ワークフローをクリア", "Clipspace": "クリップスペース", @@ -742,7 +742,7 @@ "Contact Support": "サポートに連絡", "Convert Selection to Subgraph": "選択範囲をサブグラフに変換", "Convert selected nodes to group node": "選択したノードをグループノードに変換", - "Custom Nodes (Beta)": "カスタムノード(ベータ)", + "Custom Nodes Manager": "カスタムノードマネージャ", "Delete Selected Items": "選択したアイテムを削除", "Desktop User Guide": "デスクトップユーザーガイド", "Duplicate Current Workflow": "現在のワークフローを複製", @@ -754,6 +754,7 @@ "Give Feedback": "フィードバックを送る", "Group Selected Nodes": "選択したノードをグループ化", "Help": "ヘルプ", + "Install Missing": "不足しているものをインストール", "Interrupt": "中断", "Load Default Workflow": "デフォルトワークフローを読み込む", "Manage group nodes": "グループノードを管理", diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 4933da5fb7..75418c7dca 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "사용자 정의 노드 (베타)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "누락된 팩 설치" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "업데이트 확인" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "진행 상황 대화 상자 전환" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 801abc390c..0be2d3d7ba 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -742,7 +742,7 @@ "Contact Support": "고객 지원 문의", "Convert Selection to Subgraph": "선택 영역을 서브그래프로 변환", "Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환", - "Custom Nodes (Beta)": "사용자 정의 노드 (베타)", + "Custom Nodes Manager": "사용자 정의 노드 관리자", "Delete Selected Items": "선택한 항목 삭제", "Desktop User Guide": "데스크톱 사용자 가이드", "Duplicate Current Workflow": "현재 워크플로 복제", @@ -754,6 +754,7 @@ "Give Feedback": "피드백 제공", "Group Selected Nodes": "선택한 노드 그룹화", "Help": "도움말", + "Install Missing": "누락된 설치", "Interrupt": "중단", "Load Default Workflow": "기본 워크플로 불러오기", "Manage group nodes": "그룹 노드 관리", diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index d79a2abbb0..51dd1d71fc 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "Пользовательские узлы (Бета)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "Установить отсутствующие" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "Проверить наличие обновлений" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "Переключить диалоговое окно прогресса" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index e81b068aa7..438e2113de 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -729,7 +729,7 @@ "Bypass/Unbypass Selected Nodes": "Обойти/восстановить выбранные ноды", "Canvas Toggle Link Visibility": "Переключение видимости ссылки на холст", "Canvas Toggle Lock": "Переключение блокировки холста", - "Check for Updates": "Проверить наличие обновлений", + "Check for Updates": "Проверить Обновления", "Clear Pending Tasks": "Очистить ожидающие задачи", "Clear Workflow": "Очистить рабочий процесс", "Clipspace": "Клиппространство", @@ -742,7 +742,7 @@ "Contact Support": "Связаться с поддержкой", "Convert Selection to Subgraph": "Преобразовать выделенное в подграф", "Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду", - "Custom Nodes (Beta)": "Пользовательские узлы (Бета)", + "Custom Nodes Manager": "Менеджер Пользовательских Узлов", "Delete Selected Items": "Удалить выбранные элементы", "Desktop User Guide": "Руководство пользователя для настольных ПК", "Duplicate Current Workflow": "Дублировать текущий рабочий процесс", @@ -754,6 +754,7 @@ "Give Feedback": "Оставить отзыв", "Group Selected Nodes": "Сгруппировать выбранные ноды", "Help": "Помощь", + "Install Missing": "Установить Отсутствующие", "Interrupt": "Прервать", "Load Default Workflow": "Загрузить стандартный рабочий процесс", "Manage group nodes": "Управление групповыми нодами", diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index 4cc8861654..8a4fae6467 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -167,6 +167,12 @@ "Comfy_Manager_CustomNodesManager_ShowCustomNodesMenu": { "label": "自定义节点(测试版)" }, + "Comfy_Manager_ShowMissingPacks": { + "label": "安装缺失的包" + }, + "Comfy_Manager_ShowUpdateAvailablePacks": { + "label": "检查更新" + }, "Comfy_Manager_ToggleManagerProgressDialog": { "label": "切换进度对话框" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 80371d598e..c51e188f2f 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -742,7 +742,7 @@ "Contact Support": "联系支持", "Convert Selection to Subgraph": "将选中内容转换为子图", "Convert selected nodes to group node": "将选中节点转换为组节点", - "Custom Nodes (Beta)": "自定义节点(测试版)", + "Custom Nodes Manager": "自定义节点管理器", "Delete Selected Items": "删除选定的项目", "Desktop User Guide": "桌面端用户指南", "Duplicate Current Workflow": "复制当前工作流", @@ -754,6 +754,7 @@ "Give Feedback": "提供反馈", "Group Selected Nodes": "将选中节点转换为组节点", "Help": "帮助", + "Install Missing": "安装缺失", "Interrupt": "中断", "Load Default Workflow": "加载默认工作流", "Manage group nodes": "管理组节点", From cf174d30b9b3397c7b3cce1f4349d76b141a9817 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 16:22:45 -0700 Subject: [PATCH 11/64] use correct response shape --- src/composables/useCoreCommands.ts | 7 ++++--- src/services/comfyManagerService.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 7725feba99..bba8ee8e88 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -719,9 +719,10 @@ export function useCoreCommands(): ComfyCommand[] { label: 'Custom Nodes Manager', versionAdded: '1.12.10', function: async () => { - const isLegacyManagerUI = - await useComfyManagerService().isLegacyManagerUI() - if (isLegacyManagerUI) { + const { is_legacy_manager_ui } = + (await useComfyManagerService().isLegacyManagerUI()) ?? {} + + if (is_legacy_manager_ui === true) { try { await useCommandStore().execute( 'Comfy.Manager.Menu.ToggleVisibility' // This command is registered by legacy manager FE extension diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index 0e8f245acb..79f2dd132b 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -251,7 +251,7 @@ export const useComfyManagerService = () => { const isLegacyManagerUI = async (signal?: AbortSignal) => { const errorContext = 'Checking if user set Manager to use the legacy UI' - return executeRequest( + return executeRequest<{ is_legacy_manager_ui: boolean }>( () => managerApiClient.get(ManagerRoute.IS_LEGACY_MANAGER_UI, { signal }), { errorContext } ) From e8e558b43144a6d7a84543eb9af4360e43095101 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 16:29:03 -0700 Subject: [PATCH 12/64] improve command names --- src/composables/useCoreCommands.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index bba8ee8e88..fe9aa7eb4a 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -728,6 +728,7 @@ export function useCoreCommands(): ComfyCommand[] { 'Comfy.Manager.Menu.ToggleVisibility' // This command is registered by legacy manager FE extension ) } catch (error) { + console.error('error', error) useToastStore().add({ severity: 'error', summary: t('g.error'), @@ -744,7 +745,7 @@ export function useCoreCommands(): ComfyCommand[] { { id: 'Comfy.Manager.ShowUpdateAvailablePacks', icon: 'pi pi-sync', - label: 'Check for Updates', + label: 'Check for Custom Node Updates', versionAdded: '1.17.0', function: () => { dialogService.showManagerDialog({ @@ -755,7 +756,7 @@ export function useCoreCommands(): ComfyCommand[] { { id: 'Comfy.Manager.ShowMissingPacks', icon: 'pi pi-exclamation-circle', - label: 'Install Missing', + label: 'Install Missing Custom Nodes', versionAdded: '1.17.0', function: () => { dialogService.showManagerDialog({ From 8a2747f29eeeb47d937bb6ec4d68264cdc04c296 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 14 Apr 2025 17:46:03 -0700 Subject: [PATCH 13/64] dont show missing nodes button in legacy manager mode --- .../dialog/content/LoadWorkflowWarning.vue | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/components/dialog/content/LoadWorkflowWarning.vue b/src/components/dialog/content/LoadWorkflowWarning.vue index 41d422d20c..96c632be8e 100644 --- a/src/components/dialog/content/LoadWorkflowWarning.vue +++ b/src/components/dialog/content/LoadWorkflowWarning.vue @@ -31,7 +31,7 @@ -
+
import Button from 'primevue/button' import ListBox from 'primevue/listbox' -import { computed } from 'vue' +import { computed, onMounted, ref } from 'vue' import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue' -import MissingCoreNodesMessage from '@/components/dialog/content/MissingCoreNodesMessage.vue' -import PackInstallButton from '@/components/dialog/content/manager/button/PackInstallButton.vue' import { useMissingNodes } from '@/composables/nodePack/useMissingNodes' +import { useComfyManagerService } from '@/services/comfyManagerService' import { useDialogService } from '@/services/dialogService' -import { useAboutPanelStore } from '@/stores/aboutPanelStore' import type { MissingNodeType } from '@/types/comfy' import { ManagerTab } from '@/types/comfyManagerTypes' @@ -60,22 +58,11 @@ const props = defineProps<{ missingNodeTypes: MissingNodeType[] }>() -const aboutPanelStore = useAboutPanelStore() - // Get missing node packs from workflow with loading and error states const { missingNodePacks, isLoading, error, missingCoreNodes } = useMissingNodes() -// Determines if ComfyUI-Manager is installed by checking for its badge in the about panel -// This allows us to conditionally show the Manager button only when the extension is available -// TODO: Remove this check when Manager functionality is fully migrated into core -const isManagerInstalled = computed(() => { - return aboutPanelStore.badges.some( - (badge) => - badge.label.includes('ComfyUI-Manager') || - badge.url.includes('ComfyUI-Manager') - ) -}) +const isLegacyManager = ref(false) const uniqueNodes = computed(() => { const seenTypes = new Set() @@ -103,6 +90,13 @@ const openManager = () => { initialTab: ManagerTab.Missing }) } + +onMounted(async () => { + const isLegacyResponse = await useComfyManagerService().isLegacyManagerUI() + if (isLegacyResponse?.is_legacy_manager_ui) { + isLegacyManager.value = true + } +}) diff --git a/src/components/dialog/content/manager/button/PackInstallButton.vue b/src/components/dialog/content/manager/button/PackInstallButton.vue index 7bba01ef0a..2f9751d0fd 100644 --- a/src/components/dialog/content/manager/button/PackInstallButton.vue +++ b/src/components/dialog/content/manager/button/PackInstallButton.vue @@ -10,7 +10,6 @@ :loading="isInstalling" :loading-message="$t('g.installing')" @action="installAllPacks" - @click="onClick" /> @@ -37,10 +36,6 @@ const { nodePacks, variant, label } = defineProps<{ const isInstalling = inject(IsInstallingKey, ref(false)) -const onClick = (): void => { - isInstalling.value = true -} - const managerStore = useComfyManagerStore() const createPayload = (installItem: NodePack) => { @@ -65,8 +60,6 @@ const installPack = (item: NodePack) => const installAllPacks = async () => { if (!nodePacks?.length) return - isInstalling.value = true - const uninstalledPacks = nodePacks.filter( (pack) => !managerStore.isPackInstalled(pack.id) ) diff --git a/src/components/dialog/content/manager/packCard/PackCard.vue b/src/components/dialog/content/manager/packCard/PackCard.vue index 08caeb29cb..ee2cef92de 100644 --- a/src/components/dialog/content/manager/packCard/PackCard.vue +++ b/src/components/dialog/content/manager/packCard/PackCard.vue @@ -84,10 +84,9 @@ diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 866cc1ce03..51e94188bd 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -158,6 +158,12 @@ "inWorkflow": "In Workflow", "infoPanelEmpty": "Click an item to see the info", "restartToApplyChanges": "To apply changes, please restart ComfyUI", + "clickToFinishSetup": "Click", + "toFinishSetup": "to finish setup", + "applyChanges": "Apply Changes", + "restartingBackend": "Restarting backend to apply changes...", + "extensionsSuccessfullyInstalled": "Extension(s) successfully installed and are ready to use!", + "installingDependencies": "Installing dependencies...", "loadingVersions": "Loading versions...", "selectVersion": "Select Version", "downloads": "Downloads", diff --git a/src/stores/comfyManagerStore.ts b/src/stores/comfyManagerStore.ts index 0568711f77..b9840f4212 100644 --- a/src/stores/comfyManagerStore.ts +++ b/src/stores/comfyManagerStore.ts @@ -29,6 +29,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { const enabledPacksIds = ref>(new Set()) const disabledPacksIds = ref>(new Set()) const installedPacksIds = ref>(new Set()) + const installingPacksIds = ref>(new Set()) const isStale = ref(true) const taskLogs = ref([]) @@ -49,6 +50,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { isInstalledPackId(packName) && enabledPacksIds.value.has(packName) + const isInstallingPackId = (packName: string | undefined): boolean => + !!packName && installingPacksIds.value.has(packName) + const packsToIdSet = (packs: ManagerPackInstalled[]) => packs.reduce((acc, pack) => { const id = pack.cnr_id || pack.aux_id @@ -117,7 +121,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { whenever(isStale, refreshInstalledList, { immediate: true }) whenever(uncompletedCount, () => showManagerProgressDialog()) - const withLogs = (task: () => Promise, taskName: string) => { + const withLogs = ( + task: () => Promise, + taskName: string, + packId?: string + ) => { const { startListening, stopListening, logs } = useServerLogs() const loggedTask = async () => { @@ -128,6 +136,9 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { const onComplete = async () => { await stopListening() + if (packId) { + installingPacksIds.value.delete(packId) + } setStale() } @@ -152,8 +163,11 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { } } + installingPacksIds.value.add(params.id) const task = () => managerService.installPack(params, signal) - enqueueTask(withLogs(task, `${actionDescription} ${params.id}`)) + enqueueTask( + withLogs(task, `${actionDescription} ${params.id}`, params.id) + ) }, { maxSize: 1 } ) @@ -162,14 +176,16 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { installPack.clear() installPack.cancel() const task = () => managerService.uninstallPack(params, signal) - enqueueTask(withLogs(task, t('manager.uninstalling', { id: params.id }))) + enqueueTask( + withLogs(task, t('manager.uninstalling', { id: params.id }), params.id) + ) } const updatePack = useCachedRequest( async (params: ManagerPackInfo, signal?: AbortSignal) => { updateAllPacks.cancel() const task = () => managerService.updatePack(params, signal) - enqueueTask(withLogs(task, t('g.updating', { id: params.id }))) + enqueueTask(withLogs(task, t('g.updating', { id: params.id }), params.id)) }, { maxSize: 1 } ) @@ -184,7 +200,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { const disablePack = (params: ManagerPackInfo, signal?: AbortSignal) => { const task = () => managerService.disablePack(params, signal) - enqueueTask(withLogs(task, t('g.disabling', { id: params.id }))) + enqueueTask(withLogs(task, t('g.disabling', { id: params.id }), params.id)) } const getInstalledPackVersion = (packId: string) => { @@ -212,6 +228,7 @@ export const useComfyManagerStore = defineStore('comfyManager', () => { installedPacksIds, isPackInstalled: isInstalledPackId, isPackEnabled: isEnabledPackId, + isPackInstalling: isInstallingPackId, getInstalledPackVersion, refreshInstalledList, diff --git a/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts b/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts new file mode 100644 index 0000000000..c92d4c89ad --- /dev/null +++ b/tests-ui/tests/components/dialog/footer/ManagerProgressFooter.test.ts @@ -0,0 +1,440 @@ +import { mount } from '@vue/test-utils' +import PrimeVue from 'primevue/config' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { nextTick } from 'vue' +import { createI18n } from 'vue-i18n' + +import ManagerProgressFooter from '@/components/dialog/footer/ManagerProgressFooter.vue' +import { useComfyManagerService } from '@/services/comfyManagerService' +import { + useComfyManagerStore, + useManagerProgressDialogStore +} from '@/stores/comfyManagerStore' +import { useCommandStore } from '@/stores/commandStore' +import { useDialogStore } from '@/stores/dialogStore' +import { useSettingStore } from '@/stores/settingStore' +import { TaskLog } from '@/types/comfyManagerTypes' + +// Mock modules +vi.mock('@/stores/comfyManagerStore') +vi.mock('@/stores/dialogStore') +vi.mock('@/stores/settingStore') +vi.mock('@/stores/commandStore') +vi.mock('@/services/comfyManagerService') + +// Mock useEventListener to capture the event handler +let reconnectHandler: (() => void) | null = null +vi.mock('@vueuse/core', async () => { + const actual = await vi.importActual('@vueuse/core') + return { + ...actual, + useEventListener: vi.fn( + (_target: any, event: string, handler: any, _options: any) => { + if (event === 'reconnected') { + reconnectHandler = handler + } + } + ) + } +}) +vi.mock('@/services/workflowService', () => ({ + useWorkflowService: vi.fn(() => ({ + reloadCurrentWorkflow: vi.fn().mockResolvedValue(undefined) + })) +})) +vi.mock('@/stores/workspace/colorPaletteStore', () => ({ + useColorPaletteStore: vi.fn(() => ({ + completedActivePalette: { + light_theme: false + } + })) +})) + +// Helper function to mount component with required setup +const mountComponent = (options: { captureError?: boolean } = {}) => { + const i18n = createI18n({ + legacy: false, + locale: 'en', + messages: { + en: {} + } + }) + + const config: any = { + global: { + plugins: [PrimeVue, i18n], + mocks: { + $t: (key: string) => key // Mock i18n translation + } + } + } + + // Add error handler for tests that expect errors + if (options.captureError) { + config.global.config = { + errorHandler: () => { + // Suppress error in test + } + } + } + + return mount(ManagerProgressFooter, config) +} + +describe('ManagerProgressFooter', () => { + const mockTaskLogs: TaskLog[] = [] + + const mockComfyManagerStore = { + uncompletedCount: 0, + taskLogs: mockTaskLogs, + allTasksDone: true, + clearLogs: vi.fn(), + setStale: vi.fn(), + // Add other required properties + isLoading: { value: false }, + error: { value: null }, + statusMessage: { value: 'DONE' }, + installedPacks: {}, + installedPacksIds: new Set(), + isPackInstalled: vi.fn(), + isPackEnabled: vi.fn(), + getInstalledPackVersion: vi.fn(), + refreshInstalledList: vi.fn(), + installPack: vi.fn(), + uninstallPack: vi.fn(), + updatePack: vi.fn(), + updateAllPacks: vi.fn(), + disablePack: vi.fn(), + enablePack: vi.fn() + } + + const mockDialogStore = { + closeDialog: vi.fn(), + // Add other required properties + dialogStack: { value: [] }, + showDialog: vi.fn(), + $id: 'dialog', + $state: {} as any, + $patch: vi.fn(), + $reset: vi.fn(), + $subscribe: vi.fn(), + $dispose: vi.fn(), + $onAction: vi.fn() + } + + const mockSettingStore = { + get: vi.fn().mockReturnValue(false), + set: vi.fn(), + // Add other required properties + settingValues: { value: {} }, + settingsById: { value: {} }, + exists: vi.fn(), + getDefaultValue: vi.fn(), + loadSettingValues: vi.fn(), + updateValue: vi.fn(), + $id: 'setting', + $state: {} as any, + $patch: vi.fn(), + $reset: vi.fn(), + $subscribe: vi.fn(), + $dispose: vi.fn(), + $onAction: vi.fn() + } + + const mockProgressDialogStore = { + isExpanded: false, + toggle: vi.fn(), + collapse: vi.fn(), + expand: vi.fn() + } + + const mockCommandStore = { + execute: vi.fn().mockResolvedValue(undefined) + } + + const mockComfyManagerService = { + rebootComfyUI: vi.fn().mockResolvedValue(null) + } + + beforeEach(() => { + vi.clearAllMocks() + // Reset task logs + mockTaskLogs.length = 0 + mockComfyManagerStore.taskLogs = mockTaskLogs + // Reset event handler + reconnectHandler = null + + vi.mocked(useComfyManagerStore).mockReturnValue( + mockComfyManagerStore as any + ) + vi.mocked(useDialogStore).mockReturnValue(mockDialogStore as any) + vi.mocked(useSettingStore).mockReturnValue(mockSettingStore as any) + vi.mocked(useManagerProgressDialogStore).mockReturnValue( + mockProgressDialogStore as any + ) + vi.mocked(useCommandStore).mockReturnValue(mockCommandStore as any) + vi.mocked(useComfyManagerService).mockReturnValue( + mockComfyManagerService as any + ) + }) + + describe('State 1: Queue Running', () => { + it('should display loading spinner and progress counter when queue is running', async () => { + // Setup queue running state + mockComfyManagerStore.uncompletedCount = 3 + mockTaskLogs.push( + { taskName: 'Installing pack1', logs: [] }, + { taskName: 'Installing pack2', logs: [] }, + { taskName: 'Installing pack3', logs: [] } + ) + + const wrapper = mountComponent() + + // Check loading spinner exists (DotSpinner component) + expect(wrapper.find('.inline-flex').exists()).toBe(true) + + // Check current task name is displayed + expect(wrapper.text()).toContain('Installing pack3') + + // Check progress counter (completed: 2 of 3) + expect(wrapper.text()).toMatch(/2.*3/) + + // Check expand/collapse button exists + const expandButton = wrapper.find('[aria-label="Expand"]') + expect(expandButton.exists()).toBe(true) + + // Check Apply Changes button is NOT shown + expect(wrapper.text()).not.toContain('manager.applyChanges') + }) + + it('should toggle expansion when expand button is clicked', async () => { + mockComfyManagerStore.uncompletedCount = 1 + mockTaskLogs.push({ taskName: 'Installing', logs: [] }) + + const wrapper = mountComponent() + + const expandButton = wrapper.find('[aria-label="Expand"]') + await expandButton.trigger('click') + + expect(mockProgressDialogStore.toggle).toHaveBeenCalled() + }) + }) + + describe('State 2: Tasks Completed (Waiting for Restart)', () => { + it('should display check mark and Apply Changes button when all tasks are done', async () => { + // Setup tasks completed state + mockComfyManagerStore.uncompletedCount = 0 + mockTaskLogs.push( + { taskName: 'Installed pack1', logs: [] }, + { taskName: 'Installed pack2', logs: [] } + ) + mockComfyManagerStore.allTasksDone = true + + const wrapper = mountComponent() + + // Check check mark emoji + expect(wrapper.text()).toContain('✅') + + // Check restart message (split into 3 parts) + expect(wrapper.text()).toContain('manager.clickToFinishSetup') + expect(wrapper.text()).toContain('manager.applyChanges') + expect(wrapper.text()).toContain('manager.toFinishSetup') + + // Check Apply Changes button exists + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + expect(applyButton).toBeTruthy() + + // Check no progress counter + expect(wrapper.text()).not.toMatch(/\d+.*of.*\d+/) + }) + }) + + describe('State 3: Restarting', () => { + it('should display restarting message and spinner during restart', async () => { + // Setup completed state first + mockComfyManagerStore.uncompletedCount = 0 + mockComfyManagerStore.allTasksDone = true + + const wrapper = mountComponent() + + // Click Apply Changes to trigger restart + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + await applyButton?.trigger('click') + + // Wait for state update + await nextTick() + + // Check restarting message + expect(wrapper.text()).toContain('manager.restartingBackend') + + // Check loading spinner during restart + expect(wrapper.find('.inline-flex').exists()).toBe(true) + + // Check Apply Changes button is hidden + expect(wrapper.text()).not.toContain('manager.applyChanges') + }) + }) + + describe('State 4: Restart Completed', () => { + it('should display success message and auto-close after 3 seconds', async () => { + vi.useFakeTimers() + + // Setup completed state + mockComfyManagerStore.uncompletedCount = 0 + mockComfyManagerStore.allTasksDone = true + + const wrapper = mountComponent() + + // Trigger restart + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + await applyButton?.trigger('click') + + // Wait for event listener to be set up + await nextTick() + + // Trigger the reconnect handler directly + if (reconnectHandler) { + await reconnectHandler() + } + + // Wait for restart completed state + await nextTick() + + // Check success message + expect(wrapper.text()).toContain('🎉') + expect(wrapper.text()).toContain( + 'manager.extensionsSuccessfullyInstalled' + ) + + // Check dialog closes after 3 seconds + vi.advanceTimersByTime(3000) + + await nextTick() + + expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({ + key: 'global-manager-progress-dialog' + }) + expect(mockComfyManagerStore.clearLogs).toHaveBeenCalled() + + vi.useRealTimers() + }) + }) + + describe('Common Features', () => { + it('should always display close button', async () => { + const wrapper = mountComponent() + + const closeButton = wrapper.find('[aria-label="Close"]') + expect(closeButton.exists()).toBe(true) + }) + + it('should close dialog when close button is clicked', async () => { + const wrapper = mountComponent() + + const closeButton = wrapper.find('[aria-label="Close"]') + await closeButton.trigger('click') + + expect(mockDialogStore.closeDialog).toHaveBeenCalledWith({ + key: 'global-manager-progress-dialog' + }) + }) + }) + + describe('Toast Management', () => { + it('should suppress reconnection toasts during restart', async () => { + mockComfyManagerStore.uncompletedCount = 0 + mockComfyManagerStore.allTasksDone = true + mockSettingStore.get.mockReturnValue(false) // Original setting + + const wrapper = mountComponent() + + // Click Apply Changes + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + await applyButton?.trigger('click') + + // Check toast setting was disabled + expect(mockSettingStore.set).toHaveBeenCalledWith( + 'Comfy.Toast.DisableReconnectingToast', + true + ) + }) + + it('should restore toast settings after restart completes', async () => { + mockComfyManagerStore.uncompletedCount = 0 + mockComfyManagerStore.allTasksDone = true + mockSettingStore.get.mockReturnValue(false) // Original setting + + const wrapper = mountComponent() + + // Click Apply Changes + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + await applyButton?.trigger('click') + + // Wait for event listener to be set up + await nextTick() + + // Trigger the reconnect handler directly + if (reconnectHandler) { + await reconnectHandler() + } + + // Wait for settings restoration + await nextTick() + + expect(mockSettingStore.set).toHaveBeenCalledWith( + 'Comfy.Toast.DisableReconnectingToast', + false // Restored to original + ) + }) + }) + + describe('Error Handling', () => { + it('should restore state and close dialog on restart error', async () => { + mockComfyManagerStore.uncompletedCount = 0 + mockComfyManagerStore.allTasksDone = true + + // Mock restart to throw error + mockComfyManagerService.rebootComfyUI.mockRejectedValue( + new Error('Restart failed') + ) + + const wrapper = mountComponent({ captureError: true }) + + // Click Apply Changes + const applyButton = wrapper + .findAll('button') + .find((btn) => btn.text().includes('manager.applyChanges')) + + expect(applyButton).toBeTruthy() + + // The component throws the error but Vue Test Utils catches it + // We need to check if the error handling logic was executed + await applyButton!.trigger('click').catch(() => { + // Error is expected, ignore it + }) + + // Wait for error handling + await nextTick() + + // Check dialog was closed on error + expect(mockDialogStore.closeDialog).toHaveBeenCalled() + // Check toast settings were restored + expect(mockSettingStore.set).toHaveBeenCalledWith( + 'Comfy.Toast.DisableReconnectingToast', + false + ) + // Check that the error handler was called + expect(mockComfyManagerService.rebootComfyUI).toHaveBeenCalled() + }) + }) +}) diff --git a/tests-ui/tests/store/comfyManagerStore.test.ts b/tests-ui/tests/store/comfyManagerStore.test.ts index 41ead35327..f99d2b3632 100644 --- a/tests-ui/tests/store/comfyManagerStore.test.ts +++ b/tests-ui/tests/store/comfyManagerStore.test.ts @@ -6,6 +6,8 @@ import { useComfyManagerService } from '@/services/comfyManagerService' import { useComfyManagerStore } from '@/stores/comfyManagerStore' import { InstalledPacksResponse, + ManagerChannel, + ManagerDatabaseSource, ManagerPackInstalled } from '@/types/comfyManagerTypes' @@ -13,6 +15,34 @@ vi.mock('@/services/comfyManagerService', () => ({ useComfyManagerService: vi.fn() })) +vi.mock('@/services/dialogService', () => ({ + useDialogService: () => ({ + showManagerProgressDialog: vi.fn() + }) +})) + +vi.mock('@/composables/useManagerQueue', () => { + const enqueueTaskMock = vi.fn() + + return { + useManagerQueue: () => ({ + statusMessage: ref(''), + allTasksDone: ref(false), + enqueueTask: enqueueTaskMock, + uncompletedCount: ref(0) + }), + enqueueTask: enqueueTaskMock + } +}) + +vi.mock('@/composables/useServerLogs', () => ({ + useServerLogs: () => ({ + startListening: vi.fn(), + stopListening: vi.fn(), + logs: ref([]) + }) +})) + vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: vi.fn((key) => key) @@ -33,11 +63,7 @@ interface EnabledDisabledTestCase { } describe('useComfyManagerStore', () => { - let mockManagerService: { - isLoading: ReturnType> - error: ReturnType> - listInstalledPacks: ReturnType - } + let mockManagerService: ReturnType const triggerPacksChange = async ( installedPacks: InstalledPacksResponse, @@ -55,10 +81,21 @@ describe('useComfyManagerStore', () => { mockManagerService = { isLoading: ref(false), error: ref(null), - listInstalledPacks: vi.fn().mockResolvedValue({}) + startQueue: vi.fn().mockResolvedValue(null), + resetQueue: vi.fn().mockResolvedValue(null), + getQueueStatus: vi.fn().mockResolvedValue(null), + listInstalledPacks: vi.fn().mockResolvedValue({}), + getImportFailInfo: vi.fn().mockResolvedValue(null), + installPack: vi.fn().mockResolvedValue(null), + uninstallPack: vi.fn().mockResolvedValue(null), + enablePack: vi.fn().mockResolvedValue(null), + disablePack: vi.fn().mockResolvedValue(null), + updatePack: vi.fn().mockResolvedValue(null), + updateAllPacks: vi.fn().mockResolvedValue(null), + rebootComfyUI: vi.fn().mockResolvedValue(null), + isLegacyManagerUI: vi.fn().mockResolvedValue(false) } - // @ts-expect-error Mocking the return type of useComfyManagerService vi.mocked(useComfyManagerService).mockReturnValue(mockManagerService) }) @@ -313,4 +350,90 @@ describe('useComfyManagerStore', () => { } ) }) + + describe('isPackInstalling', () => { + it('should return false for packs not being installed', () => { + const store = useComfyManagerStore() + expect(store.isPackInstalling('test-pack')).toBe(false) + expect(store.isPackInstalling(undefined)).toBe(false) + expect(store.isPackInstalling('')).toBe(false) + }) + + it('should track pack as installing when installPack is called', async () => { + const store = useComfyManagerStore() + + // Call installPack + await store.installPack.call({ + id: 'test-pack', + repository: 'https://github.com/test/test-pack', + channel: ManagerChannel.DEV, + mode: ManagerDatabaseSource.CACHE, + selected_version: 'latest', + version: 'latest' + }) + + // Check that the pack is marked as installing + expect(store.isPackInstalling('test-pack')).toBe(true) + }) + + it('should remove pack from installing list when explicitly removed', async () => { + const store = useComfyManagerStore() + + // Call installPack + await store.installPack.call({ + id: 'test-pack', + repository: 'https://github.com/test/test-pack', + channel: ManagerChannel.DEV, + mode: ManagerDatabaseSource.CACHE, + selected_version: 'latest', + version: 'latest' + }) + + // Verify pack is installing + expect(store.isPackInstalling('test-pack')).toBe(true) + + // Call installPack again for another pack to demonstrate multiple installs + await store.installPack.call({ + id: 'another-pack', + repository: 'https://github.com/test/another-pack', + channel: ManagerChannel.DEV, + mode: ManagerDatabaseSource.CACHE, + selected_version: 'latest', + version: 'latest' + }) + + // Both should be installing + expect(store.isPackInstalling('test-pack')).toBe(true) + expect(store.isPackInstalling('another-pack')).toBe(true) + }) + + it('should track multiple packs installing independently', async () => { + const store = useComfyManagerStore() + + // Install pack 1 + await store.installPack.call({ + id: 'pack-1', + repository: 'https://github.com/test/pack-1', + channel: ManagerChannel.DEV, + mode: ManagerDatabaseSource.CACHE, + selected_version: 'latest', + version: 'latest' + }) + + // Install pack 2 + await store.installPack.call({ + id: 'pack-2', + repository: 'https://github.com/test/pack-2', + channel: ManagerChannel.DEV, + mode: ManagerDatabaseSource.CACHE, + selected_version: 'latest', + version: 'latest' + }) + + // Both should be installing + expect(store.isPackInstalling('pack-1')).toBe(true) + expect(store.isPackInstalling('pack-2')).toBe(true) + expect(store.isPackInstalling('pack-3')).toBe(false) + }) + }) }) From b7f778bfc81ddbbf9bd80e06162e644ece562c6d Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Thu, 7 Aug 2025 13:06:29 -0700 Subject: [PATCH 20/64] [feat] Add reactive feature flags foundation (#4817) --- src/composables/useFeatureFlags.ts | 36 ++++++ src/scripts/api.ts | 9 +- .../tests/composables/useFeatureFlags.test.ts | 121 ++++++++++++++++++ 3 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 src/composables/useFeatureFlags.ts create mode 100644 tests-ui/tests/composables/useFeatureFlags.test.ts diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts new file mode 100644 index 0000000000..db3edf68c9 --- /dev/null +++ b/src/composables/useFeatureFlags.ts @@ -0,0 +1,36 @@ +import { computed, reactive, readonly } from 'vue' + +import { api } from '@/scripts/api' + +/** + * Known server feature flags (top-level, not extensions) + */ +export enum ServerFeatureFlag { + SUPPORTS_PREVIEW_METADATA = 'supports_preview_metadata', + MAX_UPLOAD_SIZE = 'max_upload_size' +} + +/** + * Composable for reactive access to feature flags + */ +export function useFeatureFlags() { + // Create reactive state that tracks server feature flags + const flags = reactive({ + get supportsPreviewMetadata() { + return api.getServerFeature(ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) + }, + get maxUploadSize() { + return api.getServerFeature(ServerFeatureFlag.MAX_UPLOAD_SIZE) + } + }) + + // Create a reactive computed for any feature flag + const featureFlag = (featurePath: string, defaultValue?: T) => { + return computed(() => api.getServerFeature(featurePath, defaultValue)) + } + + return { + flags: readonly(flags), + featureFlag + } +} diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 0b26353f01..46732009c3 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,4 +1,5 @@ import axios from 'axios' +import get from 'lodash/get' import defaultClientFeatureFlags from '@/config/clientFeatureFlags.json' import type { @@ -1082,21 +1083,21 @@ export class ComfyApi extends EventTarget { /** * Checks if the server supports a specific feature. - * @param featureName The name of the feature to check + * @param featureName The name of the feature to check (supports dot notation for nested values) * @returns true if the feature is supported, false otherwise */ serverSupportsFeature(featureName: string): boolean { - return this.serverFeatureFlags[featureName] === true + return get(this.serverFeatureFlags, featureName) === true } /** * Gets a server feature flag value. - * @param featureName The name of the feature to get + * @param featureName The name of the feature to get (supports dot notation for nested values) * @param defaultValue The default value if the feature is not found * @returns The feature value or default */ getServerFeature(featureName: string, defaultValue?: T): T { - return (this.serverFeatureFlags[featureName] ?? defaultValue) as T + return get(this.serverFeatureFlags, featureName, defaultValue) as T } /** diff --git a/tests-ui/tests/composables/useFeatureFlags.test.ts b/tests-ui/tests/composables/useFeatureFlags.test.ts new file mode 100644 index 0000000000..5b385f3c2c --- /dev/null +++ b/tests-ui/tests/composables/useFeatureFlags.test.ts @@ -0,0 +1,121 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { isReactive, isReadonly } from 'vue' + +import { + ServerFeatureFlag, + useFeatureFlags +} from '@/composables/useFeatureFlags' +import { api } from '@/scripts/api' + +// Mock the API module +vi.mock('@/scripts/api', () => ({ + api: { + getServerFeature: vi.fn() + } +})) + +describe('useFeatureFlags', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + describe('flags object', () => { + it('should provide reactive readonly flags', () => { + const { flags } = useFeatureFlags() + + expect(isReadonly(flags)).toBe(true) + expect(isReactive(flags)).toBe(true) + }) + + it('should access supportsPreviewMetadata', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (path, defaultValue) => { + if (path === ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA) + return true as any + return defaultValue + } + ) + + const { flags } = useFeatureFlags() + expect(flags.supportsPreviewMetadata).toBe(true) + expect(api.getServerFeature).toHaveBeenCalledWith( + ServerFeatureFlag.SUPPORTS_PREVIEW_METADATA + ) + }) + + it('should access maxUploadSize', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (path, defaultValue) => { + if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) + return 209715200 as any // 200MB + return defaultValue + } + ) + + const { flags } = useFeatureFlags() + expect(flags.maxUploadSize).toBe(209715200) + expect(api.getServerFeature).toHaveBeenCalledWith( + ServerFeatureFlag.MAX_UPLOAD_SIZE + ) + }) + + it('should return undefined when features are not available and no default provided', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (_path, defaultValue) => defaultValue as any + ) + + const { flags } = useFeatureFlags() + expect(flags.supportsPreviewMetadata).toBeUndefined() + expect(flags.maxUploadSize).toBeUndefined() + }) + }) + + describe('featureFlag', () => { + it('should create reactive computed for custom feature flags', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (path, defaultValue) => { + if (path === 'custom.feature') return 'custom-value' as any + return defaultValue + } + ) + + const { featureFlag } = useFeatureFlags() + const customFlag = featureFlag('custom.feature', 'default') + + expect(customFlag.value).toBe('custom-value') + expect(api.getServerFeature).toHaveBeenCalledWith( + 'custom.feature', + 'default' + ) + }) + + it('should handle nested paths', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (path, defaultValue) => { + if (path === 'extension.custom.nested.feature') return true as any + return defaultValue + } + ) + + const { featureFlag } = useFeatureFlags() + const nestedFlag = featureFlag('extension.custom.nested.feature', false) + + expect(nestedFlag.value).toBe(true) + }) + + it('should work with ServerFeatureFlag enum', () => { + vi.mocked(api.getServerFeature).mockImplementation( + (path, defaultValue) => { + if (path === ServerFeatureFlag.MAX_UPLOAD_SIZE) + return 104857600 as any + return defaultValue + } + ) + + const { featureFlag } = useFeatureFlags() + const maxUploadSize = featureFlag(ServerFeatureFlag.MAX_UPLOAD_SIZE) + + expect(maxUploadSize.value).toBe(104857600) + }) + }) +}) From 2e4b510a506abe9b68da54bf369c06f109a59acd Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 9 Aug 2025 12:31:45 -0700 Subject: [PATCH 21/64] [feat] Add v2/ prefix to manager service base URL (#4872) --- src/services/comfyManagerService.ts | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index 79f2dd132b..92092ba02d 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -19,25 +19,25 @@ const GENERIC_SECURITY_ERR_MSG = * API routes for ComfyUI Manager */ enum ManagerRoute { - START_QUEUE = 'v2/manager/queue/start', - RESET_QUEUE = 'v2/manager/queue/reset', - QUEUE_STATUS = 'v2/manager/queue/status', - INSTALL = 'v2/manager/queue/install', - UPDATE = 'v2/manager/queue/update', - UPDATE_ALL = 'v2/manager/queue/update_all', - UNINSTALL = 'v2/manager/queue/uninstall', - DISABLE = 'v2/manager/queue/disable', - FIX_NODE = 'v2/manager/queue/fix', - LIST_INSTALLED = 'v2/customnode/installed', - GET_NODES = 'v2/customnode/getmappings', - GET_PACKS = 'v2/customnode/getlist', - IMPORT_FAIL_INFO = 'v2/customnode/import_fail_info', - REBOOT = 'v2/manager/reboot', - IS_LEGACY_MANAGER_UI = 'v2/manager/is_legacy_manager_ui' + START_QUEUE = 'manager/queue/start', + RESET_QUEUE = 'manager/queue/reset', + QUEUE_STATUS = 'manager/queue/status', + INSTALL = 'manager/queue/install', + UPDATE = 'manager/queue/update', + UPDATE_ALL = 'manager/queue/update_all', + UNINSTALL = 'manager/queue/uninstall', + DISABLE = 'manager/queue/disable', + FIX_NODE = 'manager/queue/fix', + LIST_INSTALLED = 'customnode/installed', + GET_NODES = 'customnode/getmappings', + GET_PACKS = 'customnode/getlist', + IMPORT_FAIL_INFO = 'customnode/import_fail_info', + REBOOT = 'manager/reboot', + IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui' } const managerApiClient = axios.create({ - baseURL: api.apiURL(''), + baseURL: api.apiURL('v2/'), headers: { 'Content-Type': 'application/json' } From 3c3ed2b532ca60974ce31411fdc94b92c98fd1a1 Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Sat, 9 Aug 2025 13:42:30 -0700 Subject: [PATCH 22/64] [cleanup] Remove unused manager route enums (#4875) --- src/services/comfyManagerService.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index 92092ba02d..83f1be0809 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -27,10 +27,9 @@ enum ManagerRoute { UPDATE_ALL = 'manager/queue/update_all', UNINSTALL = 'manager/queue/uninstall', DISABLE = 'manager/queue/disable', + // FIX_NODE is currently unused but kept for potential future implementation FIX_NODE = 'manager/queue/fix', LIST_INSTALLED = 'customnode/installed', - GET_NODES = 'customnode/getmappings', - GET_PACKS = 'customnode/getlist', IMPORT_FAIL_INFO = 'customnode/import_fail_info', REBOOT = 'manager/reboot', IS_LEGACY_MANAGER_UI = 'manager/is_legacy_manager_ui' From 3352c0627d05894a5bf49eafd8b2da773afc403b Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Thu, 21 Aug 2025 13:15:32 +0900 Subject: [PATCH 23/64] fix: v2 prefix (#5145) --- src/services/comfyManagerService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/comfyManagerService.ts b/src/services/comfyManagerService.ts index 83f1be0809..330d5dd0f3 100644 --- a/src/services/comfyManagerService.ts +++ b/src/services/comfyManagerService.ts @@ -36,7 +36,7 @@ enum ManagerRoute { } const managerApiClient = axios.create({ - baseURL: api.apiURL('v2/'), + baseURL: api.apiURL('/v2/'), headers: { 'Content-Type': 'application/json' } From 7d1659c04a96e2e322923499a366185543c53ef0 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Sat, 23 Aug 2025 02:41:39 +0900 Subject: [PATCH 24/64] Fix: Restore api.ts from main branch after incorrect rebase (#5150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: api.ts file is different with main branch * Update locales [skip ci] * fix: restore support dotprop access * fix: apply locales based on manager/menu-items-migration * fix: Add missing shortcuts translation section for CI tests - Added shortcuts section with keyboardShortcuts key - Fixes failing Playwright test looking for 'Keyboard Shortcuts' aria-label - Issue was caused by incomplete rebase from main branch 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude * fix: Add missing versionMismatchWarning translations for CI tests - Added versionMismatchWarning section with all required keys - Added general versionMismatch related keys (updateFrontend, dismiss, etc.) - Fixes failing Playwright tests for version mismatch warnings - These keys were lost during the rebase from main branch 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --------- Co-authored-by: github-actions Co-authored-by: Claude --- src/locales/en/main.json | 27 +++++++++++++++++++++++++++ src/scripts/api.ts | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/locales/en/main.json b/src/locales/en/main.json index 51e94188bd..0c9d79920d 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -98,6 +98,12 @@ "nodes": "Nodes", "community": "Community", "all": "All", + "versionMismatchWarning": "Version Compatibility Warning", + "versionMismatchWarningMessage": "{warning}: {detail} Visit https://docs.comfy.org/installation/update_comfyui#common-update-issues for update instructions.", + "frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires {requiredVersion} or higher.", + "frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.", + "updateFrontend": "Update Frontend", + "dismiss": "Dismiss", "update": "Update", "updated": "Updated", "resultsCount": "Found {count} Results", @@ -431,6 +437,20 @@ "error": "Unable to start ComfyUI Desktop" } }, + "shortcuts": { + "essentials": "Essential", + "viewControls": "View Controls", + "manageShortcuts": "Manage Shortcuts", + "noKeybinding": "No keybinding", + "keyboardShortcuts": "Keyboard Shortcuts", + "subcategories": { + "workflow": "Workflow", + "node": "Node", + "queue": "Queue", + "view": "View", + "panelControls": "Panel Controls" + } + }, "serverConfig": { "modifiedConfigs": "You have modified the following server configurations. Restart to apply changes.", "revertChanges": "Revert Changes", @@ -1362,6 +1382,13 @@ "outdatedVersionGeneric": "Some nodes require a newer version of ComfyUI. Please update to use all nodes.", "coreNodesFromVersion": "Requires ComfyUI {version}:" }, + "versionMismatchWarning": { + "title": "Version Compatibility Warning", + "frontendOutdated": "Frontend version {frontendVersion} is outdated. Backend requires version {requiredVersion} or higher.", + "frontendNewer": "Frontend version {frontendVersion} may not be compatible with backend version {backendVersion}.", + "updateFrontend": "Update Frontend", + "dismiss": "Dismiss" + }, "errorDialog": { "defaultTitle": "An error occurred", "loadWorkflowTitle": "Loading aborted due to error reloading workflow data", diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 46732009c3..857626674f 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -1,5 +1,5 @@ import axios from 'axios' -import get from 'lodash/get' +import { get } from 'es-toolkit/compat' import defaultClientFeatureFlags from '@/config/clientFeatureFlags.json' import type { From 6470869eb1cffa80b76b8e4ec15b7d383bb148b6 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Sat, 23 Aug 2025 13:28:13 +0900 Subject: [PATCH 25/64] feat: Add loading state to PackInstallButton and improve UI (#5153) --- .../workflow-avif-chromium-linux.png | Bin 71504 -> 70554 bytes src/components/button/IconButton.stories.ts | 8 ++ src/components/button/IconButton.vue | 9 +- .../button/IconTextButton.stories.ts | 8 ++ src/components/button/IconTextButton.vue | 9 +- src/components/button/TextButton.stories.ts | 8 ++ src/components/button/TextButton.vue | 9 +- .../dialog/content/LoadWorkflowWarning.vue | 25 ++++- .../content/manager/PackVersionBadge.vue | 5 +- .../manager/button/PackActionButton.vue | 53 ----------- .../manager/button/PackInstallButton.vue | 52 +++++++---- .../manager/button/PackUninstallButton.vue | 15 +-- .../manager/infoPanel/InfoPanelHeader.vue | 17 +++- .../manager/infoPanel/InfoPanelMultiItem.vue | 36 +++++++- .../content/manager/packCard/PackCard.vue | 86 ++++++++---------- .../manager/packCard/PackCardFooter.vue | 10 +- .../registrySearchBar/RegistrySearchBar.vue | 13 ++- src/locales/en/main.json | 3 + src/types/buttonTypes.ts | 53 ++++++++--- 19 files changed, 260 insertions(+), 159 deletions(-) delete mode 100644 src/components/dialog/content/manager/button/PackActionButton.vue diff --git a/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/workflow-avif-chromium-linux.png b/browser_tests/tests/loadWorkflowInMedia.spec.ts-snapshots/workflow-avif-chromium-linux.png index 0dbcabedaaca22f729c75f70fdd5d134125f5b1d..9ca4c0fab0361042dd8013372d6a57aa7adbeb29 100644 GIT binary patch literal 70554 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

8{9g9=am}ntFMrBA4Z{DT%o%c@ag@-Vv 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%0Db-u7+(6i8?CF?ud_kE>aY9|n2idbXBn6B^N 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 NMY$KUr80*9{x9TR1110f literal 71504 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

PzqZ)W;f?e&p`_p+Dp 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 = { control: { type: 'select' }, options: ['primary', 'secondary', 'transparent'] }, + border: { + control: 'boolean', + description: 'Toggle border attribute' + }, + disabled: { + control: 'boolean', + description: 'Toggle disable status' + }, onClick: { action: 'clicked' } } } diff --git a/src/components/button/IconButton.vue b/src/components/button/IconButton.vue index 1a38866f75..1f5b24bac5 100644 --- a/src/components/button/IconButton.vue +++ b/src/components/button/IconButton.vue @@ -1,5 +1,5 @@ @@ -11,6 +11,7 @@ import { computed } from 'vue' import type { BaseButtonProps } from '@/types/buttonTypes' import { getBaseButtonClasses, + getBorderButtonTypeClasses, getButtonTypeClasses, getIconButtonSizeClasses } from '@/types/buttonTypes' @@ -22,6 +23,8 @@ interface IconButtonProps extends BaseButtonProps { const { size = 'md', type = 'secondary', + border = false, + disabled = false, class: className, onClick } = defineProps() @@ -29,7 +32,9 @@ const { const buttonStyle = computed(() => { const baseClasses = `${getBaseButtonClasses()} p-0` const sizeClasses = getIconButtonSizeClasses(size) - const typeClasses = getButtonTypeClasses(type) + const typeClasses = border + ? getBorderButtonTypeClasses(type) + : getButtonTypeClasses(type) return [baseClasses, sizeClasses, typeClasses, className] .filter(Boolean) diff --git a/src/components/button/IconTextButton.stories.ts b/src/components/button/IconTextButton.stories.ts index 3c08c418a9..da07d9a669 100644 --- a/src/components/button/IconTextButton.stories.ts +++ b/src/components/button/IconTextButton.stories.ts @@ -28,6 +28,14 @@ const meta: Meta = { control: { type: 'select' }, options: ['primary', 'secondary', 'transparent'] }, + border: { + control: 'boolean', + description: 'Toggle border attribute' + }, + disabled: { + control: 'boolean', + description: 'Toggle disable status' + }, iconPosition: { control: { type: 'select' }, options: ['left', 'right'] diff --git a/src/components/button/IconTextButton.vue b/src/components/button/IconTextButton.vue index 12aeba3cae..8bcdc3bf1c 100644 --- a/src/components/button/IconTextButton.vue +++ b/src/components/button/IconTextButton.vue @@ -1,5 +1,5 @@ - - diff --git a/src/components/dialog/content/manager/button/PackInstallButton.vue b/src/components/dialog/content/manager/button/PackInstallButton.vue index 2f9751d0fd..c465715291 100644 --- a/src/components/dialog/content/manager/button/PackInstallButton.vue +++ b/src/components/dialog/content/manager/button/PackInstallButton.vue @@ -1,25 +1,28 @@ diff --git a/src/components/dialog/content/manager/button/PackUninstallButton.vue b/src/components/dialog/content/manager/button/PackUninstallButton.vue index 5a740f65f6..aef627f9e5 100644 --- a/src/components/dialog/content/manager/button/PackUninstallButton.vue +++ b/src/components/dialog/content/manager/button/PackUninstallButton.vue @@ -1,27 +1,30 @@ diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue index a4bd12c0b9..910f434086 100644 --- a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue +++ b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue @@ -10,7 +10,19 @@ {{ $t('manager.packsSelected') }}
@@ -31,13 +43,15 @@ diff --git a/src/components/dialog/GlobalDialog.vue b/src/components/dialog/GlobalDialog.vue index a8b60a60c3..6edd13d357 100644 --- a/src/components/dialog/GlobalDialog.vue +++ b/src/components/dialog/GlobalDialog.vue @@ -29,7 +29,7 @@ /> diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue index 18078912d4..ea1316c314 100644 --- a/src/components/dialog/content/manager/ManagerDialogContent.vue +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -26,6 +26,35 @@ }" >
+ +
+ +
+

+ {{ $t('manager.conflicts.warningBanner.title') }} +

+

+ {{ $t('manager.conflicts.warningBanner.message') }} +

+

+ {{ $t('manager.conflicts.warningBanner.button') }} +

+
+ +
@@ -101,7 +133,8 @@ import { onMounted, onUnmounted, ref, - watch + watch, + watchEffect } from 'vue' import { useI18n } from 'vue-i18n' @@ -119,6 +152,7 @@ import { useManagerStatePersistence } from '@/composables/manager/useManagerStat import { useInstalledPacks } from '@/composables/nodePack/useInstalledPacks' import { usePackUpdateStatus } from '@/composables/nodePack/usePackUpdateStatus' import { useWorkflowPacks } from '@/composables/nodePack/useWorkflowPacks' +import { useConflictAcknowledgment } from '@/composables/useConflictAcknowledgment' import { useRegistrySearch } from '@/composables/useRegistrySearch' import { useComfyManagerStore } from '@/stores/comfyManagerStore' import { useComfyRegistryStore } from '@/stores/comfyRegistryStore' @@ -133,6 +167,7 @@ const { initialTab } = defineProps<{ const { t } = useI18n() const comfyManagerStore = useComfyManagerStore() const { getPackById } = useComfyRegistryStore() +const conflictAcknowledgment = useConflictAcknowledgment() const persistedState = useManagerStatePersistence() const initialState = persistedState.loadStoredState() @@ -149,6 +184,13 @@ const { toggle: toggleSideNav } = useResponsiveCollapse() +// Use conflict acknowledgment state from composable +const { + shouldShowManagerBanner, + dismissWarningBanner, + dismissRedDotNotification +} = conflictAcknowledgment + const tabs = ref([ { id: ManagerTab.All, label: t('g.all'), icon: 'pi-list' }, { id: ManagerTab.Installed, label: t('g.installed'), icon: 'pi-box' }, @@ -312,6 +354,13 @@ watch([isAllTab, searchResults], () => { displayPacks.value = searchResults.value }) +const onClickWarningLink = () => { + window.open( + 'https://docs.comfy.org/troubleshooting/custom-node-issues', + '_blank' + ) +} + const onResultsChange = () => { switch (selectedTab.value?.id) { case ManagerTab.Installed: @@ -472,6 +521,10 @@ watch([searchQuery, selectedTab], () => { } }) +watchEffect(() => { + dismissRedDotNotification() +}) + onBeforeUnmount(() => { persistedState.persistState({ selectedTabId: selectedTab.value?.id, diff --git a/src/components/dialog/content/manager/NodeConflictDialogContent.vue b/src/components/dialog/content/manager/NodeConflictDialogContent.vue new file mode 100644 index 0000000000..81d79b3dee --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictDialogContent.vue @@ -0,0 +1,249 @@ + + + + diff --git a/src/components/dialog/content/manager/NodeConflictFooter.vue b/src/components/dialog/content/manager/NodeConflictFooter.vue new file mode 100644 index 0000000000..c76f779080 --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictFooter.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/dialog/content/manager/NodeConflictHeader.vue b/src/components/dialog/content/manager/NodeConflictHeader.vue new file mode 100644 index 0000000000..70e30d129e --- /dev/null +++ b/src/components/dialog/content/manager/NodeConflictHeader.vue @@ -0,0 +1,12 @@ + diff --git a/src/components/dialog/content/manager/PackStatusMessage.vue b/src/components/dialog/content/manager/PackStatusMessage.vue index b31f880e9f..ab2b38a450 100644 --- a/src/components/dialog/content/manager/PackStatusMessage.vue +++ b/src/components/dialog/content/manager/PackStatusMessage.vue @@ -17,9 +17,10 @@ diff --git a/src/components/dialog/content/manager/PackVersionBadge.test.ts b/src/components/dialog/content/manager/PackVersionBadge.test.ts index 9bd8979861..71d3383d81 100644 --- a/src/components/dialog/content/manager/PackVersionBadge.test.ts +++ b/src/components/dialog/content/manager/PackVersionBadge.test.ts @@ -6,11 +6,18 @@ import { nextTick } from 'vue' import { createI18n } from 'vue-i18n' import enMessages from '@/locales/en/main.json' -import { SelectedVersion } from '@/types/comfyManagerTypes' import PackVersionBadge from './PackVersionBadge.vue' import PackVersionSelectorPopover from './PackVersionSelectorPopover.vue' +// Mock config to prevent __COMFYUI_FRONTEND_VERSION__ error +vi.mock('@/config', () => ({ + default: { + app_title: 'ComfyUI', + app_version: '1.0.0' + } +})) + const mockNodePack = { id: 'test-pack', name: 'Test Pack', @@ -120,7 +127,7 @@ describe('PackVersionBadge', () => { const badge = wrapper.find('[role="button"]') expect(badge.exists()).toBe(true) - expect(badge.find('span').text()).toBe(SelectedVersion.NIGHTLY) + expect(badge.find('span').text()).toBe('nightly') }) it('falls back to NIGHTLY when nodePack.id is missing', () => { @@ -134,7 +141,7 @@ describe('PackVersionBadge', () => { const badge = wrapper.find('[role="button"]') expect(badge.exists()).toBe(true) - expect(badge.find('span').text()).toBe(SelectedVersion.NIGHTLY) + expect(badge.find('span').text()).toBe('nightly') }) it('toggles the popover when button is clicked', async () => { diff --git a/src/components/dialog/content/manager/PackVersionBadge.vue b/src/components/dialog/content/manager/PackVersionBadge.vue index 47975937eb..0129ba7efe 100644 --- a/src/components/dialog/content/manager/PackVersionBadge.vue +++ b/src/components/dialog/content/manager/PackVersionBadge.vue @@ -1,8 +1,8 @@