diff --git a/locales/de.json b/locales/de.json
index 21d973a4..23916275 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "Send to ComfyUI",
"sendRecipe": "Send to ComfyUI"
},
+ "navigation": {
+ "label": "Rezeptnavigation",
+ "previousWithShortcut": "Vorheriges Rezept (←)",
+ "nextWithShortcut": "Nächstes Rezept (→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "Erforderliche Daten zum Erstellen des Rezepts fehlen",
"created": "Rezept erfolgreich erstellt",
"noMissingLoras": "Keine fehlenden LoRAs zum Herunterladen",
+ "noPreviousRecipe": "Kein vorheriges Rezept verfügbar",
+ "noNextRecipe": "Kein weiteres Rezept verfügbar",
"missingLorasInfoFailed": "Fehler beim Abrufen der Informationen für fehlende LoRAs",
"preparingForDownloadFailed": "Fehler beim Vorbereiten der LoRAs für den Download",
"enterLoraName": "Bitte geben Sie einen LoRA-Namen oder Syntax ein",
diff --git a/locales/en.json b/locales/en.json
index e4b682f7..e715cdeb 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "Send to ComfyUI",
"sendRecipe": "Send to ComfyUI"
},
+ "navigation": {
+ "label": "Recipe navigation",
+ "previousWithShortcut": "Previous recipe (\u2190)",
+ "nextWithShortcut": "Next recipe (\u2192)"
+ },
"workflow": {
"sendWorkflow": "Send Workflow to ComfyUI",
"sent": "Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "Missing required data to create recipe",
"created": "Recipe created successfully",
"noMissingLoras": "No missing LoRAs to download",
+ "noPreviousRecipe": "No previous recipe available",
+ "noNextRecipe": "No next recipe available",
"missingLorasInfoFailed": "Failed to get information for missing LoRAs",
"preparingForDownloadFailed": "Error preparing LoRAs for download",
"enterLoraName": "Please enter a LoRA name or syntax",
diff --git a/locales/es.json b/locales/es.json
index a93dacec..0c340376 100644
--- a/locales/es.json
+++ b/locales/es.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "Enviar a ComfyUI",
"sendRecipe": "Enviar a ComfyUI"
},
+ "navigation": {
+ "label": "Navegación de recetas",
+ "previousWithShortcut": "Receta anterior (←)",
+ "nextWithShortcut": "Siguiente receta (→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "Faltan datos necesarios para crear la receta",
"created": "Receta creada exitosamente",
"noMissingLoras": "No hay LoRAs faltantes para descargar",
+ "noPreviousRecipe": "No hay receta anterior disponible",
+ "noNextRecipe": "No hay siguiente receta disponible",
"missingLorasInfoFailed": "Error al obtener información de LoRAs faltantes",
"preparingForDownloadFailed": "Error preparando LoRAs para descarga",
"enterLoraName": "Por favor introduce un nombre de LoRA o sintaxis",
diff --git a/locales/fr.json b/locales/fr.json
index 6285939e..7fb97081 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "Envoyer vers ComfyUI",
"sendRecipe": "Envoyer vers ComfyUI"
},
+ "navigation": {
+ "label": "Navigation des recettes",
+ "previousWithShortcut": "Recette précédente (←)",
+ "nextWithShortcut": "Recette suivante (→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "Données requises manquantes pour créer le Recipe",
"created": "Recipe créé avec succès",
"noMissingLoras": "Aucun LoRA manquant à télécharger",
+ "noPreviousRecipe": "Aucune recette précédente",
+ "noNextRecipe": "Aucune recette suivante",
"missingLorasInfoFailed": "Échec de l'obtention des informations pour les LoRAs manquants",
"preparingForDownloadFailed": "Erreur lors de la préparation des LoRAs pour le téléchargement",
"enterLoraName": "Veuillez entrer un nom ou une syntaxe LoRA",
diff --git a/locales/he.json b/locales/he.json
index 2ecb064a..de13c4a8 100644
--- a/locales/he.json
+++ b/locales/he.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "שלח ל-ComfyUI",
"sendRecipe": "שלח ל-ComfyUI"
},
+ "navigation": {
+ "label": "ניווט מתכונים",
+ "previousWithShortcut": "המתכון הקודם (←)",
+ "nextWithShortcut": "המתכון הבא (→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "חסרים נתונים נדרשים ליצירת המתכון",
"created": "המתכון נוצר בהצלחה",
"noMissingLoras": "אין LoRAs חסרים להורדה",
+ "noPreviousRecipe": "אין מתכון קודם זמין",
+ "noNextRecipe": "אין מתכון נוסף זמין",
"missingLorasInfoFailed": "קבלת מידע עבור LoRAs חסרים נכשלה",
"preparingForDownloadFailed": "שגיאה בהכנת LoRAs להורדה",
"enterLoraName": "אנא הזן שם LoRA או תחביר",
diff --git a/locales/ja.json b/locales/ja.json
index e905ed5e..8bd985d4 100644
--- a/locales/ja.json
+++ b/locales/ja.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "ComfyUIへ送信",
"sendRecipe": "ComfyUIへ送信"
},
+ "navigation": {
+ "label": "レシピナビゲーション",
+ "previousWithShortcut": "前のレシピ(←)",
+ "nextWithShortcut": "次のレシピ(→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "レシピ作成に必要なデータが不足しています",
"created": "レシピを作成しました",
"noMissingLoras": "ダウンロードする不足LoRAがありません",
+ "noPreviousRecipe": "前のレシピがありません",
+ "noNextRecipe": "次のレシピがありません",
"missingLorasInfoFailed": "不足LoRAの情報取得に失敗しました",
"preparingForDownloadFailed": "ダウンロード用LoRAの準備中にエラーが発生しました",
"enterLoraName": "LoRA名または構文を入力してください",
diff --git a/locales/ko.json b/locales/ko.json
index 91fb81d9..acbbffa9 100644
--- a/locales/ko.json
+++ b/locales/ko.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "ComfyUI로 보내기",
"sendRecipe": "ComfyUI로 보내기"
},
+ "navigation": {
+ "label": "레시피 탐색",
+ "previousWithShortcut": "이전 레시피(←)",
+ "nextWithShortcut": "다음 레시피(→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "레시피 생성에 필요한 데이터가 없습니다",
"created": "레시피가 생성되었습니다",
"noMissingLoras": "다운로드할 누락된 LoRA가 없습니다",
+ "noPreviousRecipe": "이전 레시피가 없습니다",
+ "noNextRecipe": "다음 레시피가 없습니다",
"missingLorasInfoFailed": "누락된 LoRA 정보를 가져오는데 실패했습니다",
"preparingForDownloadFailed": "LoRA 다운로드 준비 오류",
"enterLoraName": "LoRA 이름 또는 문법을 입력해주세요",
diff --git a/locales/ru.json b/locales/ru.json
index 95f7bbdb..690e39ef 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "Отправить в ComfyUI",
"sendRecipe": "Отправить в ComfyUI"
},
+ "navigation": {
+ "label": "Навигация по рецептам",
+ "previousWithShortcut": "Предыдущий рецепт (←)",
+ "nextWithShortcut": "Следующий рецепт (→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "Отсутствуют необходимые данные для создания рецепта",
"created": "Рецепт успешно создан",
"noMissingLoras": "Нет отсутствующих LoRAs для загрузки",
+ "noPreviousRecipe": "Предыдущий рецепт отсутствует",
+ "noNextRecipe": "Следующий рецепт отсутствует",
"missingLorasInfoFailed": "Не удалось получить информацию для отсутствующих LoRAs",
"preparingForDownloadFailed": "Ошибка подготовки LoRAs для загрузки",
"enterLoraName": "Пожалуйста, введите название LoRA или синтаксис",
diff --git a/locales/zh-CN.json b/locales/zh-CN.json
index 0ef30301..9657fdfa 100644
--- a/locales/zh-CN.json
+++ b/locales/zh-CN.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "发送到 ComfyUI",
"sendRecipe": "发送到 ComfyUI"
},
+ "navigation": {
+ "label": "配方导航",
+ "previousWithShortcut": "上一个配方(←)",
+ "nextWithShortcut": "下一个配方(→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "缺少创建配方所需的数据",
"created": "配方创建成功",
"noMissingLoras": "没有缺失的 LoRA 可下载",
+ "noPreviousRecipe": "没有上一个配方",
+ "noNextRecipe": "没有下一个配方",
"missingLorasInfoFailed": "获取缺失 LoRA 信息失败",
"preparingForDownloadFailed": "准备下载 LoRA 时出错",
"enterLoraName": "请输入 LoRA 名称或语法",
diff --git a/locales/zh-TW.json b/locales/zh-TW.json
index c291ad0a..f803a274 100644
--- a/locales/zh-TW.json
+++ b/locales/zh-TW.json
@@ -857,6 +857,11 @@
"sendCheckpoint": "傳送到 ComfyUI",
"sendRecipe": "傳送到 ComfyUI"
},
+ "navigation": {
+ "label": "配方導覽",
+ "previousWithShortcut": "上一個配方(←)",
+ "nextWithShortcut": "下一個配方(→)"
+ },
"workflow": {
"sendWorkflow": "[TODO: Translate] Send Workflow to ComfyUI",
"sent": "[TODO: Translate] Workflow sent to ComfyUI",
@@ -1988,6 +1993,8 @@
"createMissingData": "缺少建立配方所需的資料",
"created": "配方建立成功",
"noMissingLoras": "無缺少的 LoRA 可下載",
+ "noPreviousRecipe": "沒有上一個配方",
+ "noNextRecipe": "沒有下一個配方",
"missingLorasInfoFailed": "取得缺少 LoRA 資訊失敗",
"preparingForDownloadFailed": "準備下載 LoRA 時發生錯誤",
"enterLoraName": "請輸入 LoRA 名稱或語法",
diff --git a/static/css/components/recipe-modal.css b/static/css/components/recipe-modal.css
index 4ebe5e16..022690ac 100644
--- a/static/css/components/recipe-modal.css
+++ b/static/css/components/recipe-modal.css
@@ -9,6 +9,21 @@
position: relative;
}
+/* Header row: title + nav controls. Padding reserves space for the
+ absolutely positioned nav buttons (see .modal-nav-controls in lora-modal.css). */
+.recipe-modal-header-row {
+ box-sizing: border-box;
+ width: 100%;
+ position: relative;
+ padding-right: 152px;
+}
+
+/* 56px right offset keeps the nav buttons clear of the close (x) button,
+ which is absolutely positioned at the modal-content top-right corner. */
+.recipe-modal-header-row .modal-nav-controls {
+ right: 56px;
+}
+
#recipeTagsContainer {
width: 100%;
}
diff --git a/static/js/components/RecipeModal.js b/static/js/components/RecipeModal.js
index c17f5e52..55c6c3ce 100644
--- a/static/js/components/RecipeModal.js
+++ b/static/js/components/RecipeModal.js
@@ -55,6 +55,8 @@ class RecipeModal {
constructor() {
this.promptEditorState = {};
this.recipeHydrationRequestId = 0;
+ this.navigationKeyHandler = null;
+ this.navigationInProgress = false;
this.resetLocalEditState();
this.init();
}
@@ -120,6 +122,7 @@ class RecipeModal {
this.setupCopyButtons();
this.setupStripLoraToggle();
this.setupPromptEditors();
+ this.setupNavigationControls();
// Set up tooltip positioning handlers after DOM is ready
document.addEventListener('DOMContentLoaded', () => {
this.setupTooltipPositioning();
@@ -164,6 +167,104 @@ class RecipeModal {
});
}
+ setupNavigationControls() {
+ const prevBtn = document.getElementById('recipeNavPrevBtn');
+ const nextBtn = document.getElementById('recipeNavNextBtn');
+
+ if (prevBtn) {
+ prevBtn.addEventListener('click', () => this.handleDirectionalNavigation('prev'));
+ }
+ if (nextBtn) {
+ nextBtn.addEventListener('click', () => this.handleDirectionalNavigation('next'));
+ }
+ this.updateNavigationControls();
+ }
+
+ shouldIgnoreNavigationKey(event) {
+ const target = event.target;
+ if (!target) return false;
+ const tagName = target.tagName ? target.tagName.toLowerCase() : '';
+ return target.isContentEditable || ['input', 'textarea', 'select', 'button'].includes(tagName);
+ }
+
+ updateNavigationControls() {
+ const modalElement = document.getElementById('recipeModal');
+ if (!modalElement) return;
+
+ const prevBtn = modalElement.querySelector('#recipeNavPrevBtn');
+ const nextBtn = modalElement.querySelector('#recipeNavNextBtn');
+ if (!prevBtn || !nextBtn) return;
+
+ const scroller = state.virtualScroller;
+ if (!scroller || typeof scroller.getNavigationState !== 'function') {
+ prevBtn.disabled = true;
+ nextBtn.disabled = true;
+ return;
+ }
+
+ const { hasPrev, hasNext } = scroller.getNavigationState(this.listFilePath || this.filePath || '');
+ prevBtn.disabled = this.navigationInProgress || !hasPrev;
+ nextBtn.disabled = this.navigationInProgress || !hasNext;
+ }
+
+ cleanupNavigationShortcuts() {
+ if (this.navigationKeyHandler) {
+ document.removeEventListener('keydown', this.navigationKeyHandler);
+ this.navigationKeyHandler = null;
+ }
+ this.navigationInProgress = false;
+ }
+
+ setupNavigationShortcuts() {
+ const modalElement = document.getElementById('recipeModal');
+ if (!modalElement) return;
+
+ this.cleanupNavigationShortcuts();
+
+ this.navigationKeyHandler = (event) => {
+ if (this.shouldIgnoreNavigationKey(event)) return;
+
+ if (event.key === 'ArrowLeft') {
+ event.preventDefault();
+ this.handleDirectionalNavigation('prev');
+ } else if (event.key === 'ArrowRight') {
+ event.preventDefault();
+ this.handleDirectionalNavigation('next');
+ }
+ };
+
+ document.addEventListener('keydown', this.navigationKeyHandler);
+ }
+
+ async handleDirectionalNavigation(direction) {
+ if (this.navigationInProgress) return;
+
+ const scroller = state.virtualScroller;
+ const filePath = this.listFilePath || this.filePath || '';
+
+ if (!filePath || !scroller || typeof scroller.getAdjacentItemByFilePath !== 'function') {
+ return;
+ }
+
+ this.navigationInProgress = true;
+ this.updateNavigationControls();
+
+ try {
+ const adjacent = await scroller.getAdjacentItemByFilePath(filePath, direction);
+ if (!adjacent || !adjacent.item) {
+ const toastKey = direction === 'prev' ? 'toast.recipes.noPreviousRecipe' : 'toast.recipes.noNextRecipe';
+ const toastFallback = direction === 'prev' ? 'No previous recipe available' : 'No next recipe available';
+ showToast(toastKey, {}, 'info', toastFallback);
+ return;
+ }
+
+ this.showRecipeDetails(adjacent.item);
+ } finally {
+ this.navigationInProgress = false;
+ this.updateNavigationControls();
+ }
+ }
+
// Add tooltip positioning handler to ensure correct positioning of fixed tooltips
setupTooltipPositioning() {
document.addEventListener('mouseover', (event) => {
@@ -303,7 +404,9 @@ class RecipeModal {
this.syncHeaderActions();
// Show the modal
- modalManager.showModal('recipeModal');
+ modalManager.showModal('recipeModal', null, null, () => this.cleanupNavigationShortcuts());
+ this.updateNavigationControls();
+ this.setupNavigationShortcuts();
if (this.recipeId) {
// Fire-and-forget: record this open for the "Recently Opened"
diff --git a/templates/components/recipe_modal.html b/templates/components/recipe_modal.html
index 269988a4..b130ea0b 100644
--- a/templates/components/recipe_modal.html
+++ b/templates/components/recipe_modal.html
@@ -3,7 +3,17 @@