Files
rpg-companion-sillytavern/index.js
T
ARIA 59b125fd2a Fixes #20: Call updateSectionVisibility() during init to show equipment tab
The equipment section has display:none in CSS and updateSectionVisibility()
is the only function that calls .show() on it. It was never called during
initialization, only when settings toggles changed. Added the call to the
main init flow so the equipment tab becomes visible on page load.
2026-07-12 17:56:35 +02:00

466 lines
18 KiB
JavaScript

/**
* RPG Companion - Main Entry Point
* SillyTavern extension for RPG-style character tracking.
*
* This file is a thin orchestrator that delegates to sub-modules.
* See src/ directory for the actual implementation.
*/
import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings } from '../../../extensions.js';
import { eventSource, event_types, substituteParams, chat, saveSettingsDebounced, chat_metadata, saveChatDebounced, user_avatar, getThumbnailUrl, characters, this_chid, extension_prompt_types, extension_prompt_roles, setExtensionPrompt, reloadCurrentChat, Generate, getRequestHeaders } from '../../../../script.js';
import { selected_group, getGroupMembers } from '../../../group-chats.js';
import { power_user } from '../../../power-user.js';
// Core modules
import { extensionName, extensionFolderPath } from './src/core/config.js';
import { i18n } from './src/core/i18n.js';
import { migrateToV3JSON } from './src/utils/jsonMigration.js';
import {
extensionSettings,
lastGeneratedData,
committedTrackerData,
lastActionWasSwipe,
isGenerating,
isPlotProgression,
pendingDiceRoll,
FALLBACK_AVATAR_DATA_URI,
$panelContainer,
$userStatsContainer,
$infoBoxContainer,
$thoughtsContainer,
$inventoryContainer,
$equipmentContainer,
$questsContainer,
$musicPlayerContainer,
setExtensionSettings,
updateExtensionSettings,
setLastGeneratedData,
updateLastGeneratedData,
setCommittedTrackerData,
updateCommittedTrackerData,
setLastActionWasSwipe,
setIsGenerating,
setIsPlotProgression,
setPendingDiceRoll,
setPanelContainer,
setUserStatsContainer,
setInfoBoxContainer,
setThoughtsContainer,
setInventoryContainer,
setEquipmentContainer,
setQuestsContainer,
setMusicPlayerContainer,
clearSessionAvatarPrompts,
clearDomCache,
clearDebugLogs
} from './src/core/state.js';
import { loadSettings, saveSettings, saveChatData, loadChatData, updateMessageSwipeData, commitTrackerDataFromPriorMessage } from './src/core/persistence.js';
import { registerAllEvents, on as onEvent } from './src/core/events.js';
import { addExtensionSettings } from './src/core/settingsPanel.js';
// Generation & Parsing modules
import {
generateTrackerExample,
generateTrackerInstructions,
generateContextualSummary,
generateRPGPromptText,
generateSeparateUpdatePrompt
} from './src/systems/generation/promptBuilder.js';
import { parseResponse, parseUserStats } from './src/systems/generation/parser.js';
import { updateRPGData, testExternalAPIConnection } from './src/systems/generation/apiClient.js';
import { onGenerationStarted } from './src/systems/generation/injector.js';
// Rendering modules
import { getSafeThumbnailUrl } from './src/utils/avatars.js';
import { renderUserStats } from './src/systems/rendering/userStats.js';
import { renderInfoBox, updateInfoBoxField } from './src/systems/rendering/infoBox.js';
import {
renderThoughts,
updateCharacterField,
removeCharacter,
updateChatThoughts,
createThoughtPanel
} from './src/systems/rendering/thoughts.js';
import { renderInventory } from './src/systems/rendering/inventory.js';
import { renderEquipment } from './src/systems/rendering/equipment.js';
import { renderQuests } from './src/systems/rendering/quests.js';
import { renderMusicPlayer } from './src/systems/rendering/musicPlayer.js';
import { toggleSnowflakes, initSnowflakes } from './src/systems/ui/snowflakes.js';
import { toggleDynamicWeather, initWeatherEffects, updateWeatherEffect } from './src/systems/ui/weatherEffects.js';
// Interaction modules
import { initInventoryEventListeners } from './src/systems/interaction/inventoryActions.js';
import { initEquipmentEventListeners } from './src/systems/interaction/equipmentActions.js';
// UI Systems modules
import {
applyTheme,
applyCustomTheme,
toggleCustomColors,
toggleAnimations,
updateFeatureTogglesVisibility,
updateSettingsPopupTheme,
applyCustomThemeToSettingsPopup
} from './src/systems/ui/theme.js';
import {
DiceModal,
SettingsModal,
setupDiceRoller,
setupSettingsPopup,
updateDiceDisplay,
addDiceQuickReply,
getSettingsModal,
showWelcomeModalIfNeeded,
showDeprecationModalIfNeeded
} from './src/systems/ui/modals.js';
import { initTrackerEditor } from './src/systems/ui/trackerEditor.js';
import { initPromptsEditor } from './src/systems/ui/promptsEditor.js';
import {
initChapterCheckpointUI,
injectCheckpointButton,
updateAllCheckpointIndicators,
cleanupCheckpointUI
} from './src/systems/ui/checkpointUI.js';
import { restoreCheckpointOnLoad } from './src/systems/features/chapterCheckpoint.js';
import {
togglePlotButtons,
updateCollapseToggleIcon,
setupCollapseToggle,
updatePanelVisibility,
updateSectionVisibility,
applyPanelPosition,
updateGenerationModeUI
} from './src/systems/ui/layout.js';
import {
setupMobileToggle,
constrainFabToViewport,
setupMobileTabs,
removeMobileTabs,
setupMobileKeyboardHandling,
setupContentEditableScrolling,
updateMobileTabLabels,
updateFabWidgets
} from './src/systems/ui/mobile.js';
import {
setupDesktopTabs,
removeDesktopTabs,
updateStripWidgets
} from './src/systems/ui/desktop.js';
import {
removeAlternatePresentCharactersPanel,
renderAlternatePresentCharacters
} from './src/systems/ui/alternatePresentCharacters.js';
import {
initThoughtBasedExpressions,
queueThoughtBasedExpressionsUpdate,
onThoughtBasedExpressionsSettingChanged,
onAlternatePresentCharactersVisibilityChanged,
onHideDefaultExpressionDisplaySettingChanged,
clearThoughtBasedExpressionsCache,
onThoughtBasedExpressionsChatChanged,
setThoughtBasedExpressionsRefreshHandler
} from './src/systems/integration/thoughtBasedExpressions.js';
// Feature modules
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
import { setupClassicStatsButtons } from './src/systems/features/classicStats.js';
import { ensureHtmlCleaningRegex, detectConflictingRegexScripts, ensureTrackerCleaningRegex } from './src/systems/features/htmlCleaning.js';
import { ensureJsonCleaningRegex, removeJsonCleaningRegex } from './src/systems/features/jsonCleaning.js';
import { parseAndStoreSpotifyUrl } from './src/systems/features/musicPlayer.js';
import { DEFAULT_HTML_PROMPT } from './src/systems/generation/promptBuilder.js';
import { openEncounterModal } from './src/systems/ui/encounterUI.js';
// Integration modules
import {
commitTrackerData,
onMessageSent,
onMessageReceived,
onCharacterChanged,
onChatLoaded,
onMessageDeleted,
onMessageSwiped,
scheduleChatStateRehydration,
updatePersonaAvatar,
clearExtensionPrompts,
onGenerationEnded,
initHistoryInjection
} from './src/systems/integration/sillytavern.js';
// Settings UI event listeners (extracted from initUI)
import { bindSettingsListeners, updateWeatherSubOptionsVisibility } from './src/systems/ui/settingsListeners.js';
// Set up thought-based expressions refresh handler
setThoughtBasedExpressionsRefreshHandler(() => {
renderAlternatePresentCharacters({ useCommittedFallback: true });
});
/**
* Updates UI elements that are dynamically generated and not covered by data-i18n-key.
*/
function updateDynamicLabels() {
const refreshBtn = document.getElementById('rpg-manual-update');
if (refreshBtn && !refreshBtn.disabled) {
const refreshText = i18n.getTranslation('template.mainPanel.refreshRpgInfo') || 'Refresh RPG Info';
refreshBtn.innerHTML = `<i class="fa-solid fa-sync"></i> ${refreshText}`;
}
updateDiceDisplay();
updateMobileTabLabels();
}
/**
* Initializes the UI for the extension.
*/
async function initUI() {
await i18n.init();
if (!extensionSettings.enabled) {
return;
}
// Load and inject the HTML template
const templateHtml = await renderExtensionTemplateAsync(extensionName, 'template');
$('body').append(templateHtml);
// Add mobile toggle button (FAB)
const theme = extensionSettings.theme || 'default';
const mobileToggleHtml = `
<button id="rpg-mobile-toggle" class="rpg-mobile-toggle" data-theme="${theme}" title="Toggle RPG Panel">
<i class="fa-solid fa-dice-d20"></i>
</button>
`;
$('body').append(mobileToggleHtml);
if (window.innerWidth > 1000) {
$('#rpg-mobile-toggle').hide();
}
// Cache UI elements using state setters
// Clear stale DOM references first to prevent memory leaks on panel rebuild
clearDomCache();
setPanelContainer($('#rpg-companion-panel'));
setUserStatsContainer($('#rpg-user-stats'));
setInfoBoxContainer($('#rpg-info-box'));
setThoughtsContainer($('#rpg-thoughts'));
setInventoryContainer($('#rpg-inventory'));
setEquipmentContainer($('#rpg-equipment'));
setQuestsContainer($('#rpg-quests'));
setMusicPlayerContainer($('#rpg-music-player'));
// Re-apply translations to catch all new elements from the template
i18n.applyTranslations(document.body);
// Bind all settings event listeners (extracted to settingsListeners.js)
bindSettingsListeners($);
// Initialize mobile UI
setupMobileToggle();
constrainFabToViewport();
setupMobileTabs();
setupMobileKeyboardHandling();
setupContentEditableScrolling();
// Initialize desktop UI
setupDesktopTabs();
// Initialize collapse toggle
setupCollapseToggle();
// Initialize dice roller
setupDiceRoller();
// Initialize settings popup
setupSettingsPopup();
// Initialize tracker editor
initTrackerEditor();
// Initialize prompts editor
initPromptsEditor();
// Initialize plot buttons
setupPlotButtons(sendPlotProgression, openEncounterModal);
// Initialize classic stats buttons
setupClassicStatsButtons();
// Initialize inventory event listeners
initInventoryEventListeners();
initEquipmentEventListeners();
// Initialize chapter checkpoint UI
initChapterCheckpointUI();
injectCheckpointButton();
// Expose weather effect functions globally for cross-module access
if (!window.RPGCompanion) {
window.RPGCompanion = {};
}
window.RPGCompanion.updateWeatherEffect = updateWeatherEffect;
}
// Main initialization
jQuery(async () => {
try {
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);
}
// Check if migration to v3 JSON format is needed
try {
if (extensionSettings.settingsVersion < 3) {
await migrateToV3JSON();
updateExtensionSettings({ settingsVersion: 3 });
await saveSettings();
}
} catch (error) {
console.error('[RPG Companion] Migration to v3 failed:', error);
}
// Initialize i18n early for the settings panel
await i18n.init();
// Set up a central listener for language changes to update dynamic UI parts
i18n.addEventListener('languageChanged', updateDynamicLabels);
// Add extension settings to Extensions tab
try {
await addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel);
} catch (error) {
console.error('[RPG Companion] Failed to add extension settings tab:', error);
}
// Initialize UI
try {
await initUI();
} catch (error) {
console.error('[RPG Companion] UI initialization failed:', error);
throw error;
}
// Load chat-specific data for current chat
try {
loadChatData();
scheduleChatStateRehydration();
initThoughtBasedExpressions();
updateFabWidgets();
updateStripWidgets();
// Ensure section visibility matches settings (equipment is hidden by CSS, needs explicit .show())
updateSectionVisibility();
renderEquipment();
} catch (error) {
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
}
// Import cleaning regexes
try { await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] HTML regex import failed:', error); }
try { await ensureTrackerCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] Tracker cleaning regex import failed:', error); }
try { await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] JSON cleaning regex setup failed:', error); }
// Detect conflicting regex scripts
try {
detectConflictingRegexScripts(st_extension_settings);
} catch (error) {
console.error('[RPG Companion] Conflict detection failed:', error);
}
// Initialize history injection event listeners
try { initHistoryInjection(); } catch (error) { console.error('[RPG Companion] History injection init failed:', error); }
// Register all event listeners
try {
registerAllEvents({
[event_types.MESSAGE_SENT]: onMessageSent,
[event_types.GENERATION_STARTED]: onGenerationStarted,
[event_types.MESSAGE_RECEIVED]: onMessageReceived,
[event_types.GENERATION_STOPPED]: onGenerationEnded,
[event_types.GENERATION_ENDED]: onGenerationEnded,
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts, clearDebugLogs],
[event_types.CHAT_LOADED]: onChatLoaded,
[event_types.MESSAGE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
});
// Use tracked event registration (onEvent) instead of direct eventSource.on()
// This ensures all handlers are tracked for cleanup by unregisterAllEvents()
onEvent(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => {
if (!extensionSettings.enabled) return;
const renderedMessage = chat[messageId];
if (renderedMessage && !renderedMessage.is_user && !renderedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_UPDATED, (messageId) => {
if (!extensionSettings.enabled) return;
const updatedMessage = chat[messageId];
if (updatedMessage && !updatedMessage.is_user && !updatedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_SWIPED, (messageIndex) => {
if (!extensionSettings.enabled) return;
const swipedMessage = chat[messageIndex];
if (swipedMessage && !swipedMessage.is_user && !swipedMessage.is_system) {
queueThoughtBasedExpressionsUpdate({ immediate: true });
}
});
onEvent(event_types.CHAT_CHANGED, () => {
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_SWIPE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
} catch (error) {
console.error('[RPG Companion] Event registration failed:', error);
throw error;
}
// Restore checkpoint state if one exists
await restoreCheckpointOnLoad();
// Initialize snowflakes effect if enabled
try { initSnowflakes(); } catch (error) { console.error('[RPG Companion] Snowflakes initialization failed:', error); }
// Show startup modals
try {
const deprecationModalShown = showDeprecationModalIfNeeded();
if (!deprecationModalShown) {
showWelcomeModalIfNeeded();
}
} catch (error) {
console.error('[RPG Companion] Startup modal failed:', error);
}
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);
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 }
);
}
});