feat: add setting to include trigger words in LoRA syntax, update UI and functionality, fixes #268

This commit is contained in:
Will Miao
2025-08-05 18:04:10 +08:00
parent 3b96bfe5af
commit 677a239d53
6 changed files with 116 additions and 37 deletions

View File

@@ -555,12 +555,6 @@ export class BaseModelApiClient {
async fetchModelRoots() {
try {
// For checkpoints, use the specific method that considers modelType
// if (this.modelType === 'checkpoints') {
// const pageState = this.getPageState();
// return await this.fetchModelRoots(pageState.modelType || 'checkpoint');
// }
const response = await fetch(this.apiConfig.endpoints.roots);
if (!response.ok) {
throw new Error(`Failed to fetch ${this.apiConfig.config.displayName} roots`);

View File

@@ -1,7 +1,7 @@
import { BaseContextMenu } from './BaseContextMenu.js';
import { ModelContextMenuMixin } from './ModelContextMenuMixin.js';
import { getModelApiClient, resetAndReload } from '../../api/modelApiFactory.js';
import { copyToClipboard, sendLoraToWorkflow } from '../../utils/uiHelpers.js';
import { copyLoraSyntax, sendLoraToWorkflow } from '../../utils/uiHelpers.js';
import { showExcludeModal, showDeleteModal } from '../../utils/modalUtils.js';
import { moveManager } from '../../managers/MoveManager.js';
@@ -37,7 +37,7 @@ export class LoraContextMenu extends BaseContextMenu {
break;
case 'copyname':
// Generate and copy LoRA syntax
this.copyLoraSyntax();
copyLoraSyntax(this.currentCard);
break;
case 'sendappend':
// Send LoRA to workflow (append mode)
@@ -67,16 +67,6 @@ export class LoraContextMenu extends BaseContextMenu {
}
}
// Specific LoRA methods
copyLoraSyntax() {
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}>`;
copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard');
}
sendLoraToWorkflow(replaceMode) {
const card = this.currentCard;
const usageTips = JSON.parse(card.dataset.usage_tips || '{}');

View File

@@ -1,4 +1,4 @@
import { showToast, openCivitai, copyToClipboard, sendLoraToWorkflow, openExampleImagesFolder } from '../../utils/uiHelpers.js';
import { showToast, openCivitai, copyToClipboard, copyLoraSyntax, sendLoraToWorkflow, openExampleImagesFolder } from '../../utils/uiHelpers.js';
import { state, getCurrentPageState } from '../../state/index.js';
import { showModelModal } from './ModelModal.js';
import { toggleShowcase } from './showcase/ShowcaseView.js';
@@ -166,10 +166,7 @@ function handleSendToWorkflow(card, replaceMode, modelType) {
function handleCopyAction(card, 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}>`;
copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard');
copyLoraSyntax(card);
} else if (modelType === MODEL_TYPES.CHECKPOINT) {
// Checkpoint copy functionality - copy checkpoint name
const checkpointName = card.dataset.file_name;

View File

@@ -87,6 +87,11 @@ export class SettingsManager {
if (state.global.settings.default_embedding_root === undefined) {
state.global.settings.default_embedding_root = '';
}
// Set default for includeTriggerWords if undefined
if (state.global.settings.includeTriggerWords === undefined) {
state.global.settings.includeTriggerWords = false;
}
}
async syncSettingsToBackendIfNeeded() {
@@ -213,6 +218,12 @@ export class SettingsManager {
this.updatePathTemplatePreview();
}
// Set include trigger words setting
const includeTriggerWordsCheckbox = document.getElementById('includeTriggerWords');
if (includeTriggerWordsCheckbox) {
includeTriggerWordsCheckbox.checked = state.global.settings.includeTriggerWords || false;
}
// Load base model path mappings
this.loadBaseModelMappings();
@@ -562,6 +573,8 @@ export class SettingsManager {
state.global.settings.autoDownloadExampleImages = value;
} else if (settingKey === 'compact_mode') {
state.global.settings.compactMode = value;
} else if (settingKey === 'include_trigger_words') {
state.global.settings.includeTriggerWords = value;
} else {
// For any other settings that might be added in the future
state.global.settings[settingKey] = value;
@@ -587,9 +600,9 @@ export class SettingsManager {
if (!response.ok) {
throw new Error('Failed to save setting');
}
showToast(`Settings updated: ${settingKey.replace(/_/g, ' ')}`, 'success');
}
showToast(`Settings updated: ${settingKey.replace(/_/g, ' ')}`, 'success');
// Apply frontend settings immediately
this.applyFrontendSettings();
@@ -603,7 +616,7 @@ export class SettingsManager {
}
}
if (settingKey === 'show_only_sfw') {
if (settingKey === 'show_only_sfw' || settingKey === 'blur_mature_content') {
this.reloadContent();
}
@@ -785,20 +798,13 @@ export class SettingsManager {
} else if (this.currentPage === 'checkpoints') {
// Reload the checkpoints without updating folders
await resetAndReload(false);
} else if (this.currentPage === 'embeddings') {
// Reload the embeddings without updating folders
await resetAndReload(false);
}
}
applyFrontendSettings() {
// Apply blur setting to existing content
const blurSetting = state.global.settings.blurMatureContent;
document.querySelectorAll('.model-card[data-nsfw="true"] .card-image').forEach(img => {
if (blurSetting) {
img.classList.add('nsfw-blur');
} else {
img.classList.remove('nsfw-blur');
}
});
// Apply autoplay setting to existing videos in card previews
const autoplayOnHover = state.global.settings.autoplayOnHover;
document.querySelectorAll('.card-preview video').forEach(video => {

View File

@@ -1,4 +1,4 @@
import { getCurrentPageState } from '../state/index.js';
import { state, getCurrentPageState } from '../state/index.js';
import { getStorageItem, setStorageItem } from './storageHelpers.js';
import { NODE_TYPE_ICONS, DEFAULT_NODE_COLOR } from './constants.js';
@@ -285,6 +285,76 @@ export function getNSFWLevelName(level) {
return 'Unknown';
}
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}>`;
// Check if trigger words should be included
const includeTriggerWords = state.global.settings.includeTriggerWords;
if (!includeTriggerWords) {
copyToClipboard(baseSyntax, "LoRA syntax copied to clipboard");
return;
}
// Get trigger words from metadata
const meta = card.dataset.meta ? JSON.parse(card.dataset.meta) : null;
const trainedWords = meta?.trainedWords;
if (
!trainedWords ||
!Array.isArray(trainedWords) ||
trainedWords.length === 0
) {
copyToClipboard(
baseSyntax,
"LoRA syntax copied to clipboard (no trigger words found)"
);
return;
}
let finalSyntax = baseSyntax;
if (trainedWords.length === 1) {
// Single group: append trigger words to the same line
const triggers = trainedWords[0]
.split(",")
.map((word) => word.trim())
.filter((word) => word);
if (triggers.length > 0) {
finalSyntax = `${baseSyntax}, ${triggers.join(", ")}`;
}
copyToClipboard(
finalSyntax,
"LoRA syntax with trigger words copied to clipboard"
);
} else {
// Multiple groups: format with separators
const groups = trainedWords
.map((group) => {
const triggers = group
.split(",")
.map((word) => word.trim())
.filter((word) => word);
return triggers.join(", ");
})
.filter((group) => group);
if (groups.length > 0) {
// Use separator between all groups except the first
finalSyntax = baseSyntax + ", " + groups[0];
for (let i = 1; i < groups.length; i++) {
finalSyntax += `\n${"-".repeat(17)}\n${groups[i]}`;
}
}
copyToClipboard(
finalSyntax,
"LoRA syntax with trigger word groups copied to clipboard"
);
}
}
/**
* Sends LoRA syntax to the active ComfyUI workflow
* @param {string} loraSyntax - The LoRA syntax to send

View File

@@ -308,6 +308,28 @@
</div>
</div>
</div>
<!-- Misc. Section -->
<div class="settings-section">
<h3>Misc.</h3>
<div class="setting-item">
<div class="setting-row">
<div class="setting-info">
<label for="includeTriggerWords">Include Trigger Words in LoRA Syntax</label>
</div>
<div class="setting-control">
<label class="toggle-switch">
<input type="checkbox" id="includeTriggerWords"
onchange="settingsManager.saveToggleSetting('includeTriggerWords', 'include_trigger_words')">
<span class="toggle-slider"></span>
</label>
</div>
</div>
<div class="input-help">
Include trained trigger words when copying LoRA syntax to clipboard
</div>
</div>
</div>
</div>
</div>
</div>