diff --git a/Marinara's Spaghetti Recipe.json b/Marinara's Spaghetti Recipe.json
deleted file mode 100644
index 5f1fccb..0000000
--- a/Marinara's Spaghetti Recipe.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
- "chat_completion_source": "custom",
- "openai_model": "gpt-5-2025-08-07",
- "claude_model": "claude-sonnet-4-0",
- "openrouter_model": "anthropic/claude-sonnet-4.5",
- "openrouter_use_fallback": false,
- "openrouter_group_models": true,
- "openrouter_sort_models": "context_length",
- "openrouter_providers": [],
- "openrouter_allow_fallbacks": false,
- "openrouter_middleout": "off",
- "ai21_model": "jamba-1.5-large",
- "mistralai_model": "mistral-large-latest",
- "cohere_model": "command-r-plus",
- "perplexity_model": "",
- "groq_model": "llama3-70b-8192",
- "xai_model": "grok-3-beta",
- "pollinations_model": "openai",
- "aimlapi_model": "gpt-4o-mini-2024-07-18",
- "electronhub_model": "gpt-4o-mini",
- "electronhub_sort_models": "alphabetically",
- "electronhub_group_models": false,
- "moonshot_model": "kimi-latest",
- "fireworks_model": "accounts/fireworks/models/kimi-k2-instruct",
- "cometapi_model": "gpt-4o",
- "zai_model": "glm-4.6",
- "custom_model": "claude-opus-4-1-20250805-thinking-16k-v2",
- "custom_prompt_post_processing": "semi",
- "google_model": "gemini-2.0-flash-thinking-exp-01-21",
- "vertexai_model": "gemini-2.0-flash-001",
- "nanogpt_model": "gpt-4o-mini",
- "deepseek_model": "chatgpt-4o-latest",
- "azure_api_version": "2024-02-15-preview",
- "azure_openai_model": "",
- "temperature": 1,
- "frequency_penalty": 0,
- "presence_penalty": 0,
- "top_p": 1,
- "top_k": 0,
- "top_a": 1,
- "min_p": 0,
- "repetition_penalty": 1,
- "openai_max_context": 2000000,
- "openai_max_tokens": 8192,
- "wrap_in_quotes": false,
- "names_behavior": -1,
- "send_if_empty": "",
- "impersonation_prompt": "Change of plans! Write a response as the user's {{user}} now. Match their style from the conversation history so far.",
- "new_chat_prompt": "",
- "new_group_chat_prompt": "",
- "new_example_chat_prompt": "{{trim}}",
- "continue_nudge_prompt": "Your last message got cut off. Continue writing it. Do not include any parts of the original one. Respond with a direct continuation only.",
- "bias_preset_selected": "Marinara's Logit Bias",
- "max_context_unlocked": false,
- "wi_format": "{0}",
- "scenario_format": "{{scenario}}",
- "personality_format": "{{personality}}{{trim}}",
- "group_nudge_prompt": "",
- "stream_openai": true,
- "show_external_models": false,
- "assistant_prefill": "",
- "assistant_impersonation": "",
- "claude_use_sysprompt": true,
- "use_makersuite_sysprompt": false,
- "squash_system_messages": true,
- "image_inlining": true,
- "inline_image_quality": "high",
- "bypass_status_check": false,
- "continue_prefill": false,
- "seed": -1,
- "n": 1
-}
diff --git a/RPG Companion Regexes.json b/RPG Companion Regexes.json
deleted file mode 100644
index 544c5b0..0000000
--- a/RPG Companion Regexes.json
+++ /dev/null
@@ -1,30 +0,0 @@
-[
- {
- "scriptName": "RPG: Clean HTML (From Outgoing Prompt)",
- "findRegex": "/\\s?<(?!\\!--)(?:\"[^\"]*\"|'[^']*'|[^'\">])*>/g",
- "replaceString": "",
- "trimStrings": [],
- "placement": [2],
- "disabled": false,
- "markdownOnly": false,
- "promptOnly": true,
- "runOnEdit": true,
- "substituteRegex": 0,
- "minDepth": null,
- "maxDepth": null
- },
- {
- "scriptName": "RPG: Clean Tracker Blocks (Keeping Last)",
- "findRegex": "/```\\n[\\s\\S]*?\\n---\\n[\\s\\S]*?```\\n+/g",
- "replaceString": "",
- "trimStrings": [],
- "placement": [1, 2],
- "disabled": false,
- "markdownOnly": false,
- "promptOnly": true,
- "runOnEdit": true,
- "substituteRegex": 0,
- "minDepth": 3,
- "maxDepth": null
- }
-]
diff --git a/index.js b/index.js
index 3200bae..f311e44 100644
--- a/index.js
+++ b/index.js
@@ -118,9 +118,6 @@ import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/sy
import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js';
import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js';
-// Utility modules
-import { importAllDefaults } from './src/utils/importDefaults.js';
-
// Integration modules
import {
commitTrackerData,
@@ -604,14 +601,6 @@ jQuery(async () => {
// Non-critical - continue anyway
}
- // Import default preset and regexes if user doesn't have them
- try {
- await importAllDefaults();
- } catch (error) {
- console.error('[RPG Companion] Failed to import defaults:', error);
- // Non-critical - continue anyway
- }
-
// Register all event listeners
try {
registerAllEvents({
diff --git a/src/core/persistence.js b/src/core/persistence.js
index 426de9d..c3152a2 100644
--- a/src/core/persistence.js
+++ b/src/core/persistence.js
@@ -365,6 +365,7 @@ function migrateToTrackerConfig() {
extensionSettings.trackerConfig = {
userStats: {
customStats: [],
+ showRPGAttributes: true,
rpgAttributes: [
{ id: 'str', name: 'STR', enabled: true },
{ id: 'dex', name: 'DEX', enabled: true },
@@ -457,6 +458,11 @@ function migrateToTrackerConfig() {
];
}
+ // Ensure showRPGAttributes exists (defaults to true)
+ if (extensionSettings.trackerConfig.userStats.showRPGAttributes === undefined) {
+ extensionSettings.trackerConfig.userStats.showRPGAttributes = true;
+ }
+
// Ensure all rpgAttributes have corresponding values in classicStats
if (extensionSettings.classicStats) {
for (const attr of extensionSettings.trackerConfig.userStats.rpgAttributes) {
diff --git a/src/core/state.js b/src/core/state.js
index c115e3b..1a4c190 100644
--- a/src/core/state.js
+++ b/src/core/state.js
@@ -72,6 +72,7 @@ export let extensionSettings = {
{ id: 'arousal', name: 'Arousal', enabled: true }
],
// RPG Attributes (customizable D&D-style attributes)
+ showRPGAttributes: true,
rpgAttributes: [
{ id: 'str', name: 'STR', enabled: true },
{ id: 'dex', name: 'DEX', enabled: true },
diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js
index 68f1e5b..62be66c 100644
--- a/src/systems/generation/promptBuilder.js
+++ b/src/systems/generation/promptBuilder.js
@@ -56,6 +56,41 @@ export function buildInventorySummary(inventory) {
return 'None';
}
+/**
+ * Builds a dynamic attributes string based on configured RPG attributes.
+ * Uses custom attribute names and values from classicStats.
+ *
+ * @returns {string} Formatted attributes string (e.g., "STR 10, DEX 12, INT 15, LVL 5")
+ */
+function buildAttributesString() {
+ const trackerConfig = extensionSettings.trackerConfig;
+ const classicStats = extensionSettings.classicStats;
+ const userStatsConfig = trackerConfig?.userStats;
+
+ // Get enabled attributes from config
+ const rpgAttributes = userStatsConfig?.rpgAttributes || [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+
+ const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
+
+ // Build attributes string dynamically
+ const attributeParts = enabledAttributes.map(attr => {
+ const value = classicStats[attr.id] !== undefined ? classicStats[attr.id] : 10;
+ return `${attr.name} ${value}`;
+ });
+
+ // Add level at the end
+ attributeParts.push(`LVL ${extensionSettings.level}`);
+
+ return attributeParts.join(', ');
+}
+
/**
* Generates an example block showing current tracker states in markdown code blocks.
* Uses COMMITTED data (not displayed data) for generation context.
@@ -250,7 +285,8 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
// Include attributes and dice roll only if there was a dice roll
if (extensionSettings.lastDiceRoll) {
const roll = extensionSettings.lastDiceRoll;
- instructions += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`;
+ const attributesString = buildAttributesString();
+ instructions += `${userName}'s attributes: ${attributesString}\n`;
instructions += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeeded or failed the action they attempted.\n\n`;
}
}
@@ -327,9 +363,9 @@ export function generateContextualSummary() {
// Include attributes and dice roll only if there was a dice roll
if (extensionSettings.lastDiceRoll) {
- const classicStats = extensionSettings.classicStats;
const roll = extensionSettings.lastDiceRoll;
- summary += `${userName}'s attributes: STR ${classicStats.str}, DEX ${classicStats.dex}, CON ${classicStats.con}, INT ${classicStats.int}, WIS ${classicStats.wis}, CHA ${classicStats.cha}, LVL ${extensionSettings.level}\n`;
+ const attributesString = buildAttributesString();
+ summary += `${userName}'s attributes: ${attributesString}\n`;
summary += `${userName} rolled ${roll.total} on the last ${roll.formula} roll. Based on their attributes, decide whether they succeeded or failed the action they attempted.\n\n`;
}
diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js
index fa1fbc0..d263ed1 100644
--- a/src/systems/rendering/thoughts.js
+++ b/src/systems/rendering/thoughts.js
@@ -396,7 +396,7 @@ export function renderThoughts() {
const statColor = getStatColor(statValue, extensionSettings.statBarColorLow, extensionSettings.statBarColorHigh);
html += `
- ${stat.name}: ${statValue}%
+ ${stat.name}: ${statValue}%
`;
}
@@ -431,6 +431,7 @@ export function renderThoughts() {
const character = $(this).data('character');
const field = $(this).data('field');
const value = $(this).text().trim();
+ console.log('[RPG Companion] Character stat edit:', { character, field, value });
updateCharacterField(character, field, value);
});
@@ -492,10 +493,20 @@ export function updateCharacterField(characterName, field, value) {
}
if (characterFound) {
- // Update the specific field within the character block
+ // Check if we're updating a character stat
+ const isStatField = enabledCharStats.findIndex(s => s.name === field) !== -1;
+ let statsLineExists = false;
+ let statsLineIndex = -1;
+
+ // First pass: check if Stats line exists and update other fields
for (let i = characterStartIndex; i < characterEndIndex; i++) {
const line = lines[i].trim();
+ if (line.startsWith('Stats:')) {
+ statsLineExists = true;
+ statsLineIndex = i;
+ }
+
if (field === 'name' && line.startsWith('- ')) {
lines[i] = `- ${value}`;
}
@@ -519,20 +530,68 @@ export function updateCharacterField(characterName, field, value) {
const relationshipValue = emojiToRelationship[value] || value;
lines[i] = `Relationship: ${relationshipValue}`;
}
- else if (line.startsWith('Stats:')) {
- const statIndex = enabledCharStats.findIndex(s => s.name === field);
- if (statIndex !== -1) {
- const statsContent = line.substring(line.indexOf(':') + 1).trim();
- const statParts = statsContent.split('|').map(p => p.trim());
+ }
- for (let j = 0; j < statParts.length; j++) {
- if (statParts[j].startsWith(field + ':')) {
- statParts[j] = `${field}: ${value}%`;
- break;
- }
+ // Handle stat updates
+ if (isStatField) {
+ // Clean the value: remove % if present, parse as integer, clamp 0-100
+ let cleanValue = value.replace('%', '').trim();
+ let numValue = parseInt(cleanValue);
+ if (isNaN(numValue)) {
+ numValue = 0;
+ }
+ numValue = Math.max(0, Math.min(100, numValue));
+
+ console.log('[RPG Companion] Updating stat:', { field, rawValue: value, cleanValue, numValue });
+
+ if (statsLineExists) {
+ // Update existing Stats line
+ const line = lines[statsLineIndex];
+ const statsContent = line.substring(line.indexOf(':') + 1).trim();
+ const statParts = statsContent.split('|').map(p => p.trim());
+
+ let statFound = false;
+ for (let j = 0; j < statParts.length; j++) {
+ if (statParts[j].startsWith(field + ':')) {
+ statParts[j] = `${field}: ${numValue}%`;
+ statFound = true;
+ console.log('[RPG Companion] Updated stat part:', statParts[j]);
+ break;
}
- lines[i] = `Stats: ${statParts.join(' | ')}`;
}
+
+ // If stat wasn't found in existing parts, add it
+ if (!statFound) {
+ statParts.push(`${field}: ${numValue}%`);
+ console.log('[RPG Companion] Added new stat to existing line:', `${field}: ${numValue}%`);
+ }
+
+ lines[statsLineIndex] = `Stats: ${statParts.join(' | ')}`;
+ console.log('[RPG Companion] Updated stats line:', lines[statsLineIndex]);
+ } else {
+ // Create new Stats line with all enabled stats (defaulting to 0% except the one being edited)
+ const statsParts = enabledCharStats.map(s => {
+ if (s.name === field) {
+ return `${s.name}: ${numValue}%`;
+ }
+ return `${s.name}: 0%`;
+ });
+ const newStatsLine = `Stats: ${statsParts.join(' | ')}`;
+
+ // Insert before Thoughts line or at end of character block
+ let insertIndex = characterEndIndex;
+ for (let i = characterStartIndex; i < characterEndIndex; i++) {
+ const line = lines[i].trim();
+ const thoughtsFieldName = presentCharsConfig?.thoughts?.name || 'Thoughts';
+ if (line.startsWith(thoughtsFieldName + ':')) {
+ insertIndex = i;
+ break;
+ }
+ }
+
+ lines.splice(insertIndex, 0, newStatsLine);
+ console.log('[RPG Companion] Created new stats line:', newStatsLine);
+ characterEndIndex++; // Adjust end index since we inserted a line
}
}
} else {
@@ -554,7 +613,19 @@ export function updateCharacterField(characterName, field, value) {
}
if (enabledCharStats.length > 0) {
- const statsParts = enabledCharStats.map(s => `${s.name}: ${field === s.name ? value : '0'}%`);
+ const statsParts = enabledCharStats.map(s => {
+ if (field === s.name) {
+ // Clean the value: remove % if present, parse as integer, clamp 0-100
+ let cleanValue = value.replace('%', '').trim();
+ let numValue = parseInt(cleanValue);
+ if (isNaN(numValue)) {
+ numValue = 0;
+ }
+ numValue = Math.max(0, Math.min(100, numValue));
+ return `${s.name}: ${numValue}%`;
+ }
+ return `${s.name}: 0%`;
+ });
newCharacterLines.push(`Stats: ${statsParts.join(' | ')}`);
}
@@ -565,6 +636,8 @@ export function updateCharacterField(characterName, field, value) {
lastGeneratedData.characterThoughts = lines.join('\n');
committedTrackerData.characterThoughts = lines.join('\n');
+ console.log('[RPG Companion] Updated characterThoughts data:', lastGeneratedData.characterThoughts);
+
const chat = getContext().chat;
if (chat && chat.length > 0) {
for (let i = chat.length - 1; i >= 0; i--) {
diff --git a/src/systems/rendering/userStats.js b/src/systems/rendering/userStats.js
index a181ad2..97fad33 100644
--- a/src/systems/rendering/userStats.js
+++ b/src/systems/rendering/userStats.js
@@ -179,10 +179,22 @@ export function renderUserStats() {
html += ''; // Close rpg-stats-left
// RPG Attributes section (dynamically generated from config)
- const rpgAttributes = config.rpgAttributes || [];
- const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
+ // Check if RPG Attributes section is enabled
+ const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
- if (enabledAttributes.length > 0) {
+ if (showRPGAttributes) {
+ // Use attributes from config, with fallback to defaults if not configured
+ const rpgAttributes = (config.rpgAttributes && config.rpgAttributes.length > 0) ? config.rpgAttributes : [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ const enabledAttributes = rpgAttributes.filter(attr => attr && attr.enabled && attr.name && attr.id);
+
+ if (enabledAttributes.length > 0) {
html += `
@@ -208,6 +220,7 @@ export function renderUserStats() {
`;
+ }
}
html += ''; // Close rpg-stats-content
diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js
index c27f217..f872fab 100644
--- a/src/systems/ui/trackerEditor.js
+++ b/src/systems/ui/trackerEditor.js
@@ -127,6 +127,7 @@ function resetToDefaults() {
{ id: 'hygiene', name: 'Hygiene', enabled: true },
{ id: 'arousal', name: 'Arousal', enabled: true }
],
+ showRPGAttributes: true,
rpgAttributes: [
{ id: 'str', name: 'STR', enabled: true },
{ id: 'dex', name: 'DEX', enabled: true },
@@ -221,16 +222,31 @@ function renderUserStatsTab() {
// RPG Attributes section
html += ' RPG Attributes
';
+
+ // Enable/disable toggle for entire RPG Attributes section
+ const showRPGAttributes = config.showRPGAttributes !== undefined ? config.showRPGAttributes : true;
+ html += '';
+ html += ``;
+ html += '';
+ html += '
';
+
html += '';
- const rpgAttributes = config.rpgAttributes || [
- { id: 'str', name: 'STR', enabled: true },
- { id: 'dex', name: 'DEX', enabled: true },
- { id: 'con', name: 'CON', enabled: true },
- { id: 'int', name: 'INT', enabled: true },
- { id: 'wis', name: 'WIS', enabled: true },
- { id: 'cha', name: 'CHA', enabled: true }
- ];
+ // Ensure rpgAttributes exists in the actual config (not just local fallback)
+ if (!config.rpgAttributes || config.rpgAttributes.length === 0) {
+ config.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
+ // Save the defaults back to the actual config
+ extensionSettings.trackerConfig.userStats.rpgAttributes = config.rpgAttributes;
+ }
+
+ const rpgAttributes = config.rpgAttributes;
rpgAttributes.forEach((attr, index) => {
html += `
@@ -316,8 +332,16 @@ function setupUserStatsListeners() {
// Add attribute
$('#rpg-add-attr').off('click').on('click', function() {
- if (!extensionSettings.trackerConfig.userStats.rpgAttributes) {
- extensionSettings.trackerConfig.userStats.rpgAttributes = [];
+ // Ensure rpgAttributes array exists with defaults if needed
+ if (!extensionSettings.trackerConfig.userStats.rpgAttributes || extensionSettings.trackerConfig.userStats.rpgAttributes.length === 0) {
+ extensionSettings.trackerConfig.userStats.rpgAttributes = [
+ { id: 'str', name: 'STR', enabled: true },
+ { id: 'dex', name: 'DEX', enabled: true },
+ { id: 'con', name: 'CON', enabled: true },
+ { id: 'int', name: 'INT', enabled: true },
+ { id: 'wis', name: 'WIS', enabled: true },
+ { id: 'cha', name: 'CHA', enabled: true }
+ ];
}
const newId = 'attr_' + Date.now();
extensionSettings.trackerConfig.userStats.rpgAttributes.push({
@@ -351,6 +375,11 @@ function setupUserStatsListeners() {
extensionSettings.trackerConfig.userStats.rpgAttributes[index].name = $(this).val();
});
+ // Enable/disable RPG Attributes section toggle
+ $('#rpg-show-rpg-attrs').off('change').on('change', function() {
+ extensionSettings.trackerConfig.userStats.showRPGAttributes = $(this).is(':checked');
+ });
+
// Status section toggles
$('#rpg-status-enabled').off('change').on('change', function() {
extensionSettings.trackerConfig.userStats.statusSection.enabled = $(this).is(':checked');
diff --git a/style.css b/style.css
index 77fcec8..dd6e9ad 100644
--- a/style.css
+++ b/style.css
@@ -3682,6 +3682,36 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
opacity: 0.8;
}
+/* RPG Attributes editor styles (same as custom stats) */
+.rpg-attr-toggle {
+ flex-shrink: 0;
+}
+
+.rpg-attr-name {
+ flex: 1;
+ 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-attr-remove {
+ flex-shrink: 0;
+ padding: 0.375em 0.625em;
+ background: var(--rpg-highlight);
+ border: none;
+ border-radius: 0.25em;
+ color: white;
+ cursor: pointer;
+ transition: opacity 0.2s;
+}
+
+.rpg-attr-remove:hover {
+ opacity: 0.8;
+}
+
/* Toggle rows */
.rpg-editor-toggle-row {
display: flex;