diff --git a/static/css/components/recipe-modal.css b/static/css/components/recipe-modal.css index 8d90f0e1..2be60cfd 100644 --- a/static/css/components/recipe-modal.css +++ b/static/css/components/recipe-modal.css @@ -396,14 +396,54 @@ flex-direction: column; } -.recipe-gen-params h3 { - margin-top: 0; +.gen-params-header-row { + display: flex; + align-items: center; + justify-content: space-between; margin-bottom: var(--space-2); - font-size: 1.2em; - color: var(--text-color); padding-bottom: var(--space-1); border-bottom: 1px solid var(--border-color); flex-shrink: 0; + gap: 8px; +} + +.gen-params-header-row h3 { + margin: 0; + font-size: 1.2em; + color: var(--text-color); +} + +/* Inline toggle for lora strip setting */ +.lora-strip-toggle { + flex-shrink: 0; + gap: 6px; +} + +.lora-strip-toggle .inline-toggle-label { + font-size: 0.78em; + white-space: nowrap; + opacity: 0.7; + transition: opacity 0.2s; +} + +.lora-strip-toggle:hover .inline-toggle-label { + opacity: 1; +} + +.lora-strip-toggle .toggle-switch { + width: 32px; + height: 16px; +} + +.lora-strip-toggle .toggle-slider:before { + height: 10px; + width: 10px; + left: 3px; + bottom: 3px; +} + +.lora-strip-toggle .toggle-switch input:checked + .toggle-slider:before { + transform: translateX(16px); } .gen-params-container { diff --git a/static/js/components/RecipeModal.js b/static/js/components/RecipeModal.js index f5209c2d..b461d236 100644 --- a/static/js/components/RecipeModal.js +++ b/static/js/components/RecipeModal.js @@ -2,7 +2,7 @@ import { showToast, copyToClipboard, sendLoraToWorkflow, sendModelPathToWorkflow, openCivitaiByMetadata } from '../utils/uiHelpers.js'; import { translate } from '../utils/i18nHelpers.js'; import { state } from '../state/index.js'; -import { setSessionItem, removeSessionItem } from '../utils/storageHelpers.js'; +import { setSessionItem, removeSessionItem, getStorageItem, setStorageItem } from '../utils/storageHelpers.js'; import { fetchRecipeDetails, updateRecipeMetadata } from '../api/recipeApi.js'; import { downloadManager } from '../managers/DownloadManager.js'; import { MODEL_TYPES } from '../api/apiConfig.js'; @@ -105,6 +105,7 @@ class RecipeModal { init() { this.setupCopyButtons(); + this.setupStripLoraToggle(); this.setupPromptEditors(); // Set up tooltip positioning handlers after DOM is ready document.addEventListener('DOMContentLoaded', () => { @@ -1350,14 +1351,20 @@ class RecipeModal { if (copyPromptBtn) { copyPromptBtn.addEventListener('click', () => { - const promptText = this.currentRecipe?.gen_params?.prompt || ''; + let promptText = this.currentRecipe?.gen_params?.prompt || ''; + if (this.shouldStripLoraOnCopy()) { + promptText = RecipeModal.stripLoraTags(promptText); + } this.copyToClipboard(promptText, 'Prompt copied to clipboard'); }); } if (copyNegativePromptBtn) { copyNegativePromptBtn.addEventListener('click', () => { - const negativePromptText = this.currentRecipe?.gen_params?.negative_prompt || ''; + let negativePromptText = this.currentRecipe?.gen_params?.negative_prompt || ''; + if (this.shouldStripLoraOnCopy()) { + negativePromptText = RecipeModal.stripLoraTags(negativePromptText); + } this.copyToClipboard(negativePromptText, 'Negative prompt copied to clipboard'); }); } @@ -1377,6 +1384,43 @@ class RecipeModal { } } + /** + * Strip tags from prompt text and clean up residual punctuation/whitespace. + * Handles both unescaped () and HTML-escaped (<lora:...>) variants. + * Cleans up artifacts like leading ", ", double commas, and extra whitespace. + */ + static stripLoraTags(text) { + return text + .replace(/]*>/gi, '') + .replace(/<lora:[^&]*>/gi, '') + .replace(/,(\s*,)+/g, ',') + .replace(/^,\s*/, '') + .replace(/,\s*$/, '') + .replace(/\s{2,}/g, ' ') + .trim(); + } + + shouldStripLoraOnCopy() { + const toggle = document.getElementById('stripLoraOnCopyToggle'); + return toggle ? toggle.checked : false; + } + + setupStripLoraToggle() { + const toggle = document.getElementById('stripLoraOnCopyToggle'); + if (!toggle) return; + + const stored = getStorageItem('strip_lora_on_copy'); + if (stored !== null) { + toggle.checked = stored === true; + } + + toggle.addEventListener('change', () => { + const checked = toggle.checked; + setStorageItem('strip_lora_on_copy', checked); + state.global.settings.strip_lora_on_copy = checked; + }); + } + // Fetch recipe syntax from backend and copy to clipboard async fetchAndCopyRecipeSyntax() { if (!this.recipeId) { diff --git a/static/js/state/index.js b/static/js/state/index.js index 3e0166aa..15ead32f 100644 --- a/static/js/state/index.js +++ b/static/js/state/index.js @@ -50,6 +50,7 @@ const DEFAULT_SETTINGS_BASE = Object.freeze({ download_skip_base_models: [], backup_auto_enabled: true, backup_retention_count: 5, + strip_lora_on_copy: false, }); export function createDefaultSettings() { diff --git a/templates/components/recipe_modal.html b/templates/components/recipe_modal.html index adc292d6..74ce975c 100644 --- a/templates/components/recipe_modal.html +++ b/templates/components/recipe_modal.html @@ -22,7 +22,16 @@
-

Generation Parameters

+
+

Generation Parameters

+ +