Discord Midjourney 参数可视化

在 Discord Midjourney 频道添加一个参数面板...

// ==UserScript==
// @name         Discord Midjourney 参数可视化
// @namespace    https://github.com/cwser
// @version      1.0.4
// @description  在 Discord Midjourney 频道添加一个参数面板...
// @author       cwser
// @match        https://discord.com/*
// @grant        unsafeWindow
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- 常量与参数定义 ---
    // --- Storage Keys ---
    const THEME_STORAGE_KEY = 'mjPanelThemePreference_v3';
    const PRESETS_STORAGE_KEY = 'mjPanelPresets_v1';
    const HISTORY_STORAGE_KEY = 'mjPanelHistory_v1';
    const MAX_HISTORY_ITEMS = 20;

    // 参数定义
    let params = {
        prompt: '',
        ar: '1:1',
        stylize: 100,
        weird: 0,
        chaos: 0,
        mode: 'standard', // standard, raw
        version: 'v7',   // v1, v2, v3, v4, v5, v5.1, v5.2, v6, v6.1, v7, niji4, niji5, niji6
        speed: 'relax',  // relax, fast, turbo
        draft: false,    // true, false (for v5/5.1/5.2/niji5)
        noPrompt: '',
        cref: [], // 每一项将是 { url: string, weight: string, enabled: boolean }
        sref: [], // 每一项将是 { url: string, weight: string, enabled: boolean } (现在支持独立权重)
        oref: [], // 每一项将是 { url: string, weight: string, enabled: boolean }
        directImages: [], // 每一项将是 { url: string, weight: string, enabled: boolean } (weight is the part after ::)
        iw: 1, // Global image weight for directImages, default 1, range 0-3
        sw: 100, // Global style weight for sref, default 100, range 0-1000
        cw: 100, // Global character weight for cref, default 100, range 0-100
        ow: 100, // Global overall weight for oref, default 100, range 0-1000
        tile: false,
        seed: '',
        quality: 1, // 0.25, 0.5, 1, 2, 4 (v4,v5,v6); 1 (niji default)
        stop: 100,  // 10-100
        visibility: '', // public, stealth (Pro plan only), '' (default)
        personalParams: '', // --p
        r: 1, // repeat
        includeImagine: false // 用于控制是否添加 /imagine prompt: 前缀
    };

    // --- 主题管理 ---
    // 主题相关变量
    let currentThemeMode = 'discord'; // 选项: 'light', 'dark', 'discord', 'system'
    const themeModes = ['light', 'dark', 'discord', 'system'];
    const themeTextMap = {
        'light': '浅色模式',
        'dark': '深色模式',
        'discord': '跟随Discord',
        'system': '跟随系统'
    };
    const sunIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M8 12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 12zm-6.39.261a.5.5 0 0 1 .707.707L3.732 11.26a.5.5 0 0 1-.707-.707L1.61 11.968zM12 8a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 0 1h-2a.5.5 0 0 1-.5-.5zM4.732 4.739a.5.5 0 0 1-.707-.707L5.439 2.61a.5.5 0 1 1 .707.707L4.732 4.739zM2.61 5.439a.5.5 0 0 1 .707.707L1.61 7.854a.5.5 0 1 1-.707-.707L2.61 5.439zM12 4a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2a.5.5 0 0 1 .5-.5zM4.032 1.61a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 1 1-.707.707L4.032 1.61zM11.26 1.61a.5.5 0 0 1 .707.707l-1.414 1.414a.5.5 0 1 1-.707-.707l1.414-1.414zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0z"/></svg>`;
    const moonIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/></svg>`;
    const discordIconSVG = `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M20.317 4.488c-1.54-.83-3.263-1.463-5.086-1.854a.934.934 0 00-1.003.693c-.21 1.207-.662 2.343-1.204 3.392-2.816-.087-4.804-1.53-4.804-1.53s-.103.2-.186.377C7.659 7.992 7.136 9.31 7.136 9.31s-1.482-.532-2.884-1.318c0 0-1.19 3.204 2.42 6.03 0 0-1.806 1.27-3.68 1.744a17.56 17.56 0 003.032 1.22c1.45.463 2.938.717 4.465.717 1.526 0 3.016-.254 4.465-.717.046-.016.09-.03.135-.046l.004-.002c.03-.01.06-.018.09-.027.08-.026.157-.05.237-.078.068-.025.136-.05.203-.076.085-.034.17-.066.253-.1.07-.03.14-.06.208-.09.087-.04.173-.078.26-.118.062-.03.124-.06.185-.09.09-.046.18-.09.268-.14.06-.034.12-.07.178-.1.092-.05.183-.1.273-.156.053-.033.106-.065.158-.1.088-.055.175-.11.26-.17.046-.03.092-.06.137-.093.082-.06.163-.12.242-.185.04-.03.08-.06.118-.09.075-.062.148-.124.22-.188.035-.03.068-.06.103-.09.067-.06.132-.12.196-.183.026-.025.05-.05.076-.075.168-.16.33-.322.488-.488.093-.1.184-.2.27-.3.027-.03.053-.06.078-.09.13-.15.255-.3.375-.456.023-.03.046-.06.067-.09.102-.14.2-.28.293-.42.018-.028.036-.055.052-.083.078-.13.15-.26.218-.39.01-.02.02-.04.03-.06.06-.11.112-.22.162-.33.005-.01.01-.02.015-.03.044-.1.082-.19.118-.29.002-.006.004-.01.005-.016.03-.08.056-.16.08-.24.022-.07.04-.14.056-.21.015-.06.027-.12.038-.18.01-.05.018-.09.025-.14.006-.04.01-.08.014-.12.003-.03.005-.06.006-.09.002-.04.002-.07 0-.11s0-.03-.002-.045c-.06-1.597-.27-3.143-.62-4.618zm-4.603 7.06c-1.232 0-2.232-1.022-2.232-2.282s.998-2.282 2.232-2.282c1.232 0 2.232 1.022 2.232 2.282s-1.002 2.282-2.232-2.282zm-5.94-2.282c0 1.26.998 2.282 2.232 2.282s2.232-1.022 2.232-2.282S10.94 7.026 9.707 7.026c-1.232 0-2.232 1.022-2.232 2.282z"/></svg>`;
    const systemIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M5 4a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5zm-.5 7.5A.5.5 0 0 1 4 11V5a.5.5 0 0 1 .5-.5h6a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-.5.5H4.5z"/><path d="M1.5 2A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13zM1 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9z"/></svg>`;
    const uploadIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 16 16"><path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/><path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708l3-3z"/></svg>`;
    const editIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" viewBox="0 0 16 16"><path d="M15.502 1.94a.5.5 0 0 1 0 .706L14.459 3.69l-2-2L13.502.646a.5.5 0 0 1 .707 0l1.293 1.293zm-1.75 2.456-2-2L4.939 9.21a.5.5 0 0 0-.121.196l-.805 2.414a.25.25 0 0 0 .316.316l2.414-.805a.5.5 0 0 0 .196-.12l6.813-6.814z"/><path fill-rule="evenodd" d="M1 13.5A1.5 1.5 0 0 0 2.5 15h11a1.5 1.5 0 0 0 1.5-1.5v-6a.5.5 0 0 0-1 0v6a.5.5 0 0 1-.5.5h-11a.5.5 0 0 1-.5-.5v-11a.5.5 0 0 1 .5-.5H9a.5.5 0 0 0 0-1H2.5A1.5 1.5 0 0 0 1 2.5v11z"/></svg>`;

    const themeIcons = {
        'light': sunIconSVG,
        'dark': moonIconSVG,
        'discord': discordIconSVG,
        'system': systemIconSVG
    };

    let systemThemeMediaQuery = null;
    let discordThemeObserver = null;

    function getEffectiveDarkModeState() {
        switch (currentThemeMode) {
            case 'light': return false;
            case 'dark': return true;
            case 'discord': return document.documentElement.classList.contains('theme-dark');
            case 'system': return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
            default: return document.documentElement.classList.contains('theme-dark');
        }
    }

    function applyCurrentTheme() {
        const panel = document.getElementById('mj-control-panel');
        if (!panel) return;
        const effectiveDarkMode = getEffectiveDarkModeState();
        panel.classList.toggle('dark-mode', effectiveDarkMode);
        localStorage.setItem(THEME_STORAGE_KEY, currentThemeMode);
        const themeTriggerIcon = document.getElementById('theme-trigger-icon');
        const themeTriggerText = document.getElementById('theme-trigger-text');
        if (themeTriggerIcon) {
            let iconToShow = themeIcons[currentThemeMode] || sunIconSVG;
            if (currentThemeMode === 'discord' || currentThemeMode === 'system') {
                iconToShow = effectiveDarkMode ? moonIconSVG : sunIconSVG;
            }
            themeTriggerIcon.innerHTML = iconToShow;
        }
        if (themeTriggerText) themeTriggerText.textContent = themeTextMap[currentThemeMode] || '未知主题';
        const themeOptionsMenu = document.getElementById('theme-options-menu');
        if (themeOptionsMenu) {
            themeOptionsMenu.querySelectorAll('button').forEach(opt => {
                opt.classList.toggle('active', opt.dataset.theme === currentThemeMode);
            });
        }
        setupDynamicThemeListeners();
    }

    function handleSystemThemeChange() { if (currentThemeMode === 'system') applyCurrentTheme(); }

    function handleDiscordThemeChange(mutationsList) {
         if (currentThemeMode === 'discord') {
            for (const mutation of mutationsList) {
                if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                    applyCurrentTheme(); break;
                }
            }
        }
    }

    function setupDynamicThemeListeners() {
        if (systemThemeMediaQuery) {
            systemThemeMediaQuery.removeEventListener ? systemThemeMediaQuery.removeEventListener('change', handleSystemThemeChange) : systemThemeMediaQuery.removeListener(handleSystemThemeChange);
            systemThemeMediaQuery = null;
        }
        if (discordThemeObserver) {
            discordThemeObserver.disconnect();
            discordThemeObserver = null;
        }

        if (currentThemeMode === 'system' && window.matchMedia) {
            systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
            systemThemeMediaQuery.addEventListener ? systemThemeMediaQuery.addEventListener('change', handleSystemThemeChange) : systemThemeMediaQuery.addListener(handleSystemThemeChange);
        } else if (currentThemeMode === 'discord' && typeof MutationObserver !== "undefined") {
            discordThemeObserver = new MutationObserver(handleDiscordThemeChange);
            discordThemeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
        }
    }

    // --- UI元素创建与控制 ---
    function showToast(message) {
        const toast = document.createElement('div');
        toast.textContent = message;
        toast.className = 'mj-toast';
        document.body.appendChild(toast);
        setTimeout(() => { toast.classList.add('show'); }, 10);
        setTimeout(() => {
            toast.classList.remove('show');
            setTimeout(() => { if (document.body.contains(toast)) document.body.removeChild(toast); }, 300);
        }, 2500);
    }

    function createSettingButton() {
        const button = document.createElement('button');
        button.textContent = 'MJ参数';
        button.id = 'mj-floating-settings-button';
        button.addEventListener('click', toggleControlPanel);
        document.body.appendChild(button);
    }

    function toggleControlPanel() {
        const panel = document.getElementById('mj-control-panel');
        if (panel) {
            panel.classList.toggle('visible');
             if (!panel.classList.contains('visible')) {
                const themeMenu = document.getElementById('theme-options-menu');
                if (themeMenu) themeMenu.style.display = 'none';
            }
        }
    }

    function createControlPanel() {
        const panel = document.createElement('div');
        panel.id = 'mj-control-panel';
        panel.innerHTML = `
            <div class="panel-header">
                <h3 class="panel-title">Midjourney 参数设置</h3>
                <div class="panel-header-actions">
                    <div style="position:relative;">
                        <button id="theme-dropdown-trigger" title="切换主题模式" class="theme-trigger-btn">
                            <span id="theme-trigger-icon"></span><span id="theme-trigger-text"></span>
                        </button>
                        <div id="theme-options-menu" class="theme-options-menu" style="display:none;">
                            <button data-theme="light" class="theme-option-button">${themeIcons.light} ${themeTextMap.light}</button>
                            <button data-theme="dark" class="theme-option-button">${themeIcons.dark} ${themeTextMap.dark}</button>
                            <button data-theme="discord" class="theme-option-button">${themeIcons.discord} ${themeTextMap.discord}</button>
                            <button data-theme="system" class="theme-option-button">${themeIcons.system} ${themeTextMap.system}</button>
                        </div>
                    </div>
                </div>
            </div>

            <div class="panel-main-content">
                <nav class="panel-tabs">
                    <button class="tab-link active" data-tab="tab-main">主要参数</button>
                    <button class="tab-link" data-tab="tab-references">图像参考</button>
                    <button class="tab-link" data-tab="tab-advanced">高级设置</button>
                    <button class="tab-link" data-tab="tab-presets">预设模板</button>
                    <button class="tab-link" data-tab="tab-history">历史记录</button>
                </nav>

                <div id="tab-main" class="tab-content active">
                    <div class="form-grid">
                        <div class="form-group">
                            <label for="main-prompt">主要提示词</label>
                            <textarea id="main-prompt" placeholder="输入主要提示词..."></textarea>
                        </div>
                         <div class="form-group">
                            <label for="no-prompt">排除词 (--no)</label>
                            <textarea id="no-prompt" placeholder="输入需要排除的元素..."></textarea>
                        </div>
                        <div class="form-group span-2" id="ar-section">
                            <label>图片尺寸 (--ar)</label>
                            <div class="ar-controls">
                                <div class="ar-preview-container">
                                    <div id="ratio-preview-bg"></div>
                                    <div id="ratio-preview"><div id="ratio-box">1:1</div></div>
                                </div>
                                <div class="ar-slider-group">
                                    <div id="size-buttons" class="btn-group"></div>
                                    <input type="range" id="ratio-slider" min="0" max="10" value="5">
                                </div>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="stylize">风格化 (--s)</label>
                            <div class="slider-control"><input type="range" id="stylize" min="0" max="1000" value="100" step="50"><span id="stylize-value">100</span></div>
                        </div>
                        <div class="form-group">
                            <label for="chaos">多样性 (--c)</label>
                            <div class="slider-control"><input type="range" id="chaos" min="0" max="100" value="0" step="5"><span id="chaos-value">0</span></div>
                        </div>
                        <div class="form-group">
                            <label for="weird">奇特化 (--w)</label>
                             <div class="slider-control"><input type="range" id="weird" min="0" max="3000" value="0" step="100"><span id="weird-value">0</span></div>
                        </div>
                         <div class="form-group">
                            <label for="version-select">版本 (--v, --niji)</label>
                            <select id="version-select">
                                <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>
                        </div>
                    </div>
                </div>

                <div id="tab-references" class="tab-content">
                    <div class="ref-grid-main">
                        <div class="ref-module" data-type="directImages">
                            <div class="ref-module-header">
                                <h4>图片提示 (--iw)</h4>
                                <div class="global-weight-control">
                                    <label for="iw-slider">整体强度</label>
                                    <div class="weight-slider-mini">
                                        <input type="range" id="iw-slider" min="0" max="3" value="1" step="0.1">
                                        <span id="iw-value">1</span>
                                    </div>
                                </div>
                            </div>
                            <div class="ref-input-section">
                                <input type="text" placeholder="图片URL" class="ref-url-input">
                                <input type="text" placeholder="权重" class="ref-weight-input">
                                <button class="ref-add-btn">添加</button>
                            </div>
                            <div class="ref-container-large" id="directImages-preview"></div>
                        </div>
                        <div class="ref-module" data-type="cref">
                            <div class="ref-module-header">
                                <h4>角色参考 (--cref)</h4>
                                <div class="global-weight-control">
                                    <label for="cw-slider">整体强度</label>
                                    <div class="weight-slider-mini">
                                        <input type="range" id="cw-slider" min="0" max="100" value="100" step="5">
                                        <span id="cw-value">100</span>
                                    </div>
                                </div>
                            </div>
                            <div class="ref-input-section">
                                <input type="text" placeholder="角色图片URL" class="ref-url-input">
                                <input type="number" placeholder="权重" class="ref-weight-input">
                                <button class="ref-add-btn">添加</button>
                            </div>
                            <div class="ref-container-large" id="cref-preview"></div>
                        </div>
                        <div class="ref-module" data-type="sref">
                            <div class="ref-module-header">
                                <h4>风格参考 (--sref)</h4>
                                <div class="global-weight-control">
                                    <label for="sw-slider">整体强度</label>
                                    <div class="weight-slider-mini">
                                        <input type="range" id="sw-slider" min="0" max="1000" value="100" step="10"><span id="sw-value">100</span>
                                    </div>
                                </div>
                            </div>
                            <div class="ref-input-section">
                                <input type="text" placeholder="URL、random或数字码" class="ref-url-input">
                                <input type="number" placeholder="权重" class="ref-weight-input">
                                <button class="ref-add-btn">添加</button>
                            </div>
                            <div class="ref-container-large" id="sref-preview"></div>
                        </div>
                        <div class="ref-module" data-type="oref">
                            <div class="ref-module-header">
                                <h4>全方位参考 (--oref)</h4>
                                <div class="global-weight-control">
                                    <label for="ow-slider">整体强度</label>
                                    <div class="weight-slider-mini">
                                        <input type="range" id="ow-slider" min="0" max="1000" value="100" step="50">
                                        <span id="ow-value">100</span>
                                    </div>
                                </div>
                            </div>
                            <div class="ref-input-section">
                                <input type="text" placeholder="参考图片URL" class="ref-url-input">
                                <input type="number" placeholder="权重" class="ref-weight-input">
                                <button class="ref-add-btn">添加</button>
                            </div>
                            <div class="ref-container-large" id="oref-preview"></div>
                        </div>
                    </div>
                </div>

                <div id="tab-advanced" class="tab-content">
                   <div class="form-grid">
                        <div class="form-group">
                            <label>速度</label>
                            <div class="btn-group speed-btn-group">
                                <button data-value="relax" class="speed-btn active">标准</button><button data-value="fast" class="speed-btn">快速</button><button data-value="turbo" class="speed-btn">极速</button>
                            </div>
                        </div>
                        <div class="form-group">
                            <label>模式</label>
                            <div class="btn-group mode-btn-group">
                                <button data-value="standard" class="mode-btn active">标准</button><button data-value="raw" class="mode-btn">原始</button>
                            </div>
                        </div>
                         <div class="form-group">
                            <label>可见性</label>
                            <div class="btn-group visibility-btn-group">
                                <button data-value="" class="visibility-btn active">默认</button><button data-value="public" class="visibility-btn">公开</button><button data-value="stealth" class="visibility-btn">隐身</button>
                            </div>
                        </div>
                        <div class="form-group">
                            <label for="seed-input">种子 (--seed)</label>
                            <input type="number" id="seed-input" placeholder="0-4294967295" min="0" max="4294967295">
                        </div>
                        <div class="form-group">
                            <label for="quality-slider">质量 (--q)</label>
                            <div class="slider-control"><input type="range" id="quality-slider" min="0" max="4" step="1" value="2"><span id="quality-value">1</span></div>
                        </div>
                        <div class="form-group">
                            <label for="stop-slider">停止 (--stop)</label>
                            <div class="slider-control"><input type="range" id="stop-slider" min="10" max="100" step="1" value="100"><span id="stop-value">100</span></div>
                        </div>
                        <div class="form-group">
                            <label for="r-slider">批量任务 (--r)</label>
                            <div class="slider-control"><input type="range" id="r-slider" min="1" max="10" value="1"><span id="r-value">1</span></div>
                        </div>
                         <div class="form-group">
                            <label for="personal-params">个性化 (--p)</label>
                            <input type="text" id="personal-params" placeholder="输入个性化参数">
                        </div>
                        <div class="form-group toggle-group">
                            <label for="tile-toggle-switch">重复图案 (--tile)</label>
                            <div class="toggle-switch" id="tile-toggle-switch"><div class="toggle-dot"></div></div>
                        </div>
                         <div class="form-group toggle-group">
                            <label for="draft-toggle-switch">草稿模式 (--draft)</label>
                            <div class="toggle-switch" id="draft-toggle-switch"><div class="toggle-dot"></div></div>
                        </div>
                   </div>
                </div>

                <div id="tab-presets" class="tab-content">
                    <div class="presets-controls">
                        <input type="text" id="preset-name-input" placeholder="输入预设名称..." class="preset-input-name form-group input">
                        <button id="save-preset-btn" class="action-button-primary preset-save-button">保存当前为预设</button>
                    </div>
                    <h4 class="settings-subheader">已保存的预设:</h4>
                    <ul id="presets-list-display" class="settings-list">
                        {/* 预设项会动态添加到这里 */}
                    </ul>
                </div>

                <div id="tab-history" class="tab-content">
                    <div class="history-controls">
                        <h4 class="settings-subheader">最近 <span id="history-limit-display">${MAX_HISTORY_ITEMS}</span> 条生成历史:</h4>
                        <button id="clear-history-btn" class="action-button-secondary history-clear-button">清空历史记录</button>
                    </div>
                    <ul id="history-list-display" class="settings-list">
                        {/* 历史项会动态添加到这里 */}
                    </ul>
                </div>
            </div>

            <div class="panel-footer">
                <textarea id="prompt-params" placeholder="在此处粘贴Midjourney指令后点击“解析”,或查看最终生成的参数..."></textarea>
                <div class="footer-actions">
                    <div class="toggle-group imagine-toggle">
                        <label for="imagine-toggle-switch">添加 /imagine</label>
                        <div class="toggle-switch" id="imagine-toggle-switch"><div class="toggle-dot"></div></div>
                    </div>
                    <div class="footer-buttons">
                         <button id="parse-btn" class="action-button-secondary">解析</button>
                         <button id="clear-btn" class="action-button-secondary">清空</button>
                         <button id="copy-btn" class="action-button-primary">拷贝</button>
                    </div>
                </div>
            </div>
        `;
        document.body.appendChild(panel);

        const savedTheme = localStorage.getItem(THEME_STORAGE_KEY);
        currentThemeMode = (savedTheme && themeModes.includes(savedTheme)) ? savedTheme : 'discord';

        bindControlEvents();
        applyCurrentTheme();

        document.addEventListener('click', function(event) {
            const themeMenu = document.getElementById('theme-options-menu');
            const themeTrigger = document.getElementById('theme-dropdown-trigger');
            if (themeMenu && themeTrigger && themeMenu.style.display === 'block' && !themeMenu.contains(event.target) && !themeTrigger.contains(event.target)) {
                themeMenu.style.display = 'none';
            }
        });
    }

    // --- 核心参数处理与指令生成 ---
    function resetParams() {
        params = {
            prompt: '', ar: '1:1', stylize: 100, weird: 0, chaos: 0, mode: 'standard',
            version: 'v7', speed: 'relax', draft: false, noPrompt: '',
            iw: 1, sw: 100, cw: 100, ow: 100, tile: false, seed: '',
            quality: 1, stop: 100, visibility: '', personalParams: '', r: 1,
            includeImagine: false
        };
        params.cref = [];
        params.sref = [];
        params.oref = [];
        params.directImages = [];
    }

    // MODIFIED formatImageWithWeight function for output
    const formatImageWithWeight = (url, weightValue) => {
        const weightStr = (typeof weightValue === 'string' || typeof weightValue === 'number') ? String(weightValue).trim() : '';
        if (weightStr === '') { // 如果权重为空,则只返回URL
            return url;
        }
        // 否则,返回 "URL" + "空格" + "::" + "权重值"
        // 例如: http://image.url ::1.5
        return `${url} ::${weightStr}`;
    };

    function updatePromptParams() {
        const { prompt, ar, stylize, weird, chaos, mode, draft, noPrompt, version, speed, tile, seed, quality, stop, visibility, personalParams, includeImagine, iw, sw, cw, ow } = params;
        const { cref, sref, oref, directImages } = params;

        const otherParts = [
            ar ? `--ar ${ar}` : '',
            `--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 ? `--${speed}` : '',
            tile ? '--tile' : '',
            seed ? `--seed ${seed}` : '',
            quality !== 1 ? `--q ${quality}` : '',
            stop !== 100 ? `--stop ${stop}` : '',
            visibility ? `--${visibility}` : '',
            personalParams ? `--p ${personalParams}` : '',
            params.r > 1 ? `--r ${params.r}` : ''
        ].filter(Boolean);

        const directImageUrlsArr = directImages
            .filter(item => item.enabled)
            .map(item => formatImageWithWeight(item.url, item.weight)); // Uses the MODIFIED function
        let directImageSection = directImageUrlsArr.join(' ');

        const enabledCrefs = cref.filter(item => item.enabled);
        let crefSection = '';
        if (enabledCrefs.length > 0) {
            const crefUrlsWithWeights = enabledCrefs.map(item => formatImageWithWeight(item.url, item.weight)); // Uses the MODIFIED function
            crefSection = `--cref ${crefUrlsWithWeights.join(' ')}`;
            if (cw !== 100) crefSection += ` --cw ${cw}`;
        }

        const enabledSrefs = sref.filter(item => item.enabled);
        let srefSection = '';
        if (enabledSrefs.length > 0) {
            const srefUrlsWithWeights = enabledSrefs.map(item => formatImageWithWeight(item.url, item.weight)); // Uses the MODIFIED function
            srefSection = `--sref ${srefUrlsWithWeights.join(' ')}`;
            if (params.sw !== 100) srefSection += ` --sw ${params.sw}`;
        }

        const orefUrls = oref.filter(item => item.enabled).map(item => {
             return `--oref ${formatImageWithWeight(item.url, item.weight)}`; // Uses the MODIFIED function
        });

        let orefSectionGlobal = '';
        if (oref.filter(item => item.enabled).length > 0 && ow !== 100) {
            orefSectionGlobal = `--ow ${ow}`;
        }

        const promptField = document.getElementById('prompt-params');
        if (promptField) {
            const mainPromptPart = prompt.trim();
            const iwPart = (params.iw !== 1 && typeof params.iw !== 'undefined' && directImageUrlsArr.length > 0) ? `--iw ${params.iw}` : '';

            const allParts = [
                directImageSection, mainPromptPart, iwPart, crefSection, srefSection,
                ...orefUrls, orefSectionGlobal, ...otherParts
            ].filter(Boolean);

            let finalPromptString = allParts.join(' ').trim().replace(/\s+/g, ' ');
            if (includeImagine && finalPromptString) {
                finalPromptString = `/imagine prompt: ${finalPromptString}`;
            }
            promptField.value = finalPromptString;
        }
    }

    // --- UI更新 ---
    function setInitialActiveButtons() {
        const $ = id => document.getElementById(id);
        const buttonGroups = [
            { className: 'speed-btn', param: 'speed' },
            { className: 'mode-btn', param: 'mode' },
            { className: 'visibility-btn', param: 'visibility' }
        ];
        buttonGroups.forEach(group => {
            document.querySelectorAll(`.${group.className}`).forEach(btn => {
                btn.classList.toggle('active', btn.dataset.value === params[group.param]);
            });
        });
        const ratioSlider = $('ratio-slider');
        if (ratioSlider) {
            const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1'];
            const ratioIndex = sizeMap.indexOf(params.ar);
            ratioSlider.value = ratioIndex !== -1 ? ratioIndex : 5;
            ratioSlider.dispatchEvent(new Event('input'));
        }
    }

    function updateToggleVisuals(switchId, property) {
        const switchEl = document.getElementById(switchId);
        if (switchEl) switchEl.classList.toggle('active', !!params[property]);
    }

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

        if ($('main-prompt')) $('main-prompt').value = params.prompt;
        if ($('no-prompt')) $('no-prompt').value = params.noPrompt;

        const sliderConfigs = [
            { key: 'stylize', sliderId: 'stylize', displayId: 'stylize-value', defaultValue: 100 },
            { key: 'weird', sliderId: 'weird', displayId: 'weird-value', defaultValue: 0 },
            { key: 'chaos', sliderId: 'chaos', displayId: 'chaos-value', defaultValue: 0 },
            { key: 'iw', sliderId: 'iw-slider', displayId: 'iw-value', defaultValue: 1 },
            { key: 'sw', sliderId: 'sw-slider', displayId: 'sw-value', defaultValue: 100 },
            { key: 'cw', sliderId: 'cw-slider', displayId: 'cw-value', defaultValue: 100 },
            { key: 'ow', sliderId: 'ow-slider', displayId: 'ow-value', defaultValue: 100 },
            { key: 'stop', sliderId: 'stop-slider', displayId: 'stop-value', defaultValue: 100 },
            { key: 'r', sliderId: 'r-slider', displayId: 'r-value', defaultValue: 1 }
        ];
        sliderConfigs.forEach(conf => {
            const slider = $(conf.sliderId); const display = $(conf.displayId);
            if (slider) {
                 const sliderMax = parseFloat(slider.max);
                 slider.value = Math.min(params[conf.key] !== undefined ? params[conf.key] : conf.defaultValue , sliderMax);
            }
            if (display) display.textContent = slider ? slider.value : (params[conf.key] !== undefined ? params[conf.key] : conf.defaultValue);
        });

        const qualityMap = [0.25, 0.5, 1, 2, 4];
        const qualitySlider = $('quality-slider'); const qualityValueDisplay = $('quality-value');
        if (qualitySlider && qualityValueDisplay) {
            const currentQuality = parseFloat(params.quality);
            const idx = qualityMap.indexOf(currentQuality);
            qualitySlider.value = idx !== -1 ? idx : qualityMap.indexOf(1);
            qualityValueDisplay.textContent = qualityMap[parseInt(qualitySlider.value,10)];
        }

        if ($('version-select')) $('version-select').value = params.version;
        if ($('seed-input')) $('seed-input').value = params.seed;
        if ($('personal-params')) $('personal-params').value = params.personalParams;

        const ratioSlider = $('ratio-slider');
        const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1'];
        if (ratioSlider) {
            const ratioIndex = sizeMap.indexOf(params.ar);
            ratioSlider.value = ratioIndex !== -1 ? ratioIndex : sizeMap.indexOf('1:1');
            ratioSlider.dispatchEvent(new Event('input'));
        }

        setInitialActiveButtons();

        updateToggleVisuals('tile-toggle-switch', 'tile');
        updateToggleVisuals('draft-toggle-switch', 'draft');
        updateToggleVisuals('imagine-toggle-switch', 'includeImagine');

        refreshPreviews();
        updatePromptParams();
    }

    // --- 图像参考项处理 (通用函数) ---
    function setupRefSection() {
        document.querySelectorAll('.ref-module').forEach(module => {
            const paramKey = module.dataset.type;
            const addBtn = module.querySelector('.ref-add-btn');
            const urlInput = module.querySelector('.ref-url-input');
            const weightInput = module.querySelector('.ref-weight-input');
            const container = module.querySelector('.ref-container-large');

            if (addBtn && urlInput) {
                addBtn.onclick = () => {
                    const urlValue = urlInput.value.trim();
                    if (!urlValue) {
                        showToast(`请输入${getRefTypeDisplayName(paramKey)}URL`);
                        return;
                    }
                    const weightValue = weightInput ? weightInput.value.trim() : '';
                    addReferenceItem(paramKey, urlValue, weightValue);
                    urlInput.value = '';
                    if (weightInput) weightInput.value = '';
                };
            }
            if (container) {
                let overlay = container.querySelector('.drop-overlay');
                if (!overlay) {
                    overlay = document.createElement('div');
                    overlay.className = 'drop-overlay';
                    overlay.innerHTML = `<div class="drop-overlay-content"><div class="drop-icon">${uploadIconSVG}</div><div class="drop-text">松开即可添加</div></div>`;
                    overlay.style.display = 'none';
                    container.appendChild(overlay);
                }
                setupDropZoneEvents(container, paramKey);
            }
        });
    }

    function getRefTypeDisplayName(paramKey) {
        const nameMap = { 'directImages': '图片提示', 'cref': '角色参考', 'sref': '风格参考', 'oref': '全方位参考' };
        return nameMap[paramKey] || paramKey;
    }

    function setupDropZoneEvents(dropZone, paramKey) {
        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
            dropZone.classList.add('drag-over');
            showDropMessage(dropZone, "松开即可添加");
        });
        dropZone.addEventListener('dragleave', (e) => {
            e.preventDefault();
            e.stopPropagation();
            if (!dropZone.contains(e.relatedTarget)) {
                dropZone.classList.remove('drag-over');
                hideDropMessage(dropZone);
            }
        });
        dropZone.addEventListener('drop', (e) => {
            e.preventDefault(); e.stopPropagation();
            dropZone.classList.remove('drag-over'); hideDropMessage(dropZone);
            const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain');
            if (!url) { showToast('无法获取拖拽内容的URL'); return; }
            addReferenceItem(paramKey, url, '');
        });
    }

    function showDropMessage(container, messageText) {
        const overlay = container.querySelector('.drop-overlay');
        if (overlay) {
            overlay.style.display = 'flex';
        }
    }

    function hideDropMessage(container) {
        const overlay = container.querySelector('.drop-overlay');
        if (overlay) {
            overlay.style.display = 'none';
        }
    }

    function addReferenceItem(paramKey, urlValue, weightValue = '') {
        let itemUrl = urlValue;
        let itemWeight = weightValue.trim();

        if (paramKey === 'directImages') {
            if (itemWeight !== '' && !/^\d*\.?\d*$/.test(itemWeight)) {
                showToast('图片独立权重必须是数字 (例如 0.5, 1, 2) 或留空'); return;
            }
        }

        const isImageUrl = /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(itemUrl);
        const isSrefCode = paramKey === 'sref' && (/^\d+$/.test(itemUrl) || itemUrl.toLowerCase() === 'random');

        if (paramKey !== 'sref' && !isImageUrl) { showToast('请输入有效的图片URL'); return; }
        if (paramKey === 'sref' && !isImageUrl && !isSrefCode) { showToast("sref请输入有效图片URL, 'random'或数字代码"); return; }

        const targetArray = params[paramKey];
        if (!Array.isArray(targetArray)) return;
        const checkUrl = (paramKey === 'sref' && isSrefCode) ? itemUrl.toLowerCase() : itemUrl;

        if (!targetArray.some(item => item.url === checkUrl && item.weight === itemWeight)) {
            const newItem = { url: checkUrl, weight: itemWeight, enabled: true };
            targetArray.push(newItem);
            addPreviewItem(paramKey, newItem);
            updatePromptParams();
            showToast(`已添加 ${getRefTypeDisplayName(paramKey)}`);
        } else {
            showToast(`该${getRefTypeDisplayName(paramKey)}已添加`);
        }
    }

    function showWeightEditDialog(item, paramKey, previewItem) {
        const dialog = document.createElement('div');
        dialog.className = 'weight-edit-dialog';
        const currentWeight = item.weight || '';
        dialog.innerHTML = `
            <div class="weight-edit-content">
                <h4>编辑权重</h4>
                <div class="weight-edit-input-group">
                    <label>权重值:</label>
                    <input type="text" class="weight-edit-input" value="${currentWeight}" placeholder="留空为默认权重">
                </div>
                <div class="weight-edit-buttons">
                    <button class="weight-edit-cancel">取消</button>
                    <button class="weight-edit-save">保存</button>
                </div>
            </div>
            <div class="weight-edit-overlay"></div>`;
        document.body.appendChild(dialog);
        const input = dialog.querySelector('.weight-edit-input');
        const saveBtn = dialog.querySelector('.weight-edit-save');
        const cancelBtn = dialog.querySelector('.weight-edit-cancel');
        const overlay = dialog.querySelector('.weight-edit-overlay');
        input.focus(); input.select();
        const closeDialog = () => { if (document.body.contains(dialog)) document.body.removeChild(dialog); };
        const saveWeight = () => {
            const newWeight = input.value.trim();
            if (newWeight !== '' && !/^\d*\.?\d*$/.test(newWeight)) {
                showToast('权重必须是数字 (例如 0.5, 1, 2) 或留空'); return;
            }
            item.weight = newWeight;
            updatePreviewItemWeight(previewItem, item, paramKey);
            updatePromptParams(); closeDialog(); showToast('权重已更新');
        };
        saveBtn.onclick = saveWeight;
        cancelBtn.onclick = closeDialog;
        overlay.onclick = closeDialog;
        input.addEventListener('keypress', (e) => { if (e.key === 'Enter') saveWeight(); });
        dialog.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDialog(); });
    }

    function updatePreviewItemWeight(previewItem, item, paramKey) {
        const weightDisplay = previewItem.querySelector('.weight-large');
        if (weightDisplay) {
            if (item.weight && item.weight.trim() !== '') {
                let prefix = "::"; // UI预览中的固定前缀
                weightDisplay.textContent = `${prefix}${item.weight}`; // 例如 ::1.5
                weightDisplay.style.display = 'block';
            } else {
                weightDisplay.style.display = 'none';
            }
        }
    }

    function addPreviewItem(paramKey, item) {
        const container = document.getElementById(`${paramKey}-preview`);
        if (!container) return;
        const isSrefCode = paramKey === 'sref' && (item.url.toLowerCase() === 'random' || /^\d+$/.test(item.url));
        const previewItem = document.createElement('div');
        previewItem.className = `ref-item-large ${isSrefCode ? 'code-item' : 'image-item'}`;
        if (typeof item.enabled !== 'undefined' && !item.enabled) {
             previewItem.classList.add('disabled');
        } else if (typeof item.enabled === 'undefined') {
            item.enabled = true;
        }

        let contentHtml = isSrefCode ? `<div class="ref-code-large">${item.url}</div>`
                                      : `<img src="${item.url}" class="ref-image-large" alt="参考图" onerror="this.parentElement.classList.add('load-error'); this.parentElement.innerHTML='<div class=\\'ref-error-large\\'>加载失败</div>';">`;

        // 使用 updatePreviewItemWeight 的逻辑来确保UI预览一致性
        let weightTextForDisplay = '';
        if (item.weight && item.weight.trim() !== '') {
            weightTextForDisplay = `::${item.weight.trim()}`; // e.g., ::1.5
        }
        const weightDisplaySpan = `<span class="weight-large" style="${weightTextForDisplay ? '' : 'display:none;'}">${weightTextForDisplay}</span>`;


        previewItem.innerHTML = `
            ${contentHtml}
            ${weightDisplaySpan}
            <button class="ref-edit-large" title="编辑权重">${editIconSVG}</button>
            <button class="ref-toggle-large ${item.enabled ? 'active' : ''}" title="启用/禁用">●</button>
            <button class="ref-delete-large" title="删除">×</button>`;
        container.appendChild(previewItem);

        const editBtn = previewItem.querySelector('.ref-edit-large');
        const toggleBtn = previewItem.querySelector('.ref-toggle-large');
        const deleteBtn = previewItem.querySelector('.ref-delete-large');

        if (editBtn) editBtn.onclick = (e) => { e.stopPropagation(); if (previewItem.classList.contains('load-error')) return; showWeightEditDialog(item, paramKey, previewItem); };
        if (toggleBtn) toggleBtn.onclick = (e) => {
            e.stopPropagation(); if (previewItem.classList.contains('load-error')) return;
            item.enabled = !item.enabled;
            toggleBtn.classList.toggle('active', item.enabled); previewItem.classList.toggle('disabled', !item.enabled);
            updatePromptParams();
        };
        if (deleteBtn) deleteBtn.onclick = (e) => {
            e.stopPropagation();
            const targetArray = params[paramKey];
            const index = targetArray.findIndex(i => i.url === item.url && i.weight === item.weight);
            if (index !== -1) { targetArray.splice(index, 1); previewItem.remove(); updatePromptParams(); }
        };
    }

    function refreshPreviews() {
        const refTypes = ['directImages', 'cref', 'sref', 'oref'];
        refTypes.forEach(paramKey => {
            const container = document.getElementById(`${paramKey}-preview`);
            if (container) {
                container.innerHTML = '';
                const items = params[paramKey];
                if (items && Array.isArray(items)) {
                    items.forEach(item => {
                        if (typeof item.enabled === 'undefined') item.enabled = true;
                        addPreviewItem(paramKey, item);
                    });
                }
            }
        });
    }

    // --- 指令解析 ---
    const parseRefUrlsWithWeights = (valueString) => {
        const items = [];
        const normalizedValueString = valueString.replace(/\s*::\s*/g, '::');
        const tokens = normalizedValueString.split(/\s+/).filter(t => t.trim() !== '');

        tokens.forEach(token => {
            const parts = token.split('::');
            const urlOrCode = parts[0].trim();
            let weight = '';

            if (parts.length > 1 && parts[1].trim() !== '') {
                weight = parts[1].trim();
            }

            if (urlOrCode) {
                const isSrefSpecific = (urlOrCode.toLowerCase() === 'random' || /^\d+$/.test(urlOrCode));
                const looksLikeUrl = urlOrCode.match(/^https?:\/\//i) || urlOrCode.includes('.');

                if (looksLikeUrl || isSrefSpecific) {
                     items.push({ url: urlOrCode, weight: weight, enabled: true });
                }
            }
        });
        return items;
    };

    function parseAndApplyMidjourneyCommand() {
        const $ = id => document.getElementById(id);
        const commandInput = $('prompt-params');
        if (!commandInput) return;
        let command = commandInput.value.trim();

        if (!command) {
            showToast("请输入Midjourney指令进行解析");
            return;
        }

        resetParams();

        let remainingCommand = command.replace(/^\/(imagine|i)\s*(prompt:)?\s*/i, '').trim();

        const newParams = JSON.parse(JSON.stringify(params));

        const extractParamWithValue = (regex, processor, isFlag = false) => {
            const match = remainingCommand.match(regex);
            if (match) {
                if (isFlag) {
                    processor(true, newParams);
                } else {
                    processor(match, newParams);
                }
                remainingCommand = remainingCommand.replace(regex, '').trim();
                return true;
            } else if (isFlag) {
                processor(false, newParams);
            }
            return false;
        };

        const orefItems = [];
        remainingCommand = remainingCommand.replace(/--oref\s+([^\s]+(?:\s*::\s*\S+)?)/gi, (match, content) => {
            const parts = content.split('::');
            orefItems.push({ url: parts[0].trim(), weight: (parts[1] || '').trim(), enabled: true });
            return '';
        }).trim();
        if (orefItems.length > 0) newParams.oref = orefItems;

        extractParamWithValue(/--iw\s+(\d*\.?\d+)/i, (m, p) => p.iw = parseFloat(m[1]));
        extractParamWithValue(/--sw\s+(\d+)/i, (m, p) => p.sw = Math.min(parseInt(m[1], 10), 1000));
        extractParamWithValue(/--cw\s+(\d+)/i, (m, p) => p.cw = parseInt(m[1], 10));
        extractParamWithValue(/--ow\s+(\d+)/i, (m, p) => p.ow = parseInt(m[1], 10));

        extractParamWithValue(/--ar\s+([\d:]+)/i, (m, p) => p.ar = m[1]);
        extractParamWithValue(/--(v|version)\s+([a-zA-Z0-9.]+)/i, (m, p) => p.version = 'v' + m[2].replace(/^v/i, ''));
        extractParamWithValue(/--niji\s+([a-zA-Z0-9.]+)/i, (m, p) => p.version = 'niji' + m[1].replace(/^niji/i, ''));
        extractParamWithValue(/--s\s+(\d+)/i, (m, p) => p.stylize = parseInt(m[1], 10));
        extractParamWithValue(/--w\s+(\d+)/i, (m, p) => p.weird = parseInt(m[1], 10));
        extractParamWithValue(/--c\s+(\d+)/i, (m, p) => p.chaos = parseInt(m[1], 10));
        extractParamWithValue(/--q\s+(\d*\.?\d+)/i, (m, p) => {
            const val = parseFloat(m[1]);
            const qualityMapVals = [0.25, 0.5, 1, 2, 4];
            if (qualityMapVals.includes(val)) p.quality = val;
        });
        extractParamWithValue(/--seed\s+(\d+)/i, (m, p) => p.seed = m[1]);
        extractParamWithValue(/--stop\s+(\d+)/i, (m, p) => p.stop = parseInt(m[1], 10));
        extractParamWithValue(/--p\s+((?:[^\s"-][^\s-]*|[^\s-]*[^\s"-])[^\s-]*(?:\s+(?:[^\s"-][^\s-]*|[^\s-]*[^\s"-])[^\s-]*)*)/i, (m, p) => p.personalParams = m[1].trim());
        extractParamWithValue(/--r\s+(\d+)/i, (m, p) => p.r = parseInt(m[1], 10));

        const noMatch = remainingCommand.match(/--no\s+((?:(?!--(?:ar|v|version|s|w|c|q|seed|stop|tile|draft|iw|sw|cw|ow|cref|sref|oref|p|r|niji|fast|turbo|relax|raw|public|stealth)\b)[\s\S])+)/i);
        if (noMatch) {
            newParams.noPrompt = noMatch[1].trim();
            remainingCommand = remainingCommand.replace(noMatch[0], '').trim();
        }

        extractParamWithValue(/--tile\b/i, (val, p) => p.tile = val, true);
        extractParamWithValue(/--draft\b/i, (val, p) => p.draft = val, true);

        if (extractParamWithValue(/--raw\b/i, (val, p) => { if(val) p.mode = 'raw'; }, true)) {}
        else { newParams.mode = 'standard'; }

        if (extractParamWithValue(/--fast\b/i, (val, p) => { if(val) p.speed = 'fast';}, true)) {}
        else if (extractParamWithValue(/--turbo\b/i, (val, p) => {if(val) p.speed = 'turbo';}, true)) {}
        else if (extractParamWithValue(/--relax\b/i, (val, p) => {if(val) p.speed = 'relax';}, true)) {}
        else { newParams.speed = 'relax'; }

        if (extractParamWithValue(/--public\b/i, (val, p) => {if(val) p.visibility = 'public';}, true)) {}
        else if (extractParamWithValue(/--stealth\b/i, (val, p) => {if(val) p.visibility = 'stealth';}, true)) {}
        else { newParams.visibility = '';}

        const crefMatch = remainingCommand.match(/--cref\s+((?:(?!--(?:cw|sw|ow|ar|v|version|s|w|c|q|seed|stop|tile|draft|iw|p|r|niji|fast|turbo|relax|raw|public|stealth|no|sref|oref)\b)[\s\S])+)/i);
        if (crefMatch) {
            newParams.cref = parseRefUrlsWithWeights(crefMatch[1].trim());
            remainingCommand = remainingCommand.replace(crefMatch[0], '').trim();
        }
        const srefMatch = remainingCommand.match(/--sref\s+((?:(?!--(?:cw|sw|ow|ar|v|version|s|w|c|q|seed|stop|tile|draft|iw|p|r|niji|fast|turbo|relax|raw|public|stealth|no|cref|oref)\b)[\s\S])+)/i);
        if (srefMatch) {
            newParams.sref = parseRefUrlsWithWeights(srefMatch[1].trim());
            remainingCommand = remainingCommand.replace(srefMatch[0], '').trim();
        }

        // --- Direct Images Parsing (with previous robust enhancements) START ---
        remainingCommand = remainingCommand.replace(/\s+/g, ' ').trim();
        const promptParts = remainingCommand.split(' ');
        newParams.directImages = [];
        let currentPromptPartIndex = 0;

        while(currentPromptPartIndex < promptParts.length) {
            const currentPartString = promptParts[currentPromptPartIndex];
            let imageUrl = '';
            let imageWeight = '';
            let partsConsumedInThisIteration = 0;

            if (currentPartString.toLowerCase().startsWith('http://') || currentPartString.toLowerCase().startsWith('https://')) {
                // Case A: Token is "url::weight" or "url::" (e.g., "http://host.com::1.5" or "http://host.com::")
                if (currentPartString.includes('::')) {
                    const splitToken = currentPartString.split('::');
                    if ((splitToken[0].toLowerCase().startsWith('http://') || splitToken[0].toLowerCase().startsWith('https://')) &&
                        splitToken.length === 2 &&
                        (/^\d*\.?\d*$/.test(splitToken[1]) || splitToken[1] === '')) {
                        imageUrl = splitToken[0];
                        imageWeight = splitToken[1].trim();
                        partsConsumedInThisIteration = 1;
                    } else {
                        imageUrl = currentPartString;
                        imageWeight = '';
                        partsConsumedInThisIteration = 1;
                    }
                }
                // Case B: Current token is "url", next token is "::weight" (e.g., "http://host.com", "::1.5")
                else if (currentPromptPartIndex + 1 < promptParts.length &&
                         promptParts[currentPromptPartIndex + 1].startsWith('::')) {
                    const combinedWeightPart = promptParts[currentPromptPartIndex + 1].substring(2);
                    if (/^\d*\.?\d*$/.test(combinedWeightPart) || combinedWeightPart === '') {
                        imageUrl = currentPartString;
                        imageWeight = combinedWeightPart.trim();
                        partsConsumedInThisIteration = 2;
                    } else {
                        imageUrl = currentPartString;
                        imageWeight = '';
                        partsConsumedInThisIteration = 1;
                    }
                }
                // Case C: Current token is "url", next is "::", next is "weight" (e.g., "http://host.com", "::", "1.5")
                else if (currentPromptPartIndex + 2 < promptParts.length &&
                         promptParts[currentPromptPartIndex + 1] === '::' &&
                         (/^\d*\.?\d*$/.test(promptParts[currentPromptPartIndex + 2]) || promptParts[currentPromptPartIndex + 2] === '')) {
                    imageUrl = currentPartString;
                    imageWeight = promptParts[currentPromptPartIndex + 2].trim();
                    partsConsumedInThisIteration = 3;
                }
                // Case D: Current token is just a "url"
                else {
                    imageUrl = currentPartString;
                    imageWeight = '';
                    partsConsumedInThisIteration = 1;
                }

                if (imageUrl && partsConsumedInThisIteration > 0) {
                    newParams.directImages.push({ url: imageUrl, weight: imageWeight, enabled: true });
                    currentPromptPartIndex += partsConsumedInThisIteration;
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        newParams.prompt = promptParts.slice(currentPromptPartIndex).join(' ').trim();
        // --- Direct Images Parsing END ---

        Object.assign(params, newParams);

        updateAllUIElements();
        showToast("指令解析完成并已填充参数!");
    }

    // --- 预设模板功能 ---
    function getPresets() {
        const presetsJson = localStorage.getItem(PRESETS_STORAGE_KEY);
        return presetsJson ? JSON.parse(presetsJson) : [];
    }

    function saveCurrentPreset() {
        const nameInput = document.getElementById('preset-name-input');
        const presetName = nameInput.value.trim();
        if (!presetName) {
            showToast('请输入预设名称');
            return;
        }

        let presets = getPresets();
        const existingPresetIndex = presets.findIndex(p => p.name === presetName);
        const presetData = { name: presetName, params: JSON.parse(JSON.stringify(params)) };

        if (existingPresetIndex > -1) {
            presets[existingPresetIndex] = presetData;
            showToast(`预设 "${presetName}" 已更新`);
        } else {
            presets.push(presetData);
            showToast(`预设 "${presetName}" 已保存`);
        }

        localStorage.setItem(PRESETS_STORAGE_KEY, JSON.stringify(presets));
        nameInput.value = '';
        renderPresetsList();
    }

    function loadPreset(presetName) {
        const presets = getPresets();
        const presetToLoad = presets.find(p => p.name === presetName);
        if (presetToLoad) {
            Object.assign(params, JSON.parse(JSON.stringify(presetToLoad.params)));
            updateAllUIElements();
            showToast(`预设 "${presetName}" 已加载`);
        } else {
            showToast(`未找到预设 "${presetName}"`);
        }
    }

    function deletePreset(presetName) {
        let presets = getPresets();
        presets = presets.filter(p => p.name !== presetName);
        localStorage.setItem(PRESETS_STORAGE_KEY, JSON.stringify(presets));
        showToast(`预设 "${presetName}" 已删除`);
        renderPresetsList();
    }

    function renderPresetsList() {
        const presetsListDisplay = document.getElementById('presets-list-display');
        if (!presetsListDisplay) return;
        presetsListDisplay.innerHTML = '';

        const presets = getPresets();
        if (presets.length === 0) {
            presetsListDisplay.innerHTML = '<li class="settings-list-empty">暂无预设模板。</li>';
            return;
        }

        presets.forEach(preset => {
            const listItem = document.createElement('li');
            listItem.className = 'settings-list-item';

            const nameSpan = document.createElement('span');
            nameSpan.textContent = preset.name;
            nameSpan.className = 'item-name';

            const actionsDiv = document.createElement('div');
            actionsDiv.className = 'item-actions';

            const loadButton = document.createElement('button');
            loadButton.textContent = '加载';
            loadButton.className = 'action-button-secondary item-action-btn';
            loadButton.onclick = () => loadPreset(preset.name);

            const deleteButton = document.createElement('button');
            deleteButton.textContent = '删除';
            deleteButton.className = 'action-button-danger item-action-btn';
            deleteButton.onclick = () => {
                if (confirm(`确定要删除预设 "${preset.name}" 吗?`)) {
                    deletePreset(preset.name);
                }
            };

            actionsDiv.appendChild(loadButton);
            actionsDiv.appendChild(deleteButton);
            listItem.appendChild(nameSpan);
            listItem.appendChild(actionsDiv);
            presetsListDisplay.appendChild(listItem);
        });
    }

    // --- 历史记录功能 ---
    function getHistory() {
        const historyJson = localStorage.getItem(HISTORY_STORAGE_KEY);
        return historyJson ? JSON.parse(historyJson) : [];
    }

    function legacyCopyAndRecord(el, promptValue) {
        try {
            document.execCommand('copy');
            showToast('参数已复制 (兼容模式)!');
            addPromptToHistory(promptValue);
        } catch (err) {
            showToast('复制失败!');
        }
    }

    function addPromptToHistory(promptString) {
        if (!promptString || !promptString.trim()) return;

        let history = getHistory();
        if (history.length > 0 && history[0] === promptString) {
            return;
        }

        history.unshift(promptString);
        if (history.length > MAX_HISTORY_ITEMS) {
            history = history.slice(0, MAX_HISTORY_ITEMS);
        }
        localStorage.setItem(HISTORY_STORAGE_KEY, JSON.stringify(history));
        renderHistoryList();
    }

    function loadHistoryItem(promptStringToLoad) {
        const promptField = document.getElementById('prompt-params');
        if (promptField) {
            promptField.value = promptStringToLoad;
            parseAndApplyMidjourneyCommand();
            showToast('历史记录已加载到面板');
        }
    }

    function copyHistoryItemToClipboard(promptString) {
        if (navigator.clipboard && navigator.clipboard.writeText) {
            navigator.clipboard.writeText(promptString)
                .then(() => showToast('历史记录已复制!'))
                .catch(err => showToast('复制失败!'));
        } else {
            const textarea = document.createElement('textarea');
            textarea.value = promptString;
            document.body.appendChild(textarea);
            textarea.select();
            try {
                document.execCommand('copy');
                showToast('历史记录已复制 (兼容模式)!');
            } catch (err) {
                showToast('复制失败!');
            }
            document.body.removeChild(textarea);
        }
    }

    function clearAllHistory() {
        if (confirm('确定要清空所有历史记录吗?')) {
            localStorage.removeItem(HISTORY_STORAGE_KEY);
            renderHistoryList();
            showToast('历史记录已清空');
        }
    }

    function renderHistoryList() {
        const historyListDisplay = document.getElementById('history-list-display');
        if (!historyListDisplay) return;
        historyListDisplay.innerHTML = '';

        const history = getHistory();
        if (history.length === 0) {
            historyListDisplay.innerHTML = '<li class="settings-list-empty">暂无历史记录。</li>';
            return;
        }

        const historyLimitDisplaySpan = document.getElementById('history-limit-display');
        if (historyLimitDisplaySpan) historyLimitDisplaySpan.textContent = MAX_HISTORY_ITEMS;


        history.forEach(promptStr => {
            const listItem = document.createElement('li');
            listItem.className = 'settings-list-item history-item';

            const promptTextSpan = document.createElement('span');
            promptTextSpan.textContent = promptStr.length > 80 ? promptStr.substring(0, 77) + '...' : promptStr;
            promptTextSpan.title = promptStr;
            promptTextSpan.className = 'item-name history-prompt-text';

            const actionsDiv = document.createElement('div');
            actionsDiv.className = 'item-actions';

            const loadButton = document.createElement('button');
            loadButton.textContent = '加载';
            loadButton.className = 'action-button-secondary item-action-btn';
            loadButton.onclick = () => loadHistoryItem(promptStr);

            const copyButton = document.createElement('button');
            copyButton.textContent = '复制';
            copyButton.className = 'action-button-secondary item-action-btn';
            copyButton.onclick = () => copyHistoryItemToClipboard(promptStr);

            actionsDiv.appendChild(loadButton);
            actionsDiv.appendChild(copyButton);
            listItem.appendChild(promptTextSpan);
            listItem.appendChild(actionsDiv);
            historyListDisplay.appendChild(listItem);
        });
    }

    // --- 事件绑定 ---
    function bindControlEvents() {
        const $ = id => document.getElementById(id);

        document.querySelectorAll('.tab-link').forEach(button => {
            button.addEventListener('click', () => {
                document.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active'));
                button.classList.add('active');
                document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
                const activeTabContent = $(button.dataset.tab);
                if (activeTabContent) activeTabContent.classList.add('active');
            });
        });

        const themeTriggerButton = $('theme-dropdown-trigger');
        const themeOptionsMenu = $('theme-options-menu');
        if (themeTriggerButton && themeOptionsMenu) {
            themeTriggerButton.addEventListener('click', (e) => { e.stopPropagation(); themeOptionsMenu.style.display = themeOptionsMenu.style.display === 'none' ? 'block' : 'none'; });
            document.querySelectorAll('.theme-option-button').forEach(button => {
                button.addEventListener('click', () => { currentThemeMode = button.dataset.theme; applyCurrentTheme(); themeOptionsMenu.style.display = 'none'; });
            });
        }

        if ($('main-prompt')) $('main-prompt').oninput = e => { params.prompt = e.target.value; updatePromptParams(); };
        if ($('no-prompt')) $('no-prompt').oninput = e => { params.noPrompt = e.target.value.trim(); updatePromptParams(); };

        const bindSlider = (id, property, displayId, isFloat = false, callback, defaultValue) => {
            const slider = $(id); if (!slider) return;
            const initialVal = params[property] !== undefined ? params[property] : defaultValue;
            const sliderMax = parseFloat(slider.max);
            slider.value = Math.min(initialVal, sliderMax);
            if (displayId && $(displayId)) $(displayId).textContent = slider.value;

            slider.oninput = e => {
                params[property] = isFloat ? parseFloat(e.target.value) : parseInt(e.target.value, 10);
                if (displayId && $(displayId)) $(displayId).textContent = e.target.value;
                if (callback) callback(params[property]);
                updatePromptParams();
            };
        };
        bindSlider('stylize', 'stylize', 'stylize-value', false, null, 100);
        bindSlider('weird', 'weird', 'weird-value', false, null, 0);
        bindSlider('chaos', 'chaos', 'chaos-value', false, null, 0);
        bindSlider('stop-slider', 'stop', 'stop-value', false, null, 100);
        bindSlider('r-slider', 'r', 'r-value', false, null, 1);
        bindSlider('iw-slider', 'iw', 'iw-value', true, null, 1);
        bindSlider('sw-slider', 'sw', 'sw-value', false, null, 100);
        bindSlider('cw-slider', 'cw', 'cw-value', false, null, 100);
        bindSlider('ow-slider', 'ow', 'ow-value', false, null, 100);


        const qualitySlider = $('quality-slider'); const qualityValueDisplay = $('quality-value');
        const qualityMap = [0.25, 0.5, 1, 2, 4];
        if(qualitySlider && qualityValueDisplay) {
            qualitySlider.oninput = e => {
                params.quality = qualityMap[parseInt(e.target.value, 10)];
                qualityValueDisplay.textContent = params.quality;
                updatePromptParams();
            };
        }

        const bindRadio = (className, property) => {
            document.querySelectorAll(`.${className}`).forEach(btn => {
                btn.addEventListener('click', () => {
                    document.querySelectorAll(`.${className}`).forEach(b => b.classList.remove('active'));
                    btn.classList.add('active'); params[property] = btn.dataset.value; updatePromptParams();
                });
            });
        };
        bindRadio('speed-btn', 'speed'); bindRadio('mode-btn', 'mode'); bindRadio('visibility-btn', 'visibility');

        const bindToggle = (id, property) => {
            const toggle = $(id); if (!toggle) return;
            toggle.addEventListener('click', () => { params[property] = !params[property]; updateToggleVisuals(id, property); updatePromptParams(); });
        };
        bindToggle('tile-toggle-switch', 'tile'); bindToggle('draft-toggle-switch', 'draft'); bindToggle('imagine-toggle-switch', 'includeImagine');

        if ($('version-select')) {
             $('version-select').onchange = e => { params.version = e.target.value; updatePromptParams(); };
        }
        if ($('seed-input')) {
            $('seed-input').oninput = e => {
                const value = e.target.value.trim();
                params.seed = (/^\d*$/.test(value) && (value === '' || (parseInt(value) >= 0 && parseInt(value) <= 4294967295))) ? value : params.seed;
                e.target.value = params.seed;
                updatePromptParams();
            };
        }
        if ($('personal-params')) {
             $('personal-params').oninput = e => { params.personalParams = e.target.value.trim(); updatePromptParams(); };
        }

        if ($('copy-btn')) $('copy-btn').onclick = () => {
            const textarea = $('prompt-params');
            if (!textarea || !textarea.value) {
                showToast('没有参数可以拷贝');
                return;
            }
            const promptToCopy = textarea.value;

            textarea.select();
            textarea.setSelectionRange(0, 99999);

            if (navigator.clipboard && navigator.clipboard.writeText) {
                navigator.clipboard.writeText(promptToCopy).then(() => {
                    showToast('参数已复制!');
                    addPromptToHistory(promptToCopy);
                }).catch(() => legacyCopyAndRecord(textarea, promptToCopy));
            } else {
                legacyCopyAndRecord(textarea, promptToCopy);
            }
        };

        const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1'];
        const ratioPresets = { '1:2':{w:50,h:100}, '9:16':{w:56.25,h:100}, '2:3':{w:66.67,h:100}, '3:4':{w:75,h:100}, '5:6':{w:83.33,h:100}, '1:1':{w:100,h:100}, '6:5':{w:100,h:83.33}, '4:3':{w:100,h:75}, '3:2':{w:100,h:66.67}, '16:9':{w:100,h:56.25}, '2:1':{w:100,h:50} };
        const ratioSlider = $('ratio-slider');
        if (ratioSlider) {
            ratioSlider.oninput = e => {
                const ratio = sizeMap[+e.target.value] || '1:1'; params.ar = ratio;
                const box = $('ratio-box'); const bgBox = $('ratio-preview-bg'); const preset = ratioPresets[ratio];
                if(box && preset && bgBox) {
                    const containerSize = 100;
                    let displayW = preset.w;
                    let displayH = preset.h;
                    if (displayW > containerSize || displayH > containerSize) {
                        if (displayW/displayH > 1) {
                            displayH = containerSize * (displayH / displayW);
                            displayW = containerSize;
                        } else {
                            displayW = containerSize * (displayW / displayH);
                            displayH = containerSize;
                        }
                    }
                    box.style.width = `${displayW-4}px`;
                    box.style.height = `${displayH-4}px`;
                    box.textContent = ratio;
                }
                document.querySelectorAll('#size-buttons button').forEach(btn => btn.classList.toggle('active', btn.dataset.value === ratio));
                updatePromptParams();
            };
        }

        const sizeButtonGroup = $('size-buttons');
        if (sizeButtonGroup) {
            sizeButtonGroup.innerHTML = '';
            const presetMap = { '纵向': '2:3', '正方形': '1:1', '横向': '3:2' };
            Object.entries(presetMap).forEach(([label, ratio]) => {
                const btn = document.createElement('button'); btn.textContent = label; btn.dataset.value = ratio;
                btn.onclick = () => {
                    if (ratioSlider) { const idx = sizeMap.indexOf(ratio); if (idx !== -1) { ratioSlider.value = idx; ratioSlider.dispatchEvent(new Event('input')); } }
                };
                sizeButtonGroup.appendChild(btn);
            });
        }

        if ($('clear-btn')) $('clear-btn').onclick = () => {
            resetParams();
            updateAllUIElements();
            showToast('所有参数已重置为默认值');
        };

        if ($('parse-btn')) $('parse-btn').onclick = parseAndApplyMidjourneyCommand;

        if ($('save-preset-btn')) {
            $('save-preset-btn').onclick = saveCurrentPreset;
        }
        if ($('clear-history-btn')) {
            $('clear-history-btn').onclick = clearAllHistory;
        }

        setupRefSection();
    }

    // --- 样式注入 ---
    function injectStyles() {
        const styleSheet = document.createElement("style");
        styleSheet.type = "text/css";
        styleSheet.innerText = `
            @keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }
            @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }

            #mj-floating-settings-button {
                position: fixed; right: 20px; bottom: 20px; padding: 10px 20px;
                background-color: #5865F2; color: white; border: none; border-radius: 8px;
                cursor: pointer; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                transition: background-color 0.2s ease, transform 0.2s ease; font-family: sans-serif; font-weight: 500;
            }
            #mj-floating-settings-button:hover { background-color: #4752C4; transform: scale(1.05); }

            .mj-toast {
                position: fixed; top: 20px; right: 20px; padding: 12px 18px; border-radius: 6px; z-index: 10001;
                font-family: sans-serif; font-size: 14px; transform: translateY(-100%); opacity: 0;
                transition: transform 0.3s ease, opacity 0.3s ease;
                background: #2B2D31; color: #DCDDDE; box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            }
            .mj-toast.show { transform: translateY(0); opacity: 1; }

            #mj-control-panel {
                display: flex; flex-direction: column; position: fixed; right: 20px; bottom: 80px;
                width: 880px; max-width: calc(100vw - 40px); height: 85vh; max-height: 800px;
                border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); z-index: 10000;
                overflow: hidden; font-family: sans-serif;
                transform: translateY(20px) scale(0.95); opacity: 0; pointer-events: none;
                transition: transform 0.25s ease, opacity 0.25s ease;
                background: white; color: #111827; border: 1px solid #E0E0E0;
            }
            #mj-control-panel.visible { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; }

            .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; border-bottom: 1px solid #E0E0E0; flex-shrink: 0; }
            .panel-title { margin:0; font-size:18px; font-weight:600; }
            .theme-trigger-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid #D1D5DB; background-color: white; font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s ease; }
            .theme-trigger-btn:hover { border-color: #4f46e5; }
            .theme-options-menu { position: absolute; top: calc(100% + 5px); right: 0; background-color: white; border: 1px solid #D1D5DB; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 10; min-width: 180px; padding: 4px; }
            .theme-option-button { display: flex; align-items: center; width: 100%; padding: 8px 12px; background: none; border: none; cursor: pointer; font-size: 13px; color: #1F2937; border-radius: 4px; transition: all 0.15s ease; gap: 8px; }
            .theme-option-button:hover { background-color: #f0f0f0; }
            .theme-option-button.active { background-color: #eef2ff; color: #4338ca; font-weight: 500; }

            .panel-main-content { display: flex; flex-direction: column; flex-grow: 1; overflow: hidden; }
            .panel-tabs { display: flex; padding: 12px 24px 0; border-bottom: 1px solid #E0E0E0; flex-shrink: 0; gap: 16px; }
            .tab-link { padding: 8px 4px; margin-bottom: -1px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 15px; color: #6B7280; transition: all 0.2s ease; }
            .tab-link:hover { color: #374151; }
            .tab-link.active { color: #4f46e5; border-bottom-color: #4f46e5; font-weight: 500; }

            .tab-content { display: none; padding: 20px 24px; overflow-y: auto; flex-grow: 1; }
            .tab-content.active { display: block; animation: fadeIn 0.3s ease; }
            .form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px 24px; }
            .form-group { display: flex; flex-direction: column; gap: 8px; }
            .form-group.span-2 { grid-column: span 2; }
            .form-group label { font-weight: 500; font-size: 14px; color: #1F2937; }

            .form-group input, .form-group textarea, .form-group select,
            .ref-url-input, .ref-weight-input, .weight-edit-input, .preset-input-name {
                background: white;
                color: #111827;
                border: 1px solid #D1D5DB;
                padding: 8px 12px;
                border-radius: 6px;
                font-size: 14px;
                transition: all 0.2s ease;
                box-sizing: border-box;
            }
            .form-group input:focus, .form-group textarea:focus, .form-group select:focus,
            .ref-url-input:focus, .ref-weight-input:focus, .weight-edit-input:focus, .preset-input-name:focus {
                border-color: #4f46e5;
                box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2);
                outline: none;
            }
            .form-group textarea { resize: vertical; min-height: 80px; }

            .ref-grid-main { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; padding: 0; }
            .ref-module { background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
            .ref-module-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; border-bottom: 1px solid #e5e7eb; }
            .ref-module-header h4 { margin: 0; font-size: 16px; font-weight: 600; color: #1f2937; }
            .global-weight-control { display: flex; align-items: center; gap: 8px; }
            .global-weight-control label { font-size: 12px; color: #6b7280; white-space: nowrap; }
            .weight-slider-mini { display: flex; align-items: center; gap: 6px; }
            .weight-slider-mini input[type="range"] { width: 60px; height: 4px; margin:0; padding:0; }
            .weight-slider-mini span { font-size: 12px; color: #4B5563; min-width: 25px; text-align: center; font-weight: 500; }
            .ref-input-section { display: flex; gap: 8px; align-items: center; }
            .ref-url-input { flex: 1; }
            .ref-weight-input { width: 70px; text-align: center; }
            .ref-add-btn { padding: 8px 16px; background: #4f46e5; color: white; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.2s ease; white-space: nowrap; }
            .ref-add-btn:hover { background: #4338ca; }

            .ref-container-large {
                position: relative; min-height: 90px; padding: 12px;
                background: white; border-radius: 8px; border: 2px dashed #d1d5db;
                display: flex; flex-wrap: wrap; gap: 12px; align-content: flex-start; transition: all 0.2s ease;
            }
            .ref-container-large.drag-over { border-style: solid; border-color: #4f46e5; background-color: #eef2ff; }
            .ref-container-large:empty:before {
                content: '拖拽图片到此处或使用上方输入框添加'; position: absolute; top: 50%; left: 50%;
                transform: translate(-50%, -50%); color: #9ca3af; font-size: 14px; pointer-events: none; text-align: center;
            }
            .drop-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(79, 70, 229, 0.1); border-radius: inherit; align-items: center; justify-content: center; z-index: 10; display:flex; }
            .drop-overlay-content { display: flex; flex-direction: column; align-items: center; gap: 8px; }
            .drop-icon { color: #4f46e5; } .drop-icon svg { display: block; }
            .drop-text { font-size: 14px; color: #4f46e5; font-weight: 500; }

            .ref-item-large {
                position: relative; width: 80px; height: 80px;
                border-radius: 8px; background: #f3f4f6; border: 2px solid #d1d5db;
                display: flex; align-items: center; justify-content: center;
                transition: all 0.2s ease; flex-shrink: 0; overflow: hidden;
            }
            .ref-item-large:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-color: #4f46e5; }
            .ref-item-large.disabled { opacity: 0.5; filter: grayscale(80%); }
            .ref-item-large.load-error { border-color: #ef4444; }
            .ref-image-large { width: 100%; height: 100%; object-fit: cover; }
            .ref-code-large { font-size: 11px; font-family: monospace; color: #4f46e5; text-align: center; word-break: break-all; padding: 6px; font-weight: 600; }
            .ref-error-large { font-size: 12px; color: #ef4444; text-align: center; padding: 4px; width:100%; user-select: none; }
            .weight-large { /* UI预览中图片下方权重的样式 */
                position: absolute; bottom: 3px; left: 3px; background: rgba(0,0,0,0.75); color: white;
                font-size: 10px; padding: 1px 4px; border-radius: 3px; font-family: monospace; font-weight: 600; z-index: 1;
            }
            .ref-edit-large, .ref-toggle-large, .ref-delete-large {
                width: 20px; height: 20px; border: none; border-radius: 4px;
                font-size: 12px; line-height: 1; cursor: pointer;
                transition: opacity 0.15s ease, background-color 0.15s ease, color 0.15s ease;
                display: flex; align-items: center; justify-content: center;
                position: absolute; opacity: 0; background: rgba(255,255,255,0.8);
                box-shadow: 0 1px 2px rgba(0,0,0,0.15); z-index: 2;
            }
            .ref-item-large:hover .ref-edit-large, .ref-item-large:hover .ref-toggle-large, .ref-item-large:hover .ref-delete-large { opacity: 1; }
            .ref-edit-large { top: 3px; left: 3px; color: #4f46e5; } .ref-edit-large svg { width:12px; height:12px; }
            .ref-edit-large:hover { background: #4f46e5; color: white; }
            .ref-delete-large { top: 3px; right: 3px; color: #ef4444; font-size: 14px; }
            .ref-delete-large:hover { background: #ef4444; color: white; }
            .ref-toggle-large { bottom: 3px; right: 3px; color: #6b7280; font-size: 14px; }
            .ref-toggle-large.active { background: #10b981; color: white; }
            .ref-toggle-large:not(.active):hover { background: #e0e0e0; }

            .weight-edit-dialog { position: fixed; top:0;left:0;right:0;bottom:0; z-index:10002; display:flex;align-items:center;justify-content:center; }
            .weight-edit-overlay { position:absolute;top:0;left:0;right:0;bottom:0; background:rgba(0,0,0,0.5); }
            .weight-edit-content { position:relative; background:white; border-radius:12px; padding:24px; min-width:300px; box-shadow:0 8px 24px rgba(0,0,0,0.15); z-index:1; }
            .weight-edit-content h4 { margin:0 0 16px 0; font-size:16px; font-weight:600; color:#1f2937; }
            .weight-edit-input-group { margin-bottom:20px; }
            .weight-edit-input-group label { display:block; margin-bottom:8px; font-size:14px; font-weight:500; color:#1f2937; }
            .weight-edit-buttons { display:flex; gap:12px; justify-content:flex-end; }
            .weight-edit-cancel, .weight-edit-save { padding:8px 16px; border-radius:6px; font-size:14px; font-weight:500; cursor:pointer; transition:all 0.2s ease; border:1px solid transparent; }
            .weight-edit-cancel { background:#e5e7eb; color:#374151; border-color:#d1d5db; } .weight-edit-cancel:hover { background:#d1d5db; }
            .weight-edit-save { background:#4f46e5; color:white; } .weight-edit-save:hover { background:#4338ca; }

            .slider-control { display: flex; align-items: center; gap: 12px; }
            .slider-control input[type="range"] { flex-grow: 1; margin:0; padding:0; height: 16px; }
            .slider-control span { font-size: 14px; color: #4B5563; min-width: 35px; text-align: right; }
            input[type="range"] { -webkit-appearance: none; background: transparent; cursor: pointer; width: 100%; }
            input[type="range"]::-webkit-slider-runnable-track { background: #E5E7EB; height: 6px; border-radius: 3px; }
            input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; margin-top: -5px; background-color: #4f46e5; height: 16px; width: 16px; border-radius: 50%; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.2); }
            .btn-group { display: flex; border-radius:6px; overflow:hidden; border:1px solid #d1d5db; }
            .btn-group button { flex:1; padding: 8px 10px; background:white; border:none; cursor:pointer; color: #374151; transition: all 0.2s; font-size: 13px; }
            .btn-group button:not(:last-child) { border-right: 1px solid #d1d5db; }
            .btn-group button.active { background: #4f46e5; color: white; }
            .toggle-group { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px; border-radius: 6px; background-color: #F9FAFB; }
            .toggle-group label { font-size: 14px; cursor: pointer; user-select: none; }
            .toggle-switch { position:relative; width:40px; height:20px; border-radius:10px; background:#e5e7eb; cursor:pointer; transition: background-color 0.2s ease; flex-shrink: 0; }
            .toggle-switch .toggle-dot { position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:white; box-shadow:0 1px 3px rgba(0,0,0,0.2); transition:all 0.2s ease; }
            .toggle-switch.active { background:#4f46e5; } .toggle-switch.active .toggle-dot { transform: translateX(20px); }
            #ar-section .ar-controls { display: flex; gap: 20px; align-items: center; }
            .ar-preview-container { position:relative; width:100px; height:100px; flex-shrink: 0; }
            #ratio-preview-bg { width:100px; height:100px; border:2px dashed #d1d5db; border-radius:12px; }
            #ratio-preview { position:absolute; top:0; left:0; width:100%; height:100%; display: flex; align-items: center; justify-content: center; }
            #ratio-box { background:#f3f4f6; border:2px solid #374151; border-radius:6px; display:flex; align-items:center; justify-content:center; font-size:12px; color:#374151; transition: all 0.2s ease; }
            .ar-slider-group { flex-grow: 1; display: flex; flex-direction: column; gap: 12px; }
            .ar-slider-group #size-buttons { width: 100%; }
            .panel-footer { padding: 16px 24px; border-top: 1px solid #E0E0E0; flex-shrink: 0; background-color: #f9fafb; }
            .panel-footer #prompt-params { width: 100%; height: 60px; resize: vertical; box-sizing: border-box; font-family: monospace; }
            .footer-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
            .footer-actions .imagine-toggle { padding: 0; background: none; }
            .footer-buttons { display: flex; gap: 12px; }
            .action-button-primary, .action-button-secondary { padding: 8px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid transparent; }
            .action-button-primary { background: #4f46e5; color: white; } .action-button-primary:hover { background: #4338CA; }
            .action-button-secondary { background: #E5E7EB; color: #374151; border-color: #D1D5DB; } .action-button-secondary:hover { background: #D1D5DB; }

            /* --- 新增预设与历史记录样式 --- */
            .presets-controls, .history-controls {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 15px;
                gap: 15px;
            }
            .preset-input-name {
                flex-grow: 1;
            }
            .preset-save-button, .history-clear-button {
                white-space: nowrap;
            }
            .settings-subheader {
                margin-top: 0;
                margin-bottom: 10px;
                font-size: 15px;
                font-weight: 600;
                color: #374151;
                flex-grow: 1;
            }
            .history-controls .settings-subheader {
                margin-top: 0;
            }

            .settings-list {
                list-style: none;
                padding: 0;
                margin: 0;
                max-height: calc(85vh - 300px);
                overflow-y: auto;
                border: 1px solid #E0E0E0;
                border-radius: 6px;
            }
            .settings-list-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 10px 12px;
                border-bottom: 1px solid #E0E0E0;
            }
            .settings-list-item:last-child {
                border-bottom: none;
            }
            .settings-list-item .item-name {
                flex-grow: 1;
                margin-right: 10px;
                font-size: 14px;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                cursor: default;
            }
            .settings-list-item .history-prompt-text {
                font-family: monospace;
                font-size: 13px;
            }
            .settings-list-item .item-actions {
                display: flex;
                gap: 8px;
                flex-shrink: 0;
            }
            .settings-list-item .item-action-btn {
                padding: 5px 10px;
                font-size: 13px;
            }
            .action-button-danger {
                background-color: #ef4444;
                color: white;
                border-color: #dc2626;
            }
            .action-button-danger:hover {
                background-color: #dc2626;
            }
            .settings-list-empty {
                padding: 10px 12px;
                color: #6B7280;
                text-align: center;
                font-style: italic;
            }


            /* Dark Mode Styles */
            #mj-control-panel.dark-mode { background:#2B2D31; color:#DCDDDE; border-color:#202225; }
            .dark-mode .panel-header { border-bottom-color:#202225; }
            .dark-mode .theme-trigger-btn { border-color:#2D2F34; background-color:#2B2D31; color:#DCDDDE; } .dark-mode .theme-trigger-btn:hover { border-color:#7289DA; }
            .dark-mode .theme-options-menu { background-color:#2B2D31; border-color:#202225; }
            .dark-mode .theme-option-button { color:#DCDDDE; } .dark-mode .theme-option-button:hover { background-color:#393c43; }
            .dark-mode .theme-option-button.active { background-color:#404EED; color:white; }
            .dark-mode .panel-tabs { border-bottom-color:#202225; }
            .dark-mode .tab-link { color:#8e9297; } .dark-mode .tab-link:hover { color:#dcddde; }
            .dark-mode .tab-link.active { color:#7289DA; border-bottom-color:#7289DA; }
            .dark-mode .form-group label { color:#DCDDDE; }

            .dark-mode .form-group input, .dark-mode .form-group textarea, .dark-mode .form-group select,
            .dark-mode .ref-url-input, .dark-mode .ref-weight-input, .dark-mode .weight-edit-input,
            .dark-mode .preset-input-name,
            .dark-mode .panel-footer #prompt-params {
                background:#202225;
                color:#DCDDDE;
                border-color:#40444B;
            }
            .dark-mode .form-group input:focus, .dark-mode .form-group textarea:focus, .dark-mode .form-group select:focus,
            .dark-mode .ref-url-input:focus, .dark-mode .ref-weight-input:focus, .dark-mode .weight-edit-input:focus,
            .dark-mode .preset-input-name:focus,
            .dark-mode .panel-footer #prompt-params:focus {
                border-color:#7289DA;
                box-shadow:0 0 0 2px rgba(114,137,218,0.2);
            }

            .dark-mode input[type="range"]::-webkit-slider-runnable-track { background:#40444B; }
            .dark-mode input[type="range"]::-webkit-slider-thumb { background-color:#7289DA; border-color:#2B2D31; }
            .dark-mode .slider-control span { color:#b9bbbe; }
            .dark-mode .btn-group { border-color:#2D2F34; }
            .dark-mode .btn-group button { background:#40444B; color:#DCDDDE; } .dark-mode .btn-group button:not(:last-child) { border-right-color:#2D2F34; }
            .dark-mode .btn-group button.active { background:#5865F2; color:white; }
            .dark-mode .toggle-group { background-color:#202225; }
            .dark-mode .toggle-switch { background:#4E4F52; } .dark-mode .toggle-switch .toggle-dot { background:#B9BBBE; }
            .dark-mode .toggle-switch.active { background:#5865F2; }
            .dark-mode #ratio-preview-bg { border-color:#40444B; }
            .dark-mode #ratio-box { background:#40444B; color:#DCDDDE; border-color:#70747A; }
            .dark-mode .panel-footer { background-color:#2B2D31; border-top-color:#202225; }
            .dark-mode .action-button-primary { background:#5865F2; } .dark-mode .action-button-primary:hover { background:#4752C4; }
            .dark-mode .action-button-secondary { background:#40444B; color:#DCDDDE; border-color:#2D2F34; } .dark-mode .action-button-secondary:hover { background:#4F545C; }

            .dark-mode .ref-module { background:#1a1d21; border-color:#2D2F34; }
            .dark-mode .ref-module-header { border-bottom-color:#2D2F34; } .dark-mode .ref-module-header h4 { color:#dcddde; }
            .dark-mode .global-weight-control label { color:#8e9297; } .dark-mode .weight-slider-mini span { color:#b9bbbe; }
            .dark-mode .ref-add-btn { background:#5865F2; } .dark-mode .ref-add-btn:hover { background:#4752C4; }
            .dark-mode .ref-container-large { background:#202225; border-color:#2D2F34; }
            .dark-mode .ref-container-large:empty:before { color:#8e9297; }
            .dark-mode .ref-container-large.drag-over { border-color:#5865F2; background-color:#2f3136; }
            .dark-mode .ref-item-large { background:#313338; border-color:#2D2F34; }
            .dark-mode .ref-item-large:hover { border-color:#7289DA; }
            .dark-mode .ref-code-large { color:#7289DA; } .dark-mode .ref-error-large { color:#ff6b6b; }
            .dark-mode .weight-large { background:rgba(0,0,0,0.85); }
            .dark-mode .ref-edit-large, .dark-mode .ref-toggle-large, .dark-mode .ref-delete-large { background:rgba(79,84,92,0.8); }
            .dark-mode .ref-edit-large { color:#7289DA; } .dark-mode .ref-edit-large:hover { background:#7289DA; color:white; }
            .dark-mode .ref-delete-large { color:#ff6b6b; } .dark-mode .ref-delete-large:hover { background:#ff6b6b; color:white; }
            .dark-mode .ref-toggle-large { color:#b9bbbe; } .dark-mode .ref-toggle-large.active { background:#248046; }
            .dark-mode .ref-toggle-large:not(.active):hover { background:#555c66; }
            .dark-mode .weight-edit-content { background:#2B2D31; color:#dcddde; } .dark-mode .weight-edit-content h4 { color:#dcddde; }
            .dark-mode .weight-edit-input-group label { color:#dcddde; }
            .dark-mode .weight-edit-cancel { background:#40444B; color:#dcddde; border-color:#2D2F34; } .dark-mode .weight-edit-cancel:hover { background:#4F545C; }
            .dark-mode .weight-edit-save { background:#5865F2; } .dark-mode .weight-edit-save:hover { background:#4752C4; }

            .dark-mode .settings-subheader { color: #b9bbbe; }
            .dark-mode .settings-list { border-color: #2D2F34; }
            .dark-mode .settings-list-item { border-bottom-color: #2D2F34; }
            .dark-mode .action-button-danger { background-color: #c93c3c; border-color: #b32d2d; }
            .dark-mode .action-button-danger:hover { background-color: #b32d2d; }
            .dark-mode .settings-list-empty { color: #8e9297; }
        `;
        document.head.appendChild(styleSheet);
    }

    // --- 初始化 ---
    function init() {
        injectStyles();
        resetParams();
        createSettingButton();
        createControlPanel();
        updateAllUIElements();
        renderPresetsList();
        renderHistoryList();
    }

    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(init, 500);
    } else {
        window.addEventListener('DOMContentLoaded', () => setTimeout(init, 500), { once: true });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址