File size: 4,930 Bytes
6fbbbb9
afd8fb2
6fbbbb9
afd8fb2
 
 
 
 
 
 
 
 
 
 
 
 
cedc3d6
afd8fb2
 
 
 
 
 
 
 
 
 
 
 
 
cedc3d6
afd8fb2
 
 
 
 
 
 
 
 
 
 
 
cedc3d6
afd8fb2
 
 
 
 
 
 
 
 
 
 
cedc3d6
afd8fb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d190577
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<!DOCTYPE html>
<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>