feat(localization): enhance toast messages for context menu actions, model tags, and download management with improved error handling and user feedback

This commit is contained in:
Will Miao
2025-08-31 14:27:33 +08:00
parent 987b8c8742
commit 8303196b57
14 changed files with 142 additions and 64 deletions

View File

@@ -1,5 +1,6 @@
import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
import { translate } from '../utils/i18n.js';
import { getStorageItem, getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
import {
getCompleteApiConfig,
@@ -503,22 +504,22 @@ export class BaseModelApiClient {
let completionMessage;
if (successCount === totalItems) {
completionMessage = `Successfully refreshed all ${successCount} ${this.apiConfig.config.displayName}s`;
showToast(completionMessage, 'success');
completionMessage = translate('toast.api.bulkMetadataCompleteAll', { count: successCount, type: this.apiConfig.config.displayName }, `Successfully refreshed all ${successCount} ${this.apiConfig.config.displayName}s`);
showToast('toast.api.bulkMetadataCompleteAll', { count: successCount, type: this.apiConfig.config.displayName }, 'success');
} else if (successCount > 0) {
completionMessage = `Refreshed ${successCount} of ${totalItems} ${this.apiConfig.config.displayName}s`;
showToast(completionMessage, 'warning');
completionMessage = translate('toast.api.bulkMetadataCompletePartial', { success: successCount, total: totalItems, type: this.apiConfig.config.displayName }, `Refreshed ${successCount} of ${totalItems} ${this.apiConfig.config.displayName}s`);
showToast('toast.api.bulkMetadataCompletePartial', { success: successCount, total: totalItems, type: this.apiConfig.config.displayName }, 'warning');
if (failedItems.length > 0) {
const failureMessage = failedItems.length <= 3
? failedItems.map(item => `${item.fileName}: ${item.error}`).join('\n')
: failedItems.slice(0, 3).map(item => `${item.fileName}: ${item.error}`).join('\n') +
`\n(and ${failedItems.length - 3} more)`;
showToast(`Failed refreshes:\n${failureMessage}`, 'warning', 6000);
showToast('toast.api.bulkMetadataFailureDetails', { failures: failureMessage }, 'warning', 6000);
}
} else {
completionMessage = `Failed to refresh metadata for any ${this.apiConfig.config.displayName}s`;
showToast(completionMessage, 'error');
completionMessage = translate('toast.api.bulkMetadataCompleteNone', { type: this.apiConfig.config.displayName }, `Failed to refresh metadata for any ${this.apiConfig.config.displayName}s`);
showToast('toast.api.bulkMetadataCompleteNone', { type: this.apiConfig.config.displayName }, 'error');
}
await progressController.complete(completionMessage);

View File

@@ -99,7 +99,7 @@ export class RecipeContextMenu extends BaseContextMenu {
copyRecipeSyntax() {
const recipeId = this.currentCard.dataset.id;
if (!recipeId) {
showToast('Cannot copy recipe: Missing recipe ID', 'error');
showToast('recipes.contextMenu.copyRecipe.missingId', {}, 'error');
return;
}
@@ -114,7 +114,7 @@ export class RecipeContextMenu extends BaseContextMenu {
})
.catch(err => {
console.error('Failed to copy recipe syntax: ', err);
showToast('Failed to copy recipe syntax', 'error');
showToast('recipes.contextMenu.copyRecipe.failed', {}, 'error');
});
}
@@ -122,7 +122,7 @@ export class RecipeContextMenu extends BaseContextMenu {
sendRecipeToWorkflow(replaceMode) {
const recipeId = this.currentCard.dataset.id;
if (!recipeId) {
showToast('Cannot send recipe: Missing recipe ID', 'error');
showToast('recipes.contextMenu.sendRecipe.missingId', {}, 'error');
return;
}
@@ -137,14 +137,14 @@ export class RecipeContextMenu extends BaseContextMenu {
})
.catch(err => {
console.error('Failed to send recipe to workflow: ', err);
showToast('Failed to send recipe to workflow', 'error');
showToast('recipes.contextMenu.sendRecipe.failed', {}, 'error');
});
}
// View all LoRAs in the recipe
viewRecipeLoRAs(recipeId) {
if (!recipeId) {
showToast('Cannot view LoRAs: Missing recipe ID', 'error');
showToast('recipes.contextMenu.viewLoras.missingId', {}, 'error');
return;
}
@@ -171,19 +171,19 @@ export class RecipeContextMenu extends BaseContextMenu {
// Navigate to the LoRAs page
window.location.href = '/loras';
} else {
showToast('No LoRAs found in this recipe', 'info');
showToast('recipes.contextMenu.viewLoras.noLorasFound', {}, 'info');
}
})
.catch(error => {
console.error('Error loading recipe LoRAs:', error);
showToast('Error loading recipe LoRAs: ' + error.message, 'error');
showToast('recipes.contextMenu.viewLoras.loadError', { message: error.message }, 'error');
});
}
// Download missing LoRAs
async downloadMissingLoRAs(recipeId) {
if (!recipeId) {
showToast('Cannot download LoRAs: Missing recipe ID', 'error');
showToast('recipes.contextMenu.downloadMissing.missingId', {}, 'error');
return;
}
@@ -196,7 +196,7 @@ export class RecipeContextMenu extends BaseContextMenu {
const missingLoras = recipe.loras.filter(lora => !lora.inLibrary && !lora.isDeleted);
if (missingLoras.length === 0) {
showToast('No missing LoRAs to download', 'info');
showToast('recipes.contextMenu.downloadMissing.noMissingLoras', {}, 'info');
return;
}
@@ -234,7 +234,7 @@ export class RecipeContextMenu extends BaseContextMenu {
const validLoras = lorasWithVersionInfo.filter(lora => lora !== null);
if (validLoras.length === 0) {
showToast('Failed to get information for missing LoRAs', 'error');
showToast('recipes.contextMenu.downloadMissing.getInfoFailed', {}, 'error');
return;
}
@@ -275,7 +275,7 @@ export class RecipeContextMenu extends BaseContextMenu {
window.importManager.downloadMissingLoras(recipeData, recipeId);
} catch (error) {
console.error('Error downloading missing LoRAs:', error);
showToast('Error preparing LoRAs for download: ' + error.message, 'error');
showToast('recipes.contextMenu.downloadMissing.prepareError', { message: error.message }, 'error');
} finally {
if (state.loadingManager) {
state.loadingManager.hide();

View File

@@ -143,16 +143,13 @@ async function toggleFavorite(card) {
});
if (newFavoriteState) {
const addedText = translate('modelCard.favorites.added', {}, 'Added to favorites');
showToast(addedText, 'success');
showToast('modelCard.favorites.added', {}, 'success');
} else {
const removedText = translate('modelCard.favorites.removed', {}, 'Removed from favorites');
showToast(removedText, 'success');
showToast('modelCard.favorites.removed', {}, 'success');
}
} catch (error) {
console.error('Failed to update favorite status:', error);
const errorText = translate('modelCard.favorites.updateFailed', {}, 'Failed to update favorite status');
showToast(errorText, 'error');
showToast('modelCard.favorites.updateFailed', {}, 'error');
}
}
@@ -164,8 +161,7 @@ function handleSendToWorkflow(card, replaceMode, modelType) {
sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
} else {
// Checkpoint send functionality - to be implemented
const text = translate('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'Send checkpoint to workflow - feature to be implemented');
showToast(text, 'info');
showToast('modelCard.sendToWorkflow.checkpointNotImplemented', {}, 'info');
}
}
@@ -201,8 +197,7 @@ async function handleExampleImagesAccess(card, modelType) {
}
} catch (error) {
console.error('Error checking for example images:', error);
const text = translate('modelCard.exampleImages.checkError', {}, 'Error checking for example images');
showToast(text, 'error');
showToast('modelCard.exampleImages.checkError', {}, 'error');
}
}
@@ -284,8 +279,7 @@ function showExampleAccessModal(card, modelType) {
// Get the model hash
const modelHash = card.dataset.sha256;
if (!modelHash) {
const text = translate('modelCard.exampleImages.missingHash', {}, 'Missing model hash information.');
showToast(text, 'error');
showToast('modelCard.exampleImages.missingHash', {}, 'error');
return;
}

View File

@@ -154,8 +154,7 @@ export async function setupModelDescriptionEditing(filePath) {
}
if (!newValue) {
this.innerHTML = originalValue;
const emptyErrorText = translate('modals.model.description.validation.cannotBeEmpty', {}, 'Description cannot be empty');
showToast(emptyErrorText, 'error');
showToast('modals.model.description.validation.cannotBeEmpty', {}, 'error');
exitEditMode();
return;
}
@@ -163,12 +162,10 @@ export async function setupModelDescriptionEditing(filePath) {
// Save to backend
const { getModelApiClient } = await import('../../api/modelApiFactory.js');
await getModelApiClient().saveModelMetadata(filePath, { modelDescription: newValue });
const successText = translate('modals.model.description.messages.updated', {}, 'Model description updated');
showToast(successText, 'success');
showToast('modals.model.description.messages.updated', {}, 'success');
} catch (err) {
this.innerHTML = originalValue;
const errorText = translate('modals.model.description.messages.updateFailed', {}, 'Failed to update model description');
showToast(errorText, 'error');
showToast('modals.model.description.messages.updateFailed', {}, 'error');
} finally {
exitEditMode();
}

View File

@@ -438,11 +438,9 @@ async function saveNotes(filePath) {
try {
await getModelApiClient().saveModelMetadata(filePath, { notes: content });
const successMessage = translate('modals.model.notes.saved', {}, 'Notes saved successfully');
showToast(successMessage, 'success');
showToast('modals.model.notes.saved', {}, 'success');
} catch (error) {
const errorMessage = translate('modals.model.notes.saveFailed', {}, 'Failed to save notes');
showToast(errorMessage, 'error');
showToast('modals.model.notes.saveFailed', {}, 'error');
}
}

View File

@@ -217,10 +217,10 @@ async function saveTags() {
// Exit edit mode
editBtn.click();
showToast(translate('modelTags.messages.updated', {}, 'Tags updated successfully'), 'success');
showToast('modelTags.messages.updated', {}, 'success');
} catch (error) {
console.error('Error saving tags:', error);
showToast(translate('modelTags.messages.updateFailed', {}, 'Failed to update tags'), 'error');
showToast('modelTags.messages.updateFailed', {}, 'error');
}
}
@@ -362,24 +362,21 @@ function addNewTag(tag) {
// Validation: Check length
if (tag.length > 30) {
const text = translate('modelTags.validation.maxLength', {}, 'Tag should not exceed 30 characters');
showToast(text, 'error');
showToast('modelTags.validation.maxLength', {}, 'error');
return;
}
// Validation: Check total number
const currentTags = tagsContainer.querySelectorAll('.metadata-item');
if (currentTags.length >= 30) {
const text = translate('modelTags.validation.maxCount', {}, 'Maximum 30 tags allowed');
showToast(text, 'error');
showToast('modelTags.validation.maxCount', {}, 'error');
return;
}
// Validation: Check for duplicates
const existingTags = Array.from(currentTags).map(tag => tag.dataset.tag);
if (existingTags.includes(tag)) {
const text = translate('modelTags.validation.duplicate', {}, 'This tag already exists');
showToast(text, 'error');
showToast('modelTags.validation.duplicate', {}, 'error');
return;
}

View File

@@ -445,7 +445,7 @@ export function initMediaControlHandlers(container) {
state.virtualScroller.updateSingleItem(result.model_file_path, updateData);
} else {
// Show error message
showToast(result.error || 'Failed to delete example image', 'error');
showToast('showcase.exampleImages.deleteFailed', { error: result.error }, 'error');
// Reset button state
this.disabled = false;

View File

@@ -343,7 +343,7 @@ export class DownloadManager {
this.updateTargetPath();
} catch (error) {
showToast(error.message, 'error');
showToast('downloads.loadError', { message: error.message }, 'error');
}
}
@@ -507,7 +507,7 @@ export class DownloadManager {
await resetAndReload(true);
} catch (error) {
showToast(error.message, 'error');
showToast('downloads.downloadError', { message: error.message }, 'error');
} finally {
this.loadingManager.hide();
}

View File

@@ -285,7 +285,7 @@ class ExampleImagesManager {
// Close settings modal
modalManager.closeModal('settingsModal');
} else {
showToast(data.error || 'Failed to start download', 'error');
showToast('exampleImages.downloadStartFailed', { error: data.error }, 'error');
}
} catch (error) {
console.error('Failed to start download:', error);
@@ -321,7 +321,7 @@ class ExampleImagesManager {
this.updateDownloadButtonText();
showToast('toast.exampleImages.downloadPaused', {}, 'info');
} else {
showToast(data.error || 'Failed to pause download', 'error');
showToast('exampleImages.pauseFailed', { error: data.error }, 'error');
}
} catch (error) {
console.error('Failed to pause download:', error);
@@ -357,7 +357,7 @@ class ExampleImagesManager {
this.updateDownloadButtonText();
showToast('toast.exampleImages.downloadResumed', {}, 'success');
} else {
showToast(data.error || 'Failed to resume download', 'error');
showToast('exampleImages.resumeFailed', { error: data.error }, 'error');
}
} catch (error) {
console.error('Failed to resume download:', error);

View File

@@ -282,7 +282,7 @@ export class FilterManager {
message = `Filtering by ${tagsCount} tag${tagsCount > 1 ? 's' : ''}`;
}
showToast(message, 'success');
showToast('filters.applied', { message }, 'success');
}
} else {
this.filterButton.classList.remove('active');

View File

@@ -77,7 +77,7 @@ export class DownloadManager {
if (!result.success) {
// Handle save error
console.error("Failed to save recipe:", result.error);
showToast(result.error, 'error');
showToast('import.recipeSaveFailed', { error: result.error }, 'error');
// Close modal
modalManager.closeModal('importModal');
return;
@@ -107,7 +107,7 @@ export class DownloadManager {
} catch (error) {
console.error('Error:', error);
showToast(error.message, 'error');
showToast('import.processingError', { message: error.message }, 'error');
} finally {
this.importManager.loadingManager.hide();
}

View File

@@ -136,7 +136,7 @@ export class FolderBrowser {
this.initializeFolderBrowser();
} catch (error) {
console.error('Error in API calls:', error);
showToast(error.message, 'error');
showToast('import.folderBrowserError', { message: error.message }, 'error');
}
}