FurqanAli12345's picture
Deploy static Space
475fa4f verified
Raw History Blame Contribute Delete
6.97 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>RAG Document Assistant</title>
<style>
:root{--bg:#f6f8fa;--card:#fff;--ink:#0f172a;--muted:#64748b;--line:#e2e8f0;--brand:#0f766e;--quote:#f1f5f9}
@media(prefers-color-scheme:dark){:root{--bg:#0b1220;--card:#131c2b;--ink:#e6edf6;--muted:#94a3b8;--line:#243247;--brand:#5eead4;--quote:#0e1726}}
*{box-sizing:border-box}
body{margin:0;font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;background:var(--bg);color:var(--ink);line-height:1.55}
.wrap{max-width:980px;margin:0 auto;padding:28px 18px 60px}
h1{font-size:1.7rem;margin:0 0 6px}.sub{color:var(--muted);margin:0 0 18px}
.badges span{display:inline-block;font-size:.72rem;background:var(--card);border:1px solid var(--line);color:var(--muted);border-radius:999px;padding:3px 10px;margin:2px 4px 2px 0}
.grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:16px}
@media(max-width:760px){.grid{grid-template-columns:1fr}}
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;padding:18px}
button.go{background:var(--brand);color:#04231f;border:none;border-radius:10px;padding:10px 16px;font-weight:700;cursor:pointer}
button.ghost{background:var(--bg);color:var(--ink);border:1px solid var(--line);border-radius:10px;padding:10px 16px;font-weight:600;cursor:pointer}
button:hover{filter:brightness(1.06)}
input[type=text]{width:100%;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:var(--ink);font-size:.95rem}
input[type=file]{color:var(--muted);font-size:.9rem;margin-bottom:10px;width:100%}
.row{display:flex;gap:10px;flex-wrap:wrap;margin-top:6px}
.note{color:var(--muted);font-size:.86rem;margin-top:10px}
.answer{margin-top:14px}.answer h3{margin:0 0 6px;font-size:1rem}
.src{margin-top:6px;border-left:3px solid var(--brand);padding:8px 12px;background:var(--quote);border-radius:0 8px 8px 0}
.src .h{font-size:.8rem;color:var(--muted)}.src blockquote{margin:4px 0 0;color:var(--ink);font-size:.9rem}
.chips button{font-size:.8rem;margin:4px 6px 0 0;background:var(--bg);border:1px solid var(--line);color:var(--ink);border-radius:8px;padding:5px 10px;cursor:pointer}
details{margin-top:20px;background:var(--card);border:1px solid var(--line);border-radius:14px;padding:14px 18px}
summary{cursor:pointer;font-weight:600}a{color:var(--brand)}
</style>
</head>
<body>
<div class="wrap">
<h1>📄 RAG Document Assistant</h1>
<p class="sub">Ask questions about your own documents — answers are grounded in your text, cited to the source, and it says “I don't know” instead of guessing.</p>
<div class="badges"><span>overlapping chunking</span><span>TF-IDF embeddings</span><span>cosine retrieval</span><span>citations</span><span>honest refusal</span><span>100% in-browser</span></div>
<div class="grid">
<div class="card">
<input type="file" id="files" accept=".txt,.md" multiple />
<div class="row">
<button class="go" id="buildBtn">Build index from uploads</button>
<button class="ghost" id="sampleBtn">Load sample docs</button>
</div>
<div class="note" id="status">Upload .txt / .md files, or load the sample handbook to try it.</div>
</div>
<div class="card">
<input type="text" id="q" placeholder="e.g. What is the refund window?" />
<div class="row"><button class="go" id="askBtn">Ask</button></div>
<div class="chips">
<button>What is the refund window?</button>
<button>How do I reset my password?</button>
<button>What are the support hours?</button>
<button>Who is eligible for the loyalty program?</button>
</div>
<div class="answer" id="answer"></div>
<div id="sources"></div>
</div>
</div>
<details>
<summary>ℹ️ How RAG works here, limits &amp; data</summary>
<p style="color:var(--muted)"><b>Chunking</b> splits documents into overlapping windows so context isn't cut at boundaries. <b>Embeddings</b> here use a TF-IDF vectoriser computed in your browser; <b>retrieval</b> ranks chunks by cosine similarity. Answers are built <b>only</b> from retrieved chunks and cited; below a similarity threshold the assistant returns <b>“I don't know”</b> rather than hallucinating. This static demo is <b>extractive</b> (returns the most relevant passage). The full Python version adds neural <code>sentence-transformers</code> embeddings and optional LLM generation. Uploaded files are processed in-memory and never leave your device. 🔗 <a href="https://github.com/furqunali/rag-document-assistant" target="_blank" rel="noopener">Source on GitHub</a></p>
</details>
</div>
<script src="logic.js"></script>
<script>
const RAG=window.RAG,$=(id)=>document.getElementById(id);
let retriever=null;
function indexFrom(named){
retriever=RAG.buildRetriever(named);
if(!retriever){$("status").textContent="No readable text found. Upload .txt or .md files with content.";return;}
const chunks=retriever.chunks.length;
$("status").innerHTML=`✅ Indexed <b>${chunks} chunks</b> from <b>${named.length} document(s)</b>. Embedder: <code>tfidf (in-browser)</code> · Mode: extractive (key-free).`;
}
$("sampleBtn").onclick=()=>indexFrom(RAG.SAMPLE_DOCS);
$("buildBtn").onclick=()=>{
const fl=$("files").files;
if(!fl.length){$("status").textContent="Choose one or more .txt/.md files first, or load the samples.";return;}
let pending=fl.length,named=[];
[...fl].forEach(file=>{const rd=new FileReader();
rd.onload=()=>{if(String(rd.result).trim())named.push([file.name,String(rd.result)]);
if(--pending===0)indexFrom(named);};
rd.readAsText(file);});
};
function ask(){
const q=$("q").value.trim();
if(!q){$("answer").innerHTML="Type a question about your documents.";return;}
if(!retriever){$("answer").innerHTML="⚠️ Build an index first — upload docs or load the samples.";$("sources").innerHTML="";return;}
const hits=retriever.query(q,4),res=RAG.answer(q,hits);
const modeLine=res.grounded?`<div class="note">Mode: ${res.mode} · top similarity: ${res.top_score}</div>`
:`<div class="note">(no confident match · top similarity: ${res.top_score})</div>`;
$("answer").innerHTML=`<h3>Answer</h3><div>${res.answer}</div>${modeLine}`;
$("sources").innerHTML=res.citations.length?("<h3 style='font-size:1rem;margin:14px 0 4px'>Sources</h3>"+
res.citations.map(c=>`<div class="src"><div class="h">${c.source} · chunk ${c.chunk} · sim ${c.score}</div><blockquote>${c.preview}</blockquote></div>`).join("")):"";
}
$("askBtn").onclick=ask;
$("q").addEventListener("keydown",e=>{if(e.key==="Enter")ask();});
document.querySelectorAll(".chips button").forEach(b=>b.onclick=()=>{$("q").value=b.textContent;ask();});
</script>
</body>
</html>