Merge main into phase/39-school-owners.

Keep school owners and phase 41 opinions/portraits together; ResetAsync wipes all saves so shared AppHost tests stay isolated.
This commit is contained in:
Leonid Pershin
2026-08-20 08:36:13 +03:00
44 changed files with 1388 additions and 85 deletions
+45 -7
View File
@@ -174,7 +174,8 @@ Body:
"map": null,
"countryId": "Russia",
"nativeLanguage": null,
"seed": null
"seed": null,
"portraitSettings": null
}
```
@@ -188,6 +189,10 @@ the same catalog. Omit `map` (or send `null`) to use that pack set's default lay
one from the school seed. An id that is not in the country returns `400` `unknown-native-language`.
`seed` is an optional integer. Send it to reproduce a known school; omit it (or send `null`) and
the server rolls one. Existing saves keep the seed already stored in the people file.
`portraitSettings` is the SwarmUI preset file for **this** school (same shape as
`GET /api/settings/swarmui`). Omit it (or send `null`) to copy the server template at create.
Invalid presets return `400` `invalid-portrait-settings`. Generation later uses this copy, not
the global file, so a guest watching the school draws with the same model.
| Status | Meaning |
| --- | --- |
@@ -201,6 +206,7 @@ the server rolls one. Existing saves keep the seed already stored in the people
| `400` `invalid-catalog` | The selected packs could not be loaded. |
| `400` `unknown-country` | `countryId` is not a placeable `CountryDef` in those packs. |
| `400` `unknown-native-language` | `nativeLanguage` is not in that country's `nativeLanguages`. |
| `400` `invalid-portrait-settings` | `portraitSettings` failed validation (empty presets, bad age rule, out of range). |
| `409` `school-limit-reached` | This player already owns `maxSchools` schools. |
| `409` `server-full` | The process already runs `maxSchoolsTotal` schools. |
@@ -320,6 +326,10 @@ person. `customPortraitPrompt` is the last saved user prompt for the custom vari
They are filled on the HTTP thread after the worker returns the card; generation does
not happen on this request.
`connections` is the **Связи** tab: family links with `opinion` / `opinionLabel` from catalog bands,
plus `friends`, `enemies` and `others` (every non-family non-zero pair for this person only).
The client does not compute thresholds. There is no school-wide opinions endpoint.
```json
{
"id": "f0.c0",
@@ -372,7 +382,34 @@ not happen on this request.
"hasAvatar": false,
"hasCustom": false,
"hasFullBody": false,
"customPortraitPrompt": null
"customPortraitPrompt": null,
"connections": {
"family": {
"parents": [
{
"id": "f0.p1",
"fullName": "Иванова Ольга Михайловна",
"female": true,
"opinion": 75,
"opinionLabel": "близкие друзья"
}
],
"children": [],
"siblings": [
{
"id": "f0.c1",
"fullName": "Иванов Кирилл Петрович",
"female": false,
"opinion": 45,
"opinionLabel": "друзья"
}
],
"partners": []
},
"friends": [],
"enemies": [],
"others": []
}
}
```
@@ -451,14 +488,15 @@ so the client can disable generate buttons and show reachability without trying
### `GET /api/settings/swarmui`
Returns the editable SwarmUI preset file (`swarmui.json`): named presets (model, steps, sampler,
LoRA lists, per-kind sizes/prompts), `activePresetId` and `ageRules` mapping age bands to presets.
Portrait generation resolves the preset from the person's age before building the prompt.
Returns the **default** SwarmUI preset template (`swarmui.json`): named presets (model, steps,
sampler, LoRA lists, per-kind sizes/prompts), `activePresetId` and `ageRules`. A new school copies
this into its save as `portraitSettings`. Living schools generate from that copy, not from this
file.
### `PUT /api/settings/swarmui`
Replaces the preset file after validation. Invalid preset ids, age rules or numeric ranges return
`400` `invalid-body`. Changes apply immediately to new portrait generations.
Replaces the default template after validation. Invalid preset ids, age rules or numeric ranges
return `400` `invalid-body`. Already-created schools keep the copy they were created with.
### `GET /api/settings/swarmui/discovery`