Files
miku-discord/bot/static/index.html
koko210Serve f33e2afdf7 frontend: new Prompt History section HTML + CSS
- Replace single <pre> Last Prompt with rich Prompt History viewer
- Add source filter buttons (All/Cat/Fallback), history dropdown selector
- Add metadata bar, copy-to-clipboard button, middle-truncation toggle
- Add collapsible section CSS classes for expandable subsections
2026-05-02 15:19:10 +03:00

1384 lines
76 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Miku Control Panel</title>
<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>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="panel">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<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>
<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;">
🔄 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>
<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>
<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>
<!-- 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>
</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>
<button onclick="triggerAutonomous('reaction')">React to Message</button>
<button onclick="triggerAutonomous('join-conversation')">Detect and Join Conversation</button>
<button onclick="toggleCustomPrompt()">Custom Prompt</button>
</div>
<!-- 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>
<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>
<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">What should they argue about? (optional):</label>
<input type="text" id="bipolar-context" placeholder="e.g., Who's the real Miku? Whether kindness is weakness. A petty grudge..." style="width: 100%; margin-top: 0.3rem;">
<div style="font-size: 0.75rem; color: #777; margin-top: 0.2rem;">
Leave blank for a random topic. Write anything to set the argument's theme.
</div>
</div>
<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>
</div>
<!-- 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>
<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>
<!-- 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>
<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" id="prompt-history-section">
<div class="prompt-history-header" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem;">
<h3 style="margin: 0; cursor: pointer;" onclick="togglePromptHistoryCollapse()" id="prompt-history-toggle">
▼ Prompt History
</h3>
<button onclick="loadPromptHistory()" title="Refresh" style="background: none; border: 1px solid #444; color: #aaa; cursor: pointer; padding: 0.2rem 0.5rem; border-radius: 4px; font-size: 0.85rem;">🔄</button>
</div>
<div id="prompt-history-body">
<!-- Source filter + history selector row -->
<div style="margin-bottom: 0.75rem; display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;">
<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-all" class="prompt-source-btn active" onclick="switchPromptSource('all')"
style="padding: 0.4rem 0.8rem; border: none; cursor: pointer; font-size: 0.85rem; transition: all 0.2s;">
All
</button>
<button id="prompt-src-cat" class="prompt-source-btn" onclick="switchPromptSource('cat')"
style="padding: 0.4rem 0.8rem; border: none; cursor: pointer; font-size: 0.85rem; transition: all 0.2s;">
🐱 Cat
</button>
<button id="prompt-src-fallback" class="prompt-source-btn" onclick="switchPromptSource('fallback')"
style="padding: 0.4rem 0.8rem; border: none; cursor: pointer; font-size: 0.85rem; transition: all 0.2s;">
🤖 Fallback
</button>
</div>
<select id="prompt-history-select" onchange="selectPromptEntry(this.value)" style="background: #2a2a2a; color: #ddd; border: 1px solid #444; padding: 0.35rem 0.5rem; border-radius: 4px; font-size: 0.85rem; min-width: 280px;">
<option value="">-- No prompts yet --</option>
</select>
</div>
<!-- Metadata bar -->
<div id="prompt-metadata" style="margin-bottom: 0.5rem; font-size: 0.82rem; color: #888; display: flex; flex-wrap: wrap; gap: 0.3rem 1rem;"></div>
<!-- Toolbar: copy + truncate toggle -->
<div style="margin-bottom: 0.5rem; display: flex; align-items: center; gap: 1rem;">
<button onclick="copyPromptToClipboard()" title="Copy full prompt to clipboard" style="background: #333; border: 1px solid #555; color: #aaa; cursor: pointer; padding: 0.25rem 0.6rem; border-radius: 4px; font-size: 0.8rem;">📋 Copy</button>
<label style="font-size: 0.82rem; color: #aaa; cursor: pointer; display: flex; align-items: center; gap: 0.3rem;">
<input type="checkbox" id="prompt-truncate-toggle" onchange="toggleMiddleTruncation()">
Truncate from middle
</label>
</div>
<!-- Prompt display subsections -->
<div id="prompt-display" style="max-height: 60vh; overflow-y: auto;">
<pre id="last-prompt" style="white-space: pre-wrap; word-break: break-word; background: #1a1a1a; padding: 0.75rem; border-radius: 4px; font-size: 0.8rem; line-height: 1.4; margin: 0;"></pre>
</div>
</div>
</div>
</div>
<!-- DM Management Tab Content -->
<div id="tab10" class="tab-content">
<div class="section">
<h3>📱 DM Management</h3>
<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 -->
<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">
<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>
</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">
<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>
<!-- 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>
<!-- 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>
</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>
</div>
</div>
<div class="logs" id="logs-panel">
<h3>Logs</h3>
<div id="logs-paused-banner" class="logs-paused-indicator" onclick="scrollLogsToBottom()">⏸ Auto-scroll paused — click to resume</div>
<div id="logs-content"></div>
</div>
<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>
<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>
</body>
</html>