From 1b1a8d63dbe09df10d69f6314f844e81d01a6b3a Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 16 Sep 2026 19:58:44 +0800 Subject: [PATCH] feat(recipes): show the recipe base model in the modal header Adds a base model pill at the front of the recipe modal's tags row, showing the full base model name (cards keep the abbreviation since their overlay width is constrained). Falls back to a dimmed Unknown so the header layout does not shift when hydration fills the value in. Hydration now also merges base_model. Translated in all 9 locales. --- locales/de.json | 4 +- locales/en.json | 4 +- locales/es.json | 4 +- locales/fr.json | 4 +- locales/he.json | 4 +- locales/ja.json | 4 +- locales/ko.json | 4 +- locales/ru.json | 4 +- locales/zh-CN.json | 4 +- locales/zh-TW.json | 4 +- static/css/components/recipe-modal.css | 32 +++ static/js/components/RecipeModal.js | 32 +++ templates/components/recipe_modal.html | 9 +- .../recipeModal.baseModelBadge.test.js | 234 ++++++++++++++++++ 14 files changed, 335 insertions(+), 12 deletions(-) create mode 100644 tests/frontend/components/recipeModal.baseModelBadge.test.js diff --git a/locales/de.json b/locales/de.json index 928ddda3..2acf48ff 100644 --- a/locales/de.json +++ b/locales/de.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "Basismodell", + "unknown": "Unbekannt" }, "actions": { "openFileLocation": "Dateispeicherort öffnen", diff --git a/locales/en.json b/locales/en.json index ca3d2ffd..fd7f6420 100644 --- a/locales/en.json +++ b/locales/en.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "Base Model", + "unknown": "Unknown" }, "actions": { "openFileLocation": "Open File Location", diff --git a/locales/es.json b/locales/es.json index b5622b77..21a1490b 100644 --- a/locales/es.json +++ b/locales/es.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "Modelo base", + "unknown": "Desconocido" }, "actions": { "openFileLocation": "Abrir ubicación del archivo", diff --git a/locales/fr.json b/locales/fr.json index e8b017c5..b9949a74 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "Modèle de base", + "unknown": "Inconnu" }, "actions": { "openFileLocation": "Ouvrir l’emplacement du fichier", diff --git a/locales/he.json b/locales/he.json index c94c3dda..a2c21cb3 100644 --- a/locales/he.json +++ b/locales/he.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "מודל בסיס", + "unknown": "לא ידוע" }, "actions": { "openFileLocation": "פתח מיקום קובץ", diff --git a/locales/ja.json b/locales/ja.json index ac25dee1..3d8e7eb6 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "ベースモデル", + "unknown": "不明" }, "actions": { "openFileLocation": "ファイルの場所を開く", diff --git a/locales/ko.json b/locales/ko.json index 6f239b76..4e84455d 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "베이스 모델", + "unknown": "알 수 없음" }, "actions": { "openFileLocation": "파일 위치 열기", diff --git a/locales/ru.json b/locales/ru.json index d6f131da..b32c1360 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "Базовая модель", + "unknown": "Неизвестно" }, "actions": { "openFileLocation": "Открыть расположение файла", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index c0f817ed..b95e4c8f 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "基础模型", + "unknown": "未知" }, "actions": { "openFileLocation": "打开文件位置", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 2f32e361..193deb85 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -920,7 +920,9 @@ }, "modal": { "metadata": { - "id": "ID" + "id": "ID", + "baseModel": "基礎模型", + "unknown": "未知" }, "actions": { "openFileLocation": "開啟檔案位置", diff --git a/static/css/components/recipe-modal.css b/static/css/components/recipe-modal.css index 6f061a69..843256af 100644 --- a/static/css/components/recipe-modal.css +++ b/static/css/components/recipe-modal.css @@ -28,6 +28,38 @@ width: 100%; } +/* Tags row: the base model badge shares one line with the compact tags. */ +.recipe-tags-row { + display: flex; + align-items: center; + gap: var(--space-2); + width: 100%; +} + +.recipe-tags-row #recipeTagsContainer { + flex: 1; + min-width: 0; +} + +/* Header base model badge: reuses the card .base-model-label pill shape but + swaps the on-image overlay styling (text shadow, backdrop blur) for the + accent-tinted chip look used by resource rows in this modal. */ +.recipe-base-model-badge { + flex-shrink: 0; + max-width: 160px; + text-shadow: none; + backdrop-filter: none; + background: oklch(var(--lora-accent) / 0.1); + color: var(--lora-accent); + padding: 2px 8px; +} + +.recipe-base-model-badge.is-unknown { + background: var(--surface-subtle); + color: var(--text-color); + opacity: 0.6; +} + .recipe-modal-header h2 { margin: 0 0 var(--space-1); padding: var(--space-1); diff --git a/static/js/components/RecipeModal.js b/static/js/components/RecipeModal.js index fbb577f5..262b2709 100644 --- a/static/js/components/RecipeModal.js +++ b/static/js/components/RecipeModal.js @@ -494,6 +494,7 @@ class RecipeModal { this.syncGenerationParams(hydratedRecipe.gen_params); this.syncResourcesSection(hydratedRecipe); this.syncHeaderActions(); + this.syncBaseModelBadge(); this.syncMetaFooter(); // Show the modal @@ -520,6 +521,32 @@ class RecipeModal { } } + /** + * Render the recipe-level base model badge in the header tags row. + * Unlike the width-constrained card overlay (which abbreviates), the + * modal has room for the full base model name — matching the model + * modal's info grid and this modal's resource rows. Falls back to a + * dimmed "Unknown" instead of hiding so the header layout does not + * shift when hydration fills the value in. + */ + syncBaseModelBadge() { + const badge = document.getElementById('recipeBaseModelBadge'); + if (!badge) { + return; + } + + const rawLabel = (this.currentRecipe?.base_model || '').trim(); + const unknownLabel = translate('recipes.modal.metadata.unknown', {}, 'Unknown'); + const baseModelLabel = rawLabel || unknownLabel; + const fieldLabel = translate('recipes.modal.metadata.baseModel', {}, 'Base Model'); + + badge.textContent = baseModelLabel; + badge.title = `${fieldLabel}: ${baseModelLabel}`; + badge.setAttribute('aria-label', badge.title); + badge.classList.toggle('is-unknown', !rawLabel); + badge.hidden = false; + } + /** * Render the meta footer: clickable file location (opens the recipe JSON * in the OS file manager) plus the truncated recipe ID with copy button. @@ -661,6 +688,10 @@ class RecipeModal { nextRecipe.has_workflow = fullRecipe.has_workflow; } + if (fullRecipe.base_model !== undefined) { + nextRecipe.base_model = fullRecipe.base_model; + } + if (fullRecipe.checkpoint !== undefined) { nextRecipe.checkpoint = fullRecipe.checkpoint; } else { @@ -718,6 +749,7 @@ class RecipeModal { this.updateSourceUrlDisplay(this.currentRecipe.source_path || ''); } this.syncHeaderActions(); + this.syncBaseModelBadge(); this.syncMetaFooter(); } diff --git a/templates/components/recipe_modal.html b/templates/components/recipe_modal.html index e0985473..d518b7b1 100644 --- a/templates/components/recipe_modal.html +++ b/templates/components/recipe_modal.html @@ -26,8 +26,13 @@ - -
+ +
+ +
+