Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ddc02d9bbc | |||
| 659b5bb82b | |||
| 5f72e6f549 | |||
| 0d71dcca04 | |||
| 39e2a07829 | |||
| dedfead59e |
@@ -7,15 +7,13 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
|
|||||||
|
|
||||||
## 🆕 What's New
|
## 🆕 What's New
|
||||||
|
|
||||||
### v3.3.0
|
### v3.3.2
|
||||||
|
|
||||||
- Small upgrades to the combat system.
|
- Fixed the auto-generation triggering on switching/starting new chats in separate generation mode.
|
||||||
- Regex fix.
|
- Restored the option to remove generated characters from the panel.
|
||||||
- Fixed External API logic.
|
|
||||||
- Even more minor bug fixes.
|
|
||||||
|
|
||||||
**Special thanks to all the other contributors for this project:**
|
**Special thanks to all the other contributors for this project:**
|
||||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||||
|
|
||||||
## 📥 Installation
|
## 📥 Installation
|
||||||
|
|
||||||
@@ -268,7 +266,7 @@ If you enjoy this extension, consider supporting development:
|
|||||||
## 🙏 Credits
|
## 🙏 Credits
|
||||||
|
|
||||||
**Contributors:**
|
**Contributors:**
|
||||||
SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||||
|
|
||||||
## 🚀 Planned Features
|
## 🚀 Planned Features
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import { renderInfoBox, updateInfoBoxField } from './src/systems/rendering/infoB
|
|||||||
import {
|
import {
|
||||||
renderThoughts,
|
renderThoughts,
|
||||||
updateCharacterField,
|
updateCharacterField,
|
||||||
|
removeCharacter,
|
||||||
updateChatThoughts,
|
updateChatThoughts,
|
||||||
createThoughtPanel
|
createThoughtPanel
|
||||||
} from './src/systems/rendering/thoughts.js';
|
} from './src/systems/rendering/thoughts.js';
|
||||||
|
|||||||
+1
-1
@@ -6,6 +6,6 @@
|
|||||||
"js": "index.js",
|
"js": "index.js",
|
||||||
"css": "style.css",
|
"css": "style.css",
|
||||||
"author": "Marinara",
|
"author": "Marinara",
|
||||||
"version": "3.3.0",
|
"version": "3.3.2",
|
||||||
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -43,12 +43,12 @@
|
|||||||
<i class="fa-solid fa-users"></i> <strong>Contributors:</strong>
|
<i class="fa-solid fa-users"></i> <strong>Contributors:</strong>
|
||||||
</div>
|
</div>
|
||||||
<div style="opacity: 0.8; font-size: 0.9em;">
|
<div style="opacity: 0.8; font-size: 0.9em;">
|
||||||
SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
|
||||||
v3.2.6
|
v3.3.2
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -308,6 +308,12 @@ export let isGenerating = false;
|
|||||||
*/
|
*/
|
||||||
export let isPlotProgression = false;
|
export let isPlotProgression = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicating if we're actively expecting a new message from generation
|
||||||
|
* (as opposed to loading chat history)
|
||||||
|
*/
|
||||||
|
export let isAwaitingNewMessage = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporary storage for pending dice roll (not saved until user clicks "Save Roll")
|
* Temporary storage for pending dice roll (not saved until user clicks "Save Roll")
|
||||||
*/
|
*/
|
||||||
@@ -408,6 +414,10 @@ export function setIsPlotProgression(value) {
|
|||||||
isPlotProgression = value;
|
isPlotProgression = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setIsAwaitingNewMessage(value) {
|
||||||
|
isAwaitingNewMessage = value;
|
||||||
|
}
|
||||||
|
|
||||||
export function setPendingDiceRoll(roll) {
|
export function setPendingDiceRoll(roll) {
|
||||||
pendingDiceRoll = roll;
|
pendingDiceRoll = roll;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,12 @@
|
|||||||
* Handles API calls for RPG tracker generation
|
* Handles API calls for RPG tracker generation
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { generateRaw, chat } from '../../../../../../../script.js';
|
import { generateRaw, chat, eventSource } from '../../../../../../../script.js';
|
||||||
import { executeSlashCommandsOnChatInput } from '../../../../../../../scripts/slash-commands.js';
|
import { executeSlashCommandsOnChatInput } from '../../../../../../../scripts/slash-commands.js';
|
||||||
|
|
||||||
|
// Custom event name for when RPG Companion finishes updating tracker data
|
||||||
|
// Other extensions can listen for this event to know when RPG Companion is done
|
||||||
|
export const RPG_COMPANION_UPDATE_COMPLETE = 'rpg_companion_update_complete';
|
||||||
import {
|
import {
|
||||||
extensionSettings,
|
extensionSettings,
|
||||||
lastGeneratedData,
|
lastGeneratedData,
|
||||||
@@ -397,6 +401,10 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
// This ensures the flag persists through both main generation AND tracker generation
|
// This ensures the flag persists through both main generation AND tracker generation
|
||||||
// console.log('[RPG Companion] 🔄 Tracker generation complete - resetting lastActionWasSwipe to false');
|
// console.log('[RPG Companion] 🔄 Tracker generation complete - resetting lastActionWasSwipe to false');
|
||||||
setLastActionWasSwipe(false);
|
setLastActionWasSwipe(false);
|
||||||
|
|
||||||
|
// Emit event for other extensions to know RPG Companion has finished updating
|
||||||
|
console.debug('[RPG Companion] Emitting RPG_COMPANION_UPDATE_COMPLETE event');
|
||||||
|
eventSource.emit(RPG_COMPANION_UPDATE_COMPLETE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import {
|
|||||||
committedTrackerData,
|
committedTrackerData,
|
||||||
lastActionWasSwipe,
|
lastActionWasSwipe,
|
||||||
isPlotProgression,
|
isPlotProgression,
|
||||||
|
isAwaitingNewMessage,
|
||||||
setLastActionWasSwipe,
|
setLastActionWasSwipe,
|
||||||
setIsPlotProgression,
|
setIsPlotProgression,
|
||||||
setIsGenerating,
|
setIsGenerating,
|
||||||
|
setIsAwaitingNewMessage,
|
||||||
updateLastGeneratedData,
|
updateLastGeneratedData,
|
||||||
updateCommittedTrackerData,
|
updateCommittedTrackerData,
|
||||||
$musicPlayerContainer
|
$musicPlayerContainer
|
||||||
@@ -105,6 +107,10 @@ export function onMessageSent() {
|
|||||||
// console.log('[RPG Companion] 🟢 EVENT: onMessageSent (after placeholder check)');
|
// console.log('[RPG Companion] 🟢 EVENT: onMessageSent (after placeholder check)');
|
||||||
// console.log('[RPG Companion] 🟢 NOTE: lastActionWasSwipe will be reset in onMessageReceived after generation completes');
|
// console.log('[RPG Companion] 🟢 NOTE: lastActionWasSwipe will be reset in onMessageReceived after generation completes');
|
||||||
|
|
||||||
|
// Set flag to indicate we're expecting a new message from generation
|
||||||
|
// This allows auto-update to distinguish between new generations and loading chat history
|
||||||
|
setIsAwaitingNewMessage(true);
|
||||||
|
|
||||||
// For separate mode with auto-update disabled, commit displayed tracker
|
// For separate mode with auto-update disabled, commit displayed tracker
|
||||||
if (extensionSettings.generationMode === 'separate' && !extensionSettings.autoUpdate) {
|
if (extensionSettings.generationMode === 'separate' && !extensionSettings.autoUpdate) {
|
||||||
if (lastGeneratedData.userStats || lastGeneratedData.infoBox || lastGeneratedData.characterThoughts) {
|
if (lastGeneratedData.userStats || lastGeneratedData.infoBox || lastGeneratedData.characterThoughts) {
|
||||||
@@ -250,13 +256,17 @@ export async function onMessageReceived(data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trigger auto-update if enabled (for both separate and external modes)
|
// Trigger auto-update if enabled (for both separate and external modes)
|
||||||
if (extensionSettings.autoUpdate) {
|
// Only trigger if this is a newly generated message, not loading chat history
|
||||||
|
if (extensionSettings.autoUpdate && isAwaitingNewMessage) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the awaiting flag after processing the message
|
||||||
|
setIsAwaitingNewMessage(false);
|
||||||
|
|
||||||
// Reset the swipe flag after generation completes
|
// Reset the swipe flag after generation completes
|
||||||
// This ensures that if the user swiped → auto-reply generated → flag is now cleared
|
// 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)
|
// so the next user message will be treated as a new message (not a swipe)
|
||||||
@@ -340,6 +350,7 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
if (!isExistingSwipe) {
|
if (!isExistingSwipe) {
|
||||||
// This is a NEW swipe that will trigger generation
|
// This is a NEW swipe that will trigger generation
|
||||||
setLastActionWasSwipe(true);
|
setLastActionWasSwipe(true);
|
||||||
|
setIsAwaitingNewMessage(true);
|
||||||
// console.log('[RPG Companion] 🔵 NEW swipe detected - Set lastActionWasSwipe = true');
|
// console.log('[RPG Companion] 🔵 NEW swipe detected - Set lastActionWasSwipe = true');
|
||||||
} else {
|
} else {
|
||||||
// This is navigating to an EXISTING swipe - don't change the flag
|
// This is navigating to an EXISTING swipe - don't change the flag
|
||||||
|
|||||||
@@ -498,19 +498,19 @@ export function renderInfoBox() {
|
|||||||
if (config?.widgets?.recentEvents?.enabled) {
|
if (config?.widgets?.recentEvents?.enabled) {
|
||||||
// Parse Recent Events from infoBox (supports both JSON and text formats)
|
// Parse Recent Events from infoBox (supports both JSON and text formats)
|
||||||
let recentEvents = [];
|
let recentEvents = [];
|
||||||
if (committedTrackerData.infoBox) {
|
if (infoBoxData) {
|
||||||
// Try JSON format first
|
// Try JSON format first
|
||||||
try {
|
try {
|
||||||
const parsed = typeof committedTrackerData.infoBox === 'string'
|
const parsed = typeof infoBoxData === 'string'
|
||||||
? JSON.parse(committedTrackerData.infoBox)
|
? JSON.parse(infoBoxData)
|
||||||
: committedTrackerData.infoBox;
|
: infoBoxData;
|
||||||
|
|
||||||
if (parsed && Array.isArray(parsed.recentEvents)) {
|
if (parsed && Array.isArray(parsed.recentEvents)) {
|
||||||
recentEvents = parsed.recentEvents;
|
recentEvents = parsed.recentEvents;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fall back to old text format
|
// Fall back to old text format
|
||||||
const recentEventsLine = committedTrackerData.infoBox.split('\n').find(line => line.startsWith('Recent Events:'));
|
const recentEventsLine = infoBoxData.split('\n').find(line => line.startsWith('Recent Events:'));
|
||||||
if (recentEventsLine) {
|
if (recentEventsLine) {
|
||||||
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
||||||
if (eventsString) {
|
if (eventsString) {
|
||||||
|
|||||||
@@ -3,10 +3,35 @@
|
|||||||
* Handles UI rendering for quests system (main and optional quests)
|
* Handles UI rendering for quests system (main and optional quests)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extensionSettings, $questsContainer } from '../../core/state.js';
|
import { extensionSettings, $questsContainer, committedTrackerData, lastGeneratedData } from '../../core/state.js';
|
||||||
import { saveSettings } from '../../core/persistence.js';
|
import { saveSettings, saveChatData } from '../../core/persistence.js';
|
||||||
import { isItemLocked, setItemLock } from '../generation/lockManager.js';
|
import { isItemLocked, setItemLock } from '../generation/lockManager.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs the current extensionSettings.quests to committedTrackerData.userStats
|
||||||
|
* This ensures quest changes made via UI are reflected in the data sent to AI
|
||||||
|
*/
|
||||||
|
function syncQuestsToCommittedData() {
|
||||||
|
const currentData = committedTrackerData.userStats || lastGeneratedData.userStats;
|
||||||
|
if (!currentData) return;
|
||||||
|
|
||||||
|
const trimmed = currentData.trim();
|
||||||
|
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
||||||
|
try {
|
||||||
|
const jsonData = JSON.parse(currentData);
|
||||||
|
if (jsonData && typeof jsonData === 'object') {
|
||||||
|
// Update quests in the JSON data
|
||||||
|
jsonData.quests = extensionSettings.quests || { main: 'None', optional: [] };
|
||||||
|
const updatedJSON = JSON.stringify(jsonData, null, 2);
|
||||||
|
committedTrackerData.userStats = updatedJSON;
|
||||||
|
lastGeneratedData.userStats = updatedJSON;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[RPG Quests] Failed to sync quests to committed data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to generate lock icon HTML if setting is enabled
|
* Helper to generate lock icon HTML if setting is enabled
|
||||||
* @param {string} tracker - Tracker name
|
* @param {string} tracker - Tracker name
|
||||||
@@ -250,7 +275,10 @@ function attachQuestEventHandlers() {
|
|||||||
}
|
}
|
||||||
extensionSettings.quests.optional.push(questTitle);
|
extensionSettings.quests.optional.push(questTitle);
|
||||||
}
|
}
|
||||||
|
// Sync quest changes to committedTrackerData so AI sees the addition
|
||||||
|
syncQuestsToCommittedData();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
saveChatData();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -278,7 +306,10 @@ function attachQuestEventHandlers() {
|
|||||||
|
|
||||||
if (questTitle) {
|
if (questTitle) {
|
||||||
extensionSettings.quests.main = questTitle;
|
extensionSettings.quests.main = questTitle;
|
||||||
|
// Sync quest changes to committedTrackerData so AI sees the edit
|
||||||
|
syncQuestsToCommittedData();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
saveChatData();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -293,7 +324,10 @@ function attachQuestEventHandlers() {
|
|||||||
} else {
|
} else {
|
||||||
extensionSettings.quests.optional.splice(index, 1);
|
extensionSettings.quests.optional.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
// Sync quest changes to committedTrackerData so AI sees the removal
|
||||||
|
syncQuestsToCommittedData();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
saveChatData();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -306,7 +340,10 @@ function attachQuestEventHandlers() {
|
|||||||
|
|
||||||
if (newTitle && field === 'optional' && index !== undefined) {
|
if (newTitle && field === 'optional' && index !== undefined) {
|
||||||
extensionSettings.quests.optional[index] = newTitle;
|
extensionSettings.quests.optional[index] = newTitle;
|
||||||
|
// Sync quest changes to committedTrackerData so AI sees the edit
|
||||||
|
syncQuestsToCommittedData();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
saveChatData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -391,50 +391,10 @@ export function renderThoughts() {
|
|||||||
debugLog('[RPG Thoughts] ==================== BUILDING HTML ====================');
|
debugLog('[RPG Thoughts] ==================== BUILDING HTML ====================');
|
||||||
debugLog('[RPG Thoughts] Starting HTML generation for', presentCharacters.length + ' characters');
|
debugLog('[RPG Thoughts] Starting HTML generation for', presentCharacters.length + ' characters');
|
||||||
|
|
||||||
// If no characters parsed, show a placeholder editable card
|
// If no characters parsed, show empty state (no placeholder)
|
||||||
if (presentCharacters.length === 0) {
|
if (presentCharacters.length === 0) {
|
||||||
debugLog('[RPG Thoughts] ⚠ No characters parsed - showing placeholder card');
|
debugLog('[RPG Thoughts] ⚠ No characters parsed - showing empty state');
|
||||||
// Get default character portrait
|
html += '<div class="rpg-thoughts-content"></div>';
|
||||||
let defaultPortrait = FALLBACK_AVATAR_DATA_URI;
|
|
||||||
let defaultName = 'Character';
|
|
||||||
|
|
||||||
if (this_chid !== undefined && characters[this_chid]) {
|
|
||||||
if (characters[this_chid].avatar && characters[this_chid].avatar !== 'none') {
|
|
||||||
const thumbnailUrl = getSafeThumbnailUrl('avatar', characters[this_chid].avatar);
|
|
||||||
if (thumbnailUrl) {
|
|
||||||
defaultPortrait = thumbnailUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultName = characters[this_chid].name || 'Character';
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '<div class="rpg-thoughts-content">';
|
|
||||||
html += `
|
|
||||||
<div class="rpg-character-card" data-character-name="${defaultName}">
|
|
||||||
<div class="rpg-character-avatar">
|
|
||||||
<img src="${defaultPortrait}" alt="${defaultName}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
|
||||||
<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="relationship" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">⚖️</div>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-character-info">
|
|
||||||
<div class="rpg-character-header">
|
|
||||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="emoji" title="Click to edit emoji">😊</span>
|
|
||||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="name" title="Click to edit name">${defaultName}</span>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Add custom fields dynamically
|
|
||||||
for (const field of enabledFields) {
|
|
||||||
const fieldId = field.name.toLowerCase().replace(/\s+/g, '-');
|
|
||||||
html += `
|
|
||||||
<div class="rpg-character-field rpg-character-${fieldId} rpg-editable" contenteditable="true" data-character="${defaultName}" data-field="${field.name}" title="Click to edit ${field.name}"></div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
html += '</div>';
|
|
||||||
} else {
|
} else {
|
||||||
html += '<div class="rpg-thoughts-content">';
|
html += '<div class="rpg-thoughts-content">';
|
||||||
|
|
||||||
@@ -540,6 +500,7 @@ export function renderThoughts() {
|
|||||||
<div class="rpg-character-header">
|
<div class="rpg-character-header">
|
||||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${char.name}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
||||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
|
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${char.name}" data-field="name" title="Click to edit name">${char.name}</span>
|
||||||
|
<button class="rpg-character-remove" data-character="${char.name}" title="Remove character">×</button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -650,6 +611,15 @@ export function renderThoughts() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add event listener for character remove button
|
||||||
|
$thoughtsContainer.find('.rpg-character-remove').on('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const characterName = $(this).data('character');
|
||||||
|
removeCharacter(characterName);
|
||||||
|
});
|
||||||
|
|
||||||
// Add event listener for avatar upload clicks
|
// Add event listener for avatar upload clicks
|
||||||
$thoughtsContainer.find('.rpg-avatar-upload').on('click', function(e) {
|
$thoughtsContainer.find('.rpg-avatar-upload').on('click', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -703,6 +673,121 @@ export function renderThoughts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a character from Present Characters data and re-renders.
|
||||||
|
*
|
||||||
|
* @param {string} characterName - Name of the character to remove
|
||||||
|
*/
|
||||||
|
export function removeCharacter(characterName) {
|
||||||
|
if (!lastGeneratedData.characterThoughts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if data is in JSON format
|
||||||
|
let isJSON = false;
|
||||||
|
let parsedData = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsedData = typeof lastGeneratedData.characterThoughts === 'string'
|
||||||
|
? JSON.parse(lastGeneratedData.characterThoughts)
|
||||||
|
: lastGeneratedData.characterThoughts;
|
||||||
|
|
||||||
|
if (Array.isArray(parsedData) || (parsedData && parsedData.characters)) {
|
||||||
|
isJSON = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Not JSON, treat as text format
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJSON) {
|
||||||
|
// JSON format - remove character from array
|
||||||
|
let characters = Array.isArray(parsedData) ? parsedData : parsedData.characters;
|
||||||
|
characters = characters.filter(char => char.name !== characterName);
|
||||||
|
|
||||||
|
if (Array.isArray(parsedData)) {
|
||||||
|
parsedData = characters;
|
||||||
|
} else {
|
||||||
|
parsedData.characters = characters;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedJSON = JSON.stringify(parsedData, null, 2);
|
||||||
|
lastGeneratedData.characterThoughts = updatedJSON;
|
||||||
|
committedTrackerData.characterThoughts = updatedJSON;
|
||||||
|
} else {
|
||||||
|
// Text format - remove character block
|
||||||
|
const lines = lastGeneratedData.characterThoughts.split('\n');
|
||||||
|
const dividerIndex = lines.findIndex(line => line.includes('---'));
|
||||||
|
|
||||||
|
if (dividerIndex === -1) return;
|
||||||
|
|
||||||
|
// Find the character block to remove
|
||||||
|
let startLineIndex = -1;
|
||||||
|
let endLineIndex = -1;
|
||||||
|
|
||||||
|
for (let i = dividerIndex + 1; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
|
||||||
|
// Check if this is the start of the character block
|
||||||
|
if (line.startsWith('Name:')) {
|
||||||
|
const nameMatch = line.match(/^Name:\s*(.+)/);
|
||||||
|
if (nameMatch && nameMatch[1].trim() === characterName) {
|
||||||
|
startLineIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found the start, look for the end
|
||||||
|
if (startLineIndex !== -1 && i > startLineIndex) {
|
||||||
|
// End of block is either another "Name:" line or end of content
|
||||||
|
if (line.startsWith('Name:') || i === lines.length - 1) {
|
||||||
|
endLineIndex = line.startsWith('Name:') ? i - 1 : i;
|
||||||
|
|
||||||
|
// Remove empty lines at the end of the block
|
||||||
|
while (endLineIndex > startLineIndex && !lines[endLineIndex].trim()) {
|
||||||
|
endLineIndex--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the character block
|
||||||
|
if (startLineIndex !== -1 && endLineIndex !== -1) {
|
||||||
|
lines.splice(startLineIndex, endLineIndex - startLineIndex + 1);
|
||||||
|
|
||||||
|
// Remove empty lines after removal to keep formatting clean
|
||||||
|
let i = startLineIndex;
|
||||||
|
while (i < lines.length && !lines[i].trim()) {
|
||||||
|
lines.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastGeneratedData.characterThoughts = lines.join('\n');
|
||||||
|
committedTrackerData.characterThoughts = lines.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update message 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 = lastGeneratedData.characterThoughts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveChatData();
|
||||||
|
|
||||||
|
// Re-render to show updated character list
|
||||||
|
renderThoughts();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates a specific character field in Present Characters data and re-renders.
|
* Updates a specific character field in Present Characters data and re-renders.
|
||||||
* Works with the new multi-line format.
|
* Works with the new multi-line format.
|
||||||
@@ -1847,9 +1932,9 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Desktop: show panel, hide icon with class
|
// Desktop: always start with panel expanded on page load/refresh
|
||||||
$thoughtPanel.css('display', 'block');
|
$thoughtPanel.css('display', 'block');
|
||||||
$thoughtIcon.addClass('rpg-force-hide');
|
$thoughtIcon.addClass('rpg-force-hide').removeClass('rpg-collapsed-desktop');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle viewport changes between mobile and desktop
|
// Handle viewport changes between mobile and desktop
|
||||||
@@ -1880,24 +1965,62 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
wasMobileView = isMobileNow;
|
wasMobileView = isMobileNow;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close button functionality (mobile only) - support both click and touch
|
// Close button functionality - support both click and touch
|
||||||
$thoughtPanel.find('.rpg-thought-close').on('click touchend', function(e) {
|
$thoughtPanel.find('.rpg-thought-close').on('click touchend', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
// Only hide/show in mobile view
|
|
||||||
if (window.innerWidth <= 1000) {
|
const isMobileView = window.innerWidth <= 1000;
|
||||||
|
|
||||||
|
if (isMobileView) {
|
||||||
|
// Mobile: hide panel and show icon
|
||||||
$thoughtPanel.fadeOut(200, function() {
|
$thoughtPanel.fadeOut(200, function() {
|
||||||
// Make sure icon is visible and clean state when panel closes (use selector, not variable)
|
// Make sure icon is visible and clean state when panel closes (use selector, not variable)
|
||||||
const $icon = $('#rpg-thought-icon');
|
const $icon = $('#rpg-thought-icon');
|
||||||
$icon.removeClass('rpg-hidden dragging');
|
$icon.removeClass('rpg-hidden dragging');
|
||||||
$icon.data('just-dragged', false);
|
$icon.data('just-dragged', false);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// Desktop: collapse to icon at panel position
|
||||||
|
const panelRect = $thoughtPanel[0].getBoundingClientRect();
|
||||||
|
const $icon = $('#rpg-thought-icon');
|
||||||
|
|
||||||
|
// Position icon where the panel is
|
||||||
|
$icon.css({
|
||||||
|
top: `${panelRect.top}px`,
|
||||||
|
left: isRightPanel ? `${panelRect.left}px` : 'auto',
|
||||||
|
right: isRightPanel ? 'auto' : `${window.innerWidth - panelRect.right}px`
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mark as collapsed desktop state (session only, not persisted)
|
||||||
|
$icon.addClass('rpg-collapsed-desktop');
|
||||||
|
|
||||||
|
// Hide panel and show icon
|
||||||
|
$thoughtPanel.fadeOut(200, function() {
|
||||||
|
$icon.removeClass('rpg-hidden rpg-force-hide');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Icon click/tap to show panel (mobile only)
|
// Icon click/tap to show panel
|
||||||
const handleThoughtIconTap = function(e) {
|
const handleThoughtIconTap = function(e) {
|
||||||
// Skip if we just finished dragging
|
const isMobileView = window.innerWidth <= 1000;
|
||||||
|
const $icon = $('#rpg-thought-icon');
|
||||||
|
|
||||||
|
// Desktop collapsed state: expand panel and hide icon
|
||||||
|
if (!isMobileView && $icon.hasClass('rpg-collapsed-desktop')) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Remove collapsed state (no need to save, state is session-only)
|
||||||
|
$icon.addClass('rpg-force-hide').removeClass('rpg-collapsed-desktop');
|
||||||
|
|
||||||
|
// Show panel
|
||||||
|
$('#rpg-thought-panel').fadeIn(200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if we just finished dragging (mobile only)
|
||||||
if ($thoughtIcon.data('just-dragged')) {
|
if ($thoughtIcon.data('just-dragged')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1995,3 +2118,4 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4732,11 +4732,16 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Force hide class for desktop mode - overrides media query */
|
/* Force hide class for desktop mode - overrides media query (unless collapsed) */
|
||||||
#rpg-thought-icon.rpg-force-hide {
|
#rpg-thought-icon.rpg-force-hide:not(.rpg-collapsed-desktop) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When collapsed in desktop, show icon */
|
||||||
|
#rpg-thought-icon.rpg-collapsed-desktop {
|
||||||
|
display: flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hidden state that allows transitions */
|
/* Hidden state that allows transitions */
|
||||||
#rpg-thought-icon.rpg-hidden {
|
#rpg-thought-icon.rpg-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -4780,10 +4785,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
color: var(--rpg-highlight, #e94560);
|
color: var(--rpg-highlight, #e94560);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide close button in desktop view (panel doesn't close) */
|
/* Show close button in desktop view for collapsing */
|
||||||
@media (min-width: 1001px) {
|
@media (min-width: 1001px) {
|
||||||
.rpg-thought-close {
|
.rpg-thought-close {
|
||||||
display: none !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -948,7 +948,7 @@
|
|||||||
|
|
||||||
<h4 style="margin-top: 20px; margin-bottom: 10px;"><strong>Special thanks to all the other contributors for this project:</strong></h4>
|
<h4 style="margin-top: 20px; margin-bottom: 10px;"><strong>Special thanks to all the other contributors for this project:</strong></h4>
|
||||||
<p style="margin-left: 20px; line-height: 1.6;">
|
<p style="margin-left: 20px; line-height: 1.6;">
|
||||||
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude (???), IDeathByte, Chungchandev, Joenunezb, and Amauragis.
|
Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude (???), IDeathByte, Chungchandev, Joenunezb, Amauragis, and Tomt610.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style="margin-top: 20px; text-align: center;">
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
|||||||
Reference in New Issue
Block a user