Implement portrait generation and retrieval for people in the school system. Added new API endpoints for generating and fetching portraits, including support for avatar and full-body images. Updated the client-side to handle portrait states and display options. Enhanced the person card UI to include tabs for overview and portrait, with appropriate localization strings. Updated solution files to include new test projects. Adjusted server configuration for SwarmUI integration.
ci / server (push) Failing after 3m46s
ci / client (push) Failing after 14s

This commit is contained in:
Leonid Pershin
2026-08-20 04:14:41 +03:00
parent 871178909e
commit dfa9a25dca
32 changed files with 1538 additions and 121 deletions
@@ -0,0 +1,66 @@
using System.Net;
using System.Text;
using System.Text.Json;
using HSchool.Server.Game;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace HSchool.Server.Tests;
public class SwarmUiClientTests
{
[Fact]
public async Task GenerateAsync_UsesSessionAndReturnsImageBytes()
{
var handler = new FakeHandler();
var http = new HttpClient(handler) { BaseAddress = new Uri("http://swarm.test/") };
var client = new SwarmUiClient(
http,
Options.Create(new SwarmUiOptions { BaseUrl = "http://swarm.test", TimeoutSeconds = 30 }),
NullLogger<SwarmUiClient>.Instance);
var settings = new SwarmUiSettings
{
Model = "model.safetensors",
Steps = 8,
CfgScale = 1,
Avatar = new SwarmUiSettings.SwarmUiPreset { Width = 512, Height = 512 },
};
var bytes = await client.GenerateAsync("a student", "bad", settings, PortraitKind.Avatar, CancellationToken.None);
Assert.Equal([0x89, 0x50, 0x4E, 0x47], bytes.Take(4));
Assert.Contains("/API/GetNewSession", handler.Requests[0]);
Assert.Contains("/API/GenerateText2Image", handler.Requests[1]);
}
private sealed class FakeHandler : HttpMessageHandler
{
public List<string> Requests { get; } = [];
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Requests.Add(request.RequestUri!.AbsolutePath);
if (request.RequestUri.AbsolutePath.Contains("GetNewSession", StringComparison.Ordinal))
{
var session = JsonSerializer.Serialize(new { session_id = "sess-1" });
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(session, Encoding.UTF8, "application/json"),
});
}
if (request.RequestUri.AbsolutePath.Contains("GenerateText2Image", StringComparison.Ordinal))
{
var payload = JsonSerializer.Serialize(new { images = new[] { "data:image/png;base64,iVBORw0KGgo=" } });
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StringContent(payload, Encoding.UTF8, "application/json"),
});
}
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}