diff --git a/bot/static/js/core.js b/bot/static/js/core.js index a96119e..0c1435c 100644 --- a/bot/static/js/core.js +++ b/bot/static/js/core.js @@ -29,6 +29,7 @@ let notificationTimer = null; let statusInterval = null; let logsInterval = null; let argsInterval = null; +let promptInterval = null; // Mood emoji mapping const MOOD_EMOJIS = { @@ -211,12 +212,14 @@ function startPolling() { if (!statusInterval) statusInterval = setInterval(loadStatus, 10000); if (!logsInterval) logsInterval = setInterval(loadLogs, 5000); if (!argsInterval) argsInterval = setInterval(loadActiveArguments, 5000); + if (!promptInterval) promptInterval = setInterval(loadPromptHistory, 10000); } function stopPolling() { clearInterval(statusInterval); statusInterval = null; clearInterval(logsInterval); logsInterval = null; clearInterval(argsInterval); argsInterval = null; + clearInterval(promptInterval); promptInterval = null; } // ============================================================================ @@ -248,7 +251,7 @@ function initVisibilityPolling() { stopPolling(); console.log('⏸ Tab hidden — polling paused'); } else { - loadStatus(); loadLogs(); loadActiveArguments(); + loadStatus(); loadLogs(); loadActiveArguments(); loadPromptHistory(); startPolling(); console.log('▶️ Tab visible — polling resumed'); } @@ -296,9 +299,11 @@ function initModalAccessibility() { } function initPromptSourceToggle() { - const saved = localStorage.getItem('miku-prompt-source') || 'cat'; + const saved = localStorage.getItem('miku-prompt-source') || 'all'; document.querySelectorAll('.prompt-source-btn').forEach(btn => btn.classList.remove('active')); - document.getElementById(`prompt-src-${saved}`).classList.add('active'); + const btnId = saved === 'all' ? 'prompt-src-all' : `prompt-src-${saved}`; + const btn = document.getElementById(btnId); + if (btn) btn.classList.add('active'); } function initLogsScrollDetection() { @@ -360,8 +365,10 @@ async function loadLogs() { function switchPromptSource(source) { localStorage.setItem('miku-prompt-source', source); document.querySelectorAll('.prompt-source-btn').forEach(btn => btn.classList.remove('active')); - document.getElementById(`prompt-src-${source}`).classList.add('active'); - loadLastPrompt(); + const btnId = source === 'all' ? 'prompt-src-all' : `prompt-src-${source}`; + const btn = document.getElementById(btnId); + if (btn) btn.classList.add('active'); + loadPromptHistory(); } // ============================================================================ diff --git a/bot/static/js/status.js b/bot/static/js/status.js index fd67f7b..a40b07d 100644 --- a/bot/static/js/status.js +++ b/bot/static/js/status.js @@ -57,33 +57,252 @@ async function loadStatus() { } } -// ===== Last Prompt ===== +// ===== Prompt History ===== -async function loadLastPrompt() { - const source = localStorage.getItem('miku-prompt-source') || 'cat'; - const promptEl = document.getElementById('last-prompt'); - const infoEl = document.getElementById('prompt-cat-info'); +let _promptHistoryCache = []; // cached history entries from last fetch +let _selectedPromptId = null; // currently selected entry ID +let _middleTruncation = false; // whether middle-truncation is active + +async function loadPromptHistory() { + const source = localStorage.getItem('miku-prompt-source') || 'all'; + const selectEl = document.getElementById('prompt-history-select'); try { - if (source === 'cat') { - const result = await apiCall('/prompt/cat'); - if (result.timestamp) { - infoEl.innerHTML = `User: ${escapeHtml(result.user || '?')} | Mood: ${escapeHtml(result.mood || '?')} | Time: ${new Date(result.timestamp).toLocaleString()}`; - promptEl.textContent = result.full_prompt + `\n\n${'═'.repeat(60)}\n[Cat Response]\n${result.response}`; - } else { - infoEl.textContent = ''; - promptEl.textContent = result.full_prompt || 'No Cheshire Cat interaction yet.'; - } + const url = source === 'all' ? '/prompts' : `/prompts?source=${source}`; + const result = await apiCall(url); + _promptHistoryCache = result.history || []; + + // Populate dropdown + const currentValue = selectEl.value; + selectEl.innerHTML = ''; + if (_promptHistoryCache.length === 0) { + selectEl.innerHTML = ''; } else { - infoEl.textContent = ''; - const result = await apiCall('/prompt'); - promptEl.textContent = result.prompt; + _promptHistoryCache.forEach(entry => { + const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : '?'; + const srcLabel = entry.source === 'cat' ? '🐱' : '🤖'; + const user = entry.user || '?'; + const option = document.createElement('option'); + option.value = entry.id; + option.textContent = `${srcLabel} #${entry.id} — ${user} — ${ts}`; + selectEl.appendChild(option); + }); + } + + // Restore or auto-select the latest entry + if (_selectedPromptId && _promptHistoryCache.some(e => e.id === _selectedPromptId)) { + selectEl.value = _selectedPromptId; + } else if (_promptHistoryCache.length > 0) { + selectEl.value = _promptHistoryCache[0].id; + } + + if (selectEl.value) { + await selectPromptEntry(selectEl.value); + } else { + clearPromptDisplay(); } } catch (error) { - console.error('Failed to load last prompt:', error); + console.error('Failed to load prompt history:', error); } } +async function selectPromptEntry(promptId) { + if (!promptId) { + clearPromptDisplay(); + return; + } + + _selectedPromptId = parseInt(promptId); + + // Try cache first + let entry = _promptHistoryCache.find(e => e.id === _selectedPromptId); + + // Fall back to API call if not in cache + if (!entry) { + try { + entry = await apiCall(`/prompts/${_selectedPromptId}`); + } catch (error) { + console.error('Failed to load prompt entry:', error); + clearPromptDisplay(); + return; + } + } + + if (!entry) { + clearPromptDisplay(); + return; + } + + renderPromptEntry(entry); +} + +function clearPromptDisplay() { + document.getElementById('prompt-metadata').innerHTML = ''; + document.getElementById('last-prompt').textContent = ''; +} + +function renderPromptEntry(entry) { + // Metadata bar + const metaEl = document.getElementById('prompt-metadata'); + const ts = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : '?'; + const sourceIcon = entry.source === 'cat' ? '🐱 Cat' : '🤖 Fallback'; + metaEl.innerHTML = ` + + + + + + + + + + `; + + // Parse full_prompt into sections + const sections = parsePromptSections(entry.full_prompt || ''); + + // Build display HTML with collapsible subsections + let displayHtml = ''; + + if (sections.system) { + displayHtml += buildCollapsibleSection('System Prompt', sections.system, 'system'); + } + if (sections.context) { + displayHtml += buildCollapsibleSection('Context (Memories & Tools)', sections.context, 'context'); + } + if (sections.conversation) { + displayHtml += buildCollapsibleSection('Conversation', sections.conversation, 'conversation'); + } + if (!sections.system && !sections.context && !sections.conversation) { + // Fallback: show raw full_prompt + displayHtml += `
${escapeHtml(entry.full_prompt || '')}`;
+ }
+
+ // Response section
+ if (entry.response) {
+ let responseText = entry.response;
+ if (_middleTruncation && responseText.length > 400) {
+ responseText = responseText.substring(0, 200) + '\n\n... [truncated middle] ...\n\n' + responseText.substring(responseText.length - 200);
+ }
+ displayHtml += buildCollapsibleSection('Response', escapeHtml(responseText), 'response');
+ }
+
+ // Render into the prompt-display div (using innerHTML for collapsible structure)
+ const displayEl = document.getElementById('prompt-display');
+ displayEl.innerHTML = displayHtml;
+
+ // Also set the raw text into the for copy functionality
+ let rawText = entry.full_prompt || '';
+ if (entry.response) {
+ rawText += `\n\n${'═'.repeat(60)}\n[Response]\n${entry.response}`;
+ }
+ document.getElementById('last-prompt').textContent = rawText;
+}
+
+function parsePromptSections(fullPrompt) {
+ const sections = { system: null, context: null, conversation: null };
+
+ if (!fullPrompt) return sections;
+
+ // Try to split on known section markers
+ const contextMatch = fullPrompt.match(/# Context\s*\n([\s\S]*?)(?=\n# Conversation|\nHuman:|\n$)/);
+ const convMatch = fullPrompt.match(/# Conversation until now:\s*\n([\s\S]*)/);
+
+ if (contextMatch) {
+ // Everything before # Context is the system prompt
+ const contextIdx = fullPrompt.indexOf('# Context');
+ if (contextIdx > 0) {
+ sections.system = fullPrompt.substring(0, contextIdx).trim();
+ }
+ sections.context = contextMatch[1].trim();
+ }
+
+ if (convMatch) {
+ sections.conversation = convMatch[1].trim();
+ } else {
+ // Try alternative: "Human:" at the end
+ const humanMatch = fullPrompt.match(/\nHuman:([\s\S]*)/);
+ if (humanMatch && fullPrompt.indexOf('Human:') > fullPrompt.indexOf('# Context')) {
+ sections.conversation = 'Human:' + humanMatch[1].trim();
+ }
+ }
+
+ // If no # Context marker, try "System:" prefix (fallback prompts)
+ if (!sections.system && !sections.context) {
+ const sysMatch = fullPrompt.match(/^System:\s*([\s\S]*?)(?=\nMessages:)/);
+ const msgMatch = fullPrompt.match(/Messages:\s*([\s\S]*)/);
+ if (sysMatch) {
+ sections.system = sysMatch[1].trim();
+ }
+ if (msgMatch) {
+ sections.conversation = msgMatch[1].trim();
+ }
+ }
+
+ return sections;
+}
+
+function buildCollapsibleSection(title, content, sectionId) {
+ const uniqueId = `prompt-section-${sectionId}-${Date.now()}`;
+ return `
+
+ ▼ ${escapeHtml(title)}
+
+
+ ${content}
+ `;
+}
+
+function togglePromptSubsection(id) {
+ const body = document.getElementById(id);
+ if (!body) return;
+ const header = body.previousElementSibling;
+ if (body.classList.contains('collapsed')) {
+ body.classList.remove('collapsed');
+ if (header) header.innerHTML = header.innerHTML.replace('▶', '▼');
+ } else {
+ body.classList.add('collapsed');
+ if (header) header.innerHTML = header.innerHTML.replace('▼', '▶');
+ }
+}
+
+function togglePromptHistoryCollapse() {
+ const section = document.getElementById('prompt-history-section');
+ const toggle = document.getElementById('prompt-history-toggle');
+ if (section.classList.contains('collapsed')) {
+ section.classList.remove('collapsed');
+ toggle.textContent = '▼ Prompt History';
+ } else {
+ section.classList.add('collapsed');
+ toggle.textContent = '▶ Prompt History';
+ }
+}
+
+function copyPromptToClipboard() {
+ const rawText = document.getElementById('last-prompt').textContent;
+ if (!rawText) return;
+ navigator.clipboard.writeText(rawText).then(() => {
+ showNotification('Prompt copied to clipboard', 'success');
+ }).catch(err => {
+ console.error('Failed to copy:', err);
+ showNotification('Failed to copy', 'error');
+ });
+}
+
+function toggleMiddleTruncation() {
+ _middleTruncation = document.getElementById('prompt-truncate-toggle').checked;
+ // Re-render current entry
+ if (_selectedPromptId) {
+ selectPromptEntry(_selectedPromptId);
+ }
+}
+
+// Legacy compatibility — called from core.js on page load / tab switch
+// Redirects to the new loadPromptHistory()
+async function loadLastPrompt() {
+ await loadPromptHistory();
+}
+
// ===== Autonomous Stats =====
async function loadAutonomousStats() {