// ==UserScript== // @name Discord Midjourney 参数面板 // @namespace https://github.com/cwser // @version 0.2.7 // @license MIT // @description 在 Discord 中添加 Midjourney 参数设置面板,支持完整卡片式 UI 和最新参数功能(⚠️⚠️⚠️需开启开发者模式) // @author cwser // @match https://discord.com/* // @icon https://www.midjourney.com/favicon.ico // @grant unsafeWindow // @supportURL https://github.com/cwser // @homepageURL https://github.com/cwser // @downloadURL none // ==/UserScript== (function() { 'use strict'; // 参数定义 let params = { prompt: '', // 提示词参数 ar: '1:1', stylize: 100, weird: 0, chaos: 0, mode: 'standard', // 模式:standard, raw version: 'v7', // MJ 模型版本 speed: 'relax', // 速度:relax, fast, turbo draft: false, // 是否为草稿模式 noPrompt: '', // 排除提示词 cref: [], // 角色参考 格式: {url, 权重} sref: [], // 风格参考 格式: {url, 权重} oref: [], // 全方位参考 格式: {url, 权重} iref: [], // 图像参考(这个参数在原始定义中存在,但未在UI或输出中使用,为保持一致性而保留) directImages: [], // 直接图像URL作为提示词一部分 格式: {url, 权重} // 新增参数 tile: false, // 是否生成可平铺图案 seed: '', // 种子数 quality: 1, // 图像质量,默认质量为 1 (0.25, 0.5, 1, 2, 4) stop: 100, // 停止生成百分比 visibility: '', // 可见性:public, stealth (或默认不设置) // 新增个性化参数 personalParams: '', // 个性化后缀参数 (--p) // 新增批量任务参数 r: 1 // 重复次数 }; let isDarkMode = false; // 将 isDarkMode 移至更高作用域,默认为浅色模式启动时的状态 // 定义权重参数前缀的映射 const weightPrefixes = { 'cref': '--cw', 'sref': '--sw', 'oref': '--ow', 'directImages': '--iw' }; // 显示 Toast 提示 function showToast(message) { const toast = document.createElement('div'); toast.textContent = message; // 根据当前主题设置 Toast 样式 const toastBg = isDarkMode ? '#DCDDDE' : '#2B2D31'; // 深色模式用浅色背景,浅色模式用深色背景,以保证对比度 const toastColor = isDarkMode ? '#2B2D31' : '#DCDDDE'; const toastBoxShadow = isDarkMode ? '0 4px 12px rgba(0,0,0,0.3)' : '0 4px 12px rgba(0,0,0,0.15)'; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${toastBg}; color: ${toastColor}; padding: 10px 16px; border-radius: 6px; z-index: 99999; box-shadow: ${toastBoxShadow}; transform: translateY(-20px); opacity: 0; transition: all 0.3s ease; font-family: sans-serif; /* 确保字体一致性 */ `; document.body.appendChild(toast); // 触发入场动画 setTimeout(() => { toast.style.transform = 'translateY(0)'; toast.style.opacity = '1'; }, 10); // 自动消失 setTimeout(() => { toast.style.transform = 'translateY(-20px)'; toast.style.opacity = '0'; setTimeout(() => { if (document.body.contains(toast)) { // 检查 toast 是否仍在 body 中 document.body.removeChild(toast); } }, 300); }, 2000); } // 创建设置按钮 function createSettingButton() { const button = document.createElement('button'); button.textContent = 'MJ参数'; // 更改按钮文字以便区分 button.style.cssText = ` position: fixed; right: 20px; bottom: 20px; padding: 10px 20px; background-color: #5865F2; /* Discord 主题紫色 */ color: white; border: none; border-radius: 8px; cursor: pointer; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; font-family: sans-serif; `; button.addEventListener('click', toggleControlPanel); document.body.appendChild(button); button.addEventListener('mouseenter', () => { button.style.backgroundColor = '#4752C4'; // 悬停时变深 button.style.transform = 'scale(1.05)'; }); button.addEventListener('mouseleave', () => { button.style.backgroundColor = '#5865F2'; // 恢复原色 button.style.transform = 'scale(1)'; }); } // 切换控制面板显示/隐藏 function toggleControlPanel() { const panel = document.getElementById('mj-control-panel'); if (panel) { if (panel.style.display === 'none') { panel.style.display = 'block'; panel.style.opacity = '0'; panel.style.transform = 'translateY(10px)'; setTimeout(() => { panel.style.opacity = '1'; panel.style.transform = 'translateY(0)'; }, 10); // 短暂延迟以触发过渡动画 } else { panel.style.opacity = '0'; panel.style.transform = 'translateY(10px)'; setTimeout(() => { panel.style.display = 'none'; }, 200); // 等待动画完成后再隐藏 } } } // 重置参数为默认值 function resetParams() { params = { prompt: '', ar: '1:1', stylize: 100, weird: 0, chaos: 0, mode: 'standard', version: 'v7', speed: 'relax', draft: false, noPrompt: '', cref: [], sref: [], oref: [], iref: [], directImages: [], tile: false, seed: '', quality: 1, stop: 100, visibility: '', personalParams: '', r: 1 }; } // 更新最终的提示词参数字符串 function updatePromptParams() { const { prompt, ar, stylize, weird, chaos, mode, draft, noPrompt, version, cref, sref, speed, oref, /* iref 未在输出中使用 */ directImages, tile, seed, quality, stop, visibility, personalParams } = params; // 处理其他参数 const otherParts = [ `--ar ${ar}`, `--s ${stylize}`, weird !== 0 ? `--w ${weird}` : '', // 仅当 weird 不为0时添加 chaos !== 0 ? `--c ${chaos}` : '', // 仅当 chaos 不为0时添加 mode !== 'standard' ? `--${mode}` : '', // 仅当 mode 不是 standard 时添加 draft ? '--draft' : '', // 如果 draft 为 true,添加 --draft noPrompt ? `--no ${noPrompt}` : '', // 如果 noPrompt 不为空,添加 --no version.startsWith('niji') ? `--niji ${version.replace('niji', '')}` : `--v ${version.replace('v', '')}`, // 处理 niji 和普通版本 speed !== 'relax' ? `--${speed}` : '', // 仅当 speed 不是 relax 时添加 tile ? '--tile' : '', // 如果 tile 为 true,添加 --tile seed ? `--seed ${seed}` : '', // 如果 seed 不为空,添加 --seed quality !== 1 ? `--q ${quality}` : '', // 仅在质量不是默认值 1 时添加 stop !== 100 ? `--stop ${stop}` : '', // 仅在 stop 不是默认值 100 时添加 visibility ? `--${visibility}` : '', // 如果 visibility 不为空,添加对应参数 personalParams ? `--p ${personalParams}` : '', // 如果 personalParams 不为空,添加 --p params.r > 1 ? `--r ${params.r}` : '' // 如果重复次数大于1,添加 --r ]; // 格式化带权重的图片URL const formatImageWithWeight = (url, weight, prefix) => { let formatted = url; if (weight && weight.trim() !== '') { formatted += ` ${prefix} ${weight.trim()}`; } return formatted; }; // 格式化直接图像URL列表 const directImageUrls = directImages.map(item => formatImageWithWeight(item.url, item.weight, '--iw')).join(' '); const promptField = document.getElementById('prompt-params'); if (promptField) { const allParts = [ directImageUrls, prompt.trim(), ...cref.map(item => `--cref ${formatImageWithWeight(item.url, item.weight, '--cw')}`), ...sref.map(item => `--sref ${formatImageWithWeight(item.url, item.weight, '--sw')}`), ...oref.map(item => `--oref ${formatImageWithWeight(item.url, item.weight, '--ow')}`), ...otherParts.filter(Boolean) // 过滤掉空字符串 ].filter(Boolean); promptField.value = allParts.join(' ').trim(); // 合并所有部分并去除首尾空格 } } // 创建控制面板HTML结构 function createControlPanel() { const panel = document.createElement('div'); panel.id = 'mj-control-panel'; panel.style.cssText = ` display: none; /* 初始隐藏 */ position: fixed; right: 20px; bottom: 80px; /* 调整位置,为设置按钮留出空间 */ width: 1080px; max-width: calc(100% - 40px); /* 确保在小屏幕上不会超出 */ background: white; /* 默认浅色背景 */ border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12); padding: 20px; z-index: 10000; border: 1px solid #E5E7EB; /* 浅色模式边框 */ max-height: 90vh; /* 最大高度,超出则滚动 */ overflow-y: auto; /* 内容超出时垂直滚动 */ font-family: sans-serif; transition: opacity 0.2s ease, transform 0.2s ease; /* 添加过渡动画 */ color: #111827; /* 默认浅色模式文本颜色 */ `; // 面板内部HTML结构 panel.innerHTML = `

Midjourney 参数设置

图片尺寸 (--ar)

1:1

美学参数

100
0
0

模型设置

角色参考 (--cref)

风格参考 (--sref)

全方位参考 (--oref)

图像参考 (URL --iw)

更多参数

1
100
1

可见性 (--public, --stealth)

提示词

排除词 (--no)

最终参数

`; document.body.appendChild(panel); bindControlEvents(); // 添加到DOM后绑定事件 } // 设置按钮组的初始激活状态和样式 function setInitialActiveButtons() { const buttonGroups = [ { className: 'speed-btn', param: 'speed', defaultValue: 'relax' }, { className: 'mode-btn', param: 'mode', defaultValue: 'standard' }, { className: 'visibility-btn', param: 'visibility', defaultValue: '' } // 尺寸预设按钮的激活状态由 ratioSlider 的 input 事件处理 ]; buttonGroups.forEach(group => { const buttons = document.querySelectorAll(`.${group.className}`); let currentParamValue = params[group.param] === undefined ? group.defaultValue : params[group.param]; buttons.forEach(btn => { const isActive = btn.dataset.value === currentParamValue; btn.classList.toggle('active', isActive); if (isActive) { btn.style.backgroundColor = isDarkMode ? '#5865F2' : '#4f46e5'; // 深色紫 / 浅色靛蓝 btn.style.color = 'white'; btn.style.borderColor = isDarkMode ? '#5865F2' : '#4f46e5'; } else { btn.style.backgroundColor = isDarkMode ? '#40444B' : 'white'; // 深色灰 / 浅色白 btn.style.color = isDarkMode ? '#DCDDDE' : '#374151'; // 深色浅灰 / 浅色深灰 btn.style.borderColor = isDarkMode ? '#2D2F34' : '#D1D5DB'; // 深色更深灰 / 浅色浅灰 } }); }); } // 更新拨动开关的视觉样式 function updateToggleVisuals(switchId, property) { const switchEl = document.getElementById(switchId); if (!switchEl) return; const dot = switchEl.querySelector('.toggle-dot'); if (!dot) return; if (params[property]) { // 开关激活状态 dot.style.transform = 'translateX(20px)'; switchEl.style.backgroundColor = isDarkMode ? '#5865F2' : '#4f46e5'; // 深色紫 / 浅色靛蓝 } else { // 开关非激活状态 dot.style.transform = 'translateX(0)'; switchEl.style.backgroundColor = isDarkMode ? '#4E4F52' : '#E5E7EB'; // 深色灰 / 浅色更浅灰 } } // 设置参考图片/代码区域 (cref, sref, oref, directImages) const setupRefSection = (idPrefix, paramKey) => { // idPrefix 用于HTML元素ID, paramKey 用于 params 对象的键名 const $ = id => document.getElementById(id); const addBtn = $(`${idPrefix}-add`); const urlInput = $(`${idPrefix}-url`); const weightInput = $(`${idPrefix}-weight`); const previewContainerId = `${idPrefix}-preview`; if (!addBtn || !urlInput || !weightInput) { // console.error(`初始化 ${idPrefix} 区域失败:缺少元素。请确保HTML中的ID匹配: ${idPrefix}-add, ${idPrefix}-url, ${idPrefix}-weight`); return; } addBtn.onclick = () => { const url = urlInput.value.trim(); const weight = weightInput.value.trim(); let toastMessage = `请输入${paramKey === 'sref' ? 'URL或代码' : 'URL'}`; if (!url) { showToast(toastMessage); return; } // 验证URL是否为有效的图片格式 const isImageUrl = /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url); // 验证sref是否为数字代码或 'random' const isSrefCode = paramKey === 'sref' && (/^\d+$/.test(url) || url.toLowerCase() === 'random'); if (paramKey !== 'sref' && !isImageUrl) { showToast('请输入有效的图片URL'); return; } if (paramKey === 'sref' && !isImageUrl && !isSrefCode) { showToast("sref请输入有效图片URL, 'random'或数字代码"); return; } const targetArray = params[paramKey]; // 使用 paramKey 访问 params 中的对应数组 if (!Array.isArray(targetArray)) { // console.error(`params.${paramKey} 不是一个数组。`); return; } const itemUrl = (paramKey === 'sref' && isSrefCode) ? (url.toLowerCase() === 'random' ? 'random' : url) : url; if (!targetArray.some(item => item.url === itemUrl)) { // 避免重复添加 targetArray.push({ url: itemUrl, weight }); const currentWeightPrefix = weightPrefixes[paramKey]; // 使用 paramKey 获取对应的权重前缀 if (paramKey === 'sref' && isSrefCode) { // 如果是 sref 代码 addPreviewSrefCode(previewContainerId, itemUrl, weight); } else { // 如果是图片URL addPreviewImage(previewContainerId, itemUrl, weight, paramKey, currentWeightPrefix); } urlInput.value = ''; // 清空输入框 weightInput.value = ''; // 清空权重输入框 updatePromptParams(); // 更新最终参数显示 } else { showToast(`该${(paramKey === 'sref' && isSrefCode) ? '代码' : 'URL'}已添加`); } }; }; // 绑定控制面板中的各种事件 function bindControlEvents() { const $ = id => document.getElementById(id); const themeToggleButton = $('theme-toggle'); // 定义亮色和暗色模式的图标 const sunIcon = ``; const moonIcon = ``; themeToggleButton.addEventListener('click', () => { isDarkMode = !isDarkMode; // 切换模式状态 const panel = $('mj-control-panel'); const allButtons = panel.querySelectorAll('button'); // 面板内所有按钮 const inputsAndTextareas = panel.querySelectorAll('input, textarea, select'); // 所有输入控件 const panelSections = panel.querySelectorAll('.panel-section'); // 所有区块背景 const btnGroups = panel.querySelectorAll('.btn-group'); // 按钮组 const labels = panel.querySelectorAll('label, p, h3, span'); // 所有文本元素 const divider = panel.querySelector('div[style*="height:1px; background"]'); // 分割线 const ratioBox = $('ratio-box'); // 比例预览框 const ratioPreviewBg = $('ratio-preview-bg'); // 比例预览背景虚线框 // 特定按钮 const copyButton = $('copy-btn'); const clearButton = $('clear-btn'); const refAddButtons = panel.querySelectorAll('#cref-add, #sref-add, #oref-add, #direct-image-add'); if (isDarkMode) { // ------------------ 深色模式 ------------------ panel.style.backgroundColor = '#2B2D31'; // Discord 深灰色背景 panel.style.color = '#DCDDDE'; // Discord 浅灰色文本 panel.style.borderColor = '#202225'; // Discord 更深边框 if (divider) divider.style.background = '#40444B'; // 分割线颜色 labels.forEach(label => label.style.color = '#DCDDDE'); themeToggleButton.innerHTML = sunIcon; // 切换为太阳图标 themeToggleButton.style.color = '#DCDDDE'; inputsAndTextareas.forEach(input => { input.style.backgroundColor = '#202225'; // 输入框背景 input.style.color = '#DCDDDE'; // 输入框文本颜色 input.style.borderColor = '#18191C'; // 输入框边框颜色 if (input.placeholder) input.classList.add('dark-placeholder'); else input.classList.remove('dark-placeholder'); if (input.type === 'range') { input.style.setProperty('--thumb-bg', '#DCDDDE'); // 滑块颜色 input.style.setProperty('--track-bg', '#40444B'); // 滑道颜色 } }); panelSections.forEach(el => el.style.backgroundColor = '#202225'); // 区块背景 btnGroups.forEach(el => { el.style.borderColor = '#40444B'; // 按钮组边框 el.querySelectorAll('button:not(:last-child)').forEach(b => b.style.borderRightColor = '#40444B'); // 按钮组内分割线 }); if (ratioBox) { ratioBox.style.background = '#40444B'; ratioBox.style.color = '#DCDDDE'; ratioBox.style.borderColor = '#70747A'; } if(ratioPreviewBg) ratioPreviewBg.style.borderColor = '#40444B'; // 比例预览虚线框 // 特定按钮的深色模式样式 if (copyButton) { copyButton.style.backgroundColor = '#5865F2'; // 主要操作按钮颜色 (Discord 紫) copyButton.style.color = 'white'; copyButton.style.border = '1px solid #5865F2'; } if (clearButton) { clearButton.style.backgroundColor = '#40444B'; // 次要操作按钮颜色 clearButton.style.color = '#DCDDDE'; clearButton.style.border = '1px solid #2D2F34'; } refAddButtons.forEach(btn => { btn.style.backgroundColor = '#5865F2'; // 添加按钮颜色 btn.style.color = 'white'; btn.style.border = '1px solid #5865F2'; }); // 为其他按钮(非特殊处理的)应用深色模式样式 allButtons.forEach(btn => { if (btn === copyButton || btn === clearButton || btn.id.startsWith('theme-toggle') || Array.from(refAddButtons).includes(btn)) return; if (!btn.classList.contains('active')) { // 非激活按钮 btn.style.backgroundColor = '#40444B'; btn.style.color = '#DCDDDE'; btn.style.borderColor = btn.closest('.btn-group') ? 'transparent' : '#2D2F34'; // 按钮组内按钮通常无独立边框 } else { // 激活按钮 (如速度、模式、可见性、尺寸预设中的激活项) btn.style.backgroundColor = '#5865F2'; btn.style.color = 'white'; btn.style.borderColor = btn.closest('.btn-group') ? 'transparent' : '#5865F2'; } }); } else { // ------------------ 浅色模式 ------------------ panel.style.backgroundColor = 'white'; panel.style.color = '#111827'; // 深色文本 panel.style.borderColor = '#E0E0E0'; // 浅灰色边框 (比 D1D5DB 稍柔和) if (divider) divider.style.background = '#E5E7EB'; labels.forEach(label => label.style.color = '#1F2937'); // 文本颜色调整为更深的灰色,提高对比度 themeToggleButton.innerHTML = moonIcon; // 切换为月亮图标 themeToggleButton.style.color = '#6B7280'; // 图标颜色 inputsAndTextareas.forEach(input => { input.style.backgroundColor = 'white'; input.style.color = '#111827'; input.style.borderColor = '#D1D5DB'; // 标准浅色边框 if (input.placeholder) input.classList.remove('dark-placeholder'); if (input.type === 'range') { // 移除深色模式的自定义属性,恢复到 injectStyles 中的默认浅色样式 input.style.removeProperty('--thumb-bg'); input.style.removeProperty('--track-bg'); } }); panelSections.forEach(el => el.style.backgroundColor = '#F9FAFB'); // 区块背景用非常浅的灰色 btnGroups.forEach(el => { el.style.borderColor = '#D1D5DB'; // 按钮组边框 el.querySelectorAll('button:not(:last-child)').forEach(b => b.style.borderRightColor = '#D1D5DB'); // 按钮组内分割线 }); if (ratioBox) { ratioBox.style.background = '#F3F4F6'; // 预览框背景 ratioBox.style.color = '#374151'; // 预览框文字颜色 ratioBox.style.borderColor = '#6B7280'; // 预览框边框颜色 (中度灰色,更清晰) } if(ratioPreviewBg) ratioPreviewBg.style.borderColor = '#CBD5E1'; // 比例预览虚线框 (更清晰的浅灰) // 特定按钮的浅色模式样式 if (copyButton) { copyButton.style.backgroundColor = '#4f46e5'; // 主要操作按钮颜色 (靛蓝) copyButton.style.color = 'white'; copyButton.style.border = '1px solid #4f46e5'; } if (clearButton) { clearButton.style.backgroundColor = '#E5E7EB'; // 次要操作按钮背景 (浅灰) clearButton.style.color = '#374151'; // 次要操作按钮文字 (深灰) clearButton.style.border = '1px solid #D1D5DB'; } refAddButtons.forEach(btn => { btn.style.backgroundColor = '#4f46e5'; // 添加按钮颜色 btn.style.color = 'white'; btn.style.border = '1px solid #4f46e5'; }); // 为其他按钮(非特殊处理的)应用浅色模式样式 allButtons.forEach(btn => { if (btn === copyButton || btn === clearButton || btn.id.startsWith('theme-toggle') || Array.from(refAddButtons).includes(btn)) return; if (!btn.classList.contains('active')) { // 非激活按钮 btn.style.backgroundColor = 'white'; btn.style.color = '#374151'; // 文字颜色改为深灰 btn.style.borderColor = btn.closest('.btn-group') ? 'transparent' : '#D1D5DB'; } else { // 激活按钮 btn.style.backgroundColor = '#4f46e5'; btn.style.color = 'white'; btn.style.borderColor = btn.closest('.btn-group') ? 'transparent' : '#4f46e5'; } }); } setInitialActiveButtons(); // 根据新主题刷新所有按钮组的激活状态和样式 updateToggleVisuals('tile-toggle-switch', 'tile'); // 更新 "重复图案" 开关样式 updateToggleVisuals('draft-toggle-switch', 'draft'); // 更新 "草稿" 开关样式 refreshPreviews(); // 刷新所有参考图预览的样式 }); // 绑定主要提示词输入事件 if ($('main-prompt')) { $('main-prompt').oninput = e => { params.prompt = e.target.value; updatePromptParams(); }; } // 辅助函数:绑定滑块输入事件 const bindSliderEvent = (id, property, displayId = null, isFloat = false) => { const slider = $(id); if (!slider) return; slider.oninput = e => { const value = isFloat ? parseFloat(e.target.value) : parseInt(e.target.value, 10); params[property] = value; if (displayId && $(displayId)) $(displayId).textContent = e.target.value; // 更新数值显示 updatePromptParams(); }; }; bindSliderEvent('stylize', 'stylize', 'stylize-value'); bindSliderEvent('weird', 'weird', 'weird-value'); bindSliderEvent('chaos', 'chaos', 'chaos-value'); bindSliderEvent('stop-slider', 'stop', 'stop-value'); bindSliderEvent('r-slider', 'r', 'r-value'); // 特殊处理质量滑块 (quality),因为它的值不是连续的 const qualitySlider = $('quality-slider'); const qualityValueDisplay = $('quality-value'); const qualityMap = [0.25, 0.5, 1, 2, 4]; // 质量滑块对应的实际值 if (qualitySlider && qualityValueDisplay) { const initialQualityValue = params.quality; const initialSliderIndex = qualityMap.indexOf(initialQualityValue); if (initialSliderIndex !== -1) { // 如果初始值在映射中 qualitySlider.value = initialSliderIndex; qualityValueDisplay.textContent = initialQualityValue; } else { // 默认值处理 params.quality = 1; // 重置为默认值 qualitySlider.value = qualityMap.indexOf(1); // 滑块也重置 qualityValueDisplay.textContent = params.quality; } qualitySlider.oninput = e => { const sliderIndex = parseInt(e.target.value, 10); if (sliderIndex >= 0 && sliderIndex < qualityMap.length) { params.quality = qualityMap[sliderIndex]; if (qualityValueDisplay) qualityValueDisplay.textContent = params.quality; updatePromptParams(); } }; } // 辅助函数:绑定单选按钮组事件 (如速度、模式、可见性) const bindRadioGroup = (groupClass, property, defaultValue) => { document.querySelectorAll(`.${groupClass}`).forEach(btn => { btn.addEventListener('click', () => { // 先移除同组所有按钮的激活状态和样式 document.querySelectorAll(`.${groupClass}`).forEach(b => { b.classList.remove('active'); b.style.backgroundColor = isDarkMode ? '#40444B' : 'white'; b.style.color = isDarkMode ? '#DCDDDE' : '#374151'; b.style.borderColor = isDarkMode ? (b.closest('.btn-group > div > button') ? '#2D2F34' : 'transparent') : (b.closest('.btn-group > div > button') ? '#D1D5DB' : 'transparent'); // 对于非按钮组内的独立按钮组(如可见性按钮),它们有自己的边框 if (!b.closest('.btn-group')) { b.style.borderColor = isDarkMode ? '#2D2F34' : '#D1D5DB'; } }); // 设置当前点击按钮的激活状态和样式 btn.classList.add('active'); btn.style.backgroundColor = isDarkMode ? '#5865F2' : '#4f46e5'; btn.style.color = 'white'; btn.style.borderColor = isDarkMode ? (btn.closest('.btn-group > div > button') ? '#5865F2' : 'transparent') : (btn.closest('.btn-group > div > button') ? '#4f46e5' : 'transparent'); if (!btn.closest('.btn-group')) { btn.style.borderColor = isDarkMode ? '#5865F2' : '#4f46e5'; } params[property] = btn.dataset.value; updatePromptParams(); }); }); }; bindRadioGroup('speed-btn', 'speed', 'relax'); bindRadioGroup('mode-btn', 'mode', 'standard'); bindRadioGroup('visibility-btn', 'visibility', ''); // 辅助函数:绑定拨动开关的事件 (仅处理逻辑,视觉更新由 updateToggleVisuals 完成) const bindToggleSwitchEvent = (switchId, property) => { const switchEl = $(switchId); if (!switchEl) return; switchEl.addEventListener('click', () => { params[property] = !params[property]; updateToggleVisuals(switchId, property); // 调用视觉更新函数 updatePromptParams(); }); }; bindToggleSwitchEvent('tile-toggle-switch', 'tile'); bindToggleSwitchEvent('draft-toggle-switch', 'draft'); // 初始化时设置拨动开关的视觉状态 updateToggleVisuals('tile-toggle-switch', 'tile'); updateToggleVisuals('draft-toggle-switch', 'draft'); // 绑定版本选择下拉框事件 if ($('version-select')) { $('version-select').onchange = e => { params.version = e.target.value; updatePromptParams(); }; } // 绑定排除词输入事件 if ($('no-prompt')) { $('no-prompt').oninput = e => { params.noPrompt = e.target.value.trim(); updatePromptParams(); }; } // 绑定拷贝参数按钮事件 if ($('copy-btn')) { $('copy-btn').onclick = () => { const textarea = $('prompt-params'); if (!textarea || !textarea.value) { showToast('没有参数可以拷贝'); return; } textarea.select(); // 选中内容 try { // 优先使用现代的 Clipboard API if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(textarea.value).then(() => { showToast('参数已复制!'); }).catch(err => { showToast('复制失败!请尝试手动复制。'); console.error('异步复制失败:', err); // 如果 Clipboard API 失败 (例如非安全上下文),尝试旧方法 legacyCopy(textarea); }); } else { legacyCopy(textarea); // 回退到旧方法 } } catch (err) { // 捕获 legacyCopy 中同步 execCommand 可能的错误 // legacyCopy 内部会显示自己的 toast,这里主要记录错误 console.error('外层复制尝试失败:', err); } }; } // 辅助函数:用于旧版浏览器的复制操作 function legacyCopy(textareaElement) { textareaElement.select(); try { const successful = document.execCommand('copy'); if (successful) { showToast('参数已复制 (兼容模式)!'); } else { showToast('复制失败 (兼容模式)!请手动复制。'); } } catch (err) { showToast('复制异常 (兼容模式)!请手动复制。'); console.error('兼容模式复制失败:', err); } } // 图片尺寸预设值和滑块映射 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 ($('clear-btn')) { $('clear-btn').onclick = () => { resetParams(); // 重置所有参数到默认值 // 更新UI元素以反映重置后的参数 if ($('main-prompt')) $('main-prompt').value = params.prompt; if ($('stylize')) $('stylize').value = params.stylize; if ($('stylize-value')) $('stylize-value').textContent = params.stylize; if ($('weird')) $('weird').value = params.weird; if ($('weird-value')) $('weird-value').textContent = params.weird; if ($('chaos')) $('chaos').value = params.chaos; if ($('chaos-value')) $('chaos-value').textContent = params.chaos; if ($('version-select')) $('version-select').value = params.version; if ($('no-prompt')) $('no-prompt').value = params.noPrompt; if ($('ratio-slider')) { // 特殊处理比例滑块和预览 const defaultRatioIndex = sizeMap.indexOf(params.ar); $('ratio-slider').value = defaultRatioIndex !== -1 ? defaultRatioIndex : 5; // 5 对应 '1:1' $('ratio-slider').dispatchEvent(new Event('input')); // 触发input事件来更新预览和按钮 } // 清空所有参考图URL和权重输入框 ['cref-url', 'cref-weight', 'sref-url', 'sref-weight', 'oref-url', 'oref-weight', 'direct-image-url', 'direct-image-weight'].forEach(id => { if ($(id)) $(id).value = ''; }); if ($('seed-input')) $('seed-input').value = params.seed; if ($('quality-slider') && qualityMap) { // 重置质量滑块 const resetSliderIndex = qualityMap.indexOf(params.quality); // params.quality 此时是 1 $('quality-slider').value = resetSliderIndex !== -1 ? resetSliderIndex : qualityMap.indexOf(1); } if ($('quality-value')) { $('quality-value').textContent = params.quality; } if ($('stop-slider')) $('stop-slider').value = params.stop; if ($('stop-value')) $('stop-value').textContent = params.stop; if ($('personal-params')) $('personal-params').value = params.personalParams; if ($('r-slider')) $('r-slider').value = params.r; if ($('r-value')) $('r-value').textContent = params.r; // 重新应用主题相关的视觉样式 updateToggleVisuals('tile-toggle-switch', 'tile'); updateToggleVisuals('draft-toggle-switch', 'draft'); setInitialActiveButtons(); // 重置按钮组的激活状态和样式 refreshPreviews(); // 清空并刷新参考图预览区 updatePromptParams(); // 更新最终参数显示 showToast('所有参数已重置为默认值'); }; } // 初始化各个参考图/代码区域 setupRefSection('cref', 'cref'); setupRefSection('sref', 'sref'); setupRefSection('oref', 'oref'); setupRefSection('direct-image', 'directImages'); // idPrefix 是 'direct-image', paramKey 是 'directImages' // 比例预设值对应的预览框尺寸 const ratioPresets = { '1:2': { width: 50, height: 100 }, '9:16': { width: 56.25, height: 100 }, '2:3': { width: 66.67, height: 100 }, '3:4': { width: 75, height: 100 }, '5:6': { width: 83.33, height: 100 }, '1:1': { width: 100, height: 100 }, '6:5': { width: 100, height: 83.33 }, '4:3': { width: 100, height: 75 }, '3:2': { width: 100, height: 66.67 }, '16:9': { width: 100, height: 56.25 }, '2:1': { width: 100, height: 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 preset = ratioPresets[ratio] || { width: 100, height: 100 }; // 获取对应的预览尺寸 if(box) { // 更新预览框的尺寸和文本 box.style.width = `${preset.width}px`; box.style.height = `${preset.height}px`; box.textContent = ratio; } // 更新尺寸预设按钮的激活状态 document.querySelectorAll('#size-buttons button').forEach(btn => { const btnRatio = btn.dataset.value; // 获取按钮代表的比例 const isActive = btnRatio === ratio; btn.classList.toggle('active', isActive); // 根据主题和激活状态设置样式 (与 setInitialActiveButtons 逻辑类似) if (isActive) { btn.style.backgroundColor = isDarkMode ? '#5865F2' :'#4f46e5'; btn.style.color = 'white'; btn.style.borderColor = isDarkMode ? '#5865F2' : '#4f46e5'; } else { btn.style.backgroundColor = isDarkMode ? '#40444B' : 'white'; btn.style.color = isDarkMode ? '#DCDDDE' : '#374151'; btn.style.borderColor = isDarkMode ? '#2D2F34' : '#D1D5DB'; } }); updatePromptParams(); // 更新最终参数显示 }; // 初始化滑块值 (在按钮创建和主题应用之后再触发首次事件) } // 创建尺寸预设按钮 (纵向, 正方形, 横向) const sizeButtonGroup = $('size-buttons'); if (sizeButtonGroup) { const presetMap = { '纵向': '1:2', '正方形': '1:1', '横向': '2:1' }; ['纵向', '正方形', '横向'].forEach((label) => { const btn = document.createElement('button'); btn.textContent = label; btn.dataset.value = presetMap[label]; // 将比例值存储在 dataset 中 // 初始化按钮样式 (后续由主题切换或滑块事件更新) btn.style.padding = '4px 12px'; btn.style.borderRadius = '6px'; btn.style.cursor = 'pointer'; btn.style.transition = 'all 0.2s ease'; // 初始样式基于浅色模式非激活状态 btn.style.backgroundColor = 'white'; btn.style.color = '#374151'; btn.style.borderColor = '#D1D5DB'; // 如果当前参数的ar值与此按钮匹配,则设为激活 (初始加载时) if (params.ar === presetMap[label]) { btn.classList.add('active'); // 激活样式会在后续 themeToggleButton 点击或 setInitialActiveButtons 中正确设置 } btn.onclick = () => { // 点击预设按钮时,更新滑块并触发其input事件 const ratio = presetMap[label]; const sliderIndex = sizeMap.indexOf(ratio); if (sliderIndex !== -1 && ratioSlider) { ratioSlider.value = sliderIndex; ratioSlider.dispatchEvent(new Event('input')); // 触发滑块事件以统一更新 } }; sizeButtonGroup.appendChild(btn); }); } // 绑定种子数输入事件,带验证 if ($('seed-input')) { $('seed-input').oninput = e => { const value = e.target.value.trim(); // 验证输入是否为数字,且在0到4294967295之间,或为空 if (/^\d*$/.test(value) && (value === '' || (parseInt(value) >= 0 && parseInt(value) <= 4294967295))) { params.seed = value; // 合法则更新参数 } else { e.target.value = params.seed; // 不合法则恢复上次有效值 } updatePromptParams(); }; } // 绑定个性化参数输入事件 if ($('personal-params')) { $('personal-params').oninput = e => { params.personalParams = e.target.value.trim(); updatePromptParams(); }; } // 为最终参数文本框添加点击复制功能 const promptParamsTextarea = $('prompt-params'); if (promptParamsTextarea) { promptParamsTextarea.addEventListener('click', () => { if (!promptParamsTextarea.value) { showToast('没有参数可以拷贝'); return; } // 复用拷贝按钮的逻辑 if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(promptParamsTextarea.value).then(() => { showToast('参数已复制!'); }).catch(err => { showToast('复制失败!请尝试手动复制。'); console.error('从文本框异步复制失败:', err); legacyCopy(promptParamsTextarea); // 回退 }); } else { legacyCopy(promptParamsTextarea); // 旧版浏览器 } }); } // 为所有输入控件添加焦点和失焦时的边框样式变化 document.querySelectorAll('input[type="text"], input[type="number"], textarea, select').forEach(el => { el.addEventListener('focus', () => { // 焦点颜色:深色模式用Discord紫色强调,浅色模式用主题靛蓝强调 el.style.borderColor = isDarkMode ? '#7289DA' : '#4f46e5'; }); el.addEventListener('blur', () => { // 失焦时恢复到当前主题的标准边框颜色 el.style.borderColor = isDarkMode ? '#18191C' : '#D1D5DB'; }); }); // 为面板内的按钮添加更细致的悬停效果 (不包括主题切换按钮和已激活的按钮组内按钮) document.querySelectorAll('#mj-control-panel button').forEach(btn => { let originalBg, originalColor, originalBorder; // 用于存储原始样式以便恢复 btn.addEventListener('mouseenter', () => { // 排除主题切换按钮、已激活按钮、按钮组内的非激活按钮(它们通常没有独立边框或特殊背景) if (btn.id === 'theme-toggle' || btn.classList.contains('active') || btn.closest('.btn-group button:not(.active)')) { return; } originalBg = btn.style.backgroundColor; originalColor = btn.style.color; originalBorder = btn.style.borderColor; // 特殊按钮(拷贝、清空、添加)的悬停效果 const isPrimaryAction = btn.classList.contains('action-button-primary') || btn.id.endsWith('-add'); const isSecondaryAction = btn.classList.contains('action-button-secondary'); if (isPrimaryAction) { btn.style.backgroundColor = isDarkMode ? '#4752C4' : '#4338CA'; // 深色主题用更深的紫,浅色主题用更深的靛蓝 } else if (isSecondaryAction) { btn.style.backgroundColor = isDarkMode ? '#4F545C' : '#D1D5DB'; // 深色主题用中度灰,浅色主题用更深的浅灰 } else if (!btn.closest('.btn-group')) { // 普通按钮(非按钮组内),例如可见性按钮 btn.style.backgroundColor = isDarkMode ? '#4F545C' : '#F3F4F6'; // 深色用中灰,浅色用极浅灰 } // 通用悬停效果:轻微上移和阴影 if(!btn.closest('.btn-group button')) { // 按钮组内的按钮通常不需要额外阴影和位移 btn.style.transform = 'translateY(-1px)'; btn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; } }); btn.addEventListener('mouseleave', () => { if (btn.id === 'theme-toggle' || btn.classList.contains('active') || btn.closest('.btn-group button:not(.active)')) { return; } // 恢复原始样式,除非它们是特殊按钮且未激活 const isPrimaryAction = btn.classList.contains('action-button-primary') || btn.id.endsWith('-add'); const isSecondaryAction = btn.classList.contains('action-button-secondary'); if ( (isPrimaryAction || isSecondaryAction || !btn.closest('.btn-group')) && !btn.classList.contains('active')) { btn.style.backgroundColor = originalBg; btn.style.color = originalColor; btn.style.borderColor = originalBorder; } if(!btn.closest('.btn-group button')) { btn.style.transform = 'translateY(0)'; btn.style.boxShadow = 'none'; } }); }); // 确保ratioSlider在按钮和主题应用后初始化其显示 if (ratioSlider) { const initialRatioIndex = sizeMap.indexOf(params.ar); ratioSlider.value = initialRatioIndex !== -1 ? initialRatioIndex : 5; ratioSlider.dispatchEvent(new Event('input')); // 现在触发,确保UI已准备好 } // 首次加载时,根据 isDarkMode 的初始值(默认为 false)应用一次主题 // 通过模拟点击主题切换按钮,然后再切换回来,确保所有样式基于初始状态正确应用 // 这是一个确保初始样式正确的技巧 const initialThemeState = isDarkMode; isDarkMode = !initialThemeState; // 临时反转 themeToggleButton.click(); // 应用反转后的主题,并使 isDarkMode 变回 initialThemeState // 此时 isDarkMode 是 initialThemeState,且对应样式已应用 } // 添加图片预览到容器 function addPreviewImage(containerId, url, weight, paramKey, currentWeightPrefix) { // paramKey 用于删除操作时定位参数数组 const container = document.getElementById(containerId); if (!container) return; const imgContainer = document.createElement('div'); imgContainer.style.cssText = 'position: relative; margin: 4px; animation: fadeIn 0.3s ease;'; const img = document.createElement('img'); img.src = url; img.style.cssText = ` width: 60px; height: 60px; object-fit: cover; border-radius: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); border: 1px solid ${isDarkMode ? '#40444B' : '#E5E7EB'}; /* 根据主题调整边框 */ transition: all 0.2s ease; `; img.onerror = () => { // 图片加载失败处理 const errorDiv = document.createElement('div'); errorDiv.textContent = '无效图'; errorDiv.style.cssText = `width:60px; height:60px; display:flex; align-items:center; justify-content:center; border-radius:4px; font-size:10px; text-align:center;`; if(isDarkMode) { errorDiv.style.background = '#40444B'; // 深色背景 errorDiv.style.color = '#aaa'; // 浅色文字 errorDiv.style.border = '1px solid #555'; } else { errorDiv.style.background = '#F3F4F6'; // 浅色背景 errorDiv.style.color = '#757575'; // 深色文字 errorDiv.style.border = '1px solid #E5E7EB'; } imgContainer.innerHTML = ''; // 清空原内容 (可能是img标签) imgContainer.appendChild(errorDiv); }; img.addEventListener('mouseenter', () => { img.style.transform = 'scale(1.05)'; img.style.boxShadow = `0 2px 6px ${isDarkMode ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.15)'}`; }); img.addEventListener('mouseleave', () => { img.style.transform = 'scale(1)'; img.style.boxShadow = `0 1px 3px ${isDarkMode ? 'rgba(0,0,0,0.2)' : 'rgba(0,0,0,0.1)'}`; }); // 权重徽章 const weightBadge = document.createElement('div'); weightBadge.style.cssText = ` position: absolute; bottom: 0; left: 0; background: ${isDarkMode ? 'rgba(0,0,0,0.7)' : 'rgba(255,255,255,0.8)'}; color: ${isDarkMode ? 'white' : '#374151'}; font-size: 10px; padding: 1px 3px; border-radius: 0 4px 0 0; border-top: 1px solid ${isDarkMode ? '#5865F2' : '#4f46e5'}; /* 强调边框 */ border-right: 1px solid ${isDarkMode ? '#5865F2' : '#4f46e5'}; `; const wP = currentWeightPrefix ? currentWeightPrefix.replace('--', '') : ''; // 简化权重前缀显示 weightBadge.textContent = weight && weight.trim() !== '' ? `${wP}:${weight}` : `${wP}:默认`; // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.style.cssText = ` position: absolute; top: -5px; right: -5px; background: ${isDarkMode ? 'rgba(239, 68, 68, 0.7)' : 'rgba(239, 68, 68, 0.85)'}; /* 轻微调整透明度 */ color: white; border: none; border-radius: 50%; width: 18px; height: 18px; font-size: 12px; line-height: 18px; /* 增大一点方便点击 */ text-align:center; cursor:pointer; transition:all 0.2s ease; opacity: 0.8; display:flex; align-items:center; justify-content:center; box-shadow: 0 1px 2px rgba(0,0,0,0.2); `; deleteBtn.innerHTML = '×'; // 关闭图标 deleteBtn.onclick = function(e) { // 点击删除 e.stopPropagation(); // 阻止事件冒泡 imgContainer.style.animation = 'fadeOut 0.2s ease forwards'; // 播放淡出动画 setTimeout(() => { const targetArray = params[paramKey]; // 根据 paramKey 获取正确的参数数组 if (Array.isArray(targetArray)) { const index = targetArray.findIndex(item => item.url === url); // 找到要删除的项 if (index !== -1) { targetArray.splice(index, 1); // 从数组中移除 if (container.contains(imgContainer)) container.removeChild(imgContainer); // 从DOM中移除 updatePromptParams(); // 更新最终参数显示 } } }, 200); // 等待动画完成 }; deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.opacity = '1'; deleteBtn.style.transform = 'scale(1.1)'; deleteBtn.style.background = isDarkMode ? 'rgb(239, 68, 68)' : 'rgb(220, 38, 38)'; }); deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.opacity = '0.8'; deleteBtn.style.transform = 'scale(1)'; deleteBtn.style.background = isDarkMode ? 'rgba(239, 68, 68, 0.7)' : 'rgba(239, 68, 68, 0.85)';}); imgContainer.appendChild(img); imgContainer.appendChild(weightBadge); imgContainer.appendChild(deleteBtn); container.appendChild(imgContainer); } // 添加Sref代码预览到容器 function addPreviewSrefCode(containerId, code, weight) { const container = document.getElementById(containerId); if (!container) return; const codeContainer = document.createElement('div'); codeContainer.style.cssText = ` position: relative; margin: 4px; border-radius: 4px; padding: 6px 10px; display: inline-flex; align-items: center; /* 改为 inline-flex 使其内容包裹 */ font-family: monospace; font-size: 12px; animation: fadeIn 0.3s ease; transition: background-color 0.2s, border-color 0.2s, color 0.2s; /* 平滑过渡 */ `; // 根据主题设置样式 if (isDarkMode) { codeContainer.style.background = '#202225'; // 深色背景 codeContainer.style.color = '#DCDDDE'; // 浅色文字 codeContainer.style.border = '1px solid #2D2F34'; // 深色边框 } else { codeContainer.style.background = '#F3F4F6'; // 浅色背景 codeContainer.style.color = '#111827'; // 深色文字 codeContainer.style.border = '1px solid #E0E0E0'; // 浅色边框 } const codeText = document.createElement('span'); codeText.textContent = `sref:${code}`; // 权重徽章 const weightBadge = document.createElement('div'); weightBadge.style.cssText = 'margin-left: 8px; font-size: 10px; padding: 2px 4px; border-radius: 3px; transition: background-color 0.2s, color 0.2s;'; weightBadge.textContent = weight && weight.trim() !== '' ? `sw:${weight}` : 'sw:默认'; if (isDarkMode) { weightBadge.style.background = 'rgba(88, 101, 242, 0.3)'; // 深色主题强调色背景 weightBadge.style.color = '#B9BBBE'; // 深色主题强调色文字 } else { weightBadge.style.background = 'rgba(79, 70, 229, 0.1)'; // 浅色主题强调色背景 weightBadge.style.color = '#4f46e5'; // 浅色主题强调色文字 } // 删除按钮 const deleteBtn = document.createElement('button'); deleteBtn.style.cssText = 'margin-left: 10px; border: none; border-radius: 4px; padding: 2px 5px; font-size: 10px; line-height:1; cursor:pointer; transition:all 0.2s ease;'; if (isDarkMode) { deleteBtn.style.background = 'rgba(239, 68, 68, 0.3)'; // 深色主题删除按钮背景 deleteBtn.style.color = '#FAA'; // 深色主题删除按钮文字 } else { deleteBtn.style.background = 'rgba(239, 68, 68, 0.1)'; // 浅色主题删除按钮背景 deleteBtn.style.color = '#ef4444'; // 浅色主题删除按钮文字 } deleteBtn.innerHTML = '×'; deleteBtn.onclick = function(e) { // 点击删除 e.stopPropagation(); codeContainer.style.animation = 'fadeOut 0.2s ease forwards'; setTimeout(() => { const index = params.sref.findIndex(item => item.url === code); // sref是特定的,直接用 if (index !== -1) { params.sref.splice(index, 1); if (container.contains(codeContainer)) container.removeChild(codeContainer); updatePromptParams(); } }, 200); }; // 删除按钮的悬停效果 const originalDeleteBg = deleteBtn.style.backgroundColor; // 保存原始背景色 deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.backgroundColor = isDarkMode ? 'rgba(239, 68, 68, 0.5)' : 'rgba(239, 68, 68, 0.25)'; }); deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.backgroundColor = originalDeleteBg; }); codeContainer.appendChild(codeText); codeContainer.appendChild(weightBadge); codeContainer.appendChild(deleteBtn); container.appendChild(codeContainer); } // 刷新所有参考图片/代码的预览区域 function refreshPreviews() { // paramKey 对应 params 对象中的键名和 weightPrefixes 映射的键名 const refTypes = [ { idPrefix: 'cref', paramKey: 'cref', previewId: 'cref-preview' }, { idPrefix: 'sref', paramKey: 'sref', previewId: 'sref-preview' }, { idPrefix: 'oref', paramKey: 'oref', previewId: 'oref-preview' }, { idPrefix: 'direct-image', paramKey: 'directImages', previewId: 'direct-image-preview' } ]; refTypes.forEach(ref => { const container = document.getElementById(ref.previewId); if (!container) return; container.innerHTML = ''; // 清除现有预览内容 const items = params[ref.paramKey]; // 获取对应的参数数组 if (items && Array.isArray(items)) { items.forEach(item => { const currentWeightPrefix = weightPrefixes[ref.paramKey]; // 获取权重前缀 // 如果是 sref 且为 'random' 或数字代码 if (ref.paramKey === 'sref' && (item.url === 'random' || /^\d+$/.test(item.url))) { addPreviewSrefCode(ref.previewId, item.url, item.weight); } else { // 否则为图片URL addPreviewImage(ref.previewId, item.url, item.weight, ref.paramKey, currentWeightPrefix); } }); } }); } // 注入自定义CSS样式 (例如动画、滑块样式) function injectStyles() { const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = ` @keyframes fadeIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(5px); } } /* 通用滑块样式重置 */ input[type="range"] { -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer; width: 100%;} /* 浅色模式滑块轨道和滑块本身的基础样式 */ input[type="range"]::-webkit-slider-runnable-track { background: #E5E7EB; height: 0.4rem; border-radius: 0.2rem; } input[type="range"]::-moz-range-track { background: #E5E7EB; height: 0.4rem; border-radius: 0.2rem; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; margin-top: -0.3rem; /* 使滑块垂直居中于轨道 */ background-color: #4f46e5; height: 1rem; width: 1rem; border-radius: 50%; border: 1px solid #3730a3; /* 浅色模式下更深的边框 */} input[type="range"]::-moz-range-thumb { border: none; border-radius: 50%; background-color: #4f46e5; height: 1rem; width: 1rem; border: 1px solid #3730a3;} /* 深色模式滑块轨道和滑块样式 (通过JS设置CSS变量来改变) */ input[type="range"]:is([style*="--track-bg"])::-webkit-slider-runnable-track { background: var(--track-bg); } input[type="range"]:is([style*="--track-bg"])::-moz-range-track { background: var(--track-bg); } input[type="range"]:is([style*="--thumb-bg"])::-webkit-slider-thumb { background-color: var(--thumb-bg); border-color: #B0B3B8; /* 深色模式下为滑块使用稍浅的边框 */ } input[type="range"]:is([style*="--thumb-bg"])::-moz-range-thumb { background-color: var(--thumb-bg); border-color: #B0B3B8; } /* 按钮组内按钮的样式 */ .btn-group button { border: none; } /* 移除独立边框,由父级 .btn-group 控制整体边框 */ .btn-group > button:not(:last-child), /* 直接子按钮 */ .btn-group > div > button:not(:last-child) { /* 兼容 label > div > button 结构 */ border-right: 1px solid #d1d5db; /* 浅色模式分隔线,会被JS按主题覆盖 */ } /* 深色模式下按钮组分隔线 - 会被JS按主题覆盖 */ /* html[data-theme='dark'] .btn-group button:not(:last-child), body[style*="background-color: rgb(43, 45, 49)"] .btn-group button:not(:last-child) { border-right-color: #40444B; } */ /* 深色模式下的 placeholder 颜色 */ .dark-placeholder::placeholder { color: #72767d; opacity: 1; } /* 使 textarea 可以垂直调整大小 */ textarea { resize: vertical; } `; document.head.appendChild(styleSheet); // 检查是否已加载 Font Awesome,避免重复加载 (如果其他脚本也用它) // Font Awesome 在此脚本中似乎并未直接使用其图标类,主要是SVG图标。如果需要,可以取消注释。 /* if (!document.querySelector('link[href*="font-awesome"]')) { const faLink = document.createElement('link'); faLink.rel = 'stylesheet'; faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css'; // 可以考虑使用特定版本或 SRI document.head.appendChild(faLink); } */ } // 初始化脚本 function init() { injectStyles(); // 注入CSS样式 resetParams(); // 初始化参数对象为默认值 createSettingButton(); // 创建“MJ设置”按钮 createControlPanel(); // 创建控制面板DOM结构并绑定基础事件 // 控制面板创建后,手动触发一次主题设置,确保初始状态正确显示 // (bindControlEvents 内部的 themeToggleButton 事件监听器会处理实际的样式切换) // `isDarkMode` 默认为 false, 所以第一次点击会应用浅色模式 (如果当前是深色则切换) // 再次点击会应用深色模式。我们需要确保初始加载时面板根据 `isDarkMode` 的默认值正确渲染。 // `bindControlEvents` 末尾的 themeToggleButton.click() 调用会完成这个任务。 updatePromptParams(); // 初始化时填充一次最终参数文本框 } // 等待 Discord 应用挂载后再执行初始化 const discordAppMount = document.getElementById('app-mount'); if (discordAppMount) { // 短暂延迟有时有助于确保 Discord 自身的样式已稳定 setTimeout(init, 500); } else { // 如果 app-mount 还未加载,则监听 window load 事件 window.addEventListener('load', () => setTimeout(init, 500), { once: true }); } })();