// ==UserScript== // @name Discord Midjourney 参数可视化 // @namespace https://github.com/cwser // @version 1.0.3 // @description 在 Discord Midjourney 频道添加一个参数面板... // @author cwser // @match https://discord.com/* // @grant unsafeWindow // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @license MIT // @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: string, weight: string, enabled: boolean } sref: [], // 每一项将是 { url: string, weight: string, enabled: boolean } (现在支持独立权重) oref: [], // 每一项将是 { url: string, weight: string, enabled: boolean } directImages: [], // 每一项将是 { url: string, weight: string, enabled: boolean } (weight is the part after ::) iw: 1, // Global image weight for directImages, default 1, range 0-3 sw: 100, // Global style weight for sref, default 100, range 0-100 cw: 100, // Global character weight for cref, default 100, range 0-100 ow: 100, // Global overall weight for oref, default 100, range 0-1000 tile: false, seed: '', quality: 1, stop: 100, visibility: '', personalParams: '', r: 1, includeImagine: false // 用于控制是否添加 /imagine prompt: 前缀 }; // 主题相关变量 let currentThemeMode = 'discord'; // 选项: 'light', 'dark', 'discord', 'system' const themeStorageKey = 'mjPanelThemePreference_v3'; const themeModes = ['light', 'dark', 'discord', 'system']; const themeTextMap = { 'light': '浅色模式', 'dark': '深色模式', 'discord': '跟随Discord', 'system': '跟随系统' }; const sunIconSVG = ``; const moonIconSVG = ``; const discordIconSVG = ``; const systemIconSVG = ``; const uploadIconSVG = ``; const editIconSVG = ``; const themeIcons = { 'light': sunIconSVG, 'dark': moonIconSVG, 'discord': discordIconSVG, 'system': systemIconSVG }; let systemThemeMediaQuery = null; let discordThemeObserver = null; function showToast(message) { const toast = document.createElement('div'); toast.textContent = message; toast.className = 'mj-toast'; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('show'); }, 10); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { if (document.body.contains(toast)) document.body.removeChild(toast); }, 300); }, 2500); } function createSettingButton() { const button = document.createElement('button'); button.textContent = 'MJ参数'; button.id = 'mj-floating-settings-button'; button.addEventListener('click', toggleControlPanel); document.body.appendChild(button); } function toggleControlPanel() { const panel = document.getElementById('mj-control-panel'); if (panel) { panel.classList.toggle('visible'); if (!panel.classList.contains('visible')) { const themeMenu = document.getElementById('theme-options-menu'); if (themeMenu) themeMenu.style.display = 'none'; } } } function resetParams() { params = { prompt: '', ar: '1:1', stylize: 100, weird: 0, chaos: 0, mode: 'standard', version: 'v7', speed: 'relax', draft: false, noPrompt: '', cref: [], sref: [], oref: [], directImages: [], iw: 1, sw: 100, cw: 100, ow: 100, tile: false, seed: '', quality: 1, stop: 100, visibility: '', personalParams: '', r: 1, includeImagine: false }; // Ensure arrays are truly new and empty, not just references params.directImages = []; params.cref = []; params.sref = []; params.oref = []; } function updatePromptParams() { const { prompt, ar, stylize, weird, chaos, mode, draft, noPrompt, version, speed, tile, seed, quality, stop, visibility, personalParams, includeImagine, iw, sw, cw, ow } = params; const { cref, sref, oref, directImages } = params; const otherParts = [ ar ? `--ar ${ar}` : '', `--s ${stylize}`, weird !== 0 ? `--w ${weird}` : '', chaos !== 0 ? `--c ${chaos}` : '', mode !== 'standard' ? `--${mode}` : '', draft ? '--draft' : '', noPrompt ? `--no ${noPrompt}` : '', version.startsWith('niji') ? `--niji ${version.replace('niji', '')}` : `--v ${version.replace('v', '')}`, speed ? `--${speed}` : '', tile ? '--tile' : '', seed ? `--seed ${seed}` : '', quality !== 1 ? `--q ${quality}` : '', stop !== 100 ? `--stop ${stop}` : '', visibility ? `--${visibility}` : '', personalParams ? `--p ${personalParams}` : '', params.r > 1 ? `--r ${params.r}` : '' ].filter(Boolean); // Modified: Ensures space after :: if weightStr is present const formatImageWithWeight = (url, weightValue, prefix, isDirectImage = false) => { const weightStr = (typeof weightValue === 'string' || typeof weightValue === 'number') ? String(weightValue).trim() : ''; // The isDirectImage flag became redundant as both branches were identical. // Kept structure for minimal diff, but logic is same. if (isDirectImage) { return `${url}${weightStr !== '' ? ` :: ${weightStr}` : ''}`; } return `${url}${weightStr !== '' ? ` :: ${weightStr}` : ''}`; }; const directImageUrlsArr = directImages .filter(item => item.enabled) .map(item => formatImageWithWeight(item.url, item.weight, '', true)); let directImageSection = directImageUrlsArr.join(' '); const enabledCrefs = cref.filter(item => item.enabled); let crefSection = ''; if (enabledCrefs.length > 0) { const crefUrlsWithWeights = enabledCrefs.map(item => formatImageWithWeight(item.url, item.weight, '')); crefSection = `--cref ${crefUrlsWithWeights.join(' ')}`; if (cw !== 100) crefSection += ` --cw ${cw}`; } const enabledSrefs = sref.filter(item => item.enabled); let srefSection = ''; if (enabledSrefs.length > 0) { const srefUrlsWithWeights = enabledSrefs.map(item => formatImageWithWeight(item.url, item.weight, '')); srefSection = `--sref ${srefUrlsWithWeights.join(' ')}`; if (params.sw !== 100) srefSection += ` --sw ${params.sw}`; } const orefUrls = oref.filter(item => item.enabled).map(item => { return `--oref ${formatImageWithWeight(item.url, item.weight, '')}`; }); let orefSectionGlobal = ''; if (oref.filter(item => item.enabled).length > 0 && ow !== 100) { orefSectionGlobal = `--ow ${ow}`; } const promptField = document.getElementById('prompt-params'); if (promptField) { const mainPromptPart = prompt.trim(); const iwPart = (params.iw !== 1 && typeof params.iw !== 'undefined' && directImageUrlsArr.length > 0) ? `--iw ${params.iw}` : ''; const allParts = [ directImageSection, mainPromptPart, iwPart, crefSection, srefSection, ...orefUrls, orefSectionGlobal, ...otherParts ].filter(Boolean); let finalPromptString = allParts.join(' ').trim().replace(/\s+/g, ' '); if (includeImagine && finalPromptString) { finalPromptString = `/imagine prompt: ${finalPromptString}`; } promptField.value = finalPromptString; } } function getEffectiveDarkModeState() { switch (currentThemeMode) { case 'light': return false; case 'dark': return true; case 'discord': return document.documentElement.classList.contains('theme-dark'); case 'system': return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; default: return document.documentElement.classList.contains('theme-dark'); } } function applyCurrentTheme() { const panel = document.getElementById('mj-control-panel'); if (!panel) return; const effectiveDarkMode = getEffectiveDarkModeState(); panel.classList.toggle('dark-mode', effectiveDarkMode); localStorage.setItem(themeStorageKey, currentThemeMode); const themeTriggerIcon = document.getElementById('theme-trigger-icon'); const themeTriggerText = document.getElementById('theme-trigger-text'); if (themeTriggerIcon) { let iconToShow = themeIcons[currentThemeMode] || sunIconSVG; if (currentThemeMode === 'discord' || currentThemeMode === 'system') { iconToShow = effectiveDarkMode ? moonIconSVG : sunIconSVG; } themeTriggerIcon.innerHTML = iconToShow; } if (themeTriggerText) themeTriggerText.textContent = themeTextMap[currentThemeMode] || '未知主题'; document.querySelectorAll('#theme-options-menu button').forEach(opt => { opt.classList.toggle('active', opt.dataset.theme === currentThemeMode); }); setupDynamicThemeListeners(); } function handleSystemThemeChange() { if (currentThemeMode === 'system') applyCurrentTheme(); } function handleDiscordThemeChange(mutationsList) { if (currentThemeMode === 'discord') { for (const mutation of mutationsList) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { applyCurrentTheme(); break; } } } } function setupDynamicThemeListeners() { if (systemThemeMediaQuery) { systemThemeMediaQuery.removeEventListener ? systemThemeMediaQuery.removeEventListener('change', handleSystemThemeChange) : systemThemeMediaQuery.removeListener(handleSystemThemeChange); systemThemeMediaQuery = null; } if (discordThemeObserver) { discordThemeObserver.disconnect(); discordThemeObserver = null; } if (currentThemeMode === 'system' && window.matchMedia) { systemThemeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); systemThemeMediaQuery.addEventListener ? systemThemeMediaQuery.addEventListener('change', handleSystemThemeChange) : systemThemeMediaQuery.addListener(handleSystemThemeChange); } else if (currentThemeMode === 'discord' && typeof MutationObserver !== "undefined") { discordThemeObserver = new MutationObserver(handleDiscordThemeChange); discordThemeObserver.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] }); } } function createControlPanel() { const panel = document.createElement('div'); panel.id = 'mj-control-panel'; panel.innerHTML = `

Midjourney 参数设置

1:1
100
0
0

图片提示 (--iw)

1

角色参考 (--cref)

100

风格参考 (--sref)

100

全方位参考 (--oref)

100
1
100
1
`; document.body.appendChild(panel); const savedTheme = localStorage.getItem(themeStorageKey); currentThemeMode = (savedTheme && themeModes.includes(savedTheme)) ? savedTheme : 'discord'; bindControlEvents(); applyCurrentTheme(); document.addEventListener('click', function(event) { const themeMenu = document.getElementById('theme-options-menu'); const themeTrigger = document.getElementById('theme-dropdown-trigger'); if (themeMenu && themeTrigger && themeMenu.style.display === 'block' && !themeMenu.contains(event.target) && !themeTrigger.contains(event.target)) { themeMenu.style.display = 'none'; } }); } function setInitialActiveButtons() { const $ = id => document.getElementById(id); const buttonGroups = [ { className: 'speed-btn', param: 'speed' }, { className: 'mode-btn', param: 'mode' }, { className: 'visibility-btn', param: 'visibility' } ]; buttonGroups.forEach(group => { document.querySelectorAll(`.${group.className}`).forEach(btn => { btn.classList.toggle('active', btn.dataset.value === params[group.param]); }); }); const ratioSlider = $('ratio-slider'); if (ratioSlider) { const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1']; const ratioIndex = sizeMap.indexOf(params.ar); ratioSlider.value = ratioIndex !== -1 ? ratioIndex : 5; ratioSlider.dispatchEvent(new Event('input')); } } function updateToggleVisuals(switchId, property) { const switchEl = document.getElementById(switchId); if (switchEl) switchEl.classList.toggle('active', !!params[property]); } function setupRefSection() { document.querySelectorAll('.ref-module').forEach(module => { const paramKey = module.dataset.type; const addBtn = module.querySelector('.ref-add-btn'); const urlInput = module.querySelector('.ref-url-input'); const weightInput = module.querySelector('.ref-weight-input'); const container = module.querySelector('.ref-container-large'); if (addBtn && urlInput) { addBtn.onclick = () => { const urlValue = urlInput.value.trim(); if (!urlValue) { showToast(`请输入${getRefTypeDisplayName(paramKey)}URL`); return; } const weightValue = weightInput ? weightInput.value.trim() : ''; addReferenceItem(paramKey, urlValue, weightValue); urlInput.value = ''; if (weightInput) weightInput.value = ''; }; } if (container) { let overlay = container.querySelector('.drop-overlay'); if (!overlay) { overlay = document.createElement('div'); overlay.className = 'drop-overlay'; overlay.innerHTML = `
${uploadIconSVG}
松开即可添加
`; overlay.style.display = 'none'; container.appendChild(overlay); } setupDropZoneEvents(container, paramKey); } }); } function getRefTypeDisplayName(paramKey) { const nameMap = { 'directImages': '图片提示', 'cref': '角色参考', 'sref': '风格参考', 'oref': '全方位参考' }; return nameMap[paramKey] || paramKey; } function setupDropZoneEvents(dropZone, paramKey) { dropZone.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('drag-over'); showDropMessage(dropZone, "松开即可添加"); }); dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); e.stopPropagation(); if (!dropZone.contains(e.relatedTarget)) { dropZone.classList.remove('drag-over'); hideDropMessage(dropZone); } }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('drag-over'); hideDropMessage(dropZone); const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain'); if (!url) { showToast('无法获取拖拽内容的URL'); return; } addReferenceItem(paramKey, url, ''); }); } function showDropMessage(container, messageText) { const overlay = container.querySelector('.drop-overlay'); if (overlay) { overlay.style.display = 'flex'; } } function hideDropMessage(container) { const overlay = container.querySelector('.drop-overlay'); if (overlay) { overlay.style.display = 'none'; } } function addReferenceItem(paramKey, urlValue, weightValue = '') { let itemUrl = urlValue; let itemWeight = weightValue.trim(); if (paramKey === 'directImages') { if (itemWeight !== '' && !/^\d*\.?\d*$/.test(itemWeight)) { showToast('图片独立权重必须是数字 (例如 0.5, 1, 2) 或留空'); return; } } const isImageUrl = /^https?:\/\/.+\.(jpg|jpeg|png|webp|gif|svg|bmp|tiff|ico)(\?.*)?$/i.test(itemUrl); const isSrefCode = paramKey === 'sref' && (/^\d+$/.test(itemUrl) || itemUrl.toLowerCase() === 'random'); if (paramKey !== 'sref' && !isImageUrl) { showToast('请输入有效的图片URL'); return; } if (paramKey === 'sref' && !isImageUrl && !isSrefCode) { showToast("sref请输入有效图片URL, 'random'或数字代码"); return; } const targetArray = params[paramKey]; if (!Array.isArray(targetArray)) return; const checkUrl = (paramKey === 'sref' && isSrefCode) ? itemUrl.toLowerCase() : itemUrl; if (!targetArray.some(item => item.url === checkUrl && item.weight === itemWeight)) { const newItem = { url: checkUrl, weight: itemWeight, enabled: true }; targetArray.push(newItem); addPreviewItem(paramKey, newItem); updatePromptParams(); showToast(`已添加 ${getRefTypeDisplayName(paramKey)}`); } else { showToast(`该${getRefTypeDisplayName(paramKey)}已添加`); } } function showWeightEditDialog(item, paramKey, previewItem) { const dialog = document.createElement('div'); dialog.className = 'weight-edit-dialog'; const currentWeight = item.weight || ''; dialog.innerHTML = `

编辑权重

`; document.body.appendChild(dialog); const input = dialog.querySelector('.weight-edit-input'); const saveBtn = dialog.querySelector('.weight-edit-save'); const cancelBtn = dialog.querySelector('.weight-edit-cancel'); const overlay = dialog.querySelector('.weight-edit-overlay'); input.focus(); input.select(); const closeDialog = () => { if (document.body.contains(dialog)) document.body.removeChild(dialog); }; const saveWeight = () => { const newWeight = input.value.trim(); if (newWeight !== '' && !/^\d*\.?\d*$/.test(newWeight)) { showToast('权重必须是数字 (例如 0.5, 1, 2) 或留空'); return; } item.weight = newWeight; updatePreviewItemWeight(previewItem, item, paramKey); updatePromptParams(); closeDialog(); showToast('权重已更新'); }; saveBtn.onclick = saveWeight; cancelBtn.onclick = closeDialog; overlay.onclick = closeDialog; input.addEventListener('keypress', (e) => { if (e.key === 'Enter') saveWeight(); }); dialog.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDialog(); }); } function updatePreviewItemWeight(previewItem, item, paramKey) { const weightDisplay = previewItem.querySelector('.weight-large'); if (weightDisplay) { if (item.weight && item.weight.trim() !== '') { let prefix = "::"; weightDisplay.textContent = `${prefix}${item.weight}`; weightDisplay.style.display = 'block'; } else { weightDisplay.style.display = 'none'; } } } function updateAllUIElements() { const $ = id => document.getElementById(id); if ($('main-prompt')) $('main-prompt').value = params.prompt; if ($('no-prompt')) $('no-prompt').value = params.noPrompt; const sliderConfigs = [ { key: 'stylize', sliderId: 'stylize', displayId: 'stylize-value' }, { key: 'weird', sliderId: 'weird', displayId: 'weird-value' }, { key: 'chaos', sliderId: 'chaos', displayId: 'chaos-value' }, { key: 'iw', sliderId: 'iw-slider', displayId: 'iw-value' }, { key: 'sw', sliderId: 'sw-slider', displayId: 'sw-value' }, { key: 'cw', sliderId: 'cw-slider', displayId: 'cw-value' }, { key: 'ow', sliderId: 'ow-slider', displayId: 'ow-value' }, { key: 'stop', sliderId: 'stop-slider', displayId: 'stop-value' }, { key: 'r', sliderId: 'r-slider', displayId: 'r-value' } ]; sliderConfigs.forEach(conf => { const slider = $(conf.sliderId); const display = $(conf.displayId); if (slider) { const sliderMax = parseFloat(slider.max); slider.value = Math.min(params[conf.key] !== undefined ? params[conf.key] : parseFloat(slider.defaultValue) , sliderMax); // Added default for safety } if (display) display.textContent = slider ? slider.value : (params[conf.key] !== undefined ? params[conf.key] : ''); }); const qualityMap = [0.25, 0.5, 1, 2, 4]; const qualitySlider = $('quality-slider'); const qualityValue = $('quality-value'); if (qualitySlider && qualityValue) { const currentQuality = parseFloat(params.quality); const idx = qualityMap.indexOf(currentQuality); qualitySlider.value = idx !== -1 ? idx : qualityMap.indexOf(1); // Default to 1 if not found qualityValue.textContent = qualityMap[qualitySlider.value]; } if ($('version-select')) $('version-select').value = params.version; if ($('seed-input')) $('seed-input').value = params.seed; if ($('personal-params')) $('personal-params').value = params.personalParams; const ratioSlider = $('ratio-slider'); const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1']; if (ratioSlider) { const ratioIndex = sizeMap.indexOf(params.ar); ratioSlider.value = ratioIndex !== -1 ? ratioIndex : sizeMap.indexOf('1:1'); ratioSlider.dispatchEvent(new Event('input')); // Trigger update of preview } setInitialActiveButtons(); updateToggleVisuals('tile-toggle-switch', 'tile'); updateToggleVisuals('draft-toggle-switch', 'draft'); updateToggleVisuals('imagine-toggle-switch', 'includeImagine'); refreshPreviews(); updatePromptParams(); } // MODIFIED parseRefUrlsWithWeights function const parseRefUrlsWithWeights = (valueString) => { const items = []; // 1. 规范化处理:将 "url :: weight" 或 "url:: weight" 或 "url ::weight" 统一替换为 "url::weight" const normalizedValueString = valueString.replace(/\s*::\s*/g, '::'); // 2. 按一个或多个空格分割成 token 数组,每个 token 可能是 "url" 或 "url::weight" const tokens = normalizedValueString.split(/\s+/).filter(t => t.trim() !== ''); tokens.forEach(token => { const parts = token.split('::'); const urlOrCode = parts[0].trim(); let weight = ''; if (parts.length > 1 && parts[1].trim() !== '') { weight = parts[1].trim(); } if (urlOrCode) { const isSrefSpecific = (urlOrCode.toLowerCase() === 'random' || /^\d+$/.test(urlOrCode)); const looksLikeUrl = urlOrCode.match(/^https?:\/\//i) || urlOrCode.includes('.'); if (looksLikeUrl || isSrefSpecific) { items.push({ url: urlOrCode, weight: weight, enabled: true }); } } }); return items; }; function parseAndApplyMidjourneyCommand() { const $ = id => document.getElementById(id); const commandInput = $('prompt-params'); if (!commandInput) return; let command = commandInput.value.trim(); if (!command) { showToast("请输入Midjourney指令进行解析"); return; } resetParams(); // Reset params to default before parsing a new command let remainingCommand = command; // Remove /imagine prefix remainingCommand = remainingCommand.replace(/^\/(imagine|i)\s*(prompt:)?\s*/i, '').trim(); // Create a mutable copy of default params to populate const newParams = JSON.parse(JSON.stringify(params)); // Deep copy const extractParamWithValue = (regex, processor, isFlag = false) => { const match = remainingCommand.match(regex); if (match) { if (isFlag) { processor(true, newParams); } else { processor(match, newParams); } remainingCommand = remainingCommand.replace(regex, '').trim(); return true; } else if (isFlag) { processor(false, newParams); // Ensure flags are set to false if not present } return false; }; // --oref parsing (handles multiple --oref flags, each with one URL::WEIGHT) const orefItems = []; remainingCommand = remainingCommand.replace(/--oref\s+([^\s]+(?:\s*::\s*\S+)?)/gi, (match, content) => { const parts = content.split('::'); orefItems.push({ url: parts[0].trim(), weight: (parts[1] || '').trim(), enabled: true }); return ''; }).trim(); if (orefItems.length > 0) newParams.oref = orefItems; // Parameter extraction (order can matter for overlapping patterns) extractParamWithValue(/--iw\s+(\d*\.?\d+)/i, (m, p) => p.iw = parseFloat(m[1])); extractParamWithValue(/--sw\s+(\d+)/i, (m, p) => p.sw = Math.min(parseInt(m[1], 10), 1000)); // sref --sw can go up to 1000 extractParamWithValue(/--cw\s+(\d+)/i, (m, p) => p.cw = parseInt(m[1], 10)); extractParamWithValue(/--ow\s+(\d+)/i, (m, p) => p.ow = parseInt(m[1], 10)); extractParamWithValue(/--ar\s+([\d:]+)/i, (m, p) => p.ar = m[1]); extractParamWithValue(/--(v|version)\s+([a-zA-Z0-9.]+)/i, (m, p) => p.version = 'v' + m[2].replace(/^v/i, '')); extractParamWithValue(/--niji\s+([a-zA-Z0-9.]+)/i, (m, p) => p.version = 'niji' + m[1].replace(/^niji/i, '')); extractParamWithValue(/--s\s+(\d+)/i, (m, p) => p.stylize = parseInt(m[1], 10)); extractParamWithValue(/--w\s+(\d+)/i, (m, p) => p.weird = parseInt(m[1], 10)); extractParamWithValue(/--c\s+(\d+)/i, (m, p) => p.chaos = parseInt(m[1], 10)); extractParamWithValue(/--q\s+(\d*\.?\d+)/i, (m, p) => { const val = parseFloat(m[1]); const qualityMapVals = [0.25, 0.5, 1, 2, 4]; // v5/v6 qualities if (qualityMapVals.includes(val)) p.quality = val; }); extractParamWithValue(/--seed\s+(\d+)/i, (m, p) => p.seed = m[1]); extractParamWithValue(/--stop\s+(\d+)/i, (m, p) => p.stop = parseInt(m[1], 10)); extractParamWithValue(/--p\s+((?:[^\s"-][^\s-]*|[^\s-]*[^\s"-])[^\s-]*(?:\s+(?:[^\s"-][^\s-]*|[^\s-]*[^\s"-])[^\s-]*)*)/i, (m, p) => p.personalParams = m[1].trim()); extractParamWithValue(/--r\s+(\d+)/i, (m, p) => p.r = parseInt(m[1], 10)); // --no parameter (needs to be greedy but not over param flags) const noMatch = remainingCommand.match(/--no\s+((?:(?!--(?:ar|v|s|w|c|q|seed|stop|tile|draft|iw|sw|cw|ow|cref|sref|oref|p|r|niji|fast|turbo|relax|raw|public|stealth)\b)[\s\S])+)/i); if (noMatch) { newParams.noPrompt = noMatch[1].trim(); remainingCommand = remainingCommand.replace(noMatch[0], '').trim(); } // Flags extractParamWithValue(/--tile\b/i, (val, p) => p.tile = val, true); extractParamWithValue(/--draft\b/i, (val, p) => p.draft = val, true); // Mode & Speed & Visibility (mutually exclusive for their groups) if (extractParamWithValue(/--raw\b/i, (val, p) => { if(val) p.mode = 'raw'; }, true)) {} else { newParams.mode = 'standard'; } // Default if not raw if (extractParamWithValue(/--fast\b/i, (val, p) => { if(val) p.speed = 'fast';}, true)) {} else if (extractParamWithValue(/--turbo\b/i, (val, p) => {if(val) p.speed = 'turbo';}, true)) {} else if (extractParamWithValue(/--relax\b/i, (val, p) => {if(val) p.speed = 'relax';}, true)) {} else { newParams.speed = 'relax'; } // Default speed if (extractParamWithValue(/--public\b/i, (val, p) => {if(val) p.visibility = 'public';}, true)) {} else if (extractParamWithValue(/--stealth\b/i, (val, p) => {if(val) p.visibility = 'stealth';}, true)) {} else { newParams.visibility = '';} // Default visibility // --cref and --sref using the MODIFIED parseRefUrlsWithWeights const crefMatch = remainingCommand.match(/--cref\s+((?:(?!--(?:cw|sw|ow|ar|v|s|w|c|q|seed|stop|tile|draft|iw|p|r|niji|fast|turbo|relax|raw|public|stealth|no|sref|oref)\b)[\s\S])+)/i); if (crefMatch) { newParams.cref = parseRefUrlsWithWeights(crefMatch[1].trim()); remainingCommand = remainingCommand.replace(crefMatch[0], '').trim(); } const srefMatch = remainingCommand.match(/--sref\s+((?:(?!--(?:cw|sw|ow|ar|v|s|w|c|q|seed|stop|tile|draft|iw|p|r|niji|fast|turbo|relax|raw|public|stealth|no|cref|oref)\b)[\s\S])+)/i); if (srefMatch) { newParams.sref = parseRefUrlsWithWeights(srefMatch[1].trim()); remainingCommand = remainingCommand.replace(srefMatch[0], '').trim(); } // MODIFIED Direct image parsing logic remainingCommand = remainingCommand.replace(/\s+/g, ' ').trim(); const promptParts = remainingCommand.split(' '); newParams.directImages = []; // Ensure it's clean for this parse run let promptStartIndex = 0; let currentGlobalIndex = 0; while(currentGlobalIndex < promptParts.length) { const part = promptParts[currentGlobalIndex]; let consumedPartsCount = 0; let imageUrl = ''; let imageWeight = ''; let successfullyParsedAsImage = false; if (part.toLowerCase().startsWith('http://') || part.toLowerCase().startsWith('https://')) { if (part.includes('::')) { const splitByDoubleColon = part.split('::'); if (splitByDoubleColon.length === 2) { const potentialUrl = splitByDoubleColon[0]; const potentialWeight = splitByDoubleColon[1]; if (potentialUrl.trim() !== '' && (/^\d*\.?\d*$/.test(potentialWeight) || potentialWeight === '')) { imageUrl = potentialUrl; imageWeight = potentialWeight.trim(); consumedPartsCount = 1; successfullyParsedAsImage = true; } } if (!successfullyParsedAsImage && (splitByDoubleColon[0].toLowerCase().startsWith('http://') || splitByDoubleColon[0].toLowerCase().startsWith('https://'))) { imageUrl = part; imageWeight = ''; consumedPartsCount = 1; successfullyParsedAsImage = true; } } else if (currentGlobalIndex + 2 < promptParts.length && promptParts[currentGlobalIndex + 1] === "::" && (/^\d*\.?\d*$/.test(promptParts[currentGlobalIndex + 2]) || promptParts[currentGlobalIndex + 2] === '')) { imageUrl = part; imageWeight = promptParts[currentGlobalIndex + 2].trim(); consumedPartsCount = 3; successfullyParsedAsImage = true; } else if (currentGlobalIndex + 1 < promptParts.length && promptParts[currentGlobalIndex + 1].startsWith("::")) { const potentialWeightPart = promptParts[currentGlobalIndex + 1].substring(2); if (/^\d*\.?\d*$/.test(potentialWeightPart) || potentialWeightPart === '') { imageUrl = part; imageWeight = potentialWeightPart.trim(); consumedPartsCount = 2; successfullyParsedAsImage = true; } } if (!successfullyParsedAsImage && (part.toLowerCase().startsWith('http://') || part.toLowerCase().startsWith('https://'))) { imageUrl = part; imageWeight = ''; consumedPartsCount = 1; successfullyParsedAsImage = true; } if (successfullyParsedAsImage && imageUrl) { newParams.directImages.push({ url: imageUrl, weight: imageWeight, enabled: true }); promptStartIndex += consumedPartsCount; currentGlobalIndex += consumedPartsCount; } else { break; } } else { break; } } newParams.prompt = promptParts.slice(promptStartIndex).join(' ').trim(); // Assign all parsed values to the global params object Object.assign(params, newParams); updateAllUIElements(); // Refresh UI with parsed values showToast("指令解析完成并已填充参数!"); } function bindControlEvents() { const $ = id => document.getElementById(id); document.querySelectorAll('.tab-link').forEach(button => { button.addEventListener('click', () => { document.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); button.classList.add('active'); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); $(button.dataset.tab).classList.add('active'); }); }); const themeTriggerButton = $('theme-dropdown-trigger'); const themeOptionsMenu = $('theme-options-menu'); if (themeTriggerButton && themeOptionsMenu) { themeTriggerButton.addEventListener('click', (e) => { e.stopPropagation(); themeOptionsMenu.style.display = themeOptionsMenu.style.display === 'none' ? 'block' : 'none'; }); document.querySelectorAll('.theme-option-button').forEach(button => { button.addEventListener('click', () => { currentThemeMode = button.dataset.theme; applyCurrentTheme(); themeOptionsMenu.style.display = 'none'; }); }); } if ($('main-prompt')) $('main-prompt').oninput = e => { params.prompt = e.target.value; updatePromptParams(); }; if ($('no-prompt')) $('no-prompt').oninput = e => { params.noPrompt = e.target.value.trim(); updatePromptParams(); }; const bindSlider = (id, property, displayId, isFloat = false, callback) => { const slider = $(id); if (!slider) return; // Initialize slider from params or its default value if param is undefined const initialValue = params[property] !== undefined ? params[property] : parseFloat(slider.defaultValue); const sliderMax = parseFloat(slider.max); slider.value = Math.min(initialValue, sliderMax); if (displayId && $(displayId)) $(displayId).textContent = slider.value; slider.oninput = e => { params[property] = isFloat ? parseFloat(e.target.value) : parseInt(e.target.value, 10); if (displayId && $(displayId)) $(displayId).textContent = e.target.value; if (callback) callback(params[property]); updatePromptParams(); }; }; bindSlider('stylize', 'stylize', 'stylize-value'); bindSlider('weird', 'weird', 'weird-value'); bindSlider('chaos', 'chaos', 'chaos-value'); bindSlider('stop-slider', 'stop', 'stop-value'); bindSlider('r-slider', 'r', 'r-value'); bindSlider('iw-slider', 'iw', 'iw-value', true); bindSlider('sw-slider', 'sw', 'sw-value'); bindSlider('cw-slider', 'cw', 'cw-value'); bindSlider('ow-slider', 'ow', 'ow-value'); const qualitySlider = $('quality-slider'); const qualityValueDisplay = $('quality-value'); const qualityMap = [0.25, 0.5, 1, 2, 4]; if(qualitySlider && qualityValueDisplay) { // Initial value setting for quality slider is handled in updateAllUIElements qualitySlider.oninput = e => { params.quality = qualityMap[parseInt(e.target.value, 10)]; qualityValueDisplay.textContent = params.quality; updatePromptParams(); }; } const bindRadio = (className, property) => { document.querySelectorAll(`.${className}`).forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll(`.${className}`).forEach(b => b.classList.remove('active')); btn.classList.add('active'); params[property] = btn.dataset.value; updatePromptParams(); }); }); }; bindRadio('speed-btn', 'speed'); bindRadio('mode-btn', 'mode'); bindRadio('visibility-btn', 'visibility'); const bindToggle = (id, property) => { const toggle = $(id); if (!toggle) return; // Initial visual state is handled in updateAllUIElements toggle.addEventListener('click', () => { params[property] = !params[property]; updateToggleVisuals(id, property); updatePromptParams(); }); }; bindToggle('tile-toggle-switch', 'tile'); bindToggle('draft-toggle-switch', 'draft'); bindToggle('imagine-toggle-switch', 'includeImagine'); if ($('version-select')) { $('version-select').onchange = e => { params.version = e.target.value; updatePromptParams(); }; } if ($('seed-input')) { $('seed-input').oninput = e => { const value = e.target.value.trim(); params.seed = (/^\d*$/.test(value) && (value === '' || (parseInt(value) >= 0 && parseInt(value) <= 4294967295))) ? value : params.seed; e.target.value = params.seed; // Ensure input reflects validated value updatePromptParams(); }; } if ($('personal-params')) { $('personal-params').oninput = e => { params.personalParams = e.target.value.trim(); updatePromptParams(); }; } const legacyCopy = (el) => { try { document.execCommand('copy'); showToast('参数已复制 (兼容模式)!'); } catch (err) { showToast('复制失败!'); } }; if ($('copy-btn')) $('copy-btn').onclick = () => { const textarea = $('prompt-params'); if (!textarea || !textarea.value) { showToast('没有参数可以拷贝'); return; } textarea.select(); textarea.setSelectionRange(0, 99999); // For mobile devices if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(textarea.value).then(() => showToast('参数已复制!')).catch(() => legacyCopy(textarea)); } else { legacyCopy(textarea); } }; const sizeMap = ['1:2', '9:16', '2:3', '3:4', '5:6', '1:1', '6:5', '4:3', '3:2', '16:9', '2:1']; const ratioPresets = { '1:2':{w:50,h:100}, '9:16':{w:56.25,h:100}, '2:3':{w:66.67,h:100}, '3:4':{w:75,h:100}, '5:6':{w:83.33,h:100}, '1:1':{w:100,h:100}, '6:5':{w:100,h:83.33}, '4:3':{w:100,h:75}, '3:2':{w:100,h:66.67}, '16:9':{w:100,h:56.25}, '2:1':{w:100,h:50} }; const ratioSlider = $('ratio-slider'); if (ratioSlider) { // Initial value for ratio slider is set in updateAllUIElements ratioSlider.oninput = e => { const ratio = sizeMap[+e.target.value] || '1:1'; params.ar = ratio; const box = $('ratio-box'); const bgBox = $('ratio-preview-bg'); const preset = ratioPresets[ratio]; if(box && preset && bgBox) { // Scale preview to fit 100x100 container while maintaining aspect ratio const containerSize = 100; let displayW = preset.w; let displayH = preset.h; if (displayW > containerSize || displayH > containerSize) { if (displayW/displayH > 1) { // Wider than tall displayH = containerSize * (displayH / displayW); displayW = containerSize; } else { // Taller than wide or square displayW = containerSize * (displayW / displayH); displayH = containerSize; } } box.style.width = `${displayW-4}px`; // Account for border box.style.height = `${displayH-4}px`; // Account for border box.textContent = ratio; } document.querySelectorAll('#size-buttons button').forEach(btn => btn.classList.toggle('active', btn.dataset.value === ratio)); updatePromptParams(); }; } const sizeButtonGroup = $('size-buttons'); if (sizeButtonGroup) { sizeButtonGroup.innerHTML = ''; // Clear previous buttons if any const presetMap = { '纵向': '2:3', '正方形': '1:1', '横向': '3:2' }; // Common presets Object.entries(presetMap).forEach(([label, ratio]) => { const btn = document.createElement('button'); btn.textContent = label; btn.dataset.value = ratio; btn.onclick = () => { if (ratioSlider) { const idx = sizeMap.indexOf(ratio); if (idx !== -1) { ratioSlider.value = idx; ratioSlider.dispatchEvent(new Event('input')); // Trigger slider's input event } } }; sizeButtonGroup.appendChild(btn); }); } if ($('clear-btn')) $('clear-btn').onclick = () => { resetParams(); updateAllUIElements(); // Refresh UI to default state showToast('所有参数已重置为默认值'); }; if ($('parse-btn')) $('parse-btn').onclick = parseAndApplyMidjourneyCommand; setupRefSection(); } function addPreviewItem(paramKey, item) { const container = document.getElementById(`${paramKey}-preview`); if (!container) return; const isSrefCode = paramKey === 'sref' && (item.url === 'random' || /^\d+$/.test(item.url)); const previewItem = document.createElement('div'); previewItem.className = `ref-item-large ${isSrefCode ? 'code-item' : 'image-item'}`; if (typeof item.enabled !== 'undefined' && !item.enabled) { previewItem.classList.add('disabled'); } else if (typeof item.enabled === 'undefined') { // Default to enabled if not specified item.enabled = true; } let contentHtml = isSrefCode ? `
${item.url}
` : `参考图`; let weightText = ''; if (item.weight && item.weight.trim() !== '') { let prefix = "::"; // For display consistency weightText = `${prefix}${item.weight}`; } const weightDisplay = `${weightText}`; previewItem.innerHTML = ` ${contentHtml} ${weightDisplay} `; container.appendChild(previewItem); const editBtn = previewItem.querySelector('.ref-edit-large'); const toggleBtn = previewItem.querySelector('.ref-toggle-large'); const deleteBtn = previewItem.querySelector('.ref-delete-large'); if (editBtn) editBtn.onclick = (e) => { e.stopPropagation(); if (previewItem.classList.contains('load-error')) return; showWeightEditDialog(item, paramKey, previewItem); }; if (toggleBtn) toggleBtn.onclick = (e) => { e.stopPropagation(); if (previewItem.classList.contains('load-error')) return; item.enabled = !item.enabled; toggleBtn.classList.toggle('active', item.enabled); previewItem.classList.toggle('disabled', !item.enabled); updatePromptParams(); }; if (deleteBtn) deleteBtn.onclick = (e) => { e.stopPropagation(); const targetArray = params[paramKey]; const index = targetArray.findIndex(i => i.url === item.url && i.weight === item.weight); // Find by URL and weight if (index !== -1) { targetArray.splice(index, 1); previewItem.remove(); updatePromptParams(); } }; } function refreshPreviews() { const refTypes = ['directImages', 'cref', 'sref', 'oref']; refTypes.forEach(paramKey => { const container = document.getElementById(`${paramKey}-preview`); if (container) { container.innerHTML = ''; // Clear existing previews const items = params[paramKey]; if (items && Array.isArray(items)) { items.forEach(item => { if (typeof item.enabled === 'undefined') item.enabled = true; // Ensure enabled state addPreviewItem(paramKey, item); }); } } }); } function injectStyles() { const styleSheet = document.createElement("style"); styleSheet.type = "text/css"; styleSheet.innerText = ` @keyframes fadeIn { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } #mj-floating-settings-button { position: fixed; right: 20px; bottom: 20px; padding: 10px 20px; background-color: #5865F2; color: white; border: none; border-radius: 8px; cursor: pointer; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: background-color 0.2s ease, transform 0.2s ease; font-family: sans-serif; font-weight: 500; } #mj-floating-settings-button:hover { background-color: #4752C4; transform: scale(1.05); } .mj-toast { position: fixed; top: 20px; right: 20px; padding: 12px 18px; border-radius: 6px; z-index: 10001; font-family: sans-serif; font-size: 14px; transform: translateY(-100%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease; background: #2B2D31; color: #DCDDDE; box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .mj-toast.show { transform: translateY(0); opacity: 1; } #mj-control-panel { display: flex; flex-direction: column; position: fixed; right: 20px; bottom: 80px; width: 880px; max-width: calc(100vw - 40px); height: 85vh; max-height: 800px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.15); z-index: 10000; overflow: hidden; font-family: sans-serif; transform: translateY(20px) scale(0.95); opacity: 0; pointer-events: none; transition: transform 0.25s ease, opacity 0.25s ease; background: white; color: #111827; border: 1px solid #E0E0E0; } #mj-control-panel.visible { transform: translateY(0) scale(1); opacity: 1; pointer-events: auto; } .panel-header { display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; border-bottom: 1px solid #E0E0E0; flex-shrink: 0; } .panel-title { margin:0; font-size:18px; font-weight:600; } .theme-trigger-btn { padding: 6px 12px; border-radius: 6px; border: 1px solid #D1D5DB; background-color: white; font-size: 13px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s ease; } .theme-trigger-btn:hover { border-color: #4f46e5; } .theme-options-menu { position: absolute; top: calc(100% + 5px); right: 0; background-color: white; border: 1px solid #D1D5DB; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); z-index: 10; min-width: 180px; padding: 4px; } .theme-option-button { display: flex; align-items: center; width: 100%; padding: 8px 12px; background: none; border: none; cursor: pointer; font-size: 13px; color: #1F2937; border-radius: 4px; transition: all 0.15s ease; gap: 8px; } .theme-option-button:hover { background-color: #f0f0f0; } .theme-option-button.active { background-color: #eef2ff; color: #4338ca; font-weight: 500; } .panel-main-content { display: flex; flex-direction: column; flex-grow: 1; overflow: hidden; } .panel-tabs { display: flex; padding: 12px 24px 0; border-bottom: 1px solid #E0E0E0; flex-shrink: 0; gap: 16px; } .tab-link { padding: 8px 4px; margin-bottom: -1px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-size: 15px; color: #6B7280; transition: all 0.2s ease; } .tab-link:hover { color: #374151; } .tab-link.active { color: #4f46e5; border-bottom-color: #4f46e5; font-weight: 500; } .tab-content { display: none; padding: 20px 24px; overflow-y: auto; flex-grow: 1; } .tab-content.active { display: block; animation: fadeIn 0.3s ease; } .form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px 24px; } .form-group { display: flex; flex-direction: column; gap: 8px; } .form-group.span-2 { grid-column: span 2; } .form-group label { font-weight: 500; font-size: 14px; color: #1F2937; } /* Unified Input Styles START */ .form-group input, .form-group textarea, .form-group select, .ref-url-input, .ref-weight-input, .weight-edit-input { background: white; color: #111827; border: 1px solid #D1D5DB; padding: 8px 12px; border-radius: 6px; font-size: 14px; transition: all 0.2s ease; box-sizing: border-box; } .form-group input:focus, .form-group textarea:focus, .form-group select:focus, .ref-url-input:focus, .ref-weight-input:focus, .weight-edit-input:focus { border-color: #4f46e5; box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.2); outline: none; } /* Unified Input Styles END */ .form-group textarea { resize: vertical; min-height: 80px; } .ref-grid-main { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; padding: 0; } .ref-module { background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; display: flex; flex-direction: column; gap: 12px; } .ref-module-header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 8px; border-bottom: 1px solid #e5e7eb; } .ref-module-header h4 { margin: 0; font-size: 16px; font-weight: 600; color: #1f2937; } .global-weight-control { display: flex; align-items: center; gap: 8px; } .global-weight-control label { font-size: 12px; color: #6b7280; white-space: nowrap; } .weight-slider-mini { display: flex; align-items: center; gap: 6px; } .weight-slider-mini input[type="range"] { width: 60px; height: 4px; margin:0; padding:0; } .weight-slider-mini span { font-size: 12px; color: #4B5563; min-width: 25px; text-align: center; font-weight: 500; } .ref-input-section { display: flex; gap: 8px; align-items: center; } .ref-url-input { flex: 1; } .ref-weight-input { width: 70px; text-align: center; } .ref-add-btn { padding: 8px 16px; background: #4f46e5; color: white; border: none; border-radius: 6px; font-size: 13px; cursor: pointer; transition: all 0.2s ease; white-space: nowrap; } .ref-add-btn:hover { background: #4338ca; } .ref-container-large { position: relative; min-height: 90px; padding: 12px; background: white; border-radius: 8px; border: 2px dashed #d1d5db; display: flex; flex-wrap: wrap; gap: 12px; align-content: flex-start; transition: all 0.2s ease; } .ref-container-large.drag-over { border-style: solid; border-color: #4f46e5; background-color: #eef2ff; } .ref-container-large:empty:before { content: '拖拽图片到此处或使用上方输入框添加'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #9ca3af; font-size: 14px; pointer-events: none; text-align: center; } .drop-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(79, 70, 229, 0.1); border-radius: inherit; align-items: center; justify-content: center; z-index: 10; } .drop-overlay-content { display: flex; flex-direction: column; align-items: center; gap: 8px; } .drop-icon { color: #4f46e5; } .drop-icon svg { display: block; } .drop-text { font-size: 14px; color: #4f46e5; font-weight: 500; } .ref-item-large { position: relative; width: 80px; height: 80px; border-radius: 8px; background: #f3f4f6; border: 2px solid #d1d5db; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; flex-shrink: 0; overflow: hidden; } .ref-item-large:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(0,0,0,0.15); border-color: #4f46e5; overflow: visible; } .ref-item-large.disabled { opacity: 0.5; filter: grayscale(80%); } .ref-item-large.load-error { border-color: #ef4444; } .ref-image-large { width: 100%; height: 100%; object-fit: cover; } .ref-code-large { font-size: 11px; font-family: monospace; color: #4f46e5; text-align: center; word-break: break-all; padding: 6px; font-weight: 600; } .ref-error-large { font-size: 12px; color: #ef4444; text-align: center; padding: 4px; width:100%; user-select: none; } .weight-large { position: absolute; bottom: 3px; left: 3px; background: rgba(0,0,0,0.75); color: white; font-size: 10px; padding: 1px 4px; border-radius: 3px; font-family: monospace; font-weight: 600; z-index: 1; } .ref-edit-large, .ref-toggle-large, .ref-delete-large { width: 20px; height: 20px; border: none; border-radius: 4px; font-size: 12px; line-height: 1; cursor: pointer; transition: opacity 0.15s ease, background-color 0.15s ease, color 0.15s ease; display: flex; align-items: center; justify-content: center; position: absolute; opacity: 0; background: rgba(255,255,255,0.8); box-shadow: 0 1px 2px rgba(0,0,0,0.15); z-index: 2; } .ref-item-large:hover .ref-edit-large, .ref-item-large:hover .ref-toggle-large, .ref-item-large:hover .ref-delete-large { opacity: 1; } .ref-edit-large { top: 3px; left: 3px; color: #4f46e5; } .ref-edit-large svg { width:12px; height:12px; } .ref-edit-large:hover { background: #4f46e5; color: white; } .ref-delete-large { top: 3px; right: 3px; color: #ef4444; font-size: 14px; } .ref-delete-large:hover { background: #ef4444; color: white; } .ref-toggle-large { bottom: 3px; right: 3px; color: #6b7280; font-size: 14px; } .ref-toggle-large.active { background: #10b981; color: white; } .ref-toggle-large:not(.active):hover { background: #e0e0e0; } .weight-edit-dialog { position: fixed; top:0;left:0;right:0;bottom:0; z-index:10002; display:flex;align-items:center;justify-content:center; } .weight-edit-overlay { position:absolute;top:0;left:0;right:0;bottom:0; background:rgba(0,0,0,0.5); } .weight-edit-content { position:relative; background:white; border-radius:12px; padding:24px; min-width:300px; box-shadow:0 8px 24px rgba(0,0,0,0.15); z-index:1; } .weight-edit-content h4 { margin:0 0 16px 0; font-size:16px; font-weight:600; color:#1f2937; } .weight-edit-input-group { margin-bottom:20px; } .weight-edit-input-group label { display:block; margin-bottom:8px; font-size:14px; font-weight:500; color:#1f2937; } .weight-edit-buttons { display:flex; gap:12px; justify-content:flex-end; } .weight-edit-cancel, .weight-edit-save { padding:8px 16px; border-radius:6px; font-size:14px; font-weight:500; cursor:pointer; transition:all 0.2s ease; border:1px solid transparent; } .weight-edit-cancel { background:#e5e7eb; color:#374151; border-color:#d1d5db; } .weight-edit-cancel:hover { background:#d1d5db; } .weight-edit-save { background:#4f46e5; color:white; } .weight-edit-save:hover { background:#4338ca; } .slider-control { display: flex; align-items: center; gap: 12px; } .slider-control input[type="range"] { flex-grow: 1; margin:0; padding:0; height: 16px; } .slider-control span { font-size: 14px; color: #4B5563; min-width: 35px; text-align: right; } input[type="range"] { -webkit-appearance: none; background: transparent; cursor: pointer; width: 100%; } input[type="range"]::-webkit-slider-runnable-track { background: #E5E7EB; height: 6px; border-radius: 3px; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; margin-top: -5px; background-color: #4f46e5; height: 16px; width: 16px; border-radius: 50%; border: 2px solid white; box-shadow: 0 1px 3px rgba(0,0,0,0.2); } .btn-group { display: flex; border-radius:6px; overflow:hidden; border:1px solid #d1d5db; } .btn-group button { flex:1; padding: 8px 10px; background:white; border:none; cursor:pointer; color: #374151; transition: all 0.2s; font-size: 13px; } .btn-group button:not(:last-child) { border-right: 1px solid #d1d5db; } .btn-group button.active { background: #4f46e5; color: white; } .toggle-group { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px; border-radius: 6px; background-color: #F9FAFB; } .toggle-group label { font-size: 14px; cursor: pointer; user-select: none; } .toggle-switch { position:relative; width:40px; height:20px; border-radius:10px; background:#e5e7eb; cursor:pointer; transition: background-color 0.2s ease; flex-shrink: 0; } .toggle-switch .toggle-dot { position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:white; box-shadow:0 1px 3px rgba(0,0,0,0.2); transition:all 0.2s ease; } .toggle-switch.active { background:#4f46e5; } .toggle-switch.active .toggle-dot { transform: translateX(20px); } #ar-section .ar-controls { display: flex; gap: 20px; align-items: center; } .ar-preview-container { position:relative; width:100px; height:100px; flex-shrink: 0; } #ratio-preview-bg { width:100px; height:100px; border:2px dashed #d1d5db; border-radius:12px; } #ratio-preview { position:absolute; top:0; left:0; width:100%; height:100%; display: flex; align-items: center; justify-content: center; } #ratio-box { background:#f3f4f6; border:2px solid #374151; border-radius:6px; display:flex; align-items:center; justify-content:center; font-size:12px; color:#374151; transition: all 0.2s ease; } .ar-slider-group { flex-grow: 1; display: flex; flex-direction: column; gap: 12px; } .ar-slider-group #size-buttons { width: 100%; } .panel-footer { padding: 16px 24px; border-top: 1px solid #E0E0E0; flex-shrink: 0; background-color: #f9fafb; } .panel-footer #prompt-params { width: 100%; height: 60px; resize: vertical; box-sizing: border-box; font-family: monospace; } .footer-actions { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; } .footer-actions .imagine-toggle { padding: 0; background: none; } .footer-buttons { display: flex; gap: 12px; } .action-button-primary, .action-button-secondary { padding: 8px 20px; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid transparent; } .action-button-primary { background: #4f46e5; color: white; } .action-button-primary:hover { background: #4338CA; } .action-button-secondary { background: #E5E7EB; color: #374151; border-color: #D1D5DB; } .action-button-secondary:hover { background: #D1D5DB; } /* Dark Mode Styles */ #mj-control-panel.dark-mode { background:#2B2D31; color:#DCDDDE; border-color:#202225; } .dark-mode .panel-header { border-bottom-color:#202225; } .dark-mode .theme-trigger-btn { border-color:#2D2F34; background-color:#2B2D31; color:#DCDDDE; } .dark-mode .theme-trigger-btn:hover { border-color:#7289DA; } .dark-mode .theme-options-menu { background-color:#2B2D31; border-color:#202225; } .dark-mode .theme-option-button { color:#DCDDDE; } .dark-mode .theme-option-button:hover { background-color:#393c43; } .dark-mode .theme-option-button.active { background-color:#404EED; color:white; } .dark-mode .panel-tabs { border-bottom-color:#202225; } .dark-mode .tab-link { color:#8e9297; } .dark-mode .tab-link:hover { color:#dcddde; } .dark-mode .tab-link.active { color:#7289DA; border-bottom-color:#7289DA; } .dark-mode .form-group label { color:#DCDDDE; } .dark-mode .form-group input, .dark-mode .form-group textarea, .dark-mode .form-group select, .dark-mode .ref-url-input, .dark-mode .ref-weight-input, .dark-mode .weight-edit-input, .dark-mode .panel-footer #prompt-params { background:#202225; color:#DCDDDE; border-color:#40444B; } .dark-mode .form-group input:focus, .dark-mode .form-group textarea:focus, .dark-mode .form-group select:focus, .dark-mode .ref-url-input:focus, .dark-mode .ref-weight-input:focus, .dark-mode .weight-edit-input:focus, .dark-mode .panel-footer #prompt-params:focus { border-color:#7289DA; box-shadow:0 0 0 2px rgba(114,137,218,0.2); } .dark-mode input[type="range"]::-webkit-slider-runnable-track { background:#40444B; } .dark-mode input[type="range"]::-webkit-slider-thumb { background-color:#7289DA; border-color:#2B2D31; } .dark-mode .slider-control span { color:#b9bbbe; } .dark-mode .btn-group { border-color:#2D2F34; } .dark-mode .btn-group button { background:#40444B; color:#DCDDDE; } .dark-mode .btn-group button:not(:last-child) { border-right-color:#2D2F34; } .dark-mode .btn-group button.active { background:#5865F2; color:white; } .dark-mode .toggle-group { background-color:#202225; } .dark-mode .toggle-switch { background:#4E4F52; } .dark-mode .toggle-switch .toggle-dot { background:#B9BBBE; } .dark-mode .toggle-switch.active { background:#5865F2; } .dark-mode #ratio-preview-bg { border-color:#40444B; } .dark-mode #ratio-box { background:#40444B; color:#DCDDDE; border-color:#70747A; } .dark-mode .panel-footer { background-color:#2B2D31; border-top-color:#202225; } .dark-mode .action-button-primary { background:#5865F2; } .dark-mode .action-button-primary:hover { background:#4752C4; } .dark-mode .action-button-secondary { background:#40444B; color:#DCDDDE; border-color:#2D2F34; } .dark-mode .action-button-secondary:hover { background:#4F545C; } .dark-mode .ref-module { background:#1a1d21; border-color:#2D2F34; } .dark-mode .ref-module-header { border-bottom-color:#2D2F34; } .dark-mode .ref-module-header h4 { color:#dcddde; } .dark-mode .global-weight-control label { color:#8e9297; } .dark-mode .weight-slider-mini span { color:#b9bbbe; } .dark-mode .ref-add-btn { background:#5865F2; } .dark-mode .ref-add-btn:hover { background:#4752C4; } .dark-mode .ref-container-large { background:#202225; border-color:#2D2F34; } .dark-mode .ref-container-large:empty:before { color:#8e9297; } .dark-mode .ref-container-large.drag-over { border-color:#5865F2; background-color:#2f3136; } .dark-mode .ref-item-large { background:#313338; border-color:#2D2F34; } .dark-mode .ref-item-large:hover { border-color:#7289DA; } .dark-mode .ref-code-large { color:#7289DA; } .dark-mode .ref-error-large { color:#ff6b6b; } .dark-mode .weight-large { background:rgba(0,0,0,0.85); } .dark-mode .ref-edit-large, .dark-mode .ref-toggle-large, .dark-mode .ref-delete-large { background:rgba(79,84,92,0.8); } .dark-mode .ref-edit-large { color:#7289DA; } .dark-mode .ref-edit-large:hover { background:#7289DA; color:white; } .dark-mode .ref-delete-large { color:#ff6b6b; } .dark-mode .ref-delete-large:hover { background:#ff6b6b; color:white; } .dark-mode .ref-toggle-large { color:#b9bbbe; } .dark-mode .ref-toggle-large.active { background:#248046; } .dark-mode .ref-toggle-large:not(.active):hover { background:#555c66; } .dark-mode .weight-edit-content { background:#2B2D31; color:#dcddde; } .dark-mode .weight-edit-content h4 { color:#dcddde; } .dark-mode .weight-edit-input-group label { color:#dcddde; } .dark-mode .weight-edit-cancel { background:#40444B; color:#dcddde; border-color:#2D2F34; } .dark-mode .weight-edit-cancel:hover { background:#4F545C; } .dark-mode .weight-edit-save { background:#5865F2; } .dark-mode .weight-edit-save:hover { background:#4752C4; } `; document.head.appendChild(styleSheet); } function init() { injectStyles(); resetParams(); createSettingButton(); createControlPanel(); updateAllUIElements(); // Initialize UI elements based on default params } if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(init, 500); // Delay init slightly to ensure Discord UI is fully loaded } else { window.addEventListener('DOMContentLoaded', () => setTimeout(init, 500), { once: true }); } })();