Merge pull request #12 from paperboygold/feature/defensive-initialization

fix(init): add defensive error handling for edge cases
This commit is contained in:
Spicy Marinara
2025-10-18 12:47:00 +02:00
committed by GitHub
4 changed files with 209 additions and 37 deletions
+80 -18
View File
@@ -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 }
);
}
});
+73 -14
View File
@@ -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
}
}
+53 -2
View File
@@ -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<string>} 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
}
}
+3 -3
View File
@@ -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;