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.
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>HSchool.Server.Tests</RootNamespace>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\HSchool.Server\HSchool.Server.csproj" />
|
||||
<ProjectReference Include="..\..\src\HSchool.People\HSchool.People.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,72 @@
|
||||
using HSchool.Server.Api;
|
||||
using HSchool.Server.Game;
|
||||
|
||||
namespace HSchool.Server.Tests;
|
||||
|
||||
public class PortraitPromptBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Build_IncludesAgeHairAndWornClothing()
|
||||
{
|
||||
var settings = new SwarmUiSettings
|
||||
{
|
||||
Positive = "School photo.",
|
||||
Avatar = new SwarmUiSettings.SwarmUiPreset { Positive = "Head and shoulders." },
|
||||
};
|
||||
|
||||
var card = SampleCard();
|
||||
var (positive, _) = PortraitPromptBuilder.Build(card, settings, PortraitKind.Avatar);
|
||||
|
||||
Assert.Contains("age 12", positive, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("black", positive, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("shirt", positive, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("white", positive, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("Head and shoulders.", positive, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Build_FullBody_AddsFullBodyPreset()
|
||||
{
|
||||
var settings = new SwarmUiSettings
|
||||
{
|
||||
Positive = "School photo.",
|
||||
Avatar = new SwarmUiSettings.SwarmUiPreset { Positive = "Head and shoulders." },
|
||||
FullBody = new SwarmUiSettings.SwarmUiPreset { Positive = "Standing full body." },
|
||||
};
|
||||
|
||||
var (avatarPositive, _) = PortraitPromptBuilder.Build(SampleCard(), settings, PortraitKind.Avatar);
|
||||
var (fullPositive, _) = PortraitPromptBuilder.Build(SampleCard(), settings, PortraitKind.Full);
|
||||
|
||||
Assert.Contains("Head and shoulders.", avatarPositive, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("Standing full body.", avatarPositive, StringComparison.Ordinal);
|
||||
Assert.Contains("Standing full body.", fullPositive, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static PersonCardResponse SampleCard() =>
|
||||
new(
|
||||
"f0.c0",
|
||||
"Maria Ivanova",
|
||||
"Ivanova",
|
||||
"Maria",
|
||||
"",
|
||||
true,
|
||||
12,
|
||||
new DateTime(2000, 3, 14, 0, 0, 0, DateTimeKind.Utc),
|
||||
["student"],
|
||||
5,
|
||||
"A",
|
||||
"class-1",
|
||||
null,
|
||||
null,
|
||||
[new LabeledStatResponse("HairColor", "Hair colour", "Black")],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
new PersonFamilyResponse([], [], [], []),
|
||||
[new WornItemResponse("Shirt", "Shirt", "White", "White", [], 1f, "new")],
|
||||
[],
|
||||
0f,
|
||||
0f);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user