diff --git a/locales/de.json b/locales/de.json index 9aaafe7e..b059fe35 100644 --- a/locales/de.json +++ b/locales/de.json @@ -297,7 +297,9 @@ "loraAvailability": "LoRA-Verfügbarkeit", "availabilityReady": "Einsatzbereit", "availabilityMissing": "Mit fehlenden LoRAs", - "availabilityDeleted": "Mit gelöschten LoRAs" + "availabilityDeleted": "Mit gelöschten LoRAs", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "Theme wechseln", diff --git a/locales/en.json b/locales/en.json index ec26e99e..c6a6463a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -297,7 +297,9 @@ "loraAvailability": "Lora Availability", "availabilityReady": "Ready to use", "availabilityMissing": "Has missing", - "availabilityDeleted": "Has deleted" + "availabilityDeleted": "Has deleted", + "workflow": "Workflow", + "hasWorkflow": "Has Workflow" }, "theme": { "toggle": "Toggle theme", diff --git a/locales/es.json b/locales/es.json index 35f1cbd6..0194c645 100644 --- a/locales/es.json +++ b/locales/es.json @@ -297,7 +297,9 @@ "loraAvailability": "Disponibilidad de LoRAs", "availabilityReady": "Listos para usar", "availabilityMissing": "Con LoRAs faltantes", - "availabilityDeleted": "Con LoRAs eliminados" + "availabilityDeleted": "Con LoRAs eliminados", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "Cambiar tema", diff --git a/locales/fr.json b/locales/fr.json index 66392892..1a1f880b 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -297,7 +297,9 @@ "loraAvailability": "Disponibilité des LoRAs", "availabilityReady": "Prêts à l'emploi", "availabilityMissing": "Avec LoRAs manquants", - "availabilityDeleted": "Avec LoRAs supprimés" + "availabilityDeleted": "Avec LoRAs supprimés", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "Basculer le thème", diff --git a/locales/he.json b/locales/he.json index 2713af84..5fd519b0 100644 --- a/locales/he.json +++ b/locales/he.json @@ -297,7 +297,9 @@ "loraAvailability": "זמינות LoRA", "availabilityReady": "מוכנים לשימוש", "availabilityMissing": "עם LoRAs חסרים", - "availabilityDeleted": "עם LoRAs שנמחקו" + "availabilityDeleted": "עם LoRAs שנמחקו", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "החלף ערכת נושא", diff --git a/locales/ja.json b/locales/ja.json index ae3f37c3..c649d33a 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -297,7 +297,9 @@ "loraAvailability": "LoRA の利用状況", "availabilityReady": "使用可能", "availabilityMissing": "不足 LoRA あり", - "availabilityDeleted": "削除済み LoRA あり" + "availabilityDeleted": "削除済み LoRA あり", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "テーマの切り替え", diff --git a/locales/ko.json b/locales/ko.json index e132b7dc..91123da6 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -297,7 +297,9 @@ "loraAvailability": "LoRA 가용성", "availabilityReady": "바로 사용 가능", "availabilityMissing": "누락된 LoRA 있음", - "availabilityDeleted": "삭제된 LoRA 있음" + "availabilityDeleted": "삭제된 LoRA 있음", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "테마 토글", diff --git a/locales/ru.json b/locales/ru.json index 59856844..1ba3ec53 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -297,7 +297,9 @@ "loraAvailability": "Доступность LoRAs", "availabilityReady": "Готовы к использованию", "availabilityMissing": "Есть отсутствующие", - "availabilityDeleted": "Есть удалённые" + "availabilityDeleted": "Есть удалённые", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "Переключить тему", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 1960b297..b9ec3a43 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -297,7 +297,9 @@ "loraAvailability": "LoRA 可用性", "availabilityReady": "可直接使用", "availabilityMissing": "包含缺失 LoRA", - "availabilityDeleted": "包含已删除 LoRA" + "availabilityDeleted": "包含已删除 LoRA", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "切换主题", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 6f1a8f6e..f198e20b 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -297,7 +297,9 @@ "loraAvailability": "LoRA 可用性", "availabilityReady": "可直接使用", "availabilityMissing": "包含缺少的 LoRA", - "availabilityDeleted": "包含已刪除的 LoRA" + "availabilityDeleted": "包含已刪除的 LoRA", + "workflow": "[TODO: Translate] Workflow", + "hasWorkflow": "[TODO: Translate] Has Workflow" }, "theme": { "toggle": "切換主題", diff --git a/py/routes/handlers/recipe_handlers.py b/py/routes/handlers/recipe_handlers.py index 59cedcd6..a67bf938 100644 --- a/py/routes/handlers/recipe_handlers.py +++ b/py/routes/handlers/recipe_handlers.py @@ -308,6 +308,9 @@ class RecipeListingHandler: if lora_availability: filters["lora_availability"] = lora_availability + if request.query.get("has_workflow", "").lower() == "true": + filters["has_workflow"] = True + lora_hash = request.query.get("lora_hash") checkpoint_hash = request.query.get("checkpoint_hash") diff --git a/py/services/recipe_scanner.py b/py/services/recipe_scanner.py index 2900f043..dd4a6cce 100644 --- a/py/services/recipe_scanner.py +++ b/py/services/recipe_scanner.py @@ -3738,6 +3738,12 @@ class RecipeScanner: if self._compute_availability_statuses(item) & selected ] + # Filter by embedded workflow presence + if filters.get("has_workflow"): + filtered_data = [ + item for item in filtered_data if item.get("has_workflow") + ] + # Apply sorting if not already handled by pre-sorted cache if ":" in sort_by or sort_field in ("loras_count", "random", "opened"): field, order = (sort_by.split(":") + ["desc"])[:2] diff --git a/static/js/api/recipeApi.js b/static/js/api/recipeApi.js index f75ae6ba..cf4515aa 100644 --- a/static/js/api/recipeApi.js +++ b/static/js/api/recipeApi.js @@ -183,6 +183,11 @@ export async function fetchRecipesPage(page = 1, pageSize = 100) { if (pageState.filters?.loraAvailability && pageState.filters.loraAvailability.length > 0) { params.append('lora_availability', pageState.filters.loraAvailability.join(',')); } + + // Add has-workflow filter + if (pageState.filters?.hasWorkflow) { + params.append('has_workflow', 'true'); + } } // Fetch recipes diff --git a/static/js/managers/FilterManager.js b/static/js/managers/FilterManager.js index 76640f33..29b18fc0 100644 --- a/static/js/managers/FilterManager.js +++ b/static/js/managers/FilterManager.js @@ -83,6 +83,11 @@ export class FilterManager { this.initializeLoraAvailabilityFilters(); } + // Add click handler for the has-workflow chip (recipes page only) + if (this.shouldShowLoraAvailabilityFilter()) { + this.initializeHasWorkflowFilter(); + } + // Initialize tag logic toggle this.initializeTagLogicToggle(); @@ -466,6 +471,29 @@ export class FilterManager { }); } + initializeHasWorkflowFilter() { + const hasWorkflowTags = document.querySelectorAll('.has-workflow-tag'); + hasWorkflowTags.forEach(tag => { + tag.addEventListener('click', async () => { + this.filters.hasWorkflow = !this.filters.hasWorkflow; + tag.classList.toggle('active', this.filters.hasWorkflow); + + this.updateActiveFiltersCount(); + await this.applyFilters(false); + }); + }); + + // Update selections based on stored filters + this.updateHasWorkflowSelection(); + } + + updateHasWorkflowSelection() { + const hasWorkflowTags = document.querySelectorAll('.has-workflow-tag'); + hasWorkflowTags.forEach(tag => { + tag.classList.toggle('active', Boolean(this.filters.hasWorkflow)); + }); + } + createBaseModelTags() { const baseModelTagsContainer = document.getElementById('baseModelTags'); if (!baseModelTagsContainer) return; @@ -732,6 +760,7 @@ export class FilterManager { // Update LoRA availability tags if visible on this page if (this.shouldShowLoraAvailabilityFilter()) { this.updateLoraAvailabilitySelections(); + this.updateHasWorkflowSelection(); } const autoTagEls = document.querySelectorAll('.auto-tag-filter'); @@ -763,7 +792,8 @@ export class FilterManager { const baseModelCount = this.filters.baseModel.filter(m => m !== EMPTY_WILDCARD_MARKER).length; // Active when at least one availability status is deselected const loraAvailabilityCount = this.filters.loraAvailability?.length ?? 0; - const totalActiveFilters = baseModelCount + tagFilterCount + autoTagFilterCount + licenseFilterCount + modelTypeFilterCount + loraAvailabilityCount; + const hasWorkflowCount = this.filters.hasWorkflow ? 1 : 0; + const totalActiveFilters = baseModelCount + tagFilterCount + autoTagFilterCount + licenseFilterCount + modelTypeFilterCount + loraAvailabilityCount + hasWorkflowCount; if (this.activeFiltersCount) { if (totalActiveFilters > 0) { @@ -861,6 +891,7 @@ export class FilterManager { license: {}, modelTypes: [], loraAvailability: [], + hasWorkflow: false, tagLogic: 'any' }); @@ -954,7 +985,8 @@ export class FilterManager { autoTagCount > 0 || licenseCount > 0 || modelTypeCount > 0 || - loraAvailabilityCount > 0 + loraAvailabilityCount > 0 || + Boolean(this.filters.hasWorkflow) ); } @@ -968,6 +1000,7 @@ export class FilterManager { license: this.shouldShowLicenseFilters() ? this.normalizeLicenseFilters(source.license) : {}, modelTypes: this.normalizeModelTypeFilters(source.modelTypes), loraAvailability: this.normalizeLoraAvailabilityFilters(source.loraAvailability), + hasWorkflow: Boolean(source.hasWorkflow), tagLogic: source.tagLogic || 'any' }; } @@ -1081,6 +1114,7 @@ export class FilterManager { license: { ...(this.filters.license || {}) }, modelTypes: [...(this.filters.modelTypes || [])], loraAvailability: [...(this.filters.loraAvailability || [])], + hasWorkflow: Boolean(this.filters.hasWorkflow), tagLogic: this.filters.tagLogic || 'any', search: pageState?.filters?.search ?? '' }; diff --git a/templates/components/header.html b/templates/components/header.html index de9d5a36..3c52e543 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -287,6 +287,14 @@ +