Civitai Multiple Prompt Copy Buttons (with Prompt Management)

Adds multiple prompt copy buttons in a compact, draggable floating menu with prompt management on Civitai's generate page

// ==UserScript==
// @name         Civitai Multiple Prompt Copy Buttons (with Prompt Management)
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Adds multiple prompt copy buttons in a compact, draggable floating menu with prompt management on Civitai's generate page
// @match        https://civitai.com/generate
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT

// ==/UserScript==

(function() {
    'use strict';

    let prompts = GM_getValue("savedPrompts", {
        'Ryuko': "matoi ryuuko, kill la kill, 1girl, black hair, blue eyes, medium hair, multicolored hair, red hair, streaked hair, two-tone hair",
        'Low angle': "from below, low angle, ground level",
        'High angle': "from above, high angle",
        'Kuroneko': "in the style of Kanzaki Hiro, 1girl, gokou ruri, black hair, blush, hairclip, hairband, hime cut, purple eyes, hair flower, hair ornament, hairclip, red eyes",
        'Toki': "toki (blue archive), blue archive, 1girl, blonde hair, blue eyes, blue hairband, blue halo, hairband, halo, solo",
        'Oily': "wet, wet skin, shiny, shiny skin, glossy, glossy skin, oil, oily, oily skin, sweat, sweaty, sweaty skin",
        'Dark': "dark-skinned female, dark skin, tan, tanline",
        'Blonde': "blonde hair, hime cut, long hair, straight hair, hairband"
    });

    const colors = ['#8B0000', '#00008B', '#8B008B', '#006400', '#008B8B', '#8B4500', '#8B8B00'];

    GM_addStyle(`
        #prompt-buttons-container {
            position: fixed;
            z-index: 9999;
            background-color: rgba(26,26,26,0.9);
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.3);
            font-family: Arial, sans-serif;
            width: 240px;
            display: flex;
            flex-direction: column;
        }
        #drag-handle {
            height: 24px;
            background-color: #2a2a2a;
            border-top-left-radius: 8px;
            border-top-right-radius: 8px;
            cursor: move;
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 0 8px;
        }
        #buttons-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 4px;
            padding: 4px;
            max-height: 300px;
            overflow-y: auto;
        }
        .prompt-button, #show-hide-btn, #manage-btn {
            padding: 6px;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 11px;
            font-weight: bold;
            transition: all 0.2s;
            text-align: center;
            border-radius: 4px;
        }
        .prompt-button:hover, #show-hide-btn:hover, #manage-btn:hover {
            filter: brightness(1.2);
            transform: translateY(-1px);
        }
        #show-hide-btn, #manage-btn {
            background-color: #4a4a4a;
            width: 50px;
        }
        #pin-btn {
            cursor: pointer;
            color: #fff;
            font-size: 14px;
            background: none;
            border: none;
            padding: 0;
        }
        #manage-panel {
            display: none;
            padding: 8px;
            background-color: #2a2a2a;
        }
        #manage-panel input, #manage-panel textarea {
            width: 100%;
            margin-bottom: 4px;
            padding: 4px;
        }
        #manage-panel button {
            margin-right: 4px;
            margin-bottom: 4px;
        }
    `);

    function createButton(name, prompt, color) {
        const button = document.createElement('button');
        button.className = 'prompt-button';
        button.textContent = name;
        button.style.backgroundColor = color;
        button.onclick = (e) => {
            e.stopPropagation();
            GM_setClipboard(prompt);
            const originalText = button.textContent;
            button.textContent = 'Copied!';
            setTimeout(() => button.textContent = originalText, 1000);
        };
        return button;
    }

    function makeDraggable(container, handle) {
        let isDragging = false;
        let startX, startY, startLeft, startTop;

        handle.addEventListener('mousedown', startDragging);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDragging);

        function startDragging(e) {
            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;
            startLeft = parseInt(container.style.left) || 0;
            startTop = parseInt(container.style.top) || 0;
            e.preventDefault();
        }

        function drag(e) {
            if (!isDragging) return;
            if (container.dataset.pinned === 'true') return;
            const deltaX = e.clientX - startX;
            const deltaY = e.clientY - startY;
            container.style.left = `${startLeft + deltaX}px`;
            container.style.top = `${startTop + deltaY}px`;
            savePosition(startLeft + deltaX, startTop + deltaY);
        }

        function stopDragging() {
            isDragging = false;
        }
    }

    function savePosition(x, y) {
        GM_setValue("containerX", x);
        GM_setValue("containerY", y);
    }

    function loadPosition(container) {
        const x = GM_getValue("containerX", window.innerWidth - 260);
        const y = GM_getValue("containerY", window.innerHeight - 300);
        container.style.left = `${x}px`;
        container.style.top = `${y}px`;
    }

    function updateButtonsGrid() {
        const buttonGrid = document.getElementById('buttons-grid');
        buttonGrid.innerHTML = '';
        Object.entries(prompts).forEach(([name, prompt], index) => {
            const button = createButton(name, prompt, colors[index % colors.length]);
            buttonGrid.appendChild(button);
        });
        GM_setValue("savedPrompts", prompts);
    }

    function addPromptButtons() {
        const container = document.createElement('div');
        container.id = 'prompt-buttons-container';

        const dragHandle = document.createElement('div');
        dragHandle.id = 'drag-handle';

        const showHideBtn = document.createElement('button');
        showHideBtn.id = 'show-hide-btn';
        showHideBtn.textContent = 'Hide';
        showHideBtn.onclick = toggleButtons;

        const manageBtn = document.createElement('button');
        manageBtn.id = 'manage-btn';
        manageBtn.textContent = 'Manage';
        manageBtn.onclick = toggleManagePanel;

        const pinBtn = document.createElement('button');
        pinBtn.id = 'pin-btn';
        pinBtn.innerHTML = '📌';
        pinBtn.onclick = togglePin;

        dragHandle.appendChild(showHideBtn);
        dragHandle.appendChild(manageBtn);
        dragHandle.appendChild(pinBtn);

        const buttonGrid = document.createElement('div');
        buttonGrid.id = 'buttons-grid';

        const managePanel = createManagePanel();

        container.appendChild(dragHandle);
        container.appendChild(buttonGrid);
        container.appendChild(managePanel);
        document.body.appendChild(container);

        updateButtonsGrid();
        makeDraggable(container, dragHandle);
        loadPosition(container);
    }

    function toggleButtons() {
        const buttonGrid = document.getElementById('buttons-grid');
        const showHideBtn = document.getElementById('show-hide-btn');
        if (buttonGrid.style.display === 'none') {
            buttonGrid.style.display = 'grid';
            showHideBtn.textContent = 'Hide';
        } else {
            buttonGrid.style.display = 'none';
            showHideBtn.textContent = 'Show';
        }
    }

    function togglePin() {
        const container = document.getElementById('prompt-buttons-container');
        const pinBtn = document.getElementById('pin-btn');
        if (container.dataset.pinned === 'true') {
            delete container.dataset.pinned;
            pinBtn.style.opacity = '1';
        } else {
            container.dataset.pinned = 'true';
            pinBtn.style.opacity = '0.5';
        }
    }

    function toggleManagePanel() {
        const managePanel = document.getElementById('manage-panel');
        managePanel.style.display = managePanel.style.display === 'none' ? 'block' : 'none';
    }

    function createManagePanel() {
        const panel = document.createElement('div');
        panel.id = 'manage-panel';
        panel.style.display = 'none';

        const nameInput = document.createElement('input');
        nameInput.placeholder = 'Prompt Name';

        const promptInput = document.createElement('textarea');
        promptInput.placeholder = 'Prompt Text';

        const addButton = document.createElement('button');
        addButton.textContent = 'Add';
        addButton.onclick = () => {
            if (nameInput.value && promptInput.value) {
                prompts[nameInput.value] = promptInput.value;
                updateButtonsGrid();
                nameInput.value = '';
                promptInput.value = '';
            }
        };

        const editButton = document.createElement('button');
        editButton.textContent = 'Edit';
        editButton.onclick = () => {
            if (nameInput.value && promptInput.value && prompts.hasOwnProperty(nameInput.value)) {
                prompts[nameInput.value] = promptInput.value;
                updateButtonsGrid();
            }
        };

        const removeButton = document.createElement('button');
        removeButton.textContent = 'Remove';
        removeButton.onclick = () => {
            if (prompts.hasOwnProperty(nameInput.value)) {
                delete prompts[nameInput.value];
                updateButtonsGrid();
                nameInput.value = '';
                promptInput.value = '';
            }
        };

        panel.appendChild(nameInput);
        panel.appendChild(promptInput);
        panel.appendChild(addButton);
        panel.appendChild(editButton);
        panel.appendChild(removeButton);

        return panel;
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addPromptButtons);
    } else {
        addPromptButtons();
    }
})();