Spaces:
Running
Running
| /** | |
| * LLM Playground - Interactive JavaScript | |
| * Educational demonstrations of LLM concepts | |
| */ | |
| // ======================================== | |
| // Theme Management | |
| // ======================================== | |
| class ThemeManager { | |
| constructor() { | |
| this.themeToggle = document.getElementById('themeToggle'); | |
| this.currentTheme = localStorage.getItem('theme') || 'light'; | |
| this.init(); | |
| } | |
| init() { | |
| this.applyTheme(this.currentTheme); | |
| this.themeToggle?.addEventListener('click', () => this.toggle()); | |
| } | |
| applyTheme(theme) { | |
| document.documentElement.setAttribute('data-theme', theme); | |
| const icon = this.themeToggle?.querySelector('i'); | |
| if (icon) { | |
| icon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon'; | |
| } | |
| localStorage.setItem('theme', theme); | |
| } | |
| toggle() { | |
| this.currentTheme = this.currentTheme === 'dark' ? 'light' : 'dark'; | |
| this.applyTheme(this.currentTheme); | |
| } | |
| } | |
| // ======================================== | |
| // Navigation | |
| // ======================================== | |
| class Navigation { | |
| constructor() { | |
| this.navLinks = document.querySelectorAll('.nav-link'); | |
| this.sections = document.querySelectorAll('.section'); | |
| this.init(); | |
| } | |
| init() { | |
| // Smooth scroll | |
| this.navLinks.forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| const targetId = link.getAttribute('href').slice(1); | |
| const target = document.getElementById(targetId); | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| } | |
| }); | |
| }); | |
| // Active link on scroll | |
| window.addEventListener('scroll', () => this.updateActiveLink()); | |
| } | |
| updateActiveLink() { | |
| const scrollPos = window.scrollY + 100; | |
| this.sections.forEach(section => { | |
| const top = section.offsetTop; | |
| const bottom = top + section.offsetHeight; | |
| const id = section.getAttribute('id'); | |
| if (scrollPos >= top && scrollPos < bottom) { | |
| this.navLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| if (link.getAttribute('href') === `#${id}`) { | |
| link.classList.add('active'); | |
| } | |
| }); | |
| } | |
| }); | |
| } | |
| } | |
| // ======================================== | |
| // Section Animations | |
| // ======================================== | |
| class SectionAnimator { | |
| constructor() { | |
| this.sections = document.querySelectorAll('.section'); | |
| this.init(); | |
| } | |
| init() { | |
| const observer = new IntersectionObserver((entries) => { | |
| entries.forEach(entry => { | |
| if (entry.isIntersecting) { | |
| entry.target.classList.add('visible'); | |
| } | |
| }); | |
| }, { threshold: 0.1 }); | |
| this.sections.forEach(section => observer.observe(section)); | |
| } | |
| } | |
| // ======================================== | |
| // Tab System | |
| // ======================================== | |
| class TabSystem { | |
| constructor() { | |
| this.tabButtons = document.querySelectorAll('.tab-btn'); | |
| this.init(); | |
| } | |
| init() { | |
| this.tabButtons.forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const tabId = btn.dataset.tab; | |
| const parent = btn.closest('.subsection'); | |
| // Update buttons | |
| parent.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); | |
| btn.classList.add('active'); | |
| // Update content | |
| parent.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); | |
| parent.querySelector(`#${tabId}`).classList.add('active'); | |
| }); | |
| }); | |
| } | |
| } | |
| // ======================================== | |
| // Data Cleaning Demo | |
| // ======================================== | |
| class DataCleaningDemo { | |
| constructor() { | |
| this.input = document.getElementById('cleaningInput'); | |
| this.output = document.getElementById('cleaningOutput'); | |
| this.cleanBtn = document.getElementById('cleanTextBtn'); | |
| this.checkboxes = { | |
| removeHtml: document.getElementById('removeHtml'), | |
| normalizeSpaces: document.getElementById('normalizeSpaces'), | |
| removeUrls: document.getElementById('removeUrls'), | |
| removeEmails: document.getElementById('removeEmails'), | |
| removeDuplicates: document.getElementById('removeDuplicates') | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.cleanBtn?.addEventListener('click', () => this.clean()); | |
| } | |
| clean() { | |
| let text = this.input.value; | |
| if (this.checkboxes.removeHtml?.checked) { | |
| text = text.replace(/<[^>]*>/g, ''); | |
| } | |
| if (this.checkboxes.removeUrls?.checked) { | |
| text = text.replace(/https?:\/\/[^\s]+/g, ''); | |
| } | |
| if (this.checkboxes.removeEmails?.checked) { | |
| text = text.replace(/[\w.-]+@[\w.-]+\.\w+/g, ''); | |
| } | |
| if (this.checkboxes.normalizeSpaces?.checked) { | |
| text = text.replace(/\s+/g, ' ').trim(); | |
| } | |
| if (this.checkboxes.removeDuplicates?.checked) { | |
| const lines = text.split('\n'); | |
| const unique = [...new Set(lines)]; | |
| text = unique.join('\n'); | |
| } | |
| this.output.value = text; | |
| } | |
| } | |
| // ======================================== | |
| // Tokenization Demo | |
| // ======================================== | |
| class TokenizationDemo { | |
| constructor() { | |
| this.input = document.getElementById('tokenizeInput'); | |
| this.output = document.getElementById('tokenOutput'); | |
| this.type = document.getElementById('tokenizerType'); | |
| this.tokenizeBtn = document.getElementById('tokenizeBtn'); | |
| this.tokenCount = document.getElementById('tokenCount'); | |
| this.vocabSize = document.getElementById('vocabSize'); | |
| // Simple BPE-like vocabulary | |
| this.bpeVocab = [ | |
| 'the', 'ing', 'tion', 'ed', 'er', 'es', 'en', 'al', 'ly', 're', | |
| 'an', 'or', 'is', 'it', 'at', 'on', 'as', 'ar', 'le', 'se', | |
| 'Hello', 'how', 'are', 'you', 'today', 'what', 'can', 'help', | |
| 'machine', 'learn', 'artificial', 'intelligence', 'model', 'data' | |
| ]; | |
| this.colors = [ | |
| '#6366f1', '#8b5cf6', '#a855f7', '#d946ef', '#ec4899', | |
| '#f43f5e', '#f97316', '#eab308', '#84cc16', '#22c55e', | |
| '#14b8a6', '#06b6d4', '#0ea5e9', '#3b82f6' | |
| ]; | |
| this.init(); | |
| } | |
| init() { | |
| this.tokenizeBtn?.addEventListener('click', () => this.tokenize()); | |
| this.input?.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.tokenize(); | |
| }); | |
| } | |
| tokenize() { | |
| const text = this.input.value; | |
| const type = this.type.value; | |
| let tokens = []; | |
| switch (type) { | |
| case 'word': | |
| tokens = this.wordTokenize(text); | |
| break; | |
| case 'character': | |
| tokens = this.charTokenize(text); | |
| break; | |
| case 'bpe': | |
| tokens = this.bpeTokenize(text); | |
| break; | |
| } | |
| this.displayTokens(tokens); | |
| } | |
| wordTokenize(text) { | |
| return text.match(/\w+|[^\w\s]/g) || []; | |
| } | |
| charTokenize(text) { | |
| return text.split(''); | |
| } | |
| bpeTokenize(text) { | |
| let tokens = []; | |
| const words = text.split(/\s+/); | |
| words.forEach(word => { | |
| let remaining = word; | |
| while (remaining.length > 0) { | |
| let matched = false; | |
| // Try to match longest subword from vocabulary | |
| for (let len = remaining.length; len > 0; len--) { | |
| const subword = remaining.slice(0, len); | |
| if (this.bpeVocab.includes(subword) || len === 1) { | |
| tokens.push(subword); | |
| remaining = remaining.slice(len); | |
| matched = true; | |
| break; | |
| } | |
| } | |
| if (!matched) { | |
| tokens.push(remaining[0]); | |
| remaining = remaining.slice(1); | |
| } | |
| } | |
| tokens.push(' '); | |
| }); | |
| // Remove trailing space | |
| if (tokens[tokens.length - 1] === ' ') { | |
| tokens.pop(); | |
| } | |
| return tokens; | |
| } | |
| displayTokens(tokens) { | |
| this.output.innerHTML = ''; | |
| const uniqueTokens = new Set(); | |
| tokens.forEach((token, i) => { | |
| uniqueTokens.add(token); | |
| const span = document.createElement('span'); | |
| span.className = 'token'; | |
| span.textContent = token === ' ' ? '▁' : token; | |
| span.style.backgroundColor = this.colors[i % this.colors.length] + '30'; | |
| span.style.borderColor = this.colors[i % this.colors.length]; | |
| span.title = `Token ID: ${i}`; | |
| this.output.appendChild(span); | |
| }); | |
| this.tokenCount.textContent = tokens.length; | |
| this.vocabSize.textContent = uniqueTokens.size; | |
| } | |
| } | |
| // ======================================== | |
| // Attention Visualization | |
| // ======================================== | |
| class AttentionVisualization { | |
| constructor() { | |
| this.input = document.getElementById('attentionInput'); | |
| this.showBtn = document.getElementById('showAttentionBtn'); | |
| this.matrix = document.getElementById('attentionMatrix'); | |
| this.init(); | |
| } | |
| init() { | |
| this.showBtn?.addEventListener('click', () => this.visualize()); | |
| } | |
| visualize() { | |
| const text = this.input.value; | |
| const words = text.split(/\s+/); | |
| // Generate random attention scores (simulated) | |
| const attentionScores = this.generateAttentionScores(words.length); | |
| this.renderMatrix(words, attentionScores); | |
| } | |
| generateAttentionScores(size) { | |
| const scores = []; | |
| for (let i = 0; i < size; i++) { | |
| const row = []; | |
| for (let j = 0; j < size; j++) { | |
| // Generate realistic-looking attention patterns | |
| let score = Math.random() * 0.5; | |
| // Higher attention on self | |
| if (i === j) score += 0.3; | |
| // Higher attention on nearby words | |
| if (Math.abs(i - j) === 1) score += 0.2; | |
| // Simulate attention to important positions (first, last) | |
| if (j === 0 || j === size - 1) score += 0.1; | |
| row.push(Math.min(score, 1)); | |
| } | |
| // Normalize row to sum to 1 | |
| const sum = row.reduce((a, b) => a + b, 0); | |
| scores.push(row.map(s => s / sum)); | |
| } | |
| return scores; | |
| } | |
| renderMatrix(words, scores) { | |
| this.matrix.innerHTML = ''; | |
| this.matrix.style.gridTemplateColumns = `repeat(${words.length + 1}, auto)`; | |
| // Header row | |
| const headerRow = document.createElement('div'); | |
| headerRow.className = 'attention-row'; | |
| headerRow.appendChild(this.createCell('', 'header')); | |
| words.forEach(word => { | |
| headerRow.appendChild(this.createCell(word, 'header')); | |
| }); | |
| this.matrix.appendChild(headerRow); | |
| // Data rows | |
| words.forEach((word, i) => { | |
| const row = document.createElement('div'); | |
| row.className = 'attention-row'; | |
| row.appendChild(this.createCell(word, 'header')); | |
| scores[i].forEach(score => { | |
| const cell = this.createCell(score.toFixed(2), 'value'); | |
| cell.style.backgroundColor = this.getHeatmapColor(score); | |
| row.appendChild(cell); | |
| }); | |
| this.matrix.appendChild(row); | |
| }); | |
| } | |
| createCell(content, type) { | |
| const cell = document.createElement('div'); | |
| cell.className = `attention-cell ${type}`; | |
| cell.textContent = content; | |
| return cell; | |
| } | |
| getHeatmapColor(value) { | |
| // Blue to red gradient | |
| const r = Math.round(255 * value); | |
| const b = Math.round(255 * (1 - value)); | |
| return `rgba(${r}, 50, ${b}, 0.8)`; | |
| } | |
| } | |
| // ======================================== | |
| // Text Generation Demo | |
| // ======================================== | |
| class TextGenerationDemo { | |
| constructor() { | |
| this.prompt = document.getElementById('generationPrompt'); | |
| this.method = document.getElementById('generationMethod'); | |
| this.tempSlider = document.getElementById('tempSlider'); | |
| this.tempValue = document.getElementById('tempValue'); | |
| this.maxTokensSlider = document.getElementById('maxTokensSlider'); | |
| this.maxTokensValue = document.getElementById('maxTokensValue'); | |
| this.generateBtn = document.getElementById('generateBtn'); | |
| this.output = document.getElementById('generationOutput'); | |
| this.probViz = document.getElementById('probabilityViz'); | |
| // Sample vocabulary for generation | |
| this.vocabulary = { | |
| 'The future of artificial intelligence is': [ | |
| 'bright', 'uncertain', 'promising', 'complex', 'exciting', 'challenging' | |
| ], | |
| 'bright': ['and', 'with', 'as', ',', '.', 'because'], | |
| 'uncertain': ['but', 'yet', 'however', ',', '.', 'given'], | |
| 'promising': ['with', 'as', 'and', ',', '.', 'for'], | |
| 'and': ['it', 'we', 'the', 'this', 'many', 'there'], | |
| 'with': ['many', 'new', 'great', 'significant', 'potential', 'numerous'], | |
| 'default': ['the', 'a', 'and', 'to', 'of', 'is', 'in', 'that', 'it', 'for'] | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.tempSlider?.addEventListener('input', () => { | |
| this.tempValue.textContent = (this.tempSlider.value / 100).toFixed(1); | |
| }); | |
| this.maxTokensSlider?.addEventListener('input', () => { | |
| this.maxTokensValue.textContent = this.maxTokensSlider.value; | |
| }); | |
| this.generateBtn?.addEventListener('click', () => this.generate()); | |
| } | |
| generate() { | |
| const promptText = this.prompt.value; | |
| const method = this.method.value; | |
| const temperature = this.tempSlider.value / 100; | |
| const maxTokens = parseInt(this.maxTokensSlider.value); | |
| let generated = promptText; | |
| this.output.innerHTML = ''; | |
| this.probViz.innerHTML = ''; | |
| // Animate generation | |
| this.animateGeneration(generated, method, temperature, maxTokens); | |
| } | |
| async animateGeneration(text, method, temperature, maxTokens) { | |
| this.output.textContent = text; | |
| for (let i = 0; i < maxTokens; i++) { | |
| await this.delay(100); | |
| const lastWords = text.split(' ').slice(-5).join(' '); | |
| const candidates = this.vocabulary[lastWords] || | |
| this.vocabulary[text.split(' ').pop()] || | |
| this.vocabulary['default']; | |
| const probabilities = this.calculateProbabilities(candidates, temperature); | |
| const nextToken = this.selectToken(candidates, probabilities, method); | |
| text += ' ' + nextToken; | |
| this.output.textContent = text; | |
| // Show probability visualization for last few tokens | |
| if (i < 5) { | |
| this.showProbabilities(candidates, probabilities, nextToken); | |
| } | |
| if (nextToken === '.') break; | |
| } | |
| } | |
| calculateProbabilities(candidates, temperature) { | |
| const logits = candidates.map((_, i) => 1 / (i + 1)); // Simulated logits | |
| const scaled = logits.map(l => Math.exp(l / Math.max(temperature, 0.1))); | |
| const sum = scaled.reduce((a, b) => a + b, 0); | |
| return scaled.map(s => s / sum); | |
| } | |
| selectToken(candidates, probabilities, method) { | |
| switch (method) { | |
| case 'greedy': | |
| return candidates[0]; | |
| case 'beam': | |
| // Simplified beam - just take top 3 and randomly select | |
| return candidates[Math.floor(Math.random() * Math.min(3, candidates.length))]; | |
| case 'topk': | |
| const k = 5; | |
| const topK = candidates.slice(0, k); | |
| return topK[Math.floor(Math.random() * topK.length)]; | |
| case 'topp': | |
| let cumProb = 0; | |
| const p = 0.9; | |
| for (let i = 0; i < candidates.length; i++) { | |
| cumProb += probabilities[i]; | |
| if (cumProb >= p || Math.random() < probabilities[i] * 2) { | |
| return candidates[i]; | |
| } | |
| } | |
| return candidates[0]; | |
| default: | |
| return candidates[0]; | |
| } | |
| } | |
| showProbabilities(candidates, probabilities, selected) { | |
| const container = document.createElement('div'); | |
| container.style.display = 'flex'; | |
| container.style.gap = '4px'; | |
| container.style.marginBottom = '4px'; | |
| candidates.slice(0, 5).forEach((token, i) => { | |
| const span = document.createElement('span'); | |
| span.className = 'token'; | |
| span.textContent = `${token} (${(probabilities[i] * 100).toFixed(0)}%)`; | |
| span.style.fontSize = '0.75rem'; | |
| if (token === selected) { | |
| span.style.backgroundColor = '#6366f150'; | |
| span.style.borderColor = '#6366f1'; | |
| } | |
| container.appendChild(span); | |
| }); | |
| this.probViz.appendChild(container); | |
| } | |
| delay(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| } | |
| // ======================================== | |
| // Parameter Sliders | |
| // ======================================== | |
| class ParameterSliders { | |
| constructor() { | |
| this.topkSlider = document.getElementById('topkSlider'); | |
| this.topkValue = document.getElementById('topkValue'); | |
| this.toppSlider = document.getElementById('toppSlider'); | |
| this.toppValue = document.getElementById('toppValue'); | |
| this.init(); | |
| } | |
| init() { | |
| this.topkSlider?.addEventListener('input', () => { | |
| this.topkValue.textContent = this.topkSlider.value; | |
| }); | |
| this.toppSlider?.addEventListener('input', () => { | |
| this.toppValue.textContent = (this.toppSlider.value / 100).toFixed(2); | |
| }); | |
| } | |
| } | |
| // ======================================== | |
| // RLHF Demo | |
| // ======================================== | |
| class RLHFDemo { | |
| constructor() { | |
| this.preferBtns = document.querySelectorAll('.prefer-btn'); | |
| this.rewardScores = document.getElementById('rewardScores'); | |
| this.scoreBarA = document.getElementById('scoreBarA'); | |
| this.scoreBarB = document.getElementById('scoreBarB'); | |
| this.scoreValueA = document.getElementById('scoreValueA'); | |
| this.scoreValueB = document.getElementById('scoreValueB'); | |
| this.init(); | |
| } | |
| init() { | |
| this.preferBtns.forEach(btn => { | |
| btn.addEventListener('click', () => this.showReward(btn.dataset.choice)); | |
| }); | |
| } | |
| showReward(choice) { | |
| // Simulate reward model scores | |
| const scoreA = 0.85; | |
| const scoreB = 0.42; | |
| // Highlight selected option | |
| document.querySelectorAll('.response-option').forEach(opt => { | |
| opt.classList.remove('selected'); | |
| }); | |
| document.getElementById(`response${choice}`).classList.add('selected'); | |
| // Show reward scores | |
| this.rewardScores.style.display = 'block'; | |
| // Animate bars | |
| setTimeout(() => { | |
| this.scoreBarA.style.width = `${scoreA * 100}%`; | |
| this.scoreBarB.style.width = `${scoreB * 100}%`; | |
| }, 100); | |
| } | |
| } | |
| // ======================================== | |
| // Benchmark Chart | |
| // ======================================== | |
| class BenchmarkChart { | |
| constructor() { | |
| this.select = document.getElementById('benchmarkSelect'); | |
| this.chart = document.getElementById('benchmarkChart'); | |
| this.data = { | |
| mmlu: { | |
| 'GPT-4': 86.4, | |
| 'Claude 3': 85.8, | |
| 'Gemini': 83.7, | |
| 'LLaMA 3': 79.5, | |
| 'Mistral': 73.2 | |
| }, | |
| hellaswag: { | |
| 'GPT-4': 95.3, | |
| 'Claude 3': 94.1, | |
| 'Gemini': 93.8, | |
| 'LLaMA 3': 91.2, | |
| 'Mistral': 88.7 | |
| }, | |
| humaneval: { | |
| 'GPT-4': 87.0, | |
| 'Claude 3': 84.9, | |
| 'Gemini': 74.4, | |
| 'LLaMA 3': 72.6, | |
| 'Mistral': 68.2 | |
| }, | |
| gsm8k: { | |
| 'GPT-4': 92.0, | |
| 'Claude 3': 88.1, | |
| 'Gemini': 86.5, | |
| 'LLaMA 3': 82.3, | |
| 'Mistral': 75.4 | |
| } | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.select?.addEventListener('change', () => this.render()); | |
| this.render(); | |
| } | |
| render() { | |
| const benchmark = this.select?.value || 'mmlu'; | |
| const data = this.data[benchmark]; | |
| this.chart.innerHTML = ''; | |
| const maxValue = Math.max(...Object.values(data)); | |
| Object.entries(data).forEach(([model, score]) => { | |
| const bar = document.createElement('div'); | |
| bar.className = 'chart-bar'; | |
| const fill = document.createElement('div'); | |
| fill.className = 'chart-bar-fill'; | |
| fill.style.height = `${(score / maxValue) * 220}px`; | |
| const label = document.createElement('div'); | |
| label.className = 'chart-bar-label'; | |
| label.textContent = model; | |
| const value = document.createElement('div'); | |
| value.className = 'chart-bar-value'; | |
| value.textContent = score.toFixed(1); | |
| bar.appendChild(value); | |
| bar.appendChild(fill); | |
| bar.appendChild(label); | |
| this.chart.appendChild(bar); | |
| }); | |
| } | |
| } | |
| // ======================================== | |
| // Chat Demo | |
| // ======================================== | |
| class ChatDemo { | |
| constructor() { | |
| this.systemPrompt = document.getElementById('systemPrompt'); | |
| this.chatWindow = document.getElementById('chatWindow'); | |
| this.chatInput = document.getElementById('chatInput'); | |
| this.sendBtn = document.getElementById('sendMessageBtn'); | |
| this.responses = { | |
| 'what is an llm': 'An LLM (Large Language Model) is an AI system trained on massive amounts of text data. It learns patterns in language and can generate human-like text, answer questions, and perform various language tasks.', | |
| 'transformer': 'Transformers are the architecture behind modern LLMs. They use self-attention mechanisms to process input sequences in parallel, allowing them to understand relationships between all parts of the input simultaneously.', | |
| 'attention': 'Attention is a mechanism that allows models to focus on relevant parts of the input when producing output. Self-attention computes relationships between all positions in a sequence using Query, Key, and Value projections.', | |
| 'tokenization': 'Tokenization breaks text into smaller units called tokens. Common methods include word-level, character-level, and subword tokenization (like BPE). This allows models to work with a manageable vocabulary size.', | |
| 'rlhf': 'RLHF (Reinforcement Learning from Human Feedback) aligns LLMs with human preferences. It involves collecting human comparisons, training a reward model, and optimizing the LLM using reinforcement learning (typically PPO).', | |
| 'fine-tuning': 'Fine-tuning adapts a pre-trained model to specific tasks or domains. Supervised Fine-Tuning (SFT) uses labeled examples, while RLHF further aligns the model with human preferences.', | |
| 'default': 'That\'s an interesting question about LLMs! The field is evolving rapidly. Key concepts include pre-training on large datasets, transformer architecture, tokenization, and alignment techniques like RLHF. What specific aspect would you like to explore?' | |
| }; | |
| this.init(); | |
| } | |
| init() { | |
| this.sendBtn?.addEventListener('click', () => this.sendMessage()); | |
| this.chatInput?.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.sendMessage(); | |
| }); | |
| } | |
| sendMessage() { | |
| const message = this.chatInput.value.trim(); | |
| if (!message) return; | |
| // Add user message | |
| this.addMessage(message, 'user'); | |
| this.chatInput.value = ''; | |
| // Simulate thinking delay | |
| setTimeout(() => { | |
| const response = this.getResponse(message); | |
| this.addMessage(response, 'assistant'); | |
| }, 500 + Math.random() * 500); | |
| } | |
| addMessage(content, role) { | |
| const messageDiv = document.createElement('div'); | |
| messageDiv.className = `chat-message ${role}`; | |
| const avatar = document.createElement('div'); | |
| avatar.className = 'message-avatar'; | |
| avatar.innerHTML = role === 'user' ? '<i class="fas fa-user"></i>' : '<i class="fas fa-robot"></i>'; | |
| const contentDiv = document.createElement('div'); | |
| contentDiv.className = 'message-content'; | |
| contentDiv.textContent = content; | |
| messageDiv.appendChild(avatar); | |
| messageDiv.appendChild(contentDiv); | |
| this.chatWindow.appendChild(messageDiv); | |
| this.chatWindow.scrollTop = this.chatWindow.scrollHeight; | |
| } | |
| getResponse(message) { | |
| const lower = message.toLowerCase(); | |
| for (const [key, response] of Object.entries(this.responses)) { | |
| if (lower.includes(key)) { | |
| return response; | |
| } | |
| } | |
| return this.responses['default']; | |
| } | |
| } | |
| // ======================================== | |
| // Neural Network Animation | |
| // ======================================== | |
| class NeuralNetworkAnimation { | |
| constructor() { | |
| this.canvas = document.getElementById('neuralNetworkCanvas'); | |
| this.nodes = []; | |
| this.connections = []; | |
| this.init(); | |
| } | |
| init() { | |
| if (!this.canvas) return; | |
| // Create SVG container | |
| const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | |
| svg.setAttribute('width', '100%'); | |
| svg.setAttribute('height', '100%'); | |
| svg.setAttribute('viewBox', '0 0 400 400'); | |
| this.canvas.appendChild(svg); | |
| // Create network structure | |
| const layers = [4, 6, 6, 4]; | |
| let x = 60; | |
| layers.forEach((nodeCount, layerIndex) => { | |
| const layerNodes = []; | |
| const yStep = 300 / (nodeCount + 1); | |
| for (let i = 0; i < nodeCount; i++) { | |
| const y = 50 + yStep * (i + 1); | |
| const node = this.createNode(svg, x, y); | |
| layerNodes.push({ element: node, x, y }); | |
| } | |
| // Connect to previous layer | |
| if (this.nodes.length > 0) { | |
| const prevLayer = this.nodes[this.nodes.length - 1]; | |
| prevLayer.forEach(prevNode => { | |
| layerNodes.forEach(currNode => { | |
| const line = this.createConnection(svg, prevNode, currNode); | |
| this.connections.push(line); | |
| }); | |
| }); | |
| } | |
| this.nodes.push(layerNodes); | |
| x += 100; | |
| }); | |
| // Animate | |
| this.animate(); | |
| } | |
| createNode(svg, x, y) { | |
| const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); | |
| circle.setAttribute('cx', x); | |
| circle.setAttribute('cy', y); | |
| circle.setAttribute('r', '8'); | |
| circle.setAttribute('fill', '#6366f1'); | |
| circle.setAttribute('opacity', '0.7'); | |
| svg.appendChild(circle); | |
| return circle; | |
| } | |
| createConnection(svg, from, to) { | |
| const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); | |
| line.setAttribute('x1', from.x); | |
| line.setAttribute('y1', from.y); | |
| line.setAttribute('x2', to.x); | |
| line.setAttribute('y2', to.y); | |
| line.setAttribute('stroke', '#6366f1'); | |
| line.setAttribute('stroke-width', '1'); | |
| line.setAttribute('opacity', '0.2'); | |
| svg.insertBefore(line, svg.firstChild); | |
| return line; | |
| } | |
| animate() { | |
| let index = 0; | |
| setInterval(() => { | |
| // Pulse random connections | |
| const connection = this.connections[index % this.connections.length]; | |
| connection.setAttribute('opacity', '0.8'); | |
| connection.setAttribute('stroke-width', '2'); | |
| setTimeout(() => { | |
| connection.setAttribute('opacity', '0.2'); | |
| connection.setAttribute('stroke-width', '1'); | |
| }, 200); | |
| index++; | |
| }, 50); | |
| } | |
| } | |
| // ======================================== | |
| // Playground Cards | |
| // ======================================== | |
| class PlaygroundCards { | |
| constructor() { | |
| this.cards = document.querySelectorAll('.playground-card'); | |
| this.init(); | |
| } | |
| init() { | |
| this.cards.forEach(card => { | |
| card.addEventListener('click', () => { | |
| const demo = card.dataset.demo; | |
| this.scrollToDemo(demo); | |
| }); | |
| }); | |
| } | |
| scrollToDemo(demo) { | |
| const demoMap = { | |
| 'tokenizer': 'tokenizeInput', | |
| 'attention': 'attentionInput', | |
| 'generation': 'generationPrompt', | |
| 'embeddings': 'overview' | |
| }; | |
| const target = document.getElementById(demoMap[demo]); | |
| if (target) { | |
| target.scrollIntoView({ behavior: 'smooth', block: 'center' }); | |
| target.focus(); | |
| } | |
| } | |
| } | |
| // ======================================== | |
| // Initialize Everything | |
| // ======================================== | |
| document.addEventListener('DOMContentLoaded', () => { | |
| // Core functionality | |
| new ThemeManager(); | |
| new Navigation(); | |
| new SectionAnimator(); | |
| new TabSystem(); | |
| // Interactive demos | |
| new DataCleaningDemo(); | |
| new TokenizationDemo(); | |
| new AttentionVisualization(); | |
| new TextGenerationDemo(); | |
| new ParameterSliders(); | |
| new RLHFDemo(); | |
| new BenchmarkChart(); | |
| new ChatDemo(); | |
| // Visual elements | |
| new NeuralNetworkAnimation(); | |
| new PlaygroundCards(); | |
| console.log('🚀 LLM Playground initialized!'); | |
| }); | |