feat: make Web UI mood dropdowns Evil Mode-aware

- Disable per-server mood controls when Evil Miku is active
- Show explanatory notice for disabled server mood dropdowns
- Populate global mood dropdown with evil moods when Evil Mode is on
- Fix initialization race condition by awaiting evil mode status first
- Add CSS styles for disabled mood controls
This commit is contained in:
2026-05-18 21:43:44 +03:00
parent 27f0659cc8
commit 7cb21a372b
4 changed files with 112 additions and 28 deletions

View File

@@ -915,3 +915,20 @@ body.evil-mode [style*="color: rgb(0, 123, 255)"] {
color: #888;
margin-left: auto;
}
/* Disabled mood controls (Evil Miku per-server mood lockout) */
.server-mood-controls select:disabled,
.server-mood-controls button:disabled {
opacity: 0.4;
cursor: not-allowed;
pointer-events: none;
background: #2a2a2a;
border-color: #444;
}
.evil-mode-exempt-notice {
color: #888;
font-size: 0.85rem;
font-style: italic;
margin-top: 0.5rem;
line-height: 1.4;
}

View File

@@ -383,7 +383,7 @@ async function loadProfilePictureMetadata() {
// DOMContentLoaded — main initialization
// ============================================================================
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', async function() {
initTabState();
initTabWheelScroll();
initLogsScrollDetection();
@@ -391,12 +391,13 @@ document.addEventListener('DOMContentLoaded', function() {
initModalAccessibility();
initPromptSourceToggle();
// Load evil mode status FIRST so mood dropdowns populate with the correct list
await checkEvilModeStatus();
loadStatus();
loadServers();
populateMoodDropdowns();
loadServers(); // internally calls populateMoodDropdowns() with correct evilMode
loadLastPrompt();
loadLogs();
checkEvilModeStatus();
checkBipolarModeStatus();
checkGPUStatus();
refreshLanguageStatus();

View File

@@ -90,6 +90,9 @@ function updateEvilModeUI() {
}
updateBipolarToggleVisibility();
// Update per-server mood controls to reflect evil mode state
populateMoodDropdowns();
}
// ===== GPU Selection Management =====

View File

@@ -80,13 +80,16 @@ function displayServers() {
<h4 style="margin: 0 0 0.5rem 0; color: #61dafb;">Server Mood</h4>
<div><strong>Current Mood:</strong> ${server.current_mood_name || 'neutral'} ${MOOD_EMOJIS[server.current_mood_name] || ''}</div>
<div><strong>Sleeping:</strong> ${server.is_sleeping ? 'Yes' : 'No'}</div>
<div style="margin-top: 0.5rem;">
<div class="server-mood-controls" style="margin-top: 0.5rem;">
<select id="mood-select-${String(server.guild_id)}" style="margin-right: 0.5rem; padding: 0.3rem; background: #333; color: white; border: 1px solid #555; border-radius: 3px;">
<option value="">Select Mood...</option>
</select>
<button onclick="setServerMood('${String(server.guild_id)}')" style="margin-right: 0.5rem;">Change Mood</button>
<button onclick="resetServerMood('${String(server.guild_id)}')" style="background: #ff9800;">Reset Mood</button>
</div>
<div class="evil-mode-exempt-notice" id="evil-notice-${String(server.guild_id)}" style="display: none;">
⚠️ Per-server moods are unavailable while Evil Miku is active — she applies her global mood everywhere.
</div>
</div>
</div>
`).join('');
@@ -375,7 +378,7 @@ async function repairConfig() {
}
}
// Populate mood dropdowns with available moods
// Populate mood dropdowns with available moods (Evil Mode-aware)
async function populateMoodDropdowns() {
try {
console.log('🎭 Loading available moods...');
@@ -384,17 +387,28 @@ async function populateMoodDropdowns() {
if (data.moods) {
console.log(`🎭 Found ${data.moods.length} moods:`, data.moods);
const emojiMap = evilMode ? EVIL_MOOD_EMOJIS : MOOD_EMOJIS;
// Determine which mood list to use based on evil mode
let moodList = data.moods;
let emojiMap = MOOD_EMOJIS;
let defaultMood = 'neutral';
if (evilMode) {
// Evil Miku uses evil moods for the global DM dropdown
moodList = Object.keys(EVIL_MOOD_EMOJIS);
emojiMap = EVIL_MOOD_EMOJIS;
defaultMood = 'evil_neutral';
}
// Populate the DM mood dropdown (#mood on tab1)
const dmMoodSelect = document.getElementById('mood');
if (dmMoodSelect) {
dmMoodSelect.innerHTML = '';
data.moods.forEach(mood => {
moodList.forEach(mood => {
const opt = document.createElement('option');
opt.value = mood;
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
if (mood === 'neutral') opt.selected = true;
if (mood === defaultMood) opt.selected = true;
dmMoodSelect.appendChild(opt);
});
}
@@ -403,32 +417,17 @@ async function populateMoodDropdowns() {
const chatMoodSelect = document.getElementById('chat-mood-select');
if (chatMoodSelect) {
chatMoodSelect.innerHTML = '';
data.moods.forEach(mood => {
moodList.forEach(mood => {
const opt = document.createElement('option');
opt.value = mood;
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
if (mood === 'neutral') opt.selected = true;
if (mood === defaultMood) opt.selected = true;
chatMoodSelect.appendChild(opt);
});
}
// Populate per-server mood dropdowns (mood-select-{guildId})
document.querySelectorAll('[id^="mood-select-"]').forEach(select => {
// Keep only the first option ("Select Mood...")
while (select.children.length > 1) {
select.removeChild(select.lastChild);
}
});
data.moods.forEach(mood => {
const moodOption = document.createElement('option');
moodOption.value = mood;
moodOption.textContent = `${mood} ${emojiMap[mood] || ''}`;
document.querySelectorAll('[id^="mood-select-"]').forEach(select => {
select.appendChild(moodOption.cloneNode(true));
});
});
// Update per-server mood controls based on evil mode state
updatePerServerMoodControls(data.moods, MOOD_EMOJIS);
console.log('🎭 All mood dropdowns populated successfully');
} else {
@@ -439,6 +438,70 @@ async function populateMoodDropdowns() {
}
}
// Update per-server mood controls: enable/disable based on evil mode
function updatePerServerMoodControls(regularMoods, regularEmojiMap) {
const serverSelects = document.querySelectorAll('[id^="mood-select-"]');
if (evilMode) {
// Evil Miku is active — disable per-server mood controls
serverSelects.forEach(select => {
const guildId = select.id.replace('mood-select-', '');
const controlsDiv = select.closest('.server-mood-controls');
const noticeDiv = document.getElementById(`evil-notice-${guildId}`);
// Disable the select
select.disabled = true;
// Disable buttons in the same controls div
if (controlsDiv) {
controlsDiv.querySelectorAll('button').forEach(btn => {
btn.disabled = true;
});
}
// Show the explanation notice
if (noticeDiv) {
noticeDiv.style.display = 'block';
}
});
} else {
// Normal mode — enable per-server mood controls and populate with regular moods
serverSelects.forEach(select => {
const guildId = select.id.replace('mood-select-', '');
const controlsDiv = select.closest('.server-mood-controls');
const noticeDiv = document.getElementById(`evil-notice-${guildId}`);
// Enable the select
select.disabled = false;
// Enable buttons in the same controls div
if (controlsDiv) {
controlsDiv.querySelectorAll('button').forEach(btn => {
btn.disabled = false;
});
}
// Hide the explanation notice
if (noticeDiv) {
noticeDiv.style.display = 'none';
}
// Populate with regular moods
// Keep only the first option ("Select Mood...")
while (select.children.length > 1) {
select.removeChild(select.lastChild);
}
regularMoods.forEach(mood => {
const moodOption = document.createElement('option');
moodOption.value = mood;
moodOption.textContent = `${mood} ${regularEmojiMap[mood] || ''}`;
select.appendChild(moodOption);
});
});
}
}
// Per-Server Mood Management
async function setServerMood(guildId) {
console.log(`🎭 setServerMood called with guildId: ${guildId} (type: ${typeof guildId})`);