Fix extension loading, enhance theming, add horizontal scrolling, improve emoji parsing, rename to Main Quests

This commit is contained in:
Spicy_Marinara
2025-10-26 22:31:21 +01:00
parent d68ddd601e
commit 141a3f4bec
17 changed files with 2888 additions and 437 deletions
+7
View File
@@ -17,6 +17,11 @@ import {
import { saveChatData } from '../../core/persistence.js';
import { generateSeparateUpdatePrompt } from './promptBuilder.js';
import { parseResponse, parseUserStats } from './parser.js';
import { renderUserStats } from '../rendering/userStats.js';
import { renderInfoBox } from '../rendering/infoBox.js';
import { renderThoughts } from '../rendering/thoughts.js';
import { renderInventory } from '../rendering/inventory.js';
import { renderQuests } from '../rendering/quests.js';
// Store the original preset name to restore after tracker generation
let originalPresetName = null;
@@ -192,6 +197,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
renderInfoBox();
renderThoughts();
renderInventory();
renderQuests();
} else {
// No assistant message to attach to - just update display
if (parsedData.userStats) {
@@ -201,6 +207,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
renderInfoBox();
renderThoughts();
renderInventory();
renderQuests();
}
// Save to chat metadata
+86 -7
View File
@@ -7,6 +7,45 @@ import { extensionSettings, FEATURE_FLAGS, addDebugLog } from '../../core/state.
import { saveSettings } from '../../core/persistence.js';
import { extractInventory } from './inventoryParser.js';
/**
* Helper to separate emoji from text in a string
* Handles cases where there's no comma or space after emoji
* @param {string} str - String potentially containing emoji followed by text
* @returns {{emoji: string, text: string}} Separated emoji and text
*/
function separateEmojiFromText(str) {
if (!str) return { emoji: '', text: '' };
str = str.trim();
// Regex to match emoji at the start (handles most emoji including compound ones)
// This matches emoji sequences including skin tones, gender modifiers, etc.
const emojiRegex = /^[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F000}-\u{1F02F}\u{1F0A0}-\u{1F0FF}\u{1F100}-\u{1F64F}\u{1F680}-\u{1F6FF}\u{1F910}-\u{1F96B}\u{1F980}-\u{1F9E0}\u{FE00}-\u{FE0F}\u{200D}\u{20E3}]+/u;
const emojiMatch = str.match(emojiRegex);
if (emojiMatch) {
const emoji = emojiMatch[0];
let text = str.substring(emoji.length).trim();
// Remove leading comma or space if present
text = text.replace(/^[,\s]+/, '');
return { emoji, text };
}
// No emoji found - check if there's a comma separator anyway
const commaParts = str.split(',');
if (commaParts.length >= 2) {
return {
emoji: commaParts[0].trim(),
text: commaParts.slice(1).join(',').trim()
};
}
// No clear separation - return original as text
return { emoji: '', text: str };
}
/**
* Helper to log to both console and debug logs array
*/
@@ -169,18 +208,36 @@ export function parseUserStats(statsText) {
// Format 2: Status: [Emoji], [Conditions]
// Format 3: [Emoji]: [Conditions] (legacy)
// Format 4: Mood: [Emoji] - [Conditions]
// Format 5: Status: [Emoji Conditions] (no separator - FIXED)
let moodMatch = null;
// Try new format: Status: emoji, conditions
const statusMatch = statsText.match(/Status:\s*(.+?),\s*(.+)/i);
// Try new format: Status: emoji, conditions OR Status: emojiConditions
const statusMatch = statsText.match(/Status:\s*(.+)/i);
if (statusMatch) {
moodMatch = [null, statusMatch[1].trim(), statusMatch[2].trim()];
const statusContent = statusMatch[1].trim();
const { emoji, text } = separateEmojiFromText(statusContent);
if (emoji && text) {
moodMatch = [null, emoji, text];
} else if (statusContent.includes(',')) {
// Fallback to comma split if emoji detection failed
const parts = statusContent.split(',').map(p => p.trim());
moodMatch = [null, parts[0], parts.slice(1).join(', ')];
}
}
// Try alternative: Mood: emoji, conditions
else {
const moodAltMatch = statsText.match(/Mood:\s*(.+?)[,\-]\s*(.+)/i);
// Try alternative: Mood: emoji, conditions OR Mood: emojiConditions
if (!moodMatch) {
const moodAltMatch = statsText.match(/Mood:\s*(.+)/i);
if (moodAltMatch) {
moodMatch = [null, moodAltMatch[1].trim(), moodAltMatch[2].trim()];
const moodContent = moodAltMatch[1].trim();
const { emoji, text } = separateEmojiFromText(moodContent);
if (emoji && text) {
moodMatch = [null, emoji, text];
} else if (moodContent.includes(',') || moodContent.includes('-')) {
// Fallback to comma/dash split if emoji detection failed
const parts = moodContent.split(/[,\-]/).map(p => p.trim());
moodMatch = [null, parts[0], parts.slice(1).join(', ')];
}
}
}
@@ -245,6 +302,28 @@ export function parseUserStats(statsText) {
extensionSettings.userStats.conditions = moodMatch[2].trim(); // Conditions
}
// Extract quests
const mainQuestMatch = statsText.match(/Main Quests?:\s*(.+)/i);
if (mainQuestMatch) {
extensionSettings.quests.main = mainQuestMatch[1].trim();
debugLog('[RPG Parser] Main quests extracted:', mainQuestMatch[1].trim());
}
const optionalQuestsMatch = statsText.match(/Optional Quests:\s*(.+)/i);
if (optionalQuestsMatch) {
const questsText = optionalQuestsMatch[1].trim();
if (questsText && questsText !== 'None') {
// Split by comma and clean up
extensionSettings.quests.optional = questsText
.split(',')
.map(q => q.trim())
.filter(q => q && q !== 'None');
} else {
extensionSettings.quests.optional = [];
}
debugLog('[RPG Parser] Optional quests extracted:', extensionSettings.quests.optional);
}
debugLog('[RPG Parser] Final userStats after parsing:', {
health: extensionSettings.userStats.health,
satiety: extensionSettings.userStats.satiety,
+44 -5
View File
@@ -122,9 +122,13 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
instructions += 'Assets: [Vehicles, property, major possessions, or "None"]\n';
} else {
// Legacy v1 format
instructions += 'Inventory: [Clothing/Armor, Inventory Items (list of important items/none)]\n';
instructions += 'Inventory: [Clothing/Armor, Inventory Items (list of important items, or "None")]\\n';
}
// Add quests section
instructions += 'Main Quests: [Short title of the currently active main quest (for example, "Save the world"), or "None"]\n';
instructions += 'Optional Quests: [Short titles of the currently active optional quests (for example, "Find Zandik\'s book"), or "None"]\n';
instructions += '```\n\n';
}
@@ -137,6 +141,7 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
instructions += 'Temperature: [Temperature in °C]\n';
instructions += 'Time: [Time Start → Time End]\n';
instructions += 'Location: [Location]\n';
instructions += 'Recent Events: [Up to three past events leading to the ongoing scene (short descriptors with no details, for example, "last-night date with Mary")]\n';
instructions += '```\n\n';
}
@@ -208,9 +213,23 @@ export function generateContextualSummary() {
if (stats.inventory) {
const inventorySummary = buildInventorySummary(stats.inventory);
if (inventorySummary && inventorySummary !== 'None') {
summary += `Inventory:\n${inventorySummary}\n`;
summary += `${inventorySummary}\n`;
}
}
// Add quests summary
if (extensionSettings.quests) {
if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') {
summary += `Main Quests: ${extensionSettings.quests.main}\n`;
}
if (extensionSettings.quests.optional && extensionSettings.quests.optional.length > 0) {
const optionalQuests = extensionSettings.quests.optional.filter(q => q && q !== 'None').join(', ');
if (optionalQuests) {
summary += `Optional Quests: ${optionalQuests}\n`;
}
}
}
// Include classic stats (attributes) and dice roll only if there was a dice roll
if (extensionSettings.lastDiceRoll) {
const classicStats = extensionSettings.classicStats;
@@ -224,7 +243,7 @@ export function generateContextualSummary() {
if (extensionSettings.showInfoBox && committedTrackerData.infoBox) {
// Parse info box data - support both new and legacy formats
const lines = committedTrackerData.infoBox.split('\n');
let date = '', weather = '', temp = '', time = '', location = '';
let date = '', weather = '', temp = '', time = '', location = '', recentEvents = '';
// console.log('[RPG Companion] 🔍 Parsing Info Box lines:', lines);
@@ -242,6 +261,8 @@ export function generateContextualSummary() {
time = line.replace('Time:', '').trim();
} else if (line.startsWith('Location:')) {
location = line.replace('Location:', '').trim();
} else if (line.startsWith('Recent Events:')) {
recentEvents = line.replace('Recent Events:', '').trim();
}
// Legacy format with emojis (for backward compatibility)
else if (line.includes('🗓️:')) {
@@ -264,7 +285,7 @@ export function generateContextualSummary() {
// console.log('[RPG Companion] 🔍 Parsed values - date:', date, 'weather:', weather, 'temp:', temp, 'time:', time, 'location:', location);
if (date || weather || temp || time || location) {
if (date || weather || temp || time || location || recentEvents) {
summary += `Information:\n`;
summary += `Scene: `;
if (date) summary += `${date}`;
@@ -272,7 +293,9 @@ export function generateContextualSummary() {
if (time) summary += ` | ${time}`;
if (weather) summary += ` | ${weather}`;
if (temp) summary += ` | ${temp}`;
summary += `\n\n`;
summary += `\n`;
if (recentEvents) summary += `Recent Events: ${recentEvents}\n`;
summary += `\n`;
}
}
@@ -317,6 +340,22 @@ export function generateRPGPromptText() {
} else {
promptText += `Last ${userName}'s Stats:\nNone - this is the first update.\n\n`;
}
// Add current quests to the previous data context
if (extensionSettings.quests) {
if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') {
promptText += `Main Quests: ${extensionSettings.quests.main}\n`;
} else {
promptText += `Main Quests: None\n`;
}
if (extensionSettings.quests.optional && extensionSettings.quests.optional.length > 0) {
const optionalQuests = extensionSettings.quests.optional.filter(q => q && q !== 'None').join(', ');
promptText += `Optional Quests: ${optionalQuests || 'None'}\n`;
} else {
promptText += `Optional Quests: None\n`;
}
promptText += `\n`;
}
}
if (extensionSettings.showInfoBox) {