feat(ui): add setup guidance when example images path is not configured

When users try to import custom example images without configuring the
download location, show a helpful guidance interface instead of failing
silently or showing an error after the fact.

Changes:
- ShowcaseView.js: Check if example_images_path is configured before
  showing import interface; display setup guidance with open settings button
- showcase.css: Add styles for the setup guidance state
- locales: Add translation keys for all 10 supported languages

Clicking 'Open Settings' will:
1. Open the settings modal
2. Scroll to the Example Images section
3. Highlight the section with a brief animation
4. Focus the input field

Fixes #785
This commit is contained in:
Will Miao
2026-01-28 15:53:58 +08:00
parent 2ccfbaf073
commit a78868adce
12 changed files with 193 additions and 10 deletions

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "Beispielbilder-Ordner geöffnet",
"openingFolder": "Beispielbilder-Ordner wird geöffnet",
"failedToOpen": "Fehler beim Öffnen des Beispielbilder-Ordners"
"failedToOpen": "Fehler beim Öffnen des Beispielbilder-Ordners",
"setupRequired": "Beispielbilder-Speicher",
"setupDescription": "Um benutzerdefinierte Beispielbilder hinzuzufügen, müssen Sie zuerst einen Download-Speicherort festlegen.",
"setupUsage": "Dieser Pfad wird sowohl für heruntergeladene als auch für benutzerdefinierte Beispielbilder verwendet.",
"openSettings": "Einstellungen öffnen"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "Example images folder opened",
"openingFolder": "Opening example images folder",
"failedToOpen": "Failed to open example images folder"
"failedToOpen": "Failed to open example images folder",
"setupRequired": "Example Images Storage",
"setupDescription": "To add custom example images, you need to set a download location first.",
"setupUsage": "This path is used for both downloaded and custom example images.",
"openSettings": "Open Settings"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "Carpeta de imágenes de ejemplo abierta",
"openingFolder": "Abriendo carpeta de imágenes de ejemplo",
"failedToOpen": "Error al abrir carpeta de imágenes de ejemplo"
"failedToOpen": "Error al abrir carpeta de imágenes de ejemplo",
"setupRequired": "Almacenamiento de imágenes de ejemplo",
"setupDescription": "Para agregar imágenes de ejemplo personalizadas, primero necesita establecer una ubicación de descarga.",
"setupUsage": "Esta ruta se utiliza tanto para imágenes de ejemplo descargadas como personalizadas.",
"openSettings": "Abrir configuración"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "Dossier d'images d'exemple ouvert",
"openingFolder": "Ouverture du dossier d'images d'exemple",
"failedToOpen": "Échec de l'ouverture du dossier d'images d'exemple"
"failedToOpen": "Échec de l'ouverture du dossier d'images d'exemple",
"setupRequired": "Stockage d'images d'exemple",
"setupDescription": "Pour ajouter des images d'exemple personnalisées, vous devez d'abord définir un emplacement de téléchargement.",
"setupUsage": "Ce chemin est utilisé pour les images d'exemple téléchargées et personnalisées.",
"openSettings": "Ouvrir les paramètres"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "תיקיית תמונות הדוגמה נפתחה",
"openingFolder": "פותח תיקיית תמונות דוגמה",
"failedToOpen": "פתיחת תיקיית תמונות הדוגמה נכשלה"
"failedToOpen": "פתיחת תיקיית תמונות הדוגמה נכשלה",
"setupRequired": "אחסון תמונות דוגמה",
"setupDescription": "כדי להוסיף תמונות דוגמה מותאמות אישית, עליך קודם להגדיר מיקום הורדה.",
"setupUsage": "נתיב זה משמש הן עבור תמונות דוגמה שהורדו והן עבור תמונות מותאמות אישית.",
"openSettings": "פתח הגדרות"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "例画像フォルダが開かれました",
"openingFolder": "例画像フォルダを開いています",
"failedToOpen": "例画像フォルダを開くのに失敗しました"
"failedToOpen": "例画像フォルダを開くのに失敗しました",
"setupRequired": "例画像ストレージ",
"setupDescription": "カスタム例画像を追加するには、まずダウンロード場所を設定する必要があります。",
"setupUsage": "このパスは、ダウンロードした例画像とカスタム画像の両方に使用されます。",
"openSettings": "設定を開く"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "예시 이미지 폴더가 열렸습니다",
"openingFolder": "예시 이미지 폴더를 여는 중",
"failedToOpen": "예시 이미지 폴더 열기 실패"
"failedToOpen": "예시 이미지 폴더 열기 실패",
"setupRequired": "예시 이미지 저장소",
"setupDescription": "사용자 지정 예시 이미지를 추가하려면 먼저 다운로드 위치를 설정해야 합니다.",
"setupUsage": "이 경로는 다운로드한 예시 이미지와 사용자 지정 이미지 모두에 사용됩니다.",
"openSettings": "설정 열기"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "Папка с примерами изображений открыта",
"openingFolder": "Открытие папки с примерами изображений",
"failedToOpen": "Не удалось открыть папку с примерами изображений"
"failedToOpen": "Не удалось открыть папку с примерами изображений",
"setupRequired": "Хранилище примеров изображений",
"setupDescription": "Чтобы добавить собственные примеры изображений, сначала нужно установить место загрузки.",
"setupUsage": "Этот путь используется как для загруженных, так и для пользовательских примеров изображений.",
"openSettings": "Открыть настройки"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "示例图片文件夹已打开",
"openingFolder": "正在打开示例图片文件夹",
"failedToOpen": "打开示例图片文件夹失败"
"failedToOpen": "打开示例图片文件夹失败",
"setupRequired": "示例图片存储",
"setupDescription": "要添加自定义示例图片,您需要先设置下载位置。",
"setupUsage": "此路径用于存储下载的示例图片和自定义图片。",
"openSettings": "打开设置"
}
},
"help": {

View File

@@ -1165,7 +1165,11 @@
"exampleImages": {
"opened": "範例圖片資料夾已開啟",
"openingFolder": "正在開啟範例圖片資料夾",
"failedToOpen": "開啟範例圖片資料夾失敗"
"failedToOpen": "開啟範例圖片資料夾失敗",
"setupRequired": "範例圖片儲存",
"setupDescription": "要新增自訂範例圖片,您需要先設定下載位置。",
"setupUsage": "此路徑用於儲存下載的範例圖片和自訂圖片。",
"openSettings": "開啟設定"
}
},
"help": {

View File

@@ -482,3 +482,75 @@
[data-theme="dark"] .import-container {
background: rgba(255, 255, 255, 0.03);
}
/* Setup Guidance State - When example images path is not configured */
.import-container--needs-setup {
cursor: default;
border-style: solid;
border-color: var(--border-color);
background: var(--lora-surface);
}
.import-container--needs-setup:hover {
border-color: var(--border-color);
transform: none;
}
.import-setup-guidance {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--space-2);
padding: var(--space-3) var(--space-2);
text-align: center;
}
.setup-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: var(--space-1);
}
.setup-icon i {
font-size: 1.75rem;
color: var(--lora-accent);
}
.import-setup-guidance h3 {
margin: 0;
font-size: 1.2rem;
font-weight: 600;
color: var(--text-color);
}
.setup-description {
margin: 0;
color: var(--text-color);
opacity: 0.9;
max-width: 320px;
line-height: 1.5;
}
.setup-usage {
margin: 0;
font-size: 0.85em;
color: var(--text-color);
opacity: 0.6;
font-style: italic;
}
.setup-settings-btn {
margin-top: var(--space-2);
background: var(--lora-accent) !important;
color: var(--lora-text) !important;
}
.setup-settings-btn:hover {
opacity: 0.9;
transform: translateY(-1px);
}

View File

@@ -4,6 +4,8 @@
*/
import { showToast } from '../../../utils/uiHelpers.js';
import { state } from '../../../state/index.js';
import { modalManager } from '../../../managers/ModalManager.js';
import { translate } from '../../../utils/i18nHelpers.js';
import { NSFW_LEVELS } from '../../../utils/constants.js';
import {
initLazyLoading,
@@ -275,6 +277,40 @@ function findLocalFile(img, index, exampleFiles) {
* @returns {string} HTML content for import interface
*/
function renderImportInterface(isEmpty) {
// Check if example images path is configured
const exampleImagesPath = state.global.settings.example_images_path;
const isPathConfigured = exampleImagesPath && exampleImagesPath.trim() !== '';
// If path is not configured, show setup guidance
if (!isPathConfigured) {
const title = translate('uiHelpers.exampleImages.setupRequired', {}, 'Example Images Storage');
const description = translate('uiHelpers.exampleImages.setupDescription', {}, 'To add custom example images, you need to set a download location first.');
const usage = translate('uiHelpers.exampleImages.setupUsage', {}, 'This path is used for both downloaded and custom example images.');
const openSettings = translate('uiHelpers.exampleImages.openSettings', {}, 'Open Settings');
return `
<div class="example-import-area ${isEmpty ? 'empty' : ''}">
<div class="import-container import-container--needs-setup" id="exampleImportContainer">
<div class="import-setup-guidance">
<div class="setup-icon">
<i class="fas fa-folder-plus"></i>
</div>
<h3>${title}</h3>
<p class="setup-description">
${description}
</p>
<p class="setup-usage">
${usage}
</p>
<button class="select-files-btn setup-settings-btn" id="openExampleSettingsBtn">
<i class="fas fa-cog"></i> ${openSettings}
</button>
</div>
</div>
</div>
`;
}
return `
<div class="example-import-area ${isEmpty ? 'empty' : ''}">
<div class="import-container" id="exampleImportContainer">
@@ -300,6 +336,33 @@ function renderImportInterface(isEmpty) {
`;
}
/**
* Open settings modal and scroll to example images section
*/
function openSettingsForExampleImages() {
modalManager.showModal('settingsModal');
// Wait for modal to be visible, then scroll to example images section
setTimeout(() => {
const exampleImagesInput = document.getElementById('exampleImagesPath');
if (exampleImagesInput) {
// Find the parent settings-section
const section = exampleImagesInput.closest('.settings-section');
if (section) {
section.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Add a brief highlight effect
section.style.transition = 'background-color 0.3s ease';
section.style.backgroundColor = 'rgba(66, 153, 225, 0.1)';
setTimeout(() => {
section.style.backgroundColor = '';
}, 1500);
}
// Focus the input
exampleImagesInput.focus();
}
}, 100);
}
/**
* Initialize the example import functionality
* @param {string} modelHash - The SHA256 hash of the model
@@ -311,6 +374,14 @@ export function initExampleImport(modelHash, container) {
const importContainer = container.querySelector('#exampleImportContainer');
const fileInput = container.querySelector('#exampleFilesInput');
const selectFilesBtn = container.querySelector('#selectExampleFilesBtn');
const openSettingsBtn = container.querySelector('#openExampleSettingsBtn');
// Set up "Open Settings" button for setup guidance state
if (openSettingsBtn) {
openSettingsBtn.addEventListener('click', () => {
openSettingsForExampleImages();
});
}
// Set up file selection button
if (selectFilesBtn) {