// ==UserScript==
// @name Discord Midjourney 参数面板
// @namespace https://github.com/cwser
// @version 0.2.4
// @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',
version: 'v7',
speed: 'relax',
draft: false,
noPrompt: '',
cref: [], // 格式: {url, 权重}
sref: [], // 格式: {url, 权重}
oref: [], // 格式: {url, 权重}
iref: [], // 这个参数在原始定义中存在,但未在UI或输出中使用,为保持一致性(如果未来计划使用)而保留
directImages: [], // 格式: {url, 权重}
// 新增参数
tile: false,
seed: '',
quality: 1,
stop: 100,
visibility: '',
// 新增个性化参数
personalParams: '',
// 新增批量任务参数
r: 1
};
// 显示 Toast 提示
function showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(0,0,0,0.8);
color: white;
padding: 10px 16px;
border-radius: 6px;
z-index: 99999;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateY(-20px);
opacity: 0;
transition: all 0.3s ease;
`;
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;
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;
`;
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}`, // 修改:始终包含 --s,即使为 0
weird !== 0 ? `--w ${weird}` : '',
chaos !== 0 ? `--c ${chaos}` : '',
mode !== 'standard' ? `--${mode}` : '', // 确保 'standard' 是默认值且不被添加
draft ? '--draft' : '',
noPrompt ? `--no ${noPrompt}` : '',
version.startsWith('niji') ? `--niji ${version.replace('niji', '')}` : `--v ${version.replace('v', '')}`,
speed !== 'relax' ? `--${speed}` : '', // 仅在速度不是默认的 relax 时添加
tile ? '--tile' : '',
seed ? `--seed ${seed}` : '',
quality !== 1 ? `--q ${quality}` : '', // 仅在不是默认值 1 时添加
stop !== 100 ? `--stop ${stop}` : '', // 仅在不是默认值 100 时添加
visibility ? `--${visibility}` : '', // 将会是 --public 或 --stealth
personalParams ? `--p ${personalParams}` : '',
params.r > 1 ? `--r ${params.r}` : '' // 仅在 r > 1 时添加
];
// 处理带权重的图像参考
const formatImageWithWeight = (url, weight, prefix) => {
let formatted = url;
if (weight && weight.trim() !== '') { // 检查是否提供了权重且不为空
formatted += ` ${prefix} ${weight.trim()}`;
}
return formatted;
};
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')}`), // 修改:统一 sref 处理
...oref.map(item => `--oref ${formatImageWithWeight(item.url, item.weight, '--ow')}`),
...otherParts.filter(Boolean) // 过滤掉空字符串
].filter(Boolean); // 过滤掉任何完全为空的部分
promptField.value = allParts.join(' ').trim();
}
}
// 创建控制面板
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;
`;
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: '' }
];
buttonGroups.forEach(group => {
const buttons = document.querySelectorAll(`.${group.className}`);
let currentParamValue = params[group.param] === undefined ? group.defaultValue : params[group.param];
buttons.forEach(btn => {
if (btn.dataset.value === currentParamValue) {
btn.classList.add('active');
btn.style.backgroundColor = '#4f46e5';
btn.style.color = 'white';
} else {
btn.classList.remove('active');
btn.style.backgroundColor = 'white';
btn.style.color = '#111827';
}
});
});
}
// 绑定控制面板事件
function bindControlEvents() {
const $ = id => document.getElementById(id);
let isDarkMode = false;
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 bgElements = panel.querySelectorAll('[style*="background:#f9fafb"], .btn-group');
const labels = panel.querySelectorAll('label, p, h3, span');
if (isDarkMode) {
panel.style.backgroundColor = '#2B2D31'; // Discord 暗色主题背景
panel.style.color = '#DCDDDE'; // Discord 暗色主题文本
panel.style.borderColor = '#202225'; // Discord 暗色主题边框
labels.forEach(label => label.style.color = '#DCDDDE');
themeToggleButton.innerHTML = sunIcon;
themeToggleButton.style.color = '#DCDDDE';
allButtons.forEach(btn => {
if (!btn.classList.contains('active') && !btn.id.startsWith('theme-toggle')) { // 保留激活按钮的样式
btn.style.backgroundColor = '#40444B'; // Discord 暗色按钮
btn.style.color = '#DCDDDE';
btn.style.borderColor = '#2D2F34'; // 按钮使用稍暗的边框
} else if (btn.classList.contains('active')) {
btn.style.backgroundColor = '#5865F2'; // 保留激活状态颜色
btn.style.color = 'white';
}
});
inputsAndTextareas.forEach(input => {
input.style.backgroundColor = '#202225'; // Discord 暗色输入框
input.style.color = '#DCDDDE';
input.style.borderColor = '#18191C'; // Discord 暗色输入框边框
if (input.type === 'range') {
input.style.setProperty('--thumb-bg', '#DCDDDE');
input.style.setProperty('--track-bg', '#40444B');
}
});
bgElements.forEach(el => el.style.backgroundColor = '#202225'); // 卡片背景
// 为暗色模式更新比例框样式
const ratioBox = $('ratio-box');
if (ratioBox) {
ratioBox.style.background = '#40444B';
ratioBox.style.color = '#DCDDDE';
ratioBox.style.borderColor = '#DCDDDE';
}
} else { // 亮色模式
panel.style.backgroundColor = 'white';
panel.style.color = '#111827';
panel.style.borderColor = '#E5E7EB';
labels.forEach(label => label.style.color = '#111827'); // 重置标签颜色
themeToggleButton.innerHTML = moonIcon;
themeToggleButton.style.color = '#6B7280';
allButtons.forEach(btn => {
if (!btn.classList.contains('active') && !btn.id.startsWith('theme-toggle')) {
btn.style.backgroundColor = 'white'; // 默认亮色模式按钮
btn.style.color = '#111827';
btn.style.borderColor = '#d1d5db';
} else if (btn.classList.contains('active')) {
btn.style.backgroundColor = '#4f46e5'; // 亮色模式下的激活颜色
btn.style.color = 'white';
}
});
inputsAndTextareas.forEach(input => {
input.style.backgroundColor = 'white';
input.style.color = '#111827';
input.style.borderColor = '#d1d5db';
if (input.type === 'range') {
input.style.removeProperty('--thumb-bg');
input.style.removeProperty('--track-bg');
}
});
bgElements.forEach(el => {
if(el.classList.contains('btn-group')) {
el.style.backgroundColor = 'transparent'; // 或者为按钮组指定特定的亮色模式
} else {
el.style.backgroundColor = '#f9fafb';
}
});
// 为亮色模式重置比例框样式
const ratioBox = $('ratio-box');
if (ratioBox) {
ratioBox.style.background = '#f3f4f6';
ratioBox.style.color = '#374151';
ratioBox.style.borderColor = '#374151';
}
}
setInitialActiveButtons(); // 重新应用激活按钮样式以确保在主题更改后它们是正确的
});
$('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('quality-slider', 'quality', 'quality-value', true); // 质量可以是浮点数
bindSliderEvent('stop-slider', 'stop', 'stop-value');
bindSliderEvent('r-slider', 'r', 'r-value');
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' : '#111827';
});
btn.classList.add('active');
btn.style.backgroundColor = isDarkMode ? '#5865F2' : '#4f46e5'; // 使用适合主题的激活颜色
btn.style.color = 'white';
params[property] = btn.dataset.value;
updatePromptParams();
});
});
};
bindRadioGroup('speed-btn', 'speed', 'relax');
bindRadioGroup('mode-btn', 'mode', 'standard');
bindRadioGroup('visibility-btn', 'visibility', '');
const bindToggleSwitch = (switchId, property) => {
const switchEl = $(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';
}
switchEl.addEventListener('click', () => {
params[property] = !params[property];
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';
}
updatePromptParams();
});
};
bindToggleSwitch('tile-toggle-switch', 'tile');
bindToggleSwitch('draft-toggle-switch', 'draft'); // 已修正的草稿切换开关ID
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) return;
textarea.select();
try {
document.execCommand('copy');
showToast('参数已复制!');
} catch (err) {
showToast('复制失败!');
console.error('Copy failed:', err);
}
};
}
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; // 如果未找到,则默认为 1:1
$('ratio-slider').dispatchEvent(new Event('input')); // 触发更新
}
['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')) $('quality-slider').value = params.quality;
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;
// 通过重新运行其绑定函数或手动设置来重置切换开关
bindToggleSwitch('tile-toggle-switch', 'tile');
bindToggleSwitch('draft-toggle-switch', 'draft');
setInitialActiveButtons(); // 这将正确设置单选按钮的激活状态
refreshPreviews();
updatePromptParams(); // 更新最终的参数字符串
showToast('所有参数已重置为默认值');
};
}
const setupRefSection = (type) => {
const addBtn = $(`${type}-add`);
const urlInput = $(`${type}-url`);
const weightInput = $(`${type}-weight`);
const previewContainerId = `${type}-preview`;
if (!addBtn || !urlInput || !weightInput) return;
addBtn.onclick = () => {
const url = urlInput.value.trim();
const weight = weightInput.value.trim();
if (!url) {
showToast(`请输入${type === 'sref' ? 'URL或代码' : 'URL'}`);
return;
}
const isImageUrl = /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url);
const isSrefCode = type === 'sref' && (/^\d+$/.test(url) || url.toLowerCase() === 'random');
if (type !== 'sref' && !isImageUrl) {
showToast('请输入有效的图片URL');
return;
}
if (type === 'sref' && !isImageUrl && !isSrefCode) {
showToast("请输入有效图片URL, 'random'或数字sref码");
return;
}
const targetArray = params[type === 'directImages' ? 'directImages' : type];
const srefCodeToAdd = isSrefCode ? (url.toLowerCase() === 'random' ? 'random' : url) : url;
const itemUrl = type === 'sref' ? srefCodeToAdd : url;
if (!targetArray.some(item => item.url === itemUrl)) {
targetArray.push({ url: itemUrl, weight });
if (isSrefCode) {
addPreviewSrefCode(previewContainerId, itemUrl, weight);
} else {
const weightPrefix = {'cref': '--cw', 'sref': '--sw', 'oref': '--ow', 'directImages': '--iw'}[type];
addPreviewImage(previewContainerId, itemUrl, weight, type, weightPrefix);
}
urlInput.value = '';
weightInput.value = '';
updatePromptParams();
} else {
showToast(`该${type === 'sref' && isSrefCode ? '码' : 'URL'}已添加`);
}
};
};
setupRefSection('cref');
setupRefSection('sref');
setupRefSection('oref');
setupRefSection('directImages');
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': { 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;
}
const presetMap = { '纵向': '1:2', '正方形': '1:1', '横向': '2:1' };
document.querySelectorAll('#size-buttons button').forEach(btn => {
const btnRatio = presetMap[btn.textContent];
if (btnRatio === ratio) {
btn.classList.add('active');
btn.style.backgroundColor = isDarkMode ? '#5865F2' :'#4f46e5';
btn.style.color = 'white';
} else {
btn.classList.remove('active');
btn.style.backgroundColor = isDarkMode ? '#40444B' : 'white';
btn.style.color = isDarkMode ? '#DCDDDE' : '#111827';
}
});
updatePromptParams();
};
// 触发比例滑块的初始事件以设置预览
const initialRatioIndex = sizeMap.indexOf(params.ar);
ratioSlider.value = initialRatioIndex !== -1 ? initialRatioIndex : 5; // 默认为 1:1
ratioSlider.dispatchEvent(new Event('input'));
}
const btnGroup = $('size-buttons');
if (btnGroup) {
const presetMap = { '纵向': '1:2', '正方形': '1:1', '横向': '2:1' };
['纵向', '正方形', '横向'].forEach((label, i) => {
const btn = document.createElement('button');
btn.textContent = label;
btn.dataset.value = presetMap[label];
btn.style.cssText = 'padding:4px 12px; border-radius:6px; border:1px solid #d1d5db; background:white; cursor:pointer; transition:all 0.2s ease;';
if ((isDarkMode && params.ar === presetMap[label]) || (!isDarkMode && params.ar === presetMap[label]) ) {
btn.classList.add('active');
btn.style.backgroundColor = isDarkMode ? '#5865F2' : '#4f46e5';
btn.style.color = 'white';
} else if (params.ar === presetMap[label]) { // 默认激活 1:1 (正方形)
btn.classList.add('active');
btn.style.backgroundColor = '#4f46e5';
btn.style.color = 'white';
}
btn.onclick = () => {
const ratio = presetMap[label];
const sliderIndex = sizeMap.indexOf(ratio);
if (sliderIndex !== -1 && ratioSlider) {
ratioSlider.value = sliderIndex;
ratioSlider.dispatchEvent(new Event('input'));
}
};
btnGroup.appendChild(btn);
});
}
if ($('seed-input')) {
$('seed-input').oninput = e => {
const value = e.target.value.trim();
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', () => {
promptParamsTextarea.select();
try {
document.execCommand('copy');
showToast('参数已复制!');
} catch (err) {
showToast('复制失败!');
console.error('Copy failed:', err);
}
});
}
document.querySelectorAll('input, textarea, select').forEach(el => {
el.addEventListener('focus', () => {
el.style.borderColor = isDarkMode ? '#7289DA' : '#4f46e5'; // 暗色模式下的 Discord 焦点颜色,亮色模式下的应用紫色
});
el.addEventListener('blur', () => {
el.style.borderColor = isDarkMode ? '#202225' : '#d1d5db';
});
});
document.querySelectorAll('button').forEach(btn => {
const originalBg = btn.style.backgroundColor;
const originalTransform = btn.style.transform;
const originalShadow = btn.style.boxShadow;
btn.addEventListener('mouseenter', () => {
if (!btn.classList.contains('active') && !btn.id.startsWith('theme-toggle')) {
btn.style.transform = 'translateY(-1px)';
btn.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
if (isDarkMode) btn.style.backgroundColor = '#4F545C'; // 暗色模式下悬停时略微变亮
// else btn.style.filter = 'brightness(0.95)'; // 如果需要,为亮色模式保留
}
});
btn.addEventListener('mouseleave', () => {
if (!btn.classList.contains('active')) {
btn.style.transform = originalTransform || 'translateY(0)';
btn.style.boxShadow = originalShadow || 'none';
if (isDarkMode) btn.style.backgroundColor = '#40444B'; // 返回正常的暗色按钮
// else btn.style.filter = 'none';
}
});
});
}
function addPreviewImage(containerId, url, weight, paramType, weightPrefix) {
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.width = '60px';
img.style.height = '60px';
img.style.objectFit = 'cover';
img.style.borderRadius = '4px';
img.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
img.style.transition = 'all 0.2s ease';
img.onerror = () => { // 处理损坏的图片链接
imgContainer.innerHTML = `无效图片
`;
};
img.addEventListener('mouseenter', () => { img.style.transform = 'scale(1.05)'; img.style.boxShadow = '0 4px 8px rgba(0,0,0,0.15)'; });
img.addEventListener('mouseleave', () => { img.style.transform = 'scale(1)'; img.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)'; });
const weightBadge = document.createElement('div');
weightBadge.style.cssText = 'position: absolute; bottom: 0; left: 0; background: rgba(0,0,0,0.7); color: white; font-size: 10px; padding: 1px 3px; border-radius: 0 4px 0 0;';
const wP = weightPrefix ? weightPrefix.replace('--', '') : '';
weightBadge.textContent = weight && weight.trim() !== '' ? `${wP}:${weight}` : `${wP}:默认`;
const deleteBtn = document.createElement('button');
deleteBtn.style.cssText = 'position: absolute; top: -4px; right: -4px; background: rgba(239, 68, 68, 0.9); color: white; border: none; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; line-height: 16px; text-align:center; cursor:pointer; transition:all 0.2s ease; opacity: 0.8; display:flex; align-items:center; justify-content:center;';
deleteBtn.innerHTML = '×';
deleteBtn.onclick = function(e) {
e.stopPropagation();
imgContainer.style.animation = 'fadeOut 0.2s ease forwards';
setTimeout(() => {
const targetArray = params[paramType === 'directImages' ? 'directImages' : paramType];
const index = targetArray.findIndex(item => item.url === url);
if (index !== -1) {
targetArray.splice(index, 1);
if (container.contains(imgContainer)) container.removeChild(imgContainer);
updatePromptParams();
}
}, 200);
};
deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.opacity = '1'; deleteBtn.style.transform = 'scale(1.1)'; });
deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.opacity = '0.8'; deleteBtn.style.transform = 'scale(1)'; });
imgContainer.appendChild(img);
imgContainer.appendChild(weightBadge);
imgContainer.appendChild(deleteBtn);
container.appendChild(imgContainer);
}
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; background: #f3f4f6; border-radius: 4px; padding: 6px 10px; display: flex; align-items: center; font-family: monospace; font-size: 12px; color: #111827; animation: fadeIn 0.3s ease;';
if (document.getElementById('mj-control-panel').style.backgroundColor === 'rgb(43, 45, 49)') { // 检查是否为暗色模式
codeContainer.style.background = '#202225';
codeContainer.style.color = '#DCDDDE';
}
const codeText = document.createElement('span');
codeText.textContent = `sref:${code}`;
const weightBadge = document.createElement('div');
weightBadge.style.cssText = 'margin-left: 8px; background: rgba(79, 70, 229, 0.1); color: #4f46e5; font-size: 10px; padding: 2px 4px; border-radius: 3px;';
weightBadge.textContent = weight && weight.trim() !== '' ? `sw:${weight}` : 'sw:默认';
if (document.getElementById('mj-control-panel').style.backgroundColor === 'rgb(43, 45, 49)') { // 暗色模式
weightBadge.style.background = 'rgba(88, 101, 242, 0.3)';
weightBadge.style.color = '#B9BBBE';
}
const deleteBtn = document.createElement('button');
deleteBtn.style.cssText = 'margin-left: 10px; background: rgba(239, 68, 68, 0.1); color: #ef4444; border: none; border-radius: 4px; padding: 2px 5px; font-size: 10px; line-height:1; cursor:pointer; transition:all 0.2s ease;';
deleteBtn.innerHTML = '×';
if (document.getElementById('mj-control-panel').style.backgroundColor === 'rgb(43, 45, 49)') { // 暗色模式
deleteBtn.style.background = 'rgba(239, 68, 68, 0.3)';
deleteBtn.style.color = '#FAA';
}
deleteBtn.onclick = function(e) {
e.stopPropagation();
codeContainer.style.animation = 'fadeOut 0.2s ease forwards';
setTimeout(() => {
const index = params.sref.findIndex(item => item.url === code);
if (index !== -1) {
params.sref.splice(index, 1);
if (container.contains(codeContainer)) container.removeChild(codeContainer);
updatePromptParams();
}
}, 200);
};
deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.backgroundColor = 'rgba(239, 68, 68, 0.2)'; });
deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.backgroundColor = 'rgba(239, 68, 68, 0.1)'; });
if (document.getElementById('mj-control-panel').style.backgroundColor === 'rgb(43, 45, 49)') { // 暗色模式悬停
deleteBtn.addEventListener('mouseenter', () => { deleteBtn.style.backgroundColor = 'rgba(239, 68, 68, 0.4)'; });
deleteBtn.addEventListener('mouseleave', () => { deleteBtn.style.backgroundColor = 'rgba(239, 68, 68, 0.3)'; });
}
codeContainer.appendChild(codeText);
codeContainer.appendChild(weightBadge);
codeContainer.appendChild(deleteBtn);
container.appendChild(codeContainer);
}
function refreshPreviews() {
const refTypes = [
{ type: 'cref', previewId: 'cref-preview', weightPrefix: '--cw' },
{ type: 'sref', previewId: 'sref-preview', weightPrefix: '--sw' },
{ type: 'oref', previewId: 'oref-preview', weightPrefix: '--ow' },
{ type: 'directImages', previewId: 'direct-image-preview', weightPrefix: '--iw' }
];
refTypes.forEach(ref => {
const container = document.getElementById(ref.previewId);
if (!container) return;
container.innerHTML = ''; // 清除现有预览
const items = params[ref.type];
if (items && Array.isArray(items)) {
items.forEach(item => {
if (ref.type === 'sref' && (item.url === 'random' || /^\d+$/.test(item.url))) {
addPreviewSrefCode(ref.previewId, item.url, item.weight);
} else {
addPreviewImage(ref.previewId, item.url, item.weight, ref.type, ref.weightPrefix);
}
});
}
});
}
// 注入 FontAwesome 和一些用于动画的 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;}
`;
document.head.appendChild(styleSheet);
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'; // 使用一个通用的 CDN
document.head.appendChild(faLink);
}
function init() {
injectStyles(); // 注入样式
resetParams(); // 重置参数状态
createSettingButton(); // 创建主按钮
createControlPanel(); // 创建面板(初始隐藏)
setInitialActiveButtons(); // 设置按钮的默认激活状态
updatePromptParams(); // 计算初始提示字符串
}
// 脚本加载后立即初始化
init();
})();