Merge pull request #60 from SpicyMarinara/revert-59-main
Revert "All the features"
This commit is contained in:
@@ -1,36 +1,70 @@
|
|||||||
import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings } from '../../../extensions.js';
|
import { getContext, renderExtensionTemplateAsync, extension_settings as st_extension_settings } from '../../../extensions.js';
|
||||||
import { event_types, saveSettingsDebounced, getRequestHeaders } from '../../../../script.js';
|
import { eventSource, event_types, substituteParams, chat, generateRaw, saveSettingsDebounced, chat_metadata, saveChatDebounced, user_avatar, getThumbnailUrl, characters, this_chid, extension_prompt_types, extension_prompt_roles, setExtensionPrompt, reloadCurrentChat, Generate, getRequestHeaders } from '../../../../script.js';
|
||||||
|
import { selected_group, getGroupMembers } from '../../../group-chats.js';
|
||||||
|
import { power_user } from '../../../power-user.js';
|
||||||
|
|
||||||
// Core modules
|
// Core modules
|
||||||
import { extensionName, extensionFolderPath } from './src/core/config.js';
|
import { extensionName, extensionFolderPath } from './src/core/config.js';
|
||||||
import { i18n } from './src/core/i18n.js';
|
import { i18n } from './src/core/i18n.js';
|
||||||
import {
|
import {
|
||||||
extensionSettings,
|
extensionSettings,
|
||||||
|
lastGeneratedData,
|
||||||
|
committedTrackerData,
|
||||||
|
lastActionWasSwipe,
|
||||||
|
isGenerating,
|
||||||
|
isPlotProgression,
|
||||||
|
pendingDiceRoll,
|
||||||
|
FALLBACK_AVATAR_DATA_URI,
|
||||||
|
$panelContainer,
|
||||||
|
$userStatsContainer,
|
||||||
|
$infoBoxContainer,
|
||||||
|
$thoughtsContainer,
|
||||||
|
$inventoryContainer,
|
||||||
|
$questsContainer,
|
||||||
|
setExtensionSettings,
|
||||||
|
updateExtensionSettings,
|
||||||
|
setLastGeneratedData,
|
||||||
|
updateLastGeneratedData,
|
||||||
|
setCommittedTrackerData,
|
||||||
|
updateCommittedTrackerData,
|
||||||
|
setLastActionWasSwipe,
|
||||||
|
setIsGenerating,
|
||||||
|
setIsPlotProgression,
|
||||||
|
setPendingDiceRoll,
|
||||||
setPanelContainer,
|
setPanelContainer,
|
||||||
setUserStatsContainer,
|
setUserStatsContainer,
|
||||||
setInfoBoxContainer,
|
setInfoBoxContainer,
|
||||||
setThoughtsContainer,
|
setThoughtsContainer,
|
||||||
setSkillsContainer,
|
|
||||||
setInventoryContainer,
|
setInventoryContainer,
|
||||||
setQuestsContainer
|
setQuestsContainer
|
||||||
} from './src/core/state.js';
|
} from './src/core/state.js';
|
||||||
import { loadSettings, saveSettings, loadChatData } from './src/core/persistence.js';
|
import { loadSettings, saveSettings, saveChatData, loadChatData, updateMessageSwipeData } from './src/core/persistence.js';
|
||||||
import { registerAllEvents } from './src/core/events.js';
|
import { registerAllEvents } from './src/core/events.js';
|
||||||
|
|
||||||
// Generation & Parsing modules
|
// Generation & Parsing modules
|
||||||
|
import {
|
||||||
|
generateTrackerExample,
|
||||||
|
generateTrackerInstructions,
|
||||||
|
generateContextualSummary,
|
||||||
|
generateRPGPromptText,
|
||||||
|
generateSeparateUpdatePrompt
|
||||||
|
} from './src/systems/generation/promptBuilder.js';
|
||||||
|
import { parseResponse, parseUserStats } from './src/systems/generation/parser.js';
|
||||||
import { updateRPGData } from './src/systems/generation/apiClient.js';
|
import { updateRPGData } from './src/systems/generation/apiClient.js';
|
||||||
import { onGenerationStarted } from './src/systems/generation/injector.js';
|
import { onGenerationStarted } from './src/systems/generation/injector.js';
|
||||||
|
|
||||||
// Rendering modules
|
// Rendering modules
|
||||||
|
import { getSafeThumbnailUrl } from './src/utils/avatars.js';
|
||||||
import { renderUserStats } from './src/systems/rendering/userStats.js';
|
import { renderUserStats } from './src/systems/rendering/userStats.js';
|
||||||
import { renderInfoBox } from './src/systems/rendering/infoBox.js';
|
import { renderInfoBox, updateInfoBoxField } from './src/systems/rendering/infoBox.js';
|
||||||
import {
|
import {
|
||||||
renderThoughts,
|
renderThoughts,
|
||||||
updateChatThoughts
|
updateCharacterField,
|
||||||
|
updateChatThoughts,
|
||||||
|
createThoughtPanel
|
||||||
} from './src/systems/rendering/thoughts.js';
|
} from './src/systems/rendering/thoughts.js';
|
||||||
import { renderInventory } from './src/systems/rendering/inventory.js';
|
import { renderInventory } from './src/systems/rendering/inventory.js';
|
||||||
import { renderQuests } from './src/systems/rendering/quests.js';
|
import { renderQuests } from './src/systems/rendering/quests.js';
|
||||||
import { renderSkills } from './src/systems/rendering/skills.js';
|
|
||||||
|
|
||||||
// Interaction modules
|
// Interaction modules
|
||||||
import { initInventoryEventListeners } from './src/systems/interaction/inventoryActions.js';
|
import { initInventoryEventListeners } from './src/systems/interaction/inventoryActions.js';
|
||||||
@@ -41,9 +75,12 @@ import {
|
|||||||
applyCustomTheme,
|
applyCustomTheme,
|
||||||
toggleCustomColors,
|
toggleCustomColors,
|
||||||
toggleAnimations,
|
toggleAnimations,
|
||||||
updateSettingsPopupTheme
|
updateSettingsPopupTheme,
|
||||||
|
applyCustomThemeToSettingsPopup
|
||||||
} from './src/systems/ui/theme.js';
|
} from './src/systems/ui/theme.js';
|
||||||
import {
|
import {
|
||||||
|
DiceModal,
|
||||||
|
SettingsModal,
|
||||||
setupDiceRoller,
|
setupDiceRoller,
|
||||||
setupSettingsPopup,
|
setupSettingsPopup,
|
||||||
updateDiceDisplay,
|
updateDiceDisplay,
|
||||||
@@ -55,6 +92,7 @@ import {
|
|||||||
} from './src/systems/ui/trackerEditor.js';
|
} from './src/systems/ui/trackerEditor.js';
|
||||||
import {
|
import {
|
||||||
togglePlotButtons,
|
togglePlotButtons,
|
||||||
|
updateCollapseToggleIcon,
|
||||||
setupCollapseToggle,
|
setupCollapseToggle,
|
||||||
updatePanelVisibility,
|
updatePanelVisibility,
|
||||||
updateSectionVisibility,
|
updateSectionVisibility,
|
||||||
@@ -63,6 +101,9 @@ import {
|
|||||||
} from './src/systems/ui/layout.js';
|
} from './src/systems/ui/layout.js';
|
||||||
import {
|
import {
|
||||||
setupMobileToggle,
|
setupMobileToggle,
|
||||||
|
constrainFabToViewport,
|
||||||
|
setupMobileTabs,
|
||||||
|
removeMobileTabs,
|
||||||
setupMobileKeyboardHandling,
|
setupMobileKeyboardHandling,
|
||||||
setupContentEditableScrolling,
|
setupContentEditableScrolling,
|
||||||
updateMobileTabLabels
|
updateMobileTabLabels
|
||||||
@@ -78,10 +119,11 @@ import { setupClassicStatsButtons } from './src/systems/features/classicStats.js
|
|||||||
import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js';
|
import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js';
|
||||||
import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js';
|
import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js';
|
||||||
import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js';
|
import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js';
|
||||||
import { DEFAULT_HTML_PROMPT, DEFAULT_JSON_TRACKER_PROMPT, DEFAULT_MESSAGE_INTERCEPTION_PROMPT } from './src/systems/generation/promptBuilder.js';
|
import { DEFAULT_HTML_PROMPT } from './src/systems/generation/promptBuilder.js';
|
||||||
|
|
||||||
// Integration modules
|
// Integration modules
|
||||||
import {
|
import {
|
||||||
|
commitTrackerData,
|
||||||
onMessageSent,
|
onMessageSent,
|
||||||
onMessageReceived,
|
onMessageReceived,
|
||||||
onCharacterChanged,
|
onCharacterChanged,
|
||||||
@@ -148,95 +190,6 @@ function updateDynamicLabels() {
|
|||||||
|
|
||||||
// Update mobile tab labels
|
// Update mobile tab labels
|
||||||
updateMobileTabLabels();
|
updateMobileTabLabels();
|
||||||
|
|
||||||
// Update inline interception toggle text if present
|
|
||||||
updateInterceptionToggleState();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the inline interception toggle text and styling near the send form.
|
|
||||||
*/
|
|
||||||
function updateInterceptionToggleState() {
|
|
||||||
const $toggle = $('#rpg-interception-toggle');
|
|
||||||
if ($toggle.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const active = extensionSettings.messageInterceptionActive !== false;
|
|
||||||
const labelKey = active
|
|
||||||
? 'template.settingsModal.advanced.interceptionOn'
|
|
||||||
: 'template.settingsModal.advanced.interceptionOff';
|
|
||||||
const label = i18n.getTranslation(labelKey) || (active ? 'Interception On' : 'Interception Off');
|
|
||||||
const prefix = i18n.getTranslation('template.settingsModal.advanced.interceptionModeLabel') || 'Interception:';
|
|
||||||
const icon = active ? 'fa-bolt' : 'fa-ban';
|
|
||||||
const background = active ? '#4a90e2' : '#666';
|
|
||||||
|
|
||||||
$toggle
|
|
||||||
.css({
|
|
||||||
'background-color': background,
|
|
||||||
color: '#fff'
|
|
||||||
})
|
|
||||||
.html(`<i class="fa-solid ${icon}"></i> ${prefix} ${label}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows/hides the inline interception toggle based on interception setting.
|
|
||||||
*/
|
|
||||||
function updateInterceptionToggleVisibility() {
|
|
||||||
const $toggle = $('#rpg-interception-toggle');
|
|
||||||
if ($toggle.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$toggle.toggle(extensionSettings.enableMessageInterception);
|
|
||||||
|
|
||||||
if (extensionSettings.enableMessageInterception) {
|
|
||||||
updateInterceptionToggleState();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures the extension buttons wrapper exists above the send form.
|
|
||||||
*/
|
|
||||||
function ensureExtensionButtonsWrapper() {
|
|
||||||
if ($('#extension-buttons-wrapper').length === 0) {
|
|
||||||
$('#send_form').prepend('<div id="extension-buttons-wrapper" style="text-align: center; margin: 5px auto;"></div>');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the inline interception toggle near plot buttons.
|
|
||||||
*/
|
|
||||||
function renderInterceptionToggle() {
|
|
||||||
ensureExtensionButtonsWrapper();
|
|
||||||
|
|
||||||
if ($('#rpg-interception-toggle').length === 0) {
|
|
||||||
const buttonHtml = `
|
|
||||||
<button id="rpg-interception-toggle" class="menu_button interactable" style="
|
|
||||||
background-color: #e94560;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 0 4px;
|
|
||||||
display: inline-block;
|
|
||||||
" tabindex="0" role="button">
|
|
||||||
<i class="fa-solid fa-bolt"></i> Interception: On
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
$('#extension-buttons-wrapper').append(buttonHtml);
|
|
||||||
|
|
||||||
$('#rpg-interception-toggle').on('click', () => {
|
|
||||||
const active = extensionSettings.messageInterceptionActive !== false;
|
|
||||||
extensionSettings.messageInterceptionActive = !active;
|
|
||||||
saveSettings();
|
|
||||||
updateInterceptionToggleState();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateInterceptionToggleVisibility();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,21 +278,23 @@ async function initUI() {
|
|||||||
setUserStatsContainer($('#rpg-user-stats'));
|
setUserStatsContainer($('#rpg-user-stats'));
|
||||||
setInfoBoxContainer($('#rpg-info-box'));
|
setInfoBoxContainer($('#rpg-info-box'));
|
||||||
setThoughtsContainer($('#rpg-thoughts'));
|
setThoughtsContainer($('#rpg-thoughts'));
|
||||||
setSkillsContainer($('#rpg-skills'));
|
|
||||||
setInventoryContainer($('#rpg-inventory'));
|
setInventoryContainer($('#rpg-inventory'));
|
||||||
setQuestsContainer($('#rpg-quests'));
|
setQuestsContainer($('#rpg-quests'));
|
||||||
|
|
||||||
i18n.applyTranslations(document.body);
|
// Re-apply translations to the entire body to catch all new elements from the template
|
||||||
|
i18n.applyTranslations(document.body);
|
||||||
|
|
||||||
$('#rpg-toggle-auto-update').on('change', function() {
|
// Set up event listeners (enable/disable is handled in Extensions tab)
|
||||||
|
$('#rpg-toggle-auto-update').on('change', function() {
|
||||||
extensionSettings.autoUpdate = $(this).prop('checked');
|
extensionSettings.autoUpdate = $(this).prop('checked');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-position-select').on('change', function() {
|
$('#rpg-position-select').on('change', function() {
|
||||||
extensionSettings.panelPosition = String($(this).val());
|
extensionSettings.panelPosition = String($(this).val());
|
||||||
saveSettings();
|
saveSettings();
|
||||||
applyPanelPosition();
|
applyPanelPosition();
|
||||||
|
// Recreate thought bubbles to update their position
|
||||||
updateChatThoughts();
|
updateChatThoughts();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -349,14 +304,6 @@ async function initUI() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-message-interception-depth').on('change', function() {
|
|
||||||
const value = parseInt(String($(this).val()));
|
|
||||||
if (!Number.isNaN(value)) {
|
|
||||||
extensionSettings.messageInterceptionContextDepth = value;
|
|
||||||
saveSettings();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-memory-messages').on('change', function() {
|
$('#rpg-memory-messages').on('change', function() {
|
||||||
const value = $(this).val();
|
const value = $(this).val();
|
||||||
extensionSettings.memoryMessagesToProcess = parseInt(String(value));
|
extensionSettings.memoryMessagesToProcess = parseInt(String(value));
|
||||||
@@ -392,57 +339,15 @@ async function initUI() {
|
|||||||
updateSectionVisibility();
|
updateSectionVisibility();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-inventory').on('change', function() {
|
$('#rpg-toggle-inventory').on('change', function() {
|
||||||
extensionSettings.showInventory = $(this).prop('checked');
|
extensionSettings.showInventory = $(this).prop('checked');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
updateSectionVisibility();
|
updateSectionVisibility();
|
||||||
if (window.innerWidth > 1000) {
|
|
||||||
removeDesktopTabs();
|
|
||||||
setupDesktopTabs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-toggle-simplified-inventory').on('change', function() {
|
|
||||||
extensionSettings.useSimplifiedInventory = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
renderInventory();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-toggle-quests').on('change', function() {
|
|
||||||
extensionSettings.showQuests = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
updateSectionVisibility();
|
|
||||||
renderQuests();
|
|
||||||
if (window.innerWidth > 1000) {
|
|
||||||
removeDesktopTabs();
|
|
||||||
setupDesktopTabs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-toggle-skills').on('change', function() {
|
|
||||||
extensionSettings.showSkills = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
updateSectionVisibility();
|
|
||||||
renderSkills();
|
|
||||||
if (window.innerWidth > 1000) {
|
|
||||||
removeDesktopTabs();
|
|
||||||
setupDesktopTabs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-toggle-item-skill-links').on('change', function() {
|
|
||||||
extensionSettings.enableItemSkillLinks = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
renderSkills();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-toggle-delete-skill-with-item').on('change', function() {
|
|
||||||
extensionSettings.deleteSkillWithItem = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-thoughts-in-chat').on('change', function() {
|
$('#rpg-toggle-thoughts-in-chat').on('change', function() {
|
||||||
extensionSettings.showThoughtsInChat = $(this).prop('checked');
|
extensionSettings.showThoughtsInChat = $(this).prop('checked');
|
||||||
|
// console.log('[RPG Companion] Toggle showThoughtsInChat changed to:', extensionSettings.showThoughtsInChat);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
updateChatThoughts();
|
updateChatThoughts();
|
||||||
});
|
});
|
||||||
@@ -450,14 +355,17 @@ async function initUI() {
|
|||||||
$('#rpg-toggle-always-show-bubble').on('change', function() {
|
$('#rpg-toggle-always-show-bubble').on('change', function() {
|
||||||
extensionSettings.alwaysShowThoughtBubble = $(this).prop('checked');
|
extensionSettings.alwaysShowThoughtBubble = $(this).prop('checked');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
// Force immediate save to ensure setting is persisted before any other code runs
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const extension_settings = context.extension_settings || context.extensionSettings;
|
const extension_settings = context.extension_settings || context.extensionSettings;
|
||||||
extension_settings[extensionName] = extensionSettings;
|
extension_settings[extensionName] = extensionSettings;
|
||||||
|
// Re-render thoughts to apply the setting
|
||||||
updateChatThoughts();
|
updateChatThoughts();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-html-prompt').on('change', function() {
|
$('#rpg-toggle-html-prompt').on('change', function() {
|
||||||
extensionSettings.enableHtmlPrompt = $(this).prop('checked');
|
extensionSettings.enableHtmlPrompt = $(this).prop('checked');
|
||||||
|
// console.log('[RPG Companion] Toggle enableHtmlPrompt changed to:', extensionSettings.enableHtmlPrompt);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -468,42 +376,11 @@ async function initUI() {
|
|||||||
|
|
||||||
$('#rpg-restore-default-html-prompt').on('click', function() {
|
$('#rpg-restore-default-html-prompt').on('click', function() {
|
||||||
extensionSettings.customHtmlPrompt = '';
|
extensionSettings.customHtmlPrompt = '';
|
||||||
$('#rpg-custom-html-prompt').val(DEFAULT_HTML_PROMPT);
|
$('#rpg-custom-html-prompt').val('');
|
||||||
saveSettings();
|
saveSettings();
|
||||||
toastr.success('HTML prompt restored to default');
|
toastr.success('HTML prompt restored to default');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-toggle-message-interception').on('change', function() {
|
|
||||||
extensionSettings.enableMessageInterception = $(this).prop('checked');
|
|
||||||
saveSettings();
|
|
||||||
updateInterceptionToggleVisibility();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-custom-message-interception-prompt').on('input', function() {
|
|
||||||
extensionSettings.customMessageInterceptionPrompt = $(this).val().trim();
|
|
||||||
saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-restore-default-message-interception-prompt').on('click', function() {
|
|
||||||
extensionSettings.customMessageInterceptionPrompt = '';
|
|
||||||
$('#rpg-custom-message-interception-prompt').val(DEFAULT_MESSAGE_INTERCEPTION_PROMPT);
|
|
||||||
saveSettings();
|
|
||||||
toastr.success('Message interception prompt restored to default');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Custom Tracker Prompt handlers
|
|
||||||
$('#rpg-custom-tracker-prompt').on('input', function() {
|
|
||||||
extensionSettings.customTrackerPrompt = $(this).val().trim();
|
|
||||||
saveSettings();
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-restore-default-tracker-prompt').on('click', function() {
|
|
||||||
extensionSettings.customTrackerPrompt = '';
|
|
||||||
$('#rpg-custom-tracker-prompt').val(DEFAULT_JSON_TRACKER_PROMPT);
|
|
||||||
saveSettings();
|
|
||||||
toastr.success('Tracker prompt restored to default');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#rpg-skip-guided-mode').on('change', function() {
|
$('#rpg-skip-guided-mode').on('change', function() {
|
||||||
extensionSettings.skipInjectionsForGuided = String($(this).val());
|
extensionSettings.skipInjectionsForGuided = String($(this).val());
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -511,6 +388,7 @@ async function initUI() {
|
|||||||
|
|
||||||
$('#rpg-toggle-plot-buttons').on('change', function() {
|
$('#rpg-toggle-plot-buttons').on('change', function() {
|
||||||
extensionSettings.enablePlotButtons = $(this).prop('checked');
|
extensionSettings.enablePlotButtons = $(this).prop('checked');
|
||||||
|
// console.log('[RPG Companion] Toggle enablePlotButtons changed to:', extensionSettings.enablePlotButtons);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
togglePlotButtons();
|
togglePlotButtons();
|
||||||
});
|
});
|
||||||
@@ -523,6 +401,7 @@ async function initUI() {
|
|||||||
|
|
||||||
$('#rpg-manual-update').on('click', async function() {
|
$('#rpg-manual-update').on('click', async function() {
|
||||||
if (!extensionSettings.enabled) {
|
if (!extensionSettings.enabled) {
|
||||||
|
// console.log('[RPG Companion] Extension is disabled. Please enable it in the Extensions tab.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
await updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory);
|
||||||
@@ -531,22 +410,23 @@ async function initUI() {
|
|||||||
$('#rpg-stat-bar-color-low').on('change', function() {
|
$('#rpg-stat-bar-color-low').on('change', function() {
|
||||||
extensionSettings.statBarColorLow = String($(this).val());
|
extensionSettings.statBarColorLow = String($(this).val());
|
||||||
saveSettings();
|
saveSettings();
|
||||||
renderUserStats();
|
renderUserStats(); // Re-render with new colors
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-stat-bar-color-high').on('change', function() {
|
$('#rpg-stat-bar-color-high').on('change', function() {
|
||||||
extensionSettings.statBarColorHigh = String($(this).val());
|
extensionSettings.statBarColorHigh = String($(this).val());
|
||||||
saveSettings();
|
saveSettings();
|
||||||
renderUserStats();
|
renderUserStats(); // Re-render with new colors
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Theme selection
|
||||||
$('#rpg-theme-select').on('change', function() {
|
$('#rpg-theme-select').on('change', function() {
|
||||||
extensionSettings.theme = String($(this).val());
|
extensionSettings.theme = String($(this).val());
|
||||||
saveSettings();
|
saveSettings();
|
||||||
applyTheme();
|
applyTheme();
|
||||||
toggleCustomColors();
|
toggleCustomColors();
|
||||||
updateSettingsPopupTheme(getSettingsModal());
|
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
|
||||||
updateChatThoughts();
|
updateChatThoughts(); // Recreate thought bubbles with new theme
|
||||||
});
|
});
|
||||||
|
|
||||||
// Custom color pickers
|
// Custom color pickers
|
||||||
@@ -555,8 +435,8 @@ async function initUI() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
if (extensionSettings.theme === 'custom') {
|
if (extensionSettings.theme === 'custom') {
|
||||||
applyCustomTheme();
|
applyCustomTheme();
|
||||||
updateSettingsPopupTheme(getSettingsModal());
|
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
|
||||||
updateChatThoughts();
|
updateChatThoughts(); // Update thought bubbles
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -565,8 +445,8 @@ async function initUI() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
if (extensionSettings.theme === 'custom') {
|
if (extensionSettings.theme === 'custom') {
|
||||||
applyCustomTheme();
|
applyCustomTheme();
|
||||||
updateSettingsPopupTheme(getSettingsModal());
|
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
|
||||||
updateChatThoughts();
|
updateChatThoughts(); // Update thought bubbles
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -575,8 +455,8 @@ async function initUI() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
if (extensionSettings.theme === 'custom') {
|
if (extensionSettings.theme === 'custom') {
|
||||||
applyCustomTheme();
|
applyCustomTheme();
|
||||||
updateSettingsPopupTheme(getSettingsModal());
|
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
|
||||||
updateChatThoughts();
|
updateChatThoughts(); // Update thought bubbles
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -585,11 +465,12 @@ async function initUI() {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
if (extensionSettings.theme === 'custom') {
|
if (extensionSettings.theme === 'custom') {
|
||||||
applyCustomTheme();
|
applyCustomTheme();
|
||||||
updateSettingsPopupTheme(getSettingsModal());
|
updateSettingsPopupTheme(getSettingsModal()); // Update popup theme instantly
|
||||||
updateChatThoughts();
|
updateChatThoughts(); // Update thought bubbles
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize UI state (enable/disable is in Extensions tab)
|
||||||
$('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate);
|
$('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate);
|
||||||
$('#rpg-position-select').val(extensionSettings.panelPosition);
|
$('#rpg-position-select').val(extensionSettings.panelPosition);
|
||||||
$('#rpg-update-depth').val(extensionSettings.updateDepth);
|
$('#rpg-update-depth').val(extensionSettings.updateDepth);
|
||||||
@@ -599,27 +480,14 @@ async function initUI() {
|
|||||||
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
|
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
|
||||||
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
|
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
|
||||||
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
|
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
|
||||||
$('#rpg-toggle-simplified-inventory').prop('checked', extensionSettings.useSimplifiedInventory);
|
|
||||||
$('#rpg-toggle-skills').prop('checked', extensionSettings.showSkills);
|
|
||||||
$('#rpg-toggle-item-skill-links').prop('checked', extensionSettings.enableItemSkillLinks);
|
|
||||||
$('#rpg-toggle-delete-skill-with-item').prop('checked', extensionSettings.deleteSkillWithItem);
|
|
||||||
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
|
|
||||||
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
|
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
|
||||||
$('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble);
|
$('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble);
|
||||||
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
|
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
|
||||||
$('#rpg-toggle-message-interception').prop('checked', extensionSettings.enableMessageInterception);
|
|
||||||
updateInterceptionToggleVisibility();
|
|
||||||
|
|
||||||
|
// Set default HTML prompt as actual text if no custom prompt exists
|
||||||
$('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT);
|
$('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT);
|
||||||
$('#rpg-custom-tracker-prompt').val(extensionSettings.customTrackerPrompt || DEFAULT_JSON_TRACKER_PROMPT);
|
|
||||||
$('#rpg-custom-message-interception-prompt').val(
|
|
||||||
extensionSettings.customMessageInterceptionPrompt || DEFAULT_MESSAGE_INTERCEPTION_PROMPT
|
|
||||||
);
|
|
||||||
$('#rpg-message-interception-depth').val(
|
|
||||||
extensionSettings.messageInterceptionContextDepth || extensionSettings.updateDepth || 4
|
|
||||||
);
|
|
||||||
|
|
||||||
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
|
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
|
||||||
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
|
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
|
||||||
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
|
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
|
||||||
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
|
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
|
||||||
@@ -639,18 +507,21 @@ async function initUI() {
|
|||||||
toggleCustomColors();
|
toggleCustomColors();
|
||||||
toggleAnimations();
|
toggleAnimations();
|
||||||
|
|
||||||
|
// Setup mobile toggle button
|
||||||
setupMobileToggle();
|
setupMobileToggle();
|
||||||
|
|
||||||
|
// Setup desktop tabs (only on desktop viewport)
|
||||||
if (window.innerWidth > 1000) {
|
if (window.innerWidth > 1000) {
|
||||||
setupDesktopTabs();
|
setupDesktopTabs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup collapse/expand toggle button
|
||||||
setupCollapseToggle();
|
setupCollapseToggle();
|
||||||
|
|
||||||
|
// Render initial data if available
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
renderInfoBox();
|
renderInfoBox();
|
||||||
renderThoughts();
|
renderThoughts();
|
||||||
renderSkills();
|
|
||||||
renderInventory();
|
renderInventory();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
updateDiceDisplay();
|
updateDiceDisplay();
|
||||||
@@ -660,11 +531,14 @@ async function initUI() {
|
|||||||
initTrackerEditor();
|
initTrackerEditor();
|
||||||
addDiceQuickReply();
|
addDiceQuickReply();
|
||||||
setupPlotButtons(sendPlotProgression);
|
setupPlotButtons(sendPlotProgression);
|
||||||
renderInterceptionToggle();
|
|
||||||
setupMobileKeyboardHandling();
|
setupMobileKeyboardHandling();
|
||||||
setupContentEditableScrolling();
|
setupContentEditableScrolling();
|
||||||
initInventoryEventListeners();
|
initInventoryEventListeners();
|
||||||
|
|
||||||
|
// Setup Memory Recollection button in World Info
|
||||||
setupMemoryRecollectionButton();
|
setupMemoryRecollectionButton();
|
||||||
|
|
||||||
|
// Initialize Lorebook Limiter
|
||||||
initLorebookLimiter();
|
initLorebookLimiter();
|
||||||
|
|
||||||
// Initialize character state display (NEW)
|
// Initialize character state display (NEW)
|
||||||
@@ -689,6 +563,13 @@ async function initUI() {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Rendering functions removed - now imported from src/systems/rendering/*
|
||||||
|
// (renderUserStats, renderInfoBox, renderThoughts, updateInfoBoxField,
|
||||||
|
// updateCharacterField, updateChatThoughts, createThoughtPanel)
|
||||||
|
|
||||||
|
// Event handlers removed - now imported from src/systems/integration/sillytavern.js
|
||||||
|
// (commitTrackerData, onMessageSent, onMessageReceived, onCharacterChanged,
|
||||||
|
// onMessageSwiped, updatePersonaAvatar, clearExtensionPrompts)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// CHARACTER STATE TRACKING - Event Wrappers (NEW)
|
// CHARACTER STATE TRACKING - Event Wrappers (NEW)
|
||||||
@@ -889,55 +770,76 @@ jQuery(async () => {
|
|||||||
console.log('========================================');
|
console.log('========================================');
|
||||||
console.log('[RPG Companion] Starting initialization...');
|
console.log('[RPG Companion] Starting initialization...');
|
||||||
|
|
||||||
|
// Load settings with validation
|
||||||
try {
|
try {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
|
console.error('[RPG Companion] Settings load failed, continuing with defaults:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize i18n early for the settings panel
|
||||||
await i18n.init();
|
await i18n.init();
|
||||||
|
|
||||||
|
// Set up a central listener for language changes to update dynamic UI parts
|
||||||
i18n.addEventListener('languageChanged', updateDynamicLabels);
|
i18n.addEventListener('languageChanged', updateDynamicLabels);
|
||||||
|
|
||||||
|
// Add extension settings to Extensions tab
|
||||||
try {
|
try {
|
||||||
await addExtensionSettings();
|
await addExtensionSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Failed to add extension settings tab:', error);
|
console.error('[RPG Companion] Failed to add extension settings tab:', error);
|
||||||
|
// Don't throw - extension can still work without settings tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize UI
|
||||||
try {
|
try {
|
||||||
await initUI();
|
await initUI();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] UI initialization failed:', error);
|
console.error('[RPG Companion] UI initialization failed:', error);
|
||||||
throw error;
|
throw error; // This is critical - can't continue without UI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load chat-specific data for current chat
|
||||||
try {
|
try {
|
||||||
loadChatData();
|
loadChatData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
|
console.error('[RPG Companion] Chat data load failed, using defaults:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import the HTML cleaning regex if needed
|
||||||
try {
|
try {
|
||||||
await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced);
|
await ensureHtmlCleaningRegex(st_extension_settings, saveSettingsDebounced);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] HTML regex import failed:', error);
|
console.error('[RPG Companion] HTML regex import failed:', error);
|
||||||
|
// Non-critical - continue without it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import the RPG Companion Trackers preset if needed
|
||||||
try {
|
try {
|
||||||
await ensureTrackerPresetExists();
|
await ensureTrackerPresetExists();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Preset import failed:', error);
|
console.error('[RPG Companion] Preset import failed:', error);
|
||||||
|
// Non-critical - users can manually import if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Detect conflicting regex scripts from old manual formatters
|
||||||
try {
|
try {
|
||||||
const conflicts = detectConflictingRegexScripts(st_extension_settings);
|
const conflicts = detectConflictingRegexScripts(st_extension_settings);
|
||||||
if (conflicts.length > 0) {
|
if (conflicts.length > 0) {
|
||||||
console.log('[RPG Companion] ⚠️ Detected old manual formatting regex scripts that may conflict:');
|
console.log('[RPG Companion] ⚠️ Detected old manual formatting regex scripts that may conflict:');
|
||||||
conflicts.forEach(name => console.log(` - ${name}`));
|
conflicts.forEach(name => console.log(` - ${name}`));
|
||||||
console.log('[RPG Companion] Consider disabling these regexes as the extension now handles formatting automatically.');
|
console.log('[RPG Companion] Consider disabling these regexes as the extension now handles formatting automatically.');
|
||||||
|
|
||||||
|
// Show user-friendly warning (non-blocking)
|
||||||
|
// toastr.warning(
|
||||||
|
// `Found ${conflicts.length} old RPG formatting regex script(s). These may conflict with the extension. Check console for details.`,
|
||||||
|
// 'RPG Companion Warning',
|
||||||
|
// { timeOut: 8000 }
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Conflict detection failed:', error);
|
console.error('[RPG Companion] Conflict detection failed:', error);
|
||||||
|
// Non-critical - continue anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register all event listeners (with character tracking wrappers)
|
// Register all event listeners (with character tracking wrappers)
|
||||||
@@ -953,13 +855,15 @@ jQuery(async () => {
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Event registration failed:', error);
|
console.error('[RPG Companion] Event registration failed:', error);
|
||||||
throw error;
|
throw error; // This is critical - can't continue without events
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[RPG Companion] ✅ Extension loaded successfully');
|
console.log('[RPG Companion] ✅ Extension loaded successfully');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] ❌ Critical initialization failure:', error);
|
console.error('[RPG Companion] ❌ Critical initialization failure:', error);
|
||||||
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
||||||
|
|
||||||
|
// Show user-friendly error message
|
||||||
toastr.error(
|
toastr.error(
|
||||||
'RPG Companion failed to initialize. Check console for details. Please try refreshing the page or resetting extension settings.',
|
'RPG Companion failed to initialize. Check console for details. Please try refreshing the page or resetting extension settings.',
|
||||||
'RPG Companion Error',
|
'RPG Companion Error',
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const defaultSettings = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
updateDepth: 4, // How many messages to include in the context
|
updateDepth: 4, // How many messages to include in the context
|
||||||
messageInterceptionContextDepth: 4, // How many recent messages to send when intercepting user messages
|
|
||||||
generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately
|
generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately
|
||||||
useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model
|
useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model
|
||||||
showUserStats: true,
|
showUserStats: true,
|
||||||
@@ -35,8 +34,6 @@ export const defaultSettings = {
|
|||||||
showThoughtsInChat: true, // Show thoughts overlay in chat
|
showThoughtsInChat: true, // Show thoughts overlay in chat
|
||||||
alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon
|
alwaysShowThoughtBubble: false, // Auto-expand thought bubble without clicking icon
|
||||||
enableHtmlPrompt: false, // Enable immersive HTML prompt injection
|
enableHtmlPrompt: false, // Enable immersive HTML prompt injection
|
||||||
enableMessageInterception: false, // Enable intercepting user messages with LLM rewrite
|
|
||||||
messageInterceptionActive: true, // Runtime toggle to allow/skip interception
|
|
||||||
// Controls when the extension skips injecting tracker instructions/examples/HTML
|
// Controls when the extension skips injecting tracker instructions/examples/HTML
|
||||||
// into generations that appear to be user-injected instructions. Valid values:
|
// into generations that appear to be user-injected instructions. Valid values:
|
||||||
// - 'none' -> never skip (legacy behavior: always inject)
|
// - 'none' -> never skip (legacy behavior: always inject)
|
||||||
|
|||||||
+3
-5
@@ -1,6 +1,5 @@
|
|||||||
//- No-op in case this is running outside of SillyTavern
|
//- No-op in case this is running outside of SillyTavern
|
||||||
// eslint-disable-next-line no-unused-vars
|
const { extension_settings } = typeof self.SillyTavern !== 'undefined' ? self.SillyTavern.getContext() : { extension_settings: {} };
|
||||||
const { extension_settings: _extension_settings } = typeof self.SillyTavern !== 'undefined' ? self.SillyTavern.getContext() : { extension_settings: {} };
|
|
||||||
|
|
||||||
class Internationalization {
|
class Internationalization {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -88,9 +87,8 @@ class Internationalization {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTranslation(key, fallback = null) {
|
getTranslation(key) {
|
||||||
// Return translation, or fallback, or the key itself (prevents "null" from showing)
|
return this.translations[key] || null;
|
||||||
return this.translations[key] || fallback || key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setLanguage(lang) {
|
async setLanguage(lang) {
|
||||||
|
|||||||
+47
-249
@@ -9,9 +9,11 @@ import {
|
|||||||
extensionSettings,
|
extensionSettings,
|
||||||
lastGeneratedData,
|
lastGeneratedData,
|
||||||
committedTrackerData,
|
committedTrackerData,
|
||||||
|
setExtensionSettings,
|
||||||
updateExtensionSettings,
|
updateExtensionSettings,
|
||||||
setLastGeneratedData,
|
setLastGeneratedData,
|
||||||
setCommittedTrackerData
|
setCommittedTrackerData,
|
||||||
|
FEATURE_FLAGS
|
||||||
} from './state.js';
|
} from './state.js';
|
||||||
import { migrateInventory } from '../utils/migration.js';
|
import { migrateInventory } from '../utils/migration.js';
|
||||||
import { validateStoredInventory, cleanItemString } from '../utils/security.js';
|
import { validateStoredInventory, cleanItemString } from '../utils/security.js';
|
||||||
@@ -76,14 +78,19 @@ export function loadSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateExtensionSettings(savedSettings);
|
updateExtensionSettings(savedSettings);
|
||||||
|
// console.log('[RPG Companion] Settings loaded:', extensionSettings);
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] No saved settings found, using defaults');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate inventory from v1 (string) to v2 (object) format if needed
|
// Migrate inventory if feature flag enabled
|
||||||
const migrationResult = migrateInventory(extensionSettings.userStats.inventory);
|
if (FEATURE_FLAGS.useNewInventory) {
|
||||||
if (migrationResult.migrated) {
|
const migrationResult = migrateInventory(extensionSettings.userStats.inventory);
|
||||||
console.log(`[RPG Companion] Inventory migrated from ${migrationResult.source} to v2 format`);
|
if (migrationResult.migrated) {
|
||||||
extensionSettings.userStats.inventory = migrationResult.inventory;
|
console.log(`[RPG Companion] Inventory migrated from ${migrationResult.source} to v2 format`);
|
||||||
saveSettings(); // Persist migrated inventory
|
extensionSettings.userStats.inventory = migrationResult.inventory;
|
||||||
|
saveSettings(); // Persist migrated inventory
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate to trackerConfig if it doesn't exist
|
// Migrate to trackerConfig if it doesn't exist
|
||||||
@@ -92,16 +99,6 @@ export function loadSettings() {
|
|||||||
migrateToTrackerConfig();
|
migrateToTrackerConfig();
|
||||||
saveSettings(); // Persist migration
|
saveSettings(); // Persist migration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate to new stats/skills format with descriptions
|
|
||||||
if (migrateStatsAndSkillsFormat()) {
|
|
||||||
saveSettings(); // Persist migration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate quests from legacy format to structured format
|
|
||||||
if (migrateQuestsFormat()) {
|
|
||||||
saveSettings(); // Persist migration
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Error loading settings:', error);
|
console.error('[RPG Companion] Error loading settings:', error);
|
||||||
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
console.error('[RPG Companion] Error details:', error.message, error.stack);
|
||||||
@@ -143,13 +140,6 @@ export function saveChatData() {
|
|||||||
quests: extensionSettings.quests,
|
quests: extensionSettings.quests,
|
||||||
lastGeneratedData: lastGeneratedData,
|
lastGeneratedData: lastGeneratedData,
|
||||||
committedTrackerData: committedTrackerData,
|
committedTrackerData: committedTrackerData,
|
||||||
// Structured data (JSON format)
|
|
||||||
inventoryV3: extensionSettings.inventoryV3,
|
|
||||||
skillsV2: extensionSettings.skillsV2,
|
|
||||||
skillAbilityLinks: extensionSettings.skillAbilityLinks,
|
|
||||||
infoBoxData: extensionSettings.infoBoxData,
|
|
||||||
charactersData: extensionSettings.charactersData,
|
|
||||||
questsV2: extensionSettings.questsV2,
|
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -184,6 +174,8 @@ export function updateMessageSwipeData() {
|
|||||||
infoBox: lastGeneratedData.infoBox,
|
infoBox: lastGeneratedData.infoBox,
|
||||||
characterThoughts: lastGeneratedData.characterThoughts
|
characterThoughts: lastGeneratedData.characterThoughts
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Updated message swipe data after user edit');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -195,7 +187,7 @@ export function updateMessageSwipeData() {
|
|||||||
*/
|
*/
|
||||||
export function loadChatData() {
|
export function loadChatData() {
|
||||||
if (!chat_metadata || !chat_metadata.rpg_companion) {
|
if (!chat_metadata || !chat_metadata.rpg_companion) {
|
||||||
// Reset to defaults if no data exists (new chat)
|
// Reset to defaults if no data exists
|
||||||
updateExtensionSettings({
|
updateExtensionSettings({
|
||||||
userStats: {
|
userStats: {
|
||||||
health: 100,
|
health: 100,
|
||||||
@@ -211,29 +203,11 @@ export function loadChatData() {
|
|||||||
onPerson: "None",
|
onPerson: "None",
|
||||||
stored: {},
|
stored: {},
|
||||||
assets: "None"
|
assets: "None"
|
||||||
},
|
}
|
||||||
skills: "None" // Legacy single-string skills (for Status section)
|
|
||||||
},
|
},
|
||||||
quests: {
|
quests: {
|
||||||
main: "None",
|
main: "None",
|
||||||
optional: []
|
optional: []
|
||||||
},
|
|
||||||
// Reset structured data fields
|
|
||||||
inventoryV3: {
|
|
||||||
onPerson: [],
|
|
||||||
stored: {},
|
|
||||||
assets: [],
|
|
||||||
simplified: []
|
|
||||||
},
|
|
||||||
skillsV2: {},
|
|
||||||
skillsData: {},
|
|
||||||
skillAbilityLinks: {},
|
|
||||||
skills: { list: [], categories: {} },
|
|
||||||
charactersData: [],
|
|
||||||
infoBoxData: null,
|
|
||||||
questsV2: {
|
|
||||||
main: null,
|
|
||||||
optional: []
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setLastGeneratedData({
|
setLastGeneratedData({
|
||||||
@@ -273,59 +247,18 @@ export function loadChatData() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore last generated data (sanitize null values in infoBox)
|
// Restore last generated data
|
||||||
if (savedData.lastGeneratedData) {
|
if (savedData.lastGeneratedData) {
|
||||||
const sanitizedData = { ...savedData.lastGeneratedData };
|
setLastGeneratedData({ ...savedData.lastGeneratedData });
|
||||||
if (sanitizedData.infoBox && typeof sanitizedData.infoBox === 'string') {
|
|
||||||
// Remove lines that contain "null" values
|
|
||||||
sanitizedData.infoBox = sanitizedData.infoBox
|
|
||||||
.split('\n')
|
|
||||||
.filter(line => !line.match(/:\s*null\s*$/i) && !line.match(/:\s*undefined\s*$/i))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
setLastGeneratedData(sanitizedData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore committed tracker data (sanitize null values in infoBox)
|
// Restore committed tracker data
|
||||||
if (savedData.committedTrackerData) {
|
if (savedData.committedTrackerData) {
|
||||||
const sanitizedData = { ...savedData.committedTrackerData };
|
setCommittedTrackerData({ ...savedData.committedTrackerData });
|
||||||
if (sanitizedData.infoBox && typeof sanitizedData.infoBox === 'string') {
|
|
||||||
// Remove lines that contain "null" values
|
|
||||||
sanitizedData.infoBox = sanitizedData.infoBox
|
|
||||||
.split('\n')
|
|
||||||
.filter(line => !line.match(/:\s*null\s*$/i) && !line.match(/:\s*undefined\s*$/i))
|
|
||||||
.join('\n');
|
|
||||||
}
|
|
||||||
setCommittedTrackerData(sanitizedData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore structured data (JSON format)
|
// Migrate inventory in chat data if feature flag enabled
|
||||||
if (savedData.inventoryV3) {
|
if (FEATURE_FLAGS.useNewInventory && extensionSettings.userStats.inventory) {
|
||||||
extensionSettings.inventoryV3 = savedData.inventoryV3;
|
|
||||||
}
|
|
||||||
if (savedData.skillsV2) {
|
|
||||||
extensionSettings.skillsV2 = savedData.skillsV2;
|
|
||||||
}
|
|
||||||
if (savedData.skillAbilityLinks) {
|
|
||||||
extensionSettings.skillAbilityLinks = savedData.skillAbilityLinks;
|
|
||||||
}
|
|
||||||
if (savedData.infoBoxData) {
|
|
||||||
extensionSettings.infoBoxData = savedData.infoBoxData;
|
|
||||||
}
|
|
||||||
if (savedData.charactersData) {
|
|
||||||
extensionSettings.charactersData = savedData.charactersData;
|
|
||||||
}
|
|
||||||
if (savedData.questsV2) {
|
|
||||||
extensionSettings.questsV2 = savedData.questsV2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate quests from legacy format to structured format if needed
|
|
||||||
if (migrateQuestsFormat()) {
|
|
||||||
saveChatData(); // Persist migrated quests to chat metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate inventory from v1 (string) to v2 (object) format if needed
|
|
||||||
if (extensionSettings.userStats.inventory) {
|
|
||||||
const migrationResult = migrateInventory(extensionSettings.userStats.inventory);
|
const migrationResult = migrateInventory(extensionSettings.userStats.inventory);
|
||||||
if (migrationResult.migrated) {
|
if (migrationResult.migrated) {
|
||||||
console.log(`[RPG Companion] Chat inventory migrated from ${migrationResult.source} to v2 format`);
|
console.log(`[RPG Companion] Chat inventory migrated from ${migrationResult.source} to v2 format`);
|
||||||
@@ -334,12 +267,16 @@ export function loadChatData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate inventory structure (Bug #3 fix)
|
||||||
validateInventoryStructure(extensionSettings.userStats.inventory, 'chat');
|
validateInventoryStructure(extensionSettings.userStats.inventory, 'chat');
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Loaded chat data:', savedData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates and repairs inventory structure to prevent corruption.
|
* Validates and repairs inventory structure to prevent corruption.
|
||||||
* Ensures all v2 fields exist and are the correct type.
|
* Ensures all v2 fields exist and are the correct type.
|
||||||
|
* Fixes Bug #3: Location disappears when switching tabs
|
||||||
*
|
*
|
||||||
* @param {Object} inventory - Inventory object to validate
|
* @param {Object} inventory - Inventory object to validate
|
||||||
* @param {string} source - Source of load ('settings' or 'chat') for logging
|
* @param {string} source - Source of load ('settings' or 'chat') for logging
|
||||||
@@ -382,6 +319,7 @@ function validateInventoryStructure(inventory, source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate stored field (CRITICAL for Bug #3)
|
||||||
if (!inventory.stored || typeof inventory.stored !== 'object' || Array.isArray(inventory.stored)) {
|
if (!inventory.stored || typeof inventory.stored !== 'object' || Array.isArray(inventory.stored)) {
|
||||||
console.error(`[RPG Companion] Corrupted stored inventory from ${source}, resetting to empty object`);
|
console.error(`[RPG Companion] Corrupted stored inventory from ${source}, resetting to empty object`);
|
||||||
inventory.stored = {};
|
inventory.stored = {};
|
||||||
@@ -432,15 +370,13 @@ function migrateToTrackerConfig() {
|
|||||||
userStats: {
|
userStats: {
|
||||||
customStats: [],
|
customStats: [],
|
||||||
showRPGAttributes: true,
|
showRPGAttributes: true,
|
||||||
alwaysSendAttributes: false,
|
|
||||||
allowAIUpdateAttributes: true,
|
|
||||||
rpgAttributes: [
|
rpgAttributes: [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
],
|
],
|
||||||
statusSection: {
|
statusSection: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -503,12 +439,12 @@ function migrateToTrackerConfig() {
|
|||||||
if (extensionSettings.trackerConfig.userStats.showRPGAttributes !== undefined) {
|
if (extensionSettings.trackerConfig.userStats.showRPGAttributes !== undefined) {
|
||||||
const shouldShow = extensionSettings.trackerConfig.userStats.showRPGAttributes;
|
const shouldShow = extensionSettings.trackerConfig.userStats.showRPGAttributes;
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: shouldShow },
|
{ id: 'str', name: 'STR', enabled: shouldShow },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: shouldShow },
|
{ id: 'dex', name: 'DEX', enabled: shouldShow },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: shouldShow },
|
{ id: 'con', name: 'CON', enabled: shouldShow },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: shouldShow },
|
{ id: 'int', name: 'INT', enabled: shouldShow },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: shouldShow },
|
{ id: 'wis', name: 'WIS', enabled: shouldShow },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: shouldShow }
|
{ id: 'cha', name: 'CHA', enabled: shouldShow }
|
||||||
];
|
];
|
||||||
delete extensionSettings.trackerConfig.userStats.showRPGAttributes;
|
delete extensionSettings.trackerConfig.userStats.showRPGAttributes;
|
||||||
console.log('[RPG Companion] Migrated showRPGAttributes to rpgAttributes array');
|
console.log('[RPG Companion] Migrated showRPGAttributes to rpgAttributes array');
|
||||||
@@ -517,12 +453,12 @@ function migrateToTrackerConfig() {
|
|||||||
// Ensure rpgAttributes exists even if no migration was needed
|
// Ensure rpgAttributes exists even if no migration was needed
|
||||||
if (!extensionSettings.trackerConfig.userStats.rpgAttributes) {
|
if (!extensionSettings.trackerConfig.userStats.rpgAttributes) {
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,141 +541,3 @@ function migrateToTrackerConfig() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates stats and skills to new format with description fields.
|
|
||||||
* - customStats: adds description field
|
|
||||||
* - rpgAttributes: adds description field
|
|
||||||
* - skillsSection.customFields: converts from string array to object array
|
|
||||||
* @returns {boolean} true if any migration was performed
|
|
||||||
*/
|
|
||||||
function migrateStatsAndSkillsFormat() {
|
|
||||||
let migrated = false;
|
|
||||||
|
|
||||||
if (!extensionSettings.trackerConfig?.userStats) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userStats = extensionSettings.trackerConfig.userStats;
|
|
||||||
|
|
||||||
// Migrate customStats - add description if missing
|
|
||||||
if (userStats.customStats) {
|
|
||||||
for (const stat of userStats.customStats) {
|
|
||||||
if (stat && typeof stat === 'object' && stat.description === undefined) {
|
|
||||||
stat.description = '';
|
|
||||||
migrated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate rpgAttributes - add description if missing
|
|
||||||
if (userStats.rpgAttributes) {
|
|
||||||
for (const attr of userStats.rpgAttributes) {
|
|
||||||
if (attr && typeof attr === 'object' && attr.description === undefined) {
|
|
||||||
attr.description = '';
|
|
||||||
migrated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate skillsSection.customFields - convert string array to object array
|
|
||||||
if (userStats.skillsSection?.customFields) {
|
|
||||||
const oldFields = userStats.skillsSection.customFields;
|
|
||||||
const hasOldFormat = oldFields.some(f => typeof f === 'string');
|
|
||||||
|
|
||||||
if (hasOldFormat) {
|
|
||||||
console.log('[RPG Companion] Migrating skill categories to new format');
|
|
||||||
userStats.skillsSection.customFields = oldFields.map((field, index) => {
|
|
||||||
if (typeof field === 'string') {
|
|
||||||
return {
|
|
||||||
id: 'skill_' + Date.now() + '_' + index,
|
|
||||||
name: field,
|
|
||||||
description: '',
|
|
||||||
enabled: true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Already an object, ensure it has all fields
|
|
||||||
return {
|
|
||||||
id: field.id || 'skill_' + Date.now() + '_' + index,
|
|
||||||
name: field.name || 'Skill',
|
|
||||||
description: field.description || '',
|
|
||||||
enabled: field.enabled !== false
|
|
||||||
};
|
|
||||||
});
|
|
||||||
migrated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate character stats - add description if missing
|
|
||||||
if (extensionSettings.trackerConfig?.presentCharacters?.characterStats?.customStats) {
|
|
||||||
for (const stat of extensionSettings.trackerConfig.presentCharacters.characterStats.customStats) {
|
|
||||||
if (stat && typeof stat === 'object' && stat.description === undefined) {
|
|
||||||
stat.description = '';
|
|
||||||
migrated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (migrated) {
|
|
||||||
console.log('[RPG Companion] Stats/skills format migration complete');
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrates quests from legacy format to structured format (questsV2).
|
|
||||||
* Legacy format: quests.main (string), quests.optional (string array)
|
|
||||||
* New format: questsV2.main ({name, description}), questsV2.optional (array of {name, description})
|
|
||||||
* @returns {boolean} true if any migration was performed
|
|
||||||
*/
|
|
||||||
function migrateQuestsFormat() {
|
|
||||||
let migrated = false;
|
|
||||||
|
|
||||||
// Initialize questsV2 if it doesn't exist
|
|
||||||
if (!extensionSettings.questsV2) {
|
|
||||||
extensionSettings.questsV2 = {
|
|
||||||
main: null,
|
|
||||||
optional: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate main quest if it exists in legacy format but not in new format
|
|
||||||
// Check if legacy format has data AND new format is empty/null
|
|
||||||
if (extensionSettings.quests?.main &&
|
|
||||||
extensionSettings.quests.main !== 'None' &&
|
|
||||||
extensionSettings.quests.main !== '' &&
|
|
||||||
(!extensionSettings.questsV2.main || !extensionSettings.questsV2.main.name)) {
|
|
||||||
extensionSettings.questsV2.main = {
|
|
||||||
name: extensionSettings.quests.main,
|
|
||||||
description: extensionSettings.quests?.mainDescription || ''
|
|
||||||
};
|
|
||||||
migrated = true;
|
|
||||||
console.log('[RPG Companion] Migrated main quest to structured format:', extensionSettings.quests.main);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate optional quests if they exist in legacy format but not in new format
|
|
||||||
// Check if legacy format has data AND new format is empty
|
|
||||||
if (extensionSettings.quests?.optional &&
|
|
||||||
Array.isArray(extensionSettings.quests.optional) &&
|
|
||||||
extensionSettings.quests.optional.length > 0 &&
|
|
||||||
(!extensionSettings.questsV2.optional || extensionSettings.questsV2.optional.length === 0)) {
|
|
||||||
const descriptions = extensionSettings.quests?.optionalDescriptions || [];
|
|
||||||
extensionSettings.questsV2.optional = extensionSettings.quests.optional
|
|
||||||
.filter(title => title && title !== 'None' && title !== '')
|
|
||||||
.map((title, i) => ({
|
|
||||||
name: title,
|
|
||||||
description: descriptions[i] || ''
|
|
||||||
}));
|
|
||||||
if (extensionSettings.questsV2.optional.length > 0) {
|
|
||||||
migrated = true;
|
|
||||||
console.log('[RPG Companion] Migrated optional quests to structured format:', extensionSettings.questsV2.optional.length, 'quests');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (migrated) {
|
|
||||||
console.log('[RPG Companion] Quests format migration complete');
|
|
||||||
}
|
|
||||||
|
|
||||||
return migrated;
|
|
||||||
}
|
|
||||||
|
|||||||
+31
-74
@@ -13,25 +13,15 @@ export let extensionSettings = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
autoUpdate: true,
|
autoUpdate: true,
|
||||||
updateDepth: 4, // How many messages to include in the context
|
updateDepth: 4, // How many messages to include in the context
|
||||||
messageInterceptionContextDepth: 4, // How many recent messages to send when intercepting user messages
|
|
||||||
generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately
|
generationMode: 'together', // 'separate' or 'together' - whether to generate with main response or separately
|
||||||
useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model
|
useSeparatePreset: false, // Use 'RPG Companion Trackers' preset for tracker generation instead of main API model
|
||||||
showUserStats: true,
|
showUserStats: true,
|
||||||
showInfoBox: true,
|
showInfoBox: true,
|
||||||
showCharacterThoughts: true,
|
showCharacterThoughts: true,
|
||||||
showInventory: true, // Show inventory section (v2 system)
|
showInventory: true, // Show inventory section (v2 system)
|
||||||
useSimplifiedInventory: false, // Use simplified single-list inventory instead of categorized (On Person/Stored/Assets)
|
|
||||||
showSkills: false, // Show skills as separate section (moves skills from Status to own tab)
|
|
||||||
enableItemSkillLinks: false, // Enable linking items to skills (item grants skill, removing item removes skill)
|
|
||||||
deleteSkillWithItem: false, // When true, deleting an item also deletes linked skills. When false (default), just unlinks.
|
|
||||||
showQuests: true, // Show quests section
|
|
||||||
showThoughtsInChat: true, // Show thoughts overlay in chat
|
showThoughtsInChat: true, // Show thoughts overlay in chat
|
||||||
enableHtmlPrompt: false, // Enable immersive HTML prompt injection
|
enableHtmlPrompt: false, // Enable immersive HTML prompt injection
|
||||||
customHtmlPrompt: '', // Custom HTML prompt text (empty = use default)
|
customHtmlPrompt: '', // Custom HTML prompt text (empty = use default)
|
||||||
customTrackerPrompt: '', // Custom tracker instruction prompt (empty = use default)
|
|
||||||
enableMessageInterception: false, // Enable intercepting user messages with LLM rewrite
|
|
||||||
messageInterceptionActive: true, // Runtime toggle to allow/skip interception
|
|
||||||
customMessageInterceptionPrompt: '', // Custom prompt for message interception (empty = default)
|
|
||||||
skipInjectionsForGuided: 'none', // skip injections for instruct injections and quiet prompts (GuidedGenerations compatibility)
|
skipInjectionsForGuided: 'none', // skip injections for instruct injections and quiet prompts (GuidedGenerations compatibility)
|
||||||
enablePlotButtons: true, // Show plot progression buttons above chat input
|
enablePlotButtons: true, // Show plot progression buttons above chat input
|
||||||
panelPosition: 'right', // 'left', 'right', or 'top'
|
panelPosition: 'right', // 'left', 'right', or 'top'
|
||||||
@@ -57,74 +47,42 @@ export let extensionSettings = {
|
|||||||
arousal: 0,
|
arousal: 0,
|
||||||
mood: '😐',
|
mood: '😐',
|
||||||
conditions: 'None',
|
conditions: 'None',
|
||||||
/** @type {InventoryV2} Legacy string-based inventory */
|
/** @type {InventoryV2} */
|
||||||
inventory: {
|
inventory: {
|
||||||
version: 2,
|
version: 2,
|
||||||
onPerson: "None",
|
onPerson: "None",
|
||||||
stored: {},
|
stored: {},
|
||||||
assets: "None"
|
assets: "None"
|
||||||
},
|
}
|
||||||
skills: "None" // Legacy single-string skills (for Status section)
|
|
||||||
},
|
},
|
||||||
/**
|
statNames: {
|
||||||
* Structured inventory v3 - items as objects with name, description, and skill links
|
health: 'Health',
|
||||||
* @type {{onPerson: Array<{name: string, description: string, grantsSkill?: string}>, stored: Object<string, Array>, assets: Array}}
|
satiety: 'Satiety',
|
||||||
*/
|
energy: 'Energy',
|
||||||
inventoryV3: {
|
hygiene: 'Hygiene',
|
||||||
onPerson: [], // Array of { name, description, grantsSkill? }
|
arousal: 'Arousal'
|
||||||
stored: {}, // { locationName: [{ name, description, grantsSkill? }] }
|
|
||||||
assets: [] // Array of { name, description }
|
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
* Structured skills v2 - abilities as objects with name, description, and item links
|
|
||||||
* Key is the skill category name from config
|
|
||||||
* @type {Object<string, Array<{name: string, description: string, grantedBy?: string}>>}
|
|
||||||
*/
|
|
||||||
skillsV2: {
|
|
||||||
// Example: "Combat": [{ name: "Sword Fighting", description: "Blade proficiency", grantedBy: "Iron Sword" }]
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Structured info box data (from JSON parsing)
|
|
||||||
*/
|
|
||||||
infoBoxData: {},
|
|
||||||
/**
|
|
||||||
* Structured characters data (from JSON parsing)
|
|
||||||
*/
|
|
||||||
charactersData: [],
|
|
||||||
/**
|
|
||||||
* Structured quests v2 (from JSON parsing)
|
|
||||||
*/
|
|
||||||
questsV2: {
|
|
||||||
main: null,
|
|
||||||
optional: []
|
|
||||||
},
|
|
||||||
// Legacy fields kept for backwards compatibility
|
|
||||||
skills: { list: [], categories: {} },
|
|
||||||
itemSkillLinks: {},
|
|
||||||
skillAbilityLinks: {},
|
|
||||||
skillsData: {},
|
|
||||||
// Tracker customization configuration
|
// Tracker customization configuration
|
||||||
trackerConfig: {
|
trackerConfig: {
|
||||||
userStats: {
|
userStats: {
|
||||||
// Array of custom stats (allows add/remove/rename)
|
// Array of custom stats (allows add/remove/rename)
|
||||||
customStats: [
|
customStats: [
|
||||||
{ id: 'health', name: 'Health', description: '', enabled: true },
|
{ id: 'health', name: 'Health', enabled: true },
|
||||||
{ id: 'satiety', name: 'Satiety', description: '', enabled: true },
|
{ id: 'satiety', name: 'Satiety', enabled: true },
|
||||||
{ id: 'energy', name: 'Energy', description: '', enabled: true },
|
{ id: 'energy', name: 'Energy', enabled: true },
|
||||||
{ id: 'hygiene', name: 'Hygiene', description: '', enabled: true },
|
{ id: 'hygiene', name: 'Hygiene', enabled: true },
|
||||||
{ id: 'arousal', name: 'Arousal', description: '', enabled: true }
|
{ id: 'arousal', name: 'Arousal', enabled: true }
|
||||||
],
|
],
|
||||||
// RPG Attributes (customizable D&D-style attributes)
|
// RPG Attributes (customizable D&D-style attributes)
|
||||||
showRPGAttributes: true,
|
showRPGAttributes: true,
|
||||||
alwaysSendAttributes: false, // If true, always send attributes; if false, only send with dice rolls
|
alwaysSendAttributes: false, // If true, always send attributes; if false, only send with dice rolls
|
||||||
allowAIUpdateAttributes: true, // If true, allow AI to update attributes from JSON response; if false, attributes are read-only
|
|
||||||
rpgAttributes: [
|
rpgAttributes: [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
],
|
],
|
||||||
// Status section config
|
// Status section config
|
||||||
statusSection: {
|
statusSection: {
|
||||||
@@ -132,14 +90,11 @@ export let extensionSettings = {
|
|||||||
showMoodEmoji: true,
|
showMoodEmoji: true,
|
||||||
customFields: ['Conditions'] // User can edit what to track
|
customFields: ['Conditions'] // User can edit what to track
|
||||||
},
|
},
|
||||||
// Skills section config - array of skill categories
|
// Optional skills field
|
||||||
skillsSection: {
|
skillsSection: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
label: 'Skills', // User-editable section label
|
label: 'Skills', // User-editable
|
||||||
customFields: [
|
customFields: [] // Array of skill names
|
||||||
// Each skill category has id, name, description, enabled
|
|
||||||
// Example: { id: 'combat', name: 'Combat', description: 'Fighting and weapon abilities', enabled: true }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
infoBox: {
|
infoBox: {
|
||||||
@@ -181,8 +136,8 @@ export let extensionSettings = {
|
|||||||
characterStats: {
|
characterStats: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
customStats: [
|
customStats: [
|
||||||
{ id: 'health', name: 'Health', description: '', enabled: true },
|
{ id: 'health', name: 'Health', enabled: true },
|
||||||
{ id: 'arousal', name: 'Arousal', description: '', enabled: true }
|
{ id: 'arousal', name: 'Arousal', enabled: true }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,6 +226,13 @@ export function addDebugLog(message, data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature flags for gradual rollout of new features
|
||||||
|
*/
|
||||||
|
export const FEATURE_FLAGS = {
|
||||||
|
useNewInventory: true // Enable v2 inventory system with categorized storage
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fallback avatar image (base64-encoded SVG with "?" icon)
|
* Fallback avatar image (base64-encoded SVG with "?" icon)
|
||||||
* Using base64 to avoid quote-encoding issues in HTML attributes
|
* Using base64 to avoid quote-encoding issues in HTML attributes
|
||||||
@@ -285,7 +247,6 @@ export let $userStatsContainer = null;
|
|||||||
export let $infoBoxContainer = null;
|
export let $infoBoxContainer = null;
|
||||||
export let $thoughtsContainer = null;
|
export let $thoughtsContainer = null;
|
||||||
export let $inventoryContainer = null;
|
export let $inventoryContainer = null;
|
||||||
export let $skillsContainer = null;
|
|
||||||
export let $questsContainer = null;
|
export let $questsContainer = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -355,10 +316,6 @@ export function setInventoryContainer($element) {
|
|||||||
$inventoryContainer = $element;
|
$inventoryContainer = $element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setSkillsContainer($element) {
|
|
||||||
$skillsContainer = $element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setQuestsContainer($element) {
|
export function setQuestsContainer($element) {
|
||||||
$questsContainer = $element;
|
$questsContainer = $element;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-54
@@ -30,15 +30,6 @@
|
|||||||
"template.settingsModal.display.showInfoBox": "Show Info Box",
|
"template.settingsModal.display.showInfoBox": "Show Info Box",
|
||||||
"template.settingsModal.display.showPresentCharacters": "Show Present Characters",
|
"template.settingsModal.display.showPresentCharacters": "Show Present Characters",
|
||||||
"template.settingsModal.display.showInventory": "Show Inventory",
|
"template.settingsModal.display.showInventory": "Show Inventory",
|
||||||
"template.settingsModal.display.useSimplifiedInventory": "Use Simplified Inventory",
|
|
||||||
"template.settingsModal.display.useSimplifiedInventoryNote": "Single flat list instead of On Person / Stored / Assets categories",
|
|
||||||
"template.settingsModal.display.showSkills": "Show Skills Section",
|
|
||||||
"template.settingsModal.display.showSkillsNote": "Displays skills as a separate tab instead of within Status. Configure skills in Edit Trackers.",
|
|
||||||
"template.settingsModal.display.enableItemSkillLinks": "Enable Item-Skill Links",
|
|
||||||
"template.settingsModal.display.enableItemSkillLinksNote": "Items can grant skills. Removing an item unlinks or removes the skill.",
|
|
||||||
"template.settingsModal.display.deleteSkillWithItem": "Delete skill when item removed",
|
|
||||||
"template.settingsModal.display.deleteSkillWithItemNote": "When disabled, removing an item just unlinks the skill. When enabled, the skill is deleted.",
|
|
||||||
"template.settingsModal.display.showQuests": "Show Quests",
|
|
||||||
"template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat",
|
"template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat",
|
||||||
"template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages",
|
"template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages",
|
||||||
"template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble",
|
"template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble",
|
||||||
@@ -65,19 +56,6 @@
|
|||||||
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "Only on impersonation requests",
|
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "Only on impersonation requests",
|
||||||
"template.settingsModal.advanced.skipInjectionsOptions.guided": "Always for guided or quiet prompts",
|
"template.settingsModal.advanced.skipInjectionsOptions.guided": "Always for guided or quiet prompts",
|
||||||
"template.settingsModal.advanced.skipInjectionsNote": "When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.",
|
"template.settingsModal.advanced.skipInjectionsNote": "When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.",
|
||||||
"template.settingsModal.advanced.enableMessageInterception": "Intercept & rewrite user messages with AI",
|
|
||||||
"template.settingsModal.advanced.enableMessageInterceptionNote": "When enabled, user messages are sent to the AI and rewritten in-place.",
|
|
||||||
"template.settingsModal.advanced.interceptionModeLabel": "Interception:",
|
|
||||||
"template.settingsModal.advanced.interceptionOn": "On",
|
|
||||||
"template.settingsModal.advanced.interceptionOff": "Off",
|
|
||||||
"template.settingsModal.advanced.messageInterceptionDepth": "Interception Context Messages:",
|
|
||||||
"template.settingsModal.advanced.messageInterceptionDepthNote": "How many recent messages to send with the interception prompt.",
|
|
||||||
"template.settingsModal.advanced.customMessageInterceptionPromptTitle": "Custom Message Interception Prompt:",
|
|
||||||
"template.settingsModal.advanced.restoreDefaultMessageInterceptionPrompt": "Restore Default",
|
|
||||||
"template.settingsModal.advanced.customMessageInterceptionPromptNote": "Customize the instructions sent to the AI when rewriting user messages. Leave empty to use the default guidance. The AI receives this prompt, the current RPG state JSON, and the recent messages you specify above.",
|
|
||||||
"template.settingsModal.advanced.customTrackerPromptTitle": "Custom Tracker Prompt:",
|
|
||||||
"template.settingsModal.advanced.restoreDefaultTrackerPrompt": "Restore Default",
|
|
||||||
"template.settingsModal.advanced.customTrackerPromptNote": "Customize the instructions sent to the AI for generating tracker data. Use {{user}} as a placeholder for the user's name. This is the main prompt that tells the AI how to format and update the RPG trackers.",
|
|
||||||
"template.settingsModal.advanced.customHtmlPromptTitle": "Custom HTML Prompt:",
|
"template.settingsModal.advanced.customHtmlPromptTitle": "Custom HTML Prompt:",
|
||||||
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "Restore Default",
|
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "Restore Default",
|
||||||
"template.settingsModal.advanced.customHtmlPromptNote": "Customize the HTML prompt injected when \"Enable Immersive HTML\" is enabled. The default prompt is shown above - you can edit it directly or replace it entirely. Click \"Restore Default\" to reset. This affects all generation modes (together, separate, and plot progression).",
|
"template.settingsModal.advanced.customHtmlPromptNote": "Customize the HTML prompt injected when \"Enable Immersive HTML\" is enabled. The default prompt is shown above - you can edit it directly or replace it entirely. Click \"Restore Default\" to reset. This affects all generation modes (together, separate, and plot progression).",
|
||||||
@@ -97,7 +75,6 @@
|
|||||||
"template.trackerEditorModal.userStatsTab.enableRpgAttributes": "Enable RPG Attributes Section",
|
"template.trackerEditorModal.userStatsTab.enableRpgAttributes": "Enable RPG Attributes Section",
|
||||||
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributes": "Always Include Attributes in Prompt",
|
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributes": "Always Include Attributes in Prompt",
|
||||||
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributesNote": "If disabled, attributes are only sent when a dice roll is active.",
|
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributesNote": "If disabled, attributes are only sent when a dice roll is active.",
|
||||||
"template.trackerEditorModal.userStatsTab.allowAIUpdateAttributes": "Allow AI to Update RPG Attributes",
|
|
||||||
"template.trackerEditorModal.userStatsTab.addAttributeButton": "Add Attribute",
|
"template.trackerEditorModal.userStatsTab.addAttributeButton": "Add Attribute",
|
||||||
"template.trackerEditorModal.userStatsTab.statusSectionTitle": "Status Section",
|
"template.trackerEditorModal.userStatsTab.statusSectionTitle": "Status Section",
|
||||||
"template.trackerEditorModal.userStatsTab.enableStatusSection": "Enable Status Section",
|
"template.trackerEditorModal.userStatsTab.enableStatusSection": "Enable Status Section",
|
||||||
@@ -105,10 +82,8 @@
|
|||||||
"template.trackerEditorModal.userStatsTab.statusFieldsLabel": "Status Fields (comma-separated):",
|
"template.trackerEditorModal.userStatsTab.statusFieldsLabel": "Status Fields (comma-separated):",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsSectionTitle": "Skills Section",
|
"template.trackerEditorModal.userStatsTab.skillsSectionTitle": "Skills Section",
|
||||||
"template.trackerEditorModal.userStatsTab.enableSkillsSection": "Enable Skills Section",
|
"template.trackerEditorModal.userStatsTab.enableSkillsSection": "Enable Skills Section",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsInSeparateTabNote": "Skills are displayed in a separate tab. This toggle only affects the Status section.",
|
|
||||||
"template.trackerEditorModal.userStatsTab.skillsLabelLabel": "Skills Label:",
|
"template.trackerEditorModal.userStatsTab.skillsLabelLabel": "Skills Label:",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsListLabel": "Skill Categories:",
|
"template.trackerEditorModal.userStatsTab.skillsListLabel": "Skills List (comma-separated):",
|
||||||
"template.trackerEditorModal.userStatsTab.addSkillButton": "Add Skill Category",
|
|
||||||
"template.trackerEditorModal.infoBoxTab.widgetsTitle": "Widgets",
|
"template.trackerEditorModal.infoBoxTab.widgetsTitle": "Widgets",
|
||||||
"template.trackerEditorModal.infoBoxTab.dateWidget": "Date",
|
"template.trackerEditorModal.infoBoxTab.dateWidget": "Date",
|
||||||
"template.trackerEditorModal.infoBoxTab.weatherWidget": "Weather",
|
"template.trackerEditorModal.infoBoxTab.weatherWidget": "Weather",
|
||||||
@@ -141,23 +116,15 @@
|
|||||||
"global.none": "None",
|
"global.none": "None",
|
||||||
"global.add": "Add",
|
"global.add": "Add",
|
||||||
"global.cancel": "Cancel",
|
"global.cancel": "Cancel",
|
||||||
"global.clickToEdit": "Click to edit",
|
|
||||||
"global.listView": "List view",
|
"global.listView": "List view",
|
||||||
"global.gridView": "Grid view",
|
"global.gridView": "Grid view",
|
||||||
"global.save": "Save",
|
"global.save": "Save",
|
||||||
"global.status":"Status",
|
"global.status":"Status",
|
||||||
"global.skills":"Skills",
|
|
||||||
"global.inventory":"Inventory",
|
"global.inventory":"Inventory",
|
||||||
"global.quests":"Quests",
|
"global.quests":"Quests",
|
||||||
"global.info":"Info",
|
"global.info":"Info",
|
||||||
"infobox.noData.title": "No data yet",
|
"infobox.noData.title": "No data yet",
|
||||||
"infobox.noData.instruction": "Generate a new response in the roleplay or switch to \"Separate Generation\" in Settings to access and click the \"Refresh RPG Info\" button",
|
"infobox.noData.instruction": "Generate a new response in the roleplay or switch to \"Separate Generation\" in Settings to access and click the \"Refresh RPG Info\" button",
|
||||||
"infobox.date": "Date",
|
|
||||||
"infobox.time": "Time",
|
|
||||||
"infobox.weather": "Weather",
|
|
||||||
"infobox.temperature": "Temperature",
|
|
||||||
"infobox.location": "Location",
|
|
||||||
"infobox.recentEvents": "Recent Events",
|
|
||||||
"infobox.recentEvents.title": "Recent Events",
|
"infobox.recentEvents.title": "Recent Events",
|
||||||
"infobox.recentEvents.addEventPlaceholder": "Add event...",
|
"infobox.recentEvents.addEventPlaceholder": "Add event...",
|
||||||
"inventory.section.onPerson": "On Person",
|
"inventory.section.onPerson": "On Person",
|
||||||
@@ -183,26 +150,6 @@
|
|||||||
"inventory.assets.addAssetButton": "Add Asset",
|
"inventory.assets.addAssetButton": "Add Asset",
|
||||||
"inventory.assets.addAssetPlaceholder": "Enter asset name...",
|
"inventory.assets.addAssetPlaceholder": "Enter asset name...",
|
||||||
"inventory.assets.description": "Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).",
|
"inventory.assets.description": "Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).",
|
||||||
"inventory.simplified.title": "Inventory",
|
|
||||||
"inventory.simplified.empty": "No items",
|
|
||||||
"inventory.simplified.addItemButton": "Add Item",
|
|
||||||
"inventory.simplified.addItemPlaceholder": "Enter item name...",
|
|
||||||
"inventory.simplified.removeTitle": "Remove item",
|
|
||||||
"skills.title": "Skills",
|
|
||||||
"skills.empty": "No skills configured",
|
|
||||||
"skills.emptyNote": "Configure skills in Edit Trackers → User Stats → Skills Section",
|
|
||||||
"skills.noAbilities": "No abilities yet",
|
|
||||||
"skills.addAbility": "Add ability",
|
|
||||||
"skills.addAbilityButton": "Add",
|
|
||||||
"skills.addAbilityPlaceholder": "Enter ability name...",
|
|
||||||
"skills.removeAbility": "Remove ability",
|
|
||||||
"skills.linkToItem": "Link to inventory item",
|
|
||||||
"skills.unlinkItem": "Remove item link",
|
|
||||||
"skills.gotoItem": "Go to item",
|
|
||||||
"skills.selectItemToLink": "Select item to link",
|
|
||||||
"skills.noItemsToLink": "No items in inventory to link",
|
|
||||||
"skills.linkCreated": "Skill linked to item",
|
|
||||||
"inventory.gotoLinkedSkills": "Go to linked skills",
|
|
||||||
"quests.section.main": "Main Quest",
|
"quests.section.main": "Main Quest",
|
||||||
"quests.section.optional": "Optional Quests",
|
"quests.section.optional": "Optional Quests",
|
||||||
"quests.main.title": "Main Quests",
|
"quests.main.title": "Main Quests",
|
||||||
|
|||||||
+2
-54
@@ -30,15 +30,6 @@
|
|||||||
"template.settingsModal.display.showInfoBox": "顯示資訊框",
|
"template.settingsModal.display.showInfoBox": "顯示資訊框",
|
||||||
"template.settingsModal.display.showPresentCharacters": "顯示在場角色",
|
"template.settingsModal.display.showPresentCharacters": "顯示在場角色",
|
||||||
"template.settingsModal.display.showInventory": "顯示物品欄",
|
"template.settingsModal.display.showInventory": "顯示物品欄",
|
||||||
"template.settingsModal.display.useSimplifiedInventory": "使用簡化物品欄",
|
|
||||||
"template.settingsModal.display.useSimplifiedInventoryNote": "使用單一列表取代分類(隨身/倉庫/資產)",
|
|
||||||
"template.settingsModal.display.showSkills": "顯示技能區塊",
|
|
||||||
"template.settingsModal.display.showSkillsNote": "將技能顯示為獨立分頁,而非在狀態欄內。請在編輯追蹤器中配置技能。",
|
|
||||||
"template.settingsModal.display.enableItemSkillLinks": "啟用物品-技能連結",
|
|
||||||
"template.settingsModal.display.enableItemSkillLinksNote": "物品可以授予技能。移除物品會取消連結或刪除技能。",
|
|
||||||
"template.settingsModal.display.deleteSkillWithItem": "移除物品時刪除技能",
|
|
||||||
"template.settingsModal.display.deleteSkillWithItemNote": "禁用時(預設),移除物品只會取消連結。啟用時,技能會被刪除。",
|
|
||||||
"template.settingsModal.display.showQuests": "顯示任務",
|
|
||||||
"template.settingsModal.display.showThoughtsInChat": "在聊天中顯示想法",
|
"template.settingsModal.display.showThoughtsInChat": "在聊天中顯示想法",
|
||||||
"template.settingsModal.display.showThoughtsInChatNote": "將角色想法顯示為其訊息旁的泡泡",
|
"template.settingsModal.display.showThoughtsInChatNote": "將角色想法顯示為其訊息旁的泡泡",
|
||||||
"template.settingsModal.display.alwaysShowThoughtBubble": "始終顯示想法泡泡",
|
"template.settingsModal.display.alwaysShowThoughtBubble": "始終顯示想法泡泡",
|
||||||
@@ -65,22 +56,9 @@
|
|||||||
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "僅在模擬請求時跳過",
|
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "僅在模擬請求時跳過",
|
||||||
"template.settingsModal.advanced.skipInjectionsOptions.guided": "始終跳過引導",
|
"template.settingsModal.advanced.skipInjectionsOptions.guided": "始終跳過引導",
|
||||||
"template.settingsModal.advanced.skipInjectionsNote": "當設置後,擴充功能在檢測到引導生成(通過 `instruct` 或 `quiet_prompt`)時,將根據所選模式不注入追蹤提示詞、範例或 HTML 指令。當與 GuidedGenerations 或類似擴充功能一起使用時非常有用。",
|
"template.settingsModal.advanced.skipInjectionsNote": "當設置後,擴充功能在檢測到引導生成(通過 `instruct` 或 `quiet_prompt`)時,將根據所選模式不注入追蹤提示詞、範例或 HTML 指令。當與 GuidedGenerations 或類似擴充功能一起使用時非常有用。",
|
||||||
"template.settingsModal.advanced.enableMessageInterception": "以 AI 攔截並改寫使用者訊息",
|
|
||||||
"template.settingsModal.advanced.enableMessageInterceptionNote": "啟用後,使用者訊息會連同當前 RPG 狀態與近期聊天內容一起送交 AI,然後在原位改寫。",
|
|
||||||
"template.settingsModal.advanced.interceptionModeLabel": "攔截模式:",
|
|
||||||
"template.settingsModal.advanced.interceptionOn": "開啟",
|
|
||||||
"template.settingsModal.advanced.interceptionOff": "關閉",
|
|
||||||
"template.settingsModal.advanced.messageInterceptionDepth": "攔截時的上下文訊息數:",
|
|
||||||
"template.settingsModal.advanced.messageInterceptionDepthNote": "攔截時要附帶的近期訊息數量。",
|
|
||||||
"template.settingsModal.advanced.customMessageInterceptionPromptTitle": "自訂訊息攔截提示:",
|
|
||||||
"template.settingsModal.advanced.restoreDefaultMessageInterceptionPrompt": "恢復預設",
|
|
||||||
"template.settingsModal.advanced.customMessageInterceptionPromptNote": "自訂 AI 改寫使用者訊息時的指令。留空則使用預設指引。AI 會收到這段提示詞、目前的 RPG 狀態 JSON,以及上方設定的近期訊息。",
|
|
||||||
"template.settingsModal.advanced.customTrackerPromptTitle": "自訂追蹤器提示詞:",
|
|
||||||
"template.settingsModal.advanced.restoreDefaultTrackerPrompt": "恢復預設",
|
|
||||||
"template.settingsModal.advanced.customTrackerPromptNote": "自訂發送給 AI 生成追蹤器數據的指令。使用 {{user}} 作為使用者名稱的佔位符。這是告訴 AI 如何格式化和更新 RPG 追蹤器的主要提示詞。",
|
|
||||||
"template.settingsModal.advanced.customHtmlPromptTitle": "自訂 HTML 提示詞:",
|
"template.settingsModal.advanced.customHtmlPromptTitle": "自訂 HTML 提示詞:",
|
||||||
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "恢復預設",
|
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "恢復預設",
|
||||||
"template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用"啟用沉浸式 HTML"時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊"恢復預設"以重置。這會影響所有生成模式(同時、單獨和劇情推進)。",
|
"template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用“啟用沉浸式 HTML”時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊“恢復預設”以重置。這會影響所有生成模式(同時、單獨和劇情推進)。",
|
||||||
"template.settingsModal.advanced.clearCache": "清除擴充功能快取",
|
"template.settingsModal.advanced.clearCache": "清除擴充功能快取",
|
||||||
"template.settingsModal.advanced.resetFabPositions": "重置按鈕位置",
|
"template.settingsModal.advanced.resetFabPositions": "重置按鈕位置",
|
||||||
"template.settingsModal.advanced.resetFabPositionsNote": "將所有浮動操作按鈕(切換、刷新、調試)重置為預設的左上位置。如果按鈕在螢幕外,這會很有用。",
|
"template.settingsModal.advanced.resetFabPositionsNote": "將所有浮動操作按鈕(切換、刷新、調試)重置為預設的左上位置。如果按鈕在螢幕外,這會很有用。",
|
||||||
@@ -97,7 +75,6 @@
|
|||||||
"template.trackerEditorModal.userStatsTab.enableRpgAttributes": "啟用 RPG 屬性",
|
"template.trackerEditorModal.userStatsTab.enableRpgAttributes": "啟用 RPG 屬性",
|
||||||
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributes": "始終發送屬性(prompt)",
|
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributes": "始終發送屬性(prompt)",
|
||||||
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributesNote": "將 RPG 屬性始終包含在提示詞中,即使它們未顯示在面板上也一樣。",
|
"template.trackerEditorModal.userStatsTab.alwaysIncludeAttributesNote": "將 RPG 屬性始終包含在提示詞中,即使它們未顯示在面板上也一樣。",
|
||||||
"template.trackerEditorModal.userStatsTab.allowAIUpdateAttributes": "允許 AI 更新 RPG 屬性",
|
|
||||||
"template.trackerEditorModal.userStatsTab.addAttributeButton": "添加屬性",
|
"template.trackerEditorModal.userStatsTab.addAttributeButton": "添加屬性",
|
||||||
"template.trackerEditorModal.userStatsTab.statusSectionTitle": "狀態欄",
|
"template.trackerEditorModal.userStatsTab.statusSectionTitle": "狀態欄",
|
||||||
"template.trackerEditorModal.userStatsTab.enableStatusSection": "啟用狀態欄",
|
"template.trackerEditorModal.userStatsTab.enableStatusSection": "啟用狀態欄",
|
||||||
@@ -105,7 +82,6 @@
|
|||||||
"template.trackerEditorModal.userStatsTab.statusFieldsLabel": "狀態欄欄位(以逗號分隔):",
|
"template.trackerEditorModal.userStatsTab.statusFieldsLabel": "狀態欄欄位(以逗號分隔):",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsSectionTitle": "技能欄",
|
"template.trackerEditorModal.userStatsTab.skillsSectionTitle": "技能欄",
|
||||||
"template.trackerEditorModal.userStatsTab.enableSkillsSection": "啟用技能欄",
|
"template.trackerEditorModal.userStatsTab.enableSkillsSection": "啟用技能欄",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsInSeparateTabNote": "技能已顯示在獨立分頁中。此開關僅影響狀態欄。",
|
|
||||||
"template.trackerEditorModal.userStatsTab.skillsLabelLabel": "技能欄標籤:",
|
"template.trackerEditorModal.userStatsTab.skillsLabelLabel": "技能欄標籤:",
|
||||||
"template.trackerEditorModal.userStatsTab.skillsListLabel": " 技能列表(以逗號分隔):",
|
"template.trackerEditorModal.userStatsTab.skillsListLabel": " 技能列表(以逗號分隔):",
|
||||||
"template.trackerEditorModal.infoBoxTab.widgetsTitle": "小工具",
|
"template.trackerEditorModal.infoBoxTab.widgetsTitle": "小工具",
|
||||||
@@ -140,23 +116,15 @@
|
|||||||
"global.none": "None",
|
"global.none": "None",
|
||||||
"global.add": "添加",
|
"global.add": "添加",
|
||||||
"global.cancel": "取消",
|
"global.cancel": "取消",
|
||||||
"global.clickToEdit": "點擊編輯",
|
|
||||||
"global.save": "保存",
|
"global.save": "保存",
|
||||||
"global.listView": "清單檢視",
|
"global.listView": "清單檢視",
|
||||||
"global.gridView": "格子檢視",
|
"global.gridView": "格子檢視",
|
||||||
"global.status": "狀態欄",
|
"global.status": "狀態欄",
|
||||||
"global.skills": "技能",
|
|
||||||
"global.inventory": "物品欄",
|
"global.inventory": "物品欄",
|
||||||
"global.quests": "任務",
|
"global.quests": "任務",
|
||||||
"global.info":"資訊",
|
"global.info":"資訊",
|
||||||
"infobox.noData.title": "無資訊可顯示",
|
"infobox.noData.title": "無資訊可顯示",
|
||||||
"infobox.noData.instruction": "在RP中產生新的回复,或在設定中切換到"單獨生成",然後點擊"刷新資訊"按鈕。",
|
"infobox.noData.instruction": "在RP中產生新的回复,或在設定中切換到“單獨生成”,然後點擊“刷新資訊”按鈕。",
|
||||||
"infobox.date": "日期",
|
|
||||||
"infobox.time": "時間",
|
|
||||||
"infobox.weather": "天氣",
|
|
||||||
"infobox.temperature": "溫度",
|
|
||||||
"infobox.location": "位置",
|
|
||||||
"infobox.recentEvents": "近期事件",
|
|
||||||
"infobox.recentEvents.title": "近期事件",
|
"infobox.recentEvents.title": "近期事件",
|
||||||
"infobox.recentEvents.addEventPlaceholder": "添加事件...",
|
"infobox.recentEvents.addEventPlaceholder": "添加事件...",
|
||||||
"inventory.section.onPerson": "隨身物品",
|
"inventory.section.onPerson": "隨身物品",
|
||||||
@@ -182,26 +150,6 @@
|
|||||||
"inventory.assets.addAssetButton": "添加資產",
|
"inventory.assets.addAssetButton": "添加資產",
|
||||||
"inventory.assets.addAssetPlaceholder": "輸入資產名稱...",
|
"inventory.assets.addAssetPlaceholder": "輸入資產名稱...",
|
||||||
"inventory.assets.description": "資產包括車輛(汽車、摩托車)、房產(房屋、公寓)和主要設備(車間工具、特殊物品)。",
|
"inventory.assets.description": "資產包括車輛(汽車、摩托車)、房產(房屋、公寓)和主要設備(車間工具、特殊物品)。",
|
||||||
"inventory.simplified.title": "物品欄",
|
|
||||||
"inventory.simplified.empty": "沒有物品",
|
|
||||||
"inventory.simplified.addItemButton": "添加物品",
|
|
||||||
"inventory.simplified.addItemPlaceholder": "輸入物品名稱...",
|
|
||||||
"inventory.simplified.removeTitle": "移除物品",
|
|
||||||
"skills.title": "技能",
|
|
||||||
"skills.empty": "未配置技能",
|
|
||||||
"skills.emptyNote": "請在編輯追蹤器 → 使用者狀態 → 技能區塊中配置技能",
|
|
||||||
"skills.noAbilities": "尚無能力",
|
|
||||||
"skills.addAbility": "新增能力",
|
|
||||||
"skills.addAbilityButton": "新增",
|
|
||||||
"skills.addAbilityPlaceholder": "輸入能力名稱...",
|
|
||||||
"skills.removeAbility": "移除能力",
|
|
||||||
"skills.linkToItem": "連結到物品欄物品",
|
|
||||||
"skills.unlinkItem": "移除物品連結",
|
|
||||||
"skills.gotoItem": "前往物品",
|
|
||||||
"skills.selectItemToLink": "選擇要連結的物品",
|
|
||||||
"skills.noItemsToLink": "物品欄中沒有可連結的物品",
|
|
||||||
"skills.linkCreated": "技能已連結到物品",
|
|
||||||
"inventory.gotoLinkedSkills": "前往已連結的技能",
|
|
||||||
"quests.section.main": "主線任務",
|
"quests.section.main": "主線任務",
|
||||||
"quests.section.optional": "支線任務",
|
"quests.section.optional": "支線任務",
|
||||||
"quests.main.title": "主線任務",
|
"quests.main.title": "主線任務",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
extensionSettings,
|
extensionSettings,
|
||||||
|
pendingDiceRoll,
|
||||||
setPendingDiceRoll
|
setPendingDiceRoll
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveSettings } from '../../core/persistence.js';
|
import { saveSettings } from '../../core/persistence.js';
|
||||||
|
|||||||
@@ -3,10 +3,11 @@
|
|||||||
* Adds maximum activation limit to SillyTavern's World Info system
|
* Adds maximum activation limit to SillyTavern's World Info system
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { eventSource } from '../../../../../../../script.js';
|
import { eventSource, event_types } from '../../../../../../../script.js';
|
||||||
|
|
||||||
let maxActivations = 0; // 0 = unlimited
|
let maxActivations = 0; // 0 = unlimited
|
||||||
let settingsInitialized = false;
|
let settingsInitialized = false;
|
||||||
|
let activatedEntriesThisGeneration = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the lorebook limiter
|
* Initialize the lorebook limiter
|
||||||
@@ -135,6 +136,11 @@ function injectMaxActivationsUI() {
|
|||||||
function patchWorldInfoActivation() {
|
function patchWorldInfoActivation() {
|
||||||
console.log('[Lorebook Limiter] Setting up activation limiter...');
|
console.log('[Lorebook Limiter] Setting up activation limiter...');
|
||||||
|
|
||||||
|
// We need to intercept at the module level
|
||||||
|
// Use a Proxy on the module loader
|
||||||
|
const originalDefine = window.define;
|
||||||
|
const originalRequire = window.require;
|
||||||
|
|
||||||
// Try multiple approaches to hook into the WI system
|
// Try multiple approaches to hook into the WI system
|
||||||
const attemptPatch = () => {
|
const attemptPatch = () => {
|
||||||
// Approach 1: Direct window access
|
// Approach 1: Direct window access
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
* Handles generation of lorebook entries from chat history
|
* Handles generation of lorebook entries from chat history
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { chat, generateRaw, eventSource, event_types } from '../../../../../../../script.js';
|
import { chat, characters, this_chid, generateRaw, substituteParams, eventSource, event_types } from '../../../../../../../script.js';
|
||||||
|
import { selected_group } from '../../../../../../group-chats.js';
|
||||||
import { extensionSettings, addDebugLog } from '../../core/state.js';
|
import { extensionSettings, addDebugLog } from '../../core/state.js';
|
||||||
import { checkWorldInfo, createNewWorldInfo, openWorldInfoEditor, saveWorldInfo } from '../../../../../../world-info.js';
|
import { saveSettings } from '../../core/persistence.js';
|
||||||
|
import { checkWorldInfo, createNewWorldInfo, openWorldInfoEditor, saveWorldInfo, setWorldInfoSettings } from '../../../../../../world-info.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to log to both console and debug logs array
|
* Helper to log to both console and debug logs array
|
||||||
@@ -115,6 +117,68 @@ function createConstantHeaderEntry() {
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a world info entry to a lorebook
|
||||||
|
* @param {string} lorebookUid - The filename/UID of the lorebook
|
||||||
|
* @param {Object} entry - The entry data
|
||||||
|
*/
|
||||||
|
async function saveWorldInfoEntry(lorebookUid, entry) {
|
||||||
|
try {
|
||||||
|
debugLog('[Memory Recollection] Saving entry to lorebook:', lorebookUid);
|
||||||
|
|
||||||
|
// Open the world info editor for this lorebook to load its data
|
||||||
|
await openWorldInfoEditor(lorebookUid);
|
||||||
|
|
||||||
|
// Wait for it to load
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
// Now access the loaded world info data
|
||||||
|
const worldInfo = window.world_info;
|
||||||
|
|
||||||
|
debugLog('[Memory Recollection] World info after opening:', {
|
||||||
|
type: typeof worldInfo,
|
||||||
|
isArray: Array.isArray(worldInfo),
|
||||||
|
hasEntries: worldInfo?.entries !== undefined,
|
||||||
|
keys: worldInfo ? Object.keys(worldInfo).slice(0, 10) : null
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try different structures - it might be an array or might have different properties
|
||||||
|
let entries;
|
||||||
|
if (worldInfo && typeof worldInfo === 'object') {
|
||||||
|
if (worldInfo.entries) {
|
||||||
|
entries = worldInfo.entries;
|
||||||
|
} else if (Array.isArray(worldInfo)) {
|
||||||
|
// If it's an array, convert to entries object
|
||||||
|
entries = {};
|
||||||
|
worldInfo.forEach((e, i) => {
|
||||||
|
if (e && e.uid) {
|
||||||
|
entries[e.uid] = e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entries) {
|
||||||
|
entries = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the entry
|
||||||
|
entries[entry.uid] = entry;
|
||||||
|
|
||||||
|
debugLog('[Memory Recollection] Entry added, saving world info...');
|
||||||
|
|
||||||
|
// Save using the imported saveWorldInfo function
|
||||||
|
// Pass the entries as the data structure
|
||||||
|
await saveWorldInfo(lorebookUid, { entries });
|
||||||
|
|
||||||
|
debugLog('[Memory Recollection] Entry saved successfully');
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Memory Recollection] Error saving entry:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save multiple world info entries to a lorebook at once
|
* Save multiple world info entries to a lorebook at once
|
||||||
* @param {string} lorebookUid - The filename/UID of the lorebook
|
* @param {string} lorebookUid - The filename/UID of the lorebook
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export function setupPlotButtons(handlePlotClick) {
|
|||||||
*/
|
*/
|
||||||
export async function sendPlotProgression(type) {
|
export async function sendPlotProgression(type) {
|
||||||
if (!extensionSettings.enabled) {
|
if (!extensionSettings.enabled) {
|
||||||
|
// console.log('[RPG Companion] Extension is disabled');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +83,8 @@ export async function sendPlotProgression(type) {
|
|||||||
extensionSettings.enabled = false;
|
extensionSettings.enabled = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// console.log(`[RPG Companion] Sending ${type} plot progression request...`);
|
||||||
|
|
||||||
// Build the prompt based on type
|
// Build the prompt based on type
|
||||||
let prompt = '';
|
let prompt = '';
|
||||||
if (type === 'random') {
|
if (type === 'random') {
|
||||||
@@ -97,8 +100,13 @@ export async function sendPlotProgression(type) {
|
|||||||
prompt += '\n\n' + htmlPromptText;
|
prompt += '\n\n' + htmlPromptText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set flag to indicate we're doing plot progression
|
||||||
|
// This will be used by onMessageReceived to clear the prompt after generation completes
|
||||||
setIsPlotProgression(true);
|
setIsPlotProgression(true);
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Calling Generate with continuation and plot prompt');
|
||||||
|
// console.log('[RPG Companion] Full prompt:', prompt);
|
||||||
|
|
||||||
// Pass the prompt via options with the correct property name
|
// Pass the prompt via options with the correct property name
|
||||||
// Based on /continue slash command implementation, it uses quiet_prompt (underscore, not camelCase)
|
// Based on /continue slash command implementation, it uses quiet_prompt (underscore, not camelCase)
|
||||||
const options = {
|
const options = {
|
||||||
@@ -106,7 +114,10 @@ export async function sendPlotProgression(type) {
|
|||||||
quietToLoud: true
|
quietToLoud: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Call Generate with 'continue' type and our custom prompt
|
||||||
await Generate('continue', options);
|
await Generate('continue', options);
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Plot progression generation triggered');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Error sending plot progression:', error);
|
console.error('[RPG Companion] Error sending plot progression:', error);
|
||||||
setIsPlotProgression(false);
|
setIsPlotProgression(false);
|
||||||
|
|||||||
@@ -10,14 +10,18 @@ import {
|
|||||||
lastGeneratedData,
|
lastGeneratedData,
|
||||||
committedTrackerData,
|
committedTrackerData,
|
||||||
isGenerating,
|
isGenerating,
|
||||||
|
lastActionWasSwipe,
|
||||||
setIsGenerating,
|
setIsGenerating,
|
||||||
setLastActionWasSwipe
|
setLastActionWasSwipe
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveChatData } from '../../core/persistence.js';
|
import { saveChatData } from '../../core/persistence.js';
|
||||||
import { generateSeparateUpdatePrompt } from './promptBuilder.js';
|
import { generateSeparateUpdatePrompt } from './promptBuilder.js';
|
||||||
import { parseResponse, parseUserStats, parseSkills, tryParseJSONResponse } from './parser.js';
|
import { parseResponse, parseUserStats } from './parser.js';
|
||||||
|
import { renderUserStats } from '../rendering/userStats.js';
|
||||||
|
import { renderInfoBox } from '../rendering/infoBox.js';
|
||||||
|
import { renderThoughts } from '../rendering/thoughts.js';
|
||||||
|
import { renderInventory } from '../rendering/inventory.js';
|
||||||
import { renderQuests } from '../rendering/quests.js';
|
import { renderQuests } from '../rendering/quests.js';
|
||||||
import { renderSkills } from '../rendering/skills.js';
|
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
|
|
||||||
// Store the original preset name to restore after tracker generation
|
// Store the original preset name to restore after tracker generation
|
||||||
@@ -32,8 +36,12 @@ async function getCurrentPresetName() {
|
|||||||
// Use /preset without arguments to get the current preset name
|
// Use /preset without arguments to get the current preset name
|
||||||
const result = await executeSlashCommandsOnChatInput('/preset', { quiet: true });
|
const result = await executeSlashCommandsOnChatInput('/preset', { quiet: true });
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] /preset result:', result);
|
||||||
|
|
||||||
|
// The result should be an object with a 'pipe' property containing the preset name
|
||||||
if (result && typeof result === 'object' && result.pipe) {
|
if (result && typeof result === 'object' && result.pipe) {
|
||||||
const presetName = String(result.pipe).trim();
|
const presetName = String(result.pipe).trim();
|
||||||
|
// console.log('[RPG Companion] Extracted preset name:', presetName);
|
||||||
return presetName || null;
|
return presetName || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,7 +62,11 @@ async function getCurrentPresetName() {
|
|||||||
*/
|
*/
|
||||||
async function switchToPreset(presetName) {
|
async function switchToPreset(presetName) {
|
||||||
try {
|
try {
|
||||||
|
// Use the /preset slash command to switch presets
|
||||||
|
// This is the proper way to change presets in SillyTavern
|
||||||
await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true });
|
await executeSlashCommandsOnChatInput(`/preset ${presetName}`, { quiet: true });
|
||||||
|
|
||||||
|
// console.log(`[RPG Companion] Switched to preset "${presetName}"`);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[RPG Companion] Error switching preset:', error);
|
console.error('[RPG Companion] Error switching preset:', error);
|
||||||
@@ -75,6 +87,7 @@ async function switchToPreset(presetName) {
|
|||||||
*/
|
*/
|
||||||
export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory) {
|
export async function updateRPGData(renderUserStats, renderInfoBox, renderThoughts, renderInventory) {
|
||||||
if (isGenerating) {
|
if (isGenerating) {
|
||||||
|
// console.log('[RPG Companion] Already generating, skipping...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +96,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (extensionSettings.generationMode !== 'separate') {
|
if (extensionSettings.generationMode !== 'separate') {
|
||||||
|
// console.log('[RPG Companion] Not in separate mode, skipping manual update');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,27 +132,19 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
const jsonParsed = tryParseJSONResponse(response);
|
// console.log('[RPG Companion] Raw AI response:', response);
|
||||||
|
const parsedData = parseResponse(response);
|
||||||
|
// console.log('[RPG Companion] Parsed data:', parsedData);
|
||||||
|
// console.log('[RPG Companion] parsedData.userStats:', parsedData.userStats ? parsedData.userStats.substring(0, 100) + '...' : 'null');
|
||||||
|
|
||||||
if (jsonParsed) {
|
// DON'T update lastGeneratedData here - it should only reflect the data
|
||||||
// JSON parsing succeeded - render all sections
|
// from the assistant message the user replied to, not auto-generated updates
|
||||||
console.log('[RPG Companion] JSON parsing successful');
|
// This ensures swipes/regenerations use consistent source data
|
||||||
renderUserStats();
|
|
||||||
renderInfoBox();
|
|
||||||
renderThoughts();
|
|
||||||
renderInventory();
|
|
||||||
renderQuests();
|
|
||||||
renderSkills();
|
|
||||||
saveChatData();
|
|
||||||
} else {
|
|
||||||
console.warn('[RPG Companion] JSON parsing failed, attempting legacy text parsing...');
|
|
||||||
const parsedData = parseResponse(response);
|
|
||||||
|
|
||||||
extensionSettings.charactersData = [];
|
// Store RPG data for the last assistant message (separate mode)
|
||||||
const parsedCharacterThoughts = parsedData.characterThoughts || '';
|
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
|
||||||
|
// console.log('[RPG Companion] Last message is_user:', lastMessage ? lastMessage.is_user : 'no message');
|
||||||
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
|
if (lastMessage && !lastMessage.is_user) {
|
||||||
if (lastMessage && !lastMessage.is_user) {
|
|
||||||
if (!lastMessage.extra) {
|
if (!lastMessage.extra) {
|
||||||
lastMessage.extra = {};
|
lastMessage.extra = {};
|
||||||
}
|
}
|
||||||
@@ -147,40 +153,50 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
}
|
}
|
||||||
|
|
||||||
const currentSwipeId = lastMessage.swipe_id || 0;
|
const currentSwipeId = lastMessage.swipe_id || 0;
|
||||||
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
|
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
|
||||||
userStats: parsedData.userStats,
|
userStats: parsedData.userStats,
|
||||||
infoBox: parsedData.infoBox,
|
infoBox: parsedData.infoBox,
|
||||||
characterThoughts: parsedCharacterThoughts
|
characterThoughts: parsedData.characterThoughts
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parsedData.userStats) {
|
// console.log('[RPG Companion] Stored separate mode RPG data for message swipe', currentSwipeId);
|
||||||
|
|
||||||
|
// Update lastGeneratedData for display AND future commit
|
||||||
|
if (parsedData.userStats) {
|
||||||
lastGeneratedData.userStats = parsedData.userStats;
|
lastGeneratedData.userStats = parsedData.userStats;
|
||||||
parseUserStats(parsedData.userStats);
|
parseUserStats(parsedData.userStats);
|
||||||
}
|
}
|
||||||
if (parsedData.skills) {
|
|
||||||
parseSkills(parsedData.skills);
|
|
||||||
}
|
|
||||||
if (parsedData.infoBox) {
|
if (parsedData.infoBox) {
|
||||||
lastGeneratedData.infoBox = parsedData.infoBox;
|
lastGeneratedData.infoBox = parsedData.infoBox;
|
||||||
}
|
}
|
||||||
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
|
if (parsedData.characterThoughts) {
|
||||||
|
lastGeneratedData.characterThoughts = parsedData.characterThoughts;
|
||||||
|
}
|
||||||
|
// console.log('[RPG Companion] 💾 SEPARATE MODE: Updated lastGeneratedData:', {
|
||||||
|
// userStats: lastGeneratedData.userStats ? 'exists' : 'null',
|
||||||
|
// infoBox: lastGeneratedData.infoBox ? 'exists' : 'null',
|
||||||
|
// characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null'
|
||||||
|
// });
|
||||||
|
|
||||||
const hasAnyCommittedContent = (
|
// Only auto-commit on TRULY first generation (no committed data exists at all)
|
||||||
(committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') ||
|
// This prevents auto-commit after refresh when we have saved committed data
|
||||||
(committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') ||
|
const hasAnyCommittedContent = (
|
||||||
(committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n')
|
(committedTrackerData.userStats && committedTrackerData.userStats.trim() !== '') ||
|
||||||
);
|
(committedTrackerData.infoBox && committedTrackerData.infoBox.trim() !== '' && committedTrackerData.infoBox !== 'Info Box\n---\n') ||
|
||||||
|
(committedTrackerData.characterThoughts && committedTrackerData.characterThoughts.trim() !== '' && committedTrackerData.characterThoughts !== 'Present Characters\n---\n')
|
||||||
|
);
|
||||||
|
|
||||||
if (!hasAnyCommittedContent) {
|
// Only commit if we have NO committed content at all (truly first time ever)
|
||||||
committedTrackerData.userStats = parsedData.userStats;
|
if (!hasAnyCommittedContent) {
|
||||||
committedTrackerData.infoBox = parsedData.infoBox;
|
committedTrackerData.userStats = parsedData.userStats;
|
||||||
committedTrackerData.characterThoughts = parsedCharacterThoughts;
|
committedTrackerData.infoBox = parsedData.infoBox;
|
||||||
}
|
committedTrackerData.characterThoughts = parsedData.characterThoughts;
|
||||||
|
// console.log('[RPG Companion] 🔆 FIRST TIME: Auto-committed tracker data');
|
||||||
|
}
|
||||||
|
|
||||||
// Render the updated data
|
// Render the updated data
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
renderInfoBox();
|
renderInfoBox();
|
||||||
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
|
|
||||||
renderThoughts();
|
renderThoughts();
|
||||||
renderInventory();
|
renderInventory();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
@@ -191,15 +207,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
}
|
}
|
||||||
renderUserStats();
|
renderUserStats();
|
||||||
renderInfoBox();
|
renderInfoBox();
|
||||||
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
|
|
||||||
renderThoughts();
|
renderThoughts();
|
||||||
renderInventory();
|
renderInventory();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to chat metadata
|
// Save to chat metadata
|
||||||
saveChatData();
|
saveChatData();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -214,9 +228,14 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
|||||||
|
|
||||||
setIsGenerating(false);
|
setIsGenerating(false);
|
||||||
|
|
||||||
|
// Restore button to original state
|
||||||
const $updateBtn = $('#rpg-manual-update');
|
const $updateBtn = $('#rpg-manual-update');
|
||||||
const refreshText = i18n.getTranslation('template.mainPanel.refreshRpgInfo') || 'Refresh RPG Info';
|
const refreshText = i18n.getTranslation('template.mainPanel.refreshRpgInfo') || 'Refresh RPG Info';
|
||||||
$updateBtn.html(`<i class="fa-solid fa-sync"></i> ${refreshText}`).prop('disabled', false);
|
$updateBtn.html(`<i class="fa-solid fa-sync"></i> ${refreshText}`).prop('disabled', false);
|
||||||
|
|
||||||
|
// Reset the flag after tracker generation completes
|
||||||
|
// This ensures the flag persists through both main generation AND tracker generation
|
||||||
|
// console.log('[RPG Companion] 🔄 Tracker generation complete - resetting lastActionWasSwipe to false');
|
||||||
setLastActionWasSwipe(false);
|
setLastActionWasSwipe(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,26 +10,18 @@ import {
|
|||||||
committedTrackerData,
|
committedTrackerData,
|
||||||
lastGeneratedData,
|
lastGeneratedData,
|
||||||
isGenerating,
|
isGenerating,
|
||||||
lastActionWasSwipe
|
lastActionWasSwipe,
|
||||||
|
setLastActionWasSwipe
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { evaluateSuppression } from './suppression.js';
|
import { evaluateSuppression } from './suppression.js';
|
||||||
import { parseUserStats } from './parser.js';
|
import { parseUserStats } from './parser.js';
|
||||||
import {
|
import {
|
||||||
generateJSONTrackerInstructions,
|
generateTrackerExample,
|
||||||
|
generateTrackerInstructions,
|
||||||
generateContextualSummary,
|
generateContextualSummary,
|
||||||
DEFAULT_HTML_PROMPT
|
DEFAULT_HTML_PROMPT
|
||||||
} from './promptBuilder.js';
|
} from './promptBuilder.js';
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets tracker instructions (always uses JSON format)
|
|
||||||
* @param {boolean} includeHtmlPrompt
|
|
||||||
* @param {boolean} includeContinuation
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function getTrackerInstructions(includeHtmlPrompt, includeContinuation) {
|
|
||||||
return generateJSONTrackerInstructions(includeHtmlPrompt, includeContinuation);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for generation start.
|
* Event handler for generation start.
|
||||||
* Manages tracker data commitment and prompt injection based on generation mode.
|
* Manages tracker data commitment and prompt injection based on generation mode.
|
||||||
@@ -38,7 +30,15 @@ function getTrackerInstructions(includeHtmlPrompt, includeContinuation) {
|
|||||||
* @param {Object} data - Event data
|
* @param {Object} data - Event data
|
||||||
*/
|
*/
|
||||||
export function onGenerationStarted(type, data) {
|
export function onGenerationStarted(type, data) {
|
||||||
|
// console.log('[RPG Companion] onGenerationStarted called');
|
||||||
|
// console.log('[RPG Companion] enabled:', extensionSettings.enabled);
|
||||||
|
// console.log('[RPG Companion] generationMode:', extensionSettings.generationMode);
|
||||||
|
// console.log('[RPG Companion] ⚡ EVENT: onGenerationStarted - lastActionWasSwipe =', lastActionWasSwipe, '| isGenerating =', isGenerating);
|
||||||
|
// console.log('[RPG Companion] Committed Prompt:', committedTrackerData);
|
||||||
|
|
||||||
|
// Skip tracker injection for image generation requests
|
||||||
if (data?.quietImage) {
|
if (data?.quietImage) {
|
||||||
|
// console.log('[RPG Companion] Detected image generation (quietImage=true), skipping tracker injection');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,27 +48,74 @@ export function onGenerationStarted(type, data) {
|
|||||||
|
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const chat = context.chat;
|
const chat = context.chat;
|
||||||
|
// Detect if a guided generation is active (GuidedGenerations and similar extensions
|
||||||
|
// inject an ephemeral 'instruct' injection into chatMetadata.script_injects).
|
||||||
|
// If present, we should avoid injecting RPG tracker instructions that ask
|
||||||
|
// the model to include stats/etc. This prevents conflicts when guided prompts
|
||||||
|
// are used (e.g., GuidedGenerations Extension).
|
||||||
|
// Evaluate suppression using the shared helper
|
||||||
const suppression = evaluateSuppression(extensionSettings, context, data);
|
const suppression = evaluateSuppression(extensionSettings, context, data);
|
||||||
const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt } = suppression;
|
const { shouldSuppress, skipMode, isGuidedGeneration, isImpersonationGeneration, hasQuietPrompt, instructContent, quietPromptRaw, matchedPattern } = suppression;
|
||||||
|
|
||||||
if (shouldSuppress) {
|
if (shouldSuppress) {
|
||||||
|
// Debugging: indicate active suppression and which source triggered it
|
||||||
console.debug(`[RPG Companion] Suppression active (mode=${skipMode}). isGuided=${isGuidedGeneration}, isImpersonation=${isImpersonationGeneration}, hasQuietPrompt=${hasQuietPrompt} - skipping RPG tracker injections for this generation.`);
|
console.debug(`[RPG Companion] Suppression active (mode=${skipMode}). isGuided=${isGuidedGeneration}, isImpersonation=${isImpersonationGeneration}, hasQuietPrompt=${hasQuietPrompt} - skipping RPG tracker injections for this generation.`);
|
||||||
|
|
||||||
|
// Also clear any existing RPG Companion prompts so they do not leak into this generation
|
||||||
|
// (e.g., previously set extension prompts should not be used alongside a guided prompt)
|
||||||
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
||||||
}
|
}
|
||||||
|
const lastMessage = chat && chat.length > 0 ? chat[chat.length - 1] : null;
|
||||||
|
|
||||||
|
// For SEPARATE mode only: Check if we need to commit extension data
|
||||||
|
// BUT: Only do this for the MAIN generation, not the tracker update generation
|
||||||
|
// If isGenerating is true, this is the tracker update generation (second call), so skip flag logic
|
||||||
|
// console.log('[RPG Companion DEBUG] Before generating:', lastGeneratedData.characterThoughts, ' , committed - ', committedTrackerData.characterThoughts);
|
||||||
if (extensionSettings.generationMode === 'separate' && !isGenerating) {
|
if (extensionSettings.generationMode === 'separate' && !isGenerating) {
|
||||||
if (!lastActionWasSwipe) {
|
if (!lastActionWasSwipe) {
|
||||||
|
// User sent a new message - commit lastGeneratedData before generation
|
||||||
|
// console.log('[RPG Companion] 📝 COMMIT: New message - committing lastGeneratedData');
|
||||||
|
// console.log('[RPG Companion] BEFORE commit - committedTrackerData:', {
|
||||||
|
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
|
||||||
|
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
|
||||||
|
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
|
||||||
|
// });
|
||||||
|
// console.log('[RPG Companion] BEFORE commit - lastGeneratedData:', {
|
||||||
|
// userStats: lastGeneratedData.userStats ? 'exists' : 'null',
|
||||||
|
// infoBox: lastGeneratedData.infoBox ? 'exists' : 'null',
|
||||||
|
// characterThoughts: lastGeneratedData.characterThoughts ? 'exists' : 'null'
|
||||||
|
// });
|
||||||
committedTrackerData.userStats = lastGeneratedData.userStats;
|
committedTrackerData.userStats = lastGeneratedData.userStats;
|
||||||
committedTrackerData.infoBox = lastGeneratedData.infoBox;
|
committedTrackerData.infoBox = lastGeneratedData.infoBox;
|
||||||
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
||||||
|
// console.log('[RPG Companion] AFTER commit - committedTrackerData:', {
|
||||||
|
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
|
||||||
|
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
|
||||||
|
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Reset flag after committing (ready for next cycle)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] 🔄 SWIPE: Using existing committedTrackerData (no commit)');
|
||||||
|
// console.log('[RPG Companion] committedTrackerData:', {
|
||||||
|
// userStats: committedTrackerData.userStats ? 'exists' : 'null',
|
||||||
|
// infoBox: committedTrackerData.infoBox ? 'exists' : 'null',
|
||||||
|
// characterThoughts: committedTrackerData.characterThoughts ? 'exists' : 'null'
|
||||||
|
// });
|
||||||
|
// Reset flag after using it (swipe generation complete, ready for next action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For TOGETHER mode: Check if we need to commit extension data
|
||||||
|
// Only commit when user sends a new message (not on swipes)
|
||||||
if (extensionSettings.generationMode === 'together') {
|
if (extensionSettings.generationMode === 'together') {
|
||||||
if (!lastActionWasSwipe) {
|
if (!lastActionWasSwipe) {
|
||||||
|
// User sent a new message - commit data from the last assistant message they replied to
|
||||||
|
// This ensures swipes use consistent data from before the first swipe
|
||||||
console.log('[RPG Companion] 📝 TOGETHER MODE COMMIT: New message - committing from last assistant message');
|
console.log('[RPG Companion] 📝 TOGETHER MODE COMMIT: New message - committing from last assistant message');
|
||||||
|
|
||||||
// Find the last assistant message (before the user's new message)
|
// Find the last assistant message (before the user's new message)
|
||||||
@@ -107,66 +154,110 @@ export function onGenerationStarted(type, data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the committed tracker data as source for generation
|
||||||
|
// console.log('[RPG Companion] Using committedTrackerData for generation');
|
||||||
|
// console.log('[RPG Companion] committedTrackerData.userStats:', committedTrackerData.userStats);
|
||||||
|
|
||||||
|
// Parse stats from committed data to update the extensionSettings for prompt generation
|
||||||
if (committedTrackerData.userStats) {
|
if (committedTrackerData.userStats) {
|
||||||
|
// console.log('[RPG Companion] Parsing committed userStats into extensionSettings');
|
||||||
parseUserStats(committedTrackerData.userStats);
|
parseUserStats(committedTrackerData.userStats);
|
||||||
|
// console.log('[RPG Companion] After parsing, extensionSettings.userStats:', JSON.stringify(extensionSettings.userStats));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extensionSettings.generationMode === 'together') {
|
if (extensionSettings.generationMode === 'together') {
|
||||||
const example = '';
|
// console.log('[RPG Companion] In together mode, generating prompts...');
|
||||||
const instructions = getTrackerInstructions(false, true);
|
const example = generateTrackerExample();
|
||||||
|
// Don't include HTML prompt in instructions - inject it separately to avoid duplication on swipes
|
||||||
|
const instructions = generateTrackerInstructions(false, true);
|
||||||
|
|
||||||
|
// Clear separate mode context injection - we don't use contextual summary in together mode
|
||||||
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
||||||
|
|
||||||
let lastAssistantDepth = -1;
|
// console.log('[RPG Companion] Example:', example ? 'exists' : 'empty');
|
||||||
|
// console.log('[RPG Companion] Chat length:', chat ? chat.length : 'chat is null');
|
||||||
|
|
||||||
|
// Find the last assistant message in the chat history
|
||||||
|
let lastAssistantDepth = -1; // -1 means not found
|
||||||
if (chat && chat.length > 0) {
|
if (chat && chat.length > 0) {
|
||||||
|
// console.log('[RPG Companion] Searching for last assistant message...');
|
||||||
|
// Start from depth 1 (skip depth 0 which is usually user's message or prefill)
|
||||||
for (let depth = 1; depth < chat.length; depth++) {
|
for (let depth = 1; depth < chat.length; depth++) {
|
||||||
const index = chat.length - 1 - depth;
|
const index = chat.length - 1 - depth; // Convert depth to index
|
||||||
const message = chat[index];
|
const message = chat[index];
|
||||||
|
// console.log('[RPG Companion] Checking depth', depth, 'index', index, 'message properties:', Object.keys(message));
|
||||||
|
// Check for assistant message: not user and not system
|
||||||
if (!message.is_user && !message.is_system) {
|
if (!message.is_user && !message.is_system) {
|
||||||
|
// Found assistant message at this depth
|
||||||
|
// Inject at the SAME depth to prepend to this assistant message
|
||||||
lastAssistantDepth = depth;
|
lastAssistantDepth = depth;
|
||||||
|
// console.log('[RPG Companion] Found last assistant message at depth', depth, '-> injecting at same depth:', lastAssistantDepth);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have previous tracker data and found an assistant message, inject it as an assistant message
|
||||||
if (!shouldSuppress && example && lastAssistantDepth > 0) {
|
if (!shouldSuppress && example && lastAssistantDepth > 0) {
|
||||||
setExtensionPrompt('rpg-companion-example', example, extension_prompt_types.IN_CHAT, lastAssistantDepth, false, extension_prompt_roles.ASSISTANT);
|
setExtensionPrompt('rpg-companion-example', example, extension_prompt_types.IN_CHAT, lastAssistantDepth, false, extension_prompt_roles.ASSISTANT);
|
||||||
|
// console.log('[RPG Companion] Injected tracker example as assistant message at depth:', lastAssistantDepth);
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] NOT injecting example. example:', !!example, 'lastAssistantDepth:', lastAssistantDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject the instructions as a user message at depth 0 (right before generation)
|
||||||
|
// If this is a guided generation (user explicitly injected 'instruct'), skip adding
|
||||||
|
// our tracker instructions to avoid clobbering the guided prompt.
|
||||||
if (!shouldSuppress) {
|
if (!shouldSuppress) {
|
||||||
setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER);
|
setExtensionPrompt('rpg-companion-inject', instructions, extension_prompt_types.IN_CHAT, 0, false, extension_prompt_roles.USER);
|
||||||
}
|
}
|
||||||
|
// console.log('[RPG Companion] Injected RPG tracking instructions at depth 0 (right before generation)');
|
||||||
|
|
||||||
|
// Inject HTML prompt separately at depth 0 if enabled (prevents duplication on swipes)
|
||||||
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
|
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
|
||||||
|
// Use custom HTML prompt if set, otherwise use default
|
||||||
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
|
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
|
||||||
const htmlPrompt = `\n${htmlPromptText}`;
|
const htmlPrompt = `\n${htmlPromptText}`;
|
||||||
|
|
||||||
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
|
||||||
|
// console.log('[RPG Companion] Injected HTML prompt at depth 0 for together mode');
|
||||||
} else {
|
} else {
|
||||||
|
// Clear HTML prompt if disabled
|
||||||
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
}
|
}
|
||||||
} else if (extensionSettings.generationMode === 'separate') {
|
} else if (extensionSettings.generationMode === 'separate') {
|
||||||
const currentStateJSON = generateContextualSummary();
|
// In SEPARATE mode, inject the contextual summary for main roleplay generation
|
||||||
|
const contextSummary = generateContextualSummary();
|
||||||
|
|
||||||
if (currentStateJSON) {
|
if (contextSummary) {
|
||||||
const wrappedContext = `\nHere is {{user}}'s current state in JSON format. This is merely informative, it's not your job to update it:
|
const wrappedContext = `\nHere is context information about the current scene, and what follows is the last message in the chat history:
|
||||||
<context>
|
<context>
|
||||||
\`\`\`json
|
${contextSummary}
|
||||||
${currentStateJSON}
|
|
||||||
\`\`\`
|
Ensure these details naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting performance, low hygiene influencing social interactions, environmental factors shaping the scene, or a character's emotional state coloring their responses.
|
||||||
</context>\n\n`;
|
</context>\n\n`;
|
||||||
|
|
||||||
|
// Inject context at depth 1 (before last user message) as SYSTEM
|
||||||
|
// Skip when a guided generation injection is present to avoid conflicting instructions
|
||||||
if (!shouldSuppress) {
|
if (!shouldSuppress) {
|
||||||
setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false);
|
setExtensionPrompt('rpg-companion-context', wrappedContext, extension_prompt_types.IN_CHAT, 1, false);
|
||||||
}
|
}
|
||||||
|
// console.log('[RPG Companion] Injected contextual summary for separate mode:', contextSummary);
|
||||||
} else {
|
} else {
|
||||||
|
// Clear if no data yet
|
||||||
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inject HTML prompt separately at depth 0 if enabled (same as together mode pattern)
|
||||||
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
|
if (extensionSettings.enableHtmlPrompt && !shouldSuppress) {
|
||||||
|
// Use custom HTML prompt if set, otherwise use default
|
||||||
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
|
const htmlPromptText = extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT;
|
||||||
const htmlPrompt = `\n${htmlPromptText}`;
|
const htmlPrompt = `\n${htmlPromptText}`;
|
||||||
|
|
||||||
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', htmlPrompt, extension_prompt_types.IN_CHAT, 0, false);
|
||||||
|
// console.log('[RPG Companion] Injected HTML prompt at depth 0 for separate mode');
|
||||||
} else {
|
} else {
|
||||||
|
// Clear HTML prompt if disabled
|
||||||
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
* Extracts v2 inventory data from AI-generated text
|
* Extracts v2 inventory data from AI-generated text
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extensionSettings } from '../../core/state.js';
|
|
||||||
|
|
||||||
// Type imports
|
// Type imports
|
||||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||||
|
|
||||||
@@ -103,84 +101,30 @@ export function extractLegacyInventory(text) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts simplified inventory data (single "Inventory:" line).
|
|
||||||
* Used when useSimplifiedInventory setting is enabled.
|
|
||||||
*
|
|
||||||
* Expected format: "Inventory: Sword, Shield, 3x Potions, Gold coins"
|
|
||||||
*
|
|
||||||
* @param {string} text - Text that may contain simplified inventory
|
|
||||||
* @returns {InventoryV2|null} Parsed inventory or null
|
|
||||||
*/
|
|
||||||
export function extractSimplifiedInventory(text) {
|
|
||||||
if (!text || typeof text !== 'string') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match simplified format: "Inventory: ..."
|
|
||||||
const match = text.match(/Inventory:\s*(.+?)(?:\n|$)/i);
|
|
||||||
if (match && match[1]) {
|
|
||||||
const inventoryText = match[1].trim();
|
|
||||||
// Return null for empty values like "None" or ""
|
|
||||||
if (!inventoryText || inventoryText.toLowerCase() === 'none') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Return v2 format with items stored in both 'items' (for simplified display)
|
|
||||||
// and 'onPerson' (for backward compatibility)
|
|
||||||
return {
|
|
||||||
version: 2,
|
|
||||||
onPerson: inventoryText,
|
|
||||||
stored: {},
|
|
||||||
assets: "None",
|
|
||||||
items: inventoryText, // Simplified inventory storage
|
|
||||||
simplified: true // Flag to indicate this is simplified format
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main inventory extraction function that tries v2 format first, then falls back to v1.
|
* Main inventory extraction function that tries v2 format first, then falls back to v1.
|
||||||
* Converts v1 format to v2 automatically if found.
|
* Converts v1 format to v2 automatically if found.
|
||||||
* When useSimplifiedInventory is enabled, prioritizes simple "Inventory:" format.
|
|
||||||
*
|
*
|
||||||
* @param {string} statsText - Raw stats text from AI response
|
* @param {string} statsText - Raw stats text from AI response
|
||||||
* @returns {InventoryV2|null} Parsed inventory in v2 format or null
|
* @returns {InventoryV2|null} Parsed inventory in v2 format or null
|
||||||
*/
|
*/
|
||||||
export function extractInventory(statsText) {
|
export function extractInventory(statsText) {
|
||||||
// If simplified inventory mode is enabled, try simplified format first
|
|
||||||
if (extensionSettings.useSimplifiedInventory) {
|
|
||||||
const simplifiedData = extractSimplifiedInventory(statsText);
|
|
||||||
if (simplifiedData) {
|
|
||||||
return simplifiedData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try v2 format first
|
// Try v2 format first
|
||||||
const v2Data = extractInventoryData(statsText);
|
const v2Data = extractInventoryData(statsText);
|
||||||
if (v2Data) {
|
if (v2Data) {
|
||||||
return v2Data;
|
return v2Data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to v1/simplified format and convert to v2
|
// Fallback to v1 format and convert to v2
|
||||||
const v1Data = extractLegacyInventory(statsText);
|
const v1Data = extractLegacyInventory(statsText);
|
||||||
if (v1Data) {
|
if (v1Data) {
|
||||||
// Convert v1 string to v2 format (place in onPerson)
|
// Convert v1 string to v2 format (place in onPerson)
|
||||||
const result = {
|
return {
|
||||||
version: 2,
|
version: 2,
|
||||||
onPerson: v1Data,
|
onPerson: v1Data,
|
||||||
stored: {},
|
stored: {},
|
||||||
assets: "None"
|
assets: "None"
|
||||||
};
|
};
|
||||||
|
|
||||||
// If simplified mode, also store in items field
|
|
||||||
if (extensionSettings.useSimplifiedInventory) {
|
|
||||||
result.items = v1Data;
|
|
||||||
result.simplified = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No inventory data found
|
// No inventory data found
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Parser Module
|
* Parser Module
|
||||||
* Handles parsing of AI responses to extract tracker data
|
* Handles parsing of AI responses to extract tracker data
|
||||||
* Supports both legacy text format and new JSON format
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extensionSettings, addDebugLog, lastGeneratedData, committedTrackerData } from '../../core/state.js';
|
import { extensionSettings, FEATURE_FLAGS, addDebugLog } from '../../core/state.js';
|
||||||
import { saveSettings, saveChatData } from '../../core/persistence.js';
|
import { saveSettings } from '../../core/persistence.js';
|
||||||
import { extractInventory } from './inventoryParser.js';
|
import { extractInventory } from './inventoryParser.js';
|
||||||
import { validateTrackerData } from '../../types/trackerData.js';
|
|
||||||
import { handleItemRemoved } from '../rendering/skills.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to separate emoji from text in a string
|
* Helper to separate emoji from text in a string
|
||||||
@@ -136,501 +133,19 @@ function debugLog(message, data = null) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts JSON from a code block (handles ```json ... ``` format)
|
|
||||||
* @param {string} text - Text that may contain JSON code blocks
|
|
||||||
* @returns {Object|null} Parsed JSON object or null
|
|
||||||
*/
|
|
||||||
function extractJSONFromCodeBlock(text) {
|
|
||||||
if (!text) return null;
|
|
||||||
|
|
||||||
// Match ```json ... ``` or ``` ... ``` blocks
|
|
||||||
const jsonBlockRegex = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
||||||
const matches = [...text.matchAll(jsonBlockRegex)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const content = match[1].trim();
|
|
||||||
// Check if content looks like JSON (starts with { or [)
|
|
||||||
if (content.startsWith('{') || content.startsWith('[')) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(content);
|
|
||||||
} catch (e) {
|
|
||||||
debugLog('[RPG Parser] JSON parse failed:', e.message);
|
|
||||||
// Try to fix common JSON issues
|
|
||||||
const fixed = tryFixJSON(content);
|
|
||||||
if (fixed) return fixed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to fix common JSON formatting issues
|
|
||||||
* @param {string} jsonStr - Potentially malformed JSON string
|
|
||||||
* @returns {Object|null} Fixed JSON object or null
|
|
||||||
*/
|
|
||||||
function tryFixJSON(jsonStr) {
|
|
||||||
try {
|
|
||||||
// Remove trailing commas
|
|
||||||
let fixed = jsonStr.replace(/,(\s*[}\]])/g, '$1');
|
|
||||||
// Fix unquoted keys
|
|
||||||
fixed = fixed.replace(/([{,]\s*)(\w+)(\s*:)/g, '$1"$2"$3');
|
|
||||||
return JSON.parse(fixed);
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses JSON tracker data and applies it to extension settings
|
|
||||||
* @param {Object} jsonData - Parsed JSON tracker data
|
|
||||||
* @returns {boolean} Whether parsing was successful
|
|
||||||
*/
|
|
||||||
export function parseJSONTrackerData(jsonData) {
|
|
||||||
debugLog('[RPG Parser] ==================== JSON PARSING ====================');
|
|
||||||
|
|
||||||
const validation = validateTrackerData(jsonData);
|
|
||||||
if (!validation.valid) {
|
|
||||||
debugLog('[RPG Parser] JSON validation failed:', validation.errors);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trackerConfig = extensionSettings.trackerConfig;
|
|
||||||
|
|
||||||
// Parse stats
|
|
||||||
if (jsonData.stats) {
|
|
||||||
debugLog('[RPG Parser] Parsing stats:', Object.keys(jsonData.stats));
|
|
||||||
const customStats = trackerConfig?.userStats?.customStats || [];
|
|
||||||
|
|
||||||
for (const [statName, value] of Object.entries(jsonData.stats)) {
|
|
||||||
// Find matching stat in config
|
|
||||||
const statConfig = customStats.find(s =>
|
|
||||||
s.name.toLowerCase() === statName.toLowerCase()
|
|
||||||
);
|
|
||||||
if (statConfig && typeof value === 'number') {
|
|
||||||
// Store in userStats using the stat id
|
|
||||||
extensionSettings.userStats[statConfig.id] = Math.max(0, Math.min(100, value));
|
|
||||||
debugLog(`[RPG Parser] Stat ${statConfig.name}: ${value}%`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse attributes (RPG attributes like STR, DEX, etc.)
|
|
||||||
// Only parse if allowAIUpdateAttributes is enabled
|
|
||||||
const allowAIUpdateAttributes = trackerConfig?.userStats?.allowAIUpdateAttributes !== false; // Default to true for backwards compatibility
|
|
||||||
if (jsonData.attributes && typeof jsonData.attributes === 'object' && allowAIUpdateAttributes) {
|
|
||||||
debugLog('[RPG Parser] Parsing attributes:', Object.keys(jsonData.attributes));
|
|
||||||
const rpgAttributes = trackerConfig?.userStats?.rpgAttributes || [
|
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const [attrName, value] of Object.entries(jsonData.attributes)) {
|
|
||||||
// Find matching attribute in config (case-insensitive)
|
|
||||||
const attrConfig = rpgAttributes.find(a =>
|
|
||||||
a && a.name && a.name.toLowerCase() === attrName.toLowerCase()
|
|
||||||
);
|
|
||||||
if (attrConfig && typeof value === 'number') {
|
|
||||||
// Store in classicStats using the attribute id
|
|
||||||
extensionSettings.classicStats[attrConfig.id] = Math.max(1, value);
|
|
||||||
debugLog(`[RPG Parser] Attribute ${attrConfig.name}: ${value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (jsonData.attributes && !allowAIUpdateAttributes) {
|
|
||||||
debugLog('[RPG Parser] Attributes found in response but allowAIUpdateAttributes is disabled - skipping update');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse level (only if allowAIUpdateAttributes is enabled)
|
|
||||||
if (jsonData.level !== undefined && typeof jsonData.level === 'number' && allowAIUpdateAttributes) {
|
|
||||||
extensionSettings.level = Math.max(1, jsonData.level);
|
|
||||||
debugLog(`[RPG Parser] Level: ${extensionSettings.level}`);
|
|
||||||
} else if (jsonData.level !== undefined && !allowAIUpdateAttributes) {
|
|
||||||
debugLog('[RPG Parser] Level found in response but allowAIUpdateAttributes is disabled - skipping update');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse status
|
|
||||||
if (jsonData.status) {
|
|
||||||
if (jsonData.status.mood) {
|
|
||||||
extensionSettings.userStats.mood = jsonData.status.mood;
|
|
||||||
debugLog('[RPG Parser] Mood:', jsonData.status.mood);
|
|
||||||
}
|
|
||||||
if (jsonData.status.fields) {
|
|
||||||
// Filter to only include configured status fields
|
|
||||||
const configuredFields = extensionSettings.trackerConfig?.userStats?.statusSection?.customFields || [];
|
|
||||||
const filteredValues = [];
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(jsonData.status.fields)) {
|
|
||||||
// Only include if this field is in the configured list (case-insensitive)
|
|
||||||
const isConfigured = configuredFields.some(f =>
|
|
||||||
f.toLowerCase() === key.toLowerCase()
|
|
||||||
);
|
|
||||||
if (isConfigured && value && value !== 'None' && value !== 'null') {
|
|
||||||
filteredValues.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionSettings.userStats.conditions = filteredValues.length > 0 ? filteredValues.join(', ') : 'None';
|
|
||||||
debugLog('[RPG Parser] Status fields (filtered):', filteredValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse skills (string)
|
|
||||||
if (jsonData.skills && typeof jsonData.skills === 'string') {
|
|
||||||
extensionSettings.userStats.skills = jsonData.skills;
|
|
||||||
debugLog('[RPG Parser] Skills (string format) extracted from status:', jsonData.skills);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse infoBox - normalize values and filter out null
|
|
||||||
if (jsonData.infoBox) {
|
|
||||||
const infoBox = {};
|
|
||||||
// Only copy non-null values
|
|
||||||
for (const [key, val] of Object.entries(jsonData.infoBox)) {
|
|
||||||
if (val !== null && val !== undefined && val !== 'null') {
|
|
||||||
infoBox[key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Normalize recentEvents - LLM sometimes returns string instead of array
|
|
||||||
if (infoBox.recentEvents && typeof infoBox.recentEvents === 'string') {
|
|
||||||
infoBox.recentEvents = [infoBox.recentEvents];
|
|
||||||
} else if (!infoBox.recentEvents || !Array.isArray(infoBox.recentEvents)) {
|
|
||||||
infoBox.recentEvents = [];
|
|
||||||
}
|
|
||||||
// Filter out null/empty events from array
|
|
||||||
infoBox.recentEvents = infoBox.recentEvents.filter(e => e && e !== 'null');
|
|
||||||
extensionSettings.infoBoxData = infoBox;
|
|
||||||
debugLog('[RPG Parser] InfoBox:', Object.keys(infoBox));
|
|
||||||
|
|
||||||
// Generate text format for lastGeneratedData.infoBox (needed for other UI parts)
|
|
||||||
const textLines = [];
|
|
||||||
if (infoBox.date) textLines.push(`Date: ${infoBox.date}`);
|
|
||||||
if (infoBox.time) textLines.push(`Time: ${infoBox.time}`);
|
|
||||||
if (infoBox.weather) textLines.push(`Weather: ${infoBox.weather}`);
|
|
||||||
if (infoBox.temperature) textLines.push(`Temperature: ${infoBox.temperature}`);
|
|
||||||
if (infoBox.location) textLines.push(`Location: ${infoBox.location}`);
|
|
||||||
if (infoBox.recentEvents && infoBox.recentEvents.length > 0) {
|
|
||||||
textLines.push(`Recent Events: ${infoBox.recentEvents.join(', ')}`);
|
|
||||||
}
|
|
||||||
if (textLines.length > 0) {
|
|
||||||
lastGeneratedData.infoBox = textLines.join('\n');
|
|
||||||
debugLog('[RPG Parser] Generated text format for infoBox');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse characters - store for UI rendering AND generate text format for thought bubbles
|
|
||||||
const parsedCharacters = Array.isArray(jsonData.characters) ? jsonData.characters : [];
|
|
||||||
extensionSettings.charactersData = parsedCharacters;
|
|
||||||
debugLog('[RPG Parser] Characters:', parsedCharacters.length);
|
|
||||||
|
|
||||||
// Generate text format for lastGeneratedData.characterThoughts (needed for thought bubbles)
|
|
||||||
const config = extensionSettings.trackerConfig?.presentCharacters;
|
|
||||||
const thoughtsFieldName = config?.thoughts?.name || 'Thoughts';
|
|
||||||
const lines = [];
|
|
||||||
for (const char of parsedCharacters) {
|
|
||||||
// Character name line
|
|
||||||
lines.push(`- ${char.name || 'Unknown'}`);
|
|
||||||
|
|
||||||
// Details line with emoji and fields
|
|
||||||
const details = [char.emoji || '😶'];
|
|
||||||
const charFields = char.fields || {};
|
|
||||||
for (const [key, value] of Object.entries(charFields)) {
|
|
||||||
if (value) details.push(`${key}: ${value}`);
|
|
||||||
}
|
|
||||||
lines.push(`Details: ${details.join(' | ')}`);
|
|
||||||
|
|
||||||
// Relationship line
|
|
||||||
if (char.relationship) {
|
|
||||||
lines.push(`Relationship: ${char.relationship}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats line
|
|
||||||
const charStats = char.stats || {};
|
|
||||||
if (Object.keys(charStats).length > 0) {
|
|
||||||
const statsStr = Object.entries(charStats).map(([k, v]) => `${k}: ${v}%`).join(' | ');
|
|
||||||
lines.push(`Stats: ${statsStr}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thoughts line
|
|
||||||
if (char.thoughts) {
|
|
||||||
lines.push(`${thoughtsFieldName}: ${char.thoughts}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lines.length > 0) {
|
|
||||||
lastGeneratedData.characterThoughts = lines.join('\n');
|
|
||||||
committedTrackerData.characterThoughts = lines.join('\n');
|
|
||||||
debugLog('[RPG Parser] Generated text format for characterThoughts');
|
|
||||||
} else {
|
|
||||||
// No characters provided in the JSON response - clear any stale state/UI data
|
|
||||||
lastGeneratedData.characterThoughts = '';
|
|
||||||
committedTrackerData.characterThoughts = '';
|
|
||||||
debugLog('[RPG Parser] No characters present; cleared characterThoughts state');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse inventory (structured format)
|
|
||||||
// Handle LLM variations: empty objects {} should become empty arrays []
|
|
||||||
if (jsonData.inventory) {
|
|
||||||
const normalizeArray = (val) => {
|
|
||||||
if (Array.isArray(val)) return val;
|
|
||||||
if (val && typeof val === 'object' && Object.keys(val).length === 0) return [];
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get all item names from current inventory BEFORE updating
|
|
||||||
const getItemNamesFromInventory = (inv) => {
|
|
||||||
const names = new Set();
|
|
||||||
if (!inv) return names;
|
|
||||||
// From onPerson array
|
|
||||||
if (Array.isArray(inv.onPerson)) {
|
|
||||||
inv.onPerson.forEach(item => {
|
|
||||||
const name = typeof item === 'string' ? item : item?.name;
|
|
||||||
if (name) names.add(name.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// From stored locations
|
|
||||||
if (inv.stored && typeof inv.stored === 'object') {
|
|
||||||
Object.values(inv.stored).forEach(items => {
|
|
||||||
if (Array.isArray(items)) {
|
|
||||||
items.forEach(item => {
|
|
||||||
const name = typeof item === 'string' ? item : item?.name;
|
|
||||||
if (name) names.add(name.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// From assets
|
|
||||||
if (Array.isArray(inv.assets)) {
|
|
||||||
inv.assets.forEach(item => {
|
|
||||||
const name = typeof item === 'string' ? item : item?.name;
|
|
||||||
if (name) names.add(name.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
};
|
|
||||||
|
|
||||||
const previousItemNames = getItemNamesFromInventory(extensionSettings.inventoryV3);
|
|
||||||
|
|
||||||
// Get items - prefer 'items' for simplified mode, 'onPerson' for categorized
|
|
||||||
// Also handle case where LLM uses either field
|
|
||||||
const itemsArray = normalizeArray(jsonData.inventory.items);
|
|
||||||
const onPersonArray = normalizeArray(jsonData.inventory.onPerson);
|
|
||||||
|
|
||||||
// For simplified mode: prefer 'items', fallback to 'onPerson'
|
|
||||||
// For categorized mode: prefer 'onPerson', fallback to 'items'
|
|
||||||
const simplifiedItems = itemsArray.length > 0 ? itemsArray : onPersonArray;
|
|
||||||
const onPersonItems = onPersonArray.length > 0 ? onPersonArray : itemsArray;
|
|
||||||
|
|
||||||
extensionSettings.inventoryV3 = {
|
|
||||||
onPerson: onPersonItems,
|
|
||||||
stored: jsonData.inventory.stored && typeof jsonData.inventory.stored === 'object'
|
|
||||||
? jsonData.inventory.stored : {},
|
|
||||||
assets: normalizeArray(jsonData.inventory.assets),
|
|
||||||
// For simplified mode - use whichever array has items
|
|
||||||
simplified: extensionSettings.useSimplifiedInventory ? simplifiedItems : (extensionSettings.inventoryV3?.simplified || [])
|
|
||||||
};
|
|
||||||
debugLog('[RPG Parser] Inventory parsed - onPerson:', extensionSettings.inventoryV3.onPerson.length,
|
|
||||||
'simplified:', extensionSettings.inventoryV3.simplified?.length || 0);
|
|
||||||
// Log first item to verify descriptions are preserved
|
|
||||||
if (extensionSettings.inventoryV3.onPerson[0]) {
|
|
||||||
debugLog('[RPG Parser] First onPerson item:', JSON.stringify(extensionSettings.inventoryV3.onPerson[0]));
|
|
||||||
}
|
|
||||||
if (extensionSettings.inventoryV3.simplified?.[0]) {
|
|
||||||
debugLog('[RPG Parser] First simplified item:', JSON.stringify(extensionSettings.inventoryV3.simplified[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect removed items and handle skill links
|
|
||||||
const newItemNames = getItemNamesFromInventory(extensionSettings.inventoryV3);
|
|
||||||
previousItemNames.forEach(itemName => {
|
|
||||||
if (!newItemNames.has(itemName)) {
|
|
||||||
debugLog('[RPG Parser] Item removed by LLM:', itemName);
|
|
||||||
// Handle item removal - will unlink or delete skills based on settings
|
|
||||||
handleItemRemoved(itemName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also update legacy inventory for backwards compatibility
|
|
||||||
const itemsToString = (items) => {
|
|
||||||
if (!items || items.length === 0) return 'None';
|
|
||||||
return items.map(i => typeof i === 'string' ? i : i.name).join(', ');
|
|
||||||
};
|
|
||||||
extensionSettings.userStats.inventory = {
|
|
||||||
version: 2,
|
|
||||||
onPerson: itemsToString(extensionSettings.inventoryV3.onPerson),
|
|
||||||
stored: Object.fromEntries(
|
|
||||||
Object.entries(extensionSettings.inventoryV3.stored).map(([k, v]) => [k, itemsToString(v)])
|
|
||||||
),
|
|
||||||
assets: itemsToString(extensionSettings.inventoryV3.assets),
|
|
||||||
// For simplified mode
|
|
||||||
items: extensionSettings.useSimplifiedInventory ? itemsToString(extensionSettings.inventoryV3.simplified) : undefined
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse skills (structured format) - handle array/object/string variations
|
|
||||||
if (jsonData.skills && typeof jsonData.skills === 'object') {
|
|
||||||
// Normalize skills - each category should be an array
|
|
||||||
const normalizedSkills = {};
|
|
||||||
for (const [category, abilities] of Object.entries(jsonData.skills)) {
|
|
||||||
if (Array.isArray(abilities)) {
|
|
||||||
normalizedSkills[category] = abilities;
|
|
||||||
} else if (typeof abilities === 'string') {
|
|
||||||
// LLM returned string instead of array - split by comma
|
|
||||||
normalizedSkills[category] = abilities.split(',').map(a => ({ name: a.trim(), description: '' })).filter(a => a.name);
|
|
||||||
} else {
|
|
||||||
normalizedSkills[category] = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate grantedBy references - remove if item doesn't exist
|
|
||||||
// Build set of all valid item names from current inventory (including just-parsed items)
|
|
||||||
const validItemNames = new Set();
|
|
||||||
const inv = extensionSettings.inventoryV3;
|
|
||||||
if (inv) {
|
|
||||||
const addItems = (items) => {
|
|
||||||
if (!Array.isArray(items)) return;
|
|
||||||
items.forEach(item => {
|
|
||||||
const name = typeof item === 'string' ? item : item?.name;
|
|
||||||
if (name) validItemNames.add(name.toLowerCase());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
addItems(inv.onPerson);
|
|
||||||
addItems(inv.assets);
|
|
||||||
addItems(inv.simplified);
|
|
||||||
if (inv.stored) {
|
|
||||||
Object.values(inv.stored).forEach(items => addItems(items));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each skill's grantedBy and remove if invalid
|
|
||||||
for (const abilities of Object.values(normalizedSkills)) {
|
|
||||||
if (!Array.isArray(abilities)) continue;
|
|
||||||
for (const ability of abilities) {
|
|
||||||
if (ability && typeof ability === 'object' && ability.grantedBy) {
|
|
||||||
const grantedByLower = ability.grantedBy.toLowerCase();
|
|
||||||
if (!validItemNames.has(grantedByLower)) {
|
|
||||||
debugLog('[RPG Parser] Removing invalid grantedBy:', ability.grantedBy, 'from skill:', ability.name);
|
|
||||||
delete ability.grantedBy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extensionSettings.skillsV2 = normalizedSkills;
|
|
||||||
debugLog('[RPG Parser] Skills parsed:', Object.keys(normalizedSkills));
|
|
||||||
|
|
||||||
// Update legacy skills data for backwards compatibility
|
|
||||||
for (const [category, abilities] of Object.entries(normalizedSkills)) {
|
|
||||||
if (!extensionSettings.skillsData) extensionSettings.skillsData = {};
|
|
||||||
const names = abilities.map(a => typeof a === 'string' ? a : (a?.name || '')).filter(n => n);
|
|
||||||
extensionSettings.skillsData[category] = names.join(', ') || 'None';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate grantsSkill references on items - remove if skill doesn't exist
|
|
||||||
// Build set of all valid skill names from just-parsed skills
|
|
||||||
const validSkillNames = new Set();
|
|
||||||
for (const abilities of Object.values(normalizedSkills)) {
|
|
||||||
if (!Array.isArray(abilities)) continue;
|
|
||||||
abilities.forEach(ability => {
|
|
||||||
const name = typeof ability === 'string' ? ability : ability?.name;
|
|
||||||
if (name) validSkillNames.add(name.toLowerCase());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check each item's grantsSkill and remove if invalid
|
|
||||||
const validateItemsGrantsSkill = (items) => {
|
|
||||||
if (!Array.isArray(items)) return;
|
|
||||||
for (const item of items) {
|
|
||||||
if (item && typeof item === 'object' && item.grantsSkill) {
|
|
||||||
const grantsSkillLower = item.grantsSkill.toLowerCase();
|
|
||||||
if (!validSkillNames.has(grantsSkillLower)) {
|
|
||||||
debugLog('[RPG Parser] Removing invalid grantsSkill:', item.grantsSkill, 'from item:', item.name);
|
|
||||||
delete item.grantsSkill;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (inv) {
|
|
||||||
validateItemsGrantsSkill(inv.onPerson);
|
|
||||||
validateItemsGrantsSkill(inv.assets);
|
|
||||||
validateItemsGrantsSkill(inv.simplified);
|
|
||||||
if (inv.stored) {
|
|
||||||
Object.values(inv.stored).forEach(items => validateItemsGrantsSkill(items));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse quests - handle different formats
|
|
||||||
if (jsonData.quests) {
|
|
||||||
// Normalize main quest - could be string, object with name, or null
|
|
||||||
let mainName = 'None';
|
|
||||||
let mainDesc = '';
|
|
||||||
if (jsonData.quests.main) {
|
|
||||||
if (typeof jsonData.quests.main === 'string') {
|
|
||||||
mainName = jsonData.quests.main;
|
|
||||||
} else if (jsonData.quests.main.name) {
|
|
||||||
mainName = jsonData.quests.main.name;
|
|
||||||
mainDesc = jsonData.quests.main.description || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize optional quests - could be array of strings or objects
|
|
||||||
const optionalQuests = Array.isArray(jsonData.quests.optional) ? jsonData.quests.optional : [];
|
|
||||||
const optionalNames = optionalQuests.map(q => typeof q === 'string' ? q : (q?.name || '')).filter(n => n);
|
|
||||||
const optionalDescs = optionalQuests.map(q => typeof q === 'string' ? '' : (q?.description || ''));
|
|
||||||
|
|
||||||
extensionSettings.quests = {
|
|
||||||
main: mainName,
|
|
||||||
mainDescription: mainDesc,
|
|
||||||
optional: optionalNames,
|
|
||||||
optionalDescriptions: optionalDescs
|
|
||||||
};
|
|
||||||
// Store structured quests too
|
|
||||||
extensionSettings.questsV2 = jsonData.quests;
|
|
||||||
debugLog('[RPG Parser] Quests - main:', mainName);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSettings();
|
|
||||||
saveChatData();
|
|
||||||
|
|
||||||
debugLog('[RPG Parser] JSON parsing complete');
|
|
||||||
debugLog('[RPG Parser] =======================================================');
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main entry point for parsing responses - tries JSON first, falls back to text
|
|
||||||
* @param {string} responseText - The raw AI response
|
|
||||||
* @returns {boolean} Whether JSON parsing was successful
|
|
||||||
*/
|
|
||||||
export function tryParseJSONResponse(responseText) {
|
|
||||||
const jsonData = extractJSONFromCodeBlock(responseText);
|
|
||||||
if (jsonData) {
|
|
||||||
return parseJSONTrackerData(jsonData);
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog('[RPG Parser] No valid JSON found, falling back to text parsing');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the model response to extract the different data sections.
|
* Parses the model response to extract the different data sections.
|
||||||
* Extracts tracker data from markdown code blocks in the AI response.
|
* Extracts tracker data from markdown code blocks in the AI response.
|
||||||
* Handles both separate code blocks and combined code blocks gracefully.
|
* Handles both separate code blocks and combined code blocks gracefully.
|
||||||
*
|
*
|
||||||
* @param {string} responseText - The raw AI response text
|
* @param {string} responseText - The raw AI response text
|
||||||
* @returns {{userStats: string|null, infoBox: string|null, characterThoughts: string|null, skills: string|null}} Parsed tracker data
|
* @returns {{userStats: string|null, infoBox: string|null, characterThoughts: string|null}} Parsed tracker data
|
||||||
*/
|
*/
|
||||||
export function parseResponse(responseText) {
|
export function parseResponse(responseText) {
|
||||||
const result = {
|
const result = {
|
||||||
userStats: null,
|
userStats: null,
|
||||||
infoBox: null,
|
infoBox: null,
|
||||||
characterThoughts: null,
|
characterThoughts: null
|
||||||
skills: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// DEBUG: Log full response for troubleshooting
|
// DEBUG: Log full response for troubleshooting
|
||||||
@@ -695,15 +210,7 @@ export function parseResponse(responseText) {
|
|||||||
content.match(/User Stats\s*\n\s*---/i) ||
|
content.match(/User Stats\s*\n\s*---/i) ||
|
||||||
content.match(/Player Stats\s*\n\s*---/i) ||
|
content.match(/Player Stats\s*\n\s*---/i) ||
|
||||||
// Fallback: look for stat keywords without strict header
|
// Fallback: look for stat keywords without strict header
|
||||||
(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i)) ||
|
(content.match(/Health:\s*\d+%/i) && content.match(/Energy:\s*\d+%/i));
|
||||||
// Fallback: inventory-only or quests-only blocks (no stats header)
|
|
||||||
(content.match(/^(On Person:|Inventory:|Main Quests?:|Optional Quests:)/im) &&
|
|
||||||
!content.match(/Info Box/i) && !content.match(/Present Characters/i) && !content.match(/Skills\s*\n\s*---/i));
|
|
||||||
|
|
||||||
// Match Skills section (separate from stats when showSkills is enabled)
|
|
||||||
const isSkills =
|
|
||||||
content.match(/Skills\s*\n\s*---/i) &&
|
|
||||||
!content.match(/Stats\s*\n\s*---/i); // Make sure it's not a combined block
|
|
||||||
|
|
||||||
// Match Info Box section - flexible patterns
|
// Match Info Box section - flexible patterns
|
||||||
const isInfoBox =
|
const isInfoBox =
|
||||||
@@ -724,9 +231,6 @@ export function parseResponse(responseText) {
|
|||||||
if (isStats && !result.userStats) {
|
if (isStats && !result.userStats) {
|
||||||
result.userStats = stripBrackets(content);
|
result.userStats = stripBrackets(content);
|
||||||
debugLog('[RPG Parser] ✓ Matched: Stats section');
|
debugLog('[RPG Parser] ✓ Matched: Stats section');
|
||||||
} else if (isSkills && !result.skills) {
|
|
||||||
result.skills = stripBrackets(content);
|
|
||||||
debugLog('[RPG Parser] ✓ Matched: Skills section');
|
|
||||||
} else if (isInfoBox && !result.infoBox) {
|
} else if (isInfoBox && !result.infoBox) {
|
||||||
result.infoBox = stripBrackets(content);
|
result.infoBox = stripBrackets(content);
|
||||||
debugLog('[RPG Parser] ✓ Matched: Info Box section');
|
debugLog('[RPG Parser] ✓ Matched: Info Box section');
|
||||||
@@ -742,14 +246,12 @@ export function parseResponse(responseText) {
|
|||||||
debugLog('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i)));
|
debugLog('[RPG Parser] - Has info keywords?', !!(content.match(/Date:/i) && content.match(/Location:/i)));
|
||||||
debugLog('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i));
|
debugLog('[RPG Parser] - Has "Present Characters\\n---"?', !!content.match(/Present Characters\s*\n\s*---/i));
|
||||||
debugLog('[RPG Parser] - Has new format ("- Name" + "Details:")?', !!(content.match(/^-\s+\w+/m) && content.match(/Details:/i)));
|
debugLog('[RPG Parser] - Has new format ("- Name" + "Details:")?', !!(content.match(/^-\s+\w+/m) && content.match(/Details:/i)));
|
||||||
debugLog('[RPG Parser] - Has "Skills\\n---"?', !!content.match(/Skills\s*\n\s*---/i));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('[RPG Parser] ==================== PARSE RESULTS ====================');
|
debugLog('[RPG Parser] ==================== PARSE RESULTS ====================');
|
||||||
debugLog('[RPG Parser] Found Stats:', !!result.userStats);
|
debugLog('[RPG Parser] Found Stats:', !!result.userStats);
|
||||||
debugLog('[RPG Parser] Found Skills:', !!result.skills);
|
|
||||||
debugLog('[RPG Parser] Found Info Box:', !!result.infoBox);
|
debugLog('[RPG Parser] Found Info Box:', !!result.infoBox);
|
||||||
debugLog('[RPG Parser] Found Characters:', !!result.characterThoughts);
|
debugLog('[RPG Parser] Found Characters:', !!result.characterThoughts);
|
||||||
debugLog('[RPG Parser] =======================================================');
|
debugLog('[RPG Parser] =======================================================');
|
||||||
@@ -856,13 +358,24 @@ export function parseUserStats(statsText) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract inventory - extractInventory() handles v2 format and falls back to v1 if needed
|
// Extract inventory - use v2 parser if feature flag enabled, otherwise fallback to v1
|
||||||
const inventoryData = extractInventory(statsText);
|
if (FEATURE_FLAGS.useNewInventory) {
|
||||||
if (inventoryData) {
|
const inventoryData = extractInventory(statsText);
|
||||||
extensionSettings.userStats.inventory = inventoryData;
|
if (inventoryData) {
|
||||||
debugLog('[RPG Parser] Inventory extracted:', inventoryData);
|
extensionSettings.userStats.inventory = inventoryData;
|
||||||
|
debugLog('[RPG Parser] Inventory v2 extracted:', inventoryData);
|
||||||
|
} else {
|
||||||
|
debugLog('[RPG Parser] Inventory v2 extraction failed');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
debugLog('[RPG Parser] Inventory extraction failed');
|
// Legacy v1 parsing for backward compatibility
|
||||||
|
const inventoryMatch = statsText.match(/Inventory:\s*(.+)/i);
|
||||||
|
if (inventoryMatch) {
|
||||||
|
extensionSettings.userStats.inventory = inventoryMatch[1].trim();
|
||||||
|
debugLog('[RPG Parser] Inventory v1 extracted:', inventoryMatch[1].trim());
|
||||||
|
} else {
|
||||||
|
debugLog('[RPG Parser] Inventory v1 not found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract quests
|
// Extract quests
|
||||||
@@ -895,7 +408,7 @@ export function parseUserStats(statsText) {
|
|||||||
arousal: extensionSettings.userStats.arousal,
|
arousal: extensionSettings.userStats.arousal,
|
||||||
mood: extensionSettings.userStats.mood,
|
mood: extensionSettings.userStats.mood,
|
||||||
conditions: extensionSettings.userStats.conditions,
|
conditions: extensionSettings.userStats.conditions,
|
||||||
inventory: extensionSettings.userStats.inventory
|
inventory: FEATURE_FLAGS.useNewInventory ? 'v2 object' : extensionSettings.userStats.inventory
|
||||||
});
|
});
|
||||||
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -909,128 +422,6 @@ export function parseUserStats(statsText) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses skills from the separate skills code block.
|
|
||||||
* Used when showSkills is enabled (skills appear in their own section).
|
|
||||||
* Format expected:
|
|
||||||
* Skills
|
|
||||||
* ---
|
|
||||||
* Combat: Sword Fighting, Shield Block, Parry
|
|
||||||
* Stealth: Lockpicking (via Lockpicks), Sneaking, Pickpocketing
|
|
||||||
* Magic: None
|
|
||||||
*
|
|
||||||
* Each skill category contains a comma-separated list of abilities.
|
|
||||||
* Abilities can be linked to items using "(via ItemName)" format.
|
|
||||||
*
|
|
||||||
* @param {string} skillsText - The raw skills text from AI response
|
|
||||||
*/
|
|
||||||
export function parseSkills(skillsText) {
|
|
||||||
debugLog('[RPG Parser] ==================== PARSING SKILLS ====================');
|
|
||||||
debugLog('[RPG Parser] Skills text length:', skillsText?.length + ' chars');
|
|
||||||
|
|
||||||
if (!skillsText || typeof skillsText !== 'string') {
|
|
||||||
debugLog('[RPG Parser] No skills text to parse');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Initialize data structures if needed
|
|
||||||
if (!extensionSettings.skillsData) {
|
|
||||||
extensionSettings.skillsData = {};
|
|
||||||
}
|
|
||||||
if (!extensionSettings.skillAbilityLinks) {
|
|
||||||
extensionSettings.skillAbilityLinks = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migration function handles string array → object array conversion on load
|
|
||||||
const rawCategories = extensionSettings.trackerConfig?.userStats?.skillsSection?.customFields || [];
|
|
||||||
const configuredCategories = rawCategories
|
|
||||||
.filter(cat => cat.enabled !== false)
|
|
||||||
.map(cat => cat.name);
|
|
||||||
|
|
||||||
const lines = skillsText.split('\n');
|
|
||||||
const newSkillAbilityLinks = {};
|
|
||||||
|
|
||||||
for (const line of lines) {
|
|
||||||
// Skip header lines and notes
|
|
||||||
if (line.match(/^Skills\s*$/i) || line.match(/^---/) || !line.trim() || line.match(/^\(Note:/i)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse skill category line: "CategoryName: ability1, ability2 (via Item), ability3"
|
|
||||||
const categoryMatch = line.match(/^(.+?):\s*(.*)$/);
|
|
||||||
if (categoryMatch) {
|
|
||||||
const categoryName = categoryMatch[1].trim();
|
|
||||||
const abilitiesText = categoryMatch[2].trim();
|
|
||||||
|
|
||||||
// Check if this is a configured category (case-insensitive match)
|
|
||||||
const matchedCategory = configuredCategories.find(c =>
|
|
||||||
c.toLowerCase() === categoryName.toLowerCase()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!matchedCategory) {
|
|
||||||
debugLog(`[RPG Parser] Skipping unknown skill category: ${categoryName}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse abilities (comma-separated)
|
|
||||||
if (!abilitiesText || abilitiesText.toLowerCase() === 'none') {
|
|
||||||
extensionSettings.skillsData[matchedCategory] = 'None';
|
|
||||||
debugLog(`[RPG Parser] Skill category ${matchedCategory}: None`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split by comma and parse each ability
|
|
||||||
const abilities = [];
|
|
||||||
const rawAbilities = abilitiesText.split(',').map(a => a.trim()).filter(a => a);
|
|
||||||
|
|
||||||
for (const rawAbility of rawAbilities) {
|
|
||||||
// Check for "(ItemName)" pattern - ability granted by item
|
|
||||||
// Supports both "AbilityName (ItemName)" and legacy "AbilityName (via ItemName)"
|
|
||||||
const itemMatch = rawAbility.match(/^(.+?)\s*\((?:via\s+)?(.+?)\)$/i);
|
|
||||||
|
|
||||||
if (itemMatch) {
|
|
||||||
const abilityName = itemMatch[1].trim();
|
|
||||||
const itemName = itemMatch[2].trim();
|
|
||||||
abilities.push(abilityName);
|
|
||||||
|
|
||||||
// Store the link
|
|
||||||
if (extensionSettings.enableItemSkillLinks) {
|
|
||||||
const linkKey = `${matchedCategory}::${abilityName}`;
|
|
||||||
newSkillAbilityLinks[linkKey] = itemName;
|
|
||||||
debugLog(`[RPG Parser] Linked: ${abilityName} <- ${itemName}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
abilities.push(rawAbility);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store abilities for this category
|
|
||||||
const abilitiesString = abilities.length > 0 ? abilities.join(', ') : 'None';
|
|
||||||
extensionSettings.skillsData[matchedCategory] = abilitiesString;
|
|
||||||
debugLog(`[RPG Parser] Skill category ${matchedCategory}: ${abilitiesString}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update skill-ability links if item linking is enabled
|
|
||||||
if (extensionSettings.enableItemSkillLinks && Object.keys(newSkillAbilityLinks).length > 0) {
|
|
||||||
// Merge new links with existing ones
|
|
||||||
extensionSettings.skillAbilityLinks = {
|
|
||||||
...extensionSettings.skillAbilityLinks,
|
|
||||||
...newSkillAbilityLinks
|
|
||||||
};
|
|
||||||
debugLog('[RPG Parser] Skill-ability links updated:', Object.keys(newSkillAbilityLinks).length + ' new links');
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSettings();
|
|
||||||
debugLog('[RPG Parser] Skills saved successfully');
|
|
||||||
debugLog('[RPG Parser] =======================================================');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[RPG Companion] Error parsing skills:', error);
|
|
||||||
debugLog('[RPG Parser] ERROR:', error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: Extract code blocks from text
|
* Helper: Extract code blocks from text
|
||||||
* @param {string} text - Text containing markdown code blocks
|
* @param {string} text - Text containing markdown code blocks
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { getContext } from '../../../../../../extensions.js';
|
import { getContext } from '../../../../../../extensions.js';
|
||||||
import {
|
import { chat, user_avatar, setExtensionPrompt, extension_prompt_types, updateMessageBlock } from '../../../../../../../script.js';
|
||||||
chat,
|
|
||||||
user_avatar,
|
|
||||||
setExtensionPrompt,
|
|
||||||
extension_prompt_types,
|
|
||||||
updateMessageBlock,
|
|
||||||
generateRaw
|
|
||||||
} from '../../../../../../../script.js';
|
|
||||||
|
|
||||||
// Core modules
|
// Core modules
|
||||||
import {
|
import {
|
||||||
@@ -21,20 +14,20 @@ import {
|
|||||||
lastActionWasSwipe,
|
lastActionWasSwipe,
|
||||||
isPlotProgression,
|
isPlotProgression,
|
||||||
setLastActionWasSwipe,
|
setLastActionWasSwipe,
|
||||||
setIsPlotProgression
|
setIsPlotProgression,
|
||||||
|
updateLastGeneratedData,
|
||||||
|
updateCommittedTrackerData
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { saveChatData, loadChatData } from '../../core/persistence.js';
|
import { saveChatData, loadChatData } from '../../core/persistence.js';
|
||||||
|
|
||||||
// Generation & Parsing
|
// Generation & Parsing
|
||||||
import { parseResponse, parseUserStats, parseSkills, tryParseJSONResponse } from '../generation/parser.js';
|
import { parseResponse, parseUserStats } from '../generation/parser.js';
|
||||||
import { updateRPGData } from '../generation/apiClient.js';
|
import { updateRPGData } from '../generation/apiClient.js';
|
||||||
import { generateContextualSummary, DEFAULT_MESSAGE_INTERCEPTION_PROMPT } from '../generation/promptBuilder.js';
|
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
import { renderUserStats } from '../rendering/userStats.js';
|
import { renderUserStats } from '../rendering/userStats.js';
|
||||||
import { renderInfoBox } from '../rendering/infoBox.js';
|
import { renderInfoBox } from '../rendering/infoBox.js';
|
||||||
import { renderThoughts, updateChatThoughts } from '../rendering/thoughts.js';
|
import { renderThoughts, updateChatThoughts } from '../rendering/thoughts.js';
|
||||||
import { renderSkills } from '../rendering/skills.js';
|
|
||||||
import { renderInventory } from '../rendering/inventory.js';
|
import { renderInventory } from '../rendering/inventory.js';
|
||||||
import { renderQuests } from '../rendering/quests.js';
|
import { renderQuests } from '../rendering/quests.js';
|
||||||
|
|
||||||
@@ -62,10 +55,15 @@ export function commitTrackerData() {
|
|||||||
const swipeData = message.extra.rpg_companion_swipes[swipeId];
|
const swipeData = message.extra.rpg_companion_swipes[swipeId];
|
||||||
|
|
||||||
if (swipeData) {
|
if (swipeData) {
|
||||||
|
// console.log('[RPG Companion] Committing tracker data from assistant message at index', i, 'swipe', swipeId);
|
||||||
committedTrackerData.userStats = swipeData.userStats || null;
|
committedTrackerData.userStats = swipeData.userStats || null;
|
||||||
committedTrackerData.infoBox = swipeData.infoBox || null;
|
committedTrackerData.infoBox = swipeData.infoBox || null;
|
||||||
committedTrackerData.characterThoughts = swipeData.characterThoughts || null;
|
committedTrackerData.characterThoughts = swipeData.characterThoughts || null;
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] No swipe data found for swipe', swipeId);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] No RPG data found in last assistant message');
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -77,19 +75,12 @@ export function commitTrackerData() {
|
|||||||
* Sets the flag to indicate this is NOT a swipe.
|
* Sets the flag to indicate this is NOT a swipe.
|
||||||
* In separate mode with auto-update disabled, commits the displayed tracker data.
|
* In separate mode with auto-update disabled, commits the displayed tracker data.
|
||||||
*/
|
*/
|
||||||
export async function onMessageSent() {
|
export function onMessageSent() {
|
||||||
if (!extensionSettings.enabled) return;
|
if (!extensionSettings.enabled) return;
|
||||||
|
|
||||||
|
// User sent a new message - NOT a swipe
|
||||||
setLastActionWasSwipe(false);
|
setLastActionWasSwipe(false);
|
||||||
|
// console.log('[RPG Companion] 🟢 EVENT: onMessageSent - lastActionWasSwipe =', lastActionWasSwipe);
|
||||||
// Optionally intercept and rewrite the user message via LLM
|
|
||||||
if (extensionSettings.enableMessageInterception && extensionSettings.messageInterceptionActive !== false) {
|
|
||||||
try {
|
|
||||||
await interceptAndModifyUserMessage();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[RPG Companion] Message interception failed:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In separate mode with auto-update disabled, commit displayed tracker when user sends a message
|
// In separate mode with auto-update disabled, commit displayed tracker when user sends a message
|
||||||
if (extensionSettings.generationMode === 'separate' && !extensionSettings.autoUpdate) {
|
if (extensionSettings.generationMode === 'separate' && !extensionSettings.autoUpdate) {
|
||||||
@@ -99,89 +90,14 @@ export async function onMessageSent() {
|
|||||||
committedTrackerData.infoBox = lastGeneratedData.infoBox;
|
committedTrackerData.infoBox = lastGeneratedData.infoBox;
|
||||||
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
committedTrackerData.characterThoughts = lastGeneratedData.characterThoughts;
|
||||||
|
|
||||||
|
// Save to chat metadata
|
||||||
saveChatData();
|
saveChatData();
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] 💾 Committed displayed tracker on user message (auto-update disabled)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts the last user message, asks the LLM to rewrite it using RPG state and recent chat,
|
|
||||||
* and updates the chat/DOM with the modified content.
|
|
||||||
*/
|
|
||||||
async function interceptAndModifyUserMessage() {
|
|
||||||
const context = getContext();
|
|
||||||
const chatHistory = context.chat || chat;
|
|
||||||
|
|
||||||
if (!chatHistory || chatHistory.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastMessage = chatHistory[chatHistory.length - 1];
|
|
||||||
if (!lastMessage || !lastMessage.is_user) {
|
|
||||||
return; // Only rewrite user messages
|
|
||||||
}
|
|
||||||
|
|
||||||
const originalText = lastMessage.mes || '';
|
|
||||||
const stateJson = generateContextualSummary();
|
|
||||||
const depth = extensionSettings.messageInterceptionContextDepth || extensionSettings.updateDepth || 4;
|
|
||||||
const startIndex = Math.max(0, chatHistory.length - 1 - depth);
|
|
||||||
const recentMessages = chatHistory.slice(startIndex, chatHistory.length - 1);
|
|
||||||
|
|
||||||
const recentContext = recentMessages
|
|
||||||
.map((m) => {
|
|
||||||
const role = m.is_system ? 'system' : m.is_user ? '{{user}}' : '{{char}}';
|
|
||||||
const content = (m.mes || '').replace(/\s+/g, ' ').trim();
|
|
||||||
return `- ${role}: ${content}`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const basePrompt =
|
|
||||||
(extensionSettings.customMessageInterceptionPrompt || '').trim() ||
|
|
||||||
DEFAULT_MESSAGE_INTERCEPTION_PROMPT;
|
|
||||||
|
|
||||||
const promptMessages = [
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: basePrompt
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: `{{user}}'s persona definition:\n{{persona}}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: `Current RPG state (JSON):\n${stateJson ? `\`\`\`json\n${stateJson}\n\`\`\`` : 'None'}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'system',
|
|
||||||
content: `Recent messages (newest last):\n${recentContext || 'None'}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
role: 'user',
|
|
||||||
content: `User draft message:\n${originalText}\n\nReturn only the modified message text.`
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const response = await generateRaw({
|
|
||||||
prompt: promptMessages,
|
|
||||||
quietToLoud: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response || typeof response !== 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleaned = response.trim();
|
|
||||||
if (!cleaned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update chat history and DOM
|
|
||||||
lastMessage.mes = cleaned;
|
|
||||||
const messageId = chatHistory.length - 1;
|
|
||||||
updateMessageBlock(messageId, lastMessage, { rerenderMessage: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for when a message is generated.
|
* Event handler for when a message is generated.
|
||||||
*/
|
*/
|
||||||
@@ -196,46 +112,22 @@ export async function onMessageReceived(data) {
|
|||||||
const lastMessage = chat[chat.length - 1];
|
const lastMessage = chat[chat.length - 1];
|
||||||
if (lastMessage && !lastMessage.is_user) {
|
if (lastMessage && !lastMessage.is_user) {
|
||||||
const responseText = lastMessage.mes;
|
const responseText = lastMessage.mes;
|
||||||
|
// console.log('[RPG Companion] Parsing together mode response:', responseText);
|
||||||
|
|
||||||
// Try JSON parsing first if structured data mode is enabled
|
|
||||||
const jsonParsed = tryParseJSONResponse(responseText);
|
|
||||||
|
|
||||||
if (jsonParsed) {
|
|
||||||
console.log('[RPG Companion] JSON parsing successful in together mode');
|
|
||||||
// JSON data is already applied to extensionSettings by the parser
|
|
||||||
// Just need to render and save
|
|
||||||
renderUserStats();
|
|
||||||
renderInfoBox();
|
|
||||||
renderThoughts();
|
|
||||||
renderInventory();
|
|
||||||
renderQuests();
|
|
||||||
renderSkills();
|
|
||||||
saveChatData();
|
|
||||||
return; // Skip legacy text parsing
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON parsing failed - fall back to legacy text-based parsing
|
|
||||||
console.warn('[RPG Companion] JSON parsing failed in together mode, attempting legacy text parsing...');
|
|
||||||
const parsedData = parseResponse(responseText);
|
const parsedData = parseResponse(responseText);
|
||||||
|
// console.log('[RPG Companion] Parsed data:', parsedData);
|
||||||
// Legacy text parsing does not produce structured characters; clear old state to avoid stale UI/state
|
|
||||||
extensionSettings.charactersData = [];
|
|
||||||
const parsedCharacterThoughts = parsedData.characterThoughts || '';
|
|
||||||
|
|
||||||
// Update stored data
|
// Update stored data
|
||||||
if (parsedData.userStats) {
|
if (parsedData.userStats) {
|
||||||
lastGeneratedData.userStats = parsedData.userStats;
|
lastGeneratedData.userStats = parsedData.userStats;
|
||||||
parseUserStats(parsedData.userStats);
|
parseUserStats(parsedData.userStats);
|
||||||
}
|
}
|
||||||
if (parsedData.skills) {
|
|
||||||
parseSkills(parsedData.skills);
|
|
||||||
}
|
|
||||||
if (parsedData.infoBox) {
|
if (parsedData.infoBox) {
|
||||||
lastGeneratedData.infoBox = parsedData.infoBox;
|
lastGeneratedData.infoBox = parsedData.infoBox;
|
||||||
}
|
}
|
||||||
|
if (parsedData.characterThoughts) {
|
||||||
// Response omitted characters section - clear any previous thoughts to reflect removal
|
lastGeneratedData.characterThoughts = parsedData.characterThoughts;
|
||||||
lastGeneratedData.characterThoughts = parsedCharacterThoughts;
|
}
|
||||||
|
|
||||||
// Store RPG data for this specific swipe in the message's extra field
|
// Store RPG data for this specific swipe in the message's extra field
|
||||||
if (!lastMessage.extra) {
|
if (!lastMessage.extra) {
|
||||||
@@ -249,14 +141,19 @@ export async function onMessageReceived(data) {
|
|||||||
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
|
lastMessage.extra.rpg_companion_swipes[currentSwipeId] = {
|
||||||
userStats: parsedData.userStats,
|
userStats: parsedData.userStats,
|
||||||
infoBox: parsedData.infoBox,
|
infoBox: parsedData.infoBox,
|
||||||
characterThoughts: parsedCharacterThoughts
|
characterThoughts: parsedData.characterThoughts
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Stored RPG data for swipe', currentSwipeId);
|
||||||
|
|
||||||
// If there's no committed data yet (first time generating), automatically commit
|
// If there's no committed data yet (first time generating), automatically commit
|
||||||
if (!committedTrackerData.userStats && !committedTrackerData.infoBox && !committedTrackerData.characterThoughts) {
|
if (!committedTrackerData.userStats && !committedTrackerData.infoBox && !committedTrackerData.characterThoughts) {
|
||||||
committedTrackerData.userStats = parsedData.userStats;
|
committedTrackerData.userStats = parsedData.userStats;
|
||||||
committedTrackerData.infoBox = parsedData.infoBox;
|
committedTrackerData.infoBox = parsedData.infoBox;
|
||||||
committedTrackerData.characterThoughts = parsedCharacterThoughts;
|
committedTrackerData.characterThoughts = parsedData.characterThoughts;
|
||||||
|
// console.log('[RPG Companion] 🔆 FIRST TIME: Auto-committed tracker data');
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] Data will be committed when user replies');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the tracker code blocks from the visible message
|
// Remove the tracker code blocks from the visible message
|
||||||
@@ -290,6 +187,8 @@ export async function onMessageReceived(data) {
|
|||||||
const messageId = chat.length - 1;
|
const messageId = chat.length - 1;
|
||||||
updateMessageBlock(messageId, lastMessage, { rerenderMessage: true });
|
updateMessageBlock(messageId, lastMessage, { rerenderMessage: true });
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Cleaned message, removed tracker code blocks from DOM');
|
||||||
|
|
||||||
// Save to chat metadata
|
// Save to chat metadata
|
||||||
saveChatData();
|
saveChatData();
|
||||||
}
|
}
|
||||||
@@ -302,7 +201,9 @@ export async function onMessageReceived(data) {
|
|||||||
|
|
||||||
// Reset the swipe flag after generation completes
|
// Reset the swipe flag after generation completes
|
||||||
// This ensures that if the user swiped → auto-reply generated → flag is now cleared
|
// This ensures that if the user swiped → auto-reply generated → flag is now cleared
|
||||||
|
// so the next user message will be treated as a new message (not a swipe)
|
||||||
if (lastActionWasSwipe) {
|
if (lastActionWasSwipe) {
|
||||||
|
// console.log('[RPG Companion] 🔄 Generation complete after swipe - resetting lastActionWasSwipe to false');
|
||||||
setLastActionWasSwipe(false);
|
setLastActionWasSwipe(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +211,7 @@ export async function onMessageReceived(data) {
|
|||||||
// Note: No need to clear extension prompt since we used quiet_prompt option
|
// Note: No need to clear extension prompt since we used quiet_prompt option
|
||||||
if (isPlotProgression) {
|
if (isPlotProgression) {
|
||||||
setIsPlotProgression(false);
|
setIsPlotProgression(false);
|
||||||
|
// console.log('[RPG Companion] Plot progression generation completed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +240,6 @@ export function onCharacterChanged() {
|
|||||||
renderThoughts();
|
renderThoughts();
|
||||||
renderInventory();
|
renderInventory();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
renderSkills();
|
|
||||||
|
|
||||||
// Update chat thought overlays
|
// Update chat thought overlays
|
||||||
updateChatThoughts();
|
updateChatThoughts();
|
||||||
@@ -353,6 +254,8 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Message swiped at index:', messageIndex);
|
||||||
|
|
||||||
// Get the message that was swiped
|
// Get the message that was swiped
|
||||||
const message = chat[messageIndex];
|
const message = chat[messageIndex];
|
||||||
if (!message || message.is_user) {
|
if (!message || message.is_user) {
|
||||||
@@ -369,9 +272,16 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
message.swipes[currentSwipeId].length > 0;
|
message.swipes[currentSwipeId].length > 0;
|
||||||
|
|
||||||
if (!isExistingSwipe) {
|
if (!isExistingSwipe) {
|
||||||
|
// This is a NEW swipe that will trigger generation
|
||||||
setLastActionWasSwipe(true);
|
setLastActionWasSwipe(true);
|
||||||
|
// console.log('[RPG Companion] 🔵 EVENT: onMessageSwiped (NEW generation) - lastActionWasSwipe =', lastActionWasSwipe);
|
||||||
|
} else {
|
||||||
|
// This is navigating to an EXISTING swipe - don't change the flag
|
||||||
|
// console.log('[RPG Companion] 🔵 EVENT: onMessageSwiped (existing swipe navigation) - lastActionWasSwipe unchanged =', lastActionWasSwipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Loading data for swipe', currentSwipeId);
|
||||||
|
|
||||||
// Load RPG data for this swipe into lastGeneratedData (for display only)
|
// Load RPG data for this swipe into lastGeneratedData (for display only)
|
||||||
// This updates what the user sees, but does NOT commit it
|
// This updates what the user sees, but does NOT commit it
|
||||||
// Committed data will be updated when/if the user replies to this swipe
|
// Committed data will be updated when/if the user replies to this swipe
|
||||||
@@ -387,6 +297,13 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
if (swipeData.userStats) {
|
if (swipeData.userStats) {
|
||||||
parseUserStats(swipeData.userStats);
|
parseUserStats(swipeData.userStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Loaded RPG data for swipe', currentSwipeId, '(display only, NOT committed)');
|
||||||
|
// console.log('[RPG Companion] committedTrackerData unchanged - will be updated if user replies to this swipe');
|
||||||
|
} else {
|
||||||
|
// No data for this swipe - keep existing lastGeneratedData (don't clear it)
|
||||||
|
// This ensures the display remains consistent and data is available for next commit
|
||||||
|
// console.log('[RPG Companion] No RPG data for swipe', currentSwipeId, '- keeping existing lastGeneratedData');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-render the panels (display only - committedTrackerData unchanged)
|
// Re-render the panels (display only - committedTrackerData unchanged)
|
||||||
@@ -406,6 +323,7 @@ export function onMessageSwiped(messageIndex) {
|
|||||||
export function updatePersonaAvatar() {
|
export function updatePersonaAvatar() {
|
||||||
const portraitImg = document.querySelector('.rpg-user-portrait');
|
const portraitImg = document.querySelector('.rpg-user-portrait');
|
||||||
if (!portraitImg) {
|
if (!portraitImg) {
|
||||||
|
// console.log('[RPG Companion] Portrait image element not found in DOM');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,13 +331,23 @@ export function updatePersonaAvatar() {
|
|||||||
const context = getContext();
|
const context = getContext();
|
||||||
const currentUserAvatar = context.user_avatar || user_avatar;
|
const currentUserAvatar = context.user_avatar || user_avatar;
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Attempting to update persona avatar:', currentUserAvatar);
|
||||||
|
|
||||||
// Try to get a valid thumbnail URL using our safe helper
|
// Try to get a valid thumbnail URL using our safe helper
|
||||||
if (currentUserAvatar) {
|
if (currentUserAvatar) {
|
||||||
const thumbnailUrl = getSafeThumbnailUrl('persona', currentUserAvatar);
|
const thumbnailUrl = getSafeThumbnailUrl('persona', currentUserAvatar);
|
||||||
|
|
||||||
if (thumbnailUrl) {
|
if (thumbnailUrl) {
|
||||||
|
// Only update the src if we got a valid URL
|
||||||
portraitImg.src = thumbnailUrl;
|
portraitImg.src = thumbnailUrl;
|
||||||
|
// console.log('[RPG Companion] Persona avatar updated successfully');
|
||||||
|
} else {
|
||||||
|
// Don't update the src if we couldn't get a valid URL
|
||||||
|
// This prevents 400 errors and keeps the existing image
|
||||||
|
// console.warn('[RPG Companion] Could not get valid thumbnail URL for persona avatar, keeping existing image');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] No user avatar configured, keeping existing image');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,4 +359,6 @@ export function clearExtensionPrompts() {
|
|||||||
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
|
||||||
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
|
||||||
|
// Note: rpg-companion-plot is not cleared here since it's passed via quiet_prompt option
|
||||||
|
// console.log('[RPG Companion] Cleared all extension prompts');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
|
import { extensionSettings, lastGeneratedData, committedTrackerData } from '../../core/state.js';
|
||||||
import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js';
|
import { saveSettings, saveChatData, updateMessageSwipeData } from '../../core/persistence.js';
|
||||||
|
import { buildInventorySummary } from '../generation/promptBuilder.js';
|
||||||
import { buildUserStatsText } from '../rendering/userStats.js';
|
import { buildUserStatsText } from '../rendering/userStats.js';
|
||||||
import { renderInventory, getLocationId } from '../rendering/inventory.js';
|
import { renderInventory, getLocationId } from '../rendering/inventory.js';
|
||||||
import { parseItems, serializeItems } from '../../utils/itemParser.js';
|
import { parseItems, serializeItems } from '../../utils/itemParser.js';
|
||||||
import { sanitizeLocationName, sanitizeItemName } from '../../utils/security.js';
|
import { sanitizeLocationName, sanitizeItemName } from '../../utils/security.js';
|
||||||
import { handleItemRemoved, navigateToLinkedSkills } from '../rendering/skills.js';
|
|
||||||
|
|
||||||
// Type imports
|
// Type imports
|
||||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||||
@@ -34,8 +34,7 @@ let openForms = {
|
|||||||
addLocation: false,
|
addLocation: false,
|
||||||
addItemOnPerson: false,
|
addItemOnPerson: false,
|
||||||
addItemStored: {}, // { [locationName]: true/false }
|
addItemStored: {}, // { [locationName]: true/false }
|
||||||
addItemAssets: false,
|
addItemAssets: false
|
||||||
addItemSimplified: false
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +54,7 @@ function updateLastGeneratedDataInventory() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the inline form for adding a new item.
|
* Shows the inline form for adding a new item.
|
||||||
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
|
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
|
||||||
* @param {string} [location] - Location name (required for 'stored' field)
|
* @param {string} [location] - Location name (required for 'stored' field)
|
||||||
*/
|
*/
|
||||||
export function showAddItemForm(field, location) {
|
export function showAddItemForm(field, location) {
|
||||||
@@ -77,8 +76,6 @@ export function showAddItemForm(field, location) {
|
|||||||
openForms.addItemOnPerson = true;
|
openForms.addItemOnPerson = true;
|
||||||
} else if (field === 'assets') {
|
} else if (field === 'assets') {
|
||||||
openForms.addItemAssets = true;
|
openForms.addItemAssets = true;
|
||||||
} else if (field === 'simplified') {
|
|
||||||
openForms.addItemSimplified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +88,7 @@ export function showAddItemForm(field, location) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the inline form for adding a new item.
|
* Hides the inline form for adding a new item.
|
||||||
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
|
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
|
||||||
* @param {string} [location] - Location name (required for 'stored' field)
|
* @param {string} [location] - Location name (required for 'stored' field)
|
||||||
*/
|
*/
|
||||||
export function hideAddItemForm(field, location) {
|
export function hideAddItemForm(field, location) {
|
||||||
@@ -114,8 +111,6 @@ export function hideAddItemForm(field, location) {
|
|||||||
openForms.addItemOnPerson = false;
|
openForms.addItemOnPerson = false;
|
||||||
} else if (field === 'assets') {
|
} else if (field === 'assets') {
|
||||||
openForms.addItemAssets = false;
|
openForms.addItemAssets = false;
|
||||||
} else if (field === 'simplified') {
|
|
||||||
openForms.addItemSimplified = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,26 +123,21 @@ export function hideAddItemForm(field, location) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new item to the inventory.
|
* Adds a new item to the inventory.
|
||||||
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
|
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
|
||||||
* @param {string} [location] - Location name (required for 'stored' field)
|
* @param {string} [location] - Location name (required for 'stored' field)
|
||||||
*/
|
*/
|
||||||
export function saveAddItem(field, location) {
|
export function saveAddItem(field, location) {
|
||||||
const inventory = extensionSettings.userStats.inventory;
|
const inventory = extensionSettings.userStats.inventory;
|
||||||
let inputId;
|
let inputId;
|
||||||
let descInputId;
|
|
||||||
|
|
||||||
if (field === 'stored') {
|
if (field === 'stored') {
|
||||||
inputId = `.rpg-location-item-input[data-location="${location}"]`;
|
inputId = `.rpg-location-item-input[data-location="${location}"]`;
|
||||||
descInputId = `.rpg-location-item-desc-input[data-location="${location}"]`;
|
|
||||||
} else {
|
} else {
|
||||||
inputId = `#rpg-new-item-${field}`;
|
inputId = `#rpg-new-item-${field}`;
|
||||||
descInputId = `#rpg-new-item-desc-${field}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const input = $(inputId);
|
const input = $(inputId);
|
||||||
const descInput = $(descInputId);
|
|
||||||
const rawItemName = input.val().trim();
|
const rawItemName = input.val().trim();
|
||||||
const itemDescription = descInput?.val()?.trim() || '';
|
|
||||||
|
|
||||||
if (!rawItemName) {
|
if (!rawItemName) {
|
||||||
hideAddItemForm(field, location);
|
hideAddItemForm(field, location);
|
||||||
@@ -162,41 +152,9 @@ export function saveAddItem(field, location) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update structured inventory (inventoryV3) if it exists
|
// Get current items, add new one, serialize back
|
||||||
if (extensionSettings.inventoryV3) {
|
|
||||||
const newItem = { name: itemName, description: itemDescription };
|
|
||||||
|
|
||||||
if (field === 'simplified') {
|
|
||||||
if (!extensionSettings.inventoryV3.simplified) {
|
|
||||||
extensionSettings.inventoryV3.simplified = [];
|
|
||||||
}
|
|
||||||
extensionSettings.inventoryV3.simplified.push(newItem);
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
if (!extensionSettings.inventoryV3.stored) {
|
|
||||||
extensionSettings.inventoryV3.stored = {};
|
|
||||||
}
|
|
||||||
if (!extensionSettings.inventoryV3.stored[location]) {
|
|
||||||
extensionSettings.inventoryV3.stored[location] = [];
|
|
||||||
}
|
|
||||||
extensionSettings.inventoryV3.stored[location].push(newItem);
|
|
||||||
} else if (field === 'onPerson') {
|
|
||||||
if (!extensionSettings.inventoryV3.onPerson) {
|
|
||||||
extensionSettings.inventoryV3.onPerson = [];
|
|
||||||
}
|
|
||||||
extensionSettings.inventoryV3.onPerson.push(newItem);
|
|
||||||
} else if (field === 'assets') {
|
|
||||||
if (!extensionSettings.inventoryV3.assets) {
|
|
||||||
extensionSettings.inventoryV3.assets = [];
|
|
||||||
}
|
|
||||||
extensionSettings.inventoryV3.assets.push(newItem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also update legacy inventory format for backwards compatibility
|
|
||||||
let currentString;
|
let currentString;
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
currentString = inventory.items || inventory.onPerson || 'None';
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
currentString = inventory.stored[location] || 'None';
|
currentString = inventory.stored[location] || 'None';
|
||||||
} else {
|
} else {
|
||||||
currentString = inventory[field] || 'None';
|
currentString = inventory[field] || 'None';
|
||||||
@@ -207,10 +165,7 @@ export function saveAddItem(field, location) {
|
|||||||
const newString = serializeItems(items);
|
const newString = serializeItems(items);
|
||||||
|
|
||||||
// Save back to inventory
|
// Save back to inventory
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
inventory.items = newString;
|
|
||||||
inventory.onPerson = newString;
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
inventory.stored[location] = newString;
|
inventory.stored[location] = newString;
|
||||||
} else {
|
} else {
|
||||||
inventory[field] = newString;
|
inventory[field] = newString;
|
||||||
@@ -228,70 +183,43 @@ export function saveAddItem(field, location) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes an item from the inventory.
|
* Removes an item from the inventory.
|
||||||
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
|
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
|
||||||
* @param {number} itemIndex - Index of item to remove
|
* @param {number} itemIndex - Index of item to remove
|
||||||
* @param {string} [location] - Location name (required for 'stored' field)
|
* @param {string} [location] - Location name (required for 'stored' field)
|
||||||
*/
|
*/
|
||||||
export function removeItem(field, itemIndex, location) {
|
export function removeItem(field, itemIndex, location) {
|
||||||
const inventory = extensionSettings.userStats.inventory;
|
const inventory = extensionSettings.userStats.inventory;
|
||||||
let removedItemName = null;
|
|
||||||
|
|
||||||
// Remove from structured inventory (inventoryV3) if it exists
|
// console.log('[RPG Companion] DEBUG removeItem called:', { field, itemIndex, location });
|
||||||
if (extensionSettings.inventoryV3) {
|
|
||||||
let structuredArray = null;
|
|
||||||
|
|
||||||
if (field === 'simplified' && extensionSettings.inventoryV3.simplified) {
|
// Get current items, remove the one at index, serialize back
|
||||||
structuredArray = extensionSettings.inventoryV3.simplified;
|
|
||||||
} else if (field === 'stored' && extensionSettings.inventoryV3.stored?.[location]) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.stored[location];
|
|
||||||
} else if (field === 'onPerson' && extensionSettings.inventoryV3.onPerson) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.onPerson;
|
|
||||||
} else if (field === 'assets' && extensionSettings.inventoryV3.assets) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (structuredArray && structuredArray[itemIndex]) {
|
|
||||||
removedItemName = structuredArray[itemIndex].name;
|
|
||||||
structuredArray.splice(itemIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current items from legacy format
|
|
||||||
let currentString;
|
let currentString;
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
currentString = inventory.items || inventory.onPerson || 'None';
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
currentString = inventory.stored[location] || 'None';
|
currentString = inventory.stored[location] || 'None';
|
||||||
} else {
|
} else {
|
||||||
currentString = inventory[field] || 'None';
|
currentString = inventory[field] || 'None';
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = parseItems(currentString);
|
// console.log('[RPG Companion] DEBUG currentString before removal:', currentString);
|
||||||
|
|
||||||
// Get the item name before removing (for item-skill link check) - use structured name if available
|
const items = parseItems(currentString);
|
||||||
if (!removedItemName) {
|
// console.log('[RPG Companion] DEBUG items array before removal:', items);
|
||||||
removedItemName = items[itemIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
items.splice(itemIndex, 1); // Remove item at index
|
items.splice(itemIndex, 1); // Remove item at index
|
||||||
|
// console.log('[RPG Companion] DEBUG items array after removal:', items);
|
||||||
// Check if this item was linked to a skill and handle removal
|
|
||||||
if (removedItemName && extensionSettings.enableItemSkillLinks) {
|
|
||||||
handleItemRemoved(removedItemName);
|
|
||||||
}
|
|
||||||
|
|
||||||
const newString = serializeItems(items);
|
const newString = serializeItems(items);
|
||||||
|
// console.log('[RPG Companion] DEBUG newString after removal:', newString);
|
||||||
|
|
||||||
// Save back to legacy inventory
|
// Save back to inventory
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
inventory.items = newString;
|
|
||||||
inventory.onPerson = newString;
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
inventory.stored[location] = newString;
|
inventory.stored[location] = newString;
|
||||||
} else {
|
} else {
|
||||||
inventory[field] = newString;
|
inventory[field] = newString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] DEBUG inventory after save:', inventory);
|
||||||
|
|
||||||
updateLastGeneratedDataInventory();
|
updateLastGeneratedDataInventory();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
@@ -372,11 +300,15 @@ export function saveAddLocation() {
|
|||||||
* @param {string} locationName - Name of location to remove
|
* @param {string} locationName - Name of location to remove
|
||||||
*/
|
*/
|
||||||
export function showRemoveConfirmation(locationName) {
|
export function showRemoveConfirmation(locationName) {
|
||||||
|
// console.log('[RPG Companion] DEBUG showRemoveConfirmation called for:', locationName);
|
||||||
const confirmId = `rpg-remove-confirm-${getLocationId(locationName)}`;
|
const confirmId = `rpg-remove-confirm-${getLocationId(locationName)}`;
|
||||||
|
// console.log('[RPG Companion] DEBUG confirmId:', confirmId);
|
||||||
const confirmUI = $(`#${confirmId}`);
|
const confirmUI = $(`#${confirmId}`);
|
||||||
|
// console.log('[RPG Companion] DEBUG confirmUI element found:', confirmUI.length);
|
||||||
|
|
||||||
if (confirmUI.length > 0) {
|
if (confirmUI.length > 0) {
|
||||||
confirmUI.show();
|
confirmUI.show();
|
||||||
|
// console.log('[RPG Companion] DEBUG confirmation shown');
|
||||||
} else {
|
} else {
|
||||||
console.warn('[RPG Companion] DEBUG confirmation element not found!');
|
console.warn('[RPG Companion] DEBUG confirmation element not found!');
|
||||||
}
|
}
|
||||||
@@ -400,9 +332,12 @@ export function hideRemoveConfirmation(locationName) {
|
|||||||
* @param {string} locationName - Name of location to remove
|
* @param {string} locationName - Name of location to remove
|
||||||
*/
|
*/
|
||||||
export function confirmRemoveLocation(locationName) {
|
export function confirmRemoveLocation(locationName) {
|
||||||
|
// console.log('[RPG Companion] DEBUG confirmRemoveLocation called for:', locationName);
|
||||||
const inventory = extensionSettings.userStats.inventory;
|
const inventory = extensionSettings.userStats.inventory;
|
||||||
|
// console.log('[RPG Companion] DEBUG inventory.stored before deletion:', inventory.stored);
|
||||||
|
|
||||||
delete inventory.stored[locationName];
|
delete inventory.stored[locationName];
|
||||||
|
// console.log('[RPG Companion] DEBUG inventory.stored after deletion:', inventory.stored);
|
||||||
|
|
||||||
// Remove from collapsed list if present
|
// Remove from collapsed list if present
|
||||||
const index = collapsedLocations.indexOf(locationName);
|
const index = collapsedLocations.indexOf(locationName);
|
||||||
@@ -414,6 +349,9 @@ export function confirmRemoveLocation(locationName) {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
saveChatData();
|
||||||
updateMessageSwipeData();
|
updateMessageSwipeData();
|
||||||
|
|
||||||
|
// Re-render inventory UI
|
||||||
|
// console.log('[RPG Companion] DEBUG calling renderInventory()');
|
||||||
renderInventory();
|
renderInventory();
|
||||||
}/**
|
}/**
|
||||||
* Toggles the collapsed state of a storage location section.
|
* Toggles the collapsed state of a storage location section.
|
||||||
@@ -530,16 +468,6 @@ export function initInventoryEventListeners() {
|
|||||||
removeItem(field, itemIndex, location);
|
removeItem(field, itemIndex, location);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Go to linked skills button (on inventory items)
|
|
||||||
$(document).on('click', '.rpg-item-skill-link[data-action="goto-linked-skills"]', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const itemName = $(this).data('item');
|
|
||||||
if (itemName) {
|
|
||||||
navigateToLinkedSkills(itemName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add location button - shows inline form
|
// Add location button - shows inline form
|
||||||
$(document).on('click', '.rpg-inventory-add-btn[data-action="add-location"]', function(e) {
|
$(document).on('click', '.rpg-inventory-add-btn[data-action="add-location"]', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -608,6 +536,8 @@ export function initInventoryEventListeners() {
|
|||||||
const view = $(this).data('view');
|
const view = $(this).data('view');
|
||||||
switchViewMode(field, view);
|
switchViewMode(field, view);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Inventory event listeners initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -630,6 +560,7 @@ export function restoreFormStates() {
|
|||||||
// Restore add location form
|
// Restore add location form
|
||||||
if (openForms.addLocation) {
|
if (openForms.addLocation) {
|
||||||
const form = $('#rpg-add-location-form');
|
const form = $('#rpg-add-location-form');
|
||||||
|
const input = $('#rpg-new-location-name');
|
||||||
if (form.length > 0) {
|
if (form.length > 0) {
|
||||||
form.show();
|
form.show();
|
||||||
// Don't refocus to avoid disrupting user interaction
|
// Don't refocus to avoid disrupting user interaction
|
||||||
@@ -639,6 +570,7 @@ export function restoreFormStates() {
|
|||||||
// Restore add item on person form
|
// Restore add item on person form
|
||||||
if (openForms.addItemOnPerson) {
|
if (openForms.addItemOnPerson) {
|
||||||
const form = $('#rpg-add-item-form-onPerson');
|
const form = $('#rpg-add-item-form-onPerson');
|
||||||
|
const input = $('#rpg-new-item-onPerson');
|
||||||
if (form.length > 0) {
|
if (form.length > 0) {
|
||||||
form.show();
|
form.show();
|
||||||
}
|
}
|
||||||
@@ -647,14 +579,7 @@ export function restoreFormStates() {
|
|||||||
// Restore add item assets form
|
// Restore add item assets form
|
||||||
if (openForms.addItemAssets) {
|
if (openForms.addItemAssets) {
|
||||||
const form = $('#rpg-add-item-form-assets');
|
const form = $('#rpg-add-item-form-assets');
|
||||||
if (form.length > 0) {
|
const input = $('#rpg-new-item-assets');
|
||||||
form.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore add item simplified form
|
|
||||||
if (openForms.addItemSimplified) {
|
|
||||||
const form = $('#rpg-add-item-form-simplified');
|
|
||||||
if (form.length > 0) {
|
if (form.length > 0) {
|
||||||
form.show();
|
form.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { sanitizeItemName } from '../../utils/security.js';
|
|||||||
* Updates an existing inventory item's name.
|
* Updates an existing inventory item's name.
|
||||||
* Validates, sanitizes, and persists the change.
|
* Validates, sanitizes, and persists the change.
|
||||||
*
|
*
|
||||||
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
|
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
|
||||||
* @param {number} index - Index of item in the array
|
* @param {number} index - Index of item in the array
|
||||||
* @param {string} newName - New name for the item
|
* @param {string} newName - New name for the item
|
||||||
* @param {string} [location] - Location name (required for 'stored' field)
|
* @param {string} [location] - Location name (required for 'stored' field)
|
||||||
@@ -31,38 +31,9 @@ export function updateInventoryItem(field, index, newName, location) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the OLD item name before updating (for skill link updates)
|
// Get current items for the field
|
||||||
let oldItemName = null;
|
|
||||||
if (extensionSettings.inventoryV3) {
|
|
||||||
let structuredArray = null;
|
|
||||||
|
|
||||||
if (field === 'simplified' && extensionSettings.inventoryV3.simplified) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.simplified;
|
|
||||||
} else if (field === 'stored' && extensionSettings.inventoryV3.stored?.[location]) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.stored[location];
|
|
||||||
} else if (field === 'onPerson' && extensionSettings.inventoryV3.onPerson) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.onPerson;
|
|
||||||
} else if (field === 'assets' && extensionSettings.inventoryV3.assets) {
|
|
||||||
structuredArray = extensionSettings.inventoryV3.assets;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (structuredArray && structuredArray[index]) {
|
|
||||||
const item = structuredArray[index];
|
|
||||||
oldItemName = typeof item === 'string' ? item : item.name;
|
|
||||||
// Update the structured item
|
|
||||||
if (typeof item === 'object') {
|
|
||||||
item.name = sanitizedName;
|
|
||||||
} else {
|
|
||||||
structuredArray[index] = sanitizedName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get current items for the legacy format
|
|
||||||
let currentString;
|
let currentString;
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
currentString = inventory.items || inventory.onPerson || 'None';
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
if (!location) {
|
if (!location) {
|
||||||
console.error('[RPG Companion] Location required for stored items');
|
console.error('[RPG Companion] Location required for stored items');
|
||||||
return;
|
return;
|
||||||
@@ -81,32 +52,19 @@ export function updateInventoryItem(field, index, newName, location) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get old name from legacy format if not found in structured format
|
|
||||||
if (!oldItemName) {
|
|
||||||
oldItemName = items[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the item at this index
|
// Update the item at this index
|
||||||
items[index] = sanitizedName;
|
items[index] = sanitizedName;
|
||||||
|
|
||||||
// Serialize back to string
|
// Serialize back to string
|
||||||
const newItemString = serializeItems(items);
|
const newItemString = serializeItems(items);
|
||||||
|
|
||||||
// Update the legacy inventory
|
// Update the inventory
|
||||||
if (field === 'simplified') {
|
if (field === 'stored') {
|
||||||
inventory.items = newItemString;
|
|
||||||
inventory.onPerson = newItemString;
|
|
||||||
} else if (field === 'stored') {
|
|
||||||
inventory.stored[location] = newItemString;
|
inventory.stored[location] = newItemString;
|
||||||
} else {
|
} else {
|
||||||
inventory[field] = newItemString;
|
inventory[field] = newItemString;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update skill links if the item name changed
|
|
||||||
if (oldItemName && oldItemName !== sanitizedName && extensionSettings.skillAbilityLinks) {
|
|
||||||
updateSkillLinksForRenamedItem(oldItemName, sanitizedName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update lastGeneratedData and committedTrackerData with new inventory
|
// Update lastGeneratedData and committedTrackerData with new inventory
|
||||||
updateLastGeneratedDataInventory();
|
updateLastGeneratedDataInventory();
|
||||||
|
|
||||||
@@ -119,30 +77,6 @@ export function updateInventoryItem(field, index, newName, location) {
|
|||||||
renderInventory();
|
renderInventory();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates skill-ability links when an inventory item is renamed
|
|
||||||
* @param {string} oldName - The old item name
|
|
||||||
* @param {string} newName - The new item name
|
|
||||||
*/
|
|
||||||
function updateSkillLinksForRenamedItem(oldName, newName) {
|
|
||||||
if (!extensionSettings.skillAbilityLinks) return;
|
|
||||||
|
|
||||||
const oldNameLower = oldName.toLowerCase().trim();
|
|
||||||
let updated = false;
|
|
||||||
|
|
||||||
for (const [key, linkedItem] of Object.entries(extensionSettings.skillAbilityLinks)) {
|
|
||||||
// Case-insensitive comparison to match the linking logic
|
|
||||||
if (linkedItem && linkedItem.toLowerCase().trim() === oldNameLower) {
|
|
||||||
extensionSettings.skillAbilityLinks[key] = newName;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
console.log(`[RPG Companion] Updated skill links: "${oldName}" -> "${newName}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates lastGeneratedData.userStats AND committedTrackerData.userStats to include
|
* Updates lastGeneratedData.userStats AND committedTrackerData.userStats to include
|
||||||
* current inventory in text format.
|
* current inventory in text format.
|
||||||
|
|||||||
+105
-192
@@ -51,29 +51,6 @@ function separateEmojiFromText(str) {
|
|||||||
return { emoji: '', text: str };
|
return { emoji: '', text: str };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a value is valid (not null, undefined, or the string "null")
|
|
||||||
*/
|
|
||||||
function isValidValue(val) {
|
|
||||||
return val !== null && val !== undefined && val !== 'null' && val !== '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if we have valid structured infoBox data
|
|
||||||
* @param {Object} data - The infoBoxData object
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function hasStructuredInfoBoxData(data) {
|
|
||||||
if (!data) return false;
|
|
||||||
// Handle recentEvents as either string or array
|
|
||||||
const hasEvents = data.recentEvents && (
|
|
||||||
(Array.isArray(data.recentEvents) && data.recentEvents.length > 0) ||
|
|
||||||
(typeof data.recentEvents === 'string' && data.recentEvents.length > 0 && data.recentEvents !== 'null')
|
|
||||||
);
|
|
||||||
return isValidValue(data.date) || isValidValue(data.weather) || isValidValue(data.temperature) ||
|
|
||||||
isValidValue(data.time) || isValidValue(data.location) || hasEvents;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the info box as a visual dashboard with calendar, weather, temperature, clock, and map widgets.
|
* Renders the info box as a visual dashboard with calendar, weather, temperature, clock, and map widgets.
|
||||||
* Includes event listeners for editable fields.
|
* Includes event listeners for editable fields.
|
||||||
@@ -88,28 +65,8 @@ export function renderInfoBox() {
|
|||||||
$infoBoxContainer.addClass('rpg-content-updating');
|
$infoBoxContainer.addClass('rpg-content-updating');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert structured JSON data to text format for the original fancy renderer
|
// Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh)
|
||||||
const structuredData = extensionSettings.infoBoxData;
|
const infoBoxData = lastGeneratedData.infoBox || committedTrackerData.infoBox;
|
||||||
let infoBoxData = lastGeneratedData.infoBox || committedTrackerData.infoBox;
|
|
||||||
|
|
||||||
// If we have structured data, convert it to text format
|
|
||||||
if (structuredData && hasStructuredInfoBoxData(structuredData)) {
|
|
||||||
const lines = [];
|
|
||||||
if (isValidValue(structuredData.date)) lines.push(`Date: ${structuredData.date}`);
|
|
||||||
if (isValidValue(structuredData.time)) lines.push(`Time: ${structuredData.time}`);
|
|
||||||
if (isValidValue(structuredData.weather)) lines.push(`Weather: ${structuredData.weather}`);
|
|
||||||
if (isValidValue(structuredData.temperature)) lines.push(`Temperature: ${structuredData.temperature}`);
|
|
||||||
if (isValidValue(structuredData.location)) lines.push(`Location: ${structuredData.location}`);
|
|
||||||
if (structuredData.recentEvents) {
|
|
||||||
const events = Array.isArray(structuredData.recentEvents)
|
|
||||||
? structuredData.recentEvents
|
|
||||||
: [structuredData.recentEvents];
|
|
||||||
events.filter(e => e && e !== 'null').forEach(e => lines.push(`Recent Events: ${e}`));
|
|
||||||
}
|
|
||||||
if (lines.length > 0) {
|
|
||||||
infoBoxData = lines.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no data yet, show placeholder
|
// If no data yet, show placeholder
|
||||||
if (!infoBoxData) {
|
if (!infoBoxData) {
|
||||||
@@ -128,7 +85,11 @@ export function renderInfoBox() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] renderInfoBox called with data:', infoBoxData);
|
||||||
|
|
||||||
|
// Parse the info box data
|
||||||
const lines = infoBoxData.split('\n');
|
const lines = infoBoxData.split('\n');
|
||||||
|
// console.log('[RPG Companion] Info Box split into lines:', lines);
|
||||||
const data = {
|
const data = {
|
||||||
date: '',
|
date: '',
|
||||||
weekday: '',
|
weekday: '',
|
||||||
@@ -154,124 +115,107 @@ export function renderInfoBox() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// Helper to check if a value is valid (not null/empty)
|
// console.log('[RPG Companion] Processing line:', line);
|
||||||
const isValidParsedValue = (val) => val && val !== 'null' && val !== 'undefined' && val.toLowerCase() !== 'none';
|
|
||||||
|
|
||||||
// Support both new text format (Date:) and legacy emoji format (🗓️:)
|
// Support both new text format (Date:) and legacy emoji format (🗓️:)
|
||||||
// Prioritize text format over emoji format
|
// Prioritize text format over emoji format
|
||||||
if (line.startsWith('Date:')) {
|
if (line.startsWith('Date:')) {
|
||||||
if (!parsedFields.date) {
|
if (!parsedFields.date) {
|
||||||
|
// console.log('[RPG Companion] → Matched DATE (text format)');
|
||||||
const dateStr = line.replace('Date:', '').trim();
|
const dateStr = line.replace('Date:', '').trim();
|
||||||
if (isValidParsedValue(dateStr)) {
|
const dateParts = dateStr.split(',').map(p => p.trim());
|
||||||
const dateParts = dateStr.split(',').map(p => p.trim());
|
data.weekday = dateParts[0] || '';
|
||||||
data.weekday = dateParts[0] || '';
|
data.month = dateParts[1] || '';
|
||||||
data.month = dateParts[1] || '';
|
data.year = dateParts[2] || '';
|
||||||
data.year = dateParts[2] || '';
|
data.date = dateStr;
|
||||||
data.date = dateStr;
|
parsedFields.date = true;
|
||||||
parsedFields.date = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.includes('🗓️:')) {
|
} else if (line.includes('🗓️:')) {
|
||||||
if (!parsedFields.date) {
|
if (!parsedFields.date) {
|
||||||
|
// console.log('[RPG Companion] → Matched DATE (emoji format)');
|
||||||
const dateStr = line.replace('🗓️:', '').trim();
|
const dateStr = line.replace('🗓️:', '').trim();
|
||||||
if (isValidParsedValue(dateStr)) {
|
const dateParts = dateStr.split(',').map(p => p.trim());
|
||||||
const dateParts = dateStr.split(',').map(p => p.trim());
|
data.weekday = dateParts[0] || '';
|
||||||
data.weekday = dateParts[0] || '';
|
data.month = dateParts[1] || '';
|
||||||
data.month = dateParts[1] || '';
|
data.year = dateParts[2] || '';
|
||||||
data.year = dateParts[2] || '';
|
data.date = dateStr;
|
||||||
data.date = dateStr;
|
parsedFields.date = true;
|
||||||
parsedFields.date = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('Temperature:')) {
|
} else if (line.startsWith('Temperature:')) {
|
||||||
if (!parsedFields.temperature) {
|
if (!parsedFields.temperature) {
|
||||||
|
// console.log('[RPG Companion] → Matched TEMPERATURE (text format)');
|
||||||
const tempStr = line.replace('Temperature:', '').trim();
|
const tempStr = line.replace('Temperature:', '').trim();
|
||||||
if (isValidParsedValue(tempStr)) {
|
data.temperature = tempStr;
|
||||||
data.temperature = tempStr;
|
const tempMatch = tempStr.match(/(-?\d+)/);
|
||||||
const tempMatch = tempStr.match(/(-?\d+)/);
|
if (tempMatch) {
|
||||||
if (tempMatch) {
|
data.tempValue = parseInt(tempMatch[1]);
|
||||||
data.tempValue = parseInt(tempMatch[1]);
|
|
||||||
}
|
|
||||||
parsedFields.temperature = true;
|
|
||||||
}
|
}
|
||||||
|
parsedFields.temperature = true;
|
||||||
}
|
}
|
||||||
} else if (line.includes('🌡️:')) {
|
} else if (line.includes('🌡️:')) {
|
||||||
if (!parsedFields.temperature) {
|
if (!parsedFields.temperature) {
|
||||||
|
// console.log('[RPG Companion] → Matched TEMPERATURE (emoji format)');
|
||||||
const tempStr = line.replace('🌡️:', '').trim();
|
const tempStr = line.replace('🌡️:', '').trim();
|
||||||
if (isValidParsedValue(tempStr)) {
|
data.temperature = tempStr;
|
||||||
data.temperature = tempStr;
|
const tempMatch = tempStr.match(/(-?\d+)/);
|
||||||
const tempMatch = tempStr.match(/(-?\d+)/);
|
if (tempMatch) {
|
||||||
if (tempMatch) {
|
data.tempValue = parseInt(tempMatch[1]);
|
||||||
data.tempValue = parseInt(tempMatch[1]);
|
|
||||||
}
|
|
||||||
parsedFields.temperature = true;
|
|
||||||
}
|
}
|
||||||
|
parsedFields.temperature = true;
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('Time:')) {
|
} else if (line.startsWith('Time:')) {
|
||||||
if (!parsedFields.time) {
|
if (!parsedFields.time) {
|
||||||
|
// console.log('[RPG Companion] → Matched TIME (text format)');
|
||||||
const timeStr = line.replace('Time:', '').trim();
|
const timeStr = line.replace('Time:', '').trim();
|
||||||
if (isValidParsedValue(timeStr)) {
|
data.time = timeStr;
|
||||||
data.time = timeStr;
|
const timeParts = timeStr.split('→').map(t => t.trim());
|
||||||
const timeParts = timeStr.split('→').map(t => t.trim());
|
data.timeStart = timeParts[0] || '';
|
||||||
data.timeStart = timeParts[0] || '';
|
data.timeEnd = timeParts[1] || '';
|
||||||
data.timeEnd = timeParts[1] || '';
|
parsedFields.time = true;
|
||||||
parsedFields.time = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.includes('🕒:')) {
|
} else if (line.includes('🕒:')) {
|
||||||
if (!parsedFields.time) {
|
if (!parsedFields.time) {
|
||||||
|
// console.log('[RPG Companion] → Matched TIME (emoji format)');
|
||||||
const timeStr = line.replace('🕒:', '').trim();
|
const timeStr = line.replace('🕒:', '').trim();
|
||||||
if (isValidParsedValue(timeStr)) {
|
data.time = timeStr;
|
||||||
data.time = timeStr;
|
const timeParts = timeStr.split('→').map(t => t.trim());
|
||||||
const timeParts = timeStr.split('→').map(t => t.trim());
|
data.timeStart = timeParts[0] || '';
|
||||||
data.timeStart = timeParts[0] || '';
|
data.timeEnd = timeParts[1] || '';
|
||||||
data.timeEnd = timeParts[1] || '';
|
parsedFields.time = true;
|
||||||
parsedFields.time = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('Location:')) {
|
} else if (line.startsWith('Location:')) {
|
||||||
if (!parsedFields.location) {
|
if (!parsedFields.location) {
|
||||||
const locStr = line.replace('Location:', '').trim();
|
// console.log('[RPG Companion] → Matched LOCATION (text format)');
|
||||||
if (isValidParsedValue(locStr)) {
|
data.location = line.replace('Location:', '').trim();
|
||||||
data.location = locStr;
|
parsedFields.location = true;
|
||||||
parsedFields.location = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.includes('🗺️:')) {
|
} else if (line.includes('🗺️:')) {
|
||||||
if (!parsedFields.location) {
|
if (!parsedFields.location) {
|
||||||
const locStr = line.replace('🗺️:', '').trim();
|
// console.log('[RPG Companion] → Matched LOCATION (emoji format)');
|
||||||
if (isValidParsedValue(locStr)) {
|
data.location = line.replace('🗺️:', '').trim();
|
||||||
data.location = locStr;
|
parsedFields.location = true;
|
||||||
parsedFields.location = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (line.startsWith('Weather:')) {
|
} else if (line.startsWith('Weather:')) {
|
||||||
if (!parsedFields.weather) {
|
if (!parsedFields.weather) {
|
||||||
// New text format: Weather: [Emoji], [Forecast] OR Weather: [Emoji][Forecast] (no separator - FIXED)
|
// New text format: Weather: [Emoji], [Forecast] OR Weather: [Emoji][Forecast] (no separator - FIXED)
|
||||||
const weatherStr = line.replace('Weather:', '').trim();
|
const weatherStr = line.replace('Weather:', '').trim();
|
||||||
|
const { emoji, text } = separateEmojiFromText(weatherStr);
|
||||||
|
|
||||||
// Skip null/invalid values
|
if (emoji && text) {
|
||||||
if (!isValidParsedValue(weatherStr)) {
|
data.weatherEmoji = emoji;
|
||||||
parsedFields.weather = true; // Mark as parsed so we don't try again
|
data.weatherForecast = text;
|
||||||
|
} else if (weatherStr.includes(',')) {
|
||||||
|
// Fallback to comma split if emoji detection failed
|
||||||
|
const weatherParts = weatherStr.split(',').map(p => p.trim());
|
||||||
|
data.weatherEmoji = weatherParts[0] || '';
|
||||||
|
data.weatherForecast = weatherParts[1] || '';
|
||||||
} else {
|
} else {
|
||||||
const { emoji, text } = separateEmojiFromText(weatherStr);
|
// No clear separation - assume it's all forecast text
|
||||||
|
data.weatherEmoji = '🌤️'; // Default emoji
|
||||||
if (emoji && text) {
|
data.weatherForecast = weatherStr;
|
||||||
data.weatherEmoji = emoji;
|
|
||||||
data.weatherForecast = text;
|
|
||||||
} else if (weatherStr.includes(',')) {
|
|
||||||
// Fallback to comma split if emoji detection failed
|
|
||||||
const weatherParts = weatherStr.split(',').map(p => p.trim());
|
|
||||||
data.weatherEmoji = weatherParts[0] || '';
|
|
||||||
data.weatherForecast = weatherParts[1] || '';
|
|
||||||
} else {
|
|
||||||
// No clear separation - assume it's all forecast text
|
|
||||||
data.weatherEmoji = '🌤️'; // Default emoji
|
|
||||||
data.weatherForecast = weatherStr;
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedFields.weather = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedFields.weather = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check if it's a legacy weather line (emoji format)
|
// Check if it's a legacy weather line (emoji format)
|
||||||
@@ -283,41 +227,50 @@ export function renderInfoBox() {
|
|||||||
const notDivider = !line.includes('---');
|
const notDivider = !line.includes('---');
|
||||||
const notCodeFence = !line.trim().startsWith('```');
|
const notCodeFence = !line.trim().startsWith('```');
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] → Checking weather conditions:', {
|
||||||
|
// line: line,
|
||||||
|
// hasColon: hasColon,
|
||||||
|
// notInfoBox: notInfoBox,
|
||||||
|
// notDivider: notDivider
|
||||||
|
// });
|
||||||
|
|
||||||
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
|
if (hasColon && notInfoBox && notDivider && notCodeFence && line.trim().length > 0) {
|
||||||
|
// Match format: [Weather Emoji]: [Forecast]
|
||||||
|
// Capture everything before colon as emoji, everything after as forecast
|
||||||
|
// console.log('[RPG Companion] → Testing WEATHER match for:', line);
|
||||||
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
|
const weatherMatch = line.match(/^\s*([^:]+):\s*(.+)$/);
|
||||||
if (weatherMatch) {
|
if (weatherMatch) {
|
||||||
const potentialEmoji = weatherMatch[1].trim();
|
const potentialEmoji = weatherMatch[1].trim();
|
||||||
const forecast = weatherMatch[2].trim();
|
const forecast = weatherMatch[2].trim();
|
||||||
|
|
||||||
|
// If the first part is short (likely emoji), treat as weather
|
||||||
if (potentialEmoji.length <= 5) {
|
if (potentialEmoji.length <= 5) {
|
||||||
data.weatherEmoji = potentialEmoji;
|
data.weatherEmoji = potentialEmoji;
|
||||||
data.weatherForecast = forecast;
|
data.weatherForecast = forecast;
|
||||||
parsedFields.weather = true;
|
parsedFields.weather = true;
|
||||||
|
// console.log('[RPG Companion] ✓ Weather parsed:', data.weatherEmoji, data.weatherForecast);
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] ✗ First part too long for emoji:', potentialEmoji);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] ✗ Weather regex did not match');
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// console.log('[RPG Companion] → No match for this line');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Parsed Info Box data:', {
|
||||||
|
// date: data.date,
|
||||||
|
// weatherEmoji: data.weatherEmoji,
|
||||||
|
// weatherForecast: data.weatherForecast,
|
||||||
// temperature: data.temperature,
|
// temperature: data.temperature,
|
||||||
// timeStart: data.timeStart,
|
// timeStart: data.timeStart,
|
||||||
// location: data.location
|
// location: data.location
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// Sanitize parsed values - filter out "null" strings and invalid values
|
|
||||||
const sanitize = (val) => (val && val !== 'null' && val !== 'undefined' && val.toLowerCase() !== 'none') ? val : '';
|
|
||||||
data.date = sanitize(data.date);
|
|
||||||
data.weekday = sanitize(data.weekday);
|
|
||||||
data.month = sanitize(data.month);
|
|
||||||
data.year = sanitize(data.year);
|
|
||||||
data.weatherEmoji = sanitize(data.weatherEmoji);
|
|
||||||
data.weatherForecast = sanitize(data.weatherForecast);
|
|
||||||
data.temperature = sanitize(data.temperature);
|
|
||||||
data.time = sanitize(data.time);
|
|
||||||
data.timeStart = sanitize(data.timeStart);
|
|
||||||
data.timeEnd = sanitize(data.timeEnd);
|
|
||||||
data.location = sanitize(data.location);
|
|
||||||
|
|
||||||
// Get tracker configuration
|
// Get tracker configuration
|
||||||
const config = extensionSettings.trackerConfig?.infoBox;
|
const config = extensionSettings.trackerConfig?.infoBox;
|
||||||
|
|
||||||
@@ -468,21 +421,9 @@ export function renderInfoBox() {
|
|||||||
|
|
||||||
// Row 3: Recent Events widget (notebook style) - show if enabled
|
// Row 3: Recent Events widget (notebook style) - show if enabled
|
||||||
if (config?.widgets?.recentEvents?.enabled) {
|
if (config?.widgets?.recentEvents?.enabled) {
|
||||||
// Get Recent Events from structured data (JSON) or text format
|
// Parse Recent Events from infoBox string
|
||||||
let recentEvents = [];
|
let recentEvents = [];
|
||||||
|
if (committedTrackerData.infoBox) {
|
||||||
// First check structured infoBoxData (from JSON parsing)
|
|
||||||
if (extensionSettings.infoBoxData?.recentEvents) {
|
|
||||||
const events = extensionSettings.infoBoxData.recentEvents;
|
|
||||||
if (Array.isArray(events)) {
|
|
||||||
recentEvents = events.filter(e => e && e !== 'null');
|
|
||||||
} else if (typeof events === 'string' && events !== 'null') {
|
|
||||||
recentEvents = [events];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to text format from committedTrackerData
|
|
||||||
if (recentEvents.length === 0 && committedTrackerData.infoBox) {
|
|
||||||
const recentEventsLine = committedTrackerData.infoBox.split('\n').find(line => line.startsWith('Recent Events:'));
|
const recentEventsLine = committedTrackerData.infoBox.split('\n').find(line => line.startsWith('Recent Events:'));
|
||||||
if (recentEventsLine) {
|
if (recentEventsLine) {
|
||||||
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
||||||
@@ -598,12 +539,14 @@ export function updateInfoBoxField(field, value) {
|
|||||||
// Reconstruct the Info Box text with updated field
|
// Reconstruct the Info Box text with updated field
|
||||||
const lines = lastGeneratedData.infoBox.split('\n');
|
const lines = lastGeneratedData.infoBox.split('\n');
|
||||||
let dateLineFound = false;
|
let dateLineFound = false;
|
||||||
|
let dateLineIndex = -1;
|
||||||
let weatherLineIndex = -1;
|
let weatherLineIndex = -1;
|
||||||
|
|
||||||
// Find the date line
|
// Find the date line
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i++) {
|
||||||
if (lines[i].includes('🗓️:') || lines[i].startsWith('Date:')) {
|
if (lines[i].includes('🗓️:') || lines[i].startsWith('Date:')) {
|
||||||
dateLineFound = true;
|
dateLineFound = true;
|
||||||
|
dateLineIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -837,6 +780,7 @@ export function updateInfoBoxField(field, value) {
|
|||||||
const swipeId = message.swipe_id || 0;
|
const swipeId = message.swipe_id || 0;
|
||||||
if (message.extra.rpg_companion_swipes[swipeId]) {
|
if (message.extra.rpg_companion_swipes[swipeId]) {
|
||||||
message.extra.rpg_companion_swipes[swipeId].infoBox = updatedLines.join('\n');
|
message.extra.rpg_companion_swipes[swipeId].infoBox = updatedLines.join('\n');
|
||||||
|
// console.log('[RPG Companion] Updated infoBox in message swipe data');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -867,57 +811,34 @@ function updateRecentEvent(field, value) {
|
|||||||
}[field];
|
}[field];
|
||||||
|
|
||||||
if (eventIndex !== undefined) {
|
if (eventIndex !== undefined) {
|
||||||
// Get existing events - prioritize structured data (same logic as renderInfoBox)
|
// Parse current infoBox to get existing events
|
||||||
|
const lines = (committedTrackerData.infoBox || '').split('\n');
|
||||||
let recentEvents = [];
|
let recentEvents = [];
|
||||||
|
|
||||||
// First check structured infoBoxData (from JSON parsing)
|
// Find existing Recent Events line
|
||||||
if (extensionSettings.infoBoxData?.recentEvents) {
|
const recentEventsLine = lines.find(line => line.startsWith('Recent Events:'));
|
||||||
const events = extensionSettings.infoBoxData.recentEvents;
|
if (recentEventsLine) {
|
||||||
if (Array.isArray(events)) {
|
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
||||||
// Get all valid events, preserving order (max 3)
|
if (eventsString) {
|
||||||
recentEvents = events.filter(e => e && e !== 'null').slice(0, 3);
|
recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e);
|
||||||
} else if (typeof events === 'string' && events !== 'null') {
|
|
||||||
recentEvents = [events];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to text format from committedTrackerData
|
// Ensure array has enough slots
|
||||||
if (recentEvents.length === 0 && committedTrackerData.infoBox) {
|
|
||||||
const lines = (committedTrackerData.infoBox || '').split('\n');
|
|
||||||
const recentEventsLine = lines.find(line => line.startsWith('Recent Events:'));
|
|
||||||
if (recentEventsLine) {
|
|
||||||
const eventsString = recentEventsLine.replace('Recent Events:', '').trim();
|
|
||||||
if (eventsString) {
|
|
||||||
recentEvents = eventsString.split(',').map(e => e.trim()).filter(e => e).slice(0, 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out placeholder text - treat it as empty
|
|
||||||
const placeholderText = i18n.getTranslation('infobox.recentEvents.addEventPlaceholder');
|
|
||||||
const cleanedValue = (value === placeholderText || value === 'Add event...' || value === 'Click to add event') ? '' : value.trim();
|
|
||||||
|
|
||||||
// Update the specific event in the array
|
|
||||||
// Ensure array has enough slots for the index we're updating
|
|
||||||
while (recentEvents.length <= eventIndex) {
|
while (recentEvents.length <= eventIndex) {
|
||||||
recentEvents.push('');
|
recentEvents.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the specific event
|
// Update the specific event
|
||||||
recentEvents[eventIndex] = cleanedValue;
|
recentEvents[eventIndex] = value;
|
||||||
|
|
||||||
// Filter out empty events for final storage (but preserve order of non-empty ones)
|
// Filter out empty events and rebuild the line
|
||||||
const validEvents = recentEvents.filter(e => e && e.trim());
|
const validEvents = recentEvents.filter(e => e && e.trim());
|
||||||
|
|
||||||
const newRecentEventsLine = validEvents.length > 0
|
const newRecentEventsLine = validEvents.length > 0
|
||||||
? `Recent Events: ${validEvents.join(', ')}`
|
? `Recent Events: ${validEvents.join(', ')}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
// Update infoBox with new Recent Events line
|
// Update infoBox with new Recent Events line
|
||||||
// Need to get lines from committedTrackerData if we haven't already
|
|
||||||
let lines = [];
|
|
||||||
if (committedTrackerData.infoBox) {
|
|
||||||
lines = committedTrackerData.infoBox.split('\n');
|
|
||||||
}
|
|
||||||
const updatedLines = lines.filter(line => !line.startsWith('Recent Events:'));
|
const updatedLines = lines.filter(line => !line.startsWith('Recent Events:'));
|
||||||
if (newRecentEventsLine) {
|
if (newRecentEventsLine) {
|
||||||
// Add Recent Events line at the end (before any empty lines)
|
// Add Recent Events line at the end (before any empty lines)
|
||||||
@@ -934,14 +855,6 @@ function updateRecentEvent(field, value) {
|
|||||||
committedTrackerData.infoBox = updatedLines.join('\n');
|
committedTrackerData.infoBox = updatedLines.join('\n');
|
||||||
lastGeneratedData.infoBox = updatedLines.join('\n');
|
lastGeneratedData.infoBox = updatedLines.join('\n');
|
||||||
|
|
||||||
// Also update the structured data to keep it in sync
|
|
||||||
// Store only valid events (renderInfoBox will handle showing placeholders for empty slots)
|
|
||||||
// This prevents renderInfoBox() from using stale structured data
|
|
||||||
if (!extensionSettings.infoBoxData) {
|
|
||||||
extensionSettings.infoBoxData = {};
|
|
||||||
}
|
|
||||||
extensionSettings.infoBoxData.recentEvents = validEvents;
|
|
||||||
|
|
||||||
// Update the message's swipe data
|
// Update the message's swipe data
|
||||||
const chat = getContext().chat;
|
const chat = getContext().chat;
|
||||||
if (chat && chat.length > 0) {
|
if (chat && chat.length > 0) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { getInventoryRenderOptions, restoreFormStates } from '../interaction/inv
|
|||||||
import { updateInventoryItem } from '../interaction/inventoryEdit.js';
|
import { updateInventoryItem } from '../interaction/inventoryEdit.js';
|
||||||
import { parseItems } from '../../utils/itemParser.js';
|
import { parseItems } from '../../utils/itemParser.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
import { itemHasLinkedSkills } from './skills.js';
|
|
||||||
|
|
||||||
// Type imports
|
// Type imports
|
||||||
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
|
||||||
@@ -24,23 +23,6 @@ export function getLocationId(locationName) {
|
|||||||
return locationName.replace(/[^a-zA-Z0-9\s]/g, '').replace(/\s+/g, '-');
|
return locationName.replace(/[^a-zA-Z0-9\s]/g, '').replace(/\s+/g, '-');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the skill link indicator for an inventory item
|
|
||||||
* @param {string} itemName - The item name
|
|
||||||
* @returns {string} HTML string for the link indicator (empty if no links)
|
|
||||||
*/
|
|
||||||
function getSkillLinkIndicator(itemName) {
|
|
||||||
if (!extensionSettings.enableItemSkillLinks || !extensionSettings.showSkills) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (itemHasLinkedSkills(itemName)) {
|
|
||||||
return `<button class="rpg-item-skill-link" data-action="goto-linked-skills" data-item="${escapeHtml(itemName)}" title="${i18n.getTranslation('inventory.gotoLinkedSkills')}">
|
|
||||||
<i class="fa-solid fa-star"></i>
|
|
||||||
</button>`;
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the inventory sub-tab navigation (On Person, Stored, Assets)
|
* Renders the inventory sub-tab navigation (On Person, Stored, Assets)
|
||||||
* @param {string} activeTab - Currently active sub-tab ('onPerson', 'stored', 'assets')
|
* @param {string} activeTab - Currently active sub-tab ('onPerson', 'stored', 'assets')
|
||||||
@@ -62,33 +44,6 @@ export function renderInventorySubTabs(activeTab = 'onPerson') {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the description for an item from structured inventory data
|
|
||||||
* @param {string} field - Field type ('onPerson', 'stored', 'assets', 'simplified')
|
|
||||||
* @param {number} index - Item index
|
|
||||||
* @param {string} [location] - Location name for stored items
|
|
||||||
* @returns {string} Item description or empty string
|
|
||||||
*/
|
|
||||||
function getItemDescription(field, index, location = null) {
|
|
||||||
const inv3 = extensionSettings.inventoryV3;
|
|
||||||
if (!inv3) return '';
|
|
||||||
|
|
||||||
let items;
|
|
||||||
if (field === 'onPerson') {
|
|
||||||
items = inv3.onPerson;
|
|
||||||
} else if (field === 'assets') {
|
|
||||||
items = inv3.assets;
|
|
||||||
} else if (field === 'stored' && location) {
|
|
||||||
items = inv3.stored?.[location];
|
|
||||||
} else if (field === 'simplified') {
|
|
||||||
items = inv3.simplified;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!items || !Array.isArray(items) || !items[index]) return '';
|
|
||||||
const item = items[index];
|
|
||||||
return (typeof item === 'object' ? item.description : '') || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the "On Person" inventory view with list or grid display
|
* Renders the "On Person" inventory view with list or grid display
|
||||||
* @param {string} onPersonItems - Current on-person items (comma-separated string)
|
* @param {string} onPersonItems - Current on-person items (comma-separated string)
|
||||||
@@ -104,38 +59,24 @@ export function renderOnPersonView(onPersonItems, viewMode = 'list') {
|
|||||||
} else {
|
} else {
|
||||||
if (viewMode === 'grid') {
|
if (viewMode === 'grid') {
|
||||||
// Grid view: card-style items
|
// Grid view: card-style items
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('onPerson', index);
|
<div class="rpg-item-card" data-field="onPerson" data-index="${index}">
|
||||||
return `
|
|
||||||
<div class="rpg-item-card ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="onPerson" data-index="${index}">
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
|
||||||
<i class="fa-solid fa-times"></i>
|
<i class="fa-solid fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
} else {
|
} else {
|
||||||
// List view: full-width rows
|
// List view: full-width rows
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('onPerson', index);
|
<div class="rpg-item-row" data-field="onPerson" data-index="${index}">
|
||||||
return `
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
<div class="rpg-item-row ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="onPerson" data-index="${index}">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
|
||||||
<div class="rpg-item-main-row">
|
<i class="fa-solid fa-times"></i>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
</button>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="onPerson" data-index="${index}" title="Remove item">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="onPerson" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,38 +181,24 @@ export function renderStoredView(stored, collapsedLocations = [], viewMode = 'li
|
|||||||
} else {
|
} else {
|
||||||
if (viewMode === 'grid') {
|
if (viewMode === 'grid') {
|
||||||
// Grid view: card-style items
|
// Grid view: card-style items
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('stored', index, location);
|
<div class="rpg-item-card" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
|
||||||
return `
|
|
||||||
<div class="rpg-item-card ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
|
||||||
<i class="fa-solid fa-times"></i>
|
<i class="fa-solid fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
} else {
|
} else {
|
||||||
// List view: full-width rows
|
// List view: full-width rows
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('stored', index, location);
|
<div class="rpg-item-row" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
|
||||||
return `
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
<div class="rpg-item-row ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
|
||||||
<div class="rpg-item-main-row">
|
<i class="fa-solid fa-times"></i>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
</button>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" title="Remove item">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="stored" data-location="${escapeHtml(location)}" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,38 +277,24 @@ export function renderAssetsView(assets, viewMode = 'list') {
|
|||||||
} else {
|
} else {
|
||||||
if (viewMode === 'grid') {
|
if (viewMode === 'grid') {
|
||||||
// Grid view: card-style items
|
// Grid view: card-style items
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('assets', index);
|
<div class="rpg-item-card" data-field="assets" data-index="${index}">
|
||||||
return `
|
|
||||||
<div class="rpg-item-card ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="assets" data-index="${index}">
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="Remove asset">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="Remove asset">
|
||||||
<i class="fa-solid fa-times"></i>
|
<i class="fa-solid fa-times"></i>
|
||||||
</button>
|
</button>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
} else {
|
} else {
|
||||||
// List view: full-width rows
|
// List view: full-width rows
|
||||||
itemsHtml = items.map((item, index) => {
|
itemsHtml = items.map((item, index) => `
|
||||||
const desc = getItemDescription('assets', index);
|
<div class="rpg-item-row" data-field="assets" data-index="${index}">
|
||||||
return `
|
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
||||||
<div class="rpg-item-row ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="assets" data-index="${index}">
|
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="${i18n.getTranslation('inventory.assets.removeAssetTitle')}">
|
||||||
<div class="rpg-item-main-row">
|
<i class="fa-solid fa-times"></i>
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
</button>
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="assets" data-index="${index}" title="${i18n.getTranslation('inventory.assets.removeAssetTitle')}">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="assets" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`}).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,9 +356,18 @@ function generateInventoryHTML(inventory, options = {}) {
|
|||||||
collapsedLocations = []
|
collapsedLocations = []
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// Ensure v2 structure has all required fields
|
// Handle legacy v1 format - convert to v2 for display
|
||||||
// Note: Migration functions handle v1→v2 conversion on load, so inventory should always be v2 here
|
|
||||||
let v2Inventory = inventory;
|
let v2Inventory = inventory;
|
||||||
|
if (typeof inventory === 'string') {
|
||||||
|
v2Inventory = {
|
||||||
|
version: 2,
|
||||||
|
onPerson: inventory,
|
||||||
|
stored: {},
|
||||||
|
assets: 'None'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure v2 structure has all required fields
|
||||||
if (!v2Inventory || typeof v2Inventory !== 'object') {
|
if (!v2Inventory || typeof v2Inventory !== 'object') {
|
||||||
v2Inventory = {
|
v2Inventory = {
|
||||||
version: 2,
|
version: 2,
|
||||||
@@ -522,113 +444,6 @@ export function updateInventoryDisplay(containerId, options = {}) {
|
|||||||
restoreFormStates();
|
restoreFormStates();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the simplified (single-list) inventory view
|
|
||||||
* Used when useSimplifiedInventory setting is enabled
|
|
||||||
* @param {string} itemsString - All items as a comma-separated string
|
|
||||||
* @param {string} viewMode - View mode ('list' or 'grid')
|
|
||||||
* @returns {string} HTML for simplified inventory view
|
|
||||||
*/
|
|
||||||
export function renderSimplifiedInventoryView(itemsString, viewMode = 'list') {
|
|
||||||
const items = parseItems(itemsString);
|
|
||||||
|
|
||||||
let itemsHtml = '';
|
|
||||||
if (items.length === 0) {
|
|
||||||
itemsHtml = `<div class="rpg-inventory-empty" data-i18n-key="inventory.simplified.empty">${i18n.getTranslation('inventory.simplified.empty')}</div>`;
|
|
||||||
} else {
|
|
||||||
if (viewMode === 'grid') {
|
|
||||||
// Grid view: card-style items (same as onPerson)
|
|
||||||
itemsHtml = items.map((item, index) => {
|
|
||||||
const desc = getItemDescription('simplified', index);
|
|
||||||
return `
|
|
||||||
<div class="rpg-item-card ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="simplified" data-index="${index}">
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="simplified" data-index="${index}" title="${i18n.getTranslation('inventory.simplified.removeTitle')}">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`}).join('');
|
|
||||||
} else {
|
|
||||||
// List view: full-width rows (same as onPerson)
|
|
||||||
itemsHtml = items.map((item, index) => {
|
|
||||||
const desc = getItemDescription('simplified', index);
|
|
||||||
return `
|
|
||||||
<div class="rpg-item-row ${itemHasLinkedSkills(item) ? 'rpg-has-skill-link' : ''}" data-field="simplified" data-index="${index}">
|
|
||||||
<div class="rpg-item-main-row">
|
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
|
|
||||||
${getSkillLinkIndicator(item)}
|
|
||||||
<button class="rpg-item-remove" data-action="remove-item" data-field="simplified" data-index="${index}" title="${i18n.getTranslation('inventory.simplified.removeTitle')}">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(desc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`}).join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listViewClass = viewMode === 'list' ? 'rpg-item-list-view' : 'rpg-item-grid-view';
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="rpg-inventory-container">
|
|
||||||
<div class="rpg-inventory-section" data-section="simplified">
|
|
||||||
<div class="rpg-inventory-header">
|
|
||||||
<h4 data-i18n-key="inventory.simplified.title">${i18n.getTranslation('inventory.simplified.title')}</h4>
|
|
||||||
<div class="rpg-inventory-header-actions">
|
|
||||||
<div class="rpg-view-toggle">
|
|
||||||
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="simplified" data-view="list" title="${i18n.getTranslation('global.listView')}">
|
|
||||||
<i class="fa-solid fa-list"></i>
|
|
||||||
</button>
|
|
||||||
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="simplified" data-view="grid" title="${i18n.getTranslation('global.gridView')}">
|
|
||||||
<i class="fa-solid fa-th"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="simplified" title="Add new item">
|
|
||||||
<i class="fa-solid fa-plus"></i> <span data-i18n-key="inventory.simplified.addItemButton">${i18n.getTranslation('inventory.simplified.addItemButton')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-inventory-content">
|
|
||||||
<div class="rpg-inline-form" id="rpg-add-item-form-simplified" style="display: none;">
|
|
||||||
<input type="text" class="rpg-inline-input" id="rpg-new-item-simplified" placeholder="${i18n.getTranslation('inventory.simplified.addItemPlaceholder')}" data-i18n-placeholder-key="inventory.simplified.addItemPlaceholder" />
|
|
||||||
<div class="rpg-inline-buttons">
|
|
||||||
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="simplified">
|
|
||||||
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
|
|
||||||
</button>
|
|
||||||
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="simplified">
|
|
||||||
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-list ${listViewClass}">
|
|
||||||
${itemsHtml}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if we have structured inventory data (v3 format)
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function hasStructuredInventory() {
|
|
||||||
const inv = extensionSettings.inventoryV3;
|
|
||||||
return inv && (
|
|
||||||
(inv.onPerson && inv.onPerson.length > 0) ||
|
|
||||||
(inv.assets && inv.assets.length > 0) ||
|
|
||||||
(inv.stored && Object.keys(inv.stored).length > 0) ||
|
|
||||||
(inv.simplified && inv.simplified.length > 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main inventory rendering function (matches pattern of other render functions)
|
* Main inventory rendering function (matches pattern of other render functions)
|
||||||
* Gets data from state/settings and updates DOM directly.
|
* Gets data from state/settings and updates DOM directly.
|
||||||
@@ -640,43 +455,17 @@ export function renderInventory() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let html;
|
// Get inventory data from settings
|
||||||
|
const inventory = extensionSettings.userStats.inventory;
|
||||||
|
|
||||||
// Convert structured inventory (v3) to legacy format if present
|
// Get current render options (active tab, collapsed locations)
|
||||||
// This ensures we always use the original renderer
|
const options = getInventoryRenderOptions();
|
||||||
let inventory = extensionSettings.userStats.inventory;
|
|
||||||
if (hasStructuredInventory()) {
|
|
||||||
const inv = extensionSettings.inventoryV3;
|
|
||||||
// Convert structured items to comma-separated strings
|
|
||||||
const itemsToString = (items) => {
|
|
||||||
if (!items || items.length === 0) return 'None';
|
|
||||||
return items.map(i => typeof i === 'string' ? i : i.name).join(', ');
|
|
||||||
};
|
|
||||||
inventory = {
|
|
||||||
version: 2,
|
|
||||||
onPerson: itemsToString(inv.onPerson),
|
|
||||||
stored: Object.fromEntries(
|
|
||||||
Object.entries(inv.stored || {}).map(([k, v]) => [k, itemsToString(v)])
|
|
||||||
),
|
|
||||||
assets: itemsToString(inv.assets),
|
|
||||||
// For simplified mode
|
|
||||||
items: itemsToString(inv.simplified)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we should render simplified inventory
|
|
||||||
if (extensionSettings.useSimplifiedInventory) {
|
|
||||||
const itemsString = inventory.items || inventory.onPerson || 'None';
|
|
||||||
const viewModes = extensionSettings.inventoryViewModes || {};
|
|
||||||
const viewMode = viewModes.simplified || viewModes.onPerson || 'list';
|
|
||||||
html = renderSimplifiedInventoryView(itemsString, viewMode);
|
|
||||||
} else {
|
|
||||||
const options = getInventoryRenderOptions();
|
|
||||||
html = generateInventoryHTML(inventory, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Generate HTML and update DOM
|
||||||
|
const html = generateInventoryHTML(inventory, options);
|
||||||
$inventoryContainer.html(html);
|
$inventoryContainer.html(html);
|
||||||
|
|
||||||
|
// Restore form states after re-rendering (fixes Bug #1)
|
||||||
restoreFormStates();
|
restoreFormStates();
|
||||||
|
|
||||||
// Event listener for editing item names (mobile-friendly contenteditable)
|
// Event listener for editing item names (mobile-friendly contenteditable)
|
||||||
@@ -687,45 +476,6 @@ export function renderInventory() {
|
|||||||
const newName = $(this).text().trim();
|
const newName = $(this).text().trim();
|
||||||
updateInventoryItem(field, index, newName, location);
|
updateInventoryItem(field, index, newName, location);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event listener for editing item descriptions (structured mode)
|
|
||||||
$inventoryContainer.find('.rpg-item-description.rpg-editable').on('blur', function() {
|
|
||||||
const field = $(this).data('field');
|
|
||||||
const index = parseInt($(this).data('index'));
|
|
||||||
const location = $(this).data('location');
|
|
||||||
const newDesc = $(this).text().trim();
|
|
||||||
updateStructuredItemDescription(field, index, newDesc, location);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an item's description in structured inventory
|
|
||||||
* @param {string} field - 'onPerson', 'stored', or 'assets'
|
|
||||||
* @param {number} index - Item index
|
|
||||||
* @param {string} newDescription - New description
|
|
||||||
* @param {string} [location] - Location for stored items
|
|
||||||
*/
|
|
||||||
function updateStructuredItemDescription(field, index, newDescription, location) {
|
|
||||||
const inv = extensionSettings.inventoryV3;
|
|
||||||
if (!inv) return;
|
|
||||||
|
|
||||||
let item;
|
|
||||||
if (field === 'onPerson' && inv.onPerson?.[index]) {
|
|
||||||
item = inv.onPerson[index];
|
|
||||||
} else if (field === 'assets' && inv.assets?.[index]) {
|
|
||||||
item = inv.assets[index];
|
|
||||||
} else if (field === 'stored' && location && inv.stored?.[location]?.[index]) {
|
|
||||||
item = inv.stored[location][index];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item.description = newDescription;
|
|
||||||
// Save changes
|
|
||||||
import('../../core/persistence.js').then(({ saveSettings, saveChatData }) => {
|
|
||||||
saveSettings();
|
|
||||||
saveChatData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+137
-154
@@ -1,11 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* Quests Rendering Module
|
* Quests Rendering Module
|
||||||
* Handles UI rendering for quests system (main and optional quests)
|
* Handles UI rendering for quests system (main and optional quests)
|
||||||
* Uses the same structure and styling as items/skills
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extensionSettings, $questsContainer } from '../../core/state.js';
|
import { extensionSettings, $questsContainer } from '../../core/state.js';
|
||||||
import { saveSettings, saveChatData } from '../../core/persistence.js';
|
import { saveSettings } from '../../core/persistence.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,25 +18,6 @@ function escapeHtml(text) {
|
|||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the main quest (migration handles legacy format conversion)
|
|
||||||
* @returns {{name: string, description: string}|null}
|
|
||||||
*/
|
|
||||||
function getMainQuest() {
|
|
||||||
if (extensionSettings.questsV2?.main) {
|
|
||||||
return extensionSettings.questsV2.main;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets optional quests (migration handles legacy format conversion)
|
|
||||||
* @returns {Array<{name: string, description: string}>}
|
|
||||||
*/
|
|
||||||
function getOptionalQuests() {
|
|
||||||
return extensionSettings.questsV2?.optional || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the quests sub-tab navigation (Main, Optional)
|
* Renders the quests sub-tab navigation (Main, Optional)
|
||||||
* @param {string} activeTab - Currently active sub-tab ('main', 'optional')
|
* @param {string} activeTab - Currently active sub-tab ('main', 'optional')
|
||||||
@@ -57,90 +37,89 @@ export function renderQuestsSubTabs(activeTab = 'main') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the main quest view (matches items/skills structure)
|
* Renders the main quest view
|
||||||
|
* @param {string} mainQuest - Current main quest title
|
||||||
* @returns {string} HTML for main quest view
|
* @returns {string} HTML for main quest view
|
||||||
*/
|
*/
|
||||||
export function renderMainQuestView() {
|
export function renderMainQuestView(mainQuest) {
|
||||||
const quest = getMainQuest();
|
const questDisplay = (mainQuest && mainQuest !== 'None') ? mainQuest : '';
|
||||||
const hasQuest = quest !== null;
|
const hasQuest = questDisplay.length > 0;
|
||||||
const questName = quest?.name || '';
|
|
||||||
const questDesc = quest?.description || '';
|
|
||||||
|
|
||||||
// Track if add form is open
|
|
||||||
const isFormOpen = openAddForms?.main || false;
|
|
||||||
|
|
||||||
let itemsHtml = '';
|
|
||||||
if (hasQuest) {
|
|
||||||
// Render quest as item (list view style, matching items/skills)
|
|
||||||
itemsHtml = `
|
|
||||||
<div class="rpg-item-row" data-field="main">
|
|
||||||
<div class="rpg-item-main-row">
|
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="main" data-prop="name" title="Click to edit">${escapeHtml(questName)}</span>
|
|
||||||
<button class="rpg-item-remove" data-action="remove-quest" data-field="main" title="Complete/Remove quest">
|
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="main" data-prop="description" title="Click to edit description">${escapeHtml(questDesc)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="rpg-quest-section">
|
<div class="rpg-quest-section">
|
||||||
<div class="rpg-quest-header">
|
<div class="rpg-quest-header">
|
||||||
<h3 class="rpg-quest-section-title" data-i18n-key="quests.main.title">${i18n.getTranslation('quests.main.title')}</h3>
|
<h3 class="rpg-quest-section-title" data-i18n-key="quests.main.title">${i18n.getTranslation('quests.main.title')}</h3>
|
||||||
<button class="rpg-inventory-add-btn" data-action="add-quest" data-field="main" title="${i18n.getTranslation('quests.main.addQuestTitle')}">
|
${!hasQuest ? `<button class="rpg-add-quest-btn" data-action="add-quest" data-field="main" title="${i18n.getTranslation('quests.main.addQuestTitle')}">
|
||||||
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
||||||
</button>
|
</button>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-quest-content">
|
<div class="rpg-quest-content">
|
||||||
<div class="rpg-inline-form" id="rpg-add-quest-form-main" style="display: ${isFormOpen ? 'flex' : 'none'};">
|
${hasQuest ? `
|
||||||
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="${i18n.getTranslation('quests.main.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.main.addQuestPlaceholder" />
|
<div class="rpg-inline-form" id="rpg-edit-quest-form-main" style="display: none;">
|
||||||
<div class="rpg-inline-buttons">
|
<input type="text" class="rpg-inline-input" id="rpg-edit-quest-main" value="${escapeHtml(questDisplay)}" />
|
||||||
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="main">
|
<div class="rpg-inline-buttons">
|
||||||
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
|
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-edit-quest" data-field="main">
|
||||||
</button>
|
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
|
||||||
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="main">
|
</button>
|
||||||
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
<button class="rpg-inline-btn rpg-inline-save" data-action="save-edit-quest" data-field="main">
|
||||||
</button>
|
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.save">${i18n.getTranslation('global.save')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="rpg-quest-item" data-field="main">
|
||||||
<div class="rpg-item-list rpg-item-list-view">
|
<div class="rpg-quest-title">${escapeHtml(questDisplay)}</div>
|
||||||
${itemsHtml || `<div class="rpg-inventory-empty" data-i18n-key="quests.main.empty">${i18n.getTranslation('quests.main.empty')}</div>`}
|
<div class="rpg-quest-actions">
|
||||||
</div>
|
<button class="rpg-quest-edit" data-action="edit-quest" data-field="main" title="Edit quest">
|
||||||
|
<i class="fa-solid fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="rpg-quest-remove" data-action="remove-quest" data-field="main" title="Complete/Remove quest">
|
||||||
|
<i class="fa-solid fa-check"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : `
|
||||||
|
<div class="rpg-inline-form" id="rpg-add-quest-form-main" style="display: none;">
|
||||||
|
<input type="text" class="rpg-inline-input" id="rpg-new-quest-main" placeholder="${i18n.getTranslation('quests.main.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.main.addQuestPlaceholder" />
|
||||||
|
<div class="rpg-inline-actions">
|
||||||
|
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="main">
|
||||||
|
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
|
||||||
|
</button>
|
||||||
|
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-quest" data-field="main">
|
||||||
|
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rpg-quest-empty" data-i18n-key="quests.main.empty">${i18n.getTranslation('quests.main.empty')}</div>
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
<div class="rpg-quest-hint">
|
||||||
|
<i class="fa-solid fa-lightbulb"></i>
|
||||||
|
<span data-i18n-key="quests.main.hint">${i18n.getTranslation('quests.main.hint')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the optional quests view (matches items/skills structure)
|
* Renders the optional quests view
|
||||||
|
* @param {string[]} optionalQuests - Array of optional quest titles
|
||||||
* @returns {string} HTML for optional quests view
|
* @returns {string} HTML for optional quests view
|
||||||
*/
|
*/
|
||||||
export function renderOptionalQuestsView() {
|
export function renderOptionalQuestsView(optionalQuests) {
|
||||||
const quests = getOptionalQuests().filter(q => q && q.name && q.name !== 'None');
|
const quests = optionalQuests.filter(q => q && q !== 'None');
|
||||||
|
|
||||||
// Track if add form is open
|
let questsHtml = '';
|
||||||
const isFormOpen = openAddForms?.optional || false;
|
|
||||||
|
|
||||||
let itemsHtml = '';
|
|
||||||
if (quests.length === 0) {
|
if (quests.length === 0) {
|
||||||
itemsHtml = `<div class="rpg-inventory-empty" data-i18n-key="quests.optional.empty">${i18n.getTranslation('quests.optional.empty')}</div>`;
|
questsHtml = `<div class="rpg-quest-empty" data-i18n-key="quests.optional.empty">${i18n.getTranslation('quests.optional.empty')}</div>`;
|
||||||
} else {
|
} else {
|
||||||
// Render quests as items (list view style, matching items/skills)
|
questsHtml = quests.map((quest, index) => `
|
||||||
itemsHtml = quests.map((quest, index) => `
|
<div class="rpg-quest-item" data-field="optional" data-index="${index}">
|
||||||
<div class="rpg-item-row" data-field="optional" data-index="${index}">
|
<div class="rpg-quest-title rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" title="Click to edit">${escapeHtml(quest)}</div>
|
||||||
<div class="rpg-item-main-row">
|
<div class="rpg-quest-actions">
|
||||||
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" data-prop="name" title="Click to edit">${escapeHtml(quest.name)}</span>
|
<button class="rpg-quest-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="Complete/Remove quest">
|
||||||
<button class="rpg-item-remove" data-action="remove-quest" data-field="optional" data-index="${index}" title="Complete/Remove quest">
|
<i class="fa-solid fa-check"></i>
|
||||||
<i class="fa-solid fa-times"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-item-desc-row">
|
|
||||||
<span class="rpg-item-description rpg-editable" contenteditable="true" data-field="optional" data-index="${index}" data-prop="description" title="Click to edit description">${escapeHtml(quest.description || '')}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
}
|
}
|
||||||
@@ -149,12 +128,12 @@ export function renderOptionalQuestsView() {
|
|||||||
<div class="rpg-quest-section">
|
<div class="rpg-quest-section">
|
||||||
<div class="rpg-quest-header">
|
<div class="rpg-quest-header">
|
||||||
<h3 class="rpg-quest-section-title" data-i18n-key="quests.optional.title">${i18n.getTranslation('quests.optional.title')}</h3>
|
<h3 class="rpg-quest-section-title" data-i18n-key="quests.optional.title">${i18n.getTranslation('quests.optional.title')}</h3>
|
||||||
<button class="rpg-inventory-add-btn" data-action="add-quest" data-field="optional" title="${i18n.getTranslation('quests.optional.addQuestTitle')}">
|
<button class="rpg-add-quest-btn" data-action="add-quest" data-field="optional" title="${i18n.getTranslation('quests.optional.addQuestTitle')}">
|
||||||
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
<i class="fa-solid fa-plus"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-quest-content">
|
<div class="rpg-quest-content">
|
||||||
<div class="rpg-inline-form" id="rpg-add-quest-form-optional" style="display: ${isFormOpen ? 'flex' : 'none'};">
|
<div class="rpg-inline-form" id="rpg-add-quest-form-optional" style="display: none;">
|
||||||
<input type="text" class="rpg-inline-input" id="rpg-new-quest-optional" placeholder="${i18n.getTranslation('quests.optional.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.optional.addQuestPlaceholder" />
|
<input type="text" class="rpg-inline-input" id="rpg-new-quest-optional" placeholder="${i18n.getTranslation('quests.optional.addQuestPlaceholder')}" data-i18n-placeholder-key="quests.optional.addQuestPlaceholder" />
|
||||||
<div class="rpg-inline-buttons">
|
<div class="rpg-inline-buttons">
|
||||||
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="optional">
|
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-quest" data-field="optional">
|
||||||
@@ -165,28 +144,33 @@ export function renderOptionalQuestsView() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-item-list rpg-item-list-view">
|
<div class="rpg-quest-list">
|
||||||
${itemsHtml}
|
${questsHtml}
|
||||||
|
</div>
|
||||||
|
<div class="rpg-quest-hint">
|
||||||
|
<i class="fa-solid fa-info-circle"></i>
|
||||||
|
<span data-i18n-key="quests.optional.hint">${i18n.getTranslation('quests.optional.hint')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track open add forms (matching items/skills pattern)
|
|
||||||
let openAddForms = {};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main render function for quests
|
* Main render function for quests
|
||||||
*/
|
*/
|
||||||
export function renderQuests() {
|
export function renderQuests() {
|
||||||
if (!extensionSettings.showQuests || !$questsContainer) {
|
if (!extensionSettings.showInventory || !$questsContainer) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current sub-tab from container or default to 'main'
|
// Get current sub-tab from container or default to 'main'
|
||||||
const activeSubTab = $questsContainer.data('active-subtab') || 'main';
|
const activeSubTab = $questsContainer.data('active-subtab') || 'main';
|
||||||
|
|
||||||
|
// Get quests data
|
||||||
|
const mainQuest = extensionSettings.quests.main || 'None';
|
||||||
|
const optionalQuests = extensionSettings.quests.optional || [];
|
||||||
|
|
||||||
// Build HTML
|
// Build HTML
|
||||||
let html = '<div class="rpg-quests-wrapper">';
|
let html = '<div class="rpg-quests-wrapper">';
|
||||||
html += renderQuestsSubTabs(activeSubTab);
|
html += renderQuestsSubTabs(activeSubTab);
|
||||||
@@ -194,9 +178,9 @@ export function renderQuests() {
|
|||||||
// Render active sub-tab
|
// Render active sub-tab
|
||||||
html += '<div class="rpg-quests-panels">';
|
html += '<div class="rpg-quests-panels">';
|
||||||
if (activeSubTab === 'main') {
|
if (activeSubTab === 'main') {
|
||||||
html += renderMainQuestView();
|
html += renderMainQuestView(mainQuest);
|
||||||
} else {
|
} else {
|
||||||
html += renderOptionalQuestsView();
|
html += renderOptionalQuestsView(optionalQuests);
|
||||||
}
|
}
|
||||||
html += '</div></div>';
|
html += '</div></div>';
|
||||||
|
|
||||||
@@ -207,118 +191,117 @@ export function renderQuests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attach event handlers for quest interactions (matching items/skills pattern)
|
* Attach event handlers for quest interactions
|
||||||
*/
|
*/
|
||||||
function attachQuestEventHandlers() {
|
function attachQuestEventHandlers() {
|
||||||
// Sub-tab switching
|
// Sub-tab switching
|
||||||
$questsContainer.find('.rpg-quests-subtab').off('click').on('click', function() {
|
$questsContainer.find('.rpg-quests-subtab').on('click', function() {
|
||||||
const tab = $(this).data('tab');
|
const tab = $(this).data('tab');
|
||||||
$questsContainer.data('active-subtab', tab);
|
$questsContainer.data('active-subtab', tab);
|
||||||
renderQuests();
|
renderQuests();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add quest button
|
// Add quest button
|
||||||
$questsContainer.find('[data-action="add-quest"]').off('click').on('click', function() {
|
$questsContainer.find('[data-action="add-quest"]').on('click', function() {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
openAddForms[field] = true;
|
$(`#rpg-add-quest-form-${field}`).show();
|
||||||
renderQuests();
|
$(`#rpg-new-quest-${field}`).focus();
|
||||||
setTimeout(() => {
|
|
||||||
$(`#rpg-new-quest-${field}`).focus();
|
|
||||||
}, 50);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cancel add quest
|
// Cancel add quest
|
||||||
$questsContainer.find('[data-action="cancel-add-quest"]').off('click').on('click', function() {
|
$questsContainer.find('[data-action="cancel-add-quest"]').on('click', function() {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
openAddForms[field] = false;
|
$(`#rpg-add-quest-form-${field}`).hide();
|
||||||
$(`#rpg-new-quest-${field}`).val('');
|
$(`#rpg-new-quest-${field}`).val('');
|
||||||
renderQuests();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Save add quest
|
// Save add quest
|
||||||
$questsContainer.find('[data-action="save-add-quest"]').off('click').on('click', function() {
|
$questsContainer.find('[data-action="save-add-quest"]').on('click', function() {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const nameInput = $(`#rpg-new-quest-${field}`);
|
const input = $(`#rpg-new-quest-${field}`);
|
||||||
const questTitle = nameInput.val().trim();
|
const questTitle = input.val().trim();
|
||||||
|
|
||||||
if (questTitle) {
|
if (questTitle) {
|
||||||
// Ensure structured format exists
|
|
||||||
if (!extensionSettings.questsV2) {
|
|
||||||
extensionSettings.questsV2 = { main: null, optional: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (field === 'main') {
|
if (field === 'main') {
|
||||||
extensionSettings.questsV2.main = { name: questTitle, description: '' };
|
extensionSettings.quests.main = questTitle;
|
||||||
} else {
|
} else {
|
||||||
if (!extensionSettings.questsV2.optional) {
|
if (!extensionSettings.quests.optional) {
|
||||||
extensionSettings.questsV2.optional = [];
|
extensionSettings.quests.optional = [];
|
||||||
}
|
}
|
||||||
extensionSettings.questsV2.optional.push({ name: questTitle, description: '' });
|
extensionSettings.quests.optional.push(questTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddForms[field] = false;
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
renderQuests();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Edit quest (main only)
|
||||||
|
$questsContainer.find('[data-action="edit-quest"]').on('click', function() {
|
||||||
|
const field = $(this).data('field');
|
||||||
|
$(`#rpg-edit-quest-form-${field}`).show();
|
||||||
|
$('.rpg-quest-item[data-field="main"]').hide();
|
||||||
|
$(`#rpg-edit-quest-${field}`).focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cancel edit quest
|
||||||
|
$questsContainer.find('[data-action="cancel-edit-quest"]').on('click', function() {
|
||||||
|
const field = $(this).data('field');
|
||||||
|
$(`#rpg-edit-quest-form-${field}`).hide();
|
||||||
|
$('.rpg-quest-item[data-field="main"]').show();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save edit quest
|
||||||
|
$questsContainer.find('[data-action="save-edit-quest"]').on('click', function() {
|
||||||
|
const field = $(this).data('field');
|
||||||
|
const input = $(`#rpg-edit-quest-${field}`);
|
||||||
|
const questTitle = input.val().trim();
|
||||||
|
|
||||||
|
if (questTitle) {
|
||||||
|
extensionSettings.quests.main = questTitle;
|
||||||
|
saveSettings();
|
||||||
renderQuests();
|
renderQuests();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove quest
|
// Remove quest
|
||||||
$questsContainer.find('[data-action="remove-quest"]').off('click').on('click', function() {
|
$questsContainer.find('[data-action="remove-quest"]').on('click', function() {
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const index = $(this).data('index');
|
const index = $(this).data('index');
|
||||||
|
|
||||||
if (field === 'main') {
|
if (field === 'main') {
|
||||||
if (extensionSettings.questsV2) {
|
extensionSettings.quests.main = 'None';
|
||||||
extensionSettings.questsV2.main = null;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (extensionSettings.questsV2?.optional) {
|
extensionSettings.quests.optional.splice(index, 1);
|
||||||
extensionSettings.questsV2.optional.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
saveSettings();
|
saveSettings();
|
||||||
saveChatData();
|
|
||||||
renderQuests();
|
renderQuests();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Inline editing for quests (name and description) - matching items/skills pattern
|
// Inline editing for optional quests
|
||||||
$questsContainer.off('blur', '.rpg-item-name.rpg-editable, .rpg-item-description.rpg-editable')
|
$questsContainer.find('.rpg-quest-title.rpg-editable').on('blur', function() {
|
||||||
.on('blur', '.rpg-item-name.rpg-editable, .rpg-item-description.rpg-editable', function() {
|
|
||||||
const $this = $(this);
|
const $this = $(this);
|
||||||
const field = $this.data('field');
|
const field = $this.data('field');
|
||||||
const index = $this.data('index');
|
const index = $this.data('index');
|
||||||
const prop = $this.data('prop') || 'name';
|
const newTitle = $this.text().trim();
|
||||||
const newValue = $this.text().trim();
|
|
||||||
|
|
||||||
// Ensure structured format exists
|
if (newTitle && field === 'optional' && index !== undefined) {
|
||||||
if (!extensionSettings.questsV2) {
|
extensionSettings.quests.optional[index] = newTitle;
|
||||||
extensionSettings.questsV2 = { main: null, optional: [] };
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'main') {
|
|
||||||
// Update main quest
|
|
||||||
if (!extensionSettings.questsV2.main) {
|
|
||||||
extensionSettings.questsV2.main = { name: '', description: '' };
|
|
||||||
}
|
|
||||||
extensionSettings.questsV2.main[prop] = newValue;
|
|
||||||
} else if (field === 'optional' && index !== undefined) {
|
|
||||||
// Update optional quest
|
|
||||||
if (!extensionSettings.questsV2.optional[index]) {
|
|
||||||
extensionSettings.questsV2.optional[index] = { name: '', description: '' };
|
|
||||||
}
|
|
||||||
extensionSettings.questsV2.optional[index][prop] = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSettings();
|
|
||||||
saveChatData();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Enter key to save in forms (matching items/skills pattern)
|
// Enter key to save in forms
|
||||||
$questsContainer.find('.rpg-inline-input').off('keypress').on('keypress', function(e) {
|
$questsContainer.find('.rpg-inline-input').on('keypress', function(e) {
|
||||||
if (e.which === 13) {
|
if (e.which === 13) {
|
||||||
const field = $(this).attr('id').replace('rpg-new-quest-', '');
|
const field = $(this).attr('id').includes('edit') ?
|
||||||
$(`[data-action="save-add-quest"][data-field="${field}"]`).click();
|
$(this).attr('id').replace('rpg-edit-quest-', '') :
|
||||||
|
$(this).attr('id').replace('rpg-new-quest-', '');
|
||||||
|
|
||||||
|
if ($(this).attr('id').includes('edit')) {
|
||||||
|
$(`[data-action="save-edit-quest"][data-field="${field}"]`).click();
|
||||||
|
} else {
|
||||||
|
$(`[data-action="save-add-quest"][data-field="${field}"]`).click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -101,6 +101,11 @@ function namesMatch(cardName, aiName) {
|
|||||||
return wordBoundary.test(aiCore);
|
return wordBoundary.test(aiCore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders character thoughts (Present Characters) panel.
|
||||||
|
* Displays character cards with avatars, relationship badges, and traits.
|
||||||
|
* Includes event listeners for editable character fields.
|
||||||
|
*/
|
||||||
export function renderThoughts() {
|
export function renderThoughts() {
|
||||||
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
|
if (!extensionSettings.showCharacterThoughts || !$thoughtsContainer) {
|
||||||
return;
|
return;
|
||||||
@@ -123,48 +128,8 @@ export function renderThoughts() {
|
|||||||
const relationshipFields = config?.relationshipFields || [];
|
const relationshipFields = config?.relationshipFields || [];
|
||||||
const hasRelationshipEnabled = relationshipFields.length > 0;
|
const hasRelationshipEnabled = relationshipFields.length > 0;
|
||||||
|
|
||||||
// Convert structured character data to text format for the original fancy renderer
|
// Use committedTrackerData as fallback if lastGeneratedData is empty (e.g., after page refresh)
|
||||||
// Use nullish coalescing so an empty string from the latest response clears UI
|
const characterThoughtsData = lastGeneratedData.characterThoughts || committedTrackerData.characterThoughts || '';
|
||||||
let characterThoughtsData = lastGeneratedData.characterThoughts ?? committedTrackerData.characterThoughts ?? '';
|
|
||||||
|
|
||||||
// If we have structured data, convert it to text format
|
|
||||||
if (extensionSettings.charactersData && Array.isArray(extensionSettings.charactersData) && extensionSettings.charactersData.length > 0) {
|
|
||||||
const lines = [];
|
|
||||||
for (const char of extensionSettings.charactersData) {
|
|
||||||
// Character name line
|
|
||||||
lines.push(`- ${char.name || 'Unknown'}`);
|
|
||||||
|
|
||||||
// Details line with emoji and fields
|
|
||||||
const details = [char.emoji || '😶'];
|
|
||||||
const charFields = char.fields || {};
|
|
||||||
for (const [key, value] of Object.entries(charFields)) {
|
|
||||||
if (value) details.push(`${key}: ${value}`);
|
|
||||||
}
|
|
||||||
lines.push(`Details: ${details.join(' | ')}`);
|
|
||||||
|
|
||||||
// Relationship line
|
|
||||||
if (char.relationship) {
|
|
||||||
lines.push(`Relationship: ${char.relationship}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats line
|
|
||||||
const charStats = char.stats || {};
|
|
||||||
if (Object.keys(charStats).length > 0) {
|
|
||||||
const statsStr = Object.entries(charStats).map(([k, v]) => `${k}: ${v}%`).join(' | ');
|
|
||||||
lines.push(`Stats: ${statsStr}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thoughts line
|
|
||||||
if (char.thoughts) {
|
|
||||||
const thoughtsFieldName = config?.thoughts?.name || 'Thoughts';
|
|
||||||
lines.push(`${thoughtsFieldName}: ${char.thoughts}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lines.length > 0) {
|
|
||||||
characterThoughtsData = lines.join('\n');
|
|
||||||
debugLog('[RPG Thoughts] Converted structured data to text format');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog('[RPG Thoughts] Raw characterThoughts data:', characterThoughtsData);
|
debugLog('[RPG Thoughts] Raw characterThoughts data:', characterThoughtsData);
|
||||||
debugLog('[RPG Thoughts] Data length:', characterThoughtsData.length + ' chars');
|
debugLog('[RPG Thoughts] Data length:', characterThoughtsData.length + ' chars');
|
||||||
@@ -411,16 +376,14 @@ export function renderThoughts() {
|
|||||||
debugLog(`[RPG Thoughts] Final avatar for ${char.name}:`, characterPortrait.substring(0, 50) + '...');
|
debugLog(`[RPG Thoughts] Final avatar for ${char.name}:`, characterPortrait.substring(0, 50) + '...');
|
||||||
|
|
||||||
// Get relationship badge - only if relationships are enabled in config
|
// Get relationship badge - only if relationships are enabled in config
|
||||||
let relationshipBadge = '⚖️'; // Default emoji
|
let relationshipBadge = '⚖️'; // Default
|
||||||
let relationshipText = 'Neutral'; // Default text for tooltip
|
|
||||||
let relationshipFieldName = 'Relationship';
|
let relationshipFieldName = 'Relationship';
|
||||||
|
|
||||||
if (hasRelationshipEnabled) {
|
if (hasRelationshipEnabled) {
|
||||||
// In the new format, relationship is always stored in char.Relationship
|
// In the new format, relationship is always stored in char.Relationship
|
||||||
if (char.Relationship) {
|
if (char.Relationship) {
|
||||||
relationshipText = char.Relationship;
|
// Try to map text to emoji
|
||||||
// Try to map text to emoji, fall back to default link emoji for unknown types
|
relationshipBadge = relationshipEmojis[char.Relationship] || char.Relationship;
|
||||||
relationshipBadge = relationshipEmojis[char.Relationship] || '⚖️';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,14 +396,13 @@ export function renderThoughts() {
|
|||||||
<div class="rpg-character-card" data-character-name="${escapedName}">
|
<div class="rpg-character-card" data-character-name="${escapedName}">
|
||||||
<div class="rpg-character-avatar">
|
<div class="rpg-character-avatar">
|
||||||
<img src="${characterPortrait}" alt="${escapedName}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
<img src="${characterPortrait}" alt="${escapedName}" onerror="this.style.opacity='0.5';this.onerror=null;" />
|
||||||
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="${relationshipFieldName}" title="${escapeHtmlAttr(relationshipText)}">${relationshipBadge}</div>` : ''}
|
${hasRelationshipEnabled ? `<div class="rpg-relationship-badge rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="${relationshipFieldName}" title="Click to edit (use emoji: ⚔️ ⚖️ ⭐ ❤️)">${relationshipBadge}</div>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="rpg-character-content">
|
<div class="rpg-character-content">
|
||||||
<div class="rpg-character-info">
|
<div class="rpg-character-info">
|
||||||
<div class="rpg-character-header">
|
<div class="rpg-character-header">
|
||||||
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
<span class="rpg-character-emoji rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="emoji" title="Click to edit emoji">${char.emoji}</span>
|
||||||
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="name" title="Click to edit name">${char.name}</span>
|
<span class="rpg-character-name rpg-editable" contenteditable="true" data-character="${escapedName}" data-field="name" title="Click to edit name">${char.name}</span>
|
||||||
<button class="rpg-character-remove" data-character="${escapedName}" title="Remove character">×</button>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -504,15 +466,6 @@ export function renderThoughts() {
|
|||||||
updateCharacterField(character, field, value);
|
updateCharacterField(character, field, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event handlers for remove character buttons
|
|
||||||
$thoughtsContainer.find('.rpg-character-remove').on('click', function(e) {
|
|
||||||
e.stopPropagation();
|
|
||||||
const characterName = $(this).data('character');
|
|
||||||
if (characterName && confirm(`Remove ${characterName} from present characters?`)) {
|
|
||||||
removeCharacter(characterName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove updating class after animation
|
// Remove updating class after animation
|
||||||
if (extensionSettings.enableAnimations) {
|
if (extensionSettings.enableAnimations) {
|
||||||
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
|
setTimeout(() => $thoughtsContainer.removeClass('rpg-content-updating'), 600);
|
||||||
@@ -752,114 +705,17 @@ export function updateCharacterField(characterName, field, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a character from Present Characters data and re-renders.
|
|
||||||
* Works with both structured (charactersData) and text (characterThoughts) formats.
|
|
||||||
*
|
|
||||||
* @param {string} characterName - Name of the character to remove
|
|
||||||
*/
|
|
||||||
export function removeCharacter(characterName) {
|
|
||||||
console.log('[RPG Companion] Removing character:', characterName);
|
|
||||||
|
|
||||||
// Remove from structured data if it exists
|
|
||||||
if (extensionSettings.charactersData && Array.isArray(extensionSettings.charactersData)) {
|
|
||||||
const initialLength = extensionSettings.charactersData.length;
|
|
||||||
extensionSettings.charactersData = extensionSettings.charactersData.filter(
|
|
||||||
char => char.name && char.name.toLowerCase() !== characterName.toLowerCase()
|
|
||||||
);
|
|
||||||
if (extensionSettings.charactersData.length < initialLength) {
|
|
||||||
console.log('[RPG Companion] Removed character from structured data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from text format
|
|
||||||
if (!lastGeneratedData.characterThoughts) {
|
|
||||||
console.log('[RPG Companion] No characterThoughts data to remove from');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines = lastGeneratedData.characterThoughts.split('\n');
|
|
||||||
|
|
||||||
let characterFound = false;
|
|
||||||
let inTargetCharacter = false;
|
|
||||||
let characterEndIndex = -1;
|
|
||||||
const linesToRemove = [];
|
|
||||||
|
|
||||||
// Find the character block
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i].trim();
|
|
||||||
|
|
||||||
if (line.startsWith('- ')) {
|
|
||||||
const name = line.substring(2).trim();
|
|
||||||
if (name.toLowerCase() === characterName.toLowerCase()) {
|
|
||||||
characterFound = true;
|
|
||||||
inTargetCharacter = true;
|
|
||||||
linesToRemove.push(i);
|
|
||||||
} else if (inTargetCharacter) {
|
|
||||||
characterEndIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (inTargetCharacter) {
|
|
||||||
// Include all lines until the next character or end of file
|
|
||||||
linesToRemove.push(i);
|
|
||||||
// Check if this is a character name line (next character)
|
|
||||||
if (line.startsWith('- ')) {
|
|
||||||
characterEndIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characterFound && characterEndIndex === -1) {
|
|
||||||
characterEndIndex = lines.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (characterFound && linesToRemove.length > 0) {
|
|
||||||
// Remove lines in reverse order to maintain indices
|
|
||||||
for (let i = linesToRemove.length - 1; i >= 0; i--) {
|
|
||||||
lines.splice(linesToRemove[i], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any trailing empty lines after removal
|
|
||||||
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
|
||||||
lines.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
lastGeneratedData.characterThoughts = lines.join('\n');
|
|
||||||
committedTrackerData.characterThoughts = lines.join('\n');
|
|
||||||
|
|
||||||
console.log('[RPG Companion] Removed character from text format');
|
|
||||||
|
|
||||||
// Update chat swipe data
|
|
||||||
const chat = getContext().chat;
|
|
||||||
if (chat && chat.length > 0) {
|
|
||||||
for (let i = chat.length - 1; i >= 0; i--) {
|
|
||||||
const message = chat[i];
|
|
||||||
if (!message.is_user) {
|
|
||||||
if (message.extra && message.extra.rpg_companion_swipes) {
|
|
||||||
const swipeId = message.swipe_id || 0;
|
|
||||||
if (message.extra.rpg_companion_swipes[swipeId]) {
|
|
||||||
message.extra.rpg_companion_swipes[swipeId].characterThoughts = lines.join('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
saveChatData();
|
|
||||||
renderThoughts();
|
|
||||||
updateChatThoughts();
|
|
||||||
} else {
|
|
||||||
console.log('[RPG Companion] Character not found in text format:', characterName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates or removes thought overlays in the chat.
|
* Updates or removes thought overlays in the chat.
|
||||||
* Creates floating thought bubbles positioned near character avatars.
|
* Creates floating thought bubbles positioned near character avatars.
|
||||||
*/
|
*/
|
||||||
export function updateChatThoughts() {
|
export function updateChatThoughts() {
|
||||||
|
// console.log('[RPG Companion] ======== updateChatThoughts called ========');
|
||||||
|
// console.log('[RPG Companion] Extension enabled:', extensionSettings.enabled);
|
||||||
|
// console.log('[RPG Companion] showThoughtsInChat setting:', extensionSettings.showThoughtsInChat);
|
||||||
|
// console.log('[RPG Companion] Toggle element checked:', $('#rpg-toggle-thoughts-in-chat').prop('checked'));
|
||||||
|
// console.log('[RPG Companion] lastGeneratedData.characterThoughts:', lastGeneratedData.characterThoughts);
|
||||||
|
|
||||||
// Remove existing thought panel and icon
|
// Remove existing thought panel and icon
|
||||||
$('#rpg-thought-panel').remove();
|
$('#rpg-thought-panel').remove();
|
||||||
$('#rpg-thought-icon').remove();
|
$('#rpg-thought-icon').remove();
|
||||||
@@ -867,7 +723,9 @@ export function updateChatThoughts() {
|
|||||||
$(window).off('resize.thoughtPanel');
|
$(window).off('resize.thoughtPanel');
|
||||||
$(document).off('click.thoughtPanel');
|
$(document).off('click.thoughtPanel');
|
||||||
|
|
||||||
|
// If extension is disabled, thoughts in chat are disabled, or no thoughts, just return
|
||||||
if (!extensionSettings.enabled || !extensionSettings.showThoughtsInChat || !lastGeneratedData.characterThoughts) {
|
if (!extensionSettings.enabled || !extensionSettings.showThoughtsInChat || !lastGeneratedData.characterThoughts) {
|
||||||
|
// console.log('[RPG Companion] Thoughts in chat disabled or no data');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,6 +735,8 @@ export function updateChatThoughts() {
|
|||||||
const thoughtsConfig = extensionSettings.trackerConfig?.presentCharacters?.thoughts;
|
const thoughtsConfig = extensionSettings.trackerConfig?.presentCharacters?.thoughts;
|
||||||
const thoughtsLabel = thoughtsConfig?.name || 'Thoughts';
|
const thoughtsLabel = thoughtsConfig?.name || 'Thoughts';
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Parsing thoughts from lines:', lines);
|
||||||
|
|
||||||
// Parse new format to build character map and thoughts
|
// Parse new format to build character map and thoughts
|
||||||
let currentCharName = null;
|
let currentCharName = null;
|
||||||
let currentCharEmoji = null;
|
let currentCharEmoji = null;
|
||||||
@@ -933,9 +793,13 @@ export function updateChatThoughts() {
|
|||||||
|
|
||||||
// If no thoughts parsed, return
|
// If no thoughts parsed, return
|
||||||
if (thoughtsArray.length === 0) {
|
if (thoughtsArray.length === 0) {
|
||||||
|
// console.log('[RPG Companion] No thoughts parsed, returning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Total thoughts:', thoughtsArray.length);
|
||||||
|
// console.log('[RPG Companion] Thoughts array:', thoughtsArray);
|
||||||
|
|
||||||
// Find the last message to position near
|
// Find the last message to position near
|
||||||
const $messages = $('#chat .mes');
|
const $messages = $('#chat .mes');
|
||||||
let $targetMessage = null;
|
let $targetMessage = null;
|
||||||
@@ -950,6 +814,7 @@ export function updateChatThoughts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$targetMessage) {
|
if (!$targetMessage) {
|
||||||
|
// console.log('[RPG Companion] No target message found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -969,8 +834,10 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
$('#rpg-thought-panel').remove();
|
$('#rpg-thought-panel').remove();
|
||||||
$('#rpg-thought-icon').remove();
|
$('#rpg-thought-icon').remove();
|
||||||
|
|
||||||
|
// Get the avatar position from the message
|
||||||
const $avatar = $message.find('.avatar img');
|
const $avatar = $message.find('.avatar img');
|
||||||
if (!$avatar.length) {
|
if (!$avatar.length) {
|
||||||
|
// console.log('[RPG Companion] No avatar found in message');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1167,10 +1034,14 @@ export function createThoughtPanel($message, thoughtsArray) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log('[RPG Companion] Thought panel created at:', { top, left });
|
||||||
|
|
||||||
|
// Add event handlers for editable thoughts in the bubble
|
||||||
$thoughtPanel.find('.rpg-editable').on('blur', function() {
|
$thoughtPanel.find('.rpg-editable').on('blur', function() {
|
||||||
const character = $(this).data('character');
|
const character = $(this).data('character');
|
||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const value = $(this).text().trim();
|
const value = $(this).text().trim();
|
||||||
|
// console.log('[RPG Companion] 💭 Thought bubble blur event - character:', character, 'field:', field, 'value:', value);
|
||||||
updateCharacterField(character, field, value);
|
updateCharacterField(character, field, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -55,15 +55,13 @@ export function buildUserStatsText() {
|
|||||||
text += `${stats.conditions || 'None'}\n`;
|
text += `${stats.conditions || 'None'}\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add inventory summary only if inventory is enabled
|
// Add inventory summary
|
||||||
if (extensionSettings.showInventory) {
|
|
||||||
const inventorySummary = buildInventorySummary(stats.inventory);
|
const inventorySummary = buildInventorySummary(stats.inventory);
|
||||||
text += inventorySummary;
|
text += inventorySummary;
|
||||||
}
|
|
||||||
|
|
||||||
// Add skills if enabled AND not shown in separate tab
|
// Add skills if enabled
|
||||||
if (config.skillsSection.enabled && !extensionSettings.showSkills) {
|
if (config.skillsSection.enabled && stats.skills) {
|
||||||
text += `\n${config.skillsSection.label}: ${extensionSettings.userStats?.skills || 'None'}`;
|
text += `\n${config.skillsSection.label}: ${stats.skills}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return text.trim();
|
return text.trim();
|
||||||
@@ -82,19 +80,19 @@ export function renderUserStats() {
|
|||||||
const stats = extensionSettings.userStats;
|
const stats = extensionSettings.userStats;
|
||||||
const config = extensionSettings.trackerConfig?.userStats || {
|
const config = extensionSettings.trackerConfig?.userStats || {
|
||||||
customStats: [
|
customStats: [
|
||||||
{ id: 'health', name: 'Health', description: '', enabled: true },
|
{ id: 'health', name: 'Health', enabled: true },
|
||||||
{ id: 'satiety', name: 'Satiety', description: '', enabled: true },
|
{ id: 'satiety', name: 'Satiety', enabled: true },
|
||||||
{ id: 'energy', name: 'Energy', description: '', enabled: true },
|
{ id: 'energy', name: 'Energy', enabled: true },
|
||||||
{ id: 'hygiene', name: 'Hygiene', description: '', enabled: true },
|
{ id: 'hygiene', name: 'Hygiene', enabled: true },
|
||||||
{ id: 'arousal', name: 'Arousal', description: '', enabled: true }
|
{ id: 'arousal', name: 'Arousal', enabled: true }
|
||||||
],
|
],
|
||||||
rpgAttributes: [
|
rpgAttributes: [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
],
|
],
|
||||||
statusSection: { enabled: true, showMoodEmoji: true, customFields: ['Conditions'] },
|
statusSection: { enabled: true, showMoodEmoji: true, customFields: ['Conditions'] },
|
||||||
skillsSection: { enabled: false, label: 'Skills' }
|
skillsSection: { enabled: false, label: 'Skills' }
|
||||||
@@ -167,8 +165,8 @@ export function renderUserStats() {
|
|||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skills section (conditionally rendered) - only if NOT shown in separate tab
|
// Skills section (conditionally rendered)
|
||||||
if (config.skillsSection.enabled && !extensionSettings.showSkills) {
|
if (config.skillsSection.enabled) {
|
||||||
const skillsValue = stats.skills || 'None';
|
const skillsValue = stats.skills || 'None';
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-skills-section">
|
<div class="rpg-skills-section">
|
||||||
@@ -187,12 +185,12 @@ export function renderUserStats() {
|
|||||||
if (showRPGAttributes) {
|
if (showRPGAttributes) {
|
||||||
// Use attributes from config, with fallback to defaults if not configured
|
// Use attributes from config, with fallback to defaults if not configured
|
||||||
const rpgAttributes = (config.rpgAttributes && config.rpgAttributes.length > 0) ? config.rpgAttributes : [
|
const rpgAttributes = (config.rpgAttributes && config.rpgAttributes.length > 0) ? config.rpgAttributes : [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
];
|
];
|
||||||
const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
|
const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
|
||||||
|
|
||||||
@@ -317,19 +315,23 @@ export function renderUserStats() {
|
|||||||
const field = $(this).data('field');
|
const field = $(this).data('field');
|
||||||
const value = $(this).text().trim().replace(':', '');
|
const value = $(this).text().trim().replace(':', '');
|
||||||
|
|
||||||
// Update the stat name in customStats array (new format)
|
if (!extensionSettings.statNames) {
|
||||||
const config = extensionSettings.trackerConfig?.userStats;
|
extensionSettings.statNames = {
|
||||||
if (config && config.customStats) {
|
health: 'Health',
|
||||||
const stat = config.customStats.find(s => s.id === field);
|
satiety: 'Satiety',
|
||||||
if (stat && value) {
|
energy: 'Energy',
|
||||||
stat.name = value;
|
hygiene: 'Hygiene',
|
||||||
saveSettings();
|
arousal: 'Arousal'
|
||||||
saveChatData();
|
};
|
||||||
|
|
||||||
// Re-render to update the display
|
|
||||||
renderUserStats();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extensionSettings.statNames[field] = value || extensionSettings.statNames[field];
|
||||||
|
|
||||||
|
saveSettings();
|
||||||
|
saveChatData();
|
||||||
|
|
||||||
|
// Re-render to update the display
|
||||||
|
renderUserStats();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add event listener for level editing
|
// Add event listener for level editing
|
||||||
|
|||||||
+16
-43
@@ -4,7 +4,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
import { extensionSettings } from '../../core/state.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up desktop tab navigation for organizing content.
|
* Sets up desktop tab navigation for organizing content.
|
||||||
@@ -24,47 +23,34 @@ export function setupDesktopTabs() {
|
|||||||
const $userStats = $('#rpg-user-stats');
|
const $userStats = $('#rpg-user-stats');
|
||||||
const $infoBox = $('#rpg-info-box');
|
const $infoBox = $('#rpg-info-box');
|
||||||
const $thoughts = $('#rpg-thoughts');
|
const $thoughts = $('#rpg-thoughts');
|
||||||
const $skills = $('#rpg-skills');
|
|
||||||
const $inventory = $('#rpg-inventory');
|
const $inventory = $('#rpg-inventory');
|
||||||
const $quests = $('#rpg-quests');
|
const $quests = $('#rpg-quests');
|
||||||
|
|
||||||
// If no sections exist, nothing to organize
|
// If no sections exist, nothing to organize
|
||||||
if ($userStats.length === 0 && $infoBox.length === 0 && $thoughts.length === 0 && $skills.length === 0 && $inventory.length === 0 && $quests.length === 0) {
|
if ($userStats.length === 0 && $infoBox.length === 0 && $thoughts.length === 0 && $inventory.length === 0 && $quests.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create tab navigation - conditionally show skills, inventory and quests tabs based on settings
|
// Create tab navigation
|
||||||
const skillsLabel = extensionSettings.trackerConfig?.userStats?.skillsSection?.label || 'Skills';
|
|
||||||
const skillsTabHtml = extensionSettings.showSkills ? `
|
|
||||||
<button class="rpg-tab-btn" data-tab="skills">
|
|
||||||
<i class="fa-solid fa-star"></i>
|
|
||||||
<span>${skillsLabel}</span>
|
|
||||||
</button>` : '';
|
|
||||||
|
|
||||||
const inventoryTabHtml = extensionSettings.showInventory ? `
|
|
||||||
<button class="rpg-tab-btn" data-tab="inventory">
|
|
||||||
<i class="fa-solid fa-box"></i>
|
|
||||||
<span data-i18n-key="global.inventory">Inventory</span>
|
|
||||||
</button>` : '';
|
|
||||||
|
|
||||||
const questsTabHtml = extensionSettings.showQuests ? `
|
|
||||||
<button class="rpg-tab-btn" data-tab="quests">
|
|
||||||
<i class="fa-solid fa-scroll"></i>
|
|
||||||
<span data-i18n-key="global.quests">Quests</span>
|
|
||||||
</button>` : '';
|
|
||||||
|
|
||||||
const $tabNav = $(`
|
const $tabNav = $(`
|
||||||
<div class="rpg-tabs-nav">
|
<div class="rpg-tabs-nav">
|
||||||
<button class="rpg-tab-btn active" data-tab="status">
|
<button class="rpg-tab-btn active" data-tab="status">
|
||||||
<i class="fa-solid fa-chart-simple"></i>
|
<i class="fa-solid fa-chart-simple"></i>
|
||||||
<span data-i18n-key="global.status">Status</span>
|
<span data-i18n-key="global.status">Status</span>
|
||||||
</button>${skillsTabHtml}${inventoryTabHtml}${questsTabHtml}
|
</button>
|
||||||
|
<button class="rpg-tab-btn" data-tab="inventory">
|
||||||
|
<i class="fa-solid fa-box"></i>
|
||||||
|
<span data-i18n-key="global.inventory">Inventory</span>
|
||||||
|
</button>
|
||||||
|
<button class="rpg-tab-btn" data-tab="quests">
|
||||||
|
<i class="fa-solid fa-scroll"></i>
|
||||||
|
<span data-i18n-key="global.quests">Quests</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// Create tab content containers
|
// Create tab content containers
|
||||||
const $statusTab = $('<div class="rpg-tab-content active" data-tab-content="status"></div>');
|
const $statusTab = $('<div class="rpg-tab-content active" data-tab-content="status"></div>');
|
||||||
const $skillsTab = $('<div class="rpg-tab-content" data-tab-content="skills"></div>');
|
|
||||||
const $inventoryTab = $('<div class="rpg-tab-content" data-tab-content="inventory"></div>');
|
const $inventoryTab = $('<div class="rpg-tab-content" data-tab-content="inventory"></div>');
|
||||||
const $questsTab = $('<div class="rpg-tab-content" data-tab-content="quests"></div>');
|
const $questsTab = $('<div class="rpg-tab-content" data-tab-content="quests"></div>');
|
||||||
|
|
||||||
@@ -81,15 +67,11 @@ export function setupDesktopTabs() {
|
|||||||
$statusTab.append($thoughts.detach());
|
$statusTab.append($thoughts.detach());
|
||||||
$thoughts.show();
|
$thoughts.show();
|
||||||
}
|
}
|
||||||
if (extensionSettings.showSkills && $skills.length > 0) {
|
if ($inventory.length > 0) {
|
||||||
$skillsTab.append($skills.detach());
|
|
||||||
$skills.show();
|
|
||||||
}
|
|
||||||
if (extensionSettings.showInventory && $inventory.length > 0) {
|
|
||||||
$inventoryTab.append($inventory.detach());
|
$inventoryTab.append($inventory.detach());
|
||||||
$inventory.show();
|
$inventory.show();
|
||||||
}
|
}
|
||||||
if (extensionSettings.showQuests && $quests.length > 0) {
|
if ($quests.length > 0) {
|
||||||
$questsTab.append($quests.detach());
|
$questsTab.append($quests.detach());
|
||||||
$quests.show();
|
$quests.show();
|
||||||
}
|
}
|
||||||
@@ -101,7 +83,6 @@ export function setupDesktopTabs() {
|
|||||||
const $tabsContainer = $('<div class="rpg-tabs-container"></div>');
|
const $tabsContainer = $('<div class="rpg-tabs-container"></div>');
|
||||||
$tabsContainer.append($tabNav);
|
$tabsContainer.append($tabNav);
|
||||||
$tabsContainer.append($statusTab);
|
$tabsContainer.append($statusTab);
|
||||||
$tabsContainer.append($skillsTab);
|
|
||||||
$tabsContainer.append($inventoryTab);
|
$tabsContainer.append($inventoryTab);
|
||||||
$tabsContainer.append($questsTab);
|
$tabsContainer.append($questsTab);
|
||||||
|
|
||||||
@@ -134,38 +115,32 @@ export function removeDesktopTabs() {
|
|||||||
const $userStats = $('#rpg-user-stats').detach();
|
const $userStats = $('#rpg-user-stats').detach();
|
||||||
const $infoBox = $('#rpg-info-box').detach();
|
const $infoBox = $('#rpg-info-box').detach();
|
||||||
const $thoughts = $('#rpg-thoughts').detach();
|
const $thoughts = $('#rpg-thoughts').detach();
|
||||||
const $skills = $('#rpg-skills').detach();
|
|
||||||
const $inventory = $('#rpg-inventory').detach();
|
const $inventory = $('#rpg-inventory').detach();
|
||||||
const $quests = $('#rpg-quests').detach();
|
const $quests = $('#rpg-quests').detach();
|
||||||
|
|
||||||
// Remove tabs container
|
// Remove tabs container
|
||||||
$('.rpg-tabs-container').remove();
|
$('.rpg-tabs-container').remove();
|
||||||
|
|
||||||
// Get dividers (all 5 dividers in the template)
|
// Get dividers
|
||||||
const $dividerStats = $('#rpg-divider-stats');
|
const $dividerStats = $('#rpg-divider-stats');
|
||||||
const $dividerInfo = $('#rpg-divider-info');
|
const $dividerInfo = $('#rpg-divider-info');
|
||||||
const $dividerThoughts = $('#rpg-divider-thoughts');
|
const $dividerThoughts = $('#rpg-divider-thoughts');
|
||||||
const $dividerSkills = $('#rpg-divider-skills');
|
|
||||||
const $dividerInventory = $('#rpg-divider-inventory');
|
|
||||||
|
|
||||||
// Restore original sections to content box in correct order
|
// Restore original sections to content box in correct order
|
||||||
const $contentBox = $('.rpg-content-box');
|
const $contentBox = $('.rpg-content-box');
|
||||||
|
|
||||||
// Re-insert sections in original order: User Stats, Info Box, Thoughts, Skills, Inventory, Quests
|
// Re-insert sections in original order: User Stats, Info Box, Thoughts, Inventory, Quests
|
||||||
// Each section goes before its corresponding divider
|
|
||||||
if ($dividerStats.length) {
|
if ($dividerStats.length) {
|
||||||
$dividerStats.before($userStats);
|
$dividerStats.before($userStats);
|
||||||
$dividerInfo.before($infoBox);
|
$dividerInfo.before($infoBox);
|
||||||
$dividerThoughts.before($thoughts);
|
$dividerThoughts.before($thoughts);
|
||||||
$dividerSkills.before($skills);
|
$contentBox.append($inventory);
|
||||||
$dividerInventory.before($inventory);
|
|
||||||
$contentBox.append($quests);
|
$contentBox.append($quests);
|
||||||
} else {
|
} else {
|
||||||
// Fallback if dividers don't exist
|
// Fallback if dividers don't exist
|
||||||
$contentBox.append($userStats);
|
$contentBox.append($userStats);
|
||||||
$contentBox.append($infoBox);
|
$contentBox.append($infoBox);
|
||||||
$contentBox.append($thoughts);
|
$contentBox.append($thoughts);
|
||||||
$contentBox.append($skills);
|
|
||||||
$contentBox.append($inventory);
|
$contentBox.append($inventory);
|
||||||
$contentBox.append($quests);
|
$contentBox.append($quests);
|
||||||
}
|
}
|
||||||
@@ -174,9 +149,7 @@ export function removeDesktopTabs() {
|
|||||||
$userStats.show();
|
$userStats.show();
|
||||||
$infoBox.show();
|
$infoBox.show();
|
||||||
$thoughts.show();
|
$thoughts.show();
|
||||||
$skills.show();
|
|
||||||
$inventory.show();
|
$inventory.show();
|
||||||
$quests.show();
|
|
||||||
$('.rpg-divider').show();
|
$('.rpg-divider').show();
|
||||||
|
|
||||||
console.log('[RPG Desktop] Desktop tabs removed');
|
console.log('[RPG Desktop] Desktop tabs removed');
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import {
|
|||||||
$userStatsContainer,
|
$userStatsContainer,
|
||||||
$infoBoxContainer,
|
$infoBoxContainer,
|
||||||
$thoughtsContainer,
|
$thoughtsContainer,
|
||||||
$skillsContainer,
|
$inventoryContainer
|
||||||
$inventoryContainer,
|
|
||||||
$questsContainer
|
|
||||||
} from '../../core/state.js';
|
} from '../../core/state.js';
|
||||||
import { i18n } from '../../core/i18n.js';
|
import { i18n } from '../../core/i18n.js';
|
||||||
|
|
||||||
@@ -229,41 +227,25 @@ export function updateSectionVisibility() {
|
|||||||
$userStatsContainer.toggle(extensionSettings.showUserStats);
|
$userStatsContainer.toggle(extensionSettings.showUserStats);
|
||||||
$infoBoxContainer.toggle(extensionSettings.showInfoBox);
|
$infoBoxContainer.toggle(extensionSettings.showInfoBox);
|
||||||
$thoughtsContainer.toggle(extensionSettings.showCharacterThoughts);
|
$thoughtsContainer.toggle(extensionSettings.showCharacterThoughts);
|
||||||
if ($skillsContainer) {
|
|
||||||
$skillsContainer.toggle(extensionSettings.showSkills);
|
|
||||||
}
|
|
||||||
if ($inventoryContainer) {
|
if ($inventoryContainer) {
|
||||||
$inventoryContainer.toggle(extensionSettings.showInventory);
|
$inventoryContainer.toggle(extensionSettings.showInventory);
|
||||||
}
|
}
|
||||||
if ($questsContainer) {
|
|
||||||
$questsContainer.toggle(extensionSettings.showQuests);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show/hide dividers intelligently
|
// Show/hide dividers intelligently
|
||||||
// Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible
|
// Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible
|
||||||
const showDividerAfterStats = extensionSettings.showUserStats &&
|
const showDividerAfterStats = extensionSettings.showUserStats &&
|
||||||
(extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showSkills || extensionSettings.showInventory || extensionSettings.showQuests);
|
(extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory);
|
||||||
$('#rpg-divider-stats').toggle(showDividerAfterStats);
|
$('#rpg-divider-stats').toggle(showDividerAfterStats);
|
||||||
|
|
||||||
// Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible
|
// Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible
|
||||||
const showDividerAfterInfo = extensionSettings.showInfoBox &&
|
const showDividerAfterInfo = extensionSettings.showInfoBox &&
|
||||||
(extensionSettings.showCharacterThoughts || extensionSettings.showSkills || extensionSettings.showInventory || extensionSettings.showQuests);
|
(extensionSettings.showCharacterThoughts || extensionSettings.showInventory);
|
||||||
$('#rpg-divider-info').toggle(showDividerAfterInfo);
|
$('#rpg-divider-info').toggle(showDividerAfterInfo);
|
||||||
|
|
||||||
// Divider after Thoughts: shown if Thoughts is visible AND Skills, Inventory or Quests is visible
|
// Divider after Thoughts: shown if Thoughts is visible AND Inventory is visible
|
||||||
const showDividerAfterThoughts = extensionSettings.showCharacterThoughts &&
|
const showDividerAfterThoughts = extensionSettings.showCharacterThoughts &&
|
||||||
(extensionSettings.showSkills || extensionSettings.showInventory || extensionSettings.showQuests);
|
extensionSettings.showInventory;
|
||||||
$('#rpg-divider-thoughts').toggle(showDividerAfterThoughts);
|
$('#rpg-divider-thoughts').toggle(showDividerAfterThoughts);
|
||||||
|
|
||||||
// Divider after Skills: shown if Skills is visible AND Inventory or Quests is visible
|
|
||||||
const showDividerAfterSkills = extensionSettings.showSkills &&
|
|
||||||
(extensionSettings.showInventory || extensionSettings.showQuests);
|
|
||||||
$('#rpg-divider-skills').toggle(showDividerAfterSkills);
|
|
||||||
|
|
||||||
// Divider after Inventory: shown if Inventory is visible AND Quests is visible
|
|
||||||
const showDividerAfterInventory = extensionSettings.showInventory &&
|
|
||||||
extensionSettings.showQuests;
|
|
||||||
$('#rpg-divider-inventory').toggle(showDividerAfterInventory);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -727,6 +727,7 @@ export function removeMobileTabs() {
|
|||||||
*/
|
*/
|
||||||
export function setupMobileKeyboardHandling() {
|
export function setupMobileKeyboardHandling() {
|
||||||
if (!window.visualViewport) {
|
if (!window.visualViewport) {
|
||||||
|
// console.log('[RPG Mobile] Visual Viewport API not supported');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -749,9 +750,12 @@ export function setupMobileKeyboardHandling() {
|
|||||||
// Keyboard just appeared
|
// Keyboard just appeared
|
||||||
keyboardVisible = true;
|
keyboardVisible = true;
|
||||||
$panel.addClass('rpg-keyboard-visible');
|
$panel.addClass('rpg-keyboard-visible');
|
||||||
|
// console.log('[RPG Mobile] Keyboard opened');
|
||||||
} else if (!isKeyboardShowing && keyboardVisible) {
|
} else if (!isKeyboardShowing && keyboardVisible) {
|
||||||
|
// Keyboard just disappeared
|
||||||
keyboardVisible = false;
|
keyboardVisible = false;
|
||||||
$panel.removeClass('rpg-keyboard-visible');
|
$panel.removeClass('rpg-keyboard-visible');
|
||||||
|
// console.log('[RPG Mobile] Keyboard closed');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import { saveSettings, saveChatData } from '../../core/persistence.js';
|
|||||||
import { renderUserStats } from '../rendering/userStats.js';
|
import { renderUserStats } from '../rendering/userStats.js';
|
||||||
import { updateChatThoughts } from '../rendering/thoughts.js';
|
import { updateChatThoughts } from '../rendering/thoughts.js';
|
||||||
import { renderQuests } from '../rendering/quests.js';
|
import { renderQuests } from '../rendering/quests.js';
|
||||||
import { renderSkills } from '../rendering/skills.js';
|
|
||||||
import {
|
import {
|
||||||
rollDice as rollDiceCore,
|
rollDice as rollDiceCore,
|
||||||
clearDiceRoll as clearDiceRollCore,
|
clearDiceRoll as clearDiceRollCore,
|
||||||
@@ -369,6 +368,7 @@ export function setupSettingsPopup() {
|
|||||||
const message = chat[i];
|
const message = chat[i];
|
||||||
if (message.extra && message.extra.rpg_companion_swipes) {
|
if (message.extra && message.extra.rpg_companion_swipes) {
|
||||||
delete message.extra.rpg_companion_swipes;
|
delete message.extra.rpg_companion_swipes;
|
||||||
|
// console.log('[RPG Companion] Cleared swipe data from message at index', i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,13 +390,7 @@ export function setupSettingsPopup() {
|
|||||||
arousal: 0,
|
arousal: 0,
|
||||||
mood: '😐',
|
mood: '😐',
|
||||||
conditions: 'None',
|
conditions: 'None',
|
||||||
inventory: {
|
inventory: 'None'
|
||||||
version: 2,
|
|
||||||
onPerson: "None",
|
|
||||||
stored: {},
|
|
||||||
assets: "None"
|
|
||||||
},
|
|
||||||
skills: "None" // Legacy single-string skills (for Status section)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset classic stats (attributes) to defaults
|
// Reset classic stats (attributes) to defaults
|
||||||
@@ -418,12 +412,6 @@ export function setupSettingsPopup() {
|
|||||||
optional: []
|
optional: []
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset skills data
|
|
||||||
extensionSettings.skillsV2 = {};
|
|
||||||
extensionSettings.skillsData = {};
|
|
||||||
extensionSettings.skillAbilityLinks = {};
|
|
||||||
extensionSettings.skills = { list: [], categories: {} }; // Legacy skills object
|
|
||||||
|
|
||||||
// Save everything
|
// Save everything
|
||||||
saveChatData();
|
saveChatData();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -433,7 +421,8 @@ export function setupSettingsPopup() {
|
|||||||
updateDiceDisplayCore();
|
updateDiceDisplayCore();
|
||||||
updateChatThoughts(); // Clear the thought bubble in chat
|
updateChatThoughts(); // Clear the thought bubble in chat
|
||||||
renderQuests(); // Clear and re-render quests UI
|
renderQuests(); // Clear and re-render quests UI
|
||||||
renderSkills();
|
|
||||||
|
// console.log('[RPG Companion] Chat cache cleared');
|
||||||
});
|
});
|
||||||
|
|
||||||
return settingsModal;
|
return settingsModal;
|
||||||
|
|||||||
+31
-151
@@ -8,7 +8,6 @@ import { saveSettings } from '../../core/persistence.js';
|
|||||||
import { renderUserStats } from '../rendering/userStats.js';
|
import { renderUserStats } from '../rendering/userStats.js';
|
||||||
import { renderInfoBox } from '../rendering/infoBox.js';
|
import { renderInfoBox } from '../rendering/infoBox.js';
|
||||||
import { renderThoughts } from '../rendering/thoughts.js';
|
import { renderThoughts } from '../rendering/thoughts.js';
|
||||||
import { renderSkills } from '../rendering/skills.js';
|
|
||||||
|
|
||||||
let $editorModal = null;
|
let $editorModal = null;
|
||||||
let activeTab = 'userStats';
|
let activeTab = 'userStats';
|
||||||
@@ -130,12 +129,12 @@ function resetToDefaults() {
|
|||||||
],
|
],
|
||||||
showRPGAttributes: true,
|
showRPGAttributes: true,
|
||||||
rpgAttributes: [
|
rpgAttributes: [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
],
|
],
|
||||||
statusSection: {
|
statusSection: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -210,12 +209,10 @@ function renderUserStatsTab() {
|
|||||||
html += '<div class="rpg-editor-stats-list" id="rpg-editor-stats-list">';
|
html += '<div class="rpg-editor-stats-list" id="rpg-editor-stats-list">';
|
||||||
|
|
||||||
config.customStats.forEach((stat, index) => {
|
config.customStats.forEach((stat, index) => {
|
||||||
const statDesc = stat.description || '';
|
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-editor-stat-item rpg-editor-item-with-desc" data-index="${index}">
|
<div class="rpg-editor-stat-item" data-index="${index}">
|
||||||
<input type="checkbox" ${stat.enabled ? 'checked' : ''} class="rpg-stat-toggle" data-index="${index}">
|
<input type="checkbox" ${stat.enabled ? 'checked' : ''} class="rpg-stat-toggle" data-index="${index}">
|
||||||
<input type="text" value="${stat.name}" class="rpg-stat-name" data-index="${index}" placeholder="Stat Name">
|
<input type="text" value="${stat.name}" class="rpg-stat-name" data-index="${index}" placeholder="Stat Name">
|
||||||
<input type="text" value="${statDesc}" class="rpg-stat-desc" data-index="${index}" placeholder="Description for AI">
|
|
||||||
<button class="rpg-stat-remove" data-index="${index}" title="Remove stat"><i class="fa-solid fa-trash"></i></button>
|
<button class="rpg-stat-remove" data-index="${index}" title="Remove stat"><i class="fa-solid fa-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -234,13 +231,6 @@ function renderUserStatsTab() {
|
|||||||
html += `<label for="rpg-show-rpg-attrs">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.enableRpgAttributes')}</label>`;
|
html += `<label for="rpg-show-rpg-attrs">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.enableRpgAttributes')}</label>`;
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// Allow AI to update attributes toggle
|
|
||||||
const allowAIUpdateAttributes = config.allowAIUpdateAttributes !== undefined ? config.allowAIUpdateAttributes : true;
|
|
||||||
html += '<div class="rpg-editor-toggle-row">';
|
|
||||||
html += `<input type="checkbox" id="rpg-allow-ai-update-attrs" ${allowAIUpdateAttributes ? 'checked' : ''}>`;
|
|
||||||
html += `<label for="rpg-allow-ai-update-attrs">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.allowAIUpdateAttributes')}</label>`;
|
|
||||||
html += '</div>';
|
|
||||||
|
|
||||||
// Always send attributes toggle
|
// Always send attributes toggle
|
||||||
const alwaysSendAttributes = config.alwaysSendAttributes !== undefined ? config.alwaysSendAttributes : false;
|
const alwaysSendAttributes = config.alwaysSendAttributes !== undefined ? config.alwaysSendAttributes : false;
|
||||||
html += '<div class="rpg-editor-toggle-row">';
|
html += '<div class="rpg-editor-toggle-row">';
|
||||||
@@ -254,12 +244,12 @@ function renderUserStatsTab() {
|
|||||||
// Ensure rpgAttributes exists in the actual config (not just local fallback)
|
// Ensure rpgAttributes exists in the actual config (not just local fallback)
|
||||||
if (!config.rpgAttributes || config.rpgAttributes.length === 0) {
|
if (!config.rpgAttributes || config.rpgAttributes.length === 0) {
|
||||||
config.rpgAttributes = [
|
config.rpgAttributes = [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
];
|
];
|
||||||
// Save the defaults back to the actual config
|
// Save the defaults back to the actual config
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes = config.rpgAttributes;
|
extensionSettings.trackerConfig.userStats.rpgAttributes = config.rpgAttributes;
|
||||||
@@ -268,12 +258,10 @@ function renderUserStatsTab() {
|
|||||||
const rpgAttributes = config.rpgAttributes;
|
const rpgAttributes = config.rpgAttributes;
|
||||||
|
|
||||||
rpgAttributes.forEach((attr, index) => {
|
rpgAttributes.forEach((attr, index) => {
|
||||||
const attrDesc = attr.description || '';
|
|
||||||
html += `
|
html += `
|
||||||
<div class="rpg-editor-stat-item rpg-editor-item-with-desc" data-index="${index}">
|
<div class="rpg-editor-stat-item" data-index="${index}">
|
||||||
<input type="checkbox" ${attr.enabled ? 'checked' : ''} class="rpg-attr-toggle" data-index="${index}">
|
<input type="checkbox" ${attr.enabled ? 'checked' : ''} class="rpg-attr-toggle" data-index="${index}">
|
||||||
<input type="text" value="${attr.name}" class="rpg-attr-name" data-index="${index}" placeholder="Attribute Name">
|
<input type="text" value="${attr.name}" class="rpg-attr-name" data-index="${index}" placeholder="Attribute Name">
|
||||||
<input type="text" value="${attrDesc}" class="rpg-attr-desc" data-index="${index}" placeholder="Description for AI">
|
|
||||||
<button class="rpg-attr-remove" data-index="${index}" title="Remove attribute"><i class="fa-solid fa-trash"></i></button>
|
<button class="rpg-attr-remove" data-index="${index}" title="Remove attribute"><i class="fa-solid fa-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -299,47 +287,17 @@ function renderUserStatsTab() {
|
|||||||
|
|
||||||
// Skills Section
|
// Skills Section
|
||||||
html += `<h4><i class="fa-solid fa-star"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsSectionTitle')}</h4>`;
|
html += `<h4><i class="fa-solid fa-star"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsSectionTitle')}</h4>`;
|
||||||
|
html += '<div class="rpg-editor-toggle-row">';
|
||||||
// Check if skills are shown as separate section - if so, disable the toggle
|
html += `<input type="checkbox" id="rpg-skills-enabled" ${config.skillsSection.enabled ? 'checked' : ''}>`;
|
||||||
const skillsInSeparateTab = extensionSettings.showSkills;
|
|
||||||
const skillsToggleDisabled = skillsInSeparateTab ? 'disabled' : '';
|
|
||||||
const skillsToggleStyle = skillsInSeparateTab ? 'style="opacity: 0.5; cursor: not-allowed;"' : '';
|
|
||||||
|
|
||||||
html += `<div class="rpg-editor-toggle-row" ${skillsToggleStyle}>`;
|
|
||||||
html += `<input type="checkbox" id="rpg-skills-enabled" ${config.skillsSection.enabled ? 'checked' : ''} ${skillsToggleDisabled}>`;
|
|
||||||
html += `<label for="rpg-skills-enabled">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.enableSkillsSection')}</label>`;
|
html += `<label for="rpg-skills-enabled">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.enableSkillsSection')}</label>`;
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
// Show note when skills are in separate tab
|
|
||||||
if (skillsInSeparateTab) {
|
|
||||||
html += `<small style="display: block; margin-top: -8px; margin-bottom: 12px; color: #888; font-size: 11px;" data-i18n-key="template.trackerEditorModal.userStatsTab.skillsInSeparateTabNote">${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsInSeparateTabNote')}</small>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
html += `<label>${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsLabelLabel')}</label>`;
|
html += `<label>${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsLabelLabel')}</label>`;
|
||||||
html += `<input type="text" id="rpg-skills-label" value="${config.skillsSection.label}" class="rpg-text-input" placeholder="Skills">`;
|
html += `<input type="text" id="rpg-skills-label" value="${config.skillsSection.label}" class="rpg-text-input" placeholder="Skills">`;
|
||||||
|
|
||||||
html += `<label>${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsListLabel')}</label>`;
|
html += `<label>${i18n.getTranslation('template.trackerEditorModal.userStatsTab.skillsListLabel')}</label>`;
|
||||||
html += '<div class="rpg-editor-stats-list" id="rpg-editor-skills-list">';
|
|
||||||
|
|
||||||
// Migration function handles string array → object array conversion on load
|
|
||||||
const skillFields = config.skillsSection.customFields || [];
|
const skillFields = config.skillsSection.customFields || [];
|
||||||
skillFields.forEach((skill, index) => {
|
html += `<input type="text" id="rpg-skills-fields" value="${skillFields.join(', ')}" class="rpg-text-input" placeholder="e.g., Stealth, Persuasion, Combat">`;
|
||||||
const skillName = skill.name || '';
|
|
||||||
const skillDesc = skill.description || '';
|
|
||||||
const skillEnabled = skill.enabled !== false;
|
|
||||||
|
|
||||||
html += `
|
|
||||||
<div class="rpg-editor-stat-item rpg-editor-skill-item" data-index="${index}">
|
|
||||||
<input type="checkbox" ${skillEnabled ? 'checked' : ''} class="rpg-skill-toggle" data-index="${index}">
|
|
||||||
<input type="text" value="${skillName}" class="rpg-skill-name" data-index="${index}" placeholder="Skill Category Name">
|
|
||||||
<input type="text" value="${skillDesc}" class="rpg-skill-desc" data-index="${index}" placeholder="Description for AI">
|
|
||||||
<button class="rpg-skill-remove" data-index="${index}" title="Remove skill category"><i class="fa-solid fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
html += `<button class="rpg-btn-secondary" id="rpg-add-skill"><i class="fa-solid fa-plus"></i> ${i18n.getTranslation('template.trackerEditorModal.userStatsTab.addSkillButton') || 'Add Skill Category'}</button>`;
|
|
||||||
|
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
|
|
||||||
@@ -357,7 +315,6 @@ function setupUserStatsListeners() {
|
|||||||
extensionSettings.trackerConfig.userStats.customStats.push({
|
extensionSettings.trackerConfig.userStats.customStats.push({
|
||||||
id: newId,
|
id: newId,
|
||||||
name: 'New Stat',
|
name: 'New Stat',
|
||||||
description: '',
|
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
// Initialize value if doesn't exist
|
// Initialize value if doesn't exist
|
||||||
@@ -386,30 +343,23 @@ function setupUserStatsListeners() {
|
|||||||
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
|
extensionSettings.trackerConfig.userStats.customStats[index].name = $(this).val();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update stat description
|
|
||||||
$('.rpg-stat-desc').off('blur').on('blur', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.customStats[index].description = $(this).val();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add attribute
|
// Add attribute
|
||||||
$('#rpg-add-attr').off('click').on('click', function() {
|
$('#rpg-add-attr').off('click').on('click', function() {
|
||||||
// Ensure rpgAttributes array exists with defaults if needed
|
// Ensure rpgAttributes array exists with defaults if needed
|
||||||
if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) {
|
if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) {
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
extensionSettings.trackerConfig.userStats.rpgAttributes = [
|
||||||
{ id: 'str', name: 'STR', description: '', enabled: true },
|
{ id: 'str', name: 'STR', enabled: true },
|
||||||
{ id: 'dex', name: 'DEX', description: '', enabled: true },
|
{ id: 'dex', name: 'DEX', enabled: true },
|
||||||
{ id: 'con', name: 'CON', description: '', enabled: true },
|
{ id: 'con', name: 'CON', enabled: true },
|
||||||
{ id: 'int', name: 'INT', description: '', enabled: true },
|
{ id: 'int', name: 'INT', enabled: true },
|
||||||
{ id: 'wis', name: 'WIS', description: '', enabled: true },
|
{ id: 'wis', name: 'WIS', enabled: true },
|
||||||
{ id: 'cha', name: 'CHA', description: '', enabled: true }
|
{ id: 'cha', name: 'CHA', enabled: true }
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const newId = 'attr_' + Date.now();
|
const newId = 'attr_' + Date.now();
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes.push({
|
extensionSettings.trackerConfig.userStats.rpgAttributes.push({
|
||||||
id: newId,
|
id: newId,
|
||||||
name: 'NEW',
|
name: 'NEW',
|
||||||
description: '',
|
|
||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
// Initialize value in classicStats if doesn't exist
|
// Initialize value in classicStats if doesn't exist
|
||||||
@@ -438,25 +388,15 @@ function setupUserStatsListeners() {
|
|||||||
extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val();
|
extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update attribute description
|
|
||||||
$('.rpg-attr-desc').off('blur').on('blur', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.rpgAttributes[index].description = $(this).val();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Enable/disable RPG Attributes section toggle
|
// Enable/disable RPG Attributes section toggle
|
||||||
$('#rpg-show-rpg-attrs').off('change').on('change', function() {
|
$('#rpg-show-rpg-attrs').off('change').on('change', function() {
|
||||||
extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
|
extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Always send attributes toggle
|
// Always send attributes toggle
|
||||||
$('#rpg-always-send-attrs').off('change').on('change', function() {
|
$('#rpg-always-send-attrs').off('change').on('change', function() {
|
||||||
extensionSettings.trackerConfig.userStats.alwaysSendAttributes = $(this).is(':checked');
|
extensionSettings.trackerConfig.userStats.alwaysSendAttributes = $(this).is(':checked');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-allow-ai-update-attrs').off('change').on('change', function() {
|
|
||||||
extensionSettings.trackerConfig.userStats.allowAIUpdateAttributes = $(this).is(':checked');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Status section toggles
|
// Status section toggles
|
||||||
$('#rpg-status-enabled').off('change').on('change', function() {
|
$('#rpg-status-enabled').off('change').on('change', function() {
|
||||||
@@ -475,68 +415,16 @@ function setupUserStatsListeners() {
|
|||||||
// Skills section toggles
|
// Skills section toggles
|
||||||
$('#rpg-skills-enabled').off('change').on('change', function() {
|
$('#rpg-skills-enabled').off('change').on('change', function() {
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.enabled = $(this).is(':checked');
|
extensionSettings.trackerConfig.userStats.skillsSection.enabled = $(this).is(':checked');
|
||||||
saveSettings();
|
|
||||||
// Re-render both user stats (if skills shown there) and skills section
|
|
||||||
renderUserStats();
|
|
||||||
renderSkills();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#rpg-skills-label').off('blur').on('blur', function() {
|
$('#rpg-skills-label').off('blur').on('blur', function() {
|
||||||
const newLabel = $(this).val();
|
extensionSettings.trackerConfig.userStats.skillsSection.label = $(this).val();
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.label = newLabel;
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
renderUserStats();
|
|
||||||
renderSkills();
|
|
||||||
// Update the skills tab button text if it exists
|
|
||||||
$('.rpg-tab-btn[data-tab="skills"] span').text(newLabel);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add skill category
|
$('#rpg-skills-fields').off('blur').on('blur', function() {
|
||||||
$('#rpg-add-skill').off('click').on('click', function() {
|
const fields = $(this).val().split(',').map(f => f.trim()).filter(f => f);
|
||||||
if (!extensionSettings.trackerConfig.userStats.skillsSection.customFields) {
|
extensionSettings.trackerConfig.userStats.skillsSection.customFields = fields;
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields = [];
|
|
||||||
}
|
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields.push({
|
|
||||||
id: 'skill_' + Date.now(),
|
|
||||||
name: 'New Skill',
|
|
||||||
description: '',
|
|
||||||
enabled: true
|
|
||||||
});
|
|
||||||
renderUserStatsTab();
|
|
||||||
saveSettings();
|
|
||||||
renderSkills();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove skill category
|
|
||||||
$('.rpg-skill-remove').off('click').on('click', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields.splice(index, 1);
|
|
||||||
renderUserStatsTab();
|
|
||||||
saveSettings();
|
|
||||||
renderSkills();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Toggle skill category
|
|
||||||
// Migration function handles string array → object array conversion on load
|
|
||||||
$('.rpg-skill-toggle').off('change').on('change', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields[index].enabled = $(this).is(':checked');
|
|
||||||
saveSettings();
|
|
||||||
renderSkills();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Rename skill category
|
|
||||||
$('.rpg-skill-name').off('blur').on('blur', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields[index].name = $(this).val();
|
|
||||||
saveSettings();
|
|
||||||
renderSkills();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update skill description
|
|
||||||
$('.rpg-skill-desc').off('blur').on('blur', function() {
|
|
||||||
const index = $(this).data('index');
|
|
||||||
extensionSettings.trackerConfig.userStats.skillsSection.customFields[index].description = $(this).val();
|
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -756,15 +644,7 @@ function setupPresentCharactersListeners() {
|
|||||||
if (!extensionSettings.trackerConfig.presentCharacters.relationshipEmojis) {
|
if (!extensionSettings.trackerConfig.presentCharacters.relationshipEmojis) {
|
||||||
extensionSettings.trackerConfig.presentCharacters.relationshipEmojis = {};
|
extensionSettings.trackerConfig.presentCharacters.relationshipEmojis = {};
|
||||||
}
|
}
|
||||||
|
extensionSettings.trackerConfig.presentCharacters.relationshipEmojis['New Relationship'] = '😊';
|
||||||
// Generate unique name to avoid overwriting existing "New Relationship" entries
|
|
||||||
let newName = 'New Relationship';
|
|
||||||
let counter = 1;
|
|
||||||
while (extensionSettings.trackerConfig.presentCharacters.relationshipEmojis[newName]) {
|
|
||||||
newName = `New Relationship ${counter}`;
|
|
||||||
counter++;
|
|
||||||
}
|
|
||||||
extensionSettings.trackerConfig.presentCharacters.relationshipEmojis[newName] = '😊';
|
|
||||||
|
|
||||||
// Sync relationshipFields
|
// Sync relationshipFields
|
||||||
extensionSettings.trackerConfig.presentCharacters.relationshipFields =
|
extensionSettings.trackerConfig.presentCharacters.relationshipFields =
|
||||||
|
|||||||
@@ -1,471 +0,0 @@
|
|||||||
/**
|
|
||||||
* Tracker Data Types - Unified JSON Schema
|
|
||||||
* Structure adapts dynamically based on trackerConfig settings
|
|
||||||
*
|
|
||||||
* TODO: Future enhancements:
|
|
||||||
* - Generate formal JSON Schema for prompting (helps LLMs understand structure better)
|
|
||||||
* - Validate LLM responses against schema
|
|
||||||
* - In SEPARATE mode, retry generation if schema validation fails
|
|
||||||
* - Could use libraries like ajv for validation
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerItem
|
|
||||||
* @property {string} name - Item name
|
|
||||||
* @property {string} description - Item description
|
|
||||||
* @property {string} [grantsSkill] - Optional: skill name this item grants
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerSkill
|
|
||||||
* @property {string} name - Skill/ability name
|
|
||||||
* @property {string} description - Skill description
|
|
||||||
* @property {string} [grantedBy] - Optional: item name that grants this skill
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerCharacter
|
|
||||||
* @property {string} name - Character name
|
|
||||||
* @property {string} [relationship] - Relationship type
|
|
||||||
* @property {Object.<string, string>} [fields] - Dynamic custom fields (appearance, demeanor, etc.)
|
|
||||||
* @property {string} [thoughts] - Character's inner thoughts
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerQuest
|
|
||||||
* @property {string} name - Quest name/title
|
|
||||||
* @property {string} description - Quest description/objective
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object.<string, number>} TrackerStats
|
|
||||||
* Dynamic stats object - keys are stat names from config, values are percentages
|
|
||||||
* Example: { "Health": 85, "Energy": 70, "Custom Stat": 50 }
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object.<string, number>} TrackerAttributes
|
|
||||||
* Dynamic attributes object - keys are attribute names from config (e.g., STR, DEX), values are numeric
|
|
||||||
* Example: { "STR": 15, "DEX": 12, "INT": 18 }
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerStatus
|
|
||||||
* @property {string} [mood] - Mood emoji (if enabled)
|
|
||||||
* @property {Object.<string, string>} [fields] - Dynamic custom fields from config
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerInfoBox
|
|
||||||
* Dynamic based on enabled widgets in config
|
|
||||||
* @property {string} [date] - Current date (if widget enabled)
|
|
||||||
* @property {string} [time] - Time range (if widget enabled)
|
|
||||||
* @property {string} [weather] - Weather with emoji (if widget enabled)
|
|
||||||
* @property {string} [temperature] - Temperature (if widget enabled)
|
|
||||||
* @property {string} [location] - Scene location (if widget enabled)
|
|
||||||
* @property {string} [recentEvents] - Recent events summary (if widget enabled)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerInventory
|
|
||||||
* @property {TrackerItem[]} onPerson - Items carried/worn
|
|
||||||
* @property {Object.<string, TrackerItem[]>} stored - Items stored at locations
|
|
||||||
* @property {TrackerItem[]} assets - Major possessions (vehicles, property)
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object.<string, TrackerSkill[]>} TrackerSkills
|
|
||||||
* Key is skill category name from config, value is array of abilities
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} TrackerQuests
|
|
||||||
* @property {TrackerQuest|null} main - Main quest or null
|
|
||||||
* @property {TrackerQuest[]} optional - Optional quests
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complete tracker data structure from LLM
|
|
||||||
* All fields are optional - only enabled sections are included
|
|
||||||
* @typedef {Object} TrackerData
|
|
||||||
* @property {TrackerStats} [stats] - Numeric stats (based on config)
|
|
||||||
* @property {TrackerStatus} [status] - Status info (mood, custom fields)
|
|
||||||
* @property {TrackerAttributes} [attributes] - RPG attributes (STR, DEX, etc.)
|
|
||||||
* @property {number} [level] - Character level
|
|
||||||
* @property {TrackerInfoBox} [infoBox] - Scene information (based on enabled widgets)
|
|
||||||
* @property {TrackerCharacter[]} [characters] - Present characters
|
|
||||||
* @property {TrackerInventory} [inventory] - Player inventory
|
|
||||||
* @property {TrackerSkills} [skills] - Player skills by category
|
|
||||||
* @property {TrackerQuests} [quests] - Active quests
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const TRACKER_DATA_VERSION = 3;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates empty tracker data based on current config
|
|
||||||
* @param {Object} trackerConfig - The tracker configuration
|
|
||||||
* @returns {TrackerData}
|
|
||||||
*/
|
|
||||||
export function createEmptyTrackerData(trackerConfig) {
|
|
||||||
const data = {};
|
|
||||||
|
|
||||||
// Stats based on config
|
|
||||||
if (trackerConfig?.userStats?.customStats) {
|
|
||||||
data.stats = {};
|
|
||||||
for (const stat of trackerConfig.userStats.customStats) {
|
|
||||||
if (stat.enabled) {
|
|
||||||
data.stats[stat.name] = 100;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status
|
|
||||||
data.status = { mood: '😐', fields: {} };
|
|
||||||
|
|
||||||
// Info box based on enabled widgets
|
|
||||||
if (trackerConfig?.infoBox?.widgets) {
|
|
||||||
data.infoBox = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Characters
|
|
||||||
data.characters = [];
|
|
||||||
|
|
||||||
// Inventory
|
|
||||||
data.inventory = {
|
|
||||||
onPerson: [],
|
|
||||||
stored: {},
|
|
||||||
assets: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Skills based on config categories
|
|
||||||
data.skills = {};
|
|
||||||
if (trackerConfig?.userStats?.skillsSection?.customFields) {
|
|
||||||
for (const category of trackerConfig.userStats.skillsSection.customFields) {
|
|
||||||
data.skills[category] = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quests
|
|
||||||
data.quests = {
|
|
||||||
main: null,
|
|
||||||
optional: []
|
|
||||||
};
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a JSON schema example based on tracker config
|
|
||||||
* Used in prompts to show LLM the expected format
|
|
||||||
* @param {Object} trackerConfig - The tracker configuration
|
|
||||||
* @param {Object} options - Generation options
|
|
||||||
* @returns {Object} Example JSON object
|
|
||||||
*/
|
|
||||||
export function generateSchemaExample(trackerConfig, options = {}) {
|
|
||||||
const example = {};
|
|
||||||
const {
|
|
||||||
includeStats = true,
|
|
||||||
includeInfoBox = true,
|
|
||||||
includeCharacters = true,
|
|
||||||
includeInventory = true,
|
|
||||||
includeSkills = true,
|
|
||||||
includeQuests = true,
|
|
||||||
enableItemSkillLinks = false
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
// Stats section
|
|
||||||
if (includeStats && trackerConfig?.userStats?.customStats) {
|
|
||||||
example.stats = {};
|
|
||||||
for (const stat of trackerConfig.userStats.customStats) {
|
|
||||||
if (stat.enabled) {
|
|
||||||
example.stats[stat.name] = 75; // Example value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status fields
|
|
||||||
if (trackerConfig.userStats.statusSection?.enabled) {
|
|
||||||
example.status = {};
|
|
||||||
if (trackerConfig.userStats.statusSection.showMoodEmoji) {
|
|
||||||
example.status.mood = "😊";
|
|
||||||
}
|
|
||||||
if (trackerConfig.userStats.statusSection.customFields?.length > 0) {
|
|
||||||
example.status.fields = {};
|
|
||||||
for (const field of trackerConfig.userStats.statusSection.customFields) {
|
|
||||||
example.status.fields[field] = `[${field} value]`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info Box
|
|
||||||
if (includeInfoBox && trackerConfig?.infoBox?.widgets) {
|
|
||||||
example.infoBox = {};
|
|
||||||
const widgets = trackerConfig.infoBox.widgets;
|
|
||||||
if (widgets.date?.enabled) example.infoBox.date = "Monday, March 15, 1242";
|
|
||||||
if (widgets.time?.enabled) example.infoBox.time = "14:00 → 15:30";
|
|
||||||
if (widgets.weather?.enabled) example.infoBox.weather = "☀️ Sunny";
|
|
||||||
if (widgets.temperature?.enabled) {
|
|
||||||
const unit = widgets.temperature.unit === 'F' ? '°F' : '°C';
|
|
||||||
example.infoBox.temperature = `22${unit}`;
|
|
||||||
}
|
|
||||||
if (widgets.location?.enabled) example.infoBox.location = "Forest Clearing";
|
|
||||||
if (widgets.recentEvents?.enabled) example.infoBox.recentEvents = "The party arrived at dawn";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Characters
|
|
||||||
if (includeCharacters && trackerConfig?.presentCharacters) {
|
|
||||||
const charConfig = trackerConfig.presentCharacters;
|
|
||||||
const charExample = { name: "Elena" };
|
|
||||||
|
|
||||||
if (charConfig.relationshipFields?.length > 0) {
|
|
||||||
charExample.relationship = charConfig.relationshipFields[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charConfig.customFields?.length > 0) {
|
|
||||||
charExample.fields = {};
|
|
||||||
for (const field of charConfig.customFields) {
|
|
||||||
if (field.enabled) {
|
|
||||||
charExample.fields[field.name] = `[${field.description || field.name}]`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charConfig.thoughts?.enabled) {
|
|
||||||
charExample.thoughts = "I wonder what adventures await...";
|
|
||||||
}
|
|
||||||
|
|
||||||
example.characters = [charExample];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inventory
|
|
||||||
if (includeInventory) {
|
|
||||||
const itemExample = { name: "Iron Sword", description: "A sturdy blade" };
|
|
||||||
if (enableItemSkillLinks) {
|
|
||||||
itemExample.grantsSkill = "Sword Fighting";
|
|
||||||
}
|
|
||||||
example.inventory = {
|
|
||||||
onPerson: [itemExample],
|
|
||||||
stored: { "Home": [{ name: "Gold Coins", description: "50 gold pieces" }] },
|
|
||||||
assets: [{ name: "Small House", description: "A modest dwelling" }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skills
|
|
||||||
if (includeSkills && trackerConfig?.userStats?.skillsSection?.customFields?.length > 0) {
|
|
||||||
example.skills = {};
|
|
||||||
for (const category of trackerConfig.userStats.skillsSection.customFields) {
|
|
||||||
const skillExample = { name: "Example Ability", description: "What this ability does" };
|
|
||||||
if (enableItemSkillLinks) {
|
|
||||||
skillExample.grantedBy = "Item Name";
|
|
||||||
}
|
|
||||||
example.skills[category] = [skillExample];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quests
|
|
||||||
if (includeQuests) {
|
|
||||||
example.quests = {
|
|
||||||
main: { name: "Main Quest", description: "The primary objective" },
|
|
||||||
optional: [{ name: "Side Quest", description: "An optional objective" }]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return example;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates tracker data structure
|
|
||||||
* @param {any} data - Data to validate
|
|
||||||
* @returns {{valid: boolean, errors: string[]}}
|
|
||||||
*/
|
|
||||||
export function validateTrackerData(data) {
|
|
||||||
const errors = [];
|
|
||||||
|
|
||||||
if (!data || typeof data !== 'object') {
|
|
||||||
return { valid: false, errors: ['Data must be an object'] };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate inventory structure if present (be flexible with LLM variations)
|
|
||||||
if (data.inventory) {
|
|
||||||
// Accept arrays or empty objects - normalize in parser
|
|
||||||
if (data.inventory.onPerson && !Array.isArray(data.inventory.onPerson) && Object.keys(data.inventory.onPerson).length > 0) {
|
|
||||||
errors.push('inventory.onPerson must be an array');
|
|
||||||
}
|
|
||||||
if (data.inventory.stored && typeof data.inventory.stored !== 'object') {
|
|
||||||
errors.push('inventory.stored must be an object');
|
|
||||||
}
|
|
||||||
// Accept arrays or empty objects for assets
|
|
||||||
if (data.inventory.assets && !Array.isArray(data.inventory.assets) && Object.keys(data.inventory.assets).length > 0) {
|
|
||||||
errors.push('inventory.assets must be an array');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate skills structure if present
|
|
||||||
if (data.skills && typeof data.skills !== 'object' && typeof data.skills !== 'string') {
|
|
||||||
errors.push('skills must be an object or a string');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate quests structure if present
|
|
||||||
if (data.quests) {
|
|
||||||
if (data.quests.optional && !Array.isArray(data.quests.optional)) {
|
|
||||||
errors.push('quests.optional must be an array');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate characters structure if present
|
|
||||||
if (data.characters && !Array.isArray(data.characters)) {
|
|
||||||
errors.push('characters must be an array');
|
|
||||||
}
|
|
||||||
|
|
||||||
return { valid: errors.length === 0, errors };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds all items that grant a specific skill
|
|
||||||
* @param {TrackerData} data - Tracker data
|
|
||||||
* @param {string} skillName - Skill name to search for
|
|
||||||
* @returns {TrackerItem[]}
|
|
||||||
*/
|
|
||||||
export function findItemsGrantingSkill(data, skillName) {
|
|
||||||
const items = [];
|
|
||||||
if (!data.inventory) return items;
|
|
||||||
|
|
||||||
const checkItems = (itemList) => {
|
|
||||||
if (!Array.isArray(itemList)) return;
|
|
||||||
for (const item of itemList) {
|
|
||||||
if (item.grantsSkill === skillName) {
|
|
||||||
items.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
checkItems(data.inventory.onPerson);
|
|
||||||
checkItems(data.inventory.assets);
|
|
||||||
if (data.inventory.stored) {
|
|
||||||
for (const locationItems of Object.values(data.inventory.stored)) {
|
|
||||||
checkItems(locationItems);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds all skills granted by a specific item
|
|
||||||
* @param {TrackerData} data - Tracker data
|
|
||||||
* @param {string} itemName - Item name to search for
|
|
||||||
* @returns {Array<{category: string, skill: TrackerSkill}>}
|
|
||||||
*/
|
|
||||||
export function findSkillsGrantedByItem(data, itemName) {
|
|
||||||
const skills = [];
|
|
||||||
if (!data.skills) return skills;
|
|
||||||
|
|
||||||
for (const [category, skillList] of Object.entries(data.skills)) {
|
|
||||||
if (!Array.isArray(skillList)) continue;
|
|
||||||
for (const skill of skillList) {
|
|
||||||
if (skill.grantedBy === itemName) {
|
|
||||||
skills.push({ category, skill });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return skills;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an item and optionally its linked skills from tracker data
|
|
||||||
* @param {TrackerData} data - Tracker data (mutated)
|
|
||||||
* @param {string} itemName - Item name to remove
|
|
||||||
* @param {string} location - 'onPerson', 'assets', or stored location name
|
|
||||||
* @param {boolean} removeLinkedSkills - Whether to also remove skills granted by this item
|
|
||||||
*/
|
|
||||||
export function removeItemAndLinkedSkills(data, itemName, location, removeLinkedSkills = true) {
|
|
||||||
if (!data.inventory) return;
|
|
||||||
|
|
||||||
let removedItem = null;
|
|
||||||
|
|
||||||
const removeFromList = (list) => {
|
|
||||||
if (!Array.isArray(list)) return false;
|
|
||||||
const index = list.findIndex(item => item.name === itemName);
|
|
||||||
if (index >= 0) {
|
|
||||||
removedItem = list[index];
|
|
||||||
list.splice(index, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (location === 'onPerson') {
|
|
||||||
removeFromList(data.inventory.onPerson);
|
|
||||||
} else if (location === 'assets') {
|
|
||||||
removeFromList(data.inventory.assets);
|
|
||||||
} else if (data.inventory.stored?.[location]) {
|
|
||||||
removeFromList(data.inventory.stored[location]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove linked skills if requested
|
|
||||||
if (removeLinkedSkills && removedItem?.grantsSkill && data.skills) {
|
|
||||||
for (const skillList of Object.values(data.skills)) {
|
|
||||||
if (!Array.isArray(skillList)) continue;
|
|
||||||
const skillIndex = skillList.findIndex(s =>
|
|
||||||
s.name === removedItem.grantsSkill && s.grantedBy === itemName
|
|
||||||
);
|
|
||||||
if (skillIndex >= 0) {
|
|
||||||
skillList.splice(skillIndex, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merges new tracker data with existing data
|
|
||||||
* New data overwrites existing fields, but preserves fields not in new data
|
|
||||||
* @param {TrackerData} existing - Existing tracker data
|
|
||||||
* @param {TrackerData} newData - New data from LLM
|
|
||||||
* @returns {TrackerData} Merged data
|
|
||||||
*/
|
|
||||||
export function mergeTrackerData(existing, newData) {
|
|
||||||
const merged = JSON.parse(JSON.stringify(existing || {}));
|
|
||||||
|
|
||||||
if (newData.stats) {
|
|
||||||
merged.stats = { ...merged.stats, ...newData.stats };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.status) {
|
|
||||||
merged.status = {
|
|
||||||
...merged.status,
|
|
||||||
...newData.status,
|
|
||||||
fields: { ...merged.status?.fields, ...newData.status?.fields }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.attributes) {
|
|
||||||
merged.attributes = { ...merged.attributes, ...newData.attributes };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.level !== undefined) {
|
|
||||||
merged.level = newData.level;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.infoBox) {
|
|
||||||
merged.infoBox = { ...merged.infoBox, ...newData.infoBox };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.characters) {
|
|
||||||
merged.characters = newData.characters;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.inventory) {
|
|
||||||
merged.inventory = newData.inventory;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.skills) {
|
|
||||||
merged.skills = newData.skills;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newData.quests) {
|
|
||||||
merged.quests = newData.quests;
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged;
|
|
||||||
}
|
|
||||||
@@ -14,7 +14,9 @@ import { getThumbnailUrl } from '../../../../../../script.js';
|
|||||||
* @returns {string|null} Thumbnail URL or null if unavailable/error
|
* @returns {string|null} Thumbnail URL or null if unavailable/error
|
||||||
*/
|
*/
|
||||||
export function getSafeThumbnailUrl(type, filename) {
|
export function getSafeThumbnailUrl(type, filename) {
|
||||||
|
// Return null if no filename provided
|
||||||
if (!filename || filename === 'none') {
|
if (!filename || filename === 'none') {
|
||||||
|
// console.log(`[RPG Companion] No valid filename provided for ${type} thumbnail`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +24,13 @@ export function getSafeThumbnailUrl(type, filename) {
|
|||||||
// Attempt to get thumbnail URL from SillyTavern API
|
// Attempt to get thumbnail URL from SillyTavern API
|
||||||
const url = getThumbnailUrl(type, filename);
|
const url = getThumbnailUrl(type, filename);
|
||||||
|
|
||||||
|
// Validate that we got a string back
|
||||||
if (typeof url !== 'string' || url.trim() === '') {
|
if (typeof url !== 'string' || url.trim() === '') {
|
||||||
console.warn(`[RPG Companion] getThumbnailUrl returned invalid result for ${type}:`, filename);
|
console.warn(`[RPG Companion] getThumbnailUrl returned invalid result for ${type}:`, filename);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// console.log(`[RPG Companion] Successfully generated ${type} thumbnail URL for: ${filename}`);
|
||||||
return url;
|
return url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log detailed error information for debugging
|
// Log detailed error information for debugging
|
||||||
|
|||||||
@@ -27,7 +27,9 @@ const DEFAULT_INVENTORY_V2 = {
|
|||||||
* @returns {MigrationResult} Migration result with v2 inventory and metadata
|
* @returns {MigrationResult} Migration result with v2 inventory and metadata
|
||||||
*/
|
*/
|
||||||
export function migrateInventory(inventory) {
|
export function migrateInventory(inventory) {
|
||||||
|
// Case 1: Already v2 format (has version property and is an object)
|
||||||
if (inventory && typeof inventory === 'object' && inventory.version === 2) {
|
if (inventory && typeof inventory === 'object' && inventory.version === 2) {
|
||||||
|
// console.log('[RPG Companion Migration] Inventory already v2, no migration needed');
|
||||||
return {
|
return {
|
||||||
inventory: inventory,
|
inventory: inventory,
|
||||||
migrated: false,
|
migrated: false,
|
||||||
@@ -35,7 +37,9 @@ export function migrateInventory(inventory) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case 2: null or undefined → use defaults
|
||||||
if (inventory === null || inventory === undefined) {
|
if (inventory === null || inventory === undefined) {
|
||||||
|
// console.log('[RPG Companion Migration] Inventory is null/undefined, using defaults');
|
||||||
return {
|
return {
|
||||||
inventory: { ...DEFAULT_INVENTORY_V2 },
|
inventory: { ...DEFAULT_INVENTORY_V2 },
|
||||||
migrated: true,
|
migrated: true,
|
||||||
@@ -43,9 +47,12 @@ export function migrateInventory(inventory) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Case 3: v1 string format → migrate to v2
|
||||||
if (typeof inventory === 'string') {
|
if (typeof inventory === 'string') {
|
||||||
|
// Check if it's an empty/default string
|
||||||
const trimmed = inventory.trim();
|
const trimmed = inventory.trim();
|
||||||
if (trimmed === '' || trimmed.toLowerCase() === 'none') {
|
if (trimmed === '' || trimmed.toLowerCase() === 'none') {
|
||||||
|
// console.log('[RPG Companion Migration] Inventory is empty/None, using defaults');
|
||||||
return {
|
return {
|
||||||
inventory: { ...DEFAULT_INVENTORY_V2 },
|
inventory: { ...DEFAULT_INVENTORY_V2 },
|
||||||
migrated: true,
|
migrated: true,
|
||||||
@@ -53,6 +60,8 @@ export function migrateInventory(inventory) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Non-empty v1 string → migrate to v2.onPerson
|
||||||
|
// console.log('[RPG Companion Migration] Migrating v1 string to v2.onPerson:', inventory);
|
||||||
return {
|
return {
|
||||||
inventory: {
|
inventory: {
|
||||||
version: 2,
|
version: 2,
|
||||||
|
|||||||
@@ -1224,10 +1224,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
/* Allow wrapping for long day names */
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-calendar-year {
|
.rpg-calendar-year {
|
||||||
@@ -1255,6 +1255,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-weather-forecast.rpg-editable {
|
.rpg-weather-forecast.rpg-editable {
|
||||||
@@ -1833,11 +1835,31 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
width: 100%; /* Ensure cards take full width */
|
width: 100%; /* Ensure cards take full width */
|
||||||
|
max-height: clamp(200px, 18vh, 250px);
|
||||||
box-sizing: border-box; /* Include padding and border in width calculation */
|
box-sizing: border-box; /* Include padding and border in width calculation */
|
||||||
flex-shrink: 0; /* Prevent cards from shrinking */
|
flex-shrink: 0; /* Prevent cards from shrinking */
|
||||||
overflow: visible;
|
overflow: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--rpg-border) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rpg-character-card::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-card::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-card::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--rpg-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-card::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--rpg-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
.rpg-character-card:hover {
|
.rpg-character-card:hover {
|
||||||
background: rgba(0, 0, 0, 0.4);
|
background: rgba(0, 0, 0, 0.4);
|
||||||
@@ -1883,17 +1905,37 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rpg-character-info {
|
.rpg-character-info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: clamp(3px, 0.5vh, 5px);
|
gap: clamp(3px, 0.5vh, 5px);
|
||||||
overflow: visible;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--rpg-border) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rpg-character-info::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-info::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-info::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--rpg-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rpg-character-info::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--rpg-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
/* Character header with emoji and name */
|
/* Character header with emoji and name */
|
||||||
.rpg-character-header {
|
.rpg-character-header {
|
||||||
@@ -1917,38 +1959,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
white-space: nowrap; /* Prevent name from wrapping */
|
white-space: nowrap; /* Prevent name from wrapping */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Character remove button */
|
|
||||||
.rpg-character-remove {
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: rgba(255, 0, 0, 0.2);
|
|
||||||
border: 1px solid rgba(255, 0, 0, 0.4);
|
|
||||||
border-radius: 50%;
|
|
||||||
width: clamp(18px, 2.5vh, 22px);
|
|
||||||
height: clamp(18px, 2.5vh, 22px);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: clamp(14px, 2vw, 18px);
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-character-remove:hover {
|
|
||||||
background: rgba(255, 0, 0, 0.4);
|
|
||||||
border-color: rgba(255, 0, 0, 0.6);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-character-remove:active {
|
|
||||||
transform: scale(0.95);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Character traits/status line and custom fields */
|
/* Character traits/status line and custom fields */
|
||||||
@@ -2063,14 +2073,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
outline: 1px solid var(--rpg-highlight);
|
outline: 1px solid var(--rpg-highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show full content on hover for text fields (not badges/icons) */
|
|
||||||
.rpg-editable:not(.rpg-relationship-badge):not(.rpg-character-emoji):hover {
|
|
||||||
overflow: visible !important;
|
|
||||||
text-overflow: clip !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-editable:focus,
|
.rpg-editable:focus,
|
||||||
.rpg-editable-stat:focus,
|
.rpg-editable-stat:focus,
|
||||||
.rpg-editable-stat-name:focus {
|
.rpg-editable-stat-name:focus {
|
||||||
@@ -2079,14 +2081,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
box-shadow: 0 0 8px var(--rpg-highlight);
|
box-shadow: 0 0 8px var(--rpg-highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show full content when focused for text fields (not badges/icons) */
|
|
||||||
.rpg-editable:not(.rpg-relationship-badge):not(.rpg-character-emoji):focus {
|
|
||||||
overflow: visible !important;
|
|
||||||
text-overflow: clip !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Edit button container and styling */
|
/* Edit button container and styling */
|
||||||
.rpg-edit-button-container {
|
.rpg-edit-button-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3688,7 +3682,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
margin-top: 0.5em;
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3718,7 +3711,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
|
|
||||||
.rpg-stat-remove,
|
.rpg-stat-remove,
|
||||||
.rpg-attr-remove,
|
.rpg-attr-remove,
|
||||||
.rpg-skill-remove,
|
|
||||||
.rpg-remove-relationship {
|
.rpg-remove-relationship {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding: 0.375em 0.625em;
|
padding: 0.375em 0.625em;
|
||||||
@@ -3730,48 +3722,6 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
transition: opacity 0.2s;
|
transition: opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Skills list spacing */
|
|
||||||
#rpg-editor-skills-list {
|
|
||||||
margin-top: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Items with description field (stats, attrs, skills) */
|
|
||||||
.rpg-editor-item-with-desc,
|
|
||||||
.rpg-editor-skill-item {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-name,
|
|
||||||
.rpg-stat-desc,
|
|
||||||
.rpg-attr-desc {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
padding: 0.375em 0.5em;
|
|
||||||
background: var(--rpg-bg);
|
|
||||||
border: 1px solid var(--rpg-border);
|
|
||||||
border-radius: 0.25em;
|
|
||||||
color: var(--rpg-text);
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-desc,
|
|
||||||
.rpg-stat-desc,
|
|
||||||
.rpg-attr-desc {
|
|
||||||
flex: 2;
|
|
||||||
min-width: 180px;
|
|
||||||
padding: 0.375em 0.5em;
|
|
||||||
background: var(--rpg-bg);
|
|
||||||
border: 1px solid var(--rpg-border);
|
|
||||||
border-radius: 0.25em;
|
|
||||||
color: var(--rpg-text);
|
|
||||||
font-size: 0.9em;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-toggle {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-stat-remove:hover,
|
.rpg-stat-remove:hover,
|
||||||
.rpg-attr-remove:hover,
|
.rpg-attr-remove:hover,
|
||||||
.rpg-remove-relationship:hover {
|
.rpg-remove-relationship:hover {
|
||||||
@@ -6687,759 +6637,3 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
font-size: clamp(14px, 3vw, 18px) !important;
|
font-size: clamp(14px, 3vw, 18px) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
SKILLS SECTION STYLES
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.rpg-skills-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
border-bottom: 2px solid var(--SmartThemeBorderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-header h4 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-header-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-section {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-section-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-section-title i {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-count {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-list {
|
|
||||||
min-height: 2rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skills List View */
|
|
||||||
.rpg-skills-list-view {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: transparent;
|
|
||||||
border: 2px solid var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-row.rpg-skill-active {
|
|
||||||
border-color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-row.rpg-skill-inactive {
|
|
||||||
opacity: 0.6;
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-row:hover {
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-row .rpg-skill-name {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skills Grid View */
|
|
||||||
.rpg-skills-grid-view {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 1rem 0.75rem;
|
|
||||||
background: transparent;
|
|
||||||
border: 2px solid var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card.rpg-skill-active {
|
|
||||||
border-color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card.rpg-skill-inactive {
|
|
||||||
opacity: 0.6;
|
|
||||||
border-style: dashed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card:hover {
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card .rpg-skill-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-card .rpg-skill-name {
|
|
||||||
text-align: center;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
hyphens: auto;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skill Toggle Button */
|
|
||||||
.rpg-skill-toggle {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0.3rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--SmartThemeFastUISliderColColor);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-toggle:hover {
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-active .rpg-skill-toggle {
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skill Linked Badge */
|
|
||||||
.rpg-skill-linked-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.2);
|
|
||||||
border: 1px solid var(--rpg-highlight);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-linked-badge i {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skills Empty State */
|
|
||||||
.rpg-skills-empty {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-empty i {
|
|
||||||
font-size: 2rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-empty p {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skills-empty small {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
SKILLS CATEGORY STYLES (like Inventory)
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.rpg-skill-category {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: var(--SmartThemeBlurTintColor);
|
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-title {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-title i {
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-count {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: normal;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-add-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
padding: 0.4rem 0.75rem;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--rpg-highlight);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-add-btn:hover {
|
|
||||||
background: var(--rpg-highlight);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-category-content {
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-items-empty {
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.6;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
SKILL-ITEM LINKING STYLES
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
/* Skill link badge (shows linked item name in skills section) */
|
|
||||||
.rpg-skill-link-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.3rem;
|
|
||||||
padding: 0.2rem 0.5rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.15);
|
|
||||||
border: 1px solid rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.3);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-link-badge:hover {
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.25);
|
|
||||||
border-color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-link-badge i {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-item-name {
|
|
||||||
max-width: 70px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link button (on skills without a link) */
|
|
||||||
.rpg-skill-link-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: transparent;
|
|
||||||
border: 1px dashed var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
color: var(--SmartThemeFastUISliderColColor);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-link-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: var(--rpg-highlight);
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Unlink button */
|
|
||||||
.rpg-skill-unlink-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--SmartThemeFastUISliderColColor);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
opacity: 0.5;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-unlink-btn:hover {
|
|
||||||
opacity: 1;
|
|
||||||
color: #e94560;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skill link indicator on inventory items */
|
|
||||||
.rpg-item-skill-link {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.15);
|
|
||||||
border: 1px solid rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.3);
|
|
||||||
border-radius: 50%;
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-skill-link:hover {
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.3);
|
|
||||||
transform: scale(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-skill-link i {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Items/abilities with links get a subtle highlight */
|
|
||||||
.rpg-has-skill-link,
|
|
||||||
.rpg-has-link {
|
|
||||||
border-left: 2px solid var(--rpg-highlight) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link dropdown for selecting items */
|
|
||||||
.rpg-link-dropdown {
|
|
||||||
min-width: 200px;
|
|
||||||
max-width: 300px;
|
|
||||||
max-height: 300px;
|
|
||||||
background: var(--SmartThemeBlurTintColor);
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-close {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.25rem;
|
|
||||||
opacity: 0.6;
|
|
||||||
transition: opacity 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-close:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-list {
|
|
||||||
max-height: 220px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.6rem 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-item:hover {
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-link-dropdown-item i {
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight animation for items */
|
|
||||||
@keyframes rpg-highlight-pulse {
|
|
||||||
0%, 100% {
|
|
||||||
box-shadow: 0 0 0 0 rgba(233, 69, 96, 0);
|
|
||||||
background: rgba(233, 69, 96, 0.05);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
box-shadow: 0 0 8px 2px rgba(233, 69, 96, 0.3);
|
|
||||||
background: rgba(233, 69, 96, 0.12);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-highlight-item {
|
|
||||||
animation: rpg-highlight-pulse 1.2s ease-in-out 2 !important;
|
|
||||||
border-left: 3px solid #e94560 !important;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card actions container for grid view */
|
|
||||||
.rpg-card-actions {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.25rem;
|
|
||||||
right: 0.25rem;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
STRUCTURED INVENTORY ITEMS (name + description)
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-row .rpg-item-info {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-row .rpg-item-name {
|
|
||||||
font-weight: 600;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-description {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-style: italic;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-description:empty::before {
|
|
||||||
content: "No description";
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-card {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0.75rem;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-card .rpg-item-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.25rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-card .rpg-item-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-inventory-container.rpg-structured .rpg-item-card .rpg-item-description {
|
|
||||||
font-size: 0.8em;
|
|
||||||
opacity: 0.7;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grants skill badge */
|
|
||||||
.rpg-item-grants-badge {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
background: rgba(var(--rpg-highlight-rgb, 233, 69, 96), 0.2);
|
|
||||||
border-radius: 50%;
|
|
||||||
color: var(--rpg-highlight);
|
|
||||||
font-size: 0.65rem;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Storage location styling */
|
|
||||||
.rpg-storage-location {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-location-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
background: var(--SmartThemeBlurTintColor);
|
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-location-name {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-location-count {
|
|
||||||
font-size: 0.85em;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
STRUCTURED SKILLS (name + description)
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.rpg-item-row.rpg-structured {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skill ability row layout */
|
|
||||||
.rpg-item-row.rpg-structured {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-ability-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-ability-row .rpg-item-name {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
font-weight: 600;
|
|
||||||
min-width: 60px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Link badge should shrink before the skill name */
|
|
||||||
.rpg-skill-ability-row .rpg-skill-link-badge {
|
|
||||||
flex-shrink: 1;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-ability-desc-row {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-ability-desc-row .rpg-item-description {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-style: italic;
|
|
||||||
min-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-skill-ability-desc-row .rpg-item-description:empty::before {
|
|
||||||
content: "Add description...";
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-card.rpg-structured .rpg-skill-ability-desc-row {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-card.rpg-structured .rpg-item-description {
|
|
||||||
font-size: 0.8em;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inventory item row layout with descriptions */
|
|
||||||
.rpg-item-row {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: stretch;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-main-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-main-row .rpg-item-name {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-desc-row {
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-desc-row .rpg-item-description {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-style: italic;
|
|
||||||
min-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-desc-row .rpg-item-description:empty::before {
|
|
||||||
content: "Add description...";
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-item-card .rpg-item-desc-row {
|
|
||||||
text-align: center;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================
|
|
||||||
STRUCTURED QUESTS (name + description)
|
|
||||||
============================================ */
|
|
||||||
|
|
||||||
.rpg-quest-item.rpg-structured {
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-quest-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-quest-info .rpg-quest-title {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-quest-description {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: var(--SmartThemeBodyColor);
|
|
||||||
opacity: 0.7;
|
|
||||||
font-style: italic;
|
|
||||||
min-height: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-quest-description:empty::before {
|
|
||||||
content: "Add description...";
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rpg-quest-description.rpg-editable {
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|||||||
-106
@@ -48,22 +48,11 @@
|
|||||||
<!-- Divider after Thoughts -->
|
<!-- Divider after Thoughts -->
|
||||||
<div id="rpg-divider-thoughts" class="rpg-divider"></div>
|
<div id="rpg-divider-thoughts" class="rpg-divider"></div>
|
||||||
|
|
||||||
<!-- Skills Section -->
|
|
||||||
<div id="rpg-skills" class="rpg-section rpg-skills-section">
|
|
||||||
<!-- Content will be populated by JavaScript -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Divider after Skills -->
|
|
||||||
<div id="rpg-divider-skills" class="rpg-divider"></div>
|
|
||||||
|
|
||||||
<!-- Inventory Section -->
|
<!-- Inventory Section -->
|
||||||
<div id="rpg-inventory" class="rpg-section rpg-inventory-section">
|
<div id="rpg-inventory" class="rpg-section rpg-inventory-section">
|
||||||
<!-- Content will be populated by JavaScript -->
|
<!-- Content will be populated by JavaScript -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Divider after Inventory -->
|
|
||||||
<div id="rpg-divider-inventory" class="rpg-divider"></div>
|
|
||||||
|
|
||||||
<!-- Quests Section -->
|
<!-- Quests Section -->
|
||||||
<div id="rpg-quests" class="rpg-section rpg-quests-section">
|
<div id="rpg-quests" class="rpg-section rpg-quests-section">
|
||||||
<!-- Content will be populated by JavaScript -->
|
<!-- Content will be populated by JavaScript -->
|
||||||
@@ -198,43 +187,6 @@
|
|||||||
<span data-i18n-key="template.settingsModal.display.showInventory">Show Inventory</span>
|
<span data-i18n-key="template.settingsModal.display.showInventory">Show Inventory</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="checkbox_label" style="margin-left: 24px;">
|
|
||||||
<input type="checkbox" id="rpg-toggle-simplified-inventory" />
|
|
||||||
<span data-i18n-key="template.settingsModal.display.useSimplifiedInventory">Use Simplified Inventory</span>
|
|
||||||
</label>
|
|
||||||
<small style="display: block; margin-left: 48px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.useSimplifiedInventoryNote">
|
|
||||||
Single flat list instead of On Person / Stored / Assets categories
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<label class="checkbox_label">
|
|
||||||
<input type="checkbox" id="rpg-toggle-skills" />
|
|
||||||
<span data-i18n-key="template.settingsModal.display.showSkills">Show Skills Section</span>
|
|
||||||
</label>
|
|
||||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.showSkillsNote">
|
|
||||||
Displays skills as a separate tab instead of within Status. Configure skills in Edit Trackers.
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<label class="checkbox_label" style="margin-left: 24px;">
|
|
||||||
<input type="checkbox" id="rpg-toggle-item-skill-links" />
|
|
||||||
<span data-i18n-key="template.settingsModal.display.enableItemSkillLinks">Enable Item-Skill Links</span>
|
|
||||||
</label>
|
|
||||||
<small style="display: block; margin-left: 48px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.enableItemSkillLinksNote">
|
|
||||||
Items can grant skills. Removing an item unlinks or removes the skill.
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<label class="checkbox_label" style="margin-left: 48px;">
|
|
||||||
<input type="checkbox" id="rpg-toggle-delete-skill-with-item" />
|
|
||||||
<span data-i18n-key="template.settingsModal.display.deleteSkillWithItem">Delete skill when item removed</span>
|
|
||||||
</label>
|
|
||||||
<small style="display: block; margin-left: 72px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.deleteSkillWithItemNote">
|
|
||||||
When disabled, removing an item just unlinks the skill. When enabled, the skill is deleted.
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<label class="checkbox_label">
|
|
||||||
<input type="checkbox" id="rpg-toggle-quests" />
|
|
||||||
<span data-i18n-key="template.settingsModal.display.showQuests">Show Quests</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="checkbox_label">
|
<label class="checkbox_label">
|
||||||
<input type="checkbox" id="rpg-toggle-thoughts-in-chat" />
|
<input type="checkbox" id="rpg-toggle-thoughts-in-chat" />
|
||||||
<span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span>
|
<span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span>
|
||||||
@@ -294,20 +246,6 @@
|
|||||||
<small data-i18n-key="template.settingsModal.advanced.contextMessagesNote">Number of recent messages to include (Separate mode only)</small>
|
<small data-i18n-key="template.settingsModal.advanced.contextMessagesNote">Number of recent messages to include (Separate mode only)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="checkbox_label">
|
|
||||||
<input type="checkbox" id="rpg-toggle-message-interception" />
|
|
||||||
<span data-i18n-key="template.settingsModal.advanced.enableMessageInterception">Intercept & rewrite user messages with AI</span>
|
|
||||||
</label>
|
|
||||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.enableMessageInterceptionNote">
|
|
||||||
When enabled, user messages are sent to the AI along with current RPG state and recent chat, then rewritten in-place.
|
|
||||||
</small>
|
|
||||||
|
|
||||||
<div class="rpg-setting-row">
|
|
||||||
<label for="rpg-message-interception-depth" data-i18n-key="template.settingsModal.advanced.messageInterceptionDepth">Interception Context Messages:</label>
|
|
||||||
<input type="number" id="rpg-message-interception-depth" min="1" max="20" value="4" class="rpg-input" />
|
|
||||||
<small data-i18n-key="template.settingsModal.advanced.messageInterceptionDepthNote">How many recent messages to send with the interception prompt.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="rpg-setting-row">
|
<div class="rpg-setting-row">
|
||||||
<label for="rpg-memory-messages" data-i18n-key="template.settingsModal.advanced.memoryBatchSize">Memory Batch Size:</label>
|
<label for="rpg-memory-messages" data-i18n-key="template.settingsModal.advanced.memoryBatchSize">Memory Batch Size:</label>
|
||||||
<input type="number" id="rpg-memory-messages" min="4" max="50" value="16" class="rpg-input" />
|
<input type="number" id="rpg-memory-messages" min="4" max="50" value="16" class="rpg-input" />
|
||||||
@@ -334,28 +272,6 @@
|
|||||||
When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.
|
When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
<!-- Custom Tracker Prompt Editor -->
|
|
||||||
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
|
|
||||||
<label for="rpg-custom-tracker-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customTrackerPromptTitle">
|
|
||||||
<i class="fa-solid fa-scroll" aria-hidden="true"></i> Custom Tracker Prompt:
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<textarea id="rpg-custom-tracker-prompt"
|
|
||||||
style="width: 100%; min-height: 150px; padding: 10px; border-radius: 4px;
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor); background: var(--SmartThemeBlurTintColor);
|
|
||||||
color: var(--SmartThemeBodyColor); font-family: 'Courier New', monospace; font-size: 12px;
|
|
||||||
resize: vertical; line-height: 1.5;"
|
|
||||||
placeholder=""></textarea>
|
|
||||||
<div style="margin-top: 8px; display: flex; gap: 8px;">
|
|
||||||
<button id="rpg-restore-default-tracker-prompt" class="menu_button" style="flex: 1;">
|
|
||||||
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i> <span data-i18n-key="template.settingsModal.advanced.restoreDefaultTrackerPrompt">Restore Default</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.customTrackerPromptNote">
|
|
||||||
Customize the instructions sent to the AI for generating tracker data. Use {{user}} as a placeholder for the user's name. This is the main prompt that tells the AI how to format and update the RPG trackers.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Custom HTML Prompt Editor -->
|
<!-- Custom HTML Prompt Editor -->
|
||||||
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
|
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
|
||||||
<label for="rpg-custom-html-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customHtmlPromptTitle">
|
<label for="rpg-custom-html-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customHtmlPromptTitle">
|
||||||
@@ -378,28 +294,6 @@
|
|||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Custom Message Interception Prompt Editor -->
|
|
||||||
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
|
|
||||||
<label for="rpg-custom-message-interception-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customMessageInterceptionPromptTitle">
|
|
||||||
<i class="fa-solid fa-pen-to-square" aria-hidden="true"></i> Custom Message Interception Prompt:
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<textarea id="rpg-custom-message-interception-prompt"
|
|
||||||
style="width: 100%; min-height: 120px; padding: 10px; border-radius: 4px;
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor); background: var(--SmartThemeBlurTintColor);
|
|
||||||
color: var(--SmartThemeBodyColor); font-family: 'Courier New', monospace; font-size: 12px;
|
|
||||||
resize: vertical; line-height: 1.5;"
|
|
||||||
placeholder=""></textarea>
|
|
||||||
<div style="margin-top: 8px; display: flex; gap: 8px;">
|
|
||||||
<button id="rpg-restore-default-message-interception-prompt" class="menu_button" style="flex: 1;">
|
|
||||||
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i> <span data-i18n-key="template.settingsModal.advanced.restoreDefaultMessageInterceptionPrompt">Restore Default</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.customMessageInterceptionPromptNote">
|
|
||||||
Customize the instructions sent to the AI when rewriting user messages. Leave empty to use the default guidance. The AI receives this prompt, the current RPG state JSON, and the recent messages you specify above.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Clear Cache Button -->
|
<!-- Clear Cache Button -->
|
||||||
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
|
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
|
||||||
<button id="rpg-clear-cache" class="rpg-btn-clear-cache">
|
<button id="rpg-clear-cache" class="rpg-btn-clear-cache">
|
||||||
|
|||||||
Reference in New Issue
Block a user