lvvignesh2122's picture
RAG v2: HNSW ANN, cross-encoder reranking, evaluation logging, analytics dashboard
4af310b
raw
history blame
9.08 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Gemini RAG Assistant</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: radial-gradient(1200px 600px at top, #e0e7ff 0%, #f8fafc 60%);
--card: rgba(255, 255, 255, 0.9);
--border: rgba(15, 23, 42, 0.08);
--primary: #4f46e5;
--secondary: #0ea5e9;
--text: #0f172a;
--muted: #64748b;
--error: #dc2626;
--success: #16a34a;
}
[data-theme="dark"] {
--bg: radial-gradient(1200px 600px at top, #1e1b4b 0%, #0f172a 60%);
--card: rgba(30, 41, 59, 0.9);
--border: rgba(148, 163, 184, 0.1);
--primary: #818cf8;
--secondary: #38bdf8;
--text: #f1f5f9;
--muted: #94a3b8;
--error: #f87171;
--success: #4ade80;
}
* {
box-sizing: border-box;
font-family: Inter, sans-serif;
}
body {
margin: 0;
min-height: 100vh;
background: var(--bg);
display: flex;
justify-content: center;
padding: 40px 16px;
color: var(--text);
transition: background 0.3s ease, color 0.3s ease;
}
.container {
width: 100%;
max-width: 800px;
background: var(--card);
backdrop-filter: blur(16px);
border-radius: 24px;
padding: 36px;
border: 1px solid var(--border);
box-shadow: 0 40px 120px rgba(15, 23, 42, .15);
}
h1 {
font-size: 2.2rem;
margin: 0;
font-weight: 700;
background: linear-gradient(135deg, #4f46e5, #06b6d4);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.subtitle {
margin-top: 8px;
color: var(--muted);
font-size: 1rem;
}
.card {
margin-top: 28px;
background: var(--card);
border-radius: 18px;
padding: 24px;
border: 1px solid var(--border);
}
.card h3 {
margin-top: 0;
margin-bottom: 16px;
font-size: 1.1rem;
}
input[type="file"],
textarea {
width: 100%;
padding: 14px;
border-radius: 14px;
border: 1px solid var(--border);
font-size: 0.95rem;
background: var(--card);
color: var(--text);
}
textarea {
min-height: 100px;
resize: vertical;
}
.row {
display: flex;
gap: 12px;
margin-top: 12px;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
border-radius: 14px;
border: none;
background: var(--primary);
color: white;
font-weight: 600;
cursor: pointer;
transition: all .2s ease;
}
button.secondary {
background: var(--secondary);
}
button:disabled {
opacity: .5;
cursor: not-allowed;
}
button:hover:not(:disabled) {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(79, 70, 229, .2);
}
.status {
margin-top: 10px;
font-size: .9rem;
color: var(--muted);
}
.answer {
margin-top: 24px;
padding: 22px;
border-radius: 16px;
background: var(--card);
border: 1px solid var(--border);
line-height: 1.6;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
color: var(--text);
}
.confidence-badge {
display: inline-block;
margin-top: 12px;
padding: 4px 12px;
border-radius: 20px;
background: #dcfce7;
color: #166534;
font-size: 0.8rem;
font-weight: 600;
}
.citations {
margin-top: 16px;
font-size: .85rem;
color: var(--muted);
border-top: 1px solid var(--border);
padding-top: 12px;
}
.citations ul {
margin: 6px 0 0;
padding-left: 20px;
}
.loader {
font-weight: 600;
color: var(--primary);
animation: pulse 1.2s infinite;
}
@keyframes pulse {
0% {
opacity: .4
}
50% {
opacity: 1
}
100% {
opacity: .4
}
}
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background: var(--card);
border: 1px solid var(--border);
border-radius: 12px;
padding: 10px;
cursor: pointer;
font-size: 1.4rem;
transition: transform 0.2s ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.theme-toggle:hover {
transform: scale(1.1);
}
</style>
</head>
<body>
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle dark mode">🌙</button>
<div class="container">
<h1>Gemini RAG Assistant</h1>
<div class="subtitle">Upload documents · Ask questions · Get grounded answers · <a href="/frontend/analytics.html"
style="color: var(--primary); text-decoration: none; font-weight: 600;">📊 Analytics</a></div>
<div class="card">
<h3>1. Upload Knowledge</h3>
<input type="file" id="files" multiple accept=".pdf,.txt" />
<div class="row">
<button id="uploadBtn" onclick="upload()">Upload & Index Files</button>
</div>
<div id="uploadStatus" class="status"></div>
</div>
<div class="card">
<h3>2. Ask or Summarize</h3>
<textarea id="question" placeholder="E.g., 'What are the main risks?' or 'Summarize the document'"></textarea>
<div class="row">
<button id="askBtn" onclick="ask()">Ask Question</button>
<button class="secondary" id="sumBtn" onclick="summarize()">Generate Summary</button>
</div>
</div>
<div id="answerBox" class="answer" style="display:none;"></div>
</div>
<script>
// ===== THEME TOGGLE =====
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
// Update button icon
const btn = document.querySelector('.theme-toggle');
btn.textContent = newTheme === 'dark' ? '☀️' : '🌙';
}
// Load saved theme on page load
(function () {
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
const btn = document.querySelector('.theme-toggle');
if (btn) btn.textContent = savedTheme === 'dark' ? '☀️' : '🌙';
})();
// ===== APP LOGIC =====
let busy = false;
function setBusy(state) {
busy = state;
document.getElementById("askBtn").disabled = state;
document.getElementById("sumBtn").disabled = state;
document.getElementById("uploadBtn").disabled = state;
}
async function upload() {
const files = document.getElementById("files").files;
if (!files.length) {
alert("Please select files first.");
return;
}
setBusy(true);
const statusDiv = document.getElementById("uploadStatus");
statusDiv.innerText = "Indexing documents... this may take a moment.";
const fd = new FormData();
for (let f of files) fd.append("files", f);
try {
const res = await fetch("/upload", { method: "POST", body: fd });
if (!res.ok) throw new Error("Upload failed");
const data = await res.json();
statusDiv.innerText = data.message || "Done ✅";
} catch (e) {
statusDiv.innerText = "Error uploading files.";
}
setBusy(false);
}
async function ask() {
const q = document.getElementById("question").value.trim();
if (!q) return;
setBusy(true);
const box = document.getElementById("answerBox");
box.style.display = "block";
box.innerHTML = "<span class='loader'>Thinking...</span>";
try {
const res = await fetch("/ask", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt: q })
});
const data = await res.json();
let html = `<div><strong>Answer:</strong><br>${data.answer.replace(/\n/g, '<br>')}</div>`;
if (data.confidence > 0) {
html += `<div class="confidence-badge">Confidence: ${(data.confidence * 100).toFixed(0)}%</div>`;
}
if (data.citations && data.citations.length > 0) {
html += `<div class="citations"><strong>Sources:</strong><ul>`;
data.citations.forEach(c => {
html += `<li>${c.source} (Page ${c.page})</li>`;
});
html += `</ul></div>`;
}
box.innerHTML = html;
} catch (e) {
box.innerText = "⚠️ Error communicating with the server.";
}
setBusy(false);
}
function summarize() {
document.getElementById("question").value = "Summarize the uploaded documents";
ask();
}
</script>
</body>
</html>