diff --git a/index.js b/index.js index 8422091..dc8385b 100644 --- a/index.js +++ b/index.js @@ -1,36 +1,11 @@ import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings } from '../../../extensions.js'; -import { eventSource, event_types, substituteParams, chat, generateRaw, 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'; +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, - lastGeneratedData, - committedTrackerData, - lastActionWasSwipe, - isGenerating, - isPlotProgression, - pendingDiceRoll, - FALLBACK_AVATAR_DATA_URI, - $panelContainer, - $userStatsContainer, - $infoBoxContainer, - $thoughtsContainer, - $inventoryContainer, - $questsContainer, - setExtensionSettings, - updateExtensionSettings, - setLastGeneratedData, - updateLastGeneratedData, - setCommittedTrackerData, - updateCommittedTrackerData, - setLastActionWasSwipe, - setIsGenerating, - setIsPlotProgression, - setPendingDiceRoll, setPanelContainer, setUserStatsContainer, setInfoBoxContainer, @@ -39,28 +14,19 @@ import { setInventoryContainer, setQuestsContainer } from './src/core/state.js'; -import { loadSettings, saveSettings, saveChatData, loadChatData, updateMessageSwipeData } from './src/core/persistence.js'; +import { loadSettings, saveSettings, loadChatData } from './src/core/persistence.js'; import { registerAllEvents } from './src/core/events.js'; // Generation & Parsing modules -import { - generateContextualSummary, - generateRPGPromptText, - generateSeparateUpdatePrompt -} from './src/systems/generation/promptBuilder.js'; -import { parseResponse, parseUserStats, parseSkills } from './src/systems/generation/parser.js'; import { updateRPGData } 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 { renderInfoBox } from './src/systems/rendering/infoBox.js'; import { renderThoughts, - updateCharacterField, - updateChatThoughts, - createThoughtPanel + updateChatThoughts } from './src/systems/rendering/thoughts.js'; import { renderInventory } from './src/systems/rendering/inventory.js'; import { renderQuests } from './src/systems/rendering/quests.js'; @@ -75,12 +41,9 @@ import { applyCustomTheme, toggleCustomColors, toggleAnimations, - updateSettingsPopupTheme, - applyCustomThemeToSettingsPopup + updateSettingsPopupTheme } from './src/systems/ui/theme.js'; import { - DiceModal, - SettingsModal, setupDiceRoller, setupSettingsPopup, updateDiceDisplay, @@ -92,7 +55,6 @@ import { } from './src/systems/ui/trackerEditor.js'; import { togglePlotButtons, - updateCollapseToggleIcon, setupCollapseToggle, updatePanelVisibility, updateSectionVisibility, @@ -101,9 +63,6 @@ import { } from './src/systems/ui/layout.js'; import { setupMobileToggle, - constrainFabToViewport, - setupMobileTabs, - removeMobileTabs, setupMobileKeyboardHandling, setupContentEditableScrolling, updateMobileTabLabels @@ -123,7 +82,6 @@ import { DEFAULT_HTML_PROMPT, DEFAULT_JSON_TRACKER_PROMPT, DEFAULT_MESSAGE_INTER // Integration modules import { - commitTrackerData, onMessageSent, onMessageReceived, onCharacterChanged, @@ -132,27 +90,6 @@ import { clearExtensionPrompts } from './src/systems/integration/sillytavern.js'; -// 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. @@ -351,20 +288,17 @@ async function initUI() { setInventoryContainer($('#rpg-inventory')); setQuestsContainer($('#rpg-quests')); - // Re-apply translations to the entire body to catch all new elements from the template - i18n.applyTranslations(document.body); + i18n.applyTranslations(document.body); - // Set up event listeners (enable/disable is handled in Extensions tab) - $('#rpg-toggle-auto-update').on('change', function() { + $('#rpg-toggle-auto-update').on('change', function() { extensionSettings.autoUpdate = $(this).prop('checked'); saveSettings(); }); - $('#rpg-position-select').on('change', function() { + $('#rpg-position-select').on('change', function() { extensionSettings.panelPosition = String($(this).val()); saveSettings(); applyPanelPosition(); - // Recreate thought bubbles to update their position updateChatThoughts(); }); @@ -417,11 +351,10 @@ async function initUI() { updateSectionVisibility(); }); - $('#rpg-toggle-inventory').on('change', function() { + $('#rpg-toggle-inventory').on('change', function() { extensionSettings.showInventory = $(this).prop('checked'); saveSettings(); updateSectionVisibility(); - // Re-setup desktop tabs to show/hide inventory tab if (window.innerWidth > 1000) { removeDesktopTabs(); setupDesktopTabs(); @@ -431,15 +364,14 @@ async function initUI() { $('#rpg-toggle-simplified-inventory').on('change', function() { extensionSettings.useSimplifiedInventory = $(this).prop('checked'); saveSettings(); - renderInventory(); // Re-render inventory with new mode + renderInventory(); }); $('#rpg-toggle-quests').on('change', function() { extensionSettings.showQuests = $(this).prop('checked'); saveSettings(); updateSectionVisibility(); - renderQuests(); // Re-render quests - // Re-setup desktop tabs to show/hide quests tab + renderQuests(); if (window.innerWidth > 1000) { removeDesktopTabs(); setupDesktopTabs(); @@ -450,8 +382,7 @@ async function initUI() { extensionSettings.showSkills = $(this).prop('checked'); saveSettings(); updateSectionVisibility(); - renderSkills(); // Render skills section - // Re-setup desktop tabs to show/hide skills tab + renderSkills(); if (window.innerWidth > 1000) { removeDesktopTabs(); setupDesktopTabs(); @@ -461,7 +392,6 @@ async function initUI() { $('#rpg-toggle-item-skill-links').on('change', function() { extensionSettings.enableItemSkillLinks = $(this).prop('checked'); saveSettings(); - // Re-render skills to show/hide link badges renderSkills(); }); @@ -472,7 +402,6 @@ async function initUI() { $('#rpg-toggle-thoughts-in-chat').on('change', function() { extensionSettings.showThoughtsInChat = $(this).prop('checked'); - // console.log('[RPG Companion] Toggle showThoughtsInChat changed to:', extensionSettings.showThoughtsInChat); saveSettings(); updateChatThoughts(); }); @@ -480,17 +409,14 @@ async function initUI() { $('#rpg-toggle-always-show-bubble').on('change', function() { extensionSettings.alwaysShowThoughtBubble = $(this).prop('checked'); saveSettings(); - // Force immediate save to ensure setting is persisted before any other code runs const context = getContext(); const extension_settings = context.extension_settings || context.extensionSettings; extension_settings[extensionName] = extensionSettings; - // Re-render thoughts to apply the setting updateChatThoughts(); }); $('#rpg-toggle-html-prompt').on('change', function() { extensionSettings.enableHtmlPrompt = $(this).prop('checked'); - // console.log('[RPG Companion] Toggle enableHtmlPrompt changed to:', extensionSettings.enableHtmlPrompt); saveSettings(); }); @@ -544,7 +470,6 @@ async function initUI() { $('#rpg-toggle-plot-buttons').on('change', function() { extensionSettings.enablePlotButtons = $(this).prop('checked'); - // console.log('[RPG Companion] Toggle enablePlotButtons changed to:', extensionSettings.enablePlotButtons); saveSettings(); togglePlotButtons(); }); @@ -557,7 +482,6 @@ async function initUI() { $('#rpg-manual-update').on('click', async function() { if (!extensionSettings.enabled) { - // console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.'); return; } await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory); @@ -566,23 +490,22 @@ async function initUI() { $('#rpg-stat-bar-color-low').on('change', function() { extensionSettings.statBarColorLow = String($(this).val()); saveSettings(); - renderUserStats(); // Re-render with new colors + renderUserStats(); }); $('#rpg-stat-bar-color-high').on('change', function() { extensionSettings.statBarColorHigh = String($(this).val()); saveSettings(); - renderUserStats(); // Re-render with new colors + renderUserStats(); }); - // Theme selection $('#rpg-theme-select').on('change', function() { extensionSettings.theme = String($(this).val()); saveSettings(); applyTheme(); toggleCustomColors(); - updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly - updateChatThoughts(); // Recreate thought bubbles with new theme + updateSettingsPopupTheme(getSettingsModal()); + updateChatThoughts(); }); // Custom color pickers @@ -591,8 +514,8 @@ async function initUI() { saveSettings(); if (extensionSettings.theme === 'custom') { applyCustomTheme(); - updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly - updateChatThoughts(); // Update thought bubbles + updateSettingsPopupTheme(getSettingsModal()); + updateChatThoughts(); } }); @@ -601,8 +524,8 @@ async function initUI() { saveSettings(); if (extensionSettings.theme === 'custom') { applyCustomTheme(); - updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly - updateChatThoughts(); // Update thought bubbles + updateSettingsPopupTheme(getSettingsModal()); + updateChatThoughts(); } }); @@ -611,8 +534,8 @@ async function initUI() { saveSettings(); if (extensionSettings.theme === 'custom') { applyCustomTheme(); - updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly - updateChatThoughts(); // Update thought bubbles + updateSettingsPopupTheme(getSettingsModal()); + updateChatThoughts(); } }); @@ -621,12 +544,11 @@ async function initUI() { saveSettings(); if (extensionSettings.theme === 'custom') { applyCustomTheme(); - updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly - updateChatThoughts(); // Update thought bubbles + updateSettingsPopupTheme(getSettingsModal()); + updateChatThoughts(); } }); - // Initialize UI state (enable/disable is in Extensions tab) $('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate); $('#rpg-position-select').val(extensionSettings.panelPosition); $('#rpg-update-depth').val(extensionSettings.updateDepth); @@ -647,18 +569,11 @@ async function initUI() { $('#rpg-toggle-message-interception').prop('checked', extensionSettings.enableMessageInterception); updateInterceptionToggleVisibility(); - // Set default HTML prompt as actual text if no custom prompt exists $('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT); - - // Set default tracker prompt as actual text if no custom prompt exists $('#rpg-custom-tracker-prompt').val(extensionSettings.customTrackerPrompt || DEFAULT_JSON_TRACKER_PROMPT); - - // Set default message interception prompt as actual text if no custom prompt exists $('#rpg-custom-message-interception-prompt').val( extensionSettings.customMessageInterceptionPrompt || DEFAULT_MESSAGE_INTERCEPTION_PROMPT ); - - // Message interception depth $('#rpg-message-interception-depth').val( extensionSettings.messageInterceptionContextDepth || extensionSettings.updateDepth || 4 ); @@ -683,18 +598,14 @@ async function initUI() { toggleCustomColors(); toggleAnimations(); - // Setup mobile toggle button setupMobileToggle(); - // Setup desktop tabs (only on desktop viewport) if (window.innerWidth > 1000) { setupDesktopTabs(); } - // Setup collapse/expand toggle button setupCollapseToggle(); - // Render initial data if available renderUserStats(); renderInfoBox(); renderThoughts(); @@ -712,11 +623,7 @@ async function initUI() { setupMobileKeyboardHandling(); setupContentEditableScrolling(); initInventoryEventListeners(); - - // Setup Memory Recollection button in World Info setupMemoryRecollectionButton(); - - // Initialize Lorebook Limiter initLorebookLimiter(); } @@ -724,13 +631,6 @@ async function initUI() { -// Rendering functions removed - now imported from src/systems/rendering/* -// (renderUserStats, renderInfoBox, renderThoughts, updateInfoBoxField, -// updateCharacterField, updateChatThoughts, createThoughtPanel) - -// Event handlers removed - now imported from src/systems/integration/sillytavern.js -// (commitTrackerData, onMessageSent, onMessageReceived, onCharacterChanged, -// onMessageSwiped, updatePersonaAvatar, clearExtensionPrompts) /** * Ensures the "RPG Companion Trackers" preset exists in the user's OpenAI Settings. @@ -803,79 +703,57 @@ 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); } - // 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(); } 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 + throw error; } - // Load chat-specific data for current chat try { loadChatData(); } catch (error) { console.error('[RPG Companion] Chat data load failed, using defaults:', error); } - // Import the HTML cleaning regex if needed try { await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] HTML regex import failed:', error); - // Non-critical - continue without it } - // Import the RPG Companion Trackers preset if needed try { await ensureTrackerPresetExists(); } catch (error) { console.error('[RPG Companion] Preset import failed:', error); - // Non-critical - users can manually import if needed } - // Detect conflicting regex scripts from old manual formatters 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.'); - - // 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 try { registerAllEvents({ [event_types.MESSAGE_SENT]: onMessageSent, @@ -888,15 +766,13 @@ jQuery(async () => { }); } catch (error) { console.error('[RPG Companion] Event registration failed:', error); - throw error; // This is critical - can't continue without events + 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); - - // 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', diff --git a/src/core/i18n.js b/src/core/i18n.js index 9dbb857..b5774bd 100644 --- a/src/core/i18n.js +++ b/src/core/i18n.js @@ -1,5 +1,6 @@ //- No-op in case this is running outside of SillyTavern -const { extension_settings } = typeof self.SillyTavern !== 'undefined' ? self.SillyTavern.getContext() : { extension_settings: {} }; +// eslint-disable-next-line no-unused-vars +const { extension_settings: _extension_settings } = typeof self.SillyTavern !== 'undefined' ? self.SillyTavern.getContext() : { extension_settings: {} }; class Internationalization { constructor() { diff --git a/src/core/persistence.js b/src/core/persistence.js index a23e5a3..13f24c0 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -9,11 +9,9 @@ import { extensionSettings, lastGeneratedData, committedTrackerData, - setExtensionSettings, updateExtensionSettings, setLastGeneratedData, - setCommittedTrackerData, - FEATURE_FLAGS + setCommittedTrackerData } from './state.js'; import { migrateInventory } from '../utils/migration.js'; import { validateStoredInventory, cleanItemString } from '../utils/security.js'; @@ -78,19 +76,14 @@ export function loadSettings() { } 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 - } + // Migrate inventory from v1 (string) to v2 (object) format if needed + 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 } // Migrate to trackerConfig if it doesn't exist @@ -104,6 +97,11 @@ export function loadSettings() { if (migrateStatsAndSkillsFormat()) { saveSettings(); // Persist migration } + + // Migrate quests from legacy format to structured format + if (migrateQuestsFormat()) { + saveSettings(); // Persist migration + } } catch (error) { console.error('[RPG Companion] Error loading settings:', error); console.error('[RPG Companion] Error details:', error.message, error.stack); @@ -186,8 +184,6 @@ export function updateMessageSwipeData() { infoBox: lastGeneratedData.infoBox, characterThoughts: lastGeneratedData.characterThoughts }; - - // console.log('[RPG Companion] Updated message swipe data after user edit'); break; } } @@ -323,8 +319,13 @@ export function loadChatData() { extensionSettings.questsV2 = savedData.questsV2; } - // Migrate inventory in chat data if feature flag enabled - if (FEATURE_FLAGS.useNewInventory && extensionSettings.userStats.inventory) { + // Migrate quests from legacy format to structured format if needed + if (migrateQuestsFormat()) { + saveChatData(); // Persist migrated quests to chat metadata + } + + // Migrate inventory from v1 (string) to v2 (object) format if needed + if (extensionSettings.userStats.inventory) { const migrationResult = migrateInventory(extensionSettings.userStats.inventory); if (migrationResult.migrated) { console.log(`[RPG Companion] Chat inventory migrated from ${migrationResult.source} to v2 format`); @@ -333,16 +334,12 @@ export function loadChatData() { } } - // Validate inventory structure (Bug #3 fix) validateInventoryStructure(extensionSettings.userStats.inventory, 'chat'); - - // console.log('[RPG Companion] Loaded chat data:', savedData); } /** * Validates and repairs inventory structure to prevent corruption. * Ensures all v2 fields exist and are the correct type. - * Fixes Bug #3: Location disappears when switching tabs * * @param {Object} inventory - Inventory object to validate * @param {string} source - Source of load ('settings' or 'chat') for logging @@ -385,7 +382,6 @@ function validateInventoryStructure(inventory, source) { } } - // Validate stored field (CRITICAL for Bug #3) if (!inventory.stored || typeof inventory.stored !== 'object' || Array.isArray(inventory.stored)) { console.error(`[RPG Companion] Corrupted stored inventory from ${source}, resetting to empty object`); inventory.stored = {}; @@ -690,3 +686,60 @@ function migrateStatsAndSkillsFormat() { return migrated; } + +/** + * Migrates quests from legacy format to structured format (questsV2). + * Legacy format: quests.main (string), quests.optional (string array) + * New format: questsV2.main ({name, description}), questsV2.optional (array of {name, description}) + * @returns {boolean} true if any migration was performed + */ +function migrateQuestsFormat() { + let migrated = false; + + // Initialize questsV2 if it doesn't exist + if (!extensionSettings.questsV2) { + extensionSettings.questsV2 = { + main: null, + optional: [] + }; + } + + // Migrate main quest if it exists in legacy format but not in new format + // Check if legacy format has data AND new format is empty/null + if (extensionSettings.quests?.main && + extensionSettings.quests.main !== 'None' && + extensionSettings.quests.main !== '' && + (!extensionSettings.questsV2.main || !extensionSettings.questsV2.main.name)) { + extensionSettings.questsV2.main = { + name: extensionSettings.quests.main, + description: extensionSettings.quests?.mainDescription || '' + }; + migrated = true; + console.log('[RPG Companion] Migrated main quest to structured format:', extensionSettings.quests.main); + } + + // Migrate optional quests if they exist in legacy format but not in new format + // Check if legacy format has data AND new format is empty + if (extensionSettings.quests?.optional && + Array.isArray(extensionSettings.quests.optional) && + extensionSettings.quests.optional.length > 0 && + (!extensionSettings.questsV2.optional || extensionSettings.questsV2.optional.length === 0)) { + const descriptions = extensionSettings.quests?.optionalDescriptions || []; + extensionSettings.questsV2.optional = extensionSettings.quests.optional + .filter(title => title && title !== 'None' && title !== '') + .map((title, i) => ({ + name: title, + description: descriptions[i] || '' + })); + if (extensionSettings.questsV2.optional.length > 0) { + migrated = true; + console.log('[RPG Companion] Migrated optional quests to structured format:', extensionSettings.questsV2.optional.length, 'quests'); + } + } + + if (migrated) { + console.log('[RPG Companion] Quests format migration complete'); + } + + return migrated; +} diff --git a/src/core/state.js b/src/core/state.js index 2b49b78..fed7b3e 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -103,13 +103,6 @@ export let extensionSettings = { itemSkillLinks: {}, skillAbilityLinks: {}, skillsData: {}, - statNames: { - health: 'Health', - satiety: 'Satiety', - energy: 'Energy', - hygiene: 'Hygiene', - arousal: 'Arousal' - }, // Tracker customization configuration trackerConfig: { userStats: { @@ -278,13 +271,6 @@ export function addDebugLog(message, data = null) { } } -/** - * Feature flags for gradual rollout of new features - */ -export const FEATURE_FLAGS = { - useNewInventory: true // Enable v2 inventory system with categorized storage -}; - /** * Fallback avatar image (base64-encoded SVG with "?" icon) * Using base64 to avoid quote-encoding issues in HTML attributes diff --git a/src/systems/features/dice.js b/src/systems/features/dice.js index fe5a0c4..6cf55b0 100644 --- a/src/systems/features/dice.js +++ b/src/systems/features/dice.js @@ -5,7 +5,6 @@ import { extensionSettings, - pendingDiceRoll, setPendingDiceRoll } from '../../core/state.js'; import { saveSettings } from '../../core/persistence.js'; diff --git a/src/systems/features/lorebookLimiter.js b/src/systems/features/lorebookLimiter.js index 9d2e6a5..6fe7b51 100644 --- a/src/systems/features/lorebookLimiter.js +++ b/src/systems/features/lorebookLimiter.js @@ -3,11 +3,10 @@ * Adds maximum activation limit to SillyTavern's World Info system */ -import { eventSource, event_types } from '../../../../../../../script.js'; +import { eventSource } from '../../../../../../../script.js'; let maxActivations = 0; // 0 = unlimited let settingsInitialized = false; -let activatedEntriesThisGeneration = []; /** * Initialize the lorebook limiter @@ -136,11 +135,6 @@ function injectMaxActivationsUI() { function patchWorldInfoActivation() { console.log('[Lorebook Limiter] Setting up activation limiter...'); - // We need to intercept at the module level - // Use a Proxy on the module loader - const originalDefine = window.define; - const originalRequire = window.require; - // Try multiple approaches to hook into the WI system const attemptPatch = () => { // Approach 1: Direct window access diff --git a/src/systems/features/memoryRecollection.js b/src/systems/features/memoryRecollection.js index 95c3396..0614cf2 100644 --- a/src/systems/features/memoryRecollection.js +++ b/src/systems/features/memoryRecollection.js @@ -3,11 +3,9 @@ * Handles generation of lorebook entries from chat history */ -import { chat, characters, this_chid, generateRaw, substituteParams, eventSource, event_types } from '../../../../../../../script.js'; -import { selected_group } from '../../../../../../group-chats.js'; +import { chat, generateRaw, eventSource, event_types } from '../../../../../../../script.js'; import { extensionSettings, addDebugLog } from '../../core/state.js'; -import { saveSettings } from '../../core/persistence.js'; -import { checkWorldInfo, createNewWorldInfo, openWorldInfoEditor, saveWorldInfo, setWorldInfoSettings } from '../../../../../../world-info.js'; +import { checkWorldInfo, createNewWorldInfo, openWorldInfoEditor, saveWorldInfo } from '../../../../../../world-info.js'; /** * Helper to log to both console and debug logs array @@ -117,68 +115,6 @@ function createConstantHeaderEntry() { return entry; } -/** - * Save a world info entry to a lorebook - * @param {string} lorebookUid - The filename/UID of the lorebook - * @param {Object} entry - The entry data - */ -async function saveWorldInfoEntry(lorebookUid, entry) { - try { - debugLog('[Memory Recollection] Saving entry to lorebook:', lorebookUid); - - // Open the world info editor for this lorebook to load its data - await openWorldInfoEditor(lorebookUid); - - // Wait for it to load - await new Promise(resolve => setTimeout(resolve, 500)); - - // Now access the loaded world info data - const worldInfo = window.world_info; - - debugLog('[Memory Recollection] World info after opening:', { - type: typeof worldInfo, - isArray: Array.isArray(worldInfo), - hasEntries: worldInfo?.entries !== undefined, - keys: worldInfo ? Object.keys(worldInfo).slice(0, 10) : null - }); - - // Try different structures - it might be an array or might have different properties - let entries; - if (worldInfo && typeof worldInfo === 'object') { - if (worldInfo.entries) { - entries = worldInfo.entries; - } else if (Array.isArray(worldInfo)) { - // If it's an array, convert to entries object - entries = {}; - worldInfo.forEach((e, i) => { - if (e && e.uid) { - entries[e.uid] = e; - } - }); - } - } - - if (!entries) { - entries = {}; - } - - // Add the entry - entries[entry.uid] = entry; - - debugLog('[Memory Recollection] Entry added, saving world info...'); - - // Save using the imported saveWorldInfo function - // Pass the entries as the data structure - await saveWorldInfo(lorebookUid, { entries }); - - debugLog('[Memory Recollection] Entry saved successfully'); - return { success: true }; - } catch (error) { - console.error('[Memory Recollection] Error saving entry:', error); - throw error; - } -} - /** * Save multiple world info entries to a lorebook at once * @param {string} lorebookUid - The filename/UID of the lorebook diff --git a/src/systems/features/plotProgression.js b/src/systems/features/plotProgression.js index ac27368..68d2cfc 100644 --- a/src/systems/features/plotProgression.js +++ b/src/systems/features/plotProgression.js @@ -70,7 +70,6 @@ export function setupPlotButtons(handlePlotClick) { */ export async function sendPlotProgression(type) { if (!extensionSettings.enabled) { - // console.log('[RPG Companion] Extension is disabled'); return; } @@ -83,8 +82,6 @@ export async function sendPlotProgression(type) { extensionSettings.enabled = false; try { - // console.log(`[RPG Companion] Sending ${type} plot progression request...`); - // Build the prompt based on type let prompt = ''; if (type === 'random') { @@ -100,13 +97,8 @@ export async function sendPlotProgression(type) { prompt += '\n\n' + htmlPromptText; } - // Set flag to indicate we're doing plot progression - // This will be used by onMessageReceived to clear the prompt after generation completes setIsPlotProgression(true); - // console.log('[RPG Companion] Calling Generate with continuation and plot prompt'); - // console.log('[RPG Companion] Full prompt:', prompt); - // Pass the prompt via options with the correct property name // Based on /continue slash command implementation, it uses quiet_prompt (underscore, not camelCase) const options = { @@ -114,10 +106,7 @@ export async function sendPlotProgression(type) { quietToLoud: true }; - // Call Generate with 'continue' type and our custom prompt await Generate('continue', options); - - // console.log('[RPG Companion] Plot progression generation triggered'); } catch (error) { console.error('[RPG Companion] Error sending plot progression:', error); setIsPlotProgression(false); diff --git a/src/systems/generation/apiClient.js b/src/systems/generation/apiClient.js index df50f83..a1e919d 100644 --- a/src/systems/generation/apiClient.js +++ b/src/systems/generation/apiClient.js @@ -10,17 +10,11 @@ import { lastGeneratedData, committedTrackerData, isGenerating, - lastActionWasSwipe, - setIsGenerating, - setLastActionWasSwipe + setIsGenerating } from '../../core/state.js'; import { saveChatData } from '../../core/persistence.js'; import { generateSeparateUpdatePrompt } from './promptBuilder.js'; import { parseResponse, parseUserStats, parseSkills, tryParseJSONResponse } from './parser.js'; -import { renderUserStats } from '../rendering/userStats.js'; -import { renderInfoBox } from '../rendering/infoBox.js'; -import { renderThoughts } from '../rendering/thoughts.js'; -import { renderInventory } from '../rendering/inventory.js'; import { renderQuests } from '../rendering/quests.js'; import { renderSkills } from '../rendering/skills.js'; import { i18n } from '../../core/i18n.js'; @@ -37,12 +31,8 @@ async function getCurrentPresetName() { // Use /preset without arguments to get the current preset name const result = await executeSlashCommandsOnChatInput('/preset', { quiet: true }); - // console.log('[RPG Companion] /preset result:', result); - - // The result should be an object with a 'pipe' property containing the preset name if (result && typeof result === 'object' && result.pipe) { const presetName = String(result.pipe).trim(); - // console.log('[RPG Companion] Extracted preset name:', presetName); return presetName || null; } @@ -63,11 +53,7 @@ async function getCurrentPresetName() { */ async function switchToPreset(presetName) { try { - // Use the /preset slash command to switch presets - // This is the proper way to change presets in SillyTavern await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true }); - - // console.log(`[RPG Companion] Switched to preset "${presetName}"`); return true; } catch (error) { console.error('[RPG Companion] Error switching preset:', error); @@ -88,7 +74,6 @@ async function switchToPreset(presetName) { */ export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory) { if (isGenerating) { - // console.log('[RPG Companion] Already generating, skipping...'); return; } @@ -97,7 +82,6 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough } if (extensionSettings.generationMode !== 'separate') { - // console.log('[RPG Companion] Not in separate mode, skipping manual update'); return; } @@ -133,9 +117,6 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough }); if (response) { - // console.log('[RPG Companion] Raw AI response:', response); - - // Try JSON parsing first if structured data mode is enabled const jsonParsed = tryParseJSONResponse(response); if (jsonParsed) { @@ -149,23 +130,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough renderSkills(); saveChatData(); } else { - // JSON parsing failed - try legacy text-based parsing as fallback console.warn('[RPG Companion] JSON parsing failed, attempting legacy text parsing...'); const parsedData = parseResponse(response); - // console.log('[RPG Companion] Parsed data:', parsedData); - // console.log('[RPG Companion] parsedData.userStats:', parsedData.userStats ? parsedData.userStats.substring(0, 100) + '...' : 'null'); - // Legacy text parsing does not provide structured characters; clear stale structured data - extensionSettings.charactersData = []; - const parsedCharacterThoughts = parsedData.characterThoughts || ''; + extensionSettings.charactersData = []; + const parsedCharacterThoughts = parsedData.characterThoughts || ''; - // DON'T update lastGeneratedData here - it should only reflect the data - // from the assistant message the user replied to, not auto-generated updates - // This ensures swipes/regenerations use consistent source data - - // Store RPG data for the last assistant message (separate mode) const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null; - // console.log('[RPG Companion] Last message is_user:', lastMessage ? lastMessage.is_user : 'no message'); if (lastMessage && !lastMessage.is_user) { if (!lastMessage.extra) { lastMessage.extra = {}; @@ -175,16 +146,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough } const currentSwipeId = lastMessage.swipe_id || 0; - lastMessage.extra.rpg_companion_swipes[currentSwipeId] = { - userStats: parsedData.userStats, - infoBox: parsedData.infoBox, - characterThoughts: parsedCharacterThoughts - }; + lastMessage.extra.rpg_companion_swipes[currentSwipeId] = { + userStats: parsedData.userStats, + infoBox: parsedData.infoBox, + characterThoughts: parsedCharacterThoughts + }; - // console.log('[RPG Companion] Stored separate mode RPG data for message swipe', currentSwipeId); - - // Update lastGeneratedData for display AND future commit - if (parsedData.userStats) { + if (parsedData.userStats) { lastGeneratedData.userStats = parsedData.userStats; parseUserStats(parsedData.userStats); } @@ -194,28 +162,19 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough if (parsedData.infoBox) { lastGeneratedData.infoBox = parsedData.infoBox; } - lastGeneratedData.characterThoughts = parsedCharacterThoughts; - // console.log('[RPG Companion] ๐พ SEPARATE MODE: Updated lastGeneratedData:', { - // userStats: lastGeneratedData.userStats ? 'exists' : 'null', - // infoBox: lastGeneratedData.infoBox ? 'exists' : 'null', - // characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null' - // }); + lastGeneratedData.characterThoughts = parsedCharacterThoughts; - // Only auto-commit on TRULY first generation (no committed data exists at all) - // This prevents auto-commit after refresh when we have saved committed data - const hasAnyCommittedContent = ( - (committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') || - (committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') || - (committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n') - ); + const hasAnyCommittedContent = ( + (committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') || + (committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') || + (committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n') + ); - // Only commit if we have NO committed content at all (truly first time ever) - if (!hasAnyCommittedContent) { - committedTrackerData.userStats = parsedData.userStats; - committedTrackerData.infoBox = parsedData.infoBox; - committedTrackerData.characterThoughts = parsedCharacterThoughts; - // console.log('[RPG Companion] ๐ FIRST TIME: Auto-committed tracker data'); - } + if (!hasAnyCommittedContent) { + committedTrackerData.userStats = parsedData.userStats; + committedTrackerData.infoBox = parsedData.infoBox; + committedTrackerData.characterThoughts = parsedCharacterThoughts; + } // Render the updated data renderUserStats(); @@ -254,14 +213,9 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough setIsGenerating(false); - // Restore button to original state const $updateBtn = $('#rpg-manual-update'); const refreshText = i18n.getTranslation('template.mainPanel.refreshRpgInfo') || 'Refresh RPG Info'; $updateBtn.html(` ${refreshText}`).prop('disabled', false); - - // Reset the flag after tracker generation completes - // This ensures the flag persists through both main generation AND tracker generation - // console.log('[RPG Companion] ๐ Tracker generation complete - resetting lastActionWasSwipe to false'); setLastActionWasSwipe(false); } } diff --git a/src/systems/generation/injector.js b/src/systems/generation/injector.js index 2bb1fa6..ad0b651 100644 --- a/src/systems/generation/injector.js +++ b/src/systems/generation/injector.js @@ -9,9 +9,7 @@ import { extensionSettings, committedTrackerData, lastGeneratedData, - isGenerating, - lastActionWasSwipe, - setLastActionWasSwipe + isGenerating } from '../../core/state.js'; import { evaluateSuppression } from './suppression.js'; import { parseUserStats } from './parser.js'; @@ -39,15 +37,7 @@ function getTrackerInstructions(includeHtmlPrompt, includeContinuation) { * @param {Object} data - Event data */ export function onGenerationStarted(type, data) { - // console.log('[RPG Companion] onGenerationStarted called'); - // console.log('[RPG Companion] enabled:', extensionSettings.enabled); - // console.log('[RPG Companion] generationMode:', extensionSettings.generationMode); - // console.log('[RPG Companion] โก EVENT: onGenerationStarted - lastActionWasSwipe =', lastActionWasSwipe, '| isGenerating =', isGenerating); - // console.log('[RPG Companion] Committed Prompt:', committedTrackerData); - - // Skip tracker injection for image generation requests if (data?.quietImage) { - // console.log('[RPG Companion] Detected image generation (quietImage=true), skipping tracker injection'); return; } @@ -57,74 +47,27 @@ export function onGenerationStarted(type, data) { const context = getContext(); const chat = context.chat; - // Detect if a guided generation is active (GuidedGenerations and similar extensions - // inject an ephemeral 'instruct' injection into chatMetadata.script_injects). - // If present, we should avoid injecting RPG tracker instructions that ask - // the model to include stats/etc. This prevents conflicts when guided prompts - // are used (e.g., GuidedGenerations Extension). - // Evaluate suppression using the shared helper const suppression = evaluateSuppression(extensionSettings, context, data); - const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt, instructContent, quietPromptRaw, matchedPattern } = suppression; + const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt } = suppression; if (shouldSuppress) { - // Debugging: indicate active suppression and which source triggered it console.debug(`[RPG Companion] Suppression active (mode=${skipMode}). isGuided=${isGuidedGeneration}, isImpersonation=${isImpersonationGeneration}, hasQuietPrompt=${hasQuietPrompt} - skipping RPG tracker injections for this generation.`); - - // Also clear any existing RPG Companion prompts so they do not leak into this generation - // (e.g., previously set extension prompts should not be used alongside a guided prompt) setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false); setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false); setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false); setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false); } - const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null; - // For SEPARATE mode only: Check if we need to commit extension data - // BUT: Only do this for the MAIN generation, not the tracker update generation - // If isGenerating is true, this is the tracker update generation (second call), so skip flag logic - // console.log('[RPG Companion DEBUG] Before generating:', lastGeneratedData.characterThoughts, ' , committed - ', committedTrackerData.characterThoughts); if (extensionSettings.generationMode === 'separate' && !isGenerating) { if (!lastActionWasSwipe) { - // User sent a new message - commit lastGeneratedData before generation - // console.log('[RPG Companion] ๐ COMMIT: New message - committing lastGeneratedData'); - // console.log('[RPG Companion] BEFORE commit - committedTrackerData:', { - // userStats: committedTrackerData.userStats ? 'exists' : 'null', - // infoBox: committedTrackerData.infoBox ? 'exists' : 'null', - // characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null' - // }); - // console.log('[RPG Companion] BEFORE commit - lastGeneratedData:', { - // userStats: lastGeneratedData.userStats ? 'exists' : 'null', - // infoBox: lastGeneratedData.infoBox ? 'exists' : 'null', - // characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null' - // }); committedTrackerData.userStats = lastGeneratedData.userStats; committedTrackerData.infoBox = lastGeneratedData.infoBox; committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts; - // console.log('[RPG Companion] AFTER commit - committedTrackerData:', { - // userStats: committedTrackerData.userStats ? 'exists' : 'null', - // infoBox: committedTrackerData.infoBox ? 'exists' : 'null', - // characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null' - // }); - - // Reset flag after committing (ready for next cycle) - - } else { - // console.log('[RPG Companion] ๐ SWIPE: Using existing committedTrackerData (no commit)'); - // console.log('[RPG Companion] committedTrackerData:', { - // userStats: committedTrackerData.userStats ? 'exists' : 'null', - // infoBox: committedTrackerData.infoBox ? 'exists' : 'null', - // characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null' - // }); - // Reset flag after using it (swipe generation complete, ready for next action) } } - // For TOGETHER mode: Check if we need to commit extension data - // Only commit when user sends a new message (not on swipes) if (extensionSettings.generationMode === 'together') { if (!lastActionWasSwipe) { - // User sent a new message - commit data from the last assistant message they replied to - // This ensures swipes use consistent data from before the first swipe console.log('[RPG Companion] ๐ TOGETHER MODE COMMIT: New message - committing from last assistant message'); // Find the last assistant message (before the user's new message) @@ -163,79 +106,44 @@ export function onGenerationStarted(type, data) { } } - // Use the committed tracker data as source for generation - // console.log('[RPG Companion] Using committedTrackerData for generation'); - // console.log('[RPG Companion] committedTrackerData.userStats:', committedTrackerData.userStats); - - // Parse stats from committed data to update the extensionSettings for prompt generation if (committedTrackerData.userStats) { - // console.log('[RPG Companion] Parsing committed userStats into extensionSettings'); parseUserStats(committedTrackerData.userStats); - // console.log('[RPG Companion] After parsing, extensionSettings.userStats:', JSON.stringify(extensionSettings.userStats)); } if (extensionSettings.generationMode === 'together') { - // console.log('[RPG Companion] In together mode, generating prompts...'); - const example = ''; // JSON format includes schema in instructions, no separate example needed - // Don't include HTML prompt in instructions - inject it separately to avoid duplication on swipes + const example = ''; const instructions = getTrackerInstructions(false, true); - // Clear separate mode context injection - we don't use contextual summary in together mode setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false); - // console.log('[RPG Companion] Example:', example ? 'exists' : 'empty'); - // console.log('[RPG Companion] Chat length:', chat ? chat.length : 'chat is null'); - - // Find the last assistant message in the chat history - let lastAssistantDepth = -1; // -1 means not found + let lastAssistantDepth = -1; if (chat && chat.length > 0) { - // console.log('[RPG Companion] Searching for last assistant message...'); - // Start from depth 1 (skip depth 0 which is usually user's message or prefill) for (let depth = 1; depth < chat.length; depth++) { - const index = chat.length - 1 - depth; // Convert depth to index + const index = chat.length - 1 - depth; const message = chat[index]; - // console.log('[RPG Companion] Checking depth', depth, 'index', index, 'message properties:', Object.keys(message)); - // Check for assistant message: not user and not system if (!message.is_user && !message.is_system) { - // Found assistant message at this depth - // Inject at the SAME depth to prepend to this assistant message lastAssistantDepth = depth; - // console.log('[RPG Companion] Found last assistant message at depth', depth, '-> injecting at same depth:', lastAssistantDepth); break; } } } - // If we have previous tracker data and found an assistant message, inject it as an assistant message if (!shouldSuppress && example && lastAssistantDepth > 0) { setExtensionPrompt('rpg-companion-example', example, extension_prompt_types.IN_CHAT, lastAssistantDepth, false, extension_prompt_roles.ASSISTANT); - // console.log('[RPG Companion] Injected tracker example as assistant message at depth:', lastAssistantDepth); - } else { - // console.log('[RPG Companion] NOT injecting example. example:', !!example, 'lastAssistantDepth:', lastAssistantDepth); } - // Inject the instructions as a user message at depth 0 (right before generation) - // If this is a guided generation (user explicitly injected 'instruct'), skip adding - // our tracker instructions to avoid clobbering the guided prompt. if (!shouldSuppress) { setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER); } - // console.log('[RPG Companion] Injected RPG tracking instructions at depth 0 (right before generation)'); - // Inject HTML prompt separately at depth 0 if enabled (prevents duplication on swipes) if (extensionSettings.enableHtmlPrompt && !shouldSuppress) { - // Use custom HTML prompt if set, otherwise use default const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT; const htmlPrompt = `\n${htmlPromptText}`; - setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false); - // console.log('[RPG Companion] Injected HTML prompt at depth 0 for together mode'); } else { - // Clear HTML prompt if disabled setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false); } } else if (extensionSettings.generationMode === 'separate') { - // In SEPARATE mode, inject the current state as JSON for main roleplay generation const currentStateJSON = generateContextualSummary(); if (currentStateJSON) { @@ -246,27 +154,18 @@ ${currentStateJSON} \`\`\` \n\n`; - // Inject context at depth 1 (before last user message) as SYSTEM - // Skip when a guided generation injection is present to avoid conflicting instructions if (!shouldSuppress) { setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false); } - // console.log('[RPG Companion] Injected current state JSON for separate mode:', currentStateJSON); } else { - // Clear if no data yet setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false); } - // Inject HTML prompt separately at depth 0 if enabled (same as together mode pattern) if (extensionSettings.enableHtmlPrompt && !shouldSuppress) { - // Use custom HTML prompt if set, otherwise use default const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT; const htmlPrompt = `\n${htmlPromptText}`; - setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false); - // console.log('[RPG Companion] Injected HTML prompt at depth 0 for separate mode'); } else { - // Clear HTML prompt if disabled setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false); } diff --git a/src/systems/generation/parser.js b/src/systems/generation/parser.js index ae154cf..10d7968 100644 --- a/src/systems/generation/parser.js +++ b/src/systems/generation/parser.js @@ -4,10 +4,10 @@ * Supports both legacy text format and new JSON format */ -import { extensionSettings, FEATURE_FLAGS, addDebugLog, lastGeneratedData, committedTrackerData } from '../../core/state.js'; +import { extensionSettings, addDebugLog, lastGeneratedData, committedTrackerData } from '../../core/state.js'; import { saveSettings, saveChatData } from '../../core/persistence.js'; import { extractInventory } from './inventoryParser.js'; -import { validateTrackerData, mergeTrackerData } from '../../types/trackerData.js'; +import { validateTrackerData } from '../../types/trackerData.js'; import { handleItemRemoved } from '../rendering/skills.js'; /** @@ -850,24 +850,13 @@ export function parseUserStats(statsText) { } } - // Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1 - if (FEATURE_FLAGS.useNewInventory) { - const inventoryData = extractInventory(statsText); - if (inventoryData) { - extensionSettings.userStats.inventory = inventoryData; - debugLog('[RPG Parser] Inventory v2 extracted:', inventoryData); - } else { - debugLog('[RPG Parser] Inventory v2 extraction failed'); - } + // Extract inventory - extractInventory() handles v2 format and falls back to v1 if needed + const inventoryData = extractInventory(statsText); + if (inventoryData) { + extensionSettings.userStats.inventory = inventoryData; + debugLog('[RPG Parser] Inventory extracted:', inventoryData); } else { - // Legacy v1 parsing for backward compatibility - const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i); - if (inventoryMatch) { - extensionSettings.userStats.inventory = inventoryMatch[1].trim(); - debugLog('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim()); - } else { - debugLog('[RPG Parser] Inventory v1 not found'); - } + debugLog('[RPG Parser] Inventory extraction failed'); } // Extract quests @@ -900,7 +889,7 @@ export function parseUserStats(statsText) { arousal: extensionSettings.userStats.arousal, mood: extensionSettings.userStats.mood, conditions: extensionSettings.userStats.conditions, - inventory: FEATURE_FLAGS.useNewInventory ? 'v2 object' : extensionSettings.userStats.inventory + inventory: extensionSettings.userStats.inventory }); saveSettings(); @@ -947,11 +936,11 @@ export function parseSkills(skillsText) { extensionSettings.skillAbilityLinks = {}; } - // Get configured skill categories (handle both old string and new object format) + // Migration function handles string array โ object array conversion on load const rawCategories = extensionSettings.trackerConfig?.userStats?.skillsSection?.customFields || []; const configuredCategories = rawCategories - .filter(cat => typeof cat === 'string' || cat.enabled !== false) - .map(cat => typeof cat === 'string' ? cat : cat.name); + .filter(cat => cat.enabled !== false) + .map(cat => cat.name); const lines = skillsText.split('\n'); const newSkillAbilityLinks = {}; diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index af25404..3a01195 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -4,10 +4,9 @@ */ import { getContext } from '../../../../../../extensions.js'; -import { chat, getCurrentChatDetails, characters, this_chid } from '../../../../../../../script.js'; +import { chat, characters, this_chid } from '../../../../../../../script.js'; import { selected_group, getGroupMembers, getGroupChat } from '../../../../../../group-chats.js'; -import { extensionSettings, committedTrackerData } from '../../core/state.js'; -import { generateSchemaExample } from '../../types/trackerData.js'; +import { extensionSettings } from '../../core/state.js'; // Type imports /** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */ @@ -28,7 +27,7 @@ export const DEFAULT_JSON_TRACKER_PROMPT = `At the start of every reply, output * Default message interception prompt text * Guides the LLM to rewrite the user's message based on current RPG state and recent chat */ -export const DEFAULT_MESSAGE_INTERCEPTION_PROMPT = `Act as an uncompromising Immersive Copy Editor who rewrites the user's draft to strictly adhere to {{user}}'s persona and RPG state (JSON). You must validate the feasibility of the user's intended actions for {{user}} against the JSON state; if the draft contradicts the state (e.g., acting smart while 'Intelligence' is low, or running while having a 'Leg Injury'), you are required to override the core intent, rewriting the action to portray immediate failure, struggle, or involuntary reaction instead of the user's desired success. Even further, if the intended course of action is physically impossible via the state or represents a thought process conceptually alien to the character's nature or current state, you are mandated to completely overwrite the user's intent. Aggressively rephrase vocabulary and syntax to match the character's specific cognitive capacity and tone. Keep the output concise and devoid of fluff; do not expand the narrative beyond the necessary state-enforced correction. Never include information that was not already present in the original draft. Never narrate the consequences of {{user}}'s actions, only what they are. Return ONLY the modified message text.`; +export const DEFAULT_MESSAGE_INTERCEPTION_PROMPT = `Act as an uncompromising Immersive Copy Editor who rewrites the user's draft to strictly adhere to {{user}}'s persona and RPG state (JSON). You must validate the feasibility of the user's intended actions for {{user}} to take against the JSON state; if the draft contradicts said state (e.g. acting smart while 'Intelligence' is low, or running while having a 'Leg Injury'), you are required to override the core intent, rewriting the action to portray immediate failure, struggle, or an involuntary reaction instead of the user's desired success. Even further, if the intended course of action is physically impossible via the state or represents a thought process that is conceptually alien to the character's nature or current state, you are mandated to completely overwrite the user's intent. Be careful not to confuse communicated intent (e.g. walk towards some direction, or throw a punch) with intended speech. Intent must always be overwritten with intent (e.g. if user wanted for {{user}} to run, but they have a leg injury, your correction will make them limp). Speech must always be overwritten with speech (e.g. if user means for {{user}} to speak eloquently, but they're not smart enough, you'd dumb down their choice of words or speech patterns). Never replace intent with failed speech (e.g. user communicates that {{user}} will throw a punch, or make a gesture, but you incorrectly decide that they will make muffled noises instead, as they are gagged). Aggressively rephrase vocabulary and syntax to match the character's specific cognitive capacity and tone. Keep the output concise and devoid of fluff; do not expand the narrative beyond the necessary state-enforced correction. Never include information that was not already present in the original draft. Never narrate the consequences of {{user}}'s actions, only what they are. Return ONLY the modified message text.`; /** * Gets character card information for current chat (handles both single and group chats) @@ -362,16 +361,13 @@ export function generateJSONTrackerInstructions(includeHtmlPrompt = true, includ // Skills section if (showSkills) { const skillCategories = trackerConfig?.userStats?.skillsSection?.customFields || []; - // Filter to only enabled categories and handle both old (string) and new (object) formats - const enabledCategories = skillCategories.filter(cat => { - if (typeof cat === 'string') return true; - return cat.enabled !== false; - }); + // Migration function handles string array โ object array conversion on load + const enabledCategories = skillCategories.filter(cat => cat.enabled !== false); if (enabledCategories.length > 0) { let skillsSection = ' "skills": {\n'; const categoryExamples = enabledCategories.map(cat => { - const catName = typeof cat === 'string' ? cat : cat.name; + const catName = cat.name; let skillExample = '{ "name": "Ability Name", "description": "What this ability does" }'; if (enableItemSkillLinks) { skillExample = '{ "name": "Ability", "description": "Description", "grantedBy": "Item Name" }'; @@ -407,6 +403,11 @@ export function generateJSONTrackerInstructions(includeHtmlPrompt = true, includ instructions += '- Level is a numeric value (typically 1+, represents character progression)\n'; } + if (showQuests) { + instructions += '- A main quest can be created when the current main objective changes\n'; + instructions += '- Optional quests can be created for smaller matters that need to be resolved\n'; + } + instructions += '- Items should be placeed in the inventory section, not the skills section\n'; instructions += '- Characters should be removed as soon as they leave the scene\n'; instructions += '- Your list of characters must never include {{user}}\n'; @@ -675,8 +676,6 @@ export function generateContextualSummary() { * @returns {string} Full prompt text for separate tracker generation */ export function generateRPGPromptText() { - const userName = getContext().name1; - let promptText = ''; promptText += `Here are the previous trackers in JSON format that you should consider when responding:\n`; @@ -814,7 +813,6 @@ export function generateRPGPromptText() { */ export async function generateSeparateUpdatePrompt() { const depth = extensionSettings.updateDepth; - const userName = getContext().name1; const messages = []; diff --git a/src/systems/integration/sillytavern.js b/src/systems/integration/sillytavern.js index d790712..ff07513 100644 --- a/src/systems/integration/sillytavern.js +++ b/src/systems/integration/sillytavern.js @@ -62,15 +62,10 @@ export function commitTrackerData() { const swipeData = message.extra.rpg_companion_swipes[swipeId]; if (swipeData) { - // console.log('[RPG Companion] Committing tracker data from assistant message at index', i, 'swipe', swipeId); committedTrackerData.userStats = swipeData.userStats || null; committedTrackerData.infoBox = swipeData.infoBox || null; committedTrackerData.characterThoughts = swipeData.characterThoughts || null; - } else { - // console.log('[RPG Companion] No swipe data found for swipe', swipeId); } - } else { - // console.log('[RPG Companion] No RPG data found in last assistant message'); } break; } @@ -85,9 +80,7 @@ export function commitTrackerData() { export async function onMessageSent() { if (!extensionSettings.enabled) return; - // User sent a new message - NOT a swipe setLastActionWasSwipe(false); - // console.log('[RPG Companion] ๐ข EVENT: onMessageSent - lastActionWasSwipe =', lastActionWasSwipe); // Optionally intercept and rewrite the user message via LLM if (extensionSettings.enableMessageInterception && extensionSettings.messageInterceptionActive !== false) { @@ -106,10 +99,7 @@ export async function onMessageSent() { committedTrackerData.infoBox = lastGeneratedData.infoBox; committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts; - // Save to chat metadata saveChatData(); - - // console.log('[RPG Companion] ๐พ Committed displayed tracker on user message (auto-update disabled)'); } } } @@ -206,7 +196,6 @@ export async function onMessageReceived(data) { const lastMessage = chat[chat.length - 1]; if (lastMessage && !lastMessage.is_user) { const responseText = lastMessage.mes; - // console.log('[RPG Companion] Parsing together mode response:', responseText); // Try JSON parsing first if structured data mode is enabled const jsonParsed = tryParseJSONResponse(responseText); @@ -228,7 +217,6 @@ export async function onMessageReceived(data) { // JSON parsing failed - fall back to legacy text-based parsing console.warn('[RPG Companion] JSON parsing failed in together mode, attempting legacy text parsing...'); const parsedData = parseResponse(responseText); - // console.log('[RPG Companion] Parsed data:', parsedData); // Legacy text parsing does not produce structured characters; clear old state to avoid stale UI/state extensionSettings.charactersData = []; @@ -264,16 +252,11 @@ export async function onMessageReceived(data) { characterThoughts: parsedCharacterThoughts }; - // console.log('[RPG Companion] Stored RPG data for swipe', currentSwipeId); - // If there's no committed data yet (first time generating), automatically commit if (!committedTrackerData.userStats && !committedTrackerData.infoBox && !committedTrackerData.characterThoughts) { committedTrackerData.userStats = parsedData.userStats; committedTrackerData.infoBox = parsedData.infoBox; committedTrackerData.characterThoughts = parsedCharacterThoughts; - // console.log('[RPG Companion] ๐ FIRST TIME: Auto-committed tracker data'); - } else { - // console.log('[RPG Companion] Data will be committed when user replies'); } // Remove the tracker code blocks from the visible message @@ -307,8 +290,6 @@ export async function onMessageReceived(data) { const messageId = chat.length - 1; updateMessageBlock(messageId, lastMessage, { rerenderMessage: true }); - // console.log('[RPG Companion] Cleaned message, removed tracker code blocks from DOM'); - // Save to chat metadata saveChatData(); } @@ -321,9 +302,7 @@ export async function onMessageReceived(data) { // Reset the swipe flag after generation completes // This ensures that if the user swiped โ auto-reply generated โ flag is now cleared - // so the next user message will be treated as a new message (not a swipe) if (lastActionWasSwipe) { - // console.log('[RPG Companion] ๐ Generation complete after swipe - resetting lastActionWasSwipe to false'); setLastActionWasSwipe(false); } @@ -331,7 +310,6 @@ export async function onMessageReceived(data) { // Note: No need to clear extension prompt since we used quiet_prompt option if (isPlotProgression) { setIsPlotProgression(false); - // console.log('[RPG Companion] Plot progression generation completed'); } } @@ -375,8 +353,6 @@ export function onMessageSwiped(messageIndex) { return; } - // console.log('[RPG Companion] Message swiped at index:', messageIndex); - // Get the message that was swiped const message = chat[messageIndex]; if (!message || message.is_user) { @@ -393,16 +369,9 @@ export function onMessageSwiped(messageIndex) { message.swipes[currentSwipeId].length > 0; if (!isExistingSwipe) { - // This is a NEW swipe that will trigger generation setLastActionWasSwipe(true); - // console.log('[RPG Companion] ๐ต EVENT: onMessageSwiped (NEW generation) - lastActionWasSwipe =', lastActionWasSwipe); - } else { - // This is navigating to an EXISTING swipe - don't change the flag - // console.log('[RPG Companion] ๐ต EVENT: onMessageSwiped (existing swipe navigation) - lastActionWasSwipe unchanged =', lastActionWasSwipe); } - // console.log('[RPG Companion] Loading data for swipe', currentSwipeId); - // Load RPG data for this swipe into lastGeneratedData (for display only) // This updates what the user sees, but does NOT commit it // Committed data will be updated when/if the user replies to this swipe @@ -418,13 +387,6 @@ export function onMessageSwiped(messageIndex) { if (swipeData.userStats) { parseUserStats(swipeData.userStats); } - - // console.log('[RPG Companion] Loaded RPG data for swipe', currentSwipeId, '(display only, NOT committed)'); - // console.log('[RPG Companion] committedTrackerData unchanged - will be updated if user replies to this swipe'); - } else { - // No data for this swipe - keep existing lastGeneratedData (don't clear it) - // This ensures the display remains consistent and data is available for next commit - // console.log('[RPG Companion] No RPG data for swipe', currentSwipeId, '- keeping existing lastGeneratedData'); } // Re-render the panels (display only - committedTrackerData unchanged) @@ -444,7 +406,6 @@ export function onMessageSwiped(messageIndex) { export function updatePersonaAvatar() { const portraitImg = document.querySelector('.rpg-user-portrait'); if (!portraitImg) { - // console.log('[RPG Companion] Portrait image element not found in DOM'); return; } @@ -452,23 +413,13 @@ export function updatePersonaAvatar() { const context = getContext(); const currentUserAvatar = context.user_avatar || user_avatar; - // console.log('[RPG Companion] Attempting to update persona avatar:', currentUserAvatar); - // Try to get a valid thumbnail URL using our safe helper if (currentUserAvatar) { const thumbnailUrl = getSafeThumbnailUrl('persona', currentUserAvatar); if (thumbnailUrl) { - // Only update the src if we got a valid URL portraitImg.src = thumbnailUrl; - // console.log('[RPG Companion] Persona avatar updated successfully'); - } else { - // Don't update the src if we couldn't get a valid URL - // This prevents 400 errors and keeps the existing image - // console.warn('[RPG Companion] Could not get valid thumbnail URL for persona avatar, keeping existing image'); } - } else { - // console.log('[RPG Companion] No user avatar configured, keeping existing image'); } } @@ -480,6 +431,4 @@ export function clearExtensionPrompts() { setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false); setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false); setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false); - // Note: rpg-companion-plot is not cleared here since it's passed via quiet_prompt option - // console.log('[RPG Companion] Cleared all extension prompts'); } diff --git a/src/systems/interaction/inventoryActions.js b/src/systems/interaction/inventoryActions.js index 07a3d95..5411b8d 100644 --- a/src/systems/interaction/inventoryActions.js +++ b/src/systems/interaction/inventoryActions.js @@ -5,7 +5,6 @@ import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js'; import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js'; -import { buildInventorySummary } from '../generation/promptBuilder.js'; import { buildUserStatsText } from '../rendering/userStats.js'; import { renderInventory, getLocationId } from '../rendering/inventory.js'; import { parseItems, serializeItems } from '../../utils/itemParser.js'; @@ -293,8 +292,6 @@ export function removeItem(field, itemIndex, location) { inventory[field] = newString; } - // console.log('[RPG Companion] DEBUG inventory after save:', inventory); - updateLastGeneratedDataInventory(); saveSettings(); saveChatData(); @@ -375,15 +372,11 @@ export function saveAddLocation() { * @param {string} locationName - Name of location to remove */ export function showRemoveConfirmation(locationName) { - // console.log('[RPG Companion] DEBUG showRemoveConfirmation called for:', locationName); const confirmId = `rpg-remove-confirm-${getLocationId(locationName)}`; - // console.log('[RPG Companion] DEBUG confirmId:', confirmId); const confirmUI = $(`#${confirmId}`); - // console.log('[RPG Companion] DEBUG confirmUI element found:', confirmUI.length); if (confirmUI.length > 0) { confirmUI.show(); - // console.log('[RPG Companion] DEBUG confirmation shown'); } else { console.warn('[RPG Companion] DEBUG confirmation element not found!'); } @@ -407,12 +400,9 @@ export function hideRemoveConfirmation(locationName) { * @param {string} locationName - Name of location to remove */ export function confirmRemoveLocation(locationName) { - // console.log('[RPG Companion] DEBUG confirmRemoveLocation called for:', locationName); const inventory = extensionSettings.userStats.inventory; - // console.log('[RPG Companion] DEBUG inventory.stored before deletion:', inventory.stored); delete inventory.stored[locationName]; - // console.log('[RPG Companion] DEBUG inventory.stored after deletion:', inventory.stored); // Remove from collapsed list if present const index = collapsedLocations.indexOf(locationName); @@ -424,9 +414,6 @@ export function confirmRemoveLocation(locationName) { saveSettings(); saveChatData(); updateMessageSwipeData(); - - // Re-render inventory UI - // console.log('[RPG Companion] DEBUG calling renderInventory()'); renderInventory(); }/** * Toggles the collapsed state of a storage location section. @@ -621,8 +608,6 @@ export function initInventoryEventListeners() { const view = $(this).data('view'); switchViewMode(field, view); }); - - // console.log('[RPG Companion] Inventory event listeners initialized'); } /** @@ -645,7 +630,6 @@ export function restoreFormStates() { // Restore add location form if (openForms.addLocation) { const form = $('#rpg-add-location-form'); - const input = $('#rpg-new-location-name'); if (form.length > 0) { form.show(); // Don't refocus to avoid disrupting user interaction @@ -655,7 +639,6 @@ export function restoreFormStates() { // Restore add item on person form if (openForms.addItemOnPerson) { const form = $('#rpg-add-item-form-onPerson'); - const input = $('#rpg-new-item-onPerson'); if (form.length > 0) { form.show(); } @@ -664,7 +647,6 @@ export function restoreFormStates() { // Restore add item assets form if (openForms.addItemAssets) { const form = $('#rpg-add-item-form-assets'); - const input = $('#rpg-new-item-assets'); if (form.length > 0) { form.show(); } @@ -673,7 +655,6 @@ export function restoreFormStates() { // Restore add item simplified form if (openForms.addItemSimplified) { const form = $('#rpg-add-item-form-simplified'); - const input = $('#rpg-new-item-simplified'); if (form.length > 0) { form.show(); } diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js index 76a888e..9049791 100644 --- a/src/systems/rendering/infoBox.js +++ b/src/systems/rendering/infoBox.js @@ -128,11 +128,7 @@ export function renderInfoBox() { return; } - // console.log('[RPG Companion] renderInfoBox called with data:', infoBoxData); - - // Parse the info box data const lines = infoBoxData.split('\n'); - // console.log('[RPG Companion] Info Box split into lines:', lines); const data = { date: '', weekday: '', @@ -158,8 +154,6 @@ export function renderInfoBox() { }; for (const line of lines) { - // console.log('[RPG Companion] Processing line:', line); - // Helper to check if a value is valid (not null/empty) const isValidParsedValue = (val) => val && val !== 'null' && val !== 'undefined' && val.toLowerCase() !== 'none'; @@ -167,7 +161,6 @@ export function renderInfoBox() { // Prioritize text format over emoji format if (line.startsWith('Date:')) { if (!parsedFields.date) { - // console.log('[RPG Companion] โ Matched DATE (text format)'); const dateStr = line.replace('Date:', '').trim(); if (isValidParsedValue(dateStr)) { const dateParts = dateStr.split(',').map(p => p.trim()); @@ -180,7 +173,6 @@ export function renderInfoBox() { } } else if (line.includes('๐๏ธ:')) { if (!parsedFields.date) { - // console.log('[RPG Companion] โ Matched DATE (emoji format)'); const dateStr = line.replace('๐๏ธ:', '').trim(); if (isValidParsedValue(dateStr)) { const dateParts = dateStr.split(',').map(p => p.trim()); @@ -193,7 +185,6 @@ export function renderInfoBox() { } } else if (line.startsWith('Temperature:')) { if (!parsedFields.temperature) { - // console.log('[RPG Companion] โ Matched TEMPERATURE (text format)'); const tempStr = line.replace('Temperature:', '').trim(); if (isValidParsedValue(tempStr)) { data.temperature = tempStr; @@ -206,7 +197,6 @@ export function renderInfoBox() { } } else if (line.includes('๐ก๏ธ:')) { if (!parsedFields.temperature) { - // console.log('[RPG Companion] โ Matched TEMPERATURE (emoji format)'); const tempStr = line.replace('๐ก๏ธ:', '').trim(); if (isValidParsedValue(tempStr)) { data.temperature = tempStr; @@ -219,7 +209,6 @@ export function renderInfoBox() { } } else if (line.startsWith('Time:')) { if (!parsedFields.time) { - // console.log('[RPG Companion] โ Matched TIME (text format)'); const timeStr = line.replace('Time:', '').trim(); if (isValidParsedValue(timeStr)) { data.time = timeStr; @@ -231,7 +220,6 @@ export function renderInfoBox() { } } else if (line.includes('๐:')) { if (!parsedFields.time) { - // console.log('[RPG Companion] โ Matched TIME (emoji format)'); const timeStr = line.replace('๐:', '').trim(); if (isValidParsedValue(timeStr)) { data.time = timeStr; @@ -243,7 +231,6 @@ export function renderInfoBox() { } } else if (line.startsWith('Location:')) { if (!parsedFields.location) { - // console.log('[RPG Companion] โ Matched LOCATION (text format)'); const locStr = line.replace('Location:', '').trim(); if (isValidParsedValue(locStr)) { data.location = locStr; @@ -252,7 +239,6 @@ export function renderInfoBox() { } } else if (line.includes('๐บ๏ธ:')) { if (!parsedFields.location) { - // console.log('[RPG Companion] โ Matched LOCATION (emoji format)'); const locStr = line.replace('๐บ๏ธ:', '').trim(); if (isValidParsedValue(locStr)) { data.location = locStr; @@ -297,45 +283,22 @@ export function renderInfoBox() { const notDivider = !line.includes('---'); const notCodeFence = !line.trim().startsWith('```'); - // console.log('[RPG Companion] โ Checking weather conditions:', { - // line: line, - // hasColon: hasColon, - // notInfoBox: notInfoBox, - // notDivider: notDivider - // }); - if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) { - // Match format: [Weather Emoji]: [Forecast] - // Capture everything before colon as emoji, everything after as forecast - // console.log('[RPG Companion] โ Testing WEATHER match for:', line); const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/); if (weatherMatch) { const potentialEmoji = weatherMatch[1].trim(); const forecast = weatherMatch[2].trim(); - // If the first part is short (likely emoji), treat as weather if (potentialEmoji.length <= 5) { data.weatherEmoji = potentialEmoji; data.weatherForecast = forecast; parsedFields.weather = true; - // console.log('[RPG Companion] โ Weather parsed:', data.weatherEmoji, data.weatherForecast); - } else { - // console.log('[RPG Companion] โ First part too long for emoji:', potentialEmoji); } - } else { - // console.log('[RPG Companion] โ Weather regex did not match'); } - } else { - // console.log('[RPG Companion] โ No match for this line'); } } } } - - // console.log('[RPG Companion] Parsed Info Box data:', { - // date: data.date, - // weatherEmoji: data.weatherEmoji, - // weatherForecast: data.weatherForecast, // temperature: data.temperature, // timeStart: data.timeStart, // location: data.location @@ -635,14 +598,12 @@ export function updateInfoBoxField(field, value) { // Reconstruct the Info Box text with updated field const lines = lastGeneratedData.infoBox.split('\n'); let dateLineFound = false; - let dateLineIndex = -1; let weatherLineIndex = -1; // Find the date line for (let i = 0; i < lines.length; i++) { if (lines[i].includes('๐๏ธ:') || lines[i].startsWith('Date:')) { dateLineFound = true; - dateLineIndex = i; break; } } @@ -876,7 +837,6 @@ export function updateInfoBoxField(field, value) { const swipeId = message.swipe_id || 0; if (message.extra.rpg_companion_swipes[swipeId]) { message.extra.rpg_companion_swipes[swipeId].infoBox = updatedLines.join('\n'); - // console.log('[RPG Companion] Updated infoBox in message swipe data'); } } break; @@ -907,34 +867,57 @@ function updateRecentEvent(field, value) { }[field]; if (eventIndex !== undefined) { - // Parse current infoBox to get existing events - const lines = (committedTrackerData.infoBox || '').split('\n'); + // Get existing events - prioritize structured data (same logic as renderInfoBox) let recentEvents = []; - - // Find existing Recent Events line - const recentEventsLine = lines.find(line => line.startsWith('Recent Events:')); - if (recentEventsLine) { - const eventsString = recentEventsLine.replace('Recent Events:', '').trim(); - if (eventsString) { - recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e); + + // First check structured infoBoxData (from JSON parsing) + if (extensionSettings.infoBoxData?.recentEvents) { + const events = extensionSettings.infoBoxData.recentEvents; + if (Array.isArray(events)) { + // Get all valid events, preserving order (max 3) + recentEvents = events.filter(e => e && e !== 'null').slice(0, 3); + } else if (typeof events === 'string' && events !== 'null') { + recentEvents = [events]; + } + } + + // Fallback to text format from committedTrackerData + if (recentEvents.length === 0 && committedTrackerData.infoBox) { + const lines = (committedTrackerData.infoBox || '').split('\n'); + const recentEventsLine = lines.find(line => line.startsWith('Recent Events:')); + if (recentEventsLine) { + const eventsString = recentEventsLine.replace('Recent Events:', '').trim(); + if (eventsString) { + recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e).slice(0, 3); + } } } - // Ensure array has enough slots + // Filter out placeholder text - treat it as empty + const placeholderText = i18n.getTranslation('infobox.recentEvents.addEventPlaceholder'); + const cleanedValue = (value === placeholderText || value === 'Add event...' || value === 'Click to add event') ? '' : value.trim(); + + // Update the specific event in the array + // Ensure array has enough slots for the index we're updating while (recentEvents.length <= eventIndex) { recentEvents.push(''); } - // Update the specific event - recentEvents[eventIndex] = value; - - // Filter out empty events and rebuild the line + recentEvents[eventIndex] = cleanedValue; + + // Filter out empty events for final storage (but preserve order of non-empty ones) const validEvents = recentEvents.filter(e => e && e.trim()); + const newRecentEventsLine = validEvents.length > 0 ? `Recent Events: ${validEvents.join(', ')}` : ''; // Update infoBox with new Recent Events line + // Need to get lines from committedTrackerData if we haven't already + let lines = []; + if (committedTrackerData.infoBox) { + lines = committedTrackerData.infoBox.split('\n'); + } const updatedLines = lines.filter(line => !line.startsWith('Recent Events:')); if (newRecentEventsLine) { // Add Recent Events line at the end (before any empty lines) @@ -951,6 +934,14 @@ function updateRecentEvent(field, value) { committedTrackerData.infoBox = updatedLines.join('\n'); lastGeneratedData.infoBox = updatedLines.join('\n'); + // Also update the structured data to keep it in sync + // Store only valid events (renderInfoBox will handle showing placeholders for empty slots) + // This prevents renderInfoBox() from using stale structured data + if (!extensionSettings.infoBoxData) { + extensionSettings.infoBoxData = {}; + } + extensionSettings.infoBoxData.recentEvents = validEvents; + // Update the message's swipe data const chat = getContext().chat; if (chat && chat.length > 0) { diff --git a/src/systems/rendering/inventory.js b/src/systems/rendering/inventory.js index a0015d9..ce34993 100644 --- a/src/systems/rendering/inventory.js +++ b/src/systems/rendering/inventory.js @@ -8,7 +8,7 @@ import { getInventoryRenderOptions, restoreFormStates } from '../interaction/inv import { updateInventoryItem } from '../interaction/inventoryEdit.js'; import { parseItems } from '../../utils/itemParser.js'; import { i18n } from '../../core/i18n.js'; -import { itemHasLinkedSkills, navigateToLinkedSkills } from './skills.js'; +import { itemHasLinkedSkills } from './skills.js'; // Type imports /** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */ @@ -443,18 +443,9 @@ function generateInventoryHTML(inventory, options = {}) { collapsedLocations = [] } = options; - // Handle legacy v1 format - convert to v2 for display - let v2Inventory = inventory; - if (typeof inventory === 'string') { - v2Inventory = { - version: 2, - onPerson: inventory, - stored: {}, - assets: 'None' - }; - } - // Ensure v2 structure has all required fields + // Note: Migration functions handle v1โv2 conversion on load, so inventory should always be v2 here + let v2Inventory = inventory; if (!v2Inventory || typeof v2Inventory !== 'object') { v2Inventory = { version: 2, @@ -686,7 +677,6 @@ export function renderInventory() { $inventoryContainer.html(html); - // Restore form states after re-rendering (fixes Bug #1) restoreFormStates(); // Event listener for editing item names (mobile-friendly contenteditable) diff --git a/src/systems/rendering/quests.js b/src/systems/rendering/quests.js index 5632ad8..06c497c 100644 --- a/src/systems/rendering/quests.js +++ b/src/systems/rendering/quests.js @@ -1,10 +1,11 @@ /** * Quests Rendering Module * Handles UI rendering for quests system (main and optional quests) + * Uses the same structure and styling as items/skills */ import { extensionSettings, $questsContainer } from '../../core/state.js'; -import { saveSettings } from '../../core/persistence.js'; +import { saveSettings, saveChatData } from '../../core/persistence.js'; import { i18n } from '../../core/i18n.js'; /** @@ -19,45 +20,22 @@ function escapeHtml(text) { } /** - * Checks if we have structured quests data (v2 format with name + description) - * @returns {boolean} - */ -function hasStructuredQuests() { - const q = extensionSettings.questsV2; - return q && (q.main !== undefined || q.optional !== undefined); -} - -/** - * Gets the main quest (supports both legacy and structured format) + * Gets the main quest (migration handles legacy format conversion) * @returns {{name: string, description: string}|null} */ function getMainQuest() { - if (hasStructuredQuests() && extensionSettings.questsV2.main) { + if (extensionSettings.questsV2?.main) { return extensionSettings.questsV2.main; } - // Legacy format - const title = extensionSettings.quests?.main; - if (title && title !== 'None') { - return { name: title, description: extensionSettings.quests?.mainDescription || '' }; - } return null; } /** - * Gets optional quests (supports both legacy and structured format) + * Gets optional quests (migration handles legacy format conversion) * @returns {Array<{name: string, description: string}>} */ function getOptionalQuests() { - if (hasStructuredQuests() && extensionSettings.questsV2.optional) { - return extensionSettings.questsV2.optional; - } - // Legacy format - const titles = extensionSettings.quests?.optional || []; - const descriptions = extensionSettings.quests?.optionalDescriptions || []; - return titles.map((title, i) => ({ - name: title, - description: descriptions[i] || '' - })); + return extensionSettings.questsV2?.optional || []; } /** @@ -79,94 +57,90 @@ export function renderQuestsSubTabs(activeTab = 'main') { } /** - * Renders the main quest view - * @param {string} mainQuest - Current main quest title (legacy param, ignored if structured) + * Renders the main quest view (matches items/skills structure) * @returns {string} HTML for main quest view */ -export function renderMainQuestView(mainQuest) { - // Use structured data helpers +export function renderMainQuestView() { const quest = getMainQuest(); const hasQuest = quest !== null; const questName = quest?.name || ''; const questDesc = quest?.description || ''; + + // Track if add form is open + const isFormOpen = openAddForms?.main || false; + + let itemsHtml = ''; + if (hasQuest) { + // Render quest as item (list view style, matching items/skills) + itemsHtml = ` +
This test suite validates the JSON format used for structured tracker data.
-Tests cover: JSON extraction, parsing, data structure validation, and schema generation.
-