mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-23 22:22:11 -03:00
feat(localization): enhance import modal and related components with new labels, descriptions, and error messages for improved user experience
This commit is contained in:
@@ -290,7 +290,59 @@
|
||||
"controls": {
|
||||
"import": {
|
||||
"action": "Import Recipe",
|
||||
"title": "Import a recipe from image or URL"
|
||||
"title": "Import a recipe from image or URL",
|
||||
"urlLocalPath": "URL / Local Path",
|
||||
"uploadImage": "Upload Image",
|
||||
"urlSectionDescription": "Input a Civitai image URL or local file path to import as a recipe.",
|
||||
"imageUrlOrPath": "Image URL or File Path:",
|
||||
"urlPlaceholder": "https://civitai.com/images/... or C:/path/to/image.png",
|
||||
"fetchImage": "Fetch Image",
|
||||
"uploadSectionDescription": "Upload an image with LoRA metadata to import as a recipe.",
|
||||
"selectImage": "Select Image",
|
||||
"recipeName": "Recipe Name",
|
||||
"recipeNamePlaceholder": "Enter recipe name",
|
||||
"tagsOptional": "Tags (optional)",
|
||||
"addTagPlaceholder": "Add a tag",
|
||||
"addTag": "Add",
|
||||
"noTagsAdded": "No tags added",
|
||||
"lorasInRecipe": "LoRAs in this Recipe",
|
||||
"downloadLocationPreview": "Download Location Preview:",
|
||||
"useDefaultPath": "Use Default Path",
|
||||
"useDefaultPathTooltip": "When enabled, files are automatically organized using configured path templates",
|
||||
"selectLoraRoot": "Select a LoRA root directory",
|
||||
"targetFolderPath": "Target Folder Path:",
|
||||
"folderPathPlaceholder": "Type folder path or select from tree below...",
|
||||
"createNewFolder": "Create new folder",
|
||||
"root": "Root",
|
||||
"browseFolders": "Browse Folders:",
|
||||
"downloadAndSaveRecipe": "Download & Save Recipe",
|
||||
"downloadMissingLoras": "Download Missing LoRAs",
|
||||
"saveRecipe": "Save Recipe",
|
||||
"loraCountInfo": "({existing}/{total} in library)",
|
||||
"processingInput": "Processing input...",
|
||||
"analyzingMetadata": "Analyzing image metadata...",
|
||||
"downloadingLoras": "Downloading LoRAs...",
|
||||
"savingRecipe": "Saving recipe...",
|
||||
"startingDownload": "Starting download for LoRA {current}/{total}",
|
||||
"deletedFromCivitai": "Deleted from Civitai",
|
||||
"inLibrary": "In Library",
|
||||
"notInLibrary": "Not in Library",
|
||||
"earlyAccessRequired": "This LoRA requires early access payment to download.",
|
||||
"earlyAccessEnds": "Early access ends on {date}.",
|
||||
"earlyAccess": "Early Access",
|
||||
"verifyEarlyAccess": "Verify that you have purchased early access before downloading.",
|
||||
"duplicateRecipesFound": "{count} identical recipe(s) found in your library",
|
||||
"duplicateRecipesDescription": "These recipes contain the same LoRAs with identical weights.",
|
||||
"showDuplicates": "Show duplicates",
|
||||
"hideDuplicates": "Hide duplicates",
|
||||
"loraCount": "{count} LoRAs",
|
||||
"recipePreviewAlt": "Recipe preview",
|
||||
"loraPreviewAlt": "LoRA preview",
|
||||
"errors": {
|
||||
"selectImageFile": "Please select an image file",
|
||||
"enterUrlOrPath": "Please enter a URL or file path",
|
||||
"selectLoraRoot": "Please select a LoRA root directory"
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"title": "Refresh recipe list"
|
||||
@@ -832,7 +884,15 @@
|
||||
"preparingForSharing": "Preparing recipe for sharing...",
|
||||
"downloadStarted": "Recipe download started",
|
||||
"shareError": "Error sharing recipe: {message}",
|
||||
"sharePreparationError": "Error preparing recipe for sharing"
|
||||
"sharePreparationError": "Error preparing recipe for sharing",
|
||||
"selectImageFirst": "Please select an image first",
|
||||
"enterRecipeName": "Please enter a recipe name",
|
||||
"processingError": "Processing error: {message}",
|
||||
"folderBrowserError": "Error loading folder browser: {message}",
|
||||
"recipeSaveFailed": "Failed to save recipe: {error}",
|
||||
"importFailed": "Import failed: {message}",
|
||||
"folderTreeFailed": "Failed to load folder tree",
|
||||
"folderTreeError": "Error loading folder tree"
|
||||
},
|
||||
"models": {
|
||||
"noModelsSelected": "No models selected",
|
||||
@@ -890,15 +950,10 @@
|
||||
"downloadError": "Download error: {message}"
|
||||
},
|
||||
"import": {
|
||||
"enterRecipeName": "Please enter a recipe name",
|
||||
"selectImageFirst": "Please select an image first",
|
||||
"folderTreeFailed": "Failed to load folder tree",
|
||||
"folderTreeError": "Error loading folder tree",
|
||||
"imagesImported": "Example images imported successfully",
|
||||
"importFailed": "Failed to import example images: {message}",
|
||||
"recipeSaveFailed": "Failed to save recipe: {error}",
|
||||
"processingError": "Processing error: {message}",
|
||||
"folderBrowserError": "Folder browser error: {message}"
|
||||
"importFailed": "Failed to import example images: {message}"
|
||||
},
|
||||
"triggerWords": {
|
||||
"loadFailed": "Could not load trained words",
|
||||
|
||||
@@ -290,7 +290,59 @@
|
||||
"controls": {
|
||||
"import": {
|
||||
"action": "导入配方",
|
||||
"title": "从图片或 URL 导入配方"
|
||||
"title": "从图片或 URL 导入配方",
|
||||
"urlLocalPath": "URL / 本地路径",
|
||||
"uploadImage": "上传图片",
|
||||
"urlSectionDescription": "输入 Civitai 图片 URL 或本地文件路径以导入为配方。",
|
||||
"imageUrlOrPath": "图片 URL 或文件路径:",
|
||||
"urlPlaceholder": "https://civitai.com/images/... 或 C:/path/to/image.png",
|
||||
"fetchImage": "获取图片",
|
||||
"uploadSectionDescription": "上传带有 LoRA 元数据的图片以导入为配方。",
|
||||
"selectImage": "选择图片",
|
||||
"recipeName": "配方名称",
|
||||
"recipeNamePlaceholder": "输入配方名称",
|
||||
"tagsOptional": "标签(可选)",
|
||||
"addTagPlaceholder": "添加标签",
|
||||
"addTag": "添加",
|
||||
"noTagsAdded": "未添加标签",
|
||||
"lorasInRecipe": "此配方中的 LoRA",
|
||||
"downloadLocationPreview": "下载位置预览:{path}",
|
||||
"useDefaultPath": "使用默认路径",
|
||||
"useDefaultPathTooltip": "启用后,文件将自动使用配置的路径模板进行组织",
|
||||
"selectLoraRoot": "选择 LoRA 根目录",
|
||||
"targetFolderPath": "目标文件夹路径:",
|
||||
"folderPathPlaceholder": "输入文件夹路径或从下面的树中选择...",
|
||||
"createNewFolder": "创建新文件夹",
|
||||
"root": "根目录",
|
||||
"browseFolders": "浏览文件夹:",
|
||||
"downloadAndSaveRecipe": "下载并保存配方",
|
||||
"downloadMissingLoras": "下载缺失的 LoRA",
|
||||
"saveRecipe": "保存配方",
|
||||
"loraCountInfo": "({existing}/{total} in library)",
|
||||
"processingInput": "处理输入...",
|
||||
"analyzingMetadata": "分析图像元数据...",
|
||||
"downloadingLoras": "下载 LoRA...",
|
||||
"savingRecipe": "保存配方...",
|
||||
"startingDownload": "开始下载 LoRA {current}/{total}",
|
||||
"deletedFromCivitai": "从 Civitai 中删除",
|
||||
"inLibrary": "在库中",
|
||||
"notInLibrary": "不在库中",
|
||||
"earlyAccessRequired": "此 LoRA 需要提前访问权限才能下载。",
|
||||
"earlyAccessEnds": "提前访问权限将于 {date} 结束。",
|
||||
"earlyAccess": "提前访问",
|
||||
"verifyEarlyAccess": "在下载之前,请验证您是否已购买提前访问权限。",
|
||||
"duplicateRecipesFound": "在您的库中找到 {count} 个相同的配方。",
|
||||
"duplicateRecipesDescription": "这些配方包含相同的 LoRA,权重完全相同。",
|
||||
"showDuplicates": "显示重复项",
|
||||
"hideDuplicates": "隐藏重复项",
|
||||
"loraCount": "{count} LoRA",
|
||||
"recipePreviewAlt": "配方预览",
|
||||
"loraPreviewAlt": "LoRA 预览",
|
||||
"errors": {
|
||||
"selectImageFile": "请选择一个图像文件",
|
||||
"enterUrlOrPath": "请输入 URL 或文件路径",
|
||||
"selectLoraRoot": "请选择 LoRA 根目录"
|
||||
}
|
||||
},
|
||||
"refresh": {
|
||||
"title": "刷新配方列表"
|
||||
@@ -832,7 +884,15 @@
|
||||
"preparingForSharing": "正在准备分享配方...",
|
||||
"downloadStarted": "配方下载已开始",
|
||||
"shareError": "分享配方出错:{message}",
|
||||
"sharePreparationError": "准备分享配方出错"
|
||||
"sharePreparationError": "准备分享配方出错",
|
||||
"selectImageFirst": "请先选择图片",
|
||||
"enterRecipeName": "请输入配方名称",
|
||||
"processingError": "处理出错:{message}",
|
||||
"folderBrowserError": "加载文件夹浏览器出错:{message}",
|
||||
"recipeSaveFailed": "保存配方失败:{error}",
|
||||
"importFailed": "导入失败:{message}",
|
||||
"folderTreeFailed": "加载文件夹树失败",
|
||||
"folderTreeError": "加载文件夹树出错"
|
||||
},
|
||||
"models": {
|
||||
"noModelsSelected": "未选中模型",
|
||||
@@ -890,15 +950,10 @@
|
||||
"downloadError": "下载错误:{message}"
|
||||
},
|
||||
"import": {
|
||||
"enterRecipeName": "请输入配方名称",
|
||||
"selectImageFirst": "请先选择图片",
|
||||
"folderTreeFailed": "加载文件夹树失败",
|
||||
"folderTreeError": "加载文件夹树出错",
|
||||
"imagesImported": "示例图片导入成功",
|
||||
"importFailed": "导入示例图片失败:{message}",
|
||||
"recipeSaveFailed": "保存配方失败:{error}",
|
||||
"processingError": "处理出错:{message}",
|
||||
"folderBrowserError": "文件夹浏览器出错:{message}"
|
||||
"importFailed": "导入示例图片失败:{message}"
|
||||
},
|
||||
"triggerWords": {
|
||||
"loadFailed": "无法加载训练词",
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||
import { state } from '../state/index.js';
|
||||
import { MODEL_TYPES } from '../api/apiConfig.js';
|
||||
import { showToast } from '../utils/uiHelpers.js';
|
||||
import { translate } from '../utils/i18nHelpers.js';
|
||||
|
||||
export class ImportManager {
|
||||
constructor() {
|
||||
@@ -110,7 +111,7 @@ export class ImportManager {
|
||||
if (recipeName) recipeName.value = '';
|
||||
|
||||
const tagsContainer = document.getElementById('tagsContainer');
|
||||
if (tagsContainer) tagsContainer.innerHTML = '<div class="empty-tags">No tags added</div>';
|
||||
if (tagsContainer) tagsContainer.innerHTML = `<div class="empty-tags">${translate('recipes.controls.import.noTagsAdded', {}, 'No tags added')}</div>`;
|
||||
|
||||
// Clear folder path input
|
||||
const folderPathInput = document.getElementById('importFolderPath');
|
||||
@@ -261,7 +262,7 @@ export class ImportManager {
|
||||
|
||||
this.updateTargetPath();
|
||||
} catch (error) {
|
||||
showToast('toast.import.importFailed', { message: error.message }, 'error');
|
||||
showToast('toast.recipes.importFailed', { message: error.message }, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,11 +351,11 @@ export class ImportManager {
|
||||
await this.folderTreeManager.loadTree(treeData.tree);
|
||||
} else {
|
||||
console.error('Failed to fetch folder tree:', treeData.error);
|
||||
showToast('toast.import.folderTreeFailed', {}, 'error');
|
||||
showToast('toast.recipes.folderTreeFailed', {}, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing folder tree:', error);
|
||||
showToast('toast.import.folderTreeError', {}, 'error');
|
||||
showToast('toast.recipes.folderTreeError', {}, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,9 +369,7 @@ export class ImportManager {
|
||||
const pathDisplay = document.getElementById('importTargetPathDisplay');
|
||||
const loraRoot = document.getElementById('importLoraRoot').value;
|
||||
|
||||
let fullPath = loraRoot || 'Select a LoRA root directory';
|
||||
|
||||
if (loraRoot) {
|
||||
let fullPath = loraRoot || translate('recipes.controls.import.selectLoraRoot', {}, 'Select a LoRA root directory'); if (loraRoot) {
|
||||
if (this.useDefaultPath) {
|
||||
// Show actual template path
|
||||
try {
|
||||
@@ -425,11 +424,11 @@ export class ImportManager {
|
||||
|
||||
// Update the modal title
|
||||
const modalTitle = document.querySelector('#importModal h2');
|
||||
if (modalTitle) modalTitle.textContent = 'Download Missing LoRAs';
|
||||
if (modalTitle) modalTitle.textContent = translate('recipes.controls.import.downloadMissingLoras', {}, 'Download Missing LoRAs');
|
||||
|
||||
// Update the save button text
|
||||
const saveButton = document.querySelector('#locationStep .primary-btn');
|
||||
if (saveButton) saveButton.textContent = 'Download Missing LoRAs';
|
||||
if (saveButton) saveButton.textContent = translate('recipes.controls.import.downloadMissingLoras', {}, 'Download Missing LoRAs');
|
||||
|
||||
// Hide the back button
|
||||
const backButton = document.querySelector('#locationStep .secondary-btn');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
import { translate } from '../../utils/i18nHelpers.js';
|
||||
import { getModelApiClient } from '../../api/modelApiFactory.js';
|
||||
import { MODEL_TYPES } from '../../api/apiConfig.js';
|
||||
import { getStorageItem } from '../../utils/storageHelpers.js';
|
||||
@@ -13,13 +14,13 @@ export class DownloadManager {
|
||||
const isDownloadOnly = !!this.importManager.recipeId;
|
||||
|
||||
if (!isDownloadOnly && !this.importManager.recipeName) {
|
||||
showToast('toast.import.enterRecipeName', {}, 'error');
|
||||
showToast('toast.recipes.enterRecipeName', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Show progress indicator
|
||||
this.importManager.loadingManager.showSimpleLoading(isDownloadOnly ? 'Downloading LoRAs...' : 'Saving recipe...');
|
||||
this.importManager.loadingManager.showSimpleLoading(isDownloadOnly ? translate('recipes.controls.import.downloadingLoras', {}, 'Downloading LoRAs...') : translate('recipes.controls.import.savingRecipe', {}, 'Saving recipe...'));
|
||||
|
||||
// Only send the complete recipe to save if not in download-only mode
|
||||
if (!isDownloadOnly) {
|
||||
@@ -77,7 +78,7 @@ export class DownloadManager {
|
||||
if (!result.success) {
|
||||
// Handle save error
|
||||
console.error("Failed to save recipe:", result.error);
|
||||
showToast('toast.import.recipeSaveFailed', { error: result.error }, 'error');
|
||||
showToast('toast.recipes.recipeSaveFailed', { error: result.error }, 'error');
|
||||
// Close modal
|
||||
modalManager.closeModal('importModal');
|
||||
return;
|
||||
@@ -107,7 +108,7 @@ export class DownloadManager {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showToast('toast.import.processingError', { message: error.message }, 'error');
|
||||
showToast('toast.recipes.processingError', { message: error.message }, 'error');
|
||||
} finally {
|
||||
this.importManager.loadingManager.hide();
|
||||
}
|
||||
@@ -117,7 +118,7 @@ export class DownloadManager {
|
||||
// For download, we need to validate the target path
|
||||
const loraRoot = document.getElementById('importLoraRoot')?.value;
|
||||
if (!loraRoot) {
|
||||
throw new Error('Please select a LoRA root directory');
|
||||
throw new Error(translate('recipes.controls.import.errors.selectLoraRoot', {}, 'Please select a LoRA root directory'));
|
||||
}
|
||||
|
||||
// Build target path
|
||||
@@ -195,7 +196,7 @@ export class DownloadManager {
|
||||
currentLoraProgress = 0;
|
||||
|
||||
// Initial status update for new LoRA
|
||||
this.importManager.loadingManager.setStatus(`Starting download for LoRA ${i+1}/${this.importManager.downloadableLoRAs.length}`);
|
||||
this.importManager.loadingManager.setStatus(translate('recipes.controls.import.startingDownload', { current: i+1, total: this.importManager.downloadableLoRAs.length }, `Starting download for LoRA ${i+1}/${this.importManager.downloadableLoRAs.length}`));
|
||||
updateProgress(0, completedDownloads, lora.name);
|
||||
|
||||
try {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
import { translate } from '../../utils/i18nHelpers.js';
|
||||
import { getStorageItem } from '../../utils/storageHelpers.js';
|
||||
|
||||
export class FolderBrowser {
|
||||
@@ -136,7 +137,7 @@ export class FolderBrowser {
|
||||
this.initializeFolderBrowser();
|
||||
} catch (error) {
|
||||
console.error('Error in API calls:', error);
|
||||
showToast('toast.import.folderBrowserError', { message: error.message }, 'error');
|
||||
showToast('toast.recipes.folderBrowserError', { message: error.message }, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +205,7 @@ export class FolderBrowser {
|
||||
const loraRoot = document.getElementById('importLoraRoot')?.value || '';
|
||||
const newFolder = document.getElementById('importNewFolder')?.value?.trim() || '';
|
||||
|
||||
let fullPath = loraRoot || 'Select a LoRA root directory';
|
||||
let fullPath = loraRoot || translate('recipes.controls.import.selectLoraRoot', {}, 'Select a LoRA root directory');
|
||||
|
||||
if (loraRoot) {
|
||||
if (this.importManager.selectedFolder) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
import { translate } from '../../utils/i18nHelpers.js';
|
||||
|
||||
export class ImageProcessor {
|
||||
constructor(importManager) {
|
||||
@@ -13,7 +14,7 @@ export class ImageProcessor {
|
||||
|
||||
// Validate file type
|
||||
if (!file.type.match('image.*')) {
|
||||
errorElement.textContent = 'Please select an image file';
|
||||
errorElement.textContent = translate('recipes.controls.import.errors.selectImageFile', {}, 'Please select an image file');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +33,7 @@ export class ImageProcessor {
|
||||
|
||||
// Validate input
|
||||
if (!input) {
|
||||
errorElement.textContent = 'Please enter a URL or file path';
|
||||
errorElement.textContent = translate('recipes.controls.import.errors.enterUrlOrPath', {}, 'Please enter a URL or file path');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ export class ImageProcessor {
|
||||
errorElement.textContent = '';
|
||||
|
||||
// Show loading indicator
|
||||
this.importManager.loadingManager.showSimpleLoading('Processing input...');
|
||||
this.importManager.loadingManager.showSimpleLoading(translate('recipes.controls.import.processingInput', {}, 'Processing input...'));
|
||||
|
||||
try {
|
||||
// Check if it's a URL or a local file path
|
||||
@@ -156,12 +157,12 @@ export class ImageProcessor {
|
||||
|
||||
async uploadAndAnalyzeImage() {
|
||||
if (!this.importManager.recipeImage) {
|
||||
showToast('toast.import.selectImageFirst', {}, 'error');
|
||||
showToast('toast.recipes.selectImageFirst', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.importManager.loadingManager.showSimpleLoading('Analyzing image metadata...');
|
||||
this.importManager.loadingManager.showSimpleLoading(translate('recipes.controls.import.analyzingMetadata', {}, 'Analyzing image metadata...'));
|
||||
|
||||
// Create form data for upload
|
||||
const formData = new FormData();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { showToast } from '../../utils/uiHelpers.js';
|
||||
import { translate } from '../../utils/i18nHelpers.js';
|
||||
|
||||
export class RecipeDataManager {
|
||||
constructor(importManager) {
|
||||
@@ -62,17 +63,17 @@ export class RecipeDataManager {
|
||||
// For file upload mode
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
imagePreview.innerHTML = `<img src="${e.target.result}" alt="Recipe preview">`;
|
||||
imagePreview.innerHTML = `<img src="${e.target.result}" alt="${translate('recipes.controls.import.recipePreviewAlt', {}, 'Recipe preview')}">`;
|
||||
};
|
||||
reader.readAsDataURL(this.importManager.recipeImage);
|
||||
} else if (this.importManager.recipeData && this.importManager.recipeData.image_base64) {
|
||||
// For URL mode - use the base64 image data returned from the backend
|
||||
imagePreview.innerHTML = `<img src="data:image/jpeg;base64,${this.importManager.recipeData.image_base64}" alt="Recipe preview">`;
|
||||
imagePreview.innerHTML = `<img src="data:image/jpeg;base64,${this.importManager.recipeData.image_base64}" alt="${translate('recipes.controls.import.recipePreviewAlt', {}, 'Recipe preview')}">`;
|
||||
} else if (this.importManager.importMode === 'url') {
|
||||
// Fallback for URL mode if no base64 data
|
||||
const urlInput = document.getElementById('imageUrlInput');
|
||||
if (urlInput && urlInput.value) {
|
||||
imagePreview.innerHTML = `<img src="${urlInput.value}" alt="Recipe preview" crossorigin="anonymous">`;
|
||||
imagePreview.innerHTML = `<img src="${urlInput.value}" alt="${translate('recipes.controls.import.recipePreviewAlt', {}, 'Recipe preview')}" crossorigin="anonymous">`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export class RecipeDataManager {
|
||||
const existingLoras = this.importManager.recipeData.loras.filter(lora => lora.existsLocally).length;
|
||||
const loraCountInfo = document.getElementById('loraCountInfo');
|
||||
if (loraCountInfo) {
|
||||
loraCountInfo.textContent = `(${existingLoras}/${totalLoras} in library)`;
|
||||
loraCountInfo.textContent = translate('recipes.controls.import.loraCountInfo', { existing: existingLoras, total: totalLoras }, `(${existingLoras}/${totalLoras} in library)`);
|
||||
}
|
||||
|
||||
// Display LoRAs list
|
||||
@@ -98,16 +99,16 @@ export class RecipeDataManager {
|
||||
let statusBadge;
|
||||
if (isDeleted) {
|
||||
statusBadge = `<div class="deleted-badge">
|
||||
<i class="fas fa-exclamation-circle"></i> Deleted from Civitai
|
||||
<i class="fas fa-exclamation-circle"></i> ${translate('recipes.controls.import.deletedFromCivitai', {}, 'Deleted from Civitai')}
|
||||
</div>`;
|
||||
} else {
|
||||
statusBadge = existsLocally ?
|
||||
`<div class="local-badge">
|
||||
<i class="fas fa-check"></i> In Library
|
||||
<i class="fas fa-check"></i> ${translate('recipes.controls.import.inLibrary', {}, 'In Library')}
|
||||
<div class="local-path">${localPath}</div>
|
||||
</div>` :
|
||||
`<div class="missing-badge">
|
||||
<i class="fas fa-exclamation-triangle"></i> Not in Library
|
||||
<i class="fas fa-exclamation-triangle"></i> ${translate('recipes.controls.import.notInLibrary', {}, 'Not in Library')}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -115,20 +116,20 @@ export class RecipeDataManager {
|
||||
let earlyAccessBadge = '';
|
||||
if (isEarlyAccess) {
|
||||
// Format the early access end date if available
|
||||
let earlyAccessInfo = 'This LoRA requires early access payment to download.';
|
||||
let earlyAccessInfo = translate('recipes.controls.import.earlyAccessRequired', {}, 'This LoRA requires early access payment to download.');
|
||||
if (lora.earlyAccessEndsAt) {
|
||||
try {
|
||||
const endDate = new Date(lora.earlyAccessEndsAt);
|
||||
const formattedDate = endDate.toLocaleDateString();
|
||||
earlyAccessInfo += ` Early access ends on ${formattedDate}.`;
|
||||
earlyAccessInfo += ` ${translate('recipes.controls.import.earlyAccessEnds', { date: formattedDate }, `Early access ends on ${formattedDate}.`)}`;
|
||||
} catch (e) {
|
||||
console.warn('Failed to format early access date', e);
|
||||
}
|
||||
}
|
||||
|
||||
earlyAccessBadge = `<div class="early-access-badge">
|
||||
<i class="fas fa-clock"></i> Early Access
|
||||
<div class="early-access-info">${earlyAccessInfo} Verify that you have purchased early access before downloading.</div>
|
||||
<i class="fas fa-clock"></i> ${translate('recipes.controls.import.earlyAccess', {}, 'Early Access')}
|
||||
<div class="early-access-info">${earlyAccessInfo} ${translate('recipes.controls.import.verifyEarlyAccess', {}, 'Verify that you have purchased early access before downloading.')}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
@@ -139,7 +140,7 @@ export class RecipeDataManager {
|
||||
return `
|
||||
<div class="lora-item ${existsLocally ? 'exists-locally' : isDeleted ? 'is-deleted' : 'missing-locally'} ${isEarlyAccess ? 'is-early-access' : ''}">
|
||||
<div class="lora-thumbnail">
|
||||
<img src="${lora.thumbnailUrl || '/loras_static/images/no-preview.png'}" alt="LoRA preview">
|
||||
<img src="${lora.thumbnailUrl || '/loras_static/images/no-preview.png'}" alt="${translate('recipes.controls.import.loraPreviewAlt', {}, 'LoRA preview')}">
|
||||
</div>
|
||||
<div class="lora-content">
|
||||
<div class="lora-header">
|
||||
@@ -232,12 +233,12 @@ export class RecipeDataManager {
|
||||
<div class="warning-icon"><i class="fas fa-clone"></i></div>
|
||||
<div class="warning-content">
|
||||
<div class="warning-title">
|
||||
${this.importManager.duplicateRecipes.length} identical ${this.importManager.duplicateRecipes.length === 1 ? 'recipe' : 'recipes'} found in your library
|
||||
${translate('recipes.controls.import.duplicateRecipesFound', { count: this.importManager.duplicateRecipes.length }, `${this.importManager.duplicateRecipes.length} identical ${this.importManager.duplicateRecipes.length === 1 ? 'recipe' : 'recipes'} found in your library`)}
|
||||
</div>
|
||||
<div class="warning-text">
|
||||
These recipes contain the same LoRAs with identical weights.
|
||||
${translate('recipes.controls.import.duplicateRecipesDescription', {}, 'These recipes contain the same LoRAs with identical weights.')}
|
||||
<button id="toggleDuplicatesList" class="toggle-duplicates-btn">
|
||||
Show duplicates <i class="fas fa-chevron-down"></i>
|
||||
${translate('recipes.controls.import.showDuplicates', {}, 'Show duplicates')} <i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -246,7 +247,7 @@ export class RecipeDataManager {
|
||||
${this.importManager.duplicateRecipes.map((recipe) => `
|
||||
<div class="duplicate-recipe-card">
|
||||
<div class="duplicate-recipe-preview">
|
||||
<img src="${recipe.file_url}" alt="Recipe preview">
|
||||
<img src="${recipe.file_url}" alt="${translate('recipes.controls.import.recipePreviewAlt', {}, 'Recipe preview')}">
|
||||
<div class="duplicate-recipe-title">${recipe.title}</div>
|
||||
</div>
|
||||
<div class="duplicate-recipe-details">
|
||||
@@ -254,7 +255,7 @@ export class RecipeDataManager {
|
||||
<i class="fas fa-calendar-alt"></i> ${formatDate(recipe.modified)}
|
||||
</div>
|
||||
<div class="duplicate-recipe-lora-count">
|
||||
<i class="fas fa-layer-group"></i> ${recipe.lora_count} LoRAs
|
||||
<i class="fas fa-layer-group"></i> ${translate('recipes.controls.import.loraCount', { count: recipe.lora_count }, `${recipe.lora_count} LoRAs`)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -275,9 +276,9 @@ export class RecipeDataManager {
|
||||
const icon = toggleButton.querySelector('i');
|
||||
if (icon) {
|
||||
if (list.classList.contains('collapsed')) {
|
||||
toggleButton.innerHTML = `Show duplicates <i class="fas fa-chevron-down"></i>`;
|
||||
toggleButton.innerHTML = `${translate('recipes.controls.import.showDuplicates', {}, 'Show duplicates')} <i class="fas fa-chevron-down"></i>`;
|
||||
} else {
|
||||
toggleButton.innerHTML = `Hide duplicates <i class="fas fa-chevron-up"></i>`;
|
||||
toggleButton.innerHTML = `${translate('recipes.controls.import.hideDuplicates', {}, 'Hide duplicates')} <i class="fas fa-chevron-up"></i>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -362,9 +363,9 @@ export class RecipeDataManager {
|
||||
nextButton.classList.remove('warning-btn');
|
||||
|
||||
if (missingNotDeleted > 0) {
|
||||
nextButton.textContent = 'Download Missing LoRAs';
|
||||
nextButton.textContent = translate('recipes.controls.import.downloadMissingLoras', {}, 'Download Missing LoRAs');
|
||||
} else {
|
||||
nextButton.textContent = 'Save Recipe';
|
||||
nextButton.textContent = translate('recipes.controls.import.saveRecipe', {}, 'Save Recipe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +392,7 @@ export class RecipeDataManager {
|
||||
const tagsContainer = document.getElementById('tagsContainer');
|
||||
|
||||
if (this.importManager.recipeTags.length === 0) {
|
||||
tagsContainer.innerHTML = '<div class="empty-tags">No tags added</div>';
|
||||
tagsContainer.innerHTML = `<div class="empty-tags">${translate('recipes.controls.import.noTagsAdded', {}, 'No tags added')}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -406,7 +407,7 @@ export class RecipeDataManager {
|
||||
proceedFromDetails() {
|
||||
// Validate recipe name
|
||||
if (!this.importManager.recipeName) {
|
||||
showToast('toast.import.enterRecipeName', {}, 'error');
|
||||
showToast('toast.recipes.enterRecipeName', {}, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,29 +2,29 @@
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button class="close" onclick="modalManager.closeModal('importModal')">×</button>
|
||||
<h2>Import Recipe</h2>
|
||||
<h2>{{ t('recipes.controls.import.action') }}</h2>
|
||||
</div>
|
||||
|
||||
<!-- Step 1: Upload Image or Input URL -->
|
||||
<div class="import-step" id="uploadStep">
|
||||
<div class="import-mode-toggle">
|
||||
<button class="toggle-btn active" data-mode="url" onclick="importManager.toggleImportMode('url')">
|
||||
<i class="fas fa-link"></i> URL / Local Path
|
||||
<i class="fas fa-link"></i> {{ t('recipes.controls.import.urlLocalPath') }}
|
||||
</button>
|
||||
<button class="toggle-btn" data-mode="upload" onclick="importManager.toggleImportMode('upload')">
|
||||
<i class="fas fa-upload"></i> Upload Image
|
||||
<i class="fas fa-upload"></i> {{ t('recipes.controls.import.uploadImage') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Input URL/Path Section -->
|
||||
<div class="import-section" id="urlSection">
|
||||
<p>Input a Civitai image URL or local file path to import as a recipe.</p>
|
||||
<p>{{ t('recipes.controls.import.urlSectionDescription') }}</p>
|
||||
<div class="input-group">
|
||||
<label for="imageUrlInput">Image URL or File Path:</label>
|
||||
<label for="imageUrlInput">{{ t('recipes.controls.import.imageUrlOrPath') }}</label>
|
||||
<div class="input-with-button">
|
||||
<input type="text" id="imageUrlInput" placeholder="https://civitai.com/images/... or C:/path/to/image.png">
|
||||
<input type="text" id="imageUrlInput" placeholder="{{ t('recipes.controls.import.urlPlaceholder') }}">
|
||||
<button class="primary-btn" onclick="importManager.handleUrlInput()">
|
||||
<i class="fas fa-download"></i> Fetch Image
|
||||
<i class="fas fa-download"></i> {{ t('recipes.controls.import.fetchImage') }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="error-message" id="importUrlError"></div>
|
||||
@@ -33,13 +33,13 @@
|
||||
|
||||
<!-- Upload Image Section -->
|
||||
<div class="import-section" id="uploadSection">
|
||||
<p>Upload an image with LoRA metadata to import as a recipe.</p>
|
||||
<p>{{ t('recipes.controls.import.uploadSectionDescription') }}</p>
|
||||
<div class="input-group">
|
||||
<label for="recipeImageUpload">Select Image:</label>
|
||||
<label for="recipeImageUpload">{{ t('recipes.controls.import.selectImage') }}</label>
|
||||
<div class="file-input-wrapper">
|
||||
<input type="file" id="recipeImageUpload" accept="image/*" onchange="importManager.handleImageUpload(event)">
|
||||
<div class="file-input-button">
|
||||
<i class="fas fa-upload"></i> Select Image
|
||||
<i class="fas fa-upload"></i> {{ t('recipes.controls.import.selectImage') }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="error-message" id="uploadError"></div>
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="secondary-btn" onclick="modalManager.closeModal('importModal')">Cancel</button>
|
||||
<button class="secondary-btn" onclick="modalManager.closeModal('importModal')">{{ t('common.actions.cancel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,28 +60,28 @@
|
||||
|
||||
<div class="recipe-form-container">
|
||||
<div class="input-group">
|
||||
<label for="recipeName">Recipe Name</label>
|
||||
<input type="text" id="recipeName" placeholder="Enter recipe name"
|
||||
<label for="recipeName">{{ t('recipes.controls.import.recipeName') }}</label>
|
||||
<input type="text" id="recipeName" placeholder="{{ t('recipes.controls.import.recipeNamePlaceholder') }}"
|
||||
onchange="importManager.handleRecipeNameChange(event)">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label>Tags (optional)</label>
|
||||
<label>{{ t('recipes.controls.import.tagsOptional') }}</label>
|
||||
<div class="tag-input-container">
|
||||
<input type="text" id="tagInput" placeholder="Add a tag">
|
||||
<input type="text" id="tagInput" placeholder="{{ t('recipes.controls.import.addTagPlaceholder') }}">
|
||||
<button class="secondary-btn" onclick="importManager.addTag()">
|
||||
<i class="fas fa-plus"></i> Add
|
||||
<i class="fas fa-plus"></i> {{ t('recipes.controls.import.addTag') }}
|
||||
</button>
|
||||
</div>
|
||||
<div id="tagsContainer" class="tags-container">
|
||||
<div class="empty-tags">No tags added</div>
|
||||
<div class="empty-tags">{{ t('recipes.controls.import.noTagsAdded') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label>LoRAs in this Recipe <span id="loraCountInfo" class="lora-count-info">(0/0 in library)</span></label>
|
||||
<label>{{ t('recipes.controls.import.lorasInRecipe') }} <span id="loraCountInfo" class="lora-count-info">(0/0 in library)</span></label>
|
||||
<div id="lorasList" class="loras-list">
|
||||
<!-- LoRAs will be populated here -->
|
||||
</div>
|
||||
@@ -93,8 +93,8 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="secondary-btn" onclick="importManager.backToUpload()">Back</button>
|
||||
<button class="primary-btn" onclick="importManager.proceedFromDetails()">Next</button>
|
||||
<button class="secondary-btn" onclick="importManager.backToUpload()">{{ t('common.actions.back') }}</button>
|
||||
<button class="primary-btn" onclick="importManager.proceedFromDetails()">{{ t('common.actions.next') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -104,9 +104,9 @@
|
||||
<!-- Path preview with inline toggle -->
|
||||
<div class="path-preview">
|
||||
<div class="path-preview-header">
|
||||
<label>Download Location Preview:</label>
|
||||
<div class="inline-toggle-container" title="When enabled, files are automatically organized using configured path templates">
|
||||
<span class="inline-toggle-label">Use Default Path</span>
|
||||
<label>{{ t('recipes.controls.import.downloadLocationPreview') }}</label>
|
||||
<div class="inline-toggle-container" title="{{ t('recipes.controls.import.useDefaultPathTooltip') }}">
|
||||
<span class="inline-toggle-label">{{ t('recipes.controls.import.useDefaultPath') }}</span>
|
||||
<div class="toggle-switch">
|
||||
<input type="checkbox" id="importUseDefaultPath">
|
||||
<label for="importUseDefaultPath" class="toggle-slider"></label>
|
||||
@@ -114,13 +114,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="path-display" id="importTargetPathDisplay">
|
||||
<span class="path-text">Select a LoRA root directory</span>
|
||||
<span class="path-text">{{ t('recipes.controls.import.selectLoraRoot') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Model Root Selection -->
|
||||
<div class="input-group">
|
||||
<label for="importLoraRoot">Select LoRA Root:</label>
|
||||
<label for="importLoraRoot">{{ t('recipes.controls.import.selectLoraRoot') }}</label>
|
||||
<select id="importLoraRoot"></select>
|
||||
</div>
|
||||
|
||||
@@ -128,10 +128,10 @@
|
||||
<div class="manual-path-selection" id="importManualPathSelection">
|
||||
<!-- Path input with autocomplete -->
|
||||
<div class="input-group">
|
||||
<label for="importFolderPath">Target Folder Path:</label>
|
||||
<label for="importFolderPath">{{ t('recipes.controls.import.targetFolderPath') }}</label>
|
||||
<div class="path-input-container">
|
||||
<input type="text" id="importFolderPath" placeholder="Type folder path or select from tree below..." autocomplete="off" />
|
||||
<button type="button" id="importCreateFolderBtn" class="create-folder-btn" title="Create new folder">
|
||||
<input type="text" id="importFolderPath" placeholder="{{ t('recipes.controls.import.folderPathPlaceholder') }}" autocomplete="off" />
|
||||
<button type="button" id="importCreateFolderBtn" class="create-folder-btn" title="{{ t('recipes.controls.import.createNewFolder') }}">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -141,13 +141,13 @@
|
||||
<!-- Breadcrumb navigation -->
|
||||
<div class="breadcrumb-nav" id="importBreadcrumbNav">
|
||||
<span class="breadcrumb-item root" data-path="">
|
||||
<i class="fas fa-home"></i> Root
|
||||
<i class="fas fa-home"></i> {{ t('recipes.controls.import.root') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Hierarchical folder tree -->
|
||||
<div class="input-group">
|
||||
<label>Browse Folders:</label>
|
||||
<label>{{ t('recipes.controls.import.browseFolders') }}</label>
|
||||
<div class="folder-tree-container">
|
||||
<div class="folder-tree" id="importFolderTree">
|
||||
<!-- Tree will be loaded dynamically -->
|
||||
@@ -158,8 +158,8 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="secondary-btn" onclick="importManager.backToDetails()">Back</button>
|
||||
<button class="primary-btn" onclick="importManager.saveRecipe()">Download & Save Recipe</button>
|
||||
<button class="secondary-btn" onclick="importManager.backToDetails()">{{ t('common.actions.back') }}</button>
|
||||
<button class="primary-btn" onclick="importManager.saveRecipe()">{{ t('recipes.controls.import.downloadAndSaveRecipe') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user