From 677a239d532f24471e2a1ca00f0ce0af5da42b40 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Tue, 5 Aug 2025 18:04:10 +0800 Subject: [PATCH] feat: add setting to include trigger words in LoRA syntax, update UI and functionality, fixes #268 --- static/js/api/baseModelApi.js | 6 -- .../components/ContextMenu/LoraContextMenu.js | 14 +--- static/js/components/shared/ModelCard.js | 7 +- static/js/managers/SettingsManager.js | 32 +++++---- static/js/utils/uiHelpers.js | 72 ++++++++++++++++++- .../components/modals/settings_modal.html | 22 ++++++ 6 files changed, 116 insertions(+), 37 deletions(-) diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index decd10cc..b8b0ed09 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -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`); diff --git a/static/js/components/ContextMenu/LoraContextMenu.js b/static/js/components/ContextMenu/LoraContextMenu.js index 298f593d..ee7634b6 100644 --- a/static/js/components/ContextMenu/LoraContextMenu.js +++ b/static/js/components/ContextMenu/LoraContextMenu.js @@ -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 = ``; - - copyToClipboard(loraSyntax, 'LoRA syntax copied to clipboard'); - } - sendLoraToWorkflow(replaceMode) { const card = this.currentCard; const usageTips = JSON.parse(card.dataset.usage_tips || '{}'); diff --git a/static/js/components/shared/ModelCard.js b/static/js/components/shared/ModelCard.js index e8fb6bec..90778f8b 100644 --- a/static/js/components/shared/ModelCard.js +++ b/static/js/components/shared/ModelCard.js @@ -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 = ``; - 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; diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index fa208c5a..78ae35f5 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -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 => { diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index 8a4866c8..e17a19d9 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -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 = ``; + + // 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 diff --git a/templates/components/modals/settings_modal.html b/templates/components/modals/settings_modal.html index 5cff702e..e2d5d542 100644 --- a/templates/components/modals/settings_modal.html +++ b/templates/components/modals/settings_modal.html @@ -308,6 +308,28 @@ + + +
+

Misc.

+
+
+
+ +
+
+ +
+
+
+ Include trained trigger words when copying LoRA syntax to clipboard +
+
+
\ No newline at end of file