mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Compare commits
1 Commits
b5a0725d2c
...
misc-page
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a340d397c |
@@ -9,9 +9,9 @@
|
|||||||
"back": "Zurück",
|
"back": "Zurück",
|
||||||
"next": "Weiter",
|
"next": "Weiter",
|
||||||
"backToTop": "Nach oben",
|
"backToTop": "Nach oben",
|
||||||
"add": "Hinzufügen",
|
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"help": "Hilfe"
|
"help": "Hilfe",
|
||||||
|
"add": "Hinzufügen"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "Wird geladen...",
|
"loading": "Wird geladen...",
|
||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "Rezepte",
|
"recipes": "Rezepte",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "Statistiken"
|
"statistics": "Statistiken"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "LoRAs suchen...",
|
"loras": "LoRAs suchen...",
|
||||||
"recipes": "Rezepte suchen...",
|
"recipes": "Rezepte suchen...",
|
||||||
"checkpoints": "Checkpoints suchen...",
|
"checkpoints": "Checkpoints suchen...",
|
||||||
"embeddings": "Embeddings suchen..."
|
"embeddings": "Embeddings suchen...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "Suchoptionen",
|
"options": "Suchoptionen",
|
||||||
"searchIn": "Suchen in:",
|
"searchIn": "Suchen in:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embedding-Modelle"
|
"title": "Embedding-Modelle"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "Stammverzeichnis",
|
"modelRoot": "Stammverzeichnis",
|
||||||
"collapseAll": "Alle Ordner einklappen",
|
"collapseAll": "Alle Ordner einklappen",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "Statistiken werden initialisiert",
|
"title": "Statistiken werden initialisiert",
|
||||||
"message": "Modelldaten für Statistiken werden verarbeitet. Dies kann einige Minuten dauern..."
|
"message": "Modelldaten für Statistiken werden verarbeitet. Dies kann einige Minuten dauern..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "Tipps & Tricks",
|
"title": "Tipps & Tricks",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "Rezept zum Workflow hinzugefügt",
|
"recipeAdded": "Rezept zum Workflow hinzugefügt",
|
||||||
"recipeReplaced": "Rezept im Workflow ersetzt",
|
"recipeReplaced": "Rezept im Workflow ersetzt",
|
||||||
"recipeFailedToSend": "Fehler beim Senden des Rezepts an den Workflow",
|
"recipeFailedToSend": "Fehler beim Senden des Rezepts an den Workflow",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "Keine kompatiblen Knoten im aktuellen Workflow verfügbar",
|
"noMatchingNodes": "Keine kompatiblen Knoten im aktuellen Workflow verfügbar",
|
||||||
"noTargetNodeSelected": "Kein Zielknoten ausgewählt"
|
"noTargetNodeSelected": "Kein Zielknoten ausgewählt"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Rezept",
|
"recipe": "Rezept",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "Ersetzen",
|
"replace": "Ersetzen",
|
||||||
"append": "Anhängen",
|
"append": "Anhängen",
|
||||||
"selectTargetNode": "Zielknoten auswählen",
|
"selectTargetNode": "Zielknoten auswählen",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "Recipes",
|
"recipes": "Recipes",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "Misc",
|
||||||
"statistics": "Stats"
|
"statistics": "Stats"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "Search LoRAs...",
|
"loras": "Search LoRAs...",
|
||||||
"recipes": "Search recipes...",
|
"recipes": "Search recipes...",
|
||||||
"checkpoints": "Search checkpoints...",
|
"checkpoints": "Search checkpoints...",
|
||||||
"embeddings": "Search embeddings..."
|
"embeddings": "Search embeddings...",
|
||||||
|
"misc": "Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "Search Options",
|
"options": "Search Options",
|
||||||
"searchIn": "Search In:",
|
"searchIn": "Search In:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embedding Models"
|
"title": "Embedding Models"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "VAE",
|
||||||
|
"upscaler": "Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "Root",
|
"modelRoot": "Root",
|
||||||
"collapseAll": "Collapse All Folders",
|
"collapseAll": "Collapse All Folders",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "Initializing Statistics",
|
"title": "Initializing Statistics",
|
||||||
"message": "Processing model data for statistics. This may take a few minutes..."
|
"message": "Processing model data for statistics. This may take a few minutes..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "Initializing Misc Model Manager",
|
||||||
|
"message": "Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "Tips & Tricks",
|
"title": "Tips & Tricks",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "Recipe appended to workflow",
|
"recipeAdded": "Recipe appended to workflow",
|
||||||
"recipeReplaced": "Recipe replaced in workflow",
|
"recipeReplaced": "Recipe replaced in workflow",
|
||||||
"recipeFailedToSend": "Failed to send recipe to workflow",
|
"recipeFailedToSend": "Failed to send recipe to workflow",
|
||||||
|
"vaeUpdated": "VAE updated in workflow",
|
||||||
|
"vaeFailed": "Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "No compatible nodes available in the current workflow",
|
"noMatchingNodes": "No compatible nodes available in the current workflow",
|
||||||
"noTargetNodeSelected": "No target node selected"
|
"noTargetNodeSelected": "No target node selected"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Recipe",
|
"recipe": "Recipe",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "VAE",
|
||||||
|
"upscaler": "Upscaler",
|
||||||
"replace": "Replace",
|
"replace": "Replace",
|
||||||
"append": "Append",
|
"append": "Append",
|
||||||
"selectTargetNode": "Select target node",
|
"selectTargetNode": "Select target node",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "Recetas",
|
"recipes": "Recetas",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "Estadísticas"
|
"statistics": "Estadísticas"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "Buscar LoRAs...",
|
"loras": "Buscar LoRAs...",
|
||||||
"recipes": "Buscar recetas...",
|
"recipes": "Buscar recetas...",
|
||||||
"checkpoints": "Buscar checkpoints...",
|
"checkpoints": "Buscar checkpoints...",
|
||||||
"embeddings": "Buscar embeddings..."
|
"embeddings": "Buscar embeddings...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "Opciones de búsqueda",
|
"options": "Opciones de búsqueda",
|
||||||
"searchIn": "Buscar en:",
|
"searchIn": "Buscar en:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Modelos embedding"
|
"title": "Modelos embedding"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "Raíz",
|
"modelRoot": "Raíz",
|
||||||
"collapseAll": "Colapsar todas las carpetas",
|
"collapseAll": "Colapsar todas las carpetas",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "Inicializando estadísticas",
|
"title": "Inicializando estadísticas",
|
||||||
"message": "Procesando datos del modelo para estadísticas. Esto puede tomar unos minutos..."
|
"message": "Procesando datos del modelo para estadísticas. Esto puede tomar unos minutos..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "Consejos y trucos",
|
"title": "Consejos y trucos",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "Receta añadida al flujo de trabajo",
|
"recipeAdded": "Receta añadida al flujo de trabajo",
|
||||||
"recipeReplaced": "Receta reemplazada en el flujo de trabajo",
|
"recipeReplaced": "Receta reemplazada en el flujo de trabajo",
|
||||||
"recipeFailedToSend": "Error al enviar receta al flujo de trabajo",
|
"recipeFailedToSend": "Error al enviar receta al flujo de trabajo",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "No hay nodos compatibles disponibles en el flujo de trabajo actual",
|
"noMatchingNodes": "No hay nodos compatibles disponibles en el flujo de trabajo actual",
|
||||||
"noTargetNodeSelected": "No se ha seleccionado ningún nodo de destino"
|
"noTargetNodeSelected": "No se ha seleccionado ningún nodo de destino"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Receta",
|
"recipe": "Receta",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "Reemplazar",
|
"replace": "Reemplazar",
|
||||||
"append": "Añadir",
|
"append": "Añadir",
|
||||||
"selectTargetNode": "Seleccionar nodo de destino",
|
"selectTargetNode": "Seleccionar nodo de destino",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "Recipes",
|
"recipes": "Recipes",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "Statistiques"
|
"statistics": "Statistiques"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "Rechercher des LoRAs...",
|
"loras": "Rechercher des LoRAs...",
|
||||||
"recipes": "Rechercher des recipes...",
|
"recipes": "Rechercher des recipes...",
|
||||||
"checkpoints": "Rechercher des checkpoints...",
|
"checkpoints": "Rechercher des checkpoints...",
|
||||||
"embeddings": "Rechercher des embeddings..."
|
"embeddings": "Rechercher des embeddings...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "Options de recherche",
|
"options": "Options de recherche",
|
||||||
"searchIn": "Rechercher dans :",
|
"searchIn": "Rechercher dans :",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Modèles Embedding"
|
"title": "Modèles Embedding"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "Racine",
|
"modelRoot": "Racine",
|
||||||
"collapseAll": "Réduire tous les dossiers",
|
"collapseAll": "Réduire tous les dossiers",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "Initialisation des statistiques",
|
"title": "Initialisation des statistiques",
|
||||||
"message": "Traitement des données de modèle pour les statistiques. Cela peut prendre quelques minutes..."
|
"message": "Traitement des données de modèle pour les statistiques. Cela peut prendre quelques minutes..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "Astuces et conseils",
|
"title": "Astuces et conseils",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "Recipe ajoutée au workflow",
|
"recipeAdded": "Recipe ajoutée au workflow",
|
||||||
"recipeReplaced": "Recipe remplacée dans le workflow",
|
"recipeReplaced": "Recipe remplacée dans le workflow",
|
||||||
"recipeFailedToSend": "Échec de l'envoi de la recipe au workflow",
|
"recipeFailedToSend": "Échec de l'envoi de la recipe au workflow",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "Aucun nœud compatible disponible dans le workflow actuel",
|
"noMatchingNodes": "Aucun nœud compatible disponible dans le workflow actuel",
|
||||||
"noTargetNodeSelected": "Aucun nœud cible sélectionné"
|
"noTargetNodeSelected": "Aucun nœud cible sélectionné"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Recipe",
|
"recipe": "Recipe",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "Remplacer",
|
"replace": "Remplacer",
|
||||||
"append": "Ajouter",
|
"append": "Ajouter",
|
||||||
"selectTargetNode": "Sélectionner le nœud cible",
|
"selectTargetNode": "Sélectionner le nœud cible",
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
"back": "חזור",
|
"back": "חזור",
|
||||||
"next": "הבא",
|
"next": "הבא",
|
||||||
"backToTop": "חזור למעלה",
|
"backToTop": "חזור למעלה",
|
||||||
"add": "הוסף",
|
|
||||||
"settings": "הגדרות",
|
"settings": "הגדרות",
|
||||||
"help": "עזרה"
|
"help": "עזרה",
|
||||||
|
"add": "הוסף"
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"loading": "טוען...",
|
"loading": "טוען...",
|
||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "מתכונים",
|
"recipes": "מתכונים",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "סטטיסטיקה"
|
"statistics": "סטטיסטיקה"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "חפש LoRAs...",
|
"loras": "חפש LoRAs...",
|
||||||
"recipes": "חפש מתכונים...",
|
"recipes": "חפש מתכונים...",
|
||||||
"checkpoints": "חפש checkpoints...",
|
"checkpoints": "חפש checkpoints...",
|
||||||
"embeddings": "חפש embeddings..."
|
"embeddings": "חפש embeddings...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "אפשרויות חיפוש",
|
"options": "אפשרויות חיפוש",
|
||||||
"searchIn": "חפש ב:",
|
"searchIn": "חפש ב:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "מודלי Embedding"
|
"title": "מודלי Embedding"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "שורש",
|
"modelRoot": "שורש",
|
||||||
"collapseAll": "כווץ את כל התיקיות",
|
"collapseAll": "כווץ את כל התיקיות",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "מאתחל סטטיסטיקה",
|
"title": "מאתחל סטטיסטיקה",
|
||||||
"message": "מעבד נתוני מודלים עבור סטטיסטיקה. זה עשוי לקחת מספר דקות..."
|
"message": "מעבד נתוני מודלים עבור סטטיסטיקה. זה עשוי לקחת מספר דקות..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "טיפים וטריקים",
|
"title": "טיפים וטריקים",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "מתכון נוסף ל-workflow",
|
"recipeAdded": "מתכון נוסף ל-workflow",
|
||||||
"recipeReplaced": "מתכון הוחלף ב-workflow",
|
"recipeReplaced": "מתכון הוחלף ב-workflow",
|
||||||
"recipeFailedToSend": "שליחת מתכון ל-workflow נכשלה",
|
"recipeFailedToSend": "שליחת מתכון ל-workflow נכשלה",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "אין צמתים תואמים זמינים ב-workflow הנוכחי",
|
"noMatchingNodes": "אין צמתים תואמים זמינים ב-workflow הנוכחי",
|
||||||
"noTargetNodeSelected": "לא נבחר צומת יעד"
|
"noTargetNodeSelected": "לא נבחר צומת יעד"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "מתכון",
|
"recipe": "מתכון",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "החלף",
|
"replace": "החלף",
|
||||||
"append": "הוסף",
|
"append": "הוסף",
|
||||||
"selectTargetNode": "בחר צומת יעד",
|
"selectTargetNode": "בחר צומת יעד",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "レシピ",
|
"recipes": "レシピ",
|
||||||
"checkpoints": "Checkpoint",
|
"checkpoints": "Checkpoint",
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "統計"
|
"statistics": "統計"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "LoRAを検索...",
|
"loras": "LoRAを検索...",
|
||||||
"recipes": "レシピを検索...",
|
"recipes": "レシピを検索...",
|
||||||
"checkpoints": "checkpointを検索...",
|
"checkpoints": "checkpointを検索...",
|
||||||
"embeddings": "embeddingを検索..."
|
"embeddings": "embeddingを検索...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "検索オプション",
|
"options": "検索オプション",
|
||||||
"searchIn": "検索対象:",
|
"searchIn": "検索対象:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embeddingモデル"
|
"title": "Embeddingモデル"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "ルート",
|
"modelRoot": "ルート",
|
||||||
"collapseAll": "すべてのフォルダを折りたたむ",
|
"collapseAll": "すべてのフォルダを折りたたむ",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "統計を初期化中",
|
"title": "統計を初期化中",
|
||||||
"message": "統計用のモデルデータを処理中。数分かかる場合があります..."
|
"message": "統計用のモデルデータを処理中。数分かかる場合があります..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "ヒント&コツ",
|
"title": "ヒント&コツ",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "レシピがワークフローに追加されました",
|
"recipeAdded": "レシピがワークフローに追加されました",
|
||||||
"recipeReplaced": "レシピがワークフローで置換されました",
|
"recipeReplaced": "レシピがワークフローで置換されました",
|
||||||
"recipeFailedToSend": "レシピをワークフローに送信できませんでした",
|
"recipeFailedToSend": "レシピをワークフローに送信できませんでした",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "現在のワークフローには互換性のあるノードがありません",
|
"noMatchingNodes": "現在のワークフローには互換性のあるノードがありません",
|
||||||
"noTargetNodeSelected": "ターゲットノードが選択されていません"
|
"noTargetNodeSelected": "ターゲットノードが選択されていません"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "レシピ",
|
"recipe": "レシピ",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "置換",
|
"replace": "置換",
|
||||||
"append": "追加",
|
"append": "追加",
|
||||||
"selectTargetNode": "ターゲットノードを選択",
|
"selectTargetNode": "ターゲットノードを選択",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "레시피",
|
"recipes": "레시피",
|
||||||
"checkpoints": "Checkpoint",
|
"checkpoints": "Checkpoint",
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "통계"
|
"statistics": "통계"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "LoRA 검색...",
|
"loras": "LoRA 검색...",
|
||||||
"recipes": "레시피 검색...",
|
"recipes": "레시피 검색...",
|
||||||
"checkpoints": "Checkpoint 검색...",
|
"checkpoints": "Checkpoint 검색...",
|
||||||
"embeddings": "Embedding 검색..."
|
"embeddings": "Embedding 검색...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "검색 옵션",
|
"options": "검색 옵션",
|
||||||
"searchIn": "검색 범위:",
|
"searchIn": "검색 범위:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embedding 모델"
|
"title": "Embedding 모델"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "루트",
|
"modelRoot": "루트",
|
||||||
"collapseAll": "모든 폴더 접기",
|
"collapseAll": "모든 폴더 접기",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "통계 초기화 중",
|
"title": "통계 초기화 중",
|
||||||
"message": "통계를 위한 모델 데이터를 처리하고 있습니다. 몇 분이 걸릴 수 있습니다..."
|
"message": "통계를 위한 모델 데이터를 처리하고 있습니다. 몇 분이 걸릴 수 있습니다..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "팁 & 요령",
|
"title": "팁 & 요령",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "레시피가 워크플로에 추가되었습니다",
|
"recipeAdded": "레시피가 워크플로에 추가되었습니다",
|
||||||
"recipeReplaced": "레시피가 워크플로에서 교체되었습니다",
|
"recipeReplaced": "레시피가 워크플로에서 교체되었습니다",
|
||||||
"recipeFailedToSend": "레시피를 워크플로로 전송하지 못했습니다",
|
"recipeFailedToSend": "레시피를 워크플로로 전송하지 못했습니다",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "현재 워크플로에서 호환되는 노드가 없습니다",
|
"noMatchingNodes": "현재 워크플로에서 호환되는 노드가 없습니다",
|
||||||
"noTargetNodeSelected": "대상 노드가 선택되지 않았습니다"
|
"noTargetNodeSelected": "대상 노드가 선택되지 않았습니다"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "레시피",
|
"recipe": "레시피",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "교체",
|
"replace": "교체",
|
||||||
"append": "추가",
|
"append": "추가",
|
||||||
"selectTargetNode": "대상 노드 선택",
|
"selectTargetNode": "대상 노드 선택",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "Рецепты",
|
"recipes": "Рецепты",
|
||||||
"checkpoints": "Checkpoints",
|
"checkpoints": "Checkpoints",
|
||||||
"embeddings": "Embeddings",
|
"embeddings": "Embeddings",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "Статистика"
|
"statistics": "Статистика"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "Поиск LoRAs...",
|
"loras": "Поиск LoRAs...",
|
||||||
"recipes": "Поиск рецептов...",
|
"recipes": "Поиск рецептов...",
|
||||||
"checkpoints": "Поиск checkpoints...",
|
"checkpoints": "Поиск checkpoints...",
|
||||||
"embeddings": "Поиск embeddings..."
|
"embeddings": "Поиск embeddings...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "Опции поиска",
|
"options": "Опции поиска",
|
||||||
"searchIn": "Искать в:",
|
"searchIn": "Искать в:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Модели Embedding"
|
"title": "Модели Embedding"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "Корень",
|
"modelRoot": "Корень",
|
||||||
"collapseAll": "Свернуть все папки",
|
"collapseAll": "Свернуть все папки",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "Инициализация статистики",
|
"title": "Инициализация статистики",
|
||||||
"message": "Обработка данных моделей для статистики. Это может занять несколько минут..."
|
"message": "Обработка данных моделей для статистики. Это может занять несколько минут..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "Советы и хитрости",
|
"title": "Советы и хитрости",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "Рецепт добавлен в workflow",
|
"recipeAdded": "Рецепт добавлен в workflow",
|
||||||
"recipeReplaced": "Рецепт заменён в workflow",
|
"recipeReplaced": "Рецепт заменён в workflow",
|
||||||
"recipeFailedToSend": "Не удалось отправить рецепт в workflow",
|
"recipeFailedToSend": "Не удалось отправить рецепт в workflow",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "В текущем workflow нет совместимых узлов",
|
"noMatchingNodes": "В текущем workflow нет совместимых узлов",
|
||||||
"noTargetNodeSelected": "Целевой узел не выбран"
|
"noTargetNodeSelected": "Целевой узел не выбран"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "Рецепт",
|
"recipe": "Рецепт",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "Заменить",
|
"replace": "Заменить",
|
||||||
"append": "Добавить",
|
"append": "Добавить",
|
||||||
"selectTargetNode": "Выберите целевой узел",
|
"selectTargetNode": "Выберите целевой узел",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "配方",
|
"recipes": "配方",
|
||||||
"checkpoints": "Checkpoint",
|
"checkpoints": "Checkpoint",
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "统计"
|
"statistics": "统计"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "搜索 LoRA...",
|
"loras": "搜索 LoRA...",
|
||||||
"recipes": "搜索配方...",
|
"recipes": "搜索配方...",
|
||||||
"checkpoints": "搜索 Checkpoint...",
|
"checkpoints": "搜索 Checkpoint...",
|
||||||
"embeddings": "搜索 Embedding..."
|
"embeddings": "搜索 Embedding...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "搜索选项",
|
"options": "搜索选项",
|
||||||
"searchIn": "搜索范围:",
|
"searchIn": "搜索范围:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embedding 模型"
|
"title": "Embedding 模型"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "根目录",
|
"modelRoot": "根目录",
|
||||||
"collapseAll": "折叠所有文件夹",
|
"collapseAll": "折叠所有文件夹",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "初始化统计",
|
"title": "初始化统计",
|
||||||
"message": "正在处理模型数据以生成统计信息。这可能需要几分钟..."
|
"message": "正在处理模型数据以生成统计信息。这可能需要几分钟..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "技巧与提示",
|
"title": "技巧与提示",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "配方已追加到工作流",
|
"recipeAdded": "配方已追加到工作流",
|
||||||
"recipeReplaced": "配方已替换到工作流",
|
"recipeReplaced": "配方已替换到工作流",
|
||||||
"recipeFailedToSend": "发送配方到工作流失败",
|
"recipeFailedToSend": "发送配方到工作流失败",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "当前工作流中没有兼容的节点",
|
"noMatchingNodes": "当前工作流中没有兼容的节点",
|
||||||
"noTargetNodeSelected": "未选择目标节点"
|
"noTargetNodeSelected": "未选择目标节点"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "配方",
|
"recipe": "配方",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "替换",
|
"replace": "替换",
|
||||||
"append": "追加",
|
"append": "追加",
|
||||||
"selectTargetNode": "选择目标节点",
|
"selectTargetNode": "选择目标节点",
|
||||||
|
|||||||
@@ -179,6 +179,7 @@
|
|||||||
"recipes": "配方",
|
"recipes": "配方",
|
||||||
"checkpoints": "Checkpoint",
|
"checkpoints": "Checkpoint",
|
||||||
"embeddings": "Embedding",
|
"embeddings": "Embedding",
|
||||||
|
"misc": "[TODO: Translate] Misc",
|
||||||
"statistics": "統計"
|
"statistics": "統計"
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
@@ -187,7 +188,8 @@
|
|||||||
"loras": "搜尋 LoRA...",
|
"loras": "搜尋 LoRA...",
|
||||||
"recipes": "搜尋配方...",
|
"recipes": "搜尋配方...",
|
||||||
"checkpoints": "搜尋 checkpoint...",
|
"checkpoints": "搜尋 checkpoint...",
|
||||||
"embeddings": "搜尋 embedding..."
|
"embeddings": "搜尋 embedding...",
|
||||||
|
"misc": "[TODO: Translate] Search VAE/Upscaler models..."
|
||||||
},
|
},
|
||||||
"options": "搜尋選項",
|
"options": "搜尋選項",
|
||||||
"searchIn": "搜尋範圍:",
|
"searchIn": "搜尋範圍:",
|
||||||
@@ -688,6 +690,16 @@
|
|||||||
"embeddings": {
|
"embeddings": {
|
||||||
"title": "Embedding 模型"
|
"title": "Embedding 模型"
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] VAE & Upscaler Models",
|
||||||
|
"modelTypes": {
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler"
|
||||||
|
},
|
||||||
|
"contextMenu": {
|
||||||
|
"moveToOtherTypeFolder": "[TODO: Translate] Move to {otherType} Folder"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"modelRoot": "根目錄",
|
"modelRoot": "根目錄",
|
||||||
"collapseAll": "全部摺疊資料夾",
|
"collapseAll": "全部摺疊資料夾",
|
||||||
@@ -1104,6 +1116,10 @@
|
|||||||
"title": "初始化統計",
|
"title": "初始化統計",
|
||||||
"message": "正在處理模型資料以產生統計,可能需要幾分鐘..."
|
"message": "正在處理模型資料以產生統計,可能需要幾分鐘..."
|
||||||
},
|
},
|
||||||
|
"misc": {
|
||||||
|
"title": "[TODO: Translate] Initializing Misc Model Manager",
|
||||||
|
"message": "[TODO: Translate] Scanning VAE and Upscaler models..."
|
||||||
|
},
|
||||||
"tips": {
|
"tips": {
|
||||||
"title": "小技巧",
|
"title": "小技巧",
|
||||||
"civitai": {
|
"civitai": {
|
||||||
@@ -1163,12 +1179,18 @@
|
|||||||
"recipeAdded": "配方已附加到工作流",
|
"recipeAdded": "配方已附加到工作流",
|
||||||
"recipeReplaced": "配方已取代於工作流",
|
"recipeReplaced": "配方已取代於工作流",
|
||||||
"recipeFailedToSend": "傳送配方到工作流失敗",
|
"recipeFailedToSend": "傳送配方到工作流失敗",
|
||||||
|
"vaeUpdated": "[TODO: Translate] VAE updated in workflow",
|
||||||
|
"vaeFailed": "[TODO: Translate] Failed to update VAE in workflow",
|
||||||
|
"upscalerUpdated": "[TODO: Translate] Upscaler updated in workflow",
|
||||||
|
"upscalerFailed": "[TODO: Translate] Failed to update upscaler in workflow",
|
||||||
"noMatchingNodes": "目前工作流程中沒有相容的節點",
|
"noMatchingNodes": "目前工作流程中沒有相容的節點",
|
||||||
"noTargetNodeSelected": "未選擇目標節點"
|
"noTargetNodeSelected": "未選擇目標節點"
|
||||||
},
|
},
|
||||||
"nodeSelector": {
|
"nodeSelector": {
|
||||||
"recipe": "配方",
|
"recipe": "配方",
|
||||||
"lora": "LoRA",
|
"lora": "LoRA",
|
||||||
|
"vae": "[TODO: Translate] VAE",
|
||||||
|
"upscaler": "[TODO: Translate] Upscaler",
|
||||||
"replace": "取代",
|
"replace": "取代",
|
||||||
"append": "附加",
|
"append": "附加",
|
||||||
"selectTargetNode": "選擇目標節點",
|
"selectTargetNode": "選擇目標節點",
|
||||||
|
|||||||
54
py/config.py
54
py/config.py
@@ -89,8 +89,11 @@ class Config:
|
|||||||
self.checkpoints_roots = None
|
self.checkpoints_roots = None
|
||||||
self.unet_roots = None
|
self.unet_roots = None
|
||||||
self.embeddings_roots = None
|
self.embeddings_roots = None
|
||||||
|
self.vae_roots = None
|
||||||
|
self.upscaler_roots = None
|
||||||
self.base_models_roots = self._init_checkpoint_paths()
|
self.base_models_roots = self._init_checkpoint_paths()
|
||||||
self.embeddings_roots = self._init_embedding_paths()
|
self.embeddings_roots = self._init_embedding_paths()
|
||||||
|
self.misc_roots = self._init_misc_paths()
|
||||||
# Scan symbolic links during initialization
|
# Scan symbolic links during initialization
|
||||||
self._initialize_symlink_mappings()
|
self._initialize_symlink_mappings()
|
||||||
|
|
||||||
@@ -151,6 +154,8 @@ class Config:
|
|||||||
'checkpoints': list(self.checkpoints_roots or []),
|
'checkpoints': list(self.checkpoints_roots or []),
|
||||||
'unet': list(self.unet_roots or []),
|
'unet': list(self.unet_roots or []),
|
||||||
'embeddings': list(self.embeddings_roots or []),
|
'embeddings': list(self.embeddings_roots or []),
|
||||||
|
'vae': list(self.vae_roots or []),
|
||||||
|
'upscale_models': list(self.upscaler_roots or []),
|
||||||
}
|
}
|
||||||
|
|
||||||
normalized_target_paths = _normalize_folder_paths_for_comparison(target_folder_paths)
|
normalized_target_paths = _normalize_folder_paths_for_comparison(target_folder_paths)
|
||||||
@@ -250,6 +255,7 @@ class Config:
|
|||||||
roots.extend(self.loras_roots or [])
|
roots.extend(self.loras_roots or [])
|
||||||
roots.extend(self.base_models_roots or [])
|
roots.extend(self.base_models_roots or [])
|
||||||
roots.extend(self.embeddings_roots or [])
|
roots.extend(self.embeddings_roots or [])
|
||||||
|
roots.extend(self.misc_roots or [])
|
||||||
return roots
|
return roots
|
||||||
|
|
||||||
def _build_symlink_fingerprint(self) -> Dict[str, object]:
|
def _build_symlink_fingerprint(self) -> Dict[str, object]:
|
||||||
@@ -599,6 +605,8 @@ class Config:
|
|||||||
preview_roots.update(self._expand_preview_root(root))
|
preview_roots.update(self._expand_preview_root(root))
|
||||||
for root in self.embeddings_roots or []:
|
for root in self.embeddings_roots or []:
|
||||||
preview_roots.update(self._expand_preview_root(root))
|
preview_roots.update(self._expand_preview_root(root))
|
||||||
|
for root in self.misc_roots or []:
|
||||||
|
preview_roots.update(self._expand_preview_root(root))
|
||||||
|
|
||||||
for target, link in self._path_mappings.items():
|
for target, link in self._path_mappings.items():
|
||||||
preview_roots.update(self._expand_preview_root(target))
|
preview_roots.update(self._expand_preview_root(target))
|
||||||
@@ -606,11 +614,12 @@ class Config:
|
|||||||
|
|
||||||
self._preview_root_paths = {path for path in preview_roots if path.is_absolute()}
|
self._preview_root_paths = {path for path in preview_roots if path.is_absolute()}
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Preview roots rebuilt: %d paths from %d lora roots, %d checkpoint roots, %d embedding roots, %d symlink mappings",
|
"Preview roots rebuilt: %d paths from %d lora roots, %d checkpoint roots, %d embedding roots, %d misc roots, %d symlink mappings",
|
||||||
len(self._preview_root_paths),
|
len(self._preview_root_paths),
|
||||||
len(self.loras_roots or []),
|
len(self.loras_roots or []),
|
||||||
len(self.base_models_roots or []),
|
len(self.base_models_roots or []),
|
||||||
len(self.embeddings_roots or []),
|
len(self.embeddings_roots or []),
|
||||||
|
len(self.misc_roots or []),
|
||||||
len(self._path_mappings),
|
len(self._path_mappings),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -769,6 +778,49 @@ class Config:
|
|||||||
logger.warning(f"Error initializing embedding paths: {e}")
|
logger.warning(f"Error initializing embedding paths: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def _init_misc_paths(self) -> List[str]:
|
||||||
|
"""Initialize and validate misc (VAE and upscaler) paths from ComfyUI settings"""
|
||||||
|
try:
|
||||||
|
raw_vae_paths = folder_paths.get_folder_paths("vae")
|
||||||
|
raw_upscaler_paths = folder_paths.get_folder_paths("upscale_models")
|
||||||
|
unique_paths = self._prepare_misc_paths(raw_vae_paths, raw_upscaler_paths)
|
||||||
|
|
||||||
|
logger.info("Found misc roots:" + ("\n - " + "\n - ".join(unique_paths) if unique_paths else "[]"))
|
||||||
|
|
||||||
|
if not unique_paths:
|
||||||
|
logger.warning("No valid VAE or upscaler folders found in ComfyUI configuration")
|
||||||
|
return []
|
||||||
|
|
||||||
|
return unique_paths
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error initializing misc paths: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _prepare_misc_paths(
|
||||||
|
self, vae_paths: Iterable[str], upscaler_paths: Iterable[str]
|
||||||
|
) -> List[str]:
|
||||||
|
vae_map = self._dedupe_existing_paths(vae_paths)
|
||||||
|
upscaler_map = self._dedupe_existing_paths(upscaler_paths)
|
||||||
|
|
||||||
|
merged_map: Dict[str, str] = {}
|
||||||
|
for real_path, original in {**vae_map, **upscaler_map}.items():
|
||||||
|
if real_path not in merged_map:
|
||||||
|
merged_map[real_path] = original
|
||||||
|
|
||||||
|
unique_paths = sorted(merged_map.values(), key=lambda p: p.lower())
|
||||||
|
|
||||||
|
vae_values = set(vae_map.values())
|
||||||
|
upscaler_values = set(upscaler_map.values())
|
||||||
|
self.vae_roots = [p for p in unique_paths if p in vae_values]
|
||||||
|
self.upscaler_roots = [p for p in unique_paths if p in upscaler_values]
|
||||||
|
|
||||||
|
for original_path in unique_paths:
|
||||||
|
real_path = os.path.normpath(os.path.realpath(original_path)).replace(os.sep, '/')
|
||||||
|
if real_path != original_path:
|
||||||
|
self.add_path_mapping(original_path, real_path)
|
||||||
|
|
||||||
|
return unique_paths
|
||||||
|
|
||||||
def get_preview_static_url(self, preview_path: str) -> str:
|
def get_preview_static_url(self, preview_path: str) -> str:
|
||||||
if not preview_path:
|
if not preview_path:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -184,15 +184,17 @@ class LoraManager:
|
|||||||
lora_scanner = await ServiceRegistry.get_lora_scanner()
|
lora_scanner = await ServiceRegistry.get_lora_scanner()
|
||||||
checkpoint_scanner = await ServiceRegistry.get_checkpoint_scanner()
|
checkpoint_scanner = await ServiceRegistry.get_checkpoint_scanner()
|
||||||
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
||||||
|
misc_scanner = await ServiceRegistry.get_misc_scanner()
|
||||||
|
|
||||||
# Initialize recipe scanner if needed
|
# Initialize recipe scanner if needed
|
||||||
recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
||||||
|
|
||||||
# Create low-priority initialization tasks
|
# Create low-priority initialization tasks
|
||||||
init_tasks = [
|
init_tasks = [
|
||||||
asyncio.create_task(lora_scanner.initialize_in_background(), name='lora_cache_init'),
|
asyncio.create_task(lora_scanner.initialize_in_background(), name='lora_cache_init'),
|
||||||
asyncio.create_task(checkpoint_scanner.initialize_in_background(), name='checkpoint_cache_init'),
|
asyncio.create_task(checkpoint_scanner.initialize_in_background(), name='checkpoint_cache_init'),
|
||||||
asyncio.create_task(embedding_scanner.initialize_in_background(), name='embedding_cache_init'),
|
asyncio.create_task(embedding_scanner.initialize_in_background(), name='embedding_cache_init'),
|
||||||
|
asyncio.create_task(misc_scanner.initialize_in_background(), name='misc_cache_init'),
|
||||||
asyncio.create_task(recipe_scanner.initialize_in_background(), name='recipe_cache_init')
|
asyncio.create_task(recipe_scanner.initialize_in_background(), name='recipe_cache_init')
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -252,8 +254,9 @@ class LoraManager:
|
|||||||
# Collect all model roots
|
# Collect all model roots
|
||||||
all_roots = set()
|
all_roots = set()
|
||||||
all_roots.update(config.loras_roots)
|
all_roots.update(config.loras_roots)
|
||||||
all_roots.update(config.base_models_roots)
|
all_roots.update(config.base_models_roots)
|
||||||
all_roots.update(config.embeddings_roots)
|
all_roots.update(config.embeddings_roots)
|
||||||
|
all_roots.update(config.misc_roots or [])
|
||||||
|
|
||||||
total_deleted = 0
|
total_deleted = 0
|
||||||
total_size_freed = 0
|
total_size_freed = 0
|
||||||
|
|||||||
112
py/routes/misc_model_routes.py
Normal file
112
py/routes/misc_model_routes.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
from aiohttp import web
|
||||||
|
|
||||||
|
from .base_model_routes import BaseModelRoutes
|
||||||
|
from .model_route_registrar import ModelRouteRegistrar
|
||||||
|
from ..services.misc_service import MiscService
|
||||||
|
from ..services.service_registry import ServiceRegistry
|
||||||
|
from ..config import config
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MiscModelRoutes(BaseModelRoutes):
|
||||||
|
"""Misc-specific route controller (VAE, Upscaler)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize Misc routes with Misc service"""
|
||||||
|
super().__init__()
|
||||||
|
self.template_name = "misc.html"
|
||||||
|
|
||||||
|
async def initialize_services(self):
|
||||||
|
"""Initialize services from ServiceRegistry"""
|
||||||
|
misc_scanner = await ServiceRegistry.get_misc_scanner()
|
||||||
|
update_service = await ServiceRegistry.get_model_update_service()
|
||||||
|
self.service = MiscService(misc_scanner, update_service=update_service)
|
||||||
|
self.set_model_update_service(update_service)
|
||||||
|
|
||||||
|
# Attach service dependencies
|
||||||
|
self.attach_service(self.service)
|
||||||
|
|
||||||
|
def setup_routes(self, app: web.Application):
|
||||||
|
"""Setup Misc routes"""
|
||||||
|
# Schedule service initialization on app startup
|
||||||
|
app.on_startup.append(lambda _: self.initialize_services())
|
||||||
|
|
||||||
|
# Setup common routes with 'misc' prefix (includes page route)
|
||||||
|
super().setup_routes(app, 'misc')
|
||||||
|
|
||||||
|
def setup_specific_routes(self, registrar: ModelRouteRegistrar, prefix: str):
|
||||||
|
"""Setup Misc-specific routes"""
|
||||||
|
# Misc info by name
|
||||||
|
registrar.add_prefixed_route('GET', '/api/lm/{prefix}/info/{name}', prefix, self.get_misc_info)
|
||||||
|
|
||||||
|
# VAE roots and Upscaler roots
|
||||||
|
registrar.add_prefixed_route('GET', '/api/lm/{prefix}/vae_roots', prefix, self.get_vae_roots)
|
||||||
|
registrar.add_prefixed_route('GET', '/api/lm/{prefix}/upscaler_roots', prefix, self.get_upscaler_roots)
|
||||||
|
|
||||||
|
def _validate_civitai_model_type(self, model_type: str) -> bool:
|
||||||
|
"""Validate CivitAI model type for Misc (VAE or Upscaler)"""
|
||||||
|
return model_type.lower() in ['vae', 'upscaler']
|
||||||
|
|
||||||
|
def _get_expected_model_types(self) -> str:
|
||||||
|
"""Get expected model types string for error messages"""
|
||||||
|
return "VAE or Upscaler"
|
||||||
|
|
||||||
|
def _parse_specific_params(self, request: web.Request) -> Dict:
|
||||||
|
"""Parse Misc-specific parameters"""
|
||||||
|
params: Dict = {}
|
||||||
|
|
||||||
|
if 'misc_hash' in request.query:
|
||||||
|
params['hash_filters'] = {'single_hash': request.query['misc_hash'].lower()}
|
||||||
|
elif 'misc_hashes' in request.query:
|
||||||
|
params['hash_filters'] = {
|
||||||
|
'multiple_hashes': [h.lower() for h in request.query['misc_hashes'].split(',')]
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
|
||||||
|
async def get_misc_info(self, request: web.Request) -> web.Response:
|
||||||
|
"""Get detailed information for a specific misc model by name"""
|
||||||
|
try:
|
||||||
|
name = request.match_info.get('name', '')
|
||||||
|
misc_info = await self.service.get_model_info_by_name(name)
|
||||||
|
|
||||||
|
if misc_info:
|
||||||
|
return web.json_response(misc_info)
|
||||||
|
else:
|
||||||
|
return web.json_response({"error": "Misc model not found"}, status=404)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in get_misc_info: {e}", exc_info=True)
|
||||||
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
|
|
||||||
|
async def get_vae_roots(self, request: web.Request) -> web.Response:
|
||||||
|
"""Return the list of VAE roots from config"""
|
||||||
|
try:
|
||||||
|
roots = config.vae_roots
|
||||||
|
return web.json_response({
|
||||||
|
"success": True,
|
||||||
|
"roots": roots
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting VAE roots: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
|
async def get_upscaler_roots(self, request: web.Request) -> web.Response:
|
||||||
|
"""Return the list of upscaler roots from config"""
|
||||||
|
try:
|
||||||
|
roots = config.upscaler_roots
|
||||||
|
return web.json_response({
|
||||||
|
"success": True,
|
||||||
|
"roots": roots
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting upscaler roots: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
"success": False,
|
||||||
|
"error": str(e)
|
||||||
|
}, status=500)
|
||||||
@@ -9,7 +9,7 @@ from collections import OrderedDict
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, List, Optional, Set, Tuple
|
from typing import Dict, List, Optional, Set, Tuple
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from ..utils.models import LoraMetadata, CheckpointMetadata, EmbeddingMetadata
|
from ..utils.models import LoraMetadata, CheckpointMetadata, EmbeddingMetadata, MiscMetadata
|
||||||
from ..utils.constants import CARD_PREVIEW_WIDTH, DIFFUSION_MODEL_BASE_MODELS, VALID_LORA_TYPES
|
from ..utils.constants import CARD_PREVIEW_WIDTH, DIFFUSION_MODEL_BASE_MODELS, VALID_LORA_TYPES
|
||||||
from ..utils.civitai_utils import rewrite_preview_url
|
from ..utils.civitai_utils import rewrite_preview_url
|
||||||
from ..utils.preview_selection import select_preview_media
|
from ..utils.preview_selection import select_preview_media
|
||||||
@@ -60,6 +60,10 @@ class DownloadManager:
|
|||||||
"""Get the checkpoint scanner from registry"""
|
"""Get the checkpoint scanner from registry"""
|
||||||
return await ServiceRegistry.get_checkpoint_scanner()
|
return await ServiceRegistry.get_checkpoint_scanner()
|
||||||
|
|
||||||
|
async def _get_misc_scanner(self):
|
||||||
|
"""Get the misc scanner from registry"""
|
||||||
|
return await ServiceRegistry.get_misc_scanner()
|
||||||
|
|
||||||
async def download_from_civitai(
|
async def download_from_civitai(
|
||||||
self,
|
self,
|
||||||
model_id: int = None,
|
model_id: int = None,
|
||||||
@@ -275,6 +279,7 @@ class DownloadManager:
|
|||||||
lora_scanner = await self._get_lora_scanner()
|
lora_scanner = await self._get_lora_scanner()
|
||||||
checkpoint_scanner = await self._get_checkpoint_scanner()
|
checkpoint_scanner = await self._get_checkpoint_scanner()
|
||||||
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
embedding_scanner = await ServiceRegistry.get_embedding_scanner()
|
||||||
|
misc_scanner = await self._get_misc_scanner()
|
||||||
|
|
||||||
# Check lora scanner first
|
# Check lora scanner first
|
||||||
if await lora_scanner.check_model_version_exists(model_version_id):
|
if await lora_scanner.check_model_version_exists(model_version_id):
|
||||||
@@ -299,6 +304,13 @@ class DownloadManager:
|
|||||||
"error": "Model version already exists in embedding library",
|
"error": "Model version already exists in embedding library",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Check misc scanner (VAE, Upscaler)
|
||||||
|
if await misc_scanner.check_model_version_exists(model_version_id):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Model version already exists in misc library",
|
||||||
|
}
|
||||||
|
|
||||||
# Use CivArchive provider directly when source is 'civarchive'
|
# Use CivArchive provider directly when source is 'civarchive'
|
||||||
# This prioritizes CivArchive metadata (with mirror availability info) over Civitai
|
# This prioritizes CivArchive metadata (with mirror availability info) over Civitai
|
||||||
if source == "civarchive":
|
if source == "civarchive":
|
||||||
@@ -337,6 +349,10 @@ class DownloadManager:
|
|||||||
model_type = "lora"
|
model_type = "lora"
|
||||||
elif model_type_from_info == "textualinversion":
|
elif model_type_from_info == "textualinversion":
|
||||||
model_type = "embedding"
|
model_type = "embedding"
|
||||||
|
elif model_type_from_info == "vae":
|
||||||
|
model_type = "misc"
|
||||||
|
elif model_type_from_info == "upscaler":
|
||||||
|
model_type = "misc"
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
@@ -379,6 +395,14 @@ class DownloadManager:
|
|||||||
"success": False,
|
"success": False,
|
||||||
"error": "Model version already exists in embedding library",
|
"error": "Model version already exists in embedding library",
|
||||||
}
|
}
|
||||||
|
elif model_type == "misc":
|
||||||
|
# Check misc scanner (VAE, Upscaler)
|
||||||
|
misc_scanner = await self._get_misc_scanner()
|
||||||
|
if await misc_scanner.check_model_version_exists(version_id):
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": "Model version already exists in misc library",
|
||||||
|
}
|
||||||
|
|
||||||
# Handle use_default_paths
|
# Handle use_default_paths
|
||||||
if use_default_paths:
|
if use_default_paths:
|
||||||
@@ -413,6 +437,26 @@ class DownloadManager:
|
|||||||
"error": "Default embedding root path not set in settings",
|
"error": "Default embedding root path not set in settings",
|
||||||
}
|
}
|
||||||
save_dir = default_path
|
save_dir = default_path
|
||||||
|
elif model_type == "misc":
|
||||||
|
from ..config import config
|
||||||
|
|
||||||
|
civitai_type = version_info.get("model", {}).get("type", "").lower()
|
||||||
|
if civitai_type == "vae":
|
||||||
|
default_paths = config.vae_roots
|
||||||
|
error_msg = "VAE root path not configured"
|
||||||
|
elif civitai_type == "upscaler":
|
||||||
|
default_paths = config.upscaler_roots
|
||||||
|
error_msg = "Upscaler root path not configured"
|
||||||
|
else:
|
||||||
|
default_paths = config.misc_roots
|
||||||
|
error_msg = "Misc root path not configured"
|
||||||
|
|
||||||
|
if not default_paths:
|
||||||
|
return {
|
||||||
|
"success": False,
|
||||||
|
"error": error_msg,
|
||||||
|
}
|
||||||
|
save_dir = default_paths[0] if default_paths else ""
|
||||||
|
|
||||||
# Calculate relative path using template
|
# Calculate relative path using template
|
||||||
relative_path = self._calculate_relative_path(version_info, model_type)
|
relative_path = self._calculate_relative_path(version_info, model_type)
|
||||||
@@ -515,6 +559,11 @@ class DownloadManager:
|
|||||||
version_info, file_info, save_path
|
version_info, file_info, save_path
|
||||||
)
|
)
|
||||||
logger.info(f"Creating EmbeddingMetadata for {file_name}")
|
logger.info(f"Creating EmbeddingMetadata for {file_name}")
|
||||||
|
elif model_type == "misc":
|
||||||
|
metadata = MiscMetadata.from_civitai_info(
|
||||||
|
version_info, file_info, save_path
|
||||||
|
)
|
||||||
|
logger.info(f"Creating MiscMetadata for {file_name}")
|
||||||
|
|
||||||
# 6. Start download process
|
# 6. Start download process
|
||||||
result = await self._execute_download(
|
result = await self._execute_download(
|
||||||
@@ -620,6 +669,8 @@ class DownloadManager:
|
|||||||
scanner = await self._get_checkpoint_scanner()
|
scanner = await self._get_checkpoint_scanner()
|
||||||
elif model_type == "embedding":
|
elif model_type == "embedding":
|
||||||
scanner = await ServiceRegistry.get_embedding_scanner()
|
scanner = await ServiceRegistry.get_embedding_scanner()
|
||||||
|
elif model_type == "misc":
|
||||||
|
scanner = await self._get_misc_scanner()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug("Failed to acquire scanner for %s models: %s", model_type, exc)
|
logger.debug("Failed to acquire scanner for %s models: %s", model_type, exc)
|
||||||
|
|
||||||
@@ -1016,6 +1067,9 @@ class DownloadManager:
|
|||||||
elif model_type == "embedding":
|
elif model_type == "embedding":
|
||||||
scanner = await ServiceRegistry.get_embedding_scanner()
|
scanner = await ServiceRegistry.get_embedding_scanner()
|
||||||
logger.info(f"Updating embedding cache for {actual_file_paths[0]}")
|
logger.info(f"Updating embedding cache for {actual_file_paths[0]}")
|
||||||
|
elif model_type == "misc":
|
||||||
|
scanner = await self._get_misc_scanner()
|
||||||
|
logger.info(f"Updating misc cache for {actual_file_paths[0]}")
|
||||||
|
|
||||||
adjust_cached_entry = (
|
adjust_cached_entry = (
|
||||||
getattr(scanner, "adjust_cached_entry", None)
|
getattr(scanner, "adjust_cached_entry", None)
|
||||||
@@ -1125,6 +1179,14 @@ class DownloadManager:
|
|||||||
".pkl",
|
".pkl",
|
||||||
".sft",
|
".sft",
|
||||||
}
|
}
|
||||||
|
if model_type == "misc":
|
||||||
|
return {
|
||||||
|
".ckpt",
|
||||||
|
".pt",
|
||||||
|
".bin",
|
||||||
|
".pth",
|
||||||
|
".safetensors",
|
||||||
|
}
|
||||||
return {".safetensors"}
|
return {".safetensors"}
|
||||||
|
|
||||||
async def _extract_model_files_from_archive(
|
async def _extract_model_files_from_archive(
|
||||||
|
|||||||
55
py/services/misc_scanner.py
Normal file
55
py/services/misc_scanner.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import logging
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from ..utils.models import MiscMetadata
|
||||||
|
from ..config import config
|
||||||
|
from .model_scanner import ModelScanner
|
||||||
|
from .model_hash_index import ModelHashIndex
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MiscScanner(ModelScanner):
|
||||||
|
"""Service for scanning and managing misc files (VAE, Upscaler)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# Define supported file extensions (combined from VAE and upscaler)
|
||||||
|
file_extensions = {'.safetensors', '.pt', '.bin', '.ckpt', '.pth'}
|
||||||
|
super().__init__(
|
||||||
|
model_type="misc",
|
||||||
|
model_class=MiscMetadata,
|
||||||
|
file_extensions=file_extensions,
|
||||||
|
hash_index=ModelHashIndex()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _resolve_sub_type(self, root_path: Optional[str]) -> Optional[str]:
|
||||||
|
"""Resolve the sub-type based on the root path."""
|
||||||
|
if not root_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if config.vae_roots and root_path in config.vae_roots:
|
||||||
|
return "vae"
|
||||||
|
|
||||||
|
if config.upscaler_roots and root_path in config.upscaler_roots:
|
||||||
|
return "upscaler"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def adjust_metadata(self, metadata, file_path, root_path):
|
||||||
|
"""Adjust metadata during scanning to set sub_type."""
|
||||||
|
sub_type = self._resolve_sub_type(root_path)
|
||||||
|
if sub_type:
|
||||||
|
metadata.sub_type = sub_type
|
||||||
|
return metadata
|
||||||
|
|
||||||
|
def adjust_cached_entry(self, entry: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Adjust entries loaded from the persisted cache to ensure sub_type is set."""
|
||||||
|
sub_type = self._resolve_sub_type(
|
||||||
|
self._find_root_for_file(entry.get("file_path"))
|
||||||
|
)
|
||||||
|
if sub_type:
|
||||||
|
entry["sub_type"] = sub_type
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def get_model_roots(self) -> List[str]:
|
||||||
|
"""Get misc root directories (VAE and upscaler)"""
|
||||||
|
return config.misc_roots
|
||||||
55
py/services/misc_service.py
Normal file
55
py/services/misc_service.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from .base_model_service import BaseModelService
|
||||||
|
from ..utils.models import MiscMetadata
|
||||||
|
from ..config import config
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MiscService(BaseModelService):
|
||||||
|
"""Misc-specific service implementation (VAE, Upscaler)"""
|
||||||
|
|
||||||
|
def __init__(self, scanner, update_service=None):
|
||||||
|
"""Initialize Misc service
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scanner: Misc scanner instance
|
||||||
|
update_service: Optional service for remote update tracking.
|
||||||
|
"""
|
||||||
|
super().__init__("misc", scanner, MiscMetadata, update_service=update_service)
|
||||||
|
|
||||||
|
async def format_response(self, misc_data: Dict) -> Dict:
|
||||||
|
"""Format Misc data for API response"""
|
||||||
|
# Get sub_type from cache entry (new canonical field)
|
||||||
|
sub_type = misc_data.get("sub_type", "vae")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"model_name": misc_data["model_name"],
|
||||||
|
"file_name": misc_data["file_name"],
|
||||||
|
"preview_url": config.get_preview_static_url(misc_data.get("preview_url", "")),
|
||||||
|
"preview_nsfw_level": misc_data.get("preview_nsfw_level", 0),
|
||||||
|
"base_model": misc_data.get("base_model", ""),
|
||||||
|
"folder": misc_data["folder"],
|
||||||
|
"sha256": misc_data.get("sha256", ""),
|
||||||
|
"file_path": misc_data["file_path"].replace(os.sep, "/"),
|
||||||
|
"file_size": misc_data.get("size", 0),
|
||||||
|
"modified": misc_data.get("modified", ""),
|
||||||
|
"tags": misc_data.get("tags", []),
|
||||||
|
"from_civitai": misc_data.get("from_civitai", True),
|
||||||
|
"usage_count": misc_data.get("usage_count", 0),
|
||||||
|
"notes": misc_data.get("notes", ""),
|
||||||
|
"sub_type": sub_type,
|
||||||
|
"favorite": misc_data.get("favorite", False),
|
||||||
|
"update_available": bool(misc_data.get("update_available", False)),
|
||||||
|
"civitai": self.filter_civitai_data(misc_data.get("civitai", {}), minimal=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def find_duplicate_hashes(self) -> Dict:
|
||||||
|
"""Find Misc models with duplicate SHA256 hashes"""
|
||||||
|
return self.scanner._hash_index.get_duplicate_hashes()
|
||||||
|
|
||||||
|
def find_duplicate_filenames(self) -> Dict:
|
||||||
|
"""Find Misc models with conflicting filenames"""
|
||||||
|
return self.scanner._hash_index.get_duplicate_filenames()
|
||||||
@@ -118,19 +118,24 @@ class ModelServiceFactory:
|
|||||||
|
|
||||||
|
|
||||||
def register_default_model_types():
|
def register_default_model_types():
|
||||||
"""Register the default model types (LoRA, Checkpoint, and Embedding)"""
|
"""Register the default model types (LoRA, Checkpoint, Embedding, and Misc)"""
|
||||||
from ..services.lora_service import LoraService
|
from ..services.lora_service import LoraService
|
||||||
from ..services.checkpoint_service import CheckpointService
|
from ..services.checkpoint_service import CheckpointService
|
||||||
from ..services.embedding_service import EmbeddingService
|
from ..services.embedding_service import EmbeddingService
|
||||||
|
from ..services.misc_service import MiscService
|
||||||
from ..routes.lora_routes import LoraRoutes
|
from ..routes.lora_routes import LoraRoutes
|
||||||
from ..routes.checkpoint_routes import CheckpointRoutes
|
from ..routes.checkpoint_routes import CheckpointRoutes
|
||||||
from ..routes.embedding_routes import EmbeddingRoutes
|
from ..routes.embedding_routes import EmbeddingRoutes
|
||||||
|
from ..routes.misc_model_routes import MiscModelRoutes
|
||||||
|
|
||||||
# Register LoRA model type
|
# Register LoRA model type
|
||||||
ModelServiceFactory.register_model_type('lora', LoraService, LoraRoutes)
|
ModelServiceFactory.register_model_type('lora', LoraService, LoraRoutes)
|
||||||
|
|
||||||
# Register Checkpoint model type
|
# Register Checkpoint model type
|
||||||
ModelServiceFactory.register_model_type('checkpoint', CheckpointService, CheckpointRoutes)
|
ModelServiceFactory.register_model_type('checkpoint', CheckpointService, CheckpointRoutes)
|
||||||
|
|
||||||
# Register Embedding model type
|
# Register Embedding model type
|
||||||
ModelServiceFactory.register_model_type('embedding', EmbeddingService, EmbeddingRoutes)
|
ModelServiceFactory.register_model_type('embedding', EmbeddingService, EmbeddingRoutes)
|
||||||
|
|
||||||
|
# Register Misc model type (VAE, Upscaler)
|
||||||
|
ModelServiceFactory.register_model_type('misc', MiscService, MiscModelRoutes)
|
||||||
@@ -233,23 +233,44 @@ class ServiceRegistry:
|
|||||||
async def get_embedding_scanner(cls):
|
async def get_embedding_scanner(cls):
|
||||||
"""Get or create Embedding scanner instance"""
|
"""Get or create Embedding scanner instance"""
|
||||||
service_name = "embedding_scanner"
|
service_name = "embedding_scanner"
|
||||||
|
|
||||||
if service_name in cls._services:
|
if service_name in cls._services:
|
||||||
return cls._services[service_name]
|
return cls._services[service_name]
|
||||||
|
|
||||||
async with cls._get_lock(service_name):
|
async with cls._get_lock(service_name):
|
||||||
# Double-check after acquiring lock
|
# Double-check after acquiring lock
|
||||||
if service_name in cls._services:
|
if service_name in cls._services:
|
||||||
return cls._services[service_name]
|
return cls._services[service_name]
|
||||||
|
|
||||||
# Import here to avoid circular imports
|
# Import here to avoid circular imports
|
||||||
from .embedding_scanner import EmbeddingScanner
|
from .embedding_scanner import EmbeddingScanner
|
||||||
|
|
||||||
scanner = await EmbeddingScanner.get_instance()
|
scanner = await EmbeddingScanner.get_instance()
|
||||||
cls._services[service_name] = scanner
|
cls._services[service_name] = scanner
|
||||||
logger.debug(f"Created and registered {service_name}")
|
logger.debug(f"Created and registered {service_name}")
|
||||||
return scanner
|
return scanner
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def get_misc_scanner(cls):
|
||||||
|
"""Get or create Misc scanner instance (VAE, Upscaler)"""
|
||||||
|
service_name = "misc_scanner"
|
||||||
|
|
||||||
|
if service_name in cls._services:
|
||||||
|
return cls._services[service_name]
|
||||||
|
|
||||||
|
async with cls._get_lock(service_name):
|
||||||
|
# Double-check after acquiring lock
|
||||||
|
if service_name in cls._services:
|
||||||
|
return cls._services[service_name]
|
||||||
|
|
||||||
|
# Import here to avoid circular imports
|
||||||
|
from .misc_scanner import MiscScanner
|
||||||
|
|
||||||
|
scanner = await MiscScanner.get_instance()
|
||||||
|
cls._services[service_name] = scanner
|
||||||
|
logger.debug(f"Created and registered {service_name}")
|
||||||
|
return scanner
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def clear_services(cls):
|
def clear_services(cls):
|
||||||
"""Clear all registered services - mainly for testing"""
|
"""Clear all registered services - mainly for testing"""
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ SUPPORTED_MEDIA_EXTENSIONS = {
|
|||||||
VALID_LORA_SUB_TYPES = ["lora", "locon", "dora"]
|
VALID_LORA_SUB_TYPES = ["lora", "locon", "dora"]
|
||||||
VALID_CHECKPOINT_SUB_TYPES = ["checkpoint", "diffusion_model"]
|
VALID_CHECKPOINT_SUB_TYPES = ["checkpoint", "diffusion_model"]
|
||||||
VALID_EMBEDDING_SUB_TYPES = ["embedding"]
|
VALID_EMBEDDING_SUB_TYPES = ["embedding"]
|
||||||
|
VALID_MISC_SUB_TYPES = ["vae", "upscaler"]
|
||||||
|
|
||||||
# Backward compatibility alias
|
# Backward compatibility alias
|
||||||
VALID_LORA_TYPES = VALID_LORA_SUB_TYPES
|
VALID_LORA_TYPES = VALID_LORA_SUB_TYPES
|
||||||
@@ -94,6 +95,7 @@ DEFAULT_PRIORITY_TAG_CONFIG = {
|
|||||||
"lora": ", ".join(CIVITAI_MODEL_TAGS),
|
"lora": ", ".join(CIVITAI_MODEL_TAGS),
|
||||||
"checkpoint": ", ".join(CIVITAI_MODEL_TAGS),
|
"checkpoint": ", ".join(CIVITAI_MODEL_TAGS),
|
||||||
"embedding": ", ".join(CIVITAI_MODEL_TAGS),
|
"embedding": ", ".join(CIVITAI_MODEL_TAGS),
|
||||||
|
"misc": ", ".join(CIVITAI_MODEL_TAGS),
|
||||||
}
|
}
|
||||||
|
|
||||||
# baseModel values from CivitAI that should be treated as diffusion models (unet)
|
# baseModel values from CivitAI that should be treated as diffusion models (unet)
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ class EmbeddingMetadata(BaseModelMetadata):
|
|||||||
file_name = file_info['name']
|
file_name = file_info['name']
|
||||||
base_model = determine_base_model(version_info.get('baseModel', ''))
|
base_model = determine_base_model(version_info.get('baseModel', ''))
|
||||||
sub_type = version_info.get('type', 'embedding')
|
sub_type = version_info.get('type', 'embedding')
|
||||||
|
|
||||||
# Extract tags and description if available
|
# Extract tags and description if available
|
||||||
tags = []
|
tags = []
|
||||||
description = ""
|
description = ""
|
||||||
@@ -228,7 +228,53 @@ class EmbeddingMetadata(BaseModelMetadata):
|
|||||||
tags = version_info['model']['tags']
|
tags = version_info['model']['tags']
|
||||||
if 'description' in version_info['model']:
|
if 'description' in version_info['model']:
|
||||||
description = version_info['model']['description']
|
description = version_info['model']['description']
|
||||||
|
|
||||||
|
return cls(
|
||||||
|
file_name=os.path.splitext(file_name)[0],
|
||||||
|
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
|
||||||
|
file_path=save_path.replace(os.sep, '/'),
|
||||||
|
size=file_info.get('sizeKB', 0) * 1024,
|
||||||
|
modified=datetime.now().timestamp(),
|
||||||
|
sha256=file_info['hashes'].get('SHA256', '').lower(),
|
||||||
|
base_model=base_model,
|
||||||
|
preview_url=None, # Will be updated after preview download
|
||||||
|
preview_nsfw_level=0,
|
||||||
|
from_civitai=True,
|
||||||
|
civitai=version_info,
|
||||||
|
sub_type=sub_type,
|
||||||
|
tags=tags,
|
||||||
|
modelDescription=description
|
||||||
|
)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MiscMetadata(BaseModelMetadata):
|
||||||
|
"""Represents the metadata structure for a Misc model (VAE, Upscaler)"""
|
||||||
|
sub_type: str = "vae"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_civitai_info(cls, version_info: Dict, file_info: Dict, save_path: str) -> 'MiscMetadata':
|
||||||
|
"""Create MiscMetadata instance from Civitai version info"""
|
||||||
|
file_name = file_info['name']
|
||||||
|
base_model = determine_base_model(version_info.get('baseModel', ''))
|
||||||
|
|
||||||
|
# Determine sub_type from CivitAI model type
|
||||||
|
civitai_type = version_info.get('model', {}).get('type', '').lower()
|
||||||
|
if civitai_type == 'vae':
|
||||||
|
sub_type = 'vae'
|
||||||
|
elif civitai_type == 'upscaler':
|
||||||
|
sub_type = 'upscaler'
|
||||||
|
else:
|
||||||
|
sub_type = 'vae' # Default to vae
|
||||||
|
|
||||||
|
# Extract tags and description if available
|
||||||
|
tags = []
|
||||||
|
description = ""
|
||||||
|
if 'model' in version_info:
|
||||||
|
if 'tags' in version_info['model']:
|
||||||
|
tags = version_info['model']['tags']
|
||||||
|
if 'description' in version_info['model']:
|
||||||
|
description = version_info['model']['description']
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
file_name=os.path.splitext(file_name)[0],
|
file_name=os.path.splitext(file_name)[0],
|
||||||
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
|
model_name=version_info.get('model').get('name', os.path.splitext(file_name)[0]),
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import { state } from '../state/index.js';
|
|||||||
export const MODEL_TYPES = {
|
export const MODEL_TYPES = {
|
||||||
LORA: 'loras',
|
LORA: 'loras',
|
||||||
CHECKPOINT: 'checkpoints',
|
CHECKPOINT: 'checkpoints',
|
||||||
EMBEDDING: 'embeddings' // Future model type
|
EMBEDDING: 'embeddings',
|
||||||
|
MISC: 'misc'
|
||||||
};
|
};
|
||||||
|
|
||||||
// Base API configuration for each model type
|
// Base API configuration for each model type
|
||||||
@@ -40,6 +41,15 @@ export const MODEL_CONFIG = {
|
|||||||
supportsBulkOperations: true,
|
supportsBulkOperations: true,
|
||||||
supportsMove: true,
|
supportsMove: true,
|
||||||
templateName: 'embeddings.html'
|
templateName: 'embeddings.html'
|
||||||
|
},
|
||||||
|
[MODEL_TYPES.MISC]: {
|
||||||
|
displayName: 'Misc',
|
||||||
|
singularName: 'misc',
|
||||||
|
defaultPageSize: 100,
|
||||||
|
supportsLetterFilter: false,
|
||||||
|
supportsBulkOperations: true,
|
||||||
|
supportsMove: true,
|
||||||
|
templateName: 'misc.html'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,6 +143,11 @@ export const MODEL_SPECIFIC_ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
[MODEL_TYPES.EMBEDDING]: {
|
[MODEL_TYPES.EMBEDDING]: {
|
||||||
metadata: `/api/lm/${MODEL_TYPES.EMBEDDING}/metadata`,
|
metadata: `/api/lm/${MODEL_TYPES.EMBEDDING}/metadata`,
|
||||||
|
},
|
||||||
|
[MODEL_TYPES.MISC]: {
|
||||||
|
metadata: `/api/lm/${MODEL_TYPES.MISC}/metadata`,
|
||||||
|
vae_roots: `/api/lm/${MODEL_TYPES.MISC}/vae_roots`,
|
||||||
|
upscaler_roots: `/api/lm/${MODEL_TYPES.MISC}/upscaler_roots`,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
62
static/js/api/miscApi.js
Normal file
62
static/js/api/miscApi.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { BaseModelApiClient } from './baseModelApi.js';
|
||||||
|
import { getSessionItem } from '../utils/storageHelpers.js';
|
||||||
|
|
||||||
|
export class MiscApiClient extends BaseModelApiClient {
|
||||||
|
_addModelSpecificParams(params, pageState) {
|
||||||
|
const filterMiscHash = getSessionItem('recipe_to_misc_filterHash');
|
||||||
|
const filterMiscHashes = getSessionItem('recipe_to_misc_filterHashes');
|
||||||
|
|
||||||
|
if (filterMiscHash) {
|
||||||
|
params.append('misc_hash', filterMiscHash);
|
||||||
|
} else if (filterMiscHashes) {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(filterMiscHashes) && filterMiscHashes.length > 0) {
|
||||||
|
params.append('misc_hashes', filterMiscHashes.join(','));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing misc hashes from session storage:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageState.subType) {
|
||||||
|
params.append('sub_type', pageState.subType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMiscInfo(filePath) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.specific.info, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ file_path: filePath })
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch misc info');
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching misc info:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getVaeRoots() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.specific.vae_roots, { method: 'GET' });
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch VAE roots');
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching VAE roots:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUpscalerRoots() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.specific.upscaler_roots, { method: 'GET' });
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch upscaler roots');
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching upscaler roots:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { LoraApiClient } from './loraApi.js';
|
import { LoraApiClient } from './loraApi.js';
|
||||||
import { CheckpointApiClient } from './checkpointApi.js';
|
import { CheckpointApiClient } from './checkpointApi.js';
|
||||||
import { EmbeddingApiClient } from './embeddingApi.js';
|
import { EmbeddingApiClient } from './embeddingApi.js';
|
||||||
|
import { MiscApiClient } from './miscApi.js';
|
||||||
import { MODEL_TYPES, isValidModelType } from './apiConfig.js';
|
import { MODEL_TYPES, isValidModelType } from './apiConfig.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ export function createModelApiClient(modelType) {
|
|||||||
return new CheckpointApiClient(MODEL_TYPES.CHECKPOINT);
|
return new CheckpointApiClient(MODEL_TYPES.CHECKPOINT);
|
||||||
case MODEL_TYPES.EMBEDDING:
|
case MODEL_TYPES.EMBEDDING:
|
||||||
return new EmbeddingApiClient(MODEL_TYPES.EMBEDDING);
|
return new EmbeddingApiClient(MODEL_TYPES.EMBEDDING);
|
||||||
|
case MODEL_TYPES.MISC:
|
||||||
|
return new MiscApiClient(MODEL_TYPES.MISC);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported model type: ${modelType}`);
|
throw new Error(`Unsupported model type: ${modelType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
85
static/js/components/ContextMenu/MiscContextMenu.js
Normal file
85
static/js/components/ContextMenu/MiscContextMenu.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import { BaseContextMenu } from './BaseContextMenu.js';
|
||||||
|
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
||||||
|
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
||||||
|
import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js';
|
||||||
|
import { moveManager } from '../../managers/MoveManager.js';
|
||||||
|
import { i18n } from '../../i18n/index.js';
|
||||||
|
|
||||||
|
export class MiscContextMenu extends BaseContextMenu {
|
||||||
|
constructor() {
|
||||||
|
super('miscContextMenu', '.model-card');
|
||||||
|
this.nsfwSelector = document.getElementById('nsfwLevelSelector');
|
||||||
|
this.modelType = 'misc';
|
||||||
|
this.resetAndReload = resetAndReload;
|
||||||
|
|
||||||
|
this.initNSFWSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation needed by the mixin
|
||||||
|
async saveModelMetadata(filePath, data) {
|
||||||
|
return getModelApiClient().saveModelMetadata(filePath, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
showMenu(x, y, card) {
|
||||||
|
super.showMenu(x, y, card);
|
||||||
|
|
||||||
|
// Update the "Move to other root" label based on current model type
|
||||||
|
const moveOtherItem = this.menu.querySelector('[data-action="move-other"]');
|
||||||
|
if (moveOtherItem) {
|
||||||
|
const currentType = card.dataset.sub_type || 'vae';
|
||||||
|
const otherType = currentType === 'vae' ? 'upscaler' : 'vae';
|
||||||
|
const typeLabel = i18n.t(`misc.modelTypes.${otherType}`);
|
||||||
|
moveOtherItem.innerHTML = `<i class="fas fa-exchange-alt"></i> ${i18n.t('misc.contextMenu.moveToOtherTypeFolder', { otherType: typeLabel })}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMenuAction(action) {
|
||||||
|
// First try to handle with common actions
|
||||||
|
if (ModelContextMenuMixin.handleCommonMenuActions.call(this, action)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiClient = getModelApiClient();
|
||||||
|
|
||||||
|
// Otherwise handle misc-specific actions
|
||||||
|
switch (action) {
|
||||||
|
case 'details':
|
||||||
|
// Show misc details
|
||||||
|
this.currentCard.click();
|
||||||
|
break;
|
||||||
|
case 'replace-preview':
|
||||||
|
// Add new action for replacing preview images
|
||||||
|
apiClient.replaceModelPreview(this.currentCard.dataset.filepath);
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
showDeleteModal(this.currentCard.dataset.filepath);
|
||||||
|
break;
|
||||||
|
case 'copyname':
|
||||||
|
// Copy misc model name
|
||||||
|
if (this.currentCard.querySelector('.fa-copy')) {
|
||||||
|
this.currentCard.querySelector('.fa-copy').click();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'refresh-metadata':
|
||||||
|
// Refresh metadata from CivitAI
|
||||||
|
apiClient.refreshSingleModelMetadata(this.currentCard.dataset.filepath);
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
moveManager.showMoveModal(this.currentCard.dataset.filepath, this.currentCard.dataset.sub_type);
|
||||||
|
break;
|
||||||
|
case 'move-other':
|
||||||
|
{
|
||||||
|
const currentType = this.currentCard.dataset.sub_type || 'vae';
|
||||||
|
const otherType = currentType === 'vae' ? 'upscaler' : 'vae';
|
||||||
|
moveManager.showMoveModal(this.currentCard.dataset.filepath, otherType);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'exclude':
|
||||||
|
showExcludeModal(this.currentCard.dataset.filepath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mix in shared methods
|
||||||
|
Object.assign(MiscContextMenu.prototype, ModelContextMenuMixin);
|
||||||
@@ -2,6 +2,7 @@ export { LoraContextMenu } from './LoraContextMenu.js';
|
|||||||
export { RecipeContextMenu } from './RecipeContextMenu.js';
|
export { RecipeContextMenu } from './RecipeContextMenu.js';
|
||||||
export { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
export { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
||||||
export { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
export { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
||||||
|
export { MiscContextMenu } from './MiscContextMenu.js';
|
||||||
export { GlobalContextMenu } from './GlobalContextMenu.js';
|
export { GlobalContextMenu } from './GlobalContextMenu.js';
|
||||||
export { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
export { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ import { LoraContextMenu } from './LoraContextMenu.js';
|
|||||||
import { RecipeContextMenu } from './RecipeContextMenu.js';
|
import { RecipeContextMenu } from './RecipeContextMenu.js';
|
||||||
import { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
import { CheckpointContextMenu } from './CheckpointContextMenu.js';
|
||||||
import { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
import { EmbeddingContextMenu } from './EmbeddingContextMenu.js';
|
||||||
|
import { MiscContextMenu } from './MiscContextMenu.js';
|
||||||
import { GlobalContextMenu } from './GlobalContextMenu.js';
|
import { GlobalContextMenu } from './GlobalContextMenu.js';
|
||||||
|
|
||||||
// Factory method to create page-specific context menu instances
|
// Factory method to create page-specific context menu instances
|
||||||
@@ -22,6 +24,8 @@ export function createPageContextMenu(pageType) {
|
|||||||
return new CheckpointContextMenu();
|
return new CheckpointContextMenu();
|
||||||
case 'embeddings':
|
case 'embeddings':
|
||||||
return new EmbeddingContextMenu();
|
return new EmbeddingContextMenu();
|
||||||
|
case 'misc':
|
||||||
|
return new MiscContextMenu();
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export class HeaderManager {
|
|||||||
if (path.includes('/checkpoints')) return 'checkpoints';
|
if (path.includes('/checkpoints')) return 'checkpoints';
|
||||||
if (path.includes('/embeddings')) return 'embeddings';
|
if (path.includes('/embeddings')) return 'embeddings';
|
||||||
if (path.includes('/statistics')) return 'statistics';
|
if (path.includes('/statistics')) return 'statistics';
|
||||||
|
if (path.includes('/misc')) return 'misc';
|
||||||
if (path.includes('/loras')) return 'loras';
|
if (path.includes('/loras')) return 'loras';
|
||||||
return 'unknown';
|
return 'unknown';
|
||||||
}
|
}
|
||||||
|
|||||||
119
static/js/components/controls/MiscControls.js
Normal file
119
static/js/components/controls/MiscControls.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
// MiscControls.js - Specific implementation for the Misc (VAE/Upscaler) page
|
||||||
|
import { PageControls } from './PageControls.js';
|
||||||
|
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
|
||||||
|
import { getSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
|
||||||
|
import { downloadManager } from '../../managers/DownloadManager.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MiscControls class - Extends PageControls for Misc-specific functionality
|
||||||
|
*/
|
||||||
|
export class MiscControls extends PageControls {
|
||||||
|
constructor() {
|
||||||
|
// Initialize with 'misc' page type
|
||||||
|
super('misc');
|
||||||
|
|
||||||
|
// Register API methods specific to the Misc page
|
||||||
|
this.registerMiscAPI();
|
||||||
|
|
||||||
|
// Check for custom filters (e.g., from recipe navigation)
|
||||||
|
this.checkCustomFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Misc-specific API methods
|
||||||
|
*/
|
||||||
|
registerMiscAPI() {
|
||||||
|
const miscAPI = {
|
||||||
|
// Core API functions
|
||||||
|
loadMoreModels: async (resetPage = false, updateFolders = false) => {
|
||||||
|
return await getModelApiClient().loadMoreWithVirtualScroll(resetPage, updateFolders);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetAndReload: async (updateFolders = false) => {
|
||||||
|
return await resetAndReload(updateFolders);
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshModels: async (fullRebuild = false) => {
|
||||||
|
return await getModelApiClient().refreshModels(fullRebuild);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add fetch from Civitai functionality for misc models
|
||||||
|
fetchFromCivitai: async () => {
|
||||||
|
return await getModelApiClient().fetchCivitaiMetadata();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Add show download modal functionality
|
||||||
|
showDownloadModal: () => {
|
||||||
|
downloadManager.showDownloadModal();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleBulkMode: () => {
|
||||||
|
if (window.bulkManager) {
|
||||||
|
window.bulkManager.toggleBulkMode();
|
||||||
|
} else {
|
||||||
|
console.error('Bulk manager not available');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
clearCustomFilter: async () => {
|
||||||
|
await this.clearCustomFilter();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the API
|
||||||
|
this.registerAPI(miscAPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for custom filters sent from other pages (e.g., recipe modal)
|
||||||
|
*/
|
||||||
|
checkCustomFilters() {
|
||||||
|
const filterMiscHash = getSessionItem('recipe_to_misc_filterHash');
|
||||||
|
const filterRecipeName = getSessionItem('filterMiscRecipeName');
|
||||||
|
|
||||||
|
if (filterMiscHash && filterRecipeName) {
|
||||||
|
const indicator = document.getElementById('customFilterIndicator');
|
||||||
|
const filterText = indicator?.querySelector('.customFilterText');
|
||||||
|
|
||||||
|
if (indicator && filterText) {
|
||||||
|
indicator.classList.remove('hidden');
|
||||||
|
|
||||||
|
const displayText = `Viewing misc model from: ${filterRecipeName}`;
|
||||||
|
filterText.textContent = this._truncateText(displayText, 30);
|
||||||
|
filterText.setAttribute('title', displayText);
|
||||||
|
|
||||||
|
const filterElement = indicator.querySelector('.filter-active');
|
||||||
|
if (filterElement) {
|
||||||
|
filterElement.classList.add('animate');
|
||||||
|
setTimeout(() => filterElement.classList.remove('animate'), 600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear misc custom filter and reload
|
||||||
|
*/
|
||||||
|
async clearCustomFilter() {
|
||||||
|
removeSessionItem('recipe_to_misc_filterHash');
|
||||||
|
removeSessionItem('recipe_to_misc_filterHashes');
|
||||||
|
removeSessionItem('filterMiscRecipeName');
|
||||||
|
|
||||||
|
const indicator = document.getElementById('customFilterIndicator');
|
||||||
|
if (indicator) {
|
||||||
|
indicator.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
await resetAndReload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to truncate text with ellipsis
|
||||||
|
* @param {string} text
|
||||||
|
* @param {number} maxLength
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
_truncateText(text, maxLength) {
|
||||||
|
return text.length > maxLength ? `${text.substring(0, maxLength - 3)}...` : text;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,14 @@ import { PageControls } from './PageControls.js';
|
|||||||
import { LorasControls } from './LorasControls.js';
|
import { LorasControls } from './LorasControls.js';
|
||||||
import { CheckpointsControls } from './CheckpointsControls.js';
|
import { CheckpointsControls } from './CheckpointsControls.js';
|
||||||
import { EmbeddingsControls } from './EmbeddingsControls.js';
|
import { EmbeddingsControls } from './EmbeddingsControls.js';
|
||||||
|
import { MiscControls } from './MiscControls.js';
|
||||||
|
|
||||||
// Export the classes
|
// Export the classes
|
||||||
export { PageControls, LorasControls, CheckpointsControls, EmbeddingsControls };
|
export { PageControls, LorasControls, CheckpointsControls, EmbeddingsControls, MiscControls };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory function to create the appropriate controls based on page type
|
* Factory function to create the appropriate controls based on page type
|
||||||
* @param {string} pageType - The type of page ('loras', 'checkpoints', or 'embeddings')
|
* @param {string} pageType - The type of page ('loras', 'checkpoints', 'embeddings', or 'misc')
|
||||||
* @returns {PageControls} - The appropriate controls instance
|
* @returns {PageControls} - The appropriate controls instance
|
||||||
*/
|
*/
|
||||||
export function createPageControls(pageType) {
|
export function createPageControls(pageType) {
|
||||||
@@ -19,6 +20,8 @@ export function createPageControls(pageType) {
|
|||||||
return new CheckpointsControls();
|
return new CheckpointsControls();
|
||||||
} else if (pageType === 'embeddings') {
|
} else if (pageType === 'embeddings') {
|
||||||
return new EmbeddingsControls();
|
return new EmbeddingsControls();
|
||||||
|
} else if (pageType === 'misc') {
|
||||||
|
return new MiscControls();
|
||||||
} else {
|
} else {
|
||||||
console.error(`Unknown page type: ${pageType}`);
|
console.error(`Unknown page type: ${pageType}`);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -214,6 +214,52 @@ function handleSendToWorkflow(card, replaceMode, modelType) {
|
|||||||
missingNodesMessage,
|
missingNodesMessage,
|
||||||
missingTargetMessage,
|
missingTargetMessage,
|
||||||
});
|
});
|
||||||
|
} else if (modelType === MODEL_TYPES.MISC) {
|
||||||
|
const modelPath = card.dataset.filepath;
|
||||||
|
if (!modelPath) {
|
||||||
|
const message = translate('modelCard.sendToWorkflow.missingPath', {}, 'Unable to determine model path for this card');
|
||||||
|
showToast(message, {}, 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtype = (card.dataset.sub_type || 'vae').toLowerCase();
|
||||||
|
const isVae = subtype === 'vae';
|
||||||
|
const widgetName = isVae ? 'vae_name' : 'model_name';
|
||||||
|
const actionTypeText = translate(
|
||||||
|
isVae ? 'uiHelpers.nodeSelector.vae' : 'uiHelpers.nodeSelector.upscaler',
|
||||||
|
{},
|
||||||
|
isVae ? 'VAE' : 'Upscaler'
|
||||||
|
);
|
||||||
|
const successMessage = translate(
|
||||||
|
isVae ? 'uiHelpers.workflow.vaeUpdated' : 'uiHelpers.workflow.upscalerUpdated',
|
||||||
|
{},
|
||||||
|
isVae ? 'VAE updated in workflow' : 'Upscaler updated in workflow'
|
||||||
|
);
|
||||||
|
const failureMessage = translate(
|
||||||
|
isVae ? 'uiHelpers.workflow.vaeFailed' : 'uiHelpers.workflow.upscalerFailed',
|
||||||
|
{},
|
||||||
|
isVae ? 'Failed to update VAE node' : 'Failed to update upscaler node'
|
||||||
|
);
|
||||||
|
const missingNodesMessage = translate(
|
||||||
|
'uiHelpers.workflow.noMatchingNodes',
|
||||||
|
{},
|
||||||
|
'No compatible nodes available in the current workflow'
|
||||||
|
);
|
||||||
|
const missingTargetMessage = translate(
|
||||||
|
'uiHelpers.workflow.noTargetNodeSelected',
|
||||||
|
{},
|
||||||
|
'No target node selected'
|
||||||
|
);
|
||||||
|
|
||||||
|
sendModelPathToWorkflow(modelPath, {
|
||||||
|
widgetName,
|
||||||
|
collectionType: MODEL_TYPES.MISC,
|
||||||
|
actionTypeText,
|
||||||
|
successMessage,
|
||||||
|
failureMessage,
|
||||||
|
missingNodesMessage,
|
||||||
|
missingTargetMessage,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
|
showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
|
||||||
}
|
}
|
||||||
@@ -230,6 +276,10 @@ function handleCopyAction(card, modelType) {
|
|||||||
} else if (modelType === MODEL_TYPES.EMBEDDING) {
|
} else if (modelType === MODEL_TYPES.EMBEDDING) {
|
||||||
const embeddingName = card.dataset.file_name;
|
const embeddingName = card.dataset.file_name;
|
||||||
copyToClipboard(embeddingName, 'Embedding name copied');
|
copyToClipboard(embeddingName, 'Embedding name copied');
|
||||||
|
} else if (modelType === MODEL_TYPES.MISC) {
|
||||||
|
const miscName = card.dataset.file_name;
|
||||||
|
const message = translate('modelCard.actions.miscNameCopied', {}, 'Model name copied');
|
||||||
|
copyToClipboard(miscName, message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export class AppCore {
|
|||||||
initializePageFeatures() {
|
initializePageFeatures() {
|
||||||
const pageType = this.getPageType();
|
const pageType = this.getPageType();
|
||||||
|
|
||||||
if (['loras', 'recipes', 'checkpoints', 'embeddings'].includes(pageType)) {
|
if (['loras', 'recipes', 'checkpoints', 'embeddings', 'misc'].includes(pageType)) {
|
||||||
this.initializeContextMenus(pageType);
|
this.initializeContextMenus(pageType);
|
||||||
initializeInfiniteScroll(pageType);
|
initializeInfiniteScroll(pageType);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,17 @@ export class BulkManager {
|
|||||||
deleteAll: true,
|
deleteAll: true,
|
||||||
setContentRating: true
|
setContentRating: true
|
||||||
},
|
},
|
||||||
|
[MODEL_TYPES.MISC]: {
|
||||||
|
addTags: true,
|
||||||
|
sendToWorkflow: false,
|
||||||
|
copyAll: false,
|
||||||
|
refreshAll: true,
|
||||||
|
checkUpdates: true,
|
||||||
|
moveAll: true,
|
||||||
|
autoOrganize: true,
|
||||||
|
deleteAll: true,
|
||||||
|
setContentRating: true
|
||||||
|
},
|
||||||
recipes: {
|
recipes: {
|
||||||
addTags: false,
|
addTags: false,
|
||||||
sendToWorkflow: false,
|
sendToWorkflow: false,
|
||||||
|
|||||||
51
static/js/misc.js
Normal file
51
static/js/misc.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { appCore } from './core.js';
|
||||||
|
import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } from './utils/modalUtils.js';
|
||||||
|
import { createPageControls } from './components/controls/index.js';
|
||||||
|
import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js';
|
||||||
|
import { MODEL_TYPES } from './api/apiConfig.js';
|
||||||
|
|
||||||
|
// Initialize the Misc (VAE/Upscaler) page
|
||||||
|
export class MiscPageManager {
|
||||||
|
constructor() {
|
||||||
|
// Initialize page controls
|
||||||
|
this.pageControls = createPageControls(MODEL_TYPES.MISC);
|
||||||
|
|
||||||
|
// Initialize the ModelDuplicatesManager
|
||||||
|
this.duplicatesManager = new ModelDuplicatesManager(this, MODEL_TYPES.MISC);
|
||||||
|
|
||||||
|
// Expose only necessary functions to global scope
|
||||||
|
this._exposeRequiredGlobalFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
|
_exposeRequiredGlobalFunctions() {
|
||||||
|
// Minimal set of functions that need to remain global
|
||||||
|
window.confirmDelete = confirmDelete;
|
||||||
|
window.closeDeleteModal = closeDeleteModal;
|
||||||
|
window.confirmExclude = confirmExclude;
|
||||||
|
window.closeExcludeModal = closeExcludeModal;
|
||||||
|
|
||||||
|
// Expose duplicates manager
|
||||||
|
window.modelDuplicatesManager = this.duplicatesManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
// Initialize common page features (including context menus)
|
||||||
|
appCore.initializePageFeatures();
|
||||||
|
|
||||||
|
console.log('Misc Manager initialized');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initializeMiscPage() {
|
||||||
|
// Initialize core application
|
||||||
|
await appCore.initialize();
|
||||||
|
|
||||||
|
// Initialize misc page
|
||||||
|
const miscPage = new MiscPageManager();
|
||||||
|
await miscPage.initialize();
|
||||||
|
|
||||||
|
return miscPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize everything when DOM is ready
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeMiscPage);
|
||||||
@@ -177,6 +177,35 @@ export const state = {
|
|||||||
showFavoritesOnly: false,
|
showFavoritesOnly: false,
|
||||||
showUpdateAvailableOnly: false,
|
showUpdateAvailableOnly: false,
|
||||||
duplicatesMode: false,
|
duplicatesMode: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
[MODEL_TYPES.MISC]: {
|
||||||
|
currentPage: 1,
|
||||||
|
isLoading: false,
|
||||||
|
hasMore: true,
|
||||||
|
sortBy: 'name',
|
||||||
|
activeFolder: getStorageItem(`${MODEL_TYPES.MISC}_activeFolder`),
|
||||||
|
previewVersions: new Map(),
|
||||||
|
searchManager: null,
|
||||||
|
searchOptions: {
|
||||||
|
filename: true,
|
||||||
|
modelname: true,
|
||||||
|
creator: false,
|
||||||
|
recursive: getStorageItem(`${MODEL_TYPES.MISC}_recursiveSearch`, true),
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
baseModel: [],
|
||||||
|
tags: {},
|
||||||
|
license: {},
|
||||||
|
modelTypes: []
|
||||||
|
},
|
||||||
|
bulkMode: false,
|
||||||
|
selectedModels: new Set(),
|
||||||
|
metadataCache: new Map(),
|
||||||
|
showFavoritesOnly: false,
|
||||||
|
showUpdateAvailableOnly: false,
|
||||||
|
duplicatesMode: false,
|
||||||
|
subType: 'vae'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ export const MODEL_SUBTYPE_DISPLAY_NAMES = {
|
|||||||
diffusion_model: "Diffusion Model",
|
diffusion_model: "Diffusion Model",
|
||||||
// Embedding sub-types
|
// Embedding sub-types
|
||||||
embedding: "Embedding",
|
embedding: "Embedding",
|
||||||
|
// Misc sub-types
|
||||||
|
vae: "VAE",
|
||||||
|
upscaler: "Upscaler",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Backward compatibility alias
|
// Backward compatibility alias
|
||||||
@@ -81,6 +84,8 @@ export const MODEL_SUBTYPE_ABBREVIATIONS = {
|
|||||||
checkpoint: "CKPT",
|
checkpoint: "CKPT",
|
||||||
diffusion_model: "DM",
|
diffusion_model: "DM",
|
||||||
embedding: "EMB",
|
embedding: "EMB",
|
||||||
|
vae: "VAE",
|
||||||
|
upscaler: "UP",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getSubTypeAbbreviation(subType) {
|
export function getSubTypeAbbreviation(subType) {
|
||||||
|
|||||||
@@ -28,10 +28,9 @@ async function getCardCreator(pageType) {
|
|||||||
|
|
||||||
// Function to get the appropriate data fetcher based on page type
|
// Function to get the appropriate data fetcher based on page type
|
||||||
async function getDataFetcher(pageType) {
|
async function getDataFetcher(pageType) {
|
||||||
if (pageType === 'loras' || pageType === 'embeddings' || pageType === 'checkpoints') {
|
if (pageType === 'loras' || pageType === 'embeddings' || pageType === 'checkpoints' || pageType === 'misc') {
|
||||||
return (page = 1, pageSize = 100) => getModelApiClient().fetchModelsPage(page, pageSize);
|
return (page = 1, pageSize = 100) => getModelApiClient().fetchModelsPage(page, pageSize);
|
||||||
} else if (pageType === 'recipes') {
|
} else if (pageType === 'recipes') {
|
||||||
// Import the recipeApi module and use the fetchRecipesPage function
|
|
||||||
const { fetchRecipesPage } = await import('../api/recipeApi.js');
|
const { fetchRecipesPage } = await import('../api/recipeApi.js');
|
||||||
return fetchRecipesPage;
|
return fetchRecipesPage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
{% set current_page = 'checkpoints' %}
|
{% set current_page = 'checkpoints' %}
|
||||||
{% elif current_path.startswith('/embeddings') %}
|
{% elif current_path.startswith('/embeddings') %}
|
||||||
{% set current_page = 'embeddings' %}
|
{% set current_page = 'embeddings' %}
|
||||||
|
{% elif current_path.startswith('/misc') %}
|
||||||
|
{% set current_page = 'misc' %}
|
||||||
{% elif current_path.startswith('/statistics') %}
|
{% elif current_path.startswith('/statistics') %}
|
||||||
{% set current_page = 'statistics' %}
|
{% set current_page = 'statistics' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -38,6 +40,10 @@
|
|||||||
id="embeddingsNavItem">
|
id="embeddingsNavItem">
|
||||||
<i class="fas fa-code"></i> <span>{{ t('header.navigation.embeddings') }}</span>
|
<i class="fas fa-code"></i> <span>{{ t('header.navigation.embeddings') }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/misc" class="nav-item{% if current_path.startswith('/misc') %} active{% endif %}"
|
||||||
|
id="miscNavItem">
|
||||||
|
<i class="fas fa-puzzle-piece"></i> <span>{{ t('header.navigation.misc') }}</span>
|
||||||
|
</a>
|
||||||
<a href="/statistics" class="nav-item{% if current_path.startswith('/statistics') %} active{% endif %}"
|
<a href="/statistics" class="nav-item{% if current_path.startswith('/statistics') %} active{% endif %}"
|
||||||
id="statisticsNavItem">
|
id="statisticsNavItem">
|
||||||
<i class="fas fa-chart-bar"></i> <span>{{ t('header.navigation.statistics') }}</span>
|
<i class="fas fa-chart-bar"></i> <span>{{ t('header.navigation.statistics') }}</span>
|
||||||
@@ -116,6 +122,11 @@
|
|||||||
<div class="search-option-tag active" data-option="modelname">{{ t('header.search.filters.modelname') }}</div>
|
<div class="search-option-tag active" data-option="modelname">{{ t('header.search.filters.modelname') }}</div>
|
||||||
<div class="search-option-tag active" data-option="tags">{{ t('header.search.filters.tags') }}</div>
|
<div class="search-option-tag active" data-option="tags">{{ t('header.search.filters.tags') }}</div>
|
||||||
<div class="search-option-tag" data-option="creator">{{ t('header.search.filters.creator') }}</div>
|
<div class="search-option-tag" data-option="creator">{{ t('header.search.filters.creator') }}</div>
|
||||||
|
{% elif request.path == '/misc' %}
|
||||||
|
<div class="search-option-tag active" data-option="filename">{{ t('header.search.filters.filename') }}</div>
|
||||||
|
<div class="search-option-tag active" data-option="modelname">{{ t('header.search.filters.modelname') }}</div>
|
||||||
|
<div class="search-option-tag active" data-option="tags">{{ t('header.search.filters.tags') }}</div>
|
||||||
|
<div class="search-option-tag" data-option="creator">{{ t('header.search.filters.creator') }}</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Default options for LoRAs page -->
|
<!-- Default options for LoRAs page -->
|
||||||
<div class="search-option-tag active" data-option="filename">{{ t('header.search.filters.filename') }}</div>
|
<div class="search-option-tag active" data-option="filename">{{ t('header.search.filters.filename') }}</div>
|
||||||
@@ -156,7 +167,7 @@
|
|||||||
<div class="tags-loading">{{ t('common.status.loading') }}</div>
|
<div class="tags-loading">{{ t('common.status.loading') }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if current_page == 'loras' or current_page == 'checkpoints' %}
|
{% if current_page == 'loras' or current_page == 'checkpoints' or current_page == 'misc' %}
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<h4>{{ t('header.filter.modelTypes') }}</h4>
|
<h4>{{ t('header.filter.modelTypes') }}</h4>
|
||||||
<div class="filter-tags" id="modelTypeTags">
|
<div class="filter-tags" id="modelTypeTags">
|
||||||
|
|||||||
45
templates/misc.html
Normal file
45
templates/misc.html
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ t('misc.title') }}{% endblock %}
|
||||||
|
{% block page_id %}misc{% endblock %}
|
||||||
|
|
||||||
|
{% block init_title %}{{ t('initialization.misc.title') }}{% endblock %}
|
||||||
|
{% block init_message %}{{ t('initialization.misc.message') }}{% endblock %}
|
||||||
|
{% block init_check_url %}/api/lm/misc/list?page=1&page_size=1{% endblock %}
|
||||||
|
|
||||||
|
{% block additional_components %}
|
||||||
|
|
||||||
|
<div id="miscContextMenu" class="context-menu" style="display: none;">
|
||||||
|
<div class="context-menu-item" data-action="refresh-metadata"><i class="fas fa-sync"></i> {{ t('loras.contextMenu.refreshMetadata') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="relink-civitai"><i class="fas fa-link"></i> {{ t('loras.contextMenu.relinkCivitai') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="copyname"><i class="fas fa-copy"></i> {{ t('loras.contextMenu.copyFilename') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="preview"><i class="fas fa-folder-open"></i> {{ t('loras.contextMenu.openExamples') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="download-examples"><i class="fas fa-download"></i> {{ t('loras.contextMenu.downloadExamples') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="replace-preview"><i class="fas fa-image"></i> {{ t('loras.contextMenu.replacePreview') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="set-nsfw"><i class="fas fa-exclamation-triangle"></i> {{ t('loras.contextMenu.setContentRating') }}</div>
|
||||||
|
<div class="context-menu-separator"></div>
|
||||||
|
<div class="context-menu-item" data-action="move"><i class="fas fa-folder-open"></i> {{ t('loras.contextMenu.moveToFolder') }}</div>
|
||||||
|
<div class="context-menu-item" data-action="move-other"><i class="fas fa-exchange-alt"></i> {{ t('misc.contextMenu.moveToOtherTypeFolder', {otherType: '...'}) }}</div>
|
||||||
|
<div class="context-menu-item" data-action="exclude"><i class="fas fa-eye-slash"></i> {{ t('loras.contextMenu.excludeModel') }}</div>
|
||||||
|
<div class="context-menu-item delete-item" data-action="delete"><i class="fas fa-trash"></i> {{ t('loras.contextMenu.deleteModel') }}</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% include 'components/controls.html' %}
|
||||||
|
{% include 'components/duplicates_banner.html' %}
|
||||||
|
{% include 'components/folder_sidebar.html' %}
|
||||||
|
|
||||||
|
<!-- Misc cards container -->
|
||||||
|
<div class="card-grid" id="modelGrid">
|
||||||
|
<!-- Cards will be dynamically inserted here -->
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block overlay %}
|
||||||
|
<div class="bulk-mode-overlay"></div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block main_script %}
|
||||||
|
<script type="module" src="/loras_static/js/misc.js?v={{ version }}"></script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user