// ==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 = `
模型设置
更多参数
可见性 (--public, --stealth)
`;
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 });
}
})();