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, 1250f); 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 ) { // Получаем текущий стиль var currentStyleDef = DefDatabase.GetNamedSilentFail( settings.artStyleDefName ); string currentStyleLabel = currentStyleDef?.label ?? settings.artStyleDefName; if ( listingStandard.ButtonTextLabeled( "AIImages.Settings.ArtStyle".Translate(), currentStyleLabel ) ) { List styleOptions = new List(); // Получаем все стили из DefDatabase и сортируем по sortOrder var allStyles = DefDatabase.AllDefs.OrderBy(s => s.sortOrder); foreach (var styleDef in allStyles) { string localDefName = styleDef.defName; string localLabel = styleDef.label; styleOptions.Add( new FloatMenuOption( localLabel, () => settings.artStyleDefName = localDefName ) ); } 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 ) { // Получаем все предустановки размеров из DefDatabase var allPresets = DefDatabase .AllDefs.OrderBy(p => p.sortOrder) .ToList(); if (!allPresets.Any()) { return; } listingStandard.Gap(4f); // Разбиваем на строки по 3 кнопки int buttonsPerRow = 3; float buttonWidth = 80f; float spacing = 5f; for (int i = 0; i < allPresets.Count; i += buttonsPerRow) { Rect rowRect = listingStandard.GetRect(30f); for (int j = 0; j < buttonsPerRow && (i + j) < allPresets.Count; j++) { var preset = allPresets[i + j]; float xOffset = j * (buttonWidth + spacing); if ( Widgets.ButtonText( new Rect(rowRect.x + xOffset, rowRect.y, buttonWidth, 30f), preset.label ) ) { settings.width = preset.width; settings.height = preset.height; widthBuffer = preset.width.ToString(); heightBuffer = preset.height.ToString(); } } listingStandard.Gap(4f); } } 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.CheckboxLabeled( "AIImages.Settings.SaveImagesToServer".Translate(), ref settings.saveImagesToServer, "AIImages.Settings.SaveImagesToServerTooltip".Translate() ); listingStandard.CheckboxLabeled( "AIImages.Settings.EnableDebugLogs".Translate(), ref settings.enableDebugLogs, "AIImages.Settings.EnableDebugLogsTooltip".Translate() ); 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 ) ); } } }