diff --git a/index.js b/index.js index b471d48..9a97157 100644 --- a/index.js +++ b/index.js @@ -91,6 +91,9 @@ import { import { initTrackerEditor } from './src/systems/ui/trackerEditor.js'; +import { + initPromptsEditor +} from './src/systems/ui/promptsEditor.js'; import { initChapterCheckpointUI, injectCheckpointButton, @@ -374,18 +377,6 @@ async function initUI() { saveSettings(); }); - $('#rpg-custom-html-prompt').on('input', function() { - extensionSettings.customHtmlPrompt = $(this).val().trim(); - saveSettings(); - }); - - $('#rpg-restore-default-html-prompt').on('click', function() { - extensionSettings.customHtmlPrompt = ''; - $('#rpg-custom-html-prompt').val(''); - saveSettings(); - toastr.success('HTML prompt restored to default'); - }); - $('#rpg-skip-guided-mode').on('change', function() { extensionSettings.skipInjectionsForGuided = String($(this).val()); saveSettings(); @@ -639,7 +630,7 @@ async function initUI() { // Securely store API key in localStorage instead of shared extension settings const apiKey = String($(this).val()).trim(); localStorage.setItem('rpg_companion_external_api_key', apiKey); - + // Ensure the externalApiSettings object exists, but don't store the key in it if (!extensionSettings.externalApiSettings) { extensionSettings.externalApiSettings = { @@ -690,13 +681,13 @@ async function initUI() { const $result = $('#rpg-external-api-test-result'); const $btn = $(this); const originalText = $btn.html(); - + $btn.html(' Testing...').prop('disabled', true); $result.hide().removeClass('rpg-success-message rpg-error-message'); - + try { const result = await testExternalAPIConnection(); - + if (result.success) { $result.addClass('rpg-success-message') .html(` ${result.message}`) @@ -733,9 +724,6 @@ async function initUI() { $('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble); $('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt); - // Set default HTML prompt as actual text if no custom prompt exists - $('#rpg-custom-html-prompt').val(extensionSettings.customHtmlPrompt || DEFAULT_HTML_PROMPT); - $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-encounters').prop('checked', extensionSettings.encounterSettings?.enabled ?? true); $('#rpg-encounter-history-depth').val(extensionSettings.encounterSettings?.historyDepth ?? 8); @@ -778,11 +766,11 @@ async function initUI() { // Initialize External API settings values if (extensionSettings.externalApiSettings) { $('#rpg-external-base-url').val(extensionSettings.externalApiSettings.baseUrl || ''); - + // Load API Key from secure localStorage const storedApiKey = localStorage.getItem('rpg_companion_external_api_key') || ''; $('#rpg-external-api-key').val(storedApiKey); - + $('#rpg-external-model').val(extensionSettings.externalApiSettings.model || ''); $('#rpg-external-max-tokens').val(extensionSettings.externalApiSettings.maxTokens || 8192); $('#rpg-external-temperature').val(extensionSettings.externalApiSettings.temperature ?? 0.7); @@ -822,6 +810,7 @@ async function initUI() { setupClassicStatsButtons(); setupSettingsPopup(); initTrackerEditor(); + initPromptsEditor(); addDiceQuickReply(); setupPlotButtons(sendPlotProgression, openEncounterModal); setupMobileKeyboardHandling(); diff --git a/src/systems/features/plotProgression.js b/src/systems/features/plotProgression.js index 0fbf158..79a593c 100644 --- a/src/systems/features/plotProgression.js +++ b/src/systems/features/plotProgression.js @@ -100,9 +100,11 @@ export async function sendPlotProgression(type) { // Build the prompt based on type let prompt = ''; if (type === 'random') { - prompt = 'Actually, the scene is getting stale. Introduce {{random::stakes::a plot twist::a new character::a cataclysm::a fourth-wall-breaking joke::a sudden atmospheric phenomenon::a plot hook::a running gag::an ecchi scenario::Death from Discworld::a new stake::a drama::a conflict::an angered entity::a god::a vision::a prophetic dream::Il Dottore from Genshin Impact::a new development::a civilian in need::an emotional bit::a threat::a villain::an important memory recollection::a marriage proposal::a date idea::an angry horde of villagers with pitchforks::a talking animal::an enemy::a cliffhanger::a short omniscient POV shift to a completely different character::a quest::an unexpected revelation::a scandal::an evil clone::death of an important character::harm to an important character::a romantic setup::a gossip::a messenger::a plot point from the past::a plot hole::a tragedy::a ghost::an otherworldly occurrence::a plot device::a curse::a magic device::a rival::an unexpected pregnancy::a brothel::a prostitute::a new location::a past lover::a completely random thing::a what-if scenario::a significant choice::war::love::a monster::lewd undertones::Professor Mari::a travelling troupe::a secret::a fortune-teller::something completely different::a killer::a murder mystery::a mystery::a skill check::a deus ex machina::three raccoons in a trench coat::a pet::a slave::an orphan::a psycho::tentacles::"there is only one bed" trope::accidental marriage::a fun twist::a boss battle::sexy corn::an eldritch horror::a character getting hungry, thirsty, or exhausted::horniness::a need for a bathroom break need::someone fainting::an assassination attempt::a meta narration of this all being an out of hand DND session::a dungeon::a friend in need::an old friend::a small time skip::a scene shift::Aurora Borealis, at this time of year, at this time of day, at this part of the country::a grand ball::a surprise party::zombies::foreshadowing::a Spanish Inquisition (nobody expects it)::a natural plot progression}} to make things more interesting! Be creative, but stay grounded in the setting.'; + // Use custom prompt if set, otherwise use default + prompt = extensionSettings.customPlotRandomPrompt || 'Actually, the scene is getting stale. Introduce {{random::stakes::a plot twist::a new character::a cataclysm::a fourth-wall-breaking joke::a sudden atmospheric phenomenon::a plot hook::a running gag::an ecchi scenario::Death from Discworld::a new stake::a drama::a conflict::an angered entity::a god::a vision::a prophetic dream::Il Dottore from Genshin Impact::a new development::a civilian in need::an emotional bit::a threat::a villain::an important memory recollection::a marriage proposal::a date idea::an angry horde of villagers with pitchforks::a talking animal::an enemy::a cliffhanger::a short omniscient POV shift to a completely different character::a quest::an unexpected revelation::a scandal::an evil clone::death of an important character::harm to an important character::a romantic setup::a gossip::a messenger::a plot point from the past::a plot hole::a tragedy::a ghost::an otherworldly occurrence::a plot device::a curse::a magic device::a rival::an unexpected pregnancy::a brothel::a prostitute::a new location::a past lover::a completely random thing::a what-if scenario::a significant choice::war::love::a monster::lewd undertones::Professor Mari::a travelling troupe::a secret::a fortune-teller::something completely different::a killer::a murder mystery::a mystery::a skill check::a deus ex machina::three raccoons in a trench coat::a pet::a slave::an orphan::a psycho::tentacles::"there is only one bed" trope::accidental marriage::a fun twist::a boss battle::sexy corn::an eldritch horror::a character getting hungry, thirsty, or exhausted::horniness::a need for a bathroom break need::someone fainting::an assassination attempt::a meta narration of this all being an out of hand DND session::a dungeon::a friend in need::an old friend::a small time skip::a scene shift::Aurora Borealis, at this time of year, at this time of day, at this part of the country::a grand ball::a surprise party::zombies::foreshadowing::a Spanish Inquisition (nobody expects it)::a natural plot progression}} to make things more interesting! Be creative, but stay grounded in the setting.'; } else { - prompt = 'Actually, the scene is getting stale. Progress it, to make things more interesting! Reintroduce an unresolved plot point from the past, or push the story further towards the current main goal. Be creative, but stay grounded in the setting.'; + // Use custom prompt if set, otherwise use default + prompt = extensionSettings.customPlotNaturalPrompt || 'Actually, the scene is getting stale. Progress it, to make things more interesting! Reintroduce an unresolved plot point from the past, or push the story further towards the current main goal. Be creative, but stay grounded in the setting.'; } // Add HTML prompt if enabled diff --git a/src/systems/generation/encounterPrompts.js b/src/systems/generation/encounterPrompts.js index 538e6de..be4161e 100644 --- a/src/systems/generation/encounterPrompts.js +++ b/src/systems/generation/encounterPrompts.js @@ -513,11 +513,18 @@ export async function buildCombatActionPrompt(action, combatStats) { stateMessage += `If all enemies are defeated or escape: add "combatEnd": true, "result": "victory". If all party defeated: add "combatEnd": true, "result": "defeat". It's also possible for the encounter to be interrupted by external interference (e.g., an explosion knocks everyone out, sudden environmental catastrophe, third party intervention, etc.). If this occurs, add "combatEnd": true, "result": "interrupted". Each status (if applied) has a format: {"name": "Status Name", "emoji": "💀", "duration": X}.\n`; stateMessage += `Scale combat difficulty appropriately: Powerful entities (gods, dragons, legendary creatures) should be formidable challenges requiring multiple rounds and strategic play. Weaker foes (common animals, basic enemies, minions) should be resolved more quickly, typically 2-4 rounds. Match HP damage and combat pacing to the narrative weight of the encounter. A wolf should not take 20 rounds to defeat, nor should a deity fall in one hit.\n`; stateMessage += `For the narrative, write it with intent in ${tense} tense ${person}-person ${narration} from ${pov}'s point of view.\n`; - stateMessage += `Build novel prose. Break patterns from your previous responses by varying sentence structures, rhythms, formats, and openings. Track whether a sentence shape, cadence, sensory detail, or a descriptor appeared recently, and replace it with a different one or skip it entirely. If you mentioned a physical feature, mannerism, or expression once, move on. Don't fixate on the same traits every turn. Well-placed humor, unconventional similes, and subverted turns of phrase are encouraged. Avoid GPTisms, purple prose, and anaphoras, especially the negatives with "no/not/wasn't/didn't." Refrain from describing actions or emotions via negation; focus on what does happen, not what doesn't. Absolutely no asterisks, ellipses, or em-dashes. Explicit content is allowed, no plot armor. Do not play for ${userName}. Keep your response length under 150 words. Never end on handover cues; finish naturally.\n`; - stateMessage += `CRITICAL: Do not repeat, echo, parrot, or restate distinctive words, phrases, and dialogues from the user's last message. If reacting to speech, show interpretation or response, not repetition.\n`; - stateMessage += `EXAMPLE: "Are you a gooner?" User asks.\n`; - stateMessage += `BAD: "Gooner?"\n`; - stateMessage += `GOOD: A flat look. "What type of question is that?"`; + + // Use custom combat narrative prompt if available + const customCombatPrompt = extensionSettings.customCombatNarrativePrompt; + if (customCombatPrompt) { + stateMessage += customCombatPrompt.replace(/{userName}/g, userName) + '\n'; + } else { + stateMessage += `Build novel prose. Break patterns from your previous responses by varying sentence structures, rhythms, formats, and openings. Track whether a sentence shape, cadence, sensory detail, or a descriptor appeared recently, and replace it with a different one or skip it entirely. If you mentioned a physical feature, mannerism, or expression once, move on. Don't fixate on the same traits every turn. Well-placed humor, unconventional similes, and subverted turns of phrase are encouraged. Avoid GPTisms, purple prose, and anaphoras, especially the negatives with "no/not/wasn't/didn't." Refrain from describing actions or emotions via negation; focus on what does happen, not what doesn't. Absolutely no asterisks, ellipses, or em-dashes. Explicit content is allowed, no plot armor. Do not play for ${userName}. Keep your response length under 150 words. Never end on handover cues; finish naturally.\n`; + stateMessage += `CRITICAL: Do not repeat, echo, parrot, or restate distinctive words, phrases, and dialogues from the user's last message. If reacting to speech, show interpretation or response, not repetition.\n`; + stateMessage += `EXAMPLE: "Are you a gooner?" User asks.\n`; + stateMessage += `BAD: "Gooner?"\n`; + stateMessage += `GOOD: A flat look. "What type of question is that?"`; + } messages.push({ role: 'user', diff --git a/src/systems/generation/promptBuilder.js b/src/systems/generation/promptBuilder.js index 28cc9c8..6874e31 100644 --- a/src/systems/generation/promptBuilder.js +++ b/src/systems/generation/promptBuilder.js @@ -236,11 +236,20 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Universal instruction header if (useXmlTags) { - instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in XML tags. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs). -`; + // Format specification is always hardcoded + instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in XML tags. `; } else { - instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs). -`; + // Format specification is always hardcoded + instructions += `\nAt the start of every reply, you must attach an update to the trackers in EXACTLY the same format as below, enclosed in separate Markdown code fences. `; + } + + // Append custom instruction portion if available (same for both XML and Markdown) + const customPrompt = extensionSettings.customTrackerInstructionsPrompt; + if (customPrompt) { + instructions += customPrompt.replace(/{userName}/g, userName); + } else { + instructions += `Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that ${userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. `; + instructions += `Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user's actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs).`; } // Add format specifications for each enabled tracker @@ -394,7 +403,12 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon // Only add continuation instruction if includeContinuation is true if (includeContinuation) { - instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.\n\n`; + const customPrompt = extensionSettings.customTrackerContinuationPrompt; + if (customPrompt) { + instructions += customPrompt + '\n\n'; + } else { + instructions += `After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist's performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character's emotional state coloring their responses, and so on. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.\n\n`; + } } // Include attributes based on settings (only if includeAttributes is true) diff --git a/src/systems/ui/layout.js b/src/systems/ui/layout.js index b19d3de..bd77dd0 100644 --- a/src/systems/ui/layout.js +++ b/src/systems/ui/layout.js @@ -336,15 +336,24 @@ export function updateGenerationModeUI() { $('#rpg-manual-update').hide(); $('#rpg-external-api-settings').slideUp(200); $('#rpg-separate-mode-settings').slideUp(200); + // Disable auto-update toggle (not applicable in together mode) + $('#rpg-toggle-auto-update').prop('disabled', true); + $('#rpg-auto-update-container').css('opacity', '0.5'); } else if (extensionSettings.generationMode === 'separate') { // In "separate" mode, manual update button is visible $('#rpg-manual-update').show(); $('#rpg-external-api-settings').slideUp(200); $('#rpg-separate-mode-settings').slideDown(200); + // Enable auto-update toggle (only works in separate mode) + $('#rpg-toggle-auto-update').prop('disabled', false); + $('#rpg-auto-update-container').css('opacity', '1'); } else if (extensionSettings.generationMode === 'external') { // In "external" mode, manual update button is visible AND external settings are shown $('#rpg-manual-update').show(); $('#rpg-external-api-settings').slideDown(200); $('#rpg-separate-mode-settings').slideUp(200); + // Disable auto-update toggle (not applicable in external mode) + $('#rpg-toggle-auto-update').prop('disabled', true); + $('#rpg-auto-update-container').css('opacity', '0.5'); } } diff --git a/src/systems/ui/promptsEditor.js b/src/systems/ui/promptsEditor.js new file mode 100644 index 0000000..f46af8f --- /dev/null +++ b/src/systems/ui/promptsEditor.js @@ -0,0 +1,203 @@ +/** + * Prompts Editor Module + * Provides UI for customizing all AI prompts used in the extension + */ +import { extensionSettings } from '../../core/state.js'; +import { saveSettings } from '../../core/persistence.js'; +import { DEFAULT_HTML_PROMPT } from '../generation/promptBuilder.js'; + +let $editorModal = null; +let tempPrompts = null; // Temporary prompts for cancel functionality + +// Default prompts +const DEFAULT_PROMPTS = { + html: DEFAULT_HTML_PROMPT, + plotRandom: 'Actually, the scene is getting stale. Introduce {{random::stakes::a plot twist::a new character::a cataclysm::a fourth-wall-breaking joke::a sudden atmospheric phenomenon::a plot hook::a running gag::an ecchi scenario::Death from Discworld::a new stake::a drama::a conflict::an angered entity::a god::a vision::a prophetic dream::Il Dottore from Genshin Impact::a new development::a civilian in need::an emotional bit::a threat::a villain::an important memory recollection::a marriage proposal::a date idea::an angry horde of villagers with pitchforks::a talking animal::an enemy::a cliffhanger::a short omniscient POV shift to a completely different character::a quest::an unexpected revelation::a scandal::an evil clone::death of an important character::harm to an important character::a romantic setup::a gossip::a messenger::a plot point from the past::a plot hole::a tragedy::a ghost::an otherworldly occurrence::a plot device::a curse::a magic device::a rival::an unexpected pregnancy::a brothel::a prostitute::a new location::a past lover::a completely random thing::a what-if scenario::a significant choice::war::love::a monster::lewd undertones::Professor Mari::a travelling troupe::a secret::a fortune-teller::something completely different::a killer::a murder mystery::a mystery::a skill check::a deus ex machina::three raccoons in a trench coat::a pet::a slave::an orphan::a psycho::tentacles::"there is only one bed" trope::accidental marriage::a fun twist::a boss battle::sexy corn::an eldritch horror::a character getting hungry, thirsty, or exhausted::horniness::a need for a bathroom break need::someone fainting::an assassination attempt::a meta narration of this all being an out of hand DND session::a dungeon::a friend in need::an old friend::a small time skip::a scene shift::Aurora Borealis, at this time of year, at this time of day, at this part of the country::a grand ball::a surprise party::zombies::foreshadowing::a Spanish Inquisition (nobody expects it)::a natural plot progression}} to make things more interesting! Be creative, but stay grounded in the setting.', + plotNatural: 'Actually, the scene is getting stale. Progress it, to make things more interesting! Reintroduce an unresolved plot point from the past, or push the story further towards the current main goal. Be creative, but stay grounded in the setting.', + avatar: 'Create a detailed portrait prompt focusing on the character\'s appearance, clothing, and mood. Include appropriate artistic style keywords.', + trackerInstructions: 'Replace X with actual numbers (e.g., 69) and replace all [placeholders] with concrete in-world details that {userName} perceives about the current scene and the present characters. Do NOT keep the brackets or placeholder text in your response. For example: [Location] becomes Forest Clearing, [Mood Emoji] becomes 😊. Consider the last trackers in the conversation (if they exist). Manage them accordingly and realistically; raise, lower, change, or keep the values unchanged based on the user\'s actions, the passage of time, and logical consequences (0% if the time progressed only by a few minutes, 1-5% normally, and above 5% only if a major time-skip/event occurs).', + trackerContinuation: 'After updating the trackers, continue directly from where the last message in the chat history left off. Ensure the trackers you provide naturally reflect and influence the narrative. Character behavior, dialogue, and story events should acknowledge these conditions when relevant, such as fatigue affecting the protagonist\'s performance, low hygiene influencing their social interactions, environmental factors shaping the scene, a character\'s emotional state coloring their responses, and so on. Remember, all bracketed placeholders (e.g., [Location], [Mood Emoji]) MUST be replaced with actual content without the square brackets.', + combatNarrative: 'Build novel prose. Break patterns from your previous responses by varying sentence structures, rhythms, formats, and openings. Track whether a sentence shape, cadence, sensory detail, or a descriptor appeared recently, and replace it with a different one or skip it entirely. If you mentioned a physical feature, mannerism, or expression once, move on. Don\'t fixate on the same traits every turn. Well-placed humor, unconventional similes, and subverted turns of phrase are encouraged. Avoid GPTisms, purple prose, and anaphoras, especially the negatives with "no/not/wasn\'t/didn\'t." Refrain from describing actions or emotions via negation; focus on what does happen, not what doesn\'t. Absolutely no asterisks, ellipses, or em-dashes. Explicit content is allowed, no plot armor. Do not play for {userName}. Keep your response length under 150 words. Never end on handover cues; finish naturally.\nCRITICAL: Do not repeat, echo, parrot, or restate distinctive words, phrases, and dialogues from the user\'s last message. If reacting to speech, show interpretation or response, not repetition.\nEXAMPLE: "Are you a gooner?" User asks.\nBAD: "Gooner?"\nGOOD: A flat look. "What type of question is that?"' +}; + +/** + * Initialize the prompts editor modal + */ +export function initPromptsEditor() { + $editorModal = $('#rpg-prompts-editor-popup'); + + if (!$editorModal.length) { + console.error('[RPG Companion] Prompts editor modal not found in template'); + return; + } + + // Save button + $(document).on('click', '#rpg-prompts-save', function() { + savePrompts(); + closePromptsEditor(); + toastr.success('Prompts saved successfully'); + }); + + // Cancel button + $(document).on('click', '#rpg-prompts-cancel', function() { + closePromptsEditor(); + }); + + // Close X button + $(document).on('click', '#rpg-close-prompts-editor', function() { + closePromptsEditor(); + }); + + // Restore All button + $(document).on('click', '#rpg-prompts-restore-all', function() { + restoreAllToDefaults(); + toastr.success('All prompts restored to defaults'); + }); + + // Individual restore buttons + $(document).on('click', '.rpg-restore-prompt-btn', function() { + const promptType = $(this).data('prompt'); + restorePromptToDefault(promptType); + toastr.success('Prompt restored to default'); + }); + + // Close on background click + $(document).on('click', '#rpg-prompts-editor-popup', function(e) { + if (e.target.id === 'rpg-prompts-editor-popup') { + closePromptsEditor(); + } + }); + + // Open button + $(document).on('click', '#rpg-open-prompts-editor', function() { + openPromptsEditor(); + }); +} + +/** + * Open the prompts editor modal + */ +function openPromptsEditor() { + // Create temporary copy for cancel functionality + tempPrompts = { + html: extensionSettings.customHtmlPrompt || '', + plotRandom: extensionSettings.customPlotRandomPrompt || '', + plotNatural: extensionSettings.customPlotNaturalPrompt || '', + avatar: extensionSettings.avatarLLMCustomInstruction || '', + trackerInstructions: extensionSettings.customTrackerInstructionsPrompt || '', + trackerContinuation: extensionSettings.customTrackerContinuationPrompt || '', + combatNarrative: extensionSettings.customCombatNarrativePrompt || '' + }; + + // Load current values or defaults + $('#rpg-prompt-html').val(extensionSettings.customHtmlPrompt || DEFAULT_PROMPTS.html); + $('#rpg-prompt-plot-random').val(extensionSettings.customPlotRandomPrompt || DEFAULT_PROMPTS.plotRandom); + $('#rpg-prompt-plot-natural').val(extensionSettings.customPlotNaturalPrompt || DEFAULT_PROMPTS.plotNatural); + $('#rpg-prompt-avatar').val(extensionSettings.avatarLLMCustomInstruction || DEFAULT_PROMPTS.avatar); + $('#rpg-prompt-tracker-instructions').val(extensionSettings.customTrackerInstructionsPrompt || DEFAULT_PROMPTS.trackerInstructions); + $('#rpg-prompt-tracker-continuation').val(extensionSettings.customTrackerContinuationPrompt || DEFAULT_PROMPTS.trackerContinuation); + $('#rpg-prompt-combat-narrative').val(extensionSettings.customCombatNarrativePrompt || DEFAULT_PROMPTS.combatNarrative); + + // Set theme to match current extension theme + const theme = extensionSettings.theme || 'default'; + $editorModal.attr('data-theme', theme); + + $editorModal.addClass('is-open').css('display', ''); +} + +/** + * Close the prompts editor modal + */ +function closePromptsEditor() { + // Restore from temp if canceling + if (tempPrompts) { + tempPrompts = null; + } + + $editorModal.removeClass('is-open').addClass('is-closing'); + setTimeout(() => { + $editorModal.removeClass('is-closing').hide(); + }, 200); +} + +/** + * Save all prompts from the editor + */ +function savePrompts() { + extensionSettings.customHtmlPrompt = $('#rpg-prompt-html').val().trim(); + extensionSettings.customPlotRandomPrompt = $('#rpg-prompt-plot-random').val().trim(); + extensionSettings.customPlotNaturalPrompt = $('#rpg-prompt-plot-natural').val().trim(); + extensionSettings.avatarLLMCustomInstruction = $('#rpg-prompt-avatar').val().trim(); + extensionSettings.customTrackerInstructionsPrompt = $('#rpg-prompt-tracker-instructions').val().trim(); + extensionSettings.customTrackerContinuationPrompt = $('#rpg-prompt-tracker-continuation').val().trim(); + extensionSettings.customCombatNarrativePrompt = $('#rpg-prompt-combat-narrative').val().trim(); + + saveSettings(); +} + +/** + * Restore a specific prompt to its default + * @param {string} promptType - Type of prompt to restore (html, plotRandom, plotNatural, avatar) + */ +function restorePromptToDefault(promptType) { + const defaultValue = DEFAULT_PROMPTS[promptType] || ''; + $(`#rpg-prompt-${promptType.replace(/([A-Z])/g, '-$1').toLowerCase()}`).val(defaultValue); + + // Also update the setting immediately + switch(promptType) { + case 'html': + extensionSettings.customHtmlPrompt = ''; + break; + case 'plotRandom': + extensionSettings.customPlotRandomPrompt = ''; + break; + case 'plotNatural': + extensionSettings.customPlotNaturalPrompt = ''; + break; + case 'avatar': + extensionSettings.avatarLLMCustomInstruction = ''; + break; + case 'trackerInstructions': + extensionSettings.customTrackerInstructionsPrompt = ''; + break; + case 'trackerContinuation': + extensionSettings.customTrackerContinuationPrompt = ''; + break; + case 'combatNarrative': + extensionSettings.customCombatNarrativePrompt = ''; + break; + } + + saveSettings(); +} + +/** + * Restore all prompts to their defaults + */ +function restoreAllToDefaults() { + $('#rpg-prompt-html').val(DEFAULT_PROMPTS.html); + $('#rpg-prompt-plot-random').val(DEFAULT_PROMPTS.plotRandom); + $('#rpg-prompt-plot-natural').val(DEFAULT_PROMPTS.plotNatural); + $('#rpg-prompt-avatar').val(DEFAULT_PROMPTS.avatar); + $('#rpg-prompt-tracker-instructions').val(DEFAULT_PROMPTS.trackerInstructions); + $('#rpg-prompt-tracker-continuation').val(DEFAULT_PROMPTS.trackerContinuation); + $('#rpg-prompt-combat-narrative').val(DEFAULT_PROMPTS.combatNarrative); + + // Clear all custom prompts + extensionSettings.customHtmlPrompt = ''; + extensionSettings.customPlotRandomPrompt = ''; + extensionSettings.customPlotNaturalPrompt = ''; + extensionSettings.avatarLLMCustomInstruction = ''; + extensionSettings.customTrackerInstructionsPrompt = ''; + extensionSettings.customTrackerContinuationPrompt = ''; + extensionSettings.customCombatNarrativePrompt = ''; + + saveSettings(); +} + +/** + * Get default prompts (for export/other modules) + */ +export function getDefaultPrompts() { + return { ...DEFAULT_PROMPTS }; +} diff --git a/style.css b/style.css index 315dc74..ce9e5fa 100644 --- a/style.css +++ b/style.css @@ -4098,6 +4098,41 @@ body:has(.rpg-panel.rpg-position-left) #sheld { gap: 0.5em; } +/* Prompts Editor Styles */ +.rpg-prompt-editor-section { + margin-bottom: 24px; + padding-bottom: 24px; + border-bottom: 1px solid var(--rpg-border); +} + +.rpg-prompt-editor-section:last-child { + border-bottom: none; +} + +.rpg-prompt-textarea { + width: 100%; + padding: 10px; + border-radius: 4px; + border: 1px solid var(--SmartThemeBorderColor); + background: var(--SmartThemeBlurTintColor); + color: var(--SmartThemeBodyColor); + font-family: 'Courier New', monospace; + font-size: 12px; + resize: vertical; + line-height: 1.5; + box-sizing: border-box; +} + +.rpg-prompt-textarea:focus { + outline: none; + border-color: var(--rpg-highlight); + box-shadow: 0 0 0 2px rgba(233, 69, 96, 0.2); +} + +.rpg-restore-prompt-btn { + font-size: 0.9em; +} + /* Editor buttons */ .rpg-btn-primary, .rpg-btn-secondary, diff --git a/template.html b/template.html index 2918a87..fc3d301 100644 --- a/template.html +++ b/template.html @@ -186,12 +186,6 @@ - - - - - Use character card as narrator. Infer characters from context instead of using fixed character - references. - -