Files
miku-discord/bot/static/index.html

1359 lines
74 KiB
HTML
Raw Normal View History

2025-12-07 17:15:09 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2025-12-07 17:15:09 +02:00
<title>Miku Control Panel</title>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/cropperjs/1.6.2/cropper.min.js"></script>
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
<link rel="stylesheet" href="/static/css/style.css">
2025-12-07 17:15:09 +02:00
</head>
<body>
<div class="panel">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
2026-01-09 00:03:59 +02:00
<div style="display: flex; gap: 1rem; align-items: center;">
<h1 id="panel-title">Miku Control Panel</h1>
<button id="gpu-selector-toggle" onclick="toggleGPU()" style="background: #2a5599; color: #fff; padding: 0.5rem 1rem; border: 2px solid #4a7bc9; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9rem;">
🎮 GPU: NVIDIA
</button>
</div>
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button id="bipolar-mode-toggle" onclick="toggleBipolarMode()" style="background: #333; color: #fff; padding: 0.5rem 1rem; border: 2px solid #666; border-radius: 4px; cursor: pointer; font-weight: bold;">
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
🔄 Bipolar: OFF
</button>
<button id="evil-mode-toggle" onclick="toggleEvilMode()" style="background: #333; color: #fff; padding: 0.5rem 1rem; border: 2px solid #666; border-radius: 4px; cursor: pointer; font-weight: bold;">
😈 Evil Mode: OFF
</button>
</div>
</div>
2025-12-07 17:15:09 +02:00
<p style="color: #ccc; margin-bottom: 2rem;">
💬 <strong>DM Support:</strong> Users can message Miku directly in DMs. She responds to every message using the DM mood (auto-rotating every 2 hours).
</p>
<!-- Tab Navigation -->
<div class="tab-container">
<div class="tab-buttons">
<button class="tab-button active" data-tab="tab1" onclick="switchTab('tab1')">Server Management</button>
<button class="tab-button" data-tab="tab2" onclick="switchTab('tab2')">Actions</button>
<button class="tab-button" data-tab="tab3" onclick="switchTab('tab3')">Status</button>
<button class="tab-button" data-tab="tab10" onclick="switchTab('tab10')">📱 DM Management</button>
<button class="tab-button" data-tab="tab4" onclick="switchTab('tab4')">⚙️ LLM Settings</button>
<button class="tab-button" data-tab="tab5" onclick="switchTab('tab5')">🎨 Image Generation</button>
<button class="tab-button" data-tab="tab6" onclick="switchTab('tab6')">📊 Autonomous Stats</button>
<button class="tab-button" data-tab="tab7" onclick="switchTab('tab7')">💬 Chat with LLM</button>
<button class="tab-button" data-tab="tab8" onclick="switchTab('tab8')">📞 Voice Call</button>
<button class="tab-button" data-tab="tab9" onclick="switchTab('tab9')">🧠 Memories</button>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
<button class="tab-button" data-tab="tab11" onclick="switchTab('tab11')">🖼️ Profile Picture</button>
<button class="tab-button" onclick="window.location.href='/static/system.html'">🎛️ System Settings</button>
</div>
2025-12-07 17:15:09 +02:00
<!-- Tab 1 Content -->
<div id="tab1" class="tab-content active">
<div class="section">
<label for="mood">Mood:</label>
<select id="mood">
<option value="">Loading moods...</option>
2025-12-07 17:15:09 +02:00
</select>
<button onclick="setMood()">Set Mood</button>
<button onclick="resetMood()">Reset Mood</button>
<button onclick="calmMiku()">Calm</button>
</div>
<div class="section">
<h3>Server Management</h3>
<div id="servers-list"></div>
<div class="add-server-form">
<h4>Add New Server</h4>
<div class="form-row">
<div class="form-group">
<label>Guild ID:</label>
<input type="number" id="new-guild-id" placeholder="Discord Server ID">
</div>
<div class="form-group">
<label>Server Name:</label>
<input type="text" id="new-guild-name" placeholder="Server Name">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Autonomous Channel ID:</label>
<input type="number" id="new-autonomous-channel-id" placeholder="Channel ID">
</div>
<div class="form-group">
<label>Channel Name:</label>
<input type="text" id="new-autonomous-channel-name" placeholder="Channel Name">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Bedtime Channel IDs (comma-separated):</label>
<input type="text" id="new-bedtime-channel-ids" placeholder="Channel IDs">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Enabled Features:</label>
<div class="checkbox-group">
<div class="checkbox-item">
<input type="checkbox" id="feature-autonomous" checked>
<label for="feature-autonomous">Autonomous</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="feature-bedtime" checked>
<label for="feature-bedtime">Bedtime</label>
</div>
<div class="checkbox-item">
<input type="checkbox" id="feature-monday-video" checked>
<label for="feature-monday-video">Monday Video</label>
</div>
</div>
</div>
</div>
<button onclick="addServer()">Add Server</button>
</div>
<div style="margin-top: 1rem;">
<button onclick="repairConfig()" style="background: #ff9800;">🔧 Repair Configuration</button>
<p style="font-size: 0.9rem; color: #ccc; margin-top: 0.5rem;">
Use this if you're seeing incorrect server IDs or other configuration issues
</p>
</div>
</div>
</div>
<!-- Actions Tab Content -->
<div id="tab2" class="tab-content">
<div class="section">
<h3>Autonomous Actions</h3>
<div style="margin-bottom: 1rem;">
<label for="server-select">Target Server:</label>
<select id="server-select">
<option value="all">All Servers</option>
</select>
</div>
<button onclick="triggerAutonomous('general')">Say Something General</button>
<!-- Engage User Submenu -->
<div style="margin-bottom: 1rem;">
<button onclick="toggleEngageSubmenu()">Engage User ▼</button>
<div id="engage-submenu" style="display: none; margin-left: 1rem; margin-top: 0.5rem; padding: 1rem; background: #1e1e1e; border: 1px solid #444; border-radius: 4px;">
<div style="margin-bottom: 0.5rem;">
<label for="engage-user-id" style="display: block; margin-bottom: 0.3rem;">User ID (leave empty for random):</label>
<input type="text" id="engage-user-id" placeholder="User ID" style="width: 200px;">
</div>
<div style="margin-bottom: 0.5rem;">
<label style="display: block; margin-bottom: 0.3rem;">Engagement Type:</label>
<div style="margin-left: 0.5rem;">
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="random" checked> Random (auto-detect)
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="activity"> Activity-based (comment on what they're doing)
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="general"> General conversation
</label>
<label style="display: block; margin-bottom: 0.2rem;">
<input type="radio" name="engage-type" value="status"> Status-based (online/idle/invisible)
</label>
</div>
</div>
<button onclick="triggerEngageUser()">🚀 Engage User</button>
</div>
</div>
<!-- Share Tweet Submenu -->
<div style="margin-bottom: 1rem;">
<button onclick="toggleTweetSubmenu()">Share Tweet ▼</button>
<div id="tweet-submenu" style="display: none; margin-left: 1rem; margin-top: 0.5rem; padding: 1rem; background: #1e1e1e; border: 1px solid #444; border-radius: 4px;">
<div style="margin-bottom: 0.5rem;">
<label for="tweet-url" style="display: block; margin-bottom: 0.3rem;">Tweet URL (leave empty for auto-fetch):</label>
<input type="text" id="tweet-url" placeholder="https://x.com/... or https://twitter.com/... or https://fxtwitter.com/..." style="width: 100%;">
</div>
<button onclick="triggerShareTweet()">🐦 Share Tweet</button>
</div>
</div>
2025-12-07 17:15:09 +02:00
<button onclick="triggerAutonomous('reaction')">React to Message</button>
<button onclick="triggerAutonomous('join-conversation')">Detect and Join Conversation</button>
2025-12-07 17:15:09 +02:00
<button onclick="toggleCustomPrompt()">Custom Prompt</button>
</div>
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
<!-- Bipolar Mode Section (only visible when bipolar mode is on) -->
<div id="bipolar-section" class="section" style="display: none; border: 2px solid #9932CC; padding: 1rem; border-radius: 8px; background: #1a1a2e;">
<h3 style="color: #9932CC;">🔄 Bipolar Mode Controls</h3>
2026-01-09 00:03:59 +02:00
<p style="font-size: 0.9rem; color: #aaa;">Trigger arguments or dialogues between Regular Miku and Evil Miku</p>
<!-- Persona Dialogue Section -->
<div style="margin-bottom: 2rem; padding: 1rem; background: #252540; border-radius: 8px; border: 1px solid #555;">
<h4 style="color: #6B8EFF; margin-bottom: 0.5rem;">💬 Trigger Persona Dialogue</h4>
<p style="font-size: 0.85rem; color: #999; margin-bottom: 1rem;">Start a natural conversation between the personas (can escalate to argument if tension builds)</p>
<div style="margin-bottom: 1rem;">
<label for="dialogue-message-id">Message ID:</label>
<input type="text" id="dialogue-message-id" placeholder="e.g., 1234567890123456789" style="width: 250px; margin-left: 0.5rem; font-family: monospace;">
</div>
<div style="font-size: 0.8rem; color: #888; margin-bottom: 1rem;">
💡 <strong>Tip:</strong> Right-click any bot response message in Discord and select "Copy Message ID". The opposite persona will analyze it and decide whether to interject.
</div>
<button onclick="triggerPersonaDialogue()" style="background: #6B8EFF; color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer;">
💬 Trigger Dialogue
</button>
<div id="dialogue-status" style="margin-top: 1rem; font-size: 0.9rem;"></div>
</div>
<!-- Argument Section -->
<div style="padding: 1rem; background: #2e1a2e; border-radius: 8px; border: 1px solid #555;">
<h4 style="color: #9932CC; margin-bottom: 0.5rem;">⚔️ Trigger Argument</h4>
<p style="font-size: 0.85rem; color: #999; margin-bottom: 1rem;">Force an immediate argument (bypasses dialogue system)</p>
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
<div style="margin-bottom: 1rem; display: flex; gap: 1rem; flex-wrap: wrap;">
<div>
<label for="bipolar-channel-id">Channel ID:</label>
<input type="text" id="bipolar-channel-id" placeholder="e.g., 1234567890123456789" style="width: 200px; margin-left: 0.5rem; font-family: monospace;">
</div>
<div>
<label for="bipolar-message-id">Starting Message ID (optional):</label>
<input type="text" id="bipolar-message-id" placeholder="e.g., 1234567890123456789" style="width: 200px; margin-left: 0.5rem; font-family: monospace;">
</div>
</div>
<div style="font-size: 0.8rem; color: #888; margin-bottom: 1rem;">
💡 <strong>Tip:</strong> Right-click a message in Discord and select "Copy Message ID" (enable Developer Mode in Discord settings).
If a starting message ID is provided, the opposite persona will respond to that message.
</div>
<div style="margin-bottom: 1rem;">
<label for="bipolar-context">Argument Context (optional):</label>
<input type="text" id="bipolar-context" placeholder="e.g., They're fighting about who's the real Miku..." style="width: 100%; margin-top: 0.3rem;">
</div>
<div style="margin-bottom: 1rem;">
<label for="bipolar-topic">Argument Topic (optional — overrides random topic):</label>
<input type="text" id="bipolar-topic" placeholder="e.g., Who deserves the spotlight? A philosophical debate about worth..." style="width: 100%; margin-top: 0.3rem;">
<div style="font-size: 0.75rem; color: #777; margin-top: 0.2rem;">
Leave blank for random topic selection. Enter a custom theme to frame the argument uniquely.
</div>
</div>
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
<button onclick="triggerBipolarArgument()" style="background: #9932CC; color: #fff; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer;">
⚔️ Trigger Argument
</button>
<div id="bipolar-status" style="margin-top: 1rem; font-size: 0.9rem;"></div>
2026-01-09 00:03:59 +02:00
</div>
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
<!-- Scoreboard Display -->
<div id="bipolar-scoreboard" style="margin-top: 1.5rem; padding: 1rem; background: #0f0f1e; border-radius: 8px; border: 1px solid #444;">
<h4 style="color: #9932CC; margin-bottom: 0.5rem;">🏆 Argument Scoreboard</h4>
<div id="scoreboard-content" style="font-size: 0.9rem;">
<p style="color: #888;">Loading scoreboard...</p>
</div>
<button onclick="loadScoreboard()" style="margin-top: 0.5rem; background: #444; color: #fff; border: none; padding: 0.3rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.85rem;">
🔄 Refresh
</button>
</div>
<div id="active-arguments" style="margin-top: 1rem; display: none;">
<h4>Active Arguments:</h4>
<div id="active-arguments-list"></div>
</div>
</div>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
2025-12-07 17:15:09 +02:00
<div class="section">
<h3>Figurine DM Subscribers</h3>
<!-- Subscriber Management -->
<div style="margin-bottom: 1rem;">
<h4>Subscriber Management</h4>
<div style="margin-bottom: 0.5rem;">
<button onclick="refreshFigurineSubscribers()">🔄 Refresh</button>
</div>
<div style="display: flex; gap: 10px; align-items: end; margin-bottom: 0.5rem;">
<div>
<label for="figurine-user-id">User ID:</label>
<input type="text" id="figurine-user-id" placeholder="Discord User ID (as string)" />
</div>
<button onclick="addFigurineSubscriber()"> Add Subscriber</button>
</div>
<div id="figurine-subscribers-list"></div>
</div>
<!-- Send to All Subscribers -->
<div style="margin-bottom: 1rem; border-top: 1px solid #444; padding-top: 1rem;">
<h4>Send to All Subscribers</h4>
<div style="margin-bottom: 0.5rem;">
<label for="figurine-tweet-url-all">Tweet URL (optional):</label>
<input type="text" id="figurine-tweet-url-all" placeholder="https://twitter.com/username/status/..." style="width: 300px;" />
</div>
<button onclick="sendFigurineNowToAll()">📨 Send to All Subscribers</button>
<div id="figurine-all-status" style="margin-top: 0.5rem; font-size: 0.9rem;"></div>
</div>
<!-- Send to Single User -->
<div style="border-top: 1px solid #444; padding-top: 1rem;">
<h4>Send to Single User</h4>
<div style="display: flex; gap: 10px; align-items: end; margin-bottom: 0.5rem;">
<div>
<label for="figurine-single-user-id">User ID:</label>
<input type="text" id="figurine-single-user-id" placeholder="Discord User ID" />
</div>
<div>
<label for="figurine-tweet-url-single">Tweet URL (optional):</label>
<input type="text" id="figurine-tweet-url-single" placeholder="https://twitter.com/username/status/..." style="width: 250px;" />
</div>
<button onclick="sendFigurineToSingleUser()">📨 Send to User</button>
</div>
<div id="figurine-single-status" style="margin-top: 0.5rem; font-size: 0.9rem;"></div>
</div>
</div>
<div class="section">
<h3>Manual Actions</h3>
<div style="margin-bottom: 1rem;">
<label for="manual-server-select">Target Server:</label>
<select id="manual-server-select">
<option value="all">All Servers</option>
</select>
</div>
<button onclick="forceSleep()">Force Sleep</button>
<button onclick="wakeUp()">Wake Up</button>
<button onclick="sendBedtime()">Send Bedtime</button>
<button onclick="resetConversation()">Reset Conversation</button>
</div>
<div class="section" id="custom-prompt-section">
<h3>🎙️ Send Custom Prompt to Miku</h3>
<!-- Target Selection -->
<div style="margin-bottom: 1rem;">
<label for="custom-prompt-target-type">Target Type:</label>
<select id="custom-prompt-target-type" onchange="toggleCustomPromptTarget()" style="margin-right: 1rem;">
<option value="server">Server</option>
<option value="dm">Direct Message</option>
</select>
<!-- Server Selection -->
<span id="custom-prompt-server-section">
<label for="custom-prompt-server-select">Target Server:</label>
<select id="custom-prompt-server-select">
<option value="all">All Servers</option>
</select>
</span>
<!-- DM User ID Input -->
<span id="custom-prompt-dm-section" style="display: none;">
<label for="custom-prompt-user-id">User ID:</label>
<input type="text" id="custom-prompt-user-id" placeholder="Discord User ID" style="width: 200px;" />
</span>
</div>
<div>
<label for="customPrompt">Custom Prompt:</label>
<textarea id="customPrompt" placeholder="e.g. Talk about how nice the weather is today" rows="3" style="width: 100%; margin-top: 0.5rem;"></textarea>
</div>
<div style="margin-top: 0.5rem;">
<label for="customPromptAttachment">Attach File (optional):</label>
<input type="file" id="customPromptAttachment" multiple />
</div>
<button onclick="sendCustomPrompt()" style="margin-top: 0.5rem;">Send Custom Prompt</button>
<p id="customStatus" style="color: green; margin-top: 0.5rem;"></p>
</div>
<div class="section" id="manual-message-section">
<h3>🎭 Send Message as Miku (Manual Override)</h3>
<!-- Webhook Option -->
<div style="margin-bottom: 1rem; padding: 0.5rem; background: #2a2a2a; border-radius: 4px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="checkbox" id="manual-use-webhook" onchange="toggleWebhookOptions()" style="margin-right: 0.5rem;" />
<span>Send as Webhook (allows choosing persona)</span>
</label>
<div id="webhook-persona-options" style="display: none; margin-top: 0.5rem; padding-left: 1.5rem;">
<label style="display: block; margin-bottom: 0.3rem;">
<input type="radio" name="webhook-persona" value="miku" checked style="margin-right: 0.5rem;" />
Hatsune Miku 💙 (with mood emoji)
</label>
<label style="display: block;">
<input type="radio" name="webhook-persona" value="evil" style="margin-right: 0.5rem;" />
Evil Miku 😈 (with mood emoji)
</label>
<p style="font-size: 0.8rem; color: #888; margin: 0.3rem 0 0 0;">
Note: Webhooks only work in channels, not DMs. Profile picture and mood emoji will be used.
</p>
</div>
</div>
2025-12-07 17:15:09 +02:00
<!-- Target Selection -->
<div style="margin-bottom: 1rem;">
<label for="manual-target-type">Target Type:</label>
<select id="manual-target-type" onchange="toggleManualMessageTarget()">
<option value="channel">Channel</option>
<option value="dm">Direct Message</option>
</select>
</div>
<div>
<label for="manualMessage">Message:</label>
<textarea id="manualMessage" placeholder="Type the message exactly as Miku should say it..." rows="3" style="width: 100%; margin-top: 0.5rem;"></textarea>
</div>
<div style="margin-top: 0.5rem;">
<label for="manualAttachment">Attach Files (optional):</label>
<input type="file" id="manualAttachment" multiple />
</div>
<!-- Channel ID Input -->
<div id="manual-channel-section" style="margin-top: 0.5rem;">
<label for="manualChannelId">Channel ID:</label>
<input type="text" id="manualChannelId" placeholder="Enter channel ID..." style="width: 100%;" />
</div>
<!-- User ID Input -->
<div id="manual-dm-section" style="margin-top: 0.5rem; display: none;">
<label for="manualUserId">User ID:</label>
<input type="text" id="manualUserId" placeholder="Enter user ID for DM..." style="width: 100%;" />
</div>
<!-- Reply Configuration -->
<div style="margin-top: 0.5rem;">
<label for="manualReplyMessageId">Reply to Message ID (optional):</label>
<input type="text" id="manualReplyMessageId" placeholder="Enter message ID to reply to..." style="width: 100%;" />
</div>
<div style="margin-top: 0.5rem;">
<label style="margin-right: 1rem;">Mention user in reply:</label>
<label>
<input type="radio" name="manualReplyMention" value="true" checked />
Yes (ping user)
</label>
<label style="margin-left: 1rem;">
<input type="radio" name="manualReplyMention" value="false" />
No (silent reply)
</label>
</div>
2025-12-07 17:15:09 +02:00
<button onclick="sendManualMessage()" style="margin-top: 0.5rem;">Send as Miku</button>
<p id="manualStatus" style="color: green; margin-top: 0.5rem;"></p>
</div>
<div class="section" id="message-reaction-section">
<h3>😊 Add Reaction to Message</h3>
<p style="color: #ccc; margin-bottom: 1rem;">
Make Miku react to a specific message with an emoji of your choice.
</p>
<div style="margin-bottom: 1rem;">
<label for="reactionMessageId">Message ID:</label>
<input type="text" id="reactionMessageId" placeholder="Enter message ID (right-click message > Copy ID)" style="width: 100%; margin-top: 0.5rem;" />
</div>
<div style="margin-bottom: 1rem;">
<label for="reactionChannelId">Channel ID:</label>
<input type="text" id="reactionChannelId" placeholder="Enter channel ID (right-click channel > Copy ID)" style="width: 100%; margin-top: 0.5rem;" />
</div>
<div style="margin-bottom: 1rem;">
<label for="reactionEmoji">Emoji:</label>
<input type="text" id="reactionEmoji" placeholder="Enter emoji (e.g., 💙, 👍, 🎉)" style="width: 100%; margin-top: 0.5rem;" />
<p style="font-size: 0.85rem; color: #aaa; margin-top: 0.25rem;">
You can use standard emoji or custom server emoji format (:emoji_name: for custom ones)
</p>
</div>
<button onclick="addReactionToMessage()" style="margin-top: 0.5rem;">Add Reaction</button>
<p id="reactionStatus" style="color: green; margin-top: 0.5rem;"></p>
</div>
</div>
<!-- Status Tab Content -->
<div id="tab3" class="tab-content">
<div class="section">
<h3>Status</h3>
<div id="status"></div>
</div>
<!-- Mood Activities Section (collapsible) -->
<div class="section">
<div style="cursor: pointer; user-select: none;" onclick="activitiesToggle()">
<h3 style="display: inline;"><span id="activities-toggle-icon"></span> 🎵 Mood Activities</h3>
<span id="activities-summary" style="color: #888; font-size: 0.85rem; margin-left: 0.5rem;"></span>
</div>
<div id="activities-body" style="display: none; margin-top: 1rem;">
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem; align-items: center;">
<button onclick="activitiesLoad()" style="background: #4a7bc9;">🔄 Reload from Disk</button>
<span id="activities-status" style="font-size: 0.85rem; color: #61dafb;"></span>
</div>
<!-- Current Activity Override Panel -->
<div style="margin-bottom: 1rem; padding: 0.75rem; background: #252535; border-radius: 6px; border: 1px solid #3a3a5a;">
<h4 style="margin: 0 0 0.5rem 0; color: #61dafb; font-size: 0.95rem;">🎯 Current Activity</h4>
<div id="activity-override-status" style="margin-bottom: 0.5rem; font-size: 0.85rem; color: #aaa;">
Loading...
</div>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.5rem;">
<button onclick="activityRefreshCurrent()" style="background: #555; font-size: 0.8rem; padding: 0.3rem 0.6rem;">🔄 Refresh</button>
<button onclick="activityReleaseAuto()" style="background: #27ae60; font-size: 0.8rem; padding: 0.3rem 0.6rem;">🌀 Return to Auto</button>
</div>
<details style="margin-top: 0.5rem;">
<summary style="cursor: pointer; font-size: 0.85rem; color: #61dafb;">✏️ Manual Override</summary>
<div style="margin-top: 0.5rem; display: flex; gap: 0.4rem; flex-wrap: wrap; align-items: center;">
<select id="act-manual-type" style="width: 120px; padding: 0.3rem;">
<option value="listening">🎵 Listening</option>
<option value="playing">🎮 Playing</option>
<option value="watching">📺 Watching</option>
<option value="competing">🏆 Competing</option>
<option value="streaming">🔴 Streaming</option>
</select>
<input type="text" id="act-manual-name" placeholder="Activity name" style="flex: 2; min-width: 120px; padding: 0.3rem;">
<input type="text" id="act-manual-state" placeholder="Detail (optional)" style="flex: 1; min-width: 80px; padding: 0.3rem;">
<input type="text" id="act-manual-url" placeholder="URL (streaming)" style="flex: 1; min-width: 80px; padding: 0.3rem; display: none;">
<button onclick="activitySetManual()" style="background: #e67e22; font-size: 0.8rem; padding: 0.3rem 0.8rem;">Set</button>
<button onclick="activityClearManual()" style="background: #c0392b; font-size: 0.8rem; padding: 0.3rem 0.8rem;">Clear</button>
</div>
</details>
</div>
<!-- Normal Moods subsection -->
<div style="margin-bottom: 1.5rem;">
<div style="cursor: pointer; user-select: none; padding: 0.5rem; background: #2a2a2a; border-radius: 4px; margin-bottom: 0.5rem;" onclick="activitiesSectionToggle('normal')">
<strong><span id="activities-normal-icon"></span> 😇 Normal Moods</strong>
</div>
<div id="activities-normal-body" style="display: none; padding-left: 0.5rem;">
<div id="activities-normal-list"></div>
</div>
</div>
<!-- Evil Moods subsection -->
<div style="margin-bottom: 1.5rem;">
<div style="cursor: pointer; user-select: none; padding: 0.5rem; background: #2a2a2a; border-radius: 4px; margin-bottom: 0.5rem;" onclick="activitiesSectionToggle('evil')">
<strong><span id="activities-evil-icon"></span> 😈 Evil Moods</strong>
</div>
<div id="activities-evil-body" style="display: none; padding-left: 0.5rem;">
<div id="activities-evil-list"></div>
</div>
</div>
</div>
</div>
<div class="section">
<h3>Last Prompt</h3>
<div style="margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.75rem;">
<label style="font-size: 0.9rem; color: #aaa;">Source:</label>
<div style="display: inline-flex; border-radius: 6px; overflow: hidden; border: 1px solid #444;">
<button id="prompt-src-cat" class="prompt-source-btn active" onclick="switchPromptSource('cat')"
style="padding: 0.4rem 1rem; border: none; cursor: pointer; font-size: 0.85rem; transition: all 0.2s;">
🐱 Cheshire Cat
</button>
<button id="prompt-src-fallback" class="prompt-source-btn" onclick="switchPromptSource('fallback')"
style="padding: 0.4rem 1rem; border: none; cursor: pointer; font-size: 0.85rem; transition: all 0.2s;">
🤖 Bot Fallback
</button>
</div>
</div>
<div id="prompt-cat-info" style="margin-bottom: 0.5rem; font-size: 0.85rem; color: #aaa;"></div>
<pre id="last-prompt" style="white-space: pre-wrap; word-break: break-word;"></pre>
</div>
</div>
2025-12-07 17:15:09 +02:00
<!-- DM Management Tab Content -->
<div id="tab10" class="tab-content">
2025-12-07 17:15:09 +02:00
<div class="section">
<h3>📱 DM Management</h3>
2025-12-07 17:15:09 +02:00
<div style="margin-bottom: 1rem;">
<button onclick="loadDMUsers()">🔄 Refresh DM Users</button>
<button onclick="exportAllDMs()">📤 Export All DMs</button>
<button onclick="loadBlockedUsers()" style="background: #ff9800;">🚫 View Blocked Users</button>
</div>
<div style="margin-bottom: 1rem; padding: 1rem; background: #2a2a2a; border-radius: 4px;">
<h4 style="margin-top: 0;">📊 DM Interaction Analysis</h4>
<button onclick="runDailyAnalysis()" style="background: #9c27b0;">🔍 Run Daily Analysis Now</button>
<button onclick="viewAnalysisReports()" style="background: #673ab7;">📄 View All Reports</button>
<p style="font-size: 0.85rem; margin: 0.5rem 0 0 0; color: #aaa;">
Analysis runs automatically at 2 AM daily. Reports one user per day.
</p>
</div>
<div id="dm-users-list"></div>
<div class="section" id="blocked-users-section" style="display: none; margin-top: 2rem;">
<h4>🚫 Blocked Users</h4>
<div style="margin-bottom: 1rem;">
<button onclick="hideBlockedUsers()">← Back to DM Users</button>
</div>
<div id="blocked-users-list"></div>
</div>
</div>
</div>
<!-- LLM Settings Tab Content -->
2025-12-07 17:15:09 +02:00
<div id="tab4" class="tab-content">
<div class="section">
<h3>⚙️ Language Model Settings</h3>
<p>Configure language model behavior and language mode.</p>
<!-- Language Mode Section -->
<div style="margin-bottom: 1.5rem; padding: 1rem; background: #2a2a2a; border-radius: 4px; border: 2px solid #4a7bc9;">
<h4 style="margin-top: 0; color: #61dafb;">🌐 Language Mode</h4>
<p style="margin: 0.5rem 0; color: #aaa;">Switch Miku between English and Japanese responses.</p>
<div style="margin: 1rem 0;">
<div style="margin-bottom: 1rem;">
<strong>Current Language:</strong> <span id="current-language-display" style="color: #61dafb; font-weight: bold;">English</span>
</div>
<button onclick="toggleLanguageMode()" style="background: #4a7bc9; color: #fff; padding: 0.6rem 1.2rem; border: 2px solid #61dafb; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 1rem;">
🔄 Toggle Language (English ↔ Japanese)
</button>
</div>
<div style="margin-top: 1rem; padding: 1rem; background: #1a1a1a; border-radius: 4px; border-left: 3px solid #4a7bc9;">
<div style="font-size: 0.9rem;">
<div style="margin-bottom: 0.5rem;"><strong>English Mode:</strong></div>
<ul style="margin: 0 0 0.5rem 0; padding-left: 1.5rem; color: #aaa;">
<li>Uses standard Llama 3.1 model</li>
<li>Responds in English only</li>
</ul>
<div style="margin-bottom: 0.5rem;"><strong>Japanese Mode (日本語):</strong></div>
<ul style="margin: 0 0 0; padding-left: 1.5rem; color: #aaa;">
<li>Uses Llama 3.1 Swallow model (trained for Japanese)</li>
<li>Responds entirely in Japanese</li>
</ul>
</div>
</div>
</div>
<!-- Language Mode Status Section -->
<div style="margin-bottom: 1.5rem; padding: 1rem; background: #2a2a2a; border-radius: 4px;">
<h4 style="margin-top: 0;">📊 Current Status</h4>
<div id="language-status-display" style="background: #1a1a1a; padding: 1rem; border-radius: 4px; font-family: monospace; font-size: 0.9rem;">
<p style="margin: 0.5rem 0;"><strong>Language Mode:</strong> <span id="status-language">English</span></p>
<p style="margin: 0.5rem 0;"><strong>Active Model:</strong> <span id="status-model">llama3.1</span></p>
<p style="margin: 0.5rem 0;"><strong>Available Languages:</strong> English, 日本語 (Japanese)</p>
</div>
<button onclick="refreshLanguageStatus()" style="margin-top: 1rem;">🔄 Refresh Status</button>
</div>
<!-- Information Section -->
<div style="padding: 1rem; background: #1a1a1a; border-radius: 4px; border-left: 3px solid #ff9800;">
<h4 style="margin-top: 0; color: #ff9800;"> How Language Mode Works</h4>
<ul style="margin: 0.5rem 0; padding-left: 1.5rem; font-size: 0.9rem; color: #aaa;">
<li>English mode uses your default text model for English responses</li>
<li>Japanese mode switches to Swallow and responds only in 日本語</li>
<li>All personality traits, mood system, and features work in both modes</li>
<li>Language mode is global - affects all servers and DMs</li>
<li>Conversation history is preserved across language switches</li>
</ul>
</div>
</div>
</div>
<!-- Image Generation Tab Content -->
<div id="tab5" class="tab-content">
2025-12-07 17:15:09 +02:00
<div class="section">
<h3>🎨 Image Generation System</h3>
<p>Natural language image generation powered by ComfyUI. Users can ask Miku to create images naturally without commands!</p>
<!-- Status Section -->
<div style="margin-bottom: 1.5rem;">
<h4>System Status</h4>
<div id="image-system-status" style="margin-bottom: 1rem;">
<button onclick="checkImageSystemStatus()">🔄 Check Status</button>
</div>
<div id="image-status-display" style="background: #2a2a2a; padding: 1rem; border-radius: 4px; font-family: monospace; font-size: 0.9rem;"></div>
</div>
<!-- Detection Testing -->
<div style="margin-bottom: 1.5rem;">
<h4>Test Natural Language Detection</h4>
<div style="margin-bottom: 1rem;">
<label for="detection-test-message">Test Message:</label>
<textarea id="detection-test-message" placeholder="e.g. Hey Miku, I'd like to see you swimming in a pool" rows="2" style="width: 100%; margin-top: 0.5rem;"></textarea>
</div>
<button onclick="testImageDetection()" style="margin-right: 0.5rem;">🔍 Test Detection</button>
<div id="detection-test-results" style="margin-top: 0.5rem; font-size: 0.9rem;"></div>
</div>
<!-- Manual Image Generation -->
<div style="margin-bottom: 1.5rem;">
<h4>Manual Image Generation</h4>
<div style="margin-bottom: 1rem;">
<label for="manual-image-prompt">Image Prompt:</label>
<textarea id="manual-image-prompt" placeholder="Describe the image you want to generate..." rows="3" style="width: 100%; margin-top: 0.5rem;"></textarea>
</div>
<button onclick="generateManualImage()" style="margin-right: 0.5rem;">🎨 Generate Image</button>
<div id="manual-image-status" style="margin-top: 0.5rem; font-size: 0.9rem;"></div>
<div id="manual-image-preview" style="margin-top: 1rem; text-align: center;"></div>
2025-12-07 17:15:09 +02:00
</div>
<!-- System Information -->
<div style="margin-bottom: 1.5rem;">
<h4>Image Generation Settings</h4>
<div style="background: #2a2a2a; padding: 1rem; border-radius: 4px;">
<div style="margin-bottom: 0.5rem;"><strong>ComfyUI Configuration:</strong></div>
<ul style="margin: 0; padding-left: 1.5rem;">
<li>URL: Auto-detected (tries multiple Docker networking options)</li>
<li>Workflow Template: <code>Miku_BasicWorkflow.json</code></li>
<li>Host Output Directory: <code>/home/koko210Serve/ComfyUI/output/</code></li>
<li>Container Mount Point: <code>/app/ComfyUI/output/</code></li>
<li>Generation Timeout: 300 seconds</li>
</ul>
<div style="margin-top: 1rem; font-size: 0.9rem; color: #aaa;">
<strong>Note:</strong> Make sure ComfyUI is running and the workflow template exists in the bot directory.
</div>
</div>
</div>
</div>
</div>
<!-- Autonomous Stats Tab Content -->
<div id="tab6" class="tab-content">
2025-12-07 17:15:09 +02:00
<div class="section">
<h3>📊 Autonomous V2 Decision Engine Stats</h3>
<p>Real-time monitoring of Miku's autonomous decision-making context and mood-based personality stats.</p>
<div style="margin-bottom: 1.5rem;">
<label for="autonomous-server-select">Select Server:</label>
<select id="autonomous-server-select" onchange="loadAutonomousStats()">
<option value="">-- Select a server --</option>
</select>
<button onclick="loadAutonomousStats()" style="margin-left: 0.5rem;">🔄 Refresh</button>
</div>
<div id="autonomous-stats-display"></div>
</div>
</div>
<!-- Chat with LLM Tab Content -->
<div id="tab7" class="tab-content">
<div class="section">
<h3>💬 Chat with LLM</h3>
<p>Direct chat interface with the language models. Test responses, experiment with prompts, or just chat with Miku!</p>
<!-- Configuration Options -->
<div style="background: #2a2a2a; padding: 1.5rem; border-radius: 8px; margin-bottom: 1.5rem;">
<h4 style="margin-top: 0; color: #61dafb;">⚙️ Chat Configuration</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1.5rem; margin-bottom: 1rem;">
<!-- Model Selection -->
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">🤖 Model Type:</label>
<div style="display: flex; gap: 1rem;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="chat-model-type" value="text" checked onchange="toggleChatImageUpload()">
<span style="margin-left: 0.5rem;">💬 Text Model (Fast)</span>
</label>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="chat-model-type" value="vision" onchange="toggleChatImageUpload()">
<span style="margin-left: 0.5rem;">👁️ Vision Model (Images)</span>
</label>
</div>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.3rem;">
Text model for conversations, Vision model for image analysis
</div>
</div>
<!-- System Prompt Toggle -->
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">🎭 System Prompt:</label>
<div style="display: flex; gap: 1rem;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="chat-system-prompt" value="true" checked>
<span style="margin-left: 0.5rem;">✅ Use Miku Personality</span>
</label>
<label style="display: flex; align-items: center; cursor: pointer;">
<input type="radio" name="chat-system-prompt" value="false">
<span style="margin-left: 0.5rem;">❌ Raw LLM (No Prompt)</span>
</label>
</div>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.3rem;">
With prompt: Chat as Miku. Without: Direct LLM responses
</div>
</div>
<!-- Mood Selection -->
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">😊 Miku's Mood:</label>
<select id="chat-mood-select" style="width: 100%; padding: 0.5rem; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px;">
<option value="neutral" selected>neutral</option>
</select>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.3rem;">
Choose Miku's emotional state for this conversation
</div>
</div>
</div>
<!-- Image Upload for Vision Model -->
<div id="chat-image-upload-section" style="display: none; margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #444;">
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">🖼️ Upload Image:</label>
<input type="file" id="chat-image-file" accept="image/*" style="margin-bottom: 0.5rem;">
<div style="font-size: 0.85rem; color: #aaa;">
Upload an image for the vision model to analyze
</div>
<div id="chat-image-preview" style="margin-top: 0.5rem; display: none;">
<img id="chat-image-preview-img" style="max-width: 200px; max-height: 200px; border: 1px solid #555; border-radius: 4px;">
</div>
</div>
<!-- Clear Chat Button -->
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #444;">
<button onclick="clearChatHistory()" style="background: #ff9800;">🗑️ Clear Chat History</button>
<span style="margin-left: 1rem; font-size: 0.85rem; color: #aaa;">Remove all messages from this session</span>
</div>
</div>
<!-- Chat Messages Container -->
<div id="chat-messages" style="background: #1e1e1e; border: 1px solid #444; border-radius: 8px; padding: 1rem; min-height: 400px; max-height: 500px; overflow-y: auto; margin-bottom: 1rem;">
<div style="text-align: center; color: #888; padding: 2rem;">
💬 Start chatting with the LLM! Your conversation will appear here.
</div>
</div>
<!-- Chat Input Area -->
<div style="display: flex; gap: 0.5rem; align-items: flex-end;">
<div style="flex: 1;">
<textarea
id="chat-input"
placeholder="Type your message here..."
rows="3"
style="width: 100%; padding: 0.75rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; font-family: inherit; resize: vertical;"
onkeydown="handleChatKeyPress(event)"
></textarea>
</div>
<div>
<button
id="chat-send-btn"
onclick="sendChatMessage()"
style="padding: 1rem 1.5rem; height: 100%; background: #4CAF50; font-size: 1rem; font-weight: bold;"
>
📤 Send
</button>
</div>
</div>
<div style="margin-top: 0.5rem; font-size: 0.85rem; color: #aaa;">
💡 Tip: Press Ctrl+Enter to send your message quickly
</div>
</div>
</div>
<!-- Tab 8: Voice Call Management -->
<div id="tab8" class="tab-content">
<div class="section">
<h3>📞 Initiate Voice Call</h3>
<p>Start an automated voice chat session with a user. Miku will automatically manage containers, join voice chat, and send an invitation DM.</p>
<div style="background: #2a2a2a; padding: 1.5rem; border-radius: 8px; margin-bottom: 1.5rem;">
<h4 style="margin-top: 0; color: #61dafb;">⚙️ Voice Call Configuration</h4>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; margin-bottom: 1.5rem;">
<!-- User ID Input -->
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">👤 Target User ID:</label>
<input
type="text"
id="voice-user-id"
placeholder="Discord user ID (e.g., 123456789)"
style="width: 100%; padding: 0.5rem; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; box-sizing: border-box;"
>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.3rem;">
Discord ID of the user to call
</div>
</div>
<!-- Voice Channel ID Input -->
<div>
<label style="display: block; margin-bottom: 0.5rem; font-weight: bold;">🎤 Voice Channel ID:</label>
<input
type="text"
id="voice-channel-id"
placeholder="Discord channel ID (e.g., 987654321)"
style="width: 100%; padding: 0.5rem; background: #333; color: #fff; border: 1px solid #555; border-radius: 4px; box-sizing: border-box;"
>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.3rem;">
Discord ID of the voice channel to join
</div>
</div>
</div>
<!-- Debug Mode Toggle -->
<div style="margin-bottom: 1.5rem; padding: 1rem; background: #1e1e1e; border-radius: 4px;">
<label style="display: flex; align-items: center; cursor: pointer;">
<input
type="checkbox"
id="voice-debug-mode"
style="margin-right: 0.7rem; width: 18px; height: 18px; cursor: pointer;"
>
<span style="font-weight: bold;">🐛 Debug Mode</span>
</label>
<div style="font-size: 0.85rem; color: #aaa; margin-top: 0.5rem; margin-left: 1.7rem;">
When enabled, shows voice transcriptions and responses in text channel. When disabled, voice chat is private.
</div>
</div>
<!-- Call Status Display -->
<div id="voice-call-status" style="background: #1e1e1e; padding: 1rem; border-radius: 4px; margin-bottom: 1.5rem; display: none;">
<div style="color: #61dafb; font-weight: bold; margin-bottom: 0.5rem;">📊 Call Status:</div>
<div id="voice-call-status-text" style="color: #aaa; font-size: 0.9rem;"></div>
<div id="voice-call-invite-link" style="margin-top: 0.5rem; display: none;">
<strong>Invite Link:</strong> <a id="voice-call-invite-url" href="" target="_blank" style="color: #61dafb;">View Invite</a>
</div>
</div>
<!-- Call Buttons -->
<div style="display: flex; gap: 1rem;">
<button
id="voice-call-btn"
onclick="initiateVoiceCall()"
style="background: #2ecc71; color: #000; padding: 0.7rem 1.5rem; border: 1px solid #27ae60; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 1rem;"
>
📞 Initiate Call
</button>
<button
id="voice-call-cancel-btn"
onclick="cancelVoiceCall()"
style="background: #e74c3c; color: #fff; padding: 0.7rem 1.5rem; border: 1px solid #c0392b; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 1rem; display: none;"
>
🛑 Cancel Call
</button>
</div>
</div>
<!-- Call Information -->
<div style="background: #1a1a2e; padding: 1.5rem; border-radius: 8px; border-left: 3px solid #61dafb;">
<h4 style="margin-top: 0; color: #61dafb;"> How Voice Calls Work</h4>
<ul style="color: #ddd; line-height: 1.8;">
<li><strong>Automatic Setup:</strong> STT and TTS containers start automatically</li>
<li><strong>Warmup Wait:</strong> System waits for both containers to be ready (~30-75 seconds)</li>
<li><strong>VC Join:</strong> Miku joins the specified voice channel</li>
<li><strong>DM Invitation:</strong> User receives a personalized invite DM with a voice channel link</li>
<li><strong>Auto-Listen:</strong> STT automatically starts when user joins</li>
<li><strong>Auto-Leave:</strong> Miku leaves 45 seconds after user disconnects</li>
<li><strong>Timeout:</strong> If user doesn't join within 30 minutes, call is cancelled</li>
</ul>
</div>
<!-- Call History -->
<div style="margin-top: 2rem;">
<h4 style="color: #61dafb; margin-bottom: 1rem;">📋 Recent Calls</h4>
<div id="voice-call-history" style="background: #1e1e1e; border: 1px solid #444; border-radius: 4px; padding: 1rem;">
<div style="text-align: center; color: #888;">No calls yet. Start one above!</div>
</div>
</div>
</div>
</div>
<!-- Tab 9: Memory Management -->
<div id="tab9" class="tab-content">
<div class="section">
<h3>🧠 Cheshire Cat Memory Management</h3>
<p style="color: #aaa; margin-bottom: 1rem;">
Manage Miku's long-term memories powered by the Cheshire Cat AI pipeline.
Memories are stored in Qdrant vector database and used to give Miku persistent knowledge about users.
</p>
<!-- Cat Integration Status -->
<div id="cat-status-section" style="background: #1a1a2e; border: 1px solid #444; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<h4 style="margin: 0 0 0.3rem 0;">🐱 Cheshire Cat Status</h4>
<span id="cat-status-indicator" style="color: #888;">Checking...</span>
</div>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button id="cat-toggle-btn" onclick="toggleCatIntegration()" style="background: #333; color: #fff; padding: 0.4rem 0.8rem; border: 2px solid #666; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.85rem;">
Loading...
</button>
<button onclick="refreshMemoryStats()" style="background: #2a5599; color: #fff; padding: 0.4rem 0.8rem; border: none; border-radius: 4px; cursor: pointer;">
🔄 Refresh
</button>
</div>
</div>
</div>
<!-- Memory Statistics -->
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-bottom: 1.5rem;">
<div id="stat-episodic" style="background: #1a2332; border: 1px solid #2a5599; border-radius: 8px; padding: 1rem; text-align: center;">
<div style="font-size: 2rem; font-weight: bold; color: #61dafb;" id="stat-episodic-count"></div>
<div style="color: #aaa; font-size: 0.85rem;">📝 Episodic Memories</div>
<div style="color: #666; font-size: 0.75rem; margin-top: 0.3rem;">Conversation snippets</div>
</div>
<div id="stat-declarative" style="background: #1a3322; border: 1px solid #2a9955; border-radius: 8px; padding: 1rem; text-align: center;">
<div style="font-size: 2rem; font-weight: bold; color: #6fdc6f;" id="stat-declarative-count"></div>
<div style="color: #aaa; font-size: 0.85rem;">📚 Declarative Facts</div>
<div style="color: #666; font-size: 0.75rem; margin-top: 0.3rem;">Learned knowledge</div>
</div>
<div id="stat-procedural" style="background: #332a1a; border: 1px solid #995e2a; border-radius: 8px; padding: 1rem; text-align: center;">
<div style="font-size: 2rem; font-weight: bold; color: #dcb06f;" id="stat-procedural-count"></div>
<div style="color: #aaa; font-size: 0.85rem;">⚙️ Procedural</div>
<div style="color: #666; font-size: 0.75rem; margin-top: 0.3rem;">Tools & procedures</div>
</div>
</div>
<!-- Consolidation -->
<div style="background: #1a1a2e; border: 1px solid #444; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
<h4 style="margin: 0 0 0.5rem 0;">🌙 Memory Consolidation</h4>
<p style="color: #aaa; font-size: 0.85rem; margin-bottom: 0.75rem;">
Trigger the sleep consolidation process: analyzes episodic memories, extracts important facts, and removes trivial entries.
</p>
<div style="display: flex; gap: 0.5rem; align-items: center;">
<button id="consolidate-btn" onclick="triggerConsolidation()" style="background: #5b3a8c; color: #fff; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
🌙 Run Consolidation
</button>
<span id="consolidation-status" style="color: #888; font-size: 0.85rem;"></span>
</div>
<div id="consolidation-result" style="display: none; margin-top: 0.75rem; background: #111; border: 1px solid #333; border-radius: 4px; padding: 0.75rem; font-size: 0.85rem; color: #ccc; white-space: pre-wrap; max-height: 200px; overflow-y: auto;"></div>
</div>
<!-- Declarative Facts Browser -->
<div style="background: #1a1a2e; border: 1px solid #444; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<h4 style="margin: 0;">📚 Declarative Facts</h4>
<div style="display: flex; gap: 0.5rem;">
<button onclick="showCreateMemoryModal('declarative')" style="background: #2a9955; color: #fff; padding: 0.3rem 0.7rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.85rem;">
Add Fact
</button>
<button onclick="loadFacts()" style="background: #2a5599; color: #fff; padding: 0.3rem 0.7rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.85rem;">
🔄 Load Facts
</button>
</div>
</div>
<div style="margin-bottom: 0.5rem;">
<input type="text" id="facts-search" placeholder="🔍 Search facts..."
oninput="filterMemories('facts-list', this.value)"
style="width: 100%; padding: 0.4rem; background: #242424; color: #fff; border: 1px solid #444; border-radius: 4px; box-sizing: border-box;">
</div>
<div id="facts-list" style="max-height: 400px; overflow-y: auto;">
<div style="text-align: center; color: #666; padding: 2rem;">Click "Load Facts" to view stored knowledge</div>
</div>
</div>
<!-- Episodic Memories Browser -->
<div style="background: #1a1a2e; border: 1px solid #444; border-radius: 8px; padding: 1rem; margin-bottom: 1.5rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<h4 style="margin: 0;">📝 Episodic Memories</h4>
<div style="display: flex; gap: 0.5rem;">
<button onclick="showCreateMemoryModal('episodic')" style="background: #2a9955; color: #fff; padding: 0.3rem 0.7rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.85rem;">
Add Memory
</button>
<button onclick="loadEpisodicMemories()" style="background: #2a5599; color: #fff; padding: 0.3rem 0.7rem; border: none; border-radius: 4px; cursor: pointer; font-size: 0.85rem;">
🔄 Load Memories
</button>
</div>
</div>
<div style="margin-bottom: 0.5rem;">
<input type="text" id="episodic-search" placeholder="🔍 Search memories..."
oninput="filterMemories('episodic-list', this.value)"
style="width: 100%; padding: 0.4rem; background: #242424; color: #fff; border: 1px solid #444; border-radius: 4px; box-sizing: border-box;">
</div>
<div id="episodic-list" style="max-height: 400px; overflow-y: auto;">
<div style="text-align: center; color: #666; padding: 2rem;">Click "Load Memories" to view conversation snippets</div>
</div>
</div>
<!-- DANGER ZONE: Delete All Memories -->
<div style="background: #2e1a1a; border: 2px solid #993333; border-radius: 8px; padding: 1rem;">
<h4 style="margin: 0 0 0.5rem 0; color: #ff6b6b;">⚠️ Danger Zone — Delete All Memories</h4>
<p style="color: #cc9999; font-size: 0.85rem; margin-bottom: 1rem;">
This will permanently erase ALL of Miku's memories — episodic conversations, learned facts, everything.
This action is <strong>irreversible</strong>. Miku will forget everything she has ever learned.
</p>
<!-- Step 1: Initial checkbox -->
<div id="delete-step-1" style="margin-bottom: 0.75rem;">
<label style="cursor: pointer; color: #ff9999;">
<input type="checkbox" id="delete-checkbox-1" onchange="onDeleteStep1Change()">
I understand this will permanently delete all of Miku's memories
</label>
</div>
<!-- Step 2: Second confirmation (hidden initially) -->
<div id="delete-step-2" style="display: none; margin-bottom: 0.75rem;">
<label style="cursor: pointer; color: #ff9999;">
<input type="checkbox" id="delete-checkbox-2" onchange="onDeleteStep2Change()">
I confirm this is irreversible and I want to proceed
</label>
</div>
<!-- Step 3: Type confirmation string (hidden initially) -->
<div id="delete-step-3" style="display: none; margin-bottom: 0.75rem;">
<p style="color: #ff6b6b; font-size: 0.85rem; margin-bottom: 0.5rem;">
Type exactly: <code style="background: #333; padding: 0.2rem 0.4rem; border-radius: 3px; color: #ff9999;">Yes, I am deleting Miku's memories fully.</code>
</p>
<input type="text" id="delete-confirmation-input" placeholder="Type the confirmation string..."
style="width: 100%; padding: 0.5rem; background: #1a1a1a; color: #ff9999; border: 1px solid #993333; border-radius: 4px; font-family: monospace; box-sizing: border-box;"
oninput="onDeleteInputChange()">
</div>
<!-- Final delete button (hidden initially) -->
<div id="delete-step-final" style="display: none;">
<button id="delete-all-btn" onclick="executeDeleteAllMemories()" disabled
style="background: #cc3333; color: #fff; padding: 0.5rem 1.5rem; border: none; border-radius: 4px; cursor: not-allowed; font-weight: bold; opacity: 0.5;">
🗑️ Permanently Delete All Memories
</button>
<button onclick="resetDeleteFlow()" style="background: #444; color: #ccc; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; margin-left: 0.5rem;">
Cancel
</button>
</div>
</div>
</div>
</div>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
<!-- Tab 11: Profile Picture Management -->
<div id="tab11" class="tab-content">
<div class="section">
<h3>🖼️ Profile Picture Management</h3>
<p style="font-size: 0.9rem; color: #aaa;">Change, crop, and manage Miku's profile picture. Edit descriptions and role colors.</p>
<!-- Action Buttons -->
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center;">
<button onclick="pfpChangeDanbooru()">🎨 Change (Danbooru)</button>
<button onclick="pfpRestoreFallback()">🔄 Restore Original Avatar</button>
</div>
<!-- Upload Section -->
<div style="margin-bottom: 1rem;">
<label for="pfp-tab-upload">Upload Custom Image:</label>
<input type="file" id="pfp-tab-upload" accept="image/*" style="margin-left: 0.5rem;">
<button onclick="pfpUploadCustom()">📤 Upload & Apply</button>
<div style="font-size: 0.8rem; color: #888; margin-top: 0.3rem; margin-left: 0.5rem;">
💡 Supports static images (PNG, JPG) and animated GIFs &nbsp;|&nbsp;
⚠️ Animated GIFs require Discord Nitro on the bot account
</div>
</div>
<!-- Album / Gallery -->
<div class="album-section">
<div class="album-header" onclick="albumToggle()">
<h4><span id="album-toggle-icon"></span> 📚 Profile Picture Album <span id="album-count" style="color: #888; font-weight: normal;"></span></h4>
<span class="album-disk-usage" id="album-disk-usage"></span>
</div>
<div id="album-body" style="display: none;">
<!-- Toolbar -->
<div class="album-toolbar">
<input type="file" id="album-upload" accept="image/*" multiple style="max-width: 220px;">
<button onclick="albumUpload()">📤 Add to Album</button>
<button onclick="albumAddCurrent()">📌 Archive Current PFP</button>
<button onclick="albumBulkDelete()" style="background: #c0392b;" id="album-bulk-delete-btn" disabled>🗑️ Delete Selected (<span id="album-selected-count">0</span>)</button>
<div id="album-status" style="font-size: 0.85rem; color: #61dafb; margin-left: 0.5rem;"></div>
</div>
<!-- Grid -->
<div class="album-grid" id="album-grid"></div>
<!-- Detail panel (shown when an entry is clicked) -->
<div class="album-detail" id="album-detail" style="display: none;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h4 style="margin: 0;">Selected Entry</h4>
<button onclick="albumCloseDetail()" style="background: #555; padding: 0.3rem 0.6rem;">✖ Close</button>
</div>
<div class="album-detail-previews">
<div class="pfp-preview-box">
<span class="label">📷 Original</span>
<img id="album-detail-original" src="" alt="Original" style="cursor: pointer;" onclick="albumShowCropInterface()" title="Click to crop">
<div id="album-detail-dims" style="font-size: 0.8rem; color: #666; margin-top: 0.3rem;"></div>
</div>
<div class="pfp-preview-box">
<span class="label">🎯 Cropped</span>
<img id="album-detail-cropped" src="" alt="Cropped" style="border-radius: 50%; max-width: 256px; max-height: 256px;">
</div>
</div>
<!-- Album entry crop interface -->
<div id="album-crop-section" style="display: none; margin: 1rem 0; padding: 1rem; background: #1a1a2e; border: 1px solid #444; border-radius: 8px;">
<h4 style="margin-top: 0;">✂️ Crop Album Entry</h4>
<div class="pfp-crop-container">
<img id="album-crop-image" src="" alt="Crop source">
</div>
<div style="margin-top: 0.75rem; display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button onclick="albumApplyManualCrop()" style="background: #4CAF50; color: #fff; font-weight: bold;">✂️ Apply Crop</button>
<button onclick="albumApplyAutoCrop()">🤖 Auto Crop</button>
<button onclick="albumHideCropInterface()" style="background: #666;">✖ Cancel</button>
</div>
</div>
<!-- Description -->
<div style="margin-top: 1rem;">
<label style="color: #aaa;">📝 Description:</label>
<textarea id="album-detail-description" class="pfp-description-editor" style="min-height: 80px;"></textarea>
<div style="margin-top: 0.5rem; display: flex; gap: 0.5rem;">
<button onclick="albumSaveDescription()" style="background: #4CAF50; color: #fff;">💾 Save</button>
</div>
</div>
<!-- Actions -->
<div style="margin-top: 1rem; display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button onclick="albumSetAsCurrent()" style="background: #2196F3; color: #fff; font-weight: bold;">🖼️ Set as Current PFP</button>
<button onclick="albumDeleteSelected()" style="background: #c0392b; color: #fff;">🗑️ Delete Entry</button>
</div>
<div id="album-detail-meta" style="margin-top: 0.75rem; font-size: 0.8rem; color: #888;"></div>
</div>
</div>
</div>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
<!-- Crop Mode Toggle -->
<div class="crop-mode-toggle">
<span style="color: #61dafb; font-weight: bold;">Crop Mode:</span>
<label>
<input type="radio" name="pfp-crop-mode" value="auto" checked>
🤖 Auto (face detection)
</label>
<label>
<input type="radio" name="pfp-crop-mode" value="manual">
✂️ Manual
</label>
</div>
<!-- Status -->
<div id="pfp-tab-status" style="margin-top: 0.5rem; font-size: 0.9rem; color: #61dafb;"></div>
<!-- Manual Crop Interface (hidden by default) -->
<div id="pfp-crop-section" style="display: none; margin: 1rem 0; padding: 1rem; background: #1a1a2e; border: 1px solid #444; border-radius: 8px;">
<h4 style="margin-top: 0;">✂️ Manual Crop</h4>
<p style="font-size: 0.85rem; color: #aaa; margin-bottom: 0.5rem;">
Drag to select a square crop region. Discord avatars are displayed as circles, so keep the subject centered.
</p>
<div class="pfp-crop-container">
<img id="pfp-crop-image" src="" alt="Crop source">
</div>
<div style="margin-top: 0.75rem; display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button onclick="pfpApplyManualCrop()" style="background: #4CAF50; color: #fff; font-weight: bold;">✂️ Apply Crop</button>
<button onclick="pfpApplyAutoCrop()">🤖 Use Auto Crop Instead</button>
<button onclick="pfpHideCropInterface()" style="background: #666;">✖ Cancel</button>
</div>
</div>
<!-- Image Previews -->
<div class="pfp-preview-container">
<div class="pfp-preview-box">
<span class="label">📷 Original (full resolution)</span>
<img id="pfp-preview-original" src="" alt="Original" style="cursor: pointer;" onclick="pfpRecrop()" title="Click to re-crop">
<div id="pfp-original-dims" style="font-size: 0.8rem; color: #666; margin-top: 0.3rem;"></div>
</div>
<div class="pfp-preview-box">
<span class="label">🎯 Current Avatar (cropped)</span>
<img id="pfp-preview-current" src="" alt="Current avatar" style="border-radius: 50%; max-width: 256px; max-height: 256px;">
<div style="font-size: 0.8rem; color: #666; margin-top: 0.3rem;">512×512 · displayed as circle</div>
<button onclick="pfpRecrop()" style="margin-top: 0.5rem;">✂️ Re-crop Current</button>
feat: add Profile Picture Management tab with manual crop, description editor - profile_picture_manager.py: - Add ORIGINAL_PATH constant; save full-res original before every crop - Add skip_crop param to change_profile_picture() for manual crop workflow - Add manual_crop(x,y,w,h) method with Discord avatar update + role color sync - Add auto_crop_only() to re-run face-detection crop on stored original - Add update_description() with Cheshire Cat declarative memory re-injection - Add regenerate_description() via vision model - Skip crop step if image is already at/below 512x512 - api.py: - GET /profile-picture/image/original — serve full-res original (no-cache) - GET /profile-picture/image/current — serve current cropped avatar (no-cache) - POST /profile-picture/change-no-crop — acquire image, skip auto-crop - POST /profile-picture/manual-crop — apply crop coords {x,y,width,height} - POST /profile-picture/auto-crop — re-run intelligent crop on original - POST /profile-picture/description — save freeform description + Cat inject - POST /profile-picture/regenerate-description — re-generate via vision model - GET /profile-picture/description — fetch current description text - index.html: - Add new tab11 '🖼️ Profile Picture Management' - Remove PFP + role color sections from Actions tab (tab2) - Add Cropper.js 1.6.2 via CDN for manual square crop - Tab layout: action buttons, file upload, auto/manual crop toggle, Cropper.js interface, side-by-side original/cropped previews, role color management, freeform description editor, metadata box (bottom) - Wire switchTab hook for tab11 → loadPfpTab() - All new JS functions: pfpChangeDanbooru, pfpUploadCustom, pfpRestoreFallback, pfpShowCropInterface, pfpApplyManualCrop, pfpApplyAutoCrop, pfpSaveDescription, pfpRegenerateDescription, pfpRefreshPreviews, setCustomRoleColor, resetRoleColor
2026-03-30 15:10:19 +03:00
</div>
</div>
<!-- Role Color Management -->
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #333;">
<h4>🎨 Role Color Management</h4>
<p style="font-size: 0.9rem; color: #aaa;">Manually set Miku's role color or reset to fallback (#86cecb)</p>
<div style="margin-bottom: 1rem; display: flex; gap: 10px; align-items: end;">
<div>
<label for="pfp-tab-role-color-hex">Hex Color:</label>
<input type="text" id="pfp-tab-role-color-hex" placeholder="#86cecb" maxlength="7" style="width: 100px; font-family: monospace;">
</div>
<button onclick="setCustomRoleColor()">🎨 Apply Color</button>
<button onclick="resetRoleColor()">🔄 Reset to Fallback</button>
</div>
<div id="pfp-tab-role-color-status" style="margin-top: 0.5rem; font-size: 0.9rem; color: #61dafb;"></div>
</div>
<!-- Description Editor -->
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #333;">
<h4>📝 Profile Picture Description</h4>
<p style="font-size: 0.9rem; color: #aaa;">Edit the description used for context when users ask about Miku's avatar. Saved to Cheshire Cat memory.</p>
<textarea id="pfp-description-editor" class="pfp-description-editor" placeholder="Loading description..."></textarea>
<div style="margin-top: 0.5rem; display: flex; gap: 0.5rem; flex-wrap: wrap;">
<button onclick="pfpSaveDescription()" style="background: #4CAF50; color: #fff;">💾 Save Description</button>
<button onclick="pfpRegenerateDescription()">🔄 Re-generate (Vision Model)</button>
</div>
<div id="pfp-desc-status" style="margin-top: 0.5rem; font-size: 0.9rem; color: #61dafb;"></div>
</div>
<!-- Metadata (bottom) -->
<div id="pfp-tab-metadata" style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #333; background: #1e1e1e; padding: 0.5rem; border: 1px solid #333; display: none;">
<h4 style="margin-top: 0;">Current Profile Picture Info:</h4>
<pre id="pfp-tab-metadata-content" style="margin: 0;"></pre>
</div>
</div>
</div>
fix: Phase 3 bug fixes - memory APIs, username visibility, web UI layout, Docker **Critical Bug Fixes:** 1. Per-user memory isolation bug - Changed CatAdapter from HTTP POST to WebSocket /ws/{user_id} - User_id now comes from URL path parameter (true per-user isolation) - Verified: Different users can't see each other's memories 2. Memory API 405 errors - Replaced non-existent Cat endpoint calls with Qdrant direct queries - get_memory_points(): Now uses POST /collections/{collection}/points/scroll - delete_memory_point(): Now uses POST /collections/{collection}/points/delete 3. Memory stats showing null counts - Reimplemented get_memory_stats() to query Qdrant directly - Now returns accurate counts: episodic: 20, declarative: 6, procedural: 4 4. Miku couldn't see usernames - Modified discord_bridge before_cat_reads_message hook - Prepends [Username says:] to every message text - LLM now knows who is texting: [Alice says:] Hello Miku! 5. Web UI Memory tab layout - Tab9 was positioned outside .tab-container div (showed to the right) - Moved tab9 HTML inside container, before closing divs - Memory tab now displays below tab buttons like other tabs **Code Changes:** bot/utils/cat_client.py: - Line 25: Logger name changed to 'llm' (available component) - get_memory_stats() (lines 256-285): Query Qdrant directly via HTTP GET - get_memory_points() (lines 275-310): Use Qdrant POST /points/scroll - delete_memory_point() (lines 350-370): Use Qdrant POST /points/delete cat-plugins/discord_bridge/discord_bridge.py: - Fixed .pop() → .get() (UserMessage is Pydantic BaseModelDict) - Added before_cat_reads_message logic to prepend [Username says:] - Message format: [Alice says:] message content Dockerfile.llamaswap-rocm: - Lines 37-44: Added conditional check for UI directory - if [ -d ui ] before npm install && npm run build - Fixes build failure when llama-swap UI dir doesn't exist bot/static/index.html: - Moved tab9 from lines 1554-1688 (outside container) - To position before container closing divs (now inside) - Memory tab button at line 673: 🧠 Memories **Testing & Verification:** ✅ Per-user isolation verified (Docker exec test) ✅ Memory stats showing real counts (curl test) ✅ Memory API working (facts/episodic loading) ✅ Web UI layout fixed (tab displays correctly) ✅ All 5 services running (llama-swap, llama-swap-amd, qdrant, cat, bot) ✅ Username prepending working (message context for LLM) **Result:** All Phase 3 critical bugs fixed and verified working.
2026-02-07 23:27:15 +02:00
</div>
</div>
<div class="logs" id="logs-panel">
2025-12-07 17:15:09 +02:00
<h3>Logs</h3>
<div id="logs-paused-banner" class="logs-paused-indicator" onclick="scrollLogsToBottom()">⏸ Auto-scroll paused — click to resume</div>
2025-12-07 17:15:09 +02:00
<div id="logs-content"></div>
</div>
<div id="notification"></div>
<!-- Edit Memory Modal -->
<div id="edit-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%;">
<h3 style="margin: 0 0 1rem 0; color: #61dafb;">✏️ Edit Memory</h3>
<div style="margin-bottom: 1rem;">
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Content:</label>
<textarea id="edit-memory-content" rows="5" style="width: 100%; padding: 0.5rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; font-family: monospace; box-sizing: border-box; resize: vertical;"></textarea>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Source:</label>
<input type="text" id="edit-memory-source" style="width: 100%; padding: 0.5rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<button onclick="closeEditMemoryModal()" style="background: #444; color: #ccc; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer;">
Cancel
</button>
<button onclick="saveMemoryEdit()" style="background: #2a5599; color: #fff; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
💾 Save Changes
</button>
</div>
</div>
</div>
<!-- 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 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>
<div style="margin-bottom: 1rem;">
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Content:</label>
<textarea id="create-memory-content" rows="5" placeholder="Enter the fact or memory content..." style="width: 100%; padding: 0.5rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; font-family: monospace; box-sizing: border-box; resize: vertical;"></textarea>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">User ID (optional):</label>
<input type="text" id="create-memory-user-id" placeholder="e.g., discord_123456789" style="width: 100%; padding: 0.5rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; box-sizing: border-box;">
<small style="color: #888;">Leave empty for general facts, or specify a Discord user ID for user-specific facts</small>
</div>
<div style="margin-bottom: 1rem;">
<label style="display: block; color: #ccc; margin-bottom: 0.5rem;">Source (optional):</label>
<input type="text" id="create-memory-source" placeholder="e.g., manual_admin, wiki, etc." style="width: 100%; padding: 0.5rem; background: #2a2a2a; color: #fff; border: 1px solid #555; border-radius: 4px; box-sizing: border-box;">
</div>
<div style="display: flex; gap: 0.5rem; justify-content: flex-end;">
<button onclick="closeCreateMemoryModal()" style="background: #444; color: #ccc; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer;">
Cancel
</button>
<button onclick="saveNewMemory()" style="background: #2a9955; color: #fff; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">
✨ Create Memory
</button>
</div>
</div>
</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
<script src="/static/js/core.js"></script>
<script src="/static/js/servers.js"></script>
<script src="/static/js/modes.js"></script>
<script src="/static/js/actions.js"></script>
<script src="/static/js/image-gen.js"></script>
<script src="/static/js/status.js"></script>
<script src="/static/js/dm.js"></script>
<script src="/static/js/chat.js"></script>
<script src="/static/js/memories.js"></script>
<script src="/static/js/profile.js"></script>
2025-12-07 17:15:09 +02:00
</body>
</html>