feat: more settings

This commit is contained in:
Subarashimo
2025-12-03 09:19:03 +01:00
parent 32c2543605
commit f3c224a99a
13 changed files with 369 additions and 48 deletions
+43 -2
View File
@@ -119,7 +119,7 @@ import { setupClassicStatsButtons } from './src/systems/features/classicStats.js
import { ensureHtmlCleaningRegex, detectConflictingRegexScripts } from './src/systems/features/htmlCleaning.js';
import { setupMemoryRecollectionButton, updateMemoryRecollectionButton } from './src/systems/features/memoryRecollection.js';
import { initLorebookLimiter } from './src/systems/features/lorebookLimiter.js';
import { DEFAULT_HTML_PROMPT } from './src/systems/generation/promptBuilder.js';
import { DEFAULT_HTML_PROMPT, DEFAULT_TRACKER_PROMPT } from './src/systems/generation/promptBuilder.js';
// Integration modules
import {
@@ -323,6 +323,29 @@ async function initUI() {
extensionSettings.showInventory = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
// Re-setup desktop tabs to show/hide inventory tab
if (window.innerWidth > 1000) {
removeDesktopTabs();
setupDesktopTabs();
}
});
$('#rpg-toggle-simplified-inventory').on('change', function() {
extensionSettings.useSimplifiedInventory = $(this).prop('checked');
saveSettings();
renderInventory(); // Re-render inventory with new mode
});
$('#rpg-toggle-quests').on('change', function() {
extensionSettings.showQuests = $(this).prop('checked');
saveSettings();
updateSectionVisibility();
renderQuests(); // Re-render quests
// Re-setup desktop tabs to show/hide quests tab
if (window.innerWidth > 1000) {
removeDesktopTabs();
setupDesktopTabs();
}
});
$('#rpg-toggle-thoughts-in-chat').on('change', function() {
@@ -361,6 +384,19 @@ async function initUI() {
toastr.success('HTML prompt restored to default');
});
// Custom Tracker Prompt handlers
$('#rpg-custom-tracker-prompt').on('input', function() {
extensionSettings.customTrackerPrompt = $(this).val().trim();
saveSettings();
});
$('#rpg-restore-default-tracker-prompt').on('click', function() {
extensionSettings.customTrackerPrompt = '';
$('#rpg-custom-tracker-prompt').val('');
saveSettings();
toastr.success('Tracker prompt restored to default');
});
$('#rpg-skip-guided-mode').on('change', function() {
extensionSettings.skipInjectionsForGuided = String($(this).val());
saveSettings();
@@ -460,6 +496,8 @@ async function initUI() {
$('#rpg-toggle-info-box').prop('checked', extensionSettings.showInfoBox);
$('#rpg-toggle-thoughts').prop('checked', extensionSettings.showCharacterThoughts);
$('#rpg-toggle-inventory').prop('checked', extensionSettings.showInventory);
$('#rpg-toggle-simplified-inventory').prop('checked', extensionSettings.useSimplifiedInventory);
$('#rpg-toggle-quests').prop('checked', extensionSettings.showQuests);
$('#rpg-toggle-thoughts-in-chat').prop('checked', extensionSettings.showThoughtsInChat);
$('#rpg-toggle-always-show-bubble').prop('checked', extensionSettings.alwaysShowThoughtBubble);
$('#rpg-toggle-html-prompt').prop('checked', extensionSettings.enableHtmlPrompt);
@@ -467,7 +505,10 @@ async function initUI() {
// 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-plot-buttons').prop('checked', extensionSettings.enablePlotButtons); $('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
// Set default tracker prompt as actual text if no custom prompt exists
$('#rpg-custom-tracker-prompt').val(extensionSettings.customTrackerPrompt || DEFAULT_TRACKER_PROMPT);
$('#rpg-toggle-plot-buttons').prop('checked', extensionSettings.enablePlotButtons);
$('#rpg-toggle-animations').prop('checked', extensionSettings.enableAnimations);
$('#rpg-stat-bar-color-low').val(extensionSettings.statBarColorLow);
$('#rpg-stat-bar-color-high').val(extensionSettings.statBarColorHigh);
+3
View File
@@ -19,9 +19,12 @@ export let extensionSettings = {
showInfoBox: true,
showCharacterThoughts: true,
showInventory: true, // Show inventory section (v2 system)
useSimplifiedInventory: false, // Use simplified single-list inventory instead of categorized (On Person/Stored/Assets)
showQuests: true, // Show quests section
showThoughtsInChat: true, // Show thoughts overlay in chat
enableHtmlPrompt: false, // Enable immersive HTML prompt injection
customHtmlPrompt: '', // Custom HTML prompt text (empty = use default)
customTrackerPrompt: '', // Custom tracker instruction prompt (empty = use default)
skipInjectionsForGuided: 'none', // skip injections for instruct injections and quiet prompts (GuidedGenerations compatibility)
enablePlotButtons: true, // Show plot progression buttons above chat input
panelPosition: 'right', // 'left', 'right', or 'top'
+11
View File
@@ -30,6 +30,9 @@
"template.settingsModal.display.showInfoBox": "Show Info Box",
"template.settingsModal.display.showPresentCharacters": "Show Present Characters",
"template.settingsModal.display.showInventory": "Show Inventory",
"template.settingsModal.display.useSimplifiedInventory": "Use Simplified Inventory",
"template.settingsModal.display.useSimplifiedInventoryNote": "Single flat list instead of On Person / Stored / Assets categories",
"template.settingsModal.display.showQuests": "Show Quests",
"template.settingsModal.display.showThoughtsInChat": "Show Thoughts in Chat",
"template.settingsModal.display.showThoughtsInChatNote": "Display character thoughts as overlay bubbles next to their messages",
"template.settingsModal.display.alwaysShowThoughtBubble": "Always Show Thought Bubble",
@@ -56,6 +59,9 @@
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "Only on impersonation requests",
"template.settingsModal.advanced.skipInjectionsOptions.guided": "Always for guided or quiet prompts",
"template.settingsModal.advanced.skipInjectionsNote": "When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.",
"template.settingsModal.advanced.customTrackerPromptTitle": "Custom Tracker Prompt:",
"template.settingsModal.advanced.restoreDefaultTrackerPrompt": "Restore Default",
"template.settingsModal.advanced.customTrackerPromptNote": "Customize the instructions sent to the AI for generating tracker data. Use {{user}} as a placeholder for the user's name. This is the main prompt that tells the AI how to format and update the RPG trackers.",
"template.settingsModal.advanced.customHtmlPromptTitle": "Custom HTML Prompt:",
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "Restore Default",
"template.settingsModal.advanced.customHtmlPromptNote": "Customize the HTML prompt injected when \"Enable Immersive HTML\" is enabled. The default prompt is shown above - you can edit it directly or replace it entirely. Click \"Restore Default\" to reset. This affects all generation modes (together, separate, and plot progression).",
@@ -150,6 +156,11 @@
"inventory.assets.addAssetButton": "Add Asset",
"inventory.assets.addAssetPlaceholder": "Enter asset name...",
"inventory.assets.description": "Assets include vehicles (cars, motorcycles), property (homes, apartments), and major equipment (workshop tools, special items).",
"inventory.simplified.title": "Inventory",
"inventory.simplified.empty": "No items",
"inventory.simplified.addItemButton": "Add Item",
"inventory.simplified.addItemPlaceholder": "Enter item name...",
"inventory.simplified.removeTitle": "Remove item",
"quests.section.main": "Main Quest",
"quests.section.optional": "Optional Quests",
"quests.main.title": "Main Quests",
+12 -1
View File
@@ -30,6 +30,9 @@
"template.settingsModal.display.showInfoBox": "顯示資訊框",
"template.settingsModal.display.showPresentCharacters": "顯示在場角色",
"template.settingsModal.display.showInventory": "顯示物品欄",
"template.settingsModal.display.useSimplifiedInventory": "使用簡化物品欄",
"template.settingsModal.display.useSimplifiedInventoryNote": "使用單一列表取代分類(隨身/倉庫/資產)",
"template.settingsModal.display.showQuests": "顯示任務",
"template.settingsModal.display.showThoughtsInChat": "在聊天中顯示想法",
"template.settingsModal.display.showThoughtsInChatNote": "將角色想法顯示為其訊息旁的泡泡",
"template.settingsModal.display.alwaysShowThoughtBubble": "始終顯示想法泡泡",
@@ -56,9 +59,12 @@
"template.settingsModal.advanced.skipInjectionsOptions.impersonation": "僅在模擬請求時跳過",
"template.settingsModal.advanced.skipInjectionsOptions.guided": "始終跳過引導",
"template.settingsModal.advanced.skipInjectionsNote": "當設置後,擴充功能在檢測到引導生成(通過 `instruct` 或 `quiet_prompt`)時,將根據所選模式不注入追蹤提示詞、範例或 HTML 指令。當與 GuidedGenerations 或類似擴充功能一起使用時非常有用。",
"template.settingsModal.advanced.customTrackerPromptTitle": "自訂追蹤器提示詞:",
"template.settingsModal.advanced.restoreDefaultTrackerPrompt": "恢復預設",
"template.settingsModal.advanced.customTrackerPromptNote": "自訂發送給 AI 生成追蹤器數據的指令。使用 {{user}} 作為使用者名稱的佔位符。這是告訴 AI 如何格式化和更新 RPG 追蹤器的主要提示詞。",
"template.settingsModal.advanced.customHtmlPromptTitle": "自訂 HTML 提示詞:",
"template.settingsModal.advanced.restoreDefaultHtmlPrompt": "恢復預設",
"template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用“啟用沉浸式 HTML”時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊“恢復預設”以重置。這會影響所有生成模式(同時、單獨和劇情推進)。",
"template.settingsModal.advanced.customHtmlPromptNote": "自訂啟用" HTML"時注入的 HTML 提示詞。上方顯示預設提示詞 - 您可以直接編輯或完全替換它。點擊""以重置。這會影響所有生成模式(同時、單獨和劇情推進)。",
"template.settingsModal.advanced.clearCache": "清除擴充功能快取",
"template.settingsModal.advanced.resetFabPositions": "重置按鈕位置",
"template.settingsModal.advanced.resetFabPositionsNote": "將所有浮動操作按鈕(切換、刷新、調試)重置為預設的左上位置。如果按鈕在螢幕外,這會很有用。",
@@ -150,6 +156,11 @@
"inventory.assets.addAssetButton": "添加資產",
"inventory.assets.addAssetPlaceholder": "輸入資產名稱...",
"inventory.assets.description": "資產包括車輛(汽車、摩托車)、房產(房屋、公寓)和主要設備(車間工具、特殊物品)。",
"inventory.simplified.title": "物品欄",
"inventory.simplified.empty": "沒有物品",
"inventory.simplified.addItemButton": "添加物品",
"inventory.simplified.addItemPlaceholder": "輸入物品名稱...",
"inventory.simplified.removeTitle": "移除物品",
"quests.section.main": "主線任務",
"quests.section.optional": "支線任務",
"quests.main.title": "主線任務",
+58 -2
View File
@@ -3,6 +3,8 @@
* Extracts v2 inventory data from AI-generated text
*/
import { extensionSettings } from '../../core/state.js';
// Type imports
/** @typedef {import('../../types/inventory.js').InventoryV2} InventoryV2 */
@@ -101,30 +103,84 @@ export function extractLegacyInventory(text) {
return null;
}
/**
* Extracts simplified inventory data (single "Inventory:" line).
* Used when useSimplifiedInventory setting is enabled.
*
* Expected format: "Inventory: Sword, Shield, 3x Potions, Gold coins"
*
* @param {string} text - Text that may contain simplified inventory
* @returns {InventoryV2|null} Parsed inventory or null
*/
export function extractSimplifiedInventory(text) {
if (!text || typeof text !== 'string') {
return null;
}
// Match simplified format: "Inventory: ..."
const match = text.match(/Inventory:\s*(.+?)(?:\n|$)/i);
if (match && match[1]) {
const inventoryText = match[1].trim();
// Return null for empty values like "None" or ""
if (!inventoryText || inventoryText.toLowerCase() === 'none') {
return null;
}
// Return v2 format with items stored in both 'items' (for simplified display)
// and 'onPerson' (for backward compatibility)
return {
version: 2,
onPerson: inventoryText,
stored: {},
assets: "None",
items: inventoryText, // Simplified inventory storage
simplified: true // Flag to indicate this is simplified format
};
}
return null;
}
/**
* Main inventory extraction function that tries v2 format first, then falls back to v1.
* Converts v1 format to v2 automatically if found.
* When useSimplifiedInventory is enabled, prioritizes simple "Inventory:" format.
*
* @param {string} statsText - Raw stats text from AI response
* @returns {InventoryV2|null} Parsed inventory in v2 format or null
*/
export function extractInventory(statsText) {
// If simplified inventory mode is enabled, try simplified format first
if (extensionSettings.useSimplifiedInventory) {
const simplifiedData = extractSimplifiedInventory(statsText);
if (simplifiedData) {
return simplifiedData;
}
}
// Try v2 format first
const v2Data = extractInventoryData(statsText);
if (v2Data) {
return v2Data;
}
// Fallback to v1 format and convert to v2
// Fallback to v1/simplified format and convert to v2
const v1Data = extractLegacyInventory(statsText);
if (v1Data) {
// Convert v1 string to v2 format (place in onPerson)
return {
const result = {
version: 2,
onPerson: v1Data,
stored: {},
assets: "None"
};
// If simplified mode, also store in items field
if (extensionSettings.useSimplifiedInventory) {
result.items = v1Data;
result.simplified = true;
}
return result;
}
// No inventory data found
+30 -11
View File
@@ -16,6 +16,12 @@ 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 tracker instruction prompt text
* Use {{user}} as placeholder for the user's name (will be replaced at runtime)
*/
export const DEFAULT_TRACKER_PROMPT = `At 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 {{user}} 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).`;
/**
* Gets character card information for current chat (handles both single and group chats)
* @returns {string} Formatted character information
@@ -91,11 +97,18 @@ async function getCharacterCardsInfo() {
export function buildInventorySummary(inventory) {
// Handle legacy v1 string format
if (typeof inventory === 'string') {
return inventory;
return `Inventory: ${inventory}`;
}
// Handle v2 object format
if (inventory && typeof inventory === 'object' && inventory.version === 2) {
// Check for simplified inventory mode
if (inventory.simplified || extensionSettings.useSimplifiedInventory) {
const items = inventory.items || inventory.onPerson || 'None';
return `Inventory: ${items}`;
}
// Full categorized format
let summary = '';
// Add On Person section
@@ -204,9 +217,9 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
// Only add tracker instructions if at least one tracker is enabled
if (hasAnyTrackers) {
// Universal instruction header
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).
`;
// Universal instruction header - use custom prompt if set, otherwise use default
const trackerPrompt = (extensionSettings.customTrackerPrompt || DEFAULT_TRACKER_PROMPT).replace(/\{\{user\}\}/g, userName);
instructions += `\n${trackerPrompt}\n`;
// Add format specifications for each enabled tracker
if (extensionSettings.showUserStats) {
@@ -241,9 +254,13 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
instructions += `Skills: [${skillFieldsText || 'Skill1, Skill2, etc.'}]\n`;
}
// Add inventory format based on feature flag - only if showInventory is enabled
// Add inventory format - only if showInventory is enabled
if (extensionSettings.showInventory) {
if (FEATURE_FLAGS.useNewInventory) {
if (extensionSettings.useSimplifiedInventory) {
// Simplified single-line inventory format
instructions += 'Inventory: [Items currently carried/worn/owned, or "None"]\n';
} else if (FEATURE_FLAGS.useNewInventory) {
// Full v2 categorized inventory format
instructions += 'On Person: [Items currently carried/worn, or "None"]\n';
instructions += 'Stored - [Location Name]: [Items stored at this location]\n';
instructions += '(Add multiple "Stored - [Location]:" lines as needed for different storage locations)\n';
@@ -254,9 +271,11 @@ export function generateTrackerInstructions(includeHtmlPrompt = true, includeCon
}
}
// 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';
// Add quests section - only if showQuests is enabled
if (extensionSettings.showQuests) {
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';
}
@@ -484,8 +503,8 @@ export function generateRPGPromptText() {
promptText += `Last ${userName}'s Stats:\nNone - this is the first update.\n\n`;
}
// Add current quests to the previous data context
if (extensionSettings.quests) {
// Add current quests to the previous data context - only if showQuests is enabled
if (extensionSettings.showQuests && extensionSettings.quests) {
if (extensionSettings.quests.main && extensionSettings.quests.main !== 'None') {
promptText += `Main Quests: ${extensionSettings.quests.main}\n`;
}
+37 -9
View File
@@ -34,7 +34,8 @@ let openForms = {
addLocation: false,
addItemOnPerson: false,
addItemStored: {}, // { [locationName]: true/false }
addItemAssets: false
addItemAssets: false,
addItemSimplified: false
};
/**
@@ -54,7 +55,7 @@ function updateLastGeneratedDataInventory() {
/**
* Shows the inline form for adding a new item.
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
* @param {string} [location] - Location name (required for 'stored' field)
*/
export function showAddItemForm(field, location) {
@@ -76,6 +77,8 @@ export function showAddItemForm(field, location) {
openForms.addItemOnPerson = true;
} else if (field === 'assets') {
openForms.addItemAssets = true;
} else if (field === 'simplified') {
openForms.addItemSimplified = true;
}
}
@@ -88,7 +91,7 @@ export function showAddItemForm(field, location) {
/**
* Hides the inline form for adding a new item.
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
* @param {string} [location] - Location name (required for 'stored' field)
*/
export function hideAddItemForm(field, location) {
@@ -111,6 +114,8 @@ export function hideAddItemForm(field, location) {
openForms.addItemOnPerson = false;
} else if (field === 'assets') {
openForms.addItemAssets = false;
} else if (field === 'simplified') {
openForms.addItemSimplified = false;
}
}
@@ -123,7 +128,7 @@ export function hideAddItemForm(field, location) {
/**
* Adds a new item to the inventory.
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
* @param {string} [location] - Location name (required for 'stored' field)
*/
export function saveAddItem(field, location) {
@@ -154,7 +159,10 @@ export function saveAddItem(field, location) {
// Get current items, add new one, serialize back
let currentString;
if (field === 'stored') {
if (field === 'simplified') {
// For simplified inventory, use items field or fall back to onPerson
currentString = inventory.items || inventory.onPerson || 'None';
} else if (field === 'stored') {
currentString = inventory.stored[location] || 'None';
} else {
currentString = inventory[field] || 'None';
@@ -165,7 +173,11 @@ export function saveAddItem(field, location) {
const newString = serializeItems(items);
// Save back to inventory
if (field === 'stored') {
if (field === 'simplified') {
// Update both items and onPerson for simplified mode
inventory.items = newString;
inventory.onPerson = newString;
} else if (field === 'stored') {
inventory.stored[location] = newString;
} else {
inventory[field] = newString;
@@ -183,7 +195,7 @@ export function saveAddItem(field, location) {
/**
* Removes an item from the inventory.
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
* @param {number} itemIndex - Index of item to remove
* @param {string} [location] - Location name (required for 'stored' field)
*/
@@ -194,7 +206,10 @@ export function removeItem(field, itemIndex, location) {
// Get current items, remove the one at index, serialize back
let currentString;
if (field === 'stored') {
if (field === 'simplified') {
// For simplified inventory, use items field or fall back to onPerson
currentString = inventory.items || inventory.onPerson || 'None';
} else if (field === 'stored') {
currentString = inventory.stored[location] || 'None';
} else {
currentString = inventory[field] || 'None';
@@ -212,7 +227,11 @@ export function removeItem(field, itemIndex, location) {
// console.log('[RPG Companion] DEBUG newString after removal:', newString);
// Save back to inventory
if (field === 'stored') {
if (field === 'simplified') {
// Update both items and onPerson for simplified mode
inventory.items = newString;
inventory.onPerson = newString;
} else if (field === 'stored') {
inventory.stored[location] = newString;
} else {
inventory[field] = newString;
@@ -585,6 +604,15 @@ export function restoreFormStates() {
}
}
// Restore add item simplified form
if (openForms.addItemSimplified) {
const form = $('#rpg-add-item-form-simplified');
const input = $('#rpg-new-item-simplified');
if (form.length > 0) {
form.show();
}
}
// Restore add item stored forms (for each location)
// Clean up orphaned states for deleted locations (Bug #3 fix)
if (openForms.addItemStored && typeof openForms.addItemStored === 'object') {
+10 -3
View File
@@ -14,7 +14,7 @@ import { sanitizeItemName } from '../../utils/security.js';
* Updates an existing inventory item's name.
* Validates, sanitizes, and persists the change.
*
* @param {string} field - Field name ('onPerson', 'stored', 'assets')
* @param {string} field - Field name ('onPerson', 'stored', 'assets', 'simplified')
* @param {number} index - Index of item in the array
* @param {string} newName - New name for the item
* @param {string} [location] - Location name (required for 'stored' field)
@@ -33,7 +33,10 @@ export function updateInventoryItem(field, index, newName, location) {
// Get current items for the field
let currentString;
if (field === 'stored') {
if (field === 'simplified') {
// For simplified inventory, use items field or fall back to onPerson
currentString = inventory.items || inventory.onPerson || 'None';
} else if (field === 'stored') {
if (!location) {
console.error('[RPG Companion] Location required for stored items');
return;
@@ -59,7 +62,11 @@ export function updateInventoryItem(field, index, newName, location) {
const newItemString = serializeItems(items);
// Update the inventory
if (field === 'stored') {
if (field === 'simplified') {
// Update both items and onPerson for simplified mode
inventory.items = newItemString;
inventory.onPerson = newItemString;
} else if (field === 'stored') {
inventory.stored[location] = newItemString;
} else {
inventory[field] = newItemString;
+97 -4
View File
@@ -444,6 +444,85 @@ export function updateInventoryDisplay(containerId, options = {}) {
restoreFormStates();
}
/**
* Renders the simplified (single-list) inventory view
* Used when useSimplifiedInventory setting is enabled
* @param {string} itemsString - All items as a comma-separated string
* @param {string} viewMode - View mode ('list' or 'grid')
* @returns {string} HTML for simplified inventory view
*/
export function renderSimplifiedInventoryView(itemsString, viewMode = 'list') {
const items = parseItems(itemsString);
let itemsHtml = '';
if (items.length === 0) {
itemsHtml = `<div class="rpg-inventory-empty" data-i18n-key="inventory.simplified.empty">${i18n.getTranslation('inventory.simplified.empty')}</div>`;
} else {
if (viewMode === 'grid') {
// Grid view: card-style items
itemsHtml = items.map((item, index) => `
<div class="rpg-item-card" data-field="simplified" data-index="${index}">
<button class="rpg-item-remove" data-action="remove-item" data-field="simplified" data-index="${index}" title="${i18n.getTranslation('inventory.simplified.removeTitle')}">
<i class="fa-solid fa-times"></i>
</button>
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
</div>
`).join('');
} else {
// List view: full-width rows
itemsHtml = items.map((item, index) => `
<div class="rpg-item-row" data-field="simplified" data-index="${index}">
<span class="rpg-item-name rpg-editable" contenteditable="true" data-field="simplified" data-index="${index}" title="Click to edit">${escapeHtml(item)}</span>
<button class="rpg-item-remove" data-action="remove-item" data-field="simplified" data-index="${index}" title="${i18n.getTranslation('inventory.simplified.removeTitle')}">
<i class="fa-solid fa-times"></i>
</button>
</div>
`).join('');
}
}
const listViewClass = viewMode === 'list' ? 'rpg-item-list-view' : 'rpg-item-grid-view';
return `
<div class="rpg-inventory-container">
<div class="rpg-inventory-section" data-section="simplified">
<div class="rpg-inventory-header">
<h4 data-i18n-key="inventory.simplified.title">${i18n.getTranslation('inventory.simplified.title')}</h4>
<div class="rpg-inventory-header-actions">
<div class="rpg-view-toggle">
<button class="rpg-view-btn ${viewMode === 'list' ? 'active' : ''}" data-action="switch-view" data-field="simplified" data-view="list" title="${i18n.getTranslation('global.listView')}">
<i class="fa-solid fa-list"></i>
</button>
<button class="rpg-view-btn ${viewMode === 'grid' ? 'active' : ''}" data-action="switch-view" data-field="simplified" data-view="grid" title="${i18n.getTranslation('global.gridView')}">
<i class="fa-solid fa-th"></i>
</button>
</div>
<button class="rpg-inventory-add-btn" data-action="add-item" data-field="simplified" title="Add new item">
<i class="fa-solid fa-plus"></i> <span data-i18n-key="inventory.simplified.addItemButton">${i18n.getTranslation('inventory.simplified.addItemButton')}</span>
</button>
</div>
</div>
<div class="rpg-inventory-content">
<div class="rpg-inline-form" id="rpg-add-item-form-simplified" style="display: none;">
<input type="text" class="rpg-inline-input" id="rpg-new-item-simplified" placeholder="${i18n.getTranslation('inventory.simplified.addItemPlaceholder')}" data-i18n-placeholder-key="inventory.simplified.addItemPlaceholder" />
<div class="rpg-inline-buttons">
<button class="rpg-inline-btn rpg-inline-cancel" data-action="cancel-add-item" data-field="simplified">
<i class="fa-solid fa-times"></i> <span data-i18n-key="global.cancel">${i18n.getTranslation('global.cancel')}</span>
</button>
<button class="rpg-inline-btn rpg-inline-save" data-action="save-add-item" data-field="simplified">
<i class="fa-solid fa-check"></i> <span data-i18n-key="global.add">${i18n.getTranslation('global.add')}</span>
</button>
</div>
</div>
<div class="rpg-item-list ${listViewClass}">
${itemsHtml}
</div>
</div>
</div>
</div>
`;
}
/**
* Main inventory rendering function (matches pattern of other render functions)
* Gets data from state/settings and updates DOM directly.
@@ -458,11 +537,25 @@ export function renderInventory() {
// Get inventory data from settings
const inventory = extensionSettings.userStats.inventory;
// Get current render options (active tab, collapsed locations)
const options = getInventoryRenderOptions();
let html;
// Check if we should render simplified inventory
if (extensionSettings.useSimplifiedInventory) {
// For simplified mode, combine all items into a single string
// Use the 'items' field if available (from simplified parsing),
// otherwise fall back to onPerson
const itemsString = inventory.items || inventory.onPerson || 'None';
// Get view mode from settings (use 'simplified' key or fall back to 'onPerson')
const viewModes = extensionSettings.inventoryViewModes || {};
const viewMode = viewModes.simplified || viewModes.onPerson || 'list';
html = renderSimplifiedInventoryView(itemsString, viewMode);
} else {
// Full categorized inventory
// Get current render options (active tab, collapsed locations)
const options = getInventoryRenderOptions();
html = generateInventoryHTML(inventory, options);
}
// Generate HTML and update DOM
const html = generateInventoryHTML(inventory, options);
$inventoryContainer.html(html);
// Restore form states after re-rendering (fixes Bug #1)
+1 -1
View File
@@ -160,7 +160,7 @@ export function renderOptionalQuestsView(optionalQuests) {
* Main render function for quests
*/
export function renderQuests() {
if (!extensionSettings.showInventory || !$questsContainer) {
if (!extensionSettings.showQuests || !$questsContainer) {
return;
}
+15 -10
View File
@@ -4,6 +4,7 @@
*/
import { i18n } from '../../core/i18n.js';
import { extensionSettings } from '../../core/state.js';
/**
* Sets up desktop tab navigation for organizing content.
@@ -31,21 +32,25 @@ export function setupDesktopTabs() {
return;
}
// Create tab navigation
// Create tab navigation - conditionally show inventory and quests tabs based on settings
const inventoryTabHtml = extensionSettings.showInventory ? `
<button class="rpg-tab-btn" data-tab="inventory">
<i class="fa-solid fa-box"></i>
<span data-i18n-key="global.inventory">Inventory</span>
</button>` : '';
const questsTabHtml = extensionSettings.showQuests ? `
<button class="rpg-tab-btn" data-tab="quests">
<i class="fa-solid fa-scroll"></i>
<span data-i18n-key="global.quests">Quests</span>
</button>` : '';
const $tabNav = $(`
<div class="rpg-tabs-nav">
<button class="rpg-tab-btn active" data-tab="status">
<i class="fa-solid fa-chart-simple"></i>
<span data-i18n-key="global.status">Status</span>
</button>
<button class="rpg-tab-btn" data-tab="inventory">
<i class="fa-solid fa-box"></i>
<span data-i18n-key="global.inventory">Inventory</span>
</button>
<button class="rpg-tab-btn" data-tab="quests">
<i class="fa-solid fa-scroll"></i>
<span data-i18n-key="global.quests">Quests</span>
</button>
</button>${inventoryTabHtml}${questsTabHtml}
</div>
`);
+14 -5
View File
@@ -9,7 +9,8 @@ import {
$userStatsContainer,
$infoBoxContainer,
$thoughtsContainer,
$inventoryContainer
$inventoryContainer,
$questsContainer
} from '../../core/state.js';
import { i18n } from '../../core/i18n.js';
@@ -230,22 +231,30 @@ export function updateSectionVisibility() {
if ($inventoryContainer) {
$inventoryContainer.toggle(extensionSettings.showInventory);
}
if ($questsContainer) {
$questsContainer.toggle(extensionSettings.showQuests);
}
// Show/hide dividers intelligently
// Divider after User Stats: shown if User Stats is visible AND at least one section after it is visible
const showDividerAfterStats = extensionSettings.showUserStats &&
(extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory);
(extensionSettings.showInfoBox || extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-stats').toggle(showDividerAfterStats);
// Divider after Info Box: shown if Info Box is visible AND at least one section after it is visible
const showDividerAfterInfo = extensionSettings.showInfoBox &&
(extensionSettings.showCharacterThoughts || extensionSettings.showInventory);
(extensionSettings.showCharacterThoughts || extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-info').toggle(showDividerAfterInfo);
// Divider after Thoughts: shown if Thoughts is visible AND Inventory is visible
// Divider after Thoughts: shown if Thoughts is visible AND Inventory or Quests is visible
const showDividerAfterThoughts = extensionSettings.showCharacterThoughts &&
extensionSettings.showInventory;
(extensionSettings.showInventory || extensionSettings.showQuests);
$('#rpg-divider-thoughts').toggle(showDividerAfterThoughts);
// Divider after Inventory: shown if Inventory is visible AND Quests is visible
const showDividerAfterInventory = extensionSettings.showInventory &&
extensionSettings.showQuests;
$('#rpg-divider-inventory').toggle(showDividerAfterInventory);
}
/**
+38
View File
@@ -53,6 +53,9 @@
<!-- Content will be populated by JavaScript -->
</div>
<!-- Divider after Inventory -->
<div id="rpg-divider-inventory" class="rpg-divider"></div>
<!-- Quests Section -->
<div id="rpg-quests" class="rpg-section rpg-quests-section">
<!-- Content will be populated by JavaScript -->
@@ -182,6 +185,19 @@
<span data-i18n-key="template.settingsModal.display.showInventory">Show Inventory</span>
</label>
<label class="checkbox_label" style="margin-left: 24px;">
<input type="checkbox" id="rpg-toggle-simplified-inventory" />
<span data-i18n-key="template.settingsModal.display.useSimplifiedInventory">Use Simplified Inventory</span>
</label>
<small style="display: block; margin-left: 48px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.useSimplifiedInventoryNote">
Single flat list instead of On Person / Stored / Assets categories
</small>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-quests" />
<span data-i18n-key="template.settingsModal.display.showQuests">Show Quests</span>
</label>
<label class="checkbox_label">
<input type="checkbox" id="rpg-toggle-thoughts-in-chat" />
<span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span>
@@ -267,6 +283,28 @@
When set, the extension will not inject tracker prompts, examples, or HTML instructions according to the selected mode when a guided generation (via `instruct` or `quiet_prompt`) is detected. Useful when using GuidedGenerations or similar extensions.
</small>
<!-- Custom Tracker Prompt Editor -->
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
<label for="rpg-custom-tracker-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customTrackerPromptTitle">
<i class="fa-solid fa-scroll" aria-hidden="true"></i> Custom Tracker Prompt:
</label>
<textarea id="rpg-custom-tracker-prompt"
style="width: 100%; min-height: 150px; 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;"
placeholder=""></textarea>
<div style="margin-top: 8px; display: flex; gap: 8px;">
<button id="rpg-restore-default-tracker-prompt" class="menu_button" style="flex: 1;">
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i> <span data-i18n-key="template.settingsModal.advanced.restoreDefaultTrackerPrompt">Restore Default</span>
</button>
</div>
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.customTrackerPromptNote">
Customize the instructions sent to the AI for generating tracker data. Use {{user}} as a placeholder for the user's name. This is the main prompt that tells the AI how to format and update the RPG trackers.
</small>
</div>
<!-- Custom HTML Prompt Editor -->
<div class="rpg-setting-row" style="margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--rpg-border);">
<label for="rpg-custom-html-prompt" style="display: block; margin-bottom: 8px; font-weight: 600;" data-i18n-key="template.settingsModal.advanced.customHtmlPromptTitle">