Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>Eidolon Cognitive Tutor - Learn Anything, Your Way</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| color: #1a202c; | |
| } | |
| .container { max-width: 1200px; margin: 0 auto; } | |
| .header { text-align: center; margin-bottom: 40px; color: white; } | |
| .header h1 { font-size: 42px; font-weight: 700; margin-bottom: 12px; text-shadow: 0 2px 10px rgba(0,0,0,0.2); } | |
| .header .tagline { font-size: 18px; opacity: 0.95; } | |
| .stats-bar { | |
| display: flex; | |
| gap: 20px; | |
| justify-content: center; | |
| margin-bottom: 30px; | |
| flex-wrap: wrap; | |
| } | |
| .stat-card { | |
| background: rgba(255,255,255,0.2); | |
| backdrop-filter: blur(10px); | |
| padding: 15px 25px; | |
| border-radius: 12px; | |
| color: white; | |
| border: 1px solid rgba(255,255,255,0.3); | |
| } | |
| .stat-value { font-size: 24px; font-weight: 700; } | |
| .stat-label { font-size: 13px; opacity: 0.9; margin-top: 4px; } | |
| .main-card { | |
| background: white; | |
| border-radius: 20px; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.3); | |
| overflow: hidden; | |
| } | |
| .mode-selector { | |
| display: flex; | |
| gap: 10px; | |
| padding: 20px; | |
| background: linear-gradient(to right, #f7fafc, #edf2f7); | |
| border-bottom: 2px solid #e2e8f0; | |
| overflow-x: auto; | |
| } | |
| .mode-btn { | |
| padding: 12px 20px; | |
| border: 2px solid #cbd5e1; | |
| background: white; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 14px; | |
| font-weight: 600; | |
| white-space: nowrap; | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .mode-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); } | |
| .mode-btn.active { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border-color: #667eea; | |
| } | |
| .controls-panel { | |
| padding: 20px; | |
| background: #f8fafc; | |
| border-bottom: 1px solid #e2e8f0; | |
| } | |
| .control-group { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
| gap: 20px; | |
| } | |
| .control-item label { | |
| display: block; | |
| font-size: 13px; | |
| font-weight: 600; | |
| color: #475569; | |
| margin-bottom: 8px; | |
| } | |
| .persona-selector { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| } | |
| .persona-btn { | |
| padding: 8px 16px; | |
| border: 2px solid #e2e8f0; | |
| background: white; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 13px; | |
| transition: all 0.2s; | |
| } | |
| .persona-btn:hover { border-color: #cbd5e1; } | |
| .persona-btn.active { background: #667eea; color: white; border-color: #667eea; } | |
| .slider-container { position: relative; } | |
| .slider { | |
| width: 100%; | |
| height: 8px; | |
| border-radius: 5px; | |
| background: linear-gradient(to right, #48bb78, #f6ad55, #f56565); | |
| outline: none; | |
| -webkit-appearance: none; | |
| } | |
| .slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 50%; | |
| background: white; | |
| cursor: pointer; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.2); | |
| border: 3px solid #667eea; | |
| } | |
| .difficulty-labels { | |
| display: flex; | |
| justify-content: space-between; | |
| font-size: 11px; | |
| color: #64748b; | |
| margin-top: 6px; | |
| } | |
| .chat-area { | |
| padding: 30px; | |
| min-height: 400px; | |
| } | |
| .prompt-enhance { | |
| background: #fef3c7; | |
| padding: 12px; | |
| border-radius: 8px; | |
| margin-bottom: 16px; | |
| font-size: 13px; | |
| border-left: 4px solid #f59e0b; | |
| display: none; | |
| } | |
| .prompt-enhance.show { display: block; } | |
| textarea { | |
| width: 100%; | |
| min-height: 120px; | |
| padding: 16px; | |
| border: 2px solid #e2e8f0; | |
| border-radius: 12px; | |
| font-size: 15px; | |
| font-family: inherit; | |
| resize: vertical; | |
| transition: border 0.2s; | |
| } | |
| textarea:focus { outline: none; border-color: #667eea; } | |
| .action-bar { | |
| display: flex; | |
| gap: 12px; | |
| margin-top: 16px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| border: none; | |
| padding: 14px 28px; | |
| border-radius: 10px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| font-size: 15px; | |
| transition: all 0.2s; | |
| box-shadow: 0 4px 14px rgba(102, 126, 234, 0.4); | |
| } | |
| .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); } | |
| .btn-primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } | |
| .btn-secondary { | |
| background: #f1f5f9; | |
| color: #475569; | |
| border: 2px solid #e2e8f0; | |
| padding: 12px 20px; | |
| border-radius: 10px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| font-size: 14px; | |
| transition: all 0.2s; | |
| } | |
| .btn-secondary:hover { background: #e2e8f0; } | |
| .response-area { | |
| margin-top: 24px; | |
| padding: 20px; | |
| background: #f8fafc; | |
| border-radius: 12px; | |
| min-height: 150px; | |
| border: 2px solid #e2e8f0; | |
| position: relative; | |
| } | |
| .response-area.loading { background: linear-gradient(90deg, #f8fafc 0%, #edf2f7 50%, #f8fafc 100%); background-size: 200% 100%; animation: shimmer 1.5s infinite; } | |
| @keyframes shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } | |
| .typing-indicator { display: none; align-items: center; gap: 6px; color: #64748b; } | |
| .typing-indicator.show { display: flex; } | |
| .typing-dot { width: 8px; height: 8px; background: #94a3b8; border-radius: 50%; animation: bounce 1.4s infinite; } | |
| .typing-dot:nth-child(2) { animation-delay: 0.2s; } | |
| .typing-dot:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes bounce { 0%, 60%, 100% { transform: translateY(0); } 30% { transform: translateY(-10px); } } | |
| .response-text { line-height: 1.7; white-space: pre-wrap; } | |
| .response-text code { background: #e2e8f0; padding: 2px 6px; border-radius: 4px; font-size: 14px; } | |
| .response-actions { | |
| position: absolute; | |
| top: 16px; | |
| right: 16px; | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .icon-btn { | |
| background: white; | |
| border: 1px solid #e2e8f0; | |
| padding: 8px 12px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 12px; | |
| transition: all 0.2s; | |
| } | |
| .icon-btn:hover { background: #f8fafc; border-color: #cbd5e1; } | |
| .examples-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 12px; | |
| margin-top: 20px; | |
| } | |
| .example-card { | |
| background: white; | |
| border: 2px solid #e2e8f0; | |
| padding: 14px; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| font-size: 14px; | |
| } | |
| .example-card:hover { | |
| border-color: #667eea; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(102,126,234,0.2); | |
| } | |
| .achievement-popup { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| background: white; | |
| padding: 20px; | |
| border-radius: 12px; | |
| box-shadow: 0 10px 40px rgba(0,0,0,0.3); | |
| border: 2px solid #48bb78; | |
| transform: translateX(400px); | |
| transition: transform 0.4s; | |
| z-index: 1000; | |
| max-width: 300px; | |
| } | |
| .achievement-popup.show { transform: translateX(0); } | |
| .achievement-icon { font-size: 48px; text-align: center; margin-bottom: 10px; } | |
| .achievement-title { font-weight: 700; font-size: 18px; margin-bottom: 6px; } | |
| .achievement-desc { font-size: 14px; color: #64748b; } | |
| /* RAG Pipeline Visualization Styles */ | |
| .rag-pipeline { | |
| background: #0d1117; | |
| border: 1px solid #30363d; | |
| border-radius: 12px; | |
| padding: 20px; | |
| margin-top: 20px; | |
| color: #c9d1d9; | |
| font-family: 'Consolas', 'Monaco', 'Courier New', monospace; | |
| } | |
| .rag-pipeline-header { | |
| font-size: 16px; | |
| font-weight: 700; | |
| margin-bottom: 16px; | |
| color: #58a6ff; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .rag-stage { | |
| border-left: 3px solid #21262d; | |
| padding: 16px; | |
| margin: 12px 0; | |
| background: #161b22; | |
| border-radius: 6px; | |
| transition: all 0.3s; | |
| } | |
| .rag-stage.active { | |
| border-left-color: #58a6ff; | |
| background: #1c2128; | |
| box-shadow: 0 0 0 1px #58a6ff33; | |
| } | |
| .rag-stage-title { | |
| font-weight: 700; | |
| font-size: 14px; | |
| color: #58a6ff; | |
| margin-bottom: 8px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .rag-stage-icon { | |
| font-size: 18px; | |
| } | |
| .rag-stage-content { | |
| font-size: 13px; | |
| color: #8b949e; | |
| line-height: 1.6; | |
| } | |
| .rag-embedding-preview { | |
| background: #0d1117; | |
| padding: 10px; | |
| border-radius: 4px; | |
| font-size: 11px; | |
| overflow-x: auto; | |
| margin-top: 8px; | |
| border: 1px solid #21262d; | |
| } | |
| .rag-doc-card { | |
| background: #0d1117; | |
| border: 1px solid #30363d; | |
| border-radius: 6px; | |
| padding: 12px; | |
| margin: 8px 0; | |
| transition: all 0.2s; | |
| } | |
| .rag-doc-card:hover { | |
| border-color: #58a6ff; | |
| background: #161b22; | |
| } | |
| .rag-doc-title { | |
| font-weight: 600; | |
| color: #c9d1d9; | |
| margin-bottom: 4px; | |
| font-size: 13px; | |
| } | |
| .rag-doc-snippet { | |
| color: #8b949e; | |
| font-size: 12px; | |
| margin-bottom: 8px; | |
| line-height: 1.5; | |
| } | |
| .rag-doc-meta { | |
| display: flex; | |
| gap: 12px; | |
| font-size: 11px; | |
| color: #6e7681; | |
| } | |
| .rag-score { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| padding: 2px 8px; | |
| background: #1a7f3733; | |
| border: 1px solid #1a7f37; | |
| border-radius: 4px; | |
| color: #3fb950; | |
| font-weight: 600; | |
| } | |
| .rag-score.high { background: #1a7f3733; border-color: #1a7f37; color: #3fb950; } | |
| .rag-score.medium { background: #9e6a0333; border-color: #9e6a03; color: #d29922; } | |
| .rag-score.improved { background: #1f6feb33; border-color: #1f6feb; color: #58a6ff; } | |
| .rag-score.decreased { background: #da363633; border-color: #da3636; color: #f85149; } | |
| .rag-timeline { | |
| font-size: 11px; | |
| color: #6e7681; | |
| margin-top: 16px; | |
| padding-top: 16px; | |
| border-top: 1px solid #21262d; | |
| } | |
| .rag-citation { | |
| display: inline-block; | |
| padding: 3px 8px; | |
| background: #388bfd33; | |
| border: 1px solid #388bfd; | |
| border-radius: 4px; | |
| color: #58a6ff; | |
| font-size: 11px; | |
| margin: 4px 4px 4px 0; | |
| font-weight: 600; | |
| } | |
| @media (max-width: 768px) { | |
| .header h1 { font-size: 32px; } | |
| .mode-selector { flex-wrap: nowrap; padding: 15px; } | |
| .stat-card { padding: 10px 15px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>π§ Eidolon Cognitive Tutor</h1> | |
| <div class="tagline">Learn Anything, Your Way β Personalized, Interactive, Engaging</div> | |
| </div> | |
| <div class="stats-bar"> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="question-count">0</div> | |
| <div class="stat-label">Questions Asked</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="streak-count">0π₯</div> | |
| <div class="stat-label">Learning Streak</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="stat-value" id="achievement-count">0π</div> | |
| <div class="stat-label">Achievements</div> | |
| </div> | |
| </div> | |
| <div class="main-card"> | |
| <div class="mode-selector" id="mode-selector"> | |
| <button class="mode-btn active" data-mode="standard"> | |
| <span>π</span> Standard | |
| </button> | |
| <button class="mode-btn" data-mode="socratic"> | |
| <span>π€</span> Socratic | |
| </button> | |
| <button class="mode-btn" data-mode="eli5"> | |
| <span>πΆ</span> ELI5 | |
| </button> | |
| <button class="mode-btn" data-mode="technical"> | |
| <span>π¬</span> Technical | |
| </button> | |
| <button class="mode-btn" data-mode="analogy"> | |
| <span>π</span> Analogy | |
| </button> | |
| <button class="mode-btn" data-mode="code"> | |
| <span>π»</span> Code | |
| </button> | |
| </div> | |
| <div class="controls-panel"> | |
| <div class="control-group"> | |
| <div class="control-item"> | |
| <label>Difficulty Level</label> | |
| <div class="slider-container"> | |
| <input type="range" min="1" max="5" value="3" class="slider" id="difficulty-slider"> | |
| <div class="difficulty-labels"> | |
| <span>Beginner</span> | |
| <span>Intermediate</span> | |
| <span>Expert</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="control-item"> | |
| <label>Tutor Persona</label> | |
| <div class="persona-selector"> | |
| <button class="persona-btn active" data-persona="friendly">π Friendly</button> | |
| <button class="persona-btn" data-persona="strict">π Strict</button> | |
| <button class="persona-btn" data-persona="enthusiastic">π Hyped</button> | |
| <button class="persona-btn" data-persona="professional">π Pro</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="chat-area"> | |
| <div class="prompt-enhance" id="prompt-enhance"> | |
| π‘ <strong>Suggestion:</strong> <span id="enhance-text"></span> | |
| </div> | |
| <textarea | |
| id="prompt" | |
| placeholder="Ask anything... Try 'Explain quantum computing' or 'How do I build a REST API?'" | |
| ></textarea> | |
| <div class="action-bar"> | |
| <button class="btn-primary" id="ask-btn"> | |
| <span id="ask-text">Ask Tutor</span> | |
| </button> | |
| <button class="btn-secondary" id="enhance-btn">β¨ Enhance Prompt</button> | |
| <button class="btn-secondary" id="clear-btn">ποΈ Clear</button> | |
| <button class="btn-secondary" id="surprise-btn">π² Surprise Me</button> | |
| </div> | |
| <div class="response-area" id="response-area"> | |
| <div class="typing-indicator" id="typing-indicator"> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <div class="typing-dot"></div> | |
| <span style="margin-left: 8px;">Thinking...</span> | |
| </div> | |
| <div class="response-text" id="response-text">Your tutor is ready! Pick a learning mode and ask away π</div> | |
| <div class="response-actions" style="display:none" id="response-actions"> | |
| <button class="icon-btn" id="copy-btn">π Copy</button> | |
| <button class="icon-btn" id="show-research-btn">π¬ Research</button> | |
| <button class="icon-btn" id="share-btn">π Share</button> | |
| <button class="icon-btn" id="regenerate-btn">π Regenerate</button> | |
| </div> | |
| </div> | |
| <!-- Research panel with tabs for Citations and RAG Pipeline --> | |
| <div class="research-panel" id="research-panel" style="display:none; margin-top:16px; padding:0; background:#0d1117; color:#e6eef8; border-radius:12px; border:1px solid #30363d; overflow:hidden;"> | |
| <div style="display:flex; border-bottom:1px solid #21262d; background:#161b22;"> | |
| <button class="research-tab active" data-tab="papers" style="flex:1; padding:12px; background:none; border:none; color:#58a6ff; font-weight:600; cursor:pointer; border-bottom:2px solid #58a6ff; font-size:13px;"> | |
| π Citations | |
| </button> | |
| <button class="research-tab" data-tab="rag" style="flex:1; padding:12px; background:none; border:none; color:#8b949e; font-weight:600; cursor:pointer; border-bottom:2px solid transparent; font-size:13px;"> | |
| οΏ½ RAG Pipeline | |
| </button> | |
| </div> | |
| <div class="research-tab-content" id="papers-tab" style="padding:16px;"> | |
| <div style="font-weight:700; margin-bottom:12px; font-size:14px;">Research Foundations</div> | |
| <div id="research-list" style="font-size:13px; line-height:1.5;"></div> | |
| </div> | |
| <div class="research-tab-content" id="rag-tab" style="padding:16px; display:none;"> | |
| <div id="rag-pipeline-viz"></div> | |
| </div> | |
| </div> | |
| <div class="examples-grid"> | |
| <div class="example-card" data-example="Explain Newton's laws in simple terms"> | |
| π Newton's Laws | |
| </div> | |
| <div class="example-card" data-example="How do I implement binary search?"> | |
| π» Binary Search | |
| </div> | |
| <div class="example-card" data-example="Compare supervised vs unsupervised learning"> | |
| π€ ML Comparison | |
| </div> | |
| <div class="example-card" data-example="What's the difference between HTTP and HTTPS?"> | |
| π HTTP vs HTTPS | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="achievement-popup" id="achievement-popup"> | |
| <div class="achievement-icon">π</div> | |
| <div class="achievement-title" id="achievement-title">Achievement Unlocked!</div> | |
| <div class="achievement-desc" id="achievement-desc">You've earned a new badge</div> | |
| </div> | |
| <script> | |
| // State | |
| let selectedMode = 'standard'; | |
| let selectedPersona = 'friendly'; | |
| let difficulty = 3; | |
| let sessionId = localStorage.getItem('session_id') || ''; | |
| let questionCount = parseInt(localStorage.getItem('question_count') || '0'); | |
| let achievements = JSON.parse(localStorage.getItem('achievements') || '[]'); | |
| let lastQuestionTime = Date.now(); | |
| // Elements | |
| const promptEl = document.getElementById('prompt'); | |
| const askBtn = document.getElementById('ask-btn'); | |
| const responseArea = document.getElementById('response-area'); | |
| const responseText = document.getElementById('response-text'); | |
| const typingIndicator = document.getElementById('typing-indicator'); | |
| const responseActions = document.getElementById('response-actions'); | |
| // Update stats | |
| document.getElementById('question-count').textContent = questionCount; | |
| document.getElementById('achievement-count').textContent = achievements.length + 'π'; | |
| // Calculate streak | |
| function updateStreak() { | |
| const now = Date.now(); | |
| const hoursSinceLastQuestion = (now - lastQuestionTime) / (1000 * 60 * 60); | |
| let streak = parseInt(localStorage.getItem('streak') || '1'); | |
| if (hoursSinceLastQuestion < 24) { | |
| streak++; | |
| localStorage.setItem('streak', streak); | |
| } else if (hoursSinceLastQuestion > 48) { | |
| streak = 1; | |
| localStorage.setItem('streak', '1'); | |
| } | |
| document.getElementById('streak-count').textContent = streak + 'π₯'; | |
| } | |
| updateStreak(); | |
| // Mode selection | |
| document.querySelectorAll('.mode-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| document.querySelectorAll('.mode-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| selectedMode = btn.dataset.mode; | |
| }); | |
| }); | |
| // Persona selection | |
| document.querySelectorAll('.persona-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| document.querySelectorAll('.persona-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| selectedPersona = btn.dataset.persona; | |
| }); | |
| }); | |
| // Difficulty slider | |
| document.getElementById('difficulty-slider').addEventListener('input', (e) => { | |
| difficulty = parseInt(e.target.value); | |
| }); | |
| // Example cards | |
| document.querySelectorAll('.example-card').forEach(card => { | |
| card.addEventListener('click', () => { | |
| promptEl.value = card.dataset.example; | |
| promptEl.focus(); | |
| }); | |
| }); | |
| // Enhance prompt | |
| document.getElementById('enhance-btn').addEventListener('click', () => { | |
| const prompt = promptEl.value.trim(); | |
| if (!prompt) return; | |
| const enhancements = [ | |
| `Can you explain ${prompt} with examples and use cases?`, | |
| `Please provide a detailed explanation of ${prompt}, including practical applications.`, | |
| `I'd like to understand ${prompt} - can you break it down step by step?`, | |
| `What are the key concepts behind ${prompt} and how do they relate to each other?` | |
| ]; | |
| const enhanced = enhancements[Math.floor(Math.random() * enhancements.length)]; | |
| document.getElementById('enhance-text').textContent = enhanced; | |
| document.getElementById('prompt-enhance').classList.add('show'); | |
| setTimeout(() => { | |
| if (confirm('Apply this enhanced prompt?')) { | |
| promptEl.value = enhanced; | |
| } | |
| document.getElementById('prompt-enhance').classList.remove('show'); | |
| }, 3000); | |
| }); | |
| // Surprise me | |
| document.getElementById('surprise-btn').addEventListener('click', () => { | |
| const surprises = [ | |
| "Explain the concept of emergence in complex systems", | |
| "How does the human brain process and store memories?", | |
| "What would happen if we could travel faster than light?", | |
| "Explain blockchain technology using an everyday analogy", | |
| "How do neural networks learn from data?", | |
| "What is the halting problem and why is it unsolvable?" | |
| ]; | |
| promptEl.value = surprises[Math.floor(Math.random() * surprises.length)]; | |
| }); | |
| // Clear | |
| document.getElementById('clear-btn').addEventListener('click', () => { | |
| promptEl.value = ''; | |
| responseText.textContent = 'Your tutor is ready! Pick a learning mode and ask away π'; | |
| responseActions.style.display = 'none'; | |
| }); | |
| // Copy | |
| document.getElementById('copy-btn').addEventListener('click', () => { | |
| navigator.clipboard.writeText(responseText.textContent); | |
| document.getElementById('copy-btn').textContent = 'β Copied!'; | |
| setTimeout(() => { | |
| document.getElementById('copy-btn').textContent = 'π Copy'; | |
| }, 2000); | |
| }); | |
| // Research panel toggle | |
| document.getElementById('show-research-btn').addEventListener('click', () => { | |
| const panel = document.getElementById('research-panel'); | |
| if (panel.style.display === 'none' || panel.style.display === '') { | |
| panel.style.display = 'block'; | |
| document.getElementById('show-research-btn').textContent = 'π¬ Hide Research'; | |
| } else { | |
| panel.style.display = 'none'; | |
| document.getElementById('show-research-btn').textContent = 'π¬ Research'; | |
| } | |
| }); | |
| // Research tab switching | |
| document.querySelectorAll('.research-tab').forEach(tab => { | |
| tab.addEventListener('click', () => { | |
| const targetTab = tab.dataset.tab; | |
| // Update tab buttons | |
| document.querySelectorAll('.research-tab').forEach(t => { | |
| t.classList.remove('active'); | |
| t.style.color = '#8b949e'; | |
| t.style.borderBottomColor = 'transparent'; | |
| }); | |
| tab.classList.add('active'); | |
| tab.style.color = '#58a6ff'; | |
| tab.style.borderBottomColor = '#58a6ff'; | |
| // Update tab content | |
| document.querySelectorAll('.research-tab-content').forEach(content => { | |
| content.style.display = 'none'; | |
| }); | |
| document.getElementById(targetTab + '-tab').style.display = 'block'; | |
| }); | |
| }); | |
| // Main ask function with typing animation | |
| async function askQuestion() { | |
| const prompt = promptEl.value.trim(); | |
| if (!prompt) { | |
| alert('Please enter a question!'); | |
| return; | |
| } | |
| askBtn.disabled = true; | |
| document.getElementById('ask-text').textContent = 'Thinking...'; | |
| responseArea.classList.add('loading'); | |
| typingIndicator.classList.add('show'); | |
| responseText.textContent = ''; | |
| responseActions.style.display = 'none'; | |
| try { | |
| const resp = await fetch('/api/ask', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| prompt, | |
| mode: selectedMode, | |
| difficulty, | |
| persona: selectedPersona, | |
| session_id: sessionId || undefined | |
| }) | |
| }); | |
| const data = await resp.json(); | |
| if (data.session_id && !sessionId) { | |
| sessionId = data.session_id; | |
| localStorage.setItem('session_id', sessionId); | |
| } | |
| typingIndicator.classList.remove('show'); | |
| // Prepare research data if present | |
| const researchData = data.research_data || {}; | |
| const researchPapers = researchData.papers || null; | |
| const ragPipeline = researchData.rag_pipeline || null; | |
| if (researchPapers && researchPapers.length > 0) { | |
| // Render citations | |
| renderResearch(researchPapers); | |
| } else { | |
| // Clear previous | |
| document.getElementById('research-list').innerHTML = '<div style="opacity:0.8">No citations available.</div>'; | |
| } | |
| if (ragPipeline) { | |
| // Render RAG pipeline visualization | |
| renderRAGPipeline(ragPipeline); | |
| } else { | |
| document.getElementById('rag-pipeline-viz').innerHTML = '<div style="opacity:0.8">RAG pipeline data not available.</div>'; | |
| } | |
| if (data.error) { | |
| responseText.textContent = 'β ' + data.error; | |
| } else { | |
| // Typing animation | |
| const fullText = data.result; | |
| let index = 0; | |
| responseText.textContent = ''; | |
| const typeInterval = setInterval(() => { | |
| if (index < fullText.length) { | |
| responseText.textContent += fullText[index]; | |
| index++; | |
| } else { | |
| clearInterval(typeInterval); | |
| responseActions.style.display = 'flex'; | |
| } | |
| }, 15); | |
| // Update stats | |
| questionCount++; | |
| localStorage.setItem('question_count', questionCount); | |
| document.getElementById('question-count').textContent = questionCount; | |
| lastQuestionTime = Date.now(); | |
| updateStreak(); | |
| // Check achievements | |
| checkAchievements(); | |
| } | |
| } catch (e) { | |
| typingIndicator.classList.remove('show'); | |
| responseText.textContent = 'β Request failed: ' + e.message; | |
| } finally { | |
| askBtn.disabled = false; | |
| document.getElementById('ask-text').textContent = 'Ask Tutor'; | |
| responseArea.classList.remove('loading'); | |
| } | |
| } | |
| askBtn.addEventListener('click', askQuestion); | |
| promptEl.addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' && e.ctrlKey) { | |
| askQuestion(); | |
| } | |
| }); | |
| // Achievements system | |
| function checkAchievements() { | |
| const newAchievements = []; | |
| if (questionCount === 1 && !achievements.includes('first-question')) { | |
| newAchievements.push({ id: 'first-question', title: 'Getting Started!', desc: 'Asked your first question', icon: 'π' }); | |
| } | |
| if (questionCount === 10 && !achievements.includes('curious-learner')) { | |
| newAchievements.push({ id: 'curious-learner', title: 'Curious Learner', desc: '10 questions asked!', icon: 'π' }); | |
| } | |
| if (questionCount === 50 && !achievements.includes('knowledge-seeker')) { | |
| newAchievements.push({ id: 'knowledge-seeker', title: 'Knowledge Seeker', desc: '50 questions - you\'re on fire!', icon: 'π₯' }); | |
| } | |
| // Mode explorer | |
| const modes = JSON.parse(localStorage.getItem('modes_used') || '[]'); | |
| if (!modes.includes(selectedMode)) { | |
| modes.push(selectedMode); | |
| localStorage.setItem('modes_used', JSON.stringify(modes)); | |
| if (modes.length === 6 && !achievements.includes('mode-master')) { | |
| newAchievements.push({ id: 'mode-master', title: 'Mode Master', desc: 'Tried all learning modes!', icon: 'π¨' }); | |
| } | |
| } | |
| newAchievements.forEach(achievement => { | |
| if (!achievements.includes(achievement.id)) { | |
| achievements.push(achievement.id); | |
| showAchievement(achievement); | |
| } | |
| }); | |
| localStorage.setItem('achievements', JSON.stringify(achievements)); | |
| document.getElementById('achievement-count').textContent = achievements.length + 'π'; | |
| } | |
| function showAchievement(achievement) { | |
| const popup = document.getElementById('achievement-popup'); | |
| document.getElementById('achievement-title').textContent = achievement.title; | |
| document.getElementById('achievement-desc').textContent = achievement.desc; | |
| document.querySelector('.achievement-icon').textContent = achievement.icon; | |
| popup.classList.add('show'); | |
| setTimeout(() => { | |
| popup.classList.remove('show'); | |
| }, 4000); | |
| } | |
| // Render research citations into the research panel | |
| function renderResearch(papers) { | |
| const list = document.getElementById('research-list'); | |
| if (!papers || papers.length === 0) { | |
| list.innerHTML = '<div style="opacity:0.8">No citations available for this response.</div>'; | |
| return; | |
| } | |
| let html = ''; | |
| papers.forEach(p => { | |
| const title = p.title || p.name || 'Untitled'; | |
| const authors = p.authors ? `<div style="opacity:0.8; font-size:12px; color:#8b949e;">${p.authors} β’ ${p.year} β’ ${p.venue || ''}</div>` : ''; | |
| const summary = p.summary ? `<div style="margin-top:6px; color:#c9d1d9; font-size:12px;">${p.summary}</div>` : ''; | |
| const url = p.url ? `<a href="${p.url}" target="_blank" style="color:#58a6ff; text-decoration:none; font-size:12px; display:inline-block; margin-top:6px;">π Read paper β</a>` : ''; | |
| html += `<div style="padding:12px; border-bottom:1px solid #21262d; background:#161b22; border-radius:6px; margin-bottom:8px;">` + | |
| `<div style="font-weight:700; color:#c9d1d9;">${title}</div>` + authors + summary + `<div>${url}</div></div>`; | |
| }); | |
| list.innerHTML = html; | |
| } | |
| // Render RAG Pipeline visualization | |
| function renderRAGPipeline(pipelineData) { | |
| const container = document.getElementById('rag-pipeline-viz'); | |
| if (!pipelineData || !pipelineData.stages) { | |
| container.innerHTML = '<div style="opacity:0.8">No pipeline data available.</div>'; | |
| return; | |
| } | |
| const stages = pipelineData.stages; | |
| const totalTime = pipelineData.total_time_ms || 0; | |
| let html = `<div class="rag-pipeline">`; | |
| html += `<div class="rag-pipeline-header">`; | |
| html += `<span style="font-size:20px;">π</span>`; | |
| html += `<span>Retrieval-Augmented Generation Pipeline</span>`; | |
| html += `<span style="margin-left:auto; font-size:12px; color:#8b949e;">Total: ${totalTime.toFixed(0)}ms</span>`; | |
| html += `</div>`; | |
| // Render each stage | |
| stages.forEach((stage, idx) => { | |
| const isActive = idx === stages.length - 1; // Last stage is active | |
| html += `<div class="rag-stage ${isActive ? 'active' : ''}">`; | |
| if (stage.stage === 'query_encoding') { | |
| html += `<div class="rag-stage-title">`; | |
| html += `<span class="rag-stage-icon">π€</span>`; | |
| html += `<span>1. Query Encoding</span>`; | |
| html += `<span style="margin-left:auto; font-size:11px; color:#6e7681;">${stage.timestamp_ms}ms</span>`; | |
| html += `</div>`; | |
| html += `<div class="rag-stage-content">`; | |
| html += `<div><strong>Query:</strong> "${stage.query}"</div>`; | |
| html += `<div style="margin-top:8px;"><strong>Encoding Method:</strong> ${stage.encoding_method}</div>`; | |
| html += `<div style="margin-top:8px;"><strong>Embedding Dimension:</strong> ${stage.embedding_dim}</div>`; | |
| html += `<div class="rag-embedding-preview">`; | |
| html += `<div style="color:#6e7681; margin-bottom:4px;">Embedding preview (first 10 dimensions):</div>`; | |
| html += `<code style="color:#79c0ff;">[${stage.embedding_preview.join(', ')}...]</code>`; | |
| html += `</div>`; | |
| html += `</div>`; | |
| } | |
| else if (stage.stage === 'retrieval') { | |
| html += `<div class="rag-stage-title">`; | |
| html += `<span class="rag-stage-icon">π</span>`; | |
| html += `<span>2. Document Retrieval</span>`; | |
| html += `<span style="margin-left:auto; font-size:11px; color:#6e7681;">${stage.search_time_ms}ms</span>`; | |
| html += `</div>`; | |
| html += `<div class="rag-stage-content">`; | |
| html += `<div><strong>Documents Searched:</strong> ${stage.num_documents_searched.toLocaleString()}</div>`; | |
| html += `<div style="margin-top:4px;"><strong>Top Results Retrieved:</strong> ${stage.top_k_retrieved}</div>`; | |
| html += `<div style="margin-top:12px; font-weight:600;">Retrieved Documents:</div>`; | |
| stage.documents.slice(0, 3).forEach((doc, i) => { | |
| const scoreClass = doc.relevance_score > 0.9 ? 'high' : doc.relevance_score > 0.8 ? 'medium' : ''; | |
| html += `<div class="rag-doc-card">`; | |
| html += `<div class="rag-doc-title">${i + 1}. ${doc.title}</div>`; | |
| html += `<div class="rag-doc-snippet">${doc.snippet}</div>`; | |
| html += `<div class="rag-doc-meta">`; | |
| html += `<span class="rag-score ${scoreClass}">β ${(doc.relevance_score * 100).toFixed(1)}%</span>`; | |
| html += `<span>π ${doc.source}</span>`; | |
| if (doc.citations) html += `<span>π ${doc.citations} citations</span>`; | |
| html += `</div></div>`; | |
| }); | |
| html += `</div>`; | |
| } | |
| else if (stage.stage === 'reranking') { | |
| html += `<div class="rag-stage-title">`; | |
| html += `<span class="rag-stage-icon">π</span>`; | |
| html += `<span>3. Cross-Encoder Re-ranking</span>`; | |
| html += `<span style="margin-left:auto; font-size:11px; color:#6e7681;">${stage.reranking_time_ms}ms</span>`; | |
| html += `</div>`; | |
| html += `<div class="rag-stage-content">`; | |
| html += `<div><strong>Re-ranker Model:</strong> ${stage.reranked_documents[0]?.reranker || 'cross-encoder'}</div>`; | |
| html += `<div style="margin-top:12px; font-weight:600;">Score Adjustments:</div>`; | |
| stage.reranked_documents.forEach((doc, i) => { | |
| const change = doc.score_change; | |
| const scoreClass = change > 0 ? 'improved' : change < 0 ? 'decreased' : ''; | |
| const arrow = change > 0 ? 'β' : change < 0 ? 'β' : 'β'; | |
| html += `<div class="rag-doc-card">`; | |
| html += `<div class="rag-doc-title">${i + 1}. ${doc.title}</div>`; | |
| html += `<div class="rag-doc-meta">`; | |
| html += `<span style="color:#6e7681;">Old: ${(doc.old_score * 100).toFixed(1)}%</span>`; | |
| html += `<span class="rag-score ${scoreClass}">${arrow} ${(doc.new_score * 100).toFixed(1)}%</span>`; | |
| html += `<span style="color:${change > 0 ? '#3fb950' : change < 0 ? '#f85149' : '#8b949e'};">${change > 0 ? '+' : ''}${(change * 100).toFixed(1)}%</span>`; | |
| html += `</div></div>`; | |
| }); | |
| html += `</div>`; | |
| } | |
| else if (stage.stage === 'generation') { | |
| html += `<div class="rag-stage-title">`; | |
| html += `<span class="rag-stage-icon">βοΈ</span>`; | |
| html += `<span>4. Response Generation</span>`; | |
| html += `<span style="margin-left:auto; font-size:11px; color:#6e7681;">${stage.generation_time_ms}ms</span>`; | |
| html += `</div>`; | |
| html += `<div class="rag-stage-content">`; | |
| html += `<div><strong>Context Documents:</strong> ${stage.num_context_docs}</div>`; | |
| html += `<div style="margin-top:4px;"><strong>Total Context Length:</strong> ${stage.context_length} chars</div>`; | |
| html += `<div style="margin-top:4px;"><strong>Generated Response:</strong> ${stage.response_length} chars</div>`; | |
| if (stage.citations && stage.citations.length > 0) { | |
| html += `<div style="margin-top:12px; font-weight:600;">Source Attribution:</div>`; | |
| html += `<div style="margin-top:8px;">`; | |
| stage.citations.forEach(cite => { | |
| if (cite.used) { | |
| html += `<span class="rag-citation">[${cite.id}] ${cite.title} (${(cite.relevance * 100).toFixed(0)}%)</span>`; | |
| } | |
| }); | |
| html += `</div>`; | |
| } | |
| html += `</div>`; | |
| } | |
| html += `</div>`; | |
| }); | |
| // Add timeline summary | |
| html += `<div class="rag-timeline">`; | |
| html += `<strong>Pipeline Components:</strong> `; | |
| html += pipelineData.components ? pipelineData.components.join(' β ') : 'Query Encoder β Vector DB β Re-ranker β Generator'; | |
| html += `</div>`; | |
| html += `</div>`; | |
| container.innerHTML = html; | |
| } | |
| </script> | |
| </body> | |
| </html> | |