/** * INTEGRATION EXAMPLE * This file shows how to integrate the Character State Tracking system * into the main RPG Companion extension * * Copy the relevant parts into your index.js or create a new integration module */ // ============================================================================ // STEP 1: Add imports to the top of index.js // ============================================================================ import { getCharacterState, updateCharacterState, setCharacterState, initializeRelationship, getRelationship, updateRelationship } from './src/core/characterState.js'; import { generateCharacterTrackingPrompt, generateSeparateCharacterTrackingPrompt, generateCharacterInitializationPrompt, generateRelationshipAnalysisPrompt, generateCharacterStateSummary } from './src/systems/generation/characterPromptBuilder.js'; import { parseAndApplyCharacterStateUpdate, removeCharacterStateBlock, parseCharacterInitialization, parseRelationshipAnalysis } from './src/systems/generation/characterParser.js'; import { renderCharacterStateOverview, updateCharacterStateDisplay, renderEmotionalState, renderPhysicalCondition, renderRelationships, renderInternalThoughts } from './src/systems/rendering/characterStateRenderer.js'; // ============================================================================ // STEP 2: Add character state container to UI initialization // ============================================================================ async function initUI() { // ... existing UI initialization code ... // Add character state container to the panel const characterStateHtml = `
`; // Append to panel (adjust selector based on your structure) $('#rpg-companion-panel .rpg-panel-content').append(characterStateHtml); // ... rest of UI initialization ... } // ============================================================================ // STEP 3: Hook into message received event // ============================================================================ async function onMessageReceived(data) { if (!extensionSettings.enabled) return; console.log('[Character Tracking] Processing message:', data.mes.substring(0, 100)); try { // Parse and apply character state updates from the LLM response const stateUpdate = parseAndApplyCharacterStateUpdate(data.mes); if (stateUpdate) { console.log('[Character Tracking] State updated successfully'); // Update the UI to reflect new character state updateCharacterStateDisplay(); // Optionally remove the state block from the displayed message // so users don't see the raw tracking data if (extensionSettings.hideStateBlocks) { data.mes = removeCharacterStateBlock(data.mes); } // Save character state to chat metadata for persistence saveCharacterStateToChat(); } } catch (error) { console.error('[Character Tracking] Error processing state update:', error); } // ... existing message received logic ... } // ============================================================================ // STEP 4: Hook into generation started event // ============================================================================ async function onGenerationStarted(data) { if (!extensionSettings.enabled) return; try { // Get current character state summary const stateSummary = generateCharacterStateSummary(); console.log('[Character Tracking] Current state summary:', stateSummary.substring(0, 200)); // Generate character tracking instructions const trackingPrompt = generateCharacterTrackingPrompt(); // Inject into the generation using SillyTavern's extension prompt system // This adds the character state context and tracking instructions to the LLM setExtensionPrompt( 'RPG_CHARACTER_STATE_TRACKING', trackingPrompt, extension_prompt_types.IN_PROMPT, // or AFTER_SCENARIO depending on preference 1000, // position (higher = later in prompt) false, // scan depth extension_prompt_roles.SYSTEM ); console.log('[Character Tracking] Tracking prompt injected'); } catch (error) { console.error('[Character Tracking] Error injecting tracking prompt:', error); } // ... existing generation started logic ... } // ============================================================================ // STEP 5: Chat changed event - load character state // ============================================================================ async function onChatChanged() { if (!extensionSettings.enabled) return; try { // Load character state from chat metadata loadCharacterStateFromChat(); // Render the loaded state updateCharacterStateDisplay(); console.log('[Character Tracking] Character state loaded for new chat'); } catch (error) { console.error('[Character Tracking] Error loading character state:', error); } // ... existing chat changed logic ... } // ============================================================================ // STEP 6: Persistence functions // ============================================================================ /** * 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'); // Optionally initialize from character card // initializeCharacterFromCard(); } } // ============================================================================ // STEP 7: Optional - Initialize character from card // ============================================================================ /** * Initialize character personality traits from their character card * Call this when starting a new chat or when no state exists */ async function initializeCharacterFromCard() { try { console.log('[Character Tracking] Initializing character from card...'); // Generate initialization prompt const prompt = await generateCharacterInitializationPrompt(); // Send to LLM (adjust based on your API setup) const messages = [{ role: 'user', content: prompt }]; const response = await generateRaw(messages, 'openai', false); // or your API // Parse response const traits = parseCharacterInitialization(response); if (traits) { // Apply to character state updateCharacterState({ primaryTraits: traits }); console.log('[Character Tracking] Character initialized with traits:', traits); // Save and update display saveCharacterStateToChat(); updateCharacterStateDisplay(); } } catch (error) { console.error('[Character Tracking] Failed to initialize character:', error); } } // ============================================================================ // STEP 8: Optional - Settings UI additions // ============================================================================ /** * Add character tracking settings to the extension settings panel * Add this to your addExtensionSettings() function */ function addCharacterTrackingSettings() { const settingsHtml = `

Character State Tracking

`; // Append to settings (adjust selector) $('#rpg-extension-settings').append(settingsHtml); // Set up event listeners $('#rpg-enable-character-tracking').prop('checked', extensionSettings.enableCharacterTracking || false) .on('change', function() { extensionSettings.enableCharacterTracking = $(this).prop('checked'); saveSettings(); }); $('#rpg-hide-state-blocks').prop('checked', extensionSettings.hideStateBlocks || true) .on('change', function() { extensionSettings.hideStateBlocks = $(this).prop('checked'); saveSettings(); }); $('#rpg-auto-init-character').prop('checked', extensionSettings.autoInitCharacter || false) .on('change', function() { extensionSettings.autoInitCharacter = $(this).prop('checked'); saveSettings(); }); $('#rpg-init-character-now').on('click', function() { initializeCharacterFromCard(); }); $('#rpg-reset-character-state').on('click', function() { if (confirm('Are you sure you want to reset the character state? This cannot be undone.')) { resetCharacterState(); saveCharacterStateToChat(); updateCharacterStateDisplay(); toastr.success('Character state reset'); } }); } // ============================================================================ // STEP 9: Register events in main initialization // ============================================================================ jQuery(async () => { // ... existing initialization ... // Register character tracking events registerAllEvents({ [event_types.MESSAGE_RECEIVED]: onMessageReceived, [event_types.GENERATION_STARTED]: onGenerationStarted, [event_types.CHAT_CHANGED]: onChatChanged, // ... other events ... }); // Initialize character state display if (extensionSettings.enableCharacterTracking) { updateCharacterStateDisplay(); } console.log('[Character Tracking] ✅ Character tracking system initialized'); }); // ============================================================================ // USAGE EXAMPLES // ============================================================================ // Example 1: Get current character emotional state function getCurrentMood() { const charState = getCharacterState(); const emotions = charState.secondaryStates; // Find dominant emotion let dominantEmotion = 'neutral'; let highestValue = 50; for (const [emotion, value] of Object.entries(emotions)) { if (value > highestValue) { dominantEmotion = emotion; highestValue = value; } } return { emotion: dominantEmotion, intensity: highestValue }; } // Example 2: Check relationship with user function getRelationshipWithUser() { const userName = getContext().name1; const relationship = getRelationship(userName); return { trust: relationship.trust, love: relationship.love, attraction: relationship.attraction, thoughts: relationship.currentThoughts, status: relationship.relationshipStatus }; } // Example 3: Manually update character state function makeCharacterHappy(amount, reason) { const charState = getCharacterState(); const currentHappy = charState.secondaryStates.happy || 0; const newHappy = Math.min(100, currentHappy + amount); updateCharacterState({ secondaryStates: { ...charState.secondaryStates, happy: newHappy } }); console.log(`[Character Tracking] Happiness increased by ${amount}: ${reason}`); saveCharacterStateToChat(); updateCharacterStateDisplay(); } // Example 4: Check if character is in specific emotional state function isCharacterEmotionallyAvailable() { const charState = getCharacterState(); const states = charState.secondaryStates; // Character is emotionally available if: // - Not too stressed or anxious // - Not too sad or angry // - Has some positive emotions const stressed = states.stressed || 0; const anxious = states.anxious || 0; const sad = states.sad || 0; const angry = states.angry || 0; const happy = states.happy || 0; const negativeEmotions = stressed + anxious + sad + angry; const isAvailable = negativeEmotions < 150 && happy > 20; return isAvailable; } // ============================================================================ // ADVANCED: Separate mode for character tracking // ============================================================================ /** * If you want to use SEPARATE mode (track character state in a separate API call) * instead of TOGETHER mode (track in same generation) */ async function updateCharacterStatesSeparately() { try { // Generate separate tracking prompt with chat history const messages = await generateSeparateCharacterTrackingPrompt(); // Call LLM with tracking-specific preset const response = await generateRaw(messages, 'openai', false); // Parse and apply updates const stateUpdate = parseAndApplyCharacterStateUpdate(response); if (stateUpdate) { saveCharacterStateToChat(); updateCharacterStateDisplay(); } } catch (error) { console.error('[Character Tracking] Separate update failed:', error); } } // Call this after each message if using separate mode // onMessageReceived -> updateCharacterStatesSeparately()