Greasy Fork

Greasy Fork is available in English.

Discord Midjourney 参数可视化

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Discord Midjourney 参数可视化
// @namespace    https://github.com/cwser
// @version      1.0.5
// @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="text" 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="text" 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="text" 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 (This was the previous fix from the user)
    const formatImageWithWeight = (url, weightValue) => {
        const weightStr = (typeof weightValue === 'string' || typeof weightValue === 'number') ? String(weightValue).trim() : '';
        if (weightStr === '') { // 如果权重为空,则只返回URL
            return url;
        }
        // 修正:移除 "::" 前的空格 (This comment refers to the previous fix for space before ::)
        // 例如: 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));
        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));
            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));
            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)}`;
        });

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