Add Krea portrait models and write appearance as prose.
New schools default to Realism v2.5; living saves keep their catalog. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
using HSchool.Content;
|
||||
using HSchool.Server.Api;
|
||||
|
||||
namespace HSchool.Server.Game;
|
||||
|
||||
/// <summary>Turns a person card into a Flux-style English prompt for SwarmUI.</summary>
|
||||
/// <summary>Turns a person card into an English prompt for SwarmUI.</summary>
|
||||
internal static class PortraitPromptBuilder
|
||||
{
|
||||
private const string HairColorId = "HairColor";
|
||||
private const string EyeColorId = "EyeColor";
|
||||
|
||||
public static (string Positive, string Negative) Build(
|
||||
PersonCardResponse card,
|
||||
SwarmUiResolvedProfile profile,
|
||||
@@ -22,26 +26,8 @@ internal static class PortraitPromptBuilder
|
||||
}
|
||||
|
||||
Add(parts, profile.Pose);
|
||||
Add(parts, DescribeSubject(card));
|
||||
parts.Add($"age {card.Age}");
|
||||
|
||||
foreach (var row in card.Body)
|
||||
{
|
||||
Add(parts, $"{row.Label.ToLowerInvariant()} {row.Value.ToLowerInvariant()}");
|
||||
}
|
||||
|
||||
foreach (var item in PortraitVisibleWorn.Filter(card.Worn))
|
||||
{
|
||||
var color = item.ColorLabel ?? item.Color;
|
||||
if (!string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
Add(parts, $"wearing {item.Label.ToLowerInvariant()} in {color.ToLowerInvariant()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(parts, $"wearing {item.Label.ToLowerInvariant()}");
|
||||
}
|
||||
}
|
||||
Add(parts, DescribeAppearance(card));
|
||||
Add(parts, DescribeClothing(card));
|
||||
|
||||
var positive = SwarmUiLoraFormatter.AppendEmbedTags(
|
||||
string.Join(", ", parts),
|
||||
@@ -63,6 +49,83 @@ internal static class PortraitPromptBuilder
|
||||
parts.Add(value.Trim());
|
||||
}
|
||||
|
||||
private static string DescribeAppearance(PersonCardResponse card)
|
||||
{
|
||||
var parts = new List<string> { DescribeSubject(card), $"{card.Age} years old" };
|
||||
|
||||
var hair = BodyValue(card, HairColorId);
|
||||
if (hair is not null)
|
||||
{
|
||||
parts.Add($"{hair.ToLowerInvariant()} hair");
|
||||
}
|
||||
|
||||
var eyes = BodyValue(card, EyeColorId);
|
||||
if (eyes is not null)
|
||||
{
|
||||
parts.Add($"{eyes.ToLowerInvariant()} eyes");
|
||||
}
|
||||
|
||||
var build = BodyValue(card, BodyBuilds.Attribute);
|
||||
if (build is not null && !build.Equals(BodyBuilds.Average, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parts.Add($"a {build.ToLowerInvariant()} build");
|
||||
}
|
||||
|
||||
var height = BodyValue(card, BodyBuilds.HeightAttribute);
|
||||
if (height is not null)
|
||||
{
|
||||
parts.Add($"{height} cm tall");
|
||||
}
|
||||
|
||||
if (parts.Count == 2)
|
||||
{
|
||||
return $"{parts[0]}, {parts[1]}";
|
||||
}
|
||||
|
||||
return $"{parts[0]}, {parts[1]}, with {string.Join(", ", parts.Skip(2))}";
|
||||
}
|
||||
|
||||
private static string? DescribeClothing(PersonCardResponse card)
|
||||
{
|
||||
var items = PortraitVisibleWorn.Filter(card.Worn)
|
||||
.Select(DescribeWornItem)
|
||||
.Where(item => !string.IsNullOrWhiteSpace(item))
|
||||
.ToList();
|
||||
if (items.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $"wearing {JoinList(items)}";
|
||||
}
|
||||
|
||||
private static string DescribeWornItem(WornItemResponse item)
|
||||
{
|
||||
var noun = item.Label.Trim();
|
||||
if (string.IsNullOrWhiteSpace(noun))
|
||||
{
|
||||
noun = item.DefName.Trim();
|
||||
}
|
||||
|
||||
noun = noun.ToLowerInvariant();
|
||||
var color = (item.ColorLabel ?? item.Color)?.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(color))
|
||||
{
|
||||
noun = $"{color.ToLowerInvariant()} {noun}";
|
||||
}
|
||||
|
||||
var condition = item.ConditionLabel?.Trim().ToLowerInvariant();
|
||||
noun = condition switch
|
||||
{
|
||||
"torn" => $"torn {noun}",
|
||||
"worn" => $"worn {noun}",
|
||||
"in rags" => $"{noun} in rags",
|
||||
_ => noun,
|
||||
};
|
||||
|
||||
return WithArticle(noun);
|
||||
}
|
||||
|
||||
private static string DescribeSubject(PersonCardResponse card)
|
||||
{
|
||||
if (card.Age <= 11)
|
||||
@@ -77,4 +140,36 @@ internal static class PortraitPromptBuilder
|
||||
|
||||
return card.Female ? "A young woman" : "A young man";
|
||||
}
|
||||
|
||||
private static string? BodyValue(PersonCardResponse card, string id)
|
||||
{
|
||||
foreach (var row in card.Body)
|
||||
{
|
||||
if (row.Id.Equals(id, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(row.Value))
|
||||
{
|
||||
return row.Value.Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string WithArticle(string phrase)
|
||||
{
|
||||
if (phrase.Length == 0)
|
||||
{
|
||||
return phrase;
|
||||
}
|
||||
|
||||
var first = char.ToLowerInvariant(phrase[0]);
|
||||
var article = first is 'a' or 'e' or 'i' or 'o' or 'u' ? "an" : "a";
|
||||
return $"{article} {phrase}";
|
||||
}
|
||||
|
||||
private static string JoinList(IReadOnlyList<string> items) => items.Count switch
|
||||
{
|
||||
1 => items[0],
|
||||
2 => $"{items[0]} and {items[1]}",
|
||||
_ => $"{string.Join(", ", items.Take(items.Count - 1))}, and {items[^1]}",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -471,6 +471,8 @@ internal sealed class SwarmUiModelDefinition
|
||||
public const string DreamShaperId = "DreamShaper_XL_-_Lightning_DPM++_SDE.safetensors";
|
||||
public const string EpicRealismId = "epicrealismXL_VXIAbeast4SLightning.safetensors";
|
||||
public const string LustifyId = "lustifyNSFWCheckpoint_v40DMD2.safetensors";
|
||||
public const string MuseId = "museByStableYogi_v30TurboGguf.gguf";
|
||||
public const string RealismV25Id = "realismByStableYogi_v25INT8Turbo.safetensors";
|
||||
|
||||
public string Id { get; set; } = "";
|
||||
|
||||
@@ -529,6 +531,8 @@ internal sealed class SwarmUiModelDefinition
|
||||
Lightning(DreamShaperId, "DreamShaper XL Lightning", 4, 2, 2, "dpmpp_sde", "karras", 3346112079),
|
||||
Lightning(EpicRealismId, "epicrealism XL Lightning", 7, 1.5, 0, "euler", "normal", 0),
|
||||
Lightning(LustifyId, "lustify NSFW v40 DMD2", 7, 1.5, 0, "euler", "normal", 0),
|
||||
Lightning(MuseId, "museByStableYogi v3.0 Turbo GGUF", 8, 1, 0, "euler", "simple", 0),
|
||||
Lightning(RealismV25Id, "realismByStableYogi v2.5 INT8 Turbo", 8, 1, 0, "euler", "simple", 0),
|
||||
];
|
||||
|
||||
private static SwarmUiModelDefinition Lightning(
|
||||
@@ -736,23 +740,23 @@ internal sealed class SwarmUiPresetDefinition
|
||||
{
|
||||
Id = "default",
|
||||
Label = "Default",
|
||||
Model = SwarmUiModelDefinition.BabesId,
|
||||
Model = SwarmUiModelDefinition.RealismV25Id,
|
||||
Style =
|
||||
"cinematic photo, realist detail, detailed character expressions, amazing quality, analog film grain, school portrait photograph, neutral background, natural lighting, realistic, sharp focus",
|
||||
"A cinematic school portrait photograph with natural window light, a neutral background, realistic skin, and sharp focus",
|
||||
Negative =
|
||||
"(low quality, worst quality:1.4), cgi, text, signature, watermark, extra limbs, nsfw, nude, naked, explicit, blurry, deformed, bad anatomy, logo",
|
||||
"text, signature, watermark, extra limbs, deformed anatomy, extra fingers",
|
||||
Avatar = new SwarmUiKindPreset
|
||||
{
|
||||
Width = 1024,
|
||||
Height = 1024,
|
||||
ShotType = "close up, head and shoulders portrait, facing the camera, upper body visible.",
|
||||
ShotType = "Close-up head and shoulders portrait, facing the camera, upper body visible",
|
||||
},
|
||||
Custom = new SwarmUiKindPreset { Width = 896, Height = 1152 },
|
||||
FullBody = new SwarmUiKindPreset
|
||||
{
|
||||
Width = 896,
|
||||
Height = 1152,
|
||||
ShotType = "full body standing portrait, head to toe visible, neutral pose, current outfit clearly visible.",
|
||||
ShotType = "Full-body standing portrait from head to toe, neutral pose, current outfit clearly visible",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -56,19 +56,47 @@
|
||||
"negative": "",
|
||||
"positiveLoras": [],
|
||||
"negativeLoras": []
|
||||
},
|
||||
{
|
||||
"id": "museByStableYogi_v30TurboGguf.gguf",
|
||||
"label": "museByStableYogi v3.0 Turbo GGUF",
|
||||
"steps": 8,
|
||||
"cfgScale": 1,
|
||||
"clipSkip": 0,
|
||||
"sampler": "euler",
|
||||
"scheduler": "simple",
|
||||
"seed": 0,
|
||||
"positive": "",
|
||||
"negative": "",
|
||||
"positiveLoras": [],
|
||||
"negativeLoras": []
|
||||
},
|
||||
{
|
||||
"id": "realismByStableYogi_v25INT8Turbo.safetensors",
|
||||
"label": "realismByStableYogi v2.5 INT8 Turbo",
|
||||
"steps": 8,
|
||||
"cfgScale": 1,
|
||||
"clipSkip": 0,
|
||||
"sampler": "euler",
|
||||
"scheduler": "simple",
|
||||
"seed": 0,
|
||||
"positive": "",
|
||||
"negative": "",
|
||||
"positiveLoras": [],
|
||||
"negativeLoras": []
|
||||
}
|
||||
],
|
||||
"presets": [
|
||||
{
|
||||
"id": "default",
|
||||
"label": "Default",
|
||||
"model": "babesByStableYogi_v4XLLightning.safetensors",
|
||||
"style": "cinematic photo, realist detail, detailed character expressions, amazing quality,",
|
||||
"negative": "(low quality, worst quality:1.4), cgi, text, signature, watermark, extra limbs, censored, explicit, blurry, deformed, bad anatomy, logo",
|
||||
"model": "realismByStableYogi_v25INT8Turbo.safetensors",
|
||||
"style": "A cinematic school portrait photograph with natural window light, a neutral background, realistic skin, and sharp focus",
|
||||
"negative": "text, signature, watermark, extra limbs, deformed anatomy, extra fingers",
|
||||
"avatar": {
|
||||
"width": 1024,
|
||||
"height": 1024,
|
||||
"shotType": "close up, head and shoulders portrait, facing the camera, upper body visible, avatar,"
|
||||
"shotType": "Close-up head and shoulders portrait, facing the camera, upper body visible"
|
||||
},
|
||||
"custom": {
|
||||
"width": 896,
|
||||
@@ -78,7 +106,7 @@
|
||||
"fullBody": {
|
||||
"width": 896,
|
||||
"height": 1152,
|
||||
"shotType": "full body standing portrait, head to toe visible, neutral pose, current outfit clearly visible."
|
||||
"shotType": "Full-body standing portrait from head to toe, neutral pose, current outfit clearly visible"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user