Files
rpg-companion-sillytavern/index.js
T
2025-12-05 20:51:05 +01:00

970 lines
35 KiB
JavaScript

import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings } from '../../../extensions.js';
import { event_types, saveSettingsDebounced, getRequestHeaders } from '../../../../script.js';
// Core modules
import { extensionName, extensionFolderPath } from './src/core/config.js';
import { i18n } from './src/core/i18n.js';
import {
extensionSettings,
setPanelContainer,
setUserStatsContainer,
setInfoBoxContainer,
setThoughtsContainer,
setSkillsContainer,
setInventoryContainer,
setQuestsContainer
} from './src/core/state.js';
import { loadSettings, saveSettings, loadChatData } from './src/core/persistence.js';
import { registerAllEvents } from './src/core/events.js';
// Generation & Parsing modules
import { updateRPGData } from './src/systems/generation/apiClient.js';
import { onGenerationStarted } from './src/systems/generation/injector.js';
// Rendering modules
import { renderUserStats } from './src/systems/rendering/userStats.js';
import { renderInfoBox } from './src/systems/rendering/infoBox.js';
import {
renderThoughts,
updateChatThoughts
} from './src/systems/rendering/thoughts.js';
import { renderInventory } from './src/systems/rendering/inventory.js';
import { renderQuests } from './src/systems/rendering/quests.js';
import { renderSkills } from './src/systems/rendering/skills.js';
// Interaction modules
import { initInventoryEventListeners } from './src/systems/interaction/inventoryActions.js';
// UI Systems modules
import {
applyTheme,
applyCustomTheme,
toggleCustomColors,
toggleAnimations,
updateSettingsPopupTheme
} from './src/systems/ui/theme.js';
import {
setupDiceRoller,
setupSettingsPopup,
updateDiceDisplay,
addDiceQuickReply,
getSettingsModal
} from './src/systems/ui/modals.js';
import {
initTrackerEditor
} from './src/systems/ui/trackerEditor.js';
import {
togglePlotButtons,
setupCollapseToggle,
updatePanelVisibility,
updateSectionVisibility,
applyPanelPosition,
updateGenerationModeUI
} from './src/systems/ui/layout.js';
import {
setupMobileToggle,
setupMobileKeyboardHandling,
setupContentEditableScrolling,
updateMobileTabLabels
} from './src/systems/ui/mobile.js';
import {
setupDesktopTabs,
removeDesktopTabs
} from './src/systems/ui/desktop.js';
// Feature modules
import { setupPlotButtons, sendPlotProgression } from './src/systems/features/plotProgression.js';
import { setupClassicStatsButtons } from './src/systems/features/classicStats.js';
import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js';
import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js';
import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js';
import { DEFAULT_HTML_PROMPT, DEFAULT_JSON_TRACKER_PROMPT, DEFAULT_MESSAGE_INTERCEPTION_PROMPT } from './src/systems/generation/promptBuilder.js';
// Integration modules
import {
onMessageSent,
onMessageReceived,
onCharacterChanged,
onMessageSwiped,
updatePersonaAvatar,
clearExtensionPrompts
} from './src/systems/integration/sillytavern.js';
// Character State Tracking modules (NEW)
import {
getCharacterState,
updateCharacterState,
setCharacterState
} from './src/core/characterState.js';
import {
generateCharacterTrackingPrompt
} from './src/systems/generation/characterPromptBuilder.js';
import {
parseAndApplyCharacterStateUpdate,
removeCharacterStateBlock
} from './src/systems/generation/characterParser.js';
import {
renderCharacterStateOverview,
updateCharacterStateDisplay
} from './src/systems/rendering/characterStateRenderer.js';
console.log('[Character Tracking] ✅ All character tracking modules imported successfully');
// Old state variable declarations removed - now imported from core modules
// (extensionSettings, lastGeneratedData, committedTrackerData, etc. are now in src/core/state.js)
// Utility functions removed - now imported from src/utils/avatars.js
// (getSafeThumbnailUrl)
// Persistence functions removed - now imported from src/core/persistence.js
// (loadSettings, saveSettings, saveChatData, loadChatData, updateMessageSwipeData)
// Theme functions removed - now imported from src/systems/ui/theme.js
// (applyTheme, applyCustomTheme, toggleCustomColors, toggleAnimations,
// updateSettingsPopupTheme, applyCustomThemeToSettingsPopup)
// Layout functions removed - now imported from src/systems/ui/layout.js
// (togglePlotButtons, updateCollapseToggleIcon, setupCollapseToggle,
// updatePanelVisibility, updateSectionVisibility, applyPanelPosition)
// Note: closeMobilePanelWithAnimation is only used internally by mobile.js
// Mobile UI functions removed - now imported from src/systems/ui/mobile.js
// (setupMobileToggle, constrainFabToViewport, setupMobileTabs, removeMobileTabs,
// setupMobileKeyboardHandling, setupContentEditableScrolling)
/**
* Updates UI elements that are dynamically generated and not covered by data-i18n-key.
*/
function updateDynamicLabels() {
// Update "Refresh RPG Info" button, but only if it's not disabled
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}`;
}
// Update "Last Roll" label
updateDiceDisplay();
// Update mobile tab labels
updateMobileTabLabels();
// Update inline interception toggle text if present
updateInterceptionToggleState();
}
/**
* Updates the inline interception toggle text and styling near the send form.
*/
function updateInterceptionToggleState() {
const $toggle = $('#rpg-interception-toggle');
if ($toggle.length === 0) {
return;
}
const active = extensionSettings.messageInterceptionActive !== false;
const labelKey = active
? 'template.settingsModal.advanced.interceptionOn'
: 'template.settingsModal.advanced.interceptionOff';
const label = i18n.getTranslation(labelKey) || (active ? 'Interception On' : 'Interception Off');
const prefix = i18n.getTranslation('template.settingsModal.advanced.interceptionModeLabel') || 'Interception:';
const icon = active ? 'fa-bolt' : 'fa-ban';
const background = active ? '#4a90e2' : '#666';
$toggle
.css({
'background-color': background,
color: '#fff'
})
.html(`<i class="fa-solid ${icon}"></i> ${prefix} ${label}`);
}
/**
* Shows/hides the inline interception toggle based on interception setting.
*/
function updateInterceptionToggleVisibility() {
const $toggle = $('#rpg-interception-toggle');
if ($toggle.length === 0) {
return;
}
$toggle.toggle(extensionSettings.enableMessageInterception);
if (extensionSettings.enableMessageInterception) {
updateInterceptionToggleState();
}
}
/**
* Ensures the extension buttons wrapper exists above the send form.
*/
function ensureExtensionButtonsWrapper() {
if ($('#extension-buttons-wrapper').length === 0) {
$('#send_form').prepend('<div id="extension-buttons-wrapper" style="text-align: center; margin: 5px auto;"></div>');
}
}
/**
* Renders the inline interception toggle near plot buttons.
*/
function renderInterceptionToggle() {
ensureExtensionButtonsWrapper();
if ($('#rpg-interception-toggle').length === 0) {
const buttonHtml = `
<button id="rpg-interception-toggle" class="menu_button interactable" style="
background-color: #e94560;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
margin: 0 4px;
display: inline-block;
" tabindex="0" role="button">
<i class="fa-solid fa-bolt"></i> Interception: On
</button>
`;
$('#extension-buttons-wrapper').append(buttonHtml);
$('#rpg-interception-toggle').on('click', () => {
const active = extensionSettings.messageInterceptionActive !== false;
extensionSettings.messageInterceptionActive = !active;
saveSettings();
updateInterceptionToggleState();
});
}
updateInterceptionToggleVisibility();
}
/**
* Adds the extension settings to the Extensions tab.
*/
async function addExtensionSettings() {
// Load the HTML template for the settings
const settingsHtml = await renderExtensionTemplateAsync(extensionName, 'settings');
$('#extensions_settings2').append(settingsHtml);
// Set up the enable/disable toggle
$('#rpg-extension-enabled').prop('checked', extensionSettings.enabled).on('change', async function() {
const wasEnabled = extensionSettings.enabled;
extensionSettings.enabled = $(this).prop('checked');
saveSettings();
if (!extensionSettings.enabled && wasEnabled) {
// Disabling extension - remove UI elements
clearExtensionPrompts();
updateChatThoughts(); // Remove thought bubbles
// Remove panel and toggle buttons
$('#rpg-companion-panel').remove();
$('#rpg-mobile-toggle').remove();
$('#rpg-collapse-toggle').remove();
$('#rpg-debug-toggle').remove();
$('#rpg-debug-panel').remove();
} else if (extensionSettings.enabled && !wasEnabled) {
// Enabling extension - initialize UI
await initUI();
loadChatData(); // Load chat data for current chat
updateChatThoughts(); // Create thought bubbles if data exists
}
// Update Memory Recollection button visibility
updateMemoryRecollectionButton();
});
// Set up language selector
const langSelect = $('#rpg-companion-language-select');
if (langSelect.length) {
langSelect.val(i18n.currentLanguage);
langSelect.on('change', async function() {
const selectedLanguage = $(this).val();
await i18n.setLanguage(selectedLanguage);
// We need to re-apply translations to the settings panel specifically
i18n.applyTranslations(document.getElementById('extensions_settings2'));
});
}
}
/**
* Initializes the UI for the extension.
*/
async function initUI() {
// Initialize i18n
await i18n.init();
// Only initialize UI if extension is enabled
if (!extensionSettings.enabled) {
console.log('[RPG Companion] Extension disabled - skipping UI initialization');
return;
}
// Load the HTML template using SillyTavern's template system
const templateHtml = await renderExtensionTemplateAsync(extensionName, 'template');
// Append panel to body - positioning handled by CSS
$('body').append(templateHtml);
// Add mobile toggle button (FAB - Floating Action Button)
const mobileToggleHtml = `
<button id="rpg-mobile-toggle" class="rpg-mobile-toggle" title="Toggle RPG Panel">
<i class="fa-solid fa-dice-d20"></i>
</button>
`;
$('body').append(mobileToggleHtml);
// Hide mobile toggle on desktop viewport (> 1000px)
if (window.innerWidth > 1000) {
$('#rpg-mobile-toggle').hide();
}
// Cache UI elements using state setters
setPanelContainer($('#rpg-companion-panel'));
setUserStatsContainer($('#rpg-user-stats'));
setInfoBoxContainer($('#rpg-info-box'));
setThoughtsContainer($('#rpg-thoughts'));
setSkillsContainer($('#rpg-skills'));
setInventoryContainer($('#rpg-inventory'));
setQuestsContainer($('#rpg-quests'));
i18n.applyTranslations(document.body);
$('#rpg-toggle-auto-update').on('change', function() {
extensionSettings.autoUpdate = $(this).prop('checked');
saveSettings();
});
$('#rpg-position-select').on('change', function() {
extensionSettings.panelPosition = String($(this).val());
saveSettings();
applyPanelPosition();
updateChatThoughts();
});
$('#rpg-update-depth').on('change', function() {
const value = $(this).val();
extensionSettings.updateDepth = parseInt(String(value));
saveSettings();
});
$('#rpg-message-interception-depth').on('change', function() {
const value = parseInt(String($(this).val()));
if (!Number.isNaN(value)) {
extensionSettings.messageInterceptionContextDepth = value;
saveSettings();
}
});
$('#rpg-memory-messages').on('change', function() {
const value = $(this).val();
extensionSettings.memoryMessagesToProcess = parseInt(String(value));
saveSettings();
});
$('#rpg-generation-mode').on('change', function() {
extensionSettings.generationMode = String($(this).val());
saveSettings();
updateGenerationModeUI();
});
$('#rpg-use-separate-preset').on('change', function() {
extensionSettings.useSeparatePreset = $(this).prop('checked');
saveSettings();
});
$('#rpg-toggle-user-stats').on('change', function() {
extensionSettings.showUserStats = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
});
$('#rpg-toggle-info-box').on('change', function() {
extensionSettings.showInfoBox = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
});
$('#rpg-toggle-thoughts').on('change', function() {
extensionSettings.showCharacterThoughts = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
});
$('#rpg-toggle-inventory').on('change', function() {
extensionSettings.showInventory = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
if (window.innerWidth > 1000) {
removeDesktopTabs();
setupDesktopTabs();
}
});
$('#rpg-toggle-simplified-inventory').on('change', function() {
extensionSettings.useSimplifiedInventory = $(this).prop('checked');
saveSettings();
renderInventory();
});
$('#rpg-toggle-quests').on('change', function() {
extensionSettings.showQuests = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
renderQuests();
if (window.innerWidth > 1000) {
removeDesktopTabs();
setupDesktopTabs();
}
});
$('#rpg-toggle-skills').on('change', function() {
extensionSettings.showSkills = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
renderSkills();
if (window.innerWidth > 1000) {
removeDesktopTabs();
setupDesktopTabs();
}
});
$('#rpg-toggle-item-skill-links').on('change', function() {
extensionSettings.enableItemSkillLinks = $(this).prop('checked');
saveSettings();
renderSkills();
});
$('#rpg-toggle-delete-skill-with-item').on('change', function() {
extensionSettings.deleteSkillWithItem = $(this).prop('checked');
saveSettings();
});
$('#rpg-toggle-thoughts-in-chat').on('change', function() {
extensionSettings.showThoughtsInChat = $(this).prop('checked');
saveSettings();
updateChatThoughts();
});
$('#rpg-toggle-always-show-bubble').on('change', function() {
extensionSettings.alwaysShowThoughtBubble = $(this).prop('checked');
saveSettings();
const context = getContext();
const extension_settings = context.extension_settings || context.extensionSettings;
extension_settings[extensionName] = extensionSettings;
updateChatThoughts();
});
$('#rpg-toggle-html-prompt').on('change', function() {
extensionSettings.enableHtmlPrompt = $(this).prop('checked');
saveSettings();
});
$('#rpg-custom-html-prompt').on('input', function() {
extensionSettings.customHtmlPrompt = $(this).val().trim();
saveSettings();
});
$('#rpg-restore-default-html-prompt').on('click', function() {
extensionSettings.customHtmlPrompt = '';
$('#rpg-custom-html-prompt').val(DEFAULT_HTML_PROMPT);
saveSettings();
toastr.success('HTML prompt restored to default');
});
$('#rpg-toggle-message-interception').on('change', function() {
extensionSettings.enableMessageInterception = $(this).prop('checked');
saveSettings();
updateInterceptionToggleVisibility();
});
$('#rpg-custom-message-interception-prompt').on('input', function() {
extensionSettings.customMessageInterceptionPrompt = $(this).val().trim();
saveSettings();
});
$('#rpg-restore-default-message-interception-prompt').on('click', function() {
extensionSettings.customMessageInterceptionPrompt = '';
$('#rpg-custom-message-interception-prompt').val(DEFAULT_MESSAGE_INTERCEPTION_PROMPT);
saveSettings();
toastr.success('Message interception prompt restored to default');
});
// Custom Tracker Prompt handlers
$('#rpg-custom-tracker-prompt').on('input', function() {
extensionSettings.customTrackerPrompt = $(this).val().trim();
saveSettings();
});
$('#rpg-restore-default-tracker-prompt').on('click', function() {
extensionSettings.customTrackerPrompt = '';
$('#rpg-custom-tracker-prompt').val(DEFAULT_JSON_TRACKER_PROMPT);
saveSettings();
toastr.success('Tracker prompt restored to default');
});
$('#rpg-skip-guided-mode').on('change', function() {
extensionSettings.skipInjectionsForGuided = String($(this).val());
saveSettings();
});
$('#rpg-toggle-plot-buttons').on('change', function() {
extensionSettings.enablePlotButtons = $(this).prop('checked');
saveSettings();
togglePlotButtons();
});
$('#rpg-toggle-animations').on('change', function() {
extensionSettings.enableAnimations = $(this).prop('checked');
saveSettings();
toggleAnimations();
});
$('#rpg-manual-update').on('click', async function() {
if (!extensionSettings.enabled) {
return;
}
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
});
$('#rpg-stat-bar-color-low').on('change', function() {
extensionSettings.statBarColorLow = String($(this).val());
saveSettings();
renderUserStats();
});
$('#rpg-stat-bar-color-high').on('change', function() {
extensionSettings.statBarColorHigh = String($(this).val());
saveSettings();
renderUserStats();
});
$('#rpg-theme-select').on('change', function() {
extensionSettings.theme = String($(this).val());
saveSettings();
applyTheme();
toggleCustomColors();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
});
// Custom color pickers
$('#rpg-custom-bg').on('change', function() {
extensionSettings.customColors.bg = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-accent').on('change', function() {
extensionSettings.customColors.accent = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-text').on('change', function() {
extensionSettings.customColors.text = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-highlight').on('change', function() {
extensionSettings.customColors.highlight = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate);
$('#rpg-position-select').val(extensionSettings.panelPosition);
$('#rpg-update-depth').val(extensionSettings.updateDepth);
$('#rpg-memory-messages').val(extensionSettings.memoryMessagesToProcess || 16);
$('#rpg-use-separate-preset').prop('checked', extensionSettings.useSeparatePreset);
$('#rpg-toggle-user-stats').prop('checked', extensionSettings.showUserStats);
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
$('#rpg-toggle-simplified-inventory').prop('checked', extensionSettings.useSimplifiedInventory);
$('#rpg-toggle-skills').prop('checked', extensionSettings.showSkills);
$('#rpg-toggle-item-skill-links').prop('checked', extensionSettings.enableItemSkillLinks);
$('#rpg-toggle-delete-skill-with-item').prop('checked', extensionSettings.deleteSkillWithItem);
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
$('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble);
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
$('#rpg-toggle-message-interception').prop('checked', extensionSettings.enableMessageInterception);
updateInterceptionToggleVisibility();
$('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT);
$('#rpg-custom-tracker-prompt').val(extensionSettings.customTrackerPrompt || DEFAULT_JSON_TRACKER_PROMPT);
$('#rpg-custom-message-interception-prompt').val(
extensionSettings.customMessageInterceptionPrompt || DEFAULT_MESSAGE_INTERCEPTION_PROMPT
);
$('#rpg-message-interception-depth').val(
extensionSettings.messageInterceptionContextDepth || extensionSettings.updateDepth || 4
);
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
$('#rpg-theme-select').val(extensionSettings.theme);
$('#rpg-custom-bg').val(extensionSettings.customColors.bg);
$('#rpg-custom-accent').val(extensionSettings.customColors.accent);
$('#rpg-custom-text').val(extensionSettings.customColors.text);
$('#rpg-custom-highlight').val(extensionSettings.customColors.highlight);
$('#rpg-generation-mode').val(extensionSettings.generationMode);
$('#rpg-skip-guided-mode').val(extensionSettings.skipInjectionsForGuided);
updatePanelVisibility();
updateSectionVisibility();
updateGenerationModeUI();
applyTheme();
applyPanelPosition();
toggleCustomColors();
toggleAnimations();
setupMobileToggle();
if (window.innerWidth > 1000) {
setupDesktopTabs();
}
setupCollapseToggle();
renderUserStats();
renderInfoBox();
renderThoughts();
renderSkills();
renderInventory();
renderQuests();
updateDiceDisplay();
setupDiceRoller();
setupClassicStatsButtons();
setupSettingsPopup();
initTrackerEditor();
addDiceQuickReply();
setupPlotButtons(sendPlotProgression);
renderInterceptionToggle();
setupMobileKeyboardHandling();
setupContentEditableScrolling();
initInventoryEventListeners();
setupMemoryRecollectionButton();
initLorebookLimiter();
// Initialize character state display (NEW)
// First, ensure the container exists (in case template.html didn't load)
if ($('#rpg-character-state-container').length === 0) {
console.log('[Character Tracking] Container not found, creating it dynamically...');
// Try to add to existing content box
const $contentBox = $('.rpg-content-box');
if ($contentBox.length > 0) {
$contentBox.append('<div id="rpg-character-state-container" class="rpg-section rpg-character-state-section"></div>');
console.log('[Character Tracking] ✅ Container created dynamically');
} else {
console.warn('[Character Tracking] ❌ Could not find .rpg-content-box to add container');
}
}
updateCharacterStateDisplay();
}
// ============================================================================
// CHARACTER STATE TRACKING - Event Wrappers (NEW)
// ============================================================================
/**
* Wrapper for onMessageReceived that adds character state tracking
*/
async function onMessageReceivedWithCharacterTracking(data) {
// Call original handler first
await onMessageReceived(data);
// If extension is not enabled or character tracking not active, skip
if (!extensionSettings.enabled) return;
try {
// Parse and apply character state updates from the LLM response
const stateUpdate = parseAndApplyCharacterStateUpdate(data);
if (stateUpdate) {
console.log('[Character Tracking] State updated successfully');
// Update the UI to show new character state
updateCharacterStateDisplay();
// Save character state to chat metadata
saveCharacterStateToChat();
// Optionally remove state block from displayed message
// (uncomment if you want to hide the technical state blocks)
// data.mes = removeCharacterStateBlock(data.mes);
}
} catch (error) {
console.error('[Character Tracking] Error processing state update:', error);
}
}
/**
* Wrapper for onGenerationStarted that adds character state tracking prompt
*/
async function onGenerationStartedWithCharacterTracking(data) {
// Call original handler first
await onGenerationStarted(data);
// If extension is not enabled, skip
if (!extensionSettings.enabled) return;
try {
// Generate and inject character tracking prompt
const trackingPrompt = generateCharacterTrackingPrompt();
setExtensionPrompt(
'RPG_CHARACTER_STATE_TRACKING',
trackingPrompt,
extension_prompt_types.IN_PROMPT,
1000, // position (adjust as needed)
false,
extension_prompt_roles.SYSTEM
);
console.log('[Character Tracking] Tracking prompt injected');
} catch (error) {
console.error('[Character Tracking] Error injecting tracking prompt:', error);
}
}
/**
* Wrapper for onCharacterChanged that loads character state
*/
async function onCharacterChangedWithCharacterTracking(characterId) {
// Call original handler first
await onCharacterChanged(characterId);
// If extension is not enabled, skip
if (!extensionSettings.enabled) return;
try {
// Load character state from chat metadata
loadCharacterStateFromChat();
// Update display
updateCharacterStateDisplay();
console.log('[Character Tracking] Character state loaded for new chat');
} catch (error) {
console.error('[Character Tracking] Error loading character state:', error);
}
}
/**
* Save character state to chat metadata
*/
function saveCharacterStateToChat() {
const charState = getCharacterState();
// Store in SillyTavern's chat metadata
if (!chat_metadata.rpg_extension) {
chat_metadata.rpg_extension = {};
}
chat_metadata.rpg_extension.character_state = charState;
// Save chat metadata
saveChatDebounced();
console.log('[Character Tracking] Character state saved to chat metadata');
}
/**
* Load character state from chat metadata
*/
function loadCharacterStateFromChat() {
if (chat_metadata.rpg_extension && chat_metadata.rpg_extension.character_state) {
const savedState = chat_metadata.rpg_extension.character_state;
setCharacterState(savedState);
console.log('[Character Tracking] Character state loaded from chat metadata');
} else {
console.log('[Character Tracking] No saved character state found, using defaults');
}
}
// ============================================================================
// END CHARACTER STATE TRACKING
// ============================================================================
/**
* Ensures the "RPG Companion Trackers" preset exists in the user's OpenAI Settings.
* Imports the preset file from the extension folder if it doesn't exist.
*/
async function ensureTrackerPresetExists() {
try {
const presetName = 'RPG Companion Trackers';
// Check if preset already exists by fetching settings
const checkResponse = await fetch('/api/settings/get', {
method: 'POST',
headers: getRequestHeaders()
});
if (checkResponse.ok) {
const settings = await checkResponse.json();
// openai_setting_names is an array of preset names
if (settings.openai_setting_names && settings.openai_setting_names.includes(presetName)) {
console.log(`[RPG Companion] Preset "${presetName}" already exists`);
return;
}
}
// Preset doesn't exist - import it from extension folder
console.log(`[RPG Companion] Importing preset "${presetName}"...`);
// Load preset from extension folder
const extensionPresetPath = `${extensionFolderPath}/${presetName}.json`;
const presetResponse = await fetch(`/${extensionPresetPath}`);
if (!presetResponse.ok) {
console.warn(`[RPG Companion] Could not load preset template from ${extensionPresetPath}`);
return;
}
const presetData = await presetResponse.json();
// Save preset to user's OpenAI Settings folder using SillyTavern's API
const saveResponse = await fetch('/api/presets/save', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
apiId: 'openai',
name: presetName,
preset: presetData
})
});
if (saveResponse.ok) {
console.log(`[RPG Companion] ✅ Successfully imported preset "${presetName}"`);
toastr.success(
`The "RPG Companion Trackers" preset has been imported to your OpenAI Settings.`,
'RPG Companion',
{ timeOut: 5000 }
);
} else {
console.warn(`[RPG Companion] Failed to save preset: ${saveResponse.statusText}`);
}
} catch (error) {
console.error('[RPG Companion] Error importing tracker preset:', error);
// Non-critical - users can manually import if needed
}
}
/**
* Main initialization function.
*/
jQuery(async () => {
try {
console.log('========================================');
console.log('🎭 RPG COMPANION v2.0.0 CHARACTER TRACKING');
console.log('✅ NEW VERSION WITH CHARACTER STATE TRACKING LOADED!');
console.log('========================================');
console.log('[RPG Companion] Starting initialization...');
try {
loadSettings();
} catch (error) {
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
}
await i18n.init();
i18n.addEventListener('languageChanged', updateDynamicLabels);
try {
await addExtensionSettings();
} catch (error) {
console.error('[RPG Companion] Failed to add extension settings tab:', error);
}
try {
await initUI();
} catch (error) {
console.error('[RPG Companion] UI initialization failed:', error);
throw error;
}
try {
loadChatData();
} catch (error) {
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
}
try {
await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced);
} catch (error) {
console.error('[RPG Companion] HTML regex import failed:', error);
}
try {
await ensureTrackerPresetExists();
} catch (error) {
console.error('[RPG Companion] Preset import failed:', error);
}
try {
const conflicts = detectConflictingRegexScripts(st_extension_settings);
if (conflicts.length > 0) {
console.log('[RPG Companion] ⚠️ Detected old manual formatting regex scripts that may conflict:');
conflicts.forEach(name => console.log(` - ${name}`));
console.log('[RPG Companion] Consider disabling these regexes as the extension now handles formatting automatically.');
}
} catch (error) {
console.error('[RPG Companion] Conflict detection failed:', error);
}
// Register all event listeners (with character tracking wrappers)
try {
registerAllEvents({
[event_types.MESSAGE_SENT]: onMessageSent,
[event_types.GENERATION_STARTED]: onGenerationStartedWithCharacterTracking, // MODIFIED: Now uses character tracking wrapper
[event_types.MESSAGE_RECEIVED]: onMessageReceivedWithCharacterTracking, // MODIFIED: Now uses character tracking wrapper
[event_types.CHAT_CHANGED]: [onCharacterChangedWithCharacterTracking, updatePersonaAvatar], // MODIFIED: Now uses character tracking wrapper
[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;
}
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 }
);
}
});