Add holiday promotion, snowflakes effect, and Spotify music widget

- Added holiday promotion banner with 2026WITHMARINARA discount code
- Added dismiss functionality for promotion with persistent state
- Implemented snowflakes animation effect with toggle
- Added Spotify music widget above chat input
- Widget matches extension theme colors and positioning
- Added Display Options toggles to show/hide feature toggles
- Improved responsive design and mobile support
This commit is contained in:
Spicy_Marinara
2025-12-30 20:56:38 +01:00
parent 51535c5fdc
commit 3f58c7ceca
17 changed files with 1170 additions and 199 deletions
+9 -3
View File
@@ -12,18 +12,21 @@ import {
isGenerating,
lastActionWasSwipe,
setIsGenerating,
setLastActionWasSwipe
setLastActionWasSwipe,
$musicPlayerContainer
} from '../../core/state.js';
import { saveChatData } from '../../core/persistence.js';
import {
generateSeparateUpdatePrompt
} from './promptBuilder.js';
import { parseResponse, parseUserStats } from './parser.js';
import { parseAndStoreSpotifyUrl } from '../features/musicPlayer.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';
import { renderMusicPlayer } from '../rendering/musicPlayer.js';
import { i18n } from '../../core/i18n.js';
import { generateAvatarsForCharacters } from '../features/avatarGenerator.js';
@@ -121,7 +124,7 @@ export async function testExternalAPIConnection() {
if (!baseUrl || !apiKey || !model) {
return {
success: false,
message: !apiKey
message: !apiKey
? 'API Key not found. Please re-enter it in settings (keys are stored locally per-browser).'
: 'Please fill in all required fields (Base URL, API Key, and Model)'
};
@@ -267,6 +270,8 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
if (response) {
// console.log('[RPG Companion] Raw AI response:', response);
const parsedData = parseResponse(response);
// Parse and store Spotify URL if feature is enabled
parseAndStoreSpotifyUrl(response);
// console.log('[RPG Companion] Parsed data:', parsedData);
// console.log('[RPG Companion] parsedData.userStats:', parsedData.userStats ? parsedData.userStats.substring(0, 100) + '...' : 'null');
@@ -346,6 +351,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
renderThoughts();
renderInventory();
renderQuests();
renderMusicPlayer($musicPlayerContainer[0]);
// Save to chat metadata
saveChatData();
@@ -356,7 +362,7 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
const charactersNeedingAvatars = parseCharactersFromThoughts(parsedData.characterThoughts);
if (charactersNeedingAvatars.length > 0) {
console.log('[RPG Companion] Generating avatars for:', charactersNeedingAvatars);
// Generate avatars - this awaits completion
await generateAvatarsForCharacters(charactersNeedingAvatars, (names) => {
// Callback when generation starts - re-render to show loading spinners
+32 -1
View File
@@ -19,7 +19,9 @@ import {
generateTrackerExample,
generateTrackerInstructions,
generateContextualSummary,
DEFAULT_HTML_PROMPT
DEFAULT_HTML_PROMPT,
DEFAULT_SPOTIFY_PROMPT,
SPOTIFY_FORMAT_INSTRUCTION
} from './promptBuilder.js';
import { restoreCheckpointOnLoad } from '../features/chapterCheckpoint.js';
@@ -67,6 +69,7 @@ export async function onGenerationStarted(type, data) {
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-spotify', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
}
@@ -230,6 +233,19 @@ export async function onGenerationStarted(type, data) {
// Clear HTML prompt if disabled
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
}
// Inject Spotify prompt separately at depth 0 if enabled
if (extensionSettings.enableSpotifyMusic && !shouldSuppress) {
// Use custom Spotify prompt if set, otherwise use default
const spotifyPromptText = extensionSettings.customSpotifyPrompt || DEFAULT_SPOTIFY_PROMPT;
const spotifyPrompt = `\n${spotifyPromptText} ${SPOTIFY_FORMAT_INSTRUCTION}`;
setExtensionPrompt('rpg-companion-spotify', spotifyPrompt, extension_prompt_types.IN_CHAT, 0, false);
// console.log('[RPG Companion] Injected Spotify prompt at depth 0 for together mode');
} else {
// Clear Spotify prompt if disabled
setExtensionPrompt('rpg-companion-spotify', '', extension_prompt_types.IN_CHAT, 0, false);
}
} else if (extensionSettings.generationMode === 'separate') {
// In SEPARATE mode, inject the contextual summary for main roleplay generation
const contextSummary = generateContextualSummary();
@@ -266,6 +282,19 @@ Ensure these details naturally reflect and influence the narrative. Character be
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
}
// Inject Spotify prompt separately at depth 0 if enabled
if (extensionSettings.enableSpotifyMusic && !shouldSuppress) {
// Use custom Spotify prompt if set, otherwise use default
const spotifyPromptText = extensionSettings.customSpotifyPrompt || DEFAULT_SPOTIFY_PROMPT;
const spotifyPrompt = `\n${spotifyPromptText} ${SPOTIFY_FORMAT_INSTRUCTION}`;
setExtensionPrompt('rpg-companion-spotify', spotifyPrompt, extension_prompt_types.IN_CHAT, 0, false);
// console.log('[RPG Companion] Injected Spotify prompt at depth 0 for separate mode');
} else {
// Clear Spotify prompt if disabled
setExtensionPrompt('rpg-companion-spotify', '', extension_prompt_types.IN_CHAT, 0, false);
}
// Clear together mode injections
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
@@ -274,5 +303,7 @@ Ensure these details naturally reflect and influence the narrative. Character be
setExtensionPrompt('rpg-companion-inject', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-example', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-context', '', extension_prompt_types.IN_CHAT, 1, false);
setExtensionPrompt('rpg-companion-html', '', extension_prompt_types.IN_CHAT, 0, false);
setExtensionPrompt('rpg-companion-spotify', '', extension_prompt_types.IN_CHAT, 0, false);
}
}
+24
View File
@@ -16,6 +16,16 @@ import { extensionSettings, committedTrackerData, FEATURE_FLAGS } from '../../co
*/
export const DEFAULT_HTML_PROMPT = `If appropriate, include inline HTML, CSS, and JS segments whenever they enhance visual storytelling (e.g., for in-world screens, posters, books, letters, signs, crests, labels, etc.). Style them to match the setting's theme (e.g., fantasy, sci-fi), keep the text readable, and embed all assets directly (using inline SVGs only with no external scripts, libraries, or fonts). Use these elements freely and naturally within the narrative as characters would encounter them, including animations, 3D effects, pop-ups, dropdowns, websites, and so on. Do not wrap the HTML/CSS/JS in code fences!`;
/**
* Default Spotify music prompt text (customizable by users)
*/
export const DEFAULT_SPOTIFY_PROMPT = `If appropriate for the current scene's mood and atmosphere, suggest a song that fits the ambiance. Choose music that enhances the emotional tone, setting, or action of the scene.`;
/**
* Spotify format instruction (constant, not editable by users)
*/
export const SPOTIFY_FORMAT_INSTRUCTION = `Include it in this exact format: <spotify:Song Title - Artist Name/>.`;
/**
* Gets character card information for current chat (handles both single and group chats)
* @returns {string} Formatted character information
@@ -445,6 +455,20 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
instructions += htmlPrompt;
}
// Append Spotify music prompt if enabled AND includeHtmlPrompt is true
if (extensionSettings.enableSpotifyMusic && includeHtmlPrompt) {
// Add separator
if (hasAnyTrackers || extensionSettings.enableHtmlPrompt) {
instructions += `\n\n`;
} else {
instructions += `\n`;
}
// Use custom Spotify prompt if set, otherwise use default
const spotifyPrompt = extensionSettings.customSpotifyPrompt || DEFAULT_SPOTIFY_PROMPT;
instructions += spotifyPrompt + ' ' + SPOTIFY_FORMAT_INSTRUCTION;
}
return instructions;
}