Files
miku-discord/bot/static/js/servers.js

685 lines
26 KiB
JavaScript
Raw Normal View History

refactor: Modularize monolithic HTML control panel into organized components This commit completes a major refactoring of the Miku control panel from a single 7,191-line monolithic HTML file to a modern modular architecture: CHANGES: - Extracted 872 lines of CSS into css/style.css - Created 10 specialized JavaScript modules (4,964 lines total): * core.js: Global state, utilities, initialization, polling system * servers.js: Server management and mood handling * modes.js: Evil mode, GPU selection, bipolar mode, scoreboard * actions.js: Autonomous/manual actions, custom prompts, reactions * image-gen.js: Image generation system * status.js: Status display and statistics * dm.js: DM user management and conversation analysis * chat.js: LLM chat interface with streaming and voice calls * memories.js: Cheshire Cat memory integration (episodic/declarative/procedural) * profile.js: Profile picture, album gallery, activities editor - Cleaned index.html to 1,351 lines (structure only, zero inline JS/CSS) - Removed 12 duplicate variable declarations - Maintained strict script load order for dependency resolution - Added backup comment to index.html.bak for historical reference VERIFICATION COMPLETED: ✓ All 191 functions/variables from original accounted for ✓ Cross-referenced with backup to ensure nothing lost ✓ All onclick handlers and modal systems validated ✓ No circular dependencies or broken references ✓ HTML structure integrity verified (11 tabs, all buttons/modals intact) ✓ CropperJS CDN links preserved The refactored code is production-ready with improved maintainability and clear separation of concerns.
2026-04-29 20:56:49 +03:00
// ===== Server Management Functions =====
async function loadServers() {
try {
console.log('🎭 Loading servers...');
const data = await apiCall('/servers');
console.log('🎭 Servers response:', data);
if (data.servers) {
servers = data.servers;
console.log(`🎭 Loaded ${servers.length} servers:`, servers);
// Debug: Log each server's guild_id
servers.forEach((server, index) => {
console.log(`🎭 Server ${index}: guild_id = ${server.guild_id}, name = ${server.guild_name}`);
});
// Debug: Show raw response data
console.log('🎭 Raw API response data:', JSON.stringify(data, null, 2));
// Display servers
displayServers();
populateServerDropdowns();
populateMoodDropdowns(); // Populate mood dropdowns after servers are loaded
} else {
console.warn('🎭 No servers found in response');
servers = [];
}
} catch (error) {
console.error('🎭 Failed to load servers:', error);
servers = [];
}
}
function displayServers() {
const container = document.getElementById('servers-list');
if (servers.length === 0) {
container.innerHTML = '<p>No servers configured</p>';
return;
}
container.innerHTML = servers.map(server => `
<div class="server-card">
<div class="server-header">
<div class="server-name">${server.guild_name}</div>
<div class="server-actions">
<button onclick="editServer('${String(server.guild_id)}')">Edit</button>
<button onclick="removeServer('${String(server.guild_id)}')" style="background: #d32f2f;">Remove</button>
</div>
</div>
<div><strong>Guild ID:</strong> ${server.guild_id}</div>
<div><strong>Autonomous Channel:</strong> #${server.autonomous_channel_name} (${server.autonomous_channel_id})</div>
<div><strong>Bedtime Channels:</strong> ${server.bedtime_channel_ids.join(', ')}</div>
<div><strong>Features:</strong>
${server.enabled_features.map(feature => `<span class="feature-tag">${feature}</span>`).join('')}
</div>
<div><strong>Autonomous Interval:</strong> ${server.autonomous_interval_minutes} minutes</div>
<div><strong>Conversation Detection:</strong> ${server.conversation_detection_interval_minutes} minutes</div>
<div><strong>Bedtime Range:</strong> ${String(server.bedtime_hour || 21).padStart(2, '0')}:${String(server.bedtime_minute || 0).padStart(2, '0')} - ${String(server.bedtime_hour_end || 23).padStart(2, '0')}:${String(server.bedtime_minute_end || 59).padStart(2, '0')}</div>
<!-- Bedtime Configuration -->
<div style="margin-top: 1rem; padding: 1rem; background: #2a2a2a; border-radius: 4px;">
<h4 style="margin: 0 0 0.5rem 0; color: #61dafb;">Bedtime Settings</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 0.5rem;">
<div>
<label style="display: block; font-size: 0.9rem; margin-bottom: 0.2rem;">Start Time:</label>
<input type="time" id="bedtime-start-${String(server.guild_id)}" value="${String(server.bedtime_hour || 21).padStart(2, '0')}:${String(server.bedtime_minute || 0).padStart(2, '0')}" style="padding: 0.3rem; background: #333; color: white; border: 1px solid #555; border-radius: 3px; width: 100%;">
</div>
<div>
<label style="display: block; font-size: 0.9rem; margin-bottom: 0.2rem;">End Time:</label>
<input type="time" id="bedtime-end-${String(server.guild_id)}" value="${String(server.bedtime_hour_end || 23).padStart(2, '0')}:${String(server.bedtime_minute_end || 59).padStart(2, '0')}" style="padding: 0.3rem; background: #333; color: white; border: 1px solid #555; border-radius: 3px; width: 100%;">
</div>
</div>
<button onclick="updateBedtimeRange('${String(server.guild_id)}')" style="background: #4caf50;">Update Bedtime Range</button>
</div>
<!-- Per-Server Mood Display -->
<div style="margin-top: 1rem; padding: 1rem; background: #2a2a2a; border-radius: 4px;">
<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;">
<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>
</div>
`).join('');
// Debug: Log what element IDs were created
console.log('🎭 Server cards rendered. Checking for mood-select elements:');
document.querySelectorAll('[id^="mood-select-"]').forEach(el => {
console.log(`🎭 Found mood-select element: ${el.id}`);
});
// Populate mood dropdowns after server cards are created
populateMoodDropdowns();
}
async function populateServerDropdowns() {
const serverSelect = document.getElementById('server-select');
const manualServerSelect = document.getElementById('manual-server-select');
const customPromptServerSelect = document.getElementById('custom-prompt-server-select');
// Clear existing options except "All Servers"
serverSelect.innerHTML = '<option value="all">All Servers</option>';
manualServerSelect.innerHTML = '<option value="all">All Servers</option>';
customPromptServerSelect.innerHTML = '<option value="all">All Servers</option>';
console.log('🎭 Populating server dropdowns with', servers.length, 'servers');
// Add server options
servers.forEach(server => {
console.log(`🎭 Adding server to dropdown: ${server.guild_name} (guild_id: ${server.guild_id}, type: ${typeof server.guild_id})`);
const option = document.createElement('option');
option.value = server.guild_id;
option.textContent = server.guild_name;
serverSelect.appendChild(option.cloneNode(true));
manualServerSelect.appendChild(option);
customPromptServerSelect.appendChild(option.cloneNode(true));
});
// Debug: Check what's actually in the manual-server-select dropdown
console.log('🎭 manual-server-select options:');
Array.from(manualServerSelect.options).forEach((opt, idx) => {
console.log(` [${idx}] value="${opt.value}" text="${opt.textContent}"`);
});
// Populate autonomous stats dropdown
populateAutonomousServerDropdown();
}
// Figurine subscribers UI functions (must be global for onclick handlers)
async function refreshFigurineSubscribers() {
try {
console.log('🔄 Figurines: Fetching subscribers...');
const data = await apiCall('/figurines/subscribers');
console.log('📋 Figurines: Received subscribers:', data);
displayFigurineSubscribers(data.subscribers || []);
showNotification('Subscribers refreshed');
} catch (e) {
console.error('❌ Figurines: Failed to fetch subscribers:', e);
}
}
function displayFigurineSubscribers(subscribers) {
const container = document.getElementById('figurine-subscribers-list');
if (!container) return;
if (!subscribers.length) {
container.innerHTML = '<p>No subscribers yet.</p>';
return;
}
let html = '<ul>';
subscribers.forEach(uid => {
const uidStr = String(uid);
html += `<li><code>${uidStr}</code> <button onclick="removeFigurineSubscriber('${uidStr}')">Remove</button></li>`;
});
html += '</ul>';
container.innerHTML = html;
}
async function addFigurineSubscriber() {
try {
console.log(' Figurines: Adding subscriber...');
const uid = document.getElementById('figurine-user-id').value.trim();
if (!uid) {
showNotification('Enter a user ID', 'error');
return;
}
const form = new FormData();
form.append('user_id', uid);
const res = await fetch('/figurines/subscribers', { method: 'POST', body: form });
const data = await res.json();
console.log(' Figurines: Add subscriber response:', data);
if (data.status === 'ok') {
showNotification('Subscriber added');
document.getElementById('figurine-user-id').value = '';
refreshFigurineSubscribers();
} else {
showNotification(data.message || 'Failed to add subscriber', 'error');
}
} catch (e) {
console.error('❌ Figurines: Failed to add subscriber:', e);
showNotification('Failed to add subscriber', 'error');
}
}
async function removeFigurineSubscriber(uid) {
try {
console.log(`🗑️ Figurines: Removing subscriber ${uid}...`);
const data = await apiCall(`/figurines/subscribers/${uid}`, 'DELETE');
console.log('🗑️ Figurines: Remove subscriber response:', data);
if (data.status === 'ok') {
showNotification('Subscriber removed');
refreshFigurineSubscribers();
} else {
showNotification(data.message || 'Failed to remove subscriber', 'error');
}
} catch (e) {
console.error('❌ Figurines: Failed to remove subscriber:', e);
}
}
async function sendFigurineNowToAll() {
try {
console.log('📨 Figurines: Triggering send to all subscribers...');
const tweetUrl = document.getElementById('figurine-tweet-url-all').value.trim();
const statusDiv = document.getElementById('figurine-all-status');
statusDiv.textContent = 'Sending...';
statusDiv.style.color = evilMode ? '#ff4444' : '#007bff';
const formData = new FormData();
if (tweetUrl) {
formData.append('tweet_url', tweetUrl);
}
const res = await fetch('/figurines/send_now', {
method: 'POST',
body: formData
});
const data = await res.json();
console.log('📨 Figurines: Send to all response:', data);
if (data.status === 'ok') {
showNotification('Figurine DMs queued for all subscribers');
statusDiv.textContent = 'Queued successfully';
statusDiv.style.color = '#28a745';
document.getElementById('figurine-tweet-url-all').value = ''; // Clear input
} else {
showNotification(data.message || 'Bot not ready', 'error');
statusDiv.textContent = 'Failed: ' + (data.message || 'Unknown error');
statusDiv.style.color = '#dc3545';
}
} catch (e) {
console.error('❌ Figurines: Failed to queue figurine DMs for all:', e);
showNotification('Failed to queue figurine DMs', 'error');
document.getElementById('figurine-all-status').textContent = 'Error: ' + e.message;
document.getElementById('figurine-all-status').style.color = '#dc3545';
}
}
async function sendFigurineToSingleUser() {
try {
const userId = document.getElementById('figurine-single-user-id').value.trim();
const tweetUrl = document.getElementById('figurine-tweet-url-single').value.trim();
const statusDiv = document.getElementById('figurine-single-status');
if (!userId) {
showNotification('Enter a user ID', 'error');
return;
}
console.log(`📨 Figurines: Sending to single user ${userId}, tweet: ${tweetUrl || 'random'}`);
statusDiv.textContent = 'Sending...';
statusDiv.style.color = evilMode ? '#ff4444' : '#007bff';
const formData = new FormData();
formData.append('user_id', userId);
if (tweetUrl) {
formData.append('tweet_url', tweetUrl);
}
const res = await fetch('/figurines/send_to_user', {
method: 'POST',
body: formData
});
const data = await res.json();
console.log('📨 Figurines: Send to single user response:', data);
if (data.status === 'ok') {
showNotification(`Figurine DM queued for user ${userId}`);
statusDiv.textContent = 'Queued successfully';
statusDiv.style.color = '#28a745';
document.getElementById('figurine-single-user-id').value = ''; // Clear inputs
document.getElementById('figurine-tweet-url-single').value = '';
} else {
showNotification(data.message || 'Failed to queue DM', 'error');
statusDiv.textContent = 'Failed: ' + (data.message || 'Unknown error');
statusDiv.style.color = '#dc3545';
}
} catch (e) {
console.error('❌ Figurines: Failed to queue figurine DM for single user:', e);
showNotification('Failed to queue figurine DM', 'error');
document.getElementById('figurine-single-status').textContent = 'Error: ' + e.message;
document.getElementById('figurine-single-status').style.color = '#dc3545';
}
}
// Keep the old function for backward compatibility
async function sendFigurineNow() {
return sendFigurineNowToAll();
}
async function addServer() {
// Don't use parseInt() for Discord IDs - they're too large for JS integers
const guildId = document.getElementById('new-guild-id').value.trim();
const guildName = document.getElementById('new-guild-name').value;
const autonomousChannelId = document.getElementById('new-autonomous-channel-id').value.trim();
const autonomousChannelName = document.getElementById('new-autonomous-channel-name').value;
const bedtimeChannelIds = document.getElementById('new-bedtime-channel-ids').value
.split(',').map(id => id.trim()).filter(id => id.length > 0);
const enabledFeatures = [];
if (document.getElementById('feature-autonomous').checked) enabledFeatures.push('autonomous');
if (document.getElementById('feature-bedtime').checked) enabledFeatures.push('bedtime');
if (document.getElementById('feature-monday-video').checked) enabledFeatures.push('monday_video');
if (!guildId || !guildName || !autonomousChannelId || !autonomousChannelName) {
showNotification('Please fill in all required fields', 'error');
return;
}
try {
await apiCall('/servers', 'POST', {
guild_id: guildId,
guild_name: guildName,
autonomous_channel_id: autonomousChannelId,
autonomous_channel_name: autonomousChannelName,
bedtime_channel_ids: bedtimeChannelIds.length > 0 ? bedtimeChannelIds : [autonomousChannelId],
enabled_features: enabledFeatures
});
showNotification('Server added successfully');
loadServers();
// Clear form
document.getElementById('new-guild-id').value = '';
document.getElementById('new-guild-name').value = '';
document.getElementById('new-autonomous-channel-id').value = '';
document.getElementById('new-autonomous-channel-name').value = '';
document.getElementById('new-bedtime-channel-ids').value = '';
} catch (error) {
console.error('Failed to add server:', error);
}
}
async function removeServer(guildId) {
if (!confirm('Are you sure you want to remove this server?')) {
return;
}
try {
await apiCall(`/servers/${guildId}`, 'DELETE');
showNotification('Server removed successfully');
loadServers();
} catch (error) {
console.error('Failed to remove server:', error);
}
}
async function editServer(guildId) {
// For now, just show a notification - you can implement a full edit form later
showNotification('Edit functionality coming soon!');
}
async function repairConfig() {
if (!confirm('This will attempt to repair corrupted server configurations. Are you sure?')) {
return;
}
try {
await apiCall('/servers/repair', 'POST');
showNotification('Configuration repair initiated. Please refresh the page to see updated server list.');
loadServers(); // Reload servers to reflect potential changes
} catch (error) {
console.error('Failed to repair config:', error);
showNotification(error.message || 'Failed to repair configuration', 'error');
}
}
// Populate mood dropdowns with available moods
async function populateMoodDropdowns() {
try {
console.log('🎭 Loading available moods...');
const data = await apiCall('/moods/available');
console.log('🎭 Available moods response:', data);
if (data.moods) {
console.log(`🎭 Found ${data.moods.length} moods:`, data.moods);
const emojiMap = evilMode ? EVIL_MOOD_EMOJIS : MOOD_EMOJIS;
// Populate the DM mood dropdown (#mood on tab1)
const dmMoodSelect = document.getElementById('mood');
if (dmMoodSelect) {
dmMoodSelect.innerHTML = '';
data.moods.forEach(mood => {
const opt = document.createElement('option');
opt.value = mood;
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
if (mood === 'neutral') opt.selected = true;
dmMoodSelect.appendChild(opt);
});
}
// Populate the chat mood dropdown (#chat-mood-select on tab7)
const chatMoodSelect = document.getElementById('chat-mood-select');
if (chatMoodSelect) {
chatMoodSelect.innerHTML = '';
data.moods.forEach(mood => {
const opt = document.createElement('option');
opt.value = mood;
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
if (mood === 'neutral') 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));
});
});
console.log('🎭 All mood dropdowns populated successfully');
} else {
console.warn('🎭 No moods found in response');
}
} catch (error) {
console.error('🎭 Failed to load available moods:', error);
}
}
// Per-Server Mood Management
async function setServerMood(guildId) {
console.log(`🎭 setServerMood called with guildId: ${guildId} (type: ${typeof guildId})`);
// Ensure guildId is a string for consistency
const guildIdStr = String(guildId);
console.log(`🎭 Using guildId as string: ${guildIdStr}`);
// Debug: Check what elements exist
const elementId = `mood-select-${guildIdStr}`;
console.log(`🎭 Looking for element with ID: ${elementId}`);
const moodSelect = document.getElementById(elementId);
console.log(`🎭 Found element:`, moodSelect);
if (!moodSelect) {
console.error(`🎭 ERROR: Element with ID '${elementId}' not found!`);
console.log(`🎭 Available mood-select elements:`, document.querySelectorAll('[id^="mood-select-"]'));
showNotification(`Error: Mood selector not found for server ${guildIdStr}`, 'error');
return;
}
const selectedMood = moodSelect.value;
console.log(`🎭 Setting mood for server ${guildIdStr} to ${selectedMood}`);
if (!selectedMood) {
showNotification('Please select a mood', 'error');
return;
}
// Get the button and store original text before any changes
const button = moodSelect.nextElementSibling;
const originalText = button.textContent;
try {
// Show loading state
button.textContent = 'Changing...';
button.disabled = true;
console.log(`🎭 Making API call to /servers/${guildIdStr}/mood with mood: ${selectedMood}`);
const response = await apiCall(`/servers/${guildIdStr}/mood`, 'POST', { mood: selectedMood });
console.log(`🎭 API response:`, response);
if (response.status === 'ok') {
showNotification(`Server mood changed to ${selectedMood} ${MOOD_EMOJIS[selectedMood] || ''}`);
// Reset dropdown selection
moodSelect.value = '';
// Reload servers to show updated mood
loadServers();
} else {
showNotification(`Failed to change mood: ${response.message}`, 'error');
}
} catch (error) {
console.error(`🎭 Error setting mood:`, error);
showNotification(`Failed to change mood: ${error}`, 'error');
} finally {
// Restore button state
button.textContent = originalText;
button.disabled = false;
}
}
async function resetServerMood(guildId) {
console.log(`🎭 resetServerMood called with guildId: ${guildId} (type: ${typeof guildId})`);
// Ensure guildId is a string for consistency
const guildIdStr = String(guildId);
console.log(`🎭 Using guildId as string: ${guildIdStr}`);
const button = document.querySelector(`button[onclick="resetServerMood('${guildIdStr}')"]`);
const originalText = button ? button.textContent : 'Reset';
try {
// Show loading state
if (button) {
button.textContent = 'Resetting...';
button.disabled = true;
}
await apiCall(`/servers/${guildIdStr}/mood/reset`, 'POST');
showNotification(`Server mood reset to neutral`);
// Reload servers to show updated mood
loadServers();
} catch (error) {
showNotification(`Failed to reset mood: ${error}`, 'error');
} finally {
// Restore button state
if (button) {
button.textContent = originalText;
button.disabled = false;
}
}
}
async function updateBedtimeRange(guildId) {
console.log(`⏰ updateBedtimeRange called with guildId: ${guildId}`);
// Ensure guildId is a string for consistency
const guildIdStr = String(guildId);
// Get the time values from the inputs
const startTimeInput = document.getElementById(`bedtime-start-${guildIdStr}`);
const endTimeInput = document.getElementById(`bedtime-end-${guildIdStr}`);
if (!startTimeInput || !endTimeInput) {
showNotification('Could not find bedtime time inputs', 'error');
return;
}
const startTime = startTimeInput.value; // Format: "HH:MM"
const endTime = endTimeInput.value; // Format: "HH:MM"
if (!startTime || !endTime) {
showNotification('Please enter both start and end times', 'error');
return;
}
// Parse the times
const [startHour, startMinute] = startTime.split(':').map(Number);
const [endHour, endMinute] = endTime.split(':').map(Number);
const button = document.querySelector(`button[onclick="updateBedtimeRange('${guildIdStr}')"]`);
const originalText = button ? button.textContent : 'Update Bedtime Range';
try {
// Show loading state
if (button) {
button.textContent = 'Updating...';
button.disabled = true;
}
// Send the update request
await apiCall(`/servers/${guildIdStr}/bedtime-range`, 'POST', {
bedtime_hour: startHour,
bedtime_minute: startMinute,
bedtime_hour_end: endHour,
bedtime_minute_end: endMinute
});
showNotification(`Bedtime range updated: ${startTime} - ${endTime}`);
// Reload servers to show updated configuration
loadServers();
} catch (error) {
console.error('Failed to update bedtime range:', error);
} finally {
// Restore button state
if (button) {
button.textContent = originalText;
button.disabled = false;
}
}
}
// Mood Management
async function setMood() {
const mood = document.getElementById('mood').value;
try {
// Use different endpoint for evil mode
const endpoint = evilMode ? '/evil-mode/mood' : '/mood';
await apiCall(endpoint, 'POST', { mood: mood });
showNotification(`Mood set to ${mood}`);
currentMood = mood;
} catch (error) {
console.error('Failed to set mood:', error);
}
}
async function resetMood() {
try {
if (evilMode) {
await apiCall('/evil-mode/mood', 'POST', { mood: 'evil_neutral' });
showNotification('Evil mood reset to evil_neutral');
currentMood = 'evil_neutral';
document.getElementById('mood').value = 'evil_neutral';
} else {
await apiCall('/mood/reset', 'POST');
showNotification('Mood reset to neutral');
currentMood = 'neutral';
document.getElementById('mood').value = 'neutral';
}
} catch (error) {
console.error('Failed to reset mood:', error);
}
}
async function calmMiku() {
try {
if (evilMode) {
await apiCall('/evil-mode/mood', 'POST', { mood: 'evil_neutral' });
showNotification('Evil Miku has been calmed down');
currentMood = 'evil_neutral';
document.getElementById('mood').value = 'evil_neutral';
} else {
await apiCall('/mood/calm', 'POST');
showNotification('Miku has been calmed down');
}
} catch (error) {
console.error('Failed to calm Miku:', error);
}
}
// ===== Language Mode Functions =====
async function refreshLanguageStatus() {
try {
const result = await apiCall('/language');
document.getElementById('current-language-display').textContent =
result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English';
document.getElementById('status-language').textContent =
result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English';
document.getElementById('status-model').textContent = result.current_model;
console.log('Language status:', result);
} catch (error) {
console.error('Failed to get language status:', error);
showNotification('Failed to load language status', 'error');
}
}
async function toggleLanguageMode() {
try {
const result = await apiCall('/language/toggle', 'POST');
// Update UI
document.getElementById('current-language-display').textContent =
result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English';
document.getElementById('status-language').textContent =
result.language_mode === 'japanese' ? '日本語 (Japanese)' : 'English';
document.getElementById('status-model').textContent = result.model_now_using;
// Show notification
showNotification(result.message, 'success');
console.log('Language toggled:', result);
} catch (error) {
console.error('Failed to toggle language mode:', error);
showNotification('Failed to toggle language mode', 'error');
}
}