diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll
index 0bda9d2..1afd608 100644
Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ
diff --git a/Assemblies/StableDiffusionNet.Core.dll b/Assemblies/StableDiffusionNet.Core.dll
new file mode 100644
index 0000000..b44c8c0
Binary files /dev/null and b/Assemblies/StableDiffusionNet.Core.dll differ
diff --git a/Source/AIImages/AIImages.csproj b/Source/AIImages/AIImages.csproj
index 881a33e..eec9089 100644
--- a/Source/AIImages/AIImages.csproj
+++ b/Source/AIImages/AIImages.csproj
@@ -18,6 +18,6 @@
-
+
diff --git a/Source/AIImages/Services/StableDiffusionNetAdapter.cs b/Source/AIImages/Services/StableDiffusionNetAdapter.cs
index 413c424..3a89d04 100644
--- a/Source/AIImages/Services/StableDiffusionNetAdapter.cs
+++ b/Source/AIImages/Services/StableDiffusionNetAdapter.cs
@@ -1,31 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AIImages.Models;
-using Newtonsoft.Json;
+using StableDiffusionNet;
+using StableDiffusionNet.Exceptions;
+using StableDiffusionNet.Interfaces;
+using StableDiffusionNet.Models.Requests;
using Verse;
namespace AIImages.Services
{
///
/// Адаптер для Stable Diffusion API (AUTOMATIC1111 WebUI)
- /// TODO: В будущем можно мигрировать на библиотеку StableDiffusionNet когда API будет полностью совместимо
+ /// Использует библиотеку StableDiffusionNet для работы с API
///
public class StableDiffusionNetAdapter : IStableDiffusionApiService, IDisposable
{
- // Shared HttpClient для предотвращения socket exhaustion
- // См: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines
- private static readonly HttpClient _sharedHttpClient = new HttpClient
- {
- Timeout = TimeSpan.FromMinutes(5),
- };
-
- private readonly string _apiEndpoint;
+ private readonly IStableDiffusionClient _client;
private readonly string _saveFolderPath;
private bool _disposed;
@@ -39,8 +33,6 @@ namespace AIImages.Services
);
}
- _apiEndpoint = apiEndpoint;
-
// Определяем путь для сохранения
_saveFolderPath = Path.Combine(GenFilePaths.SaveDataFolderPath, savePath);
@@ -50,6 +42,13 @@ namespace AIImages.Services
Directory.CreateDirectory(_saveFolderPath);
}
+ // Создаем клиент StableDiffusion используя Builder
+ _client = new StableDiffusionClientBuilder()
+ .WithBaseUrl(apiEndpoint)
+ .WithTimeout(300) // 5 минут в секундах
+ .WithRetry(retryCount: 3, retryDelayMilliseconds: 1000)
+ .Build();
+
Log.Message(
$"[AI Images] StableDiffusion adapter initialized with endpoint: {apiEndpoint}"
);
@@ -73,52 +72,34 @@ namespace AIImages.Services
$"[AI Images] Starting image generation with prompt: {request.Prompt.Substring(0, Math.Min(50, request.Prompt.Length))}..."
);
- // Формируем JSON запрос для AUTOMATIC1111 API
- var apiRequest = new
+ // Маппируем наш запрос на запрос библиотеки StableDiffusionNet
+ var sdRequest = new TextToImageRequest
{
- prompt = request.Prompt,
- negative_prompt = request.NegativePrompt,
- steps = request.Steps,
- cfg_scale = request.CfgScale,
- width = request.Width,
- height = request.Height,
- sampler_name = request.Sampler,
- scheduler = request.Scheduler,
- seed = request.Seed,
- save_images = false,
- send_images = true,
+ Prompt = request.Prompt,
+ NegativePrompt = request.NegativePrompt,
+ Steps = request.Steps,
+ CfgScale = request.CfgScale,
+ Width = request.Width,
+ Height = request.Height,
+ SamplerName = request.Sampler,
+ Scheduler = request.Scheduler,
+ Seed = request.Seed,
+ // SaveImages и SendImages не нужны - библиотека всегда возвращает изображения
};
- string jsonRequest = JsonConvert.SerializeObject(apiRequest);
- var content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
-
- // Отправляем запрос с поддержкой cancellation
- string endpoint = $"{_apiEndpoint}/sdapi/v1/txt2img";
- HttpResponseMessage response = await _sharedHttpClient.PostAsync(
- endpoint,
- content,
+ // Выполняем запрос через библиотеку (с встроенной retry логикой)
+ var response = await _client.TextToImage.GenerateAsync(
+ sdRequest,
cancellationToken
);
- if (!response.IsSuccessStatusCode)
- {
- string errorContent = await response.Content.ReadAsStringAsync();
- Log.Error(
- $"[AI Images] API request failed: {response.StatusCode} - {errorContent}"
- );
- return GenerationResult.Failure($"API Error: {response.StatusCode}");
- }
-
- string jsonResponse = await response.Content.ReadAsStringAsync();
- var apiResponse = JsonConvert.DeserializeObject(jsonResponse);
-
- if (apiResponse?.images == null || apiResponse.images.Length == 0)
+ if (response?.Images == null || response.Images.Count == 0)
{
return GenerationResult.Failure("No images returned from API");
}
// Декодируем изображение из base64
- byte[] imageData = Convert.FromBase64String(apiResponse.images[0]);
+ byte[] imageData = Convert.FromBase64String(response.Images[0]);
// Сохраняем изображение
string fileName = $"pawn_{DateTime.Now:yyyyMMdd_HHmmss}.png";
@@ -144,6 +125,11 @@ namespace AIImages.Services
Log.Error($"[AI Images] HTTP error: {ex.Message}");
return GenerationResult.Failure($"Connection error: {ex.Message}");
}
+ catch (StableDiffusionException ex)
+ {
+ Log.Error($"[AI Images] StableDiffusion API error: {ex.Message}");
+ return GenerationResult.Failure($"API Error: {ex.Message}");
+ }
catch (Exception ex)
{
Log.Error($"[AI Images] Unexpected error: {ex.Message}\n{ex.StackTrace}");
@@ -160,12 +146,8 @@ namespace AIImages.Services
try
{
- string endpoint = $"{apiEndpoint}/sdapi/v1/sd-models";
- HttpResponseMessage response = await _sharedHttpClient.GetAsync(
- endpoint,
- cancellationToken
- );
- return response.IsSuccessStatusCode;
+ // Используем встроенный метод PingAsync библиотеки
+ return await _client.PingAsync(cancellationToken);
}
catch (Exception ex)
{
@@ -183,24 +165,16 @@ namespace AIImages.Services
try
{
- string endpoint = $"{apiEndpoint}/sdapi/v1/sd-models";
- HttpResponseMessage response = await _sharedHttpClient.GetAsync(
- endpoint,
- cancellationToken
- );
-
- if (!response.IsSuccessStatusCode)
- return new List();
-
- string jsonResponse = await response.Content.ReadAsStringAsync();
- var models = JsonConvert.DeserializeObject>(jsonResponse);
+ // Используем Models сервис библиотеки
+ var models = await _client.Models.GetModelsAsync(cancellationToken);
var modelNames = new List();
if (models != null)
{
foreach (var model in models)
{
- modelNames.Add(model.title ?? model.model_name);
+ // Используем Title или ModelName в зависимости от того, что доступно
+ modelNames.Add(model.Title ?? model.ModelName);
}
}
@@ -223,25 +197,13 @@ namespace AIImages.Services
try
{
- string endpoint = $"{apiEndpoint}/sdapi/v1/samplers";
- HttpResponseMessage response = await _sharedHttpClient.GetAsync(
- endpoint,
- cancellationToken
- );
-
- if (!response.IsSuccessStatusCode)
- return GetDefaultSamplers();
-
- string jsonResponse = await response.Content.ReadAsStringAsync();
- var samplers = JsonConvert.DeserializeObject>(jsonResponse);
+ // Используем Samplers сервис библиотеки
+ var samplers = await _client.Samplers.GetSamplersAsync(cancellationToken);
var samplerNames = new List();
if (samplers != null)
{
- foreach (var sampler in samplers)
- {
- samplerNames.Add(sampler.name);
- }
+ samplerNames.AddRange(samplers);
}
Log.Message($"[AI Images] Found {samplerNames.Count} samplers");
@@ -263,25 +225,13 @@ namespace AIImages.Services
try
{
- string endpoint = $"{apiEndpoint}/sdapi/v1/schedulers";
- HttpResponseMessage response = await _sharedHttpClient.GetAsync(
- endpoint,
- cancellationToken
- );
-
- if (!response.IsSuccessStatusCode)
- return GetDefaultSchedulers();
-
- string jsonResponse = await response.Content.ReadAsStringAsync();
- var schedulers = JsonConvert.DeserializeObject>(jsonResponse);
+ // Используем Schedulers сервис библиотеки (доступен с версии 1.1.1)
+ var schedulers = await _client.Schedulers.GetSchedulersAsync(cancellationToken);
var schedulerNames = new List();
if (schedulers != null)
{
- foreach (var scheduler in schedulers)
- {
- schedulerNames.Add(scheduler.name);
- }
+ schedulerNames.AddRange(schedulers);
}
Log.Message($"[AI Images] Found {schedulerNames.Count} schedulers");
@@ -346,32 +296,13 @@ namespace AIImages.Services
if (_disposed)
return;
- // Не dispose shared HttpClient - он используется глобально
+ // Dispose клиента StableDiffusion
+ if (_client is IDisposable disposableClient)
+ {
+ disposableClient.Dispose();
+ }
+
_disposed = true;
}
-
- // Вспомогательные классы для десериализации JSON ответов
-#pragma warning disable S3459, S1144, IDE1006 // Properties set by JSON deserializer
- private sealed class Txt2ImgResponse
- {
- public string[] images { get; set; }
- }
-
- private sealed class SdModel
- {
- public string title { get; set; }
- public string model_name { get; set; }
- }
-
- private sealed class SdSampler
- {
- public string name { get; set; }
- }
-
- private sealed class SdScheduler
- {
- public string name { get; set; }
- }
-#pragma warning restore S3459, S1144, IDE1006
}
}