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
|
|
|
|
// ============================================================================
|
|
|
|
|
|
// Miku Control Panel — Memory Management Module
|
|
|
|
|
|
// ============================================================================
|
|
|
|
|
|
|
|
|
|
|
|
async function refreshMemoryStats() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// Fetch Cat status
|
|
|
|
|
|
const statusData = await apiCall('/memory/status');
|
|
|
|
|
|
|
|
|
|
|
|
const indicator = document.getElementById('cat-status-indicator');
|
|
|
|
|
|
const toggleBtn = document.getElementById('cat-toggle-btn');
|
|
|
|
|
|
|
|
|
|
|
|
if (statusData.healthy) {
|
|
|
|
|
|
indicator.innerHTML = `<span style="color: #6fdc6f;">● Connected</span> — ${statusData.url}`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
indicator.innerHTML = `<span style="color: #ff6b6b;">● Disconnected</span> — ${statusData.url}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (statusData.circuit_breaker_active) {
|
|
|
|
|
|
indicator.innerHTML += ` <span style="color: #dcb06f;">(circuit breaker active)</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
toggleBtn.textContent = statusData.enabled ? '🐱 Cat: ON' : '😿 Cat: OFF';
|
|
|
|
|
|
toggleBtn.style.background = statusData.enabled ? '#2a7a2a' : '#7a2a2a';
|
|
|
|
|
|
toggleBtn.style.borderColor = statusData.enabled ? '#4a9a4a' : '#9a4a4a';
|
|
|
|
|
|
|
|
|
|
|
|
// Fetch memory stats
|
|
|
|
|
|
const statsData = await apiCall('/memory/stats');
|
|
|
|
|
|
|
|
|
|
|
|
if (statsData.success && statsData.collections) {
|
|
|
|
|
|
const collections = {};
|
|
|
|
|
|
statsData.collections.forEach(c => { collections[c.name] = c.vectors_count; });
|
|
|
|
|
|
|
|
|
|
|
|
document.getElementById('stat-episodic-count').textContent = collections['episodic'] ?? '—';
|
|
|
|
|
|
document.getElementById('stat-declarative-count').textContent = collections['declarative'] ?? '—';
|
|
|
|
|
|
document.getElementById('stat-procedural-count').textContent = collections['procedural'] ?? '—';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
document.getElementById('stat-episodic-count').textContent = '—';
|
|
|
|
|
|
document.getElementById('stat-declarative-count').textContent = '—';
|
|
|
|
|
|
document.getElementById('stat-procedural-count').textContent = '—';
|
|
|
|
|
|
}
|
feat(memory): add automated nightly consolidation at 4:00 AM UTC
Step 2 of memory system overhaul: automated scheduling.
- New consolidation_scheduler.py: run_nightly_consolidation() function that
checks Cat health, triggers consolidation via WebSocket, and tracks
run history with success/failure stats
- bot.py on_ready: register APScheduler cron job (hour=4, minute=0)
alongside the existing daily DM analysis job
- routes/memory.py: expose consolidation status (last_run, last_result,
last_error, is_running, total_runs, successful_runs) in the
/memory/status API response
- Web UI: show consolidation schedule info (last run time, success/fail,
run counts) below the manual consolidate button, with 'running now'
indicator when active
The 'sleep consolidation' metaphor is now actually automated instead of
being manual-only.
2026-05-15 13:54:54 +03:00
|
|
|
|
|
|
|
|
|
|
// Show consolidation schedule info if available
|
|
|
|
|
|
const consInfo = document.getElementById('consolidation-schedule-info');
|
|
|
|
|
|
if (consInfo && statusData.consolidation) {
|
|
|
|
|
|
let infoHtml = '';
|
|
|
|
|
|
const cons = statusData.consolidation;
|
|
|
|
|
|
if (cons.last_run) {
|
|
|
|
|
|
const lastRun = new Date(cons.last_run).toLocaleString();
|
|
|
|
|
|
infoHtml += `🕐 Last run: ${lastRun}`;
|
|
|
|
|
|
if (cons.last_error) {
|
|
|
|
|
|
infoHtml += ` <span style="color: #ff6b6b;">❌ ${escapeHtml(cons.last_error)}</span>`;
|
|
|
|
|
|
} else if (cons.last_result) {
|
|
|
|
|
|
infoHtml += ` <span style="color: #6fdc6f;">✅</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
infoHtml += `<br>`;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
infoHtml += `🕐 Last run: never<br>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
infoHtml += `📊 Runs: ${cons.successful_runs}/${cons.total_runs} successful`;
|
|
|
|
|
|
if (cons.is_running) {
|
|
|
|
|
|
infoHtml += ` <span style="color: #dcb06f;">⏳ (running now)</span>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
consInfo.innerHTML = infoHtml;
|
|
|
|
|
|
}
|
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
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Error refreshing memory stats:', err);
|
|
|
|
|
|
document.getElementById('cat-status-indicator').innerHTML = '<span style="color: #ff6b6b;">● Error checking status</span>';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function toggleCatIntegration() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const statusData = await apiCall('/memory/status');
|
|
|
|
|
|
const newState = !statusData.enabled;
|
|
|
|
|
|
|
|
|
|
|
|
const formData = new FormData();
|
|
|
|
|
|
formData.append('enabled', newState);
|
|
|
|
|
|
const res = await fetch('/memory/toggle', { method: 'POST', body: formData });
|
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
showNotification(`Cheshire Cat ${newState ? 'enabled' : 'disabled'}`, newState ? 'success' : 'info');
|
|
|
|
|
|
refreshMemoryStats();
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
showNotification('Failed to toggle Cat integration', 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function triggerConsolidation() {
|
|
|
|
|
|
const btn = document.getElementById('consolidate-btn');
|
|
|
|
|
|
const status = document.getElementById('consolidation-status');
|
|
|
|
|
|
const resultDiv = document.getElementById('consolidation-result');
|
|
|
|
|
|
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.textContent = '⏳ Running...';
|
|
|
|
|
|
status.textContent = 'Consolidation in progress (this may take a few minutes)...';
|
2026-05-17 11:31:26 +03:00
|
|
|
|
status.style.color = '#dcb06f';
|
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
|
|
|
|
resultDiv.style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall('/memory/consolidate', 'POST');
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
2026-05-17 11:31:26 +03:00
|
|
|
|
status.textContent = '⏳ Consolidation started — waiting for completion...';
|
|
|
|
|
|
|
|
|
|
|
|
// Poll /memory/status until consolidation finishes
|
|
|
|
|
|
const pollInterval = 5000; // 5 seconds
|
|
|
|
|
|
const maxPolls = 120; // 10 minutes max
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < maxPolls; i++) {
|
|
|
|
|
|
await new Promise(r => setTimeout(r, pollInterval));
|
|
|
|
|
|
|
|
|
|
|
|
const statusData = await apiCall('/memory/status');
|
|
|
|
|
|
const cons = statusData.consolidation;
|
|
|
|
|
|
|
|
|
|
|
|
if (!cons.is_running) {
|
|
|
|
|
|
// Consolidation finished
|
|
|
|
|
|
if (cons.last_error) {
|
|
|
|
|
|
status.textContent = '❌ ' + cons.last_error;
|
|
|
|
|
|
status.style.color = '#ff6b6b';
|
|
|
|
|
|
resultDiv.textContent = 'Error: ' + cons.last_error;
|
|
|
|
|
|
resultDiv.style.display = 'block';
|
|
|
|
|
|
showNotification('Consolidation failed: ' + cons.last_error, 'error');
|
|
|
|
|
|
} else {
|
|
|
|
|
|
status.textContent = '✅ Consolidation complete!';
|
|
|
|
|
|
status.style.color = '#6fdc6f';
|
|
|
|
|
|
resultDiv.textContent = cons.last_result || 'Consolidation finished successfully.';
|
|
|
|
|
|
resultDiv.style.display = 'block';
|
|
|
|
|
|
showNotification('Memory consolidation complete', 'success');
|
|
|
|
|
|
}
|
|
|
|
|
|
refreshMemoryStats();
|
|
|
|
|
|
break;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Still running — update status message
|
|
|
|
|
|
status.textContent = `⏳ Consolidation still running... (${Math.round((i + 1) * pollInterval / 1000)}s elapsed)`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// If we exited the loop without finishing
|
|
|
|
|
|
const finalStatus = await apiCall('/memory/status');
|
|
|
|
|
|
if (finalStatus.consolidation?.is_running) {
|
|
|
|
|
|
status.textContent = '⏳ Consolidation still running — check back later';
|
|
|
|
|
|
status.style.color = '#dcb06f';
|
|
|
|
|
|
}
|
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
|
|
|
|
} else {
|
|
|
|
|
|
status.textContent = '❌ ' + (data.error || 'Consolidation failed');
|
|
|
|
|
|
status.style.color = '#ff6b6b';
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
status.textContent = '❌ Error: ' + err.message;
|
|
|
|
|
|
status.style.color = '#ff6b6b';
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.textContent = '🌙 Run Consolidation';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadFacts() {
|
|
|
|
|
|
const listDiv = document.getElementById('facts-list');
|
|
|
|
|
|
listDiv.innerHTML = '<div style="text-align: center; color: #888; padding: 1rem;">Loading facts...</div>';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall('/memory/facts');
|
|
|
|
|
|
|
|
|
|
|
|
if (!data.success || data.count === 0) {
|
|
|
|
|
|
listDiv.innerHTML = '<div style="text-align: center; color: #666; padding: 2rem;">No declarative facts stored yet.</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let html = '';
|
|
|
|
|
|
data.facts.forEach((fact, i) => {
|
|
|
|
|
|
const source = fact.metadata?.source || 'unknown';
|
|
|
|
|
|
const when = fact.metadata?.when ? new Date(fact.metadata.when * 1000).toLocaleString() : 'unknown';
|
2026-05-12 15:12:49 +03:00
|
|
|
|
const persona = fact.metadata?.persona || 'miku';
|
|
|
|
|
|
const personaBadge = persona === 'evil_miku'
|
|
|
|
|
|
? '<span style="background: #7a2a2a; color: #ff9999; font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: 3px; margin-left: 0.3rem;">😈 Evil Miku</span>'
|
|
|
|
|
|
: '<span style="background: #2a5a7a; color: #99ccff; font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: 3px; margin-left: 0.3rem;">🎤 Miku</span>';
|
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 factDataJson = escapeJsonForAttribute(fact);
|
|
|
|
|
|
html += `
|
|
|
|
|
|
<div class="memory-item" style="background: #242424; padding: 0.6rem 0.8rem; margin-bottom: 0.4rem; border-radius: 4px; border-left: 3px solid #2a9955; display: flex; justify-content: space-between; align-items: flex-start;">
|
|
|
|
|
|
<div style="flex: 1;">
|
2026-05-12 15:12:49 +03:00
|
|
|
|
<div style="color: #ddd; font-size: 0.9rem;">${escapeHtml(fact.content)}${personaBadge}</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 style="color: #666; font-size: 0.75rem; margin-top: 0.3rem;">
|
|
|
|
|
|
Source: ${escapeHtml(source)} · ${when}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="display: flex; gap: 0.3rem; flex-shrink: 0;">
|
|
|
|
|
|
<button data-memory='${factDataJson}' onclick='showEditMemoryModalFromButton(this, "declarative", "${fact.id}")'
|
|
|
|
|
|
style="background: none; border: none; color: #5599cc; cursor: pointer; padding: 0.2rem 0.4rem; font-size: 0.85rem;"
|
|
|
|
|
|
title="Edit this fact">✏️</button>
|
|
|
|
|
|
<button onclick="deleteMemoryPoint('declarative', '${fact.id}', this)"
|
|
|
|
|
|
style="background: none; border: none; color: #993333; cursor: pointer; padding: 0.2rem 0.4rem; font-size: 0.85rem;"
|
|
|
|
|
|
title="Delete this fact">🗑️</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
listDiv.innerHTML = `<div style="color: #888; font-size: 0.8rem; margin-bottom: 0.5rem;">${data.count} facts loaded</div>` + html;
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
listDiv.innerHTML = `<div style="color: #ff6b6b; padding: 1rem;">Error loading facts: ${err.message}</div>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function loadEpisodicMemories() {
|
|
|
|
|
|
const listDiv = document.getElementById('episodic-list');
|
|
|
|
|
|
listDiv.innerHTML = '<div style="text-align: center; color: #888; padding: 1rem;">Loading memories...</div>';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall('/memory/episodic');
|
|
|
|
|
|
|
|
|
|
|
|
if (!data.success || data.count === 0) {
|
|
|
|
|
|
listDiv.innerHTML = '<div style="text-align: center; color: #666; padding: 2rem;">No episodic memories stored yet.</div>';
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let html = '';
|
|
|
|
|
|
data.memories.forEach((mem, i) => {
|
|
|
|
|
|
const source = mem.metadata?.source || 'unknown';
|
|
|
|
|
|
const when = mem.metadata?.when ? new Date(mem.metadata.when * 1000).toLocaleString() : 'unknown';
|
2026-05-12 15:12:49 +03:00
|
|
|
|
const persona = mem.metadata?.persona || 'miku';
|
|
|
|
|
|
const personaBadge = persona === 'evil_miku'
|
|
|
|
|
|
? '<span style="background: #7a2a2a; color: #ff9999; font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: 3px; margin-left: 0.3rem;">😈 Evil Miku</span>'
|
|
|
|
|
|
: '<span style="background: #2a5a7a; color: #99ccff; font-size: 0.7rem; padding: 0.1rem 0.4rem; border-radius: 3px; margin-left: 0.3rem;">🎤 Miku</span>';
|
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 memDataJson = escapeJsonForAttribute(mem);
|
|
|
|
|
|
html += `
|
|
|
|
|
|
<div class="memory-item" style="background: #242424; padding: 0.6rem 0.8rem; margin-bottom: 0.4rem; border-radius: 4px; border-left: 3px solid #2a5599; display: flex; justify-content: space-between; align-items: flex-start;">
|
|
|
|
|
|
<div style="flex: 1;">
|
2026-05-12 15:12:49 +03:00
|
|
|
|
<div style="color: #ddd; font-size: 0.9rem;">${escapeHtml(mem.content)}${personaBadge}</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 style="color: #666; font-size: 0.75rem; margin-top: 0.3rem;">
|
|
|
|
|
|
Source: ${escapeHtml(source)} · ${when}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style="display: flex; gap: 0.3rem; flex-shrink: 0;">
|
|
|
|
|
|
<button data-memory='${memDataJson}' onclick='showEditMemoryModalFromButton(this, "episodic", "${mem.id}")'
|
|
|
|
|
|
style="background: none; border: none; color: #5599cc; cursor: pointer; padding: 0.2rem 0.4rem; font-size: 0.85rem;"
|
|
|
|
|
|
title="Edit this memory">✏️</button>
|
|
|
|
|
|
<button onclick="deleteMemoryPoint('episodic', '${mem.id}', this)"
|
|
|
|
|
|
style="background: none; border: none; color: #993333; cursor: pointer; padding: 0.2rem 0.4rem; font-size: 0.85rem;"
|
|
|
|
|
|
title="Delete this memory">🗑️</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>`;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
listDiv.innerHTML = `<div style="color: #888; font-size: 0.8rem; margin-bottom: 0.5rem;">${data.count} memories loaded</div>` + html;
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
listDiv.innerHTML = `<div style="color: #ff6b6b; padding: 1rem;">Error loading memories: ${err.message}</div>`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteMemoryPoint(collection, pointId, btnElement) {
|
|
|
|
|
|
if (!confirm(`Delete this ${collection} memory point?`)) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall(`/memory/point/${collection}/${pointId}`, 'DELETE');
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
// Remove the row from the UI
|
|
|
|
|
|
const row = btnElement.closest('div[style*="margin-bottom"]');
|
|
|
|
|
|
if (row) row.remove();
|
|
|
|
|
|
showNotification('Memory point deleted', 'success');
|
|
|
|
|
|
refreshMemoryStats();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showNotification('Failed to delete: ' + (data.error || 'Unknown error'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to delete memory point:', err);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Delete All Memories — Multi-step confirmation flow
|
|
|
|
|
|
function onDeleteStep1Change() {
|
|
|
|
|
|
const checked = document.getElementById('delete-checkbox-1').checked;
|
|
|
|
|
|
document.getElementById('delete-step-2').style.display = checked ? 'block' : 'none';
|
|
|
|
|
|
if (!checked) {
|
|
|
|
|
|
document.getElementById('delete-checkbox-2').checked = false;
|
|
|
|
|
|
document.getElementById('delete-step-3').style.display = 'none';
|
|
|
|
|
|
document.getElementById('delete-step-final').style.display = 'none';
|
|
|
|
|
|
document.getElementById('delete-confirmation-input').value = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onDeleteStep2Change() {
|
|
|
|
|
|
const checked = document.getElementById('delete-checkbox-2').checked;
|
|
|
|
|
|
document.getElementById('delete-step-3').style.display = checked ? 'block' : 'none';
|
|
|
|
|
|
document.getElementById('delete-step-final').style.display = checked ? 'block' : 'none';
|
|
|
|
|
|
if (!checked) {
|
|
|
|
|
|
document.getElementById('delete-confirmation-input').value = '';
|
|
|
|
|
|
updateDeleteButton();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onDeleteInputChange() {
|
|
|
|
|
|
updateDeleteButton();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateDeleteButton() {
|
|
|
|
|
|
const input = document.getElementById('delete-confirmation-input').value;
|
|
|
|
|
|
const expected = "Yes, I am deleting Miku's memories fully.";
|
|
|
|
|
|
const btn = document.getElementById('delete-all-btn');
|
|
|
|
|
|
const match = input === expected;
|
|
|
|
|
|
|
|
|
|
|
|
btn.disabled = !match;
|
|
|
|
|
|
btn.style.cursor = match ? 'pointer' : 'not-allowed';
|
|
|
|
|
|
btn.style.opacity = match ? '1' : '0.5';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function executeDeleteAllMemories() {
|
|
|
|
|
|
const input = document.getElementById('delete-confirmation-input').value;
|
|
|
|
|
|
const expected = "Yes, I am deleting Miku's memories fully.";
|
|
|
|
|
|
|
|
|
|
|
|
if (input !== expected) {
|
|
|
|
|
|
showNotification('Confirmation string does not match', 'error');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const btn = document.getElementById('delete-all-btn');
|
|
|
|
|
|
btn.disabled = true;
|
|
|
|
|
|
btn.textContent = '⏳ Deleting...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall('/memory/delete', 'POST', { confirmation: input });
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
showNotification('All memories have been permanently deleted', 'success');
|
|
|
|
|
|
resetDeleteFlow();
|
|
|
|
|
|
refreshMemoryStats();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showNotification('Deletion failed: ' + (data.error || 'Unknown error'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to delete all memories:', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
btn.disabled = false;
|
|
|
|
|
|
btn.textContent = '🗑️ Permanently Delete All Memories';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function resetDeleteFlow() {
|
|
|
|
|
|
document.getElementById('delete-checkbox-1').checked = false;
|
|
|
|
|
|
document.getElementById('delete-checkbox-2').checked = false;
|
|
|
|
|
|
document.getElementById('delete-confirmation-input').value = '';
|
|
|
|
|
|
document.getElementById('delete-step-2').style.display = 'none';
|
|
|
|
|
|
document.getElementById('delete-step-3').style.display = 'none';
|
|
|
|
|
|
document.getElementById('delete-step-final').style.display = 'none';
|
|
|
|
|
|
updateDeleteButton();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Memory Edit/Create Modal Functions
|
|
|
|
|
|
// currentEditMemory declared in core.js
|
|
|
|
|
|
|
|
|
|
|
|
function showEditMemoryModalFromButton(button, collection, pointId) {
|
|
|
|
|
|
const memoryJson = button.getAttribute('data-memory');
|
|
|
|
|
|
// Unescape HTML entities back to JSON
|
|
|
|
|
|
const unescapedJson = memoryJson
|
|
|
|
|
|
.replace(/"/g, '"')
|
|
|
|
|
|
.replace(/'/g, "'")
|
|
|
|
|
|
.replace(/</g, '<')
|
|
|
|
|
|
.replace(/>/g, '>')
|
|
|
|
|
|
.replace(/&/g, '&');
|
|
|
|
|
|
const memory = JSON.parse(unescapedJson);
|
|
|
|
|
|
showEditMemoryModal(collection, pointId, memory);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showEditMemoryModal(collection, pointId, memoryData) {
|
|
|
|
|
|
const memory = typeof memoryData === 'string' ? JSON.parse(memoryData) : memoryData;
|
|
|
|
|
|
currentEditMemory = { collection, pointId, memory };
|
|
|
|
|
|
|
|
|
|
|
|
const modal = document.getElementById('edit-memory-modal');
|
|
|
|
|
|
const contentField = document.getElementById('edit-memory-content');
|
|
|
|
|
|
const sourceField = document.getElementById('edit-memory-source');
|
|
|
|
|
|
|
|
|
|
|
|
contentField.value = memory.content || '';
|
|
|
|
|
|
sourceField.value = memory.metadata?.source || '';
|
|
|
|
|
|
|
|
|
|
|
|
modal.style.display = 'flex';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeEditMemoryModal() {
|
|
|
|
|
|
document.getElementById('edit-memory-modal').style.display = 'none';
|
|
|
|
|
|
currentEditMemory = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function saveMemoryEdit() {
|
|
|
|
|
|
if (!currentEditMemory) return;
|
|
|
|
|
|
|
|
|
|
|
|
const content = document.getElementById('edit-memory-content').value.trim();
|
|
|
|
|
|
const source = document.getElementById('edit-memory-source').value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
if (!content) {
|
|
|
|
|
|
showNotification('Content cannot be empty', 'error');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const { collection, pointId } = currentEditMemory;
|
|
|
|
|
|
const saveBtn = document.querySelector('#edit-memory-modal button[onclick="saveMemoryEdit()"]');
|
|
|
|
|
|
saveBtn.disabled = true;
|
|
|
|
|
|
saveBtn.textContent = 'Saving...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall(`/memory/point/${collection}/${pointId}`, 'PUT', {
|
|
|
|
|
|
content: content,
|
|
|
|
|
|
metadata: { source: source || 'manual_edit' }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
showNotification('Memory updated successfully', 'success');
|
|
|
|
|
|
closeEditMemoryModal();
|
|
|
|
|
|
// Reload the appropriate list
|
|
|
|
|
|
if (collection === 'declarative') {
|
|
|
|
|
|
loadFacts();
|
|
|
|
|
|
} else if (collection === 'episodic') {
|
|
|
|
|
|
loadEpisodicMemories();
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showNotification('Failed to update: ' + (data.error || 'Unknown error'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to save memory edit:', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saveBtn.disabled = false;
|
|
|
|
|
|
saveBtn.textContent = 'Save Changes';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function showCreateMemoryModal(collection) {
|
|
|
|
|
|
const modal = document.getElementById('create-memory-modal');
|
|
|
|
|
|
document.getElementById('create-memory-collection').value = collection;
|
|
|
|
|
|
document.getElementById('create-memory-content').value = '';
|
|
|
|
|
|
document.getElementById('create-memory-user-id').value = '';
|
|
|
|
|
|
document.getElementById('create-memory-source').value = 'manual';
|
|
|
|
|
|
|
|
|
|
|
|
// Update modal title based on collection type
|
|
|
|
|
|
const title = collection === 'declarative' ? 'Add New Fact' : 'Add New Memory';
|
|
|
|
|
|
document.querySelector('#create-memory-modal h3').textContent = title;
|
|
|
|
|
|
|
|
|
|
|
|
modal.style.display = 'flex';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function closeCreateMemoryModal() {
|
|
|
|
|
|
document.getElementById('create-memory-modal').style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Modal keyboard and backdrop close handlers
|
|
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
|
|
|
|
if (e.key === 'Escape') {
|
|
|
|
|
|
const editModal = document.getElementById('edit-memory-modal');
|
|
|
|
|
|
const createModal = document.getElementById('create-memory-modal');
|
|
|
|
|
|
if (editModal && editModal.style.display !== 'none') closeEditMemoryModal();
|
|
|
|
|
|
if (createModal && createModal.style.display !== 'none') closeCreateMemoryModal();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
async function saveNewMemory() {
|
|
|
|
|
|
const collection = document.getElementById('create-memory-collection').value;
|
|
|
|
|
|
const content = document.getElementById('create-memory-content').value.trim();
|
|
|
|
|
|
const userId = document.getElementById('create-memory-user-id').value.trim();
|
|
|
|
|
|
const source = document.getElementById('create-memory-source').value.trim();
|
|
|
|
|
|
|
|
|
|
|
|
if (!content) {
|
|
|
|
|
|
showNotification('Content cannot be empty', 'error');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const createBtn = document.querySelector('#create-memory-modal button[onclick="saveNewMemory()"]');
|
|
|
|
|
|
createBtn.disabled = true;
|
|
|
|
|
|
createBtn.textContent = 'Creating...';
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = await apiCall('/memory/create', 'POST', {
|
|
|
|
|
|
collection: collection,
|
|
|
|
|
|
content: content,
|
|
|
|
|
|
user_id: userId || null,
|
|
|
|
|
|
source: source || 'manual',
|
|
|
|
|
|
metadata: {}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (data.success) {
|
|
|
|
|
|
showNotification(`${collection === 'declarative' ? 'Fact' : 'Memory'} created successfully`, 'success');
|
|
|
|
|
|
closeCreateMemoryModal();
|
|
|
|
|
|
// Reload the appropriate list
|
|
|
|
|
|
if (collection === 'declarative') {
|
|
|
|
|
|
loadFacts();
|
|
|
|
|
|
} else if (collection === 'episodic') {
|
|
|
|
|
|
loadEpisodicMemories();
|
|
|
|
|
|
}
|
|
|
|
|
|
refreshMemoryStats();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
showNotification('Failed to create: ' + (data.error || 'Unknown error'), 'error');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to save new memory:', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
createBtn.disabled = false;
|
|
|
|
|
|
createBtn.textContent = 'Create Memory';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Search/Filter Function
|
|
|
|
|
|
function filterMemories(listId, searchTerm) {
|
|
|
|
|
|
const listDiv = document.getElementById(listId);
|
|
|
|
|
|
const items = listDiv.querySelectorAll('.memory-item');
|
|
|
|
|
|
const term = searchTerm.toLowerCase().trim();
|
|
|
|
|
|
|
|
|
|
|
|
items.forEach(item => {
|
|
|
|
|
|
const content = item.textContent.toLowerCase();
|
|
|
|
|
|
if (term === '' || content.includes(term)) {
|
|
|
|
|
|
item.style.display = 'flex';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
item.style.display = 'none';
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|