From 32c4f6782260ad59e4c8b03da03a95acbf0937ef Mon Sep 17 00:00:00 2001 From: Spicy_Marinara Date: Sun, 1 Feb 2026 14:42:00 +0100 Subject: [PATCH] v3.7.1: Weather keywords, character stat editing fix, scroll bug fix, avatar layout - Improved weather generation: Added hard templates for weather keywords to ensure LLM generates valid weather patterns that match dynamic effects - Fixed character stat editing bug: Now properly handles array format stats from LLM (values no longer revert on blur) - Fixed scroll/viewport bug: Mobile-only scrollIntoView prevents page jumping on desktop when editing fields - Changed Present Characters avatar display: Avatar now aligned with name in header row, fields take full width below - Updated descriptions and labels --- README.md | 13 +++-- manifest.json | 2 +- settings.html | 2 +- src/i18n/en.json | 2 +- src/systems/generation/jsonPromptHelpers.js | 7 ++- src/systems/rendering/thoughts.js | 36 +++++++++---- src/systems/ui/mobile.js | 5 ++ src/systems/ui/weatherEffects.js | 60 ++++++++++++++++++++- style.css | 28 ++++++---- 9 files changed, 123 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6f53155..25457ab 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,13 @@ An immersive RPG extension for browsers that tracks character stats, scene infor ## 🆕 What's New -### v3.7.0 +### v3.7.1 -- Added omniscience filter. -- Added new prompts available for customization. -- Added opacity to the color selector. -- Overwritten SillyTavern's dumb-ahh trim logic when joining prompts. -- Fixed custom attributes not allowing value increase/decrease. -- Various bug fixes. +- Improved instructions for the model to generate dynamic weather. +- Small fixes and updates to descriptions. +- Fixed a scroll/viewport bug that moved everything up after you edited fields. +- Changed the display of avatars for present characters. +- **Special thanks to all the other contributors for this project:** Paperboygold, Munimunigamer, Subarashimo, Lilminzyu, Claude, IDeathByte, Chungchandev, Joenunezb, Amauragis, Tomt610, and Jakstein! diff --git a/manifest.json b/manifest.json index fa03ec4..26ae263 100644 --- a/manifest.json +++ b/manifest.json @@ -6,6 +6,6 @@ "js": "index.js", "css": "style.css", "author": "Marinara", - "version": "3.7.0", + "version": "3.7.1", "homePage": "https://github.com/SpicyMarinara/rpg-companion-sillytavern" } diff --git a/settings.html b/settings.html index 58b1cdb..4acebed 100644 --- a/settings.html +++ b/settings.html @@ -49,7 +49,7 @@
- v3.7.0 + v3.7.1
diff --git a/src/i18n/en.json b/src/i18n/en.json index 4f16aa3..1dabbb4 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -159,7 +159,7 @@ "template.trackerEditorModal.presentCharactersTab.aiInstructionLabel": "AI Instruction:", "template.trackerEditorModal.presentCharactersTab.characterStatsTitle": "Character Stats", "template.trackerEditorModal.presentCharactersTab.trackCharacterStats": "Track Character Stats", - "template.trackerEditorModal.presentCharactersTab.characterStatsHint": "Create stats to track for each character (displayed as colored bars).", + "template.trackerEditorModal.presentCharactersTab.characterStatsHint": "Create stats to track for each character (displayed as colored numbers).", "template.trackerEditorModal.presentCharactersTab.addCharacterStatButton": "Add Character Stat", "template.mainPanel.title": "RPG Companion", "template.mainPanel.lastRoll": "Last Roll:", diff --git a/src/systems/generation/jsonPromptHelpers.js b/src/systems/generation/jsonPromptHelpers.js index 61c9d30..26478cf 100644 --- a/src/systems/generation/jsonPromptHelpers.js +++ b/src/systems/generation/jsonPromptHelpers.js @@ -5,6 +5,8 @@ import { extensionSettings, committedTrackerData } from '../../core/state.js'; import { getContext } from '../../../../../../extensions.js'; +import { getWeatherKeywordsAsPromptString } from '../ui/weatherEffects.js'; +import { i18n } from '../../core/i18n.js'; /** * Converts a field name to snake_case for use as JSON key @@ -132,7 +134,10 @@ export function buildInfoBoxJSONInstruction() { } if (widgets.weather?.enabled) { - instruction += (hasFields ? ',\n' : '') + ' "weather": {"emoji": "Weather Emoji", "forecast": "Forecast"}'; + // Get valid weather keywords for the current language to guide LLM generation + const currentLang = i18n.currentLanguage || 'en'; + const weatherHint = getWeatherKeywordsAsPromptString(currentLang); + instruction += (hasFields ? ',\n' : '') + ` "weather": {"emoji": "Weather Emoji", "forecast": "Forecast"} // ${weatherHint}`; hasFields = true; } diff --git a/src/systems/rendering/thoughts.js b/src/systems/rendering/thoughts.js index f6d5552..0876c78 100644 --- a/src/systems/rendering/thoughts.js +++ b/src/systems/rendering/thoughts.js @@ -492,17 +492,19 @@ export function renderThoughts() { html += `
-
- ${char.name} - ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''} +
+
+ ${char.name} + ${hasRelationshipEnabled ? `
${relationshipBadge}
` : ''} +
+
+ ${char.emoji} + ${char.name} + +
-
- ${char.emoji} - ${char.name} - -
`; // Render custom fields dynamically @@ -1060,11 +1062,25 @@ export function updateCharacterField(characterName, field, value) { // Check if it's a character stat const isStatField = enabledCharStats.findIndex(s => s.name === field) !== -1; if (isStatField) { - if (!char.stats) char.stats = {}; let numValue = parseInt(value.replace('%', '').trim()); if (isNaN(numValue)) numValue = 0; numValue = Math.max(0, Math.min(100, numValue)); - char.stats[field] = numValue; + + // Handle both array format (from LLM) and object format + if (Array.isArray(char.stats)) { + // Array format: [{name: "Health", value: 80}] + const statIndex = char.stats.findIndex(s => s.name === field); + if (statIndex !== -1) { + char.stats[statIndex].value = numValue; + } else { + // Stat not found in array - add it + char.stats.push({ name: field, value: numValue }); + } + } else { + // Object format: {Health: 80} or undefined + if (!char.stats) char.stats = {}; + char.stats[field] = numValue; + } } else { // It's a custom detail field - store in details object if (!char.details) char.details = {}; diff --git a/src/systems/ui/mobile.js b/src/systems/ui/mobile.js index 9a537d3..f018cf8 100644 --- a/src/systems/ui/mobile.js +++ b/src/systems/ui/mobile.js @@ -794,12 +794,17 @@ export function setupMobileKeyboardHandling() { /** * Handles focus on contenteditable fields to ensure they're visible when keyboard appears. * Uses smooth scrolling to bring focused field into view with proper padding. + * Only applies on mobile viewports where virtual keyboard can obscure content. */ export function setupContentEditableScrolling() { const $panel = $('#rpg-companion-panel'); // Use event delegation for all contenteditable fields $panel.on('focusin', '[contenteditable="true"]', function(e) { + // Only apply scrolling behavior on mobile (where virtual keyboard appears) + const isMobile = window.innerWidth <= 1000; + if (!isMobile) return; + const $field = $(this); // Small delay to let keyboard animate in diff --git a/src/systems/ui/weatherEffects.js b/src/systems/ui/weatherEffects.js index 0bef359..21e3139 100644 --- a/src/systems/ui/weatherEffects.js +++ b/src/systems/ui/weatherEffects.js @@ -107,7 +107,8 @@ function getCurrentTime() { // Patterns for specific weather conditions (order matters - combined effects first) // Grouped by languages for easy editing -const WEATHER_PATTERNS_BY_LANGUAGE = { +// EXPORTED: Used by jsonPromptHelpers.js to provide valid weather keywords to LLM +export const WEATHER_PATTERNS_BY_LANGUAGE = { en: [ { id: "blizzard", patterns: [ "blizzard" ] }, // Snow + Wind { id: "storm", patterns: [ "storm", "thunder", "lightning" ] }, // Rain + Lightning @@ -130,6 +131,63 @@ const WEATHER_PATTERNS_BY_LANGUAGE = { ], } +/** + * Get valid weather keywords for LLM prompt injection. + * Returns weather patterns for specified language or all languages. + * This ensures LLM generates responses that exactly match our expected patterns. + * + * @param {string} [language] - Language code (e.g., 'en', 'ru'). If not specified, returns all languages. + * @returns {Object} Object with weather type IDs as keys and arrays of valid keywords as values + * @example + * // Returns: { blizzard: ["blizzard"], storm: ["storm", "thunder", "lightning"], ... } + * getWeatherKeywordsForPrompt('en'); + */ +export function getWeatherKeywordsForPrompt(language) { + const result = {}; + + // Get patterns for specified language or merge all languages + const languagesToProcess = language && WEATHER_PATTERNS_BY_LANGUAGE[language] + ? { [language]: WEATHER_PATTERNS_BY_LANGUAGE[language] } + : WEATHER_PATTERNS_BY_LANGUAGE; + + for (const [lang, patterns] of Object.entries(languagesToProcess)) { + for (const { id, patterns: keywords } of patterns) { + if (!result[id]) { + result[id] = []; + } + // Add keywords, avoiding duplicates + for (const keyword of keywords) { + if (!result[id].includes(keyword)) { + result[id].push(keyword); + } + } + } + } + + return result; +} + +/** + * Get weather keywords as a formatted string for LLM instructions. + * Provides a clear template showing valid weather forecast values. + * + * @param {string} [language] - Language code. If not specified, uses all available patterns. + * @returns {string} Formatted string for prompt injection + * @example + * // Returns: 'Valid forecast values: "blizzard", "storm", "thunder", "lightning", "wind", ...' + * getWeatherKeywordsAsPromptString('en'); + */ +export function getWeatherKeywordsAsPromptString(language) { + const keywords = getWeatherKeywordsForPrompt(language); + const allKeywords = []; + + for (const patterns of Object.values(keywords)) { + allKeywords.push(...patterns); + } + + return `Valid forecast values (use one of these exactly): ${allKeywords.map(k => `"${k}"`).join(', ')}`; +} + /** * Parse weather text to determine effect type */ diff --git a/style.css b/style.css index 4029010..68b01ef 100644 --- a/style.css +++ b/style.css @@ -2118,8 +2118,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld { /* Present Characters - Character Cards */ .rpg-character-card { display: flex; - align-items: flex-start; - gap: clamp(8px, 1vw, 12px); + flex-direction: column; + gap: clamp(6px, 0.8vh, 10px); padding: clamp(6px, 1vh, 8px); background: rgba(0, 0, 0, 0.3); border-radius: clamp(4px, 0.5vh, 6px); @@ -2157,6 +2157,14 @@ body:has(.rpg-panel.rpg-position-left) #sheld { border-color: var(--rpg-highlight); } +/* Header row with avatar and name */ +.rpg-character-header-row { + display: flex; + align-items: center; + gap: clamp(8px, 1vw, 12px); + width: 100%; +} + /* Character avatar container with relationship badge */ .rpg-character-avatar { position: relative; @@ -2164,8 +2172,8 @@ body:has(.rpg-panel.rpg-position-left) #sheld { } .rpg-character-avatar img { - width: clamp(35px, 6vh, 45px); - height: clamp(35px, 6vh, 45px); + width: clamp(30px, 5vh, 40px); + height: clamp(30px, 5vh, 40px); border-radius: 50%; border: 2px solid var(--rpg-highlight); object-fit: cover; @@ -2232,13 +2240,12 @@ body:has(.rpg-panel.rpg-position-left) #sheld { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } -/* Character info section */ +/* Character info section - now takes full width below header row */ .rpg-character-content { - flex: 1; - min-width: 0; + width: 100%; display: flex; flex-direction: column; - gap: 0; + gap: clamp(3px, 0.5vh, 5px); overflow: hidden; } @@ -2271,13 +2278,14 @@ body:has(.rpg-panel.rpg-position-left) #sheld { background: var(--rpg-highlight); } -/* Character header with emoji and name */ +/* Character header with emoji and name - now inside header row */ .rpg-character-header { display: flex; align-items: center; gap: clamp(4px, 0.5vw, 6px); flex-wrap: nowrap; /* Prevent wrapping */ - position: relative; + flex: 1; + min-width: 0; } .rpg-character-emoji {