Spaces:
Running
Running
| <html lang="en" class="h-full bg-gray-950 text-gray-100"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RAG Visualizer • Browser RAG</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2"></script> | |
| <!-- Add other CDNs if needed: e.g., for charts or flow --> | |
| <style> | |
| /* Custom styles for highlights, nodes, etc. */ | |
| .vector-row { transition: all 0.3s; } | |
| .highlight { animation: pulse 1.5s; background-color: #4f46e5; } | |
| @keyframes pulse { 0%,100% {opacity:1} 50% {opacity:0.7} } | |
| .node { border: 2px solid #6366f1; } | |
| </style> | |
| </head> | |
| <body class="h-screen flex flex-col overflow-hidden"> | |
| <header class="bg-gray-900 p-4 border-b border-gray-700 flex justify-between"> | |
| <h1 class="text-2xl font-bold">Browser RAG Visualizer</h1> | |
| <div id="status" class="text-sm text-green-400">Models loading...</div> | |
| </header> | |
| <div class="flex flex-1 overflow-hidden"> | |
| <!-- Left: Chat --> | |
| <div class="w-1/3 border-r border-gray-700 flex flex-col"> | |
| <div id="chat" class="flex-1 p-4 overflow-y-auto space-y-4"></div> | |
| <div class="p-4 border-t border-gray-700"> | |
| <input id="chatInput" type="text" class="w-full bg-gray-800 p-3 rounded" placeholder="Ask a question..."> | |
| </div> | |
| </div> | |
| <!-- Main Area --> | |
| <div class="flex-1 flex flex-col"> | |
| <!-- Node Flow --> | |
| <div class="h-1/3 border-b border-gray-700 p-4 overflow-auto" id="flow"> | |
| <!-- Simplified nodes: draggable divs or SVG --> | |
| <div class="flex gap-4"> | |
| <div class="node p-4 rounded bg-gray-800 min-w-32">Embed</div> | |
| <div class="node p-4 rounded bg-gray-800 min-w-32">Store (VectorDB)</div> | |
| <div class="node p-4 rounded bg-gray-800 min-w-32">Retrieve Top-K</div> | |
| <div class="node p-4 rounded bg-gray-800 min-w-32">Rerank</div> | |
| <div class="node p-4 rounded bg-gray-800 min-w-32">Generate</div> | |
| </div> | |
| </div> | |
| <!-- Vector Table & Controls --> | |
| <div class="flex-1 p-4 overflow-auto"> | |
| <h2 class="text-lg mb-2">Vector Database</h2> | |
| <button onclick="addEntry()" class="bg-indigo-600 px-4 py-2 rounded mb-4">Add Entry</button> | |
| <table class="w-full" id="vectorTable"> | |
| <thead><tr><th>Text</th><th>Metadata</th><th>Date</th></tr></thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <!-- Right: Details --> | |
| <div class="w-1/4 border-l border-gray-700 p-4 overflow-y-auto"> | |
| <h2>Top-K / Context</h2> | |
| <div id="topk"></div> | |
| <h2 class="mt-6">Reranking</h2> | |
| <div id="rerank"></div> | |
| </div> | |
| </div> | |
| <script> | |
| // Global state | |
| let vectors = []; // {id, text, embedding, metadata, date} | |
| let embedder, generator, reranker; | |
| async function initModels() { | |
| const status = document.getElementById('status'); | |
| status.textContent = 'Loading embedding model...'; | |
| embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { device: 'webgpu' }); // or 'wasm' | |
| status.textContent = 'Loading LLM...'; | |
| // generator = await pipeline('text-generation', 'Xenova/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4' }); | |
| status.textContent = 'Ready!'; | |
| } | |
| async function getEmbedding(text) { | |
| const output = await embedder(text, { pooling: 'mean', normalize: true }); | |
| return Array.from(output.data); | |
| } | |
| async function addEntry() { | |
| const text = prompt("Enter text to add:"); | |
| if (!text) return; | |
| const emb = await getEmbedding(text); | |
| vectors.push({ | |
| id: Date.now(), | |
| text, | |
| embedding: emb, | |
| metadata: { source: "user" }, | |
| date: new Date().toISOString() | |
| }); | |
| renderTable(); | |
| // Animate node | |
| } | |
| function cosineSimilarity(a, b) { | |
| // Simple implementation | |
| let dot = 0, magA = 0, magB = 0; | |
| for (let i = 0; i < a.length; i++) { | |
| dot += a[i] * b[i]; | |
| magA += a[i] ** 2; | |
| magB += b[i] ** 2; | |
| } | |
| return dot / (Math.sqrt(magA) * Math.sqrt(magB)); | |
| } | |
| async function search(query, k=5) { | |
| const qEmb = await getEmbedding(query); | |
| const scored = vectors.map(v => ({ | |
| ...v, | |
| score: cosineSimilarity(qEmb, v.embedding) | |
| })).sort((a,b) => b.score - a.score).slice(0,k); | |
| // Highlight in table + show topk | |
| renderTopK(scored); | |
| // Trigger rerank, etc. | |
| return scored; | |
| } | |
| function renderTable() { | |
| // Populate tbody with rows, add click handlers | |
| } | |
| // Chat handler: on submit -> search -> (rerank) -> context -> generate -> append to chat | |
| // Init | |
| window.onload = () => { | |
| initModels(); | |
| // Tailwind script already loaded | |
| }; | |
| </script> | |
| </body> | |
| </html> |