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 <cursoragent@cursor.com>
This commit is contained in:
@@ -11,21 +11,21 @@ LoRA и embeddings можно поставить на модель, на пре
|
|||||||
|
|
||||||
## Задачи
|
## Задачи
|
||||||
|
|
||||||
- [ ] Discovery: список `embeddings` из Swarm (`models.Embedding`)
|
- [x] Discovery: список `embeddings` из Swarm (`models.Embedding`)
|
||||||
- [ ] Списки LoRA и embeddings (name + weight) на модели, пресете и avatar/custom/full.
|
- [x] Списки LoRA и embeddings (name + weight) на модели, пресете и avatar/custom/full.
|
||||||
Склейка модель → пресет → кадр. Пустые списки в шаблоне
|
Склейка модель → пресет → кадр. Пустые списки в шаблоне
|
||||||
- [ ] Генерация: все LoRA в `loras` и `loraweights`; embeddings в positive/negative как
|
- [x] Генерация: все LoRA в `loras` и `loraweights`; embeddings в positive/negative как
|
||||||
`<embed:name>`. Не писать `<lora:>` в промпт
|
`<embed:name>`. Не писать `<lora:>` в промпт
|
||||||
- [ ] UI: комбобоксы из discovery на модели, в спойлере пресета и в секции кадра. `t(...)`
|
- [x] UI: комбобоксы из discovery на модели, в спойлере пресета и в секции кадра. `t(...)`
|
||||||
- [ ] HTTP в [`protocol.md`](../../protocol.md), сокет не бампить
|
- [x] HTTP в [`protocol.md`](../../protocol.md), сокет не бампить
|
||||||
|
|
||||||
## Тесты, без которых фаза не закрыта
|
## Тесты, без которых фаза не закрыта
|
||||||
|
|
||||||
- [ ] Discovery парсит embeddings; офлайн — пустой список
|
- [x] Discovery парсит embeddings; офлайн — пустой список
|
||||||
- [ ] Склейка LoRA/embed: модель + пресет + kind
|
- [x] Склейка LoRA/embed: модель + пресет + kind
|
||||||
- [ ] Промпт содержит `<embed:…>` и не содержит `<lora:`
|
- [x] Промпт содержит `<embed:…>` и не содержит `<lora:`
|
||||||
- [ ] Generate шлёт `loras` и `loraweights` раздельно
|
- [x] Generate шлёт `loras` и `loraweights` раздельно
|
||||||
- [ ] Диалог: поля embeddings у модели и у вида кадра
|
- [x] Диалог: поля embeddings у модели и у вида кадра
|
||||||
|
|
||||||
## Критерий готовности
|
## Критерий готовности
|
||||||
|
|
||||||
|
|||||||
+12
-9
@@ -507,9 +507,10 @@ land under `saves/{id}.portraits/` and survive until the school is deleted.
|
|||||||
|
|
||||||
Returns the positive and negative prompts SwarmUI would receive, without generating an image.
|
Returns the positive and negative prompts SwarmUI would receive, without generating an image.
|
||||||
Same `kind` query as GET portrait. For `kind=custom`, optional query `promptExtra` is appended after
|
Same `kind` query as GET portrait. For `kind=custom`, optional query `promptExtra` is appended after
|
||||||
the shot type; when omitted, the last saved custom prompt is used if one exists. Unknown school
|
the shot type; when omitted, the last saved custom prompt is used if one exists. Configured
|
||||||
is `404` `unknown-school`; unknown person is `404` `unknown-person`. Invalid `kind` is
|
embeddings appear as `<embed:name>` (weight ≠ 1: `<embed:name:w>`); LoRA is not written into the
|
||||||
`400` `invalid-query`; custom without a usable prompt is `400` `invalid-body`.
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -528,8 +529,10 @@ so the client can disable generate buttons and show reachability without trying
|
|||||||
### `GET /api/settings/swarmui`
|
### `GET /api/settings/swarmui`
|
||||||
|
|
||||||
Returns the **default** SwarmUI template (`swarmui.json`): a `models` catalog (id, default
|
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
|
generation knobs, usually empty base positive/negative, LoRA and embedding lists), named presets
|
||||||
negative, optional generation overrides, per-kind size/`shotType`), `activePresetId` and `ageRules`.
|
(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
|
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
|
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`).
|
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
|
When SwarmUI is configured and reachable, proxies `ListT2IParams` and returns
|
||||||
`{ connected, models, loras, embeddings, samplers, schedulers }` for the settings UI. LoRA and
|
`{ 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.
|
embeddings can be set on the model, the preset, and each shot kind; layers concatenate. The client
|
||||||
The client hides Swarm models that are missing from the template catalog. When SwarmUI is off or unreachable, `connected`
|
hides Swarm models that are missing from the template catalog. When SwarmUI is off or unreachable,
|
||||||
is false and the lists are empty; the picker then shows the catalog ids so fields can be filled
|
`connected` is false and the lists are empty; the picker then shows the catalog ids so fields can
|
||||||
manually.
|
be filled manually.
|
||||||
|
|
||||||
### `GET /api/schools/{id}/dress-rules`
|
### `GET /api/schools/{id}/dress-rules`
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
|
|||||||
const ageRulesHost = el('div', { class: 'settings-age-rules' });
|
const ageRulesHost = el('div', { class: 'settings-age-rules' });
|
||||||
|
|
||||||
let config: SwarmUiSettingsFile | null = null;
|
let config: SwarmUiSettingsFile | null = null;
|
||||||
let discovery: SwarmUiDiscovery = { connected: false, models: [], loras: [], samplers: [], schedulers: [] };
|
let discovery: SwarmUiDiscovery = { connected: false, models: [], loras: [], embeddings: [], samplers: [], schedulers: [] };
|
||||||
let editingPresetId = '';
|
let editingPresetId = '';
|
||||||
|
|
||||||
const cancelButton = el('button', { class: 'button', type: 'button', onClick: () => modal.close(null) });
|
const cancelButton = el('button', { class: 'button', type: 'button', onClick: () => modal.close(null) });
|
||||||
@@ -239,6 +239,11 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
|
|||||||
return host;
|
return host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.positiveLoras ??= [];
|
||||||
|
model.negativeLoras ??= [];
|
||||||
|
model.positiveEmbeddings ??= [];
|
||||||
|
model.negativeEmbeddings ??= [];
|
||||||
|
|
||||||
host.append(
|
host.append(
|
||||||
textareaField(t('settingsModelPositive'), model.positive, (value) => {
|
textareaField(t('settingsModelPositive'), model.positive, (value) => {
|
||||||
model.positive = value;
|
model.positive = value;
|
||||||
|
|||||||
@@ -550,6 +550,10 @@ internal sealed class SwarmUiModelDefinition
|
|||||||
Sampler = sampler,
|
Sampler = sampler,
|
||||||
Scheduler = scheduler,
|
Scheduler = scheduler,
|
||||||
Seed = seed,
|
Seed = seed,
|
||||||
|
PositiveLoras = [],
|
||||||
|
NegativeLoras = [],
|
||||||
|
PositiveEmbeddings = [],
|
||||||
|
NegativeEmbeddings = [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ public class SwarmUiLoraFormatterTests
|
|||||||
|
|
||||||
Assert.Equal(["pos.safetensors", "neg.safetensors"], discovery.Embeddings);
|
Assert.Equal(["pos.safetensors", "neg.safetensors"], discovery.Embeddings);
|
||||||
Assert.Equal(["style.safetensors"], discovery.Loras);
|
Assert.Equal(["style.safetensors"], discovery.Loras);
|
||||||
|
Assert.Empty(SwarmUiDiscovery.Offline.Embeddings);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user