diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md new file mode 100644 index 0000000..8f634fb --- /dev/null +++ b/IMPLEMENTATION_COMPLETE.md @@ -0,0 +1,146 @@ +# Tracker Customization Feature - Implementation Complete β + +## Summary +Implemented a comprehensive tracker customization system allowing users to fully customize what trackers display, track, and format. + +## What Was Implemented + +### 1. Settings Schema (state.js) +- Added `trackerConfig` with three sections: + - `userStats`: customStats array, RPG attributes toggle, status section config, skills section config + - `infoBox`: widget toggles (date/weather/temp/time/location/events), date format, temperature unit + - `presentCharacters`: customFields array, character stats config + +### 2. Edit Trackers Modal (trackerEditor.js - 600+ lines) +- Complete UI with 3 tabs: User Stats | Info Box | Present Characters +- **User Stats Tab:** + - Add/remove/rename custom stats + - Toggle RPG attributes (STR/DEX/CON/INT/WIS/CHA/LVL) + - Configure status section (enable/disable, mood emoji, custom fields) + - Configure skills section (enable/disable, custom fields) +- **Info Box Tab:** + - Toggle individual widgets (date, weather, temperature, time, location, recent events) + - Select date format (dd/mm/yy, mm/dd/yy, yyyy-mm-dd) + - Choose temperature unit (Celsius/Fahrenheit) +- **Present Characters Tab:** + - Add/remove/rename custom character fields + - Reorder fields with up/down buttons + - Select character stats to track +- Save/Cancel/Reset functionality + +### 3. Migration System (persistence.js) +- `migrateToTrackerConfig()` converts old `statNames` to new `customStats` format +- Auto-runs on settings load +- Ensures backward compatibility with existing user data + +### 4. Dynamic Rendering + +**User Stats (userStats.js):** +- `renderUserStats()` loops through enabled customStats only +- Conditionally renders: + - RPG attributes section + - Status section with optional mood emoji + - Skills section +- `buildUserStatsText()` generates dynamic tracker text from config + +**Info Box (infoBox.js):** +- `renderInfoBox()` conditionally renders widgets based on toggles +- Applies date format conversions +- Converts temperature between Celsius and Fahrenheit +- Maintains responsive CSS grid layout + +**Present Characters (thoughts.js):** +- `renderThoughts()` parses custom fields dynamically +- Renders character cards with variable field count +- Relationship badge conditional on "Relationship" field existence +- Generic `.rpg-character-field` class for all custom fields + +### 5. Dynamic Prompt Generation (promptBuilder.js) +- `generateTrackerInstructions()` builds prompts from `trackerConfig`: + - User Stats format from enabled customStats array + - RPG attributes line if enabled + - Status/Skills sections if enabled + - Info Box format with only enabled widgets + - Present Characters format with custom fields + +### 6. Flexible Parsing (parser.js) +- `parseUserStats()` updated to: + - Parse custom stat names dynamically using regex + - Parse RPG attributes if enabled + - Parse status section with optional mood emoji + - Parse skills section if enabled + - Store stats using normalized IDs + +### 7. UI Integration +- Added "Edit Trackers" button next to Settings button +- Modal HTML in template.html with tab navigation +- Complete CSS styling for all editor UI elements +- Mobile-responsive design + +### 8. CSS Updates +- Added `.rpg-skills-section` styling +- Added `.rpg-character-field` generic styling +- Updated `.rpg-settings-buttons-row` for two-button layout +- 300+ lines of tracker editor modal CSS +- Flexbox layouts auto-handle variable content counts + +## Files Modified + +**Core:** +- `src/core/state.js` - Added trackerConfig schema +- `src/core/persistence.js` - Added migration function + +**UI:** +- `src/systems/ui/trackerEditor.js` - NEW FILE (600+ lines) +- `template.html` - Edit Trackers button, modal HTML +- `style.css` - Editor styling, new sections CSS + +**Rendering:** +- `src/systems/rendering/userStats.js` - Dynamic rendering +- `src/systems/rendering/infoBox.js` - Widget toggles, format conversion +- `src/systems/rendering/thoughts.js` - Custom fields rendering + +**Generation:** +- `src/systems/generation/promptBuilder.js` - Dynamic instructions +- `src/systems/generation/parser.js` - Flexible parsing + +**Integration:** +- `index.js` - Import and initialize tracker editor + +## Testing Checklist + +- [ ] Open Edit Trackers modal +- [ ] User Stats Tab: + - [ ] Add/remove/rename stats + - [ ] Toggle RPG attributes + - [ ] Enable/disable status section + - [ ] Add custom status fields + - [ ] Enable/disable skills section + - [ ] Add custom skill fields +- [ ] Info Box Tab: + - [ ] Toggle each widget on/off + - [ ] Change date format + - [ ] Change temperature unit +- [ ] Present Characters Tab: + - [ ] Add/remove/rename fields + - [ ] Reorder fields + - [ ] Select character stats +- [ ] Save and verify: + - [ ] Panels update with new configuration + - [ ] AI receives correct prompt format + - [ ] AI response parses correctly + - [ ] Manual edits work + - [ ] Settings persist after refresh + +## Known Features + +1. **Backward Compatible**: Old settings automatically migrate to new format +2. **Fully Dynamic**: All rendering adapts to user configuration +3. **Format Conversion**: Automatic date format and temperature unit conversion +4. **Flexible Parsing**: Handles variable stat names and field counts +5. **Mobile-Friendly**: All UI elements responsive +6. **Validation**: Prevents duplicate stat/field names + +## Ready for Testing! + +All phases complete. Zero compilation errors. Ready to test in SillyTavern! π diff --git a/README.md b/README.md index 25e5cb4..b03d301 100644 --- a/README.md +++ b/README.md @@ -27,30 +27,37 @@ An immersive RPG extension for browsers that tracks character stats, scene infor ### Core Functionality -- **π User Stats Tracker**: Visual progress bars for health, sustenance, energy, hygiene, arousal, mood, and conditions -- **π Info Box Dashboard**: Beautiful widgets displaying date, weather, temperature, time, and location of the current scene -- **π Character Thoughts**: Floating thought bubbles showing AI characters' internal monologue +- **π User Stats Tracker**: Fully customizable stats with visual progress bars, custom status fields, skills section, and dynamic inventory management +- **π Info Box Dashboard**: Configurable widgets for date, weather, temperature, time, location, and recent events +- **π Present Characters Panel**: Track multiple characters with custom fields, relationship badges, character-specific stats, and internal thoughts +- **π Floating Thought Bubbles**: Optional thought bubbles positioned next to character avatars in chat - **π² Classic RPG Stats**: STR, DEX, CON, INT, WIS, CHA attributes with dice roll support -- **π¦ Inventory System**: Track items your character is carrying +- **π¦ Advanced Inventory System**: Multi-location storage (On Person, Stored locations, Assets) with v2 format +- **π― Character Stats**: Track health, energy, or any custom stats for each present character with color interpolation - **π Immersive HTML**: Enhance the immersion by including creative HTML/CSS/JS elements in your roleplay - **β‘οΈ Plot Progression**: Progress the plot with randomized events or natural progression with a click of a button - **π¨ Multiple Themes**: Cyberpunk, Fantasy, Minimal, Dark, Light, and Custom themes -- **βοΈ Live Editing**: Edit stats, thoughts, weather, and more directly in the panels +- **βοΈ Live Editing**: Edit all tracker fields directly in the panels with auto-save - **πΎ Per-Swipe Data Storage**: Each swipe preserves its own tracker data +- **ποΈ Tracker Configuration**: Customize every aspect of trackers - add/remove stats, fields, widgets, and more ### Smart Features - **π Swipe Detection**: Automatically handles swipes and maintains correct tracker context - **π Context-Aware**: Weather, stats, and character states naturally influence the narrative -- **π Multiple Characters**: Tracks thoughts and relationships for all present characters +- **π Multiple Characters**: Tracks thoughts, relationships, and stats for all present characters - **π Thought Bubbles in Chat**: Optional floating thought bubbles positioned next to character avatars - **π Customizable Colors**: Create your own theme with custom color schemes -- **π± Mobile Support**: Works on mobile and tablet devices +- **π± Mobile Support**: Responsive design with horizontal scrolling for stats +- **π§ Advanced Configuration**: Add custom stats, fields, and widgets through Tracker Settings +- **π¨ Color Interpolation**: Stats smoothly transition from low to high colors based on values +- **π¬ Multi-line Format**: Clean, structured format for AI generation and parsing +- **π§Ή Auto-cleanup**: Automatically removes placeholder brackets from AI responses ### To-Do 1. Allow users to use a different model for the separate trackers generation -2. Make all trackers and fields customizable +2. ~~Make all trackers and fields customizable~~ β Done! 3. ~~Kill myself~~ ## βοΈ Settings @@ -140,11 +147,31 @@ Cons: You can edit most fields by clicking on them: -- **Stats**: Click on percentage values, mood emoji, conditions, or inventory -- **Info Box**: Click on date fields, weather, temperature, time, or location -- **Character Thoughts**: Click on emoji, name, traits, relationship, or thoughts +- **User Stats**: Click on stat percentages, mood emoji, status fields, skills, inventory items, or quests +- **Info Box**: Click on date fields, weather, temperature, time, location, or recent events +- **Present Characters**: Click on character emoji, name, custom fields, relationship badge, or stats +- **Thought Bubbles**: Click on thought text to edit (bubble will refresh to maintain positioning) -Note: When editing character thoughts in the floating bubble, the bubble will refresh to maintain proper positioning. +### Tracker Configuration + +Access comprehensive customization through the Tracker Settings button: + +**User Stats Configuration:** +- Add/remove custom stats with unique names +- Configure Status section (mood emoji + custom fields) +- Configure Skills section with custom skill fields +- Toggle RPG attributes display + +**Info Box Configuration:** +- Enable/disable individual widgets (Date, Weather, Temperature, Time, Location, Recent Events) +- Choose temperature unit (Celsius/Fahrenheit) + +**Present Characters Configuration:** +- Add custom character fields (appearance, action, demeanor, etc.) +- Configure relationship status options +- Enable character-specific stats tracking +- Customize thought bubble label and description +- All fields are dynamically generated in prompts ### Swipe Support diff --git a/TRACKER_CUSTOMIZATION_PLAN.md b/TRACKER_CUSTOMIZATION_PLAN.md new file mode 100644 index 0000000..5384492 --- /dev/null +++ b/TRACKER_CUSTOMIZATION_PLAN.md @@ -0,0 +1,142 @@ +# Tracker Customization Implementation Plan + +## Overview +Allow users to fully customize what trackers display, including custom fields, toggles, and formats. + +## Settings Schema Design + +```javascript +extensionSettings.trackerConfig = { + userStats: { + // Array of custom stats (allows add/remove/rename) + customStats: [ + { id: 'health', name: 'Health', enabled: true, value: 100 }, + { id: 'satiety', name: 'Satiety', enabled: true, value: 100 }, + { id: 'energy', name: 'Energy', enabled: true, value: 100 }, + { id: 'hygiene', name: 'Hygiene', enabled: true, value: 100 }, + { id: 'arousal', name: 'Arousal', enabled: true, value: 0 } + ], + showRPGAttributes: true, + statusSection: { + enabled: true, + showMoodEmoji: true, + customFields: ['Conditions'] + }, + skillsSection: { + enabled: false, + label: 'Skills' + } + }, + + infoBox: { + widgets: { + date: { enabled: true, format: 'Weekday, Month, Year' }, + weather: { enabled: true }, + temperature: { enabled: true, unit: 'C' }, + time: { enabled: true }, + location: { enabled: true }, + recentEvents: { enabled: true } + } + }, + + presentCharacters: { + showEmoji: true, + showName: true, + customFields: [ + { id: 'physicalState', label: 'Physical State', enabled: true, placeholder: 'Visible traits' }, + { id: 'demeanor', label: 'Demeanor Cue', enabled: true, placeholder: 'Observable demeanor' }, + { id: 'relationship', label: 'Relationship', enabled: true, type: 'relationship' }, + { id: 'internalMonologue', label: 'Internal Monologue', enabled: true, placeholder: 'First person thoughts' } + ], + characterStats: { + enabled: false, + stats: [] + } + } +} +``` + +## Implementation Phases + +### Phase 1: State Management β +- Update state.js with trackerConfig schema +- Add migration logic for existing users +- Ensure persistence in loadSettings/saveSettings + +### Phase 2: Edit Trackers Modal UI +- Create src/systems/ui/trackerEditor.js +- Add "Edit Trackers" button in template.html +- Build tabbed modal interface with save/cancel/reset + +### Phase 3: User Stats Customization +- Tab UI for managing custom stats array +- RPG attributes toggle +- Status section configuration +- Skills field configuration + +### Phase 4: Info Box Customization +- Tab UI for widget toggles +- Date format selector +- Temperature unit toggle + +### Phase 5: Present Characters Customization +- Tab UI for custom fields management +- Character stats configuration +- Field ordering and custom additions + +### Phase 6: Dynamic Rendering +- Update renderUserStats() for variable stats +- Update renderInfoBox() for conditional widgets +- Update renderThoughts() for custom fields + +### Phase 7: Dynamic Prompts +- Update generateTrackerInstructions() +- Build prompts from trackerConfig +- Handle variable formats + +### Phase 8: Flexible Parsing +- Update parser.js for variable formats +- Handle custom stat names +- Parse custom character fields + +### Phase 9: Responsive CSS +- Support variable stat counts +- Conditional widget visibility +- Mobile-friendly layouts for all configs + +### Phase 10: Testing +- Test minimal configurations +- Test maximal configurations +- Test custom field names +- Verify mobile responsiveness + +## Files to Modify + +1. **State & Persistence** + - src/core/state.js + - src/core/persistence.js + - src/utils/migration.js + +2. **UI Components** + - template.html (add button) + - src/systems/ui/trackerEditor.js (NEW) + - src/systems/ui/modals.js (register new modal) + +3. **Rendering** + - src/systems/rendering/userStats.js + - src/systems/rendering/infoBox.js + - src/systems/rendering/thoughts.js + +4. **Generation** + - src/systems/generation/promptBuilder.js + - src/systems/generation/parser.js + +5. **Styling** + - style.css + +## Critical Success Factors +- Backward compatibility via migration +- Mobile-first responsive design +- Flexible parsing handles variable formats +- CSS adapts without breaking existing layouts +- Settings persist correctly across sessions diff --git a/index.js b/index.js index 0c4e2e1..f311e44 100644 --- a/index.js +++ b/index.js @@ -86,6 +86,9 @@ import { addDiceQuickReply, getSettingsModal } from './src/systems/ui/modals.js'; +import { + initTrackerEditor +} from './src/systems/ui/trackerEditor.js'; import { togglePlotButtons, updateCollapseToggleIcon, @@ -435,6 +438,7 @@ async function initUI() { setupDiceRoller(); setupClassicStatsButtons(); setupSettingsPopup(); + initTrackerEditor(); addDiceQuickReply(); setupPlotButtons(sendPlotProgression); setupMobileKeyboardHandling(); diff --git a/src/core/persistence.js b/src/core/persistence.js index 8fd7897..e5dabca 100644 --- a/src/core/persistence.js +++ b/src/core/persistence.js @@ -95,6 +95,13 @@ export function loadSettings() { saveSettings(); // Persist migrated inventory } } + + // Migrate to trackerConfig if it doesn't exist + if (!extensionSettings.trackerConfig) { + console.log('[RPG Companion] Migrating to trackerConfig format'); + migrateToTrackerConfig(); + saveSettings(); // Persist migration + } } catch (error) { console.error('[RPG Companion] Error loading settings:', error); console.error('[RPG Companion] Error details:', error.message, error.stack); @@ -345,3 +352,137 @@ function validateInventoryStructure(inventory, source) { } } } + +/** + * Migrates old settings format to new trackerConfig format + * Converts statNames to customStats array and sets up default config + */ +function migrateToTrackerConfig() { + // Initialize trackerConfig if it doesn't exist + if (!extensionSettings.trackerConfig) { + extensionSettings.trackerConfig = { + userStats: { + customStats: [], + showRPGAttributes: true, + statusSection: { + enabled: true, + showMoodEmoji: true, + customFields: ['Conditions'] + }, + skillsSection: { + enabled: false, + label: 'Skills' + } + }, + infoBox: { + widgets: { + date: { enabled: true, format: 'Weekday, Month, Year' }, + weather: { enabled: true }, + temperature: { enabled: true, unit: 'C' }, + time: { enabled: true }, + location: { enabled: true }, + recentEvents: { enabled: true } + } + }, + presentCharacters: { + showEmoji: true, + showName: true, + customFields: [ + { id: 'physicalState', label: 'Physical State', enabled: true, placeholder: 'Visible Physical State (up to three traits)' }, + { id: 'demeanor', label: 'Demeanor Cue', enabled: true, placeholder: 'Observable Demeanor Cue (one trait)' }, + { id: 'relationship', label: 'Relationship', enabled: true, type: 'relationship', placeholder: 'Enemy/Neutral/Friend/Lover' }, + { id: 'internalMonologue', label: 'Internal Monologue', enabled: true, placeholder: 'Internal Monologue (in first person POV, up to three sentences long)' } + ], + characterStats: { + enabled: false, + stats: [] + } + } + }; + } + + // Migrate old statNames to customStats if statNames exists + if (extensionSettings.statNames && extensionSettings.trackerConfig.userStats.customStats.length === 0) { + const statOrder = ['health', 'satiety', 'energy', 'hygiene', 'arousal']; + extensionSettings.trackerConfig.userStats.customStats = statOrder.map(id => ({ + id: id, + name: extensionSettings.statNames[id] || id.charAt(0).toUpperCase() + id.slice(1), + enabled: true + })); + console.log('[RPG Companion] Migrated statNames to customStats array'); + } + + // Ensure all stats have corresponding values in userStats + if (extensionSettings.userStats) { + for (const stat of extensionSettings.trackerConfig.userStats.customStats) { + if (extensionSettings.userStats[stat.id] === undefined) { + extensionSettings.userStats[stat.id] = stat.id === 'arousal' ? 0 : 100; + } + } + } + + // Migrate old presentCharacters structure to new format + if (extensionSettings.trackerConfig.presentCharacters) { + const pc = extensionSettings.trackerConfig.presentCharacters; + + // Check if using old flat customFields structure (has 'label' or 'placeholder' keys) + if (pc.customFields && pc.customFields.length > 0) { + const hasOldFormat = pc.customFields.some(f => f.label || f.placeholder || f.type === 'relationship'); + + if (hasOldFormat) { + console.log('[RPG Companion] Migrating Present Characters to new structure'); + + // Extract relationship fields from old customFields + const relationshipFields = ['Lover', 'Friend', 'Ally', 'Enemy', 'Neutral']; + + // Extract non-relationship fields and convert to new format + const newCustomFields = pc.customFields + .filter(f => f.type !== 'relationship' && f.id !== 'internalMonologue') + .map(f => ({ + id: f.id, + name: f.label || f.name || 'Field', + enabled: f.enabled !== false, + description: f.placeholder || f.description || '' + })); + + // Extract thoughts config from old Internal Monologue field + const thoughtsField = pc.customFields.find(f => f.id === 'internalMonologue'); + const thoughts = { + enabled: thoughtsField ? (thoughtsField.enabled !== false) : true, + name: 'Thoughts', + description: thoughtsField?.placeholder || 'Internal monologue (in first person POV, up to three sentences long)' + }; + + // Update to new structure + pc.relationshipFields = relationshipFields; + pc.customFields = newCustomFields; + pc.thoughts = thoughts; + + console.log('[RPG Companion] Present Characters migration complete'); + saveSettings(); // Persist the migration + } + } + + // Ensure new structure exists even if migration wasn't needed + if (!pc.relationshipFields) { + pc.relationshipFields = ['Lover', 'Friend', 'Ally', 'Enemy', 'Neutral']; + } + if (!pc.relationshipEmojis) { + // Create default emoji mapping from relationshipFields + pc.relationshipEmojis = { + 'Lover': 'β€οΈ', + 'Friend': 'β', + 'Ally': 'π€', + 'Enemy': 'βοΈ', + 'Neutral': 'βοΈ' + }; + } + if (!pc.thoughts) { + pc.thoughts = { + enabled: true, + name: 'Thoughts', + description: 'Internal monologue (in first person POV, up to three sentences long)' + }; + } + } +} diff --git a/src/core/state.js b/src/core/state.js index 5164554..34e95d5 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -60,6 +60,76 @@ export let extensionSettings = { hygiene: 'Hygiene', arousal: 'Arousal' }, + // Tracker customization configuration + trackerConfig: { + userStats: { + // Array of custom stats (allows add/remove/rename) + customStats: [ + { id: 'health', name: 'Health', enabled: true }, + { id: 'satiety', name: 'Satiety', enabled: true }, + { id: 'energy', name: 'Energy', enabled: true }, + { id: 'hygiene', name: 'Hygiene', enabled: true }, + { id: 'arousal', name: 'Arousal', enabled: true } + ], + // RPG Attributes toggle + showRPGAttributes: true, + // Status section config + statusSection: { + enabled: true, + showMoodEmoji: true, + customFields: ['Conditions'] // User can edit what to track + }, + // Optional skills field + skillsSection: { + enabled: false, + label: 'Skills' // User-editable + } + }, + infoBox: { + widgets: { + date: { enabled: true, format: 'Weekday, Month, Year' }, // Format options in UI + weather: { enabled: true }, + temperature: { enabled: true, unit: 'C' }, // 'C' or 'F' + time: { enabled: true }, + location: { enabled: true }, + recentEvents: { enabled: true } + } + }, + presentCharacters: { + // Fixed fields (always shown) + showEmoji: true, + showName: true, + // Relationship fields (shown after name, separated by /) + relationshipFields: ['Lover', 'Friend', 'Ally', 'Enemy', 'Neutral'], + // Relationship to emoji mapping (shown on character portraits) + relationshipEmojis: { + 'Lover': 'β€οΈ', + 'Friend': 'β', + 'Ally': 'π€', + 'Enemy': 'βοΈ', + 'Neutral': 'βοΈ' + }, + // Custom fields (appearance, demeanor, etc. - shown after relationship, separated by |) + customFields: [ + { id: 'appearance', name: 'Appearance', enabled: true, description: 'Visible physical appearance (clothing, hair, notable features)' }, + { id: 'demeanor', name: 'Demeanor', enabled: true, description: 'Observable demeanor or emotional state' } + ], + // Thoughts configuration (separate line) + thoughts: { + enabled: true, + name: 'Thoughts', + description: 'Internal monologue (in first person POV, up to three sentences long)' + }, + // Character stats toggle (optional feature) + characterStats: { + enabled: false, + customStats: [ + { id: 'health', name: 'Health', enabled: true }, + { id: 'arousal', name: 'Arousal', enabled: true } + ] + } + } + }, quests: { main: "None", // Current main quest title optional: [] // Array of optional quest titles diff --git a/src/systems/generation/injector.js b/src/systems/generation/injector.js index c98d447..e4d8955 100644 --- a/src/systems/generation/injector.js +++ b/src/systems/generation/injector.js @@ -118,6 +118,9 @@ export function onGenerationStarted(type, data) { // Don't include HTML prompt in instructions - inject it separately to avoid duplication on swipes const instructions = generateTrackerInstructions(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'); diff --git a/src/systems/generation/parser.js b/src/systems/generation/parser.js index 2472ffc..3a6d020 100644 --- a/src/systems/generation/parser.js +++ b/src/systems/generation/parser.js @@ -47,10 +47,11 @@ function separateEmojiFromText(str) { } /** - * Helper to strip enclosing brackets from text - * Removes [], {}, and () from the entire text if it's wrapped - * @param {string} text - Text that may be wrapped in brackets - * @returns {string} Text with brackets removed + * Helper to strip enclosing brackets from text and remove placeholder brackets + * Removes [], {}, and () from the entire text if it's wrapped, plus removes + * placeholder content like [Location], [Mood Emoji], etc. + * @param {string} text - Text that may contain brackets + * @returns {string} Text with brackets and placeholders removed */ function stripBrackets(text) { if (!text) return text; @@ -68,7 +69,58 @@ function stripBrackets(text) { text = text.substring(1, text.length - 1).trim(); } - return text; + // Remove placeholder text patterns like [Location], [Mood Emoji], [Name], etc. + // Pattern matches: [anything with letters/spaces inside] + // This preserves actual content while removing template placeholders + const placeholderPattern = /\[([A-Za-z\s\/]+)\]/g; + + // Check if a bracketed text looks like a placeholder vs real content + const isPlaceholder = (match, content) => { + // Common placeholder words to detect + const placeholderKeywords = [ + 'location', 'mood', 'emoji', 'name', 'description', 'placeholder', + 'time', 'date', 'weather', 'temperature', 'action', 'appearance', + 'skill', 'quest', 'item', 'character', 'field', 'value', 'details', + 'relationship', 'thoughts', 'stat', 'status', 'lover', 'friend', + 'enemy', 'neutral', 'weekday', 'month', 'year', 'forecast' + ]; + + const lowerContent = content.toLowerCase().trim(); + + // If it contains common placeholder keywords, it's likely a placeholder + if (placeholderKeywords.some(keyword => lowerContent.includes(keyword))) { + return true; + } + + // If it's a short generic phrase (1-3 words) with only letters/spaces, might be placeholder + const wordCount = content.trim().split(/\s+/).length; + if (wordCount <= 3 && /^[A-Za-z\s\/]+$/.test(content)) { + return true; + } + + return false; + }; + + // Replace placeholders with empty string, keep real content + text = text.replace(placeholderPattern, (match, content) => { + if (isPlaceholder(match, content)) { + return ''; // Remove placeholder + } + return match; // Keep real bracketed content + }); + + // Clean up any resulting empty labels (e.g., "Status: " with nothing after) + text = text.replace(/^([A-Za-z\s]+):\s*$/gm, ''); // Remove lines that are just "Label: " with nothing + text = text.replace(/^([A-Za-z\s]+):\s*,/gm, '$1:'); // Fix "Label: ," patterns + text = text.replace(/:\s*\|/g, ':'); // Fix ": |" patterns + text = text.replace(/\|\s*\|/g, '|'); // Fix "| |" patterns (double pipes from removed content) + text = text.replace(/\|\s*$/gm, ''); // Remove trailing pipes at end of lines + + // Clean up multiple spaces and empty lines + text = text.replace(/\s{2,}/g, ' '); // Multiple spaces to single space + text = text.replace(/^\s*\n/gm, ''); // Remove empty lines + + return text.trim(); } /** @@ -173,8 +225,8 @@ export function parseResponse(responseText) { content.match(/Present Characters\s*\n\s*---/i) || content.match(/Characters\s*\n\s*---/i) || content.match(/Character Thoughts\s*\n\s*---/i) || - // Fallback: look for table-like structure with emoji and pipes - (content.includes(" | ") && (content.includes("Thoughts") || content.includes("π"))); + // Fallback: look for new multi-line format patterns + (content.match(/^-\s+\w+/m) && content.match(/Details:/i)); if (isStats && !result.userStats) { result.userStats = stripBrackets(content); @@ -193,7 +245,7 @@ export function parseResponse(responseText) { debugLog('[RPG Parser] - Has "Info Box\\n---"?', !!content.match(/Info Box\s*\n\s*---/i)); debugLog('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i))); debugLog('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i)); - debugLog('[RPG Parser] - Has " | " + thoughts?', !!(content.includes(" | ") && (content.includes("Thoughts") || content.includes("π")))); + debugLog('[RPG Parser] - Has new format ("- Name" + "Details:")?', !!(content.match(/^-\s+\w+/m) && content.match(/Details:/i))); } } } @@ -219,89 +271,93 @@ export function parseUserStats(statsText) { debugLog('[RPG Parser] Stats text preview:', statsText.substring(0, 200)); try { - // Extract percentages and mood/conditions - const healthMatch = statsText.match(/Health:\s*(\d+)%/); - const satietyMatch = statsText.match(/Satiety:\s*(\d+)%/); - const energyMatch = statsText.match(/Energy:\s*(\d+)%/); - const hygieneMatch = statsText.match(/Hygiene:\s*(\d+)%/); - const arousalMatch = statsText.match(/Arousal:\s*(\d+)%/); + // Get custom stat configuration + const trackerConfig = extensionSettings.trackerConfig; + const customStats = trackerConfig?.userStats?.customStats || []; + const enabledStats = customStats.filter(s => s && s.enabled && s.name && s.id); - debugLog('[RPG Parser] Stat matches:', { - health: healthMatch ? healthMatch[1] : 'NOT FOUND', - satiety: satietyMatch ? satietyMatch[1] : 'NOT FOUND', - energy: energyMatch ? energyMatch[1] : 'NOT FOUND', - hygiene: hygieneMatch ? hygieneMatch[1] : 'NOT FOUND', - arousal: arousalMatch ? arousalMatch[1] : 'NOT FOUND' - }); + debugLog('[RPG Parser] Enabled custom stats:', enabledStats.map(s => s.name)); - // Match mood/status with multiple format variations - // Format 1: Status: [Emoji, Conditions] - // Format 2: Status: [Emoji], [Conditions] - // Format 3: [Emoji]: [Conditions] (legacy) - // Format 4: Mood: [Emoji] - [Conditions] - // Format 5: Status: [Emoji Conditions] (no separator - FIXED) - let moodMatch = null; - - // Try new format: Status: emoji, conditions OR Status: emojiConditions - const statusMatch = statsText.match(/Status:\s*(.+)/i); - if (statusMatch) { - const statusContent = statusMatch[1].trim(); - const { emoji, text } = separateEmojiFromText(statusContent); - if (emoji && text) { - moodMatch = [null, emoji, text]; - } else if (statusContent.includes(',')) { - // Fallback to comma split if emoji detection failed - const parts = statusContent.split(',').map(p => p.trim()); - moodMatch = [null, parts[0], parts.slice(1).join(', ')]; + // Dynamically parse custom stats + for (const stat of enabledStats) { + const statRegex = new RegExp(`${stat.name}:\\s*(\\d+)%`, 'i'); + const match = statsText.match(statRegex); + if (match) { + // Store using the stat ID (lowercase normalized name) + const statId = stat.id; + extensionSettings.userStats[statId] = parseInt(match[1]); + debugLog(`[RPG Parser] Parsed ${stat.name}:`, match[1]); + } else { + debugLog(`[RPG Parser] ${stat.name} NOT FOUND`); } } - // Try alternative: Mood: emoji, conditions OR Mood: emojiConditions - if (!moodMatch) { - const moodAltMatch = statsText.match(/Mood:\s*(.+)/i); - if (moodAltMatch) { - const moodContent = moodAltMatch[1].trim(); - const { emoji, text } = separateEmojiFromText(moodContent); - if (emoji && text) { - moodMatch = [null, emoji, text]; - } else if (moodContent.includes(',') || moodContent.includes('-')) { - // Fallback to comma/dash split if emoji detection failed - const parts = moodContent.split(/[,\-]/).map(p => p.trim()); - moodMatch = [null, parts[0], parts.slice(1).join(', ')]; + // Parse RPG attributes if enabled + if (trackerConfig?.userStats?.showRPGAttributes) { + const strMatch = statsText.match(/STR:\s*(\d+)/i); + const dexMatch = statsText.match(/DEX:\s*(\d+)/i); + const conMatch = statsText.match(/CON:\s*(\d+)/i); + const intMatch = statsText.match(/INT:\s*(\d+)/i); + const wisMatch = statsText.match(/WIS:\s*(\d+)/i); + const chaMatch = statsText.match(/CHA:\s*(\d+)/i); + const lvlMatch = statsText.match(/LVL:\s*(\d+)/i); + + if (strMatch) extensionSettings.classicStats.str = parseInt(strMatch[1]); + if (dexMatch) extensionSettings.classicStats.dex = parseInt(dexMatch[1]); + if (conMatch) extensionSettings.classicStats.con = parseInt(conMatch[1]); + if (intMatch) extensionSettings.classicStats.int = parseInt(intMatch[1]); + if (wisMatch) extensionSettings.classicStats.wis = parseInt(wisMatch[1]); + if (chaMatch) extensionSettings.classicStats.cha = parseInt(chaMatch[1]); + if (lvlMatch) extensionSettings.level = parseInt(lvlMatch[1]); + + debugLog('[RPG Parser] RPG Attributes parsed'); + } + + // Match status section if enabled + const statusConfig = trackerConfig?.userStats?.statusSection; + if (statusConfig?.enabled) { + let moodMatch = null; + + // Try Status: format + const statusMatch = statsText.match(/Status:\s*(.+)/i); + if (statusMatch) { + const statusContent = statusMatch[1].trim(); + + // Extract mood emoji if enabled + if (statusConfig.showMoodEmoji) { + const { emoji, text } = separateEmojiFromText(statusContent); + if (emoji) { + extensionSettings.userStats.mood = emoji; + // Remaining text contains custom status fields + if (text) { + extensionSettings.userStats.conditions = text; + } + moodMatch = true; + } + } else { + // No mood emoji, whole status is conditions + extensionSettings.userStats.conditions = statusContent; + moodMatch = true; } } + + debugLog('[RPG Parser] Status match:', { + found: !!moodMatch, + mood: extensionSettings.userStats.mood, + conditions: extensionSettings.userStats.conditions + }); } - // Legacy format fallback: [Emoji]: [Conditions] - if (!moodMatch) { - const lines = statsText.split('\n'); - for (let i = 0; i < lines.length; i++) { - const line = lines[i].trim(); - // Skip lines with percentages or known keywords - if (line.includes('%') || - line.toLowerCase().startsWith('inventory:') || - line.toLowerCase().startsWith('status:') || - line.toLowerCase().startsWith('health:') || - line.toLowerCase().startsWith('energy:') || - line.toLowerCase().startsWith('satiety:') || - line.toLowerCase().startsWith('hygiene:') || - line.toLowerCase().startsWith('arousal:')) continue; - - // Match emoji/mood followed by colon and conditions - const match = line.match(/^(.+?):\s*(.+)$/); - if (match && match[1].length <= 10) { // Emoji/mood should be short - moodMatch = match; - break; - } + // Parse skills section if enabled + const skillsConfig = trackerConfig?.userStats?.skillsSection; + if (skillsConfig?.enabled) { + const skillsMatch = statsText.match(/Skills:\s*(.+)/i); + if (skillsMatch) { + extensionSettings.userStats.skills = skillsMatch[1].trim(); + debugLog('[RPG Parser] Skills extracted:', skillsMatch[1].trim()); } } - debugLog('[RPG Parser] Mood/Status match:', { - found: !!moodMatch, - emoji: moodMatch ? moodMatch[1] : 'NOT FOUND', - conditions: moodMatch ? moodMatch[2] : 'NOT FOUND' - }); - // Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1 if (FEATURE_FLAGS.useNewInventory) { const inventoryData = extractInventory(statsText); @@ -322,17 +378,6 @@ export function parseUserStats(statsText) { } } - // Update extension settings - if (healthMatch) extensionSettings.userStats.health = parseInt(healthMatch[1]); - if (satietyMatch) extensionSettings.userStats.satiety = parseInt(satietyMatch[1]); - if (energyMatch) extensionSettings.userStats.energy = parseInt(energyMatch[1]); - if (hygieneMatch) extensionSettings.userStats.hygiene = parseInt(hygieneMatch[1]); - if (arousalMatch) extensionSettings.userStats.arousal = parseInt(arousalMatch[1]); - if (moodMatch) { - extensionSettings.userStats.mood = moodMatch[1].trim(); // Emoji - extensionSettings.userStats.conditions = moodMatch[2].trim(); // Conditions - } - // Extract quests const mainQuestMatch = statsText.match(/Main Quests?:\s*(.+)/i); if (mainQuestMatch) { diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 2392776..4049536 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -92,6 +92,7 @@ export function generateTrackerExample() { export function generateTrackerInstructions(includeHtmlPrompt = true, includeContinuation = true) { const userName = getContext().name1; const classicStats = extensionSettings.classicStats; + const trackerConfig = extensionSettings.trackerConfig; let instructions = ''; // Check if any trackers are enabled @@ -104,24 +105,36 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Add format specifications for each enabled tracker if (extensionSettings.showUserStats) { - // Get custom stat names with fallback defaults - const statNames = extensionSettings.statNames || { - health: 'Health', - satiety: 'Satiety', - energy: 'Energy', - hygiene: 'Hygiene', - arousal: 'Arousal' - }; + const userStatsConfig = trackerConfig?.userStats; + const enabledStats = userStatsConfig?.customStats?.filter(s => s && s.enabled && s.name) || []; instructions += '```\n'; instructions += `${userName}'s Stats\n`; instructions += '---\n'; - instructions += `- ${statNames.health}: X%\n`; - instructions += `- ${statNames.satiety}: X%\n`; - instructions += `- ${statNames.energy}: X%\n`; - instructions += `- ${statNames.hygiene}: X%\n`; - instructions += `- ${statNames.arousal}: X%\n`; - instructions += 'Status: [Mood Emoji, Conditions (up to three traits)]\n'; + + // Add custom stats dynamically + for (const stat of enabledStats) { + instructions += `- ${stat.name}: X%\n`; + } + + // Add status section if enabled + if (userStatsConfig?.statusSection?.enabled) { + const statusFields = userStatsConfig.statusSection.customFields || []; + const statusFieldsText = statusFields.map(f => `${f}`).join(', '); + + if (userStatsConfig.statusSection.showMoodEmoji) { + instructions += `Status: [Mood Emoji${statusFieldsText ? ', ' + statusFieldsText : ''}]\n`; + } else if (statusFieldsText) { + instructions += `Status: [${statusFieldsText}]\n`; + } + } + + // Add skills section if enabled + if (userStatsConfig?.skillsSection?.enabled) { + const skillFields = userStatsConfig.skillsSection.customFields || []; + const skillFieldsText = skillFields.map(f => `[${f}]`).join(', '); + instructions += `Skills: [${skillFieldsText || 'Skill1, Skill2, etc.'}]\n`; + } // Add inventory format based on feature flag if (FEATURE_FLAGS.useNewInventory) { @@ -142,23 +155,90 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon } if (extensionSettings.showInfoBox) { + const infoBoxConfig = trackerConfig?.infoBox; + const widgets = infoBoxConfig?.widgets || {}; + instructions += '```\n'; instructions += 'Info Box\n'; instructions += '---\n'; - instructions += 'Date: [Weekday, Month, Year]\n'; - instructions += 'Weather: [Weather Emoji, Forecast]\n'; - instructions += 'Temperature: [Temperature in Β°C]\n'; - instructions += 'Time: [Time Start β Time End]\n'; - instructions += 'Location: [Location]\n'; - instructions += 'Recent Events: [Up to three past events leading to the ongoing scene (short descriptors with no details, for example, "last-night date with Mary")]\n'; + + // Add only enabled widgets + if (widgets.date?.enabled) { + instructions += 'Date: [Weekday, Month, Year]\n'; + } + if (widgets.weather?.enabled) { + instructions += 'Weather: [Weather Emoji, Forecast]\n'; + } + if (widgets.temperature?.enabled) { + const unit = widgets.temperature.unit === 'fahrenheit' ? 'Β°F' : 'Β°C'; + instructions += `Temperature: [Temperature in ${unit}]\n`; + } + if (widgets.time?.enabled) { + instructions += 'Time: [Time Start β Time End]\n'; + } + if (widgets.location?.enabled) { + instructions += 'Location: [Location]\n'; + } + if (widgets.recentEvents?.enabled) { + instructions += 'Recent Events: [Up to three past events leading to the ongoing scene (short descriptors with no details, for example, "last-night date with Mary")]\n'; + } + instructions += '```\n\n'; } if (extensionSettings.showCharacterThoughts) { + const presentCharsConfig = trackerConfig?.presentCharacters; + const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || []; + const relationshipFields = presentCharsConfig?.relationshipFields || []; + const thoughtsConfig = presentCharsConfig?.thoughts; + const characterStats = presentCharsConfig?.characterStats; + const enabledCharStats = characterStats?.enabled && characterStats?.customStats?.filter(s => s && s.enabled && s.name) || []; + instructions += '```\n'; instructions += 'Present Characters\n'; instructions += '---\n'; - instructions += `[Present Character's Emoji (do not include ${userName}; state "Unavailable" if no major characters are present in the scene)]: [Name, Visible Physical State (up to three traits), Observable Demeanor Cue (one trait)] | [Enemy/Neutral/Friend/Lover] | [Internal Monologue (in first person POV, up to three sentences long)]\n`; + + // Build relationship placeholders (e.g., "Lover/Friend") + const relationshipPlaceholders = relationshipFields + .filter(r => r && r.trim()) + .map(r => `${r}`) + .join('/'); + + // Build custom field placeholders (e.g., "[Appearance] | [Current Action]") + const fieldPlaceholders = enabledFields + .map(f => `[${f.name}]`) + .join(' | '); + + // Character block format + instructions += `- [Name (do not include ${userName}; state "Unavailable" if no major characters are present in the scene)]\n`; + + // Details line with emoji and custom fields + if (fieldPlaceholders) { + instructions += `Details: [Present Character's Emoji] | ${fieldPlaceholders}\n`; + } else { + instructions += `Details: [Present Character's Emoji]\n`; + } + + // Relationship line (only if relationships are enabled) + if (relationshipPlaceholders) { + instructions += `Relationship: [${relationshipPlaceholders}]\n`; + } + + // Stats line (if enabled) + if (enabledCharStats.length > 0) { + const statPlaceholders = enabledCharStats.map(s => `${s.name}: X%`).join(' | '); + instructions += `Stats: ${statPlaceholders}\n`; + } + + // Thoughts line (if enabled) + if (thoughtsConfig?.enabled) { + const thoughtsName = thoughtsConfig.name || 'Thoughts'; + const thoughtsDescription = thoughtsConfig.description || 'Internal monologue (in first person POV, up to three sentences long)'; + instructions += `${thoughtsName}: [${thoughtsDescription}]\n`; + } + + instructions += `- β¦ (Repeat the format above for every other present major character)\n`; + instructions += '```\n\n'; } @@ -197,7 +277,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon /** * Generates a formatted contextual summary for SEPARATE mode injection. - * This creates a hybrid summary with clean formatting for main roleplay generation. + * Includes the full tracker data in original format (without code fences and separators). * Uses COMMITTED data (not displayed data) for generation context. * * @returns {string} Formatted contextual summary @@ -207,124 +287,52 @@ export function generateContextualSummary() { const userName = getContext().name1; let summary = ''; - // console.log('[RPG Companion] generateContextualSummary called'); - // console.log('[RPG Companion] committedTrackerData.userStats:', committedTrackerData.userStats); - // console.log('[RPG Companion] extensionSettings.userStats:', JSON.stringify(extensionSettings.userStats)); + // Helper function to clean tracker data (remove code fences and separator lines) + const cleanTrackerData = (data) => { + if (!data) return ''; + return data + .split('\n') + .filter(line => { + const trimmed = line.trim(); + return trimmed && + !trimmed.startsWith('```') && + trimmed !== '---'; + }) + .join('\n'); + }; - // Parse the data into readable format + // Add User Stats tracker data if enabled if (extensionSettings.showUserStats && committedTrackerData.userStats) { - const stats = extensionSettings.userStats; - // console.log('[RPG Companion] Building stats summary with:', stats); - summary += `${userName}'s Stats:\n`; - summary += `Condition: Health ${stats.health}%, Satiety ${stats.satiety}%, Energy ${stats.energy}%, Hygiene ${stats.hygiene}%, Arousal ${stats.arousal}% | ${stats.mood} ${stats.conditions}\n`; - - // Add inventory summary using v2-aware builder - if (stats.inventory) { - const inventorySummary = buildInventorySummary(stats.inventory); - if (inventorySummary && inventorySummary !== 'None') { - summary += `${inventorySummary}\n`; - } + const cleanedStats = cleanTrackerData(committedTrackerData.userStats); + if (cleanedStats) { + summary += cleanedStats + '\n\n'; } - - // Add quests summary - if (extensionSettings.quests) { - if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') { - summary += `Main Quests: ${extensionSettings.quests.main}\n`; - } - if (extensionSettings.quests.optional && extensionSettings.quests.optional.length > 0) { - const optionalQuests = extensionSettings.quests.optional.filter(q => q && q !== 'None').join(', '); - if (optionalQuests) { - summary += `Optional Quests: ${optionalQuests}\n`; - } - } - } - - // Include classic stats (attributes) and dice roll only if there was a dice roll - if (extensionSettings.lastDiceRoll) { - const classicStats = extensionSettings.classicStats; - const roll = extensionSettings.lastDiceRoll; - summary += `Attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`; - summary += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeed or fail the action they attempt.\n`; - } - summary += `\n`; } + // Add Info Box tracker data if enabled if (extensionSettings.showInfoBox && committedTrackerData.infoBox) { - // Parse info box data - support both new and legacy formats - const lines = committedTrackerData.infoBox.split('\n'); - let date = '', weather = '', temp = '', time = '', location = '', recentEvents = ''; - - // console.log('[RPG Companion] π Parsing Info Box lines:', lines); - - for (const line of lines) { - // console.log('[RPG Companion] π Processing line:', line); - - // New format with text labels - if (line.startsWith('Date:')) { - date = line.replace('Date:', '').trim(); - } else if (line.startsWith('Weather:')) { - weather = line.replace('Weather:', '').trim(); - } else if (line.startsWith('Temperature:')) { - temp = line.replace('Temperature:', '').trim(); - } else if (line.startsWith('Time:')) { - time = line.replace('Time:', '').trim(); - } else if (line.startsWith('Location:')) { - location = line.replace('Location:', '').trim(); - } else if (line.startsWith('Recent Events:')) { - recentEvents = line.replace('Recent Events:', '').trim(); - } - // Legacy format with emojis (for backward compatibility) - else if (line.includes('ποΈ:')) { - date = line.replace('ποΈ:', '').trim(); - } else if (line.includes('π‘οΈ:')) { - temp = line.replace('π‘οΈ:', '').trim(); - } else if (line.includes('π:')) { - time = line.replace('π:', '').trim(); - } else if (line.includes('πΊοΈ:')) { - location = line.replace('πΊοΈ:', '').trim(); - } else { - // Check for weather emojis in legacy format - const weatherEmojis = ['π€οΈ', 'βοΈ', 'β ', 'π¦οΈ', 'π§οΈ', 'βοΈ', 'π©οΈ', 'π¨οΈ', 'βοΈ', 'π«οΈ']; - const startsWithWeatherEmoji = weatherEmojis.some(emoji => line.startsWith(emoji + ':')); - if (startsWithWeatherEmoji && !line.includes('π‘οΈ') && !line.includes('πΊοΈ')) { - weather = line.substring(line.indexOf(':') + 1).trim(); - } - } - } - - // console.log('[RPG Companion] π Parsed values - date:', date, 'weather:', weather, 'temp:', temp, 'time:', time, 'location:', location); - - if (date || weather || temp || time || location || recentEvents) { - summary += `Information:\n`; - summary += `Scene: `; - if (date) summary += `${date}`; - if (location) summary += ` | ${location}`; - if (time) summary += ` | ${time}`; - if (weather) summary += ` | ${weather}`; - if (temp) summary += ` | ${temp}`; - summary += `\n`; - if (recentEvents) summary += `Recent Events: ${recentEvents}\n`; - summary += `\n`; + const cleanedInfoBox = cleanTrackerData(committedTrackerData.infoBox); + if (cleanedInfoBox) { + summary += cleanedInfoBox + '\n\n'; } } + // Add Present Characters tracker data if enabled if (extensionSettings.showCharacterThoughts && committedTrackerData.characterThoughts) { - const lines = committedTrackerData.characterThoughts.split('\n').filter(l => l.trim() && !l.includes('---') && !l.includes('Present Characters')); - - if (lines.length > 0 && !lines[0].toLowerCase().includes('unavailable')) { - summary += `Present Characters And Their Thoughts:\n`; - for (const line of lines) { - const parts = line.split('|').map(p => p.trim()); - if (parts.length >= 3) { - const nameAndState = parts[0]; // Emoji, name, physical state, demeanor - const relationship = parts[1]; - const thoughts = parts[2]; - summary += `${nameAndState} (${relationship}) | ${thoughts}\n`; - } - } + const cleanedThoughts = cleanTrackerData(committedTrackerData.characterThoughts); + if (cleanedThoughts) { + summary += cleanedThoughts + '\n\n'; } } + // Include attributes and dice roll only if there was a dice roll + if (extensionSettings.lastDiceRoll) { + const classicStats = extensionSettings.classicStats; + const roll = extensionSettings.lastDiceRoll; + summary += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`; + summary += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeeded or failed the action they attempted.\n\n`; + } + return summary.trim(); } @@ -354,14 +362,10 @@ export function generateRPGPromptText() { if (extensionSettings.quests) { if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') { promptText += `Main Quests: ${extensionSettings.quests.main}\n`; - } else { - promptText += `Main Quests: None\n`; } if (extensionSettings.quests.optional && extensionSettings.quests.optional.length > 0) { const optionalQuests = extensionSettings.quests.optional.filter(q => q && q !== 'None').join(', '); promptText += `Optional Quests: ${optionalQuests || 'None'}\n`; - } else { - promptText += `Optional Quests: None\n`; } promptText += `\n`; } diff --git a/src/systems/rendering/infoBox.js b/src/systems/rendering/infoBox.js index c42d4c9..4a4a5e6 100644 --- a/src/systems/rendering/infoBox.js +++ b/src/systems/rendering/infoBox.js @@ -270,151 +270,211 @@ export function renderInfoBox() { // location: data.location // }); + // Get tracker configuration + const config = extensionSettings.trackerConfig?.infoBox; + // Build visual dashboard HTML // Wrap all content in a scrollable container let html = '