Merge pull request #79 from munimunigamer/external-mode
feature: Add External API Generation Mode with Secure Key Storage
This commit is contained in:
@@ -51,7 +51,7 @@ import {
|
||||
generateSeparateUpdatePrompt
|
||||
} from './src/systems/generation/promptBuilder.js';
|
||||
import { parseResponse, parseUserStats } from './src/systems/generation/parser.js';
|
||||
import { updateRPGData } from './src/systems/generation/apiClient.js';
|
||||
import { updateRPGData, testExternalAPIConnection } from './src/systems/generation/apiClient.js';
|
||||
import { onGenerationStarted } from './src/systems/generation/injector.js';
|
||||
|
||||
// Rendering modules
|
||||
@@ -624,6 +624,99 @@ async function initUI() {
|
||||
}
|
||||
});
|
||||
|
||||
// External API settings event handlers
|
||||
$('#rpg-external-base-url').on('change', function() {
|
||||
if (!extensionSettings.externalApiSettings) {
|
||||
extensionSettings.externalApiSettings = {
|
||||
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
|
||||
};
|
||||
}
|
||||
extensionSettings.externalApiSettings.baseUrl = String($(this).val()).trim();
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
$('#rpg-external-api-key').on('change', function() {
|
||||
// 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 = {
|
||||
baseUrl: '', model: '', maxTokens: 8192, temperature: 0.7
|
||||
};
|
||||
saveSettings();
|
||||
}
|
||||
});
|
||||
|
||||
$('#rpg-external-model').on('change', function() {
|
||||
if (!extensionSettings.externalApiSettings) {
|
||||
extensionSettings.externalApiSettings = {
|
||||
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
|
||||
};
|
||||
}
|
||||
extensionSettings.externalApiSettings.model = String($(this).val()).trim();
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
$('#rpg-external-max-tokens').on('change', function() {
|
||||
if (!extensionSettings.externalApiSettings) {
|
||||
extensionSettings.externalApiSettings = {
|
||||
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
|
||||
};
|
||||
}
|
||||
extensionSettings.externalApiSettings.maxTokens = parseInt(String($(this).val()));
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
$('#rpg-external-temperature').on('change', function() {
|
||||
if (!extensionSettings.externalApiSettings) {
|
||||
extensionSettings.externalApiSettings = {
|
||||
baseUrl: '', apiKey: '', model: '', maxTokens: 8192, temperature: 0.7
|
||||
};
|
||||
}
|
||||
extensionSettings.externalApiSettings.temperature = parseFloat(String($(this).val()));
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
$('#rpg-toggle-api-key-visibility').on('click', function() {
|
||||
const $input = $('#rpg-external-api-key');
|
||||
const type = $input.attr('type') === 'password' ? 'text' : 'password';
|
||||
$input.attr('type', type);
|
||||
$(this).find('i').toggleClass('fa-eye fa-eye-slash');
|
||||
});
|
||||
|
||||
$('#rpg-test-external-api').on('click', async function() {
|
||||
const $result = $('#rpg-external-api-test-result');
|
||||
const $btn = $(this);
|
||||
const originalText = $btn.html();
|
||||
|
||||
$btn.html('<i class="fa-solid fa-spinner fa-spin"></i> 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(`<i class="fa-solid fa-check-circle"></i> ${result.message}`)
|
||||
.slideDown();
|
||||
toastr.success(result.message);
|
||||
} else {
|
||||
$result.addClass('rpg-error-message')
|
||||
.html(`<i class="fa-solid fa-exclamation-circle"></i> ${result.message}`)
|
||||
.slideDown();
|
||||
toastr.error(result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
$result.addClass('rpg-error-message')
|
||||
.html(`<i class="fa-solid fa-exclamation-circle"></i> Error: ${error.message}`)
|
||||
.slideDown();
|
||||
} finally {
|
||||
$btn.html(originalText).prop('disabled', false);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize UI state (enable/disable is in Extensions tab)
|
||||
$('#rpg-toggle-auto-update').prop('checked', extensionSettings.autoUpdate);
|
||||
$('#rpg-position-select').val(extensionSettings.panelPosition);
|
||||
@@ -681,6 +774,20 @@ async function initUI() {
|
||||
$('#rpg-custom-accent').val(extensionSettings.customColors.accent);
|
||||
$('#rpg-custom-text').val(extensionSettings.customColors.text);
|
||||
$('#rpg-custom-highlight').val(extensionSettings.customColors.highlight);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
$('#rpg-generation-mode').val(extensionSettings.generationMode);
|
||||
$('#rpg-skip-guided-mode').val(extensionSettings.skipInjectionsForGuided);
|
||||
$('#rpg-save-tracker-history').prop('checked', extensionSettings.saveTrackerHistory);
|
||||
|
||||
@@ -172,6 +172,14 @@ export let extensionSettings = {
|
||||
// Auto avatar generation settings
|
||||
autoGenerateAvatars: false, // Master toggle for auto-generating avatars
|
||||
avatarLLMCustomInstruction: '', // Custom instruction for LLM prompt generation
|
||||
// External API settings for 'external' generation mode
|
||||
externalApiSettings: {
|
||||
baseUrl: '', // OpenAI-compatible API base URL (e.g., "https://api.openai.com/v1")
|
||||
// apiKey is NOT stored here for security. It is stored in localStorage('rpg_companion_api_key')
|
||||
model: '', // Model identifier (e.g., "gpt-4o-mini")
|
||||
maxTokens: 8192, // Maximum tokens for generation
|
||||
temperature: 0.7 // Temperature setting for generation
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+12
-1
@@ -53,7 +53,18 @@
|
||||
"template.settingsModal.advanced.generationMode": "Generation Mode:",
|
||||
"template.settingsModal.advanced.generationModeOptions.together": "Together with Main Generation",
|
||||
"template.settingsModal.advanced.generationModeOptions.separate": "Separate Generation",
|
||||
"template.settingsModal.advanced.generationModeNote": "Together: Adds RPG tracking to main roleplay. Separate: Generates RPG data separately (manual or auto).",
|
||||
"template.settingsModal.advanced.generationModeNote": "Together: Adds RPG tracking to main roleplay. Separate: Generates RPG data separately (manual or auto). External: Connects to an OpenAI-compatible endpoint directly.",
|
||||
"template.settingsModal.advanced.generationModeOptions.external": "External API",
|
||||
"template.settingsModal.advanced.externalApi.title": "External API Settings",
|
||||
"template.settingsModal.advanced.externalApi.baseUrl": "API Base URL",
|
||||
"template.settingsModal.advanced.externalApi.baseUrlNote": "OpenAI-compatible endpoint (e.g., OpenAI, OpenRouter, local LLM server)",
|
||||
"template.settingsModal.advanced.externalApi.apiKey": "API Key",
|
||||
"template.settingsModal.advanced.externalApi.apiKeyNote": "Your API key for the external service",
|
||||
"template.settingsModal.advanced.externalApi.model": "Model",
|
||||
"template.settingsModal.advanced.externalApi.modelNote": "Model identifier (e.g., gpt-4o-mini, claude-3-haiku, mistral-7b)",
|
||||
"template.settingsModal.advanced.externalApi.maxTokens": "Max Tokens",
|
||||
"template.settingsModal.advanced.externalApi.temperature": "Temperature",
|
||||
"template.settingsModal.advanced.externalApi.testConnection": "Test Connection",
|
||||
"template.settingsModal.advanced.contextMessages": "Context Messages:",
|
||||
"template.settingsModal.advanced.contextMessagesNote": "Number of recent messages to include (Separate mode only)",
|
||||
"template.settingsModal.advanced.memoryBatchSize": "Memory Batch Size:",
|
||||
|
||||
+12
-1
@@ -44,7 +44,18 @@
|
||||
"template.settingsModal.advanced.generationMode": "生成模式:",
|
||||
"template.settingsModal.advanced.generationModeOptions.together": "同時生成",
|
||||
"template.settingsModal.advanced.generationModeOptions.separate": "單獨生成",
|
||||
"template.settingsModal.advanced.generationModeNote": "同時生成:將 RPG 追蹤添加到主要提示詞中一同生成。單獨生成:分開生成 RPG 數據。(就是手動或自動的差別)。",
|
||||
"template.settingsModal.advanced.generationModeNote": "同時生成:將 RPG 追蹤添加到主要提示詞中一同生成。單獨生成:分開生成 RPG 數據。(就是手動或自動的差別)。外部 API:直接連接 OpenAI 兼容端點生成數據。",
|
||||
"template.settingsModal.advanced.generationModeOptions.external": "外部 API",
|
||||
"template.settingsModal.advanced.externalApi.title": "外部 API 設定",
|
||||
"template.settingsModal.advanced.externalApi.baseUrl": "API 基礎 URL",
|
||||
"template.settingsModal.advanced.externalApi.baseUrlNote": "OpenAI 兼容端點(例如 OpenAI、OpenRouter、本地 LLM 伺服器)",
|
||||
"template.settingsModal.advanced.externalApi.apiKey": "API 金鑰",
|
||||
"template.settingsModal.advanced.externalApi.apiKeyNote": "外部服務的 API 金鑰",
|
||||
"template.settingsModal.advanced.externalApi.model": "模型",
|
||||
"template.settingsModal.advanced.externalApi.modelNote": "模型識別碼(例如 gpt-4o-mini、claude-3-haiku、mistral-7b)",
|
||||
"template.settingsModal.advanced.externalApi.maxTokens": "最大 Token",
|
||||
"template.settingsModal.advanced.externalApi.temperature": "溫度 (Temperature)",
|
||||
"template.settingsModal.advanced.externalApi.testConnection": "測試連接",
|
||||
"template.settingsModal.advanced.contextMessages": "上下文訊息:",
|
||||
"template.settingsModal.advanced.contextMessagesNote": "包含的最近訊息數量(僅限單獨生成模式)",
|
||||
"template.settingsModal.advanced.memoryBatchSize": "記憶批次大小:",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { selected_group, getGroupMembers } from '../../../../../../group-chats.j
|
||||
import { extensionSettings, sessionAvatarPrompts, setSessionAvatarPrompt } from '../../core/state.js';
|
||||
import { saveSettings } from '../../core/persistence.js';
|
||||
import { generateAvatarPromptGenerationPrompt, parseAvatarPromptsResponse } from '../generation/promptBuilder.js';
|
||||
import { getCurrentPresetName, switchToPreset } from '../generation/apiClient.js';
|
||||
import { getCurrentPresetName, switchToPreset, generateWithExternalAPI } from '../generation/apiClient.js';
|
||||
|
||||
// Generation state - tracks characters currently being generated
|
||||
const pendingGenerations = new Set();
|
||||
@@ -260,10 +260,17 @@ async function generateLLMPrompts(characterNames) {
|
||||
console.log('[RPG Avatar] Generating LLM prompts for:', characterNames);
|
||||
|
||||
const promptMessages = await generateAvatarPromptGenerationPrompt(characterNames);
|
||||
const response = await generateRaw({
|
||||
let response;
|
||||
|
||||
if (extensionSettings.generationMode === 'external') {
|
||||
console.log('[RPG Avatar] Using external API for avatar prompt generation');
|
||||
response = await generateWithExternalAPI(promptMessages);
|
||||
} else {
|
||||
response = await generateRaw({
|
||||
prompt: promptMessages,
|
||||
quietToLoud: false
|
||||
});
|
||||
}
|
||||
|
||||
if (response) {
|
||||
const prompts = parseAvatarPromptsResponse(response);
|
||||
|
||||
@@ -30,6 +30,123 @@ import { generateAvatarsForCharacters } from '../features/avatarGenerator.js';
|
||||
// Store the original preset name to restore after tracker generation
|
||||
let originalPresetName = null;
|
||||
|
||||
/**
|
||||
* Generates tracker data using an external OpenAI-compatible API.
|
||||
* Used when generationMode is 'external'.
|
||||
*
|
||||
* @param {Array<{role: string, content: string}>} messages - Array of message objects for the API
|
||||
* @returns {Promise<string>} The generated response content
|
||||
* @throws {Error} If the API call fails or configuration is invalid
|
||||
*/
|
||||
export async function generateWithExternalAPI(messages) {
|
||||
const { baseUrl, model, maxTokens, temperature } = extensionSettings.externalApiSettings || {};
|
||||
// Retrieve API key from secure storage (not shared extension settings)
|
||||
const apiKey = localStorage.getItem('rpg_companion_external_api_key');
|
||||
|
||||
// Validate required settings
|
||||
if (!baseUrl || !baseUrl.trim()) {
|
||||
throw new Error('External API base URL is not configured');
|
||||
}
|
||||
if (!apiKey || !apiKey.trim()) {
|
||||
throw new Error('External API key is not found. If you switched browsers or cleared your cache, please re-enter your API key in the extension settings.');
|
||||
}
|
||||
if (!model || !model.trim()) {
|
||||
throw new Error('External API model is not configured');
|
||||
}
|
||||
|
||||
// Normalize base URL (remove trailing slash if present)
|
||||
const normalizedBaseUrl = baseUrl.trim().replace(/\/+$/, '');
|
||||
const endpoint = `${normalizedBaseUrl}/chat/completions`;
|
||||
|
||||
console.log(`[RPG Companion] Calling external API: ${normalizedBaseUrl} with model: ${model}`);
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiKey.trim()}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: model.trim(),
|
||||
messages: messages,
|
||||
max_tokens: maxTokens || 2048,
|
||||
temperature: temperature ?? 0.7
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
let errorMessage = `External API error: ${response.status} ${response.statusText}`;
|
||||
try {
|
||||
const errorJson = JSON.parse(errorText);
|
||||
if (errorJson.error?.message) {
|
||||
errorMessage = `External API error: ${errorJson.error.message}`;
|
||||
}
|
||||
} catch (e) {
|
||||
// If parsing fails, use the raw text if it's short enough
|
||||
if (errorText.length < 200) {
|
||||
errorMessage = `External API error: ${errorText}`;
|
||||
}
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.choices || !data.choices[0] || !data.choices[0].message) {
|
||||
throw new Error('Invalid response format from external API');
|
||||
}
|
||||
|
||||
const content = data.choices[0].message.content;
|
||||
console.log('[RPG Companion] External API response received successfully');
|
||||
|
||||
return content;
|
||||
} catch (error) {
|
||||
if (error.name === 'TypeError' && (error.message.includes('fetch') || error.message.includes('Failed to fetch') || error.message.includes('NetworkError'))) {
|
||||
throw new Error(`CORS Access Blocked: This API endpoint (${normalizedBaseUrl}) does not allow direct access from a browser. This is a browser security restriction (CORS), not a bug in the extension. Please use an endpoint that supports CORS (like OpenRouter or a local proxy) or use SillyTavern's internal API system (Separate Mode).`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the external API connection with a simple request.
|
||||
* @returns {Promise<{success: boolean, message: string, model?: string}>}
|
||||
*/
|
||||
export async function testExternalAPIConnection() {
|
||||
const { baseUrl, model } = extensionSettings.externalApiSettings || {};
|
||||
const apiKey = localStorage.getItem('rpg_companion_external_api_key');
|
||||
|
||||
if (!baseUrl || !apiKey || !model) {
|
||||
return {
|
||||
success: false,
|
||||
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)'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const testMessages = [
|
||||
{ role: 'user', content: 'Respond with exactly: "Connection successful"' }
|
||||
];
|
||||
|
||||
const response = await generateWithExternalAPI(testMessages);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Connection successful! Model: ${model}`,
|
||||
model: model
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error.message || 'Connection failed'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current preset name using the /preset command
|
||||
* @returns {Promise<string|null>} Current preset name or null if unavailable
|
||||
@@ -100,11 +217,13 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
||||
return;
|
||||
}
|
||||
|
||||
if (extensionSettings.generationMode !== 'separate') {
|
||||
// console.log('[RPG Companion] Not in separate mode, skipping manual update');
|
||||
if (extensionSettings.generationMode !== 'separate' && extensionSettings.generationMode !== 'external') {
|
||||
// console.log('[RPG Companion] Not in separate or external mode, skipping manual update');
|
||||
return;
|
||||
}
|
||||
|
||||
const isExternalMode = extensionSettings.generationMode === 'external';
|
||||
|
||||
try {
|
||||
setIsGenerating(true);
|
||||
|
||||
@@ -114,13 +233,14 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
||||
$updateBtn.html(`<i class="fa-solid fa-spinner fa-spin"></i> ${updatingText}`).prop('disabled', true);
|
||||
|
||||
// Save current preset name before switching (if we're going to switch)
|
||||
if (extensionSettings.useSeparatePreset) {
|
||||
// Note: Preset switching is only used in separate mode, not external mode
|
||||
if (!isExternalMode && extensionSettings.useSeparatePreset) {
|
||||
originalPresetName = await getCurrentPresetName();
|
||||
console.log(`[RPG Companion] Saved original preset: "${originalPresetName}"`);
|
||||
}
|
||||
|
||||
// Switch to separate preset if enabled
|
||||
if (extensionSettings.useSeparatePreset) {
|
||||
// Switch to separate preset if enabled (separate mode only)
|
||||
if (!isExternalMode && extensionSettings.useSeparatePreset) {
|
||||
const switched = await switchToPreset('RPG Companion Trackers');
|
||||
if (!switched) {
|
||||
console.warn('[RPG Companion] Failed to switch to RPG Companion Trackers preset. Using current preset.');
|
||||
@@ -130,11 +250,19 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
||||
|
||||
const prompt = await generateSeparateUpdatePrompt();
|
||||
|
||||
// Generate using raw prompt (uses current preset, no chat history)
|
||||
const response = await generateRaw({
|
||||
// Generate response based on mode
|
||||
let response;
|
||||
if (isExternalMode) {
|
||||
// External mode: Use external OpenAI-compatible API directly
|
||||
console.log('[RPG Companion] Using external API for tracker generation');
|
||||
response = await generateWithExternalAPI(prompt);
|
||||
} else {
|
||||
// Separate mode: Use SillyTavern's generateRaw
|
||||
response = await generateRaw({
|
||||
prompt: prompt,
|
||||
quietToLoud: false
|
||||
});
|
||||
}
|
||||
|
||||
if (response) {
|
||||
// console.log('[RPG Companion] Raw AI response:', response);
|
||||
@@ -245,6 +373,9 @@ export async function updateRPGData(renderUserStats, renderInfoBox, renderThough
|
||||
|
||||
} catch (error) {
|
||||
console.error('[RPG Companion] Error updating RPG data:', error);
|
||||
if (isExternalMode) {
|
||||
toastr.error(error.message, 'RPG Companion External API Error');
|
||||
}
|
||||
} finally {
|
||||
// Restore original preset if we switched to a separate one
|
||||
if (originalPresetName && extensionSettings.useSeparatePreset) {
|
||||
|
||||
@@ -293,8 +293,17 @@ export function updateGenerationModeUI() {
|
||||
if (extensionSettings.generationMode === 'together') {
|
||||
// In "together" mode, manual update button is hidden
|
||||
$('#rpg-manual-update').hide();
|
||||
} else {
|
||||
$('#rpg-external-api-settings').slideUp(200);
|
||||
$('#rpg-separate-mode-settings').slideUp(200);
|
||||
} 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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
+262
-91
@@ -73,16 +73,19 @@
|
||||
|
||||
<!-- Manual Update Button -->
|
||||
<button id="rpg-manual-update" class="rpg-btn-primary rpg-manual-update-btn">
|
||||
<i class="fa-solid fa-sync"></i> <span data-i18n-key="template.mainPanel.refreshRpgInfo">Refresh RPG Info</span>
|
||||
<i class="fa-solid fa-sync"></i> <span data-i18n-key="template.mainPanel.refreshRpgInfo">Refresh RPG
|
||||
Info</span>
|
||||
</button>
|
||||
|
||||
<!-- Settings and Edit Trackers Buttons Row -->
|
||||
<div class="rpg-settings-buttons-row">
|
||||
<button id="rpg-open-tracker-editor" class="rpg-btn-settings rpg-btn-half">
|
||||
<i class="fa-solid fa-sliders"></i> <span data-i18n-key="template.mainPanel.editTrackersButton">Edit Trackers</span>
|
||||
<i class="fa-solid fa-sliders"></i> <span data-i18n-key="template.mainPanel.editTrackersButton">Edit
|
||||
Trackers</span>
|
||||
</button>
|
||||
<button id="rpg-open-settings" class="rpg-btn-settings rpg-btn-half">
|
||||
<i class="fa-solid fa-gear"></i> <span data-i18n-key="template.mainPanel.settingsButton">Settings</span>
|
||||
<i class="fa-solid fa-gear"></i> <span
|
||||
data-i18n-key="template.mainPanel.settingsButton">Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,79 +93,103 @@
|
||||
</div>
|
||||
|
||||
<!-- Settings Modal -->
|
||||
<div id="rpg-settings-popup" class="rpg-settings-popup" role="dialog" aria-modal="true" aria-labelledby="rpg-settings-title">
|
||||
<div id="rpg-settings-popup" class="rpg-settings-popup" role="dialog" aria-modal="true"
|
||||
aria-labelledby="rpg-settings-title">
|
||||
<div class="rpg-settings-popup-content">
|
||||
<header class="rpg-settings-popup-header">
|
||||
<h3 id="rpg-settings-title">
|
||||
<i class="fa-solid fa-gear" aria-hidden="true"></i>
|
||||
<span data-i18n-key="template.settingsTitle">RPG Companion Settings</span>
|
||||
</h3>
|
||||
<button id="rpg-close-settings" class="rpg-popup-close" type="button" aria-label="Close settings">×</button>
|
||||
<button id="rpg-close-settings" class="rpg-popup-close" type="button"
|
||||
aria-label="Close settings">×</button>
|
||||
</header>
|
||||
<div class="rpg-settings-popup-body">
|
||||
<div class="rpg-settings-group">
|
||||
<h4 data-i18n-key="template.settingsModal.themeTitle"><i class="fa-solid fa-palette" aria-hidden="true"></i> Theme</h4>
|
||||
<h4 data-i18n-key="template.settingsModal.themeTitle"><i class="fa-solid fa-palette"
|
||||
aria-hidden="true"></i> Theme</h4>
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-theme-select" data-i18n-key="template.settingsModal.themeLabel">Visual Theme:</label>
|
||||
<label for="rpg-theme-select" data-i18n-key="template.settingsModal.themeLabel">Visual
|
||||
Theme:</label>
|
||||
<select id="rpg-theme-select" class="rpg-select">
|
||||
<option value="default" data-i18n-key="template.settingsModal.themeOptions.default">Default</option>
|
||||
<option value="sci-fi" data-i18n-key="template.settingsModal.themeOptions.sciFi">Sci-Fi (Synthwave)</option>
|
||||
<option value="fantasy" data-i18n-key="template.settingsModal.themeOptions.fantasy">Fantasy (Rustic Parchment)</option>
|
||||
<option value="cyberpunk" data-i18n-key="template.settingsModal.themeOptions.cyberpunk">Cyberpunk (Neon Grid)</option>
|
||||
<option value="custom" data-i18n-key="template.settingsModal.themeOptions.custom">Custom</option>
|
||||
<option value="default" data-i18n-key="template.settingsModal.themeOptions.default">Default
|
||||
</option>
|
||||
<option value="sci-fi" data-i18n-key="template.settingsModal.themeOptions.sciFi">Sci-Fi
|
||||
(Synthwave)</option>
|
||||
<option value="fantasy" data-i18n-key="template.settingsModal.themeOptions.fantasy">Fantasy
|
||||
(Rustic Parchment)</option>
|
||||
<option value="cyberpunk" data-i18n-key="template.settingsModal.themeOptions.cyberpunk">
|
||||
Cyberpunk (Neon Grid)</option>
|
||||
<option value="custom" data-i18n-key="template.settingsModal.themeOptions.custom">Custom
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Custom Theme Colors (Hidden by default) -->
|
||||
<div id="rpg-custom-colors" class="rpg-custom-colors" style="display: none;">
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-custom-bg" data-i18n-key="template.settingsModal.themeOptions.custom.background">Background:</label>
|
||||
<label for="rpg-custom-bg"
|
||||
data-i18n-key="template.settingsModal.themeOptions.custom.background">Background:</label>
|
||||
<input type="color" id="rpg-custom-bg" value="#1a1a2e" />
|
||||
</div>
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-custom-accent" data-i18n-key="template.settingsModal.themeOptions.custom.accent">Accent:</label>
|
||||
<label for="rpg-custom-accent"
|
||||
data-i18n-key="template.settingsModal.themeOptions.custom.accent">Accent:</label>
|
||||
<input type="color" id="rpg-custom-accent" value="#16213e" />
|
||||
</div>
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-custom-text" data-i18n-key="template.settingsModal.themeOptions.custom.text">Text:</label>
|
||||
<label for="rpg-custom-text"
|
||||
data-i18n-key="template.settingsModal.themeOptions.custom.text">Text:</label>
|
||||
<input type="color" id="rpg-custom-text" value="#eaeaea" />
|
||||
</div>
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-custom-highlight" data-i18n-key="template.settingsModal.themeOptions.custom.highlight">Highlight:</label>
|
||||
<label for="rpg-custom-highlight"
|
||||
data-i18n-key="template.settingsModal.themeOptions.custom.highlight">Highlight:</label>
|
||||
<input type="color" id="rpg-custom-highlight" value="#e94560" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-stat-bar-color-low" data-i18n-key="template.settingsModal.theme.statBarLow">Stat Bar Color (Low):</label>
|
||||
<label for="rpg-stat-bar-color-low" data-i18n-key="template.settingsModal.theme.statBarLow">Stat Bar
|
||||
Color (Low):</label>
|
||||
<input type="color" id="rpg-stat-bar-color-low" value="#cc3333" />
|
||||
<small data-i18n-key="template.settingsModal.theme.statBarLowNote">Color when stats are at 0%</small>
|
||||
<small data-i18n-key="template.settingsModal.theme.statBarLowNote">Color when stats are at
|
||||
0%</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-stat-bar-color-high" data-i18n-key="template.settingsModal.theme.statBarHigh">Stat Bar Color (High):</label>
|
||||
<label for="rpg-stat-bar-color-high" data-i18n-key="template.settingsModal.theme.statBarHigh">Stat
|
||||
Bar Color (High):</label>
|
||||
<input type="color" id="rpg-stat-bar-color-high" value="#33cc66" />
|
||||
<small data-i18n-key="template.settingsModal.theme.statBarHighNote">Color when stats are at 100%</small>
|
||||
<small data-i18n-key="template.settingsModal.theme.statBarHighNote">Color when stats are at
|
||||
100%</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rpg-settings-group">
|
||||
<h4 data-i18n-key="template.settingsModal.displayTitle"><i class="fa-solid fa-toggle-on" aria-hidden="true"></i> Display Options</h4>
|
||||
<small class="notes" style="display: block; margin-bottom: 10px;" data-i18n-key="template.settingsModal.displayNote">
|
||||
<i class="fa-solid fa-info-circle" aria-hidden="true"></i> Use the Extensions tab to enable/disable the RPG Companion extension.
|
||||
<h4 data-i18n-key="template.settingsModal.displayTitle"><i class="fa-solid fa-toggle-on"
|
||||
aria-hidden="true"></i> Display Options</h4>
|
||||
<small class="notes" style="display: block; margin-bottom: 10px;"
|
||||
data-i18n-key="template.settingsModal.displayNote">
|
||||
<i class="fa-solid fa-info-circle" aria-hidden="true"></i> Use the Extensions tab to enable/disable
|
||||
the RPG Companion extension.
|
||||
</small>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-position-select" data-i18n-key="template.settingsModal.display.panelPosition">Panel Position:</label>
|
||||
<label for="rpg-position-select" data-i18n-key="template.settingsModal.display.panelPosition">Panel
|
||||
Position:</label>
|
||||
<select id="rpg-position-select" class="rpg-select">
|
||||
<option value="right" data-i18n-key="template.settingsModal.display.panelPositionOptions.right">Right Sidebar</option>
|
||||
<option value="left" data-i18n-key="template.settingsModal.display.panelPositionOptions.left">Left Sidebar</option>
|
||||
<option value="right" data-i18n-key="template.settingsModal.display.panelPositionOptions.right">
|
||||
Right Sidebar</option>
|
||||
<option value="left" data-i18n-key="template.settingsModal.display.panelPositionOptions.left">
|
||||
Left Sidebar</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-auto-update" />
|
||||
<span data-i18n-key="template.settingsModal.display.toggleAutoUpdate">Auto-update after messages</span>
|
||||
<span data-i18n-key="template.settingsModal.display.toggleAutoUpdate">Auto-update after
|
||||
messages</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
@@ -177,15 +204,18 @@
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-thoughts" />
|
||||
<span data-i18n-key="template.settingsModal.display.showPresentCharacters">Show Present Characters</span>
|
||||
<span data-i18n-key="template.settingsModal.display.showPresentCharacters">Show Present
|
||||
Characters</span>
|
||||
</label>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-narrator-mode" />
|
||||
<span data-i18n-key="template.settingsModal.display.narratorMode">Narrator Mode</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.narratorModeNote">
|
||||
Use character card as narrator. Infer characters from context instead of using fixed character references.
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.narratorModeNote">
|
||||
Use character card as narrator. Infer characters from context instead of using fixed character
|
||||
references.
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label">
|
||||
@@ -202,15 +232,18 @@
|
||||
<input type="checkbox" id="rpg-toggle-thoughts-in-chat" />
|
||||
<span data-i18n-key="template.settingsModal.display.showThoughtsInChat">Show Thoughts in Chat</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.showThoughtsInChatNote">
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.showThoughtsInChatNote">
|
||||
Display character thoughts as overlay bubbles next to their messages
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-always-show-bubble" />
|
||||
<span data-i18n-key="template.settingsModal.display.alwaysShowThoughtBubble">Always Show Thought Bubble</span>
|
||||
<span data-i18n-key="template.settingsModal.display.alwaysShowThoughtBubble">Always Show Thought
|
||||
Bubble</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.alwaysShowThoughtBubbleNote">
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.alwaysShowThoughtBubbleNote">
|
||||
Auto-expand thought bubble without clicking the icon first
|
||||
</small>
|
||||
|
||||
@@ -218,15 +251,18 @@
|
||||
<input type="checkbox" id="rpg-toggle-animations" />
|
||||
<span data-i18n-key="template.settingsModal.display.enableAnimations">Enable Animations</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.enableAnimationsNote">
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.enableAnimationsNote">
|
||||
Smooth transitions for stats, content updates, and dice rolls
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-plot-buttons" />
|
||||
<span data-i18n-key="template.settingsModal.display.showPlotProgressionButtons">Show Plot Progression Buttons</span>
|
||||
<span data-i18n-key="template.settingsModal.display.showPlotProgressionButtons">Show Plot
|
||||
Progression Buttons</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.showPlotProgressionButtonsNote">
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.showPlotProgressionButtonsNote">
|
||||
Display buttons above chat input for plot progression prompts
|
||||
</small>
|
||||
|
||||
@@ -234,7 +270,8 @@
|
||||
<input type="checkbox" id="rpg-toggle-dice-display" />
|
||||
<span data-i18n-key="template.settingsModal.display.showDiceDisplay">Show Dice Roll Display</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.showDiceDisplayNote">
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.showDiceDisplayNote">
|
||||
Display the "Last Roll" indicator in the panel.
|
||||
</small>
|
||||
|
||||
@@ -242,27 +279,36 @@
|
||||
<input type="checkbox" id="rpg-toggle-debug-mode" />
|
||||
<span data-i18n-key="template.settingsModal.display.enableDebugMode">Enable Debug Mode</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.enableDebugModeNote">
|
||||
Shows parser logs in a mobile-friendly UI panel. Useful for troubleshooting. Look for the red bug button.
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.enableDebugModeNote">
|
||||
Shows parser logs in a mobile-friendly UI panel. Useful for troubleshooting. Look for the red bug
|
||||
button.
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-toggle-auto-avatars" />
|
||||
<span data-i18n-key="template.settingsModal.display.autoGenerateAvatars">Auto-generate Missing Avatars</span>
|
||||
<span data-i18n-key="template.settingsModal.display.autoGenerateAvatars">Auto-generate Missing
|
||||
Avatars</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.autoGenerateAvatarsNote">
|
||||
Automatically generate avatars for characters without custom images using the Image Generation Plugin
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.autoGenerateAvatarsNote">
|
||||
Automatically generate avatars for characters without custom images using the Image Generation
|
||||
Plugin
|
||||
</small>
|
||||
|
||||
<!-- Avatar options container - conditionally visible -->
|
||||
<div id="rpg-avatar-options" style="margin-left: 24px; margin-top: 12px; display: none;">
|
||||
<div class="rpg-setting-row" style="margin-top: 12px;">
|
||||
<label for="rpg-avatar-llm-instruction" style="display: block; margin-bottom: 8px;" data-i18n-key="template.settingsModal.display.avatarLLMInstruction">
|
||||
<label for="rpg-avatar-llm-instruction" style="display: block; margin-bottom: 8px;"
|
||||
data-i18n-key="template.settingsModal.display.avatarLLMInstruction">
|
||||
LLM Instruction:
|
||||
</label>
|
||||
<textarea id="rpg-avatar-llm-instruction" class="rpg-textarea" rows="3" placeholder="Create a detailed portrait prompt focusing on the character's appearance, clothing, and mood..."></textarea>
|
||||
<small style="display: block; margin-top: 4px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.display.avatarLLMInstructionNote">
|
||||
The LLM will use character cards, tracker data, and chat context to generate detailed prompts
|
||||
<textarea id="rpg-avatar-llm-instruction" class="rpg-textarea" rows="3"
|
||||
placeholder="Create a detailed portrait prompt focusing on the character's appearance, clothing, and mood..."></textarea>
|
||||
<small style="display: block; margin-top: 4px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.display.avatarLLMInstructionNote">
|
||||
The LLM will use character cards, tracker data, and chat context to generate detailed
|
||||
prompts
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -281,7 +327,8 @@
|
||||
|
||||
<div class="rpg-setting-row" style="margin-top: 12px;">
|
||||
<label for="rpg-encounter-history-depth">Chat History Depth:</label>
|
||||
<input type="number" id="rpg-encounter-history-depth" min="1" max="20" value="8" class="rpg-input" />
|
||||
<input type="number" id="rpg-encounter-history-depth" min="1" max="20" value="8"
|
||||
class="rpg-input" />
|
||||
<small>Number of recent messages to include in combat initialization</small>
|
||||
</div>
|
||||
|
||||
@@ -295,93 +342,207 @@
|
||||
</div>
|
||||
|
||||
<div class="rpg-settings-group">
|
||||
<h4 data-i18n-key="template.settingsModal.advancedTitle"><i class="fa-solid fa-sliders" aria-hidden="true"></i> Advanced</h4>
|
||||
<h4 data-i18n-key="template.settingsModal.advancedTitle"><i class="fa-solid fa-sliders"
|
||||
aria-hidden="true"></i> Advanced</h4>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-generation-mode" data-i18n-key="template.settingsModal.advanced.generationMode">Generation Mode:</label>
|
||||
<label for="rpg-generation-mode"
|
||||
data-i18n-key="template.settingsModal.advanced.generationMode">Generation Mode:</label>
|
||||
<select id="rpg-generation-mode" class="rpg-select">
|
||||
<option value="together" data-i18n-key="template.settingsModal.advanced.generationModeOptions.together">Together with Main Generation</option>
|
||||
<option value="separate" data-i18n-key="template.settingsModal.advanced.generationModeOptions.separate">Separate Generation</option>
|
||||
<option value="together"
|
||||
data-i18n-key="template.settingsModal.advanced.generationModeOptions.together">Together with
|
||||
Main Generation</option>
|
||||
<option value="separate"
|
||||
data-i18n-key="template.settingsModal.advanced.generationModeOptions.separate">Separate
|
||||
Generation</option>
|
||||
<option value="external"
|
||||
data-i18n-key="template.settingsModal.advanced.generationModeOptions.external">External API
|
||||
</option>
|
||||
</select>
|
||||
<small data-i18n-key="template.settingsModal.advanced.generationModeNote">Together: Adds RPG tracking to main roleplay. Separate: Generates RPG data separately (manual or auto).</small>
|
||||
<small data-i18n-key="template.settingsModal.advanced.generationModeNote">Together: Adds RPG
|
||||
tracking to main roleplay. Separate: Generates RPG data separately (manual or auto).</small>
|
||||
</div>
|
||||
|
||||
<!-- External API Settings (shown only when External API mode is selected) -->
|
||||
<div id="rpg-external-api-settings" class="rpg-external-api-settings"
|
||||
style="display: none; padding: 12px; border-left: 2px solid var(--SmartThemeBorderColor); margin-top: 8px;">
|
||||
<h5 style="margin-top: 0; margin-bottom: 12px; color: var(--SmartThemeBodyColor);">
|
||||
<i class="fa-solid fa-cloud" aria-hidden="true"></i>
|
||||
<span data-i18n-key="template.settingsModal.advanced.externalApi.title">External API
|
||||
Configuration</span>
|
||||
</h5>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-external-base-url"
|
||||
data-i18n-key="template.settingsModal.advanced.externalApi.baseUrl">API Base URL:</label>
|
||||
<input type="text" id="rpg-external-base-url" class="rpg-input"
|
||||
placeholder="https://api.openai.com/v1" style="width: 100%;" />
|
||||
<small data-i18n-key="template.settingsModal.advanced.externalApi.baseUrlNote">OpenAI-compatible
|
||||
endpoint (e.g., OpenAI, OpenRouter, local LLM server)</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-update-depth" data-i18n-key="template.settingsModal.advanced.contextMessages">Context Messages:</label>
|
||||
<label for="rpg-external-api-key"
|
||||
data-i18n-key="template.settingsModal.advanced.externalApi.apiKey">API Key:</label>
|
||||
<div style="display: flex; gap: 8px; width: 100%;">
|
||||
<input type="password" id="rpg-external-api-key" class="rpg-input" placeholder="sk-..."
|
||||
style="flex: 1;" />
|
||||
<button id="rpg-toggle-api-key-visibility" class="menu_button" type="button"
|
||||
title="Show/Hide API Key" style="padding: 4px 8px;">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
<small data-i18n-key="template.settingsModal.advanced.externalApi.apiKeyNote">Your API key for
|
||||
the external service</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-external-model"
|
||||
data-i18n-key="template.settingsModal.advanced.externalApi.model">Model:</label>
|
||||
<input type="text" id="rpg-external-model" class="rpg-input" placeholder="gpt-4o-mini"
|
||||
style="width: 100%;" />
|
||||
<small data-i18n-key="template.settingsModal.advanced.externalApi.modelNote">Model identifier
|
||||
(e.g., gpt-4o-mini, claude-3-haiku, mistral-7b)</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row" style="display: flex; gap: 16px;">
|
||||
<div style="flex: 1;">
|
||||
<label for="rpg-external-max-tokens"
|
||||
data-i18n-key="template.settingsModal.advanced.externalApi.maxTokens">Max
|
||||
Tokens:</label>
|
||||
<input type="number" id="rpg-external-max-tokens" class="rpg-input" min="256" max="16384"
|
||||
value="8192" style="width: 100%;" />
|
||||
</div>
|
||||
<div style="flex: 1;">
|
||||
<label for="rpg-external-temperature"
|
||||
data-i18n-key="template.settingsModal.advanced.externalApi.temperature">Temperature:</label>
|
||||
<input type="number" id="rpg-external-temperature" class="rpg-input" min="0" max="2"
|
||||
step="0.1" value="0.7" style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 12px;">
|
||||
<button id="rpg-test-external-api" class="menu_button" type="button" style="width: 100%; display: flex; align-items: center; gap: 8px;">
|
||||
<i class="fa-solid fa-plug"></i>
|
||||
<span data-i18n-key="template.settingsModal.advanced.externalApi.testConnection">Test
|
||||
Connection</span>
|
||||
</button>
|
||||
<div id="rpg-external-api-test-result"
|
||||
style="margin-top: 8px; padding: 8px; border-radius: 4px; display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-update-depth"
|
||||
data-i18n-key="template.settingsModal.advanced.contextMessages">Context Messages:</label>
|
||||
<input type="number" id="rpg-update-depth" min="1" max="20" value="4" class="rpg-input" />
|
||||
<small data-i18n-key="template.settingsModal.advanced.contextMessagesNote">Number of recent messages to include (Separate mode only)</small>
|
||||
<small data-i18n-key="template.settingsModal.advanced.contextMessagesNote">Number of recent messages
|
||||
to include (Separate mode only)</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-memory-messages" data-i18n-key="template.settingsModal.advanced.memoryBatchSize">Memory Batch Size:</label>
|
||||
<label for="rpg-memory-messages"
|
||||
data-i18n-key="template.settingsModal.advanced.memoryBatchSize">Memory Batch Size:</label>
|
||||
<input type="number" id="rpg-memory-messages" min="4" max="50" value="16" class="rpg-input" />
|
||||
<small data-i18n-key="template.settingsModal.advanced.memoryBatchSizeNote">Number of messages to process per batch in Memory Recollection</small>
|
||||
<small data-i18n-key="template.settingsModal.advanced.memoryBatchSizeNote">Number of messages to
|
||||
process per batch in Memory Recollection</small>
|
||||
</div>
|
||||
|
||||
<div id="rpg-separate-mode-settings">
|
||||
<label class="checkbox_label">
|
||||
<input type="checkbox" id="rpg-use-separate-preset" />
|
||||
<span data-i18n-key="template.settingsModal.advanced.useSeparatePreset">Use model connected to RPG Companion Trackers preset</span>
|
||||
<span data-i18n-key="template.settingsModal.advanced.useSeparatePreset">Use model connected to RPG
|
||||
Companion Trackers preset</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.useSeparatePresetNote">
|
||||
Separate mode only. When enabled, tracker generation will use the model from the "RPG Companion Trackers" preset instead of your main API model. The preset will be switched automatically during generation and restored afterward. Select the desired model in that preset and make sure the "Bind presets to API connections" toggle is on (next to the import/export preset buttons).
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.advanced.useSeparatePresetNote">
|
||||
Separate mode only. When enabled, tracker generation will use the model from the "RPG Companion
|
||||
Trackers" preset instead of your main API model. The preset will be switched automatically during
|
||||
generation and restored afterward. Select the desired model in that preset and make sure the "Bind
|
||||
presets to API connections" toggle is on (next to the import/export preset buttons).
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="rpg-setting-row">
|
||||
<label for="rpg-skip-guided-mode" data-i18n-key="template.settingsModal.advanced.skipInjections">Skip Injections during Guided Generations:</label>
|
||||
<label for="rpg-skip-guided-mode"
|
||||
data-i18n-key="template.settingsModal.advanced.skipInjections">Skip Injections during Guided
|
||||
Generations:</label>
|
||||
<select id="rpg-skip-guided-mode" class="rpg-select">
|
||||
<option value="none" data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.none">Never skip</option>
|
||||
<option value="impersonation" data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.impersonation">Only on impersonation requests</option>
|
||||
<option value="guided" data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.guided">Always for guided or quiet prompts</option>
|
||||
<option value="none" data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.none">
|
||||
Never skip</option>
|
||||
<option value="impersonation"
|
||||
data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.impersonation">Only on
|
||||
impersonation requests</option>
|
||||
<option value="guided"
|
||||
data-i18n-key="template.settingsModal.advanced.skipInjectionsOptions.guided">Always for
|
||||
guided or quiet prompts</option>
|
||||
</select>
|
||||
</div>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="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.
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="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.
|
||||
</small>
|
||||
|
||||
<label class="checkbox_label" style="margin-top: 16px;">
|
||||
<input type="checkbox" id="rpg-save-tracker-history" />
|
||||
<span data-i18n-key="template.settingsModal.advanced.saveTrackerHistory">Save Tracker History in Chat</span>
|
||||
<span data-i18n-key="template.settingsModal.advanced.saveTrackerHistory">Save Tracker History in
|
||||
Chat</span>
|
||||
</label>
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.saveTrackerHistoryNote">
|
||||
When enabled, tracker data is saved in chat history for each message. In Together mode, trackers appear in <trackers> XML tags (hidden from display). In Separate mode, tracker data is stored in message metadata. When disabled, only the most recent trackers are kept.
|
||||
<small style="display: block; margin-left: 24px; margin-top: -8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.advanced.saveTrackerHistoryNote">
|
||||
When enabled, tracker data is saved in chat history for each message. In Together mode, trackers
|
||||
appear in <trackers> XML tags (hidden from display). In Separate mode, tracker data is stored
|
||||
in message metadata. When disabled, only the most recent trackers are kept.
|
||||
</small>
|
||||
|
||||
<!-- 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">
|
||||
<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">
|
||||
<i class="fa-solid fa-code" aria-hidden="true"></i> Custom HTML Prompt:
|
||||
</label>
|
||||
|
||||
<textarea id="rpg-custom-html-prompt"
|
||||
style="width: 100%; min-height: 120px; padding: 10px; border-radius: 4px;
|
||||
<textarea id="rpg-custom-html-prompt" style="width: 100%; min-height: 120px; 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>
|
||||
resize: vertical; line-height: 1.5;" placeholder=""></textarea>
|
||||
<div style="margin-top: 8px; display: flex; gap: 8px;">
|
||||
<button id="rpg-restore-default-html-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.restoreDefaultHtmlPrompt">Restore Default</span>
|
||||
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i> <span
|
||||
data-i18n-key="template.settingsModal.advanced.restoreDefaultHtmlPrompt">Restore
|
||||
Default</span>
|
||||
</button>
|
||||
</div>
|
||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;" data-i18n-key="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).
|
||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="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).
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Clear Cache Button -->
|
||||
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
|
||||
<button id="rpg-clear-cache" class="rpg-btn-clear-cache">
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i> <span data-i18n-key="template.settingsModal.advanced.clearCache">Clear Extension Cache</span>
|
||||
<i class="fa-solid fa-trash" aria-hidden="true"></i> <span
|
||||
data-i18n-key="template.settingsModal.advanced.clearCache">Clear Extension Cache</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Reset FAB Positions Button -->
|
||||
<div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--rpg-border);">
|
||||
<button id="rpg-reset-fab-positions" class="rpg-btn-reset-fab">
|
||||
<i class="fa-solid fa-arrows-rotate" aria-hidden="true"></i> <span data-i18n-key="template.settingsModal.advanced.resetFabPositions">Reset Button Positions</span>
|
||||
<i class="fa-solid fa-arrows-rotate" aria-hidden="true"></i> <span
|
||||
data-i18n-key="template.settingsModal.advanced.resetFabPositions">Reset Button
|
||||
Positions</span>
|
||||
</button>
|
||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;" data-i18n-key="template.settingsModal.advanced.resetFabPositionsNote">
|
||||
Resets all floating action buttons (toggle, refresh, debug) to default top-left positions. Useful if buttons are off-screen.
|
||||
<small style="display: block; margin-top: 8px; color: #888; font-size: 11px;"
|
||||
data-i18n-key="template.settingsModal.advanced.resetFabPositionsNote">
|
||||
Resets all floating action buttons (toggle, refresh, debug) to default top-left positions.
|
||||
Useful if buttons are off-screen.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -407,7 +568,8 @@
|
||||
<div class="rpg-dice-selector">
|
||||
<div class="rpg-dice-input-group">
|
||||
<label for="rpg-dice-count">Number of Dice:</label>
|
||||
<input type="number" id="rpg-dice-count" name="dice-count" min="1" max="20" value="1" class="rpg-input" />
|
||||
<input type="number" id="rpg-dice-count" name="dice-count" min="1" max="20" value="1"
|
||||
class="rpg-input" />
|
||||
</div>
|
||||
<div class="rpg-dice-input-group">
|
||||
<label for="rpg-dice-sides">Dice Type:</label>
|
||||
@@ -437,7 +599,8 @@
|
||||
|
||||
<div id="rpg-dice-result" class="rpg-dice-result" hidden aria-live="polite">
|
||||
<div class="rpg-dice-result-label">Result:</div>
|
||||
<output id="rpg-dice-result-value" class="rpg-dice-result-value" for="rpg-dice-count rpg-dice-sides">0</output>
|
||||
<output id="rpg-dice-result-value" class="rpg-dice-result-value"
|
||||
for="rpg-dice-count rpg-dice-sides">0</output>
|
||||
<div id="rpg-dice-result-details" class="rpg-dice-result-details" role="status"></div>
|
||||
<button id="rpg-dice-save-btn" class="rpg-btn-primary rpg-dice-save-btn" type="button">
|
||||
<i class="fa-solid fa-check" aria-hidden="true"></i>
|
||||
@@ -449,26 +612,31 @@
|
||||
</div>
|
||||
|
||||
<!-- Tracker Editor Modal -->
|
||||
<div id="rpg-tracker-editor-popup" class="rpg-settings-popup" role="dialog" aria-modal="true" aria-labelledby="rpg-editor-title" style="display: none;">
|
||||
<div id="rpg-tracker-editor-popup" class="rpg-settings-popup" role="dialog" aria-modal="true"
|
||||
aria-labelledby="rpg-editor-title" style="display: none;">
|
||||
<div class="rpg-settings-popup-content">
|
||||
<header class="rpg-settings-popup-header">
|
||||
<h3 id="rpg-editor-title">
|
||||
<i class="fa-solid fa-sliders" aria-hidden="true"></i>
|
||||
<span data-i18n-key="template.trackerEditorModal.title">Edit Trackers</span>
|
||||
</h3>
|
||||
<button id="rpg-close-tracker-editor" class="rpg-popup-close" type="button" aria-label="Close tracker editor">×</button>
|
||||
<button id="rpg-close-tracker-editor" class="rpg-popup-close" type="button"
|
||||
aria-label="Close tracker editor">×</button>
|
||||
</header>
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="rpg-editor-tabs">
|
||||
<button class="rpg-editor-tab active" data-tab="userStats">
|
||||
<i class="fa-solid fa-heart-pulse"></i> <span data-i18n-key="template.trackerEditorModal.tabs.userStats">User Stats</span>
|
||||
<i class="fa-solid fa-heart-pulse"></i> <span
|
||||
data-i18n-key="template.trackerEditorModal.tabs.userStats">User Stats</span>
|
||||
</button>
|
||||
<button class="rpg-editor-tab" data-tab="infoBox">
|
||||
<i class="fa-solid fa-info-circle"></i> <span data-i18n-key="template.trackerEditorModal.tabs.infoBox">Info Box</span>
|
||||
<i class="fa-solid fa-info-circle"></i> <span
|
||||
data-i18n-key="template.trackerEditorModal.tabs.infoBox">Info Box</span>
|
||||
</button>
|
||||
<button class="rpg-editor-tab" data-tab="presentCharacters">
|
||||
<i class="fa-solid fa-users"></i> <span data-i18n-key="template.trackerEditorModal.tabs.presentCharacters">Present Characters</span>
|
||||
<i class="fa-solid fa-users"></i> <span
|
||||
data-i18n-key="template.trackerEditorModal.tabs.presentCharacters">Present Characters</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -481,12 +649,15 @@
|
||||
|
||||
<footer class="rpg-settings-popup-footer">
|
||||
<button id="rpg-editor-reset" class="rpg-btn-secondary" type="button">
|
||||
<i class="fa-solid fa-rotate-left"></i> <span data-i18n-key="template.trackerEditorModal.buttons.reset">Reset to Defaults</span>
|
||||
<i class="fa-solid fa-rotate-left"></i> <span
|
||||
data-i18n-key="template.trackerEditorModal.buttons.reset">Reset to Defaults</span>
|
||||
</button>
|
||||
<div class="rpg-footer-right">
|
||||
<button id="rpg-editor-cancel" class="rpg-btn-secondary" type="button" data-i18n-key="template.trackerEditorModal.buttons.cancel">Cancel</button>
|
||||
<button id="rpg-editor-cancel" class="rpg-btn-secondary" type="button"
|
||||
data-i18n-key="template.trackerEditorModal.buttons.cancel">Cancel</button>
|
||||
<button id="rpg-editor-save" class="rpg-btn-primary" type="button">
|
||||
<i class="fa-solid fa-save"></i> <span data-i18n-key="template.trackerEditorModal.buttons.save">Save & Apply</span>
|
||||
<i class="fa-solid fa-save"></i> <span data-i18n-key="template.trackerEditorModal.buttons.save">Save
|
||||
& Apply</span>
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
Reference in New Issue
Block a user