Enhance SwarmUI integration by adding swarm connection status to the API and updating client-side components to reflect this status. Improved localization strings for various SwarmUI states and refactored ApplicantsDialog and PersonCard components to utilize the new connection information, ensuring a more responsive user experience.
This commit is contained in:
@@ -28,6 +28,33 @@ internal sealed class SwarmUiClient
|
||||
|
||||
public bool IsConfigured => !string.IsNullOrWhiteSpace(_options.BaseUrl);
|
||||
|
||||
/// <summary>Opens a session to verify SwarmUI responds; does not generate an image.</summary>
|
||||
public async Task<bool> ProbeAsync(TimeSpan timeout, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsConfigured)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
using var timeoutSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
timeoutSource.CancelAfter(timeout);
|
||||
|
||||
try
|
||||
{
|
||||
await RefreshSessionAsync(timeoutSource.Token);
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "SwarmUI probe failed.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<byte[]> GenerateAsync(
|
||||
string prompt,
|
||||
string negativePrompt,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
namespace HSchool.Server.Game;
|
||||
|
||||
/// <summary>Caches a lightweight SwarmUI reachability probe so /api/status stays cheap.</summary>
|
||||
internal sealed class SwarmUiHealthService(SwarmUiClient client)
|
||||
{
|
||||
private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(15);
|
||||
private static readonly TimeSpan ProbeTimeout = TimeSpan.FromSeconds(5);
|
||||
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
private bool? _lastConnected;
|
||||
private DateTime _lastCheckUtc = DateTime.MinValue;
|
||||
|
||||
/// <summary><c>null</c> when SwarmUI is not configured.</summary>
|
||||
public async Task<bool?> GetConnectedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!client.IsConfigured)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (DateTime.UtcNow - _lastCheckUtc < CacheTtl && _lastConnected.HasValue)
|
||||
{
|
||||
return _lastConnected;
|
||||
}
|
||||
|
||||
await _gate.WaitAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - _lastCheckUtc < CacheTtl && _lastConnected.HasValue)
|
||||
{
|
||||
return _lastConnected;
|
||||
}
|
||||
|
||||
_lastConnected = await client.ProbeAsync(ProbeTimeout, cancellationToken);
|
||||
_lastCheckUtc = DateTime.UtcNow;
|
||||
return _lastConnected;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_gate.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ builder.Services.AddSingleton<GameSocketHandler>();
|
||||
builder.Services.AddSingleton<GameLoopService>();
|
||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<GameLoopService>());
|
||||
builder.Services.AddSwarmUi(builder.Configuration);
|
||||
builder.Services.AddSingleton<SwarmUiHealthService>();
|
||||
builder.Services.AddSingleton(sp => SwarmUiSettings.Load(sp.GetRequiredService<IHostEnvironment>(), sp.GetRequiredService<ILoggerFactory>().CreateLogger("SwarmUiSettings")));
|
||||
builder.Services.AddSingleton<PortraitService>();
|
||||
|
||||
@@ -60,16 +61,19 @@ app.MapSchoolEndpoints();
|
||||
app.MapTimetableEndpoints();
|
||||
app.MapModEndpoints();
|
||||
|
||||
app.MapGet("/api/status", (GameLoopService loop, ClientRegistry clients, IOptions<SwarmUiOptions> swarm) =>
|
||||
app.MapGet("/api/status", async (GameLoopService loop, ClientRegistry clients, IOptions<SwarmUiOptions> swarm, SwarmUiHealthService swarmHealth, CancellationToken cancellationToken) =>
|
||||
{
|
||||
var state = loop.SchoolsState;
|
||||
var configured = !string.IsNullOrWhiteSpace(swarm.Value.BaseUrl);
|
||||
var connected = configured ? await swarmHealth.GetConnectedAsync(cancellationToken) : null;
|
||||
return new GameStatusResponse(
|
||||
loop.CurrentTick,
|
||||
loop.Options.TickRate,
|
||||
state.Schools.Count,
|
||||
state.MaxSchools,
|
||||
clients.Count,
|
||||
!string.IsNullOrWhiteSpace(swarm.Value.BaseUrl));
|
||||
configured,
|
||||
connected);
|
||||
})
|
||||
.WithName("GetGameStatus");
|
||||
|
||||
@@ -119,7 +123,8 @@ internal sealed record GameStatusResponse(
|
||||
int Schools,
|
||||
int MaxSchools,
|
||||
int Connections,
|
||||
bool SwarmUiConfigured);
|
||||
bool SwarmUiConfigured,
|
||||
bool? SwarmUiConnected);
|
||||
|
||||
/// <summary>Exposed so <c>WebApplicationFactory</c>-style tests can reference the entry point.</summary>
|
||||
public partial class Program;
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"model": "pornmasterFlux2Klein_v4TurboFp8.safetensors",
|
||||
"steps": 8,
|
||||
"cfgScale": 1,
|
||||
"clipSkip": 1,
|
||||
"sampler": "euler",
|
||||
"seed": -1,
|
||||
"positive": "School portrait photograph, neutral background, natural lighting, realistic, sharp focus.",
|
||||
"negative": "nsfw, nude, naked, explicit, blurry, deformed, extra limbs, bad anatomy, watermark, text, logo",
|
||||
"model": "dreamshaperXL_lightningDPMSDE.safetensors",
|
||||
"steps": 4,
|
||||
"cfgScale": 2,
|
||||
"clipSkip": 2,
|
||||
"sampler": "DPM++ SDE Karras",
|
||||
"seed": 3346112079,
|
||||
"positive": "cinematic photo, realist detail, detailed character expressions, amazing quality, analog film grain, school portrait photograph, neutral background, natural lighting, realistic, sharp focus",
|
||||
"negative": "(low quality, worst quality:1.4), cgi, text, signature, watermark, extra limbs, nsfw, nude, naked, explicit, blurry, deformed, bad anatomy, logo",
|
||||
"avatar": {
|
||||
"width": 512,
|
||||
"height": 512,
|
||||
"positive": "Head and shoulders portrait, facing the camera, upper body visible."
|
||||
"positive": "close up, head and shoulders portrait, facing the camera, upper body visible."
|
||||
},
|
||||
"fullBody": {
|
||||
"width": 768,
|
||||
"height": 1024,
|
||||
"positive": "Full body standing portrait, head to toe visible, neutral pose, current outfit clearly visible."
|
||||
"positive": "full body standing portrait, head to toe visible, neutral pose, current outfit clearly visible."
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user