index.html•21.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RAG Self-Learning Example - 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;
}
.main-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 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;
}
.query-section {
margin-bottom: 1.5rem;
}
textarea, input[type="text"] {
width: 100%;
padding: 0.75rem;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-family: inherit;
font-size: 1rem;
resize: vertical;
}
textarea:focus, input[type="text"]:focus {
outline: none;
border-color: #667eea;
}
.btn {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
font-size: 1rem;
transition: opacity 0.3s ease;
}
.btn:hover {
opacity: 0.9;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-group {
display: flex;
gap: 1rem;
margin-top: 1rem;
}
.response-area {
background: #f8f9fa;
border-radius: 6px;
padding: 1rem;
min-height: 200px;
margin-top: 1rem;
white-space: pre-wrap;
line-height: 1.6;
}
.stat-card {
background: #f8f9fa;
border-radius: 6px;
padding: 1rem;
margin-bottom: 1rem;
}
.stat-label {
color: #666;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.stat-value {
color: #333;
font-size: 1.5rem;
font-weight: bold;
}
.document-list {
max-height: 300px;
overflow-y: auto;
}
.document-item {
background: #f8f9fa;
border-radius: 6px;
padding: 1rem;
margin-bottom: 0.5rem;
cursor: pointer;
transition: background 0.2s ease;
}
.document-item:hover {
background: #e9ecef;
}
.document-title {
font-weight: 600;
color: #333;
margin-bottom: 0.25rem;
}
.document-preview {
color: #666;
font-size: 0.9rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.learning-indicator {
background: #d4edda;
color: #155724;
padding: 0.5rem 1rem;
border-radius: 6px;
margin-top: 1rem;
display: none;
}
.learning-indicator.show {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.feedback-buttons {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
.feedback-btn {
padding: 0.5rem 1rem;
border: 2px solid #e0e0e0;
background: white;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
}
.feedback-btn:hover {
border-color: #667eea;
background: #f8f9fa;
}
.feedback-btn.positive {
border-color: #28a745;
color: #28a745;
}
.feedback-btn.negative {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>📚 RAG Self-Learning System</h1>
<p class="subtitle">Retrieval-Augmented Generation with Continuous Learning</p>
</header>
<div class="main-grid">
<div class="card">
<h2>Query Interface</h2>
<div class="query-section">
<label for="query"><strong>Ask a Question:</strong></label>
<textarea id="query" rows="3" placeholder="Enter your question here..."></textarea>
</div>
<div class="btn-group">
<button class="btn" id="submitQuery">Submit Query</button>
<button class="btn" id="clearBtn">Clear</button>
</div>
<div class="response-area" id="response">No response yet. Try asking a question!</div>
<div class="feedback-buttons" id="feedbackButtons" style="display: none;">
<button class="feedback-btn" id="positiveBtn">👍 Helpful</button>
<button class="feedback-btn" id="negativeBtn">👎 Not Helpful</button>
</div>
<div class="learning-indicator" id="learningIndicator">
✨ System learned from your feedback!
</div>
</div>
<div>
<div class="card">
<h2>System Stats</h2>
<div class="stat-card">
<div class="stat-label">Total Documents</div>
<div class="stat-value" id="docCount">0</div>
</div>
<div class="stat-card">
<div class="stat-label">Queries Processed</div>
<div class="stat-value" id="queryCount">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="btn-group">
<button class="btn" id="exportBtn">Export Data</button>
<button class="btn" id="resetBtn">Reset</button>
</div>
</div>
<div class="card" style="margin-top: 2rem;">
<h2>Add Document</h2>
<input type="text" id="docTitle" placeholder="Document title..." style="margin-bottom: 0.5rem;">
<textarea id="docContent" rows="4" placeholder="Document content..."></textarea>
<button class="btn" id="addDocBtn" style="margin-top: 0.5rem;">Add to Knowledge Base</button>
</div>
</div>
</div>
<div class="card">
<h2>Knowledge Base</h2>
<div class="document-list" id="documentList">
<p style="color: #666; text-align: center; padding: 2rem;">No documents yet. Add some knowledge!</p>
</div>
</div>
</div>
<script type="module">
/**
* RAG Self-Learning Example
*
* This demonstrates a Retrieval-Augmented Generation system that:
* 1. Stores documents as vector embeddings
* 2. Retrieves relevant documents for queries
* 3. Learns from user feedback to improve retrieval
* 4. Adapts query understanding based on interaction patterns
*/
// Initialize AgentDB with WASM backend
let db = null;
let queryHistory = [];
let feedbackData = [];
let currentQueryId = null;
async function initDB() {
// In a real implementation, import AgentDB WASM
// For this demo, we'll simulate the behavior
console.log('Initializing AgentDB WASM backend...');
// Simulated database
db = {
documents: [],
queries: [],
patterns: [],
async addDocument(doc) {
// Simulate embedding generation
const embedding = generateEmbedding(doc.content);
this.documents.push({
id: Date.now().toString(),
title: doc.title,
content: doc.content,
embedding: embedding,
timestamp: Date.now()
});
updateStats();
},
async search(query, k = 5) {
// Simulate vector search
const queryEmbedding = generateEmbedding(query);
// Calculate similarity scores
const results = this.documents.map(doc => ({
...doc,
score: cosineSimilarity(queryEmbedding, doc.embedding)
}));
// Sort by score and return top k
return results
.sort((a, b) => b.score - a.score)
.slice(0, k);
},
async storeQueryPattern(query, results, feedback) {
// Store query pattern for learning
this.patterns.push({
query: query,
queryEmbedding: generateEmbedding(query),
results: results,
feedback: feedback,
timestamp: Date.now()
});
// Learn from feedback to adjust future retrievals
if (feedback === 'positive') {
this.learnFromSuccess(query, results);
} else if (feedback === 'negative') {
this.learnFromFailure(query, results);
}
},
learnFromSuccess(query, results) {
// Reinforce successful query patterns
console.log('Learning from successful query:', query);
// In real implementation: update weights, boost similar document rankings
},
learnFromFailure(query, results) {
// Learn from unsuccessful queries
console.log('Learning from unsuccessful query:', query);
// In real implementation: penalize similar patterns, explore alternatives
}
};
// Load sample documents
await loadSampleDocuments();
updateStats();
}
async function loadSampleDocuments() {
const samples = [
{
title: "What is AgentDB?",
content: "AgentDB is an ultra-fast agent memory and vector database for AI agents. It works in Node.js and browsers with WASM support, providing blazing-fast vector search and persistent reasoning patterns."
},
{
title: "ReasoningBank Overview",
content: "ReasoningBank is a built-in memory and learning system for AI agents. It includes PatternMatcher for storing reasoning patterns, ExperienceCurator for managing task experiences, and MemoryOptimizer for efficient long-term storage."
},
{
title: "HNSW Index",
content: "The Hierarchical Navigable Small World (HNSW) index provides 12x faster search with 97% recall accuracy. It uses a graph-based structure for approximate nearest neighbor search at scale."
}
];
for (const doc of samples) {
await db.addDocument(doc);
}
}
function generateEmbedding(text) {
// Simulate embedding generation (in real app, use OpenAI/Cohere/local model)
// Simple bag-of-words hash for demo
const words = text.toLowerCase().split(/\s+/);
const embedding = new Array(384).fill(0); // Simulate 384-dim embedding
words.forEach((word, i) => {
const hash = simpleHash(word);
for (let j = 0; j < embedding.length; j++) {
embedding[j] += Math.sin(hash * (j + 1)) * 0.1;
}
});
// Normalize
const norm = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
return embedding.map(val => val / norm);
}
function simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash = hash & hash;
}
return hash;
}
function cosineSimilarity(a, b) {
let dot = 0, normA = 0, normB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}
async function handleQuery() {
const query = document.getElementById('query').value.trim();
if (!query) return;
const responseArea = document.getElementById('response');
responseArea.textContent = 'Searching knowledge base...';
// Search for relevant documents
const results = await db.search(query, 3);
// Generate response from retrieved documents
let response = `Found ${results.length} relevant documents:\n\n`;
if (results.length > 0) {
results.forEach((doc, i) => {
response += `${i + 1}. ${doc.title} (${(doc.score * 100).toFixed(1)}% match)\n`;
response += ` ${doc.content.substring(0, 150)}...\n\n`;
});
} else {
response = "No relevant documents found. Try adding more content to the knowledge base!";
}
responseArea.textContent = response;
// Store query for feedback learning
currentQueryId = Date.now().toString();
queryHistory.push({
id: currentQueryId,
query: query,
results: results,
timestamp: Date.now()
});
// Show feedback buttons
document.getElementById('feedbackButtons').style.display = 'flex';
// Update stats
document.getElementById('queryCount').textContent = queryHistory.length;
}
function handleFeedback(isPositive) {
if (!currentQueryId) return;
const query = queryHistory.find(q => q.id === currentQueryId);
if (!query) return;
const feedback = isPositive ? 'positive' : 'negative';
// Store feedback for learning
db.storeQueryPattern(query.query, query.results, feedback);
feedbackData.push({
queryId: currentQueryId,
feedback: feedback,
timestamp: Date.now()
});
// Show learning indicator
const indicator = document.getElementById('learningIndicator');
indicator.classList.add('show');
setTimeout(() => indicator.classList.remove('show'), 3000);
// Hide feedback buttons
document.getElementById('feedbackButtons').style.display = 'none';
currentQueryId = null;
// Update learning rate
updateStats();
}
async function addDocument() {
const title = document.getElementById('docTitle').value.trim();
const content = document.getElementById('docContent').value.trim();
if (!title || !content) {
alert('Please provide both title and content');
return;
}
await db.addDocument({ title, content });
// Clear inputs
document.getElementById('docTitle').value = '';
document.getElementById('docContent').value = '';
// Update document list
renderDocuments();
}
function renderDocuments() {
const listEl = document.getElementById('documentList');
if (db.documents.length === 0) {
listEl.innerHTML = '<p style="color: #666; text-align: center; padding: 2rem;">No documents yet. Add some knowledge!</p>';
return;
}
listEl.innerHTML = db.documents.map(doc => `
<div class="document-item">
<div class="document-title">${doc.title}</div>
<div class="document-preview">${doc.content}</div>
</div>
`).join('');
}
function updateStats() {
document.getElementById('docCount').textContent = db.documents.length;
document.getElementById('queryCount').textContent = queryHistory.length;
// Calculate learning rate (positive feedback ratio)
if (feedbackData.length > 0) {
const positiveCount = feedbackData.filter(f => f.feedback === 'positive').length;
const rate = (positiveCount / feedbackData.length * 100).toFixed(0);
document.getElementById('learningRate').textContent = `${rate}%`;
}
renderDocuments();
}
function exportData() {
const data = {
documents: db.documents,
queries: queryHistory,
feedback: feedbackData,
patterns: db.patterns,
exportDate: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `rag-data-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
function reset() {
if (confirm('Are you sure you want to reset all data?')) {
queryHistory = [];
feedbackData = [];
currentQueryId = null;
db.documents = [];
db.queries = [];
db.patterns = [];
document.getElementById('query').value = '';
document.getElementById('response').textContent = 'No response yet. Try asking a question!';
document.getElementById('feedbackButtons').style.display = 'none';
loadSampleDocuments();
updateStats();
}
}
// Event listeners
document.getElementById('submitQuery').addEventListener('click', handleQuery);
document.getElementById('clearBtn').addEventListener('click', () => {
document.getElementById('query').value = '';
document.getElementById('response').textContent = 'No response yet. Try asking a question!';
document.getElementById('feedbackButtons').style.display = 'none';
});
document.getElementById('positiveBtn').addEventListener('click', () => handleFeedback(true));
document.getElementById('negativeBtn').addEventListener('click', () => handleFeedback(false));
document.getElementById('addDocBtn').addEventListener('click', addDocument);
document.getElementById('exportBtn').addEventListener('click', exportData);
document.getElementById('resetBtn').addEventListener('click', reset);
// Enter key to submit
document.getElementById('query').addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleQuery();
}
});
// Initialize on load
initDB();
</script>
</body>
</html>