Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neural AI Tutor | Interactive Learning Platform</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background: #0a0a0f; | |
| color: #fff; | |
| font-family: 'Segoe UI', system-ui, sans-serif; | |
| overflow: hidden; | |
| height: 100vh; | |
| display: flex; | |
| } | |
| /* Main Canvas - Prominently Displayed */ | |
| #canvasContainer { | |
| flex: 1; | |
| position: relative; | |
| background: linear-gradient(135deg, #0a0a0f 0%, #0f0f1a 100%); | |
| } | |
| canvas { | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Left Sidebar - Hidden Panels */ | |
| .sidebar { | |
| width: 80px; | |
| background: rgba(15, 15, 25, 0.9); | |
| backdrop-filter: blur(10px); | |
| border-right: 1px solid rgba(255, 255, 255, 0.05); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 20px 10px; | |
| z-index: 1000; | |
| transition: width 0.3s ease; | |
| } | |
| .sidebar:hover, | |
| .sidebar.expanded { | |
| width: 380px; | |
| } | |
| .sidebar:hover .panel-content, | |
| .sidebar.expanded .panel-content { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| .panel-toggle { | |
| width: 100%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-bottom: 30px; | |
| padding: 15px 0; | |
| cursor: pointer; | |
| border-radius: 12px; | |
| transition: all 0.3s ease; | |
| background: rgba(90, 108, 255, 0.1); | |
| border: 1px solid rgba(90, 108, 255, 0.2); | |
| } | |
| .panel-toggle:hover { | |
| background: rgba(90, 108, 255, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .toggle-icon { | |
| font-size: 1.5em; | |
| color: #a0b0ff; | |
| } | |
| .panel-content { | |
| width: 100%; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: all 0.3s ease; | |
| overflow-y: auto; | |
| max-height: calc(100vh - 120px); | |
| } | |
| .panel-section { | |
| margin-bottom: 25px; | |
| background: rgba(20, 20, 30, 0.7); | |
| border-radius: 15px; | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| overflow: hidden; | |
| } | |
| .panel-header { | |
| padding: 15px; | |
| background: rgba(30, 30, 45, 0.8); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .panel-header:hover { | |
| background: rgba(40, 40, 60, 0.8); | |
| } | |
| .panel-header h3 { | |
| color: #a0b0ff; | |
| font-size: 1em; | |
| font-weight: 500; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .panel-header i { | |
| font-size: 1.2em; | |
| } | |
| .panel-body { | |
| padding: 20px; | |
| display: none; | |
| } | |
| .panel-body.active { | |
| display: block; | |
| } | |
| .chevron { | |
| transition: transform 0.3s ease; | |
| } | |
| .chevron.rotated { | |
| transform: rotate(90deg); | |
| } | |
| /* Chat Interface - Horizontal and Centered */ | |
| .chat-overlay { | |
| position: absolute; | |
| bottom: 30px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 800px; | |
| max-width: 85vw; | |
| background: rgba(20, 20, 30, 0.85); | |
| backdrop-filter: blur(20px); | |
| border-radius: 20px; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 15px 40px rgba(0, 0, 0, 0.4); | |
| z-index: 100; | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| opacity: 0; | |
| animation: fadeIn 0.5s ease 1s forwards; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .chat-overlay.collapsed { | |
| width: 400px; | |
| height: 70px; | |
| bottom: 20px; | |
| } | |
| .chat-overlay.collapsed .chat-header { | |
| border-radius: 15px; | |
| padding: 15px; | |
| } | |
| .chat-overlay.collapsed .chat-messages, | |
| .chat-overlay.collapsed .chat-input-container { | |
| display: none; | |
| } | |
| .chat-overlay.collapsed .chat-header { | |
| cursor: pointer; | |
| } | |
| .chat-overlay.collapsed .chat-header:hover { | |
| background: rgba(35, 35, 50, 0.9); | |
| } | |
| @keyframes fadeIn { | |
| to { | |
| opacity: 1; | |
| } | |
| } | |
| .chat-header { | |
| padding: 20px; | |
| background: rgba(25, 25, 40, 0.9); | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| transition: all 0.3s ease; | |
| } | |
| .chat-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| .chat-title h2 { | |
| color: #a0b0ff; | |
| font-weight: 400; | |
| font-size: 1.3em; | |
| } | |
| .ai-avatar { | |
| width: 40px; | |
| height: 40px; | |
| background: linear-gradient(135deg, #5a6cff, #00ff9d); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.3em; | |
| } | |
| .chat-subtitle { | |
| color: #8892b0; | |
| font-size: 0.85em; | |
| line-height: 1.5; | |
| } | |
| .chat-controls { | |
| display: flex; | |
| gap: 10px; | |
| align-items: center; | |
| } | |
| .collapse-btn { | |
| width: 36px; | |
| height: 36px; | |
| border-radius: 10px; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| color: #a0b0ff; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| } | |
| .collapse-btn:hover { | |
| background: rgba(90, 108, 255, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .chat-messages { | |
| display: flex; | |
| flex-direction: row; | |
| gap: 15px; | |
| padding: 20px; | |
| overflow-x: auto; | |
| overflow-y: hidden; | |
| height: 160px; | |
| scrollbar-width: thin; | |
| scrollbar-color: #5a6cff rgba(255, 255, 255, 0.05); | |
| white-space: nowrap; | |
| } | |
| .chat-messages::-webkit-scrollbar { | |
| height: 6px; | |
| } | |
| .chat-messages::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 3px; | |
| } | |
| .chat-messages::-webkit-scrollbar-thumb { | |
| background: #5a6cff; | |
| border-radius: 3px; | |
| } | |
| .message { | |
| min-width: 280px; | |
| max-width: 350px; | |
| padding: 15px; | |
| border-radius: 15px; | |
| line-height: 1.4; | |
| position: relative; | |
| animation: messageAppear 0.3s ease; | |
| white-space: normal; | |
| word-wrap: break-word; | |
| display: inline-block; | |
| vertical-align: top; | |
| margin-right: 15px; | |
| } | |
| @keyframes messageAppear { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .message-user { | |
| background: linear-gradient(135deg, #5a6cff, #7a8aff); | |
| color: white; | |
| } | |
| .message-ai { | |
| background: rgba(255, 255, 255, 0.08); | |
| color: #e0e0ff; | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .message-content { | |
| font-size: 0.95em; | |
| margin-bottom: 8px; | |
| } | |
| .message-time { | |
| font-size: 0.7em; | |
| opacity: 0.7; | |
| text-align: right; | |
| } | |
| .message-typing { | |
| min-width: 280px; | |
| padding: 15px; | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 15px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .typing-dot { | |
| width: 8px; | |
| height: 8px; | |
| background: #a0b0ff; | |
| border-radius: 50%; | |
| animation: typing 1.4s infinite ease-in-out; | |
| } | |
| .typing-dot:nth-child(1) { animation-delay: -0.32s; } | |
| .typing-dot:nth-child(2) { animation-delay: -0.16s; } | |
| @keyframes typing { | |
| 0%, 80%, 100% { transform: translateY(0); } | |
| 40% { transform: translateY(-10px); } | |
| } | |
| .chat-input-container { | |
| padding: 20px; | |
| background: rgba(15, 15, 25, 0.9); | |
| border-top: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .input-wrapper { | |
| display: flex; | |
| gap: 12px; | |
| align-items: flex-end; | |
| } | |
| .chat-input { | |
| flex: 1; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 12px; | |
| padding: 15px 18px; | |
| color: white; | |
| font-family: 'Segoe UI', system-ui, sans-serif; | |
| font-size: 0.95em; | |
| resize: none; | |
| min-height: 50px; | |
| max-height: 100px; | |
| transition: all 0.3s ease; | |
| } | |
| .chat-input:focus { | |
| outline: none; | |
| border-color: #5a6cff; | |
| background: rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2); | |
| } | |
| .chat-input::placeholder { | |
| color: rgba(255, 255, 255, 0.4); | |
| } | |
| .input-buttons { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .voice-input-btn, | |
| .send-btn { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 12px; | |
| background: linear-gradient(135deg, #5a6cff, #7a8aff); | |
| border: none; | |
| color: white; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| font-size: 1.1em; | |
| } | |
| .voice-input-btn:hover, | |
| .send-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(90, 108, 255, 0.4); | |
| } | |
| .voice-input-btn.listening { | |
| background: linear-gradient(135deg, #ff5a5a, #ff7a7a); | |
| animation: pulseListening 1.5s infinite; | |
| } | |
| @keyframes pulseListening { | |
| 0%, 100% { box-shadow: 0 0 0 0 rgba(255, 90, 90, 0.7); } | |
| 70% { box-shadow: 0 0 0 10px rgba(255, 90, 90, 0); } | |
| } | |
| .send-btn { | |
| background: rgba(0, 255, 157, 0.2); | |
| border: 1px solid rgba(0, 255, 157, 0.3); | |
| color: #00ff9d; | |
| } | |
| .send-btn:hover { | |
| background: rgba(0, 255, 157, 0.3); | |
| } | |
| /* Control Elements in Sidebar */ | |
| .control-group { | |
| margin-bottom: 20px; | |
| } | |
| .control-label { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 10px; | |
| color: #a0b0ff; | |
| font-size: 0.9em; | |
| font-weight: 500; | |
| } | |
| .slider-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| } | |
| input[type="range"] { | |
| flex: 1; | |
| height: 6px; | |
| background: rgba(255, 255, 255, 0.1); | |
| border-radius: 3px; | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| background: #5a6cff; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| input[type="range"]::-webkit-slider-thumb:hover { | |
| background: #7a8aff; | |
| transform: scale(1.1); | |
| } | |
| .value-display { | |
| min-width: 40px; | |
| text-align: center; | |
| font-family: 'Courier New', monospace; | |
| color: #5a6cff; | |
| font-weight: 500; | |
| font-size: 0.9em; | |
| } | |
| .preset-buttons { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 8px; | |
| margin-top: 15px; | |
| } | |
| .preset-btn { | |
| padding: 10px; | |
| background: rgba(90, 108, 255, 0.1); | |
| border: 1px solid rgba(90, 108, 255, 0.3); | |
| color: #a0b0ff; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 0.85em; | |
| text-align: center; | |
| } | |
| .preset-btn:hover { | |
| background: rgba(90, 108, 255, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .preset-btn.active { | |
| background: rgba(90, 108, 255, 0.4); | |
| color: white; | |
| } | |
| .voice-control-group { | |
| margin-bottom: 20px; | |
| } | |
| .voice-select { | |
| width: 100%; | |
| padding: 12px 15px; | |
| background: rgba(255, 255, 255, 0.07); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 10px; | |
| color: white; | |
| font-family: 'Segoe UI', system-ui, sans-serif; | |
| font-size: 0.95em; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .voice-select:focus { | |
| outline: none; | |
| border-color: #5a6cff; | |
| background: rgba(255, 255, 255, 0.1); | |
| box-shadow: 0 0 0 2px rgba(90, 108, 255, 0.2); | |
| } | |
| .voice-select option { | |
| background: #1a1a2a; | |
| color: white; | |
| padding: 10px; | |
| } | |
| .voice-control-buttons { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: 10px; | |
| margin-top: 15px; | |
| } | |
| .voice-btn { | |
| padding: 12px; | |
| background: rgba(90, 108, 255, 0.1); | |
| border: 1px solid rgba(90, 108, 255, 0.3); | |
| color: #a0b0ff; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| font-size: 0.9em; | |
| } | |
| .voice-btn:hover { | |
| background: rgba(90, 108, 255, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .voice-btn.active { | |
| background: rgba(90, 108, 255, 0.3); | |
| color: white; | |
| } | |
| .voice-btn.stop { | |
| background: rgba(255, 90, 90, 0.1); | |
| border-color: rgba(255, 90, 90, 0.3); | |
| color: #ff9d9d; | |
| } | |
| .voice-btn.stop:hover { | |
| background: rgba(255, 90, 90, 0.2); | |
| } | |
| .voice-btn.stop.active { | |
| background: rgba(255, 90, 90, 0.3); | |
| } | |
| .voice-preview-btn { | |
| width: 100%; | |
| padding: 12px; | |
| background: rgba(0, 255, 157, 0.1); | |
| border: 1px solid rgba(0, 255, 157, 0.3); | |
| border-radius: 10px; | |
| color: #00ff9d; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| margin-top: 10px; | |
| font-size: 0.9em; | |
| } | |
| .voice-preview-btn:hover { | |
| background: rgba(0, 255, 157, 0.2); | |
| transform: translateY(-2px); | |
| } | |
| .voice-status { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin-top: 15px; | |
| padding: 10px; | |
| background: rgba(0, 255, 157, 0.1); | |
| border-radius: 10px; | |
| font-size: 0.85em; | |
| color: #00ff9d; | |
| border: 1px solid rgba(0, 255, 157, 0.2); | |
| } | |
| /* System Status Bar */ | |
| .status-bar { | |
| position: absolute; | |
| top: 20px; | |
| right: 20px; | |
| background: rgba(20, 20, 30, 0.8); | |
| backdrop-filter: blur(10px); | |
| padding: 12px 20px; | |
| border-radius: 12px; | |
| font-size: 0.85em; | |
| color: #8892b0; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .pulse-indicator { | |
| width: 12px; | |
| height: 12px; | |
| background: #00ff9d; | |
| border-radius: 50%; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| } | |
| /* Loading and Error Screens */ | |
| .loading-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #0a0a0f; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 2000; | |
| } | |
| .spinner { | |
| width: 60px; | |
| height: 60px; | |
| border: 3px solid rgba(90, 108, 255, 0.3); | |
| border-top-color: #5a6cff; | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| margin-bottom: 20px; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .error-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: #0a0a0f; | |
| display: none; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 2001; | |
| text-align: center; | |
| padding: 20px; | |
| } | |
| .error-screen h2 { | |
| color: #ff5a5a; | |
| margin-bottom: 20px; | |
| } | |
| .error-screen p { | |
| color: #8892b0; | |
| margin-bottom: 30px; | |
| max-width: 500px; | |
| } | |
| .retry-btn { | |
| padding: 12px 30px; | |
| background: #5a6cff; | |
| color: white; | |
| border: none; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| font-size: 1em; | |
| transition: all 0.2s; | |
| } | |
| .retry-btn:hover { | |
| background: #7a8aff; | |
| transform: translateY(-2px); | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 1200px) { | |
| .chat-overlay { | |
| width: 700px; | |
| } | |
| .chat-overlay.collapsed { | |
| width: 350px; | |
| } | |
| } | |
| @media (max-width: 768px) { | |
| body { | |
| flex-direction: column; | |
| } | |
| .sidebar { | |
| width: 100%; | |
| height: 60px; | |
| flex-direction: row; | |
| padding: 10px; | |
| } | |
| .sidebar:hover, | |
| .sidebar.expanded { | |
| height: auto; | |
| min-height: 300px; | |
| } | |
| .panel-content { | |
| position: absolute; | |
| top: 60px; | |
| left: 0; | |
| width: 100%; | |
| background: rgba(15, 15, 25, 0.95); | |
| padding: 20px; | |
| } | |
| .chat-overlay { | |
| width: 95%; | |
| max-width: 95vw; | |
| } | |
| .chat-overlay.collapsed { | |
| width: 300px; | |
| } | |
| .chat-messages { | |
| height: 140px; | |
| } | |
| .message { | |
| min-width: 250px; | |
| max-width: 300px; | |
| } | |
| } | |
| /* Scrollbar Styling */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(255, 255, 255, 0.05); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #5a6cff; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #7a8aff; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Left Sidebar with Collapsible Panels --> | |
| <div class="sidebar" id="sidebar"> | |
| <div class="panel-toggle" onclick="toggleSidebar()"> | |
| <i class="fas fa-bars toggle-icon"></i> | |
| </div> | |
| <div class="panel-content"> | |
| <!-- Visualization Controls --> | |
| <div class="panel-section"> | |
| <div class="panel-header" onclick="togglePanel('visualizationPanel')"> | |
| <h3><i class="fas fa-brain"></i> Visualization Controls</h3> | |
| <i class="fas fa-chevron-right chevron" id="visualizationChevron"></i> | |
| </div> | |
| <div class="panel-body" id="visualizationPanel"> | |
| <div class="control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-wave-square"></i> | |
| <span>Animation Intensity</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" id="intensity" min="0" max="100" value="50"> | |
| <span class="value-display" id="intensityValue">50</span> | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-atom"></i> | |
| <span>Particle Count</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" id="particleCount" min="100" max="5000" value="2000" step="100"> | |
| <span class="value-display" id="particleCountValue">2000</span> | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-bolt"></i> | |
| <span>Energy Level</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" id="energy" min="0" max="100" value="30"> | |
| <span class="value-display" id="energyValue">30</span> | |
| </div> | |
| </div> | |
| <div class="preset-buttons"> | |
| <button class="preset-btn" data-preset="listening">🎤 Listening</button> | |
| <button class="preset-btn" data-preset="processing">⚡ Processing</button> | |
| <button class="preset-btn" data-preset="responding">💬 Responding</button> | |
| <button class="preset-btn" data-preset="exploring">🔍 Exploring</button> | |
| <button class="preset-btn" data-preset="teaching">📚 Teaching</button> | |
| <button class="preset-btn" data-preset="idle">🌀 Idle</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Voice Controls --> | |
| <div class="panel-section"> | |
| <div class="panel-header" onclick="togglePanel('voicePanel')"> | |
| <h3><i class="fas fa-robot"></i> Voice Synthesis</h3> | |
| <i class="fas fa-chevron-right chevron" id="voiceChevron"></i> | |
| </div> | |
| <div class="panel-body" id="voicePanel"> | |
| <div class="voice-control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-user-circle"></i> | |
| <span>Voice Selection</span> | |
| </div> | |
| <select class="voice-select" id="voiceSelect"> | |
| <option value="">Loading voices...</option> | |
| </select> | |
| </div> | |
| <div class="voice-control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-tachometer-alt"></i> | |
| <span>Speech Rate</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" class="voice-slider" id="rateSlider" min="0.5" max="2" step="0.1" value="1"> | |
| <span class="value-display" id="rateValue">1.0x</span> | |
| </div> | |
| </div> | |
| <div class="voice-control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-wave-square"></i> | |
| <span>Pitch Variation</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" class="voice-slider" id="pitchSlider" min="0.5" max="2" step="0.1" value="1"> | |
| <span class="value-display" id="pitchValue">1.0</span> | |
| </div> | |
| </div> | |
| <div class="voice-control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-volume-up"></i> | |
| <span>Volume Level</span> | |
| </div> | |
| <div class="slider-container"> | |
| <input type="range" class="voice-slider" id="volumeSlider" min="0.1" max="1" step="0.1" value="0.8"> | |
| <span class="value-display" id="volumeValue">80%</span> | |
| </div> | |
| </div> | |
| <div class="voice-control-buttons"> | |
| <button class="voice-btn" id="autoSpeakBtn" data-enabled="true"> | |
| <i class="fas fa-bullhorn"></i> | |
| Auto-Speak | |
| </button> | |
| <button class="voice-btn stop" id="stopBtn"> | |
| <i class="fas fa-stop"></i> | |
| Stop | |
| </button> | |
| </div> | |
| <button class="voice-preview-btn" id="previewBtn"> | |
| <i class="fas fa-play"></i> | |
| Preview Voice (Little Red Riding Hood) | |
| </button> | |
| <div class="voice-status" id="voiceStatus" style="display: none;"> | |
| <i class="fas fa-comment-dots"></i> | |
| <span id="statusMessage">Speaking...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- System Info --> | |
| <div class="panel-section"> | |
| <div class="panel-header" onclick="togglePanel('systemPanel')"> | |
| <h3><i class="fas fa-info-circle"></i> System Info</h3> | |
| <i class="fas fa-chevron-right chevron" id="systemChevron"></i> | |
| </div> | |
| <div class="panel-body" id="systemPanel"> | |
| <div class="control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-cube"></i> | |
| <span>3D Renderer</span> | |
| </div> | |
| <div style="color: #8892b0; font-size: 0.9em; padding: 10px 0;"> | |
| Three.js v128<br> | |
| WebGL 2.0 Enabled | |
| </div> | |
| </div> | |
| <div class="control-group"> | |
| <div class="control-label"> | |
| <i class="fas fa-microchip"></i> | |
| <span>Performance</span> | |
| </div> | |
| <div style="color: #8892b0; font-size: 0.9em; padding: 10px 0;"> | |
| <div id="fpsCounter">FPS: 60</div> | |
| <div id="particleCounter">Particles: 2000</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Visualization Canvas --> | |
| <div id="canvasContainer"> | |
| <canvas id="mainCanvas"></canvas> | |
| <!-- Status Bar --> | |
| <div class="status-bar"> | |
| <span class="pulse-indicator"></span> | |
| <span id="statusText">Neural AI Tutor Ready</span> | |
| </div> | |
| <!-- Centered Horizontal Chat Interface --> | |
| <div class="chat-overlay" id="chatOverlay"> | |
| <div class="chat-header"> | |
| <div class="chat-title"> | |
| <div class="ai-avatar"> | |
| <i class="fas fa-brain"></i> | |
| </div> | |
| <div> | |
| <h2>Neural AI Tutor</h2> | |
| <div class="chat-subtitle"> | |
| Interactive learning assistant with neural visualization | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-controls"> | |
| <button class="collapse-btn" id="collapseBtn" title="Collapse chat"> | |
| <i class="fas fa-chevron-down"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="chat-messages" id="chatMessages"> | |
| <div class="message message-ai"> | |
| <div class="message-content"> | |
| Welcome to the Neural AI Tutor! I'm your interactive learning assistant. | |
| I can help explain complex concepts, answer questions, and guide your learning journey. | |
| </div> | |
| <div class="message-time">Just now</div> | |
| </div> | |
| <div class="message message-ai"> | |
| <div class="message-content"> | |
| You can type your questions or use voice input. The neural visualization | |
| responds to our conversation, showing how knowledge connects in real-time. | |
| </div> | |
| <div class="message-time">Just now</div> | |
| </div> | |
| <div class="message message-ai"> | |
| <div class="message-content"> | |
| Try asking me about quantum computing, neural networks, blockchain, | |
| calculus, or any other subject you're curious about! | |
| </div> | |
| <div class="message-time">Just now</div> | |
| </div> | |
| <!-- Typing indicator (hidden by default) --> | |
| <div class="message-typing" id="typingIndicator" style="display: none;"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <span style="color: #a0b0ff; font-size: 0.9em; margin-left: 10px;">Thinking...</span> | |
| </div> | |
| </div> | |
| <div class="chat-input-container"> | |
| <div class="input-wrapper"> | |
| <textarea | |
| class="chat-input" | |
| id="chatInput" | |
| placeholder="Ask me anything about science, math, technology, or any subject..." | |
| rows="1" | |
| ></textarea> | |
| <div class="input-buttons"> | |
| <button class="voice-input-btn" id="voiceBtn" title="Start voice input"> | |
| <i class="fas fa-microphone"></i> | |
| </button> | |
| <button class="send-btn" id="sendBtn" title="Send message"> | |
| <i class="fas fa-paper-plane"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading Screen --> | |
| <div class="loading-screen" id="loadingScreen"> | |
| <div class="spinner"></div> | |
| <div id="loadingText">Initializing Neural AI Tutor...</div> | |
| </div> | |
| <!-- Error Screen --> | |
| <div class="error-screen" id="errorScreen"> | |
| <h2>⚠️ System Initialization Failed</h2> | |
| <p id="errorMessage">Unable to load 3D visualization engine. Please check your browser compatibility or try again.</p> | |
| <button class="retry-btn" id="retryBtn">Retry Initialization</button> | |
| </div> | |
| </div> | |
| <script> | |
| // Sidebar and Panel Management | |
| function toggleSidebar() { | |
| const sidebar = document.getElementById('sidebar'); | |
| sidebar.classList.toggle('expanded'); | |
| } | |
| function togglePanel(panelId) { | |
| const panel = document.getElementById(panelId); | |
| const chevron = document.getElementById(panelId.replace('Panel', 'Chevron')); | |
| panel.classList.toggle('active'); | |
| chevron.classList.toggle('rotated'); | |
| } | |
| // Chat Collapse Functionality | |
| let isChatCollapsed = false; | |
| const chatOverlay = document.getElementById('chatOverlay'); | |
| const collapseBtn = document.getElementById('collapseBtn'); | |
| collapseBtn.addEventListener('click', function(e) { | |
| e.stopPropagation(); | |
| toggleChat(); | |
| }); | |
| // Click on collapsed chat header to expand | |
| chatOverlay.addEventListener('click', function(e) { | |
| if (isChatCollapsed && e.target.closest('.chat-header')) { | |
| toggleChat(); | |
| } | |
| }); | |
| function toggleChat() { | |
| isChatCollapsed = !isChatCollapsed; | |
| chatOverlay.classList.toggle('collapsed'); | |
| const icon = collapseBtn.querySelector('i'); | |
| if (isChatCollapsed) { | |
| icon.className = 'fas fa-chevron-up'; | |
| collapseBtn.title = 'Expand chat'; | |
| } else { | |
| icon.className = 'fas fa-chevron-down'; | |
| collapseBtn.title = 'Collapse chat'; | |
| } | |
| } | |
| // Check if Three.js loaded properly | |
| function checkThreeJS() { | |
| if (typeof THREE === 'undefined') { | |
| console.error('Three.js failed to load'); | |
| showError('Three.js library failed to load. Please check your internet connection.'); | |
| return false; | |
| } | |
| return true; | |
| } | |
| function showError(message) { | |
| const errorScreen = document.getElementById('errorScreen'); | |
| const errorMessage = document.getElementById('errorMessage'); | |
| const loadingScreen = document.getElementById('loadingScreen'); | |
| loadingScreen.style.display = 'none'; | |
| errorMessage.textContent = message; | |
| errorScreen.style.display = 'flex'; | |
| } | |
| function hideLoading() { | |
| const loadingScreen = document.getElementById('loadingScreen'); | |
| loadingScreen.style.opacity = '0'; | |
| setTimeout(() => { | |
| loadingScreen.style.display = 'none'; | |
| }, 500); | |
| } | |
| function updateStatus(text) { | |
| document.getElementById('statusText').textContent = text; | |
| document.getElementById('loadingText').textContent = text; | |
| } | |
| // Voice Synthesis System (unchanged from previous version) | |
| class VoiceSynthesis { | |
| constructor() { | |
| this.synth = window.speechSynthesis; | |
| this.voices = []; | |
| this.currentVoice = null; | |
| this.isAutoSpeak = true; | |
| this.isSpeaking = false; | |
| this.currentUtterance = null; | |
| this.settings = { | |
| rate: 1.0, | |
| pitch: 1.0, | |
| volume: 0.8 | |
| }; | |
| // Little Red Riding Hood story for voice preview | |
| this.testScript = "Once upon a time, there was a little girl who lived in a village near the forest. " + | |
| "Whenever she went out, the little girl wore a red riding cloak, so everyone in the village called her Little Red Riding Hood. " + | |
| "One morning, Little Red Riding Hood asked her mother if she could go to visit her grandmother as it had been awhile since they'd seen each other. " + | |
| "That's a wonderful idea, her mother said. So they packed a nice basket for Little Red Riding Hood to take to her grandmother. " + | |
| "When the basket was ready, the little girl put on her red cloak and kissed her mother goodbye. " + | |
| "Remember, go straight to Grandma's house, her mother cautioned. Don't dawdle along the way and please don't talk to strangers! " + | |
| "The woods are dangerous. I will be careful, Little Red Riding Hood promised her mother. " + | |
| "The grandmother lived out in the woods, a half hour from the village. When Little Red Riding Hood entered the woods a wolf came up to her. " + | |
| "She did not know what a wicked animal he was, and was not afraid of him. Good day, Little Red Riding Hood, said the wolf. " + | |
| "Thank you kindly, wolf, she replied. Where are you going so early, Little Red Riding Hood? " + | |
| "To my grandmother's house. What do you have there in your basket? " + | |
| "Cake and wine. Yesterday was baking day, so poor sick grandmother is to have something good, to make her stronger. " + | |
| "Where does your grandmother live, Little Red Riding Hood? A good quarter of an hour farther in the woods. " + | |
| "Her house is under the three large oak trees. You can tell by the nut trees which are below. " + | |
| "The wolf thought to himself, What a tender young creature! What a nice plump mouthful! She will be better to eat than the old woman. " + | |
| "I must act craftily, so as to catch both. So he walked for a short time by Little Red Riding Hood's side, and then he said, " + | |
| "See, Little Red Riding Hood, how pretty the flowers are about here! Why do you not look around? " + | |
| "I believe, too, that you do not hear how sweetly the little birds are singing. " + | |
| "You walk gravely along as if you were going to school, while everything else out here in the wood is merry. " + | |
| "Little Red Riding Hood raised her eyes, and when she saw the sunbeams dancing here and there through the trees, and pretty flowers growing everywhere, " + | |
| "she thought, Suppose I take grandmother a fresh nosegay. That would please her too. It is so early in the day that I shall still get there in good time. " + | |
| "And so she ran from the path into the woods to look for flowers. And whenever she had picked one, she fancied that she saw a still prettier one farther on, " + | |
| "and ran after it, and so got deeper and deeper into the woods."; | |
| this.initVoices(); | |
| this.setupEventListeners(); | |
| } | |
| initVoices() { | |
| // Wait for voices to be loaded | |
| const loadVoices = () => { | |
| this.voices = this.synth.getVoices(); | |
| this.populateVoiceList(); | |
| // Select a high-quality natural-sounding English voice | |
| this.selectBestVoice(); | |
| }; | |
| if (this.synth.onvoiceschanged !== undefined) { | |
| this.synth.onvoiceschanged = loadVoices; | |
| } | |
| // Load immediately if voices are already available | |
| if (this.synth.getVoices().length > 0) { | |
| loadVoices(); | |
| } | |
| } | |
| selectBestVoice() { | |
| // Filter for English voices only | |
| const englishVoices = this.voices.filter(voice => | |
| voice.lang.startsWith('en-') | |
| ); | |
| if (englishVoices.length === 0) { | |
| console.warn('No English voices found'); | |
| return; | |
| } | |
| // Prioritize natural-sounding voices | |
| const preferredVoices = [ | |
| 'Google UK English Male', | |
| 'Google US English', | |
| 'Microsoft David - English (United States)', | |
| 'Microsoft Zira - English (United States)', | |
| 'Alex', | |
| 'Samantha', | |
| 'Daniel', | |
| 'Karen', | |
| 'Moira', | |
| 'Tessa' | |
| ]; | |
| // Try to find a preferred voice | |
| for (const voiceName of preferredVoices) { | |
| const voice = englishVoices.find(v => v.name.includes(voiceName)); | |
| if (voice) { | |
| this.currentVoice = voice; | |
| this.updateVoiceSelect(voice); | |
| this.showStatus(`Selected: ${voice.name}`, 3000); | |
| break; | |
| } | |
| } | |
| // Fallback to first English voice | |
| if (!this.currentVoice && englishVoices.length > 0) { | |
| this.currentVoice = englishVoices[0]; | |
| this.updateVoiceSelect(englishVoices[0]); | |
| this.showStatus(`Selected: ${englishVoices[0].name}`, 3000); | |
| } | |
| } | |
| populateVoiceList() { | |
| const voiceSelect = document.getElementById('voiceSelect'); | |
| voiceSelect.innerHTML = ''; | |
| // Filter for English voices only | |
| const englishVoices = this.voices.filter(voice => | |
| voice.lang.startsWith('en-') | |
| ); | |
| if (englishVoices.length === 0) { | |
| const option = document.createElement('option'); | |
| option.textContent = 'No English voices found'; | |
| option.disabled = true; | |
| voiceSelect.appendChild(option); | |
| return; | |
| } | |
| // Sort English voices by dialect preference | |
| const sortedVoices = englishVoices.sort((a, b) => { | |
| const langOrder = ['en-US', 'en-GB', 'en-AU', 'en-CA', 'en-IN']; | |
| const indexA = langOrder.indexOf(a.lang); | |
| const indexB = langOrder.indexOf(b.lang); | |
| if (indexA !== -1 && indexB !== -1) return indexA - indexB; | |
| if (indexA !== -1) return -1; | |
| if (indexB !== -1) return 1; | |
| return a.name.localeCompare(b.name); | |
| }); | |
| // Add voices to select | |
| sortedVoices.forEach(voice => { | |
| const option = document.createElement('option'); | |
| option.value = voice.name; | |
| option.textContent = `${voice.name} (${this.getDialectName(voice.lang)})`; | |
| option.dataset.lang = voice.lang; | |
| voiceSelect.appendChild(option); | |
| }); | |
| } | |
| getDialectName(langCode) { | |
| const dialects = { | |
| 'en-US': 'US English', | |
| 'en-GB': 'UK English', | |
| 'en-AU': 'Australian English', | |
| 'en-CA': 'Canadian English', | |
| 'en-IN': 'Indian English', | |
| 'en-IE': 'Irish English', | |
| 'en-ZA': 'South African English', | |
| 'en-NZ': 'New Zealand English' | |
| }; | |
| return dialects[langCode] || langCode; | |
| } | |
| updateVoiceSelect(voice) { | |
| const voiceSelect = document.getElementById('voiceSelect'); | |
| const options = voiceSelect.querySelectorAll('option'); | |
| options.forEach(option => { | |
| if (option.value === voice.name) { | |
| option.selected = true; | |
| } | |
| }); | |
| } | |
| setupEventListeners() { | |
| // Voice selection | |
| document.getElementById('voiceSelect').addEventListener('change', (e) => { | |
| const selectedVoice = this.voices.find(v => v.name === e.target.value); | |
| if (selectedVoice) { | |
| this.currentVoice = selectedVoice; | |
| this.showStatus(`Voice changed to: ${selectedVoice.name}`, 2000); | |
| } | |
| }); | |
| // Rate slider | |
| document.getElementById('rateSlider').addEventListener('input', (e) => { | |
| const value = parseFloat(e.target.value); | |
| this.settings.rate = value; | |
| document.getElementById('rateValue').textContent = value.toFixed(1) + 'x'; | |
| }); | |
| // Pitch slider | |
| document.getElementById('pitchSlider').addEventListener('input', (e) => { | |
| const value = parseFloat(e.target.value); | |
| this.settings.pitch = value; | |
| document.getElementById('pitchValue').textContent = value.toFixed(1); | |
| }); | |
| // Volume slider | |
| document.getElementById('volumeSlider').addEventListener('input', (e) => { | |
| const value = parseFloat(e.target.value); | |
| this.settings.volume = value; | |
| document.getElementById('volumeValue').textContent = Math.round(value * 100) + '%'; | |
| }); | |
| // Auto-speak toggle | |
| document.getElementById('autoSpeakBtn').addEventListener('click', (e) => { | |
| this.isAutoSpeak = !this.isAutoSpeak; | |
| const btn = e.target.closest('.voice-btn'); | |
| btn.dataset.enabled = this.isAutoSpeak; | |
| btn.innerHTML = this.isAutoSpeak | |
| ? '<i class="fas fa-bullhorn"></i> Auto-Speak' | |
| : '<i class="fas fa-volume-mute"></i> Auto-Speak'; | |
| btn.classList.toggle('active', this.isAutoSpeak); | |
| // Show status message | |
| this.showStatus(this.isAutoSpeak ? 'Auto-speak enabled' : 'Auto-speak disabled', 2000); | |
| }); | |
| // Stop button - immediately stops all speech | |
| document.getElementById('stopBtn').addEventListener('click', () => { | |
| this.stopAllSpeech(); | |
| }); | |
| // Preview button - reads Little Red Riding Hood | |
| document.getElementById('previewBtn').addEventListener('click', () => { | |
| this.previewCurrentVoice(); | |
| }); | |
| // Update status displays when speaking | |
| this.synth.onstart = () => { | |
| this.isSpeaking = true; | |
| document.getElementById('stopBtn').classList.add('active'); | |
| this.showStatus('Speaking...'); | |
| // Update 3D visualization | |
| if (window.visualization) { | |
| window.visualization.setPreset('teaching'); | |
| } | |
| }; | |
| this.synth.onend = () => { | |
| this.isSpeaking = false; | |
| document.getElementById('stopBtn').classList.remove('active'); | |
| this.hideStatus(); | |
| // Update 3D visualization | |
| if (window.visualization) { | |
| setTimeout(() => { | |
| window.visualization.setPreset('idle'); | |
| }, 1000); | |
| } | |
| }; | |
| } | |
| speak(text) { | |
| if (!this.isAutoSpeak || !text || this.isSpeaking) return; | |
| this.stopAllSpeech(); | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| if (this.currentVoice) { | |
| utterance.voice = this.currentVoice; | |
| } | |
| utterance.rate = this.settings.rate; | |
| utterance.pitch = this.settings.pitch; | |
| utterance.volume = this.settings.volume; | |
| // Event listeners for this utterance | |
| utterance.onerror = (event) => { | |
| console.error('Speech synthesis error:', event); | |
| this.hideStatus(); | |
| }; | |
| this.currentUtterance = utterance; | |
| this.synth.speak(utterance); | |
| } | |
| // Stops ALL speech immediately | |
| stopAllSpeech() { | |
| if (this.synth.speaking) { | |
| this.synth.cancel(); // This stops ALL utterances immediately | |
| this.isSpeaking = false; | |
| this.currentUtterance = null; | |
| document.getElementById('stopBtn').classList.remove('active'); | |
| this.hideStatus(); | |
| } | |
| } | |
| previewCurrentVoice() { | |
| this.stopAllSpeech(); | |
| this.speak(this.testScript); | |
| this.showStatus('Reading: Little Red Riding Hood', 3000); | |
| } | |
| showStatus(message, duration = null) { | |
| const statusEl = document.getElementById('voiceStatus'); | |
| const messageEl = document.getElementById('statusMessage'); | |
| messageEl.textContent = message; | |
| statusEl.style.display = 'flex'; | |
| if (duration) { | |
| setTimeout(() => { | |
| this.hideStatus(); | |
| }, duration); | |
| } | |
| } | |
| hideStatus() { | |
| const statusEl = document.getElementById('voiceStatus'); | |
| statusEl.style.display = 'none'; | |
| } | |
| updateSettingsFromUI() { | |
| this.settings.rate = parseFloat(document.getElementById('rateSlider').value); | |
| this.settings.pitch = parseFloat(document.getElementById('pitchSlider').value); | |
| this.settings.volume = parseFloat(document.getElementById('volumeSlider').value); | |
| } | |
| } | |
| // Chat Interface Functionality (updated for horizontal layout) | |
| class ChatInterface { | |
| constructor() { | |
| this.isListening = false; | |
| this.initChat(); | |
| } | |
| initChat() { | |
| this.chatInput = document.getElementById('chatInput'); | |
| this.chatMessages = document.getElementById('chatMessages'); | |
| this.voiceBtn = document.getElementById('voiceBtn'); | |
| this.sendBtn = document.getElementById('sendBtn'); | |
| this.typingIndicator = document.getElementById('typingIndicator'); | |
| this.setupEventListeners(); | |
| this.autoResizeTextarea(); | |
| } | |
| setupEventListeners() { | |
| // Send button click | |
| this.sendBtn.addEventListener('click', () => this.sendMessage()); | |
| // Enter key to send (Shift+Enter for new line) | |
| this.chatInput.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && !e.shiftKey) { | |
| e.preventDefault(); | |
| this.sendMessage(); | |
| } | |
| }); | |
| // Voice button click | |
| this.voiceBtn.addEventListener('click', () => this.toggleVoiceInput()); | |
| // Auto-resize textarea | |
| this.chatInput.addEventListener('input', () => this.autoResizeTextarea()); | |
| } | |
| autoResizeTextarea() { | |
| const textarea = this.chatInput; | |
| textarea.style.height = 'auto'; | |
| const newHeight = Math.min(textarea.scrollHeight, 100); | |
| textarea.style.height = newHeight + 'px'; | |
| } | |
| toggleVoiceInput() { | |
| this.isListening = !this.isListening; | |
| if (this.isListening) { | |
| // Start "listening" state | |
| this.voiceBtn.classList.add('listening'); | |
| this.voiceBtn.innerHTML = '<i class="fas fa-stop"></i>'; | |
| // Update 3D visualization to listening mode | |
| if (window.visualization) { | |
| window.visualization.setPreset('listening'); | |
| } | |
| // Simulate voice input (in a real app, this would capture actual audio) | |
| setTimeout(() => { | |
| this.simulateVoiceInput(); | |
| }, 1500); | |
| } else { | |
| // Stop "listening" state | |
| this.voiceBtn.classList.remove('listening'); | |
| this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>'; | |
| // Update 3D visualization to processing mode | |
| if (window.visualization) { | |
| window.visualization.setPreset('processing'); | |
| } | |
| } | |
| } | |
| simulateVoiceInput() { | |
| if (!this.isListening) return; | |
| // Simulate capturing voice input | |
| const simulatedQuestions = [ | |
| "Can you explain quantum computing in simple terms?", | |
| "How do neural networks actually learn from data?", | |
| "What's the difference between artificial intelligence and machine learning?", | |
| "Could you help me understand how blockchain technology works?", | |
| "Explain the concept of derivatives in calculus for beginners" | |
| ]; | |
| const randomQuestion = simulatedQuestions[Math.floor(Math.random() * simulatedQuestions.length)]; | |
| // Add user message | |
| this.addMessage(randomQuestion, 'user'); | |
| // Process and respond | |
| this.processAIResponse(randomQuestion); | |
| // Stop listening after receiving input | |
| setTimeout(() => { | |
| this.isListening = false; | |
| this.voiceBtn.classList.remove('listening'); | |
| this.voiceBtn.innerHTML = '<i class="fas fa-microphone"></i>'; | |
| }, 500); | |
| } | |
| sendMessage() { | |
| const message = this.chatInput.value.trim(); | |
| if (message === '') return; | |
| // Add user message | |
| this.addMessage(message, 'user'); | |
| // Clear input | |
| this.chatInput.value = ''; | |
| this.autoResizeTextarea(); | |
| // Process and respond | |
| this.processAIResponse(message); | |
| } | |
| addMessage(content, sender) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `message message-${sender}`; | |
| const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | |
| messageDiv.innerHTML = ` | |
| <div class="message-content">${this.escapeHtml(content)}</div> | |
| <div class="message-time">${time}</div> | |
| `; | |
| this.chatMessages.appendChild(messageDiv); | |
| // Scroll to right to show latest message | |
| this.chatMessages.scrollLeft = this.chatMessages.scrollWidth; | |
| // Update 3D visualization | |
| if (window.visualization) { | |
| if (sender === 'user') { | |
| window.visualization.setPreset('processing'); | |
| } | |
| } | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| processAIResponse(userMessage) { | |
| // Show typing indicator | |
| this.showTypingIndicator(); | |
| // Update 3D visualization | |
| if (window.visualization) { | |
| window.visualization.setPreset('processing'); | |
| } | |
| // Simulate AI processing time | |
| const processingTime = 1000 + Math.random() * 2000; | |
| setTimeout(() => { | |
| this.hideTypingIndicator(); | |
| // Generate AI response | |
| const response = this.generateAIResponse(userMessage); | |
| // Add AI response | |
| this.addMessage(response, 'ai'); | |
| // Speak the response if voice synthesis is available | |
| if (window.voiceSynthesis && window.voiceSynthesis.isAutoSpeak) { | |
| setTimeout(() => { | |
| window.voiceSynthesis.speak(response); | |
| }, 300); | |
| } | |
| // Update 3D visualization | |
| if (window.visualization) { | |
| window.visualization.setPreset('responding'); | |
| } | |
| // Return to idle after a delay | |
| setTimeout(() => { | |
| if (window.visualization) { | |
| window.visualization.setPreset('idle'); | |
| } | |
| }, 3000); | |
| }, processingTime); | |
| } | |
| showTypingIndicator() { | |
| this.typingIndicator.style.display = 'flex'; | |
| this.chatMessages.scrollLeft = this.chatMessages.scrollWidth; | |
| } | |
| hideTypingIndicator() { | |
| this.typingIndicator.style.display = 'none'; | |
| } | |
| generateAIResponse(userMessage) { | |
| // Enhanced AI response generator with educational content | |
| const responses = { | |
| quantum: "Quantum computing uses quantum bits or 'qubits' that can exist in multiple states simultaneously (superposition). This allows quantum computers to process vast amounts of data in parallel. Unlike classical bits (0 or 1), qubits can be 0, 1, or both at the same time.", | |
| neural: "Neural networks learn through backpropagation with gradient descent. They consist of layers of interconnected 'neurons' that process information. During training, the network adjusts connection weights to minimize prediction errors.", | |
| ai: "Artificial Intelligence (AI) is the broader concept of machines being able to carry out tasks in a way we would consider 'smart'. Machine Learning (ML) is a subset of AI where machines learn from data without explicit programming.", | |
| blockchain: "Blockchain is a distributed, decentralized digital ledger that records transactions across many computers. Each 'block' contains a cryptographic hash of the previous block, creating an immutable chain.", | |
| calculus: "Derivatives measure instantaneous rate of change - essentially the slope of a curve at a specific point. If f(x) is your function, the derivative f'(x) tells you how fast f(x) is changing at any x.", | |
| default: "I understand you're asking about " + userMessage.substring(0, 40) + "... This is an interesting topic! Let me provide a clear explanation with key concepts and real-world applications." | |
| }; | |
| const lowerMessage = userMessage.toLowerCase(); | |
| if (lowerMessage.includes('quantum')) return responses.quantum; | |
| if (lowerMessage.includes('neural') || lowerMessage.includes('network')) return responses.neural; | |
| if (lowerMessage.includes('artificial intelligence') || lowerMessage.includes(' ai ') || lowerMessage.includes('machine learning')) return responses.ai; | |
| if (lowerMessage.includes('blockchain')) return responses.blockchain; | |
| if (lowerMessage.includes('calculus') || lowerMessage.includes('derivative') || lowerMessage.includes('differentiation')) return responses.calculus; | |
| return responses.default; | |
| } | |
| } | |
| // Main Visualization Class (unchanged from previous version) | |
| class AITutorVisualization { | |
| constructor() { | |
| if (!checkThreeJS()) return; | |
| this.scene = null; | |
| this.camera = null; | |
| this.renderer = null; | |
| this.particles = null; | |
| this.controls = null; | |
| this.params = { | |
| intensity: 0.5, | |
| particleCount: 2000, | |
| energy: 0.3, | |
| mode: 'idle' | |
| }; | |
| this.audioData = new Array(32).fill(0); | |
| this.time = 0; | |
| this.animationFrameId = null; | |
| try { | |
| this.init(); | |
| this.createParticles(); | |
| this.setupControls(); | |
| this.animate(); | |
| updateStatus('Neural AI System Ready'); | |
| setTimeout(hideLoading, 500); | |
| } catch (error) { | |
| console.error('Initialization error:', error); | |
| showError('Failed to initialize 3D visualization: ' + error.message); | |
| } | |
| } | |
| init() { | |
| updateStatus('Initializing neural network visualization...'); | |
| // Scene | |
| this.scene = new THREE.Scene(); | |
| this.scene.background = new THREE.Color(0x0a0a0f); | |
| // Camera | |
| this.camera = new THREE.PerspectiveCamera( | |
| 75, | |
| window.innerWidth / window.innerHeight, | |
| 0.1, | |
| 1000 | |
| ); | |
| this.camera.position.z = 5; | |
| // Renderer | |
| const canvas = document.getElementById('mainCanvas'); | |
| this.renderer = new THREE.WebGLRenderer({ | |
| canvas: canvas, | |
| antialias: true, | |
| alpha: true | |
| }); | |
| this.renderer.setSize(window.innerWidth, window.innerHeight); | |
| this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); | |
| // Add fog | |
| this.scene.fog = new THREE.Fog(0x0a0a0f, 10, 25); | |
| // Add lights | |
| const ambientLight = new THREE.AmbientLight(0x222244, 0.5); | |
| this.scene.add(ambientLight); | |
| const directionalLight = new THREE.DirectionalLight(0x5a6cff, 1); | |
| directionalLight.position.set(5, 3, 5); | |
| this.scene.add(directionalLight); | |
| // Orbit controls | |
| updateStatus('Configuring interaction controls...'); | |
| this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement); | |
| this.controls.enableDamping = true; | |
| this.controls.dampingFactor = 0.05; | |
| } | |
| createParticles() { | |
| updateStatus('Generating neural particles...'); | |
| const geometry = new THREE.BufferGeometry(); | |
| const positions = new Float32Array(this.params.particleCount * 3); | |
| const colors = new Float32Array(this.params.particleCount * 3); | |
| for (let i = 0; i < this.params.particleCount; i++) { | |
| const i3 = i * 3; | |
| // Fibonacci sphere distribution | |
| const phi = Math.acos(-1 + (2 * i) / this.params.particleCount); | |
| const theta = Math.sqrt(this.params.particleCount * Math.PI) * phi; | |
| const x = Math.cos(theta) * Math.sin(phi); | |
| const y = Math.sin(theta) * Math.sin(phi); | |
| const z = Math.cos(phi); | |
| positions[i3] = x; | |
| positions[i3 + 1] = y; | |
| positions[i3 + 2] = z; | |
| // Color gradient based on position | |
| colors[i3] = 0.5 + x * 0.5; | |
| colors[i3 + 1] = 0.3 + y * 0.7; | |
| colors[i3 + 2] = 0.8 + z * 0.2; | |
| } | |
| geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); | |
| geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); | |
| const material = new THREE.PointsMaterial({ | |
| size: 0.03, | |
| vertexColors: true, | |
| transparent: true, | |
| opacity: 0.8, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| this.particles = new THREE.Points(geometry, material); | |
| this.scene.add(this.particles); | |
| // Add central neural core | |
| const coreGeometry = new THREE.SphereGeometry(0.5, 32, 32); | |
| const coreMaterial = new THREE.MeshBasicMaterial({ | |
| color: 0x5a6cff, | |
| wireframe: true, | |
| transparent: true, | |
| opacity: 0.1 | |
| }); | |
| const core = new THREE.Mesh(coreGeometry, coreMaterial); | |
| this.scene.add(core); | |
| } | |
| setupControls() { | |
| // Slider controls | |
| const intensitySlider = document.getElementById('intensity'); | |
| const particleCountSlider = document.getElementById('particleCount'); | |
| const energySlider = document.getElementById('energy'); | |
| const intensityValue = document.getElementById('intensityValue'); | |
| const particleCountValue = document.getElementById('particleCountValue'); | |
| const energyValue = document.getElementById('energyValue'); | |
| intensitySlider.addEventListener('input', (e) => { | |
| this.params.intensity = e.target.value / 100; | |
| intensityValue.textContent = e.target.value; | |
| }); | |
| particleCountSlider.addEventListener('input', (e) => { | |
| this.params.particleCount = parseInt(e.target.value); | |
| particleCountValue.textContent = e.target.value; | |
| this.updateParticleCount(); | |
| }); | |
| energySlider.addEventListener('input', (e) => { | |
| this.params.energy = e.target.value / 100; | |
| energyValue.textContent = e.target.value; | |
| }); | |
| // Preset buttons | |
| document.querySelectorAll('.preset-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| const preset = e.target.dataset.preset; | |
| this.setPreset(preset); | |
| }); | |
| }); | |
| // Retry button | |
| document.getElementById('retryBtn').addEventListener('click', () => { | |
| location.reload(); | |
| }); | |
| } | |
| setPreset(preset) { | |
| this.params.mode = preset; | |
| // Update UI to show active preset | |
| document.querySelectorAll('.preset-btn').forEach(btn => { | |
| btn.classList.toggle('active', btn.dataset.preset === preset); | |
| }); | |
| // Update status | |
| const statusMap = { | |
| 'listening': '🎤 Listening for input...', | |
| 'processing': '⚡ Processing thoughts...', | |
| 'responding': '💬 Formulating response...', | |
| 'exploring': '🔍 Exploring concepts...', | |
| 'teaching': '📚 Teaching mode active', | |
| 'idle': '🌀 Neural system idle' | |
| }; | |
| if (statusMap[preset]) { | |
| updateStatus(statusMap[preset]); | |
| } | |
| } | |
| updateParticleCount() { | |
| if (this.particles) { | |
| this.scene.remove(this.particles); | |
| } | |
| this.createParticles(); | |
| } | |
| simulateAudioData() { | |
| // Simulate audio data for visualization | |
| const time = performance.now() * 0.001; | |
| const frequency = 0.5 + this.params.energy * 2; | |
| for (let i = 0; i < this.audioData.length; i++) { | |
| const base = Math.sin(time * frequency + i * 0.3) * 0.5 + 0.5; | |
| const variation = Math.sin(time * 2 + i * 0.5) * 0.2; | |
| this.audioData[i] = base + variation * this.params.intensity; | |
| } | |
| } | |
| updateParticles() { | |
| if (!this.particles) return; | |
| const positions = this.particles.geometry.attributes.position.array; | |
| const originalPositions = this.particles.geometry.attributes.originalPosition; | |
| const colors = this.particles.geometry.attributes.color.array; | |
| // Store original positions if not already stored | |
| if (!originalPositions) { | |
| const origPos = new Float32Array(positions.length); | |
| origPos.set(positions); | |
| this.particles.geometry.setAttribute('originalPosition', new THREE.BufferAttribute(origPos, 3)); | |
| } | |
| const origPositions = this.particles.geometry.attributes.originalPosition.array; | |
| const time = this.time; | |
| for (let i = 0; i < this.params.particleCount; i++) { | |
| const i3 = i * 3; | |
| const x = origPositions[i3]; | |
| const y = origPositions[i3 + 1]; | |
| const z = origPositions[i3 + 2]; | |
| let radius = 2.0; | |
| let intensity = this.params.intensity; | |
| // Apply different behaviors based on mode | |
| switch(this.params.mode) { | |
| case 'listening': | |
| radius += Math.sin(time * 3 + i * 0.01) * 0.5 * intensity; | |
| break; | |
| case 'processing': | |
| radius += Math.sin(time * 5 + i * 0.02) * 0.6 * intensity; | |
| break; | |
| case 'responding': | |
| radius += (Math.sin(time * 2 + i * 0.005) + 1) * 0.4 * intensity; | |
| break; | |
| case 'exploring': | |
| radius += Math.sin(time * 1.5 + i * 0.015) * 0.7 * intensity; | |
| break; | |
| case 'teaching': | |
| radius += Math.sin(time * 4 + i * 0.008) * 0.55 * intensity; | |
| break; | |
| default: // idle | |
| radius += Math.sin(time * 0.5 + i * 0.01) * 0.2 * intensity; | |
| } | |
| // Add energy-based pulsation | |
| radius += Math.sin(time * 8) * 0.2 * this.params.energy; | |
| // Add audio data influence | |
| const audioIndex = Math.floor((i / this.params.particleCount) * this.audioData.length); | |
| radius += this.audioData[audioIndex] * 0.5 * intensity; | |
| positions[i3] = x * radius; | |
| positions[i3 + 1] = y * radius; | |
| positions[i3 + 2] = z * radius; | |
| // Update colors based on mode | |
| const colorShift = Math.sin(time * 0.5 + i * 0.001) * 0.1; | |
| colors[i3] = 0.5 + x * 0.5 + colorShift; | |
| colors[i3 + 1] = 0.3 + y * 0.7 + colorShift; | |
| colors[i3 + 2] = 0.8 + z * 0.2 + colorShift; | |
| } | |
| this.particles.geometry.attributes.position.needsUpdate = true; | |
| this.particles.geometry.attributes.color.needsUpdate = true; | |
| // Rotate particles slowly | |
| this.particles.rotation.y = time * 0.1; | |
| this.particles.rotation.x = Math.sin(time * 0.05) * 0.1; | |
| } | |
| animate() { | |
| this.animationFrameId = requestAnimationFrame(() => this.animate()); | |
| this.time += 0.016; | |
| this.simulateAudioData(); | |
| this.updateParticles(); | |
| this.controls.update(); | |
| this.renderer.render(this.scene, this.camera); | |
| } | |
| onWindowResize() { | |
| this.camera.aspect = window.innerWidth / window.innerHeight; | |
| this.camera.updateProjectionMatrix(); | |
| this.renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| destroy() { | |
| if (this.animationFrameId) { | |
| cancelAnimationFrame(this.animationFrameId); | |
| } | |
| if (this.controls) { | |
| this.controls.dispose(); | |
| } | |
| if (this.renderer) { | |
| this.renderer.dispose(); | |
| } | |
| } | |
| } | |
| // Initialize when page loads | |
| window.addEventListener('load', () => { | |
| updateStatus('Loading Neural AI Tutor...'); | |
| // Auto-expand sidebar on hover | |
| const sidebar = document.getElementById('sidebar'); | |
| sidebar.addEventListener('mouseenter', () => { | |
| sidebar.classList.add('expanded'); | |
| }); | |
| sidebar.addEventListener('mouseleave', () => { | |
| sidebar.classList.remove('expanded'); | |
| }); | |
| // Wait a moment for libraries to load | |
| setTimeout(() => { | |
| // Initialize 3D visualization | |
| window.visualization = new AITutorVisualization(); | |
| // Initialize chat interface | |
| window.chatInterface = new ChatInterface(); | |
| // Initialize voice synthesis | |
| if ('speechSynthesis' in window) { | |
| window.voiceSynthesis = new VoiceSynthesis(); | |
| updateStatus('Voice synthesis initialized'); | |
| } else { | |
| console.warn('Speech synthesis not supported in this browser'); | |
| document.getElementById('voiceStatus').style.display = 'flex'; | |
| document.getElementById('statusMessage').textContent = 'Voice synthesis not supported'; | |
| document.getElementById('statusMessage').style.color = '#ff5a5a'; | |
| } | |
| if (window.visualization && window.visualization.scene) { | |
| // Handle window resize | |
| window.addEventListener('resize', () => window.visualization.onWindowResize()); | |
| // Handle key controls | |
| window.addEventListener('keydown', (e) => { | |
| if (e.code === 'Space') { | |
| window.visualization.setPreset( | |
| window.visualization.params.mode === 'idle' ? 'listening' : 'idle' | |
| ); | |
| } | |
| // Ctrl+Space to stop speech | |
| if (e.code === 'Space' && e.ctrlKey && window.voiceSynthesis) { | |
| window.voiceSynthesis.stopAllSpeech(); | |
| } | |
| // Escape key closes all panels | |
| if (e.code === 'Escape') { | |
| sidebar.classList.remove('expanded'); | |
| } | |
| }); | |
| // Set initial preset | |
| window.visualization.setPreset('idle'); | |
| // Auto-open visualization panel | |
| setTimeout(() => { | |
| togglePanel('visualizationPanel'); | |
| }, 1000); | |
| } | |
| }, 100); | |
| }); | |
| // Handle page unload | |
| window.addEventListener('beforeunload', () => { | |
| if (window.visualization) { | |
| window.visualization.destroy(); | |
| } | |
| if (window.voiceSynthesis) { | |
| window.voiceSynthesis.stopAllSpeech(); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |