feat(ui): add configurable model card footer action, fixes #249

This commit is contained in:
Will Miao
2025-10-17 08:43:35 +08:00
parent 52bf93e430
commit 6d9be814a5
16 changed files with 120 additions and 13 deletions

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Checkpoint-Name kopiert", "checkpointNameCopied": "Checkpoint-Name kopiert",
"toggleBlur": "Unschärfe umschalten", "toggleBlur": "Unschärfe umschalten",
"show": "Anzeigen", "show": "Anzeigen",
"openExampleImages": "Beispielbilder-Ordner öffnen" "openExampleImages": "Beispielbilder-Ordner öffnen",
"replacePreview": "Vorschau ersetzen"
}, },
"nsfw": { "nsfw": {
"matureContent": "Nicht jugendfreie Inhalte", "matureContent": "Nicht jugendfreie Inhalte",
@@ -240,6 +241,12 @@
"always": "Kopf- und Fußzeilen sind immer sichtbar", "always": "Kopf- und Fußzeilen sind immer sichtbar",
"hover": "Kopf- und Fußzeilen erscheinen nur beim Darüberfahren mit der Maus" "hover": "Kopf- und Fußzeilen erscheinen nur beim Darüberfahren mit der Maus"
}, },
"modelCardFooterAction": "Aktion der Modellkarten-Schaltfläche",
"modelCardFooterActionOptions": {
"exampleImages": "Beispielbilder öffnen",
"replacePreview": "Vorschau ersetzen"
},
"modelCardFooterActionHelp": "Wähle aus, was die Schaltfläche unten rechts auf der Karte ausführt.",
"modelNameDisplay": "Anzeige des Modellnamens", "modelNameDisplay": "Anzeige des Modellnamens",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "Modellname", "modelName": "Modellname",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Checkpoint name copied", "checkpointNameCopied": "Checkpoint name copied",
"toggleBlur": "Toggle blur", "toggleBlur": "Toggle blur",
"show": "Show", "show": "Show",
"openExampleImages": "Open Example Images Folder" "openExampleImages": "Open Example Images Folder",
"replacePreview": "Replace Preview"
}, },
"nsfw": { "nsfw": {
"matureContent": "Mature Content", "matureContent": "Mature Content",
@@ -240,6 +241,12 @@
"always": "Headers and footers are always visible", "always": "Headers and footers are always visible",
"hover": "Headers and footers only appear when hovering over a card" "hover": "Headers and footers only appear when hovering over a card"
}, },
"modelCardFooterAction": "Model Card Button Action",
"modelCardFooterActionOptions": {
"exampleImages": "Open Example Images",
"replacePreview": "Replace Preview"
},
"modelCardFooterActionHelp": "Choose what the bottom-right card button does.",
"modelNameDisplay": "Model Name Display", "modelNameDisplay": "Model Name Display",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "Model Name", "modelName": "Model Name",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Nombre del checkpoint copiado", "checkpointNameCopied": "Nombre del checkpoint copiado",
"toggleBlur": "Alternar difuminado", "toggleBlur": "Alternar difuminado",
"show": "Mostrar", "show": "Mostrar",
"openExampleImages": "Abrir carpeta de imágenes de ejemplo" "openExampleImages": "Abrir carpeta de imágenes de ejemplo",
"replacePreview": "Reemplazar vista previa"
}, },
"nsfw": { "nsfw": {
"matureContent": "Contenido para adultos", "matureContent": "Contenido para adultos",
@@ -240,6 +241,12 @@
"always": "Los encabezados y pies de página siempre son visibles", "always": "Los encabezados y pies de página siempre son visibles",
"hover": "Los encabezados y pies de página solo aparecen al pasar el ratón sobre una tarjeta" "hover": "Los encabezados y pies de página solo aparecen al pasar el ratón sobre una tarjeta"
}, },
"modelCardFooterAction": "Acción del botón de tarjeta de modelo",
"modelCardFooterActionOptions": {
"exampleImages": "Abrir imágenes de ejemplo",
"replacePreview": "Reemplazar vista previa"
},
"modelCardFooterActionHelp": "Elige qué hace el botón en la esquina inferior derecha de la tarjeta.",
"modelNameDisplay": "Visualización del nombre del modelo", "modelNameDisplay": "Visualización del nombre del modelo",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "Nombre del modelo", "modelName": "Nombre del modelo",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Nom du checkpoint copié", "checkpointNameCopied": "Nom du checkpoint copié",
"toggleBlur": "Basculer le flou", "toggleBlur": "Basculer le flou",
"show": "Afficher", "show": "Afficher",
"openExampleImages": "Ouvrir le dossier d'images d'exemple" "openExampleImages": "Ouvrir le dossier d'images d'exemple",
"replacePreview": "Remplacer l'aperçu"
}, },
"nsfw": { "nsfw": {
"matureContent": "Contenu pour adultes", "matureContent": "Contenu pour adultes",
@@ -240,6 +241,12 @@
"always": "Les en-têtes et pieds de page sont toujours visibles", "always": "Les en-têtes et pieds de page sont toujours visibles",
"hover": "Les en-têtes et pieds de page n'apparaissent qu'au survol d'une carte" "hover": "Les en-têtes et pieds de page n'apparaissent qu'au survol d'une carte"
}, },
"modelCardFooterAction": "Action du bouton de carte de modèle",
"modelCardFooterActionOptions": {
"exampleImages": "Ouvrir les images d'exemple",
"replacePreview": "Remplacer l'aperçu"
},
"modelCardFooterActionHelp": "Choisissez ce que fait le bouton en bas à droite de la carte.",
"modelNameDisplay": "Affichage du nom du modèle", "modelNameDisplay": "Affichage du nom du modèle",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "Nom du modèle", "modelName": "Nom du modèle",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "שם Checkpoint הועתק", "checkpointNameCopied": "שם Checkpoint הועתק",
"toggleBlur": "הפעל/כבה טשטוש", "toggleBlur": "הפעל/כבה טשטוש",
"show": "הצג", "show": "הצג",
"openExampleImages": "פתח תיקיית תמונות דוגמה" "openExampleImages": "פתח תיקיית תמונות דוגמה",
"replacePreview": "החלף תצוגה מקדימה"
}, },
"nsfw": { "nsfw": {
"matureContent": "תוכן למבוגרים", "matureContent": "תוכן למבוגרים",
@@ -240,6 +241,12 @@
"always": "כותרות עליונות ותחתונות תמיד גלויות", "always": "כותרות עליונות ותחתונות תמיד גלויות",
"hover": "כותרות עליונות ותחתונות מופיעות רק בעת ריחוף מעל כרטיס" "hover": "כותרות עליונות ותחתונות מופיעות רק בעת ריחוף מעל כרטיס"
}, },
"modelCardFooterAction": "פעולת כפתור כרטיס מודל",
"modelCardFooterActionOptions": {
"exampleImages": "פתח תמונות דוגמה",
"replacePreview": "החלף תצוגה מקדימה"
},
"modelCardFooterActionHelp": "בחר מה עושה הכפתור בפינה הימנית התחתונה של הכרטיס.",
"modelNameDisplay": "תצוגת שם מודל", "modelNameDisplay": "תצוגת שם מודל",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "שם מודל", "modelName": "שם מודל",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "checkpointの名前をコピーしました", "checkpointNameCopied": "checkpointの名前をコピーしました",
"toggleBlur": "ぼかしの切り替え", "toggleBlur": "ぼかしの切り替え",
"show": "表示", "show": "表示",
"openExampleImages": "例画像フォルダを開く" "openExampleImages": "例画像フォルダを開く",
"replacePreview": "プレビューを置換"
}, },
"nsfw": { "nsfw": {
"matureContent": "成人向けコンテンツ", "matureContent": "成人向けコンテンツ",
@@ -240,6 +241,12 @@
"always": "ヘッダーとフッターが常に表示されます", "always": "ヘッダーとフッターが常に表示されます",
"hover": "カードにホバーしたときのみヘッダーとフッターが表示されます" "hover": "カードにホバーしたときのみヘッダーとフッターが表示されます"
}, },
"modelCardFooterAction": "モデルカードボタンのアクション",
"modelCardFooterActionOptions": {
"exampleImages": "例画像を開く",
"replacePreview": "プレビューを置換"
},
"modelCardFooterActionHelp": "カード右下のボタンが何をするかを選択します。",
"modelNameDisplay": "モデル名表示", "modelNameDisplay": "モデル名表示",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "モデル名", "modelName": "モデル名",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Checkpoint 이름 복사됨", "checkpointNameCopied": "Checkpoint 이름 복사됨",
"toggleBlur": "블러 토글", "toggleBlur": "블러 토글",
"show": "보기", "show": "보기",
"openExampleImages": "예시 이미지 폴더 열기" "openExampleImages": "예시 이미지 폴더 열기",
"replacePreview": "미리보기 교체"
}, },
"nsfw": { "nsfw": {
"matureContent": "성인 콘텐츠", "matureContent": "성인 콘텐츠",
@@ -240,6 +241,12 @@
"always": "헤더와 푸터가 항상 보입니다", "always": "헤더와 푸터가 항상 보입니다",
"hover": "카드에 마우스를 올렸을 때만 헤더와 푸터가 나타납니다" "hover": "카드에 마우스를 올렸을 때만 헤더와 푸터가 나타납니다"
}, },
"modelCardFooterAction": "모델 카드 버튼 동작",
"modelCardFooterActionOptions": {
"exampleImages": "예시 이미지 열기",
"replacePreview": "미리보기 교체"
},
"modelCardFooterActionHelp": "카드 우측 하단 버튼이 수행할 작업을 선택하세요.",
"modelNameDisplay": "모델명 표시", "modelNameDisplay": "모델명 표시",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "모델명", "modelName": "모델명",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Имя checkpoint скопировано", "checkpointNameCopied": "Имя checkpoint скопировано",
"toggleBlur": "Переключить размытие", "toggleBlur": "Переключить размытие",
"show": "Показать", "show": "Показать",
"openExampleImages": "Открыть папку с примерами" "openExampleImages": "Открыть папку с примерами",
"replacePreview": "Заменить превью"
}, },
"nsfw": { "nsfw": {
"matureContent": "Контент для взрослых", "matureContent": "Контент для взрослых",
@@ -240,6 +241,12 @@
"always": "Заголовки и подписи всегда видны", "always": "Заголовки и подписи всегда видны",
"hover": "Заголовки и подписи появляются только при наведении на карточку" "hover": "Заголовки и подписи появляются только при наведении на карточку"
}, },
"modelCardFooterAction": "Действие кнопки карточки модели",
"modelCardFooterActionOptions": {
"exampleImages": "Открыть примеры изображений",
"replacePreview": "Заменить превью"
},
"modelCardFooterActionHelp": "Выберите, что делает кнопка в правом нижнем углу карточки.",
"modelNameDisplay": "Отображение названия модели", "modelNameDisplay": "Отображение названия модели",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "Название модели", "modelName": "Название модели",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "检查点名称已复制", "checkpointNameCopied": "检查点名称已复制",
"toggleBlur": "切换模糊", "toggleBlur": "切换模糊",
"show": "显示", "show": "显示",
"openExampleImages": "打开示例图片文件夹" "openExampleImages": "打开示例图片文件夹",
"replacePreview": "替换预览"
}, },
"nsfw": { "nsfw": {
"matureContent": "成熟内容", "matureContent": "成熟内容",
@@ -240,6 +241,12 @@
"always": "标题和底部始终显示", "always": "标题和底部始终显示",
"hover": "仅在悬停卡片时显示标题和底部" "hover": "仅在悬停卡片时显示标题和底部"
}, },
"modelCardFooterAction": "模型卡片按钮操作",
"modelCardFooterActionOptions": {
"exampleImages": "打开示例图片",
"replacePreview": "替换预览"
},
"modelCardFooterActionHelp": "选择右下角卡片按钮的功能。",
"modelNameDisplay": "模型名称显示", "modelNameDisplay": "模型名称显示",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "模型名称", "modelName": "模型名称",

View File

@@ -101,7 +101,8 @@
"checkpointNameCopied": "Checkpoint 名稱已複製", "checkpointNameCopied": "Checkpoint 名稱已複製",
"toggleBlur": "切換模糊", "toggleBlur": "切換模糊",
"show": "顯示", "show": "顯示",
"openExampleImages": "開啟範例圖片資料夾" "openExampleImages": "開啟範例圖片資料夾",
"replacePreview": "更換預覽圖"
}, },
"nsfw": { "nsfw": {
"matureContent": "成熟內容", "matureContent": "成熟內容",
@@ -240,6 +241,12 @@
"always": "標題與頁腳始終可見", "always": "標題與頁腳始終可見",
"hover": "標題與頁腳僅在滑鼠懸停時顯示" "hover": "標題與頁腳僅在滑鼠懸停時顯示"
}, },
"modelCardFooterAction": "模型卡片按鈕操作",
"modelCardFooterActionOptions": {
"exampleImages": "開啟範例圖片",
"replacePreview": "更換預覽圖"
},
"modelCardFooterActionHelp": "選擇右下角卡片按鈕的功能。",
"modelNameDisplay": "模型名稱顯示", "modelNameDisplay": "模型名稱顯示",
"modelNameDisplayOptions": { "modelNameDisplayOptions": {
"modelName": "模型名稱", "modelName": "模型名稱",

View File

@@ -163,6 +163,7 @@ class SettingsHandler:
"show_only_sfw", "show_only_sfw",
"compact_mode", "compact_mode",
"priority_tags", "priority_tags",
"model_card_footer_action",
"model_name_display", "model_name_display",
) )

View File

@@ -46,6 +46,7 @@ DEFAULT_SETTINGS: Dict[str, Any] = {
"compact_mode": False, "compact_mode": False,
"priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(), "priority_tags": DEFAULT_PRIORITY_TAG_CONFIG.copy(),
"model_name_display": "model_name", "model_name_display": "model_name",
"model_card_footer_action": "example_images",
} }
@@ -317,6 +318,7 @@ class SettingsManager:
'cardInfoDisplay': 'card_info_display', 'cardInfoDisplay': 'card_info_display',
'includeTriggerWords': 'include_trigger_words', 'includeTriggerWords': 'include_trigger_words',
'compactMode': 'compact_mode', 'compactMode': 'compact_mode',
'modelCardFooterAction': 'model_card_footer_action',
} }
updated = False updated = False

View File

@@ -491,7 +491,13 @@ export function createModelCard(model, modelType) {
// Generate UI text with i18n support // Generate UI text with i18n support
const toggleBlurTitle = translate('modelCard.actions.toggleBlur', {}, 'Toggle blur'); const toggleBlurTitle = translate('modelCard.actions.toggleBlur', {}, 'Toggle blur');
const showButtonText = translate('modelCard.actions.show', {}, 'Show'); const showButtonText = translate('modelCard.actions.show', {}, 'Show');
const openExampleImagesTitle = translate('modelCard.actions.openExampleImages', {}, 'Open Example Images Folder'); const footerActionSetting = state.global.settings.model_card_footer_action || 'example_images';
const footerActionTitle = footerActionSetting === 'replace_preview'
? translate('modelCard.actions.replacePreview', {}, 'Replace Preview')
: translate('modelCard.actions.openExampleImages', {}, 'Open Example Images Folder');
const footerActionIcon = footerActionSetting === 'replace_preview'
? 'fas fa-image'
: 'fas fa-folder-open';
card.innerHTML = ` card.innerHTML = `
<div class="card-preview ${shouldBlur ? 'blurred' : ''}"> <div class="card-preview ${shouldBlur ? 'blurred' : ''}">
@@ -525,8 +531,8 @@ export function createModelCard(model, modelType) {
${model.civitai?.name ? `<span class="version-name">${model.civitai.name}</span>` : ''} ${model.civitai?.name ? `<span class="version-name">${model.civitai.name}</span>` : ''}
</div> </div>
<div class="card-actions"> <div class="card-actions">
<i class="fas fa-folder-open" <i class="${footerActionIcon}"
title="${openExampleImagesTitle}"> title="${footerActionTitle}">
</i> </i>
</div> </div>
</div> </div>

View File

@@ -290,6 +290,12 @@ export class SettingsManager {
cardInfoDisplaySelect.value = state.global.settings.card_info_display || 'always'; cardInfoDisplaySelect.value = state.global.settings.card_info_display || 'always';
} }
// Set model card footer action
const modelCardFooterActionSelect = document.getElementById('modelCardFooterAction');
if (modelCardFooterActionSelect) {
modelCardFooterActionSelect.value = state.global.settings.model_card_footer_action || 'example_images';
}
// Set model name display setting // Set model name display setting
const modelNameDisplaySelect = document.getElementById('modelNameDisplay'); const modelNameDisplaySelect = document.getElementById('modelNameDisplay');
if (modelNameDisplaySelect) { if (modelNameDisplaySelect) {
@@ -1221,6 +1227,10 @@ export class SettingsManager {
if (settingKey === 'model_name_display') { if (settingKey === 'model_name_display') {
this.reloadContent(); this.reloadContent();
} }
if (settingKey === 'model_card_footer_action') {
this.reloadContent();
}
} catch (error) { } catch (error) {
showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error'); showToast('toast.settings.settingSaveFailed', { message: error.message }, 'error');
} }

View File

@@ -27,6 +27,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({
display_density: 'default', display_density: 'default',
card_info_display: 'always', card_info_display: 'always',
model_name_display: 'model_name', model_name_display: 'model_name',
model_card_footer_action: 'example_images',
include_trigger_words: false, include_trigger_words: false,
compact_mode: false, compact_mode: false,
priority_tags: { ...DEFAULT_PRIORITY_TAG_CONFIG }, priority_tags: { ...DEFAULT_PRIORITY_TAG_CONFIG },

View File

@@ -173,6 +173,23 @@
</div> </div>
</div> </div>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">
<label for="modelCardFooterAction">{{ t('settings.layoutSettings.modelCardFooterAction') }}</label>
</div>
<div class="setting-control select-control">
<select id="modelCardFooterAction" onchange="settingsManager.saveSelectSetting('modelCardFooterAction', 'model_card_footer_action')">
<option value="example_images">{{ t('settings.layoutSettings.modelCardFooterActionOptions.exampleImages') }}</option>
<option value="replace_preview">{{ t('settings.layoutSettings.modelCardFooterActionOptions.replacePreview') }}</option>
</select>
</div>
</div>
<div class="input-help">
{{ t('settings.layoutSettings.modelCardFooterActionHelp') }}
</div>
</div>
<!-- Language Selection --> <!-- Language Selection -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">