| {% extends "base.html" %} |
|
|
| {% block title %}{{ personality_type.title() }} Bot - Chat{% endblock %} |
|
|
| {% block content %} |
| <div class="container-fluid"> |
| <div class="row"> |
| |
| <div class="col-lg-8 col-md-7"> |
| <div class="terminal-card h-100"> |
| |
| <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> |
| |
| |
| <div class="bot-status-bar"> |
| <div class="status-info"> |
| <span class="status-indicator"></span> |
| <span class="status-text">Online & Ready</span> |
| </div> |
| </div> |
|
|
| |
| <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> |
| |
| |
| <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> |
|
|
| |
| <div class="message-input-area p-4 border-top border-secondary"> |
| |
| <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> |
| |
| |
| <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> |
|
|
| |
| <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> |
| |
| |
| <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> |
|
|
| |
| <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> |
|
|
| |
| <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, '&') |
| .replace(/</g, '<') |
| .replace(/>/g, '>') |
| .replace(/\"/g, '"') |
| .replace(/'/g, '''); |
| |
| 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"> </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); |
| } |
| |
| |
| .example-prompts-compact { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| justify-content: center; |
| } |
| |
| |
| .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-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); |
| } |
| |
| |
| .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; |
| } |
| |
| |
| .text-light { |
| color: #f8f9fa !important; |
| } |
| |
| .text-muted { |
| color: #adb5bd !important; |
| } |
| |
| small.text-light { |
| color: #e9ecef !important; |
| } |
| |
| #personality-description { |
| font-weight: 500; |
| color: #ced4da !important; |
| } |
| |
| |
| .terminal-card { |
| background: linear-gradient(135deg, #1a1a1a, #2d2d2d) !important; |
| border: 1px solid var(--terminal-border) !important; |
| } |
| |
| |
| .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; |
| } |
| |
| |
| .btn i { |
| font-size: 0.9rem; |
| margin-right: 0.5rem; |
| vertical-align: middle; |
| } |
| |
| |
| i.fas { |
| min-width: 16px; |
| } |
| |
| |
| .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-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 { |
| 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-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); } |
| } |
| |
| |
| @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 %} |