Fix tracker issues and add deprecation notice

This commit is contained in:
Spicy_Marinara
2026-05-04 13:08:52 +02:00
parent 70792f8a2a
commit 38fb3d8c51
11 changed files with 423 additions and 42 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"MD013": false
}
+11 -4
View File
@@ -11,7 +11,7 @@ An immersive RPG extension for browsers that tracks character stats, scene infor
Moving on to developing the Marinara Engine frontend, the extension will now be maintained by the community! Moving on to developing the Marinara Engine frontend, the extension will now be maintained by the community!
https://github.com/Pasta-Devs/Marinara-Engine <https://github.com/Pasta-Devs/Marinara-Engine>
## 📥 Installation ## 📥 Installation
@@ -21,7 +21,7 @@ https://github.com/Pasta-Devs/Marinara-Engine
3. Go to Install extension 3. Go to Install extension
4. Copy-paste this link: https://github.com/SpicyMarinara/rpg-companion-sillytavern 4. Copy-paste this link: <https://github.com/SpicyMarinara/rpg-companion-sillytavern>
5. Press Install for all users/Install just for me 5. Press Install for all users/Install just for me
@@ -99,11 +99,13 @@ AI: Trackers + Full roleplay response
↓ Main chat shows clean roleplay text ↓ Main chat shows clean roleplay text
Pros: Pros:
- Single API call - Single API call
- Faster response - Faster response
- Simpler setup - Simpler setup
Cons: Cons:
- Tracker formatting mixed in AI response - Tracker formatting mixed in AI response
- May affect roleplay quality slightly - May affect roleplay quality slightly
@@ -127,11 +129,13 @@ AI: Separate call with just the tracker data
↓ Context summary injected into the next generation ↓ Context summary injected into the next generation
Pros: Pros:
- Clean roleplay responses - Clean roleplay responses
- Better roleplay quality - Better roleplay quality
- Contextual summary enhances immersion - Contextual summary enhances immersion
Cons: Cons:
- Extra API call - Extra API call
- Slightly slower - Slightly slower
@@ -163,16 +167,19 @@ You can edit most fields by clicking on them:
Access comprehensive customization through the Tracker Settings button: Access comprehensive customization through the Tracker Settings button:
**User Stats Configuration:** **User Stats Configuration:**
- Add/remove custom stats with unique names - Add/remove custom stats with unique names
- Configure Status section (mood emoji + custom fields) - Configure Status section (mood emoji + custom fields)
- Configure Skills section with custom skill fields - Configure Skills section with custom skill fields
- Toggle RPG attributes display - Toggle RPG attributes display
**Info Box Configuration:** **Info Box Configuration:**
- Enable/disable individual widgets (Date, Weather, Temperature, Time, Location, Recent Events) - Enable/disable individual widgets (Date, Weather, Temperature, Time, Location, Recent Events)
- Choose temperature unit (Celsius/Fahrenheit) - Choose temperature unit (Celsius/Fahrenheit)
**Present Characters Configuration:** **Present Characters Configuration:**
- Add custom character fields (appearance, action, demeanor, etc.) - Add custom character fields (appearance, action, demeanor, etc.)
- Configure relationship status options - Configure relationship status options
- Enable character-specific stats tracking - Enable character-specific stats tracking
@@ -199,11 +206,11 @@ This extension detects when a "guided generation" prompt is submitted (for examp
If you want tracker prompts to apply during a guided generation, run the update via separate generation or temporarily disable guided generation in the other extension. If you want tracker prompts to apply during a guided generation, run the update via separate generation or temporarily disable guided generation in the other extension.
There is a new setting "Skip Tracker & HTML Injections during Guided Generations" in the RPG Companion settings (Advanced section). It now supports three modes: There is a new setting "Skip Tracker & HTML Injections during Guided Generations" in the RPG Companion settings (Advanced section). It now supports three modes:
- none: never skip (always inject the tracker prompts as usual, default) - none: never skip (always inject the tracker prompts as usual, default)
- impersonation: only skip when an impersonation-style guided generation is detected - impersonation: only skip when an impersonation-style guided generation is detected
- guided: skip whenever a guided `instruct` or `quiet_prompt` generation is detected - guided: skip whenever a guided `instruct` or `quiet_prompt` generation is detected
## 🎨 Themes ## 🎨 Themes
Choose from 6 beautiful themes: Choose from 6 beautiful themes:
@@ -286,4 +293,4 @@ SpicyMarinara, Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDea
Made with ❤️ by Marinara Made with ❤️ by Marinara
PS I'm looking for a job or a sponsor to fund my custom AI frontend, contact me if interested: PS I'm looking for a job or a sponsor to fund my custom AI frontend, contact me if interested:
mgrabower97@gmail.com [mgrabower97@gmail.com](mailto:mgrabower97@gmail.com)
+8 -4
View File
@@ -95,7 +95,8 @@ import {
updateDiceDisplay, updateDiceDisplay,
addDiceQuickReply, addDiceQuickReply,
getSettingsModal, getSettingsModal,
showWelcomeModalIfNeeded showWelcomeModalIfNeeded,
showDeprecationModalIfNeeded
} from './src/systems/ui/modals.js'; } from './src/systems/ui/modals.js';
import { import {
initTrackerEditor initTrackerEditor
@@ -1511,11 +1512,14 @@ jQuery(async () => {
// Non-critical - continue without it // Non-critical - continue without it
} }
// Show welcome modal for v3.0 on first launch // Show deprecation notice once for this release; otherwise keep the old welcome flow.
try { try {
showWelcomeModalIfNeeded(); const deprecationModalShown = showDeprecationModalIfNeeded();
if (!deprecationModalShown) {
showWelcomeModalIfNeeded();
}
} catch (error) { } catch (error) {
console.error('[RPG Companion] Welcome modal failed:', error); console.error('[RPG Companion] Startup modal failed:', error);
// Non-critical - continue without it // Non-critical - continue without it
} }
+1 -1
View File
@@ -6,6 +6,6 @@
"js": "index.js", "js": "index.js",
"css": "style.css", "css": "style.css",
"author": "Marinara", "author": "Marinara",
"version": "3.7.3", "version": "3.7.4",
"homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern" "homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern"
} }
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "rpg-complanion-sillytavern", "name": "rpg-complanion-sillytavern",
"version": "3.7.3", "version": "3.7.4",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
+279 -10
View File
@@ -3,7 +3,7 @@
* Handles saving/loading extension settings and chat data * Handles saving/loading extension settings and chat data
*/ */
import { saveSettingsDebounced, chat_metadata, saveChatDebounced } from '../../../../../../script.js'; import { saveSettingsDebounced, chat_metadata, saveChatDebounced, getCurrentChatId } from '../../../../../../script.js';
import { getContext } from '../../../../../extensions.js'; import { getContext } from '../../../../../extensions.js';
import { import {
extensionSettings, extensionSettings,
@@ -23,6 +23,245 @@ import { validateStoredInventory, cleanItemString } from '../utils/security.js';
import { migrateToV3JSON } from '../utils/jsonMigration.js'; import { migrateToV3JSON } from '../utils/jsonMigration.js';
const extensionName = 'third-party/rpg-companion-sillytavern'; const extensionName = 'third-party/rpg-companion-sillytavern';
const CURRENT_SETTINGS_VERSION = 5;
const DEFAULT_USER_STATS = {
health: 100,
satiety: 100,
energy: 100,
hygiene: 100,
arousal: 0,
mood: '😐',
conditions: 'None',
skills: [],
inventory: {
version: 2,
onPerson: "None",
clothing: "None",
stored: {},
assets: "None"
}
};
const DEFAULT_EXTENSION_SETTINGS = cloneSerializable(extensionSettings);
DEFAULT_EXTENSION_SETTINGS.settingsVersion = CURRENT_SETTINGS_VERSION;
let hasDeferredChatDataSave = false;
function cloneSerializable(value) {
if (value === undefined) {
return undefined;
}
try {
return structuredClone(value);
} catch {
return JSON.parse(JSON.stringify(value));
}
}
function isPlainObject(value) {
return !!value && typeof value === 'object' && !Array.isArray(value);
}
function mergeWithDefaults(defaultValue, savedValue) {
if (savedValue === undefined) {
return cloneSerializable(defaultValue);
}
if (isPlainObject(defaultValue) && isPlainObject(savedValue)) {
const merged = cloneSerializable(defaultValue);
for (const [key, value] of Object.entries(savedValue)) {
merged[key] = mergeWithDefaults(defaultValue[key], value);
}
return merged;
}
return cloneSerializable(savedValue);
}
function parseMaybeJSON(value) {
if (typeof value !== 'string') {
return value;
}
const trimmed = value.trim();
if (!trimmed || (!trimmed.startsWith('{') && !trimmed.startsWith('['))) {
return value;
}
try {
return JSON.parse(trimmed);
} catch {
return value;
}
}
function stringifyInventoryItems(items) {
if (typeof items === 'string') {
return items.trim() || 'None';
}
if (!Array.isArray(items)) {
return 'None';
}
const text = items
.map(item => {
if (isPlainObject(item) && item.name) {
const quantity = Number(item.quantity);
return quantity > 1 ? `${quantity}x ${item.name}` : item.name;
}
return String(item || '').trim();
})
.filter(Boolean)
.join(', ');
return text || 'None';
}
function normalizeStoredInventory(stored) {
if (!isPlainObject(stored)) {
return {};
}
const normalized = {};
for (const [location, items] of Object.entries(stored)) {
normalized[location] = stringifyInventoryItems(items);
}
return normalized;
}
function normalizeInventoryValue(inventory) {
const parsedInventory = parseMaybeJSON(inventory);
if (isPlainObject(parsedInventory) && (
Array.isArray(parsedInventory.onPerson)
|| Array.isArray(parsedInventory.clothing)
|| Array.isArray(parsedInventory.assets)
|| isPlainObject(parsedInventory.stored)
)) {
return {
version: 2,
onPerson: stringifyInventoryItems(parsedInventory.onPerson),
clothing: stringifyInventoryItems(parsedInventory.clothing),
stored: normalizeStoredInventory(parsedInventory.stored),
assets: stringifyInventoryItems(parsedInventory.assets)
};
}
const migrationResult = migrateInventory(parsedInventory);
return mergeWithDefaults(DEFAULT_USER_STATS.inventory, migrationResult.inventory);
}
function normalizeUserStatsValue(userStats) {
const parsedStats = parseMaybeJSON(userStats);
const normalized = cloneSerializable(DEFAULT_USER_STATS);
if (!isPlainObject(parsedStats)) {
return normalized;
}
if (Array.isArray(parsedStats.stats)) {
for (const stat of parsedStats.stats) {
if (!stat || typeof stat !== 'object') continue;
const id = stat.id || stat.name?.toLowerCase?.();
if (id && stat.value !== undefined) {
normalized[id] = stat.value;
}
}
} else {
for (const [key, value] of Object.entries(parsedStats)) {
if (!['stats', 'status', 'inventory', 'quests'].includes(key) && value !== undefined) {
normalized[key] = cloneSerializable(value);
}
}
}
if (isPlainObject(parsedStats.status)) {
for (const [key, value] of Object.entries(parsedStats.status)) {
if (value !== undefined) {
normalized[key] = cloneSerializable(value);
}
}
}
if (parsedStats.inventory !== undefined) {
normalized.inventory = normalizeInventoryValue(parsedStats.inventory);
}
for (const [key, defaultValue] of Object.entries(DEFAULT_USER_STATS)) {
if (typeof defaultValue !== 'number') continue;
const numericValue = Number(normalized[key]);
normalized[key] = Number.isFinite(numericValue) ? numericValue : defaultValue;
}
return mergeWithDefaults(DEFAULT_USER_STATS, normalized);
}
function normalizeQuestValue(quest) {
let value = quest;
while (isPlainObject(value) && value.value !== undefined) {
value = value.value;
}
if (typeof value === 'string') {
return value.trim() || 'None';
}
if (isPlainObject(value)) {
return value.title || value.description || JSON.stringify(value);
}
return value == null ? 'None' : String(value);
}
function normalizeQuestsValue(quests) {
if (!isPlainObject(quests)) {
return { main: 'None', optional: [] };
}
const optionalSource = Array.isArray(quests.optional)
? quests.optional
: (Array.isArray(quests.active) ? quests.active : []);
return {
main: normalizeQuestValue(quests.main),
optional: optionalSource
.map(normalizeQuestValue)
.filter(quest => quest && quest !== 'None')
};
}
function normalizeSettings(savedSettings) {
const sourceSettings = isPlainObject(savedSettings) ? savedSettings : {};
const normalized = mergeWithDefaults(DEFAULT_EXTENSION_SETTINGS, sourceSettings);
const savedVersion = Number(sourceSettings.settingsVersion);
normalized.settingsVersion = Number.isFinite(savedVersion) && savedVersion > 0 ? savedVersion : 1;
normalized.userStats = normalizeUserStatsValue(sourceSettings.userStats);
const parsedUserStats = parseMaybeJSON(sourceSettings.userStats);
if (sourceSettings.quests !== undefined) {
normalized.quests = normalizeQuestsValue(sourceSettings.quests);
} else if (isPlainObject(parsedUserStats) && parsedUserStats.quests !== undefined) {
normalized.quests = normalizeQuestsValue(parsedUserStats.quests);
}
return {
settings: normalized,
changed: JSON.stringify(normalized) !== JSON.stringify(savedSettings)
};
}
function isChatDataSaveReady() {
return !!(
chat_metadata
&& typeof chat_metadata === 'object'
&& chat_metadata.integrity
&& getCurrentChatId()
);
}
function hasTrackerPayload(payload) { function hasTrackerPayload(payload) {
return !!(payload && typeof payload === 'object' && ( return !!(payload && typeof payload === 'object' && (
@@ -273,7 +512,8 @@ function validateSettings(settings) {
// Check for required top-level properties // Check for required top-level properties
if (typeof settings.enabled !== 'boolean' || if (typeof settings.enabled !== 'boolean' ||
typeof settings.autoUpdate !== 'boolean' || typeof settings.autoUpdate !== 'boolean' ||
!settings.userStats || typeof settings.userStats !== 'object') { !settings.userStats || typeof settings.userStats !== 'object' ||
Array.isArray(settings.userStats)) {
console.warn('[RPG Companion] Settings validation failed: missing required properties'); console.warn('[RPG Companion] Settings validation failed: missing required properties');
return false; return false;
} }
@@ -282,7 +522,8 @@ function validateSettings(settings) {
const stats = settings.userStats; const stats = settings.userStats;
if (typeof stats.health !== 'number' || if (typeof stats.health !== 'number' ||
typeof stats.satiety !== 'number' || typeof stats.satiety !== 'number' ||
typeof stats.energy !== 'number') { typeof stats.energy !== 'number' ||
!stats.inventory || typeof stats.inventory !== 'object') {
console.warn('[RPG Companion] Settings validation failed: invalid userStats structure'); console.warn('[RPG Companion] Settings validation failed: invalid userStats structure');
return false; return false;
} }
@@ -307,21 +548,23 @@ export function loadSettings() {
if (extension_settings[extensionName]) { if (extension_settings[extensionName]) {
const savedSettings = extension_settings[extensionName]; const savedSettings = extension_settings[extensionName];
const normalizedResult = normalizeSettings(savedSettings);
const normalizedSettings = normalizedResult.settings;
// Validate loaded settings // Validate loaded settings after schema repair/normalization
if (!validateSettings(savedSettings)) { if (!validateSettings(normalizedSettings)) {
console.warn('[RPG Companion] Loaded settings failed validation, using defaults'); console.warn('[RPG Companion] Loaded settings failed validation, using defaults');
console.warn('[RPG Companion] Invalid settings:', savedSettings); console.warn('[RPG Companion] Invalid settings:', normalizedSettings);
// Save valid defaults to replace corrupt data // Save valid defaults to replace corrupt data
saveSettings(); saveSettings();
return; return;
} }
updateExtensionSettings(savedSettings); updateExtensionSettings(normalizedSettings);
// Perform settings migrations based on version // Perform settings migrations based on version
const currentVersion = extensionSettings.settingsVersion || 1; const currentVersion = extensionSettings.settingsVersion || 1;
let settingsChanged = false; let settingsChanged = normalizedResult.changed;
// Migration to version 2: Enable dynamic weather for existing users // Migration to version 2: Enable dynamic weather for existing users
if (currentVersion < 2) { if (currentVersion < 2) {
@@ -455,7 +698,8 @@ export function saveSettings() {
* Saves RPG data to the current chat's metadata. * Saves RPG data to the current chat's metadata.
*/ */
export function saveChatData() { export function saveChatData() {
if (!chat_metadata) { if (!isChatDataSaveReady()) {
hasDeferredChatDataSave = true;
return; return;
} }
@@ -480,6 +724,16 @@ export function saveChatData() {
saveChatDebounced(); saveChatDebounced();
} }
export function flushDeferredChatDataSave() {
if (!hasDeferredChatDataSave || !isChatDataSaveReady()) {
return false;
}
hasDeferredChatDataSave = false;
saveChatData();
return true;
}
/** /**
* Mirrors a tracker data entry into message.swipe_info so it survives page reloads. * Mirrors a tracker data entry into message.swipe_info so it survives page reloads.
* ST only serializes swipe_info to disk; message.extra is in-memory only. * ST only serializes swipe_info to disk; message.extra is in-memory only.
@@ -711,6 +965,7 @@ export function loadChatData() {
inventory: { inventory: {
version: 2, version: 2,
onPerson: "None", onPerson: "None",
clothing: "None",
stored: {}, stored: {},
assets: "None" assets: "None"
} }
@@ -830,6 +1085,7 @@ function validateInventoryStructure(inventory, source) {
extensionSettings.userStats.inventory = { extensionSettings.userStats.inventory = {
version: 2, version: 2,
onPerson: "None", onPerson: "None",
clothing: "None",
stored: {}, stored: {},
assets: "None" assets: "None"
}; };
@@ -861,6 +1117,20 @@ function validateInventoryStructure(inventory, source) {
} }
} }
// Validate clothing field
if (typeof inventory.clothing !== 'string') {
console.warn(`[RPG Companion] Invalid clothing from ${source}, resetting to "None"`);
inventory.clothing = "None";
needsSave = true;
} else {
const cleanedClothing = cleanItemString(inventory.clothing);
if (cleanedClothing !== inventory.clothing) {
console.warn(`[RPG Companion] Cleaned corrupted items from clothing inventory (${source})`);
inventory.clothing = cleanedClothing;
needsSave = true;
}
}
// Validate stored field (CRITICAL for Bug #3) // 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`);
@@ -1559,4 +1829,3 @@ export function importPresets(importData, overwrite = false) {
return importCount; return importCount;
} }
+16 -20
View File
@@ -10,7 +10,7 @@
* Extension settings - persisted to SillyTavern settings * Extension settings - persisted to SillyTavern settings
*/ */
export let extensionSettings = { export let extensionSettings = {
settingsVersion: 4, // Version number for settings migrations settingsVersion: 5, // Version number for settings migrations
enabled: true, enabled: true,
autoUpdate: false, autoUpdate: false,
updateDepth: 4, // How many messages to include in the context updateDepth: 4, // How many messages to include in the context
@@ -108,27 +108,23 @@ export let extensionSettings = {
stats: { enabled: true }, // All stats as compact numbers stats: { enabled: true }, // All stats as compact numbers
attributes: { enabled: true } // Compact RPG attributes display attributes: { enabled: true } // Compact RPG attributes display
}, },
userStats: JSON.stringify({ userStats: {
stats: [ health: 100,
{ id: 'health', name: 'Health', value: 100 }, satiety: 100,
{ id: 'satiety', name: 'Satiety', value: 100 }, energy: 100,
{ id: 'energy', name: 'Energy', value: 100 }, hygiene: 100,
{ id: 'hygiene', name: 'Hygiene', value: 100 }, arousal: 0,
{ id: 'arousal', name: 'Arousal', value: 0 } mood: '😐',
], conditions: 'None',
status: { skills: [],
mood: '😐',
conditions: 'None'
},
inventory: { inventory: {
onPerson: [], version: 2,
stored: [] onPerson: "None",
}, clothing: "None",
quests: { stored: {},
active: [], assets: "None"
completed: []
} }
}, null, 2), },
statNames: { statNames: {
health: 'Health', health: 'Health',
satiety: 'Satiety', satiety: 'Satiety',
+4 -1
View File
@@ -33,7 +33,8 @@ import {
setMessageSwipeTrackerData, setMessageSwipeTrackerData,
getSwipeData, getSwipeData,
commitTrackerDataFromPriorMessage, commitTrackerDataFromPriorMessage,
inheritSwipeDataFromPriorMessage inheritSwipeDataFromPriorMessage,
flushDeferredChatDataSave
} from '../../core/persistence.js'; } from '../../core/persistence.js';
import { i18n } from '../../core/i18n.js'; import { i18n } from '../../core/i18n.js';
@@ -389,6 +390,7 @@ export function onChatLoaded() {
restoreOrRepairLatestTrackerState(); restoreOrRepairLatestTrackerState();
maybeRehydrateUserStatsFromDisplayData(); maybeRehydrateUserStatsFromDisplayData();
rerenderRpgState(); rerenderRpgState();
flushDeferredChatDataSave();
scheduleChatStateRehydration(); scheduleChatStateRehydration();
updateAllCheckpointIndicators(); updateAllCheckpointIndicators();
} }
@@ -658,6 +660,7 @@ export function onCharacterChanged() {
// Load chat-specific data when switching chats // Load chat-specific data when switching chats
loadChatData(); loadChatData();
flushDeferredChatDataSave();
// chat_metadata may not reflect the actual chat tail for branches, so // chat_metadata may not reflect the actual chat tail for branches, so
// loadChatData() may have just restored stale data from the parent chat. // loadChatData() may have just restored stale data from the parent chat.
+1 -1
View File
@@ -219,7 +219,7 @@ export function renderOptionalQuestsView(optionalQuests) {
* Main render function for quests * Main render function for quests
*/ */
export function renderQuests() { export function renderQuests() {
if (!extensionSettings.showInventory || !$questsContainer) { if (!extensionSettings.showQuests || !$questsContainer) {
return; return;
} }
+63
View File
@@ -612,6 +612,28 @@ export function showWelcomeModalIfNeeded() {
} }
} }
/**
* Shows the deprecation notice once for users updating to the deprecation release.
* @returns {boolean} True when the modal was displayed.
*/
export function showDeprecationModalIfNeeded() {
const DEPRECATION_NOTICE_VERSION = '3.7.4';
const STORAGE_KEY = 'rpg_companion_deprecation_notice_seen';
try {
const seenVersion = localStorage.getItem(STORAGE_KEY);
if (seenVersion !== DEPRECATION_NOTICE_VERSION) {
showDeprecationModal(DEPRECATION_NOTICE_VERSION, STORAGE_KEY);
return true;
}
} catch (error) {
console.error('[RPG Companion] Failed to check deprecation modal status:', error);
}
return false;
}
/** /**
* Shows the welcome modal * Shows the welcome modal
* @param {string} version - The version to mark as seen * @param {string} version - The version to mark as seen
@@ -663,3 +685,44 @@ function showWelcomeModal(version, storageKey) {
} }
}, { once: true }); }, { once: true });
} }
function showDeprecationModal(version, storageKey) {
const modal = document.getElementById('rpg-deprecation-modal');
if (!modal) {
console.error('[RPG Companion] Deprecation modal element not found');
return;
}
const theme = extensionSettings.theme || 'default';
modal.setAttribute('data-theme', theme);
modal.style.display = 'flex';
modal.classList.add('is-open');
const closeBtn = document.getElementById('rpg-deprecation-close');
const gotItBtn = document.getElementById('rpg-deprecation-got-it');
const closeModal = () => {
modal.classList.add('is-closing');
setTimeout(() => {
modal.style.display = 'none';
modal.classList.remove('is-open', 'is-closing');
}, 200);
try {
localStorage.setItem(storageKey, version);
} catch (error) {
console.error('[RPG Companion] Failed to save deprecation modal status:', error);
}
};
closeBtn?.addEventListener('click', closeModal, { once: true });
gotItBtn?.addEventListener('click', closeModal, { once: true });
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal();
}
}, { once: true });
}
+36
View File
@@ -212,6 +212,42 @@
</div> </div>
</div> </div>
<!-- Deprecation Notice Modal -->
<div id="rpg-deprecation-modal" class="rpg-settings-popup" role="dialog" aria-modal="true"
aria-labelledby="rpg-deprecation-title" style="display: none;">
<div class="rpg-settings-popup-content" style="max-width: 640px;">
<header class="rpg-settings-popup-header">
<h3 id="rpg-deprecation-title">
<i class="fa-solid fa-circle-info"></i>
<span data-i18n-key="deprecation.title">RPG Companion becomes deprecated!</span>
</h3>
<button id="rpg-deprecation-close" class="rpg-popup-close" type="button"
aria-label="Close Deprecation Notice">
<i class="fa-solid fa-times"></i>
</button>
</header>
<div class="rpg-settings-popup-body" style="max-height: 520px; overflow-y: auto; padding: 20px;">
<p data-i18n-key="deprecation.body.support">Thank you all for the continuous support. The extension will continue to function in its current state and will receive occasional bug fixes/features if provided by the community members. However, I (Marinara) won't be actively developing it further.</p>
<p>Why? The reason is simple, <strong data-i18n-key="deprecation.body.reasonEmphasis">I am no longer using SillyTavern as a frontend, and have instead moved on to develop my own frontend called MarinaraEngine.</strong> It's free, open-source, and plug-and-play, centered around utilizing agents, and already has all the RPG Companion's features built in and comes with a multitude of other, custom features (such as different chat modes, for Discord-styled conversations, classic Roleplay, and a brand new game mode that offers you an RPG with VN-style visuals).</p>
<p data-i18n-key="deprecation.linkIntro">If you're interested, check it out here:</p>
<p>
<a href="https://github.com/Pasta-Devs/Marinara-Engine" target="_blank" rel="noopener noreferrer" class="menu_button" style="display: inline-block;">
<i class="fa-brands fa-github"></i> MarinaraEngine
</a>
</p>
<p style="margin-top: 24px;"><strong><em data-i18n-key="deprecation.signoff">Cheers and happy gooning!</em></strong></p>
</div>
<footer class="rpg-settings-popup-footer">
<button id="rpg-deprecation-got-it" class="rpg-btn-primary" type="button" style="width: 100%;">
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.gotIt">Got it!</span>
</button>
</footer>
</div>
</div>
<!-- Settings Modal --> <!-- Settings Modal -->
<div id="rpg-settings-popup" class="rpg-settings-popup" role="dialog" aria-modal="true" <div id="rpg-settings-popup" class="rpg-settings-popup" role="dialog" aria-modal="true"
aria-labelledby="rpg-settings-title"> aria-labelledby="rpg-settings-title">