// ==UserScript==
// @name Discord Midjourney 参数面板
// @namespace https://github.com/cwser
// @version 0.1.1
// @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, weight}
sref: [], // 格式: {url, weight}
oref: [], // 格式: {url, weight}
iref: [],
directImages: [], // 格式: {url, weight}
// 新增参数
tile: false,
seed: '',
quality: 1,
stop: 100,
visibility: ''
};
function createSettingButton() {
const button = document.createElement('button');
button.textContent = 'MJ设置';
button.style.position = 'fixed';
button.style.right = '20px';
button.style.bottom = '20px';
button.style.padding = '10px 20px';
button.style.backgroundColor = '#5865F2';
button.style.color = 'white';
button.style.border = 'none';
button.style.borderRadius = '8px';
button.style.cursor = 'pointer';
button.style.zIndex = '9999';
button.addEventListener('click', toggleControlPanel);
document.body.appendChild(button);
}
function toggleControlPanel() {
const panel = document.getElementById('mj-control-panel');
if (panel) {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
}
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: ''
};
}
function updatePromptParams() {
const { prompt, ar, stylize, weird, chaos, mode, draft, noPrompt, version, cref, sref, speed, oref, iref, directImages, tile, seed, quality, stop, visibility } = params;
// 处理其他参数
const otherParts = [
`--ar ${ar}`,
stylize !== 0 ? `--s ${stylize}` : '',
weird !== 0 ? `--w ${weird}` : '',
chaos !== 0 ? `--c ${chaos}` : '',
mode !== 'standard' ? `--${mode}` : '',
draft ? '--draft' : '',
noPrompt ? `--no ${noPrompt}` : '',
version.startsWith('niji') ? `--niji ${version.replace('niji', '')}` : `--v ${version.replace('v', '')}`,
`--${speed}`,
// 新增参数处理
tile ? '--tile' : '',
seed ? `--seed ${seed}` : '',
quality !== 1 ? `--q ${quality}` : '',
stop !== 100 ? `--stop ${stop}` : '',
visibility ? `--${visibility}` : ''
];
// 处理带权重的图像参考
const formatImageWithWeight = (url, weight, prefix) => {
return weight ? `${url} ${prefix} ${weight}` : url;
};
// 直接图像参考不使用参数标识,直接添加到提示词中
const directImageUrls = directImages.map(item => formatImageWithWeight(item.url, item.weight, '--iw')).join(' ');
const promptField = document.getElementById('prompt-params');
if (promptField) {
// 构建完整提示词:图像参考URL + 主提示词 + --cref/--sref/--oref + 其他参数
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();
}
}
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;
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;
`;
panel.innerHTML = `
模型设置
`;
document.body.appendChild(panel);
bindControlEvents();
}
function bindControlEvents() {
const $ = id => document.getElementById(id);
// 绑定提示词输入框事件
$('main-prompt').oninput = e => {
params.prompt = e.target.value;
updatePromptParams();
};
// 绑定已有控件事件
$('stylize').oninput = e => { params.stylize = +e.target.value; updatePromptParams(); };
$('weird').oninput = e => { params.weird = +e.target.value; updatePromptParams(); };
$('chaos').oninput = e => { params.chaos = +e.target.value; updatePromptParams(); };
$('mode-select').onchange = e => { params.mode = e.target.value; updatePromptParams(); };
$('version-select').onchange = e => { params.version = e.target.value; updatePromptParams(); };
$('draft-toggle').onchange = e => { params.draft = e.target.checked; updatePromptParams(); };
$('speed-select').onchange = e => { params.speed = e.target.value; updatePromptParams(); };
$('no-prompt').oninput = e => { params.noPrompt = e.target.value.trim(); updatePromptParams(); };
$('copy-btn').onclick = () => {
const textarea = $('prompt-params');
textarea.select();
document.execCommand('copy');
alert('参数已复制!');
};
// 重置所有控件状态
$('clear-btn').onclick = () => {
resetParams();
// 重置所有控件为默认值
$('main-prompt').value = '';
$('stylize').value = 100;
$('weird').value = 0;
$('chaos').value = 0;
$('mode-select').value = 'standard';
$('version-select').value = 'v7';
$('draft-toggle').checked = false;
$('speed-select').value = 'relax';
$('no-prompt').value = '';
$('ratio-slider').value = 5;
$('oref-url').value = '';
$('direct-image-url').value = '';
$('cref-weight').value = '';
$('sref-weight').value = '';
$('oref-weight').value = '';
$('direct-image-weight').value = '';
// 重置新增参数
$('tile-toggle').checked = false;
$('seed-input').value = '';
$('quality-slider').value = 1;
$('quality-value').textContent = '1';
$('stop-slider').value = 100;
$('stop-value').textContent = '100';
// 重置可见性按钮状态
const visibilityBtns = document.querySelectorAll('.visibility-btn');
visibilityBtns.forEach(btn => {
btn.style.backgroundColor = 'white';
btn.style.color = '#111827';
});
params.visibility = '';
// 触发事件以更新UI
$('main-prompt').dispatchEvent(new Event('input'));
$('stylize').dispatchEvent(new Event('input'));
$('weird').dispatchEvent(new Event('input'));
$('chaos').dispatchEvent(new Event('input'));
$('mode-select').dispatchEvent(new Event('change'));
$('version-select').dispatchEvent(new Event('change'));
$('draft-toggle').dispatchEvent(new Event('change'));
$('speed-select').dispatchEvent(new Event('change'));
$('no-prompt').dispatchEvent(new Event('input'));
$('ratio-slider').dispatchEvent(new Event('input'));
$('tile-toggle').dispatchEvent(new Event('change'));
$('seed-input').dispatchEvent(new Event('input'));
$('quality-slider').dispatchEvent(new Event('input'));
$('stop-slider').dispatchEvent(new Event('input'));
// 清空输入框
$('cref-url').value = '';
$('sref-url').value = '';
// 刷新预览
refreshPreviews();
// 显示重置成功提示
alert('所有参数已重置为默认值');
};
// 绑定角色参考事件
$('cref-add').onclick = () => {
const url = $('cref-url').value.trim();
const weight = $('cref-weight').value.trim();
if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
if (!params.cref.some(item => item.url === url)) {
params.cref.push({url, weight});
addPreviewImage('cref-preview', url, weight, 'cref', '--cw');
$('cref-url').value = '';
$('cref-weight').value = '';
updatePromptParams();
} else {
alert('该URL已添加');
}
} else {
alert('请输入有效图片URL');
}
};
// 绑定风格参考事件
$('sref-add').onclick = () => {
const url = $('sref-url').value.trim();
const weight = $('sref-weight').value.trim();
if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
if (!params.sref.some(item => item.url === url)) {
params.sref.push({url, weight});
addPreviewImage('sref-preview', url, weight, 'sref', '--sw');
$('sref-url').value = '';
$('sref-weight').value = '';
updatePromptParams();
} else {
alert('该URL已添加');
}
} else {
alert('请输入有效图片URL');
}
};
// 绑定全方位参考事件
$('oref-add').onclick = () => {
const url = $('oref-url').value.trim();
const weight = $('oref-weight').value.trim();
if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
if (!params.oref.some(item => item.url === url)) {
params.oref.push({url, weight});
addPreviewImage('oref-preview', url, weight, 'oref', '--ow');
$('oref-url').value = '';
$('oref-weight').value = '';
updatePromptParams();
} else {
alert('该URL已添加');
}
} else {
alert('请输入有效图片URL');
}
};
// 绑定直接图像参考事件
$('direct-image-add').onclick = () => {
const url = $('direct-image-url').value.trim();
const weight = $('direct-image-weight').value.trim();
if (url && /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(url)) {
if (!params.directImages.some(item => item.url === url)) {
params.directImages.push({url, weight});
addPreviewImage('direct-image-preview', url, weight, 'directImages', '--iw');
$('direct-image-url').value = '';
$('direct-image-weight').value = '';
updatePromptParams();
} else {
alert('该URL已添加');
}
} else {
alert('请输入有效图片URL');
}
};
// 绑定比例滑块事件
const sizeMap = ['1:2', '19:6', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '6:19', '2:1'];
const ratioPresets = {
'1:2': { width: 50, height: 100 },
'19:6': { 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 },
'6:19': { width: 100, height: 56.25 },
'2:1': { width: 100, height: 50 }
};
$('ratio-slider').oninput = e => {
const ratio = sizeMap[+e.target.value] || '1:1';
params.ar = ratio;
const box = document.getElementById('ratio-box');
const preset = ratioPresets[ratio] || { width: 100, height: 100 };
box.style.width = `${preset.width}px`;
box.style.height = `${preset.height}px`;
box.textContent = ratio;
updatePromptParams();
};
// 设置比例预设按钮
const btnGroup = document.getElementById('size-buttons');
const presetMap = {
'纵向': '1:2',
'正方形': '1:1',
'横向': '2:1'
};
['纵向','正方形','横向'].forEach((label, i) => {
const btn = document.createElement('button');
btn.textContent = label;
btn.style.cssText = 'padding:4px 12px; border-radius:6px; border:1px solid #d1d5db; background:white; cursor:pointer;';
// 修复:正确映射预设比例
btn.onclick = () => {
const ratio = presetMap[label];
const sliderIndex = sizeMap.indexOf(ratio);
if (sliderIndex !== -1) {
$('ratio-slider').value = sliderIndex;
$('ratio-slider').dispatchEvent(new Event('input'));
}
};
btnGroup.appendChild(btn);
});
// 绑定新增参数事件
$('tile-toggle').onchange = e => {
params.tile = e.target.checked;
updatePromptParams();
};
$('seed-input').oninput = e => {
const value = e.target.value.trim();
if (/^\d*$/.test(value) && (value === '' || (parseInt(value) >= 0 && parseInt(value) <= 4294967295))) {
params.seed = value;
updatePromptParams();
} else {
e.target.value = params.seed; // 恢复之前的值
}
};
$('quality-slider').oninput = e => {
const value = parseFloat(e.target.value);
params.quality = value;
$('quality-value').textContent = value;
updatePromptParams();
};
$('stop-slider').oninput = e => {
const value = parseInt(e.target.value);
params.stop = value;
$('stop-value').textContent = value;
updatePromptParams();
};
// 可见性按钮事件
const visibilityBtns = document.querySelectorAll('.visibility-btn');
visibilityBtns.forEach(btn => {
btn.onclick = () => {
// 重置所有按钮样式
visibilityBtns.forEach(b => {
b.style.backgroundColor = 'white';
b.style.color = '#111827';
});
// 设置当前按钮样式
btn.style.backgroundColor = '#4f46e5';
btn.style.color = 'white';
// 设置可见性参数
if (btn.id === 'stealth-btn') {
params.visibility = 'stealth';
} else if (btn.id === 'public-btn') {
params.visibility = 'public';
} else {
params.visibility = '';
}
updatePromptParams();
};
});
}
// 添加预览图片并绑定删除功能
function addPreviewImage(containerId, url, weight, paramType, weightPrefix) {
const container = document.getElementById(containerId);
const imgContainer = document.createElement('div');
imgContainer.style.cssText = 'position: relative; margin: 4px;';
const img = document.createElement('img');
img.src = url;
img.style.width = '60px';
img.style.height = '60px';
img.style.objectFit = 'cover';
img.style.borderRadius = '4px';
// 添加权重显示
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 4px;';
weightBadge.textContent = weight ? `${weightPrefix.replace('--', '')}:${weight}` : `${weightPrefix.replace('--', '')}:默认`;
const deleteBtn = document.createElement('button');
deleteBtn.style.cssText = 'position: absolute; top: -4px; right: -4px; background: rgba(0,0,0,0.7); color: white; border: none; border-radius: 50%; width: 16px; height: 16px; font-size: 10px; line-height: 1; cursor: pointer;';
deleteBtn.textContent = '×';
deleteBtn.onclick = function(e) {
e.stopPropagation();
const index = paramType === 'cref'
? params.cref.findIndex(item => item.url === url)
: paramType === 'sref'
? params.sref.findIndex(item => item.url === url)
: paramType === 'oref'
? params.oref.findIndex(item => item.url === url)
: params.directImages.findIndex(item => item.url === url);
if (index !== -1) {
if (paramType === 'cref') {
params.cref.splice(index, 1);
} else if (paramType === 'sref') {
params.sref.splice(index, 1);
} else if (paramType === 'oref') {
params.oref.splice(index, 1);
} else { // directImages
params.directImages.splice(index, 1);
}
container.removeChild(imgContainer);
updatePromptParams();
}
};
imgContainer.appendChild(img);
imgContainer.appendChild(weightBadge);
imgContainer.appendChild(deleteBtn);
container.appendChild(imgContainer);
}
// 刷新预览区域
function refreshPreviews() {
['cref', 'sref', 'oref', 'directImages'].forEach(type => {
const container = document.getElementById(`${type === 'directImages' ? 'direct-image' : type}-preview`);
container.innerHTML = '';
const weightPrefix = {
'cref': '--cw',
'sref': '--sw',
'oref': '--ow',
'directImages': '--iw'
}[type];
params[type].forEach(item => {
addPreviewImage(`${type === 'directImages' ? 'direct-image' : type}-preview`, item.url, item.weight, type, weightPrefix);
});
});
}
function init() {
resetParams();
createSettingButton();
createControlPanel();
// 手动触发比例滑块的input事件以更新预览
const ratioSlider = document.getElementById('ratio-slider');
if (ratioSlider) ratioSlider.dispatchEvent(new Event('input'));
updatePromptParams();
}
window.addEventListener('load', init);
})();