Merge pull request #12 from paperboygold/feature/defensive-initialization
fix(init): add defensive error handling for edge cases
This commit is contained in:
@@ -108,7 +108,7 @@ import {
|
|||||||
// Feature modules
|
// Feature modules
|
||||||
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
|
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
|
||||||
import { setupClassicStatsButtons } from './src/systems/features/classicStats.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
|
// Integration modules
|
||||||
import {
|
import {
|
||||||
@@ -418,18 +418,69 @@ async function initUI() {
|
|||||||
* Main initialization function.
|
* Main initialization function.
|
||||||
*/
|
*/
|
||||||
jQuery(async () => {
|
jQuery(async () => {
|
||||||
|
try {
|
||||||
|
console.log('[RPG Companion] Starting initialization...');
|
||||||
|
|
||||||
|
// Load settings with validation
|
||||||
try {
|
try {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add extension settings to Extensions tab
|
||||||
|
try {
|
||||||
await addExtensionSettings();
|
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();
|
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
|
// Load chat-specific data for current chat
|
||||||
|
try {
|
||||||
loadChatData();
|
loadChatData();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
|
||||||
|
}
|
||||||
|
|
||||||
// Import the HTML cleaning regex if needed
|
// Import the HTML cleaning regex if needed
|
||||||
|
try {
|
||||||
await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced);
|
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
|
// Register all event listeners
|
||||||
|
try {
|
||||||
registerAllEvents({
|
registerAllEvents({
|
||||||
[event_types.MESSAGE_SENT]: onMessageSent,
|
[event_types.MESSAGE_SENT]: onMessageSent,
|
||||||
[event_types.GENERATION_STARTED]: onGenerationStarted,
|
[event_types.GENERATION_STARTED]: onGenerationStarted,
|
||||||
@@ -439,10 +490,21 @@ jQuery(async () => {
|
|||||||
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
|
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
|
||||||
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
|
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log('[RPG Companion] Extension loaded successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Failed to initialize:', error);
|
console.error('[RPG Companion] Event registration failed:', error);
|
||||||
throw error;
|
throw error; // This is critical - can't continue without events
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[RPG Companion] ✅ Extension loaded successfully');
|
||||||
|
} catch (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 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+61
-2
@@ -18,13 +18,66 @@ import { migrateInventory } from '../utils/migration.js';
|
|||||||
|
|
||||||
const extensionName = 'third-party/rpg-companion-sillytavern';
|
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.
|
* Loads the extension settings from the global settings object.
|
||||||
* Automatically migrates v1 inventory to v2 format if needed.
|
* Automatically migrates v1 inventory to v2 format if needed.
|
||||||
*/
|
*/
|
||||||
export function loadSettings() {
|
export function loadSettings() {
|
||||||
if (power_user.extensions && power_user.extensions[extensionName]) {
|
try {
|
||||||
updateExtensionSettings(power_user.extensions[extensionName]);
|
// 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);
|
// console.log('[RPG Companion] Settings loaded:', extensionSettings);
|
||||||
} else {
|
} else {
|
||||||
// console.log('[RPG Companion] No saved settings found, using defaults');
|
// console.log('[RPG Companion] No saved settings found, using defaults');
|
||||||
@@ -39,6 +92,12 @@ export function loadSettings() {
|
|||||||
saveSettings(); // Persist migrated 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,37 @@
|
|||||||
* Automatically imports HTML cleaning regex to strip HTML tags from outgoing prompts
|
* 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.
|
* 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.
|
* This regex removes HTML tags from outgoing prompts to prevent formatting issues.
|
||||||
@@ -11,10 +42,25 @@
|
|||||||
*/
|
*/
|
||||||
export async function ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced) {
|
export async function ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced) {
|
||||||
try {
|
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
|
// Check if the HTML cleaning regex already exists
|
||||||
const scriptName = 'Clean HTML (From Outgoing Prompt)';
|
const scriptName = 'Clean HTML (From Outgoing Prompt)';
|
||||||
const existingScripts = st_extension_settings?.regex || [];
|
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) {
|
if (alreadyExists) {
|
||||||
console.log('[RPG Companion] HTML cleaning regex already exists, skipping import');
|
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);
|
st_extension_settings.regex.push(regexScript);
|
||||||
|
|
||||||
// Save the changes using the already-imported function
|
// Save the changes using the already-imported function
|
||||||
|
if (typeof saveSettingsDebounced === 'function') {
|
||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
|
} else {
|
||||||
|
console.warn('[RPG Companion] saveSettingsDebounced is not a function, cannot save HTML regex');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('[RPG Companion] ✅ HTML cleaning regex imported successfully');
|
console.log('[RPG Companion] ✅ HTML cleaning regex imported successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Failed to import HTML cleaning regex:', 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
|
// Don't throw - this is a nice-to-have feature
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2866,7 +2866,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
background: var(--rpg-highlight, #e94560);
|
background: var(--rpg-highlight, #e94560);
|
||||||
color: white;
|
color: white;
|
||||||
border: 2px solid var(--rpg-bg, rgba(30, 30, 50, 0.95));
|
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;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -2894,7 +2894,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 1.85vw;
|
font-size: clamp(1.3rem, 1.4rem, 1.5rem);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
animation: thoughtIconPulse 2s ease-in-out infinite;
|
animation: thoughtIconPulse 2s ease-in-out infinite;
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
|
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 */
|
/* Thought content on the right */
|
||||||
.rpg-thought-content {
|
.rpg-thought-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: clamp(0.68vw, 0.7vw, 0.72vw);
|
font-size: clamp(0.85rem, 0.9rem, 0.95rem);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: var(--rpg-text, #eaeaea);
|
color: var(--rpg-text, #eaeaea);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
|||||||
Reference in New Issue
Block a user