diff --git a/locales/de.json b/locales/de.json index a3e6c0a3..20590fb5 100644 --- a/locales/de.json +++ b/locales/de.json @@ -188,7 +188,8 @@ "creator": "Ersteller", "title": "Rezept-Titel", "loraName": "LoRA-Dateiname", - "loraModel": "LoRA-Modellname" + "loraModel": "LoRA-Modellname", + "prompt": "Prompt" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "Gefiltert nach LoRA", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "Nur Favoriten anzeigen", + "action": "Favoriten" } }, "duplicates": { diff --git a/locales/en.json b/locales/en.json index 6c74910d..494d3972 100644 --- a/locales/en.json +++ b/locales/en.json @@ -188,7 +188,8 @@ "creator": "Creator", "title": "Recipe Title", "loraName": "LoRA Filename", - "loraModel": "LoRA Model Name" + "loraModel": "LoRA Model Name", + "prompt": "Prompt" } }, "filter": { diff --git a/locales/es.json b/locales/es.json index c5fec9b2..6ba9846e 100644 --- a/locales/es.json +++ b/locales/es.json @@ -188,7 +188,8 @@ "creator": "Creador", "title": "Título de la receta", "loraName": "Nombre de archivo LoRA", - "loraModel": "Nombre del modelo LoRA" + "loraModel": "Nombre del modelo LoRA", + "prompt": "Prompt" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "Filtrado por LoRA", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "Mostrar solo favoritos", + "action": "Favoritos" } }, "duplicates": { diff --git a/locales/fr.json b/locales/fr.json index b3ea3a71..7871346e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -188,7 +188,8 @@ "creator": "Créateur", "title": "Titre de la recipe", "loraName": "Nom de fichier LoRA", - "loraModel": "Nom du modèle LoRA" + "loraModel": "Nom du modèle LoRA", + "prompt": "Prompt" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "Filtré par LoRA", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "Afficher uniquement les favoris", + "action": "Favoris" } }, "duplicates": { diff --git a/locales/he.json b/locales/he.json index 6593f53f..37977a96 100644 --- a/locales/he.json +++ b/locales/he.json @@ -188,7 +188,8 @@ "creator": "יוצר", "title": "כותרת מתכון", "loraName": "שם קובץ LoRA", - "loraModel": "שם מודל LoRA" + "loraModel": "שם מודל LoRA", + "prompt": "הנחיה" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "מסונן לפי LoRA", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "הצג מועדפים בלבד", + "action": "מועדפים" } }, "duplicates": { diff --git a/locales/ja.json b/locales/ja.json index 39cd96d7..da0a812a 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -188,7 +188,8 @@ "creator": "作成者", "title": "レシピタイトル", "loraName": "LoRAファイル名", - "loraModel": "LoRAモデル名" + "loraModel": "LoRAモデル名", + "prompt": "プロンプト" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "LoRAでフィルタ済み", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "お気に入りのみ表示", + "action": "お気に入り" } }, "duplicates": { diff --git a/locales/ko.json b/locales/ko.json index de949e24..cbae5fe9 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -188,7 +188,8 @@ "creator": "제작자", "title": "레시피 제목", "loraName": "LoRA 파일명", - "loraModel": "LoRA 모델명" + "loraModel": "LoRA 모델명", + "prompt": "프롬프트" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "LoRA로 필터링됨", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "즐겨찾기만 표시", + "action": "즐겨찾기" } }, "duplicates": { diff --git a/locales/ru.json b/locales/ru.json index df091b78..edfa486d 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -188,7 +188,8 @@ "creator": "Автор", "title": "Название рецепта", "loraName": "Имя файла LoRA", - "loraModel": "Название модели LoRA" + "loraModel": "Название модели LoRA", + "prompt": "Запрос" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "Фильтр по LoRA", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "Только избранные", + "action": "Избранное" } }, "duplicates": { diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 1a5d9192..b4c0f45a 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -188,7 +188,8 @@ "creator": "创作者", "title": "配方标题", "loraName": "LoRA 文件名", - "loraModel": "LoRA 模型名称" + "loraModel": "LoRA 模型名称", + "prompt": "提示词" } }, "filter": { @@ -1484,4 +1485,4 @@ "learnMore": "浏览器插件教程" } } -} \ No newline at end of file +} diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 422cf582..b3b106b2 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -188,7 +188,8 @@ "creator": "創作者", "title": "配方標題", "loraName": "LoRA 檔案名稱", - "loraModel": "LoRA 模型名稱" + "loraModel": "LoRA 模型名稱", + "prompt": "提示詞" } }, "filter": { @@ -593,8 +594,8 @@ }, "filteredByLora": "已依 LoRA 篩選", "favorites": { - "title": "[TODO: Translate] Show Favorites Only", - "action": "[TODO: Translate] Favorites" + "title": "僅顯示收藏", + "action": "收藏" } }, "duplicates": { diff --git a/py/routes/handlers/recipe_handlers.py b/py/routes/handlers/recipe_handlers.py index dc4ed239..a798f0b6 100644 --- a/py/routes/handlers/recipe_handlers.py +++ b/py/routes/handlers/recipe_handlers.py @@ -163,6 +163,7 @@ class RecipeListingHandler: "tags": request.query.get("search_tags", "true").lower() == "true", "lora_name": request.query.get("search_lora_name", "true").lower() == "true", "lora_model": request.query.get("search_lora_model", "true").lower() == "true", + "prompt": request.query.get("search_prompt", "true").lower() == "true", } filters: Dict[str, Any] = {} diff --git a/py/services/recipe_scanner.py b/py/services/recipe_scanner.py index e8652c00..a646e97c 100644 --- a/py/services/recipe_scanner.py +++ b/py/services/recipe_scanner.py @@ -1107,6 +1107,14 @@ class RecipeScanner: if fuzzy_match(str(lora.get('modelName', '')), search): return True + # Search in prompt and negative_prompt if enabled + if search_options.get('prompt', True) and 'gen_params' in item: + gen_params = item['gen_params'] + if fuzzy_match(str(gen_params.get('prompt', '')), search): + return True + if fuzzy_match(str(gen_params.get('negative_prompt', '')), search): + return True + # No match found return False diff --git a/static/js/api/recipeApi.js b/static/js/api/recipeApi.js index b12cf005..7f700a21 100644 --- a/static/js/api/recipeApi.js +++ b/static/js/api/recipeApi.js @@ -96,6 +96,7 @@ export async function fetchRecipesPage(page = 1, pageSize = 100) { params.append('search_tags', pageState.searchOptions.tags.toString()); params.append('search_lora_name', pageState.searchOptions.loraName.toString()); params.append('search_lora_model', pageState.searchOptions.loraModel.toString()); + params.append('search_prompt', (pageState.searchOptions.prompt || false).toString()); params.append('fuzzy', 'true'); } } diff --git a/static/js/recipes.js b/static/js/recipes.js index 1b495ad7..f74eb583 100644 --- a/static/js/recipes.js +++ b/static/js/recipes.js @@ -106,6 +106,7 @@ class RecipeManager { tags: true, // Recipe tags loraName: true, // LoRA file name loraModel: true, // LoRA model name + prompt: true, // Prompt search recursive: true }; } diff --git a/templates/components/header.html b/templates/components/header.html index e2942c39..95c7c01e 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -8,52 +8,60 @@ {% set current_path = request.path %} {% if current_path.startswith('/loras/recipes') %} - {% set current_page = 'recipes' %} + {% set current_page = 'recipes' %} {% elif current_path.startswith('/checkpoints') %} - {% set current_page = 'checkpoints' %} + {% set current_page = 'checkpoints' %} {% elif current_path.startswith('/embeddings') %} - {% set current_page = 'embeddings' %} + {% set current_page = 'embeddings' %} {% elif current_path.startswith('/statistics') %} - {% set current_page = 'statistics' %} + {% set current_page = 'statistics' %} {% else %} - {% set current_page = 'loras' %} + {% set current_page = 'loras' %} {% endif %} {% set search_disabled = current_page == 'statistics' %} - {% set search_placeholder_key = 'header.search.notAvailable' if search_disabled else 'header.search.placeholders.' ~ current_page %} + {% set search_placeholder_key = 'header.search.notAvailable' if search_disabled else 'header.search.placeholders.' ~ + current_page %} {% set header_search_class = 'header-search disabled' if search_disabled else 'header-search' %} - +
- + - -
- +
@@ -97,6 +105,7 @@
{{ t('header.search.filters.tags') }}
{{ t('header.search.filters.loraName') }}
{{ t('header.search.filters.loraModel') }}
+
{{ t('header.search.filters.prompt') }}
{% elif request.path == '/checkpoints' %}
{{ t('header.search.filters.filename') }}
{{ t('header.search.filters.modelname') }}
@@ -165,4 +174,4 @@ {{ t('header.filter.clearAll') }}
-
+ \ No newline at end of file diff --git a/tests/services/test_recipe_scanner.py b/tests/services/test_recipe_scanner.py index 4acc6a82..87afaad2 100644 --- a/tests/services/test_recipe_scanner.py +++ b/tests/services/test_recipe_scanner.py @@ -549,3 +549,55 @@ async def test_get_paginated_data_filters_by_favorite(recipe_scanner): result_fav_false = await scanner.get_paginated_data(page=1, page_size=10, filters={"favorite": False}) assert len(result_fav_false["items"]) == 2 + +@pytest.mark.asyncio +async def test_get_paginated_data_filters_by_prompt(recipe_scanner): + scanner, _ = recipe_scanner + + # Add a recipe with a specific prompt + await scanner.add_recipe({ + "id": "prompt-recipe", + "file_path": "path/prompt.png", + "title": "Prompt Recipe", + "modified": 1.0, + "created_date": 1.0, + "loras": [], + "gen_params": { + "prompt": "a beautiful forest landscape" + } + }) + + # Add a recipe with a specific negative prompt + await scanner.add_recipe({ + "id": "neg-prompt-recipe", + "file_path": "path/neg.png", + "title": "Negative Prompt Recipe", + "modified": 2.0, + "created_date": 2.0, + "loras": [], + "gen_params": { + "negative_prompt": "ugly, blurry mountains" + } + }) + + await asyncio.sleep(0) + + # Test search in prompt + result_prompt = await scanner.get_paginated_data( + page=1, page_size=10, search="forest", search_options={"prompt": True} + ) + assert len(result_prompt["items"]) == 1 + assert result_prompt["items"][0]["id"] == "prompt-recipe" + + # Test search in negative prompt + result_neg = await scanner.get_paginated_data( + page=1, page_size=10, search="mountains", search_options={"prompt": True} + ) + assert len(result_neg["items"]) == 1 + assert result_neg["items"][0]["id"] == "neg-prompt-recipe" + + # Test search disabled (should not find by prompt) + result_disabled = await scanner.get_paginated_data( + page=1, page_size=10, search="forest", search_options={"prompt": False} + ) + assert len(result_disabled["items"]) == 0