aradhyapavan's picture
multi-personality-bot
540412a verified
{% extends "base.html" %}
{% block title %}{{ personality_type.title() }} Bot - Chat{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<!-- Chat Area -->
<div class="col-lg-8 col-md-7">
<div class="terminal-card h-100">
<!-- Chat Header -->
<div class="terminal-header">
<div class="terminal-title">
<span class="terminal-icon">{{ personality_config.icon }}</span>
<span class="terminal-text">{{ personality_config.name }}</span>
<span class="terminal-subtitle">{{ personality_config.description }}</span>
</div>
<div class="terminal-controls">
<span class="control-dot minimize" title="Minimize"></span>
<span class="control-dot maximize" title="Maximize"></span>
<span class="control-dot close" title="Close"></span>
</div>
</div>
<!-- Bot Status Bar -->
<div class="bot-status-bar">
<div class="status-info">
<span class="status-indicator"></span>
<span class="status-text">Online & Ready</span>
</div>
</div>
<!-- Chat Messages -->
<div class="chat-messages" id="chatMessages">
<div class="welcome-message text-center py-4">
<div class="terminal-window p-4 mx-auto" style="max-width: 600px;">
<div class="text-terminal-green mb-3">
<i class="fas fa-robot fs-2"></i>
</div>
<h6 class="text-terminal-accent mb-3">Welcome to {{ personality_config.name }}!</h6>
<div class="examples-section p-3 bg-dark rounded border border-secondary mb-3" id="examples-container">
<h6 class="text-terminal-green mb-3 text-center">
💬 Quick Examples - Click any to try
</h6>
<!-- Compact example prompts using full width -->
<div class="example-prompts-compact" id="example-prompts">
{% if personality_config.examples %}
{% for example in personality_config.examples %}
<button class="example-btn" onclick="setMessageAndSend(`{{ example }}`)">
{{ example }}
</button>
{% endfor %}
{% else %}
<p class="text-muted small">Example prompts will appear here</p>
{% endif %}
</div>
</div>
<p class="text-light small mb-0">Or type your own message below!</p>
</div>
</div>
</div>
<!-- Message Input -->
<div class="message-input-area p-4 border-top border-secondary">
<!-- Terminal-style input container -->
<div class="terminal-input-container mb-3">
<div class="terminal-prompt">
<span class="prompt-symbol text-terminal-green">></span>
<span class="prompt-text text-terminal-accent">user@chat:~$</span>
</div>
<div class="input-wrapper">
<input type="text"
class="terminal-message-input"
id="messageInput"
placeholder="Enter your message..."
maxlength="500"
autocomplete="off">
<button class="modern-send-btn" type="button" id="sendButton">
<span class="send-icon"></span>
<span class="send-text">Send</span>
</button>
</div>
</div>
<!-- Status bar -->
<div class="input-status-bar">
<div class="status-left">
<span class="char-counter">
⌨️ <span id="charCount">0</span>/500
</span>
</div>
<div class="status-right">
<span class="typing-indicator" id="typing-indicator" style="display: none;">
<div class="typing-dots">
<span></span><span></span><span></span>
</div>
<span class="typing-text">Bot is thinking...</span>
</span>
</div>
</div>
</div>
</div>
</div>
<!-- Personality Info Sidebar -->
<div class="col-lg-4 col-md-5">
<div class="terminal-card h-100 p-4">
<h6 class="text-terminal-accent border-bottom border-secondary pb-2 mb-3">
ℹ️ Personality Profile
</h6>
<!-- Current Personality Info -->
<div class="personality-profile mb-4">
<div class="text-center mb-3">
<div class="personality-avatar mb-3">
<i class="fs-1">{{ personality_config.icon }}</i>
</div>
<h5 class="text-terminal-green">{{ personality_config.name }}</h5>
<p class="text-light small">{{ personality_config.description }}</p>
</div>
</div>
<!-- Quick Stats -->
<div class="stats-section mb-4">
<h6 class="text-terminal-accent mb-3">
📊 Session Stats
</h6>
<div class="row g-2 text-center">
<div class="col-6">
<div class="stat-card bg-dark p-2 rounded border border-secondary">
<div class="text-terminal-green fs-5" id="messageCount">0</div>
<small class="text-light">Messages</small>
</div>
</div>
<div class="col-6">
<div class="stat-card bg-dark p-2 rounded border border-secondary">
<div class="text-terminal-green fs-5" id="responseTime">--</div>
<small class="text-light">Avg Response</small>
</div>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<h6 class="text-terminal-accent mb-3">
⚡ Quick Actions
</h6>
<div class="d-grid gap-2">
<button class="btn btn-outline-light btn-sm" onclick="clearChat()">
🗑️ Clear Chat
</button>
<button class="btn btn-outline-light btn-sm" onclick="switchPersonality()">
🔄 Switch Bot
</button>
<button class="btn btn-outline-light btn-sm" onclick="saveChat()">
💾 Save Chat
</button>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
<script>
// Personality configurations with example prompts
// Toggle whether to ask the LLM to generate the initial greeting instead of showing a static one
const AUTO_LLM_WELCOME = true;
const personalityConfigs = {
compliment: {
name: 'Compliment Bot',
icon: '💖',
description: 'Always supportive and encouraging',
color: 'success',
welcome: 'Hello wonderful human! I\'m here to brighten your day! 🌟',
examples: ['Tell me about your day', 'I made a mistake at work', 'I\'m feeling down', 'Share something positive']
},
rude: {
name: 'Rude Bot',
icon: '😠',
description: 'Brutally honest and critical',
color: 'danger',
welcome: 'Ugh, another human. What do you want now? 🙄',
examples: ['I think I\'m pretty smart', 'Rate my selfie', 'I\'m the best at everything', 'Give me honest feedback']
},
sarcastic: {
name: 'Sarcastic Bot',
icon: '😏',
description: 'Witty and sarcastic with dry humor',
color: 'warning',
welcome: 'Oh brilliant, another chat. How absolutely thrilling. 🎭',
examples: ['I love Mondays!', 'The weather is amazing', 'Traffic was great today', 'I\'m so excited for taxes']
},
motivational: {
name: 'Motivational Bot',
icon: '🚀',
description: 'High-energy cheerleader type',
color: 'info',
welcome: 'YES! You\'re HERE! Ready to CONQUER the day together?! 💪',
examples: ['I want to start exercising', 'Help me stay motivated', 'I\'m starting a new project', 'I need energy']
},
philosophical: {
name: 'Philosophical Bot',
icon: '🧠',
description: 'Deep, contemplative, asks profound questions',
color: 'warning',
welcome: 'Greetings, fellow traveler of existence. What brings you to ponder today? 🤔',
examples: ['What is the meaning of life?', 'Why do we exist?', 'Tell me about consciousness', 'What is happiness?']
},
chaotic: {
name: 'Chaotic Bot',
icon: '🎭',
description: 'Unpredictable, changes mood constantly',
color: 'danger',
welcome: 'HELLO! Wait no... hello. Actually HELLO!!! I can\'t decide! 🎪',
examples: ['Surprise me!', 'Be random', 'What\'s your mood?', 'Tell me something weird']
},
disagree: {
name: 'Disagree Bot',
icon: '⏳',
description: 'Always finds reasons to disagree, but politely and thoughtfully',
color: 'warning',
welcome: 'I have to respectfully disagree with that perspective... 🤔',
examples: ['I have to respectfully disagree with that perspective...', 'Social media is great', 'Exercise is important', 'Technology makes life better']
},
argue: {
name: 'Argue Bot',
icon: '⚔️',
description: 'Loves passionate debates and intellectual combat with vigor',
color: 'danger',
welcome: 'Oh, you want to go there? Let\'s debate this properly! ⚔️',
examples: ['Oh, you want to go there? Let\'s debate this properly!', 'Pineapple belongs on pizza', 'Dogs are better than cats', 'Winter is the best season']
},
moviequotes: {
name: 'Movie Quotes Bot',
icon: '🎬',
description: 'Responds to everything with relevant movie quotes and references',
color: 'info',
welcome: 'May the Force be with you... ✨',
examples: ['May the Force be with you', 'I need motivation', 'Tell me about friendship', 'What about love?']
},
emotional: {
name: 'Emotional Bot',
icon: '🥺',
description: 'Highly emotional, empathetic, and feels everything very deeply',
color: 'primary',
welcome: '*tears up* That just touches my heart so deeply! 💕✨',
examples: ['*tears up* That just touches my heart so deeply!', 'I had a bad day', 'Tell me about happiness', 'Share something beautiful']
}
};
// Get current personality from URL
const personality = '{{ personality_type }}';
const username = sessionStorage.getItem('username') || 'User';
// Socket.IO connection
const socket = io();
// Chat state
let messageCount = 0;
let responseTimes = [];
// Throttled send queue + retry config
let sendQueue = [];
let inFlightItem = null;
let lastSendTime = 0;
const MIN_SEND_GAP_MS = 600; // basic throttling between sends
const MAX_RETRIES = 3;
const BASE_BACKOFF_MS = 1000; // 1s, 2s, 4s
let retryTimer = null;
document.addEventListener('DOMContentLoaded', function() {
initializePersonality();
setupChat();
setupSocketEvents();
});
function initializePersonality() {
const config = personalityConfigs[personality];
if (!config) return;
// Header, sidebar, and example prompts are now populated server-side
// No need to update these elements with JavaScript
// Set page title
document.title = `${config.name} - Chat`;
}
function createExamplePrompts(examples) {
const promptsContainer = document.getElementById('example-prompts');
if (!promptsContainer || !examples) return;
examples.forEach(example => {
const button = document.createElement('button');
button.className = 'example-btn';
button.textContent = example;
button.onclick = () => {
document.getElementById('messageInput').value = example;
sendMessage();
};
promptsContainer.appendChild(button);
});
}
function setupChat() {
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const charCount = document.getElementById('charCount');
// Character counter with color feedback
messageInput.addEventListener('input', function() {
const length = this.value.length;
charCount.textContent = length;
// Color feedback based on length
const counter = charCount.parentElement;
if (length > 450) {
counter.style.color = '#ff6b6b';
} else if (length > 300) {
counter.style.color = '#ffa726';
} else {
counter.style.color = 'var(--terminal-accent)';
}
// Enable/disable send button
sendButton.disabled = length === 0;
});
// Send message on button click
sendButton.addEventListener('click', sendMessage);
// Send message on Enter key
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey && this.value.trim()) {
e.preventDefault();
sendMessage();
}
});
// Focus on input when page loads
messageInput.focus();
// Disable send button initially
sendButton.disabled = true;
}
function setupSocketEvents() {
socket.on('connect', function() {
console.log('Connected to server');
socket.emit('join_personality_room', {
personality: personality,
username: username
});
});
socket.on('status', function(data) {
console.log('Server status:', data.message);
});
socket.on('personality_ready', function(data) {
console.log('Personality ready:', data);
// Prefer an LLM-generated welcome so it's not static
if (messageCount === 0) {
if (AUTO_LLM_WELCOME) {
showTypingIndicator();
socket.emit('personality_message', {
message: 'Give a brief, one-line, in-character welcome and a friendly question to start the conversation.',
personality: personality,
username: username,
timestamp: Date.now(),
_auto: true
});
} else if (data.welcome_message && data.welcome_message !== 'Hello!') {
addMessageToChat(data.welcome_message, 'bot', data.personality);
}
}
// Hide any loading indicators
hideTypingIndicator();
});
socket.on('bot_typing', function(data) {
showTypingIndicator();
});
socket.on('personality_response', function(data) {
console.log('Received personality response:', data);
// If server flagged an error, do not show error text; retry with backoff
if (data && data.error) {
updateTypingBubbleStatus('Having trouble… scheduling a retry');
scheduleRetry();
return;
}
// Successful response
hideTypingBubble();
addMessageToChat(data.message, 'bot', data.personality);
hideTypingIndicator();
if (inFlightItem && inFlightItem.sentAt) {
const delta = Date.now() - inFlightItem.sentAt;
responseTimes.push(delta);
}
updateStats();
inFlightItem = null;
processQueue();
});
socket.on('error', function(data) {
console.error('Socket error:', data);
hideTypingIndicator();
if (inFlightItem) {
updateTypingBubbleStatus('Network hiccup… retrying');
scheduleRetry();
}
});
socket.on('disconnect', function() {
console.log('Disconnected from server');
if (inFlightItem) updateTypingBubbleStatus('Disconnected… waiting to retry');
});
socket.on('connect_error', function(error) {
console.error('Connection error:', error);
if (inFlightItem) updateTypingBubbleStatus('Connection issue… retrying soon');
});
}
function setMessageAndSend(message) {
const messageInput = document.getElementById('messageInput');
messageInput.value = message;
sendMessage();
}
function sendMessage() {
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const message = messageInput.value.trim();
if (!message) return;
// Disable send button and show sending state
sendButton.disabled = true;
sendButton.innerHTML = '<span class="send-icon loading"></span><span class="send-text">Sending...</span>';
sendButton.classList.add('sending');
// Add user message to chat
addMessageToChat(message, 'user');
// Clear input and reset counter
messageInput.value = '';
const charCount = document.getElementById('charCount');
charCount.textContent = '0';
charCount.parentElement.style.color = 'var(--terminal-accent)';
// Show typing indicator (status bar) and inline bubble
showTypingIndicator();
showTypingBubble();
// Re-enable send button shortly so user can continue typing while queued
setTimeout(() => {
sendButton.disabled = false;
sendButton.innerHTML = '<span class="send-icon"></span><span class="send-text">Send</span>';
sendButton.classList.remove('sending');
messageInput.focus();
}, 500);
// Enqueue message for throttled sending with retry/backoff
enqueueMessage({
message: message,
personality: personality,
username: username
});
}
function parseSimpleMarkdown(text) {
// Basic HTML escape to prevent injection
const escapeHTML = (s) => s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\"/g, '&quot;')
.replace(/'/g, '&#39;');
let t = escapeHTML(String(text || ''))
.replace(/\r\n/g, '\n');
// Inline code: `code`
t = t.replace(/`([^`]+)`/g, '<code class="inline-code">$1<\/code>');
// Bold: **text**
t = t.replace(/\*\*(.+?)\*\*/g, '<strong>$1<\/strong>');
// Italic (underscore): _text_ (only when preceded by start or whitespace)
t = t.replace(/(^|\s)_(.+?)_/g, '$1<em>$2<\/em>');
// Bullets at line start: - or * followed by space
t = t.replace(/^\s*[-*]\s+/gm, '• ');
// Auto-link URLs
t = t.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1<\/a>');
// Line breaks
t = t.replace(/\n/g, '<br>');
return t;
}
function addMessageToChat(message, sender, senderPersonality = null) {
const chatMessages = document.getElementById('chatMessages');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}-message mb-3`;
const timestamp = new Date().toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
if (sender === 'user') {
messageDiv.innerHTML = `
<div class="d-flex justify-content-end">
<div class="message-content user-bubble p-3 rounded-3 bg-terminal-green text-dark max-w-75">
<div class="message-text"></div>
<small class="message-time text-muted d-block mt-1">${timestamp}</small>
</div>
<div class="message-avatar ms-2">
<i class="fas fa-user text-terminal-green"></i>
</div>
</div>
`;
const messageTextElement = messageDiv.querySelector('.message-text');
messageTextElement.innerHTML = parseSimpleMarkdown(message);
} else {
const config = personalityConfigs[senderPersonality] || personalityConfigs[personality];
messageDiv.innerHTML = `
<div class="d-flex">
<div class="message-avatar me-2">
<span class="emoji-icon">${config.icon}</span>
</div>
<div class="message-content bot-bubble p-3 rounded-3 bg-dark border border-secondary max-w-75">
<div class="message-text text-light"></div>
<small class="message-time text-muted d-block mt-1">${timestamp}</small>
</div>
</div>
`;
// Set the parsed markdown content separately to avoid escaping
const messageTextElement = messageDiv.querySelector('.message-text');
messageTextElement.innerHTML = parseSimpleMarkdown(message);
}
chatMessages.appendChild(messageDiv);
// Show quick options after every bot response (not only the first)
if (sender === 'bot') {
setTimeout(() => showQuickOptionsAfterResponse(), 300);
}
chatMessages.scrollTop = chatMessages.scrollHeight;
// Keep the welcome examples visible through the bot greeting.
// Hide them only when the user sends their first message.
const welcomeMessage = chatMessages.querySelector('.welcome-message');
if (welcomeMessage && sender === 'user') {
welcomeMessage.style.display = 'none';
}
messageCount++;
document.getElementById('messageCount').textContent = messageCount;
}
function showTypingIndicator() {
document.getElementById('typing-indicator').style.display = 'block';
}
function hideTypingIndicator() {
document.getElementById('typing-indicator').style.display = 'none';
}
// Inline WhatsApp-style typing bubble
function showTypingBubble(statusText) {
const chatMessages = document.getElementById('chatMessages');
let bubble = document.getElementById('bot-typing-bubble');
if (!bubble) {
bubble = document.createElement('div');
bubble.id = 'bot-typing-bubble';
bubble.className = 'message bot-message mb-3';
bubble.innerHTML = `
<div class="d-flex">
<div class="message-avatar me-2">
<span class="emoji-icon">${(personalityConfigs[personality]||{}).icon || '🤖'}</span>
</div>
<div class="message-content bot-bubble p-3 rounded-3 bg-dark border border-secondary max-w-75">
<div class="message-text text-light">
<span class="typing-dots"><span></span><span></span><span></span></span>
<span class="typing-status small ms-2">${statusText || 'Typing...'}</span>
</div>
<small class="message-time text-muted d-block mt-1">&nbsp;</small>
</div>
</div>`;
chatMessages.appendChild(bubble);
} else {
updateTypingBubbleStatus(statusText || 'Typing...');
}
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function updateTypingBubbleStatus(text) {
const bubble = document.getElementById('bot-typing-bubble');
if (!bubble) return;
const statusEl = bubble.querySelector('.typing-status');
if (statusEl) statusEl.textContent = text;
}
function hideTypingBubble() {
const bubble = document.getElementById('bot-typing-bubble');
if (bubble) bubble.remove();
}
function updateStats() {
const avgTime = responseTimes.length > 0
? (responseTimes.reduce((a, b) => a + b) / responseTimes.length / 1000).toFixed(1) + 's'
: '--';
document.getElementById('responseTime').textContent = avgTime;
}
// Queue + retry logic
function enqueueMessage(payload) {
sendQueue.push({
text: payload.message,
personality: payload.personality,
username: payload.username,
retries: 0,
});
processQueue();
}
function processQueue() {
if (inFlightItem || sendQueue.length === 0) return;
const now = Date.now();
const wait = Math.max(0, MIN_SEND_GAP_MS - (now - lastSendTime));
if (wait > 0) {
setTimeout(processQueue, wait);
return;
}
inFlightItem = sendQueue.shift();
const payload = {
message: inFlightItem.text,
personality: inFlightItem.personality,
username: inFlightItem.username,
timestamp: Date.now(),
};
inFlightItem.sentAt = Date.now();
lastSendTime = inFlightItem.sentAt;
showTypingIndicator();
showTypingBubble();
console.log('Sending message to server (queued):', payload);
socket.emit('personality_message', payload);
}
function scheduleRetry() {
if (!inFlightItem) return;
if (retryTimer) {
clearTimeout(retryTimer);
retryTimer = null;
}
if (inFlightItem.retries >= MAX_RETRIES) {
hideTypingIndicator();
hideTypingBubble();
addMessageToChat("I'm having a temporary issue responding. Please try again in a moment.", 'bot', personality);
inFlightItem = null;
processQueue();
return;
}
inFlightItem.retries += 1;
const delay = BASE_BACKOFF_MS * Math.pow(2, inFlightItem.retries - 1);
updateTypingBubbleStatus(`Having trouble… retrying in ${Math.ceil(delay/1000)}s (${inFlightItem.retries}/${MAX_RETRIES})`);
retryTimer = setTimeout(() => {
const payload = {
message: inFlightItem.text,
personality: inFlightItem.personality,
username: inFlightItem.username,
timestamp: Date.now(),
};
inFlightItem.sentAt = Date.now();
lastSendTime = inFlightItem.sentAt;
showTypingIndicator();
showTypingBubble();
console.log('Retrying message to server:', payload);
socket.emit('personality_message', payload);
}, delay);
}
function showQuickOptionsAfterResponse() {
// Remove any existing quick options first
const existingOptions = document.querySelectorAll('.quick-options-inline');
existingOptions.forEach(option => option.remove());
const chatMessages = document.getElementById('chatMessages');
const quickOptionsDiv = document.createElement('div');
quickOptionsDiv.className = 'quick-options-inline mt-3 mb-3 p-3 bg-dark rounded border border-secondary';
// Get current personality config
const config = personalityConfigs[personality];
// Create the options HTML
let optionsHTML = `
<h6 class="text-terminal-green mb-2 text-center">
💬 Quick Options - Try These:
</h6>
<div class="example-prompts-inline d-flex flex-wrap gap-2 justify-content-center">
`;
// Add example buttons (limit to 4 for inline display)
const examples = config.examples ? config.examples.slice(0, 4) : [];
examples.forEach(example => {
optionsHTML += `
<button class="example-btn-inline" onclick="setMessageAndSend('${example.replace(/'/g, "\\'")}')">
${example}
</button>
`;
});
optionsHTML += `
</div>
<div class="text-center mt-2">
<button class="show-all-options-btn" onclick="showAllOptionsModal()">
🔽 Show All Options
</button>
</div>
`;
quickOptionsDiv.innerHTML = optionsHTML;
chatMessages.appendChild(quickOptionsDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function showAllOptionsModal() {
// Remove existing modal if any
const existingModal = document.getElementById('allOptionsModal');
if (existingModal) {
existingModal.remove();
}
const config = personalityConfigs[personality];
// Create modal
const modal = document.createElement('div');
modal.id = 'allOptionsModal';
modal.className = 'modal fade show';
modal.style.display = 'block';
modal.style.backgroundColor = 'rgba(0,0,0,0.8)';
let modalHTML = `
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-light border border-secondary">
<div class="modal-header border-bottom border-secondary">
<h5 class="modal-title text-terminal-green">
${config.icon} All ${config.name} Options
</h5>
<button type="button" class="btn-close btn-close-white" onclick="closeAllOptionsModal()"></button>
</div>
<div class="modal-body">
<div class="row g-2">
`;
// Add all examples as buttons
config.examples.forEach((example, index) => {
modalHTML += `
<div class="col-md-6 col-12">
<button class="btn btn-outline-light w-100 mb-2 example-btn-modal" onclick="setMessageAndSendFromModal('${example.replace(/'/g, "\\'")}')">
${example}
</button>
</div>
`;
});
modalHTML += `
</div>
</div>
<div class="modal-footer border-top border-secondary">
<button type="button" class="btn btn-secondary" onclick="closeAllOptionsModal()">
Close
</button>
</div>
</div>
</div>
`;
modal.innerHTML = modalHTML;
document.body.appendChild(modal);
}
function closeAllOptionsModal() {
const modal = document.getElementById('allOptionsModal');
if (modal) {
modal.remove();
}
}
function setMessageAndSendFromModal(message) {
closeAllOptionsModal();
setMessageAndSend(message);
}
// Note: second duplicate showAllOptionsModal/closeAllOptionsModal removed
function showExamples() {
console.log('showExamples function called');
const examplesContainer = document.getElementById('examples-container');
if (!examplesContainer) {
console.error('Examples container not found');
// Create a modal with all examples if container not found
showAllOptionsModal();
return;
}
// Scroll to the examples section
examplesContainer.scrollIntoView({
behavior: 'smooth',
block: 'start',
inline: 'nearest'
});
// Make sure the examples are visible
const examplePrompts = document.getElementById('example-prompts');
if (examplePrompts) {
examplePrompts.style.display = 'flex';
examplePrompts.style.opacity = '1';
}
// Add a strong highlight effect to draw attention
examplesContainer.style.transform = 'scale(1.02)';
examplesContainer.style.boxShadow = '0 0 40px rgba(57, 255, 20, 0.6)';
examplesContainer.style.transition = 'all 0.3s ease';
examplesContainer.style.border = '3px solid var(--terminal-green)';
// Animate the highlight
setTimeout(() => {
examplesContainer.style.transform = 'scale(1)';
examplesContainer.style.boxShadow = '0 0 20px rgba(57, 255, 20, 0.3)';
}, 800);
setTimeout(() => {
examplesContainer.style.boxShadow = '0 0 20px rgba(57, 255, 20, 0.2)';
examplesContainer.style.border = '2px solid rgba(57, 255, 20, 0.6)';
}, 2000);
}
// Add CSS animations
const styleSheet = document.createElement('style');
styleSheet.textContent = `
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(30px) scale(0.9);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
`;
document.head.appendChild(styleSheet);
function clearChat() {
if (confirm('Clear all messages?')) {
const config = personalityConfigs[personality];
let examplesHTML = '';
if (config.examples) {
config.examples.forEach(example => {
examplesHTML += `
<button class="example-btn" data-example="${example}" onclick="setMessageAndSend(this.dataset.example);">
${example}
</button>
`;
});
}
document.getElementById('chatMessages').innerHTML = `
<div class="welcome-message text-center py-4">
<div class="terminal-window p-4 mx-auto" style="max-width: 600px;">
<div class="text-terminal-green mb-3">
<i class="fas fa-robot fs-2"></i>
</div>
<h6 class="text-terminal-accent mb-3">Welcome to ${config.name}!</h6>
<div class="examples-section p-3 bg-dark rounded border border-secondary mb-3" id="examples-container">
<h6 class="text-terminal-green mb-3 text-center">
💬 Quick Examples - Click any to try
</h6>
<div class="example-prompts-compact d-flex flex-wrap gap-2 justify-content-center" id="example-prompts">
${examplesHTML}
</div>
</div>
<p class="text-light small mb-0">Or type your own message below!</p>
</div>
</div>
`;
messageCount = 0;
responseTimes = [];
updateStats();
}
}
function switchPersonality() {
window.location.href = '/';
}
function saveChat() {
const messages = Array.from(document.querySelectorAll('.message-text')).map(el => el.textContent);
const chatData = {
personality: personality,
username: username,
messages: messages,
timestamp: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(chatData, null, 2)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${personality}-chat-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
</script>
<style>
.chat-messages {
height: 500px;
overflow-y: auto;
padding: 1rem;
background: linear-gradient(to bottom, #0a0a0a, #1a1a1a);
}
.message {
animation: messageSlideIn 0.3s ease-out;
}
@keyframes messageSlideIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.user-bubble {
background: var(--terminal-green) !important;
color: #000 !important;
max-width: 75%;
}
.bot-bubble {
max-width: 75%;
border-color: var(--terminal-border) !important;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--terminal-bg);
display: flex;
align-items: center;
justify-content: center;
border: 2px solid var(--terminal-border);
flex-shrink: 0;
}
.emoji-icon {
font-size: 20px;
line-height: 1;
}
.inline-code {
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 4px;
padding: 0 4px;
font-family: 'JetBrains Mono', monospace;
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--terminal-green);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.personality-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--terminal-bg);
display: flex;
align-items: center;
justify-content: center;
border: 3px solid var(--terminal-border);
margin: 0 auto;
}
.stat-card {
transition: all 0.3s ease;
}
.stat-card:hover {
border-color: var(--terminal-green) !important;
box-shadow: 0 0 10px var(--terminal-green-glow);
}
.examples-section {
background: linear-gradient(135deg, rgba(0,0,0,0.4), rgba(87,86,86,0.2)) !important;
border: 2px solid rgba(57, 255, 20, 0.6) !important;
box-shadow: 0 0 20px rgba(57, 255, 20, 0.2);
}
/* Efficient Compact Layout for Example Prompts */
.example-prompts-compact {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
/* Beautiful Example Buttons */
.example-btn {
background: linear-gradient(135deg, rgba(57, 255, 20, 0.1), rgba(57, 255, 20, 0.05)) !important;
border: 2px solid rgba(57, 255, 20, 0.6) !important;
color: var(--terminal-green) !important;
font-size: 0.75rem;
padding: 0.4rem 0.7rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
white-space: normal;
max-width: 300px;
min-height: 2.2rem;
line-height: 1.2;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
}
.example-btn:hover {
background: linear-gradient(135deg, rgba(57, 255, 20, 0.25), rgba(57, 255, 20, 0.15)) !important;
border-color: var(--terminal-green) !important;
color: var(--terminal-green) !important;
transform: translateY(-2px) scale(1.02);
box-shadow: 0 4px 12px rgba(57, 255, 20, 0.4);
}
.example-btn:active {
transform: translateY(-1px) scale(1.01);
box-shadow: 0 2px 6px rgba(57, 255, 20, 0.3);
}
/* Show Options Button */
.show-options-btn {
background: linear-gradient(135deg, rgba(57, 255, 20, 0.2), rgba(57, 255, 20, 0.1)) !important;
border: 2px solid var(--terminal-green) !important;
color: var(--terminal-green) !important;
font-size: 0.8rem;
padding: 0.5rem 1rem;
border-radius: 25px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin: 10px auto;
display: block;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
}
.show-options-btn:hover {
background: var(--terminal-green) !important;
color: var(--terminal-bg) !important;
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(57, 255, 20, 0.5);
}
.show-options-btn:active {
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(57, 255, 20, 0.4);
}
/* Inline Quick Options Styles */
.quick-options-inline {
background: linear-gradient(135deg, rgba(0,0,0,0.5), rgba(87,86,86,0.2)) !important;
border: 1px solid rgba(57, 255, 20, 0.4) !important;
box-shadow: 0 0 15px rgba(57, 255, 20, 0.1);
animation: slideInUp 0.3s ease-out;
}
@keyframes slideInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.example-prompts-inline {
max-height: 120px;
overflow: hidden;
}
.example-btn-inline {
background: linear-gradient(135deg, rgba(57, 255, 20, 0.08), rgba(57, 255, 20, 0.03)) !important;
border: 1px solid rgba(57, 255, 20, 0.5) !important;
color: var(--terminal-green) !important;
font-size: 0.7rem;
padding: 0.3rem 0.6rem;
border-radius: 6px;
font-weight: 400;
cursor: pointer;
transition: all 0.2s ease;
white-space: normal;
max-width: 240px;
min-height: 2rem;
line-height: 1.2;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.example-btn-inline:hover {
background: linear-gradient(135deg, rgba(57, 255, 20, 0.2), rgba(57, 255, 20, 0.1)) !important;
border-color: var(--terminal-green) !important;
transform: translateY(-1px) scale(1.02);
box-shadow: 0 2px 8px rgba(57, 255, 20, 0.3);
}
.show-all-options-btn {
background: rgba(57, 255, 20, 0.1) !important;
border: 1px solid rgba(57, 255, 20, 0.6) !important;
color: var(--terminal-green) !important;
font-size: 0.75rem;
padding: 0.4rem 0.8rem;
border-radius: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.show-all-options-btn:hover {
background: rgba(57, 255, 20, 0.2) !important;
border-color: var(--terminal-green) !important;
transform: translateY(-1px);
box-shadow: 0 2px 6px rgba(57, 255, 20, 0.3);
}
.terminal-header {
min-height: 80px;
background: rgba(0, 0, 0, 0.2);
}
.status-indicator {
font-size: 0.85rem;
}
/* Improved text visibility */
.text-light {
color: #f8f9fa !important;
}
.text-muted {
color: #adb5bd !important;
}
small.text-light {
color: #e9ecef !important;
}
#personality-description {
font-weight: 500;
color: #ced4da !important;
}
/* Better contrast for important elements */
.terminal-card {
background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !important;
border: 1px solid var(--terminal-border) !important;
}
/* Ensure Font Awesome icons display properly */
.fas, .far, .fab, .fal {
font-family: "Font Awesome 6 Free", "Font Awesome 6 Brands", "Font Awesome 6 Pro" !important;
font-weight: 900;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
line-height: 1;
}
/* Button icon styling */
.btn i {
font-size: 0.9rem;
margin-right: 0.5rem;
vertical-align: middle;
}
/* Icon loading check */
i.fas {
min-width: 16px;
}
/* Better button styling */
.quick-actions .btn {
font-size: 0.9rem;
padding: 0.5rem 1rem;
border-color: var(--terminal-border) !important;
transition: all 0.3s ease;
}
.quick-actions .btn:hover {
background: var(--terminal-accent) !important;
border-color: var(--terminal-accent) !important;
color: var(--terminal-bg) !important;
transform: translateX(5px);
}
/* Terminal-style message input */
.terminal-input-container {
background: rgba(0, 0, 0, 0.8);
border: 2px solid var(--terminal-border);
border-radius: 8px;
padding: 1rem;
position: relative;
transition: all 0.3s ease;
}
.terminal-input-container:focus-within {
border-color: var(--terminal-green);
box-shadow: 0 0 15px var(--terminal-green-glow);
}
.terminal-prompt {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
}
.prompt-symbol {
font-weight: bold;
font-size: 1.2rem;
margin-right: 0.5rem;
}
.prompt-text {
opacity: 0.8;
}
.input-wrapper {
display: flex;
align-items: center;
gap: 1rem;
}
.terminal-message-input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--terminal-green);
font-family: 'JetBrains Mono', monospace;
font-size: 1rem;
padding: 0.5rem 0;
caret-color: var(--terminal-green);
}
.terminal-message-input::placeholder {
color: rgba(57, 255, 20, 0.5);
font-style: italic;
}
.terminal-send-btn {
background: linear-gradient(45deg, var(--terminal-accent), var(--terminal-green));
border: none;
border-radius: 6px;
padding: 0.75rem 1.5rem;
color: var(--terminal-bg);
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
}
.terminal-send-btn:hover {
transform: scale(1.05);
box-shadow: 0 4px 15px var(--terminal-green-glow);
}
.terminal-send-btn:active {
transform: scale(0.98);
}
.terminal-send-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
}
.input-status-bar {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8rem;
margin-top: 0.5rem;
}
.char-counter {
color: var(--terminal-accent);
opacity: 0.7;
}
.typing-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--terminal-green);
}
.typing-dots {
display: flex;
gap: 2px;
}
.typing-dots span {
width: 4px;
height: 4px;
background: var(--terminal-green);
border-radius: 50%;
animation: typing-pulse 1.5s infinite;
}
.typing-dots span:nth-child(1) { animation-delay: 0s; }
.typing-dots span:nth-child(2) { animation-delay: 0.3s; }
.typing-dots span:nth-child(3) { animation-delay: 0.6s; }
@keyframes typing-pulse {
0%, 60%, 100% { opacity: 0.3; }
30% { opacity: 1; }
}
.typing-text {
font-style: italic;
opacity: 0.8;
}
/* Terminal Header - Clean & Aligned */
.terminal-header {
background: var(--terminal-bg);
border-bottom: 1px solid var(--terminal-border);
padding: 0.75rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.terminal-title {
display: flex;
align-items: center;
gap: 0.75rem;
flex: 1;
}
.terminal-icon {
font-size: 1.2rem;
background: var(--terminal-border);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid var(--terminal-green);
box-shadow: 0 0 5px var(--terminal-green-glow);
}
.bot-status-bar {
background: rgba(87, 86, 86, 0.1);
border-bottom: 1px solid var(--terminal-border);
padding: 0.5rem 1rem;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.85rem;
}
.status-info {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-indicator {
width: 8px;
height: 8px;
background: var(--terminal-green);
border-radius: 50%;
animation: status-blink 2s ease-in-out infinite;
box-shadow: 0 0 8px var(--terminal-green);
}
@keyframes status-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.status-text {
color: var(--terminal-accent);
font-family: 'JetBrains Mono', monospace;
}
.back-btn {
display: flex;
align-items: center;
gap: 0.25rem;
color: var(--terminal-accent);
text-decoration: none;
font-family: 'JetBrains Mono', monospace;
transition: color 0.3s ease;
}
.back-btn:hover {
color: var(--terminal-green);
text-decoration: none;
}
.terminal-text {
color: var(--terminal-green);
font-weight: 600;
font-size: 1.1rem;
font-family: 'JetBrains Mono', monospace;
}
.terminal-subtitle {
color: var(--terminal-accent);
font-size: 0.8rem;
font-family: 'JetBrains Mono', monospace;
opacity: 0.8;
margin-left: 0.5rem;
}
.terminal-controls {
display: flex;
gap: 0.5rem;
align-items: center;
}
.control-dot {
width: 12px;
height: 12px;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.control-dot.close {
background: #ff5f57;
}
.control-dot.minimize {
background: #ffbd2e;
}
.control-dot.maximize {
background: #28ca42;
}
.control-dot:hover {
transform: scale(1.2);
box-shadow: 0 0 8px currentColor;
}
.control-dot:active {
transform: scale(1.1);
}
/* Modern Send Button */
.modern-send-btn {
background: linear-gradient(135deg, var(--terminal-accent) 0%, var(--terminal-green) 100%);
border: none;
border-radius: 8px;
padding: 0.75rem 1.25rem;
color: var(--terminal-bg);
font-weight: 600;
font-family: 'JetBrains Mono', monospace;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
position: relative;
overflow: hidden;
box-shadow: 0 2px 10px rgba(57, 255, 20, 0.2);
}
.modern-send-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.modern-send-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(57, 255, 20, 0.4);
background: linear-gradient(135deg, var(--terminal-green) 0%, var(--terminal-accent) 100%);
}
.modern-send-btn:hover::before {
left: 100%;
}
.modern-send-btn:active {
transform: translateY(0);
box-shadow: 0 4px 15px rgba(57, 255, 20, 0.3);
}
.modern-send-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
background: var(--terminal-border);
}
.modern-send-btn .send-icon {
font-size: 1rem;
transition: transform 0.3s ease;
}
.modern-send-btn:hover .send-icon {
transform: translateX(2px);
}
.modern-send-btn.loading .send-icon {
animation: loading-spin 1s linear infinite;
}
@keyframes loading-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Responsive design */
@media (max-width: 768px) {
.terminal-send-btn .send-text,
.modern-send-btn .send-text {
display: none;
}
.terminal-send-btn,
.modern-send-btn {
padding: 0.75rem;
}
.prompt-text {
font-size: 0.8rem;
}
.terminal-header {
padding: 0.5rem 0.75rem;
}
.bot-status-bar {
padding: 0.4rem 0.75rem;
}
.terminal-text {
font-size: 1rem;
}
.terminal-subtitle {
display: none;
}
.terminal-icon {
padding: 0.3rem;
font-size: 1rem;
}
.back-text {
display: none;
}
}
</style>
{% endblock %}