Fix tracker issues and add deprecation notice
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"MD013": false
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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',
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user