feat: add roleplay tools sidebar UI

Created left-sliding sidebar for roleplay features:
- Slides in from left (opposite of settings sidebar from right)
- Three tabs: World Info, Author's Note, Persona
- Hamburger menu button in header
- Overlay backdrop closes sidebar
- Tab switching with smooth transitions
- Responsive design matching settings panel

UI structure ready for implementation of:
- World Info/Lorebook entries with keyword triggers
- Author's Note for prompt injection
- Persona system for user character description
This commit is contained in:
2025-10-14 18:07:19 -07:00
parent 83e9793dce
commit 5d32489c3c
3 changed files with 261 additions and 0 deletions

View File

@@ -15,6 +15,13 @@
<div class="app-container"> <div class="app-container">
<header class="app-header"> <header class="app-header">
<div class="header-content"> <div class="header-content">
<div class="header-left-controls">
<button id="roleplay-btn" class="icon-btn" title="Roleplay Tools">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path d="M2 4h12M2 8h12M2 12h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="character-display"> <div class="character-display">
<div class="avatar-circle"></div> <div class="avatar-circle"></div>
<span id="character-header-name"></span> <span id="character-header-name"></span>
@@ -67,6 +74,103 @@
</div> </div>
</main> </main>
<!-- Roleplay sidebar overlay -->
<div class="roleplay-overlay" id="roleplay-overlay"></div>
<!-- Roleplay sidebar (left) -->
<div class="roleplay-panel" id="roleplay-panel">
<div class="roleplay-header">
<h2>Roleplay Tools</h2>
<button id="close-roleplay-btn" class="icon-btn">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<line x1="4" y1="4" x2="12" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
<line x1="12" y1="4" x2="4" y2="12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
</button>
</div>
<div class="roleplay-tabs">
<button class="roleplay-tab-btn active" data-tab="worldinfo">World Info</button>
<button class="roleplay-tab-btn" data-tab="authorsnote">Author's Note</button>
<button class="roleplay-tab-btn" data-tab="persona">Persona</button>
</div>
<div id="worldinfo-tab" class="roleplay-tab-content active">
<div class="roleplay-content">
<div class="form-group">
<label>World Info / Lorebook</label>
<p style="color: var(--text-secondary); font-size: 12px; margin-bottom: 12px;">
Create entries that inject context when keywords are mentioned.
</p>
<button type="button" id="add-worldinfo-btn" class="btn-secondary" style="width: 100%;">
+ Add Entry
</button>
</div>
<div id="worldinfo-list" class="worldinfo-list">
<!-- World info entries will be added here -->
</div>
</div>
</div>
<div id="authorsnote-tab" class="roleplay-tab-content">
<div class="roleplay-content">
<div class="form-group">
<label for="authors-note-text">Author's Note</label>
<p style="color: var(--text-secondary); font-size: 12px; margin-bottom: 8px;">
Instructions inserted near the end of the prompt before the latest messages.
</p>
<textarea
id="authors-note-text"
placeholder="Write in present tense. Focus on sensory details..."
rows="6"
></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="authors-note-enabled" />
Enable Author's Note
</label>
</div>
<button type="button" id="save-authors-note-btn" class="btn-primary" style="width: 100%;">
Save Author's Note
</button>
</div>
</div>
<div id="persona-tab" class="roleplay-tab-content">
<div class="roleplay-content">
<div class="form-group">
<label for="persona-name">Persona Name</label>
<input
type="text"
id="persona-name"
placeholder="Your character name"
/>
</div>
<div class="form-group">
<label for="persona-description">Persona Description</label>
<p style="color: var(--text-secondary); font-size: 12px; margin-bottom: 8px;">
Describe yourself as the user in this roleplay.
</p>
<textarea
id="persona-description"
placeholder="Describe your character's appearance, personality, background..."
rows="8"
></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="persona-enabled" />
Enable Persona
</label>
</div>
<button type="button" id="save-persona-btn" class="btn-primary" style="width: 100%;">
Save Persona
</button>
</div>
</div>
</div>
<!-- Settings overlay backdrop --> <!-- Settings overlay backdrop -->
<div class="settings-overlay" id="settings-overlay"></div> <div class="settings-overlay" id="settings-overlay"></div>

View File

@@ -1061,8 +1061,24 @@ function hideSettings() {
overlay.classList.remove('show'); overlay.classList.remove('show');
} }
// Show/hide roleplay panel
function showRoleplayPanel() {
const panel = document.getElementById('roleplay-panel');
const overlay = document.getElementById('roleplay-overlay');
panel.classList.add('open');
overlay.classList.add('show');
}
function hideRoleplayPanel() {
const panel = document.getElementById('roleplay-panel');
const overlay = document.getElementById('roleplay-overlay');
panel.classList.remove('open');
overlay.classList.remove('show');
}
// Tab switching // Tab switching
function setupTabs() { function setupTabs() {
// Settings tabs
const tabBtns = document.querySelectorAll('.tab-btn'); const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content'); const tabContents = document.querySelectorAll('.tab-content');
@@ -1079,6 +1095,24 @@ function setupTabs() {
document.getElementById(`${targetTab}-tab`).classList.add('active'); document.getElementById(`${targetTab}-tab`).classList.add('active');
}); });
}); });
// Roleplay tabs
const roleplayTabBtns = document.querySelectorAll('.roleplay-tab-btn');
const roleplayTabContents = document.querySelectorAll('.roleplay-tab-content');
roleplayTabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const targetTab = btn.getAttribute('data-tab');
// Remove active class from all roleplay tabs and contents
roleplayTabBtns.forEach(b => b.classList.remove('active'));
roleplayTabContents.forEach(c => c.classList.remove('active'));
// Add active class to clicked tab and corresponding content
btn.classList.add('active');
document.getElementById(`${targetTab}-tab`).classList.add('active');
});
});
} }
// Handle form submission // Handle form submission
@@ -1246,6 +1280,9 @@ function setupAppControls() {
document.getElementById('settings-btn').addEventListener('click', showSettings); document.getElementById('settings-btn').addEventListener('click', showSettings);
document.getElementById('close-settings-btn').addEventListener('click', hideSettings); document.getElementById('close-settings-btn').addEventListener('click', hideSettings);
document.getElementById('settings-overlay').addEventListener('click', hideSettings); document.getElementById('settings-overlay').addEventListener('click', hideSettings);
document.getElementById('roleplay-btn').addEventListener('click', showRoleplayPanel);
document.getElementById('close-roleplay-btn').addEventListener('click', hideRoleplayPanel);
document.getElementById('roleplay-overlay').addEventListener('click', hideRoleplayPanel);
document.getElementById('clear-btn').addEventListener('click', clearHistory); document.getElementById('clear-btn').addEventListener('click', clearHistory);
document.getElementById('export-chat-btn').addEventListener('click', exportChatHistory); document.getElementById('export-chat-btn').addEventListener('click', exportChatHistory);
document.getElementById('import-chat-btn').addEventListener('click', importChatHistory); document.getElementById('import-chat-btn').addEventListener('click', importChatHistory);

View File

@@ -1505,6 +1505,117 @@ body.view-comfortable .message-content pre {
margin: 12px 0; margin: 12px 0;
} }
/* Roleplay Panel - Slide-in from Left */
.roleplay-panel {
position: fixed;
top: 0;
left: -500px;
width: 500px;
height: 100vh;
background: var(--bg-primary);
border-right: 1px solid var(--border);
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow-y: auto;
padding: 20px;
transition: left 0.3s ease;
}
.roleplay-panel.open {
left: 0;
}
.roleplay-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.roleplay-overlay.show {
opacity: 1;
pointer-events: auto;
}
.roleplay-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.roleplay-header h2 {
font-size: 24px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.roleplay-tabs {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.roleplay-tab-btn {
flex: 1;
padding: 10px 16px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: inherit;
}
.roleplay-tab-btn:hover {
background: var(--border);
color: var(--text-primary);
}
.roleplay-tab-btn.active {
background: var(--accent);
color: white;
border-color: var(--accent);
}
.roleplay-tab-content {
display: none;
}
.roleplay-tab-content.active {
display: block;
}
.roleplay-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.worldinfo-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}
.header-left-controls {
display: flex;
gap: 8px;
}
/* Responsive */ /* Responsive */
@media (max-width: 600px) { @media (max-width: 600px) {
.messages-list { .messages-list {
@@ -1527,4 +1638,13 @@ body.view-comfortable .message-content pre {
.settings-panel.open { .settings-panel.open {
right: 0; right: 0;
} }
.roleplay-panel {
width: 100%;
left: -100%;
}
.roleplay-panel.open {
left: 0;
}
} }