diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index bb2c302..7cf787b 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -401,6 +401,7 @@ export function generateJSONTrackerInstructions(includeHtmlPrompt = true, includ } instructions += '- Empty arrays [] for sections with no items\n'; + instructions += '- Items may be added or removed from all sections\n'; instructions += '- null for main quest if none active\n'; // Add stat descriptions if any have descriptions diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index 436db8a..1bed375 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -478,6 +478,7 @@ export function renderThoughts() {
${char.emoji} ${char.name} +
`; @@ -541,6 +542,15 @@ export function renderThoughts() { updateCharacterField(character, field, value); }); + // Add event handlers for remove character buttons + $thoughtsContainer.find('.rpg-character-remove').on('click', function(e) { + e.stopPropagation(); + const characterName = $(this).data('character'); + if (characterName && confirm(`Remove ${characterName} from present characters?`)) { + removeCharacter(characterName); + } + }); + // Remove updating class after animation if (extensionSettings.enableAnimations) { setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600); @@ -780,6 +790,112 @@ export function updateCharacterField(characterName, field, value) { } } +/** + * Removes a character from Present Characters data and re-renders. + * Works with both structured (charactersData) and text (characterThoughts) formats. + * + * @param {string} characterName - Name of the character to remove + */ +export function removeCharacter(characterName) { + console.log('[RPG Companion] Removing character:', characterName); + + // Remove from structured data if it exists + if (extensionSettings.charactersData && Array.isArray(extensionSettings.charactersData)) { + const initialLength = extensionSettings.charactersData.length; + extensionSettings.charactersData = extensionSettings.charactersData.filter( + char => char.name && char.name.toLowerCase() !== characterName.toLowerCase() + ); + if (extensionSettings.charactersData.length < initialLength) { + console.log('[RPG Companion] Removed character from structured data'); + } + } + + // Remove from text format + if (!lastGeneratedData.characterThoughts) { + console.log('[RPG Companion] No characterThoughts data to remove from'); + return; + } + + const lines = lastGeneratedData.characterThoughts.split('\n'); + const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters; + + let characterFound = false; + let inTargetCharacter = false; + let characterStartIndex = -1; + let characterEndIndex = -1; + const linesToRemove = []; + + // Find the character block + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + + if (line.startsWith('- ')) { + const name = line.substring(2).trim(); + if (name.toLowerCase() === characterName.toLowerCase()) { + characterFound = true; + inTargetCharacter = true; + characterStartIndex = i; + linesToRemove.push(i); + } else if (inTargetCharacter) { + characterEndIndex = i; + break; + } + } else if (inTargetCharacter) { + // Include all lines until the next character or end of file + linesToRemove.push(i); + // Check if this is a character name line (next character) + if (line.startsWith('- ')) { + characterEndIndex = i; + break; + } + } + } + + if (characterFound && characterEndIndex === -1) { + characterEndIndex = lines.length; + } + + if (characterFound && linesToRemove.length > 0) { + // Remove lines in reverse order to maintain indices + for (let i = linesToRemove.length - 1; i >= 0; i--) { + lines.splice(linesToRemove[i], 1); + } + + // Clean up any trailing empty lines after removal + while (lines.length > 0 && lines[lines.length - 1].trim() === '') { + lines.pop(); + } + + lastGeneratedData.characterThoughts = lines.join('\n'); + committedTrackerData.characterThoughts = lines.join('\n'); + + console.log('[RPG Companion] Removed character from text format'); + + // Update chat swipe data + const chat = getContext().chat; + if (chat && chat.length > 0) { + for (let i = chat.length - 1; i >= 0; i--) { + const message = chat[i]; + if (!message.is_user) { + if (message.extra && message.extra.rpg_companion_swipes) { + const swipeId = message.swipe_id || 0; + if (message.extra.rpg_companion_swipes[swipeId]) { + message.extra.rpg_companion_swipes[swipeId].characterThoughts = lines.join('\n'); + } + } + break; + } + } + } + + saveChatData(); + renderThoughts(); + updateChatThoughts(); + } else { + console.log('[RPG Companion] Character not found in text format:', characterName); + } +} + /** * Updates or removes thought overlays in the chat. * Creates floating thought bubbles positioned near character avatars. diff --git a/style.css b/style.css index 45e92ec..c8379e7 100644 --- a/style.css +++ b/style.css @@ -1957,6 +1957,38 @@ body:has(.rpg-panel.rpg-position-left) #sheld { white-space: nowrap; /* Prevent name from wrapping */ overflow: hidden; text-overflow: ellipsis; + flex: 1; + min-width: 0; +} + +/* Character remove button */ +.rpg-character-remove { + flex-shrink: 0; + background: rgba(255, 0, 0, 0.2); + border: 1px solid rgba(255, 0, 0, 0.4); + border-radius: 50%; + width: clamp(18px, 2.5vh, 22px); + height: clamp(18px, 2.5vh, 22px); + display: flex; + align-items: center; + justify-content: center; + font-size: clamp(14px, 2vw, 18px); + color: var(--rpg-highlight); + cursor: pointer; + transition: all 0.2s ease; + padding: 0; + line-height: 1; + margin-left: auto; +} + +.rpg-character-remove:hover { + background: rgba(255, 0, 0, 0.4); + border-color: rgba(255, 0, 0, 0.6); + transform: scale(1.1); +} + +.rpg-character-remove:active { + transform: scale(0.95); } /* Character traits/status line and custom fields */