Enhance AIImages mod by adding cancellation support for image generation, improving user experience with localized strings for cancellation actions in English and Russian. Refactor service integration for better dependency management and update AIImages.dll to reflect these changes.

This commit is contained in:
Leonid Pershin
2025-10-26 19:10:45 +03:00
parent 3434927342
commit 02b0143186
11 changed files with 974 additions and 174 deletions

View File

@@ -0,0 +1,287 @@
using System;
using System.Collections.Generic;
using AIImages.Settings;
namespace AIImages.Validation
{
/// <summary>
/// Валидатор для настроек мода AI Images
/// </summary>
public static class SettingsValidator
{
/// <summary>
/// Валидирует все настройки и возвращает список ошибок
/// </summary>
public static ValidationResult Validate(AIImagesModSettings settings)
{
var result = new ValidationResult();
if (settings == null)
{
result.AddError("Settings object is null");
return result;
}
// Валидация API endpoint
ValidateApiEndpoint(settings.apiEndpoint, result);
// Валидация размеров изображения
ValidateImageDimensions(settings.width, settings.height, result);
// Валидация steps
ValidateSteps(settings.steps, result);
// Валидация CFG scale
ValidateCfgScale(settings.cfgScale, result);
// Валидация sampler и scheduler
ValidateSamplerAndScheduler(
settings.selectedSampler,
settings.selectedScheduler,
result
);
// Валидация пути сохранения
ValidateSavePath(settings.savePath, result);
return result;
}
private static void ValidateApiEndpoint(string endpoint, ValidationResult result)
{
if (string.IsNullOrWhiteSpace(endpoint))
{
result.AddError("API endpoint cannot be empty");
return;
}
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out Uri uri))
{
result.AddError($"Invalid API endpoint format: {endpoint}");
return;
}
if (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps)
{
result.AddWarning($"API endpoint should use HTTP or HTTPS protocol: {endpoint}");
}
// Проверка на localhost/127.0.0.1
if (
uri.Host != "localhost"
&& uri.Host != "127.0.0.1"
&& !uri.Host.StartsWith("192.168.")
)
{
result.AddWarning(
"API endpoint is not pointing to a local address. Make sure the API is accessible."
);
}
}
private static void ValidateImageDimensions(int width, int height, ValidationResult result)
{
const int minDimension = 64;
const int maxDimension = 2048;
const int recommendedMin = 512;
const int recommendedMax = 1024;
if (width < minDimension || width > maxDimension)
{
result.AddError(
$"Width must be between {minDimension} and {maxDimension}. Current: {width}"
);
}
if (height < minDimension || height > maxDimension)
{
result.AddError(
$"Height must be between {minDimension} and {maxDimension}. Current: {height}"
);
}
// Проверка кратности 8 (рекомендация для Stable Diffusion)
if (width % 8 != 0)
{
result.AddWarning(
$"Width should be divisible by 8 for optimal results. Current: {width}"
);
}
if (height % 8 != 0)
{
result.AddWarning(
$"Height should be divisible by 8 for optimal results. Current: {height}"
);
}
// Предупреждения о производительности
if (width > recommendedMax || height > recommendedMax)
{
result.AddWarning(
$"Large image dimensions ({width}x{height}) may result in slow generation and high memory usage"
);
}
if (width < recommendedMin || height < recommendedMin)
{
result.AddWarning(
$"Small image dimensions ({width}x{height}) may result in lower quality"
);
}
}
private static void ValidateSteps(int steps, ValidationResult result)
{
const int minSteps = 1;
const int maxSteps = 150;
const int recommendedMin = 20;
const int recommendedMax = 50;
if (steps < minSteps || steps > maxSteps)
{
result.AddError(
$"Steps must be between {minSteps} and {maxSteps}. Current: {steps}"
);
}
if (steps < recommendedMin)
{
result.AddWarning(
$"Low steps value ({steps}) may result in lower quality. Recommended: {recommendedMin}-{recommendedMax}"
);
}
if (steps > recommendedMax)
{
result.AddWarning(
$"High steps value ({steps}) may result in slow generation with minimal quality improvement"
);
}
}
private static void ValidateCfgScale(float cfgScale, ValidationResult result)
{
const float minCfg = 1.0f;
const float maxCfg = 30.0f;
const float recommendedMin = 5.0f;
const float recommendedMax = 15.0f;
if (cfgScale < minCfg || cfgScale > maxCfg)
{
result.AddError(
$"CFG Scale must be between {minCfg} and {maxCfg}. Current: {cfgScale}"
);
}
if (cfgScale < recommendedMin)
{
result.AddWarning(
$"Low CFG scale ({cfgScale}) may ignore prompt. Recommended: {recommendedMin}-{recommendedMax}"
);
}
if (cfgScale > recommendedMax)
{
result.AddWarning(
$"High CFG scale ({cfgScale}) may result in over-saturated or distorted images"
);
}
}
private static void ValidateSamplerAndScheduler(
string sampler,
string scheduler,
ValidationResult result
)
{
if (string.IsNullOrWhiteSpace(sampler))
{
result.AddWarning("Sampler is not selected. A default sampler will be used.");
}
if (string.IsNullOrWhiteSpace(scheduler))
{
result.AddWarning("Scheduler is not selected. A default scheduler will be used.");
}
}
private static void ValidateSavePath(string savePath, ValidationResult result)
{
if (string.IsNullOrWhiteSpace(savePath))
{
result.AddError("Save path cannot be empty");
return;
}
// Проверка на недопустимые символы в пути
char[] invalidChars = System.IO.Path.GetInvalidPathChars();
if (savePath.IndexOfAny(invalidChars) >= 0)
{
result.AddError($"Save path contains invalid characters: {savePath}");
}
}
}
/// <summary>
/// Результат валидации настроек
/// </summary>
public class ValidationResult
{
private readonly List<string> _errors = new List<string>();
private readonly List<string> _warnings = new List<string>();
public bool IsValid => _errors.Count == 0;
public bool HasWarnings => _warnings.Count > 0;
public IReadOnlyList<string> Errors => _errors;
public IReadOnlyList<string> Warnings => _warnings;
public void AddError(string error)
{
if (!string.IsNullOrWhiteSpace(error))
{
_errors.Add(error);
}
}
public void AddWarning(string warning)
{
if (!string.IsNullOrWhiteSpace(warning))
{
_warnings.Add(warning);
}
}
public string GetErrorsAsString()
{
return string.Join("\n", _errors);
}
public string GetWarningsAsString()
{
return string.Join("\n", _warnings);
}
public string GetAllMessagesAsString()
{
var messages = new List<string>();
if (_errors.Count > 0)
{
messages.Add("Errors:");
messages.AddRange(_errors);
}
if (_warnings.Count > 0)
{
if (messages.Count > 0)
messages.Add("");
messages.Add("Warnings:");
messages.AddRange(_warnings);
}
return string.Join("\n", messages);
}
}
}