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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
    }
})();