From 5fe0660c64b9c336c65e022de361c17924fac7fe Mon Sep 17 00:00:00 2001 From: Will Miao Date: Sat, 25 Oct 2025 16:41:35 +0800 Subject: [PATCH] feat: add update available indicator to model cards - Add CSS custom properties for update badge styling in both light and dark themes - Create new card header info layout with flexbox for better content organization - Implement model-update-badge component with glow effects and proper spacing - Add has-update class to cards when updates are available with visual border indicators - Update ModelCard.js to conditionally render update badges based on model data - Include internationalization support for update badge labels and tooltips The changes provide users with clear visual indicators when model updates are available, improving the user experience by making update status immediately visible without requiring manual checks. --- 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/base.css | 6 ++++ static/css/components/card.css | 37 +++++++++++++++++++++++- static/js/components/shared/ModelCard.js | 20 +++++++++++-- 13 files changed, 99 insertions(+), 4 deletions(-) diff --git a/locales/de.json b/locales/de.json index 21bbb0f3..db6a6e82 100644 --- a/locales/de.json +++ b/locales/de.json @@ -127,6 +127,10 @@ "checkError": "Fehler beim Überprüfen der Beispielbilder", "missingHash": "Fehlende Modell-Hash-Informationen.", "noRemoteImagesAvailable": "Keine Remote-Beispielbilder für dieses Modell auf Civitai verfügbar" + }, + "badges": { + "update": "Update", + "updateAvailable": "Update verfügbar" } }, "globalContextMenu": { diff --git a/locales/en.json b/locales/en.json index e8d76aa7..e43296d0 100644 --- a/locales/en.json +++ b/locales/en.json @@ -127,6 +127,10 @@ "checkError": "Error checking for example images", "missingHash": "Missing model hash information.", "noRemoteImagesAvailable": "No remote example images available for this model on Civitai" + }, + "badges": { + "update": "Update", + "updateAvailable": "Update available" } }, "globalContextMenu": { diff --git a/locales/es.json b/locales/es.json index 53b10dfc..fd1dc6c4 100644 --- a/locales/es.json +++ b/locales/es.json @@ -127,6 +127,10 @@ "checkError": "Error al verificar imágenes de ejemplo", "missingHash": "Falta información del hash del modelo.", "noRemoteImagesAvailable": "No hay imágenes de ejemplo remotas disponibles para este modelo en Civitai" + }, + "badges": { + "update": "Actualización", + "updateAvailable": "Actualización disponible" } }, "globalContextMenu": { diff --git a/locales/fr.json b/locales/fr.json index dc14d6e4..71890fcb 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -127,6 +127,10 @@ "checkError": "Erreur lors de la vérification des images d'exemple", "missingHash": "Informations de hachage du modèle manquantes.", "noRemoteImagesAvailable": "Aucune image d'exemple distante disponible pour ce modèle sur Civitai" + }, + "badges": { + "update": "Mise à jour", + "updateAvailable": "Mise à jour disponible" } }, "globalContextMenu": { diff --git a/locales/he.json b/locales/he.json index 6e4b5940..4c5308f0 100644 --- a/locales/he.json +++ b/locales/he.json @@ -127,6 +127,10 @@ "checkError": "שגיאה בבדיקת תמונות דוגמה", "missingHash": "חסר מידע hash של המודל.", "noRemoteImagesAvailable": "אין תמונות דוגמה מרוחקות זמינות למודל זה ב-Civitai" + }, + "badges": { + "update": "עדכון", + "updateAvailable": "עדכון זמין" } }, "globalContextMenu": { diff --git a/locales/ja.json b/locales/ja.json index 84ae2124..136c21e4 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -127,6 +127,10 @@ "checkError": "例画像の確認中にエラーが発生しました", "missingHash": "モデルハッシュ情報がありません。", "noRemoteImagesAvailable": "このモデルのCivitaiでのリモート例画像は利用できません" + }, + "badges": { + "update": "アップデート", + "updateAvailable": "アップデートがあります" } }, "globalContextMenu": { diff --git a/locales/ko.json b/locales/ko.json index 088e67b4..14903ef9 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -127,6 +127,10 @@ "checkError": "예시 이미지 확인 중 오류", "missingHash": "모델 해시 정보가 없습니다.", "noRemoteImagesAvailable": "Civitai에서 이 모델의 원격 예시 이미지를 사용할 수 없습니다" + }, + "badges": { + "update": "업데이트", + "updateAvailable": "업데이트 가능" } }, "globalContextMenu": { diff --git a/locales/ru.json b/locales/ru.json index 0021f803..b72c91b1 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -127,6 +127,10 @@ "checkError": "Ошибка проверки примеров изображений", "missingHash": "Отсутствует хеш модели.", "noRemoteImagesAvailable": "Нет удаленных примеров изображений для этой модели на Civitai" + }, + "badges": { + "update": "Обновление", + "updateAvailable": "Доступно обновление" } }, "globalContextMenu": { diff --git a/locales/zh-CN.json b/locales/zh-CN.json index d33b0eb9..be002614 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -127,6 +127,10 @@ "checkError": "检查示例图片时出错", "missingHash": "缺少模型哈希信息。", "noRemoteImagesAvailable": "此模型在 Civitai 上没有远程示例图片" + }, + "badges": { + "update": "更新", + "updateAvailable": "有可用更新" } }, "globalContextMenu": { diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 04c82bbe..0ce1a382 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -127,6 +127,10 @@ "checkError": "檢查範例圖片時發生錯誤", "missingHash": "缺少模型雜湊資訊。", "noRemoteImagesAvailable": "此模型在 Civitai 上無遠端範例圖片" + }, + "badges": { + "update": "更新", + "updateAvailable": "有可用更新" } }, "globalContextMenu": { diff --git a/static/css/base.css b/static/css/base.css index fc479161..ed1599ba 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -53,6 +53,9 @@ html, body { --lora-error: oklch(75% 0.32 29); --lora-warning: oklch(var(--lora-warning-l) var(--lora-warning-c) var(--lora-warning-h)); --lora-success: oklch(var(--lora-success-l) var(--lora-success-c) var(--lora-success-h)); + --badge-update-bg: oklch(72% 0.2 220); + --badge-update-text: oklch(28% 0.03 220); + --badge-update-glow: oklch(72% 0.2 220 / 0.28); /* Spacing Scale */ --space-1: calc(8px * 1); @@ -100,6 +103,9 @@ html[data-theme="light"] { --lora-border: oklch(90% 0.02 256 / 0.15); --lora-text: oklch(98% 0.02 256); --lora-warning: oklch(75% 0.25 80); /* Modified to be used with oklch() */ + --badge-update-bg: oklch(62% 0.18 220); + --badge-update-text: oklch(98% 0.02 240); + --badge-update-glow: oklch(62% 0.18 220 / 0.4); } body { diff --git a/static/css/components/card.css b/static/css/components/card.css index cf63df36..01f57c9c 100644 --- a/static/css/components/card.css +++ b/static/css/components/card.css @@ -296,6 +296,18 @@ min-height: 20px; } +.card-header-info { + display: flex; + align-items: center; + gap: 6px; + flex: 1; + min-width: 0; +} + +.card-header-info .base-model-label { + flex-shrink: 1; +} + .card-actions i { margin-left: var(--space-1); cursor: pointer; @@ -422,6 +434,7 @@ border-radius: var(--border-radius-xs); backdrop-filter: blur(2px); font-size: 0.85em; + line-height: 1.2; } /* Style for version name */ @@ -575,4 +588,26 @@ 15% { opacity: 1; transform: translateY(0); } 85% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(0); } - } \ No newline at end of file + } + +.model-card.has-update { + border-color: color-mix(in oklab, var(--badge-update-bg) 60%, transparent); + box-shadow: 0 0 0 1px color-mix(in oklab, var(--badge-update-bg) 45%, transparent); +} + +.model-update-badge { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 2px 10px; + border-radius: var(--border-radius-xs); + background: var(--badge-update-bg); + color: var(--badge-update-text); + font-size: 0.7rem; + font-weight: 600; + letter-spacing: 0.04em; + text-transform: uppercase; + box-shadow: 0 4px 12px var(--badge-update-glow); + border: 1px solid color-mix(in oklab, var(--badge-update-bg) 55%, transparent); + white-space: nowrap; +} diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js index aa777292..633e2825 100644 --- a/static/js/components/shared/ModelCard.js +++ b/static/js/components/shared/ModelCard.js @@ -432,6 +432,8 @@ export function createModelCard(model, modelType) { card.dataset.notes = model.notes || ''; card.dataset.base_model = model.base_model || 'Unknown'; card.dataset.favorite = model.favorite ? 'true' : 'false'; + const hasUpdateAvailable = Boolean(model.update_available); + card.dataset.update_available = hasUpdateAvailable ? 'true' : 'false'; // LoRA specific data if (modelType === MODEL_TYPES.LORA) { @@ -507,6 +509,9 @@ export function createModelCard(model, modelType) { // Get favorite status from model data const isFavorite = model.favorite === true; + if (hasUpdateAvailable) { + card.classList.add('has-update'); + } // Generate action icons based on model type with i18n support const favoriteTitle = isFavorite ? @@ -531,6 +536,8 @@ export function createModelCard(model, modelType) { copyTitle = translate('modelCard.actions.copyLoRASyntax', {}, 'Copy value'); } + const updateBadgeLabel = translate('modelCard.badges.update', {}, 'Update'); + const updateBadgeTooltip = translate('modelCard.badges.updateAvailable', {}, 'Update available'); const actionIcons = ` @@ -568,9 +575,16 @@ export function createModelCard(model, modelType) { `` : ''} - - ${model.base_model} - +
+ + ${model.base_model} + + ${hasUpdateAvailable ? ` + + ${updateBadgeLabel} + + ` : ''} +
${actionIcons}