quickgrid commited on
Commit
ff3a191
·
verified ·
1 Parent(s): afd8fb2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1890 -114
index.html CHANGED
@@ -1,140 +1,1916 @@
1
  <!DOCTYPE html>
2
- <html lang="en" class="h-full bg-gray-950 text-gray-100">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>RAG Visualizer Browser RAG</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdn.jsdelivr.net/npm/@xenova/transformers@2.17.2"></script>
9
- <!-- Add other CDNs if needed: e.g., for charts or flow -->
10
- <style>
11
- /* Custom styles for highlights, nodes, etc. */
12
- .vector-row { transition: all 0.3s; }
13
- .highlight { animation: pulse 1.5s; background-color: #4f46e5; }
14
- @keyframes pulse { 0%,100% {opacity:1} 50% {opacity:0.7} }
15
- .node { border: 2px solid #6366f1; }
16
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </head>
18
- <body class="h-screen flex flex-col overflow-hidden">
19
- <header class="bg-gray-900 p-4 border-b border-gray-700 flex justify-between">
20
- <h1 class="text-2xl font-bold">Browser RAG Visualizer</h1>
21
- <div id="status" class="text-sm text-green-400">Models loading...</div>
22
- </header>
23
-
24
- <div class="flex flex-1 overflow-hidden">
25
- <!-- Left: Chat -->
26
- <div class="w-1/3 border-r border-gray-700 flex flex-col">
27
- <div id="chat" class="flex-1 p-4 overflow-y-auto space-y-4"></div>
28
- <div class="p-4 border-t border-gray-700">
29
- <input id="chatInput" type="text" class="w-full bg-gray-800 p-3 rounded" placeholder="Ask a question...">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  </div>
31
  </div>
32
-
33
- <!-- Main Area -->
34
- <div class="flex-1 flex flex-col">
35
- <!-- Node Flow -->
36
- <div class="h-1/3 border-b border-gray-700 p-4 overflow-auto" id="flow">
37
- <!-- Simplified nodes: draggable divs or SVG -->
38
- <div class="flex gap-4">
39
- <div class="node p-4 rounded bg-gray-800 min-w-32">Embed</div>
40
- <div class="node p-4 rounded bg-gray-800 min-w-32">Store (VectorDB)</div>
41
- <div class="node p-4 rounded bg-gray-800 min-w-32">Retrieve Top-K</div>
42
- <div class="node p-4 rounded bg-gray-800 min-w-32">Rerank</div>
43
- <div class="node p-4 rounded bg-gray-800 min-w-32">Generate</div>
44
- </div>
45
  </div>
46
-
47
- <!-- Vector Table & Controls -->
48
- <div class="flex-1 p-4 overflow-auto">
49
- <h2 class="text-lg mb-2">Vector Database</h2>
50
- <button onclick="addEntry()" class="bg-indigo-600 px-4 py-2 rounded mb-4">Add Entry</button>
51
- <table class="w-full" id="vectorTable">
52
- <thead><tr><th>Text</th><th>Metadata</th><th>Date</th></tr></thead>
53
- <tbody></tbody>
54
- </table>
 
 
 
 
 
 
 
 
 
 
 
55
  </div>
56
  </div>
57
-
58
- <!-- Right: Details -->
59
- <div class="w-1/4 border-l border-gray-700 p-4 overflow-y-auto">
60
- <h2>Top-K / Context</h2>
61
- <div id="topk"></div>
62
- <h2 class="mt-6">Reranking</h2>
63
- <div id="rerank"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  </div>
65
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
 
67
- <script>
68
- // Global state
69
- let vectors = []; // {id, text, embedding, metadata, date}
70
- let embedder, generator, reranker;
71
-
72
- async function initModels() {
73
- const status = document.getElementById('status');
74
- status.textContent = 'Loading embedding model...';
75
- embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', { device: 'webgpu' }); // or 'wasm'
76
-
77
- status.textContent = 'Loading LLM...';
78
- // generator = await pipeline('text-generation', 'Xenova/Qwen2.5-0.5B-Instruct', { device: 'webgpu', dtype: 'q4' });
79
-
80
- status.textContent = 'Ready!';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- async function getEmbedding(text) {
84
- const output = await embedder(text, { pooling: 'mean', normalize: true });
85
- return Array.from(output.data);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
- async function addEntry() {
89
- const text = prompt("Enter text to add:");
90
- if (!text) return;
91
- const emb = await getEmbedding(text);
92
- vectors.push({
93
- id: Date.now(),
94
- text,
95
- embedding: emb,
96
- metadata: { source: "user" },
97
- date: new Date().toISOString()
98
- });
99
- renderTable();
100
- // Animate node
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
 
 
102
 
103
- function cosineSimilarity(a, b) {
104
- // Simple implementation
105
- let dot = 0, magA = 0, magB = 0;
106
- for (let i = 0; i < a.length; i++) {
107
- dot += a[i] * b[i];
108
- magA += a[i] ** 2;
109
- magB += b[i] ** 2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
- return dot / (Math.sqrt(magA) * Math.sqrt(magB));
112
  }
 
 
 
113
 
114
- async function search(query, k=5) {
115
- const qEmb = await getEmbedding(query);
116
- const scored = vectors.map(v => ({
117
- ...v,
118
- score: cosineSimilarity(qEmb, v.embedding)
119
- })).sort((a,b) => b.score - a.score).slice(0,k);
120
-
121
- // Highlight in table + show topk
122
- renderTopK(scored);
123
- // Trigger rerank, etc.
124
- return scored;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- function renderTable() {
128
- // Populate tbody with rows, add click handlers
 
 
 
 
 
 
 
 
129
  }
 
 
130
 
131
- // Chat handler: on submit -> search -> (rerank) -> context -> generate -> append to chat
 
 
 
132
 
133
- // Init
134
- window.onload = () => {
135
- initModels();
136
- // Tailwind script already loaded
137
- };
138
- </script>
 
 
 
139
  </body>
140
- </html>
 
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RAG Visualizer - Browser-Based RAG Pipeline</title>
7
+ <style>
8
+ :root {
9
+ --bg-primary: #0d1117;
10
+ --bg-secondary: #161b22;
11
+ --bg-tertiary: #1c2333;
12
+ --bg-card: #1e2a3a;
13
+ --border: #30363d;
14
+ --border-active: #58a6ff;
15
+ --text-primary: #e6edf3;
16
+ --text-secondary: #8b949e;
17
+ --text-muted: #6e7681;
18
+ --accent-blue: #58a6ff;
19
+ --accent-purple: #bc8cff;
20
+ --accent-green: #3fb950;
21
+ --accent-orange: #f0883e;
22
+ --accent-red: #f85149;
23
+ --accent-yellow: #d29922;
24
+ --glow-blue: rgba(88, 166, 255, 0.3);
25
+ --glow-purple: rgba(188, 140, 255, 0.3);
26
+ --glow-green: rgba(63, 185, 80, 0.3);
27
+ --glow-orange: rgba(240, 136, 62, 0.3);
28
+ }
29
+
30
+ * { margin: 0; padding: 0; box-sizing: border-box; }
31
+
32
+ body {
33
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
34
+ background: var(--bg-primary);
35
+ color: var(--text-primary);
36
+ height: 100vh;
37
+ overflow: hidden;
38
+ }
39
+
40
+ .app-container {
41
+ display: grid;
42
+ grid-template-columns: 380px 1fr;
43
+ grid-template-rows: 50px 1fr;
44
+ height: 100vh;
45
+ gap: 1px;
46
+ background: var(--border);
47
+ }
48
+
49
+ .header {
50
+ grid-column: 1 / -1;
51
+ background: var(--bg-secondary);
52
+ display: flex;
53
+ align-items: center;
54
+ padding: 0 16px;
55
+ gap: 16px;
56
+ border-bottom: 1px solid var(--border);
57
+ z-index: 10;
58
+ }
59
+
60
+ .header-logo {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 8px;
64
+ font-weight: 700;
65
+ font-size: 16px;
66
+ color: var(--accent-blue);
67
+ }
68
+
69
+ .header-logo svg { width: 24px; height: 24px; }
70
+
71
+ .header-status {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 8px;
75
+ font-size: 12px;
76
+ color: var(--text-secondary);
77
+ }
78
+
79
+ .status-dot {
80
+ width: 8px;
81
+ height: 8px;
82
+ border-radius: 50%;
83
+ background: var(--accent-red);
84
+ animation: none;
85
+ }
86
+
87
+ .status-dot.ready { background: var(--accent-green); animation: pulse 2s infinite; }
88
+ .status-dot.loading { background: var(--accent-yellow); animation: pulse 1s infinite; }
89
+
90
+ @keyframes pulse {
91
+ 0%, 100% { opacity: 1; }
92
+ 50% { opacity: 0.5; }
93
+ }
94
+
95
+ .header-actions {
96
+ margin-left: auto;
97
+ display: flex;
98
+ gap: 8px;
99
+ }
100
+
101
+ .btn {
102
+ padding: 6px 14px;
103
+ border-radius: 6px;
104
+ border: 1px solid var(--border);
105
+ background: var(--bg-tertiary);
106
+ color: var(--text-primary);
107
+ font-size: 12px;
108
+ cursor: pointer;
109
+ transition: all 0.2s;
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 6px;
113
+ }
114
+
115
+ .btn:hover { border-color: var(--accent-blue); background: rgba(88,166,255,0.1); }
116
+ .btn-primary { background: var(--accent-blue); color: #000; border-color: var(--accent-blue); }
117
+ .btn-primary:hover { background: #79b8ff; }
118
+ .btn-danger { border-color: var(--accent-red); color: var(--accent-red); }
119
+ .btn-danger:hover { background: rgba(248,81,73,0.1); }
120
+ .btn-sm { padding: 4px 10px; font-size: 11px; }
121
+ .btn:disabled { opacity: 0.5; cursor: not-allowed; }
122
+
123
+ .chat-panel {
124
+ background: var(--bg-secondary);
125
+ display: flex;
126
+ flex-direction: column;
127
+ border-right: 1px solid var(--border);
128
+ overflow: hidden;
129
+ }
130
+
131
+ .chat-tabs {
132
+ display: flex;
133
+ border-bottom: 1px solid var(--border);
134
+ background: var(--bg-tertiary);
135
+ }
136
+
137
+ .chat-tab {
138
+ padding: 10px 16px;
139
+ font-size: 12px;
140
+ cursor: pointer;
141
+ color: var(--text-secondary);
142
+ border-bottom: 2px solid transparent;
143
+ transition: all 0.2s;
144
+ }
145
+
146
+ .chat-tab.active { color: var(--accent-blue); border-bottom-color: var(--accent-blue); }
147
+ .chat-tab:hover { color: var(--text-primary); }
148
+
149
+ .chat-messages {
150
+ flex: 1;
151
+ overflow-y: auto;
152
+ padding: 16px;
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: 12px;
156
+ }
157
+
158
+ .chat-messages::-webkit-scrollbar { width: 6px; }
159
+ .chat-messages::-webkit-scrollbar-track { background: transparent; }
160
+ .chat-messages::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
161
+
162
+ .message {
163
+ max-width: 90%;
164
+ padding: 10px 14px;
165
+ border-radius: 12px;
166
+ font-size: 13px;
167
+ line-height: 1.5;
168
+ animation: fadeIn 0.3s ease;
169
+ }
170
+
171
+ @keyframes fadeIn {
172
+ from { opacity: 0; transform: translateY(8px); }
173
+ to { opacity: 1; transform: translateY(0); }
174
+ }
175
+
176
+ .message-user {
177
+ align-self: flex-end;
178
+ background: var(--accent-blue);
179
+ color: #000;
180
+ border-bottom-right-radius: 4px;
181
+ }
182
+
183
+ .message-ai {
184
+ align-self: flex-start;
185
+ background: var(--bg-card);
186
+ border: 1px solid var(--border);
187
+ border-bottom-left-radius: 4px;
188
+ }
189
+
190
+ .message-context {
191
+ align-self: flex-start;
192
+ background: rgba(88,166,255,0.08);
193
+ border: 1px solid rgba(88,166,255,0.2);
194
+ border-bottom-left-radius: 4px;
195
+ font-size: 12px;
196
+ color: var(--text-secondary);
197
+ }
198
+
199
+ .message-context .ctx-label {
200
+ font-weight: 600;
201
+ color: var(--accent-blue);
202
+ margin-bottom: 4px;
203
+ font-size: 11px;
204
+ text-transform: uppercase;
205
+ letter-spacing: 0.5px;
206
+ }
207
+
208
+ .message-system {
209
+ align-self: center;
210
+ background: rgba(63,185,80,0.1);
211
+ border: 1px solid rgba(63,185,80,0.2);
212
+ color: var(--accent-green);
213
+ font-size: 11px;
214
+ text-align: center;
215
+ padding: 6px 12px;
216
+ }
217
+
218
+ .chat-input-area {
219
+ padding: 12px;
220
+ border-top: 1px solid var(--border);
221
+ background: var(--bg-tertiary);
222
+ }
223
+
224
+ .chat-input-wrapper {
225
+ display: flex;
226
+ gap: 8px;
227
+ align-items: flex-end;
228
+ }
229
+
230
+ .chat-input {
231
+ flex: 1;
232
+ padding: 10px 14px;
233
+ border-radius: 8px;
234
+ border: 1px solid var(--border);
235
+ background: var(--bg-secondary);
236
+ color: var(--text-primary);
237
+ font-size: 13px;
238
+ resize: none;
239
+ outline: none;
240
+ font-family: inherit;
241
+ min-height: 40px;
242
+ max-height: 120px;
243
+ }
244
+
245
+ .chat-input:focus { border-color: var(--accent-blue); }
246
+
247
+ .send-btn {
248
+ width: 40px;
249
+ height: 40px;
250
+ border-radius: 8px;
251
+ border: none;
252
+ background: var(--accent-blue);
253
+ color: #000;
254
+ cursor: pointer;
255
+ display: flex;
256
+ align-items: center;
257
+ justify-content: center;
258
+ transition: all 0.2s;
259
+ flex-shrink: 0;
260
+ }
261
+
262
+ .send-btn:hover { background: #79b8ff; transform: scale(1.05); }
263
+ .send-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
264
+
265
+ .right-panel {
266
+ background: var(--bg-primary);
267
+ overflow: hidden;
268
+ display: flex;
269
+ flex-direction: column;
270
+ }
271
+
272
+ .right-tabs {
273
+ display: flex;
274
+ border-bottom: 1px solid var(--border);
275
+ background: var(--bg-secondary);
276
+ padding: 0 12px;
277
+ flex-shrink: 0;
278
+ }
279
+
280
+ .right-tab {
281
+ padding: 10px 18px;
282
+ font-size: 12px;
283
+ cursor: pointer;
284
+ color: var(--text-secondary);
285
+ border-bottom: 2px solid transparent;
286
+ transition: all 0.2s;
287
+ white-space: nowrap;
288
+ }
289
+
290
+ .right-tab.active { color: var(--accent-purple); border-bottom-color: var(--accent-purple); }
291
+ .right-tab:hover { color: var(--text-primary); }
292
+
293
+ .right-content {
294
+ flex: 1;
295
+ overflow: hidden;
296
+ position: relative;
297
+ }
298
+
299
+ .tab-panel {
300
+ display: none;
301
+ height: 100%;
302
+ overflow-y: auto;
303
+ padding: 16px;
304
+ }
305
+
306
+ .tab-panel.active { display: block; }
307
+
308
+ .tab-panel::-webkit-scrollbar { width: 6px; }
309
+ .tab-panel::-webkit-scrollbar-track { background: transparent; }
310
+ .tab-panel::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
311
+
312
+ .section-title {
313
+ font-size: 14px;
314
+ font-weight: 600;
315
+ margin-bottom: 12px;
316
+ color: var(--text-primary);
317
+ display: flex;
318
+ align-items: center;
319
+ gap: 8px;
320
+ }
321
+
322
+ .section-title svg { width: 18px; height: 18px; }
323
+
324
+ /* Vector DB Table */
325
+ .vector-table-container {
326
+ overflow-x: auto;
327
+ border: 1px solid var(--border);
328
+ border-radius: 8px;
329
+ background: var(--bg-secondary);
330
+ }
331
+
332
+ .vector-table {
333
+ width: 100%;
334
+ border-collapse: collapse;
335
+ font-size: 12px;
336
+ }
337
+
338
+ .vector-table th {
339
+ padding: 10px 12px;
340
+ text-align: left;
341
+ background: var(--bg-tertiary);
342
+ color: var(--text-secondary);
343
+ font-weight: 600;
344
+ font-size: 11px;
345
+ text-transform: uppercase;
346
+ letter-spacing: 0.5px;
347
+ border-bottom: 1px solid var(--border);
348
+ position: sticky;
349
+ top: 0;
350
+ z-index: 2;
351
+ }
352
+
353
+ .vector-table td {
354
+ padding: 8px 12px;
355
+ border-bottom: 1px solid var(--border);
356
+ color: var(--text-primary);
357
+ transition: all 0.3s;
358
+ }
359
+
360
+ .vector-table tr:hover td { background: rgba(88,166,255,0.05); }
361
+
362
+ .vector-table tr.highlighted td {
363
+ background: rgba(88,166,255,0.15) !important;
364
+ animation: highlightPulse 1.5s ease;
365
+ }
366
+
367
+ @keyframes highlightPulse {
368
+ 0% { background: rgba(88,166,255,0.3); }
369
+ 100% { background: rgba(88,166,255,0.15); }
370
+ }
371
+
372
+ .vector-table tr.selected td {
373
+ background: rgba(63,185,80,0.15) !important;
374
+ border-left: 3px solid var(--accent-green);
375
+ }
376
+
377
+ .vector-preview {
378
+ max-width: 200px;
379
+ overflow: hidden;
380
+ text-overflow: ellipsis;
381
+ white-space: nowrap;
382
+ color: var(--text-muted);
383
+ font-family: monospace;
384
+ font-size: 11px;
385
+ }
386
+
387
+ .score-badge {
388
+ display: inline-block;
389
+ padding: 2px 8px;
390
+ border-radius: 10px;
391
+ font-size: 11px;
392
+ font-weight: 600;
393
+ background: rgba(88,166,255,0.15);
394
+ color: var(--accent-blue);
395
+ }
396
+
397
+ .rank-badge {
398
+ display: inline-block;
399
+ width: 24px;
400
+ height: 24px;
401
+ border-radius: 50%;
402
+ text-align: center;
403
+ line-height: 24px;
404
+ font-size: 11px;
405
+ font-weight: 700;
406
+ }
407
+
408
+ .rank-1 { background: rgba(240,136,62,0.2); color: var(--accent-orange); }
409
+ .rank-2 { background: rgba(88,166,255,0.2); color: var(--accent-blue); }
410
+ .rank-3 { background: rgba(188,140,255,0.2); color: var(--accent-purple); }
411
+ .rank-other { background: rgba(139,148,158,0.15); color: var(--text-muted); }
412
+
413
+ /* Add Entry Form */
414
+ .add-entry-form {
415
+ background: var(--bg-secondary);
416
+ border: 1px solid var(--border);
417
+ border-radius: 8px;
418
+ padding: 16px;
419
+ margin-bottom: 16px;
420
+ }
421
+
422
+ .form-group {
423
+ margin-bottom: 12px;
424
+ }
425
+
426
+ .form-label {
427
+ display: block;
428
+ font-size: 12px;
429
+ color: var(--text-secondary);
430
+ margin-bottom: 6px;
431
+ font-weight: 500;
432
+ }
433
+
434
+ .form-input, .form-textarea, .form-select {
435
+ width: 100%;
436
+ padding: 8px 12px;
437
+ border-radius: 6px;
438
+ border: 1px solid var(--border);
439
+ background: var(--bg-primary);
440
+ color: var(--text-primary);
441
+ font-size: 13px;
442
+ font-family: inherit;
443
+ outline: none;
444
+ transition: border-color 0.2s;
445
+ }
446
+
447
+ .form-input:focus, .form-textarea:focus, .form-select:focus {
448
+ border-color: var(--accent-blue);
449
+ }
450
+
451
+ .form-textarea { resize: vertical; min-height: 80px; }
452
+
453
+ .form-row {
454
+ display: grid;
455
+ grid-template-columns: 1fr 1fr;
456
+ gap: 12px;
457
+ }
458
+
459
+ /* Reranking Section */
460
+ .rerank-card {
461
+ background: var(--bg-secondary);
462
+ border: 1px solid var(--border);
463
+ border-radius: 8px;
464
+ padding: 14px;
465
+ margin-bottom: 10px;
466
+ transition: all 0.3s;
467
+ position: relative;
468
+ overflow: hidden;
469
+ }
470
+
471
+ .rerank-card::before {
472
+ content: '';
473
+ position: absolute;
474
+ top: 0;
475
+ left: 0;
476
+ width: 4px;
477
+ height: 100%;
478
+ background: var(--border);
479
+ }
480
+
481
+ .rerank-card.rerank-1::before { background: var(--accent-orange); }
482
+ .rerank-card.rerank-2::before { background: var(--accent-blue); }
483
+ .rerank-card.rerank-3::before { background: var(--accent-purple); }
484
+
485
+ .rerank-card-header {
486
+ display: flex;
487
+ justify-content: space-between;
488
+ align-items: center;
489
+ margin-bottom: 8px;
490
+ }
491
+
492
+ .rerank-score {
493
+ font-size: 20px;
494
+ font-weight: 700;
495
+ }
496
+
497
+ .rerank-content {
498
+ font-size: 13px;
499
+ line-height: 1.5;
500
+ color: var(--text-primary);
501
+ }
502
+
503
+ .rerank-meta {
504
+ display: flex;
505
+ gap: 12px;
506
+ margin-top: 8px;
507
+ font-size: 11px;
508
+ color: var(--text-muted);
509
+ }
510
+
511
+ /* Node Editor */
512
+ .node-editor-container {
513
+ height: 100%;
514
+ position: relative;
515
+ overflow: hidden;
516
+ background: var(--bg-primary);
517
+ }
518
+
519
+ .node-canvas {
520
+ width: 100%;
521
+ height: 100%;
522
+ position: relative;
523
+ }
524
+
525
+ .node-canvas svg {
526
+ width: 100%;
527
+ height: 100%;
528
+ position: absolute;
529
+ top: 0;
530
+ left: 0;
531
+ }
532
+
533
+ .node {
534
+ position: absolute;
535
+ width: 220px;
536
+ background: var(--bg-card);
537
+ border: 1px solid var(--border);
538
+ border-radius: 10px;
539
+ overflow: hidden;
540
+ cursor: grab;
541
+ transition: box-shadow 0.3s, border-color 0.3s;
542
+ z-index: 5;
543
+ }
544
+
545
+ .node:hover { box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
546
+ .node.active { border-color: var(--accent-blue); box-shadow: 0 0 20px var(--glow-blue); }
547
+ .node.processing { border-color: var(--accent-green); box-shadow: 0 0 20px var(--glow-green); animation: nodePulse 1.5s infinite; }
548
+
549
+ @keyframes nodePulse {
550
+ 0%, 100% { box-shadow: 0 0 20px var(--glow-green); }
551
+ 50% { box-shadow: 0 0 30px var(--glow-green); }
552
+ }
553
+
554
+ .node-header {
555
+ padding: 10px 12px;
556
+ font-size: 12px;
557
+ font-weight: 600;
558
+ display: flex;
559
+ align-items: center;
560
+ gap: 8px;
561
+ border-bottom: 1px solid var(--border);
562
+ }
563
+
564
+ .node-header .node-icon {
565
+ width: 20px;
566
+ height: 20px;
567
+ border-radius: 4px;
568
+ display: flex;
569
+ align-items: center;
570
+ justify-content: center;
571
+ font-size: 12px;
572
+ }
573
+
574
+ .node-icon-chat { background: rgba(88,166,255,0.2); color: var(--accent-blue); }
575
+ .node-icon-embed { background: rgba(188,140,255,0.2); color: var(--accent-purple); }
576
+ .node-icon-vectordb { background: rgba(63,185,80,0.2); color: var(--accent-green); }
577
+ .node-icon-rerank { background: rgba(240,136,62,0.2); color: var(--accent-orange); }
578
+ .node-icon-llm { background: rgba(248,81,73,0.2); color: var(--accent-red); }
579
+ .node-icon-output { background: rgba(210,153,34,0.2); color: var(--accent-yellow); }
580
+
581
+ .node-body {
582
+ padding: 10px 12px;
583
+ font-size: 11px;
584
+ color: var(--text-secondary);
585
+ min-height: 60px;
586
+ }
587
+
588
+ .node-ports {
589
+ display: flex;
590
+ justify-content: space-between;
591
+ padding: 0 8px;
592
+ margin-top: 4px;
593
+ }
594
+
595
+ .port {
596
+ width: 12px;
597
+ height: 12px;
598
+ border-radius: 50%;
599
+ border: 2px solid var(--border);
600
+ background: var(--bg-primary);
601
+ cursor: crosshair;
602
+ transition: all 0.2s;
603
+ }
604
+
605
+ .port:hover { border-color: var(--accent-blue); background: var(--accent-blue); }
606
+ .port.input { margin-top: -6px; }
607
+ .port.output { margin-top: -6px; }
608
+
609
+ .node-settings {
610
+ padding: 8px 12px;
611
+ border-top: 1px solid var(--border);
612
+ background: rgba(0,0,0,0.2);
613
+ }
614
+
615
+ .node-setting {
616
+ display: flex;
617
+ justify-content: space-between;
618
+ align-items: center;
619
+ margin-bottom: 4px;
620
+ }
621
+
622
+ .node-setting label {
623
+ font-size: 10px;
624
+ color: var(--text-muted);
625
+ }
626
+
627
+ .node-setting input, .node-setting select {
628
+ width: 80px;
629
+ padding: 2px 6px;
630
+ border-radius: 3px;
631
+ border: 1px solid var(--border);
632
+ background: var(--bg-primary);
633
+ color: var(--text-primary);
634
+ font-size: 10px;
635
+ outline: none;
636
+ }
637
+
638
+ /* Connection lines */
639
+ .connection {
640
+ stroke: var(--border);
641
+ stroke-width: 2;
642
+ fill: none;
643
+ transition: stroke 0.3s;
644
+ }
645
+
646
+ .connection.active {
647
+ stroke: var(--accent-green);
648
+ stroke-width: 3;
649
+ filter: drop-shadow(0 0 4px var(--glow-green));
650
+ }
651
+
652
+ .connection.processing {
653
+ stroke: var(--accent-blue);
654
+ stroke-width: 3;
655
+ filter: drop-shadow(0 0 4px var(--glow-blue));
656
+ animation: flowLine 1s linear infinite;
657
+ stroke-dasharray: 8 4;
658
+ }
659
+
660
+ @keyframes flowLine {
661
+ to { stroke-dashoffset: -12; }
662
+ }
663
+
664
+ /* Stats bar */
665
+ .stats-bar {
666
+ display: flex;
667
+ gap: 16px;
668
+ padding: 12px 16px;
669
+ background: var(--bg-secondary);
670
+ border-bottom: 1px solid var(--border);
671
+ flex-shrink: 0;
672
+ }
673
+
674
+ .stat-item {
675
+ display: flex;
676
+ align-items: center;
677
+ gap: 6px;
678
+ font-size: 12px;
679
+ }
680
+
681
+ .stat-value {
682
+ font-weight: 700;
683
+ color: var(--accent-blue);
684
+ }
685
+
686
+ .stat-label {
687
+ color: var(--text-muted);
688
+ font-size: 11px;
689
+ }
690
+
691
+ /* Progress bar */
692
+ .progress-bar {
693
+ width: 100%;
694
+ height: 4px;
695
+ background: var(--bg-primary);
696
+ border-radius: 2px;
697
+ overflow: hidden;
698
+ margin-top: 8px;
699
+ }
700
+
701
+ .progress-fill {
702
+ height: 100%;
703
+ background: var(--accent-blue);
704
+ border-radius: 2px;
705
+ transition: width 0.5s ease;
706
+ animation: shimmer 2s infinite;
707
+ }
708
+
709
+ @keyframes shimmer {
710
+ 0% { opacity: 1; }
711
+ 50% { opacity: 0.7; }
712
+ 100% { opacity: 1; }
713
+ }
714
+
715
+ /* Model loading overlay */
716
+ .model-overlay {
717
+ position: fixed;
718
+ top: 0;
719
+ left: 0;
720
+ right: 0;
721
+ bottom: 0;
722
+ background: rgba(13,17,23,0.9);
723
+ display: flex;
724
+ flex-direction: column;
725
+ align-items: center;
726
+ justify-content: center;
727
+ z-index: 1000;
728
+ gap: 20px;
729
+ }
730
+
731
+ .model-overlay.hidden { display: none; }
732
+
733
+ .model-spinner {
734
+ width: 48px;
735
+ height: 48px;
736
+ border: 3px solid var(--border);
737
+ border-top-color: var(--accent-blue);
738
+ border-radius: 50%;
739
+ animation: spin 1s linear infinite;
740
+ }
741
+
742
+ @keyframes spin {
743
+ to { transform: rotate(360deg); }
744
+ }
745
+
746
+ .model-title {
747
+ font-size: 18px;
748
+ font-weight: 600;
749
+ color: var(--text-primary);
750
+ }
751
+
752
+ .model-status {
753
+ font-size: 13px;
754
+ color: var(--text-secondary);
755
+ text-align: center;
756
+ }
757
+
758
+ .model-progress {
759
+ width: 300px;
760
+ height: 6px;
761
+ background: var(--bg-tertiary);
762
+ border-radius: 3px;
763
+ overflow: hidden;
764
+ }
765
+
766
+ .model-progress-fill {
767
+ height: 100%;
768
+ background: linear-gradient(90deg, var(--accent-blue), var(--accent-purple));
769
+ border-radius: 3px;
770
+ transition: width 0.3s;
771
+ }
772
+
773
+ /* Vector visualization */
774
+ .vector-viz {
775
+ display: flex;
776
+ gap: 2px;
777
+ align-items: end;
778
+ height: 40px;
779
+ padding: 4px 0;
780
+ }
781
+
782
+ .vector-bar {
783
+ width: 4px;
784
+ background: var(--accent-blue);
785
+ border-radius: 1px;
786
+ transition: height 0.3s;
787
+ opacity: 0.7;
788
+ }
789
+
790
+ .vector-bar:nth-child(odd) { background: var(--accent-purple); }
791
+
792
+ /* Tooltip */
793
+ .tooltip {
794
+ position: absolute;
795
+ background: var(--bg-card);
796
+ border: 1px solid var(--border);
797
+ border-radius: 6px;
798
+ padding: 8px 12px;
799
+ font-size: 11px;
800
+ color: var(--text-primary);
801
+ z-index: 100;
802
+ pointer-events: none;
803
+ box-shadow: 0 4px 12px rgba(0,0,0,0.3);
804
+ max-width: 200px;
805
+ }
806
+
807
+ /* Empty state */
808
+ .empty-state {
809
+ display: flex;
810
+ flex-direction: column;
811
+ align-items: center;
812
+ justify-content: center;
813
+ padding: 40px;
814
+ color: var(--text-muted);
815
+ text-align: center;
816
+ gap: 12px;
817
+ }
818
+
819
+ .empty-state svg { width: 48px; height: 48px; opacity: 0.3; }
820
+ .empty-state p { font-size: 13px; }
821
+
822
+ /* Context panel in chat */
823
+ .context-panel {
824
+ background: rgba(88,166,255,0.05);
825
+ border: 1px solid rgba(88,166,255,0.15);
826
+ border-radius: 8px;
827
+ padding: 10px;
828
+ margin: 8px 0;
829
+ font-size: 12px;
830
+ }
831
+
832
+ .context-panel h4 {
833
+ font-size: 11px;
834
+ color: var(--accent-blue);
835
+ margin-bottom: 6px;
836
+ text-transform: uppercase;
837
+ letter-spacing: 0.5px;
838
+ }
839
+
840
+ .context-item {
841
+ padding: 6px 8px;
842
+ background: rgba(0,0,0,0.2);
843
+ border-radius: 4px;
844
+ margin-bottom: 4px;
845
+ font-size: 11px;
846
+ color: var(--text-secondary);
847
+ border-left: 2px solid var(--accent-blue);
848
+ }
849
+
850
+ .context-item:last-child { margin-bottom: 0; }
851
+
852
+ /* Loading animation in chat */
853
+ .typing-indicator {
854
+ display: flex;
855
+ gap: 4px;
856
+ padding: 12px 16px;
857
+ }
858
+
859
+ .typing-dot {
860
+ width: 8px;
861
+ height: 8px;
862
+ border-radius: 50%;
863
+ background: var(--text-muted);
864
+ animation: typingBounce 1.4s infinite;
865
+ }
866
+
867
+ .typing-dot:nth-child(2) { animation-delay: 0.2s; }
868
+ .typing-dot:nth-child(3) { animation-delay: 0.4s; }
869
+
870
+ @keyframes typingBounce {
871
+ 0%, 60%, 100% { transform: translateY(0); }
872
+ 30% { transform: translateY(-6px); }
873
+ }
874
+
875
+ /* Responsive */
876
+ @media (max-width: 768px) {
877
+ .app-container {
878
+ grid-template-columns: 1fr;
879
+ grid-template-rows: 50px 1fr;
880
+ }
881
+ .chat-panel { display: none; }
882
+ }
883
+
884
+ /* Scrollbar global */
885
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
886
+ ::-webkit-scrollbar-track { background: transparent; }
887
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
888
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
889
+
890
+ /* Dimension visualization */
891
+ .dim-viz {
892
+ display: grid;
893
+ grid-template-columns: repeat(32, 1fr);
894
+ gap: 1px;
895
+ max-width: 256px;
896
+ }
897
+
898
+ .dim-cell {
899
+ height: 8px;
900
+ border-radius: 1px;
901
+ transition: background 0.3s;
902
+ }
903
+
904
+ /* Node editor controls */
905
+ .editor-controls {
906
+ position: absolute;
907
+ bottom: 16px;
908
+ right: 16px;
909
+ display: flex;
910
+ gap: 8px;
911
+ z-index: 10;
912
+ }
913
+
914
+ .editor-controls .btn {
915
+ width: 36px;
916
+ height: 36px;
917
+ padding: 0;
918
+ display: flex;
919
+ align-items: center;
920
+ justify-content: center;
921
+ border-radius: 8px;
922
+ }
923
+
924
+ /* Settings panel */
925
+ .settings-panel {
926
+ position: absolute;
927
+ top: 8px;
928
+ right: 8px;
929
+ background: var(--bg-card);
930
+ border: 1px solid var(--border);
931
+ border-radius: 8px;
932
+ padding: 12px;
933
+ z-index: 10;
934
+ min-width: 200px;
935
+ }
936
+
937
+ .settings-panel h4 {
938
+ font-size: 12px;
939
+ margin-bottom: 8px;
940
+ color: var(--accent-purple);
941
+ }
942
+
943
+ /* Entry added animation */
944
+ @keyframes entryAdded {
945
+ 0% { background: rgba(63,185,80,0.3); }
946
+ 100% { background: transparent; }
947
+ }
948
+
949
+ .entry-new {
950
+ animation: entryAdded 2s ease;
951
+ }
952
+ </style>
953
  </head>
954
+ <body>
955
+
956
+ <div class="model-overlay" id="modelOverlay">
957
+ <div class="model-spinner"></div>
958
+ <div class="model-title">Loading RAG Pipeline Models</div>
959
+ <div class="model-status" id="modelStatus">Initializing...</div>
960
+ <div class="model-progress">
961
+ <div class="model-progress-fill" id="modelProgressFill" style="width: 0%"></div>
962
+ </div>
963
+ <div style="font-size:11px;color:var(--text-muted)">Models are cached after first download</div>
964
+ </div>
965
+
966
+ <div class="app-container">
967
+ <div class="header">
968
+ <div class="header-logo">
969
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
970
+ <path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
971
+ </svg>
972
+ RAG Visualizer
973
+ </div>
974
+ <div class="header-status">
975
+ <div class="status-dot" id="statusDot"></div>
976
+ <span id="statusText">Loading models...</span>
977
+ </div>
978
+ <div class="header-actions">
979
+ <button class="btn btn-sm" onclick="exportDB()">
980
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
981
+ Export
982
+ </button>
983
+ <button class="btn btn-sm" onclick="clearAll()">
984
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
985
+ Clear
986
+ </button>
987
+ </div>
988
+ </div>
989
+
990
+ <div class="chat-panel">
991
+ <div class="chat-tabs">
992
+ <div class="chat-tab active" data-tab="chat">💬 Chat</div>
993
+ <div class="chat-tab" data-tab="context">📋 Context</div>
994
+ <div class="chat-tab" data-tab="history">📜 History</div>
995
+ </div>
996
+ <div class="chat-messages" id="chatMessages">
997
+ <div class="message message-system">
998
+ 🤖 RAG Visualizer loaded. Models: all-MiniLM-L6-v2 (embed), ms-marco-MiniLM-L-6-v2 (rerank), Qwen2.5-0.5B (generate)
999
  </div>
1000
  </div>
1001
+ <div class="chat-input-area">
1002
+ <div class="chat-input-wrapper">
1003
+ <textarea class="chat-input" id="chatInput" placeholder="Ask a question..." rows="1" onkeydown="handleChatKey(event)"></textarea>
1004
+ <button class="send-btn" id="sendBtn" onclick="sendMessage()">
1005
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
1006
+ </button>
 
 
 
 
 
 
 
1007
  </div>
1008
+ </div>
1009
+ </div>
1010
+
1011
+ <div class="right-panel">
1012
+ <div class="stats-bar" id="statsBar">
1013
+ <div class="stat-item">
1014
+ <span class="stat-value" id="statEntries">0</span>
1015
+ <span class="stat-label">Entries</span>
1016
+ </div>
1017
+ <div class="stat-item">
1018
+ <span class="stat-value" id="statTopK">3</span>
1019
+ <span class="stat-label">Top-K</span>
1020
+ </div>
1021
+ <div class="stat-item">
1022
+ <span class="stat-value" id="statEmbedDim">384</span>
1023
+ <span class="stat-label">Embed Dims</span>
1024
+ </div>
1025
+ <div class="stat-item">
1026
+ <span class="stat-value" id="statLatency">0ms</span>
1027
+ <span class="stat-label">Latency</span>
1028
  </div>
1029
  </div>
1030
+
1031
+ <div class="right-tabs">
1032
+ <div class="right-tab active" data-tab="vectors">🗄️ Vector DB</div>
1033
+ <div class="right-tab" data-tab="add">➕ Add Entry</div>
1034
+ <div class="right-tab" data-tab="rerank">🔄 Reranking</div>
1035
+ <div class="right-tab" data-tab="nodes">🔗 Node Editor</div>
1036
+ </div>
1037
+
1038
+ <div class="right-content">
1039
+ <!-- Vector DB Tab -->
1040
+ <div class="tab-panel active" id="panel-vectors">
1041
+ <div class="section-title">
1042
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
1043
+ Vector Database (LanceDB-style)
1044
+ </div>
1045
+ <div class="vector-table-container">
1046
+ <table class="vector-table" id="vectorTable">
1047
+ <thead>
1048
+ <tr>
1049
+ <th>#</th>
1050
+ <th>ID</th>
1051
+ <th>Content</th>
1052
+ <th>Vector Preview</th>
1053
+ <th>Score</th>
1054
+ <th>Date</th>
1055
+ <th>Metadata</th>
1056
+ </tr>
1057
+ </thead>
1058
+ <tbody id="vectorTableBody">
1059
+ </tbody>
1060
+ </table>
1061
+ </div>
1062
+ <div class="empty-state" id="emptyVectors">
1063
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
1064
+ <p>No entries yet. Add entries to get started!</p>
1065
+ </div>
1066
+ </div>
1067
+
1068
+ <!-- Add Entry Tab -->
1069
+ <div class="tab-panel" id="panel-add">
1070
+ <div class="section-title">
1071
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
1072
+ Add Entry to Vector DB
1073
+ </div>
1074
+ <div class="add-entry-form">
1075
+ <div class="form-group">
1076
+ <label class="form-label">Content / Text</label>
1077
+ <textarea class="form-textarea" id="addContent" placeholder="Enter the text content to embed and store..."></textarea>
1078
+ </div>
1079
+ <div class="form-row">
1080
+ <div class="form-group">
1081
+ <label class="form-label">Source</label>
1082
+ <input class="form-input" id="addSource" placeholder="e.g., document.pdf, article.md">
1083
+ </div>
1084
+ <div class="form-group">
1085
+ <label class="form-label">Category</label>
1086
+ <select class="form-select" id="addCategory">
1087
+ <option value="general">General</option>
1088
+ <option value="technical">Technical</option>
1089
+ <option value="faq">FAQ</option>
1090
+ <option value="documentation">Documentation</option>
1091
+ </select>
1092
+ </div>
1093
+ </div>
1094
+ <div class="form-group">
1095
+ <label class="form-label">Custom Metadata (JSON, optional)</label>
1096
+ <textarea class="form-textarea" id="addMetadata" placeholder='{"author": "John", "version": "1.0"}' style="min-height:50px"></textarea>
1097
+ </div>
1098
+ <button class="btn btn-primary" onclick="addEntry()" id="addEntryBtn">
1099
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
1100
+ Embed & Store in Vector DB
1101
+ </button>
1102
+ <div class="progress-bar" id="addProgress" style="display:none">
1103
+ <div class="progress-fill" id="addProgressFill" style="width:0%"></div>
1104
+ </div>
1105
+ </div>
1106
+
1107
+ <div class="section-title" style="margin-top:20px">
1108
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
1109
+ Quick Add Samples
1110
+ </div>
1111
+ <div style="display:flex;flex-wrap:wrap;gap:8px">
1112
+ <button class="btn btn-sm" onclick="addSample(0)">🤖 AI/ML</button>
1113
+ <button class="btn btn-sm" onclick="addSample(1)">🔧 RAG Pipeline</button>
1114
+ <button class="btn btn-sm" onclick="addSample(2)">📊 Vector Search</button>
1115
+ <button class="btn btn-sm" onclick="addSample(3)">🧠 Neural Networks</button>
1116
+ <button class="btn btn-sm" onclick="addSample(4)">💻 Web Development</button>
1117
+ <button class="btn btn-sm" onclick="addSample(5)"> Security</button>
1118
+ </div>
1119
+ </div>
1120
+
1121
+ <!-- Reranking Tab -->
1122
+ <div class="tab-panel" id="panel-rerank">
1123
+ <div class="section-title">
1124
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>
1125
+ Reranking Results (Cross-Encoder)
1126
+ <span style="font-size:11px;color:var(--text-muted);font-weight:400">Model: ms-marco-MiniLM-L-6-v2</span>
1127
+ </div>
1128
+ <div id="rerankResults">
1129
+ <div class="empty-state">
1130
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>
1131
+ <p>Send a query to see reranking results</p>
1132
+ </div>
1133
+ </div>
1134
+ </div>
1135
+
1136
+ <!-- Node Editor Tab -->
1137
+ <div class="tab-panel" id="panel-nodes">
1138
+ <div class="node-editor-container" id="nodeEditor">
1139
+ <div class="node-canvas" id="nodeCanvas">
1140
+ <svg id="connectionSvg"></svg>
1141
+ </div>
1142
+ <div class="editor-controls">
1143
+ <button class="btn" onclick="resetNodePositions()" title="Reset Layout">
1144
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/></svg>
1145
+ </button>
1146
+ <button class="btn" onclick="runPipeline()" title="Run Pipeline">
1147
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"/></svg>
1148
+ </button>
1149
+ </div>
1150
+ </div>
1151
+ </div>
1152
  </div>
1153
  </div>
1154
+ </div>
1155
+
1156
+ <script type="module">
1157
+ import { pipeline, env } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.3.3/dist/transformers.mjs';
1158
+
1159
+ // ==================== GLOBAL STATE ====================
1160
+ window.ragState = {
1161
+ embeddingModel: null,
1162
+ rerankerModel: null,
1163
+ generationModel: null,
1164
+ vectorDB: [],
1165
+ chatHistory: [],
1166
+ topK: 3,
1167
+ isReady: false,
1168
+ isProcessing: false,
1169
+ nodePositions: {},
1170
+ currentContext: []
1171
+ };
1172
+
1173
+ // ==================== CONFIG ====================
1174
+ const CONFIG = {
1175
+ embedModel: 'Xenova/all-MiniLM-L6-v2',
1176
+ rerankModel: 'Xenova/ms-marco-MiniLM-L-6-v2',
1177
+ genModel: 'onnx-community/Qwen2.5-0.5B-Instruct',
1178
+ embedDim: 384,
1179
+ topK: 3,
1180
+ similarityThreshold: 0.1
1181
+ };
1182
+
1183
+ // ==================== SAMPLE DATA ====================
1184
+ const SAMPLES = [
1185
+ { content: "Retrieval-Augmented Generation (RAG) is an AI framework that combines retrieval of relevant documents with generative language models. It allows LLMs to access external knowledge bases to produce more accurate, up-to-date, and factually grounded responses. RAG reduces hallucination by grounding responses in retrieved context.", source: "rag_guide.md", category: "technical", metadata: { topic: "RAG", author: "AI Team" }},
1186
+ { content: "Vector databases store data as high-dimensional vectors (embeddings) that capture semantic meaning. Popular vector DBs include LanceDB, ChromaDB, Qdrant, Milvus, and Pinecone. They enable efficient similarity search using algorithms like HNSW, IVF, or brute-force cosine similarity for nearest neighbor retrieval.", source: "vector_db_comparison.md", category: "documentation", meta { topic: "VectorDB", author: "Data Team" }},
1187
+ { content: "Cross-encoder reranking improves retrieval accuracy by scoring query-document pairs directly. Unlike bi-encoders that compute query and document embeddings independently, cross-encoders process both together through a transformer, capturing fine-grained interactions. The ms-marco-MiniLM-L-6-v2 model is a lightweight yet effective reranker.", source: "reranking_guide.md", category: "technical", metadata: { topic: "Reranking", author: "ML Team" }},
1188
+ { content: "Neural networks are computing systems inspired by biological neural networks. They consist of interconnected nodes (neurons) organized in layers: input, hidden, and output. Deep learning uses networks with many hidden layers to learn hierarchical representations of data, enabling breakthroughs in image recognition, NLP, and game playing.", source: "neural_networks.md", category: "technical", meta { topic: "Deep Learning", author: "Research Team" }},
1189
+ { content: "Modern web development involves frameworks like React, Vue, Angular, and Svelte for frontend, and Node.js, Python, Go, or Rust for backend. Static site generators (SSG) like Next.js, Astro, and Hugo enable fast, SEO-friendly websites. WebAssembly (WASM) allows running compiled languages in the browser for near-native performance.", source: "webdev_overview.md", category: "documentation", metadata: { topic: "WebDev", author: "Frontend Team" }},
1190
+ { content: "Cybersecurity best practices include: implementing zero-trust architecture, using multi-factor authentication, regular security audits, encrypting data at rest and in transit, keeping software updated, conducting penetration testing, and following the principle of least privilege. OWASP Top 10 provides guidance on common web vulnerabilities.", source: "security_guide.md", category: "faq", metadata: { topic: "Security", author: "SecOps Team" }}
1191
+ ];
1192
+
1193
+ // ==================== INITIALIZATION ====================
1194
+ async function init() {
1195
+ updateStatus('loading', 'Loading embedding model...');
1196
+ updateProgress(10);
1197
+
1198
+ try {
1199
+ window.ragState.embeddingModel = await pipeline('feature-extraction', CONFIG.embedModel, {
1200
+ dtype: 'fp32',
1201
+ progress_callback: (p) => updateModelProgress(p, 'Embedding')
1202
+ });
1203
+ updateStatus('loading', 'Loading reranker model...');
1204
+ updateProgress(40);
1205
+
1206
+ window.ragState.rerankerModel = await pipeline('text-classification', CONFIG.rerankModel, {
1207
+ dtype: 'fp32',
1208
+ progress_callback: (p) => updateModelProgress(p, 'Reranker')
1209
+ });
1210
+ updateStatus('loading', 'Loading Qwen2.5-0.5B generator...');
1211
+ updateProgress(70);
1212
+
1213
+ window.ragState.generationModel = await pipeline('text-generation', CONFIG.genModel, {
1214
+ dtype: 'q8',
1215
+ progress_callback: (p) => updateModelProgress(p, 'Qwen2.5')
1216
+ });
1217
+ updateProgress(100);
1218
+
1219
+ window.ragState.isReady = true;
1220
+ updateStatus('ready', 'All models loaded');
1221
+ hideOverlay();
1222
+ initNodeEditor();
1223
+ loadFromStorage();
1224
+ renderVectorTable();
1225
+ addSystemMessage('✅ All models loaded successfully. Ready to chat!');
1226
+ } catch (err) {
1227
+ console.error('Model loading error:', err);
1228
+ updateStatus('loading', 'Error: ' + err.message);
1229
+ addSystemMessage(' Model loading failed: ' + err.message);
1230
+ hideOverlay();
1231
+ }
1232
+ }
1233
+
1234
+ // ==================== UI HELPERS ====================
1235
+ function updateStatus(state, text) {
1236
+ const dot = document.getElementById('statusDot');
1237
+ const txt = document.getElementById('statusText');
1238
+ dot.className = 'status-dot ' + state;
1239
+ txt.textContent = text;
1240
+ }
1241
+
1242
+ function updateProgress(pct) {
1243
+ document.getElementById('modelProgressFill').style.width = pct + '%';
1244
+ }
1245
+
1246
+ function updateModelProgress(progress, modelName) {
1247
+ const status = document.getElementById('modelStatus');
1248
+ const statusText = document.getElementById('statusText');
1249
+ if (progress.status === 'progress') {
1250
+ const pct = Math.round(progress.progress * 100);
1251
+ status.textContent = `Loading ${modelName}: ${pct}%`;
1252
+ statusText.textContent = `Loading ${modelName}... ${pct}%`;
1253
+ }
1254
+ }
1255
+
1256
+ function hideOverlay() {
1257
+ document.getElementById('modelOverlay').classList.add('hidden');
1258
+ }
1259
+
1260
+ function addSystemMessage(text) {
1261
+ const container = document.getElementById('chatMessages');
1262
+ const div = document.createElement('div');
1263
+ div.className = 'message message-system';
1264
+ div.textContent = text;
1265
+ container.appendChild(div);
1266
+ container.scrollTop = container.scrollHeight;
1267
+ }
1268
+
1269
+ function addUserMessage(text) {
1270
+ const container = document.getElementById('chatMessages');
1271
+ const div = document.createElement('div');
1272
+ div.className = 'message message-user';
1273
+ div.textContent = text;
1274
+ container.appendChild(div);
1275
+ container.scrollTop = container.scrollHeight;
1276
+ }
1277
+
1278
+ function addAIMessage(text) {
1279
+ const container = document.getElementById('chatMessages');
1280
+ const div = document.createElement('div');
1281
+ div.className = 'message message-ai';
1282
+ div.textContent = text;
1283
+ container.appendChild(div);
1284
+ container.scrollTop = container.scrollHeight;
1285
+ }
1286
+
1287
+ function addContextMessage(contexts) {
1288
+ const container = document.getElementById('chatMessages');
1289
+ const div = document.createElement('div');
1290
+ div.className = 'message message-context';
1291
+ let html = '<div class="ctx-label">📋 Retrieved Context (Top-' + window.ragState.topK + ')</div>';
1292
+ contexts.forEach((ctx, i) => {
1293
+ html += `<div class="context-item"><strong>Source ${i+1}</strong> (score: ${ctx.score.toFixed(4)}): ${ctx.content.substring(0, 200)}...</div>`;
1294
+ });
1295
+ div.innerHTML = html;
1296
+ container.appendChild(div);
1297
+ container.scrollTop = container.scrollHeight;
1298
+ }
1299
+
1300
+ function showTyping() {
1301
+ const container = document.getElementById('chatMessages');
1302
+ const div = document.createElement('div');
1303
+ div.className = 'message message-ai typing-indicator';
1304
+ div.id = 'typingIndicator';
1305
+ div.innerHTML = '<div class="typing-dot"></div><div class="typing-dot"></div><div class="typing-dot"></div>';
1306
+ container.appendChild(div);
1307
+ container.scrollTop = container.scrollHeight;
1308
+ }
1309
+
1310
+ function hideTyping() {
1311
+ const el = document.getElementById('typingIndicator');
1312
+ if (el) el.remove();
1313
+ }
1314
+
1315
+ // ==================== VECTOR OPERATIONS ====================
1316
+ function cosineSimilarity(a, b) {
1317
+ let dot = 0, normA = 0, normB = 0;
1318
+ for (let i = 0; i < a.length; i++) {
1319
+ dot += a[i] * b[i];
1320
+ normA += a[i] * a[i];
1321
+ normB += b[i] * b[i];
1322
+ }
1323
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB) + 1e-10);
1324
+ }
1325
+
1326
+ async function embedText(text) {
1327
+ if (!window.ragState.embeddingModel) throw new Error('Embedding model not loaded');
1328
+ const output = await window.ragState.embeddingModel(text, { pooling: 'mean', normalize: true });
1329
+ return Array.from(output.data);
1330
+ }
1331
+
1332
+ async function rerank(query, documents) {
1333
+ if (!window.ragState.rerankerModel) throw new Error('Reranker not loaded');
1334
+ const pairs = documents.map(doc => [query, doc.content]);
1335
+ const results = await window.ragState.rerankerModel(pairs, { topk: 1 });
1336
+
1337
+ let scored = documents.map((doc, i) => {
1338
+ const res = results[i];
1339
+ const label = res.label || 'LABEL_1';
1340
+ const score = label === 'LABEL_1' ? res.score : 1 - res.score;
1341
+ return { ...doc, rerankScore: score };
1342
+ });
1343
+
1344
+ scored.sort((a, b) => b.rerankScore - a.rerankScore);
1345
+ return scored;
1346
+ }
1347
+
1348
+ function vectorSearch(queryEmbedding, topK = 3) {
1349
+ const db = window.ragState.vectorDB;
1350
+ if (db.length === 0) return [];
1351
+
1352
+ const scored = db.map(entry => ({
1353
+ ...entry,
1354
+ score: cosineSimilarity(queryEmbedding, entry.vector)
1355
+ }));
1356
+
1357
+ scored.sort((a, b) => b.score - a.score);
1358
+ return scored.slice(0, topK);
1359
+ }
1360
+
1361
+ // ==================== CHAT ====================
1362
+ async function sendMessage() {
1363
+ const input = document.getElementById('chatInput');
1364
+ const query = input.value.trim();
1365
+ if (!query || window.ragState.isProcessing) return;
1366
+
1367
+ input.value = '';
1368
+ addUserMessage(query);
1369
+ window.ragState.isProcessing = true;
1370
+
1371
+ const startTime = performance.now();
1372
+
1373
+ try {
1374
+ // Step 1: Embed query
1375
+ animateNode('input');
1376
+ animateNode('embed');
1377
+ const queryEmbedding = await embedText(query);
1378
+
1379
+ // Step 2: Vector search
1380
+ animateNode('vectordb');
1381
+ const topKResults = vectorSearch(queryEmbedding, window.ragState.topK);
1382
+
1383
+ // Highlight in table
1384
+ highlightTableRows(topKResults.map(r => r.id));
1385
+
1386
+ // Step 3: Rerank
1387
+ animateNode('rerank');
1388
+ const rerankedResults = await rerank(query, topKResults);
1389
+
1390
+ // Update reranking panel
1391
+ renderRerankResults(query, rerankedResults);
1392
+
1393
+ // Step 4: Build context
1394
+ animateNode('context');
1395
+ const contextTexts = rerankedResults.slice(0, window.ragState.topK).map(r => r.content);
1396
+ const context = contextTexts.join('\n\n---\n\n');
1397
+
1398
+ window.ragState.currentContext = rerankedResults.slice(0, window.ragState.topK).map(r => ({
1399
+ content: r.content,
1400
+ score: r.score,
1401
+ source: r.source
1402
+ }));
1403
+
1404
+ addContextMessage(window.ragState.currentContext);
1405
+
1406
+ // Step 5: Generate response
1407
+ animateNode('llm');
1408
+ showTyping();
1409
+
1410
+ const systemPrompt = `You are a helpful AI assistant using RAG (Retrieval-Augmented Generation). Answer based on the provided context. If the context doesn't contain relevant information, say so. Be concise and accurate.\n\nContext:\n${context}`;
1411
+
1412
+ const response = await window.ragState.generationModel(query, {
1413
+ max_new_tokens: 512,
1414
+ temperature: 0.7,
1415
+ do_sample: true,
1416
+ top_p: 0.9,
1417
+ chat_template: true
1418
+ });
1419
+
1420
+ hideTyping();
1421
+ animateNode('output');
1422
 
1423
+ const answerText = response[0].generated_text;
1424
+ // Clean up the response
1425
+ const cleanAnswer = answerText.replace(/^.*?(?=Answer|Based|According|The|This|Here|I)/s, '').trim();
1426
+ addAIMessage(cleanAnswer || answerText);
1427
+
1428
+ // Update latency
1429
+ const latency = Math.round(performance.now() - startTime);
1430
+ document.getElementById('statLatency').textContent = latency + 'ms';
1431
+
1432
+ // Deactivate nodes
1433
+ setTimeout(() => {
1434
+ clearNodeAnimations();
1435
+ }, 1000);
1436
+
1437
+ } catch (err) {
1438
+ console.error('Chat error:', err);
1439
+ hideTyping();
1440
+ addSystemMessage('❌ Error: ' + err.message);
1441
+ }
1442
+
1443
+ window.ragState.isProcessing = false;
1444
+ document.getElementById('sendBtn').disabled = false;
1445
+ }
1446
+
1447
+ function handleChatKey(e) {
1448
+ if (e.key === 'Enter' && !e.shiftKey) {
1449
+ e.preventDefault();
1450
+ sendMessage();
1451
+ }
1452
+ }
1453
+
1454
+ // ==================== ADD ENTRY ====================
1455
+ async function addEntry() {
1456
+ const content = document.getElementById('addContent').value.trim();
1457
+ const source = document.getElementById('addSource').value.trim() || 'manual';
1458
+ const category = document.getElementById('addCategory').value;
1459
+ const metadataStr = document.getElementById('addMetadata').value.trim();
1460
+
1461
+ if (!content) {
1462
+ alert('Please enter content');
1463
+ return;
1464
+ }
1465
+
1466
+ const btn = document.getElementById('addEntryBtn');
1467
+ btn.disabled = true;
1468
+ const progressBar = document.getElementById('addProgress');
1469
+ const progressFill = document.getElementById('addProgressFill');
1470
+ progressBar.style.display = 'block';
1471
+ progressFill.style.width = '20%';
1472
+
1473
+ try {
1474
+ progressFill.style.width = '40%';
1475
+ const vector = await embedText(content);
1476
+ progressFill.style.width = '70%';
1477
+
1478
+ let metadata = {};
1479
+ if (metadataStr) {
1480
+ try { metadata = JSON.parse(metadataStr); } catch(e) { metadata = { raw: metadataStr }; }
1481
+ }
1482
+
1483
+ const entry = {
1484
+ id: 'entry_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5),
1485
+ content: content,
1486
+ vector: vector,
1487
+ score: null,
1488
+ rerankScore: null,
1489
+ date: new Date().toISOString(),
1490
+ source: source,
1491
+ category: category,
1492
+ meta metadata
1493
+ };
1494
+
1495
+ window.ragState.vectorDB.push(entry);
1496
+ progressFill.style.width = '100%';
1497
+
1498
+ setTimeout(() => {
1499
+ progressBar.style.display = 'none';
1500
+ progressFill.style.width = '0%';
1501
+ }, 500);
1502
+
1503
+ renderVectorTable();
1504
+ saveToStorage();
1505
+ updateStats();
1506
+
1507
+ // Clear form
1508
+ document.getElementById('addContent').value = '';
1509
+ document.getElementById('addSource').value = '';
1510
+ document.getElementById('addMetadata').value = '';
1511
+
1512
+ addSystemMessage('✅ Entry added to Vector DB: ' + content.substring(0, 50) + '...');
1513
+ } catch (err) {
1514
+ console.error('Add entry error:', err);
1515
+ addSystemMessage(' Failed to add entry: ' + err.message);
1516
+ progressBar.style.display = 'none';
1517
+ }
1518
+
1519
+ btn.disabled = false;
1520
+ }
1521
+
1522
+ function addSample(idx) {
1523
+ const sample = SAMPLES[idx];
1524
+ document.getElementById('addContent').value = sample.content;
1525
+ document.getElementById('addSource').value = sample.source;
1526
+ document.getElementById('addCategory').value = sample.category;
1527
+ document.getElementById('addMetadata').value = JSON.stringify(sample.metadata);
1528
+
1529
+ // Auto-add
1530
+ setTimeout(() => addEntry(), 100);
1531
+ }
1532
+
1533
+ // ==================== RENDERING ====================
1534
+ function renderVectorTable() {
1535
+ const tbody = document.getElementById('vectorTableBody');
1536
+ const emptyState = document.getElementById('emptyVectors');
1537
+ const db = window.ragState.vectorDB;
1538
+
1539
+ if (db.length === 0) {
1540
+ tbody.innerHTML = '';
1541
+ emptyState.style.display = 'flex';
1542
+ document.querySelector('.vector-table-container').style.display = 'none';
1543
+ return;
1544
+ }
1545
+
1546
+ emptyState.style.display = 'none';
1547
+ document.querySelector('.vector-table-container').style.display = 'block';
1548
+
1549
+ tbody.innerHTML = db.map((entry, i) => {
1550
+ const vecPreview = entry.vector ? entry.vector.slice(0, 8).map(v => v.toFixed(3)).join(', ') + '...' : '-';
1551
+ const date = new Date(entry.date).toLocaleString();
1552
+ const meta = entry.metadata ? Object.keys(entry.metadata).slice(0, 2).map(k => k + ':' + entry.metadata[k]).join(', ') : '-';
1553
+
1554
+ return `<tr id="row_${entry.id}" data-id="${entry.id}">
1555
+ <td style="color:var(--text-muted)">${i + 1}</td>
1556
+ <td style="font-family:monospace;font-size:11px;color:var(--accent-blue)">${entry.id.substring(0, 16)}...</td>
1557
+ <td style="max-width:250px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${entry.content.substring(0, 80)}...</td>
1558
+ <td class="vector-preview">[${vecPreview}]</td>
1559
+ <td>${entry.score !== null ? '<span class="score-badge">' + entry.score.toFixed(4) + '</span>' : '-'}</td>
1560
+ <td style="font-size:11px;color:var(--text-muted)">${date}</td>
1561
+ <td style="font-size:11px;color:var(--text-muted)">${meta}</td>
1562
+ </tr>`;
1563
+ }).join('');
1564
+ }
1565
+
1566
+ function highlightTableRows(ids) {
1567
+ // Remove previous highlights
1568
+ document.querySelectorAll('.vector-table tr').forEach(tr => {
1569
+ tr.classList.remove('highlighted', 'selected');
1570
+ });
1571
+
1572
+ ids.forEach((id, i) => {
1573
+ const row = document.getElementById('row_' + id);
1574
+ if (row) {
1575
+ setTimeout(() => {
1576
+ row.classList.add('highlighted');
1577
+ setTimeout(() => {
1578
+ row.classList.add('selected');
1579
+ }, 500);
1580
+ }, i * 200);
1581
  }
1582
+ });
1583
+ }
1584
+
1585
+ function renderRerankResults(query, results) {
1586
+ const container = document.getElementById('rerankResults');
1587
+
1588
+ if (results.length === 0) {
1589
+ container.innerHTML = '<div class="empty-state"><p>No results to rerank</p></div>';
1590
+ return;
1591
+ }
1592
+
1593
+ container.innerHTML = '<div style="margin-bottom:12px;font-size:12px;color:var(--text-muted)">Query: <strong style="color:var(--text-primary)">' + query + '</strong></div>' +
1594
+ results.map((r, i) => {
1595
+ const rankClass = i === 0 ? 'rerank-1' : i === 1 ? 'rerank-2' : i === 2 ? 'rerank-3' : '';
1596
+ const rankNum = i + 1;
1597
+ return `<div class="rerank-card ${rankClass}">
1598
+ <div class="rerank-card-header">
1599
+ <span><span class="rank-badge rank-${rankNum <= 3 ? rankNum : 'other'}">${rankNum}</span> <span style="font-size:12px;color:var(--text-secondary)">Score: ${r.rerankScore !== undefined ? r.rerankScore.toFixed(4) : '-'}</span></span>
1600
+ <span style="font-size:11px;color:var(--text-muted)">Original: ${(r.score || 0).toFixed(4)}</span>
1601
+ </div>
1602
+ <div class="rerank-content">${r.content}</div>
1603
+ <div class="rerank-meta">
1604
+ <span>📄 ${r.source || 'unknown'}</span>
1605
+ <span>️ ${r.category || 'general'}</span>
1606
+ <span>📅 ${new Date(r.date).toLocaleDateString()}</span>
1607
+ </div>
1608
+ </div>`;
1609
+ }).join('');
1610
+ }
1611
+
1612
+ function updateStats() {
1613
+ document.getElementById('statEntries').textContent = window.ragState.vectorDB.length;
1614
+ document.getElementById('statTopK').textContent = window.ragState.topK;
1615
+ }
1616
 
1617
+ // ==================== NODE EDITOR ====================
1618
+ const NODES = [
1619
+ { id: 'input', label: 'Chat Input', icon: '', iconClass: 'node-icon-chat', x: 30, y: 150, desc: 'User query input', settings: { maxTokens: 512 }},
1620
+ { id: 'embed', label: 'Embedding', icon: '🔮', iconClass: 'node-icon-embed', x: 280, y: 150, desc: 'all-MiniLM-L6-v2 (384d)', settings: { model: 'all-MiniLM-L6-v2', dims: 384, normalize: true }},
1621
+ { id: 'vectordb', label: 'Vector DB', icon: '🗄️', iconClass: 'node-icon-vectordb', x: 530, y: 150, desc: 'LanceDB - Cosine Search', settings: { metric: 'cosine', topK: 3, index: 'flat' }},
1622
+ { id: 'rerank', label: 'Reranker', icon: '🔄', iconClass: 'node-icon-rerank', x: 780, y: 150, desc: 'ms-marco-MiniLM-L-6-v2', settings: { model: 'ms-marco-MiniLM', topK: 3 }},
1623
+ { id: 'llm', label: 'Qwen2.5-0.5B', icon: '🧠', iconClass: 'node-icon-llm', x: 1030, y: 150, desc: 'Text Generation', settings: { model: 'Qwen2.5-0.5B', maxTokens: 512, temp: 0.7, topP: 0.9 }},
1624
+ { id: 'output', label: 'Response', icon: '✨', iconClass: 'node-icon-output', x: 1280, y: 150, desc: 'Generated answer with context', settings: { format: 'text' }}
1625
+ ];
1626
+
1627
+ const CONNECTIONS = [
1628
+ { from: 'input', to: 'embed' },
1629
+ { from: 'embed', to: 'vectordb' },
1630
+ { from: 'vectordb', to: 'rerank' },
1631
+ { from: 'rerank', to: 'llm' },
1632
+ { from: 'llm', to: 'output' }
1633
+ ];
1634
+
1635
+ function initNodeEditor() {
1636
+ const canvas = document.getElementById('nodeCanvas');
1637
+
1638
+ NODES.forEach(node => {
1639
+ const el = document.createElement('div');
1640
+ el.className = 'node';
1641
+ el.id = 'node_' + node.id;
1642
+ el.style.left = node.x + 'px';
1643
+ el.style.top = node.y + 'px';
1644
+ el.innerHTML = `
1645
+ <div class="node-header">
1646
+ <span class="node-icon ${node.iconClass}">${node.icon}</span>
1647
+ <span>${node.label}</span>
1648
+ </div>
1649
+ <div class="node-body">${node.desc}</div>
1650
+ <div class="node-settings">
1651
+ ${renderNodeSettings(node)}
1652
+ </div>
1653
+ <div class="node-ports">
1654
+ <div class="port input" data-node="${node.id}" data-type="input"></div>
1655
+ <div class="port output" data-node="${node.id}" data-type="output"></div>
1656
+ </div>
1657
+ `;
1658
+ canvas.appendChild(el);
1659
+
1660
+ // Drag
1661
+ makeDraggable(el, node);
1662
+ });
1663
+
1664
+ drawConnections();
1665
+ }
1666
+
1667
+ function renderNodeSettings(node) {
1668
+ let html = '';
1669
+ for (const [key, val] of Object.entries(node.settings)) {
1670
+ html += `<div class="node-setting">
1671
+ <label>${key}</label>
1672
+ <input value="${val}" data-node="${node.id}" data-key="${key}" onchange="updateNodeSetting('${node.id}','${key}',this.value)">
1673
+ </div>`;
1674
+ }
1675
+ return html;
1676
+ }
1677
+
1678
+ function updateNodeSetting(nodeId, key, value) {
1679
+ const node = NODES.find(n => n.id === nodeId);
1680
+ if (node) {
1681
+ node.settings[key] = isNaN(value) ? value : Number(value);
1682
+ if (key === 'topK') {
1683
+ window.ragState.topK = Number(value);
1684
+ document.getElementById('statTopK').textContent = value;
1685
  }
1686
+ }
1687
+ }
1688
+
1689
+ function drawConnections() {
1690
+ const svg = document.getElementById('connectionSvg');
1691
+ svg.innerHTML = '';
1692
+
1693
+ CONNECTIONS.forEach(conn => {
1694
+ const fromEl = document.getElementById('node_' + conn.from);
1695
+ const toEl = document.getElementById('node_' + conn.to);
1696
+ if (!fromEl || !toEl) return;
1697
+
1698
+ const fromRect = fromEl.getBoundingClientRect();
1699
+ const toRect = toEl.getBoundingClientRect();
1700
+ const canvasRect = document.getElementById('nodeCanvas').getBoundingClientRect();
1701
+
1702
+ const x1 = fromRect.right - canvasRect.left;
1703
+ const y1 = fromRect.top + fromRect.height / 2 - canvasRect.top;
1704
+ const x2 = toRect.left - canvasRect.left;
1705
+ const y2 = toRect.top + toRect.height / 2 - canvasRect.top;
1706
+
1707
+ const midX = (x1 + x2) / 2;
1708
+
1709
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1710
+ path.setAttribute('d', `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`);
1711
+ path.setAttribute('class', 'connection');
1712
+ path.setAttribute('id', 'conn_' + conn.from + '_' + conn.to);
1713
+ svg.appendChild(path);
1714
+ });
1715
+ }
1716
+
1717
+ function makeDraggable(el, node) {
1718
+ let isDragging = false;
1719
+ let startX, startY, startLeft, startTop;
1720
 
1721
+ el.addEventListener('mousedown', (e) => {
1722
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
1723
+ isDragging = true;
1724
+ startX = e.clientX;
1725
+ startY = e.clientY;
1726
+ startLeft = el.offsetLeft;
1727
+ startTop = el.offsetTop;
1728
+ el.style.zIndex = 10;
1729
+ el.style.cursor = 'grabbing';
1730
+ e.preventDefault();
1731
+ });
1732
+
1733
+ document.addEventListener('mousemove', (e) => {
1734
+ if (!isDragging) return;
1735
+ const dx = e.clientX - startX;
1736
+ const dy = e.clientY - startY;
1737
+ el.style.left = (startLeft + dx) + 'px';
1738
+ el.style.top = (startTop + dy) + 'px';
1739
+ node.x = startLeft + dx;
1740
+ node.y = startTop + dy;
1741
+ drawConnections();
1742
+ });
1743
+
1744
+ document.addEventListener('mouseup', () => {
1745
+ if (isDragging) {
1746
+ isDragging = false;
1747
+ el.style.cursor = 'grab';
1748
+ el.style.zIndex = 5;
1749
  }
1750
+ });
1751
+ }
1752
 
1753
+ function animateNode(nodeId) {
1754
+ const el = document.getElementById('node_' + nodeId);
1755
+ if (el) {
1756
+ el.classList.add('processing');
1757
+ // Activate connections
1758
+ CONNECTIONS.forEach(conn => {
1759
+ if (conn.from === nodeId) {
1760
+ const path = document.getElementById('conn_' + conn.from + '_' + conn.to);
1761
+ if (path) path.classList.add('processing');
1762
+ }
1763
+ if (conn.to === nodeId) {
1764
+ const path = document.getElementById('conn_' + conn.from + '_' + conn.to);
1765
+ if (path) path.classList.add('processing');
1766
+ }
1767
+ });
1768
+ }
1769
+ }
1770
+
1771
+ function clearNodeAnimations() {
1772
+ document.querySelectorAll('.node').forEach(n => {
1773
+ n.classList.remove('processing', 'active');
1774
+ });
1775
+ document.querySelectorAll('.connection').forEach(c => {
1776
+ c.classList.remove('processing', 'active');
1777
+ });
1778
+ }
1779
+
1780
+ function resetNodePositions() {
1781
+ const defaultPositions = [
1782
+ { id: 'input', x: 30, y: 150 },
1783
+ { id: 'embed', x: 280, y: 150 },
1784
+ { id: 'vectordb', x: 530, y: 150 },
1785
+ { id: 'rerank', x: 780, y: 150 },
1786
+ { id: 'llm', x: 1030, y: 150 },
1787
+ { id: 'output', x: 1280, y: 150 }
1788
+ ];
1789
+
1790
+ defaultPositions.forEach(pos => {
1791
+ const node = NODES.find(n => n.id === pos.id);
1792
+ if (node) {
1793
+ node.x = pos.x;
1794
+ node.y = pos.y;
1795
+ const el = document.getElementById('node_' + pos.id);
1796
+ if (el) {
1797
+ el.style.left = pos.x + 'px';
1798
+ el.style.top = pos.y + 'px';
1799
  }
 
1800
  }
1801
+ });
1802
+ drawConnections();
1803
+ }
1804
 
1805
+ function runPipeline() {
1806
+ const input = document.getElementById('chatInput').value.trim();
1807
+ if (!input) {
1808
+ addSystemMessage('⚠️ Enter a query in the chat to run the pipeline');
1809
+ return;
1810
+ }
1811
+ sendMessage();
1812
+ }
1813
+
1814
+ // ==================== STORAGE ====================
1815
+ function saveToStorage() {
1816
+ try {
1817
+ const data = {
1818
+ vectorDB: window.ragState.vectorDB.map(e => ({
1819
+ ...e,
1820
+ vector: e.vector ? Array.from(e.vector) : null
1821
+ })),
1822
+ chatHistory: window.ragState.chatHistory,
1823
+ topK: window.ragState.topK
1824
+ };
1825
+ localStorage.setItem('rag_visualizer_db', JSON.stringify(data));
1826
+ } catch (e) {
1827
+ console.warn('Storage save failed:', e);
1828
+ }
1829
+ }
1830
+
1831
+ function loadFromStorage() {
1832
+ try {
1833
+ const data = localStorage.getItem('rag_visualizer_db');
1834
+ if (data) {
1835
+ const parsed = JSON.parse(data);
1836
+ window.ragState.vectorDB = parsed.vectorDB || [];
1837
+ window.ragState.chatHistory = parsed.chatHistory || [];
1838
+ window.ragState.topK = parsed.topK || 3;
1839
+ renderVectorTable();
1840
+ updateStats();
1841
  }
1842
+ } catch (e) {
1843
+ console.warn('Storage load failed:', e);
1844
+ }
1845
+ }
1846
+
1847
+ function exportDB() {
1848
+ const data = {
1849
+ vectorDB: window.ragState.vectorDB.map(e => ({
1850
+ ...e,
1851
+ vector: e.vector ? Array.from(e.vector) : null
1852
+ })),
1853
+ chatHistory: window.ragState.chatHistory,
1854
+ exportDate: new Date().toISOString()
1855
+ };
1856
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
1857
+ const url = URL.createObjectURL(blob);
1858
+ const a = document.createElement('a');
1859
+ a.href = url;
1860
+ a.download = 'rag_visualizer_export_' + Date.now() + '.json';
1861
+ a.click();
1862
+ URL.revokeObjectURL(url);
1863
+ addSystemMessage('📦 Database exported');
1864
+ }
1865
+
1866
+ function clearAll() {
1867
+ if (confirm('Clear all data including vector DB and chat history?')) {
1868
+ window.ragState.vectorDB = [];
1869
+ window.ragState.chatHistory = [];
1870
+ localStorage.removeItem('rag_visualizer_db');
1871
+ renderVectorTable();
1872
+ updateStats();
1873
+ document.getElementById('chatMessages').innerHTML = '<div class="message message-system">🤖 RAG Visualizer reset. Models still loaded.</div>';
1874
+ addSystemMessage('️ All data cleared');
1875
+ }
1876
+ }
1877
+
1878
+ // ==================== TABS ====================
1879
+ document.querySelectorAll('.chat-tab').forEach(tab => {
1880
+ tab.addEventListener('click', () => {
1881
+ document.querySelectorAll('.chat-tab').forEach(t => t.classList.remove('active'));
1882
+ tab.classList.add('active');
1883
+ });
1884
+ });
1885
 
1886
+ document.querySelectorAll('.right-tab').forEach(tab => {
1887
+ tab.addEventListener('click', () => {
1888
+ document.querySelectorAll('.right-tab').forEach(t => t.classList.remove('active'));
1889
+ tab.classList.add('active');
1890
+ const panelId = 'panel-' + tab.dataset.tab;
1891
+ document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
1892
+ document.getElementById(panelId).classList.add('active');
1893
+
1894
+ if (tab.dataset.tab === 'nodes') {
1895
+ setTimeout(drawConnections, 100);
1896
  }
1897
+ });
1898
+ });
1899
 
1900
+ // ==================== RESIZE HANDLER ====================
1901
+ window.addEventListener('resize', () => {
1902
+ drawConnections();
1903
+ });
1904
 
1905
+ // ==================== AUTO-RESIZE TEXTAREA ====================
1906
+ document.getElementById('chatInput').addEventListener('input', function() {
1907
+ this.style.height = 'auto';
1908
+ this.style.height = Math.min(this.scrollHeight, 120) + 'px';
1909
+ });
1910
+
1911
+ // ==================== START ====================
1912
+ init();
1913
+ </script>
1914
  </body>
1915
+ </html>
1916
+