Greasy Fork

Greasy Fork is available in English.

Discord Midjourney 参数面板

在 Discord 中添加 Midjourney 参数设置面板,支持完整卡片式 UI 和最新参数功能(v7 支持)

当前为 2025-05-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         Discord Midjourney 参数面板
// @namespace    https://github.com/cwser
// @version      0.1.0
// @license      MIT
// @description  在 Discord 中添加 Midjourney 参数设置面板,支持完整卡片式 UI 和最新参数功能(v7 支持)
// @author       cwser
// @match        https://discord.com/*
// @icon         https://www.midjourney.com/favicon.ico
// @grant        unsafeWindow
// @supportURL   https://github.com/cwser
// @homepageURL  https://github.com/cwser
// ==/UserScript==

(function () {
    'use strict';

    let params = {};

    function createSettingButton() {
        const button = document.createElement('button');
        button.textContent = 'MJ设置';
        button.style.position = 'fixed';
        button.style.right = '20px';
        button.style.bottom = '20px';
        button.style.padding = '10px 20px';
        button.style.backgroundColor = '#5865F2';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '8px';
        button.style.cursor = 'pointer';
        button.style.zIndex = '9999';
        button.addEventListener('click', toggleControlPanel);
        document.body.appendChild(button);
    }

    function toggleControlPanel() {
        const panel = document.getElementById('mj-control-panel');
        if (panel) {
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        }
    }

    function resetParams() {
        params = {
            ar: '1:1',
            stylize: 100,
            weird: 0,
            chaos: 0,
            mode: 'standard',
            version: 'v7',
            speed: 'relax',
            draft: false,
            noPrompt: '',
            cref: [],
            sref: []
        };
    }

    function updatePromptParams() {
        const { ar, stylize, weird, chaos, mode, draft, noPrompt, version, cref, sref, speed } = params;

        // 优先处理URL参数(cref和sref)
        const urlParts = [
            ...cref.map(url => `--cref ${url}`),
            ...sref.map(url => `--sref ${url}`)
        ];

        // 处理其他参数
        const otherParts = [
            `--ar ${ar}`,
            stylize !== 0 ? `--s ${stylize}` : '',
            weird !== 0 ? `--w ${weird}` : '',
            chaos !== 0 ? `--c ${chaos}` : '',
            mode !== 'standard' ? `--${mode}` : '',
            draft ? '--draft' : '',
            noPrompt ? `--no ${noPrompt}` : '',
            version.startsWith('niji') ? `--niji ${version.replace('niji', '')}` : `--v ${version.replace('v', '')}`,
            `--${speed}`
        ];

        const promptField = document.getElementById('prompt-params');
        if (promptField) {
            const allParts = [...urlParts, ...otherParts.filter(Boolean)];
            promptField.value = allParts.join(' ');
        }
    }

    function createControlPanel() {
        const panel = document.createElement('div');
        panel.id = 'mj-control-panel';
        panel.style.cssText = `
            display: none;
            position: fixed;
            right: 20px;
            bottom: 80px;
            width: 1080px;
            background: white;
            border-radius: 12px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
            padding: 20px;
            z-index: 10000;
            border: 1px solid #E5E7EB;
            max-height: 90vh;
            overflow-y: auto;
            font-family: sans-serif;
        `;

        panel.innerHTML = `
            <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:20px;">
                <h3 style="margin:0; color:#111827; font-size:18px; font-weight:600;">Midjourney 参数设置</h3>
                <div style="flex:1; height:1px; background:#e5e7eb; margin:0 16px;"></div>
            </div>

            <div style="display:grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap:20px;">
                <div style="background:#f9fafb; padding:16px; border-radius:8px;">
                    <p style="margin:0 0 8px 0; font-weight:500;">图片尺寸</p>
                    <div style="display:flex; align-items:center; gap:20px;">
                        <div style="position:relative; width:100px; height:100px;">
                            <div style="position:absolute; top:0; left:0; width:100px; height:100px; border:2px dashed #d1d5db; border-radius:12px;"></div>
                            <div id="ratio-preview" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); width:100px; height:100px;">
                                <div style="width:100px; height:100px; border:2px dashed #d1d5db; border-radius:12px; position:absolute; top:0; left:0;"></div>
                                <div id="ratio-box" style="position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); background:#f3f4f6; border:2px solid #374151; border-radius:6px; display:flex; align-items:center; justify-content:center; font-size:12px; color:#374151; padding:2px 4px; min-width:20px; min-height:12px; box-sizing: border-box;">1:1</div>
                            </div>
                        </div>
                        <div style="flex:1;">
                            <div id="size-buttons" style="display:flex; gap:8px; margin-bottom:8px;"></div>
                            <input type="range" id="ratio-slider" min="0" max="10" value="5" style="width:100%;">
                        </div>
                    </div>
                </div>

                <div style="background:#f9fafb; padding:16px; border-radius:8px;">
                    <p style="margin:0 0 8px 0; font-weight:500;">美学参数</p>
                    <label>风格化 <input type="range" id="stylize" min="0" max="1000" value="100"></label><br>
                    <label>奇特化 <input type="range" id="weird" min="0" max="3000" value="0"></label><br>
                    <label>多样性 <input type="range" id="chaos" min="0" max="100" value="0"></label>
                </div>

                <div style="background:#f9fafb; padding:16px; border-radius:8px; display: flex; flex-direction: column; gap: 10px;">
                    <p style="margin:0 0 8px 0; font-weight:500;">模型设置</p>
                    <div style="display:flex; gap:10px; align-items:center;">
                        <label style="flex:1;">速度
                            <select id="speed-select" style="width:100%; padding:6px;">
                                <option value="relax">标准</option>
                                <option value="fast">快速</option>
                                <option value="turbo">极速</option>
                            </select>
                        </label>
                        <label style="flex:1;">模式
                            <select id="mode-select" style="width:100%; padding:6px;">
                                <option value="standard">标准</option>
                                <option value="raw">原始</option>
                            </select>
                        </label>
                    </div>
                    <div style="display:flex; gap:10px; align-items:center;">
                        <label style="flex:1;">草稿
                            <input type="checkbox" id="draft-toggle">
                        </label>
                        <label style="flex:2;">版本
                            <select id="version-select" style="width:100%; padding:6px;">
                                <option value="v7">v7</option>
                                <option value="v6.1">v6.1</option>
                                <option value="v6">v6</option>
                                <option value="v5.2">v5.2</option>
                                <option value="v5.1">v5.1</option>
                                <option value="v5">v5</option>
                                <option value="v4">v4</option>
                                <option value="v3">v3</option>
                                <option value="v2">v2</option>
                                <option value="v1">v1</option>
                                <option value="niji6">niji6</option>
                                <option value="niji5">niji5</option>
                                <option value="niji4">niji4</option>
                            </select>
                        </label>
                    </div>
                </div>

                <div style="background:#f9fafb; padding:16px; border-radius:8px;">
                    <p style="margin:0 0 8px 0; font-weight:500;">角色参考 (--cref)</p>
                    <input type="text" id="cref-url" placeholder="粘贴角色图片URL" style="width:100%; padding:6px;">
                    <button id="cref-add" style="margin-top:6px; padding:4px 8px; background:#4f46e5; color:white; border:none; border-radius:4px; cursor:pointer;">添加</button>
                    <div id="cref-preview" style="margin-top:10px; display:flex; flex-wrap:wrap; gap:8px;"></div>
                </div>

                <div style="background:#f9fafb; padding:16px; border-radius:8px;">
                    <p style="margin:0 0 8px 0; font-weight:500;">风格参考 (--sref)</p>
                    <input type="text" id="sref-url" placeholder="粘贴风格图片URL" style="width:100%; padding:6px;">
                    <button id="sref-add" style="margin-top:6px; padding:4px 8px; background:#4f46e5; color:white; border:none; border-radius:4px; cursor:pointer;">添加</button>
                    <div id="sref-preview" style="margin-top:10px; display:flex; flex-wrap:wrap; gap:8px;"></div>
                </div>
            </div>

            <div style="margin-top:20px; border-top:1px solid #e5e7eb; padding-top:16px; display:flex; gap:16px;">
                <textarea id="prompt-params" style="flex:2; height:80px; padding:10px;" readonly></textarea>
                <textarea id="no-prompt" placeholder="输入需要排除的元素,多个用空格分隔" style="flex:1; height:80px; padding:10px;"></textarea>
            </div>

            <div style="margin-top:16px; display:flex; gap:10px;">
                <button id="copy-btn" style="flex:1; padding:8px; background:#4f46e5; color:white; border:none; border-radius:6px; cursor:pointer;">拷贝参数</button>
                <button id="clear-btn" style="flex:1; padding:8px; background:#e5e7eb; color:#111827; border:none; border-radius:6px; cursor:pointer;">清空参数</button>
            </div>
        `;

        document.body.appendChild(panel);
        bindControlEvents();
    }

    function bindControlEvents() {
        const $ = id => document.getElementById(id);

        // 绑定已有控件事件
        $('stylize').oninput = e => { params.stylize = +e.target.value; updatePromptParams(); };
        $('weird').oninput = e => { params.weird = +e.target.value; updatePromptParams(); };
        $('chaos').oninput = e => { params.chaos = +e.target.value; updatePromptParams(); };
        $('mode-select').onchange = e => { params.mode = e.target.value; updatePromptParams(); };
        $('version-select').onchange = e => { params.version = e.target.value; updatePromptParams(); };
        $('draft-toggle').onchange = e => { params.draft = e.target.checked; updatePromptParams(); };
        $('speed-select').onchange = e => { params.speed = e.target.value; updatePromptParams(); };
        $('no-prompt').oninput = e => { params.noPrompt = e.target.value.trim(); updatePromptParams(); };
        $('copy-btn').onclick = () => {
            const textarea = $('prompt-params');
            textarea.select();
            document.execCommand('copy');
            alert('参数已复制!');
        };

        // 修复:重置所有控件状态
        $('clear-btn').onclick = () => {
            resetParams();

            // 重置所有控件为默认值
            $('stylize').value = 100;
            $('weird').value = 0;
            $('chaos').value = 0;
            $('mode-select').value = 'standard';
            $('version-select').value = 'v7';
            $('draft-toggle').checked = false;
            $('speed-select').value = 'relax';
            $('no-prompt').value = '';
            $('ratio-slider').value = 5;

            // 触发事件以更新UI
            $('stylize').dispatchEvent(new Event('input'));
            $('weird').dispatchEvent(new Event('input'));
            $('chaos').dispatchEvent(new Event('input'));
            $('mode-select').dispatchEvent(new Event('change'));
            $('version-select').dispatchEvent(new Event('change'));
            $('draft-toggle').dispatchEvent(new Event('change'));
            $('speed-select').dispatchEvent(new Event('change'));
            $('no-prompt').dispatchEvent(new Event('input'));
            $('ratio-slider').dispatchEvent(new Event('input'));

            // 清空输入框
            $('cref-url').value = '';
            $('sref-url').value = '';

            // 刷新预览
            refreshPreviews();

            // 显示重置成功提示
            alert('所有参数已重置为默认值');
        };

        // 绑定角色参考事件
        $('cref-add').onclick = () => {
            const url = $('cref-url').value.trim();
            if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
                if (!params.cref.includes(url)) {
                    params.cref.push(url);
                    addPreviewImage('cref-preview', url, 'cref');
                    $('cref-url').value = '';
                    updatePromptParams();
                } else {
                    alert('该URL已添加');
                }
            } else {
                alert('请输入有效图片URL');
            }
        };

        // 绑定风格参考事件
        $('sref-add').onclick = () => {
            const url = $('sref-url').value.trim();
            if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
                if (!params.sref.includes(url)) {
                    params.sref.push(url);
                    addPreviewImage('sref-preview', url, 'sref');
                    $('sref-url').value = '';
                    updatePromptParams();
                } else {
                    alert('该URL已添加');
                }
            } else {
                alert('请输入有效图片URL');
            }
        };

        // 绑定比例滑块事件
        const sizeMap = ['1:2', '19:6', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '6:19', '2:1'];
        const ratioPresets = {
            '1:2': { width: 50, height: 100 },
            '19:6': { width: 56.25, height: 100 },
            '2:3': { width: 66.67, height: 100 },
            '3:4': { width: 75, height: 100 },
            '5:6': { width: 83.33, height: 100 },
            '1:1': { width: 100, height: 100 },
            '6:5': { width: 100, height: 83.33 },
            '4:3': { width: 100, height: 75 },
            '3:2': { width: 100, height: 66.67 },
            '6:19': { width: 100, height: 56.25 },
            '2:1': { width: 100, height: 50 }
        };

        $('ratio-slider').oninput = e => {
            const ratio = sizeMap[+e.target.value] || '1:1';
            params.ar = ratio;
            const box = document.getElementById('ratio-box');
            const preset = ratioPresets[ratio] || { width: 100, height: 100 };
            box.style.width = `${preset.width}px`;
            box.style.height = `${preset.height}px`;
            box.textContent = ratio;
            updatePromptParams();
        };

        // 设置比例预设按钮
        const btnGroup = document.getElementById('size-buttons');
        const presetMap = {
            '纵向': '1:2',
            '正方形': '1:1',
            '横向': '2:1'
        };

        ['纵向','正方形','横向'].forEach((label, i) => {
            const btn = document.createElement('button');
            btn.textContent = label;
            btn.style.cssText = 'padding:4px 12px; border-radius:6px; border:1px solid #d1d5db; background:white; cursor:pointer;';

            // 修复:正确映射预设比例
            btn.onclick = () => {
                const ratio = presetMap[label];
                const sliderIndex = sizeMap.indexOf(ratio);

                if (sliderIndex !== -1) {
                    $('ratio-slider').value = sliderIndex;
                    $('ratio-slider').dispatchEvent(new Event('input'));
                }
            };

            btnGroup.appendChild(btn);
        });
    }

    // 添加预览图片并绑定删除功能
    function addPreviewImage(containerId, url, paramType) {
        const container = document.getElementById(containerId);
        const imgContainer = document.createElement('div');
        imgContainer.style.cssText = 'position: relative; margin: 4px;';

        const img = document.createElement('img');
        img.src = url;
        img.style.width = '60px';
        img.style.height = '60px';
        img.style.objectFit = 'cover';
        img.style.borderRadius = '4px';

        const deleteBtn = document.createElement('button');
        deleteBtn.style.cssText = 'position: absolute; top: -4px; right: -4px; background: rgba(0,0,0,0.7); color: white; border: none; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; line-height: 1; cursor: pointer;';
        deleteBtn.textContent = '×';
        deleteBtn.onclick = function(e) {
            e.stopPropagation();
            const index = paramType === 'cref'
                ? params.cref.indexOf(url)
                : params.sref.indexOf(url);

            if (index !== -1) {
                if (paramType === 'cref') {
                    params.cref.splice(index, 1);
                } else {
                    params.sref.splice(index, 1);
                }
                container.removeChild(imgContainer);
                updatePromptParams();
            }
        };

        imgContainer.appendChild(img);
        imgContainer.appendChild(deleteBtn);
        container.appendChild(imgContainer);
    }

    // 刷新预览区域
    function refreshPreviews() {
        ['cref', 'sref'].forEach(type => {
            const container = document.getElementById(`${type}-preview`);
            container.innerHTML = '';
            params[type].forEach(url => {
                addPreviewImage(`${type}-preview`, url, type);
            });
        });
    }

    function init() {
        resetParams();
        createSettingButton();
        createControlPanel();
        // 手动触发比例滑块的input事件以更新预览
        const ratioSlider = document.getElementById('ratio-slider');
        if (ratioSlider) ratioSlider.dispatchEvent(new Event('input'));
        updatePromptParams();
    }

    window.addEventListener('load', init);
})();