Compare commits
8
Commits
e4bb48d9ea
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03400ad5d5 | ||
|
|
34553c18f7 | ||
|
|
6f906d0d97 | ||
|
|
3b78f284a1 | ||
|
|
6e816e1436 | ||
|
|
28ef82aae7 | ||
|
|
59b125fd2a | ||
|
|
4bd29a207a |
@@ -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`
|
||||
@@ -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(() => {
|
||||
@@ -208,168 +207,11 @@ function updateDynamicLabels() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI for the extension.
|
||||
* 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).
|
||||
*/
|
||||
async function initUI() {
|
||||
await i18n.init();
|
||||
|
||||
if (!extensionSettings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load and inject the HTML template
|
||||
const templateHtml = await renderExtensionTemplateAsync(extensionName, 'template');
|
||||
$('body').append(templateHtml);
|
||||
|
||||
// Add mobile toggle button (FAB)
|
||||
const theme = extensionSettings.theme || 'default';
|
||||
const mobileToggleHtml = `
|
||||
<button id="rpg-mobile-toggle" class="rpg-mobile-toggle" data-theme="${theme}" title="Toggle RPG Panel">
|
||||
<i class="fa-solid fa-dice-d20"></i>
|
||||
</button>
|
||||
`;
|
||||
$('body').append(mobileToggleHtml);
|
||||
|
||||
if (window.innerWidth > 1000) {
|
||||
$('#rpg-mobile-toggle').hide();
|
||||
}
|
||||
|
||||
// Cache UI elements using state setters
|
||||
// Clear stale DOM references first to prevent memory leaks on panel rebuild
|
||||
clearDomCache();
|
||||
setPanelContainer($('#rpg-companion-panel'));
|
||||
setUserStatsContainer($('#rpg-user-stats'));
|
||||
setInfoBoxContainer($('#rpg-info-box'));
|
||||
setThoughtsContainer($('#rpg-thoughts'));
|
||||
setInventoryContainer($('#rpg-inventory'));
|
||||
setEquipmentContainer($('#rpg-equipment'));
|
||||
setQuestsContainer($('#rpg-quests'));
|
||||
setMusicPlayerContainer($('#rpg-music-player'));
|
||||
|
||||
// Re-apply translations to catch all new elements from the template
|
||||
i18n.applyTranslations(document.body);
|
||||
|
||||
// Bind all settings event listeners (extracted to settingsListeners.js)
|
||||
bindSettingsListeners($);
|
||||
|
||||
// Initialize mobile UI
|
||||
setupMobileToggle();
|
||||
constrainFabToViewport();
|
||||
setupMobileTabs();
|
||||
setupMobileKeyboardHandling();
|
||||
setupContentEditableScrolling();
|
||||
|
||||
// Initialize desktop UI
|
||||
setupDesktopTabs();
|
||||
|
||||
// Initialize collapse toggle
|
||||
setupCollapseToggle();
|
||||
|
||||
// Initialize dice roller
|
||||
setupDiceRoller();
|
||||
|
||||
// Initialize settings popup
|
||||
setupSettingsPopup();
|
||||
|
||||
// Initialize tracker editor
|
||||
initTrackerEditor();
|
||||
|
||||
// Initialize prompts editor
|
||||
initPromptsEditor();
|
||||
|
||||
// Initialize plot buttons
|
||||
setupPlotButtons(sendPlotProgression, openEncounterModal);
|
||||
|
||||
// Initialize classic stats buttons
|
||||
setupClassicStatsButtons();
|
||||
|
||||
// Initialize inventory event listeners
|
||||
initInventoryEventListeners();
|
||||
initEquipmentEventListeners();
|
||||
|
||||
// Initialize chapter checkpoint UI
|
||||
initChapterCheckpointUI();
|
||||
injectCheckpointButton();
|
||||
|
||||
// Expose weather effect functions globally for cross-module access
|
||||
if (!window.RPGCompanion) {
|
||||
window.RPGCompanion = {};
|
||||
}
|
||||
window.RPGCompanion.updateWeatherEffect = updateWeatherEffect;
|
||||
}
|
||||
|
||||
// Main initialization
|
||||
jQuery(async () => {
|
||||
try {
|
||||
console.log('[RPG Companion] Starting initialization...');
|
||||
|
||||
// Load settings with validation
|
||||
try {
|
||||
loadSettings();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
|
||||
}
|
||||
|
||||
// Check if migration to v3 JSON format is needed
|
||||
try {
|
||||
if (extensionSettings.settingsVersion < 3) {
|
||||
await migrateToV3JSON();
|
||||
updateExtensionSettings({ settingsVersion: 3 });
|
||||
await saveSettings();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Migration to v3 failed:', error);
|
||||
}
|
||||
|
||||
// Initialize i18n early for the settings panel
|
||||
await i18n.init();
|
||||
|
||||
// Set up a central listener for language changes to update dynamic UI parts
|
||||
i18n.addEventListener('languageChanged', updateDynamicLabels);
|
||||
|
||||
// Add extension settings to Extensions tab
|
||||
try {
|
||||
await addExtensionSettings($, renderExtensionTemplateAsync, clearExtensionPrompts, updateChatThoughts, cleanupCheckpointUI, clearThoughtBasedExpressionsCache, toggleDynamicWeather, initUI, loadChatData, scheduleChatStateRehydration, initThoughtBasedExpressions, injectCheckpointButton, updateAllCheckpointIndicators, removeAlternatePresentCharactersPanel);
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Failed to add extension settings tab:', error);
|
||||
}
|
||||
|
||||
// Initialize UI
|
||||
try {
|
||||
await initUI();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] UI initialization failed:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Load chat-specific data for current chat
|
||||
try {
|
||||
loadChatData();
|
||||
scheduleChatStateRehydration();
|
||||
initThoughtBasedExpressions();
|
||||
updateFabWidgets();
|
||||
updateStripWidgets();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
|
||||
}
|
||||
|
||||
// Import cleaning regexes
|
||||
try { await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] HTML regex import failed:', error); }
|
||||
try { await ensureTrackerCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] Tracker cleaning regex import failed:', error); }
|
||||
try { await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] JSON cleaning regex setup failed:', error); }
|
||||
|
||||
// Detect conflicting regex scripts
|
||||
try {
|
||||
detectConflictingRegexScripts(st_extension_settings);
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Conflict detection failed:', error);
|
||||
}
|
||||
|
||||
// Initialize history injection event listeners
|
||||
try { initHistoryInjection(); } catch (error) { console.error('[RPG Companion] History injection init failed:', error); }
|
||||
|
||||
// Register all event listeners
|
||||
try {
|
||||
function registerExtensionEvents() {
|
||||
registerAllEvents({
|
||||
[event_types.MESSAGE_SENT]: onMessageSent,
|
||||
[event_types.GENERATION_STARTED]: onGenerationStarted,
|
||||
@@ -427,6 +269,188 @@ jQuery(async () => {
|
||||
clearThoughtBasedExpressionsCache();
|
||||
setTimeout(() => onThoughtBasedExpressionsChatChanged(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI for the extension.
|
||||
*/
|
||||
async function initUI() {
|
||||
await i18n.init();
|
||||
|
||||
if (!extensionSettings.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load and inject the HTML template
|
||||
const templateHtml = await renderExtensionTemplateAsync(extensionName, 'template');
|
||||
$('body').append(templateHtml);
|
||||
|
||||
// Add mobile toggle button (FAB)
|
||||
const theme = extensionSettings.theme || 'default';
|
||||
const mobileToggleHtml = `
|
||||
<button id="rpg-mobile-toggle" class="rpg-mobile-toggle" data-theme="${theme}" title="Toggle RPG Panel">
|
||||
<i class="fa-solid fa-dice-d20"></i>
|
||||
</button>
|
||||
`;
|
||||
$('body').append(mobileToggleHtml);
|
||||
|
||||
if (window.innerWidth > 1000) {
|
||||
$('#rpg-mobile-toggle').hide();
|
||||
}
|
||||
|
||||
// Cache UI elements using state setters
|
||||
// Clear stale DOM references first to prevent memory leaks on panel rebuild
|
||||
clearDomCache();
|
||||
setPanelContainer($('#rpg-companion-panel'));
|
||||
setUserStatsContainer($('#rpg-user-stats'));
|
||||
setInfoBoxContainer($('#rpg-info-box'));
|
||||
setThoughtsContainer($('#rpg-thoughts'));
|
||||
setInventoryContainer($('#rpg-inventory'));
|
||||
setEquipmentContainer($('#rpg-equipment'));
|
||||
setQuestsContainer($('#rpg-quests'));
|
||||
setMusicPlayerContainer($('#rpg-music-player'));
|
||||
|
||||
// Re-apply translations to catch all new elements from the template
|
||||
i18n.applyTranslations(document.body);
|
||||
|
||||
// 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();
|
||||
setupMobileTabs();
|
||||
setupMobileKeyboardHandling();
|
||||
setupContentEditableScrolling();
|
||||
|
||||
// Initialize desktop UI
|
||||
setupDesktopTabs();
|
||||
|
||||
// Initialize collapse toggle
|
||||
setupCollapseToggle();
|
||||
|
||||
// Initialize dice roller
|
||||
setupDiceRoller();
|
||||
|
||||
// Initialize settings popup
|
||||
setupSettingsPopup();
|
||||
|
||||
// Initialize tracker editor
|
||||
initTrackerEditor();
|
||||
|
||||
// Initialize prompts editor
|
||||
initPromptsEditor();
|
||||
|
||||
// Initialize plot buttons
|
||||
setupPlotButtons(sendPlotProgression, openEncounterModal);
|
||||
|
||||
// Initialize classic stats buttons
|
||||
setupClassicStatsButtons();
|
||||
|
||||
// Initialize inventory event listeners
|
||||
initInventoryEventListeners();
|
||||
initEquipmentEventListeners();
|
||||
|
||||
// Initialize chapter checkpoint UI
|
||||
initChapterCheckpointUI();
|
||||
injectCheckpointButton();
|
||||
|
||||
// Expose weather effect functions globally for cross-module access
|
||||
if (!window.RPGCompanion) {
|
||||
window.RPGCompanion = {};
|
||||
}
|
||||
window.RPGCompanion.updateWeatherEffect = updateWeatherEffect;
|
||||
}
|
||||
|
||||
// Main initialization
|
||||
jQuery(async () => {
|
||||
try {
|
||||
console.log('[RPG Companion] Starting initialization...');
|
||||
|
||||
// Load settings with validation
|
||||
try {
|
||||
await loadSettings();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
|
||||
}
|
||||
|
||||
// Check if migration to v3 JSON format is needed
|
||||
try {
|
||||
if (extensionSettings.settingsVersion < 3) {
|
||||
await migrateToV3JSON();
|
||||
updateExtensionSettings({ settingsVersion: 3 });
|
||||
await saveSettings();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Migration to v3 failed:', error);
|
||||
}
|
||||
|
||||
// Initialize i18n early for the settings panel
|
||||
await i18n.init();
|
||||
|
||||
// Set up a central listener for language changes to update dynamic UI parts
|
||||
i18n.addEventListener('languageChanged', updateDynamicLabels);
|
||||
|
||||
// Add extension settings to Extensions tab
|
||||
try {
|
||||
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);
|
||||
}
|
||||
|
||||
// Initialize UI
|
||||
try {
|
||||
await initUI();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] UI initialization failed:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Load chat-specific data for current chat
|
||||
try {
|
||||
loadChatData();
|
||||
scheduleChatStateRehydration();
|
||||
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);
|
||||
}
|
||||
|
||||
// Import cleaning regexes
|
||||
try { await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] HTML regex import failed:', error); }
|
||||
try { await ensureTrackerCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] Tracker cleaning regex import failed:', error); }
|
||||
try { await ensureJsonCleaningRegex(st_extension_settings, saveSettingsDebounced); } catch (error) { console.error('[RPG Companion] JSON cleaning regex setup failed:', error); }
|
||||
|
||||
// Detect conflicting regex scripts
|
||||
try {
|
||||
detectConflictingRegexScripts(st_extension_settings);
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Conflict detection failed:', error);
|
||||
}
|
||||
|
||||
// Initialize history injection event listeners
|
||||
try { initHistoryInjection(); } catch (error) { console.error('[RPG Companion] History injection init failed:', error); }
|
||||
|
||||
// Register all event listeners
|
||||
try {
|
||||
registerExtensionEvents();
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Event registration failed:', error);
|
||||
throw error;
|
||||
|
||||
+1
-1
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
@@ -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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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]) };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+657
-171
@@ -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());
|
||||
// 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();
|
||||
updateMobilePanelPosition();
|
||||
// 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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1671,7 +1671,7 @@ function setupHistoryPersistenceListeners() {
|
||||
messageCount: 5,
|
||||
injectionPosition: 'assistant_message_end',
|
||||
contextPreamble: '',
|
||||
externalApiOnly: false
|
||||
sendAllEnabledOnRefresh: false
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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> <span data-i18n-key="template.promptsEditor.restoreDefault">Restore Default</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user