Compare commits

..

2 Commits

13 changed files with 476 additions and 160 deletions

Binary file not shown.

View File

@@ -45,10 +45,16 @@
<AIImages.Settings.ApiEndpoint>API Endpoint</AIImages.Settings.ApiEndpoint> <AIImages.Settings.ApiEndpoint>API Endpoint</AIImages.Settings.ApiEndpoint>
<AIImages.Settings.TestConnection>Test Connection</AIImages.Settings.TestConnection> <AIImages.Settings.TestConnection>Test Connection</AIImages.Settings.TestConnection>
<AIImages.Settings.LoadModels>Load Available Models</AIImages.Settings.LoadModels> <AIImages.Settings.LoadModels>Load Available Models</AIImages.Settings.LoadModels>
<AIImages.Settings.Model>Model</AIImages.Settings.Model>
<AIImages.Settings.NoModelSelected>No model selected</AIImages.Settings.NoModelSelected>
<AIImages.Settings.LoadModelsFirst>Load models first</AIImages.Settings.LoadModelsFirst>
<AIImages.Settings.LoadSamplersSchedulers>Load Samplers &amp; Schedulers</AIImages.Settings.LoadSamplersSchedulers>
<AIImages.Settings.ConnectionSuccess>Successfully connected to API!</AIImages.Settings.ConnectionSuccess> <AIImages.Settings.ConnectionSuccess>Successfully connected to API!</AIImages.Settings.ConnectionSuccess>
<AIImages.Settings.ConnectionFailed>Failed to connect to API. Check endpoint and ensure Stable Diffusion WebUI is running.</AIImages.Settings.ConnectionFailed> <AIImages.Settings.ConnectionFailed>Failed to connect to API. Check endpoint and ensure Stable Diffusion WebUI is running.</AIImages.Settings.ConnectionFailed>
<AIImages.Settings.ModelsLoaded>Loaded {0} models from API</AIImages.Settings.ModelsLoaded> <AIImages.Settings.ModelsLoaded>Loaded {0} models from API</AIImages.Settings.ModelsLoaded>
<AIImages.Settings.NoModelsFound>No models found. Check API connection.</AIImages.Settings.NoModelsFound> <AIImages.Settings.NoModelsFound>No models found. Check API connection.</AIImages.Settings.NoModelsFound>
<AIImages.Settings.SamplersSchedulersLoaded>Loaded {0} samplers and {1} schedulers from API</AIImages.Settings.SamplersSchedulersLoaded>
<AIImages.Settings.NoSamplersSchedulersFound>No samplers or schedulers found. Check API connection.</AIImages.Settings.NoSamplersSchedulersFound>
<AIImages.Settings.GenerationSection>Generation Settings</AIImages.Settings.GenerationSection> <AIImages.Settings.GenerationSection>Generation Settings</AIImages.Settings.GenerationSection>
<AIImages.Settings.GenerationSectionTooltip>Configure image generation parameters</AIImages.Settings.GenerationSectionTooltip> <AIImages.Settings.GenerationSectionTooltip>Configure image generation parameters</AIImages.Settings.GenerationSectionTooltip>
<AIImages.Settings.ArtStyle>Art Style</AIImages.Settings.ArtStyle> <AIImages.Settings.ArtStyle>Art Style</AIImages.Settings.ArtStyle>
@@ -58,6 +64,7 @@
<AIImages.Settings.Width>Width</AIImages.Settings.Width> <AIImages.Settings.Width>Width</AIImages.Settings.Width>
<AIImages.Settings.Height>Height</AIImages.Settings.Height> <AIImages.Settings.Height>Height</AIImages.Settings.Height>
<AIImages.Settings.Sampler>Sampler</AIImages.Settings.Sampler> <AIImages.Settings.Sampler>Sampler</AIImages.Settings.Sampler>
<AIImages.Settings.Scheduler>Schedule Type</AIImages.Settings.Scheduler>
<AIImages.Settings.PromptsSection>Prompts</AIImages.Settings.PromptsSection> <AIImages.Settings.PromptsSection>Prompts</AIImages.Settings.PromptsSection>
<AIImages.Settings.PromptsSectionTooltip>Base prompts that will be added to all generations</AIImages.Settings.PromptsSectionTooltip> <AIImages.Settings.PromptsSectionTooltip>Base prompts that will be added to all generations</AIImages.Settings.PromptsSectionTooltip>
<AIImages.Settings.BasePositivePrompt>Base Positive Prompt</AIImages.Settings.BasePositivePrompt> <AIImages.Settings.BasePositivePrompt>Base Positive Prompt</AIImages.Settings.BasePositivePrompt>

View File

@@ -45,10 +45,16 @@
<AIImages.Settings.ApiEndpoint>Адрес API</AIImages.Settings.ApiEndpoint> <AIImages.Settings.ApiEndpoint>Адрес API</AIImages.Settings.ApiEndpoint>
<AIImages.Settings.TestConnection>Проверить соединение</AIImages.Settings.TestConnection> <AIImages.Settings.TestConnection>Проверить соединение</AIImages.Settings.TestConnection>
<AIImages.Settings.LoadModels>Загрузить доступные модели</AIImages.Settings.LoadModels> <AIImages.Settings.LoadModels>Загрузить доступные модели</AIImages.Settings.LoadModels>
<AIImages.Settings.Model>Модель</AIImages.Settings.Model>
<AIImages.Settings.NoModelSelected>Модель не выбрана</AIImages.Settings.NoModelSelected>
<AIImages.Settings.LoadModelsFirst>Сначала загрузите модели</AIImages.Settings.LoadModelsFirst>
<AIImages.Settings.LoadSamplersSchedulers>Загрузить сэмплеры и планировщики</AIImages.Settings.LoadSamplersSchedulers>
<AIImages.Settings.ConnectionSuccess>Успешное подключение к API!</AIImages.Settings.ConnectionSuccess> <AIImages.Settings.ConnectionSuccess>Успешное подключение к API!</AIImages.Settings.ConnectionSuccess>
<AIImages.Settings.ConnectionFailed>Не удалось подключиться к API. Проверьте адрес и убедитесь, что Stable Diffusion WebUI запущен.</AIImages.Settings.ConnectionFailed> <AIImages.Settings.ConnectionFailed>Не удалось подключиться к API. Проверьте адрес и убедитесь, что Stable Diffusion WebUI запущен.</AIImages.Settings.ConnectionFailed>
<AIImages.Settings.ModelsLoaded>Загружено {0} моделей из API</AIImages.Settings.ModelsLoaded> <AIImages.Settings.ModelsLoaded>Загружено {0} моделей из API</AIImages.Settings.ModelsLoaded>
<AIImages.Settings.NoModelsFound>Модели не найдены. Проверьте подключение к API.</AIImages.Settings.NoModelsFound> <AIImages.Settings.NoModelsFound>Модели не найдены. Проверьте подключение к API.</AIImages.Settings.NoModelsFound>
<AIImages.Settings.SamplersSchedulersLoaded>Загружено {0} сэмплеров и {1} планировщиков из API</AIImages.Settings.SamplersSchedulersLoaded>
<AIImages.Settings.NoSamplersSchedulersFound>Сэмплеры и планировщики не найдены. Проверьте подключение к API.</AIImages.Settings.NoSamplersSchedulersFound>
<AIImages.Settings.GenerationSection>Настройки генерации</AIImages.Settings.GenerationSection> <AIImages.Settings.GenerationSection>Настройки генерации</AIImages.Settings.GenerationSection>
<AIImages.Settings.GenerationSectionTooltip>Настройка параметров генерации изображений</AIImages.Settings.GenerationSectionTooltip> <AIImages.Settings.GenerationSectionTooltip>Настройка параметров генерации изображений</AIImages.Settings.GenerationSectionTooltip>
<AIImages.Settings.ArtStyle>Художественный стиль</AIImages.Settings.ArtStyle> <AIImages.Settings.ArtStyle>Художественный стиль</AIImages.Settings.ArtStyle>
@@ -58,6 +64,7 @@
<AIImages.Settings.Width>Ширина</AIImages.Settings.Width> <AIImages.Settings.Width>Ширина</AIImages.Settings.Width>
<AIImages.Settings.Height>Высота</AIImages.Settings.Height> <AIImages.Settings.Height>Высота</AIImages.Settings.Height>
<AIImages.Settings.Sampler>Сэмплер</AIImages.Settings.Sampler> <AIImages.Settings.Sampler>Сэмплер</AIImages.Settings.Sampler>
<AIImages.Settings.Scheduler>Тип планировщика</AIImages.Settings.Scheduler>
<AIImages.Settings.PromptsSection>Промпты</AIImages.Settings.PromptsSection> <AIImages.Settings.PromptsSection>Промпты</AIImages.Settings.PromptsSection>
<AIImages.Settings.PromptsSectionTooltip>Базовые промпты, которые будут добавлены ко всем генерациям</AIImages.Settings.PromptsSectionTooltip> <AIImages.Settings.PromptsSectionTooltip>Базовые промпты, которые будут добавлены ко всем генерациям</AIImages.Settings.PromptsSectionTooltip>
<AIImages.Settings.BasePositivePrompt>Базовый позитивный промпт</AIImages.Settings.BasePositivePrompt> <AIImages.Settings.BasePositivePrompt>Базовый позитивный промпт</AIImages.Settings.BasePositivePrompt>

View File

@@ -12,6 +12,7 @@ namespace AIImages.Models
public int Width { get; set; } public int Width { get; set; }
public int Height { get; set; } public int Height { get; set; }
public string Sampler { get; set; } public string Sampler { get; set; }
public string Scheduler { get; set; }
public int Seed { get; set; } public int Seed { get; set; }
public string Model { get; set; } public string Model { get; set; }
} }

View File

@@ -16,6 +16,7 @@ namespace AIImages.Models
public string BodyType { get; set; } public string BodyType { get; set; }
public Color SkinColor { get; set; } public Color SkinColor { get; set; }
public string HairStyle { get; set; } public string HairStyle { get; set; }
public string HairDefName { get; set; }
public Color HairColor { get; set; } public Color HairColor { get; set; }
public List<Trait> Traits { get; set; } public List<Trait> Traits { get; set; }
public List<ApparelData> Apparel { get; set; } public List<ApparelData> Apparel { get; set; }
@@ -33,7 +34,9 @@ namespace AIImages.Models
public class ApparelData public class ApparelData
{ {
public string Label { get; set; } public string Label { get; set; }
public string DefName { get; set; }
public string Material { get; set; } public string Material { get; set; }
public string MaterialDefName { get; set; }
public QualityCategory? Quality { get; set; } public QualityCategory? Quality { get; set; }
public Color Color { get; set; } public Color Color { get; set; }
public string LayerType { get; set; } public string LayerType { get; set; }

View File

@@ -12,10 +12,10 @@ namespace AIImages.Models
public int Width { get; set; } public int Width { get; set; }
public int Height { get; set; } public int Height { get; set; }
public string Sampler { get; set; } public string Sampler { get; set; }
public string Scheduler { get; set; }
public int Seed { get; set; } public int Seed { get; set; }
public string Model { get; set; } public string Model { get; set; }
public ArtStyle ArtStyle { get; set; } public ArtStyle ArtStyle { get; set; }
public ShotType ShotType { get; set; }
public StableDiffusionSettings() public StableDiffusionSettings()
{ {
@@ -25,9 +25,9 @@ namespace AIImages.Models
Width = 512; Width = 512;
Height = 768; Height = 768;
Sampler = "Euler a"; Sampler = "Euler a";
Scheduler = "Automatic";
Seed = -1; // Случайный seed Seed = -1; // Случайный seed
ArtStyle = ArtStyle.Realistic; ArtStyle = ArtStyle.Realistic;
ShotType = ShotType.Portrait;
PositivePrompt = ""; PositivePrompt = "";
NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality"; NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality";
} }
@@ -38,6 +38,7 @@ namespace AIImages.Models
/// </summary> /// </summary>
public enum ArtStyle public enum ArtStyle
{ {
None, // Без стиля
Realistic, Realistic,
SemiRealistic, SemiRealistic,
Anime, Anime,
@@ -47,16 +48,4 @@ namespace AIImages.Models
Sketch, Sketch,
CellShaded, CellShaded,
} }
/// <summary>
/// Тип кадра/композиции
/// </summary>
public enum ShotType
{
Portrait, // Портрет (голова и плечи)
HalfBody, // Половина тела
FullBody, // Полное тело
CloseUp, // Крупный план
ThreeQuarter, // Три четверти
}
} }

View File

@@ -39,6 +39,7 @@ namespace AIImages.Services
string string
> >
{ {
{ ArtStyle.None, "" },
{ ArtStyle.Realistic, "photorealistic, hyperrealistic, realistic photo, photography" }, { ArtStyle.Realistic, "photorealistic, hyperrealistic, realistic photo, photography" },
{ ArtStyle.SemiRealistic, "semi-realistic, detailed illustration, realistic art" }, { ArtStyle.SemiRealistic, "semi-realistic, detailed illustration, realistic art" },
{ ArtStyle.Anime, "anime style, manga style, anime character" }, { ArtStyle.Anime, "anime style, manga style, anime character" },
@@ -52,18 +53,6 @@ namespace AIImages.Services
{ ArtStyle.CellShaded, "cell shaded, flat colors, toon shading, stylized" }, { ArtStyle.CellShaded, "cell shaded, flat colors, toon shading, stylized" },
}; };
private static readonly Dictionary<ShotType, string> ShotTypePrompts = new Dictionary<
ShotType,
string
>
{
{ ShotType.Portrait, "portrait, head and shoulders" },
{ ShotType.HalfBody, "half body shot, waist up" },
{ ShotType.FullBody, "full body, full length" },
{ ShotType.CloseUp, "close up, face focus, detailed face" },
{ ShotType.ThreeQuarter, "three-quarter view, 3/4 view" },
};
public string GeneratePositivePrompt( public string GeneratePositivePrompt(
PawnAppearanceData appearanceData, PawnAppearanceData appearanceData,
StableDiffusionSettings settings StableDiffusionSettings settings
@@ -75,18 +64,17 @@ namespace AIImages.Services
StringBuilder prompt = new StringBuilder(); StringBuilder prompt = new StringBuilder();
// 1. Художественный стиль // 1. Художественный стиль
if (ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt)) if (
ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt)
&& !string.IsNullOrEmpty(stylePrompt)
)
{ {
prompt.Append(stylePrompt); prompt.Append(stylePrompt);
prompt.Append(", "); prompt.Append(", ");
} }
// 2. Тип кадра // 2. Тип кадра - автоматически добавляем "portrait" для генерации персонажей
if (ShotTypePrompts.TryGetValue(settings.ShotType, out string shotPrompt)) prompt.Append("portrait, head and shoulders of ");
{
prompt.Append(shotPrompt);
prompt.Append(" of ");
}
// 3. Базовое описание (возраст и пол) // 3. Базовое описание (возраст и пол)
prompt.Append(GetAgeAndGenderDescription(appearanceData)); prompt.Append(GetAgeAndGenderDescription(appearanceData));
@@ -157,16 +145,18 @@ namespace AIImages.Services
); );
// Специфичные для стиля негативы // Специфичные для стиля негативы
if ( switch (settings.ArtStyle)
settings.ArtStyle == ArtStyle.Realistic
|| settings.ArtStyle == ArtStyle.SemiRealistic
)
{ {
case ArtStyle.Realistic:
case ArtStyle.SemiRealistic:
negativePrompt.Append("cartoon, anime, painting, drawing, illustration, "); negativePrompt.Append("cartoon, anime, painting, drawing, illustration, ");
} break;
else if (settings.ArtStyle == ArtStyle.Anime) case ArtStyle.Anime:
{
negativePrompt.Append("realistic, photo, photography, 3d, "); negativePrompt.Append("realistic, photo, photography, 3d, ");
break;
case ArtStyle.None:
// Без дополнительных негативных промптов для стиля None
break;
} }
// Пользовательский негативный промпт // Пользовательский негативный промпт
@@ -244,7 +234,7 @@ namespace AIImages.Services
private string GetHairDescription(PawnAppearanceData data) private string GetHairDescription(PawnAppearanceData data)
{ {
if (string.IsNullOrEmpty(data.HairStyle)) if (string.IsNullOrEmpty(data.HairDefName))
return ""; return "";
StringBuilder hair = new StringBuilder(); StringBuilder hair = new StringBuilder();
@@ -254,10 +244,8 @@ namespace AIImages.Services
hair.Append(hairColor); hair.Append(hairColor);
hair.Append(" "); hair.Append(" ");
// Стиль прически (упрощаем сложные названия) // Стиль прически - используем DefName для английского названия
string style = data string style = CleanDefName(data.HairDefName)
.HairStyle.ToLower()
.Replace("_", " ")
.Replace("shaved", "very short") .Replace("shaved", "very short")
.Replace("mohawk", "mohawk hairstyle"); .Replace("mohawk", "mohawk hairstyle");
@@ -310,15 +298,15 @@ namespace AIImages.Services
itemDesc.Append(" "); itemDesc.Append(" ");
} }
// Материал // Материал - используем DefName для английского названия
if (!string.IsNullOrEmpty(item.Material)) if (!string.IsNullOrEmpty(item.MaterialDefName))
{ {
itemDesc.Append(item.Material.ToLower()); itemDesc.Append(CleanDefName(item.MaterialDefName));
itemDesc.Append(" "); itemDesc.Append(" ");
} }
// Название предмета // Название предмета - используем DefName для английского названия
itemDesc.Append(item.Label.ToLower()); itemDesc.Append(CleanDefName(item.DefName));
items.Add(itemDesc.ToString()); items.Add(itemDesc.ToString());
} }
@@ -332,22 +320,56 @@ namespace AIImages.Services
{ {
var baseTags = "highly detailed, professional, masterpiece, best quality"; var baseTags = "highly detailed, professional, masterpiece, best quality";
if (style == ArtStyle.Realistic || style == ArtStyle.SemiRealistic) switch (style)
{ {
case ArtStyle.None:
return baseTags;
case ArtStyle.Realistic:
case ArtStyle.SemiRealistic:
return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus"; return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus";
} case ArtStyle.Anime:
else if (style == ArtStyle.Anime)
{
return $"{baseTags}, anime masterpiece, high resolution, vibrant colors"; return $"{baseTags}, anime masterpiece, high resolution, vibrant colors";
} case ArtStyle.ConceptArt:
else if (style == ArtStyle.ConceptArt)
{
return $"{baseTags}, trending on artstation, professional digital art"; return $"{baseTags}, trending on artstation, professional digital art";
} default:
else
{
return baseTags; return baseTags;
} }
} }
/// <summary>
/// Преобразует defName в читаемый английский текст для промпта
/// Пример: "Apparel_Pants" -> "pants", "Synthread" -> "synthread"
/// </summary>
private string CleanDefName(string defName)
{
if (string.IsNullOrEmpty(defName))
return "";
string cleaned = defName;
// Убираем распространенные префиксы RimWorld
cleaned = System.Text.RegularExpressions.Regex.Replace(
cleaned,
"^(Apparel_|Armor_|Weapon_|Thing_)",
"",
System.Text.RegularExpressions.RegexOptions.IgnoreCase
);
// Разделяем CamelCase на слова
cleaned = System.Text.RegularExpressions.Regex.Replace(
cleaned,
"([a-z])([A-Z])",
"$1 $2"
);
// Заменяем подчеркивания на пробелы
cleaned = cleaned.Replace("_", " ");
// Убираем множественные пробелы
cleaned = System.Text.RegularExpressions.Regex.Replace(cleaned, @"\s+", " ");
// Приводим к нижнему регистру и убираем лишние пробелы
return cleaned.Trim().ToLower();
}
} }
} }

View File

@@ -28,5 +28,10 @@ namespace AIImages.Services
/// Получает список доступных сэмплеров /// Получает список доступных сэмплеров
/// </summary> /// </summary>
Task<List<string>> GetAvailableSamplers(string apiEndpoint); Task<List<string>> GetAvailableSamplers(string apiEndpoint);
/// <summary>
/// Получает список доступных schedulers
/// </summary>
Task<List<string>> GetAvailableSchedulers(string apiEndpoint);
} }
} }

View File

@@ -25,6 +25,7 @@ namespace AIImages.Services
BodyType = pawn.story.bodyType?.defName, BodyType = pawn.story.bodyType?.defName,
SkinColor = pawn.story.SkinColor, SkinColor = pawn.story.SkinColor,
HairStyle = pawn.story.hairDef?.label, HairStyle = pawn.story.hairDef?.label,
HairDefName = pawn.story.hairDef?.defName,
HairColor = pawn.story.HairColor, HairColor = pawn.story.HairColor,
}; };
@@ -42,7 +43,9 @@ namespace AIImages.Services
var apparelData = new ApparelData var apparelData = new ApparelData
{ {
Label = apparel.def.label, Label = apparel.def.label,
DefName = apparel.def.defName,
Material = apparel.Stuff?.label, Material = apparel.Stuff?.label,
MaterialDefName = apparel.Stuff?.defName,
Color = apparel.DrawColor, Color = apparel.DrawColor,
LayerType = apparel.def.apparel?.LastLayer.ToString(), LayerType = apparel.def.apparel?.LastLayer.ToString(),
Durability = apparel.HitPoints, Durability = apparel.HitPoints,

View File

@@ -50,6 +50,7 @@ namespace AIImages.Services
width = request.Width, width = request.Width,
height = request.Height, height = request.Height,
sampler_name = request.Sampler, sampler_name = request.Sampler,
scheduler = request.Scheduler,
seed = request.Seed, seed = request.Seed,
save_images = false, save_images = false,
send_images = true, send_images = true,
@@ -186,6 +187,38 @@ namespace AIImages.Services
} }
} }
public async Task<List<string>> 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<List<SdScheduler>>(jsonResponse);
var schedulerNames = new List<string>();
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<string> GetDefaultSamplers() private List<string> GetDefaultSamplers()
{ {
return new List<string> return new List<string>
@@ -212,6 +245,19 @@ namespace AIImages.Services
}; };
} }
private List<string> GetDefaultSchedulers()
{
return new List<string>
{
"Automatic",
"Uniform",
"Karras",
"Exponential",
"Polyexponential",
"SGM Uniform",
};
}
// Вспомогательные классы для десериализации JSON ответов // Вспомогательные классы для десериализации JSON ответов
#pragma warning disable S3459, S1144 // Properties set by JSON deserializer #pragma warning disable S3459, S1144 // Properties set by JSON deserializer
private sealed class Txt2ImgResponse private sealed class Txt2ImgResponse
@@ -229,6 +275,11 @@ namespace AIImages.Services
{ {
public string name { get; set; } public string name { get; set; }
} }
private sealed class SdScheduler
{
public string name { get; set; }
}
#pragma warning restore S3459, S1144 #pragma warning restore S3459, S1144
} }
} }

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using AIImages.Models; using AIImages.Models;
using Verse; using Verse;
@@ -13,6 +14,17 @@ namespace AIImages.Settings
public string apiEndpoint = "http://127.0.0.1:7860"; public string apiEndpoint = "http://127.0.0.1:7860";
public string selectedModel = ""; public string selectedModel = "";
public string selectedSampler = "Euler a"; public string selectedSampler = "Euler a";
public string selectedScheduler = "Automatic";
// Кэшированные списки из API (не сохраняются)
[Unsaved]
public List<string> availableModels = new List<string>();
[Unsaved]
public List<string> availableSamplers = new List<string>();
[Unsaved]
public List<string> availableSchedulers = new List<string>();
// Настройки генерации // Настройки генерации
public int steps = 30; public int steps = 30;
@@ -28,7 +40,6 @@ namespace AIImages.Settings
// Художественный стиль // Художественный стиль
public ArtStyle artStyle = ArtStyle.Realistic; public ArtStyle artStyle = ArtStyle.Realistic;
public ShotType shotType = ShotType.Portrait;
// Путь для сохранения // Путь для сохранения
public string savePath = "AIImages/Generated"; public string savePath = "AIImages/Generated";
@@ -43,6 +54,7 @@ namespace AIImages.Settings
Scribe_Values.Look(ref apiEndpoint, "apiEndpoint", "http://127.0.0.1:7860"); Scribe_Values.Look(ref apiEndpoint, "apiEndpoint", "http://127.0.0.1:7860");
Scribe_Values.Look(ref selectedModel, "selectedModel", ""); Scribe_Values.Look(ref selectedModel, "selectedModel", "");
Scribe_Values.Look(ref selectedSampler, "selectedSampler", "Euler a"); 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 steps, "steps", 30);
Scribe_Values.Look(ref cfgScale, "cfgScale", 7.5f); Scribe_Values.Look(ref cfgScale, "cfgScale", 7.5f);
@@ -58,7 +70,6 @@ namespace AIImages.Settings
); );
Scribe_Values.Look(ref artStyle, "artStyle", ArtStyle.Realistic); Scribe_Values.Look(ref artStyle, "artStyle", ArtStyle.Realistic);
Scribe_Values.Look(ref shotType, "shotType", ShotType.Portrait);
Scribe_Values.Look(ref savePath, "savePath", "AIImages/Generated"); Scribe_Values.Look(ref savePath, "savePath", "AIImages/Generated");
@@ -81,10 +92,10 @@ namespace AIImages.Settings
Width = width, Width = width,
Height = height, Height = height,
Sampler = selectedSampler, Sampler = selectedSampler,
Scheduler = selectedScheduler,
Seed = seed, Seed = seed,
Model = selectedModel, Model = selectedModel,
ArtStyle = artStyle, ArtStyle = artStyle,
ShotType = shotType,
PositivePrompt = basePositivePrompt, PositivePrompt = basePositivePrompt,
NegativePrompt = baseNegativePrompt, NegativePrompt = baseNegativePrompt,
}; };

View File

@@ -21,13 +21,7 @@ namespace AIImages
public static void DoSettingsWindowContents(Rect inRect, AIImagesModSettings settings) public static void DoSettingsWindowContents(Rect inRect, AIImagesModSettings settings)
{ {
// Инициализируем буферы при первом вызове InitializeBuffers(settings);
if (string.IsNullOrEmpty(stepsBuffer))
{
stepsBuffer = settings.steps.ToString();
widthBuffer = settings.width.ToString();
heightBuffer = settings.height.ToString();
}
Listing_Standard listingStandard = new Listing_Standard(); Listing_Standard listingStandard = new Listing_Standard();
Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, 1200f); Rect viewRect = new Rect(0f, 0f, inRect.width - 20f, 1200f);
@@ -35,7 +29,31 @@ namespace AIImages
Widgets.BeginScrollView(inRect, ref scrollPosition, viewRect); Widgets.BeginScrollView(inRect, ref scrollPosition, viewRect);
listingStandard.Begin(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( listingStandard.Label(
"AIImages.Settings.ApiSection".Translate(), "AIImages.Settings.ApiSection".Translate(),
-1f, -1f,
@@ -47,21 +65,66 @@ namespace AIImages
settings.apiEndpoint = listingStandard.TextEntry(settings.apiEndpoint); settings.apiEndpoint = listingStandard.TextEntry(settings.apiEndpoint);
listingStandard.Gap(8f); listingStandard.Gap(8f);
// Кнопка проверки подключения
if (listingStandard.ButtonText("AIImages.Settings.TestConnection".Translate())) if (listingStandard.ButtonText("AIImages.Settings.TestConnection".Translate()))
{ {
_ = TestApiConnection(settings.apiEndpoint); _ = TestApiConnection(settings.apiEndpoint);
} }
// Кнопка загрузки моделей
if (listingStandard.ButtonText("AIImages.Settings.LoadModels".Translate())) if (listingStandard.ButtonText("AIImages.Settings.LoadModels".Translate()))
{ {
_ = LoadModelsFromApi(settings); _ = 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<FloatMenuOption> modelOptions = new List<FloatMenuOption>();
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( listingStandard.Label(
"AIImages.Settings.GenerationSection".Translate(), "AIImages.Settings.GenerationSection".Translate(),
-1f, -1f,
@@ -69,7 +132,28 @@ namespace AIImages
); );
listingStandard.GapLine(); 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 ( if (
listingStandard.ButtonTextLabeled( listingStandard.ButtonTextLabeled(
"AIImages.Settings.ArtStyle".Translate(), "AIImages.Settings.ArtStyle".Translate(),
@@ -87,41 +171,13 @@ namespace AIImages
} }
Find.WindowStack.Add(new FloatMenu(styleOptions)); Find.WindowStack.Add(new FloatMenu(styleOptions));
} }
// Shot Type
if (
listingStandard.ButtonTextLabeled(
"AIImages.Settings.ShotType".Translate(),
settings.shotType.ToString()
)
)
{
List<FloatMenuOption> shotOptions = new List<FloatMenuOption>();
foreach (ShotType shot in Enum.GetValues(typeof(ShotType)))
{
ShotType localShot = shot;
shotOptions.Add(
new FloatMenuOption(shot.ToString(), () => settings.shotType = localShot)
);
}
Find.WindowStack.Add(new FloatMenu(shotOptions));
} }
listingStandard.Gap(8f); private static void DrawSizeSettings(
Listing_Standard listingStandard,
// Steps AIImagesModSettings settings
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
listingStandard.Label("AIImages.Settings.Width".Translate() + ":"); listingStandard.Label("AIImages.Settings.Width".Translate() + ":");
widthBuffer = listingStandard.TextEntry(widthBuffer); widthBuffer = listingStandard.TextEntry(widthBuffer);
if (int.TryParse(widthBuffer, out int width)) if (int.TryParse(widthBuffer, out int width))
@@ -129,7 +185,6 @@ namespace AIImages
settings.width = Mathf.Clamp(width, 64, 2048); settings.width = Mathf.Clamp(width, 64, 2048);
} }
// Height
listingStandard.Label("AIImages.Settings.Height".Translate() + ":"); listingStandard.Label("AIImages.Settings.Height".Translate() + ":");
heightBuffer = listingStandard.TextEntry(heightBuffer); heightBuffer = listingStandard.TextEntry(heightBuffer);
if (int.TryParse(heightBuffer, out int height)) if (int.TryParse(heightBuffer, out int height))
@@ -137,41 +192,119 @@ namespace AIImages
settings.height = Mathf.Clamp(height, 64, 2048); settings.height = Mathf.Clamp(height, 64, 2048);
} }
// Common size presets DrawSizePresets(listingStandard, settings);
listingStandard.Gap(4f);
Rect presetRect = listingStandard.GetRect(30f);
if (Widgets.ButtonText(new Rect(presetRect.x, presetRect.y, 80f, 30f), "512x512"))
{
settings.width = 512;
settings.height = 512;
widthBuffer = "512";
heightBuffer = "512";
} }
if (Widgets.ButtonText(new Rect(presetRect.x + 85f, presetRect.y, 80f, 30f), "512x768"))
{ private static void DrawSizePresets(
settings.width = 512; Listing_Standard listingStandard,
settings.height = 768; AIImagesModSettings settings
widthBuffer = "512";
heightBuffer = "768";
}
if (
Widgets.ButtonText(new Rect(presetRect.x + 170f, presetRect.y, 80f, 30f), "768x768")
) )
{ {
settings.width = 768; listingStandard.Gap(4f);
settings.height = 768; Rect presetRect1 = listingStandard.GetRect(30f);
widthBuffer = "768"; DrawPresetButton(presetRect1, 0f, "512x512", 512, 512, settings);
heightBuffer = "768"; 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);
} }
listingStandard.Gap(12f); 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();
}
}
// Sampler private static void DrawSamplerSchedulerSettings(
listingStandard.Label("AIImages.Settings.Sampler".Translate() + ":"); Listing_Standard listingStandard,
settings.selectedSampler = listingStandard.TextEntry(settings.selectedSampler); AIImagesModSettings settings
)
{
DrawSamplerDropdown(listingStandard, settings);
DrawSchedulerDropdown(listingStandard, settings);
listingStandard.Gap(12f); listingStandard.Gap(12f);
}
// === Prompts === private static void DrawSamplerDropdown(
Listing_Standard listingStandard,
AIImagesModSettings settings
)
{
if (
listingStandard.ButtonTextLabeled(
"AIImages.Settings.Sampler".Translate(),
settings.selectedSampler
)
)
{
List<FloatMenuOption> samplerOptions = new List<FloatMenuOption>();
var availableSamplers = settings.availableSamplers.Any()
? settings.availableSamplers
: new List<string> { 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<FloatMenuOption> schedulerOptions = new List<FloatMenuOption>();
var availableSchedulers = settings.availableSchedulers.Any()
? settings.availableSchedulers
: new List<string> { 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( listingStandard.Label(
"AIImages.Settings.PromptsSection".Translate(), "AIImages.Settings.PromptsSection".Translate(),
-1f, -1f,
@@ -186,8 +319,13 @@ namespace AIImages
listingStandard.Label("AIImages.Settings.BaseNegativePrompt".Translate() + ":"); listingStandard.Label("AIImages.Settings.BaseNegativePrompt".Translate() + ":");
settings.baseNegativePrompt = listingStandard.TextEntry(settings.baseNegativePrompt, 3); settings.baseNegativePrompt = listingStandard.TextEntry(settings.baseNegativePrompt, 3);
listingStandard.Gap(12f); listingStandard.Gap(12f);
}
// === Options === private static void DrawOptionsSettings(
Listing_Standard listingStandard,
AIImagesModSettings settings
)
{
listingStandard.Label("AIImages.Settings.OptionsSection".Translate()); listingStandard.Label("AIImages.Settings.OptionsSection".Translate());
listingStandard.GapLine(); listingStandard.GapLine();
@@ -206,12 +344,8 @@ namespace AIImages
listingStandard.Gap(12f); listingStandard.Gap(12f);
// Save path
listingStandard.Label("AIImages.Settings.SavePath".Translate() + ":"); listingStandard.Label("AIImages.Settings.SavePath".Translate() + ":");
settings.savePath = listingStandard.TextEntry(settings.savePath); settings.savePath = listingStandard.TextEntry(settings.savePath);
listingStandard.End();
Widgets.EndScrollView();
} }
private static async System.Threading.Tasks.Task TestApiConnection(string endpoint) private static async System.Threading.Tasks.Task TestApiConnection(string endpoint)
@@ -250,6 +384,7 @@ namespace AIImages
{ {
Log.Message("[AI Images] Loading models from API..."); Log.Message("[AI Images] Loading models from API...");
var models = await AIImagesMod.ApiService.GetAvailableModels(settings.apiEndpoint); var models = await AIImagesMod.ApiService.GetAvailableModels(settings.apiEndpoint);
settings.availableModels = models;
if (models.Count > 0) if (models.Count > 0)
{ {
@@ -258,8 +393,13 @@ namespace AIImages
MessageTypeDefOf.PositiveEvent MessageTypeDefOf.PositiveEvent
); );
// Если модель не выбрана, выбираем первую if (
if (string.IsNullOrEmpty(settings.selectedModel) && models.Count > 0) (
string.IsNullOrEmpty(settings.selectedModel)
|| !models.Contains(settings.selectedModel)
)
&& models.Count > 0
)
{ {
settings.selectedModel = models[0]; settings.selectedModel = models[0];
} }
@@ -280,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
);
}
}
} }
} }

View File

@@ -131,6 +131,7 @@ namespace AIImages
Width = generationSettings.Width, Width = generationSettings.Width,
Height = generationSettings.Height, Height = generationSettings.Height,
Sampler = generationSettings.Sampler, Sampler = generationSettings.Sampler,
Scheduler = generationSettings.Scheduler,
Seed = generationSettings.Seed, Seed = generationSettings.Seed,
Model = AIImagesMod.Settings.apiEndpoint, Model = AIImagesMod.Settings.apiEndpoint,
}; };
@@ -269,8 +270,8 @@ namespace AIImages
{ {
float imageSize = Mathf.Min(rect.width, 400f); float imageSize = Mathf.Min(rect.width, 400f);
Rect imageRect = new Rect( Rect imageRect = new Rect(
(rect.width - imageSize) / 2f, rect.x + (rect.width - imageSize) / 2f,
curY, rect.y + curY,
imageSize, imageSize,
imageSize imageSize
); );
@@ -282,8 +283,8 @@ namespace AIImages
// Placeholder для изображения // Placeholder для изображения
float placeholderSize = Mathf.Min(rect.width, 300f); float placeholderSize = Mathf.Min(rect.width, 300f);
Rect placeholderRect = new Rect( Rect placeholderRect = new Rect(
(rect.width - placeholderSize) / 2f, rect.x + (rect.width - placeholderSize) / 2f,
curY, rect.y + curY,
placeholderSize, placeholderSize,
placeholderSize placeholderSize
); );
@@ -299,7 +300,10 @@ namespace AIImages
{ {
Text.Font = GameFont.Small; Text.Font = GameFont.Small;
float statusHeight = Text.CalcHeight(generationStatus, rect.width); float statusHeight = Text.CalcHeight(generationStatus, rect.width);
Widgets.Label(new Rect(0f, curY, rect.width, statusHeight), generationStatus); Widgets.Label(
new Rect(rect.x, rect.y + curY, rect.width, statusHeight),
generationStatus
);
curY += statusHeight + 10f; curY += statusHeight + 10f;
} }
@@ -307,7 +311,7 @@ namespace AIImages
Text.Font = GameFont.Small; Text.Font = GameFont.Small;
if ( if (
Widgets.ButtonText( Widgets.ButtonText(
new Rect(0f, curY, rect.width, 35f), new Rect(rect.x, rect.y + curY, rect.width, 35f),
isGenerating isGenerating
? "AIImages.Generation.Generating".Translate() ? "AIImages.Generation.Generating".Translate()
: "AIImages.Generation.Generate".Translate() : "AIImages.Generation.Generate".Translate()
@@ -321,7 +325,7 @@ namespace AIImages
// Промпт секция // Промпт секция
Text.Font = GameFont.Medium; Text.Font = GameFont.Medium;
Widgets.Label( Widgets.Label(
new Rect(0f, curY, rect.width, 30f), new Rect(rect.x, rect.y + curY, rect.width, 30f),
"AIImages.Prompt.SectionTitle".Translate() "AIImages.Prompt.SectionTitle".Translate()
); );
curY += 35f; curY += 35f;
@@ -334,7 +338,7 @@ namespace AIImages
); );
float promptHeight = Mathf.Min(Text.CalcHeight(promptText, rect.width), 150f); float promptHeight = Mathf.Min(Text.CalcHeight(promptText, rect.width), 150f);
Rect promptRect = new Rect(0f, curY, rect.width, promptHeight); Rect promptRect = new Rect(rect.x, rect.y + curY, rect.width, promptHeight);
// Рисуем промпт в скроллируемой области если он длинный // Рисуем промпт в скроллируемой области если он длинный
Widgets.DrawBoxSolid(promptRect, new Color(0.1f, 0.1f, 0.1f, 0.5f)); Widgets.DrawBoxSolid(promptRect, new Color(0.1f, 0.1f, 0.1f, 0.5f));
@@ -344,7 +348,7 @@ namespace AIImages
// Кнопка копирования промпта // Кнопка копирования промпта
if ( if (
Widgets.ButtonText( Widgets.ButtonText(
new Rect(0f, curY, rect.width / 2f - 5f, 30f), new Rect(rect.x, rect.y + curY, rect.width / 2f - 5f, 30f),
"AIImages.Prompt.CopyButton".Translate() "AIImages.Prompt.CopyButton".Translate()
) )
) )
@@ -356,7 +360,12 @@ namespace AIImages
// Кнопка обновления данных // Кнопка обновления данных
if ( if (
Widgets.ButtonText( Widgets.ButtonText(
new Rect(rect.width / 2f + 5f, curY, rect.width / 2f - 5f, 30f), new Rect(
rect.x + rect.width / 2f + 5f,
rect.y + curY,
rect.width / 2f - 5f,
30f
),
"AIImages.Window.Refresh".Translate() "AIImages.Window.Refresh".Translate()
) )
) )
@@ -370,7 +379,7 @@ namespace AIImages
curY += 35f; curY += 35f;
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f);
Widgets.Label( Widgets.Label(
new Rect(0f, curY, rect.width, 25f), new Rect(rect.x, rect.y + curY, rect.width, 25f),
"AIImages.Prompt.Copied".Translate() "AIImages.Prompt.Copied".Translate()
); );
GUI.color = Color.white; GUI.color = Color.white;