diff --git a/public/assets/images/fallback-gradient-avatar.svg b/public/assets/images/fallback-gradient-avatar.svg new file mode 100644 index 000000000..90b860f61 --- /dev/null +++ b/public/assets/images/fallback-gradient-avatar.svg @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/components/common/ContentDivider.vue b/src/components/common/ContentDivider.vue new file mode 100644 index 000000000..c893fe4d6 --- /dev/null +++ b/src/components/common/ContentDivider.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/components/dialog/content/manager/ManagerDialogContent.vue b/src/components/dialog/content/manager/ManagerDialogContent.vue new file mode 100644 index 000000000..bdd6dedbc --- /dev/null +++ b/src/components/dialog/content/manager/ManagerDialogContent.vue @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + selectNodePack(item, event)" + > + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/dialog/content/manager/ManagerHeader.vue b/src/components/dialog/content/manager/ManagerHeader.vue new file mode 100644 index 000000000..33a5e3fe1 --- /dev/null +++ b/src/components/dialog/content/manager/ManagerHeader.vue @@ -0,0 +1,14 @@ + + + + + {{ $t('manager.discoverCommunityContent') }} + + + + + + + diff --git a/src/components/dialog/content/manager/ManagerNavSidebar.vue b/src/components/dialog/content/manager/ManagerNavSidebar.vue new file mode 100644 index 000000000..1da69fdc8 --- /dev/null +++ b/src/components/dialog/content/manager/ManagerNavSidebar.vue @@ -0,0 +1,50 @@ + + + + + diff --git a/src/components/dialog/content/manager/PackInstallButton.vue b/src/components/dialog/content/manager/PackInstallButton.vue new file mode 100644 index 000000000..3bff8721e --- /dev/null +++ b/src/components/dialog/content/manager/PackInstallButton.vue @@ -0,0 +1,24 @@ + + + + {{ multi ? $t('manager.installSelected') : $t('g.install') }} + + + + + diff --git a/src/components/dialog/content/manager/PackStatusMessage.vue b/src/components/dialog/content/manager/PackStatusMessage.vue new file mode 100644 index 000000000..4d214480f --- /dev/null +++ b/src/components/dialog/content/manager/PackStatusMessage.vue @@ -0,0 +1,82 @@ + + + + {{ $t(`manager.status.${statusLabel}`) }} + + + + diff --git a/src/components/dialog/content/manager/PackVersionBadge.vue b/src/components/dialog/content/manager/PackVersionBadge.vue new file mode 100644 index 000000000..ebd6486fa --- /dev/null +++ b/src/components/dialog/content/manager/PackVersionBadge.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanel.vue b/src/components/dialog/content/manager/infoPanel/InfoPanel.vue new file mode 100644 index 000000000..0c45f60dd --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/InfoPanel.vue @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue new file mode 100644 index 000000000..f2a9bafba --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/InfoPanelMultiItem.vue @@ -0,0 +1,68 @@ + + + + + + + + + {{ nodePacks.length }} + {{ $t('manager.packsSelected') }} + + + + + + + + + + + + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/InfoTabs.vue b/src/components/dialog/content/manager/infoPanel/InfoTabs.vue new file mode 100644 index 000000000..946353f24 --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/InfoTabs.vue @@ -0,0 +1,37 @@ + + + + + {{ $t('g.description') }} + {{ $t('g.nodes') }} + + + + + + + + + + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/InfoTextSection.vue b/src/components/dialog/content/manager/infoPanel/InfoTextSection.vue new file mode 100644 index 000000000..6e71daa14 --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/InfoTextSection.vue @@ -0,0 +1,39 @@ + + + + {{ section.title }} + + + + {{ section.text }} + + + + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/MarkdownText.vue b/src/components/dialog/content/manager/infoPanel/MarkdownText.vue new file mode 100644 index 000000000..a4dcf12e9 --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/MarkdownText.vue @@ -0,0 +1,108 @@ + + + + + + + {{ segment.text }} + + {{ segment.text }} + {{ segment.text }} + {{ segment.text }} + {{ segment.text }} + + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/MetadataRow.vue b/src/components/dialog/content/manager/infoPanel/MetadataRow.vue new file mode 100644 index 000000000..349dc510b --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/MetadataRow.vue @@ -0,0 +1,17 @@ + + + + {{ label }}: + + + {{ value }} + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue b/src/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue new file mode 100644 index 000000000..430d0cc4d --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/tabs/DescriptionTabPanel.vue @@ -0,0 +1,143 @@ + + + + + {{ $t('manager.noDescription') }} + + + + + diff --git a/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue b/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue new file mode 100644 index 000000000..e910dfe65 --- /dev/null +++ b/src/components/dialog/content/manager/infoPanel/tabs/NodesTabPanel.vue @@ -0,0 +1,39 @@ + + + + + + + + + + + + diff --git a/src/components/dialog/content/manager/packCard/PackCard.vue b/src/components/dialog/content/manager/packCard/PackCard.vue new file mode 100644 index 000000000..a0f7a511e --- /dev/null +++ b/src/components/dialog/content/manager/packCard/PackCard.vue @@ -0,0 +1,103 @@ + + + + + + + {{ + $t('manager.nodePack') + }} + + + + + {{ formatNumber(nodePack.downloads) }} + + + + + + + + + + + + + + {{ nodePack.name }} + + + + {{ nodePack.description }} + + + + + + + + + + + {{ nodePack.publisher.name }} + + + {{ nodePack.latest_version.version }} + + + + {{ $t('g.updated') }} + {{ new Date(nodePack.latest_version.createdAt).toLocaleDateString() }} + + + + + + + diff --git a/src/components/dialog/content/manager/packCard/PackCardHeader.vue b/src/components/dialog/content/manager/packCard/PackCardHeader.vue new file mode 100644 index 000000000..34d0a9c17 --- /dev/null +++ b/src/components/dialog/content/manager/packCard/PackCardHeader.vue @@ -0,0 +1,33 @@ + + + + + + + {{ nodePack?.name }} + + + + + + + + + + diff --git a/src/components/dialog/content/manager/packIcon/PackIcon.vue b/src/components/dialog/content/manager/packIcon/PackIcon.vue new file mode 100644 index 000000000..e0bf0e195 --- /dev/null +++ b/src/components/dialog/content/manager/packIcon/PackIcon.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/dialog/content/manager/packIcon/PackIconStacked.vue b/src/components/dialog/content/manager/packIcon/PackIconStacked.vue new file mode 100644 index 000000000..cb0b40131 --- /dev/null +++ b/src/components/dialog/content/manager/packIcon/PackIconStacked.vue @@ -0,0 +1,41 @@ + + + + + + + + + +{{ nodePacks.length - maxVisible }} + + + + + diff --git a/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue b/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue new file mode 100644 index 000000000..0c9921690 --- /dev/null +++ b/src/components/dialog/content/manager/registrySearchBar/RegistrySearchBar.vue @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + {{ $t('g.resultsCount', { count: searchResults.length }) }} + + + + + + + diff --git a/src/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue b/src/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue new file mode 100644 index 000000000..4ac1e72d2 --- /dev/null +++ b/src/components/dialog/content/manager/registrySearchBar/SearchFilterDropdown.vue @@ -0,0 +1,46 @@ + + + {{ label }}: + + + + + diff --git a/src/composables/useCoreCommands.ts b/src/composables/useCoreCommands.ts index 5f50f21f7..5e77023c9 100644 --- a/src/composables/useCoreCommands.ts +++ b/src/composables/useCoreCommands.ts @@ -581,6 +581,15 @@ export function useCoreCommands(): ComfyCommand[] { app.canvas.deleteSelected() app.canvas.setDirty(true, true) } + }, + { + id: 'Comfy.Manager.CustomNodesManager', + icon: 'pi pi-puzzle', + label: 'Custom Nodes Manager', + versionAdded: '1.12.10', + function: () => { + dialogService.showManagerDialog() + } } ] } diff --git a/src/composables/useRegistrySearch.ts b/src/composables/useRegistrySearch.ts index 761856158..b322f19b1 100644 --- a/src/composables/useRegistrySearch.ts +++ b/src/composables/useRegistrySearch.ts @@ -7,6 +7,7 @@ import type { components } from '@/types/comfyRegistryTypes' const SEARCH_DEBOUNCE_TIME = 256 const DEFAULT_PAGE_SIZE = 60 +const DEFAULT_SORT_FIELD: keyof components['schemas']['Node'] = 'downloads' /** * Composable for managing UI state of Comfy Node Registry search. @@ -18,6 +19,7 @@ export function useRegistrySearch() { const searchQuery = ref('') const pageNumber = ref(1) const pageSize = ref(DEFAULT_PAGE_SIZE) + const sortField = ref(DEFAULT_SORT_FIELD) const searchResults = ref([]) const search = async () => { @@ -26,7 +28,8 @@ export function useRegistrySearch() { const result = isEmptySearch ? await registryStore.listAllPacks({ page: pageNumber.value, - limit: pageSize.value + limit: pageSize.value, + sort: [sortField.value] }) : await registryService.search({ search: searchQuery.value, @@ -45,12 +48,12 @@ export function useRegistrySearch() { } } - // Debounce search when query changes const debouncedSearch = debounce(search, SEARCH_DEBOUNCE_TIME) + + // Debounce search when query changes watch(() => searchQuery.value, debouncedSearch) - // Normal search when page number changes and on load - watch(() => pageNumber.value, search, { immediate: true }) + watch(() => [pageNumber.value, sortField.value], search, { immediate: true }) onUnmounted(() => { debouncedSearch.cancel() // Cancel debounced searches @@ -61,6 +64,7 @@ export function useRegistrySearch() { return { pageNumber, pageSize, + sortField, searchQuery, searchResults, isLoading: registryService.isLoading, diff --git a/src/locales/en/commands.json b/src/locales/en/commands.json index 4bdb3518c..c48685efb 100644 --- a/src/locales/en/commands.json +++ b/src/locales/en/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "Load Default Workflow" }, + "Comfy_Manager_CustomNodesManager": { + "label": "Custom Nodes Manager" + }, "Comfy_NewBlankWorkflow": { "label": "New Blank Workflow" }, diff --git a/src/locales/en/main.json b/src/locales/en/main.json index d173aeeab..561ee49be 100644 --- a/src/locales/en/main.json +++ b/src/locales/en/main.json @@ -78,7 +78,55 @@ "control_after_generate": "control after generate", "control_before_generate": "control before generate", "choose_file_to_upload": "choose file to upload", - "capture": "capture" + "capture": "capture", + "nodes": "Nodes", + "community": "Community", + "all": "All", + "updated": "Updated", + "resultsCount": "Found {count} Results", + "status": "Status", + "description": "Description", + "name": "Name", + "category": "Category", + "sort": "Sort", + "filter": "Filter" + }, + "manager": { + "title": "Custom Nodes Manager", + "downloads": "Downloads", + "repository": "Repository", + "license": "License", + "createdBy": "Created By", + "totalNodes": "Total Nodes", + "discoverCommunityContent": "Discover community-made Node Packs, Extensions, and more...", + "errorConnecting": "Error connecting to the Comfy Node Registry.", + "noResultsFound": "No results found matching your search.", + "tryDifferentSearch": "Please try a different search query.", + "tryAgainLater": "Please try again later.", + "nodePack": "Node Pack", + "searchPlaceholder": "Search", + "version": "Version", + "lastUpdated": "Last Updated", + "noDescription": "No description available", + "installSelected": "Install Selected", + "packsSelected": "Packs Selected", + "status": { + "active": "Active", + "pending": "Pending", + "flagged": "Flagged", + "deleted": "Deleted", + "banned": "Banned", + "unknown": "Unknown" + }, + "sort": { + "rating": "Rating", + "downloads": "Most Popular" + }, + "filter": { + "nodePack": "Node Pack", + "enabled": "Enabled", + "disabled": "Disabled" + } }, "issueReport": { "submitErrorReport": "Submit Error Report (Optional)", @@ -512,6 +560,7 @@ "ComfyUI Issues": "ComfyUI Issues", "Interrupt": "Interrupt", "Load Default Workflow": "Load Default Workflow", + "Custom Nodes Manager": "Custom Nodes Manager", "New": "New", "Clipspace": "Clipspace", "Open": "Open", diff --git a/src/locales/fr/commands.json b/src/locales/fr/commands.json index 0a634ee5f..2b446e1d3 100644 --- a/src/locales/fr/commands.json +++ b/src/locales/fr/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "Charger le flux de travail par défaut" }, + "Comfy_Manager_CustomNodesManager": { + "label": "Gestionnaire de Nœuds Personnalisés" + }, "Comfy_NewBlankWorkflow": { "label": "Nouveau flux de travail vierge" }, diff --git a/src/locales/fr/main.json b/src/locales/fr/main.json index 3af61afd1..f8cbc082d 100644 --- a/src/locales/fr/main.json +++ b/src/locales/fr/main.json @@ -116,14 +116,17 @@ "g": { "about": "À propos", "add": "Ajouter", + "all": "Tout", "back": "Retour", "cancel": "Annuler", "capture": "capture", + "category": "Catégorie", "choose_file_to_upload": "choisissez le fichier à télécharger", "close": "Fermer", "color": "Couleur", "comingSoon": "Bientôt disponible", "command": "Commande", + "community": "Communauté", "confirm": "Confirmer", "continue": "Continuer", "control_after_generate": "contrôle après génération", @@ -134,6 +137,7 @@ "customizeFolder": "Personnaliser le dossier", "delete": "Supprimer", "deprecated": "DEPR", + "description": "Description", "devices": "Appareils", "disableAll": "Désactiver tout", "download": "Télécharger", @@ -144,6 +148,7 @@ "export": "Exportation", "extensionName": "Nom de l'extension", "feedback": "Commentaires", + "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.", "goToNode": "Aller au nœud", @@ -157,6 +162,7 @@ "loadWorkflow": "Charger le flux de travail", "loading": "Chargement", "logs": "Journaux", + "name": "Nom", "newFolder": "Nouveau dossier", "next": "Suivant", "no": "Non", @@ -164,6 +170,7 @@ "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", "ok": "OK", "openNewIssue": "Ouvrir un nouveau problème", "overwrite": "Écraser", @@ -177,6 +184,7 @@ "reportSent": "Rapport soumis", "reset": "Réinitialiser", "resetKeybindingsTooltip": "Réinitialiser les raccourcis clavier par défaut", + "resultsCount": "{count} Résultats Trouvés", "save": "Enregistrer", "searchExtensions": "Rechercher des extensions", "searchFailedMessage": "Nous n'avons trouvé aucun paramètre correspondant à votre recherche. Essayez d'ajuster vos termes de recherche.", @@ -187,9 +195,12 @@ "searchWorkflows": "Rechercher des flux de travail", "settings": "Paramètres", "showReport": "Afficher le rapport", + "sort": "Trier", + "status": "Statut", "success": "Succès", "systemInfo": "Informations système", "terminal": "Terminal", + "updated": "Mis à jour", "upload": "Téléverser", "videoFailedToLoad": "Échec du chargement de la vidéo", "workflow": "Flux de travail" @@ -359,6 +370,43 @@ "terminalDefaultMessage": "Lorsque vous exécutez une commande de dépannage, toute sortie sera affichée ici.", "title": "Maintenance" }, + "manager": { + "createdBy": "Créé par", + "discoverCommunityContent": "Découvrez les packs de nœuds, extensions et plus encore créés par la communauté...", + "downloads": "Téléchargements", + "errorConnecting": "Erreur de connexion au registre de nœuds Comfy.", + "filter": { + "disabled": "Désactivé", + "enabled": "Activé", + "nodePack": "Pack de Nœuds" + }, + "installSelected": "Installer sélectionné", + "lastUpdated": "Dernière mise à jour", + "license": "Licence", + "noDescription": "Aucune description disponible", + "noResultsFound": "Aucun résultat trouvé correspondant à votre recherche.", + "nodePack": "Pack de Nœuds", + "packsSelected": "Packs sélectionnés", + "repository": "Référentiel", + "searchPlaceholder": "Recherche", + "sort": { + "downloads": "Le plus populaire", + "rating": "Évaluation" + }, + "status": { + "active": "Actif", + "banned": "Banni", + "deleted": "Supprimé", + "flagged": "Signalé", + "pending": "En attente", + "unknown": "Inconnu" + }, + "title": "Gestionnaire de Nœuds Personnalisés", + "totalNodes": "Total de Nœuds", + "tryAgainLater": "Veuillez réessayer plus tard.", + "tryDifferentSearch": "Veuillez essayer une autre requête de recherche.", + "version": "Version" + }, "menu": { "autoQueue": "File d'attente automatique", "batchCount": "Nombre de lots", @@ -398,6 +446,7 @@ "ComfyUI Forum": "Forum ComfyUI", "ComfyUI Issues": "Problèmes de ComfyUI", "Convert selected nodes to group node": "Convertir les nœuds sélectionnés en nœud de groupe", + "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", diff --git a/src/locales/ja/commands.json b/src/locales/ja/commands.json index fdbed6af9..76b9919ca 100644 --- a/src/locales/ja/commands.json +++ b/src/locales/ja/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "デフォルトのワークフローを読み込む" }, + "Comfy_Manager_CustomNodesManager": { + "label": "カスタムノードマネージャ" + }, "Comfy_NewBlankWorkflow": { "label": "新しい空のワークフロー" }, diff --git a/src/locales/ja/main.json b/src/locales/ja/main.json index 54352d5b1..89b7f38ed 100644 --- a/src/locales/ja/main.json +++ b/src/locales/ja/main.json @@ -116,14 +116,17 @@ "g": { "about": "情報", "add": "追加", + "all": "すべて", "back": "戻る", "cancel": "キャンセル", "capture": "キャプチャ", + "category": "カテゴリ", "choose_file_to_upload": "アップロードするファイルを選択", "close": "閉じる", "color": "色", "comingSoon": "近日公開", "command": "コマンド", + "community": "コミュニティ", "confirm": "確認", "continue": "続ける", "control_after_generate": "生成後の制御", @@ -134,6 +137,7 @@ "customizeFolder": "フォルダーをカスタマイズ", "delete": "削除", "deprecated": "非推奨", + "description": "説明", "devices": "デバイス", "disableAll": "すべて無効にする", "download": "ダウンロード", @@ -144,6 +148,7 @@ "export": "エクスポート", "extensionName": "拡張機能名", "feedback": "フィードバック", + "filter": "フィルタ", "findIssues": "問題を見つける", "firstTimeUIMessage": "新しいUIを初めて使用しています。「メニュー > 新しいメニューを使用 > 無効」を選択することで古いUIに戻すことが可能です。", "goToNode": "ノードに移動", @@ -157,6 +162,7 @@ "loadWorkflow": "ワークフローを読み込む", "loading": "読み込み中", "logs": "ログ", + "name": "名前", "newFolder": "新しいフォルダー", "next": "次へ", "no": "いいえ", @@ -164,6 +170,7 @@ "noTasksFound": "タスクが見つかりません", "noTasksFoundMessage": "キューにタスクがありません。", "noWorkflowsFound": "ワークフローが見つかりません。", + "nodes": "ノード", "ok": "OK", "openNewIssue": "新しい問題を開く", "overwrite": "上書き", @@ -177,6 +184,7 @@ "reportSent": "レポートが送信されました", "reset": "リセット", "resetKeybindingsTooltip": "キーバインディングをデフォルトにリセット", + "resultsCount": "{count}件の結果が見つかりました", "save": "保存", "searchExtensions": "拡張機能を検索", "searchFailedMessage": "検索に一致する設定が見つかりませんでした。検索キーワードを調整してみてください。", @@ -187,9 +195,12 @@ "searchWorkflows": "ワークフローを検索", "settings": "設定", "showReport": "レポートを表示", + "sort": "並び替え", + "status": "ステータス", "success": "成功", "systemInfo": "システム情報", "terminal": "ターミナル", + "updated": "更新済み", "upload": "アップロード", "videoFailedToLoad": "ビデオの読み込みに失敗しました", "workflow": "ワークフロー" @@ -359,6 +370,43 @@ "terminalDefaultMessage": "トラブルシューティングコマンドを実行すると、出力はここに表示されます。", "title": "メンテナンス" }, + "manager": { + "createdBy": "作成者", + "discoverCommunityContent": "コミュニティが作成したノードパック、拡張機能などを探す...", + "downloads": "ダウンロード", + "errorConnecting": "Comfy Node Registryへの接続エラー。", + "filter": { + "disabled": "無効", + "enabled": "有効", + "nodePack": "ノードパック" + }, + "installSelected": "選択したものをインストール", + "lastUpdated": "最終更新日", + "license": "ライセンス", + "noDescription": "説明はありません", + "noResultsFound": "検索に一致する結果が見つかりませんでした。", + "nodePack": "ノードパック", + "packsSelected": "選択したパック", + "repository": "リポジトリ", + "searchPlaceholder": "検索", + "sort": { + "downloads": "最も人気", + "rating": "評価" + }, + "status": { + "active": "アクティブ", + "banned": "禁止", + "deleted": "削除済み", + "flagged": "フラグ付き", + "pending": "保留中", + "unknown": "不明" + }, + "title": "カスタムノードマネージャ", + "totalNodes": "合計ノード数", + "tryAgainLater": "後ほど再試行してください。", + "tryDifferentSearch": "別の検索クエリを試してみてください。", + "version": "バージョン" + }, "menu": { "autoQueue": "自動キュー", "batchCount": "バッチ数", @@ -398,6 +446,7 @@ "ComfyUI Forum": "ComfyUI フォーラム", "ComfyUI Issues": "ComfyUIの問題", "Convert selected nodes to group node": "選択したノードをグループノードに変換", + "Custom Nodes Manager": "カスタムノードマネージャ", "Delete Selected Items": "選択したアイテムを削除", "Desktop User Guide": "デスクトップユーザーガイド", "Duplicate Current Workflow": "現在のワークフローを複製", diff --git a/src/locales/ko/commands.json b/src/locales/ko/commands.json index 01873fb90..4653e4c86 100644 --- a/src/locales/ko/commands.json +++ b/src/locales/ko/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "기본 워크플로 로드" }, + "Comfy_Manager_CustomNodesManager": { + "label": "사용자 정의 노드 관리자" + }, "Comfy_NewBlankWorkflow": { "label": "새로운 빈 워크플로" }, diff --git a/src/locales/ko/main.json b/src/locales/ko/main.json index 81ba1b15b..4d0e91d2d 100644 --- a/src/locales/ko/main.json +++ b/src/locales/ko/main.json @@ -116,14 +116,17 @@ "g": { "about": "정보", "add": "추가", + "all": "모두", "back": "뒤로", "cancel": "취소", "capture": "캡처", + "category": "카테고리", "choose_file_to_upload": "업로드할 파일 선택", "close": "닫기", "color": "색상", "comingSoon": "곧 출시 예정", "command": "명령", + "community": "커뮤니티", "confirm": "확인", "continue": "계속", "control_after_generate": "생성 후 제어", @@ -134,6 +137,7 @@ "customizeFolder": "폴더 사용자 정의", "delete": "삭제", "deprecated": "사용 중단", + "description": "설명", "devices": "장치", "disableAll": "모두 비활성화", "download": "다운로드", @@ -144,6 +148,7 @@ "export": "내보내기", "extensionName": "확장 이름", "feedback": "피드백", + "filter": "필터", "findIssues": "문제 찾기", "firstTimeUIMessage": "새 UI를 처음 사용합니다. \"메뉴 > 새 메뉴 사용 > 비활성화\"를 선택하여 이전 UI로 복원하세요.", "goToNode": "노드로 이동", @@ -157,6 +162,7 @@ "loadWorkflow": "워크플로 로드", "loading": "로딩 중", "logs": "로그", + "name": "이름", "newFolder": "새 폴더", "next": "다음", "no": "아니오", @@ -164,6 +170,7 @@ "noTasksFound": "작업을 찾을 수 없습니다.", "noTasksFoundMessage": "대기열에 작업이 없습니다.", "noWorkflowsFound": "워크플로를 찾을 수 없습니다.", + "nodes": "노드", "ok": "확인", "openNewIssue": "새 문제 열기", "overwrite": "덮어쓰기", @@ -177,6 +184,7 @@ "reportSent": "보고서 제출됨", "reset": "재설정", "resetKeybindingsTooltip": "키 바인딩을 기본값으로 재설정", + "resultsCount": "{count} 개의 결과를 찾았습니다", "save": "저장", "searchExtensions": "확장 검색", "searchFailedMessage": "검색어와 일치하는 설정을 찾을 수 없습니다. 검색어를 조정해 보세요.", @@ -187,9 +195,12 @@ "searchWorkflows": "워크플로 검색", "settings": "설정", "showReport": "보고서 보기", + "sort": "정렬", + "status": "상태", "success": "성공", "systemInfo": "시스템 정보", "terminal": "터미널", + "updated": "업데이트됨", "upload": "업로드", "videoFailedToLoad": "비디오를 로드하지 못했습니다.", "workflow": "워크플로" @@ -359,6 +370,43 @@ "terminalDefaultMessage": "문제 해결 명령을 실행하면 출력이 여기에 표시됩니다.", "title": "유지 보수" }, + "manager": { + "createdBy": "작성자", + "discoverCommunityContent": "커뮤니티에서 만든 노드 팩, 확장 프로그램 등을 찾아보세요...", + "downloads": "다운로드", + "errorConnecting": "Comfy Node Registry에 연결하는 중 오류가 발생했습니다.", + "filter": { + "disabled": "비활성화", + "enabled": "활성화", + "nodePack": "노드 팩" + }, + "installSelected": "선택한 항목 설치", + "lastUpdated": "마지막 업데이트", + "license": "라이선스", + "noDescription": "설명이 없습니다", + "noResultsFound": "검색과 일치하는 결과가 없습니다.", + "nodePack": "노드 팩", + "packsSelected": "선택한 팩", + "repository": "저장소", + "searchPlaceholder": "검색", + "sort": { + "downloads": "가장 인기 있는", + "rating": "평점" + }, + "status": { + "active": "활성", + "banned": "금지됨", + "deleted": "삭제됨", + "flagged": "플래그 표시됨", + "pending": "대기 중", + "unknown": "알 수 없음" + }, + "title": "사용자 정의 노드 관리자", + "totalNodes": "총 노드", + "tryAgainLater": "나중에 다시 시도해 주세요.", + "tryDifferentSearch": "다른 검색어를 시도해 주세요.", + "version": "버전" + }, "menu": { "autoQueue": "자동 실행 큐", "batchCount": "배치 수", @@ -398,6 +446,7 @@ "ComfyUI Forum": "ComfyUI 포럼", "ComfyUI Issues": "ComfyUI 이슈 페이지", "Convert selected nodes to group node": "선택한 노드를 그룹 노드로 변환", + "Custom Nodes Manager": "사용자 정의 노드 관리자", "Delete Selected Items": "선택한 항목 삭제", "Desktop User Guide": "데스크톱 사용자 가이드", "Duplicate Current Workflow": "현재 워크플로 복제", diff --git a/src/locales/ru/commands.json b/src/locales/ru/commands.json index af2cda7ee..45d2a5867 100644 --- a/src/locales/ru/commands.json +++ b/src/locales/ru/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "Загрузить стандартный рабочий процесс" }, + "Comfy_Manager_CustomNodesManager": { + "label": "Менеджер Пользовательских Узлов" + }, "Comfy_NewBlankWorkflow": { "label": "Новый пустой рабочий процесс" }, diff --git a/src/locales/ru/main.json b/src/locales/ru/main.json index f1ed3f25d..365bf73b9 100644 --- a/src/locales/ru/main.json +++ b/src/locales/ru/main.json @@ -116,14 +116,17 @@ "g": { "about": "О программе", "add": "Добавить", + "all": "Все", "back": "Назад", "cancel": "Отмена", "capture": "захват", + "category": "Категория", "choose_file_to_upload": "выберите файл для загрузки", "close": "Закрыть", "color": "Цвет", "comingSoon": "Скоро будет", "command": "Команда", + "community": "Сообщество", "confirm": "Подтвердить", "continue": "Продолжить", "control_after_generate": "управление после генерации", @@ -134,6 +137,7 @@ "customizeFolder": "Настроить папку", "delete": "Удалить", "deprecated": "Устарело", + "description": "Описание", "devices": "Устройства", "disableAll": "Отключить все", "download": "Скачать", @@ -144,6 +148,7 @@ "export": "Экспорт", "extensionName": "Название расширения", "feedback": "Обратная связь", + "filter": "Фильтр", "findIssues": "Найти проблемы", "firstTimeUIMessage": "Вы впервые используете новый интерфейс. Выберите \"Меню > Использовать новое меню > Отключено\", чтобы восстановить старый интерфейс.", "goToNode": "Перейти к ноде", @@ -157,6 +162,7 @@ "loadWorkflow": "Загрузить рабочий процесс", "loading": "Загрузка", "logs": "Логи", + "name": "Имя", "newFolder": "Новая папка", "next": "Далее", "no": "Нет", @@ -164,6 +170,7 @@ "noTasksFound": "Задачи не найдены", "noTasksFoundMessage": "В очереди нет задач.", "noWorkflowsFound": "Рабочие процессы не найдены.", + "nodes": "Узлы", "ok": "ОК", "openNewIssue": "Открыть новую проблему", "overwrite": "Перезаписать", @@ -177,6 +184,7 @@ "reportSent": "Отчёт отправлен", "reset": "Сбросить", "resetKeybindingsTooltip": "Сбросить сочетания клавиш по умолчанию", + "resultsCount": "Найдено {count} результатов", "save": "Сохранить", "searchExtensions": "Поиск расширений", "searchFailedMessage": "Мы не смогли найти настройки, соответствующие вашему запросу. Попробуйте изменить поисковые термины.", @@ -187,9 +195,12 @@ "searchWorkflows": "Поиск рабочих процессов", "settings": "Настройки", "showReport": "Показать отчёт", + "sort": "Сортировать", + "status": "Статус", "success": "Успех", "systemInfo": "Информация о системе", "terminal": "Терминал", + "updated": "Обновлено", "upload": "Загрузить", "videoFailedToLoad": "Не удалось загрузить видео", "workflow": "Рабочий процесс" @@ -359,6 +370,43 @@ "terminalDefaultMessage": "Когда вы запускаете команду для устранения неполадок, любой вывод будет отображаться здесь.", "title": "Обслуживание" }, + "manager": { + "createdBy": "Создано", + "discoverCommunityContent": "Откройте для себя пакеты узлов, расширения и многое другое, созданные сообществом...", + "downloads": "Загрузки", + "errorConnecting": "Ошибка подключения к реестру Comfy Node.", + "filter": { + "disabled": "Отключено", + "enabled": "Включено", + "nodePack": "Пакет Узлов" + }, + "installSelected": "Установить выбранное", + "lastUpdated": "Последнее обновление", + "license": "Лицензия", + "noDescription": "Описание отсутствует", + "noResultsFound": "По вашему запросу ничего не найдено.", + "nodePack": "Пакет Узлов", + "packsSelected": "Выбрано пакетов", + "repository": "Репозиторий", + "searchPlaceholder": "Поиск", + "sort": { + "downloads": "Самые популярные", + "rating": "Рейтинг" + }, + "status": { + "active": "Активный", + "banned": "Заблокировано", + "deleted": "Удалено", + "flagged": "Отмечено", + "pending": "В ожидании", + "unknown": "Неизвестно" + }, + "title": "Менеджер Пользовательских Узлов", + "totalNodes": "Всего Узлов", + "tryAgainLater": "Пожалуйста, попробуйте позже.", + "tryDifferentSearch": "Пожалуйста, попробуйте изменить запрос.", + "version": "Версия" + }, "menu": { "autoQueue": "Автоочередь", "batchCount": "Количество пакетов", @@ -398,6 +446,7 @@ "ComfyUI Forum": "Форум ComfyUI", "ComfyUI Issues": "Проблемы ComfyUI", "Convert selected nodes to group node": "Преобразовать выбранные ноды в групповую ноду", + "Custom Nodes Manager": "Менеджер Пользовательских Узлов", "Delete Selected Items": "Удалить выбранные элементы", "Desktop User Guide": "Руководство пользователя для настольных ПК", "Duplicate Current Workflow": "Дублировать текущий рабочий процесс", diff --git a/src/locales/zh/commands.json b/src/locales/zh/commands.json index 082586fa9..d4a7cf93e 100644 --- a/src/locales/zh/commands.json +++ b/src/locales/zh/commands.json @@ -125,6 +125,9 @@ "Comfy_LoadDefaultWorkflow": { "label": "加载默认工作流" }, + "Comfy_Manager_CustomNodesManager": { + "label": "自定义节点管理器" + }, "Comfy_NewBlankWorkflow": { "label": "新建空白工作流" }, diff --git a/src/locales/zh/main.json b/src/locales/zh/main.json index 045ad875c..6be7615e2 100644 --- a/src/locales/zh/main.json +++ b/src/locales/zh/main.json @@ -116,14 +116,17 @@ "g": { "about": "关于", "add": "添加", + "all": "全部", "back": "返回", "cancel": "取消", "capture": "捕获", + "category": "类别", "choose_file_to_upload": "选择要上传的文件", "close": "关闭", "color": "颜色", "comingSoon": "即将推出", "command": "指令", + "community": "社区", "confirm": "确认", "continue": "继续", "control_after_generate": "生成后控制", @@ -134,6 +137,7 @@ "customizeFolder": "自定义文件夹", "delete": "删除", "deprecated": "已弃用", + "description": "描述", "devices": "设备", "disableAll": "禁用全部", "download": "下载", @@ -144,6 +148,7 @@ "export": "导出", "extensionName": "扩展名称", "feedback": "反馈", + "filter": "过滤", "findIssues": "查找问题", "firstTimeUIMessage": "这是您第一次使用新界面。选择 \"菜单 > 使用新菜单 > 禁用\" 来恢复旧界面。", "goToNode": "转到节点", @@ -157,6 +162,7 @@ "loadWorkflow": "加载工作流", "loading": "加载中", "logs": "日志", + "name": "名称", "newFolder": "新文件夹", "next": "下一个", "no": "否", @@ -164,6 +170,7 @@ "noTasksFound": "未找到任务", "noTasksFoundMessage": "队列中没有任务。", "noWorkflowsFound": "未找到工作流。", + "nodes": "节点", "ok": "确定", "openNewIssue": "打开新问题", "overwrite": "覆盖", @@ -177,6 +184,7 @@ "reportSent": "报告已提交", "reset": "重置", "resetKeybindingsTooltip": "将快捷键重置为默认", + "resultsCount": "找到 {count} 个结果", "save": "保存", "searchExtensions": "搜索扩展", "searchFailedMessage": "我们找不到任何与您的搜索匹配的设置。请尝试调整您的搜索词。", @@ -187,9 +195,12 @@ "searchWorkflows": "搜索工作流", "settings": "设置", "showReport": "显示报告", + "sort": "排序", + "status": "状态", "success": "成功", "systemInfo": "系统信息", "terminal": "终端", + "updated": "已更新", "upload": "上传", "videoFailedToLoad": "视频加载失败", "workflow": "工作流" @@ -359,6 +370,43 @@ "terminalDefaultMessage": "当你运行一个故障排除命令时,任何输出都会在这里显示。", "title": "维护" }, + "manager": { + "createdBy": "创建者", + "discoverCommunityContent": "发现社区制作的节点包,扩展等等...", + "downloads": "下载", + "errorConnecting": "连接到Comfy节点注册表时出错。", + "filter": { + "disabled": "已禁用", + "enabled": "已启用", + "nodePack": "节点包" + }, + "installSelected": "安装选定", + "lastUpdated": "最后更新", + "license": "许可证", + "noDescription": "无可用描述", + "noResultsFound": "未找到符合您搜索的结果。", + "nodePack": "节点包", + "packsSelected": "选定的包", + "repository": "仓库", + "searchPlaceholder": "搜索", + "sort": { + "downloads": "最受欢迎", + "rating": "评级" + }, + "status": { + "active": "活跃", + "banned": "已禁止", + "deleted": "已删除", + "flagged": "已标记", + "pending": "待定", + "unknown": "未知" + }, + "title": "自定义节点管理器", + "totalNodes": "节点总数", + "tryAgainLater": "请稍后再试。", + "tryDifferentSearch": "请尝试不同的搜索查询。", + "version": "版本" + }, "menu": { "autoQueue": "自动执行", "batchCount": "批次数量", @@ -398,6 +446,7 @@ "ComfyUI Forum": "ComfyUI 论坛", "ComfyUI Issues": "ComfyUI 问题", "Convert selected nodes to group node": "将选中节点转换为组节点", + "Custom Nodes Manager": "自定义节点管理器", "Delete Selected Items": "删除选定的项目", "Desktop User Guide": "桌面端用户指南", "Duplicate Current Workflow": "复制当前工作流", diff --git a/src/services/comfyRegistryService.ts b/src/services/comfyRegistryService.ts index 15ce08bc7..df120108c 100644 --- a/src/services/comfyRegistryService.ts +++ b/src/services/comfyRegistryService.ts @@ -23,6 +23,9 @@ export const useComfyRegistryService = () => { const nodeDefStore = useNodeDefStore() + const isLocalNodePack = (nodePackId: string) => + !!nodeDefStore.nodeDefsByName[nodePackId] + const isLocalNode = (nodeName: string, nodePackId: string) => { if (!nodeDefStore.nodeDefsByName[nodeName]) return false return ( @@ -31,6 +34,16 @@ export const useComfyRegistryService = () => { ) } + /** + * Check if the node definitions for the pack are available + */ + const packNodesAvailable = (node: components['schemas']['Node']) => { + if (node.id && isLocalNodePack(node.id)) return true + if (node.latest_version?.comfy_node_extract_status !== 'success') + return false + return true + } + const handleApiError = ( err: unknown, context: string, @@ -100,24 +113,23 @@ export const useComfyRegistryService = () => { } /** - * Get Comfy Node definition for a specific node in a specific version of a node pack + * Get the Comfy Node definitions in a specific version of a node pack * @param packId - The ID of the node pack * @param versionId - The version of the node pack - * @param comfyNodeName - The name of the comfy node (corresponds to `ComfyNodeDef#name`) - * @returns The node definition or null if not found or an error occurred + * @returns The node definitions or null if not found or an error occurred */ - const getNodeDef = async ( - packId: components['schemas']['Node']['id'], - versionId: components['schemas']['NodeVersion']['id'], - comfyNodeName: components['schemas']['ComfyNode']['comfy_node_name'], + const getNodeDefs = async ( + params: { + packId: components['schemas']['Node']['id'] + versionId: components['schemas']['NodeVersion']['id'] + }, signal?: AbortSignal ) => { - if (!comfyNodeName || !packId) return null - if (isLocalNode(comfyNodeName, packId)) - return nodeDefStore.nodeDefsByName[comfyNodeName] + const { packId, versionId } = params + if (!packId || !versionId) return null - const endpoint = `/nodes/${packId}/versions/${versionId}/comfy-nodes/${comfyNodeName}` - const errorContext = 'Failed to get node definition' + const endpoint = `/nodes/${packId}/versions/${versionId}/comfy-nodes` + const errorContext = 'Failed to get node definitions' const routeSpecificErrors = { 403: 'This pack has been banned and its definition is not available', 404: 'The requested node, version, or comfy node does not exist' @@ -125,7 +137,7 @@ export const useComfyRegistryService = () => { return executeApiRequest( () => - registryApiClient.get(endpoint, { + registryApiClient.get(endpoint, { signal }), errorContext, @@ -133,6 +145,33 @@ export const useComfyRegistryService = () => { ) } + /** + * Get a Comfy Node definition for a specific node in a specific version of a node pack + * @param packId - The ID of the node pack + * @param versionId - The version of the node pack + * @param comfyNodeName - The name of the comfy node (corresponds to `ComfyNodeDef#name`) + * @returns The node definition or null if not found or an error occurred + */ + const getNodeDef = async ( + params: { + packId: components['schemas']['Node']['id'] + versionId: components['schemas']['NodeVersion']['id'] + comfyNodeName: components['schemas']['ComfyNode']['comfy_node_name'] + }, + signal?: AbortSignal + ) => { + const { packId, versionId, comfyNodeName } = params + if (!comfyNodeName || !packId || !versionId) return null + if (isLocalNode(comfyNodeName, packId)) + return nodeDefStore.nodeDefsByName[comfyNodeName] + + const nodeDefs = await getNodeDefs({ packId, versionId }, signal) + return ( + nodeDefs?.find((nodeDef) => nodeDef.comfy_node_name === comfyNodeName) || + null + ) + } + /** * Get a paginated list of packs matching specific criteria. * Search packs using `search` param. Search individual nodes using `comfy_node_search` param. @@ -335,6 +374,8 @@ export const useComfyRegistryService = () => { getPublisherById, listPacksForPublisher, getNodeDef, - postPackReview + getNodeDefs, + postPackReview, + packNodesAvailable } } diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index 5c956922f..adc49a9d9 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -5,6 +5,8 @@ import LoadWorkflowWarning from '@/components/dialog/content/LoadWorkflowWarning import MissingModelsWarning from '@/components/dialog/content/MissingModelsWarning.vue' import PromptDialogContent from '@/components/dialog/content/PromptDialogContent.vue' import SettingDialogContent from '@/components/dialog/content/SettingDialogContent.vue' +import ManagerDialogContent from '@/components/dialog/content/manager/ManagerDialogContent.vue' +import ManagerHeader from '@/components/dialog/content/manager/ManagerHeader.vue' import SettingDialogHeader from '@/components/dialog/header/SettingDialogHeader.vue' import TemplateWorkflowsContent from '@/components/templates/TemplateWorkflowsContent.vue' import TemplateWorkflowsDialogHeader from '@/components/templates/TemplateWorkflowsDialogHeader.vue' @@ -101,6 +103,24 @@ export const useDialogService = () => { }) } + function showManagerDialog( + props: InstanceType['$props'] = {} + ) { + dialogStore.showDialog({ + key: 'global-manager', + component: ManagerDialogContent, + headerComponent: ManagerHeader, + dialogComponentProps: { + closable: false, + pt: { + header: { class: '!p-0 !m-0' }, + content: { class: '!px-0 h-[83vh] w-[90vw] overflow-y-hidden' } + } + }, + props + }) + } + async function prompt({ title, message, @@ -182,6 +202,7 @@ export const useDialogService = () => { showExecutionErrorDialog, showTemplateWorkflowsDialog, showIssueReportDialog, + showManagerDialog, prompt, confirm } diff --git a/src/stores/comfyRegistryStore.ts b/src/stores/comfyRegistryStore.ts index a188430e4..69cf1926e 100644 --- a/src/stores/comfyRegistryStore.ts +++ b/src/stores/comfyRegistryStore.ts @@ -12,6 +12,7 @@ type NodePack = components['schemas']['Node'] type ListPacksParams = operations['listAllNodes']['parameters']['query'] type ListPacksResult = operations['listAllNodes']['responses'][200]['content']['application/json'] +type ComfyNode = components['schemas']['ComfyNode'] /** * Store for managing remote custom nodes @@ -23,6 +24,9 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => { typeof useCachedRequest > let getPackByIdHandler: ReturnType> + let getNodeDefsHandler: ReturnType< + typeof useCachedRequest<{ packId: string; versionId: string }, ComfyNode[]> + > const recentListResult = ref([]) const hasPacks = computed(() => recentListResult.value.length > 0) @@ -58,6 +62,23 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => { return getPackByIdHandler.call(packId) } + /** + * Get the node definitions for a pack + */ + const getNodeDefs = async ( + packId: NodePack['id'], + versionId: NonNullable['id'] + ) => { + if (!packId || !versionId) return null + + getNodeDefsHandler ??= useCachedRequest< + { packId: string; versionId: string }, + ComfyNode[] + >(registryService.getNodeDefs, { maxSize: PACK_BY_ID_CACHE_SIZE }) + + return getNodeDefsHandler.call({ packId, versionId }) + } + /** * Clear all cached data */ @@ -80,6 +101,8 @@ export const useComfyRegistryStore = defineStore('comfyRegistry', () => { listAllPacks, getPackById, + getNodeDefs, + clearCache, cancelRequests, diff --git a/src/stores/dialogStore.ts b/src/stores/dialogStore.ts index 925a6b553..6fdda26db 100644 --- a/src/stores/dialogStore.ts +++ b/src/stores/dialogStore.ts @@ -11,6 +11,7 @@ interface CustomDialogComponentProps { maximizable?: boolean maximized?: boolean onClose?: () => void + closable?: boolean pt?: DialogPassThroughOptions } diff --git a/src/types/comfyManagerTypes.ts b/src/types/comfyManagerTypes.ts new file mode 100644 index 000000000..064a96cff --- /dev/null +++ b/src/types/comfyManagerTypes.ts @@ -0,0 +1,14 @@ +import type { components } from '@/types/comfyRegistryTypes' + +export interface TabItem { + id: string + label: string + icon: string +} + +export type NodeField = keyof components['schemas']['Node'] | null + +export interface SearchOption { + id: T + label: string +} diff --git a/src/utils/formatUtil.ts b/src/utils/formatUtil.ts index 624a1bf6b..74fc8dc70 100644 --- a/src/utils/formatUtil.ts +++ b/src/utils/formatUtil.ts @@ -320,3 +320,11 @@ export const paramsToCacheKey = (params: unknown): string => { */ export const generateRandomSuffix = (): string => Math.random().toString(36).substring(2, 6) + +/** + * Formats a number to a locale-specific string + * @param num The number to format + * @returns The formatted number or 'N/A' if the number is undefined + */ +export const formatNumber = (num?: number): string => + num?.toLocaleString() ?? 'N/A'
{{ segment.text }}
+ {{ $t('manager.noDescription') }} +
+ {{ nodePack.description }} +