Files
miku-discord/bot/static/js/servers.js
koko210Serve 694590a620 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

685 lines
26 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===== 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');
}
}