From 9e217e93eb141c31964bf9afd7e9e22923e37682 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Thu, 20 Aug 2026 14:58:26 +0300 Subject: [PATCH] Stack portrait LoRAs and embeddings from model, preset, and shot kind. Swarm loads LoRAs via loras/loraweights; embeddings go in the prompt as embed tags, not lora tags. Co-authored-by: Cursor --- .../off-queue/61-portrait-loras-embeds.md | 20 +++++++++--------- docs/protocol.md | 21 +++++++++++-------- .../src/ui/swarmUiSettingsDialog.ts | 7 ++++++- .../Game/SwarmUiSettingsStore.cs | 4 ++++ .../SwarmUiLoraFormatterTests.cs | 1 + 5 files changed, 33 insertions(+), 20 deletions(-) diff --git a/docs/phases/off-queue/61-portrait-loras-embeds.md b/docs/phases/off-queue/61-portrait-loras-embeds.md index 647ecaf..1b451b9 100644 --- a/docs/phases/off-queue/61-portrait-loras-embeds.md +++ b/docs/phases/off-queue/61-portrait-loras-embeds.md @@ -11,21 +11,21 @@ LoRA и embeddings можно поставить на модель, на пре ## Задачи -- [ ] Discovery: список `embeddings` из Swarm (`models.Embedding`) -- [ ] Списки LoRA и embeddings (name + weight) на модели, пресете и avatar/custom/full. +- [x] Discovery: список `embeddings` из Swarm (`models.Embedding`) +- [x] Списки LoRA и embeddings (name + weight) на модели, пресете и avatar/custom/full. Склейка модель → пресет → кадр. Пустые списки в шаблоне -- [ ] Генерация: все LoRA в `loras` и `loraweights`; embeddings в positive/negative как +- [x] Генерация: все LoRA в `loras` и `loraweights`; embeddings в positive/negative как ``. Не писать `` в промпт -- [ ] UI: комбобоксы из discovery на модели, в спойлере пресета и в секции кадра. `t(...)` -- [ ] HTTP в [`protocol.md`](../../protocol.md), сокет не бампить +- [x] UI: комбобоксы из discovery на модели, в спойлере пресета и в секции кадра. `t(...)` +- [x] HTTP в [`protocol.md`](../../protocol.md), сокет не бампить ## Тесты, без которых фаза не закрыта -- [ ] Discovery парсит embeddings; офлайн — пустой список -- [ ] Склейка LoRA/embed: модель + пресет + kind -- [ ] Промпт содержит `` и не содержит `` и не содержит `` (weight ≠ 1: ``); LoRA is not written into the +prompt. Unknown school is `404` `unknown-school`; unknown person is `404` `unknown-person`. Invalid +`kind` is `400` `invalid-query`; custom without a usable prompt is `400` `invalid-body`. ```json { @@ -528,8 +529,10 @@ so the client can disable generate buttons and show reachability without trying ### `GET /api/settings/swarmui` Returns the **default** SwarmUI template (`swarmui.json`): a `models` catalog (id, default -generation knobs, usually empty base positive/negative), named presets (model id, `style`, extra -negative, optional generation overrides, per-kind size/`shotType`), `activePresetId` and `ageRules`. +generation knobs, usually empty base positive/negative, LoRA and embedding lists), named presets +(model id, `style`, extra negative, optional generation overrides, per-kind size/`shotType`, LoRA +and embedding lists), `activePresetId` and `ageRules`. LoRA and embeddings on the model, preset and +shot kind concatenate at generate time. The model picker in the UI is Swarm's list intersected with `models`. A new school copies this into its save as `portraitSettings`. Living schools generate from that copy, not from this file. Older saves without `models` lift on load (`positive` → `style`, kind `positive` → `shotType`). @@ -543,10 +546,10 @@ return `400` `invalid-body`. Already-created schools keep the copy they were cre When SwarmUI is configured and reachable, proxies `ListT2IParams` and returns `{ connected, models, loras, embeddings, samplers, schedulers }` for the settings UI. LoRA and -embeddings can be set on the model, the preset, and each shot kind; layers concatenate. -The client hides Swarm models that are missing from the template catalog. When SwarmUI is off or unreachable, `connected` -is false and the lists are empty; the picker then shows the catalog ids so fields can be filled -manually. +embeddings can be set on the model, the preset, and each shot kind; layers concatenate. The client +hides Swarm models that are missing from the template catalog. When SwarmUI is off or unreachable, +`connected` is false and the lists are empty; the picker then shows the catalog ids so fields can +be filled manually. ### `GET /api/schools/{id}/dress-rules` diff --git a/src/HSchool.Client/src/ui/swarmUiSettingsDialog.ts b/src/HSchool.Client/src/ui/swarmUiSettingsDialog.ts index 6732d91..fcd0750 100644 --- a/src/HSchool.Client/src/ui/swarmUiSettingsDialog.ts +++ b/src/HSchool.Client/src/ui/swarmUiSettingsDialog.ts @@ -25,7 +25,7 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise modal.close(null) }); @@ -239,6 +239,11 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise { model.positive = value; diff --git a/src/HSchool.Server/Game/SwarmUiSettingsStore.cs b/src/HSchool.Server/Game/SwarmUiSettingsStore.cs index 0169ed0..52fd36d 100644 --- a/src/HSchool.Server/Game/SwarmUiSettingsStore.cs +++ b/src/HSchool.Server/Game/SwarmUiSettingsStore.cs @@ -550,6 +550,10 @@ internal sealed class SwarmUiModelDefinition Sampler = sampler, Scheduler = scheduler, Seed = seed, + PositiveLoras = [], + NegativeLoras = [], + PositiveEmbeddings = [], + NegativeEmbeddings = [], }; } diff --git a/tests/HSchool.Server.Tests/SwarmUiLoraFormatterTests.cs b/tests/HSchool.Server.Tests/SwarmUiLoraFormatterTests.cs index 196f717..e2650ff 100644 --- a/tests/HSchool.Server.Tests/SwarmUiLoraFormatterTests.cs +++ b/tests/HSchool.Server.Tests/SwarmUiLoraFormatterTests.cs @@ -63,6 +63,7 @@ public class SwarmUiLoraFormatterTests Assert.Equal(["pos.safetensors", "neg.safetensors"], discovery.Embeddings); Assert.Equal(["style.safetensors"], discovery.Loras); + Assert.Empty(SwarmUiDiscovery.Offline.Embeddings); } [Fact]