Implement custom portrait generation: update API, UI, and localization to support user-defined prompts for portraits, replacing half-body option with custom variant.
ci / server (push) Failing after 3m38s
ci / client (push) Failing after 14s

This commit is contained in:
Leonid Pershin
2026-08-20 05:16:40 +03:00
parent df0aaad403
commit ac0470bbc4
19 changed files with 301 additions and 76 deletions
+12 -7
View File
@@ -264,8 +264,9 @@ overload does not slow walking. `hasLocker` is true when the pupil has an assign
is how many items remain at home, not the list. The people list does not include any of these
fields. Today's history is a separate GET.
`hasAvatar`, `hasHalfBody` and `hasFullBody` tell the client whether PNG files already exist on disk for this
person. They are filled on the HTTP thread after the worker returns the card; generation does
`hasAvatar`, `hasCustom` and `hasFullBody` tell the client whether PNG files already exist on disk for this
person. `customPortraitPrompt` is the last saved user prompt for the custom variant (null when none).
They are filled on the HTTP thread after the worker returns the card; generation does
not happen on this request.
```json
@@ -318,8 +319,9 @@ not happen on this request.
"hasLocker": false,
"homeCount": 3,
"hasAvatar": false,
"hasHalfBody": false,
"hasFullBody": false
"hasCustom": false,
"hasFullBody": false,
"customPortraitPrompt": null
}
```
@@ -358,15 +360,18 @@ the action or apparel def the caption was built from.
### `GET /api/schools/{id}/people/{personId}/portrait`
Returns a generated PNG when one exists. Query `kind=avatar|half|full` selects head-and-shoulders,
waist-up or full-body. Unknown school is `404` `unknown-school`; unknown person is `404` `unknown-person`;
Returns a generated PNG when one exists. Query `kind=avatar|custom|full` selects head-and-shoulders,
a custom prompt variant or full-body. Unknown school is `404` `unknown-school`; unknown person is `404` `unknown-person`;
missing file is `404` `portrait-missing`. Invalid `kind` is `400` `invalid-query`. Content-Type
is `image/png`. Opening the card does not generate; use POST when the player asks.
### `POST /api/schools/{id}/people/{personId}/portrait`
Generates (or regenerates) a portrait through SwarmUI on the server. Same `kind` query as GET.
Success is `201` with `{ "kind", "hasAvatar", "hasHalfBody", "hasFullBody" }` and a `Location` header pointing
For `kind=custom` the body is `{ "promptExtra": "..." }` — appended to the base SwarmUI prompt and
the person's body/clothing; required, non-empty, at most 2000 characters. Avatar and full-body POST
need no body.
Success is `201` with `{ "kind", "hasAvatar", "hasCustom", "hasFullBody", "customPortraitPrompt" }` and a `Location` header pointing
at GET. SwarmUI is not configured when `SwarmUi:BaseUrl` is empty — `503` `swarmui-not-configured`.
Swarm errors are `502` `swarmui-unavailable`; a slow backend is `504` `swarmui-timeout`. Files
land under `saves/{id}.portraits/` and survive until the school is deleted.