Add SwarmUI settings management and portrait generation enhancements

- Introduced new API endpoints for managing SwarmUI settings, including fetching and saving presets and age rules.
- Updated the portrait generation logic to utilize the new settings structure, allowing for dynamic preset selection based on age.
- Enhanced UI components to support SwarmUI settings, including localization for new strings and improved styling for settings sections.
- Added tests to verify the functionality of new settings endpoints and portrait generation behavior.

This commit lays the groundwork for more flexible and user-friendly portrait generation options.
This commit is contained in:
Leonid Pershin
2026-08-20 06:06:45 +03:00
parent e0b7122f3b
commit 5a400be792
32 changed files with 2178 additions and 184 deletions
+22 -12
View File
@@ -5,7 +5,7 @@ namespace HSchool.Server.Game;
internal sealed class PortraitService(
SchoolStore store,
SwarmUiClient swarm,
SwarmUiSettings settings,
SwarmUiSettingsStore settingsStore,
GameCommandQueue commands,
ILogger<PortraitService> logger)
{
@@ -74,12 +74,15 @@ internal sealed class PortraitService(
return PortraitPromptBuildResult.UnknownSchool;
}
var (positive, negative) = PortraitPromptBuilder.Build(outcome.Card, settings, kind, resolved);
var profile = settingsStore.Resolve(outcome.Card.Age, kind);
var (positive, negative) = PortraitPromptBuilder.Build(outcome.Card, profile, kind, resolved);
return PortraitPromptBuildResult.Succeeded(
kind,
positive,
negative,
kind == PortraitKind.Custom ? resolved : null);
kind == PortraitKind.Custom ? resolved : null,
profile.PresetId,
profile.PresetLabel);
}
public async Task<PortraitGenerationResult> GenerateAsync(
@@ -119,11 +122,12 @@ internal sealed class PortraitService(
return PortraitGenerationResult.UnknownSchool;
}
var (positive, negative) = PortraitPromptBuilder.Build(outcome.Card, settings, kind, promptExtra);
var profile = settingsStore.Resolve(outcome.Card.Age, kind);
var (positive, negative) = PortraitPromptBuilder.Build(outcome.Card, profile, kind, promptExtra);
try
{
var bytes = await swarm.GenerateAsync(positive, negative, settings, kind, cancellationToken);
var bytes = await swarm.GenerateAsync(positive, negative, profile, cancellationToken);
store.SavePortrait(schoolId, personId, kind, bytes, kind == PortraitKind.Custom ? promptExtra : null);
var flags = Flags(schoolId, personId);
var savedPrompt = kind == PortraitKind.Custom ? promptExtra : store.TryReadCustomPortraitPrompt(schoolId, personId);
@@ -203,30 +207,36 @@ internal sealed record PortraitPromptBuildResult(
PortraitKind Kind,
string Positive,
string Negative,
string? PromptExtra)
string? PromptExtra,
string PresetId,
string PresetLabel)
{
public static PortraitPromptBuildResult UnknownSchool { get; } =
new(PortraitPromptBuildOutcome.UnknownSchool, default, string.Empty, string.Empty, null);
new(PortraitPromptBuildOutcome.UnknownSchool, default, string.Empty, string.Empty, null, "", "");
public static PortraitPromptBuildResult UnknownPerson { get; } =
new(PortraitPromptBuildOutcome.UnknownPerson, default, string.Empty, string.Empty, null);
new(PortraitPromptBuildOutcome.UnknownPerson, default, string.Empty, string.Empty, null, "", "");
public static PortraitPromptBuildResult InvalidPrompt { get; } =
new(PortraitPromptBuildOutcome.InvalidPrompt, default, string.Empty, string.Empty, null);
new(PortraitPromptBuildOutcome.InvalidPrompt, default, string.Empty, string.Empty, null, "", "");
public static PortraitPromptBuildResult Succeeded(
PortraitKind kind,
string positive,
string negative,
string? promptExtra) =>
new(PortraitPromptBuildOutcome.Succeeded, kind, positive, negative, promptExtra);
string? promptExtra,
string presetId,
string presetLabel) =>
new(PortraitPromptBuildOutcome.Succeeded, kind, positive, negative, promptExtra, presetId, presetLabel);
}
internal sealed record PortraitPromptResponse(
string Kind,
string Positive,
string Negative,
string? PromptExtra);
string? PromptExtra,
string PresetId,
string PresetLabel);
internal enum PortraitGenerationOutcome
{