index.html•16.4 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Adaptive Recommendations - AgentDB</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 2rem;
}
.container { max-width: 1200px; margin: 0 auto; }
header {
background: white;
border-radius: 12px;
padding: 2rem;
margin-bottom: 2rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
h1 { color: #333; margin-bottom: 0.5rem; }
.subtitle { color: #666; }
.grid { display: grid; grid-template-columns: 2fr 1fr; gap: 2rem; }
.card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.card h2 { color: #333; margin-bottom: 1rem; }
.content-card {
background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%);
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.content-card:hover {
border-color: #667eea;
transform: translateY(-2px);
}
.content-card.shown { border-color: #28a745; background: #d4edda22; }
.content-header {
display: flex;
justify-content: space-between;
align-items: start;
margin-bottom: 0.75rem;
}
.content-title {
font-weight: 600;
color: #333;
font-size: 1.1rem;
}
.content-score {
background: #667eea;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 600;
}
.content-description {
color: #666;
line-height: 1.6;
margin-bottom: 0.75rem;
}
.content-tags {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
background: #f0f0f0;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.85rem;
color: #666;
}
.feedback-buttons {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.feedback-btn {
flex: 1;
padding: 0.5rem;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.feedback-btn.like {
background: #28a745;
color: white;
}
.feedback-btn.dislike {
background: #dc3545;
color: white;
}
.feedback-btn:hover { opacity: 0.8; }
.stat-card {
background: #f8f9fa;
border-radius: 6px;
padding: 1rem;
margin-bottom: 0.5rem;
}
.stat-label { color: #666; font-size: 0.9rem; }
.stat-value {
color: #333;
font-size: 1.5rem;
font-weight: bold;
margin-top: 0.25rem;
}
.algorithm-info {
background: #e3f2fd;
border-left: 4px solid #2196f3;
padding: 1rem;
border-radius: 4px;
margin-top: 1rem;
}
.algorithm-info h3 {
color: #2196f3;
margin-bottom: 0.5rem;
font-size: 1rem;
}
.algorithm-info p {
color: #666;
font-size: 0.9rem;
line-height: 1.5;
}
.progress-indicator {
margin-top: 1rem;
}
.progress-label {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
font-size: 0.9rem;
color: #666;
}
.progress-bar {
width: 100%;
height: 8px;
background: #e0e0e0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🎨 Adaptive Recommendation System</h1>
<p class="subtitle">Multi-Armed Bandit with Thompson Sampling - Real-time Learning</p>
</header>
<div class="grid">
<div class="card">
<h2>Content Feed</h2>
<p style="color: #666; margin-bottom: 1rem;">
Click on content to view. Like or dislike to help the system learn your preferences!
</p>
<div id="contentFeed"></div>
</div>
<div>
<div class="card" style="margin-bottom: 1rem;">
<h2>Learning Stats</h2>
<div class="stat-card">
<div class="stat-label">Content Viewed</div>
<div class="stat-value" id="viewedCount">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Positive Feedback</div>
<div class="stat-value" id="likeCount">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Learning Rate</div>
<div class="stat-value" id="learningRate">0%</div>
</div>
<div class="stat-card">
<div class="stat-label">Exploration Rate</div>
<div class="stat-value" id="exploreRate">50%</div>
</div>
</div>
<div class="card">
<h2>Category Preferences</h2>
<div id="categoryStats"></div>
</div>
</div>
</div>
<div class="card" style="margin-top: 2rem;">
<h2>How It Works</h2>
<div class="algorithm-info">
<h3>🎰 Multi-Armed Bandit (Thompson Sampling)</h3>
<p>
This system uses Thompson Sampling to balance exploration (trying new content categories)
and exploitation (showing categories you've liked before). It maintains a Beta distribution
for each category and samples from it to decide what to recommend next. Your feedback
updates the distributions in real-time, making recommendations increasingly personalized.
</p>
</div>
</div>
</div>
<script type="module">
const CONTENT_POOL = [
{ id: 1, category: 'tech', title: 'Latest AI Breakthroughs', description: 'Discover cutting-edge developments in artificial intelligence', tags: ['AI', 'Research', 'Innovation'] },
{ id: 2, category: 'tech', title: 'Cloud Computing Guide', description: 'Complete guide to modern cloud architecture', tags: ['Cloud', 'DevOps', 'Architecture'] },
{ id: 3, category: 'tech', title: 'Quantum Computing 101', description: 'Introduction to quantum computing principles', tags: ['Quantum', 'Physics', 'Computing'] },
{ id: 4, category: 'business', title: 'Startup Success Stories', description: 'Learn from founders who built billion-dollar companies', tags: ['Startup', 'Entrepreneurship'] },
{ id: 5, category: 'business', title: 'Marketing Strategies 2024', description: 'Latest trends in digital marketing', tags: ['Marketing', 'Strategy', 'Growth'] },
{ id: 6, category: 'business', title: 'Leadership Best Practices', description: 'How to build and lead high-performing teams', tags: ['Leadership', 'Management'] },
{ id: 7, category: 'science', title: 'Space Exploration Update', description: 'Recent discoveries from Mars missions', tags: ['Space', 'Mars', 'Exploration'] },
{ id: 8, category: 'science', title: 'Climate Change Research', description: 'Latest findings on climate science', tags: ['Climate', 'Environment', 'Research'] },
{ id: 9, category: 'science', title: 'Neuroscience Insights', description: 'How the brain processes information', tags: ['Neuroscience', 'Brain', 'Psychology'] },
{ id: 10, category: 'lifestyle', title: 'Healthy Living Tips', description: 'Science-backed health and wellness advice', tags: ['Health', 'Wellness', 'Fitness'] },
{ id: 11, category: 'lifestyle', title: 'Travel Destinations 2024', description: 'Must-visit places around the world', tags: ['Travel', 'Adventure', 'Culture'] },
{ id: 12, category: 'lifestyle', title: 'Mindfulness Practices', description: 'Meditation and stress reduction techniques', tags: ['Mindfulness', 'Mental Health'] },
];
let viewedContent = new Set();
let categoryBandits = {
tech: { alpha: 1, beta: 1 },
business: { alpha: 1, beta: 1 },
science: { alpha: 1, beta: 1 },
lifestyle: { alpha: 1, beta: 1 }
};
let feedbackHistory = [];
function betaSample(alpha, beta) {
// Approximate Beta distribution sampling using gamma distributions
const x = gammaSample(alpha, 1);
const y = gammaSample(beta, 1);
return x / (x + y);
}
function gammaSample(shape, scale) {
// Marsaglia and Tsang method for gamma sampling
if (shape < 1) {
return gammaSample(shape + 1, scale) * Math.pow(Math.random(), 1 / shape);
}
const d = shape - 1/3;
const c = 1 / Math.sqrt(9 * d);
while (true) {
let x, v;
do {
x = normalSample(0, 1);
v = 1 + c * x;
} while (v <= 0);
v = v * v * v;
x = x * x;
const u = Math.random();
if (u < 1 - 0.0331 * x * x) {
return scale * d * v;
}
if (Math.log(u) < 0.5 * x + d * (1 - v + Math.log(v))) {
return scale * d * v;
}
}
}
function normalSample(mean, variance) {
// Box-Muller transform
const u1 = Math.random();
const u2 = Math.random();
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
return mean + Math.sqrt(variance) * z;
}
function selectCategory() {
const samples = {};
Object.entries(categoryBandits).forEach(([category, { alpha, beta }]) => {
samples[category] = betaSample(alpha, beta);
});
return Object.entries(samples).reduce((a, b) => a[1] > b[1] ? a : b)[0];
}
function getRandomContent(category) {
const available = CONTENT_POOL.filter(c => c.category === category && !viewedContent.has(c.id));
if (available.length === 0) {
viewedContent.clear();
return getRandomContent(category);
}
return available[Math.floor(Math.random() * available.length)];
}
function renderFeed() {
const feed = document.getElementById('contentFeed');
const category = selectCategory();
const content = getRandomContent(category);
const card = document.createElement('div');
card.className = 'content-card';
card.innerHTML = `
<div class="content-header">
<div class="content-title">${content.title}</div>
<div class="content-score">${category}</div>
</div>
<div class="content-description">${content.description}</div>
<div class="content-tags">
${content.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
</div>
<div class="feedback-buttons">
<button class="feedback-btn like">👍 Like</button>
<button class="feedback-btn dislike">👎 Not Interested</button>
</div>
`;
card.querySelector('.like').addEventListener('click', (e) => {
e.stopPropagation();
handleFeedback(content, true);
card.classList.add('shown');
setTimeout(renderFeed, 500);
});
card.querySelector('.dislike').addEventListener('click', (e) => {
e.stopPropagation();
handleFeedback(content, false);
card.classList.add('shown');
setTimeout(renderFeed, 500);
});
feed.insertBefore(card, feed.firstChild);
while (feed.children.length > 5) {
feed.removeChild(feed.lastChild);
}
viewedContent.add(content.id);
updateStats();
}
function handleFeedback(content, liked) {
feedbackHistory.push({
content: content,
liked: liked,
timestamp: Date.now()
});
// Update bandit
if (liked) {
categoryBandits[content.category].alpha += 1;
} else {
categoryBandits[content.category].beta += 1;
}
updateStats();
renderCategoryStats();
}
function updateStats() {
document.getElementById('viewedCount').textContent = feedbackHistory.length;
const likes = feedbackHistory.filter(f => f.liked).length;
document.getElementById('likeCount').textContent = likes;
if (feedbackHistory.length > 0) {
const rate = (likes / feedbackHistory.length * 100).toFixed(0);
document.getElementById('learningRate').textContent = `${rate}%`;
}
// Calculate exploration rate (entropy of category distribution)
const totalFeedback = Object.values(categoryBandits).reduce((sum, b) => sum + b.alpha + b.beta, 0);
const entropy = Object.values(categoryBandits).reduce((sum, b) => {
const prob = (b.alpha + b.beta) / totalFeedback;
return sum - (prob > 0 ? prob * Math.log2(prob) : 0);
}, 0);
const maxEntropy = Math.log2(Object.keys(categoryBandits).length);
const exploreRate = ((entropy / maxEntropy) * 100).toFixed(0);
document.getElementById('exploreRate').textContent = `${exploreRate}%`;
}
function renderCategoryStats() {
const container = document.getElementById('categoryStats');
container.innerHTML = Object.entries(categoryBandits)
.map(([category, { alpha, beta }]) => {
const total = alpha + beta;
const mean = alpha / total;
const percentage = (mean * 100).toFixed(0);
return `
<div class="progress-indicator">
<div class="progress-label">
<span>${category.charAt(0).toUpperCase() + category.slice(1)}</span>
<span>${percentage}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" style="width: ${percentage}%"></div>
</div>
</div>
`;
}).join('');
}
// Initialize
renderFeed();
renderCategoryStats();
</script>
</body>
</html>