Riffusion Preset Manager

Saves and manages prompt and negative prompt presets for Riffusion's Compose tab, with 4-row preset display, search, inter-page drag-n-drop, and position editing.

// ==UserScript==
// @name         Riffusion Preset Manager
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Saves and manages prompt and negative prompt presets for Riffusion's Compose tab, with 4-row preset display, search, inter-page drag-n-drop, and position editing.
// @author       Graph1ks with Google AI
// @match        *://www.riffusion.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_addElement
// @grant        GM_info
// @grant        GM_xmlhttpRequest
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const RIFFUSION_ACCENT_COLOR = '#7c3aed';
    const RPM_UI_PREFIX = 'rpm';
    const CREATE_BAR_SELECTOR = 'div[data-sentry-component="Create"] > div[aria-haspopup="dialog"]';

    const MANAGER_CONFIGS = {
        promptRiffusion: {
            id: 'prompt-riffusion',
            uiTitle: 'Riffusion Prompt Presets',
            storageKey: 'riffusionPromptPresets_v1', // Do not change unless the .json format changes for the Prompt Presets. Consider creating a conversation function for old presets
            uiPositionStorageKey: 'riffusionPromptUIPos_v1.1',
            uiSizeStorageKey: 'riffusionPromptUISize_v1.1',
            exportFileName: 'riffusion_prompts.json',
            defaultPresetsUrl: 'https://lyricism.neocities.org/data/prompts.json',
            targetInputSelector: function() {
                const composeTabContent = document.querySelector('div[data-state="active"][role="tabpanel"][id*="compose"]');
                if (!composeTabContent) return [];
                return Array.from(composeTabContent.querySelectorAll('div[data-sentry-component="AdvancedSoundRow"] textarea[placeholder="Enter prompt..."]'));
            },
            applyPreset: applyReactControlledInputPreset,
            itemsPerPageIntegrated: 20, // 4 rows * 5 cols
        },
        negativePromptRiffusion: {
            id: 'negative-prompt-riffusion',
            uiTitle: 'Riffusion Negative Prompt Presets',
            storageKey: 'riffusionNegativePresets_v1', // Do not change unless the .json format changes for the Prompt Presets. Consider creating a conversation function for old presets
            uiPositionStorageKey: 'riffusionNegativeUIPos_v1.1',
            uiSizeStorageKey: 'riffusionNegativeUISize_v1.1',
            exportFileName: 'riffusion_negative_prompts.json',
            defaultPresetsUrl: 'https://lyricism.neocities.org/data/stylereduction.json',
            targetInputSelector: function() {
                const composeTabContent = document.querySelector('div[data-state="active"][role="tabpanel"][id*="compose"]');
                if (!composeTabContent) return [];
                return Array.from(composeTabContent.querySelectorAll('div[data-sentry-component="AdvancedSoundRow"] textarea[placeholder="Negative prompt..."]'));
            },
            applyPreset: applyReactControlledInputPreset,
            itemsPerPageIntegrated: 20, // 4 rows * 5 cols
        }
    };

    GM_addElement('link', { href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200', rel: 'stylesheet' });
    const ICONS = { settings: 'settings', add: 'add_circle', delete: 'delete_sweep', close: 'close', upload: 'file_upload', download: 'file_download', confirm: 'check_circle', cancel: 'cancel', drag_handle: 'drag_indicator', edit: 'edit', save: 'save', apply: 'input', expand_more: 'expand_more', expand_less: 'expand_less', manage: 'tune', sort_alpha: 'sort_by_alpha', prev: 'arrow_back_ios', next: 'arrow_forward_ios', delete_forever: 'delete_forever', library_add: 'library_add', chevron_right: 'chevron_right', search: 'search' };
    function createIcon(iconName, extraClass = '') { const span = document.createElement('span'); span.className = `material-symbols-outlined ${RPM_UI_PREFIX}-icon ${extraClass}`; span.textContent = iconName; return span; }
    function debounce(func, delay) { let timeout; return function(...args) { const context = this; clearTimeout(timeout); timeout = setTimeout(() => func.apply(context, args), delay); }; }

    async function applyReactControlledInputPreset(inputElement, valueToSet, managerId) {
        if (!inputElement) { RiffusionIntegration.log(`[${managerId || RPM_UI_PREFIX}] Apply Error: Target input not found.`); return false; }
        let reactPropsKey = null;
        for (const key in inputElement) { if (key.startsWith("__reactProps$")) { reactPropsKey = key; break; } }
        if (reactPropsKey && inputElement[reactPropsKey] && typeof inputElement[reactPropsKey].onChange === 'function') {
            const reactProps = inputElement[reactPropsKey];
            const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
            if (nativeInputValueSetter) nativeInputValueSetter.call(inputElement, valueToSet);
            else inputElement.value = valueToSet;
            reactProps.onChange({ target: inputElement, currentTarget: inputElement, value: valueToSet });
        } else { inputElement.value = valueToSet; }
        inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
        inputElement.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
        inputElement.focus(); inputElement.blur();
        await new Promise(resolve => setTimeout(resolve, 100));
        let success = inputElement.value === valueToSet;
        if (!success && valueToSet === "" && inputElement.value !== "") { inputElement.value = ""; inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true, cancelable: true })); await new Promise(resolve => setTimeout(resolve, 50)); success = inputElement.value === valueToSet; }
        return success;
    }

    class PresetManager {
        constructor(config) {
            this.config = config; this.presets = [];
            this.ui = { managerWindow: null, presetListContainer: null, newPresetNameInput: null, newPresetValueInput: null, newPresetPositionInput: null, newPresetPositionTotalSpan: null, resizeHandle: null, managerPaginationControls: null };
            this.isDragging = false; this.dragOffsetX = 0; this.dragOffsetY = 0;
            this.isResizing = false; this.resizeStartX = 0; this.resizeStartWidth = 0;
            this.editingPresetIndex = null; this.originalAddButtonText = null; this.originalAddButtonOnClick = null;
            this.draggedPresetElement = null; this.draggedPresetOriginalIndex = -1;
            this.DEFAULT_ITEMS_PER_MANAGER_PAGE = 20;
            this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE;
            this.managerCurrentPage = 1;
            this.pageChangeTimer = null; this.PAGE_CHANGE_DRAG_HOVER_DELAY = 750;
            this.loadPresets();
        }

        addPreset(name, value) { if (this.editingPresetIndex !== null) { this.cancelEditPreset(); } if (!name?.trim() || value === undefined) { alert("Preset name cannot be empty."); return; } const trimmedName = name.trim(); const trimmedValue = value.trim(); if (this.presets.some(p => p.name.toLowerCase() === trimmedName.toLowerCase())) { alert(`A preset with the name "${trimmedName}" already exists.`); return; } this.presets.unshift({ name: trimmedName, value: trimmedValue }); this.savePresets(); this.renderPresetList(); if (this.ui.newPresetNameInput && this.ui.newPresetValueInput) { this.ui.newPresetNameInput.value = ''; this.ui.newPresetValueInput.value = ''; this.ui.newPresetNameInput.focus(); } RiffusionIntegration.refreshIntegratedUI(); }
        loadPresets() { const stored = GM_getValue(this.config.storageKey); if (stored) { try { this.presets = JSON.parse(stored); if (!Array.isArray(this.presets) || !this.presets.every(p => typeof p === 'object' && 'name' in p && 'value' in p)) { this.presets = []; } } catch (e) { this.presets = []; } } }
        savePresets() { GM_setValue(this.config.storageKey, JSON.stringify(this.presets)); RiffusionIntegration.refreshIntegratedUI(); }
        deletePreset(indexInFullArray) { if (this.editingPresetIndex === indexInFullArray) { this.cancelEditPreset(); } if (indexInFullArray < 0 || indexInFullArray >= this.presets.length) return; this.presets.splice(indexInFullArray, 1); this.savePresets(); const totalPages = Math.ceil(this.presets.length / this.itemsPerPageManager); if (this.managerCurrentPage > totalPages && totalPages > 0) { this.managerCurrentPage = totalPages; } else if (totalPages === 0) { this.managerCurrentPage = 1; } this.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); }
        _handlePageButtonDragOver(event, isNextButton) { event.preventDefault(); if (!this.draggedPresetElement) return; const button = event.currentTarget; button.classList.add(`${RPM_UI_PREFIX}-page-btn-drag-hotspot`); if (!this.pageChangeTimer) { this.pageChangeTimer = setTimeout(() => { if (isNextButton) { if (this.managerCurrentPage < Math.ceil(this.presets.length / this.itemsPerPageManager)) { this.managerCurrentPage++; this.renderPresetList(); } } else { if (this.managerCurrentPage > 1) { this.managerCurrentPage--; this.renderPresetList(); } } this.pageChangeTimer = null; }, this.PAGE_CHANGE_DRAG_HOVER_DELAY); } }
        _handlePageButtonDragLeave(event) { event.currentTarget.classList.remove(`${RPM_UI_PREFIX}-page-btn-drag-hotspot`); if (this.pageChangeTimer) { clearTimeout(this.pageChangeTimer); this.pageChangeTimer = null; } }
        renderPresetList() { if (!this.ui.presetListContainer || !this.ui.managerPaginationControls) return; this.ui.presetListContainer.innerHTML = ''; this.ui.managerPaginationControls.innerHTML = ''; if (this.presets.length === 0) { this.ui.presetListContainer.innerHTML = `<p class="${RPM_UI_PREFIX}-no-presets ${RPM_UI_PREFIX}-no-presets-${this.config.id}">No presets yet.</p>`; this.ui.managerPaginationControls.style.display = 'none'; return; } const totalPresets = this.presets.length; const totalPages = Math.ceil(totalPresets / this.itemsPerPageManager); if (this.managerCurrentPage > totalPages && totalPages > 0) this.managerCurrentPage = totalPages; if (this.managerCurrentPage < 1) this.managerCurrentPage = 1; const startIndex = (this.managerCurrentPage - 1) * this.itemsPerPageManager; const endIndex = Math.min(startIndex + this.itemsPerPageManager, totalPresets); const presetsToDisplay = this.presets.slice(startIndex, endIndex); presetsToDisplay.forEach((preset, indexInPage) => { const originalIndex = startIndex + indexInPage; const item = document.createElement('div'); item.className = `${RPM_UI_PREFIX}-preset-item ${RPM_UI_PREFIX}-preset-item-${this.config.id}`; item.title = `Value: ${preset.value}`; item.draggable = true; item.dataset.originalIndex = originalIndex; item.onclick = (e) => { if (e.target.closest('button')) return; if (this.config.id.includes('negative')) { RiffusionIntegration.handleApplyNegativePreset(preset.value); } else { RiffusionIntegration.handleApplyPositivePreset(preset.value); } }; const nameSpan = document.createElement('span'); nameSpan.className = `${RPM_UI_PREFIX}-preset-name`; nameSpan.textContent = preset.name; item.appendChild(nameSpan); const controlsDiv = document.createElement('div'); controlsDiv.className = `${RPM_UI_PREFIX}-preset-item-controls`; const editBtn = document.createElement('button'); editBtn.className = `${RPM_UI_PREFIX}-icon-btn ${RPM_UI_PREFIX}-edit-btn`; editBtn.appendChild(createIcon(ICONS.edit, `${RPM_UI_PREFIX}-edit-icon-fix`)); editBtn.title = "Edit"; editBtn.onclick = (e) => { e.stopPropagation(); this.startEditPreset(originalIndex); }; controlsDiv.appendChild(editBtn); const deleteBtn = document.createElement('button'); deleteBtn.className = `${RPM_UI_PREFIX}-icon-btn ${RPM_UI_PREFIX}-delete-btn`; deleteBtn.appendChild(createIcon(ICONS.delete)); deleteBtn.title = "Delete"; deleteBtn.onclick = (e) => { e.stopPropagation(); this.showConfirm(`Delete preset "${preset.name}"?`, () => this.deletePreset(originalIndex)); }; controlsDiv.appendChild(deleteBtn); item.appendChild(controlsDiv); this.ui.presetListContainer.appendChild(item); item.addEventListener('dragstart', this._handleDragStart.bind(this)); item.addEventListener('dragend', this._handleDragEnd.bind(this)); }); if (totalPages > 1) { this.ui.managerPaginationControls.style.display = 'flex'; const prevBtn = document.createElement('button'); prevBtn.className = `${RPM_UI_PREFIX}-page-btn ${RPM_UI_PREFIX}-page-prev`; prevBtn.appendChild(createIcon(ICONS.prev)); prevBtn.disabled = this.managerCurrentPage <= 1; prevBtn.onclick = () => { if (this.managerCurrentPage > 1) { this.managerCurrentPage--; this.renderPresetList(); } }; prevBtn.addEventListener('dragover', (e) => this._handlePageButtonDragOver(e, false)); prevBtn.addEventListener('dragleave', this._handlePageButtonDragLeave.bind(this)); const pageInfo = document.createElement('span'); pageInfo.className = `${RPM_UI_PREFIX}-page-info`; pageInfo.textContent = `Page ${this.managerCurrentPage} / ${totalPages}`; const nextBtn = document.createElement('button'); nextBtn.className = `${RPM_UI_PREFIX}-page-btn ${RPM_UI_PREFIX}-page-next`; nextBtn.appendChild(createIcon(ICONS.next)); nextBtn.disabled = this.managerCurrentPage >= totalPages; nextBtn.onclick = () => { if (this.managerCurrentPage < totalPages) { this.managerCurrentPage++; this.renderPresetList(); } }; nextBtn.addEventListener('dragover', (e) => this._handlePageButtonDragOver(e, true)); nextBtn.addEventListener('dragleave', this._handlePageButtonDragLeave.bind(this)); this.ui.managerPaginationControls.appendChild(prevBtn); this.ui.managerPaginationControls.appendChild(pageInfo); this.ui.managerPaginationControls.appendChild(nextBtn); } else { this.ui.managerPaginationControls.style.display = 'none'; } }
        _handleDragStart(e) { this.draggedPresetElement = e.target.closest(`.${RPM_UI_PREFIX}-preset-item`); if (!this.draggedPresetElement) return; this.draggedPresetOriginalIndex = parseInt(this.draggedPresetElement.dataset.originalIndex); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', this.draggedPresetOriginalIndex.toString()); this.draggedPresetElement.classList.add(`${RPM_UI_PREFIX}-dragging-item`); this.ui.presetListContainer.classList.add(`${RPM_UI_PREFIX}-list-dragging-active`); }
        _handleDragEnd(e) { if (this.draggedPresetElement) { this.draggedPresetElement.classList.remove(`${RPM_UI_PREFIX}-dragging-item`); } this.ui.presetListContainer.querySelectorAll(`.${RPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${RPM_UI_PREFIX}-drag-over-target`)); this.ui.presetListContainer.classList.remove(`${RPM_UI_PREFIX}-list-dragging-active`); this.draggedPresetElement = null; this.draggedPresetOriginalIndex = -1; if (this.pageChangeTimer) { clearTimeout(this.pageChangeTimer); this.pageChangeTimer = null; } this.ui.managerPaginationControls.querySelectorAll(`.${RPM_UI_PREFIX}-page-btn-drag-hotspot`).forEach(b => b.classList.remove(`${RPM_UI_PREFIX}-page-btn-drag-hotspot`)); RiffusionIntegration.refreshIntegratedUI(); }
        _handleDragOver(e) { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; if (!this.ui.presetListContainer.classList.contains(`${RPM_UI_PREFIX}-list-dragging-active`)) return; const targetItem = e.target.closest(`.${RPM_UI_PREFIX}-preset-item`); this.ui.presetListContainer.querySelectorAll(`.${RPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${RPM_UI_PREFIX}-drag-over-target`)); if (targetItem && targetItem !== this.draggedPresetElement) { targetItem.classList.add(`${RPM_UI_PREFIX}-drag-over-target`); } }
        _handleDragLeave(e) { const targetItem = e.target.closest(`.${RPM_UI_PREFIX}-preset-item`); if (targetItem) { if (!targetItem.contains(e.relatedTarget) ) { targetItem.classList.remove(`${RPM_UI_PREFIX}-drag-over-target`); } } }
        _handleDrop(e) { e.preventDefault(); if (!this.draggedPresetElement || this.draggedPresetOriginalIndex === -1) return; const targetItemElement = e.target.closest(`.${RPM_UI_PREFIX}-preset-item`); this.ui.presetListContainer.querySelectorAll(`.${RPM_UI_PREFIX}-drag-over-target`).forEach(el => el.classList.remove(`${RPM_UI_PREFIX}-drag-over-target`)); if (!targetItemElement || targetItemElement === this.draggedPresetElement) return; const fromIndex = this.draggedPresetOriginalIndex; const toIndex = parseInt(targetItemElement.dataset.originalIndex); if (fromIndex === toIndex) return; const itemToMove = this.presets.splice(fromIndex, 1)[0]; this.presets.splice(toIndex, 0, itemToMove); this.savePresets(); this.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); }
        startEditPreset(index) { if (index < 0 || index >= this.presets.length) return; this.editingPresetIndex = index; const preset = this.presets[index]; if (this.ui.newPresetNameInput && this.ui.newPresetValueInput && this.ui.newPresetPositionInput && this.ui.newPresetPositionTotalSpan && this.ui.managerWindow) { this.ui.newPresetNameInput.value = preset.name; this.ui.newPresetValueInput.value = preset.value; this.ui.newPresetPositionInput.value = index + 1; this.ui.newPresetPositionInput.max = this.presets.length.toString(); this.ui.newPresetPositionTotalSpan.textContent = ` of ${this.presets.length}`; this.ui.newPresetPositionInput.style.display = 'inline-block'; this.ui.newPresetPositionTotalSpan.style.display = 'inline-block'; this.ui.newPresetNameInput.focus(); const addButton = this.ui.managerWindow.querySelector(`.${RPM_UI_PREFIX}-add-btn-${this.config.id}`); if (addButton) { if (!this.originalAddButtonOnClick) { this.originalAddButtonText = addButton.innerHTML; this.originalAddButtonOnClick = addButton.onclick; } addButton.innerHTML = ''; addButton.appendChild(createIcon(ICONS.save)); addButton.appendChild(document.createTextNode(' Save Edit')); addButton.onclick = () => this.saveEditedPreset(); } this.addCancelEditButton(addButton); } }
        addCancelEditButton(addButtonReference) { if (!this.ui.managerWindow) return; let cancelBtn = this.ui.managerWindow.querySelector(`.${RPM_UI_PREFIX}-cancel-edit-btn`); const addControlsContainer = this.ui.managerWindow.querySelector(`.${RPM_UI_PREFIX}-add-controls-container`); if (!cancelBtn && addControlsContainer) { cancelBtn = document.createElement('button'); cancelBtn.className = `${RPM_UI_PREFIX}-btn ${RPM_UI_PREFIX}-btn-secondary ${RPM_UI_PREFIX}-cancel-edit-btn`; cancelBtn.appendChild(createIcon(ICONS.cancel)); cancelBtn.appendChild(document.createTextNode(' Cancel')); addControlsContainer.insertBefore(cancelBtn, addButtonReference); } if(cancelBtn) { cancelBtn.onclick = () => this.cancelEditPreset(); cancelBtn.style.display = 'inline-flex'; } }
        removeCancelEditButton() { if (!this.ui.managerWindow) return; const cancelBtn = this.ui.managerWindow.querySelector(`.${RPM_UI_PREFIX}-cancel-edit-btn`); if (cancelBtn) { cancelBtn.remove(); } }
        cancelEditPreset() { this.editingPresetIndex = null; if (this.ui.newPresetNameInput && this.ui.newPresetValueInput) { this.ui.newPresetNameInput.value = ''; this.ui.newPresetValueInput.value = ''; } if(this.ui.newPresetPositionInput) {this.ui.newPresetPositionInput.value = ''; this.ui.newPresetPositionInput.style.display = 'none'; } if(this.ui.newPresetPositionTotalSpan) { this.ui.newPresetPositionTotalSpan.textContent = ''; this.ui.newPresetPositionTotalSpan.style.display = 'none'; } this.restoreAddButton(); this.removeCancelEditButton(); }
        restoreAddButton() { if (!this.ui.managerWindow) return; const addButton = this.ui.managerWindow.querySelector(`.${RPM_UI_PREFIX}-add-btn-${this.config.id}`); if (addButton && this.originalAddButtonText !== null && this.originalAddButtonOnClick !== null) { addButton.innerHTML = this.originalAddButtonText; addButton.onclick = this.originalAddButtonOnClick; this.originalAddButtonText = null; this.originalAddButtonOnClick = null; } else if (addButton) { addButton.innerHTML = ''; addButton.appendChild(createIcon(ICONS.add)); addButton.appendChild(document.createTextNode(' Add Preset')); addButton.onclick = () => this.addPreset(this.ui.newPresetNameInput.value, this.ui.newPresetValueInput.value); } }
        saveEditedPreset() { if (this.editingPresetIndex === null || this.editingPresetIndex < 0 || this.editingPresetIndex >= this.presets.length) { this.cancelEditPreset(); return; } if (!this.ui.newPresetNameInput || !this.ui.newPresetValueInput || !this.ui.newPresetPositionInput) return; const newName = this.ui.newPresetNameInput.value.trim(); const newValue = this.ui.newPresetValueInput.value.trim(); if (!newName) { alert("Preset name cannot be empty."); return; } const conflictingPreset = this.presets.find((p, i) => i !== this.editingPresetIndex && p.name.toLowerCase() === newName.toLowerCase() ); if (conflictingPreset) { alert(`A preset with the name "${newName}" already exists.`); return; } let newPosition = parseInt(this.ui.newPresetPositionInput.value, 10) -1; if (isNaN(newPosition) || newPosition < 0 || newPosition >= this.presets.length) { alert(`Invalid position. Must be between 1 and ${this.presets.length}.`); this.ui.newPresetPositionInput.focus(); return; } this.presets[this.editingPresetIndex].name = newName; this.presets[this.editingPresetIndex].value = newValue; if (newPosition !== this.editingPresetIndex) { const itemToMove = this.presets.splice(this.editingPresetIndex, 1)[0]; this.presets.splice(newPosition, 0, itemToMove); } this.savePresets(); this.renderPresetList(); this.cancelEditPreset(); if (this.ui.newPresetNameInput) this.ui.newPresetNameInput.focus(); RiffusionIntegration.refreshIntegratedUI(); }
        createManagerWindow() { if (document.getElementById(`${RPM_UI_PREFIX}-window-${this.config.id}`)) { this.ui.managerWindow = document.getElementById(`${RPM_UI_PREFIX}-window-${this.config.id}`); return; } this.ui.managerWindow = document.createElement('div'); this.ui.managerWindow.id = `${RPM_UI_PREFIX}-window-${this.config.id}`; this.ui.managerWindow.className = `${RPM_UI_PREFIX}-window`; this.ui.managerWindow.style.display = 'none'; const header = document.createElement('div'); header.className = `${RPM_UI_PREFIX}-header`; const dragHandle = createIcon(ICONS.drag_handle, `${RPM_UI_PREFIX}-drag-handle-icon`); header.appendChild(dragHandle); const titleSpan = document.createElement('span'); titleSpan.className = `${RPM_UI_PREFIX}-header-title`; titleSpan.textContent = this.config.uiTitle; header.appendChild(titleSpan); const headerControls = document.createElement('div'); headerControls.className = `${RPM_UI_PREFIX}-header-controls`; const sortBtn = this.createHeaderButton(ICONS.sort_alpha, "Sort A-Z", this.sortPresetsByName.bind(this)); headerControls.appendChild(sortBtn); const closeBtn = this.createHeaderButton(ICONS.close, "Close", this.toggleManagerWindow.bind(this)); headerControls.appendChild(closeBtn); header.appendChild(headerControls); this.ui.managerWindow.appendChild(header); this.ui.presetListContainer = document.createElement('div'); this.ui.presetListContainer.className = `${RPM_UI_PREFIX}-list-container`; this.ui.presetListContainer.addEventListener('dragover', this._handleDragOver.bind(this)); this.ui.presetListContainer.addEventListener('drop', this._handleDrop.bind(this)); this.ui.presetListContainer.addEventListener('dragleave', this._handleDragLeave.bind(this)); this.ui.managerWindow.appendChild(this.ui.presetListContainer); this.ui.managerPaginationControls = document.createElement('div'); this.ui.managerPaginationControls.className = `${RPM_UI_PREFIX}-manager-pagination-controls`; this.ui.managerWindow.appendChild(this.ui.managerPaginationControls); const addArea = document.createElement('div'); addArea.className = `${RPM_UI_PREFIX}-add-area`; const nameAndPositionContainer = document.createElement('div'); nameAndPositionContainer.className = `${RPM_UI_PREFIX}-name-pos-container`; this.ui.newPresetNameInput = document.createElement('input'); this.ui.newPresetNameInput.type = 'text'; this.ui.newPresetNameInput.placeholder = 'Preset Name'; this.ui.newPresetNameInput.className = `${RPM_UI_PREFIX}-input ${RPM_UI_PREFIX}-name-input`; nameAndPositionContainer.appendChild(this.ui.newPresetNameInput); this.ui.newPresetPositionInput = document.createElement('input'); this.ui.newPresetPositionInput.type = 'number'; this.ui.newPresetPositionInput.className = `${RPM_UI_PREFIX}-input ${RPM_UI_PREFIX}-pos-input`; this.ui.newPresetPositionInput.style.display = 'none'; this.ui.newPresetPositionInput.min = "1"; nameAndPositionContainer.appendChild(this.ui.newPresetPositionInput); this.ui.newPresetPositionTotalSpan = document.createElement('span'); this.ui.newPresetPositionTotalSpan.className = `${RPM_UI_PREFIX}-pos-total`; this.ui.newPresetPositionTotalSpan.style.display = 'none'; nameAndPositionContainer.appendChild(this.ui.newPresetPositionTotalSpan); addArea.appendChild(nameAndPositionContainer); this.ui.newPresetValueInput = document.createElement('input'); this.ui.newPresetValueInput.type = 'text'; this.ui.newPresetValueInput.placeholder = this.config.id.includes('negative') ? 'Negative prompt text...' : 'Prompt text...'; this.ui.newPresetValueInput.className = `${RPM_UI_PREFIX}-input`; addArea.appendChild(this.ui.newPresetValueInput); const addControlsContainer = document.createElement('div'); addControlsContainer.className = `${RPM_UI_PREFIX}-add-controls-container`; const addButton = document.createElement('button'); addButton.className = `${RPM_UI_PREFIX}-btn ${RPM_UI_PREFIX}-add-btn ${RPM_UI_PREFIX}-add-btn-${this.config.id}`; this.originalAddButtonText = `${createIcon(ICONS.add).outerHTML}Add Preset`; this.originalAddButtonOnClick = () => this.addPreset(this.ui.newPresetNameInput.value, this.ui.newPresetValueInput.value); addButton.innerHTML = this.originalAddButtonText; addButton.onclick = this.originalAddButtonOnClick; addControlsContainer.appendChild(addButton); addArea.appendChild(addControlsContainer); this.ui.managerWindow.appendChild(addArea); const footer = document.createElement('div'); footer.className = `${RPM_UI_PREFIX}-footer`; const importBtn = this.createFooterButton(ICONS.upload, "Import", this.handleImport.bind(this)); const exportBtn = this.createFooterButton(ICONS.download, "Export", this.handleExport.bind(this)); const loadDefaultsBtn = this.createFooterButton(ICONS.library_add, "Load Defaults", this.loadDefaultPresets.bind(this)); const deleteAllBtn = this.createFooterButton(ICONS.delete_forever, "Delete All", this.deleteAllPresets.bind(this), `${RPM_UI_PREFIX}-footer-btn-danger`); footer.appendChild(importBtn); footer.appendChild(exportBtn); footer.appendChild(loadDefaultsBtn); footer.appendChild(deleteAllBtn); this.ui.managerWindow.appendChild(footer); this.ui.resizeHandle = document.createElement('div'); this.ui.resizeHandle.className = `${RPM_UI_PREFIX}-resize-handle`; this.ui.resizeHandle.innerHTML = '↔'; this.ui.managerWindow.appendChild(this.ui.resizeHandle); this.makeResizable(this.ui.resizeHandle, this.ui.managerWindow); document.body.appendChild(this.ui.managerWindow); this.makeDraggable(header, this.ui.managerWindow); this.loadWindowPosition(); this.loadWindowSize(); this.ensureWindowInViewport(); }
        createHeaderButton(icon, title, onClickHandler) { const btn = document.createElement('button'); btn.className = `${RPM_UI_PREFIX}-icon-btn ${RPM_UI_PREFIX}-header-action-btn`; btn.title = title; btn.appendChild(createIcon(icon)); btn.onclick = onClickHandler; return btn; }
        sortPresetsByName() { this.presets.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); this.savePresets(); this.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); }
        createFooterButton(icon, title, onClickHandler, extraClass = '') { const btn = document.createElement('button'); btn.className = `${RPM_UI_PREFIX}-icon-btn ${RPM_UI_PREFIX}-footer-btn ${extraClass}`; btn.title = title; btn.appendChild(createIcon(icon)); btn.onclick = onClickHandler; return btn; }
        toggleManagerWindow() { if (!this.ui.managerWindow || !document.body.contains(this.ui.managerWindow)) { this.createManagerWindow(); } if (this.editingPresetIndex !== null) { this.cancelEditPreset(); } const win = this.ui.managerWindow; const isVisible = win.style.display === 'block'; win.style.display = isVisible ? 'none' : 'block'; if (!isVisible) { this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE; this.managerCurrentPage = 1; this.loadWindowSize(); this.renderPresetList(); if (this.ui.newPresetNameInput) this.ui.newPresetNameInput.focus(); this.ensureWindowInViewport(); } }
        makeDraggable(dragHandle, elementToDrag) { dragHandle.onmousedown = (e) => { if (e.target.closest(`.${RPM_UI_PREFIX}-icon-btn, .${RPM_UI_PREFIX}-resize-handle, .${RPM_UI_PREFIX}-header-title`)) { if (e.target.closest(`.${RPM_UI_PREFIX}-header-title`) || e.target.classList.contains(`${RPM_UI_PREFIX}-drag-handle-icon`)) {} else return; } e.preventDefault(); this.isDragging = true; const rect = elementToDrag.getBoundingClientRect(); this.dragOffsetX = e.clientX - rect.left; this.dragOffsetY = e.clientY - rect.top; document.onmousemove = this.dragElement.bind(this); document.onmouseup = this.stopDrag.bind(this); dragHandle.style.cursor = 'grabbing'; const dragIcon = dragHandle.querySelector(`.${RPM_UI_PREFIX}-drag-handle-icon`); if(dragIcon) dragIcon.style.cursor = 'grabbing'; }; }
        dragElement(e) { if (!this.isDragging || !this.ui.managerWindow) return; e.preventDefault(); this.ui.managerWindow.style.left = `${e.clientX - this.dragOffsetX}px`; this.ui.managerWindow.style.top = `${e.clientY - this.dragOffsetY}px`; }
        stopDrag() { if (!this.isDragging) return; this.isDragging = false; document.onmouseup = null; document.onmousemove = null; const dragHandle = this.ui.managerWindow?.querySelector(`.${RPM_UI_PREFIX}-header`); if(dragHandle) dragHandle.style.cursor = 'grab'; const dragIcon = this.ui.managerWindow?.querySelector(`.${RPM_UI_PREFIX}-drag-handle-icon`); if(dragIcon) dragIcon.style.cursor = 'grab'; this.saveWindowPosition(); this.ensureWindowInViewport(); }
        makeResizable(resizeHandle, elementToResize) { resizeHandle.onmousedown = (e) => { e.preventDefault(); e.stopPropagation(); this.isResizing = true; this.resizeStartX = e.clientX; this.resizeStartWidth = parseInt(document.defaultView.getComputedStyle(elementToResize).width, 10); document.body.style.cursor = 'ew-resize'; resizeHandle.style.cursor = 'ew-resize'; document.onmousemove = this.resizeElement.bind(this); document.onmouseup = this.stopResize.bind(this); }; }
        resizeElement(e) { if (!this.isResizing || !this.ui.managerWindow) return; e.preventDefault(); const cs = document.defaultView.getComputedStyle(this.ui.managerWindow); const minWidth = parseInt(cs.minWidth, 10) || 300; const maxWidth = window.innerWidth * 0.95; let newWidth = this.resizeStartWidth + (e.clientX - this.resizeStartX); newWidth = Math.max(minWidth, Math.min(newWidth, maxWidth)); this.ui.managerWindow.style.width = `${newWidth}px`; }
        stopResize() { if (!this.isResizing) return; this.isResizing = false; document.onmousemove = null; document.onmouseup = null; if (this.ui.resizeHandle) this.ui.resizeHandle.style.cursor = 'ew-resize'; document.body.style.cursor = 'default'; this.saveWindowSize(); this.ensureWindowInViewport(); }
        updateVisibleItemsInManager() { if (this.ui.managerWindow && this.ui.managerWindow.style.display !== 'none') { this.itemsPerPageManager = this.DEFAULT_ITEMS_PER_MANAGER_PAGE; this.renderPresetList(); } }
        saveWindowSize() { if (!this.ui.managerWindow || !this.config.uiSizeStorageKey) return; const size = { width: this.ui.managerWindow.style.width }; if (size.width) GM_setValue(this.config.uiSizeStorageKey, JSON.stringify(size)); }
        applyDefaultWidth() { if (!this.ui.managerWindow) return; this.ui.managerWindow.style.width = "1200px"; }
        setDefaultSize() { if (!this.ui.managerWindow) return; this.applyDefaultWidth(); }
        loadWindowSize() { if (!this.ui.managerWindow || !this.config.uiSizeStorageKey) return this.setDefaultSize(); const storedSize = GM_getValue(this.config.uiSizeStorageKey); if (storedSize) { try { const size = JSON.parse(storedSize); if (size.width && CSS.supports('width', size.width)) this.ui.managerWindow.style.width = size.width; else this.applyDefaultWidth(); } catch (e) { this.setDefaultSize(); } } else { this.setDefaultSize(); } this.updateVisibleItemsInManager(); }
        saveWindowPosition() { if(!this.ui.managerWindow) return; const pos = { top: this.ui.managerWindow.offsetTop, left: this.ui.managerWindow.offsetLeft }; GM_setValue(this.config.uiPositionStorageKey, JSON.stringify(pos)); }
        loadWindowPosition() { if(!this.ui.managerWindow) return; const storedPos = GM_getValue(this.config.uiPositionStorageKey); if (storedPos) { try { const pos = JSON.parse(storedPos); if (typeof pos.top === 'number' && typeof pos.left === 'number') { this.ui.managerWindow.style.top = `${pos.top}px`; this.ui.managerWindow.style.left = `${pos.left}px`; } else { this.setDefaultPosition(); } } catch (e) { this.setDefaultPosition(); } } else { this.setDefaultPosition(); } }
        setDefaultPosition() { if(!this.ui.managerWindow) return; const winWidth = parseInt(this.ui.managerWindow.style.width || "1200"); const winHeight = this.ui.managerWindow.offsetHeight; let defaultTop = (window.innerHeight - winHeight) / 2 ; let defaultLeft = (window.innerWidth - winWidth) / 2; defaultTop = Math.max(10, Math.min(defaultTop, window.innerHeight - winHeight - 10)); defaultLeft = Math.max(10, Math.min(defaultLeft, window.innerWidth - winWidth - 10)); this.ui.managerWindow.style.top = `${Math.round(defaultTop)}px`; this.ui.managerWindow.style.left = `${Math.round(defaultLeft)}px`; }
        ensureWindowInViewport() { if (!this.ui.managerWindow) return; const win = this.ui.managerWindow; const cs = window.getComputedStyle(win); let currentL = parseFloat(win.style.left); if (isNaN(currentL)) currentL = (window.innerWidth - (parseFloat(win.style.width) || parseFloat(cs.minWidth) || 0)) / 2; let currentT = parseFloat(win.style.top); if (isNaN(currentT)) currentT = (window.innerHeight - win.offsetHeight) / 2; let rect; const originalDisplay = win.style.display; if (originalDisplay === 'none') { win.style.visibility = 'hidden'; win.style.display = 'block'; rect = win.getBoundingClientRect(); win.style.display = originalDisplay; win.style.visibility = 'visible'; } else { rect = win.getBoundingClientRect(); } const vpW = window.innerWidth; const vpH = window.innerHeight; let newL = currentL; let newT = currentT; if (rect.left < 0) newL = 0; if (rect.right > vpW) newL = Math.max(0, vpW - rect.width); if (rect.top < 0) newT = 0; if (rect.bottom > vpH) newT = Math.max(0, vpH - rect.height); if (Math.round(newL) !== Math.round(currentL) || Math.round(newT) !== Math.round(currentT)) { win.style.left = `${Math.round(newL)}px`; win.style.top = `${Math.round(newT)}px`; this.saveWindowPosition(); } let currentWidth = rect.width; let newWidth = currentWidth; const minAllowedWidth = parseFloat(cs.minWidth) || 300; const maxAllowedWidth = parseFloat(cs.maxWidth) || (vpW * 0.95); if (currentWidth > vpW - 20) newWidth = vpW - 20; newWidth = Math.max(minAllowedWidth, Math.min(newWidth, maxAllowedWidth)); if (Math.round(newWidth) !== Math.round(currentWidth)) { if(Math.round(newWidth) !== Math.round(currentWidth)) win.style.width = `${Math.round(newWidth)}px`; this.saveWindowSize(); } }
        showConfirm(message, onConfirm) { if (!sharedConfirmDialog.dialog) sharedConfirmDialog.create(); sharedConfirmDialog.show(message, onConfirm); }
        handleExport() { const dataStr = JSON.stringify(this.presets, null, 2); const blob = new Blob([dataStr], {type: "application/json"}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = this.config.exportFileName; a.click(); URL.revokeObjectURL(url); a.remove(); }
        handleImport() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.onchange = async (e) => { const file = e.target.files[0]; if (!file) return; const text = await file.text(); try { const imported = JSON.parse(text); if (Array.isArray(imported) && imported.every(p => typeof p === 'object' && 'name' in p && 'value' in p)) { const existingNames = new Set(this.presets.map(p => p.name.toLowerCase())); let added = 0; imported.reverse().forEach(pNew => { if (typeof pNew.name === 'string' && typeof pNew.value === 'string' && !existingNames.has(pNew.name.toLowerCase())) { this.presets.unshift({name: pNew.name.trim(), value: pNew.value.trim()}); existingNames.add(pNew.name.toLowerCase()); added++; } }); this.savePresets(); this.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); alert(`Imported ${added} new presets for ${this.config.uiTitle}.`); } else { alert("Invalid file format."); } } catch (err) { alert("Error reading file: " + err.message); } }; input.click(); }
        deleteAllPresets() { this.showConfirm(`Are you sure you want to delete ALL ${this.presets.length} presets for '${this.config.uiTitle}'? This action cannot be undone.`, () => { if (this.editingPresetIndex !== null) this.cancelEditPreset(); this.presets = []; this.managerCurrentPage = 1; this.savePresets(); this.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); alert('All presets deleted.'); }); }
        loadDefaultPresets() { if (!this.config.defaultPresetsUrl) { alert("No default presets URL configured for this manager."); return; } const self = this; GM_xmlhttpRequest({ method: "GET", url: this.config.defaultPresetsUrl, onload: function(response) { try { if (response.status < 200 || response.status >= 300) { throw new Error(`HTTP error! status: ${response.status} ${response.statusText}`); } const defaultData = JSON.parse(response.responseText); if (!Array.isArray(defaultData) || !defaultData.every(p => typeof p === 'object' && 'name' in p && typeof p.name === 'string' && 'value' in p && typeof p.value === 'string')) { alert("Invalid default presets data structure from URL."); return; } const existingNames = new Set(self.presets.map(p => p.name.toLowerCase())); let addedCount = 0; let skippedCount = 0; defaultData.reverse().forEach(pDefault => { const trimmedName = pDefault.name.trim(); const trimmedValue = pDefault.value.trim(); if (trimmedName && !existingNames.has(trimmedName.toLowerCase())) { self.presets.unshift({ name: trimmedName, value: trimmedValue }); existingNames.add(trimmedName.toLowerCase()); addedCount++; } else { skippedCount++; } }); if (addedCount > 0) self.savePresets(); self.renderPresetList(); RiffusionIntegration.refreshIntegratedUI(); alert(`Added ${addedCount} new default presets for ${self.config.uiTitle}. ${skippedCount} duplicates were skipped.`); } catch (error) { alert(`Failed to process default presets: ${error.message}`); } }, onerror: function() { alert(`Failed to load default presets for ${self.config.uiTitle}. Network error.`); } }); }
    }

    const sharedConfirmDialog = { dialog: null, create: function() { this.dialog = document.createElement('div'); this.dialog.id = `${RPM_UI_PREFIX}-confirm-dialog-shared`; this.dialog.className = `${RPM_UI_PREFIX}-modal-overlay`; this.dialog.style.display = 'none'; const box = document.createElement('div'); box.className = `${RPM_UI_PREFIX}-dialog-box`; const msgP = document.createElement('p'); msgP.id = `${RPM_UI_PREFIX}-confirm-msg-shared`; const btnsDiv = document.createElement('div'); btnsDiv.className = `${RPM_UI_PREFIX}-dialog-buttons`; const yesBtn = document.createElement('button'); yesBtn.id = `${RPM_UI_PREFIX}-confirm-yes-shared`; yesBtn.className = `${RPM_UI_PREFIX}-btn ${RPM_UI_PREFIX}-btn-danger`; yesBtn.appendChild(createIcon(ICONS.confirm)); yesBtn.appendChild(document.createTextNode(' Confirm')); const noBtn = document.createElement('button'); noBtn.id = `${RPM_UI_PREFIX}-confirm-no-shared`; noBtn.className = `${RPM_UI_PREFIX}-btn ${RPM_UI_PREFIX}-btn-secondary`; noBtn.appendChild(createIcon(ICONS.cancel)); noBtn.appendChild(document.createTextNode(' Cancel')); btnsDiv.appendChild(yesBtn); btnsDiv.appendChild(noBtn); box.appendChild(msgP); box.appendChild(btnsDiv); this.dialog.appendChild(box); document.body.appendChild(this.dialog); }, show: function(message, onConfirmCallback) { if (!this.dialog) this.create(); this.dialog.querySelector(`#${RPM_UI_PREFIX}-confirm-msg-shared`).textContent = message; this.dialog.style.display = 'flex'; const yesBtn = this.dialog.querySelector(`#${RPM_UI_PREFIX}-confirm-yes-shared`); const noBtn = this.dialog.querySelector(`#${RPM_UI_PREFIX}-confirm-no-shared`); const newYes = yesBtn.cloneNode(true); yesBtn.parentNode.replaceChild(newYes, yesBtn); const newNo = noBtn.cloneNode(true); noBtn.parentNode.replaceChild(newNo, noBtn); newYes.onclick = () => { this.dialog.style.display = 'none'; onConfirmCallback(); }; newNo.onclick = () => { this.dialog.style.display = 'none'; }; } };

    const RiffusionIntegration = {
        ui: { mainCollapsibleSection: null, collapsibleHeader: null, collapsibleContent: null, promptPresetTargetSelector: null, promptPresetListContainer: null, negativePresetTargetSelector: null, negativePresetListContainer: null, isAccordionOpen: GM_getValue('rpmAccordionOpenState', false), promptIntegratedCurrentPage: GM_getValue('rpmPromptIntegratedPage_v1', 1), negativeIntegratedCurrentPage: GM_getValue('rpmNegativeIntegratedPage_v1', 1), promptSearchTerm: '', negativeSearchTerm: '' },
        promptManager: null, negativePromptManager: null, selectedPositivePromptTargetIndex: 0, selectedNegativePromptTargetIndex: 0,
        isIntegratedUISetup: false, DEBUG_MODE: false,
        observerInstance: null, observerOptions: { childList: true, subtree: true, attributes: true, attributeFilter: ['data-state', 'class', 'style', 'id'] },
        _checkForComposeTimeout: null, _searchDebouncers: {},
        log(...args) { if (this.DEBUG_MODE) { console.log(`[${RPM_UI_PREFIX} DEBUG]`, ...args); } },
        init(promptMgr, negativeMgr) { this.promptManager = promptMgr; this.negativePromptManager = negativeMgr; this.ui.isAccordionOpen = GM_getValue('rpmAccordionOpenState', false); this.ui.promptIntegratedCurrentPage = parseInt(GM_getValue('rpmPromptIntegratedPage_v1', 1), 10) || 1; this.ui.negativeIntegratedCurrentPage = parseInt(GM_getValue('rpmNegativeIntegratedPage_v1', 1), 10) || 1; this.observerInstance = new MutationObserver((mutations) => { let triggerComposeCheck = false; let advancedSoundRowChanged = false; for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'data-state') { const targetElement = mutation.target; if (targetElement.nodeType === Node.ELEMENT_NODE && targetElement.matches) { if (targetElement.matches(CREATE_BAR_SELECTOR) && targetElement.getAttribute('data-state') === 'open') { triggerComposeCheck = true; } else if (targetElement.matches('div[role="dialog"][id*="radix-"]')) { triggerComposeCheck = true; } else if (targetElement.matches('button[role="tab"][id*="radix-"]')) { triggerComposeCheck = true; } } } if (mutation.type === 'childList' && !triggerComposeCheck) { const checkNodes = (nodeList) => { for (const node of Array.from(nodeList)) { if (node.nodeType === Node.ELEMENT_NODE && node.matches) { if (node.matches('div[role="dialog"][id*="radix-"]') || node.matches('div[role="tabpanel"][id*="compose"]') || node.matches('.overflow-x-hidden.overflow-y-auto.sm\\:pb-4') || node.matches('div[data-sentry-component="DetailsSection"]')) { if (!node.closest(`#${RPM_UI_PREFIX}-main-collapsible-section`)) return true; } } } return false; }; if (checkNodes(mutation.addedNodes) || checkNodes(mutation.removedNodes)) { triggerComposeCheck = true; } } if (this.isIntegratedUISetup && this.ui.mainCollapsibleSection && document.body.contains(this.ui.mainCollapsibleSection)) { let changedInThisMutation = false; if (mutation.target.nodeType === Node.ELEMENT_NODE && mutation.target.closest && mutation.target.closest('div[data-sentry-component="AdvancedSoundRow"], div[data-sentry-component="AdvancedCompose"]')) { changedInThisMutation = true; } else if (mutation.type === 'childList') { const nodesToCheck = Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)); for (const node of nodesToCheck) { if (node.nodeType === Node.ELEMENT_NODE && node.closest && node.closest('div[data-sentry-component="AdvancedSoundRow"], div[data-sentry-component="AdvancedCompose"]')) { changedInThisMutation = true; break; } } } if (changedInThisMutation) { advancedSoundRowChanged = true; } } if (triggerComposeCheck && advancedSoundRowChanged) break; } if (triggerComposeCheck) { clearTimeout(this._checkForComposeTimeout); this._checkForComposeTimeout = setTimeout(() => this.checkForComposeUIDisplay(), 350); } if (advancedSoundRowChanged) { this.disconnectObserver(); try { this.updatePositivePromptTargetSelector(); this.updateNegativePromptTargetSelector(); } finally { this.connectObserver(); } } }); this.connectObserver(); setTimeout(() => this.checkForComposeUIDisplay(), 1200); },
        disconnectObserver() { if (this.observerInstance) { this.observerInstance.disconnect(); } },
        connectObserver() { if (this.observerInstance) { this.observerInstance.observe(document.body, this.observerOptions); } },
        async checkForComposeUIDisplay() { this.disconnectObserver(); const createBar = document.querySelector(CREATE_BAR_SELECTOR); let dialogShouldBeOpenBasedOnCreateBar = createBar && createBar.getAttribute('data-state') === 'open'; let composeDropdown = document.querySelector('div[role="dialog"][data-state="open"][id*="radix-"]'); if (!composeDropdown && dialogShouldBeOpenBasedOnCreateBar) { await new Promise(resolve => setTimeout(resolve, 200)); composeDropdown = document.querySelector('div[role="dialog"][data-state="open"][id*="radix-"]'); } if (!composeDropdown) { if (this.isIntegratedUISetup) { this.isIntegratedUISetup = false; } this.connectObserver(); return; } const composeTabButton = composeDropdown.querySelector('button[role="tab"][id*="compose"]'); const composeTabContent = composeDropdown.querySelector('div[role="tabpanel"][id*="compose"]'); if (composeTabButton && composeTabButton.getAttribute('data-state') === 'active' && composeTabContent && composeTabContent.getAttribute('data-state') === 'active') { const scrollableContainer = composeTabContent.querySelector('.overflow-x-hidden.overflow-y-auto.sm\\:pb-4'); if (!scrollableContainer) { this.connectObserver(); return; } const detailsSection = scrollableContainer.querySelector('div[data-sentry-component="DetailsSection"]'); if (!detailsSection) { this.connectObserver(); return; } const existingSectionById = document.getElementById(`${RPM_UI_PREFIX}-main-collapsible-section`); if (!existingSectionById || !scrollableContainer.contains(existingSectionById)) { if (existingSectionById && !scrollableContainer.contains(existingSectionById)) existingSectionById.remove(); this.injectMainCollapsibleSection(scrollableContainer, detailsSection); } else { this.ui.mainCollapsibleSection = existingSectionById; this.isIntegratedUISetup = true; this.ui.collapsibleHeader = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-collapsible-header`); this.ui.collapsibleContent = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-collapsible-content`); this.ui.promptPresetTargetSelector = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-target-selector[data-type="positive"]`); this.ui.negativePresetTargetSelector = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-target-selector[data-type="negative"]`); this.ui.promptPresetListContainer = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-subsection[data-type="positive"] .${RPM_UI_PREFIX}-integrated-preset-list-area`); this.ui.negativePresetListContainer = this.ui.mainCollapsibleSection.querySelector(`.${RPM_UI_PREFIX}-subsection[data-type="negative"] .${RPM_UI_PREFIX}-integrated-preset-list-area`); if (!this.ui.collapsibleHeader || !this.ui.collapsibleContent || !this.ui.promptPresetTargetSelector || !this.ui.negativePresetTargetSelector || !this.ui.promptPresetListContainer || !this.ui.negativePresetListContainer) { existingSectionById.remove(); this.isIntegratedUISetup = false; this.injectMainCollapsibleSection(scrollableContainer, detailsSection); } else { if (this.ui.collapsibleContent) this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const icon = this.ui.collapsibleHeader?.querySelector(`.${RPM_UI_PREFIX}-expand-icon`); if (icon) icon.textContent = this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); this.refreshIntegratedUI(); } } } else { if (this.isIntegratedUISetup) this.isIntegratedUISetup = false; } this.connectObserver(); },
        _debouncedSearchHandler: function(type, searchTerm) { if (type === 'positive') { if (this.ui.promptSearchTerm !== searchTerm) { this.ui.promptIntegratedCurrentPage = 1; GM_setValue('rpmPromptIntegratedPage_v1', 1); } this.ui.promptSearchTerm = searchTerm; } else if (type === 'negative') { if (this.ui.negativeSearchTerm !== searchTerm) { this.ui.negativeIntegratedCurrentPage = 1; GM_setValue('rpmNegativeIntegratedPage_v1', 1); } this.ui.negativeSearchTerm = searchTerm; } this.refreshIntegratedUI(); },
        createSubSectionUI(type, title, managerInstance, targetSelectorRef, listContainerRef) { const section = document.createElement('div'); section.className = `${RPM_UI_PREFIX}-subsection`; section.dataset.type = type; const headerEl = document.createElement('h4'); headerEl.className = `${RPM_UI_PREFIX}-subsection-header`; headerEl.textContent = title; section.appendChild(headerEl); const controls = document.createElement('div'); controls.className = `${RPM_UI_PREFIX}-subsection-controls`; const applyToLabel = document.createElement('span'); applyToLabel.textContent = 'Apply to: '; controls.appendChild(applyToLabel); this.ui[targetSelectorRef] = document.createElement('select'); this.ui[targetSelectorRef].className = `${RPM_UI_PREFIX}-target-selector`; this.ui[targetSelectorRef].dataset.type = type; this.ui[targetSelectorRef].onclick = (e) => e.stopPropagation(); this.ui[targetSelectorRef].onchange = (e) => { e.stopPropagation(); if (type === 'positive') this.selectedPositivePromptTargetIndex = parseInt(e.target.value); else this.selectedNegativePromptTargetIndex = parseInt(e.target.value); }; controls.appendChild(this.ui[targetSelectorRef]); const manageBtn = document.createElement('button'); manageBtn.className = `${RPM_UI_PREFIX}-btn ${RPM_UI_PREFIX}-btn-secondary ${RPM_UI_PREFIX}-manage-btn`; manageBtn.appendChild(createIcon(ICONS.manage)); manageBtn.appendChild(document.createTextNode(' Manage')); manageBtn.onclick = (e) => { e.stopPropagation(); managerInstance.toggleManagerWindow(); }; controls.appendChild(manageBtn); const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.className = `${RPM_UI_PREFIX}-integrated-search-input`; searchInput.placeholder = `Search ${title}...`; searchInput.setAttribute('aria-label', `Search ${title}`); if (!this._searchDebouncers[type]) { this._searchDebouncers[type] = debounce((currentSearchTerm) => { this._debouncedSearchHandler(type, currentSearchTerm); }, 300); } searchInput.addEventListener('input', (e) => { this._searchDebouncers[type](e.target.value.trim()); }); controls.appendChild(searchInput); section.appendChild(controls); const presetArea = document.createElement('div'); presetArea.className = `${RPM_UI_PREFIX}-integrated-preset-list-area`; this.ui[listContainerRef] = presetArea; section.appendChild(presetArea); return section; },
        injectMainCollapsibleSection(scrollableContainer, detailsSection) { const oldSectionGlobally = document.getElementById(`${RPM_UI_PREFIX}-main-collapsible-section`); if (oldSectionGlobally) { oldSectionGlobally.remove(); } this.ui.mainCollapsibleSection = document.createElement('div'); this.ui.mainCollapsibleSection.id = `${RPM_UI_PREFIX}-main-collapsible-section`; this.ui.mainCollapsibleSection.className = `${RPM_UI_PREFIX}-integrated-section`; this.ui.collapsibleHeader = document.createElement('div'); this.ui.collapsibleHeader.className = `${RPM_UI_PREFIX}-collapsible-header`; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); const titleDiv = document.createElement('div'); titleDiv.className = `${RPM_UI_PREFIX}-collapsible-title-container`; const iconSpan = createIcon(this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right, `${RPM_UI_PREFIX}-expand-icon`); titleDiv.appendChild(iconSpan); titleDiv.appendChild(document.createTextNode(' Prompt & Preset Tools')); this.ui.collapsibleHeader.appendChild(titleDiv); this.ui.collapsibleHeader.onclick = (e) => { e.stopPropagation(); this.toggleAccordion(); }; this.ui.collapsibleContent = document.createElement('div'); this.ui.collapsibleContent.className = `${RPM_UI_PREFIX}-collapsible-content`; this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const promptSection = this.createSubSectionUI('positive', 'Prompt Presets', this.promptManager, 'promptPresetTargetSelector', 'promptPresetListContainer'); this.ui.collapsibleContent.appendChild(promptSection); const negativeSection = this.createSubSectionUI('negative', 'Negative Prompt Presets', this.negativePromptManager, 'negativePresetTargetSelector', 'negativePresetListContainer'); this.ui.collapsibleContent.appendChild(negativeSection); this.ui.mainCollapsibleSection.appendChild(this.ui.collapsibleHeader); this.ui.mainCollapsibleSection.appendChild(this.ui.collapsibleContent); Array.from(scrollableContainer.querySelectorAll(`.${RPM_UI_PREFIX}-custom-divider`)).forEach(d => d.remove()); scrollableContainer.insertBefore(this.ui.mainCollapsibleSection, detailsSection); const newDivider = document.createElement('div'); newDivider.className = `bg-border-primary h-px my-0 ${RPM_UI_PREFIX}-custom-divider`; scrollableContainer.insertBefore(newDivider, detailsSection); this.isIntegratedUISetup = true; this.refreshIntegratedUI(); },
        toggleAccordion() { this.ui.isAccordionOpen = !this.ui.isAccordionOpen; GM_setValue('rpmAccordionOpenState', this.ui.isAccordionOpen); if (this.ui.collapsibleContent && this.ui.collapsibleHeader) { this.ui.collapsibleContent.style.display = this.ui.isAccordionOpen ? 'block' : 'none'; const icon = this.ui.collapsibleHeader.querySelector(`.${RPM_UI_PREFIX}-expand-icon`); if (icon) icon.textContent = this.ui.isAccordionOpen ? ICONS.expand_less : ICONS.chevron_right; this.ui.collapsibleHeader.classList.toggle('open', this.ui.isAccordionOpen); } },
        refreshIntegratedUI() { if (!this.isIntegratedUISetup || !this.ui.mainCollapsibleSection || !document.body.contains(this.ui.mainCollapsibleSection)) { return; } this.updatePositivePromptTargetSelector(); this.updateNegativePromptTargetSelector(); this.renderIntegratedPresetList(this.promptManager, this.ui.promptPresetListContainer, 'positive'); this.renderIntegratedPresetList(this.negativePromptManager, this.ui.negativePresetListContainer, 'negative'); },
        updatePositivePromptTargetSelector() { if (!this.isIntegratedUISetup || !this.ui.promptPresetTargetSelector || !this.ui.promptPresetTargetSelector.isConnected) return; const currentLogicalIndex = this.selectedPositivePromptTargetIndex; this.ui.promptPresetTargetSelector.innerHTML = ''; const positivePromptInputs = MANAGER_CONFIGS.promptRiffusion.targetInputSelector(); if (positivePromptInputs.length === 0) { const opt = document.createElement('option'); opt.value = "-1"; opt.textContent = "No Prompt Inputs"; this.ui.promptPresetTargetSelector.appendChild(opt); this.ui.promptPresetTargetSelector.disabled = true; this.selectedPositivePromptTargetIndex = -1; } else { positivePromptInputs.forEach((el, index) => { const opt = document.createElement('option'); opt.value = index.toString(); opt.textContent = `Prompt ${index + 1}`; this.ui.promptPresetTargetSelector.appendChild(opt); }); this.ui.promptPresetTargetSelector.disabled = false; if (currentLogicalIndex >= 0 && currentLogicalIndex < positivePromptInputs.length) { this.ui.promptPresetTargetSelector.value = currentLogicalIndex.toString(); } else { this.ui.promptPresetTargetSelector.value = "0"; this.selectedPositivePromptTargetIndex = 0; } } },
        updateNegativePromptTargetSelector() { if (!this.isIntegratedUISetup || !this.ui.negativePresetTargetSelector || !this.ui.negativePresetTargetSelector.isConnected) return; const currentLogicalIndex = this.selectedNegativePromptTargetIndex; this.ui.negativePresetTargetSelector.innerHTML = ''; const negativePromptInputs = MANAGER_CONFIGS.negativePromptRiffusion.targetInputSelector(); if (negativePromptInputs.length === 0) { const opt = document.createElement('option'); opt.value = "-1"; opt.textContent = "No Neg Inputs"; this.ui.negativePresetTargetSelector.appendChild(opt); this.ui.negativePresetTargetSelector.disabled = true; this.selectedNegativePromptTargetIndex = -1; } else { negativePromptInputs.forEach((el, index) => { const opt = document.createElement('option'); opt.value = index.toString(); opt.textContent = `Negative ${index + 1}`; this.ui.negativePresetTargetSelector.appendChild(opt); }); this.ui.negativePresetTargetSelector.disabled = false; if (currentLogicalIndex >= 0 && currentLogicalIndex < negativePromptInputs.length) { this.ui.negativePresetTargetSelector.value = currentLogicalIndex.toString(); } else { this.ui.negativePresetTargetSelector.value = "0"; this.selectedNegativePromptTargetIndex = 0; } } },
        renderIntegratedPresetList(manager, areaContainer, type) { let currentSearchTerm = (type === 'positive' ? this.ui.promptSearchTerm : this.ui.negativeSearchTerm) || ''; currentSearchTerm = currentSearchTerm.trim().toLowerCase(); if (!this.isIntegratedUISetup || !manager || !areaContainer || !areaContainer.isConnected) return; areaContainer.innerHTML = ''; const listElement = document.createElement('div'); listElement.className = `${RPM_UI_PREFIX}-integrated-preset-list`; areaContainer.appendChild(listElement); let presetsToConsider = manager.presets; if (currentSearchTerm !== '') { presetsToConsider = manager.presets.filter(preset => preset.name.toLowerCase().includes(currentSearchTerm)); } const itemsPerPage = manager.config.itemsPerPageIntegrated; let currentPage = (type === 'positive') ? this.ui.promptIntegratedCurrentPage : this.ui.negativeIntegratedCurrentPage; const totalFilteredPresets = presetsToConsider.length; const totalPages = Math.ceil(totalFilteredPresets / itemsPerPage); if (currentPage > totalPages && totalPages > 0) currentPage = totalPages; if (currentPage < 1 && totalPages > 0) currentPage = 1; else if (totalPages === 0) currentPage = 1; if (type === 'positive') { this.ui.promptIntegratedCurrentPage = currentPage; GM_setValue('rpmPromptIntegratedPage_v1', currentPage); } else { this.ui.negativeIntegratedCurrentPage = currentPage; GM_setValue('rpmNegativeIntegratedPage_v1', currentPage); } const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; const presetsToDisplay = presetsToConsider.slice(startIndex, endIndex); if (totalFilteredPresets === 0) { listElement.innerHTML = `<p class="${RPM_UI_PREFIX}-no-presets-integrated">${currentSearchTerm !== '' ? 'No presets match search.' : 'No presets stored.'}</p>`; const existingPagination = areaContainer.querySelector(`.${RPM_UI_PREFIX}-pagination-controls`); if (existingPagination) existingPagination.classList.add(`${RPM_UI_PREFIX}-hidden`); return; } presetsToDisplay.forEach(preset => { const button = document.createElement('button'); button.className = `${RPM_UI_PREFIX}-integrated-preset-item`; button.title = `Apply: ${preset.name}\nValue: ${preset.value}`; const textWrapper = document.createElement('span'); textWrapper.className = `${RPM_UI_PREFIX}-preset-text-wrapper`; textWrapper.textContent = preset.name; button.appendChild(textWrapper); button.onclick = async (e) => { e.stopPropagation(); button.classList.add(`${RPM_UI_PREFIX}-applying`); if (type === 'positive') { await this.handleApplyPositivePreset(preset.value); } else { await this.handleApplyNegativePreset(preset.value); } await new Promise(resolve => setTimeout(resolve, 100)); button.classList.remove(`${RPM_UI_PREFIX}-applying`); }; listElement.appendChild(button); }); let paginationControls = areaContainer.querySelector(`.${RPM_UI_PREFIX}-pagination-controls`); if (!paginationControls) { paginationControls = document.createElement('div'); paginationControls.className = `${RPM_UI_PREFIX}-pagination-controls`; areaContainer.appendChild(paginationControls); } else { paginationControls.innerHTML = ''; } if (totalPages > 1) { paginationControls.classList.remove(`${RPM_UI_PREFIX}-hidden`); const prevBtn = document.createElement('button'); prevBtn.className = `${RPM_UI_PREFIX}-page-btn ${RPM_UI_PREFIX}-page-prev`; prevBtn.appendChild(createIcon(ICONS.prev)); const pageInfoEl = document.createElement('span'); pageInfoEl.className = `${RPM_UI_PREFIX}-page-info`; pageInfoEl.textContent = `Page ${currentPage} / ${totalPages}`; const nextBtn = document.createElement('button'); nextBtn.className = `${RPM_UI_PREFIX}-page-btn ${RPM_UI_PREFIX}-page-next`; nextBtn.appendChild(createIcon(ICONS.next)); prevBtn.disabled = currentPage <= 1; nextBtn.disabled = currentPage >= totalPages; prevBtn.onclick = (e) => { e.stopPropagation(); if (type === 'positive' && this.ui.promptIntegratedCurrentPage > 1) { this.ui.promptIntegratedCurrentPage--; GM_setValue('rpmPromptIntegratedPage_v1', this.ui.promptIntegratedCurrentPage); } else if (type === 'negative' && this.ui.negativeIntegratedCurrentPage > 1) { this.ui.negativeIntegratedCurrentPage--; GM_setValue('rpmNegativeIntegratedPage_v1', this.ui.negativeIntegratedCurrentPage); } this.renderIntegratedPresetList(manager, areaContainer, type); }; nextBtn.onclick = (e) => { e.stopPropagation(); if (type === 'positive' && this.ui.promptIntegratedCurrentPage < totalPages) { this.ui.promptIntegratedCurrentPage++; GM_setValue('rpmPromptIntegratedPage_v1', this.ui.promptIntegratedCurrentPage); } else if (type === 'negative' && this.ui.negativeIntegratedCurrentPage < totalPages) { this.ui.negativeIntegratedCurrentPage++; GM_setValue('rpmNegativeIntegratedPage_v1', this.ui.negativeIntegratedCurrentPage); } this.renderIntegratedPresetList(manager, areaContainer, type); }; paginationControls.appendChild(prevBtn); paginationControls.appendChild(pageInfoEl); paginationControls.appendChild(nextBtn); } else { paginationControls.classList.add(`${RPM_UI_PREFIX}-hidden`); } },
        async handleApplyPositivePreset(presetValue) { const targetInputs = MANAGER_CONFIGS.promptRiffusion.targetInputSelector(); if (this.selectedPositivePromptTargetIndex >= 0 && targetInputs.length > this.selectedPositivePromptTargetIndex) { await applyReactControlledInputPreset(targetInputs[this.selectedPositivePromptTargetIndex], presetValue, 'prompt-riffusion-integrated'); } },
        async handleApplyNegativePreset(presetValue) { const targetInputs = MANAGER_CONFIGS.negativePromptRiffusion.targetInputSelector(); if (this.selectedNegativePromptTargetIndex >= 0 && targetInputs.length > this.selectedNegativePromptTargetIndex) { await applyReactControlledInputPreset(targetInputs[this.selectedNegativePromptTargetIndex], presetValue, 'negative-prompt-riffusion-integrated'); } else if (targetInputs.length > 0 && this.selectedNegativePromptTargetIndex < 0) { await applyReactControlledInputPreset(targetInputs[0], presetValue, 'negative-prompt-riffusion-integrated'); this.selectedNegativePromptTargetIndex = 0; this.updateNegativePromptTargetSelector(); } }
    };

    GM_addStyle(`
        :root { --rpm-accent-color: ${RIFFUSION_ACCENT_COLOR}; --rpm-bg-primary: #1a1a1d; --rpm-bg-secondary: #28282c; --rpm-bg-tertiary: #36363a; --rpm-bg-item-hover: #424246; --rpm-text-primary: #f0f0f0; --rpm-text-secondary: #bdbdbd; --rpm-border-primary: #4a4a4e; --rpm-border-input: #58585c; --rpm-button-icon-color: var(--rpm-text-secondary); --rpm-button-icon-hover-color: var(--rpm-text-primary); --rpm-button-secondary-bg: #505054; --rpm-button-secondary-hover-bg: #606064; --rpm-preset-item-height: 60px; --rpm-preset-item-gap: 8px; --rpm-list-padding: 12px; --rpm-preset-list-rows: 4; --rpm-integrated-preset-item-height: 46px; --rpm-integrated-preset-item-gap: 8px; }
        .${RPM_UI_PREFIX}-icon { font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20; vertical-align: middle; line-height: 1; }
        .${RPM_UI_PREFIX}-btn .${RPM_UI_PREFIX}-icon { margin-right: 5px; }
        .${RPM_UI_PREFIX}-icon-btn .${RPM_UI_PREFIX}-icon { margin-right: 0; }
        .${RPM_UI_PREFIX}-window { font-family: Inter, ui-sans-serif, system-ui, sans-serif; position: fixed; z-index: 20005; width: 1200px; background-color: var(--rpm-bg-primary); color: var(--rpm-text-primary); border: 1px solid var(--rpm-border-primary); border-radius: 8px; box-shadow: 0 8px 25px rgba(0,0,0,0.5); display: flex; flex-direction: column; overflow: hidden; min-width: 500px; }
        .${RPM_UI_PREFIX}-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background-color: var(--rpm-bg-secondary); border-bottom: 1px solid var(--rpm-border-primary); cursor: grab; flex-shrink: 0; user-select: none; }
        .${RPM_UI_PREFIX}-drag-handle-icon { color: var(--rpm-text-secondary); margin-right: 8px; cursor: grab; font-size: 22px; }
        .${RPM_UI_PREFIX}-header-title { font-size: 0.9em; font-weight: 600; color: var(--rpm-text-primary); flex-grow: 1; }
        .${RPM_UI_PREFIX}-header-controls { display: flex; align-items: center; gap: 6px; }
        .${RPM_UI_PREFIX}-icon-btn { background-color: transparent; border: none; color: var(--rpm-button-icon-color); padding: 6px; border-radius: 4px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background-color 0.2s, color 0.2s; }
        .${RPM_UI_PREFIX}-icon-btn:hover { background-color: var(--rpm-bg-tertiary); color: var(--rpm-button-icon-hover-color); }
        .${RPM_UI_PREFIX}-icon-btn .${RPM_UI_PREFIX}-icon { font-size: 20px; }
        .${RPM_UI_PREFIX}-list-container { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--rpm-preset-item-gap); padding: var(--rpm-list-padding); overflow-y: auto; height: calc( (var(--rpm-preset-item-height) * var(--rpm-preset-list-rows)) + (var(--rpm-preset-item-gap) * (var(--rpm-preset-list-rows) - 1)) + (var(--rpm-list-padding) * 2) ); background-color: var(--rpm-bg-primary); flex-shrink: 0; align-content: start; box-sizing: border-box; }
        .${RPM_UI_PREFIX}-list-container::-webkit-scrollbar { width: 10px; } .${RPM_UI_PREFIX}-list-container::-webkit-scrollbar-track { background: var(--rpm-bg-secondary); border-radius: 5px; } .${RPM_UI_PREFIX}-list-container::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; border: 2px solid var(--rpm-bg-secondary); } .${RPM_UI_PREFIX}-list-container::-webkit-scrollbar-thumb:hover { background: #777; }
        .${RPM_UI_PREFIX}-preset-item { background-color: var(--rpm-bg-tertiary); border: 1px solid var(--rpm-border-primary); border-radius: 6px; padding: 10px; height: var(--rpm-preset-item-height); box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; align-items: center; position: relative; cursor: pointer; transition: background-color 0.2s; overflow: hidden; }
        .${RPM_UI_PREFIX}-preset-item:hover { background-color: var(--rpm-bg-item-hover); }
        .${RPM_UI_PREFIX}-preset-item.${RPM_UI_PREFIX}-dragging-item { opacity: 0.5; border: 1px dashed var(--rpm-accent-color); background-color: #222; cursor: grabbing; }
        .${RPM_UI_PREFIX}-preset-item.${RPM_UI_PREFIX}-drag-over-target { border-top: 3px solid var(--rpm-accent-color) !important; }
        .${RPM_UI_PREFIX}-list-container.${RPM_UI_PREFIX}-list-dragging-active .${RPM_UI_PREFIX}-preset-item:not(.${RPM_UI_PREFIX}-dragging-item):hover { background-color: #5a5a5a; }
        .${RPM_UI_PREFIX}-preset-name { font-size: 0.8em; line-height: 1.4em; color: var(--rpm-text-primary); width: 100%; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; word-break: break-word; text-align: center; }
        .${RPM_UI_PREFIX}-preset-item-controls { position: absolute; bottom: 4px; right: 4px; display: none; flex-direction: row; gap: 3px; background-color: rgba(30, 30, 30, 0.85); padding: 4px; border-radius: 4px; z-index: 1; }
        .${RPM_UI_PREFIX}-preset-item:hover .${RPM_UI_PREFIX}-preset-item-controls { display: flex; }
        .${RPM_UI_PREFIX}-preset-item-controls .${RPM_UI_PREFIX}-icon-btn { padding: 3px; background-color: transparent; }
        .${RPM_UI_PREFIX}-preset-item-controls .${RPM_UI_PREFIX}-icon-btn .${RPM_UI_PREFIX}-icon { font-size: 16px; }
        .${RPM_UI_PREFIX}-manager-pagination-controls, .${RPM_UI_PREFIX}-pagination-controls { display: flex; justify-content: center; align-items: center; padding: 8px 12px; border-top: 1px solid var(--rpm-border-primary); background-color: var(--rpm-bg-secondary); flex-shrink: 0; gap: 10px; }
        .${RPM_UI_PREFIX}-pagination-controls { margin-top: 12px; }
        .${RPM_UI_PREFIX}-page-btn { background-color: var(--rpm-bg-tertiary); border: 1px solid var(--rpm-border-input); color: var(--rpm-text-primary); padding: 5px 8px; border-radius: 4px; cursor: pointer; font-size: 0.8em; line-height: 1; display: inline-flex; align-items: center; }
        .${RPM_UI_PREFIX}-page-btn .${RPM_UI_PREFIX}-icon { font-size: 18px !important; margin-right:0; }
        .${RPM_UI_PREFIX}-page-btn:disabled { opacity: 0.5; cursor: not-allowed; }
        .${RPM_UI_PREFIX}-page-btn:hover:not(:disabled) { background-color: var(--rpm-accent-color); border-color: var(--rpm-accent-color); color: white; }
        .${RPM_UI_PREFIX}-page-btn.${RPM_UI_PREFIX}-page-btn-drag-hotspot { background-color: color-mix(in srgb, var(--rpm-accent-color) 60%, black) !important; outline: 1px solid var(--rpm-accent-color); }
        .${RPM_UI_PREFIX}-page-info { font-size: 0.75em; color: var(--rpm-text-secondary); min-width: 70px; text-align: center; }
        .${RPM_UI_PREFIX}-add-area { padding: 12px; border-top: 1px solid var(--rpm-border-primary); background-color: var(--rpm-bg-secondary); display: flex; flex-direction: column; gap: 8px; flex-shrink: 0; }
        .${RPM_UI_PREFIX}-manager-pagination-controls[style*="display: flex;"] + .${RPM_UI_PREFIX}-add-area { border-top: none; }
        .${RPM_UI_PREFIX}-name-pos-container { display: flex; gap: 8px; align-items: center; }
        .${RPM_UI_PREFIX}-input.${RPM_UI_PREFIX}-name-input { flex-grow: 1; }
        .${RPM_UI_PREFIX}-input.${RPM_UI_PREFIX}-pos-input { width: 70px; max-width: 75px; text-align: right; padding: 8px; flex-shrink: 0; -moz-appearance: textfield; }
        .${RPM_UI_PREFIX}-input.${RPM_UI_PREFIX}-pos-input::-webkit-outer-spin-button, .${RPM_UI_PREFIX}-input.${RPM_UI_PREFIX}-pos-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
        .${RPM_UI_PREFIX}-pos-total { font-size: 0.8em; color: var(--rpm-text-secondary); flex-shrink: 0; margin-left: -4px; /* Adjust for closer spacing to number input */ }
        .${RPM_UI_PREFIX}-add-area input[type="text"], .${RPM_UI_PREFIX}-add-area input[type="number"] { padding: 8px 10px; background-color: var(--rpm-bg-primary); border: 1px solid var(--rpm-border-input); color: var(--rpm-text-primary); border-radius: 4px; font-size: 0.85em; box-sizing: border-box; }
        .${RPM_UI_PREFIX}-add-controls-container { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
        .${RPM_UI_PREFIX}-btn { background-color: var(--rpm-accent-color); color: white; border: none; padding: 7px 12px; border-radius: 4px; cursor: pointer; font-size: 0.85em; display: inline-flex; align-items: center; gap: 5px; transition: background-color 0.2s; line-height: 1.2; }
        .${RPM_UI_PREFIX}-btn:hover { background-color: color-mix(in srgb, var(--rpm-accent-color) 85%, black); }
        .${RPM_UI_PREFIX}-btn .${RPM_UI_PREFIX}-icon { font-size: 18px; }
        .${RPM_UI_PREFIX}-btn.secondary, .${RPM_UI_PREFIX}-btn-secondary { background-color: var(--rpm-button-secondary-bg); }
        .${RPM_UI_PREFIX}-btn.secondary:hover, .${RPM_UI_PREFIX}-btn-secondary:hover { background-color: var(--rpm-button-secondary-hover-bg); }
        .${RPM_UI_PREFIX}-btn-danger { background-color: #e53e3e; } .${RPM_UI_PREFIX}-btn-danger:hover { background-color: #c53030; }
        .${RPM_UI_PREFIX}-footer { display: flex; justify-content: flex-end; align-items: center; padding: 8px 12px; padding-right: 28px; border-top: 1px solid var(--rpm-border-primary); background-color: var(--rpm-bg-secondary); flex-shrink: 0; gap: 8px; position: relative; }
        .${RPM_UI_PREFIX}-footer-btn-danger { border-color: color-mix(in srgb, #e53e3e 60%, black); color: #e57373; }
        .${RPM_UI_PREFIX}-footer-btn-danger:hover { background-color: #e53e3e; border-color: #e53e3e; color: white; }
        .${RPM_UI_PREFIX}-resize-handle { width: 20px; height: 20px; position: absolute; bottom: 2px; right: 2px; cursor: ew-resize; display: flex; align-items: center; justify-content: center; color: var(--rpm-text-secondary); user-select: none; font-size: 16px; line-height: 1; z-index: 10; }
        .${RPM_UI_PREFIX}-resize-handle:hover { color: var(--rpm-text-primary); }
        .${RPM_UI_PREFIX}-no-presets { text-align: center; padding: 20px; color: var(--rpm-text-secondary); grid-column: 1 / -1; font-size: 0.9em;}
        .${RPM_UI_PREFIX}-integrated-section { padding-top: 0.5rem; padding-bottom: 0.5rem; padding-left: 1rem; padding-right: 1rem; background-color: transparent; color: var(--rpm-text-primary); }
        .${RPM_UI_PREFIX}-custom-divider { background-color: var(--rpm-border-primary); height: 1px; margin-top: 0.75rem; margin-bottom: 0.75rem; }
        .${RPM_UI_PREFIX}-collapsible-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 8px 0px; font-weight: 600; color: var(--rpm-text-primary); font-size: 1rem; border-bottom: 1px solid transparent; }
        .${RPM_UI_PREFIX}-collapsible-header.open { border-bottom-color: var(--rpm-border-primary); }
        .${RPM_UI_PREFIX}-collapsible-title-container { display: flex; align-items: center; transition: color 0.2s; flex-grow: 1; }
        .${RPM_UI_PREFIX}-collapsible-header:hover .${RPM_UI_PREFIX}-collapsible-title-container { color: var(--rpm-text-secondary); }
        .${RPM_UI_PREFIX}-expand-icon { margin-right: 0.35rem; width: 1.25rem; transition: transform 0.2s; font-size: 1.2em !important; color: currentColor; }
        .${RPM_UI_PREFIX}-collapsible-content { padding-top: 0.5rem; padding-left: calc(1.25rem + 0.35rem); overflow: hidden; display: none; }
        .${RPM_UI_PREFIX}-collapsible-header.open + .${RPM_UI_PREFIX}-collapsible-content { display: block; }
        .${RPM_UI_PREFIX}-subsection { margin-bottom: 1rem; } .${RPM_UI_PREFIX}-subsection:last-child { margin-bottom: 0; }
        .${RPM_UI_PREFIX}-subsection-header { font-size: 0.875rem; font-weight: 500; color: var(--rpm-text-secondary); margin-bottom: 0.6rem; padding-bottom: 0.3rem; border-bottom: 1px solid var(--rpm-border-primary); }
        .${RPM_UI_PREFIX}-subsection-controls { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.6rem; font-size: 0.875rem; color: var(--rpm-text-secondary); flex-wrap: wrap; }
        .${RPM_UI_PREFIX}-target-selector { background-color: var(--rpm-bg-secondary); color: var(--rpm-text-primary); border: 1px solid var(--rpm-border-primary); border-radius: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.8rem; min-width: 120px; flex-shrink: 0; }
        .${RPM_UI_PREFIX}-target-selector:hover { border-color: var(--rpm-accent-color); }
        .${RPM_UI_PREFIX}-manage-btn { padding: 0.25rem 0.6rem !important; font-size: 0.8rem !important; background-color: var(--rpm-bg-tertiary) !important; color: var(--rpm-text-primary) !important; border: 1px solid var(--rpm-border-primary) !important; flex-shrink: 0; }
        .${RPM_UI_PREFIX}-manage-btn:hover { background-color: var(--rpm-bg-secondary) !important; }
        .${RPM_UI_PREFIX}-manage-btn .${RPM_UI_PREFIX}-icon { font-size: 16px !important; color: var(--rpm-text-primary) !important;}
        .${RPM_UI_PREFIX}-integrated-search-input { flex-grow: 1; min-width: 150px; padding: 0.25rem 0.5rem; font-size: 0.8rem; background-color: var(--rpm-bg-secondary); border: 1px solid var(--rpm-border-input); color: var(--rpm-text-primary); border-radius: 0.375rem; box-sizing: border-box; }
        .${RPM_UI_PREFIX}-subsection-controls > span { flex-shrink: 0; }
        .${RPM_UI_PREFIX}-integrated-preset-list-area { }
        .${RPM_UI_PREFIX}-integrated-preset-list { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--rpm-integrated-preset-item-gap); }
        .${RPM_UI_PREFIX}-integrated-preset-item { background-color: var(--rpm-bg-tertiary); color: var(--rpm-text-primary); border: 1px solid var(--rpm-border-input); border-radius: 5px; min-height: var(--rpm-integrated-preset-item-height); padding: 6px 8px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.78em; line-height: 1.35; text-align: center; cursor: pointer; transition: background-color 0.2s, border-color 0.2s, color 0.2s; overflow: hidden; }
        .${RPM_UI_PREFIX}-integrated-preset-item:hover { background-color: var(--rpm-accent-color); border-color: var(--rpm-accent-color); color: white; }
        .${RPM_UI_PREFIX}-preset-text-wrapper { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-overflow: ellipsis; word-break: break-word; width: 100%; }
        .${RPM_UI_PREFIX}-no-presets-integrated { grid-column: 1 / -1; text-align: center; color: var(--rpm-text-secondary); padding: 10px 0; font-size: 0.9em; }
        .${RPM_UI_PREFIX}-hidden { display: none !important; }
        .${RPM_UI_PREFIX}-applying { animation: ${RPM_UI_PREFIX}-flash 0.5s ease-out; }
        @keyframes ${RPM_UI_PREFIX}-flash { 0%, 100% { background-color: var(--rpm-bg-tertiary); } 50% { background-color: #4caf50; color: white; } }
        .${RPM_UI_PREFIX}-modal-overlay { position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,.7); display:flex;align-items:center;justify-content:center;z-index:20006 !important; }
        .${RPM_UI_PREFIX}-dialog-box { background-color: var(--rpm-bg-secondary);padding:25px;border-radius:8px;box-shadow:0 5px 15px rgba(0,0,0,.5); width:350px;max-width:80vw;text-align:center;color:var(--rpm-text-primary); }
        .${RPM_UI_PREFIX}-dialog-box p {margin-bottom:20px;font-size:1.1em;}
        .${RPM_UI_PREFIX}-dialog-buttons {display:flex;justify-content:space-around;}
    `);

    if (!window.riffusionPresetManagers) window.riffusionPresetManagers = {};
    window.riffusionPresetManagers.prompt = new PresetManager(MANAGER_CONFIGS.promptRiffusion);
    window.riffusionPresetManagers.negativePrompt = new PresetManager(MANAGER_CONFIGS.negativePromptRiffusion);

    RiffusionIntegration.init(
        window.riffusionPresetManagers.prompt,
        window.riffusionPresetManagers.negativePrompt
    );

    console.log("Riffusion Preset Manager v" + (typeof GM_info !== 'undefined' ? GM_info.script.version : '1.3.1') + " initialized.");

})();