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.
466 lines
18 KiB
JavaScript
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 }
|
|
);
|
|
}
|
|
});
|