feat: replace character creation prompts with themed modal

Replace native prompt() dialogs with a themed inline form modal for character creation:
- Modal displays with name input and system prompt textarea
- Includes Cancel and Create buttons
- Escape key closes the modal
- Auto-focuses name input when opened
- Proper event listener cleanup
- Matches existing UI theme and styling

This provides a much better user experience with the application's dark theme, compared to the browser's default prompt dialogs which looked out of place.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-16 22:28:42 -07:00
parent 600b50f239
commit 86a9d54e70
3 changed files with 173 additions and 12 deletions

View File

@@ -740,5 +740,50 @@
<img id="avatar-modal-img" src="" alt="Avatar" /> <img id="avatar-modal-img" src="" alt="Avatar" />
</div> </div>
</div> </div>
<!-- New Character modal -->
<div id="new-character-modal" class="new-character-modal" style="display: none;">
<div class="new-character-overlay"></div>
<div class="new-character-content">
<div class="new-character-header">
<h3>Create New Character</h3>
<button id="close-new-character-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>
<form id="new-character-form">
<div class="form-group">
<label for="new-character-name">Character Name</label>
<input
type="text"
id="new-character-name"
placeholder="Enter a name for the new character"
required
autofocus
/>
</div>
<div class="form-group">
<label for="new-character-system-prompt">System Prompt</label>
<textarea
id="new-character-system-prompt"
placeholder="You are a helpful AI assistant..."
rows="6"
required
></textarea>
</div>
<div class="new-character-actions">
<button type="button" id="cancel-new-character-btn" class="btn-secondary">
Cancel
</button>
<button type="submit" class="btn-primary">
Create
</button>
</div>
</form>
</div>
</div>
</body> </body>
</html> </html>

View File

@@ -1773,20 +1773,66 @@ async function handleCharacterSwitch() {
// Handle new character creation // Handle new character creation
async function handleNewCharacter() { async function handleNewCharacter() {
const name = prompt('Enter a name for the new character:'); const modal = document.getElementById('new-character-modal');
if (!name) return; const overlay = modal.querySelector('.new-character-overlay');
const form = document.getElementById('new-character-form');
const nameInput = document.getElementById('new-character-name');
const systemPromptInput = document.getElementById('new-character-system-prompt');
const closeBtn = document.getElementById('close-new-character-btn');
const cancelBtn = document.getElementById('cancel-new-character-btn');
const systemPrompt = prompt('Enter the system prompt for the new character:', 'You are a helpful AI assistant.'); // Reset form
if (!systemPrompt) return; form.reset();
systemPromptInput.value = 'You are a helpful AI assistant.';
try { // Show modal
const newCharacter = await invoke('create_character', { name, systemPrompt }); modal.style.display = 'flex';
await loadCharacters();
characterSelect.value = newCharacter.id; // Focus name input after a brief delay to ensure it's visible
} catch (error) { setTimeout(() => nameInput.focus(), 100);
console.error('Failed to create character:', error);
addMessage(`Failed to create character: ${error}`, false); // Handle form submission
} const handleSubmit = async (e) => {
e.preventDefault();
const name = nameInput.value.trim();
const systemPrompt = systemPromptInput.value.trim();
if (!name || !systemPrompt) return;
try {
const newCharacter = await invoke('create_character', { name, systemPrompt });
await loadCharacters();
characterSelect.value = newCharacter.id;
// Hide modal
modal.style.display = 'none';
// Remove event listeners
form.removeEventListener('submit', handleSubmit);
overlay.removeEventListener('click', handleClose);
closeBtn.removeEventListener('click', handleClose);
cancelBtn.removeEventListener('click', handleClose);
} catch (error) {
console.error('Failed to create character:', error);
addMessage(`Failed to create character: ${error}`, false);
}
};
// Handle close
const handleClose = () => {
modal.style.display = 'none';
form.removeEventListener('submit', handleSubmit);
overlay.removeEventListener('click', handleClose);
closeBtn.removeEventListener('click', handleClose);
cancelBtn.removeEventListener('click', handleClose);
};
// Attach event listeners
form.addEventListener('submit', handleSubmit);
overlay.addEventListener('click', handleClose);
closeBtn.addEventListener('click', handleClose);
cancelBtn.addEventListener('click', handleClose);
} }
// Handle character deletion // Handle character deletion
@@ -3528,6 +3574,13 @@ window.addEventListener('DOMContentLoaded', () => {
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
// Escape key handling // Escape key handling
if (e.key === 'Escape') { if (e.key === 'Escape') {
// Close new character modal
const newCharacterModal = document.getElementById('new-character-modal');
if (newCharacterModal && newCharacterModal.style.display !== 'none') {
document.getElementById('cancel-new-character-btn').click();
return;
}
// Close avatar modal // Close avatar modal
if (avatarModal.style.display !== 'none') { if (avatarModal.style.display !== 'none') {
hideAvatarModal(); hideAvatarModal();

View File

@@ -1450,6 +1450,69 @@ body {
transform: scale(0.95); transform: scale(0.95);
} }
/* New Character Modal */
.new-character-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10001;
display: flex;
align-items: center;
justify-content: center;
}
.new-character-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
cursor: pointer;
}
.new-character-content {
position: relative;
z-index: 1;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
width: 90%;
max-width: 500px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
.new-character-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.new-character-header h3 {
font-size: 20px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
}
.new-character-actions {
display: flex;
gap: 12px;
margin-top: 20px;
}
.new-character-actions button {
flex: 1;
}
/* Theme Preview */ /* Theme Preview */
.theme-preview-container { .theme-preview-container {
margin-top: 20px; margin-top: 20px;