Spaces:
Running
Running
Update index.html
Browse files- index.html +1890 -114
index.html
CHANGED
|
@@ -1,140 +1,1916 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="en"
|
| 3 |
<head>
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
</head>
|
| 18 |
-
<body
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
</
|
| 23 |
-
|
| 24 |
-
<div class="
|
| 25 |
-
<
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
</div>
|
| 31 |
</div>
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
<
|
| 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 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
-
|
| 58 |
-
<
|
| 59 |
-
|
| 60 |
-
<
|
| 61 |
-
<div
|
| 62 |
-
<
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</div>
|
| 65 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
| 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 |
</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 |
+
|