9 Commits
Author SHA1 Message Date
Pakobbix 03400ad5d5 Merge pull request 'Fix: comprehensive bug review — restore refactor regressions & fix bugs' (#24) from fix-comprehensive-bug-review into main
Reviewed-on: #24
2026-08-16 19:52:33 +00:00
ARIA 34553c18f7 Fix: comprehensive bug review — restore refactor regressions & fix critical/major/minor bugs
Critical:
- responseExtractor.js: fix import depth (7x ../ -> 6x) that broke the module
  graph for encounters, separate/external generation, and auto-avatars
- settingsListeners.js: restore ~25 undefined functions, ~40 lost event
  bindings, and the entire 'Initialize UI state' block dropped by the c14c141
  refactor; add initializeSettingsUIState()
- Restore all External API mode handlers (base-url, api-key, model,
  max-tokens, temperature, key-visibility, test-connection) + value init
- validator.js: convert CJS->ESM (crashed under type:module), fix
  glob.sync->globSync (v13 API), fix '!key in obj' precedence bugs

Major:
- sillytavern.js: add missing updateMessageBlock import
- infoBox.js: add missing saveSettings import
- userStats.js: fix double-escaped quantity regex

Minor:
- config.js: settingsVersion 6->7, restore showLockIcons, fix apiKey comment
- events.js: off() tracking + unregister/re-register on disable/enable
- escapeHtml: escape quotes (XSS) in all 3 copies
- template.html: data-prompt dialogue-coloring -> dialogueColoring
- trackerEditor.js: stale externalApiOnly -> sendAllEnabledOnRefresh
- suppression.js: fix '[object Object]' on empty instruct value
- persistence.js: await migrateToV3JSON (loadSettings now async)
- weatherEffects.js: JSON.parse -> repairJSON
- mobile.js: fix corrupted comment line
- Add missing debugMode setting; settings.html v3.7.2 -> v3.7.4

Dead code removed:
- Unused imports/vars (promptBuilder, jsonPromptHelpers, encounterPrompts,
  injector), withChangeDetection, trackJQueryHandler/cleanupJQueryEvents,
  renderThoughtsSidebarOnly, updateCheckpointButtonInMenu, addDiceQuickReply,
  setupRefreshButtonDrag/setupDebugButtonDrag (~430 lines)

Verified: 78/78 tests pass, style.css in sync, validator runs clean.
2026-08-16 21:42:14 +02:00
Pakobbix 6f906d0d97 Merge pull request 'Branch cleanup — remove 9 stale remote branches' (#23) from issue-22-branch-cleanup into main
Reviewed-on: #23
2026-07-13 10:17:01 +00:00
ARIA 3b78f284a1 Fixes #22: Document branch cleanup — removed 9 stale remote branches 2026-07-13 12:15:46 +02:00
Pakobbix 6e816e1436 Merge pull request 'Fixes #20: After change errors (JSON parse + equipment tab)' (#21) from issue-20-fix-json-parse-equipment into main
Reviewed-on: #21
2026-07-12 16:22:01 +00:00
ARIA 28ef82aae7 Fix: remove broken import in desktop.js that caused 404 and extension load failure 2026-07-12 18:14:59 +02:00
ARIA 59b125fd2a Fixes #20: Call updateSectionVisibility() during init to show equipment tab
The equipment section has display:none in CSS and updateSectionVisibility()
is the only function that calls .show() on it. It was never called during
initialization, only when settings toggles changed. Added the call to the
main init flow so the equipment tab becomes visible on page load.
2026-07-12 17:56:35 +02:00
ARIA 4bd29a207a Fixes #20: Fix JSON parse errors and empty equipment tab
- Replace JSON.parse() with repairJSON() in desktop.js and mobile.js
  to handle malformed JSON from infoBox/userStats data
- Add defensive initialization in renderEquipment(): ensure showEquipment
  defaults to true and equipment data structure exists
- Add explicit renderEquipment() call during initialization
2026-07-12 17:33:08 +02:00
Pakobbix e4bb48d9ea Update README.md 2026-07-12 15:09:11 +00:00
35 changed files with 864 additions and 849 deletions
+13
View File
@@ -0,0 +1,13 @@
# Branch Cleanup — July 13, 2026
The following stale remote branches were deleted, leaving only `main`:
- `SpicyMarinara-patch-1`
- `feature/equipment-system`
- `pr-109`
- `revert-111-main`
- `revert-116-revert-111-main`
- `revert-36-feat/v2-widget-dashboard-system`
- `revert-40-feat/responsive-dashboard-layout`
- `revert-59-main`
- `test-pr90-pr91-combined`
+4 -15
View File
@@ -2,16 +2,8 @@
An immersive RPG extension for browsers that tracks character stats, scene information, and character thoughts in a beautiful, customizable UI panel. All automated! Works with any preset. Choose between Together or Separate generation modes for context and generation control.
[![My Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da)](https://discord.com/invite/KdAkTg94ME)
[![Support Me](https://img.shields.io/badge/Ko--fi-Support%20Creator-ff5e5b)](https://ko-fi.com/marinara_spaghetti)
## 🆕 What's New
### DEPRECATED
Moving on to developing the Marinara Engine frontend, the extension will now be maintained by the community!
<https://github.com/Pasta-Devs/Marinara-Engine>
[![Marinara Discord](https://img.shields.io/badge/Discord-Join%20Server-7289da)](https://discord.com/invite/KdAkTg94ME)
[![Support Original Author](https://img.shields.io/badge/Ko--fi-Support%20Creator-ff5e5b)](https://ko-fi.com/marinara_spaghetti)
## 📥 Installation
@@ -21,7 +13,7 @@ Moving on to developing the Marinara Engine frontend, the extension will now be
3. Go to Install extension
4. Copy-paste this link: <https://github.com/SpicyMarinara/rpg-companion-sillytavern>
4. Copy-paste this link: <https://gitea.zephyre.one/Pakobbix/rpg-companion-sillytavern>
5. Press Install for all users/Install just for me
@@ -334,7 +326,4 @@ SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDea
---
Made with ❤️ by Marinara
PS I'm looking for a job or a sponsor to fund my custom AI frontend, contact me if interested:
[mgrabower97@gmail.com](mailto:mgrabower97@gmail.com)
Made with ❤️ by Marinara
+86 -62
View File
@@ -55,7 +55,7 @@ import {
clearDebugLogs
} from './src/core/state.js';
import { loadSettings, saveSettings, saveChatData, loadChatData, updateMessageSwipeData, commitTrackerDataFromPriorMessage } from './src/core/persistence.js';
import { registerAllEvents, on as onEvent } from './src/core/events.js';
import { registerAllEvents, on as onEvent, unregisterAllEvents } from './src/core/events.js';
import { addExtensionSettings } from './src/core/settingsPanel.js';
// Generation & Parsing modules
@@ -108,7 +108,6 @@ import {
setupDiceRoller,
setupSettingsPopup,
updateDiceDisplay,
addDiceQuickReply,
getSettingsModal,
showWelcomeModalIfNeeded,
showDeprecationModalIfNeeded
@@ -187,7 +186,7 @@ import {
} from './src/systems/integration/sillytavern.js';
// Settings UI event listeners (extracted from initUI)
import { bindSettingsListeners, updateWeatherSubOptionsVisibility } from './src/systems/ui/settingsListeners.js';
import { bindSettingsListeners, initializeSettingsUIState } from './src/systems/ui/settingsListeners.js';
// Set up thought-based expressions refresh handler
setThoughtBasedExpressionsRefreshHandler(() => {
@@ -207,6 +206,71 @@ function updateDynamicLabels() {
updateMobileTabLabels();
}
/**
* Registers all SillyTavern event handlers for the extension.
* Idempotent per enable cycle: call unregisterAllEvents() before re-registering
* (e.g. when the extension is re-enabled after being disabled).
*/
function registerExtensionEvents() {
registerAllEvents({
[event_types.MESSAGE_SENT]: onMessageSent,
[event_types.GENERATION_STARTED]: onGenerationStarted,
[event_types.MESSAGE_RECEIVED]: onMessageReceived,
[event_types.GENERATION_STOPPED]: onGenerationEnded,
[event_types.GENERATION_ENDED]: onGenerationEnded,
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts, clearDebugLogs],
[event_types.CHAT_LOADED]: onChatLoaded,
[event_types.MESSAGE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
});
// Use tracked event registration (onEvent) instead of direct eventSource.on()
// This ensures all handlers are tracked for cleanup by unregisterAllEvents()
onEvent(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => {
if (!extensionSettings.enabled) return;
const renderedMessage = chat[messageId];
if (renderedMessage && !renderedMessage.is_user && !renderedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_UPDATED, (messageId) => {
if (!extensionSettings.enabled) return;
const updatedMessage = chat[messageId];
if (updatedMessage && !updatedMessage.is_user && !updatedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_SWIPED, (messageIndex) => {
if (!extensionSettings.enabled) return;
const swipedMessage = chat[messageIndex];
if (swipedMessage && !swipedMessage.is_user && !swipedMessage.is_system) {
queueThoughtBasedExpressionsUpdate({ immediate: true });
}
});
onEvent(event_types.CHAT_CHANGED, () => {
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_SWIPE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
}
/**
* Initializes the UI for the extension.
*/
@@ -252,6 +316,19 @@ async function initUI() {
// Bind all settings event listeners (extracted to settingsListeners.js)
bindSettingsListeners($);
// Sync settings modal inputs with saved settings, then apply startup UI state
initializeSettingsUIState();
updatePanelVisibility();
updateSectionVisibility();
updateGenerationModeUI();
applyTheme();
applyPanelPosition();
toggleCustomColors();
toggleAnimations();
updateFeatureTogglesVisibility();
togglePlotButtons();
initWeatherEffects();
// Initialize mobile UI
setupMobileToggle();
constrainFabToViewport();
@@ -305,7 +382,7 @@ jQuery(async () => {
// Load settings with validation
try {
loadSettings();
await loadSettings();
} catch (error) {
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
}
@@ -329,7 +406,7 @@ jQuery(async () => {
// Add extension settings to Extensions tab
try {
await addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel);
await addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel, registerExtensionEvents, unregisterAllEvents, initHistoryInjection);
} catch (error) {
console.error('[RPG Companion] Failed to add extension settings tab:', error);
}
@@ -349,6 +426,9 @@ jQuery(async () => {
initThoughtBasedExpressions();
updateFabWidgets();
updateStripWidgets();
// Ensure section visibility matches settings (equipment is hidden by CSS, needs explicit .show())
updateSectionVisibility();
renderEquipment();
} catch (error) {
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
}
@@ -370,63 +450,7 @@ jQuery(async () => {
// Register all event listeners
try {
registerAllEvents({
[event_types.MESSAGE_SENT]: onMessageSent,
[event_types.GENERATION_STARTED]: onGenerationStarted,
[event_types.MESSAGE_RECEIVED]: onMessageReceived,
[event_types.GENERATION_STOPPED]: onGenerationEnded,
[event_types.GENERATION_ENDED]: onGenerationEnded,
[event_types.CHAT_CHANGED]: [onCharacterChanged, updatePersonaAvatar, restoreCheckpointOnLoad, clearSessionAvatarPrompts, clearDebugLogs],
[event_types.CHAT_LOADED]: onChatLoaded,
[event_types.MESSAGE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPE_DELETED]: onMessageDeleted,
[event_types.MESSAGE_SWIPED]: onMessageSwiped,
[event_types.USER_MESSAGE_RENDERED]: updatePersonaAvatar,
[event_types.SETTINGS_UPDATED]: updatePersonaAvatar
});
// Use tracked event registration (onEvent) instead of direct eventSource.on()
// This ensures all handlers are tracked for cleanup by unregisterAllEvents()
onEvent(event_types.CHARACTER_MESSAGE_RENDERED, (messageId) => {
if (!extensionSettings.enabled) return;
const renderedMessage = chat[messageId];
if (renderedMessage && !renderedMessage.is_user && !renderedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_UPDATED, (messageId) => {
if (!extensionSettings.enabled) return;
const updatedMessage = chat[messageId];
if (updatedMessage && !updatedMessage.is_user && !updatedMessage.is_system) {
queueThoughtBasedExpressionsUpdate();
}
});
onEvent(event_types.MESSAGE_SWIPED, (messageIndex) => {
if (!extensionSettings.enabled) return;
const swipedMessage = chat[messageIndex];
if (swipedMessage && !swipedMessage.is_user && !swipedMessage.is_system) {
queueThoughtBasedExpressionsUpdate({ immediate: true });
}
});
onEvent(event_types.CHAT_CHANGED, () => {
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
onEvent(event_types.MESSAGE_SWIPE_DELETED, () => {
if (!extensionSettings.enabled) return;
clearThoughtBasedExpressionsCache();
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
});
registerExtensionEvents();
} catch (error) {
console.error('[RPG Companion] Event registration failed:', error);
throw error;
+1 -1
View File
@@ -58,7 +58,7 @@
</div>
<div style="margin-top: 10px; text-align: center; opacity: 0.6; font-size: 0.85em;">
v3.7.2
v3.7.4
</div>
</div>
</div>
+4 -2
View File
@@ -23,8 +23,9 @@ export const extensionFolderPath = isUserExtension
* Imported by state.js to initialize extensionSettings.
*/
export const defaultSettings = {
settingsVersion: 6, // Version number for settings migrations
settingsVersion: 7, // Version number for settings migrations
enabled: true,
debugMode: false, // Enable verbose debug logging (developer flag)
autoUpdate: false,
updateDepth: 4, // How many messages to include in the context
generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately
@@ -37,6 +38,7 @@ export const defaultSettings = {
showInventory: true, // Show inventory section (v2 system)
showEquipment: true, // Show equipment section
showQuests: true, // Show quests section
showLockIcons: true, // Show lock/unlock icons on tracker items
showThoughtsInChat: true, // Show thoughts overlay in chat
thoughtsInChatStyle: 'corner', // 'corner' or 'inline'
narratorMode: false, // Use character card as narrator instead of fixed character references
@@ -315,7 +317,7 @@ export const defaultSettings = {
// External API settings for 'external' generation mode
externalApiSettings: {
baseUrl: '', // OpenAI-compatible API base URL (e.g., "https://api.openai.com/v1")
// apiKey is NOT stored here for security. It is stored in localStorage('rpg_companion_api_key')
// apiKey is NOT stored here for security. It is stored in localStorage('rpg_companion_external_api_key')
model: '', // Model identifier (e.g., "gpt-4o-mini")
maxTokens: 8192, // Maximum tokens for generation
temperature: 0.7 // Temperature setting for generation
+12
View File
@@ -42,6 +42,18 @@ export function once(eventType, handler) {
*/
export function off(eventType, handler) {
eventSource.off(eventType, handler);
// Remove from tracking so unregisterAllEvents() doesn't hold a stale reference
const handlers = registeredHandlers.get(eventType);
if (handlers) {
const index = handlers.indexOf(handler);
if (index !== -1) {
handlers.splice(index, 1);
}
if (handlers.length === 0) {
registeredHandlers.delete(eventType);
}
}
}
/**
+2 -2
View File
@@ -559,7 +559,7 @@ function validateSettings(settings) {
* Loads the extension settings from the global settings object.
* Automatically migrates v1 inventory to v2 format if needed.
*/
export function loadSettings() {
export async function loadSettings() {
try {
const context = getContext();
const extension_settings = context.extension_settings || context.extensionSettings;
@@ -601,7 +601,7 @@ export function loadSettings() {
// Migration to version 3: Convert text trackers to JSON format
if (currentVersion < 3) {
// console.log('[RPG Companion] Migrating settings to version 3 (JSON tracker format)');
migrateToV3JSON();
await migrateToV3JSON();
extensionSettings.settingsVersion = 3;
settingsChanged = true;
}
+10 -1
View File
@@ -25,8 +25,11 @@ import { i18n } from './i18n.js';
* @param {Function} injectCheckpointButton - Add checkpoint buttons
* @param {Function} updateAllCheckpointIndicators - Update checkpoint button states
* @param {Function} removeAlternatePresentCharactersPanel - Remove alt characters panel
* @param {Function} registerExtensionEvents - (Re)register all SillyTavern event handlers
* @param {Function} unregisterAllEvents - Unregister all tracked SillyTavern event handlers
* @param {Function} initHistoryInjection - (Re)register history injection listeners
*/
export async function addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel) {
export async function addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel, registerExtensionEvents, unregisterAllEvents, initHistoryInjection) {
const settingsHtml = await renderExtensionTemplateAsync(extensionName, 'settings');
$('#extensions_settings2').append(settingsHtml);
@@ -43,12 +46,18 @@ export async function addExtensionSettings($, renderExtensionTemplateAsync, clea
clearThoughtBasedExpressionsCache();
toggleDynamicWeather(false);
// Unregister all tracked event handlers to avoid leaks while disabled
unregisterAllEvents();
$('#rpg-companion-panel').remove();
$('#rpg-mobile-toggle').remove();
$('#rpg-collapse-toggle').remove();
$('#rpg-plot-buttons').remove();
removeAlternatePresentCharactersPanel();
} else if (extensionSettings.enabled && !wasEnabled) {
// Re-register event handlers that were unregistered on disable
registerExtensionEvents();
initHistoryInjection();
await initUI();
loadChatData();
scheduleChatStateRehydration();
-32
View File
@@ -287,36 +287,4 @@ export function clearDebugLogs() {
debugLogs.length = 0;
}
/**
* Tracks jQuery delegated event handlers for cleanup.
* Each handler is stored with its namespace for targeted unbinding.
*/
const jqueryEventHandlers = [];
/**
* Register a jQuery delegated event handler for tracking.
* @param {string} event - Event type with optional namespace (e.g., 'click.rpgCompanion')
* @param {string} selector - Delegated selector
* @param {Function} handler - Event handler function
* @returns {Function} The same handler for chaining
*/
export function trackJQueryHandler(event, selector, handler) {
jqueryEventHandlers.push({ event, selector, handler });
return handler;
}
/**
* Unbinds all tracked jQuery delegated event handlers.
* Call this when the extension is disabled or the panel is destroyed.
* @param {Function} $ - jQuery function (passed from caller)
*/
export function cleanupJQueryEvents($) {
for (const { event, selector, handler } of jqueryEventHandlers) {
if (handler) {
$(document).off(event, selector, handler);
} else {
$(document).off(event, selector);
}
}
jqueryEventHandlers.length = 0;
}
+11 -8
View File
@@ -1,7 +1,10 @@
const fs = require('fs-extra');
const path = require('path');
const chokidar = require('chokidar');
const glob = require('glob');
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import chokidar from 'chokidar';
import { globSync } from 'glob';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const COMPILED_DIR = __dirname // path.join(__dirname, 'compiled');
@@ -11,7 +14,7 @@ function findUnlocalizedText() {
console.log(`\n🔎 Scanning for unlocalized text in ${srcDir}...`);
const files = glob.sync(`${srcDir}/**/*.{html,js,jsx}`, {
const files = globSync(`${srcDir}/**/*.{html,js,jsx}`, {
ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
});
@@ -101,7 +104,7 @@ function validateTranslations() {
// Get all keys from reference locale
const referenceKeys = Object.keys(translations[referenceLocale]);
console.log(`🔢 Reference locale has ${referenceKeys.size} unique keys`);
console.log(`🔢 Reference locale has ${referenceKeys.length} unique keys`);
// Track statistics
const stats = {
@@ -127,7 +130,7 @@ function validateTranslations() {
// Check for missing keys
for (const key of referenceKeys) {
if (!key in translations[locale]) {
if (!(key in translations[locale])) {
stats.missingKeys[locale].push(key);
} else {
// Check for type mismatches
@@ -146,7 +149,7 @@ function validateTranslations() {
// Check for extra keys
for (const key of localeKeys) {
if (!key in translations[referenceLocale]) {
if (!(key in translations[referenceLocale])) {
stats.extraKeys[locale].push(key);
}
}
+6 -3
View File
@@ -35,7 +35,10 @@ export const SLOTS_LIST = Object.entries(EQUIPMENT_CATEGORIES).flatMap(([type, d
*/
export function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
+1 -10
View File
@@ -115,13 +115,4 @@ export function clearDiceRoll() {
updateDiceDisplay();
}
/**
* Adds the Roll Dice quick reply button.
*/
export function addDiceQuickReply() {
// Create quick reply button if Quick Replies exist
if (window.quickReplyApi) {
// Quick Reply API integration would go here
// For now, the dice display in the sidebar serves as the button
}
}
@@ -11,7 +11,6 @@ import { currentEncounter } from '../features/encounterState.js';
import { repairJSON } from '../../utils/jsonRepair.js';
import { isPresentCharactersEnabled } from '../../utils/presentCharacters.js';
import { buildInventorySummary, generateTrackerInstructions, generateTrackerExample } from './promptBuilder.js';
import { applyLocks } from './lockManager.js';
/**
* Gets character information from the current chat
-4
View File
@@ -39,10 +39,6 @@ let currentSuppressionState = false;
// Type imports
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
// Track the latest user message we committed for to prevent duplicate commits
// when GENERATION_STARTED can fire multiple times for the same turn.
let lastCommittedUserMessageSignature = null;
// Store context map for prompt injection (used by event handlers)
let pendingContextMap = new Map();
+1 -4
View File
@@ -3,8 +3,7 @@
* Helper functions for building JSON format tracker prompts
*/
import { extensionSettings, committedTrackerData } from '../../core/state.js';
import { getContext } from '../../../../../../extensions.js';
import { extensionSettings } from '../../core/state.js';
import { getWeatherKeywordsAsPromptString } from '../ui/weatherEffects.js';
import { i18n } from '../../core/i18n.js';
@@ -39,7 +38,6 @@ function toFieldKey(name) {
* @returns {string} JSON format instruction for user stats
*/
export function buildUserStatsJSONInstruction() {
const userName = getContext().name1;
const trackerConfig = extensionSettings.trackerConfig;
const userStatsConfig = trackerConfig?.userStats;
const enabledStats = userStatsConfig?.customStats?.filter(s => s && s.enabled && s.name) || [];
@@ -184,7 +182,6 @@ export function buildInfoBoxJSONInstruction() {
* @returns {string} JSON format instruction for present characters
*/
export function buildCharactersJSONInstruction() {
const userName = getContext().name1;
const presentCharsConfig = extensionSettings.trackerConfig?.presentCharacters;
const enabledFields = presentCharsConfig?.customFields?.filter(f => f && f.enabled && f.name) || [];
const relationshipsEnabled = presentCharsConfig?.relationships?.enabled !== false;
+2 -3
View File
@@ -4,8 +4,8 @@
*/
import { getContext } from '../../../../../../extensions.js';
import { chat, getCurrentChatDetails, characters, this_chid } from '../../../../../../../script.js';
import { selected_group, getGroupMembers, getGroupChat, groups } from '../../../../../../group-chats.js';
import { chat, characters, this_chid } from '../../../../../../../script.js';
import { selected_group, getGroupMembers, groups } from '../../../../../../group-chats.js';
import { extensionSettings, committedTrackerData, FEATURE_FLAGS } from '../../core/state.js';
import {
buildUserStatsJSONInstruction,
@@ -1246,7 +1246,6 @@ export async function generateSeparateUpdatePrompt() {
// /hide command automatically handles checkpoint filtering
// Add chat history as separate user/assistant messages with per-message historical context
const recentMessages = chat.slice(-depth);
const startIndex = chat.length - depth;
const position = historyPersistence?.injectionPosition || 'assistant_message_end';
// Build a map of which messages should get context based on position setting
+1 -1
View File
@@ -26,7 +26,7 @@ export function evaluateSuppression(extensionSettings, context, data) {
let instructContent = '';
if (instructObj) {
if (typeof instructObj === 'object') {
instructContent = String(instructObj.value || instructObj || '');
instructContent = String(instructObj.value || '');
} else {
instructContent = String(instructObj);
}
+1 -1
View File
@@ -4,7 +4,7 @@
*/
import { getContext } from '../../../../../../extensions.js';
import { chat, chat_metadata, user_avatar, setExtensionPrompt, extension_prompt_types } from '../../../../../../../script.js';
import { chat, chat_metadata, user_avatar, setExtensionPrompt, extension_prompt_types, updateMessageBlock } from '../../../../../../../script.js';
// Core modules
import {
@@ -5,7 +5,6 @@
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js';
import { buildInventorySummary } from '../generation/promptBuilder.js';
import { buildUserStatsText } from '../rendering/userStats.js';
import { renderInventory, getLocationId } from '../rendering/inventory.js';
import { parseItems, serializeItems } from '../../utils/itemParser.js';
+19
View File
@@ -146,10 +146,29 @@ function generateEquipmentHTML() {
* Gets data from state/settings and updates DOM directly.
*/
export function renderEquipment() {
// Ensure showEquipment defaults to true if undefined
if (extensionSettings.showEquipment === undefined) {
extensionSettings.showEquipment = true;
}
if (!$equipmentContainer || !extensionSettings.showEquipment) {
return;
}
// Ensure equipment data structure exists (defensive initialization)
if (!extensionSettings.userStats?.equipment) {
extensionSettings.userStats = extensionSettings.userStats || {};
extensionSettings.userStats.equipment = {
items: [],
slots: {
helmet: null, ring1: null, ring2: null, ring3: null, ring4: null,
ring5: null, ring6: null, ring7: null, ring8: null, ring9: null, ring10: null,
necklace: null, bodyArmor: null, pants: null, shoes: null, gloves: null,
accessory1: null, accessory2: null, accessory3: null
}
};
}
const html = generateEquipmentHTML();
updateIfChanged($equipmentContainer, html, 'rpg-equipment');
+1 -1
View File
@@ -10,7 +10,7 @@ import {
committedTrackerData,
$infoBoxContainer
} from '../../core/state.js';
import { saveChatData, setMessageSwipeTrackerField } from '../../core/persistence.js';
import { saveChatData, saveSettings, setMessageSwipeTrackerField } from '../../core/persistence.js';
import { i18n } from '../../core/i18n.js';
import { isItemLocked } from '../generation/lockManager.js';
import { repairJSON } from '../../utils/jsonRepair.js';
+6 -3
View File
@@ -645,7 +645,10 @@ export function renderInventory() {
*/
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
+7 -3
View File
@@ -57,9 +57,13 @@ function getLockIconHtml(tracker, path) {
* @returns {string} Escaped HTML
*/
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
if (!text) return '';
return String(text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
/**
-15
View File
@@ -131,21 +131,6 @@ export function clearAllCaches() {
htmlCache.clear();
}
/**
* Wraps an existing render function with change detection.
* The wrapped function generates HTML, compares it to the cache,
* and only updates the DOM if content changed.
*
* @param {Function} renderFn - Function that returns { $container, html } or calls updateIfChanged internally
* @param {string} cacheKey - Cache key for this render target
* @returns {Function} Wrapped render function
*/
export function withChangeDetection(renderFn, cacheKey) {
return function () {
return renderFn();
};
}
/**
* Batches multiple DOM updates into a single reflow by using
* document fragment pattern. Reduces layout thrashing.
-24
View File
@@ -1301,30 +1301,6 @@ export function updateCharacterField(characterName, field, value) {
// Note: Don't call renderThoughts() here - it would overwrite the user's edits
}
/**
* Renders only the sidebar thoughts panel without updating chat bubbles
*/
function renderThoughtsSidebarOnly() {
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
return;
}
// This is a simplified version that only updates the sidebar
// Copy the rendering logic from renderThoughts but skip the updateChatThoughts call
const thoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts;
if (!thoughtsData) {
$thoughtsContainer.html('<div class="rpg-inventory-empty">' + (i18n.getTranslation('thoughts.empty') || 'No character data generated yet') + '</div>');
return;
}
// Re-render sidebar content (this would be the full logic from renderThoughts)
// For now, just call renderThoughts but set a flag
const originalShowInChat = extensionSettings.showThoughtsInChat;
extensionSettings.showThoughtsInChat = false;
renderThoughts();
extensionSettings.showThoughtsInChat = originalShowInChat;
}
/**
* Updates or removes thoughts shown in chat.
* Renders either the original corner bubbles or inline dropdown cards.
+1 -1
View File
@@ -143,7 +143,7 @@ function updateUserStatsData() {
if (!itemString) return [];
const items = itemString.split(',').map(s => s.trim()).filter(s => s);
return items.map(item => {
const qtyMatch = item.match(/^(\\d+)x\\s+(.+)$/);
const qtyMatch = item.match(/^(\d+)x\s+(.+)$/);
if (qtyMatch) {
return { name: qtyMatch[2].trim(), quantity: parseInt(qtyMatch[1]) };
}
-27
View File
@@ -339,31 +339,4 @@ function processExpandedButton(messageBlock) {
}
/**
* Update the checkpoint button in an existing menu
* @param {HTMLElement} menu - The extraMesButtons or mes_buttons container
* @param {number} messageId - The message index
*/
function updateCheckpointButtonInMenu(menu, messageId) {
if (!menu) return;
// Find the checkpoint button (either dropdown or expanded)
const existingButton = menu.querySelector('.rpg-checkpoint-button, .rpg-checkpoint-button-expanded');
if (!existingButton) return;
const isCheckpoint = isCheckpointMessage(messageId);
// Update icon
const icon = existingButton.querySelector('i');
if (icon) {
icon.className = isCheckpoint ? 'fa-solid fa-bookmark' : 'fa-regular fa-bookmark';
icon.style.color = isCheckpoint ? '#4a9eff' : '';
}
// Update tooltip
existingButton.title = isCheckpoint
? 'Clear Chapter Start'
: 'Set Chapter Start — When bookmarked, this message will count as the first message in the chat history, skipping earlier ones.';
const translationKey = isCheckpoint ? 'checkpoint.clearChapterStart' : 'checkpoint.setChapterStart';
existingButton.setAttribute('data-i18n', translationKey);
}
+3 -2
View File
@@ -6,6 +6,7 @@
import { i18n } from '../../core/i18n.js';
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
import { hexToRgba } from './theme.js';
import { repairJSON } from '../../utils/jsonRepair.js';
/**
* Helper to parse time string and calculate clock hand angles
@@ -50,7 +51,7 @@ export function updateStripWidgets() {
let infoData = null;
if (infoBox) {
try {
infoData = typeof infoBox === 'string' ? JSON.parse(infoBox) : infoBox;
infoData = typeof infoBox === 'string' ? repairJSON(infoBox) : infoBox;
} catch (e) {
console.warn('[RPG Strip Widgets] Failed to parse infoBox:', e);
}
@@ -124,7 +125,7 @@ export function updateStripWidgets() {
const userStatsData = lastGeneratedData?.userStats || committedTrackerData?.userStats;
if (userStatsData) {
try {
const parsedStats = typeof userStatsData === 'string' ? JSON.parse(userStatsData) : userStatsData;
const parsedStats = typeof userStatsData === 'string' ? repairJSON(userStatsData) : userStatsData;
if (parsedStats?.stats) {
allStats = parsedStats.stats;
}
+8 -435
View File
@@ -9,6 +9,7 @@ import { closeMobilePanelWithAnimation, updateCollapseToggleIcon } from './layou
import { setupDesktopTabs, removeDesktopTabs } from './desktop.js';
import { i18n } from '../../core/i18n.js';
import { hexToRgba } from './theme.js';
import { repairJSON } from '../../utils/jsonRepair.js';
/**
* Updates the text labels of the mobile navigation tabs based on the current language.
@@ -503,7 +504,10 @@ export function setupMobileToggle() {
// top: $panel.css('top'),
// bottom: $panel.css('top'),
// transform: $panel.css('transform'),
// visibility: $panel.css('visibility')\n // }\n // });\n setupMobileTabs();
// visibility: $panel.css('visibility')
// }
// });
setupMobileTabs();
// Set initial icon for mobile
updateCollapseToggleIcon();
// Show mobile toggle on mobile viewport
@@ -860,437 +864,6 @@ export function setupContentEditableScrolling() {
});
}
/**
* Sets up the mobile refresh button with drag functionality.
* Same pattern as mobile toggle button.
* Tap = refresh, drag = reposition
*/
export function setupRefreshButtonDrag() {
const $refreshBtn = $('#rpg-manual-update-mobile');
if ($refreshBtn.length === 0) {
console.warn('[RPG Mobile] Refresh button not found in DOM');
return;
}
// console.log('[RPG Mobile] setupRefreshButtonDrag called');
// Load and apply saved position
if (extensionSettings.mobileRefreshPosition) {
const pos = extensionSettings.mobileRefreshPosition;
// console.log('[RPG Mobile] Loading saved refresh button position:', pos);
// Apply saved position
if (pos.top) $refreshBtn.css('top', pos.top);
if (pos.right) $refreshBtn.css('right', pos.right);
if (pos.bottom) $refreshBtn.css('bottom', pos.bottom);
if (pos.left) $refreshBtn.css('left', pos.left);
// Constrain to viewport after position is applied
requestAnimationFrame(() => constrainFabToViewport($refreshBtn));
}
// Touch/drag state
let isDragging = false;
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
let buttonStartX = 0;
let buttonStartY = 0;
const LONG_PRESS_DURATION = 200;
const MOVE_THRESHOLD = 10;
let rafId = null;
let pendingX = null;
let pendingY = null;
// Update position using requestAnimationFrame
function updatePosition() {
if (pendingX !== null && pendingY !== null) {
$refreshBtn.css({
left: pendingX + 'px',
top: pendingY + 'px',
right: 'auto',
bottom: 'auto'
});
pendingX = null;
pendingY = null;
}
rafId = null;
}
// Touch start
$refreshBtn.on('touchstart', function(e) {
const touch = e.originalEvent.touches[0];
touchStartTime = Date.now();
touchStartX = touch.clientX;
touchStartY = touch.clientY;
const offset = $refreshBtn.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
isDragging = false;
});
// Touch move
$refreshBtn.on('touchmove', function(e) {
const touch = e.originalEvent.touches[0];
const deltaX = touch.clientX - touchStartX;
const deltaY = touch.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$refreshBtn.addClass('dragging');
}
if (isDragging) {
e.preventDefault();
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
const buttonWidth = $refreshBtn.outerWidth();
const buttonHeight = $refreshBtn.outerHeight();
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
pendingX = newX;
pendingY = newY;
if (!rafId) {
rafId = requestAnimationFrame(updatePosition);
}
}
});
// Touch end
$refreshBtn.on('touchend', function(e) {
if (isDragging) {
// Save new position
const offset = $refreshBtn.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.mobileRefreshPosition = newPosition;
saveSettings();
setTimeout(() => {
$refreshBtn.removeClass('dragging');
}, 50);
// Set flag to prevent click handler from firing
$refreshBtn.data('just-dragged', true);
setTimeout(() => {
$refreshBtn.data('just-dragged', false);
}, 100);
isDragging = false;
}
});
// Mouse support for desktop
let mouseDown = false;
$refreshBtn.on('mousedown', function(e) {
e.preventDefault();
touchStartTime = Date.now();
touchStartX = e.clientX;
touchStartY = e.clientY;
const offset = $refreshBtn.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
mouseDown = true;
isDragging = false;
});
$(document).on('mousemove', function(e) {
if (!mouseDown) return;
const deltaX = e.clientX - touchStartX;
const deltaY = e.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$refreshBtn.addClass('dragging');
}
if (isDragging) {
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
const buttonWidth = $refreshBtn.outerWidth();
const buttonHeight = $refreshBtn.outerHeight();
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
pendingX = newX;
pendingY = newY;
if (!rafId) {
rafId = requestAnimationFrame(updatePosition);
}
}
});
$(document).on('mouseup', function(e) {
if (mouseDown && isDragging) {
const offset = $refreshBtn.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.mobileRefreshPosition = newPosition;
saveSettings();
setTimeout(() => {
$refreshBtn.removeClass('dragging');
}, 50);
$refreshBtn.data('just-dragged', true);
setTimeout(() => {
$refreshBtn.data('just-dragged', false);
}, 100);
}
mouseDown = false;
isDragging = false;
});
}
/**
* Sets up drag functionality for the debug toggle FAB button
* Same pattern as refresh button drag
*/
export function setupDebugButtonDrag() {
const $debugBtn = $('#rpg-debug-toggle');
if ($debugBtn.length === 0) {
console.warn('[RPG Mobile] Debug button not found in DOM');
return;
}
// console.log('[RPG Mobile] setupDebugButtonDrag called');
// Load and apply saved position
if (extensionSettings.debugFabPosition) {
const pos = extensionSettings.debugFabPosition;
// console.log('[RPG Mobile] Loading saved debug button position:', pos);
// Apply saved position
if (pos.top) $debugBtn.css('top', pos.top);
if (pos.right) $debugBtn.css('right', pos.right);
if (pos.bottom) $debugBtn.css('bottom', pos.bottom);
if (pos.left) $debugBtn.css('left', pos.left);
// Constrain to viewport after position is applied
requestAnimationFrame(() => constrainFabToViewport($debugBtn));
}
// Touch/drag state
let isDragging = false;
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
let buttonStartX = 0;
let buttonStartY = 0;
const LONG_PRESS_DURATION = 200;
const MOVE_THRESHOLD = 10;
let rafId = null;
let pendingX = null;
let pendingY = null;
// Update position using requestAnimationFrame
function updatePosition() {
if (pendingX !== null && pendingY !== null) {
$debugBtn.css({
left: pendingX + 'px',
top: pendingY + 'px',
right: 'auto',
bottom: 'auto'
});
pendingX = null;
pendingY = null;
}
rafId = null;
}
// Touch start
$debugBtn.on('touchstart', function(e) {
const touch = e.originalEvent.touches[0];
touchStartTime = Date.now();
touchStartX = touch.clientX;
touchStartY = touch.clientY;
const offset = $debugBtn.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
isDragging = false;
});
// Touch move
$debugBtn.on('touchmove', function(e) {
const touch = e.originalEvent.touches[0];
const deltaX = touch.clientX - touchStartX;
const deltaY = touch.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$debugBtn.addClass('dragging');
}
if (isDragging) {
e.preventDefault();
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
const buttonWidth = $debugBtn.outerWidth();
const buttonHeight = $debugBtn.outerHeight();
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
pendingX = newX;
pendingY = newY;
if (!rafId) {
rafId = requestAnimationFrame(updatePosition);
}
}
});
// Touch end
$debugBtn.on('touchend', function(e) {
if (isDragging) {
// Save new position
const offset = $debugBtn.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.debugFabPosition = newPosition;
saveSettings();
setTimeout(() => {
$debugBtn.removeClass('dragging');
}, 50);
// Set flag to prevent click handler from firing
$debugBtn.data('just-dragged', true);
setTimeout(() => {
$debugBtn.data('just-dragged', false);
}, 100);
isDragging = false;
}
});
// Mouse support for desktop
let mouseDown = false;
$debugBtn.on('mousedown', function(e) {
e.preventDefault();
touchStartTime = Date.now();
touchStartX = e.clientX;
touchStartY = e.clientY;
const offset = $debugBtn.offset();
buttonStartX = offset.left;
buttonStartY = offset.top;
mouseDown = true;
isDragging = false;
});
$(document).on('mousemove.rpgDebugDrag', function(e) {
if (!mouseDown) return;
const deltaX = e.clientX - touchStartX;
const deltaY = e.clientY - touchStartY;
const timeSinceStart = Date.now() - touchStartTime;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) {
isDragging = true;
$debugBtn.addClass('dragging');
}
if (isDragging) {
let newX = buttonStartX + deltaX;
let newY = buttonStartY + deltaY;
const buttonWidth = $debugBtn.outerWidth();
const buttonHeight = $debugBtn.outerHeight();
const minX = 10;
const maxX = window.innerWidth - buttonWidth - 10;
const minY = 10;
const maxY = window.innerHeight - buttonHeight - 10;
newX = Math.max(minX, Math.min(maxX, newX));
newY = Math.max(minY, Math.min(maxY, newY));
pendingX = newX;
pendingY = newY;
if (!rafId) {
rafId = requestAnimationFrame(updatePosition);
}
}
});
$(document).on('mouseup.rpgDebugDrag', function(e) {
if (mouseDown && isDragging) {
const offset = $debugBtn.offset();
const newPosition = {
left: offset.left + 'px',
top: offset.top + 'px'
};
extensionSettings.debugFabPosition = newPosition;
saveSettings();
setTimeout(() => {
$debugBtn.removeClass('dragging');
}, 50);
$debugBtn.data('just-dragged', true);
setTimeout(() => {
$debugBtn.data('just-dragged', false);
}, 100);
}
mouseDown = false;
isDragging = false;
});
}
// ============================================
// FAB WIDGETS - Info display around FAB button
// ============================================
@@ -1322,7 +895,7 @@ export function updateFabWidgets() {
let infoData = null;
if (infoBox) {
try {
infoData = typeof infoBox === 'string' ? JSON.parse(infoBox) : infoBox;
infoData = typeof infoBox === 'string' ? repairJSON(infoBox) : infoBox;
} catch (e) {
console.warn('[RPG FAB Widgets] Failed to parse infoBox:', e);
}
@@ -1332,7 +905,7 @@ export function updateFabWidgets() {
let statsData = null;
if (userStats) {
try {
statsData = typeof userStats === 'string' ? JSON.parse(userStats) : userStats;
statsData = typeof userStats === 'string' ? repairJSON(userStats) : userStats;
} catch (e) {
console.warn('[RPG FAB Widgets] Failed to parse userStats:', e);
}
@@ -1444,7 +1017,7 @@ export function updateFabWidgets() {
let allStats = [];
try {
const userStatsJson = extensionSettings.userStats;
const parsedUserStats = typeof userStatsJson === 'string' ? JSON.parse(userStatsJson) : userStatsJson;
const parsedUserStats = typeof userStatsJson === 'string' ? repairJSON(userStatsJson) : userStatsJson;
if (parsedUserStats?.stats) {
allStats = parsedUserStats.stats;
}
+1 -10
View File
@@ -26,8 +26,7 @@ import { renderEquipment } from '../rendering/equipment.js';
import {
rollDice as rollDiceCore,
clearDiceRoll as clearDiceRollCore,
updateDiceDisplay as updateDiceDisplayCore,
addDiceQuickReply as addDiceQuickReplyCore
updateDiceDisplay as updateDiceDisplayCore
} from '../features/dice.js';
import { i18n } from '../../core/i18n.js';
@@ -578,14 +577,6 @@ export function updateDiceDisplay() {
updateDiceDisplayCore();
}
/**
* Adds the Roll Dice quick reply button.
* Backwards compatible wrapper for dice module.
*/
export function addDiceQuickReply() {
addDiceQuickReplyCore();
}
/**
* Returns the SettingsModal instance for external use
* @returns {SettingsModal} The global SettingsModal instance
+659 -173
View File
@@ -4,12 +4,39 @@
* Extracted from index.js initUI() to reduce main entry point size.
*/
import { extensionSettings, updateExtensionSettings } from '../../core/state.js';
import { saveSettings } from '../../core/persistence.js';
/* eslint-disable no-unused-vars */
/* These functions receive jQuery-wrapped DOM elements from initUI() */
/* eslint-disable no-unused-vars */
import { getContext } from '../../../../../../extensions.js';
import { extensionSettings, $musicPlayerContainer } from '../../core/state.js';
import { saveSettings, commitTrackerDataFromPriorMessage } from '../../core/persistence.js';
import {
applyPanelPosition,
updateGenerationModeUI,
updateSectionVisibility,
togglePlotButtons
} from './layout.js';
import { renderThoughts, updateChatThoughts } from '../rendering/thoughts.js';
import { renderUserStats } from '../rendering/userStats.js';
import { renderInfoBox } from '../rendering/infoBox.js';
import { renderInventory } from '../rendering/inventory.js';
import { renderEquipment } from '../rendering/equipment.js';
import { renderQuests } from '../rendering/quests.js';
import { renderMusicPlayer } from '../rendering/musicPlayer.js';
import { toggleDynamicWeather } from './weatherEffects.js';
import {
applyTheme,
updateSettingsPopupTheme,
toggleCustomColors,
applyCustomTheme,
updateFeatureTogglesVisibility
} from './theme.js';
import {
onAlternatePresentCharactersVisibilityChanged,
onThoughtBasedExpressionsSettingChanged,
onHideDefaultExpressionDisplaySettingChanged
} from '../integration/thoughtBasedExpressions.js';
import { updateStripWidgets } from './desktop.js';
import { updateFabWidgets } from './mobile.js';
import { updateDiceDisplay, getSettingsModal } from './modals.js';
import { updateRPGData, testExternalAPIConnection } from '../generation/apiClient.js';
/**
* Bind all settings panel event listeners.
@@ -29,6 +56,7 @@ export function bindSettingsListeners($) {
extensionSettings.panelPosition = String($(this).val());
saveSettings();
applyPanelPosition();
// Recreate thought bubbles to update their position
updateChatThoughts();
});
@@ -47,11 +75,11 @@ export function bindSettingsListeners($) {
});
// Section visibility toggles
bindSectionToggle($, 'rpg-toggle-user-stats', 'showUserStats', () => {});
bindSectionToggle($, 'rpg-toggle-info-box', 'showInfoBox', () => {});
bindSectionToggle($, 'rpg-toggle-inventory', 'showInventory', () => {});
bindSectionToggle($, 'rpg-toggle-equipment', 'showEquipment', () => {});
bindSectionToggle($, 'rpg-toggle-quests', 'showQuests', () => {});
bindSectionToggle($, 'rpg-toggle-user-stats', 'showUserStats');
bindSectionToggle($, 'rpg-toggle-info-box', 'showInfoBox');
bindSectionToggle($, 'rpg-toggle-inventory', 'showInventory');
bindSectionToggle($, 'rpg-toggle-equipment', 'showEquipment');
bindSectionToggle($, 'rpg-toggle-quests', 'showQuests');
// Thoughts section with render callback
$('#rpg-toggle-thoughts').on('change', function() {
@@ -160,25 +188,12 @@ export function bindSettingsListeners($) {
saveSettings();
});
// Dismiss holiday promo
$('#rpg-dismiss-promo').on('click', function() {
extensionSettings.dismissedHolidayPromo = true;
saveSettings();
$('#rpg-holiday-promo').fadeOut(300);
});
// Skip injections for guided mode
$('#rpg-skip-guided-mode').on('change', function() {
extensionSettings.skipInjectionsForGuided = String($(this).val());
saveSettings();
});
// Save tracker history
$('#rpg-save-tracker-history').on('change', function() {
extensionSettings.saveTrackerHistory = $(this).prop('checked');
saveSettings();
});
// Randomized plot
$('#rpg-toggle-randomized-plot').on('change', function() {
extensionSettings.enableRandomizedPlot = $(this).prop('checked');
@@ -200,7 +215,7 @@ export function bindSettingsListeners($) {
}
extensionSettings.encounterSettings.enabled = $(this).prop('checked');
saveSettings();
togglePlotButtons();
togglePlotButtons(); // This also controls encounter button visibility
});
// Encounter history depth
@@ -222,171 +237,642 @@ export function bindSettingsListeners($) {
saveSettings();
});
// Combat narrative style settings
bindCombatNarrativeSetting($, 'rpg-combat-tense', 'tense');
bindCombatNarrativeSetting($, 'rpg-combat-person', 'person');
bindCombatNarrativeSetting($, 'rpg-combat-narration', 'narration');
bindCombatNarrativeSetting($, 'rpg-combat-pov', 'pov');
// Summary narrative style settings
bindSummaryNarrativeSetting($, 'rpg-summary-tense', 'tense');
bindSummaryNarrativeSetting($, 'rpg-summary-person', 'person');
bindSummaryNarrativeSetting($, 'rpg-summary-narration', 'narration');
bindSummaryNarrativeSetting($, 'rpg-summary-pov', 'pov');
// Theme selector
$('#rpg-theme-select').on('change', function() {
extensionSettings.theme = String($(this).val());
saveSettings();
applyTheme();
updateSettingsPopupTheme();
});
// Custom theme toggle
$('#rpg-toggle-custom-theme').on('change', function() {
extensionSettings.enableCustomTheme = $(this).prop('checked');
saveSettings();
toggleCustomColors();
});
// Custom theme color pickers
$('#rpg-custom-bg-color').on('input', function() {
extensionSettings.customBgColor = $(this).val();
saveSettings();
applyCustomTheme();
});
$('#rpg-custom-accent-color').on('input', function() {
extensionSettings.customAccentColor = $(this).val();
saveSettings();
applyCustomTheme();
});
$('#rpg-custom-text-color').on('input', function() {
extensionSettings.customTextColor = $(this).val();
saveSettings();
applyCustomTheme();
});
$('#rpg-custom-highlight-color').on('input', function() {
extensionSettings.customHighlightColor = $(this).val();
saveSettings();
applyCustomTheme();
});
$('#rpg-custom-border-color').on('input', function() {
extensionSettings.customBorderColor = $(this).val();
saveSettings();
applyCustomTheme();
});
// Custom reset button
$('#rpg-reset-custom-theme').on('click', function() {
extensionSettings.customBgColor = '';
extensionSettings.customAccentColor = '';
extensionSettings.customTextColor = '';
extensionSettings.customHighlightColor = '';
extensionSettings.customBorderColor = '';
saveSettings();
applyTheme();
updateSettingsPopupTheme();
});
// Animations toggle
$('#rpg-toggle-animations').on('change', function() {
extensionSettings.enableAnimations = $(this).prop('checked');
saveSettings();
toggleAnimations();
});
// Panel width
$('#rpg-panel-width').on('change', function() {
extensionSettings.panelWidth = $(this).val();
saveSettings();
updatePanelVisibility();
});
// Feature toggles visibility
$('#rpg-toggle-feature-toggles').on('change', function() {
extensionSettings.showFeatureToggles = $(this).prop('checked');
// Feature toggle visibility controls
$('#rpg-toggle-show-html-toggle').on('change', function() {
extensionSettings.showHtmlToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
// Weather sub-options visibility toggle
$('#rpg-toggle-dynamic-weather-suboptions').on('change', function() {
extensionSettings.showDynamicWeatherToggle = $(this).prop('checked');
$('#rpg-toggle-show-dialogue-coloring-toggle').on('change', function() {
extensionSettings.showDialogueColoringToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-deception-toggle').on('change', function() {
extensionSettings.showDeceptionToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-omniscience-toggle').on('change', function() {
extensionSettings.showOmniscienceToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-cyoa-toggle').on('change', function() {
extensionSettings.showCYOAToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-spotify-toggle').on('change', function() {
extensionSettings.showSpotifyToggle = $(this).prop('checked');
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-dynamic-weather-toggle').on('change', function() {
extensionSettings.showDynamicWeatherToggle = $(this).prop('checked');
// Also disable the feature when hiding the toggle
if (!extensionSettings.showDynamicWeatherToggle) {
extensionSettings.enableDynamicWeather = false;
$('#rpg-toggle-dynamic-weather').prop('checked', false);
toggleDynamicWeather(false);
}
saveSettings();
updateFeatureTogglesVisibility();
updateWeatherSubOptionsVisibility();
});
// Strip widgets
bindStripWidgetToggle($, 'rpg-strip-widget-weather', 'weatherIcon');
bindStripWidgetToggle($, 'rpg-strip-widget-clock', 'clock');
bindStripWidgetToggle($, 'rpg-strip-widget-date', 'date');
bindStripWidgetToggle($, 'rpg-strip-widget-location', 'location');
bindStripWidgetToggle($, 'rpg-strip-widget-stats', 'stats');
bindStripWidgetToggle($, 'rpg-strip-widget-attributes', 'attributes');
// Mobile panel position
$('#rpg-mobile-position-select').on('change', function() {
extensionSettings.mobilePanelPosition = String($(this).val());
saveSettings();
updateMobilePanelPosition();
// Weather sub-options (background and foreground) - radio buttons
$('#rpg-toggle-weather-background').on('change', function() {
if ($(this).prop('checked')) {
extensionSettings.weatherBackground = true;
extensionSettings.weatherForeground = false;
saveSettings();
// Re-apply weather effect
if (extensionSettings.enableDynamicWeather) {
toggleDynamicWeather(false);
toggleDynamicWeather(true);
}
}
});
$('#rpg-toggle-weather-foreground').on('change', function() {
if ($(this).prop('checked')) {
extensionSettings.weatherBackground = false;
extensionSettings.weatherForeground = true;
saveSettings();
// Re-apply weather effect
if (extensionSettings.enableDynamicWeather) {
toggleDynamicWeather(false);
toggleDynamicWeather(true);
}
}
});
$('#rpg-toggle-show-narrator-mode').on('change', function() {
extensionSettings.showNarratorMode = $(this).prop('checked');
// Also disable the feature when hiding the toggle
if (!extensionSettings.showNarratorMode) {
extensionSettings.narratorMode = false;
$('#rpg-toggle-narrator').prop('checked', false);
}
saveSettings();
updateFeatureTogglesVisibility();
});
$('#rpg-toggle-show-auto-avatars').on('change', function() {
extensionSettings.showAutoAvatars = $(this).prop('checked');
// Also disable the feature when hiding the toggle
if (!extensionSettings.showAutoAvatars) {
extensionSettings.autoGenerateAvatars = false;
$('#rpg-toggle-auto-avatars-panel').prop('checked', false);
}
saveSettings();
updateFeatureTogglesVisibility();
});
// Auto avatar generation panel toggle
$('#rpg-toggle-auto-avatars-panel').on('change', function() {
extensionSettings.autoGenerateAvatars = $(this).prop('checked');
saveSettings();
// Re-render thoughts to update tooltips (regenerate vs delete)
renderThoughts();
});
// Dice display toggle
$('#rpg-toggle-dice-display').on('change', function() {
extensionSettings.showDiceDisplay = $(this).prop('checked');
saveSettings();
updateDiceDisplay();
});
// Mobile FAB Widget toggles - simplified, no position saving (auto-positioned)
$('#rpg-toggle-fab-widgets-enabled').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
extensionSettings.mobileFabWidgets.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
$('#rpg-fab-widget-options').toggle(extensionSettings.mobileFabWidgets.enabled);
});
$('#rpg-toggle-fab-weather-icon').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.weatherIcon) extensionSettings.mobileFabWidgets.weatherIcon = {};
extensionSettings.mobileFabWidgets.weatherIcon.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-weather-desc').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.weatherDesc) extensionSettings.mobileFabWidgets.weatherDesc = {};
extensionSettings.mobileFabWidgets.weatherDesc.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-clock').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.clock) extensionSettings.mobileFabWidgets.clock = {};
extensionSettings.mobileFabWidgets.clock.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-date').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.date) extensionSettings.mobileFabWidgets.date = {};
extensionSettings.mobileFabWidgets.date.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-location').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.location) extensionSettings.mobileFabWidgets.location = {};
extensionSettings.mobileFabWidgets.location.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-stats').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.stats) extensionSettings.mobileFabWidgets.stats = {};
extensionSettings.mobileFabWidgets.stats.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
$('#rpg-toggle-fab-attributes').on('change', function() {
if (!extensionSettings.mobileFabWidgets) extensionSettings.mobileFabWidgets = {};
if (!extensionSettings.mobileFabWidgets.attributes) extensionSettings.mobileFabWidgets.attributes = {};
extensionSettings.mobileFabWidgets.attributes.enabled = $(this).prop('checked');
saveSettings();
updateFabWidgets();
});
// Desktop Strip Widget toggles
$('#rpg-toggle-strip-widgets-enabled').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
extensionSettings.desktopStripWidgets.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
$('#rpg-strip-widget-options').toggle(extensionSettings.desktopStripWidgets.enabled);
});
$('#rpg-toggle-strip-weather-icon').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.weatherIcon) extensionSettings.desktopStripWidgets.weatherIcon = {};
extensionSettings.desktopStripWidgets.weatherIcon.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-clock').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.clock) extensionSettings.desktopStripWidgets.clock = {};
extensionSettings.desktopStripWidgets.clock.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-date').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.date) extensionSettings.desktopStripWidgets.date = {};
extensionSettings.desktopStripWidgets.date.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-location').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.location) extensionSettings.desktopStripWidgets.location = {};
extensionSettings.desktopStripWidgets.location.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-stats').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.stats) extensionSettings.desktopStripWidgets.stats = {};
extensionSettings.desktopStripWidgets.stats.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
$('#rpg-toggle-strip-attributes').on('change', function() {
if (!extensionSettings.desktopStripWidgets) extensionSettings.desktopStripWidgets = {};
if (!extensionSettings.desktopStripWidgets.attributes) extensionSettings.desktopStripWidgets.attributes = {};
extensionSettings.desktopStripWidgets.attributes.enabled = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
// Manual update button
$('#rpg-manual-update').on('click', async function() {
if (!extensionSettings.enabled) {
return;
}
const currentChat = getContext().chat;
let lastAssistantIndex = -1;
for (let i = currentChat.length - 1; i >= 0; i--) {
if (!currentChat[i].is_user && !currentChat[i].is_system) {
lastAssistantIndex = i;
break;
}
}
if (lastAssistantIndex !== -1) {
commitTrackerDataFromPriorMessage(lastAssistantIndex);
}
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
});
// Strip widget refresh button - same functionality as main refresh button
$('#rpg-strip-refresh').on('click', async function() {
if (!extensionSettings.enabled) {
return;
}
const currentChat = getContext().chat;
let lastAssistantIndex = -1;
for (let i = currentChat.length - 1; i >= 0; i--) {
if (!currentChat[i].is_user && !currentChat[i].is_system) {
lastAssistantIndex = i;
break;
}
}
if (lastAssistantIndex !== -1) {
commitTrackerDataFromPriorMessage(lastAssistantIndex);
}
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
});
// Stat bar colors
$('#rpg-stat-bar-color-low').on('change', function() {
extensionSettings.statBarColorLow = String($(this).val());
saveSettings();
renderUserStats(); // Re-render with new colors
});
$('#rpg-stat-bar-color-low-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.statBarColorLowOpacity = opacity;
$('#rpg-stat-bar-color-low-opacity-value').text(opacity + '%');
renderUserStats();
}).on('change', function() {
saveSettings();
});
$('#rpg-stat-bar-color-high').on('change', function() {
extensionSettings.statBarColorHigh = String($(this).val());
saveSettings();
renderUserStats(); // Re-render with new colors
});
$('#rpg-stat-bar-color-high-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.statBarColorHighOpacity = opacity;
$('#rpg-stat-bar-color-high-opacity-value').text(opacity + '%');
renderUserStats();
}).on('change', function() {
saveSettings();
});
// Theme selection
$('#rpg-theme-select').on('change', function() {
extensionSettings.theme = String($(this).val());
saveSettings();
applyTheme();
toggleCustomColors();
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
updateChatThoughts(); // Recreate thought bubbles with new theme
});
// Custom color pickers
$('#rpg-custom-bg').on('change', function() {
extensionSettings.customColors.bg = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-bg-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.customColors.bgOpacity = opacity;
$('#rpg-custom-bg-opacity-value').text(opacity + '%');
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
}).on('change', function() {
saveSettings();
});
$('#rpg-custom-accent').on('change', function() {
extensionSettings.customColors.accent = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-accent-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.customColors.accentOpacity = opacity;
$('#rpg-custom-accent-opacity-value').text(opacity + '%');
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
}).on('change', function() {
saveSettings();
});
$('#rpg-custom-text').on('change', function() {
extensionSettings.customColors.text = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-text-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.customColors.textOpacity = opacity;
$('#rpg-custom-text-opacity-value').text(opacity + '%');
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
}).on('change', function() {
saveSettings();
});
$('#rpg-custom-highlight').on('change', function() {
extensionSettings.customColors.highlight = String($(this).val());
saveSettings();
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
});
$('#rpg-custom-highlight-opacity').on('input', function() {
const opacity = Number($(this).val());
extensionSettings.customColors.highlightOpacity = opacity;
$('#rpg-custom-highlight-opacity-value').text(opacity + '%');
if (extensionSettings.theme === 'custom') {
applyCustomTheme();
updateSettingsPopupTheme(getSettingsModal());
updateChatThoughts();
}
}).on('change', function() {
saveSettings();
});
// External API settings event handlers
$('#rpg-external-base-url').on('change', function() {
if (!extensionSettings.externalApiSettings) {
extensionSettings.externalApiSettings = {
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
};
}
extensionSettings.externalApiSettings.baseUrl = String($(this).val()).trim();
saveSettings();
});
$('#rpg-external-api-key').on('change', function() {
// Securely store API key in localStorage instead of shared extension settings
const apiKey = String($(this).val()).trim();
localStorage.setItem('rpg_companion_external_api_key', apiKey);
// Ensure the externalApiSettings object exists, but don't store the key in it
if (!extensionSettings.externalApiSettings) {
extensionSettings.externalApiSettings = {
baseUrl: '', model: '', maxTokens: 8192, temperature: 0.7
};
saveSettings();
}
});
$('#rpg-external-model').on('change', function() {
if (!extensionSettings.externalApiSettings) {
extensionSettings.externalApiSettings = {
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
};
}
extensionSettings.externalApiSettings.model = String($(this).val()).trim();
saveSettings();
});
$('#rpg-external-max-tokens').on('change', function() {
if (!extensionSettings.externalApiSettings) {
extensionSettings.externalApiSettings = {
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
};
}
extensionSettings.externalApiSettings.maxTokens = parseInt(String($(this).val()));
saveSettings();
});
$('#rpg-external-temperature').on('change', function() {
if (!extensionSettings.externalApiSettings) {
extensionSettings.externalApiSettings = {
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
};
}
extensionSettings.externalApiSettings.temperature = parseFloat(String($(this).val()));
saveSettings();
});
$('#rpg-toggle-api-key-visibility').on('click', function() {
const $input = $('#rpg-external-api-key');
const type = $input.attr('type') === 'password' ? 'text' : 'password';
$input.attr('type', type);
$(this).find('i').toggleClass('fa-eye fa-eye-slash');
});
$('#rpg-test-external-api').on('click', async function() {
const $result = $('#rpg-external-api-test-result');
const $btn = $(this);
const originalText = $btn.html();
$btn.html('<i class="fa-solid fa-spinner fa-spin"></i> Testing...').prop('disabled', true);
$result.hide().removeClass('rpg-success-message rpg-error-message');
try {
const result = await testExternalAPIConnection();
if (result.success) {
$result.addClass('rpg-success-message')
.html(`<i class="fa-solid fa-check-circle"></i> ${result.message}`)
.slideDown();
toastr.success(result.message);
} else {
$result.addClass('rpg-error-message')
.html(`<i class="fa-solid fa-exclamation-circle"></i> ${result.message}`)
.slideDown();
toastr.error(result.message);
}
} catch (error) {
$result.addClass('rpg-error-message')
.html(`<i class="fa-solid fa-exclamation-circle"></i> Error: ${error.message}`)
.slideDown();
} finally {
$btn.html(originalText).prop('disabled', false);
}
});
}
/**
* Synchronize all settings modal inputs with the currently saved settings.
* Called once during UI initialization after the template is in the DOM.
*/
export function initializeSettingsUIState() {
// Initialize UI state (enable/disable is in Extensions tab)
$('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate);
$('#rpg-position-select').val(extensionSettings.panelPosition);
$('#rpg-update-depth').val(extensionSettings.updateDepth);
$('#rpg-toggle-user-stats').prop('checked', extensionSettings.showUserStats);
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
$('#rpg-toggle-alt-present-characters').prop('checked', extensionSettings.showAlternatePresentCharactersPanel ?? false);
$('#rpg-toggle-thought-based-expressions').prop('checked', extensionSettings.enableThoughtBasedExpressions === true);
$('#rpg-toggle-hide-default-expressions').prop('checked', extensionSettings.hideDefaultExpressionDisplay === true);
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
$('#rpg-toggle-equipment').prop('checked', extensionSettings.showEquipment);
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
$('#rpg-toggle-lock-icons').prop('checked', extensionSettings.showLockIcons ?? true);
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
$('#rpg-toggle-inline-thoughts').prop('checked', (extensionSettings.thoughtsInChatStyle || 'corner') === 'inline');
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
$('#rpg-toggle-dialogue-coloring').prop('checked', extensionSettings.enableDialogueColoring);
$('#rpg-toggle-deception').prop('checked', extensionSettings.enableDeceptionSystem ?? false);
$('#rpg-toggle-omniscience').prop('checked', extensionSettings.enableOmniscienceFilter ?? false);
$('#rpg-toggle-cyoa').prop('checked', extensionSettings.enableCYOA ?? false);
$('#rpg-toggle-spotify-music').prop('checked', extensionSettings.enableSpotifyMusic);
$('#rpg-toggle-dynamic-weather').prop('checked', extensionSettings.enableDynamicWeather);
$('#rpg-toggle-narrator').prop('checked', extensionSettings.narratorMode);
// Feature toggle visibility settings
$('#rpg-toggle-show-html-toggle').prop('checked', extensionSettings.showHtmlToggle ?? true);
$('#rpg-toggle-show-dialogue-coloring-toggle').prop('checked', extensionSettings.showDialogueColoringToggle ?? true);
$('#rpg-toggle-show-deception-toggle').prop('checked', extensionSettings.showDeceptionToggle ?? true);
$('#rpg-toggle-show-omniscience-toggle').prop('checked', extensionSettings.showOmniscienceToggle ?? true);
$('#rpg-toggle-show-cyoa-toggle').prop('checked', extensionSettings.showCYOAToggle ?? true);
$('#rpg-toggle-show-spotify-toggle').prop('checked', extensionSettings.showSpotifyToggle ?? true);
$('#rpg-toggle-show-dynamic-weather-toggle').prop('checked', extensionSettings.showDynamicWeatherToggle ?? true);
$('#rpg-toggle-weather-background').prop('checked', extensionSettings.weatherBackground ?? true);
$('#rpg-toggle-weather-foreground').prop('checked', extensionSettings.weatherForeground ?? false);
$('#rpg-toggle-show-narrator-mode').prop('checked', extensionSettings.showNarratorMode ?? true);
$('#rpg-toggle-show-auto-avatars').prop('checked', extensionSettings.showAutoAvatars ?? true);
$('#rpg-toggle-randomized-plot').prop('checked', extensionSettings.enableRandomizedPlot ?? true);
$('#rpg-toggle-natural-plot').prop('checked', extensionSettings.enableNaturalPlot ?? true);
$('#rpg-toggle-encounters').prop('checked', extensionSettings.encounterSettings?.enabled ?? true);
$('#rpg-encounter-history-depth').val(extensionSettings.encounterSettings?.historyDepth ?? 8);
$('#rpg-toggle-autosave-logs').prop('checked', extensionSettings.encounterSettings?.autoSaveLogs ?? true);
// Initialize avatar options (panel toggle)
$('#rpg-toggle-auto-avatars-panel').prop('checked', extensionSettings.autoGenerateAvatars || false);
$('#rpg-toggle-dice-display').prop('checked', extensionSettings.showDiceDisplay);
// Initialize Mobile FAB Widget checkboxes
const fabWidgets = extensionSettings.mobileFabWidgets || {};
$('#rpg-toggle-fab-widgets-enabled').prop('checked', fabWidgets.enabled || false);
$('#rpg-toggle-fab-weather-icon').prop('checked', fabWidgets.weatherIcon?.enabled || false);
$('#rpg-toggle-fab-weather-desc').prop('checked', fabWidgets.weatherDesc?.enabled || false);
$('#rpg-toggle-fab-clock').prop('checked', fabWidgets.clock?.enabled || false);
$('#rpg-toggle-fab-date').prop('checked', fabWidgets.date?.enabled || false);
$('#rpg-toggle-fab-location').prop('checked', fabWidgets.location?.enabled || false);
$('#rpg-toggle-fab-stats').prop('checked', fabWidgets.stats?.enabled || false);
$('#rpg-toggle-fab-attributes').prop('checked', fabWidgets.attributes?.enabled || false);
// Toggle visibility of widget options based on master toggle
$('#rpg-fab-widget-options').toggle(fabWidgets.enabled || false);
// Initialize Desktop Strip Widget checkboxes
const stripWidgets = extensionSettings.desktopStripWidgets || {};
$('#rpg-toggle-strip-widgets-enabled').prop('checked', stripWidgets.enabled || false);
$('#rpg-toggle-strip-weather-icon').prop('checked', stripWidgets.weatherIcon?.enabled ?? true);
$('#rpg-toggle-strip-clock').prop('checked', stripWidgets.clock?.enabled ?? true);
$('#rpg-toggle-strip-date').prop('checked', stripWidgets.date?.enabled ?? true);
$('#rpg-toggle-strip-location').prop('checked', stripWidgets.location?.enabled ?? true);
$('#rpg-toggle-strip-stats').prop('checked', stripWidgets.stats?.enabled ?? true);
$('#rpg-toggle-strip-attributes').prop('checked', stripWidgets.attributes?.enabled ?? true);
// Toggle visibility of strip widget options based on master toggle
$('#rpg-strip-widget-options').toggle(stripWidgets.enabled || false);
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
$('#rpg-stat-bar-color-low-opacity').val(extensionSettings.statBarColorLowOpacity ?? 100);
$('#rpg-stat-bar-color-low-opacity-value').text((extensionSettings.statBarColorLowOpacity ?? 100) + '%');
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
$('#rpg-stat-bar-color-high-opacity').val(extensionSettings.statBarColorHighOpacity ?? 100);
$('#rpg-stat-bar-color-high-opacity-value').text((extensionSettings.statBarColorHighOpacity ?? 100) + '%');
$('#rpg-theme-select').val(extensionSettings.theme);
$('#rpg-custom-bg').val(extensionSettings.customColors.bg);
$('#rpg-custom-bg-opacity').val(extensionSettings.customColors.bgOpacity ?? 100);
$('#rpg-custom-bg-opacity-value').text((extensionSettings.customColors.bgOpacity ?? 100) + '%');
$('#rpg-custom-accent').val(extensionSettings.customColors.accent);
$('#rpg-custom-accent-opacity').val(extensionSettings.customColors.accentOpacity ?? 100);
$('#rpg-custom-accent-opacity-value').text((extensionSettings.customColors.accentOpacity ?? 100) + '%');
$('#rpg-custom-text').val(extensionSettings.customColors.text);
$('#rpg-custom-text-opacity').val(extensionSettings.customColors.textOpacity ?? 100);
$('#rpg-custom-text-opacity-value').text((extensionSettings.customColors.textOpacity ?? 100) + '%');
$('#rpg-custom-highlight').val(extensionSettings.customColors.highlight);
$('#rpg-custom-highlight-opacity').val(extensionSettings.customColors.highlightOpacity ?? 100);
$('#rpg-custom-highlight-opacity-value').text((extensionSettings.customColors.highlightOpacity ?? 100) + '%');
// Initialize External API settings values
if (extensionSettings.externalApiSettings) {
$('#rpg-external-base-url').val(extensionSettings.externalApiSettings.baseUrl || '');
// Load API Key from secure localStorage
const storedApiKey = localStorage.getItem('rpg_companion_external_api_key') || '';
$('#rpg-external-api-key').val(storedApiKey);
$('#rpg-external-model').val(extensionSettings.externalApiSettings.model || '');
$('#rpg-external-max-tokens').val(extensionSettings.externalApiSettings.maxTokens || 8192);
$('#rpg-external-temperature').val(extensionSettings.externalApiSettings.temperature ?? 0.7);
}
$('#rpg-generation-mode').val(extensionSettings.generationMode);
$('#rpg-skip-guided-mode').val(extensionSettings.skipInjectionsForGuided);
// Weather sub-options visibility follows the dynamic weather toggle visibility setting
updateWeatherSubOptionsVisibility();
}
/**
* Helper: Bind a simple section visibility toggle
*/
function bindSectionToggle($, elementId, settingKey, callback) {
function bindSectionToggle($, elementId, settingKey) {
$(`#${elementId}`).on('change', function() {
extensionSettings[settingKey] = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
if (callback) callback();
});
}
/**
* Helper: Bind combat narrative setting
*/
function bindCombatNarrativeSetting($, elementId, key) {
$(`#${elementId}`).on('change', function() {
if (!extensionSettings.encounterSettings) {
extensionSettings.encounterSettings = {};
}
if (!extensionSettings.encounterSettings.combatNarrative) {
extensionSettings.encounterSettings.combatNarrative = {};
}
extensionSettings.encounterSettings.combatNarrative[key] = $(this).val();
saveSettings();
});
}
/**
* Helper: Bind summary narrative setting
*/
function bindSummaryNarrativeSetting($, elementId, key) {
$(`#${elementId}`).on('change', function() {
if (!extensionSettings.encounterSettings) {
extensionSettings.encounterSettings = {};
}
if (!extensionSettings.encounterSettings.summaryNarrative) {
extensionSettings.encounterSettings.summaryNarrative = {};
}
extensionSettings.encounterSettings.summaryNarrative[key] = $(this).val();
saveSettings();
});
}
/**
* Helper: Bind strip widget toggle
*/
function bindStripWidgetToggle($, elementId, widgetKey) {
$(`#${elementId}`).on('change', function() {
if (!extensionSettings.stripWidgets) {
extensionSettings.stripWidgets = { weatherIcon: true, clock: true, date: true, location: true, stats: true, attributes: true };
}
extensionSettings.stripWidgets[widgetKey] = $(this).prop('checked');
saveSettings();
updateStripWidgets();
});
}
@@ -402,4 +888,4 @@ export function updateWeatherSubOptionsVisibility() {
} else {
$weatherSubOptions.hide();
}
}
}
+1 -1
View File
@@ -1671,7 +1671,7 @@ function setupHistoryPersistenceListeners() {
messageCount: 5,
injectionPosition: 'assistant_message_end',
contextPreamble: '',
externalApiOnly: false
sendAllEnabledOnRefresh: false
};
}
+1 -1
View File
@@ -225,7 +225,7 @@ function getCurrentWeather() {
// Try to parse as JSON first (new format)
try {
const parsed = typeof infoBoxData === 'string' ? JSON.parse(infoBoxData) : infoBoxData;
const parsed = typeof infoBoxData === 'string' ? repairJSON(infoBoxData) : infoBoxData;
if (parsed && parsed.weather) {
// Return the forecast text from the weather object
return parsed.weather.forecast || parsed.weather.emoji || null;
+1 -1
View File
@@ -10,7 +10,7 @@
* intercepts the raw fetch response as a fallback.
*/
import { generateRaw } from '../../../../../../../script.js';
import { generateRaw } from '../../../../../../script.js';
/**
* Extracts text from any API response shape (Anthropic content-block arrays,
+1 -1
View File
@@ -1114,7 +1114,7 @@
Injected when "Enable Dialogue Coloring" is enabled. Affects all generation modes.
</small>
<textarea id="rpg-prompt-dialogue-coloring" class="rpg-prompt-textarea" rows="4"></textarea>
<button class="menu_button rpg-restore-prompt-btn" data-prompt="dialogue-coloring" style="margin-top: 8px;">
<button class="menu_button rpg-restore-prompt-btn" data-prompt="dialogueColoring" style="margin-top: 8px;">
<i class="fa-solid fa-rotate-left"></i>&nbsp;<span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
</button>
</div>