| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>CodeLlama Pro - AI Code Assistant</title> |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css" rel="stylesheet"> |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| color: #333; |
| } |
| |
| .container { |
| max-width: 1400px; |
| margin: 0 auto; |
| padding: 20px; |
| min-height: 100vh; |
| } |
| |
| .header { |
| text-align: center; |
| margin-bottom: 30px; |
| color: white; |
| } |
| |
| .header h1 { |
| font-size: 3rem; |
| margin-bottom: 10px; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); |
| } |
| |
| .header p { |
| font-size: 1.2rem; |
| opacity: 0.9; |
| } |
| |
| .main-content { |
| display: grid; |
| grid-template-columns: 1fr 1fr; |
| gap: 30px; |
| height: calc(100vh - 200px); |
| } |
| |
| .panel { |
| background: rgba(255, 255, 255, 0.95); |
| backdrop-filter: blur(10px); |
| border-radius: 20px; |
| box-shadow: 0 20px 40px rgba(0,0,0,0.1); |
| padding: 30px; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .panel-header { |
| display: flex; |
| align-items: center; |
| margin-bottom: 20px; |
| padding-bottom: 15px; |
| border-bottom: 2px solid #eee; |
| } |
| |
| .panel-header i { |
| font-size: 1.5rem; |
| margin-right: 15px; |
| color: #667eea; |
| } |
| |
| .panel-header h2 { |
| font-size: 1.5rem; |
| color: #333; |
| } |
| |
| .form-group { |
| margin-bottom: 20px; |
| } |
| |
| .form-group label { |
| display: block; |
| margin-bottom: 8px; |
| font-weight: 600; |
| color: #555; |
| } |
| |
| .textarea-wrapper { |
| position: relative; |
| flex: 1; |
| } |
| |
| textarea, .code-output { |
| width: 100%; |
| padding: 15px; |
| border: 2px solid #e0e0e0; |
| border-radius: 12px; |
| font-family: 'Consolas', 'Monaco', 'Courier New', monospace; |
| font-size: 14px; |
| resize: vertical; |
| transition: all 0.3s ease; |
| background: #fafafa; |
| } |
| |
| textarea:focus { |
| outline: none; |
| border-color: #667eea; |
| box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); |
| } |
| |
| .code-output { |
| background: #2d3748; |
| color: #e2e8f0; |
| min-height: 200px; |
| overflow-y: auto; |
| white-space: pre-wrap; |
| word-break: break-all; |
| } |
| |
| .btn { |
| padding: 12px 30px; |
| border: none; |
| border-radius: 25px; |
| font-size: 16px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| text-decoration: none; |
| } |
| |
| .btn-primary { |
| background: linear-gradient(45deg, #667eea, #764ba2); |
| color: white; |
| } |
| |
| .btn-primary:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); |
| } |
| |
| .btn-secondary { |
| background: #6c757d; |
| color: white; |
| } |
| |
| .btn-secondary:hover { |
| background: #5a6268; |
| transform: translateY(-2px); |
| } |
| |
| .btn-danger { |
| background: #dc3545; |
| color: white; |
| } |
| |
| .btn-danger:hover { |
| background: #c82333; |
| transform: translateY(-2px); |
| } |
| |
| .btn:disabled { |
| opacity: 0.6; |
| cursor: not-allowed; |
| transform: none !important; |
| } |
| |
| .action-buttons { |
| display: flex; |
| gap: 15px; |
| margin-top: 20px; |
| flex-wrap: wrap; |
| } |
| |
| .status-bar { |
| background: rgba(255, 255, 255, 0.9); |
| padding: 15px 30px; |
| border-radius: 15px; |
| margin-bottom: 20px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
| } |
| |
| .status-indicator { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .status-dot { |
| width: 12px; |
| height: 12px; |
| border-radius: 50%; |
| background: #dc3545; |
| animation: pulse 2s infinite; |
| } |
| |
| .status-dot.loading { |
| background: #ffc107; |
| } |
| |
| .status-dot.ready { |
| background: #28a745; |
| animation: none; |
| } |
| |
| @keyframes pulse { |
| 0% { opacity: 1; } |
| 50% { opacity: 0.5; } |
| 100% { opacity: 1; } |
| } |
| |
| .loading-spinner { |
| display: inline-block; |
| width: 20px; |
| height: 20px; |
| border: 3px solid #f3f3f3; |
| border-top: 3px solid #667eea; |
| border-radius: 50%; |
| animation: spin 1s linear infinite; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .explanation-panel { |
| background: linear-gradient(145deg, #f8f9ff, #e6f3ff); |
| border: 1px solid #d0e7ff; |
| border-radius: 12px; |
| padding: 20px; |
| margin-top: 15px; |
| } |
| |
| .explanation-panel h3 { |
| color: #2563eb; |
| margin-bottom: 10px; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .copy-btn { |
| position: absolute; |
| top: 10px; |
| right: 10px; |
| background: rgba(0,0,0,0.7); |
| color: white; |
| border: none; |
| padding: 8px 12px; |
| border-radius: 6px; |
| cursor: pointer; |
| font-size: 12px; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| } |
| |
| .textarea-wrapper:hover .copy-btn, |
| .code-output-wrapper:hover .copy-btn { |
| opacity: 1; |
| } |
| |
| .code-output-wrapper { |
| position: relative; |
| flex: 1; |
| } |
| |
| .toast { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| background: #28a745; |
| color: white; |
| padding: 15px 25px; |
| border-radius: 8px; |
| box-shadow: 0 5px 15px rgba(0,0,0,0.2); |
| z-index: 1000; |
| opacity: 0; |
| transform: translateX(100px); |
| transition: all 0.3s ease; |
| } |
| |
| .toast.show { |
| opacity: 1; |
| transform: translateX(0); |
| } |
| |
| .toast.error { |
| background: #dc3545; |
| } |
| |
| .toast.info { |
| background: #17a2b8; |
| } |
| |
| .session-info { |
| position: absolute; |
| top: 10px; |
| left: 10px; |
| background: rgba(255, 255, 255, 0.9); |
| padding: 5px 10px; |
| border-radius: 15px; |
| font-size: 12px; |
| color: #666; |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| } |
| |
| .user-indicator { |
| width: 8px; |
| height: 8px; |
| border-radius: 50%; |
| background: #28a745; |
| } |
| |
| @media (max-width: 768px) { |
| .main-content { |
| grid-template-columns: 1fr; |
| gap: 20px; |
| } |
| |
| .header h1 { |
| font-size: 2rem; |
| } |
| |
| .container { |
| padding: 10px; |
| } |
| |
| .action-buttons { |
| flex-direction: column; |
| } |
| } |
| |
| .char-counter { |
| position: absolute; |
| bottom: 10px; |
| right: 15px; |
| font-size: 12px; |
| color: #666; |
| background: rgba(255,255,255,0.9); |
| padding: 2px 6px; |
| border-radius: 4px; |
| } |
| |
| .session-controls { |
| display: flex; |
| gap: 10px; |
| align-items: center; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <div class="session-info"> |
| <div class="user-indicator"></div> |
| <span id="sessionInfo">Session Active</span> |
| </div> |
|
|
| <div class="header"> |
| <h1><i class="fas fa-code"></i> CodeLlama Pro</h1> |
| <p>Advanced AI-Powered Code Assistant & Editor</p> |
| </div> |
|
|
| <div class="status-bar"> |
| <div class="status-indicator"> |
| <div class="status-dot" id="statusDot"></div> |
| <span id="statusText">Initializing model...</span> |
| </div> |
| <div class="session-controls"> |
| <button class="btn btn-danger" id="clearSessionBtn" onclick="clearSession()"> |
| <i class="fas fa-trash"></i> Clear Session |
| </button> |
| <button class="btn btn-secondary" id="loadModelBtn" onclick="loadModel()"> |
| <i class="fas fa-download"></i> Load Model |
| </button> |
| </div> |
| </div> |
|
|
| <div class="main-content"> |
| <div class="panel"> |
| <div class="panel-header"> |
| <i class="fas fa-edit"></i> |
| <h2>Code Input & Instructions</h2> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="existingCode">Existing Code (Optional)</label> |
| <div class="textarea-wrapper"> |
| <textarea id="existingCode" placeholder="Paste your existing code here (optional)..." rows="10"></textarea> |
| <button class="copy-btn" onclick="copyToClipboard('existingCode')"> |
| <i class="fas fa-copy"></i> Copy |
| </button> |
| <div class="char-counter" id="codeCounter">0 characters</div> |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label for="instruction">Instructions (Required) *</label> |
| <div class="textarea-wrapper"> |
| <textarea id="instruction" placeholder="Describe what you want me to do with the code..." rows="6" required></textarea> |
| <div class="char-counter" id="instructionCounter">0 characters</div> |
| </div> |
| </div> |
|
|
| <div class="action-buttons"> |
| <button class="btn btn-primary" id="generateBtn" onclick="generateCode()" disabled> |
| <i class="fas fa-magic"></i> Generate Code |
| </button> |
| <button class="btn btn-secondary" id="explainBtn" onclick="explainCode()"> |
| <i class="fas fa-lightbulb"></i> Explain Code |
| </button> |
| </div> |
| </div> |
|
|
| <div class="panel"> |
| <div class="panel-header"> |
| <i class="fas fa-code"></i> |
| <h2>Generated Code</h2> |
| </div> |
|
|
| <div class="code-output-wrapper"> |
| <div class="code-output" id="codeOutput"> |
| <div style="color: #a0aec0; font-style: italic; text-align: center; margin-top: 80px;"> |
| <i class="fas fa-rocket" style="font-size: 3rem; margin-bottom: 20px; display: block;"></i> |
| Generated code will appear here... |
| <br><br> |
| Load the model first, then provide instructions to get started! |
| </div> |
| </div> |
| <button class="copy-btn" onclick="copyToClipboard('codeOutput')"> |
| <i class="fas fa-copy"></i> Copy Code |
| </button> |
| </div> |
|
|
| <div class="explanation-panel" id="explanationPanel" style="display: none;"> |
| <h3><i class="fas fa-info-circle"></i> Explanation</h3> |
| <div id="explanationText"></div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="toast" id="toast"></div> |
|
|
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script> |
| <script> |
| let modelLoaded = false; |
| let isGenerating = false; |
| let sessionId = null; |
| let lastSaveTime = null; |
| |
| |
| function generateSessionId() { |
| return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now(); |
| } |
| |
| function initializeSession() { |
| |
| sessionId = localStorage.getItem('codellama_session_id'); |
| |
| if (!sessionId) { |
| |
| sessionId = generateSessionId(); |
| localStorage.setItem('codellama_session_id', sessionId); |
| localStorage.setItem('codellama_session_created', new Date().toISOString()); |
| showToast('Welcome! New session created.', 'info'); |
| } else { |
| |
| restoreSession(); |
| showToast('Welcome back! Session restored.', 'success'); |
| } |
| |
| updateSessionInfo(); |
| } |
| |
| function saveSession() { |
| if (!sessionId) return; |
| |
| const sessionData = { |
| existingCode: document.getElementById('existingCode').value, |
| instruction: document.getElementById('instruction').value, |
| codeOutput: document.getElementById('codeOutput').textContent, |
| explanationText: document.getElementById('explanationText').textContent, |
| explanationVisible: document.getElementById('explanationPanel').style.display !== 'none', |
| timestamp: new Date().toISOString() |
| }; |
| |
| localStorage.setItem(`codellama_session_${sessionId}`, JSON.stringify(sessionData)); |
| lastSaveTime = new Date(); |
| updateSessionInfo(); |
| } |
| |
| function restoreSession() { |
| if (!sessionId) return; |
| |
| const savedData = localStorage.getItem(`codellama_session_${sessionId}`); |
| if (!savedData) return; |
| |
| try { |
| const sessionData = JSON.parse(savedData); |
| |
| |
| document.getElementById('existingCode').value = sessionData.existingCode || ''; |
| document.getElementById('instruction').value = sessionData.instruction || ''; |
| |
| |
| const codeOutput = document.getElementById('codeOutput'); |
| if (sessionData.codeOutput && !sessionData.codeOutput.includes('Generated code will appear here')) { |
| codeOutput.textContent = sessionData.codeOutput; |
| |
| Prism.highlightElement(codeOutput); |
| } |
| |
| |
| if (sessionData.explanationText && sessionData.explanationVisible) { |
| document.getElementById('explanationText').textContent = sessionData.explanationText; |
| document.getElementById('explanationPanel').style.display = 'block'; |
| } |
| |
| |
| updateCharCounters(); |
| |
| lastSaveTime = new Date(sessionData.timestamp); |
| |
| } catch (error) { |
| console.error('Error restoring session:', error); |
| showToast('Error restoring session data.', 'error'); |
| } |
| } |
| |
| function clearSession() { |
| if (confirm('Are you sure you want to clear your session? This will remove all your work and start fresh.')) { |
| |
| if (sessionId) { |
| localStorage.removeItem(`codellama_session_${sessionId}`); |
| } |
| localStorage.removeItem('codellama_session_id'); |
| localStorage.removeItem('codellama_session_created'); |
| |
| |
| document.getElementById('existingCode').value = ''; |
| document.getElementById('instruction').value = ''; |
| document.getElementById('codeOutput').innerHTML = ` |
| <div style="color: #a0aec0; font-style: italic; text-align: center; margin-top: 80px;"> |
| <i class="fas fa-rocket" style="font-size: 3rem; margin-bottom: 20px; display: block;"></i> |
| Generated code will appear here... |
| <br><br> |
| Load the model first, then provide instructions to get started! |
| </div> |
| `; |
| document.getElementById('explanationPanel').style.display = 'none'; |
| |
| |
| sessionId = generateSessionId(); |
| localStorage.setItem('codellama_session_id', sessionId); |
| localStorage.setItem('codellama_session_created', new Date().toISOString()); |
| |
| updateCharCounters(); |
| updateSessionInfo(); |
| showToast('Session cleared! Starting fresh.', 'success'); |
| } |
| } |
| |
| function updateSessionInfo() { |
| const sessionInfo = document.getElementById('sessionInfo'); |
| const createdTime = localStorage.getItem('codellama_session_created'); |
| |
| if (createdTime) { |
| const created = new Date(createdTime); |
| const now = new Date(); |
| const diffHours = Math.floor((now - created) / (1000 * 60 * 60)); |
| |
| if (diffHours < 1) { |
| sessionInfo.textContent = 'Active (New)'; |
| } else if (diffHours < 24) { |
| sessionInfo.textContent = `Active (${diffHours}h ago)`; |
| } else { |
| const diffDays = Math.floor(diffHours / 24); |
| sessionInfo.textContent = `Active (${diffDays}d ago)`; |
| } |
| } |
| } |
| |
| function updateCharCounters() { |
| const codeTextarea = document.getElementById('existingCode'); |
| const instructionTextarea = document.getElementById('instruction'); |
| |
| document.getElementById('codeCounter').textContent = codeTextarea.value.length + ' characters'; |
| document.getElementById('instructionCounter').textContent = instructionTextarea.value.length + ' characters'; |
| } |
| |
| |
| function setupAutoSave() { |
| const elements = ['existingCode', 'instruction']; |
| |
| elements.forEach(id => { |
| const element = document.getElementById(id); |
| element.addEventListener('input', () => { |
| clearTimeout(window.autoSaveTimeout); |
| window.autoSaveTimeout = setTimeout(saveSession, 2000); |
| }); |
| }); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', function() { |
| initializeSession(); |
| checkModelStatus(); |
| setupCharCounters(); |
| setupAutoResize(); |
| setupAutoSave(); |
| |
| |
| setInterval(checkModelStatus, 5000); |
| |
| |
| setInterval(saveSession, 30000); |
| |
| |
| window.addEventListener('beforeunload', saveSession); |
| }); |
| |
| |
| function setupCharCounters() { |
| const codeTextarea = document.getElementById('existingCode'); |
| const instructionTextarea = document.getElementById('instruction'); |
| |
| codeTextarea.addEventListener('input', updateCharCounters); |
| instructionTextarea.addEventListener('input', updateCharCounters); |
| |
| |
| updateCharCounters(); |
| } |
| |
| |
| function setupAutoResize() { |
| const textareas = document.querySelectorAll('textarea'); |
| textareas.forEach(textarea => { |
| textarea.addEventListener('input', function() { |
| this.style.height = 'auto'; |
| this.style.height = (this.scrollHeight) + 'px'; |
| }); |
| }); |
| } |
| |
| |
| async function checkModelStatus() { |
| try { |
| const response = await fetch('/api/status'); |
| const data = await response.json(); |
| |
| const statusDot = document.getElementById('statusDot'); |
| const statusText = document.getElementById('statusText'); |
| const generateBtn = document.getElementById('generateBtn'); |
| const loadModelBtn = document.getElementById('loadModelBtn'); |
| |
| if (data.is_loaded) { |
| statusDot.className = 'status-dot ready'; |
| statusText.textContent = 'Model Ready'; |
| generateBtn.disabled = false; |
| loadModelBtn.style.display = 'none'; |
| modelLoaded = true; |
| } else if (data.is_loading) { |
| statusDot.className = 'status-dot loading'; |
| statusText.innerHTML = '<span class="loading-spinner"></span> Loading Model...'; |
| generateBtn.disabled = true; |
| loadModelBtn.disabled = true; |
| } else { |
| statusDot.className = 'status-dot'; |
| statusText.textContent = 'Model Not Loaded'; |
| generateBtn.disabled = true; |
| loadModelBtn.disabled = false; |
| loadModelBtn.style.display = 'inline-flex'; |
| } |
| } catch (error) { |
| console.error('Error checking status:', error); |
| } |
| } |
| |
| |
| async function loadModel() { |
| try { |
| const response = await fetch('/api/load_model', { method: 'POST' }); |
| const data = await response.json(); |
| |
| if (data.status === 'loading') { |
| showToast('Model loading started...', 'info'); |
| } else if (data.status === 'loaded') { |
| showToast('Model already loaded!', 'success'); |
| } |
| |
| checkModelStatus(); |
| } catch (error) { |
| showToast('Error loading model: ' + error.message, 'error'); |
| } |
| } |
| |
| |
| async function generateCode() { |
| if (!modelLoaded || isGenerating) return; |
| |
| const existingCode = document.getElementById('existingCode').value; |
| const instruction = document.getElementById('instruction').value.trim(); |
| |
| if (!instruction) { |
| showToast('Please provide instructions!', 'error'); |
| return; |
| } |
| |
| isGenerating = true; |
| const generateBtn = document.getElementById('generateBtn'); |
| const originalText = generateBtn.innerHTML; |
| |
| generateBtn.innerHTML = '<span class="loading-spinner"></span> Generating...'; |
| generateBtn.disabled = true; |
| |
| try { |
| const response = await fetch('/api/generate', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| existing_code: existingCode, |
| instruction: instruction |
| }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.error) { |
| showToast('Error: ' + data.error, 'error'); |
| return; |
| } |
| |
| |
| const codeOutput = document.getElementById('codeOutput'); |
| codeOutput.textContent = data.code || data.full_response; |
| |
| |
| Prism.highlightElement(codeOutput); |
| |
| |
| if (data.explanation) { |
| const explanationPanel = document.getElementById('explanationPanel'); |
| const explanationText = document.getElementById('explanationText'); |
| explanationText.textContent = data.explanation; |
| explanationPanel.style.display = 'block'; |
| } |
| |
| |
| saveSession(); |
| |
| showToast('Code generated successfully!', 'success'); |
| |
| } catch (error) { |
| showToast('Error generating code: ' + error.message, 'error'); |
| } finally { |
| isGenerating = false; |
| generateBtn.innerHTML = originalText; |
| generateBtn.disabled = false; |
| } |
| } |
| |
| |
| async function explainCode() { |
| if (!modelLoaded) { |
| showToast('Please load the model first!', 'error'); |
| return; |
| } |
| |
| const existingCode = document.getElementById('existingCode').value.trim(); |
| const codeOutput = document.getElementById('codeOutput').textContent.trim(); |
| |
| const codeToExplain = existingCode || codeOutput; |
| |
| if (!codeToExplain || codeToExplain.includes('Generated code will appear here')) { |
| showToast('Please provide code to explain!', 'error'); |
| return; |
| } |
| |
| const explainBtn = document.getElementById('explainBtn'); |
| const originalText = explainBtn.innerHTML; |
| explainBtn.innerHTML = '<span class="loading-spinner"></span> Explaining...'; |
| explainBtn.disabled = true; |
| |
| try { |
| const response = await fetch('/api/explain', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ code: codeToExplain }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (data.error) { |
| showToast('Error: ' + data.error, 'error'); |
| return; |
| } |
| |
| |
| const explanationPanel = document.getElementById('explanationPanel'); |
| const explanationText = document.getElementById('explanationText'); |
| explanationText.textContent = data.explanation; |
| explanationPanel.style.display = 'block'; |
| |
| |
| saveSession(); |
| |
| showToast('Code explanation generated!', 'success'); |
| |
| } catch (error) { |
| showToast('Error explaining code: ' + error.message, 'error'); |
| } finally { |
| explainBtn.innerHTML = originalText; |
| explainBtn.disabled = false; |
| } |
| } |
| |
| |
| async function copyToClipboard(elementId) { |
| const element = document.getElementById(elementId); |
| const text = element.value || element.textContent; |
| |
| try { |
| await navigator.clipboard.writeText(text); |
| showToast('Copied to clipboard!', 'success'); |
| } catch (error) { |
| |
| const textArea = document.createElement('textarea'); |
| textArea.value = text; |
| document.body.appendChild(textArea); |
| textArea.select(); |
| document.execCommand('copy'); |
| document.body.removeChild(textArea); |
| showToast('Copied to clipboard!', 'success'); |
| } |
| } |
| |
| |
| function showToast(message, type = 'success') { |
| const toast = document.getElementById('toast'); |
| toast.textContent = message; |
| toast.className = `toast show ${type}`; |
| |
| setTimeout(() => { |
| toast.className = 'toast'; |
| }, 3000); |
| } |
| |
| |
| document.addEventListener('keydown', function(e) { |
| if (e.ctrlKey || e.metaKey) { |
| if (e.key === 'Enter') { |
| e.preventDefault(); |
| generateCode(); |
| } else if (e.key === 'e') { |
| e.preventDefault(); |
| explainCode(); |
| } else if (e.key === 's') { |
| e.preventDefault(); |
| saveSession(); |
| showToast('Session saved manually!', 'success'); |
| } |
| } |
| }); |
| </script> |
| </body> |
| </html> |