| <!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> |
| |
| 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); |
| |
| |
| const btn = document.querySelector('.theme-toggle'); |
| btn.textContent = newTheme === 'dark' ? '☀️' : '🌙'; |
| } |
| |
| |
| (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' ? '☀️' : '🌙'; |
| })(); |
| |
| |
| 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> |