diff --git a/index.js b/index.js index a0b696d..5e67d9e 100644 --- a/index.js +++ b/index.js @@ -108,7 +108,7 @@ import { // Feature modules import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js'; import { setupClassicStatsButtons } from './src/systems/features/classicStats.js'; -import { ensureHtmlCleaningRegex } from './src/systems/features/htmlCleaning.js'; +import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js'; // Integration modules import { @@ -419,30 +419,92 @@ async function initUI() { */ jQuery(async () => { try { - loadSettings(); - await addExtensionSettings(); - await initUI(); + console.log('[RPG Companion] Starting initialization...'); + + // Load settings with validation + try { + loadSettings(); + } catch (error) { + console.error('[RPG Companion] Settings load failed, continuing with defaults:', error); + } + + // Add extension settings to Extensions tab + try { + await addExtensionSettings(); + } catch (error) { + console.error('[RPG Companion] Failed to add extension settings tab:', error); + // Don't throw - extension can still work without settings tab + } + + // Initialize UI + try { + await initUI(); + } catch (error) { + console.error('[RPG Companion] UI initialization failed:', error); + throw error; // This is critical - can't continue without UI + } // Load chat-specific data for current chat - loadChatData(); + try { + loadChatData(); + } catch (error) { + console.error('[RPG Companion] Chat data load failed, using defaults:', error); + } // Import the HTML cleaning regex if needed - await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); + try { + await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); + } catch (error) { + console.error('[RPG Companion] HTML regex import failed:', error); + // Non-critical - continue without it + } + + // Detect conflicting regex scripts from old manual formatters + try { + const conflicts = detectConflictingRegexScripts(st_extension_settings); + if (conflicts.length > 0) { + console.warn('[RPG Companion] ⚠️ Detected old manual formatting regex scripts that may conflict:'); + conflicts.forEach(name => console.warn(` - ${name}`)); + console.warn('[RPG Companion] Consider disabling these regexes as the extension now handles formatting automatically.'); + + // Show user-friendly warning (non-blocking) + toastr.warning( + `Found ${conflicts.length} old RPG formatting regex script(s). These may conflict with the extension. Check console for details.`, + 'RPG Companion Warning', + { timeOut: 8000 } + ); + } + } catch (error) { + console.error('[RPG Companion] Conflict detection failed:', error); + // Non-critical - continue anyway + } // Register all event listeners - registerAllEvents({ - [event_types.MESSAGE_SENT]: onMessageSent, - [event_types.GENERATION_STARTED]: onGenerationStarted, - [event_types.MESSAGE_RECEIVED]: onMessageReceived, - [event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar], - [event_types.MESSAGE_SWIPED]: onMessageSwiped, - [event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar, - [event_types.SETTINGS_UPDATED]: updatePersonaAvatar - }); + try { + registerAllEvents({ + [event_types.MESSAGE_SENT]: onMessageSent, + [event_types.GENERATION_STARTED]: onGenerationStarted, + [event_types.MESSAGE_RECEIVED]: onMessageReceived, + [event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar], + [event_types.MESSAGE_SWIPED]: onMessageSwiped, + [event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar, + [event_types.SETTINGS_UPDATED]: updatePersonaAvatar + }); + } catch (error) { + console.error('[RPG Companion] Event registration failed:', error); + throw error; // This is critical - can't continue without events + } - // console.log('[RPG Companion] Extension loaded successfully'); + console.log('[RPG Companion] ✅ Extension loaded successfully'); } catch (error) { - console.error('[RPG Companion] Failed to initialize:', error); - throw error; + console.error('[RPG Companion] ❌ Critical initialization failure:', error); + console.error('[RPG Companion] Error details:', error.message, error.stack); + + // Show user-friendly error message + toastr.error( + 'RPG Companion failed to initialize. Check console for details. Please try refreshing the page or resetting extension settings.', + 'RPG Companion Error', + { timeOut: 10000 } + ); } }); diff --git a/src/core/persistence.js b/src/core/persistence.js index e143bdd..7db626d 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -18,26 +18,85 @@ import { migrateInventory } from '../utils/migration.js'; const extensionName = 'third-party/rpg-companion-sillytavern'; +/** + * Validates extension settings structure + * @param {Object} settings - Settings object to validate + * @returns {boolean} True if valid, false otherwise + */ +function validateSettings(settings) { + if (!settings || typeof settings !== 'object') { + return false; + } + + // Check for required top-level properties + if (typeof settings.enabled !== 'boolean' || + typeof settings.autoUpdate !== 'boolean' || + !settings.userStats || typeof settings.userStats !== 'object') { + console.warn('[RPG Companion] Settings validation failed: missing required properties'); + return false; + } + + // Validate userStats structure + const stats = settings.userStats; + if (typeof stats.health !== 'number' || + typeof stats.satiety !== 'number' || + typeof stats.energy !== 'number') { + console.warn('[RPG Companion] Settings validation failed: invalid userStats structure'); + return false; + } + + return true; +} + /** * Loads the extension settings from the global settings object. * Automatically migrates v1 inventory to v2 format if needed. */ export function loadSettings() { - if (power_user.extensions && power_user.extensions[extensionName]) { - updateExtensionSettings(power_user.extensions[extensionName]); - // console.log('[RPG Companion] Settings loaded:', extensionSettings); - } else { - // console.log('[RPG Companion] No saved settings found, using defaults'); - } - - // Migrate inventory if feature flag enabled - if (FEATURE_FLAGS.useNewInventory) { - const migrationResult = migrateInventory(extensionSettings.userStats.inventory); - if (migrationResult.migrated) { - console.log(`[RPG Companion] Inventory migrated from ${migrationResult.source} to v2 format`); - extensionSettings.userStats.inventory = migrationResult.inventory; - saveSettings(); // Persist migrated inventory + try { + // Validate power_user structure + if (!power_user || typeof power_user !== 'object') { + console.warn('[RPG Companion] power_user is not available, using default settings'); + return; } + + if (!power_user.extensions) { + power_user.extensions = {}; + console.log('[RPG Companion] Created power_user.extensions object'); + } + + if (power_user.extensions[extensionName]) { + const savedSettings = power_user.extensions[extensionName]; + + // Validate loaded settings + if (!validateSettings(savedSettings)) { + console.warn('[RPG Companion] Loaded settings failed validation, using defaults'); + console.warn('[RPG Companion] Invalid settings:', savedSettings); + // Save valid defaults to replace corrupt data + saveSettings(); + return; + } + + updateExtensionSettings(savedSettings); + // console.log('[RPG Companion] Settings loaded:', extensionSettings); + } else { + // console.log('[RPG Companion] No saved settings found, using defaults'); + } + + // Migrate inventory if feature flag enabled + if (FEATURE_FLAGS.useNewInventory) { + const migrationResult = migrateInventory(extensionSettings.userStats.inventory); + if (migrationResult.migrated) { + console.log(`[RPG Companion] Inventory migrated from ${migrationResult.source} to v2 format`); + extensionSettings.userStats.inventory = migrationResult.inventory; + saveSettings(); // Persist migrated inventory + } + } + } catch (error) { + console.error('[RPG Companion] Error loading settings:', error); + console.error('[RPG Companion] Error details:', error.message, error.stack); + console.warn('[RPG Companion] Using default settings due to load error'); + // Settings will remain at defaults from state.js } } diff --git a/src/systems/features/htmlCleaning.js b/src/systems/features/htmlCleaning.js index 3c1e4b0..8023389 100644 --- a/src/systems/features/htmlCleaning.js +++ b/src/systems/features/htmlCleaning.js @@ -3,6 +3,37 @@ * Automatically imports HTML cleaning regex to strip HTML tags from outgoing prompts */ +/** + * Detects old manual regex formatters that might conflict with the extension + * @param {Object} st_extension_settings - SillyTavern extension settings object + * @returns {Array} Array of conflicting regex script names + */ +export function detectConflictingRegexScripts(st_extension_settings) { + const conflictingNames = [ + 'Format User\'s Stats (Only Output)', + 'Format User\'s Stats (Only Display)', + 'Format Info Box (Only Output)', + 'Format Info Box (Only Display)', + 'Format Character\'s Thoughts (Only Output)', + 'Format Character\'s Thoughts (Only Display)', + 'Format Character Thoughts (Only Output)', + 'Format Character Thoughts (Only Display)' + ]; + + const existingScripts = st_extension_settings?.regex || []; + const conflicts = []; + + for (const script of existingScripts) { + if (script && script.scriptName && conflictingNames.some(name => + script.scriptName.toLowerCase().includes(name.toLowerCase()) + )) { + conflicts.push(script.scriptName); + } + } + + return conflicts; +} + /** * Automatically imports the HTML cleaning regex script if it doesn't already exist. * This regex removes HTML tags from outgoing prompts to prevent formatting issues. @@ -11,10 +42,25 @@ */ export async function ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced) { try { + // Validate extension settings structure + if (!st_extension_settings || typeof st_extension_settings !== 'object') { + console.warn('[RPG Companion] Invalid extension_settings object, skipping HTML regex import'); + return; + } + // Check if the HTML cleaning regex already exists const scriptName = 'Clean HTML (From Outgoing Prompt)'; const existingScripts = st_extension_settings?.regex || []; - const alreadyExists = existingScripts.some(script => script.scriptName === scriptName); + + // Validate regex array + if (!Array.isArray(existingScripts)) { + console.warn('[RPG Companion] extension_settings.regex is not an array, resetting to empty array'); + st_extension_settings.regex = []; + } + + const alreadyExists = existingScripts.some(script => + script && typeof script === 'object' && script.scriptName === scriptName + ); if (alreadyExists) { console.log('[RPG Companion] HTML cleaning regex already exists, skipping import'); @@ -55,11 +101,16 @@ export async function ensureHtmlCleaningRegex(st_extension_settings, saveSetting st_extension_settings.regex.push(regexScript); // Save the changes using the already-imported function - saveSettingsDebounced(); + if (typeof saveSettingsDebounced === 'function') { + saveSettingsDebounced(); + } else { + console.warn('[RPG Companion] saveSettingsDebounced is not a function, cannot save HTML regex'); + } console.log('[RPG Companion] ✅ HTML cleaning regex imported successfully'); } catch (error) { console.error('[RPG Companion] Failed to import HTML cleaning regex:', error); + console.error('[RPG Companion] Error details:', error.message, error.stack); // Don't throw - this is a nice-to-have feature } } diff --git a/style.css b/style.css index ed880ba..b1732ce 100644 --- a/style.css +++ b/style.css @@ -2866,7 +2866,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld { background: var(--rpg-highlight, #e94560); color: white; border: 2px solid var(--rpg-bg, rgba(30, 30, 50, 0.95)); - font-size: 1.7vw; + font-size: clamp(0.9rem, 1rem, 1.1rem); line-height: 1; cursor: pointer; display: flex; @@ -2894,7 +2894,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld { display: flex; align-items: center; justify-content: center; - font-size: 1.85vw; + font-size: clamp(1.3rem, 1.4rem, 1.5rem); cursor: pointer; animation: thoughtIconPulse 2s ease-in-out infinite; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); @@ -3016,7 +3016,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld { /* Thought content on the right */ .rpg-thought-content { flex: 1; - font-size: clamp(0.68vw, 0.7vw, 0.72vw); + font-size: clamp(0.85rem, 0.9rem, 0.95rem); line-height: 1.5; color: var(--rpg-text, #eaeaea); font-style: italic;