// ============================================================================ // Miku Control Panel — Status Module // Status display, last prompt, autonomous stats // ============================================================================ // ===== Status ===== async function loadStatus() { try { const result = await apiCall('/status'); const statusDiv = document.getElementById('status'); if (result.evil_mode !== undefined && result.evil_mode !== evilMode) { evilMode = result.evil_mode; updateEvilModeUI(); if (evilMode && result.mood) { const moodSelect = document.getElementById('mood'); if (moodSelect) moodSelect.value = result.mood; } } if (result.mood) { const moodSelect = document.getElementById('mood'); if (moodSelect && moodSelect.querySelector(`option[value="${result.mood}"]`)) { moodSelect.value = result.mood; } currentMood = result.mood; } let serverMoodsHtml = ''; if (result.server_moods) { serverMoodsHtml = '
Server Moods:
'; for (const [guildId, mood] of Object.entries(result.server_moods)) { const server = servers.find(s => s.guild_id == guildId); const serverName = server ? server.guild_name : `Server ${guildId}`; const emojiMap = evilMode ? EVIL_MOOD_EMOJIS : MOOD_EMOJIS; serverMoodsHtml += `• ${serverName}: ${mood} ${emojiMap[mood] || ''}
`; } serverMoodsHtml += '
'; } const moodEmoji = evilMode ? (EVIL_MOOD_EMOJIS[result.mood] || '') : (MOOD_EMOJIS[result.mood] || ''); const moodLabel = evilMode ? `😈 ${result.mood} ${moodEmoji}` : `${result.mood} ${moodEmoji}`; statusDiv.innerHTML = `
Status: ${result.status}
DM Mood: ${moodLabel}
Servers: ${result.servers}
Active Schedulers: ${result.active_schedulers}
💬 DM Support: Users can message Miku directly in DMs. She responds to every DM message using the DM mood (auto-rotating every 2 hours).
${serverMoodsHtml} `; } catch (error) { console.error('Failed to load status:', error); } } // ===== Prompt History ===== let _promptHistoryCache = []; // cached history entries from last fetch let _selectedPromptId = null; // currently selected entry ID let _middleTruncation = false; // whether middle-truncation is active async function loadPromptHistory() { const source = localStorage.getItem('miku-prompt-source') || 'all'; const selectEl = document.getElementById('prompt-history-select'); try { const url = source === 'all' ? '/prompts' : `/prompts?source=${source}`; const result = await apiCall(url); _promptHistoryCache = result.history || []; // Populate dropdown const currentValue = selectEl.value; selectEl.innerHTML = ''; if (_promptHistoryCache.length === 0) { selectEl.innerHTML = ''; } else { _promptHistoryCache.forEach(entry => { const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : '?'; const srcLabel = entry.source === 'cat' ? '🐱' : '🤖'; const user = entry.user || '?'; const option = document.createElement('option'); option.value = entry.id; option.textContent = `${srcLabel} #${entry.id} — ${user} — ${ts}`; selectEl.appendChild(option); }); } // Restore or auto-select the latest entry if (_selectedPromptId && _promptHistoryCache.some(e => e.id === _selectedPromptId)) { selectEl.value = _selectedPromptId; } else if (_promptHistoryCache.length > 0) { selectEl.value = _promptHistoryCache[0].id; } if (selectEl.value) { await selectPromptEntry(selectEl.value); } else { clearPromptDisplay(); } } catch (error) { console.error('Failed to load prompt history:', error); } } async function selectPromptEntry(promptId) { if (!promptId) { clearPromptDisplay(); return; } _selectedPromptId = parseInt(promptId); // Try cache first let entry = _promptHistoryCache.find(e => e.id === _selectedPromptId); // Fall back to API call if not in cache if (!entry) { try { entry = await apiCall(`/prompts/${_selectedPromptId}`); } catch (error) { console.error('Failed to load prompt entry:', error); clearPromptDisplay(); return; } } if (!entry) { clearPromptDisplay(); return; } renderPromptEntry(entry); } function clearPromptDisplay() { document.getElementById('prompt-metadata').innerHTML = ''; document.getElementById('prompt-display').innerHTML = '
No prompt selected.
'; document.getElementById('last-prompt').textContent = ''; } function renderPromptEntry(entry) { // Metadata bar const metaEl = document.getElementById('prompt-metadata'); const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : '?'; const sourceIcon = entry.source === 'cat' ? '🐱 Cat' : '🤖 Fallback'; metaEl.innerHTML = ` #${entry.id} Source: ${sourceIcon} User: ${escapeHtml(entry.user || '?')} Mood: ${escapeHtml(entry.mood || '?')} Guild: ${escapeHtml(entry.guild || '?')} Channel: ${escapeHtml(entry.channel || '?')} Model: ${escapeHtml(entry.model || '?')} Type: ${escapeHtml(entry.response_type || '?')} Time: ${ts} `; // Parse full_prompt into sections const sections = parsePromptSections(entry.full_prompt || ''); // Build display HTML with collapsible subsections let displayHtml = ''; if (sections.system) { displayHtml += buildCollapsibleSection('System Prompt', sections.system, 'system'); } if (sections.context) { displayHtml += buildCollapsibleSection('Context (Memories & Tools)', sections.context, 'context'); } if (sections.conversation) { displayHtml += buildCollapsibleSection('Conversation', sections.conversation, 'conversation'); } if (!sections.system && !sections.context && !sections.conversation) { // Fallback: show raw full_prompt displayHtml += `
${escapeHtml(entry.full_prompt || '')}
`; } // Response section if (entry.response) { let responseText = entry.response; if (_middleTruncation && responseText.length > 400) { responseText = responseText.substring(0, 200) + '\n\n... [truncated middle] ...\n\n' + responseText.substring(responseText.length - 200); } displayHtml += buildCollapsibleSection('Response', responseText, 'response'); } // Render into the prompt-display div (using innerHTML for collapsible structure) const displayEl = document.getElementById('prompt-display'); displayEl.innerHTML = displayHtml; // Also set the raw text into the
 for copy functionality
  let rawText = entry.full_prompt || '';
  if (entry.response) {
    rawText += `\n\n${'═'.repeat(60)}\n[Response]\n${entry.response}`;
  }
  document.getElementById('last-prompt').textContent = rawText;
}

function parsePromptSections(fullPrompt) {
  const sections = { system: null, context: null, conversation: null };

  if (!fullPrompt) return sections;

  // Try to split on known section markers
  const contextMatch = fullPrompt.match(/# Context\s*\n([\s\S]*?)(?=\n# Conversation|\nHuman:|\n$)/);
  const convMatch = fullPrompt.match(/# Conversation until now:\s*\n([\s\S]*)/);

  if (contextMatch) {
    // Everything before # Context is the system prompt
    const contextIdx = fullPrompt.indexOf('# Context');
    if (contextIdx > 0) {
      sections.system = fullPrompt.substring(0, contextIdx).trim();
    }
    sections.context = contextMatch[1].trim();
  }

  if (convMatch) {
    sections.conversation = convMatch[1].trim();
  } else {
    // Try alternative: "Human:" at the end
    const humanMatch = fullPrompt.match(/\nHuman:([\s\S]*)/);
    if (humanMatch && fullPrompt.indexOf('Human:') > fullPrompt.indexOf('# Context')) {
      sections.conversation = 'Human:' + humanMatch[1].trim();
    }
  }

  // If no # Context marker, try "System:" prefix (fallback prompts)
  if (!sections.system && !sections.context) {
    const sysMatch = fullPrompt.match(/^System:\s*([\s\S]*?)(?=\nMessages:)/);
    const msgMatch = fullPrompt.match(/Messages:\s*([\s\S]*)/);
    if (sysMatch) {
      sections.system = sysMatch[1].trim();
    }
    if (msgMatch) {
      sections.conversation = msgMatch[1].trim();
    }
  }

  return sections;
}

function buildCollapsibleSection(title, content, sectionId) {
  const uniqueId = `prompt-section-${sectionId}-${Date.now()}`;
  return `
    
▼ ${escapeHtml(title)}
${escapeHtml(content)}
`; } function togglePromptSubsection(id) { const body = document.getElementById(id); if (!body) return; const header = body.previousElementSibling; if (body.classList.contains('collapsed')) { body.classList.remove('collapsed'); if (header) header.innerHTML = header.innerHTML.replace('▶', '▼'); } else { body.classList.add('collapsed'); if (header) header.innerHTML = header.innerHTML.replace('▼', '▶'); } } function togglePromptHistoryCollapse() { const section = document.getElementById('prompt-history-section'); const toggle = document.getElementById('prompt-history-toggle'); if (section.classList.contains('collapsed')) { section.classList.remove('collapsed'); toggle.textContent = '▼ Prompt History'; } else { section.classList.add('collapsed'); toggle.textContent = '▶ Prompt History'; } } function copyPromptToClipboard() { const rawText = document.getElementById('last-prompt').textContent; if (!rawText) return; navigator.clipboard.writeText(rawText).then(() => { showNotification('Prompt copied to clipboard', 'success'); }).catch(err => { console.error('Failed to copy:', err); showNotification('Failed to copy', 'error'); }); } function toggleMiddleTruncation() { _middleTruncation = document.getElementById('prompt-truncate-toggle').checked; // Re-render current entry if (_selectedPromptId) { selectPromptEntry(_selectedPromptId); } } // Legacy compatibility — called from core.js on page load / tab switch // Redirects to the new loadPromptHistory() async function loadLastPrompt() { await loadPromptHistory(); } // ===== Autonomous Stats ===== async function loadAutonomousStats() { const serverSelect = document.getElementById('autonomous-server-select'); const selectedGuildId = serverSelect.value; if (!selectedGuildId) { document.getElementById('autonomous-stats-display').innerHTML = '

Please select a server to view autonomous stats.

'; return; } try { const data = await apiCall('/autonomous/stats'); if (!data.servers || !data.servers[selectedGuildId]) { document.getElementById('autonomous-stats-display').innerHTML = '

Server not found or not initialized.

'; return; } const serverData = data.servers[selectedGuildId]; displayAutonomousStats(serverData); } catch (error) { console.error('Failed to load autonomous stats:', error); } } function displayAutonomousStats(data) { const container = document.getElementById('autonomous-stats-display'); if (!data.context) { container.innerHTML = `

⚠️ Context Not Initialized

This server hasn't had any activity yet. Context tracking will begin once messages are sent.

Current Mood: ${data.mood} ${MOOD_EMOJIS[data.mood] || ''}
Energy: ${data.mood_profile.energy}
Sociability: ${data.mood_profile.sociability}
Impulsiveness: ${data.mood_profile.impulsiveness}
`; return; } const ctx = data.context; const profile = data.mood_profile; const lastActionMin = Math.floor(ctx.time_since_last_action / 60); const lastInteractionMin = Math.floor(ctx.time_since_last_interaction / 60); container.innerHTML = `

🎭 Mood & Personality Profile

Current Mood
${data.mood} ${MOOD_EMOJIS[data.mood] || ''}
Energy Level
${(profile.energy * 100).toFixed(0)}%
Sociability
${(profile.sociability * 100).toFixed(0)}%
Impulsiveness
${(profile.impulsiveness * 100).toFixed(0)}%

📈 Activity Metrics

Messages (Last 5 min) ⚡ ephemeral
${ctx.messages_last_5min}
Messages (Last Hour) ⚡ ephemeral
${ctx.messages_last_hour}
Conversation Momentum 💾 saved
${(ctx.conversation_momentum * 100).toFixed(0)}%
Decays with downtime (half-life: 10min)
Unique Users Active ⚡ ephemeral
${ctx.unique_users_active}

👥 User Events

Users Joined Recently
${ctx.users_joined_recently}
Status Changes
${ctx.users_status_changed}
Active Activities
${ctx.users_started_activity.length}
${ctx.users_started_activity.length > 0 ? `
${ctx.users_started_activity.join(', ')}
` : ''}

⏱️ Timing & Context

Time Since Last Action 💾 saved
${lastActionMin} min
${ctx.time_since_last_action.toFixed(1)}s
Time Since Last Interaction 💾 saved
${lastInteractionMin} min
${ctx.time_since_last_interaction.toFixed(1)}s
Messages Since Last Appearance 💾 saved
${ctx.messages_since_last_appearance}
Current Time Context ⚡ ephemeral
${ctx.hour_of_day}:00
${ctx.is_weekend ? '📅 Weekend' : '📆 Weekday'}

🧠 Base Energy Level

From current mood personality
${(ctx.mood_energy_level * 100).toFixed(0)}%
💡 Combined with activity metrics to determine action likelihood.
📝 High energy = shorter wait times, higher action chance.
💾 Persisted across restarts
`; } function getStatColor(value) { if (value >= 0.8) return '#4caf50'; if (value >= 0.6) return '#8bc34a'; if (value >= 0.4) return '#ffc107'; if (value >= 0.2) return '#ff9800'; return '#f44336'; } function getMomentumColor(value) { if (value >= 0.7) return '#4caf50'; if (value >= 0.4) return '#2196f3'; return '#9e9e9e'; } function populateAutonomousServerDropdown() { const select = document.getElementById('autonomous-server-select'); if (!select) return; const currentValue = select.value; select.innerHTML = ''; servers.forEach(server => { const option = document.createElement('option'); option.value = server.guild_id; option.textContent = `${server.guild_name} (${server.guild_id})`; select.appendChild(option); }); if (currentValue && servers.some(s => String(s.guild_id) === currentValue)) { select.value = currentValue; } }