checkpoint before checking out main

This commit is contained in:
Leonid Pershin
2026-08-20 14:43:00 +03:00
parent 477f8cf6c9
commit 5d46550f12
18 changed files with 429 additions and 64 deletions
+6
View File
@@ -53,7 +53,10 @@ const ru = {
settingsOverrides: 'Настройки генерации пресета',
settingsPositiveLoras: 'Positive LoRA',
settingsNegativeLoras: 'Negative LoRA',
settingsPositiveEmbeddings: 'Positive embeddings',
settingsNegativeEmbeddings: 'Negative embeddings',
settingsAddLora: 'Добавить LoRA',
settingsAddEmbedding: 'Добавить embedding',
settingsAvatarPreset: 'Аватар',
settingsCustomPreset: 'Свой промпт (размеры)',
settingsFullBodyPreset: 'В полный рост',
@@ -431,7 +434,10 @@ const en: Messages = {
settingsOverrides: 'Preset generation overrides',
settingsPositiveLoras: 'Positive LoRA',
settingsNegativeLoras: 'Negative LoRA',
settingsPositiveEmbeddings: 'Positive embeddings',
settingsNegativeEmbeddings: 'Negative embeddings',
settingsAddLora: 'Add LoRA',
settingsAddEmbedding: 'Add embedding',
settingsAvatarPreset: 'Avatar',
settingsCustomPreset: 'Custom prompt (size)',
settingsFullBodyPreset: 'Full body',
+9
View File
@@ -498,6 +498,10 @@ export interface SwarmUiKindPreset {
width: number;
height: number;
shotType: string;
positiveLoras?: SwarmUiLoraEntry[] | null;
negativeLoras?: SwarmUiLoraEntry[] | null;
positiveEmbeddings?: SwarmUiLoraEntry[] | null;
negativeEmbeddings?: SwarmUiLoraEntry[] | null;
}
export interface SwarmUiLoraEntry {
@@ -518,6 +522,8 @@ export interface SwarmUiModelDefinition {
negative: string;
positiveLoras: SwarmUiLoraEntry[];
negativeLoras: SwarmUiLoraEntry[];
positiveEmbeddings: SwarmUiLoraEntry[];
negativeEmbeddings: SwarmUiLoraEntry[];
}
export interface SwarmUiPresetDefinition {
@@ -534,6 +540,8 @@ export interface SwarmUiPresetDefinition {
seed?: number | null;
positiveLoras?: SwarmUiLoraEntry[] | null;
negativeLoras?: SwarmUiLoraEntry[] | null;
positiveEmbeddings?: SwarmUiLoraEntry[] | null;
negativeEmbeddings?: SwarmUiLoraEntry[] | null;
avatar: SwarmUiKindPreset;
custom: SwarmUiKindPreset;
fullBody: SwarmUiKindPreset;
@@ -556,6 +564,7 @@ export interface SwarmUiDiscovery {
connected: boolean;
models: readonly string[];
loras: readonly string[];
embeddings: readonly string[];
samplers: readonly string[];
schedulers: readonly string[];
}
@@ -128,6 +128,8 @@ describe('createSchoolDialog', () => {
negative: '',
positiveLoras: [],
negativeLoras: [],
positiveEmbeddings: [],
negativeEmbeddings: [],
},
],
presets: [
@@ -43,6 +43,8 @@ function settings(): SwarmUiSettingsFile {
negative: '',
positiveLoras: [],
negativeLoras: [],
positiveEmbeddings: [],
negativeEmbeddings: [],
},
{
id: 'other.safetensors',
@@ -57,6 +59,8 @@ function settings(): SwarmUiSettingsFile {
negative: '',
positiveLoras: [],
negativeLoras: [],
positiveEmbeddings: [],
negativeEmbeddings: [],
},
],
presets: [
@@ -80,6 +84,7 @@ function discovery(overrides: Partial<SwarmUiDiscovery> = {}): SwarmUiDiscovery
connected: true,
models: ['template.safetensors', 'not-in-config.safetensors'],
loras: [],
embeddings: [],
samplers: ['euler'],
schedulers: ['normal'],
...overrides,
@@ -135,7 +140,8 @@ describe('swarmUiSettingsDialog', () => {
expect([...dialog.querySelectorAll('.field__label')].some((node) => node.textContent === t('settingsStyle'))).toBe(true);
expect([...dialog.querySelectorAll('.field__label')].some((node) => node.textContent === t('settingsShotType'))).toBe(true);
expect(dialog.querySelector('.settings-model')?.textContent).toContain(t('settingsModelDefaults'));
expect(dialog.querySelector('.settings-model')?.textContent).toContain(t('settingsPositiveEmbeddings'));
expect([...dialog.querySelectorAll('.settings-kind .settings-subtitle')].some((node) => node.textContent === t('settingsPositiveEmbeddings'))).toBe(true);
const spoiler = dialog.querySelector('details.settings-overrides');
if (!(spoiler instanceof HTMLDetailsElement)) {
throw new Error('overrides spoiler is missing');
@@ -264,8 +264,10 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
numberField(t('settingsSeed'), model.seed, (value) => {
model.seed = value;
}),
loraSection(t('settingsPositiveLoras'), model.positiveLoras),
loraSection(t('settingsNegativeLoras'), model.negativeLoras),
loraSection(t('settingsPositiveLoras'), model.positiveLoras, discovery.loras, t('settingsAddLora')),
loraSection(t('settingsNegativeLoras'), model.negativeLoras, discovery.loras, t('settingsAddLora')),
loraSection(t('settingsPositiveEmbeddings'), model.positiveEmbeddings, discovery.embeddings, t('settingsAddEmbedding')),
loraSection(t('settingsNegativeEmbeddings'), model.negativeEmbeddings, discovery.embeddings, t('settingsAddEmbedding')),
);
return host;
}
@@ -295,16 +297,33 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
}),
);
const positiveLoras = preset.positiveLoras ?? [];
const negativeLoras = preset.negativeLoras ?? [];
preset.positiveLoras = preset.positiveLoras ?? null;
const positiveHost = loraSection(t('settingsPositiveLoras'), positiveLoras, () => {
preset.positiveLoras = positiveLoras;
});
const negativeHost = loraSection(t('settingsNegativeLoras'), negativeLoras, () => {
preset.negativeLoras = negativeLoras;
});
body.append(positiveHost, negativeHost);
const bindList = (
title: string,
current: SwarmUiLoraEntry[] | null | undefined,
names: readonly string[],
addLabel: string,
assign: (items: SwarmUiLoraEntry[]) => void,
): HTMLElement => {
const items = current ?? [];
return loraSection(title, items, names, addLabel, () => {
assign(items);
});
};
body.append(
bindList(t('settingsPositiveLoras'), preset.positiveLoras, discovery.loras, t('settingsAddLora'), (items) => {
preset.positiveLoras = items;
}),
bindList(t('settingsNegativeLoras'), preset.negativeLoras, discovery.loras, t('settingsAddLora'), (items) => {
preset.negativeLoras = items;
}),
bindList(t('settingsPositiveEmbeddings'), preset.positiveEmbeddings, discovery.embeddings, t('settingsAddEmbedding'), (items) => {
preset.positiveEmbeddings = items;
}),
bindList(t('settingsNegativeEmbeddings'), preset.negativeEmbeddings, discovery.embeddings, t('settingsAddEmbedding'), (items) => {
preset.negativeEmbeddings = items;
}),
);
details.append(body);
return details;
}
@@ -367,32 +386,38 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
}
}
function loraSection(title: string, loras: SwarmUiLoraEntry[], onMutate?: () => void): HTMLElement {
function loraSection(
title: string,
items: SwarmUiLoraEntry[],
names: readonly string[],
addLabel: string,
onMutate?: () => void,
): HTMLElement {
const host = el('div', { class: 'settings-loras' });
const heading = el('h4', { class: 'settings-subtitle', text: title });
const rows = el('div', { class: 'settings-lora-rows' });
const paintRows = (): void => {
rows.replaceChildren();
for (const lora of loras) {
const nameField = choiceField('', lora.name, discovery.loras, (value) => {
lora.name = value;
for (const item of items) {
const nameField = choiceField('', item.name, names, (value) => {
item.name = value;
});
const weightInput = el('input', { class: 'input', type: 'number', value: String(lora.weight) });
const weightInput = el('input', { class: 'input', type: 'number', value: String(item.weight) });
weightInput.step = '0.05';
weightInput.min = '-4';
weightInput.max = '4';
weightInput.addEventListener('input', () => {
lora.weight = Number(weightInput.value);
item.weight = Number(weightInput.value);
});
const remove = el('button', {
class: 'button button--small button--danger',
type: 'button',
text: '×',
onClick: () => {
const index = loras.indexOf(lora);
const index = items.indexOf(item);
if (index >= 0) {
loras.splice(index, 1);
items.splice(index, 1);
}
paintRows();
@@ -406,9 +431,9 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
const add = el('button', {
class: 'button button--small',
type: 'button',
text: t('settingsAddLora'),
text: addLabel,
onClick: () => {
loras.push({ name: discovery.loras[0] ?? '', weight: 1 });
items.push({ name: names[0] ?? '', weight: 1 });
onMutate?.();
paintRows();
},
@@ -419,6 +444,10 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
}
function kindSection(title: string, preset: SwarmUiPresetDefinition['avatar']): HTMLElement {
preset.positiveLoras ??= [];
preset.negativeLoras ??= [];
preset.positiveEmbeddings ??= [];
preset.negativeEmbeddings ??= [];
return el(
'div',
{ class: 'settings-kind' },
@@ -432,6 +461,10 @@ export function swarmUiSettingsDialog(initial?: SwarmUiSettingsFile): Promise<Sw
textareaField(t('settingsShotType'), preset.shotType, (value) => {
preset.shotType = value;
}),
loraSection(t('settingsPositiveLoras'), preset.positiveLoras, discovery.loras, t('settingsAddLora')),
loraSection(t('settingsNegativeLoras'), preset.negativeLoras, discovery.loras, t('settingsAddLora')),
loraSection(t('settingsPositiveEmbeddings'), preset.positiveEmbeddings, discovery.embeddings, t('settingsAddEmbedding')),
loraSection(t('settingsNegativeEmbeddings'), preset.negativeEmbeddings, discovery.embeddings, t('settingsAddEmbedding')),
);
}