| <!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.85); |
| --border: rgba(15,23,42,0.08); |
| --primary: #4f46e5; |
| --secondary: #0ea5e9; |
| --text: #0f172a; |
| --muted: #64748b; |
| --error: #dc2626; |
| } |
| |
| * { 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); |
| } |
| |
| .container { |
| width: 100%; |
| max-width: 980px; |
| 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); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| .subtitle { |
| margin-top: 8px; |
| color: var(--muted); |
| font-size: 1rem; |
| } |
| |
| .card { |
| margin-top: 28px; |
| background: white; |
| 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; |
| } |
| |
| textarea { |
| min-height: 120px; |
| resize: vertical; |
| } |
| |
| .row { |
| display: flex; |
| gap: 12px; |
| margin-top: 12px; |
| flex-wrap: wrap; |
| } |
| |
| button { |
| padding: 12px 18px; |
| 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 10px 25px rgba(79,70,229,.35); |
| } |
| |
| .status { |
| margin-top: 10px; |
| font-size: .9rem; |
| color: var(--muted); |
| } |
| |
| .answer { |
| margin-top: 24px; |
| padding: 20px; |
| border-radius: 16px; |
| background: #f8fafc; |
| border: 1px solid var(--border); |
| white-space: pre-wrap; |
| line-height: 1.6; |
| } |
| |
| .error { |
| color: var(--error); |
| margin-top: 10px; |
| font-weight: 500; |
| } |
| |
| .loader { |
| font-weight: 600; |
| color: var(--primary); |
| animation: pulse 1.2s infinite; |
| } |
| |
| @keyframes pulse { |
| 0% { opacity: .4 } |
| 50% { opacity: 1 } |
| 100% { opacity: .4 } |
| } |
| |
| footer { |
| text-align: center; |
| margin-top: 28px; |
| font-size: .8rem; |
| color: var(--muted); |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <div class="container"> |
| <h1>Gemini RAG Assistant</h1> |
| <div class="subtitle"> |
| Upload documents · Ask questions · Get grounded answers |
| </div> |
|
|
| |
| <div class="card"> |
| <h3>📄 Upload documents</h3> |
| <input type="file" id="files" multiple /> |
| <div class="row"> |
| <button id="uploadBtn" onclick="upload()">Upload & Index</button> |
| </div> |
| <div id="uploadStatus" class="status"></div> |
| </div> |
|
|
| |
| <div class="card"> |
| <h3>💬 Ask or summarize</h3> |
| <textarea id="question" placeholder="Ask something about your documents…"></textarea> |
| <div class="row"> |
| <button id="askBtn" onclick="ask()">Ask</button> |
| <button class="secondary" id="sumBtn" onclick="summarize()">Summarize</button> |
| </div> |
| </div> |
|
|
| |
| <div id="answerBox" class="answer" style="display:none;"></div> |
| <div id="errorBox" class="error"></div> |
|
|
| <footer> |
| Built with FastAPI · FAISS · Gemini |
| </footer> |
| </div> |
|
|
| <script> |
| 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) return; |
| |
| setBusy(true); |
| document.getElementById("uploadStatus").innerText = "Indexing documents…"; |
| |
| const fd = new FormData(); |
| for (let f of files) fd.append("files", f); |
| |
| const res = await fetch("/upload", { method: "POST", body: fd }); |
| const data = await res.json(); |
| |
| document.getElementById("uploadStatus").innerText = data.message || "Done ✅"; |
| setBusy(false); |
| } |
| |
| async function ask() { |
| const q = document.getElementById("question").value.trim(); |
| if (!q || busy) return; |
| |
| setBusy(true); |
| document.getElementById("errorBox").innerText = ""; |
| document.getElementById("answerBox").style.display = "block"; |
| document.getElementById("answerBox").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(); |
| document.getElementById("answerBox").innerText = data.answer; |
| } catch { |
| document.getElementById("errorBox").innerText = |
| "⚠️ LLM quota exceeded. Please wait ~1 minute and retry."; |
| } |
| |
| setBusy(false); |
| } |
| |
| function summarize() { |
| document.getElementById("question").value = |
| "Summarize the uploaded documents in 5 bullet points."; |
| ask(); |
| } |
| </script> |
| </body> |
| </html> |
|
|