diff --git a/index.js b/index.js index 037592d..f91172b 100644 --- a/index.js +++ b/index.js @@ -105,6 +105,9 @@ import { setupDesktopTabs, removeDesktopTabs } from './src/systems/ui/desktop.js'; +import { + updateDebugUIVisibility +} from './src/systems/ui/debug.js'; // Feature modules import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js'; @@ -190,6 +193,13 @@ function addExtensionSettings() { updateChatThoughts(); // This will re-create the thought bubble if data exists } }); + + // Set up the debug mode toggle + $('#rpg-debug-mode').prop('checked', extensionSettings.debugMode).on('change', function() { + extensionSettings.debugMode = $(this).prop('checked'); + saveSettings(); + updateDebugUIVisibility(); + }); } /** @@ -455,6 +465,9 @@ async function initUI() { setupContentEditableScrolling(); setupRefreshButtonDrag(); initInventoryEventListeners(); + + // Initialize debug UI if debug mode is enabled + updateDebugUIVisibility(); } diff --git a/settings.html b/settings.html index a6d40ea..069a9fe 100644 --- a/settings.html +++ b/settings.html @@ -11,6 +11,12 @@ Toggle to enable/disable the RPG Companion extension. Configure additional settings within the panel itself. + + Enable debug logging visible in UI. Useful for troubleshooting parsing issues on mobile devices. Shows a red bug button to view parser logs. +
Discord diff --git a/src/core/config.js b/src/core/config.js index 2fe9030..82ad9c7 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -78,5 +78,6 @@ export const defaultSettings = { cha: 10 }, lastDiceRoll: null, // Store last dice roll result - collapsedInventoryLocations: [] // Array of collapsed storage location names + collapsedInventoryLocations: [], // Array of collapsed storage location names + debugMode: false // Enable debug logging visible in UI (for mobile debugging) }; diff --git a/src/core/state.js b/src/core/state.js index c837bba..6cff0d4 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -123,6 +123,12 @@ export const FEATURE_FLAGS = { useNewInventory: true // Enable v2 inventory system with categorized storage }; +/** + * Debug logs storage for mobile-friendly debugging + * Stores parser logs that can be viewed in UI + */ +export let debugLogs = []; + /** * Fallback avatar image (base64-encoded SVG with "?" icon) * Using base64 to avoid quote-encoding issues in HTML attributes @@ -204,3 +210,26 @@ export function setThoughtsContainer($element) { export function setInventoryContainer($element) { $inventoryContainer = $element; } + +export function addDebugLog(message, data = null) { + const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; // HH:MM:SS + const logEntry = { + timestamp, + message, + data: data ? JSON.stringify(data, null, 2) : null + }; + debugLogs.push(logEntry); + + // Keep only last 100 entries to avoid memory issues + if (debugLogs.length > 100) { + debugLogs.shift(); + } +} + +export function clearDebugLogs() { + debugLogs = []; +} + +export function getDebugLogs() { + return debugLogs; +} diff --git a/src/systems/generation/parser.js b/src/systems/generation/parser.js index 6b8adab..9026b45 100644 --- a/src/systems/generation/parser.js +++ b/src/systems/generation/parser.js @@ -3,10 +3,20 @@ * Handles parsing of AI responses to extract tracker data */ -import { extensionSettings, FEATURE_FLAGS } from '../../core/state.js'; +import { extensionSettings, FEATURE_FLAGS, addDebugLog } from '../../core/state.js'; import { saveSettings } from '../../core/persistence.js'; import { extractInventory } from './inventoryParser.js'; +/** + * Helper to log to both console and debug logs array + */ +function debugLog(message, data = null) { + console.log(message, data || ''); + if (extensionSettings.debugMode) { + addDebugLog(message, data); + } +} + /** * Parses the model response to extract the different data sections. * Extracts tracker data from markdown code blocks in the AI response. @@ -22,22 +32,22 @@ export function parseResponse(responseText) { }; // DEBUG: Log full response for troubleshooting - console.log('[RPG Parser] ==================== PARSING AI RESPONSE ===================='); - console.log('[RPG Parser] Response length:', responseText.length, 'chars'); - console.log('[RPG Parser] First 500 chars:', responseText.substring(0, 500)); + debugLog('[RPG Parser] ==================== PARSING AI RESPONSE ===================='); + debugLog('[RPG Parser] Response length:', responseText.length + ' chars'); + debugLog('[RPG Parser] First 500 chars:', responseText.substring(0, 500)); // Extract code blocks const codeBlockRegex = /```([^`]+)```/g; const matches = [...responseText.matchAll(codeBlockRegex)]; - console.log('[RPG Parser] Found', matches.length, 'code blocks'); + debugLog('[RPG Parser] Found', matches.length + ' code blocks'); for (let i = 0; i < matches.length; i++) { const match = matches[i]; const content = match[1].trim(); - console.log(`[RPG Parser] --- Code Block ${i + 1} ---`); - console.log('[RPG Parser] First 300 chars:', content.substring(0, 300)); + debugLog(`[RPG Parser] --- Code Block ${i + 1} ---`); + debugLog('[RPG Parser] First 300 chars:', content.substring(0, 300)); // Match Stats section - flexible patterns const isStats = @@ -65,30 +75,30 @@ export function parseResponse(responseText) { if (isStats) { result.userStats = content; - console.log('[RPG Parser] ✓ Matched: Stats section'); + debugLog('[RPG Parser] ✓ Matched: Stats section'); } else if (isInfoBox) { result.infoBox = content; - console.log('[RPG Parser] ✓ Matched: Info Box section'); + debugLog('[RPG Parser] ✓ Matched: Info Box section'); } else if (isCharacters) { result.characterThoughts = content; - console.log('[RPG Parser] ✓ Matched: Present Characters section'); - console.log('[RPG Parser] Full content:', content); + debugLog('[RPG Parser] ✓ Matched: Present Characters section'); + debugLog('[RPG Parser] Full content:', content); } else { - console.log('[RPG Parser] ✗ No match - checking patterns:'); - console.log('[RPG Parser] - Has "Stats\\n---"?', !!content.match(/Stats\s*\n\s*---/i)); - console.log('[RPG Parser] - Has stat keywords?', !!(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i))); - console.log('[RPG Parser] - Has "Info Box\\n---"?', !!content.match(/Info Box\s*\n\s*---/i)); - console.log('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i))); - console.log('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i)); - console.log('[RPG Parser] - Has " | " + thoughts?', !!(content.includes(" | ") && (content.includes("Thoughts") || content.includes("💭")))); + debugLog('[RPG Parser] ✗ No match - checking patterns:'); + debugLog('[RPG Parser] - Has "Stats\\n---"?', !!content.match(/Stats\s*\n\s*---/i)); + debugLog('[RPG Parser] - Has stat keywords?', !!(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i))); + debugLog('[RPG Parser] - Has "Info Box\\n---"?', !!content.match(/Info Box\s*\n\s*---/i)); + debugLog('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i))); + debugLog('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i)); + debugLog('[RPG Parser] - Has " | " + thoughts?', !!(content.includes(" | ") && (content.includes("Thoughts") || content.includes("💭")))); } } - console.log('[RPG Parser] ==================== PARSE RESULTS ===================='); - console.log('[RPG Parser] Found Stats:', !!result.userStats); - console.log('[RPG Parser] Found Info Box:', !!result.infoBox); - console.log('[RPG Parser] Found Characters:', !!result.characterThoughts); - console.log('[RPG Parser] ======================================================='); + debugLog('[RPG Parser] ==================== PARSE RESULTS ===================='); + debugLog('[RPG Parser] Found Stats:', !!result.userStats); + debugLog('[RPG Parser] Found Info Box:', !!result.infoBox); + debugLog('[RPG Parser] Found Characters:', !!result.characterThoughts); + debugLog('[RPG Parser] ======================================================='); return result; } @@ -100,9 +110,9 @@ export function parseResponse(responseText) { * @param {string} statsText - The raw stats text from AI response */ export function parseUserStats(statsText) { - console.log('[RPG Parser] ==================== PARSING USER STATS ===================='); - console.log('[RPG Parser] Stats text length:', statsText.length, 'chars'); - console.log('[RPG Parser] Stats text preview:', statsText.substring(0, 200)); + debugLog('[RPG Parser] ==================== PARSING USER STATS ===================='); + debugLog('[RPG Parser] Stats text length:', statsText.length + ' chars'); + debugLog('[RPG Parser] Stats text preview:', statsText.substring(0, 200)); try { // Extract percentages and mood/conditions @@ -112,7 +122,7 @@ export function parseUserStats(statsText) { const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/); const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/); - console.log('[RPG Parser] Stat matches:', { + debugLog('[RPG Parser] Stat matches:', { health: healthMatch ? healthMatch[1] : 'NOT FOUND', satiety: satietyMatch ? satietyMatch[1] : 'NOT FOUND', energy: energyMatch ? energyMatch[1] : 'NOT FOUND', @@ -164,7 +174,7 @@ export function parseUserStats(statsText) { } } - console.log('[RPG Parser] Mood/Status match:', { + debugLog('[RPG Parser] Mood/Status match:', { found: !!moodMatch, emoji: moodMatch ? moodMatch[1] : 'NOT FOUND', conditions: moodMatch ? moodMatch[2] : 'NOT FOUND' @@ -175,18 +185,18 @@ export function parseUserStats(statsText) { const inventoryData = extractInventory(statsText); if (inventoryData) { extensionSettings.userStats.inventory = inventoryData; - console.log('[RPG Parser] Inventory v2 extracted:', inventoryData); + debugLog('[RPG Parser] Inventory v2 extracted:', inventoryData); } else { - console.log('[RPG Parser] Inventory v2 extraction failed'); + debugLog('[RPG Parser] Inventory v2 extraction failed'); } } else { // Legacy v1 parsing for backward compatibility const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i); if (inventoryMatch) { extensionSettings.userStats.inventory = inventoryMatch[1].trim(); - console.log('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim()); + debugLog('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim()); } else { - console.log('[RPG Parser] Inventory v1 not found'); + debugLog('[RPG Parser] Inventory v1 not found'); } } @@ -201,7 +211,7 @@ export function parseUserStats(statsText) { extensionSettings.userStats.conditions = moodMatch[2].trim(); // Conditions } - console.log('[RPG Parser] Final userStats after parsing:', { + debugLog('[RPG Parser] Final userStats after parsing:', { health: extensionSettings.userStats.health, satiety: extensionSettings.userStats.satiety, energy: extensionSettings.userStats.energy, @@ -213,11 +223,13 @@ export function parseUserStats(statsText) { }); saveSettings(); - console.log('[RPG Parser] Settings saved successfully'); - console.log('[RPG Parser] ======================================================='); + debugLog('[RPG Parser] Settings saved successfully'); + debugLog('[RPG Parser] ======================================================='); } catch (error) { console.error('[RPG Companion] Error parsing user stats:', error); console.error('[RPG Companion] Stack trace:', error.stack); + debugLog('[RPG Parser] ERROR:', error.message); + debugLog('[RPG Parser] Stack:', error.stack); } } diff --git a/src/systems/ui/debug.js b/src/systems/ui/debug.js new file mode 100644 index 0000000..ecb7a4d --- /dev/null +++ b/src/systems/ui/debug.js @@ -0,0 +1,156 @@ +/** + * Debug UI Module + * Provides mobile-friendly debug log viewer for troubleshooting parsing issues + */ + +import { extensionSettings, getDebugLogs, clearDebugLogs } from '../../core/state.js'; + +/** + * Creates and injects the debug panel into the page + */ +export function createDebugPanel() { + // Remove existing debug panel if any + $('#rpg-debug-panel').remove(); + $('#rpg-debug-toggle').remove(); + + // Create debug panel HTML + const debugPanelHtml = ` +
+
+

🔍 Debug Logs

+
+ + + +
+
+
+
+ `; + + // Create debug toggle button (FAB-style) + const debugToggleHtml = ` + + `; + + // Append to body + $('body').append(debugPanelHtml); + $('body').append(debugToggleHtml); + + // Set up event handlers + setupDebugEventHandlers(); + + // Initial log render + renderDebugLogs(); +} + +/** + * Sets up event handlers for debug panel + */ +function setupDebugEventHandlers() { + // Toggle button + $('#rpg-debug-toggle').on('click', function() { + $('#rpg-debug-panel').toggleClass('rpg-debug-open'); + renderDebugLogs(); // Refresh logs when opening + }); + + // Close button + $('#rpg-debug-close').on('click', function() { + $('#rpg-debug-panel').removeClass('rpg-debug-open'); + }); + + // Copy button + $('#rpg-debug-copy').on('click', function() { + const logs = getDebugLogs(); + const logsText = logs.map(log => { + let text = `[${log.timestamp}] ${log.message}`; + if (log.data) { + text += `\n${log.data}`; + } + return text; + }).join('\n\n'); + + navigator.clipboard.writeText(logsText).then(() => { + // Show feedback + const $btn = $(this); + const $icon = $btn.find('i'); + $icon.removeClass('fa-copy').addClass('fa-check'); + setTimeout(() => { + $icon.removeClass('fa-check').addClass('fa-copy'); + }, 1500); + }).catch(err => { + console.error('Failed to copy logs:', err); + alert('Failed to copy logs. Please use browser console instead.'); + }); + }); + + // Clear button + $('#rpg-debug-clear').on('click', function() { + if (confirm('Clear all debug logs?')) { + clearDebugLogs(); + renderDebugLogs(); + } + }); +} + +/** + * Renders debug logs to the panel + */ +function renderDebugLogs() { + const logs = getDebugLogs(); + const $logsContainer = $('#rpg-debug-logs'); + + if (logs.length === 0) { + $logsContainer.html('
No logs yet. Logs will appear when parser runs.
'); + return; + } + + // Build logs HTML + const logsHtml = logs.map(log => { + let html = `
`; + html += `[${log.timestamp}] `; + html += `${escapeHtml(log.message)}`; + if (log.data) { + html += `
${escapeHtml(log.data)}
`; + } + html += `
`; + return html; + }).join(''); + + $logsContainer.html(logsHtml); + + // Auto-scroll to bottom + $logsContainer[0].scrollTop = $logsContainer[0].scrollHeight; +} + +/** + * Escapes HTML to prevent XSS + */ +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +/** + * Shows or hides debug UI based on debug mode setting + */ +export function updateDebugUIVisibility() { + if (extensionSettings.debugMode) { + if ($('#rpg-debug-panel').length === 0) { + createDebugPanel(); + } + $('#rpg-debug-toggle').show(); + } else { + $('#rpg-debug-toggle').hide(); + $('#rpg-debug-panel').remove(); + } +} diff --git a/style.css b/style.css index d6b53af..56d1c8a 100644 --- a/style.css +++ b/style.css @@ -4779,3 +4779,174 @@ body:has(.rpg-panel.rpg-position-left) #sheld { min-height: 2rem; } } + +/* =================================================================== + Debug Panel Styles - Mobile-Friendly Debug Log Viewer + =================================================================== */ + +/* Debug toggle button (FAB-style) */ +.rpg-debug-toggle { + display: none; /* Hidden by default, shown when debugMode is enabled */ + align-items: center; + justify-content: center; + position: fixed; + bottom: 20px; + left: 20px; + width: 50px; + height: 50px; + border-radius: 50%; + background: #ff6b6b; + border: 2px solid #c92a2a; + color: white; + font-size: 1.5rem; + cursor: pointer; + z-index: 10003; /* Above everything else */ + box-shadow: 0 4px 12px rgba(255, 107, 107, 0.5); + transition: all 0.3s ease; +} + +.rpg-debug-toggle:hover { + transform: scale(1.1); + box-shadow: 0 6px 16px rgba(255, 107, 107, 0.7); +} + +.rpg-debug-toggle:active { + transform: scale(0.95); +} + +@media (max-width: 1000px) { + .rpg-debug-toggle { + display: flex; /* Show on mobile when debugMode is enabled */ + } +} + +/* Debug panel */ +.rpg-debug-panel { + position: fixed; + bottom: 0; + left: 0; + right: 0; + height: 60vh; + background: var(--SmartThemeBlurTintColor, #1a1a1a); + border-top: 2px solid var(--SmartThemeBorderColor, #333); + z-index: 10002; + display: flex; + flex-direction: column; + transform: translateY(100%); + transition: transform 0.3s ease; + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.5); +} + +.rpg-debug-panel.rpg-debug-open { + transform: translateY(0); +} + +.rpg-debug-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid var(--SmartThemeBorderColor, #333); + background: var(--SmartThemeBodyColor, #0d0d0d); +} + +.rpg-debug-header h3 { + margin: 0; + font-size: 1.2rem; + color: var(--rpg-text, #ecf0f1); +} + +.rpg-debug-actions { + display: flex; + gap: 0.5rem; +} + +.rpg-debug-actions button { + background: var(--SmartThemeBlurTintColor, #2a2a2a); + border: 1px solid var(--SmartThemeBorderColor, #444); + color: var(--rpg-text, #ecf0f1); + width: 36px; + height: 36px; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; +} + +.rpg-debug-actions button:hover { + background: var(--SmartThemeBorderColor, #333); + transform: scale(1.05); +} + +.rpg-debug-actions button:active { + transform: scale(0.95); +} + +.rpg-debug-logs { + flex: 1; + overflow-y: auto; + padding: 1rem; + font-family: 'Courier New', Courier, monospace; + font-size: 0.85rem; + line-height: 1.4; + color: var(--rpg-text, #ecf0f1); +} + +.rpg-debug-empty { + text-align: center; + padding: 2rem; + color: #888; + font-style: italic; +} + +.rpg-debug-entry { + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.rpg-debug-entry:last-child { + border-bottom: none; +} + +.rpg-debug-time { + color: #888; + font-size: 0.75rem; +} + +.rpg-debug-message { + color: #4fc3f7; +} + +.rpg-debug-data { + margin: 0.5rem 0 0 0; + padding: 0.75rem; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow-x: auto; + color: #9ccc65; + font-size: 0.8rem; + white-space: pre-wrap; + word-break: break-word; +} + +/* Desktop view - smaller panel in bottom right */ +@media (min-width: 1001px) { + .rpg-debug-panel { + bottom: 20px; + left: auto; + right: 20px; + width: 600px; + max-width: 90vw; + height: 400px; + border-radius: 12px; + border: 2px solid var(--SmartThemeBorderColor, #333); + } + + .rpg-debug-toggle { + display: flex; /* Show on desktop too when debugMode enabled */ + } +}