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>
|
2026-05-18 21:43:44 +03:00
|
|
|
|
<div class="server-mood-controls" style="margin-top: 0.5rem;">
|
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
|
|
|
|
<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>
|
2026-05-18 21:43:44 +03:00
|
|
|
|
<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>
|
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
|
|
|
|
</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');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 21:43:44 +03:00
|
|
|
|
// Populate mood dropdowns with available moods (Evil Mode-aware)
|
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
|
|
|
|
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);
|
2026-05-18 21:43:44 +03:00
|
|
|
|
|
|
|
|
|
|
// 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';
|
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
|
|
// Populate the DM mood dropdown (#mood on tab1)
|
|
|
|
|
|
const dmMoodSelect = document.getElementById('mood');
|
|
|
|
|
|
if (dmMoodSelect) {
|
|
|
|
|
|
dmMoodSelect.innerHTML = '';
|
2026-05-18 21:43:44 +03:00
|
|
|
|
moodList.forEach(mood => {
|
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
|
|
|
|
const opt = document.createElement('option');
|
|
|
|
|
|
opt.value = mood;
|
|
|
|
|
|
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
|
2026-05-18 21:43:44 +03:00
|
|
|
|
if (mood === defaultMood) opt.selected = true;
|
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
|
|
|
|
dmMoodSelect.appendChild(opt);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Populate the chat mood dropdown (#chat-mood-select on tab7)
|
|
|
|
|
|
const chatMoodSelect = document.getElementById('chat-mood-select');
|
|
|
|
|
|
if (chatMoodSelect) {
|
|
|
|
|
|
chatMoodSelect.innerHTML = '';
|
2026-05-18 21:43:44 +03:00
|
|
|
|
moodList.forEach(mood => {
|
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
|
|
|
|
const opt = document.createElement('option');
|
|
|
|
|
|
opt.value = mood;
|
|
|
|
|
|
opt.textContent = `${emojiMap[mood] || ''} ${mood}`.trim();
|
2026-05-18 21:43:44 +03:00
|
|
|
|
if (mood === defaultMood) opt.selected = true;
|
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
|
|
|
|
chatMoodSelect.appendChild(opt);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 21:43:44 +03:00
|
|
|
|
// Update per-server mood controls based on evil mode state
|
|
|
|
|
|
updatePerServerMoodControls(data.moods, MOOD_EMOJIS);
|
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
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-18 21:43:44 +03:00
|
|
|
|
// 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);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
// 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');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-05-20 13:55:35 +03:00
|
|
|
|
|
|
|
|
|
|
// ===== Model Selection Functions =====
|
|
|
|
|
|
|
|
|
|
|
|
let availableModelsData = null;
|
|
|
|
|
|
|
|
|
|
|
|
async function loadAvailableModels() {
|
|
|
|
|
|
console.log('📋 loadAvailableModels() called');
|
|
|
|
|
|
try {
|
|
|
|
|
|
const loadingEl = document.getElementById('model-selection-loading');
|
|
|
|
|
|
const controlsEl = document.getElementById('model-selection-controls');
|
|
|
|
|
|
const infoEl = document.getElementById('model-selection-info');
|
|
|
|
|
|
const infoTextEl = document.getElementById('model-selection-info-text');
|
|
|
|
|
|
|
|
|
|
|
|
if (loadingEl) loadingEl.style.display = 'block';
|
|
|
|
|
|
if (controlsEl) controlsEl.style.display = 'none';
|
|
|
|
|
|
if (infoEl) infoEl.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
console.log('📋 Fetching /models/available...');
|
|
|
|
|
|
const result = await apiCall('/models/available');
|
|
|
|
|
|
console.log('📋 /models/available response:', result);
|
|
|
|
|
|
availableModelsData = result;
|
|
|
|
|
|
|
|
|
|
|
|
if (loadingEl) loadingEl.style.display = 'none';
|
|
|
|
|
|
if (controlsEl) controlsEl.style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
showNotification('Failed to load models: ' + (result.error || 'Unknown error'), 'error');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Populate dropdowns
|
|
|
|
|
|
const allModels = result.all || [];
|
|
|
|
|
|
const gpuMap = result.gpu_map || {};
|
|
|
|
|
|
console.log('📋 Populating dropdowns with models:', allModels);
|
|
|
|
|
|
|
|
|
|
|
|
const personas = [
|
|
|
|
|
|
{ id: 'regular', selectId: 'model-regular', badgeId: 'model-regular-badge' },
|
|
|
|
|
|
{ id: 'evil', selectId: 'model-evil', badgeId: 'model-evil-badge' },
|
|
|
|
|
|
{ id: 'japanese', selectId: 'model-japanese', badgeId: 'model-japanese-badge' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const p of personas) {
|
|
|
|
|
|
const select = document.getElementById(p.selectId);
|
|
|
|
|
|
if (!select) continue;
|
|
|
|
|
|
|
|
|
|
|
|
// Save current selection
|
|
|
|
|
|
const currentVal = select.value;
|
|
|
|
|
|
|
|
|
|
|
|
select.innerHTML = '';
|
|
|
|
|
|
for (const model of allModels) {
|
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
|
option.value = model;
|
|
|
|
|
|
option.textContent = model;
|
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Restore selection if still valid
|
|
|
|
|
|
if (currentVal && allModels.includes(currentVal)) {
|
|
|
|
|
|
select.value = currentVal;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update badges for current selections
|
|
|
|
|
|
updateModelBadges(allModels, gpuMap);
|
|
|
|
|
|
|
|
|
|
|
|
// Show info about source
|
|
|
|
|
|
if (infoEl && infoTextEl) {
|
|
|
|
|
|
if (result.source === 'fallback') {
|
|
|
|
|
|
infoTextEl.textContent = '⚠️ Could not reach llama-swap containers. Showing known models from config.';
|
|
|
|
|
|
infoEl.style.display = 'block';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
infoEl.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Load current status to sync dropdowns
|
|
|
|
|
|
await refreshModelStatus();
|
|
|
|
|
|
|
|
|
|
|
|
console.log('Available models loaded:', result);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to load available models:', error);
|
|
|
|
|
|
const loadingEl = document.getElementById('model-selection-loading');
|
|
|
|
|
|
if (loadingEl) loadingEl.textContent = '❌ Failed to load models. Click "Refresh Models" to retry.';
|
|
|
|
|
|
showNotification('Failed to load available models', 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateModelBadges(allModels, gpuMap) {
|
|
|
|
|
|
const personas = [
|
|
|
|
|
|
{ selectId: 'model-regular', badgeId: 'model-regular-badge' },
|
|
|
|
|
|
{ selectId: 'model-evil', badgeId: 'model-evil-badge' },
|
|
|
|
|
|
{ selectId: 'model-japanese', badgeId: 'model-japanese-badge' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const p of personas) {
|
|
|
|
|
|
const select = document.getElementById(p.selectId);
|
|
|
|
|
|
const badge = document.getElementById(p.badgeId);
|
|
|
|
|
|
if (!select || !badge) continue;
|
|
|
|
|
|
|
|
|
|
|
|
const model = select.value;
|
|
|
|
|
|
const gpus = gpuMap[model];
|
|
|
|
|
|
|
|
|
|
|
|
if (!gpus) {
|
|
|
|
|
|
badge.textContent = '';
|
|
|
|
|
|
badge.style.display = 'none';
|
|
|
|
|
|
} else if (gpus.length === 2 || (gpus.includes('nvidia') && gpus.includes('amd'))) {
|
|
|
|
|
|
badge.textContent = '✅ Both GPUs';
|
|
|
|
|
|
badge.style.background = '#1b5e20';
|
|
|
|
|
|
badge.style.color = '#a5d6a7';
|
|
|
|
|
|
badge.style.display = 'inline';
|
|
|
|
|
|
} else if (gpus.includes('nvidia')) {
|
|
|
|
|
|
badge.textContent = '⚠️ NVIDIA Only';
|
|
|
|
|
|
badge.style.background = '#e65100';
|
|
|
|
|
|
badge.style.color = '#ffcc80';
|
|
|
|
|
|
badge.style.display = 'inline';
|
|
|
|
|
|
} else if (gpus.includes('amd')) {
|
|
|
|
|
|
badge.textContent = '⚠️ AMD Only';
|
|
|
|
|
|
badge.style.background = '#e65100';
|
|
|
|
|
|
badge.style.color = '#ffcc80';
|
|
|
|
|
|
badge.style.display = 'inline';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function selectModel(persona) {
|
|
|
|
|
|
console.log(`📋 selectModel('${persona}') called`);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const selectId = 'model-' + persona;
|
|
|
|
|
|
const select = document.getElementById(selectId);
|
|
|
|
|
|
if (!select) {
|
|
|
|
|
|
console.warn(`📋 select element #${selectId} not found`);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const model = select.value;
|
|
|
|
|
|
if (!model) {
|
|
|
|
|
|
showNotification('Please select a model first', 'error');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📋 Setting ${persona} model to '${model}'...`);
|
|
|
|
|
|
const result = await apiCall('/models/select', 'POST', {
|
|
|
|
|
|
persona: persona,
|
|
|
|
|
|
model: model,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`📋 /models/select response:`, result);
|
|
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
|
showNotification(`${persona.charAt(0).toUpperCase() + persona.slice(1)} model set to '${model}'`, 'success');
|
|
|
|
|
|
// Update badges
|
|
|
|
|
|
if (availableModelsData) {
|
|
|
|
|
|
updateModelBadges(availableModelsData.all || [], availableModelsData.gpu_map || {});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Refresh language status display (active model may have changed)
|
|
|
|
|
|
refreshLanguageStatus();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showNotification('Failed to set model: ' + (result.error || 'Unknown error'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to select model:', error);
|
|
|
|
|
|
showNotification('Failed to set model: ' + error.message, 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshModelStatus() {
|
|
|
|
|
|
console.log('📋 refreshModelStatus() called');
|
|
|
|
|
|
try {
|
|
|
|
|
|
const result = await apiCall('/models/status');
|
|
|
|
|
|
console.log('📋 /models/status response:', result);
|
|
|
|
|
|
if (!result.success) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Sync dropdowns with current globals
|
|
|
|
|
|
const personas = [
|
|
|
|
|
|
{ id: 'regular', selectId: 'model-regular' },
|
|
|
|
|
|
{ id: 'evil', selectId: 'model-evil' },
|
|
|
|
|
|
{ id: 'japanese', selectId: 'model-japanese' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (const p of personas) {
|
|
|
|
|
|
const select = document.getElementById(p.selectId);
|
|
|
|
|
|
if (!select) continue;
|
|
|
|
|
|
const currentModel = result[p.id];
|
|
|
|
|
|
// Check if this value exists in the dropdown
|
|
|
|
|
|
let found = false;
|
|
|
|
|
|
for (const option of select.options) {
|
|
|
|
|
|
if (option.value === currentModel) {
|
|
|
|
|
|
select.value = currentModel;
|
|
|
|
|
|
found = true;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!found && currentModel) {
|
|
|
|
|
|
// Add it if it doesn't exist (e.g., a model that wasn't in the API response)
|
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
|
option.value = currentModel;
|
|
|
|
|
|
option.textContent = currentModel;
|
|
|
|
|
|
select.appendChild(option);
|
|
|
|
|
|
select.value = currentModel;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Update badges
|
|
|
|
|
|
if (availableModelsData) {
|
|
|
|
|
|
updateModelBadges(availableModelsData.all || [], availableModelsData.gpu_map || {});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('Failed to refresh model status:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|