// ============================================================================ // 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 = '
${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', escapeHtml(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)}
${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;
}
}