diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll
index 1045dbb..8b8bc0f 100644
Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ
diff --git a/Languages/English/Keyed/AIImages.xml b/Languages/English/Keyed/AIImages.xml
index 28a0bce..3e9adf6 100644
--- a/Languages/English/Keyed/AIImages.xml
+++ b/Languages/English/Keyed/AIImages.xml
@@ -45,10 +45,16 @@
API Endpoint
Test Connection
Load Available Models
+ Model
+ No model selected
+ Load models first
+ Load Samplers & Schedulers
Successfully connected to API!
Failed to connect to API. Check endpoint and ensure Stable Diffusion WebUI is running.
Loaded {0} models from API
No models found. Check API connection.
+ Loaded {0} samplers and {1} schedulers from API
+ No samplers or schedulers found. Check API connection.
Generation Settings
Configure image generation parameters
Art Style
@@ -58,6 +64,7 @@
Width
Height
Sampler
+ Schedule Type
Prompts
Base prompts that will be added to all generations
Base Positive Prompt
diff --git a/Languages/Russian/Keyed/AIImages.xml b/Languages/Russian/Keyed/AIImages.xml
index fcdd1d7..33070b5 100644
--- a/Languages/Russian/Keyed/AIImages.xml
+++ b/Languages/Russian/Keyed/AIImages.xml
@@ -45,10 +45,16 @@
Адрес API
Проверить соединение
Загрузить доступные модели
+ Модель
+ Модель не выбрана
+ Сначала загрузите модели
+ Загрузить сэмплеры и планировщики
Успешное подключение к API!
Не удалось подключиться к API. Проверьте адрес и убедитесь, что Stable Diffusion WebUI запущен.
Загружено {0} моделей из API
Модели не найдены. Проверьте подключение к API.
+ Загружено {0} сэмплеров и {1} планировщиков из API
+ Сэмплеры и планировщики не найдены. Проверьте подключение к API.
Настройки генерации
Настройка параметров генерации изображений
Художественный стиль
@@ -58,6 +64,7 @@
Ширина
Высота
Сэмплер
+ Тип планировщика
Промпты
Базовые промпты, которые будут добавлены ко всем генерациям
Базовый позитивный промпт
diff --git a/Source/AIImages/Models/GenerationRequest.cs b/Source/AIImages/Models/GenerationRequest.cs
index 3843729..057949d 100644
--- a/Source/AIImages/Models/GenerationRequest.cs
+++ b/Source/AIImages/Models/GenerationRequest.cs
@@ -12,6 +12,7 @@ namespace AIImages.Models
public int Width { get; set; }
public int Height { get; set; }
public string Sampler { get; set; }
+ public string Scheduler { get; set; }
public int Seed { get; set; }
public string Model { get; set; }
}
diff --git a/Source/AIImages/Models/StableDiffusionSettings.cs b/Source/AIImages/Models/StableDiffusionSettings.cs
index 634d918..90ffef8 100644
--- a/Source/AIImages/Models/StableDiffusionSettings.cs
+++ b/Source/AIImages/Models/StableDiffusionSettings.cs
@@ -12,6 +12,7 @@ namespace AIImages.Models
public int Width { get; set; }
public int Height { get; set; }
public string Sampler { get; set; }
+ public string Scheduler { get; set; }
public int Seed { get; set; }
public string Model { get; set; }
public ArtStyle ArtStyle { get; set; }
@@ -24,6 +25,7 @@ namespace AIImages.Models
Width = 512;
Height = 768;
Sampler = "Euler a";
+ Scheduler = "Automatic";
Seed = -1; // Случайный seed
ArtStyle = ArtStyle.Realistic;
PositivePrompt = "";
diff --git a/Source/AIImages/Services/IStableDiffusionApiService.cs b/Source/AIImages/Services/IStableDiffusionApiService.cs
index 247175c..3e6617a 100644
--- a/Source/AIImages/Services/IStableDiffusionApiService.cs
+++ b/Source/AIImages/Services/IStableDiffusionApiService.cs
@@ -28,5 +28,10 @@ namespace AIImages.Services
/// Получает список доступных сэмплеров
///
Task> GetAvailableSamplers(string apiEndpoint);
+
+ ///
+ /// Получает список доступных schedulers
+ ///
+ Task> GetAvailableSchedulers(string apiEndpoint);
}
}
diff --git a/Source/AIImages/Services/StableDiffusionApiService.cs b/Source/AIImages/Services/StableDiffusionApiService.cs
index 34a409a..3bb9373 100644
--- a/Source/AIImages/Services/StableDiffusionApiService.cs
+++ b/Source/AIImages/Services/StableDiffusionApiService.cs
@@ -50,6 +50,7 @@ namespace AIImages.Services
width = request.Width,
height = request.Height,
sampler_name = request.Sampler,
+ scheduler = request.Scheduler,
seed = request.Seed,
save_images = false,
send_images = true,
@@ -186,6 +187,38 @@ namespace AIImages.Services
}
}
+ public async Task> GetAvailableSchedulers(string apiEndpoint)
+ {
+ try
+ {
+ string endpoint = $"{apiEndpoint}/sdapi/v1/schedulers";
+ HttpResponseMessage response = await httpClient.GetAsync(endpoint);
+
+ if (!response.IsSuccessStatusCode)
+ return GetDefaultSchedulers();
+
+ string jsonResponse = await response.Content.ReadAsStringAsync();
+ var schedulers = JsonConvert.DeserializeObject>(jsonResponse);
+
+ var schedulerNames = new List();
+ if (schedulers != null)
+ {
+ foreach (var scheduler in schedulers)
+ {
+ schedulerNames.Add(scheduler.name);
+ }
+ }
+
+ Log.Message($"[AI Images] Found {schedulerNames.Count} schedulers");
+ return schedulerNames;
+ }
+ catch (Exception ex)
+ {
+ Log.Warning($"[AI Images] Failed to load schedulers: {ex.Message}");
+ return GetDefaultSchedulers();
+ }
+ }
+
private List GetDefaultSamplers()
{
return new List
@@ -212,6 +245,19 @@ namespace AIImages.Services
};
}
+ private List GetDefaultSchedulers()
+ {
+ return new List
+ {
+ "Automatic",
+ "Uniform",
+ "Karras",
+ "Exponential",
+ "Polyexponential",
+ "SGM Uniform",
+ };
+ }
+
// Вспомогательные классы для десериализации JSON ответов
#pragma warning disable S3459, S1144 // Properties set by JSON deserializer
private sealed class Txt2ImgResponse
@@ -229,6 +275,11 @@ namespace AIImages.Services
{
public string name { get; set; }
}
+
+ private sealed class SdScheduler
+ {
+ public string name { get; set; }
+ }
#pragma warning restore S3459, S1144
}
}
diff --git a/Source/AIImages/Settings/AIImagesModSettings.cs b/Source/AIImages/Settings/AIImagesModSettings.cs
index 388b62f..e081f14 100644
--- a/Source/AIImages/Settings/AIImagesModSettings.cs
+++ b/Source/AIImages/Settings/AIImagesModSettings.cs
@@ -1,3 +1,4 @@
+using System.Collections.Generic;
using AIImages.Models;
using Verse;
@@ -13,6 +14,17 @@ namespace AIImages.Settings
public string apiEndpoint = "http://127.0.0.1:7860";
public string selectedModel = "";
public string selectedSampler = "Euler a";
+ public string selectedScheduler = "Automatic";
+
+ // Кэшированные списки из API (не сохраняются)
+ [Unsaved]
+ public List availableModels = new List();
+
+ [Unsaved]
+ public List availableSamplers = new List();
+
+ [Unsaved]
+ public List availableSchedulers = new List();
// Настройки генерации
public int steps = 30;
@@ -42,6 +54,7 @@ namespace AIImages.Settings
Scribe_Values.Look(ref apiEndpoint, "apiEndpoint", "http://127.0.0.1:7860");
Scribe_Values.Look(ref selectedModel, "selectedModel", "");
Scribe_Values.Look(ref selectedSampler, "selectedSampler", "Euler a");
+ Scribe_Values.Look(ref selectedScheduler, "selectedScheduler", "Automatic");
Scribe_Values.Look(ref steps, "steps", 30);
Scribe_Values.Look(ref cfgScale, "cfgScale", 7.5f);
@@ -79,6 +92,7 @@ namespace AIImages.Settings
Width = width,
Height = height,
Sampler = selectedSampler,
+ Scheduler = selectedScheduler,
Seed = seed,
Model = selectedModel,
ArtStyle = artStyle,
diff --git a/Source/AIImages/UI/AIImagesSettingsUI.cs b/Source/AIImages/UI/AIImagesSettingsUI.cs
index 6c7cd7f..3efd7c5 100644
--- a/Source/AIImages/UI/AIImagesSettingsUI.cs
+++ b/Source/AIImages/UI/AIImagesSettingsUI.cs
@@ -21,13 +21,7 @@ namespace AIImages
public static void DoSettingsWindowContents(Rect inRect, AIImagesModSettings settings)
{
- // Инициализируем буферы при первом вызове
- if (string.IsNullOrEmpty(stepsBuffer))
- {
- stepsBuffer = settings.steps.ToString();
- widthBuffer = settings.width.ToString();
- heightBuffer = settings.height.ToString();
- }
+ InitializeBuffers(settings);
Listing_Standard listingStandard = new Listing_Standard();
Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, 1200f);
@@ -35,7 +29,31 @@ namespace AIImages
Widgets.BeginScrollView(inRect, ref scrollPosition, viewRect);
listingStandard.Begin(viewRect);
- // === API Settings ===
+ DrawApiSettings(listingStandard, settings);
+ DrawGenerationSettings(listingStandard, settings);
+ DrawSamplerSchedulerSettings(listingStandard, settings);
+ DrawPromptsSettings(listingStandard, settings);
+ DrawOptionsSettings(listingStandard, settings);
+
+ listingStandard.End();
+ Widgets.EndScrollView();
+ }
+
+ private static void InitializeBuffers(AIImagesModSettings settings)
+ {
+ if (string.IsNullOrEmpty(stepsBuffer))
+ {
+ stepsBuffer = settings.steps.ToString();
+ widthBuffer = settings.width.ToString();
+ heightBuffer = settings.height.ToString();
+ }
+ }
+
+ private static void DrawApiSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Label(
"AIImages.Settings.ApiSection".Translate(),
-1f,
@@ -47,21 +65,66 @@ namespace AIImages
settings.apiEndpoint = listingStandard.TextEntry(settings.apiEndpoint);
listingStandard.Gap(8f);
- // Кнопка проверки подключения
if (listingStandard.ButtonText("AIImages.Settings.TestConnection".Translate()))
{
_ = TestApiConnection(settings.apiEndpoint);
}
- // Кнопка загрузки моделей
if (listingStandard.ButtonText("AIImages.Settings.LoadModels".Translate()))
{
_ = LoadModelsFromApi(settings);
}
- listingStandard.Gap(12f);
+ DrawModelDropdown(listingStandard, settings);
- // === Generation Settings ===
+ if (listingStandard.ButtonText("AIImages.Settings.LoadSamplersSchedulers".Translate()))
+ {
+ _ = LoadSamplersAndSchedulers(settings);
+ }
+
+ listingStandard.Gap(12f);
+ }
+
+ private static void DrawModelDropdown(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
+ if (
+ listingStandard.ButtonTextLabeled(
+ "AIImages.Settings.Model".Translate(),
+ string.IsNullOrEmpty(settings.selectedModel)
+ ? "AIImages.Settings.NoModelSelected".Translate()
+ : settings.selectedModel
+ )
+ )
+ {
+ List modelOptions = new List();
+ if (!settings.availableModels.Any())
+ {
+ modelOptions.Add(
+ new FloatMenuOption("AIImages.Settings.LoadModelsFirst".Translate(), null)
+ );
+ }
+ else
+ {
+ foreach (string model in settings.availableModels)
+ {
+ string localModel = model;
+ modelOptions.Add(
+ new FloatMenuOption(model, () => settings.selectedModel = localModel)
+ );
+ }
+ }
+ Find.WindowStack.Add(new FloatMenu(modelOptions));
+ }
+ }
+
+ private static void DrawGenerationSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Label(
"AIImages.Settings.GenerationSection".Translate(),
-1f,
@@ -69,7 +132,28 @@ namespace AIImages
);
listingStandard.GapLine();
- // Art Style
+ DrawArtStyleDropdown(listingStandard, settings);
+ listingStandard.Gap(8f);
+
+ listingStandard.Label("AIImages.Settings.Steps".Translate() + $": {settings.steps}");
+ settings.steps = (int)listingStandard.Slider(settings.steps, 1, 150);
+ listingStandard.Gap(8f);
+
+ listingStandard.Label(
+ "AIImages.Settings.CfgScale".Translate() + $": {settings.cfgScale:F1}"
+ );
+ settings.cfgScale = listingStandard.Slider(settings.cfgScale, 1f, 30f);
+ listingStandard.Gap(8f);
+
+ DrawSizeSettings(listingStandard, settings);
+ listingStandard.Gap(12f);
+ }
+
+ private static void DrawArtStyleDropdown(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
if (
listingStandard.ButtonTextLabeled(
"AIImages.Settings.ArtStyle".Translate(),
@@ -87,22 +171,13 @@ namespace AIImages
}
Find.WindowStack.Add(new FloatMenu(styleOptions));
}
+ }
- listingStandard.Gap(8f);
-
- // Steps
- listingStandard.Label("AIImages.Settings.Steps".Translate() + $": {settings.steps}");
- settings.steps = (int)listingStandard.Slider(settings.steps, 1, 150);
- listingStandard.Gap(8f);
-
- // CFG Scale
- listingStandard.Label(
- "AIImages.Settings.CfgScale".Translate() + $": {settings.cfgScale:F1}"
- );
- settings.cfgScale = listingStandard.Slider(settings.cfgScale, 1f, 30f);
- listingStandard.Gap(8f);
-
- // Width
+ private static void DrawSizeSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Label("AIImages.Settings.Width".Translate() + ":");
widthBuffer = listingStandard.TextEntry(widthBuffer);
if (int.TryParse(widthBuffer, out int width))
@@ -110,7 +185,6 @@ namespace AIImages
settings.width = Mathf.Clamp(width, 64, 2048);
}
- // Height
listingStandard.Label("AIImages.Settings.Height".Translate() + ":");
heightBuffer = listingStandard.TextEntry(heightBuffer);
if (int.TryParse(heightBuffer, out int height))
@@ -118,41 +192,119 @@ namespace AIImages
settings.height = Mathf.Clamp(height, 64, 2048);
}
- // Common size presets
+ DrawSizePresets(listingStandard, settings);
+ }
+
+ private static void DrawSizePresets(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Gap(4f);
- Rect presetRect = listingStandard.GetRect(30f);
- if (Widgets.ButtonText(new Rect(presetRect.x, presetRect.y, 80f, 30f), "512x512"))
+ Rect presetRect1 = listingStandard.GetRect(30f);
+ DrawPresetButton(presetRect1, 0f, "512x512", 512, 512, settings);
+ DrawPresetButton(presetRect1, 85f, "512x768", 512, 768, settings);
+ DrawPresetButton(presetRect1, 170f, "768x768", 768, 768, settings);
+
+ listingStandard.Gap(4f);
+ Rect presetRect2 = listingStandard.GetRect(30f);
+ DrawPresetButton(presetRect2, 0f, "896x1152", 896, 1152, settings, 90f);
+ DrawPresetButton(presetRect2, 95f, "1024x1024", 1024, 1024, settings, 90f);
+ }
+
+ private static void DrawPresetButton(
+ Rect rect,
+ float xOffset,
+ string label,
+ int width,
+ int height,
+ AIImagesModSettings settings,
+ float buttonWidth = 80f
+ )
+ {
+ if (Widgets.ButtonText(new Rect(rect.x + xOffset, rect.y, buttonWidth, 30f), label))
{
- settings.width = 512;
- settings.height = 512;
- widthBuffer = "512";
- heightBuffer = "512";
- }
- if (Widgets.ButtonText(new Rect(presetRect.x + 85f, presetRect.y, 80f, 30f), "512x768"))
- {
- settings.width = 512;
- settings.height = 768;
- widthBuffer = "512";
- heightBuffer = "768";
+ settings.width = width;
+ settings.height = height;
+ widthBuffer = width.ToString();
+ heightBuffer = height.ToString();
}
+ }
+
+ private static void DrawSamplerSchedulerSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
+ DrawSamplerDropdown(listingStandard, settings);
+ DrawSchedulerDropdown(listingStandard, settings);
+ listingStandard.Gap(12f);
+ }
+
+ private static void DrawSamplerDropdown(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
if (
- Widgets.ButtonText(new Rect(presetRect.x + 170f, presetRect.y, 80f, 30f), "768x768")
+ listingStandard.ButtonTextLabeled(
+ "AIImages.Settings.Sampler".Translate(),
+ settings.selectedSampler
+ )
)
{
- settings.width = 768;
- settings.height = 768;
- widthBuffer = "768";
- heightBuffer = "768";
+ List samplerOptions = new List();
+ var availableSamplers = settings.availableSamplers.Any()
+ ? settings.availableSamplers
+ : new List { settings.selectedSampler };
+
+ foreach (string sampler in availableSamplers)
+ {
+ string localSampler = sampler;
+ samplerOptions.Add(
+ new FloatMenuOption(sampler, () => settings.selectedSampler = localSampler)
+ );
+ }
+ Find.WindowStack.Add(new FloatMenu(samplerOptions));
}
+ }
- listingStandard.Gap(12f);
+ private static void DrawSchedulerDropdown(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
+ if (
+ listingStandard.ButtonTextLabeled(
+ "AIImages.Settings.Scheduler".Translate(),
+ settings.selectedScheduler
+ )
+ )
+ {
+ List schedulerOptions = new List();
+ var availableSchedulers = settings.availableSchedulers.Any()
+ ? settings.availableSchedulers
+ : new List { settings.selectedScheduler };
- // Sampler
- listingStandard.Label("AIImages.Settings.Sampler".Translate() + ":");
- settings.selectedSampler = listingStandard.TextEntry(settings.selectedSampler);
- listingStandard.Gap(12f);
+ foreach (string scheduler in availableSchedulers)
+ {
+ string localScheduler = scheduler;
+ schedulerOptions.Add(
+ new FloatMenuOption(
+ scheduler,
+ () => settings.selectedScheduler = localScheduler
+ )
+ );
+ }
+ Find.WindowStack.Add(new FloatMenu(schedulerOptions));
+ }
+ }
- // === Prompts ===
+ private static void DrawPromptsSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Label(
"AIImages.Settings.PromptsSection".Translate(),
-1f,
@@ -167,8 +319,13 @@ namespace AIImages
listingStandard.Label("AIImages.Settings.BaseNegativePrompt".Translate() + ":");
settings.baseNegativePrompt = listingStandard.TextEntry(settings.baseNegativePrompt, 3);
listingStandard.Gap(12f);
+ }
- // === Options ===
+ private static void DrawOptionsSettings(
+ Listing_Standard listingStandard,
+ AIImagesModSettings settings
+ )
+ {
listingStandard.Label("AIImages.Settings.OptionsSection".Translate());
listingStandard.GapLine();
@@ -187,12 +344,8 @@ namespace AIImages
listingStandard.Gap(12f);
- // Save path
listingStandard.Label("AIImages.Settings.SavePath".Translate() + ":");
settings.savePath = listingStandard.TextEntry(settings.savePath);
-
- listingStandard.End();
- Widgets.EndScrollView();
}
private static async System.Threading.Tasks.Task TestApiConnection(string endpoint)
@@ -231,6 +384,7 @@ namespace AIImages
{
Log.Message("[AI Images] Loading models from API...");
var models = await AIImagesMod.ApiService.GetAvailableModels(settings.apiEndpoint);
+ settings.availableModels = models;
if (models.Count > 0)
{
@@ -239,8 +393,13 @@ namespace AIImages
MessageTypeDefOf.PositiveEvent
);
- // Если модель не выбрана, выбираем первую
- if (string.IsNullOrEmpty(settings.selectedModel) && models.Count > 0)
+ if (
+ (
+ string.IsNullOrEmpty(settings.selectedModel)
+ || !models.Contains(settings.selectedModel)
+ )
+ && models.Count > 0
+ )
{
settings.selectedModel = models[0];
}
@@ -261,5 +420,73 @@ namespace AIImages
);
}
}
+
+ private static async System.Threading.Tasks.Task LoadSamplersAndSchedulers(
+ AIImagesModSettings settings
+ )
+ {
+ try
+ {
+ Log.Message("[AI Images] Loading samplers and schedulers from API...");
+
+ var samplers = await AIImagesMod.ApiService.GetAvailableSamplers(
+ settings.apiEndpoint
+ );
+ settings.availableSamplers = samplers;
+
+ var schedulers = await AIImagesMod.ApiService.GetAvailableSchedulers(
+ settings.apiEndpoint
+ );
+ settings.availableSchedulers = schedulers;
+
+ int totalCount = samplers.Count + schedulers.Count;
+ if (totalCount > 0)
+ {
+ Messages.Message(
+ "AIImages.Settings.SamplersSchedulersLoaded".Translate(
+ samplers.Count,
+ schedulers.Count
+ ),
+ MessageTypeDefOf.PositiveEvent
+ );
+
+ if (
+ (
+ string.IsNullOrEmpty(settings.selectedSampler)
+ || !samplers.Contains(settings.selectedSampler)
+ )
+ && samplers.Count > 0
+ )
+ {
+ settings.selectedSampler = samplers[0];
+ }
+
+ if (
+ (
+ string.IsNullOrEmpty(settings.selectedScheduler)
+ || !schedulers.Contains(settings.selectedScheduler)
+ )
+ && schedulers.Count > 0
+ )
+ {
+ settings.selectedScheduler = schedulers[0];
+ }
+ }
+ else
+ {
+ Messages.Message(
+ "AIImages.Settings.NoSamplersSchedulersFound".Translate(),
+ MessageTypeDefOf.RejectInput
+ );
+ }
+ }
+ catch (Exception ex)
+ {
+ Messages.Message(
+ $"Error loading samplers/schedulers: {ex.Message}",
+ MessageTypeDefOf.RejectInput
+ );
+ }
+ }
}
}
diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs
index 8476fdd..0719248 100644
--- a/Source/AIImages/Window_AIImage.cs
+++ b/Source/AIImages/Window_AIImage.cs
@@ -131,6 +131,7 @@ namespace AIImages
Width = generationSettings.Width,
Height = generationSettings.Height,
Sampler = generationSettings.Sampler,
+ Scheduler = generationSettings.Scheduler,
Seed = generationSettings.Seed,
Model = AIImagesMod.Settings.apiEndpoint,
};