Compare commits
6 Commits
66881f4c88
...
a434f11561
| Author | SHA1 | Date | |
|---|---|---|---|
| a434f11561 | |||
| a217f18649 | |||
| ed9df5ff81 | |||
| 6a35718a7c | |||
| e2077705de | |||
| 8ca94fbafc |
@@ -2,6 +2,7 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Miku Control Panel</title>
|
<title>Miku Control Panel</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -27,6 +28,31 @@
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
border-left: 2px solid #333;
|
border-left: 2px solid #333;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logs-content {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log-line { line-height: 1.4; }
|
||||||
|
.log-line.log-error { color: #ff6b6b; }
|
||||||
|
.log-line.log-warning { color: #ffd93d; }
|
||||||
|
.log-line.log-info { color: #0f0; }
|
||||||
|
.log-line.log-debug { color: #888; }
|
||||||
|
|
||||||
|
.logs-paused-indicator {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: rgba(50, 50, 0, 0.9);
|
||||||
|
color: #ffd93d;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.25rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
select, button, input {
|
select, button, input {
|
||||||
@@ -660,15 +686,15 @@
|
|||||||
<!-- Tab Navigation -->
|
<!-- Tab Navigation -->
|
||||||
<div class="tab-container">
|
<div class="tab-container">
|
||||||
<div class="tab-buttons">
|
<div class="tab-buttons">
|
||||||
<button class="tab-button active" onclick="switchTab('tab1')">Server Management</button>
|
<button class="tab-button active" data-tab="tab1" onclick="switchTab('tab1')">Server Management</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab2')">Actions</button>
|
<button class="tab-button" data-tab="tab2" onclick="switchTab('tab2')">Actions</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab3')">Status</button>
|
<button class="tab-button" data-tab="tab3" onclick="switchTab('tab3')">Status</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab4')">⚙️ LLM Settings</button>
|
<button class="tab-button" data-tab="tab4" onclick="switchTab('tab4')">⚙️ LLM Settings</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab5')">🎨 Image Generation</button>
|
<button class="tab-button" data-tab="tab5" onclick="switchTab('tab5')">🎨 Image Generation</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab6')">📊 Autonomous Stats</button>
|
<button class="tab-button" data-tab="tab6" onclick="switchTab('tab6')">📊 Autonomous Stats</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab7')">💬 Chat with LLM</button>
|
<button class="tab-button" data-tab="tab7" onclick="switchTab('tab7')">💬 Chat with LLM</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab8')">📞 Voice Call</button>
|
<button class="tab-button" data-tab="tab8" onclick="switchTab('tab8')">📞 Voice Call</button>
|
||||||
<button class="tab-button" onclick="switchTab('tab9')">🧠 Memories</button>
|
<button class="tab-button" data-tab="tab9" onclick="switchTab('tab9')">🧠 Memories</button>
|
||||||
<button class="tab-button" onclick="window.location.href='/static/system.html'">🎛️ System Settings</button>
|
<button class="tab-button" onclick="window.location.href='/static/system.html'">🎛️ System Settings</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1717,8 +1743,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logs">
|
<div class="logs" id="logs-panel">
|
||||||
<h3>Logs</h3>
|
<h3>Logs</h3>
|
||||||
|
<div id="logs-paused-banner" class="logs-paused-indicator" onclick="scrollLogsToBottom()">⏸ Auto-scroll paused — click to resume</div>
|
||||||
<div id="logs-content"></div>
|
<div id="logs-content"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1750,6 +1777,7 @@
|
|||||||
<!-- Create Memory Modal -->
|
<!-- Create Memory Modal -->
|
||||||
<div id="create-memory-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; align-items: center; justify-content: center;">
|
<div id="create-memory-modal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 2000; align-items: center; justify-content: center;">
|
||||||
<div style="background: #1e1e1e; border: 2px solid #555; border-radius: 8px; padding: 2rem; max-width: 600px; width: 90%;">
|
<div style="background: #1e1e1e; border: 2px solid #555; border-radius: 8px; padding: 2rem; max-width: 600px; width: 90%;">
|
||||||
|
<input type="hidden" id="create-memory-collection" value="">
|
||||||
<h3 style="margin: 0 0 1rem 0; color: #6fdc6f;" id="create-modal-title">➕ Create New Memory</h3>
|
<h3 style="margin: 0 0 1rem 0; color: #6fdc6f;" id="create-modal-title">➕ Create New Memory</h3>
|
||||||
<div style="margin-bottom: 1rem;">
|
<div style="margin-bottom: 1rem;">
|
||||||
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Content:</label>
|
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Content:</label>
|
||||||
@@ -1830,8 +1858,12 @@ function switchTab(tabId) {
|
|||||||
// Show the selected tab content
|
// Show the selected tab content
|
||||||
document.getElementById(tabId).classList.add('active');
|
document.getElementById(tabId).classList.add('active');
|
||||||
|
|
||||||
// Add active class to the clicked tab button
|
// Add active class to the matching tab button via data-tab attribute
|
||||||
event.target.classList.add('active');
|
const activeBtn = document.querySelector(`.tab-button[data-tab="${tabId}"]`);
|
||||||
|
if (activeBtn) activeBtn.classList.add('active');
|
||||||
|
|
||||||
|
// Persist active tab to localStorage
|
||||||
|
localStorage.setItem('miku-active-tab', tabId);
|
||||||
|
|
||||||
console.log(`🔄 Switched to ${tabId}`);
|
console.log(`🔄 Switched to ${tabId}`);
|
||||||
if (tabId === 'tab1') {
|
if (tabId === 'tab1') {
|
||||||
@@ -1846,6 +1878,12 @@ function switchTab(tabId) {
|
|||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Restore persisted tab from localStorage
|
||||||
|
const savedTab = localStorage.getItem('miku-active-tab');
|
||||||
|
if (savedTab && document.getElementById(savedTab)) {
|
||||||
|
switchTab(savedTab);
|
||||||
|
}
|
||||||
|
|
||||||
loadStatus();
|
loadStatus();
|
||||||
loadServers();
|
loadServers();
|
||||||
loadLastPrompt();
|
loadLastPrompt();
|
||||||
@@ -1857,6 +1895,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
console.log('🚀 DOMContentLoaded - initializing figurine subscribers list');
|
console.log('🚀 DOMContentLoaded - initializing figurine subscribers list');
|
||||||
refreshFigurineSubscribers();
|
refreshFigurineSubscribers();
|
||||||
loadProfilePictureMetadata();
|
loadProfilePictureMetadata();
|
||||||
|
initLogsScrollDetection(); // Set up log auto-scroll tracking
|
||||||
|
|
||||||
|
// Enable mouse wheel horizontal scrolling on tab bar
|
||||||
|
const tabButtonsEl = document.querySelector('.tab-buttons');
|
||||||
|
if (tabButtonsEl) {
|
||||||
|
tabButtonsEl.addEventListener('wheel', function(e) {
|
||||||
|
if (e.deltaY !== 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
tabButtonsEl.scrollLeft += e.deltaY;
|
||||||
|
}
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
// Set up periodic updates
|
// Set up periodic updates
|
||||||
setInterval(loadStatus, 10000);
|
setInterval(loadStatus, 10000);
|
||||||
@@ -2418,12 +2468,15 @@ async function resetServerMood(guildId) {
|
|||||||
const guildIdStr = String(guildId);
|
const guildIdStr = String(guildId);
|
||||||
console.log(`🎭 Using guildId as string: ${guildIdStr}`);
|
console.log(`🎭 Using guildId as string: ${guildIdStr}`);
|
||||||
|
|
||||||
|
const button = document.querySelector(`button[onclick="resetServerMood('${guildIdStr}')"]`);
|
||||||
|
const originalText = button ? button.textContent : 'Reset';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Show loading state
|
// Show loading state
|
||||||
const button = document.querySelector(`button[onclick="resetServerMood('${guildIdStr}')"]`);
|
if (button) {
|
||||||
const originalText = button.textContent;
|
|
||||||
button.textContent = 'Resetting...';
|
button.textContent = 'Resetting...';
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
await apiCall(`/servers/${guildIdStr}/mood/reset`, 'POST');
|
await apiCall(`/servers/${guildIdStr}/mood/reset`, 'POST');
|
||||||
showNotification(`Server mood reset to neutral`);
|
showNotification(`Server mood reset to neutral`);
|
||||||
@@ -2434,11 +2487,12 @@ async function resetServerMood(guildId) {
|
|||||||
showNotification(`Failed to reset mood: ${error}`, 'error');
|
showNotification(`Failed to reset mood: ${error}`, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
// Restore button state
|
// Restore button state
|
||||||
const button = document.querySelector(`button[onclick="resetServerMood('${guildIdStr}')"]`);
|
if (button) {
|
||||||
button.textContent = originalText;
|
button.textContent = originalText;
|
||||||
button.disabled = false;
|
button.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function updateBedtimeRange(guildId) {
|
async function updateBedtimeRange(guildId) {
|
||||||
console.log(`⏰ updateBedtimeRange called with guildId: ${guildId}`);
|
console.log(`⏰ updateBedtimeRange called with guildId: ${guildId}`);
|
||||||
@@ -3984,10 +4038,60 @@ async function loadLastPrompt() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log auto-scroll state
|
||||||
|
let logsAutoScroll = true;
|
||||||
|
|
||||||
|
function initLogsScrollDetection() {
|
||||||
|
const logsPanel = document.getElementById('logs-panel');
|
||||||
|
if (!logsPanel) return;
|
||||||
|
logsPanel.addEventListener('scroll', function() {
|
||||||
|
// If user is within 50px of the bottom, re-enable auto-scroll
|
||||||
|
const atBottom = logsPanel.scrollHeight - logsPanel.scrollTop - logsPanel.clientHeight < 50;
|
||||||
|
logsAutoScroll = atBottom;
|
||||||
|
const banner = document.getElementById('logs-paused-banner');
|
||||||
|
if (banner) banner.style.display = atBottom ? 'none' : 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scrollLogsToBottom() {
|
||||||
|
const logsPanel = document.getElementById('logs-panel');
|
||||||
|
if (logsPanel) {
|
||||||
|
logsPanel.scrollTop = logsPanel.scrollHeight;
|
||||||
|
logsAutoScroll = true;
|
||||||
|
const banner = document.getElementById('logs-paused-banner');
|
||||||
|
if (banner) banner.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyLogLine(line) {
|
||||||
|
const upper = line.toUpperCase();
|
||||||
|
if (upper.includes(' ERROR ') || upper.includes(' CRITICAL ') || upper.startsWith('ERROR') || upper.startsWith('CRITICAL') || upper.includes('TRACEBACK')) return 'log-error';
|
||||||
|
if (upper.includes(' WARNING ') || upper.startsWith('WARNING')) return 'log-warning';
|
||||||
|
if (upper.includes(' DEBUG ') || upper.startsWith('DEBUG')) return 'log-debug';
|
||||||
|
return 'log-info';
|
||||||
|
}
|
||||||
|
|
||||||
async function loadLogs() {
|
async function loadLogs() {
|
||||||
try {
|
try {
|
||||||
const result = await apiCall('/logs');
|
const result = await apiCall('/logs');
|
||||||
document.getElementById('logs-content').textContent = result;
|
const logsContent = document.getElementById('logs-content');
|
||||||
|
const lines = (result || '').split('\n');
|
||||||
|
logsContent.innerHTML = lines.map(line => {
|
||||||
|
if (!line.trim()) return '';
|
||||||
|
const cls = classifyLogLine(line);
|
||||||
|
return `<div class="log-line ${cls}">${escapeHtml(line)}</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
// Auto-scroll to bottom if user hasn't scrolled up
|
||||||
|
if (logsAutoScroll) {
|
||||||
|
scrollLogsToBottom();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load logs:', error);
|
console.error('Failed to load logs:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user