using System; using System.Collections.Generic; using System.Linq; using AIImages.Helpers; using AIImages.Models; using AIImages.Services; using AIImages.Settings; using RimWorld; using UnityEngine; using Verse; namespace AIImages { /// /// UI для настроек мода в меню настроек RimWorld /// public static class AIImagesSettingsUI { private static Vector2 scrollPosition = Vector2.zero; private static string stepsBuffer; private static string widthBuffer; private static string heightBuffer; public static void DoSettingsWindowContents( Rect inRect, AIImagesModSettings settings, ServiceContainer serviceContainer ) { InitializeBuffers(settings); Listing_Standard listingStandard = new Listing_Standard(); Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, 1200f); Widgets.BeginScrollView(inRect, ref scrollPosition, viewRect); listingStandard.Begin(viewRect); DrawApiSettings(listingStandard, settings, serviceContainer); 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, ServiceContainer serviceContainer ) { listingStandard.Label( "AIImages.Settings.ApiSection".Translate(), -1f, "AIImages.Settings.ApiSectionTooltip".Translate() ); listingStandard.GapLine(); string oldEndpoint = settings.apiEndpoint; listingStandard.Label("AIImages.Settings.ApiEndpoint".Translate() + ":"); settings.apiEndpoint = listingStandard.TextEntry(settings.apiEndpoint); listingStandard.Gap(8f); // Если endpoint изменился, пересоздаем API сервис if (oldEndpoint != settings.apiEndpoint) { try { serviceContainer.RecreateApiService(); } catch (Exception ex) { Log.Error($"[AI Images] Failed to recreate API service: {ex.Message}"); Messages.Message( "Failed to update API endpoint. Check the log.", MessageTypeDefOf.RejectInput ); } } if (listingStandard.ButtonText("AIImages.Settings.TestConnection".Translate())) { TestApiConnection(serviceContainer.ApiService, settings.apiEndpoint); } if (listingStandard.ButtonText("AIImages.Settings.LoadFromApi".Translate())) { LoadAllFromApi(serviceContainer.ApiService, settings); } DrawModelDropdown(listingStandard, 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, "AIImages.Settings.GenerationSectionTooltip".Translate() ); listingStandard.GapLine(); 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(), settings.artStyle.ToString() ) ) { List styleOptions = new List(); foreach (ArtStyle style in Enum.GetValues(typeof(ArtStyle))) { ArtStyle localStyle = style; styleOptions.Add( new FloatMenuOption(style.ToString(), () => settings.artStyle = localStyle) ); } Find.WindowStack.Add(new FloatMenu(styleOptions)); } } 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)) { settings.width = Mathf.Clamp(width, 64, 2048); } listingStandard.Label("AIImages.Settings.Height".Translate() + ":"); heightBuffer = listingStandard.TextEntry(heightBuffer); if (int.TryParse(heightBuffer, out int height)) { settings.height = Mathf.Clamp(height, 64, 2048); } DrawSizePresets(listingStandard, settings); } private static void DrawSizePresets( Listing_Standard listingStandard, AIImagesModSettings settings ) { listingStandard.Gap(4f); 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 = 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 ( listingStandard.ButtonTextLabeled( "AIImages.Settings.Sampler".Translate(), settings.selectedSampler ) ) { 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)); } } 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 }; foreach (string scheduler in availableSchedulers) { string localScheduler = scheduler; schedulerOptions.Add( new FloatMenuOption( scheduler, () => settings.selectedScheduler = localScheduler ) ); } Find.WindowStack.Add(new FloatMenu(schedulerOptions)); } } private static void DrawPromptsSettings( Listing_Standard listingStandard, AIImagesModSettings settings ) { listingStandard.Label( "AIImages.Settings.PromptsSection".Translate(), -1f, "AIImages.Settings.PromptsSectionTooltip".Translate() ); listingStandard.GapLine(); listingStandard.Label("AIImages.Settings.BasePositivePrompt".Translate() + ":"); settings.basePositivePrompt = listingStandard.TextEntry(settings.basePositivePrompt, 3); listingStandard.Gap(8f); listingStandard.Label("AIImages.Settings.BaseNegativePrompt".Translate() + ":"); settings.baseNegativePrompt = listingStandard.TextEntry(settings.baseNegativePrompt, 3); listingStandard.Gap(12f); } private static void DrawOptionsSettings( Listing_Standard listingStandard, AIImagesModSettings settings ) { listingStandard.Label("AIImages.Settings.OptionsSection".Translate()); listingStandard.GapLine(); listingStandard.CheckboxLabeled( "AIImages.Settings.AutoLoadModels".Translate(), ref settings.autoLoadModels ); listingStandard.CheckboxLabeled( "AIImages.Settings.ShowTechnicalInfo".Translate(), ref settings.showTechnicalInfo ); listingStandard.CheckboxLabeled( "AIImages.Settings.SaveHistory".Translate(), ref settings.saveGenerationHistory ); listingStandard.Gap(12f); listingStandard.Label("AIImages.Settings.SavePath".Translate() + ":"); settings.savePath = listingStandard.TextEntry(settings.savePath); listingStandard.Gap(12f); // Кнопка очистки всех изображений if (listingStandard.ButtonText("AIImages.Settings.ClearAllImages".Translate())) { ClearAllGeneratedImages(settings); } } private static void TestApiConnection( IStableDiffusionApiService apiService, string endpoint ) { _ = AsyncHelper.RunAsync( async () => { Log.Message($"[AI Images] Testing connection to {endpoint}..."); bool available = await apiService.CheckApiAvailability(endpoint); if (available) { Messages.Message( "AIImages.Settings.ConnectionSuccess".Translate(), MessageTypeDefOf.PositiveEvent ); } else { Messages.Message( "AIImages.Settings.ConnectionFailed".Translate(), MessageTypeDefOf.RejectInput ); } }, "API Connection Test" ); } private static void LoadAllFromApi( IStableDiffusionApiService apiService, AIImagesModSettings settings ) { _ = AsyncHelper.RunAsync( async () => { Log.Message("[AI Images] Loading models, samplers and schedulers from API..."); // Загружаем модели var models = await apiService.GetAvailableModels(settings.apiEndpoint); settings.availableModels = models; // Загружаем семплеры var samplers = await apiService.GetAvailableSamplers(settings.apiEndpoint); settings.availableSamplers = samplers; // Загружаем schedulers var schedulers = await apiService.GetAvailableSchedulers(settings.apiEndpoint); settings.availableSchedulers = schedulers; int totalCount = models.Count + samplers.Count + schedulers.Count; if (totalCount > 0) { ShowSuccessMessage(models.Count, samplers.Count, schedulers.Count); AutoSelectDefaults(settings, models, samplers, schedulers); } else { Messages.Message( "AIImages.Settings.NothingLoaded".Translate(), MessageTypeDefOf.RejectInput ); } }, "Load API Data" ); } private static void ShowSuccessMessage(int modelCount, int samplerCount, int schedulerCount) { Messages.Message( "AIImages.Settings.AllLoaded".Translate(modelCount, samplerCount, schedulerCount), MessageTypeDefOf.PositiveEvent ); } private static void AutoSelectDefaults( AIImagesModSettings settings, List models, List samplers, List schedulers ) { AutoSelectIfNeeded(ref settings.selectedModel, models, settings.selectedModel); AutoSelectIfNeeded(ref settings.selectedSampler, samplers, settings.selectedSampler); AutoSelectIfNeeded( ref settings.selectedScheduler, schedulers, settings.selectedScheduler ); } private static void AutoSelectIfNeeded( ref string selectedValue, List availableValues, string currentValue ) { bool needsSelection = string.IsNullOrEmpty(currentValue) || !availableValues.Contains(currentValue); if (needsSelection && availableValues.Count > 0) { selectedValue = availableValues[0]; } } private static void ClearAllGeneratedImages(AIImagesModSettings settings) { Find.WindowStack.Add( Dialog_MessageBox.CreateConfirmation( "AIImages.Settings.ClearAllImagesConfirm".Translate(), delegate { int deletedCount = PawnPortraitHelper.ClearAllPortraits(settings.savePath); Messages.Message( "AIImages.Settings.ClearAllImagesSuccess".Translate(deletedCount), MessageTypeDefOf.PositiveEvent ); }, destructive: true ) ); } } }