feat: support clip strength in LoRA usage tips, fixes #401

This commit is contained in:
Will Miao
2025-09-22 13:42:36 +08:00
parent 49e03d658b
commit b91f06405d
15 changed files with 90 additions and 19 deletions

View File

@@ -1,7 +1,7 @@
import { BaseContextMenu } from './BaseContextMenu.js';
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
import { copyLoraSyntax, sendLoraToWorkflow } from '../../utils/uiHelpers.js';
import { copyLoraSyntax, sendLoraToWorkflow, buildLoraSyntax } from '../../utils/uiHelpers.js';
import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js';
import { moveManager } from '../../managers/MoveManager.js';
@@ -70,9 +70,8 @@ export class LoraContextMenu extends BaseContextMenu {
sendLoraToWorkflow(replaceMode) {
const card = this.currentCard;
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
const strength = usageTips.strength || 1;
const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
const loraSyntax = buildLoraSyntax(card.dataset.file_name, usageTips);
sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
}
}

View File

@@ -1,4 +1,4 @@
import { showToast, openCivitai, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, openExampleImagesFolder } from '../../utils/uiHelpers.js';
import { showToast, openCivitai, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, openExampleImagesFolder, buildLoraSyntax } from '../../utils/uiHelpers.js';
import { state, getCurrentPageState } from '../../state/index.js';
import { showModelModal } from './ModelModal.js';
import { toggleShowcase } from './showcase/ShowcaseView.js';
@@ -155,8 +155,7 @@ async function toggleFavorite(card) {
function handleSendToWorkflow(card, replaceMode, modelType) {
if (modelType === MODEL_TYPES.LORA) {
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');
const strength = usageTips.strength || 1;
const loraSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
const loraSyntax = buildLoraSyntax(card.dataset.file_name, usageTips);
sendLoraToWorkflow(loraSyntax, replaceMode, 'lora');
} else {
// Checkpoint send functionality - to be implemented

View File

@@ -271,6 +271,7 @@ function renderLoraSpecificContent(lora, escapedWords) {
<option value="strength_min">${translate('modals.model.usageTips.strengthMin', {}, 'Strength Min')}</option>
<option value="strength_max">${translate('modals.model.usageTips.strengthMax', {}, 'Strength Max')}</option>
<option value="strength">${translate('modals.model.usageTips.strength', {}, 'Strength')}</option>
<option value="clip_strength">${translate('modals.model.usageTips.clipStrength', {}, 'Clip Strength')}</option>
<option value="clip_skip">${translate('modals.model.usageTips.clipSkip', {}, 'Clip Skip')}</option>
</select>
<input type="number" id="preset-value" step="0.01" placeholder="${translate('modals.model.usageTips.valuePlaceholder', {}, 'Value')}" style="display:none;">

View File

@@ -1,5 +1,5 @@
import { state, getCurrentPageState } from '../state/index.js';
import { showToast, copyToClipboard, sendLoraToWorkflow } from '../utils/uiHelpers.js';
import { showToast, copyToClipboard, sendLoraToWorkflow, buildLoraSyntax } from '../utils/uiHelpers.js';
import { updateCardsForBulkMode } from '../components/shared/ModelCard.js';
import { modalManager } from './ModalManager.js';
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
@@ -321,8 +321,7 @@ export class BulkManager {
if (metadata) {
const usageTips = JSON.parse(metadata.usageTips || '{}');
const strength = usageTips.strength || 1;
loraSyntaxes.push(`<lora:${metadata.fileName}:${strength}>`);
loraSyntaxes.push(buildLoraSyntax(metadata.fileName, usageTips));
} else {
missingLoras.push(filepath);
}
@@ -361,8 +360,7 @@ export class BulkManager {
if (metadata) {
const usageTips = JSON.parse(metadata.usageTips || '{}');
const strength = usageTips.strength || 1;
loraSyntaxes.push(`<lora:${metadata.fileName}:${strength}>`);
loraSyntaxes.push(buildLoraSyntax(metadata.fileName, usageTips));
} else {
missingLoras.push(filepath);
}

View File

@@ -295,10 +295,48 @@ export function getNSFWLevelName(level) {
return 'Unknown';
}
function parseUsageTipNumber(value) {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
}
if (typeof value === 'string') {
const parsed = parseFloat(value);
if (Number.isFinite(parsed)) {
return parsed;
}
}
return null;
}
export function getLoraStrengthsFromUsageTips(usageTips = {}) {
const parsedStrength = parseUsageTipNumber(usageTips.strength);
const clipStrengthSource = usageTips.clip_strength ?? usageTips.clipStrength;
const parsedClipStrength = parseUsageTipNumber(clipStrengthSource);
return {
strength: parsedStrength !== null ? parsedStrength : 1,
hasStrength: parsedStrength !== null,
clipStrength: parsedClipStrength,
hasClipStrength: parsedClipStrength !== null,
};
}
export function buildLoraSyntax(fileName, usageTips = {}) {
const { strength, hasStrength, clipStrength, hasClipStrength } = getLoraStrengthsFromUsageTips(usageTips);
if (hasClipStrength) {
const modelStrength = hasStrength ? strength : 1;
return `<lora:${fileName}:${modelStrength}:${clipStrength}>`;
}
return `<lora:${fileName}:${strength}>`;
}
export function copyLoraSyntax(card) {
const usageTips = JSON.parse(card.dataset.usage_tips || "{}");
const strength = usageTips.strength || 1;
const baseSyntax = `<lora:${card.dataset.file_name}:${strength}>`;
const baseSyntax = buildLoraSyntax(card.dataset.file_name, usageTips);
// Check if trigger words should be included
const includeTriggerWords = state.global.settings.includeTriggerWords;