RAG Application
This is the final output - We need to use the python 3.12.0 version to achieve this as there are dependent libraries.
Your folder structure should look like -
WorkspaceFolder/
├── chroma_db/
├── uploads/
├── templates/
│ └── index.html
├── requirements.txt
├── app.py
Frontend
- HTML5
- CSS3
- Bootstrap 5
- JQuery
- AJAX
Backend
- Python 3.12.0
- Flask (server)
Features Covered
index.html looks like below -
<!doctype html> <html lang= "en" > <head> <meta charset= "utf-8" /> <meta name= "viewport" content= "width=device-width, initial-scale=1" /> <title> Venkatesh RAG Chatbot </title> <!-- Markdown + syntax highlighting --> <script src= "https://cdn.jsdelivr.net/npm/marked/marked.min.js" ></script> <link rel= "stylesheet" href= "https://cdn.jsdelivr.net/npm/highlight.js@11/styles/atom-one-dark.min.css" /> <script src= "https://cdn.jsdelivr.net/npm/highlight.js@11/lib/common.min.js" ></script> <style> :root { --bg : #0e1117 ; --panel : #161b22 ; --panel2 : #1c2330 ; --border : #2a3140 ; --text : #e6edf3 ; --muted : #8b949e ; --accent : #4f8cff ; --danger : #ff5c5c ; --ok : #3fb950 ; } * { box-sizing : border-box } html , body { height : 100% ; margin : 0 ; font-family : system-ui , Segoe UI , Roboto , sans-serif ; background : var ( --bg ); color : var ( --text )} #app { display : flex ; height : 100vh } /* ---------- Sidebar ---------- */ #sidebar { width : 300px ; background : var ( --panel ); border-right : 1px solid var ( --border ); display : flex ; flex-direction : column ; transition : width .2s } #sidebar .collapsed { width : 44px } #sidebar .collapsed .side-content { display : none } .side-head { display : flex ; align-items : center ; justify-content : space-between ; padding : 12px ; border-bottom : 1px solid var ( --border )} .side-head h3 { font-size : 14px ; margin : 0 ; color : var ( --muted )} .side-content { padding : 12px ; overflow : auto ; flex : 1 ; display : flex ; flex-direction : column ; gap : 10px } .btn { background : var ( --panel2 ); border : 1px solid var ( --border ); color : var ( --text ); padding : 8px 10px ; border-radius : 6px ; cursor : pointer ; font-size : 13px } .btn :hover { border-color : var ( --accent )} .btn.primary { background : var ( --accent ); border-color : var ( --accent ); color : #fff } .btn.danger { color : var ( --danger ); border-color : var ( --danger )} .path { font-size : 11px ; color : var ( --muted ); word-break : break-all ; min-height : 14px } .row { display : flex ; gap : 6px } /* file list */ #fileList { list-style : none ; padding : 0 ; margin : 0 ; display : flex ; flex-direction : column ; gap : 6px ; max-height : 45vh ; overflow-y : auto } #fileList li { display : flex ; align-items : center ; justify-content : space-between ; background : var ( --panel2 ); border : 1px solid var ( --border ); border-radius : 6px ; padding : 6px 8px ; font-size : 12px } #fileList li span { overflow : hidden ; text-overflow : ellipsis ; white-space : nowrap } .x { cursor : pointer ; color : var ( --danger ); font-weight : 700 ; margin-left : 8px } #collapseBtn { background : none ; border : 0 ; color : var ( --muted ); font-size : 18px ; cursor : pointer } /* ---------- Chat ---------- */ #main { flex : 1 ; display : flex ; flex-direction : column } #header { padding : 14px 20px ; border-bottom : 1px solid var ( --border ); display : flex ; align-items : center ; gap : 10px } #header h1 { font-size : 15px ; margin : 0 } #chat { flex : 1 ; overflow-y : auto ; padding : 24px ; display : flex ; flex-direction : column ; gap : 16px } .msg { max-width : 820px ; padding : 12px 16px ; border-radius : 12px ; line-height : 1.55 ; font-size : 14px ; white-space : normal } .msg.user { align-self : flex-end ; background : #23324a } .msg.bot { align-self : flex-start ; background : var ( --panel ); border : 1px solid var ( --border )} .msg code { background : #000 ; color : #e6edf3 ; padding : 1px 4px ; border-radius : 4px ; font-size : 12px } .msg pre { position : relative ; background : #000 ; border-radius : 8px ; padding : 12px ; overflow-x : auto ; border : 1px solid var ( --border )} .msg pre code { background : none ; padding : 0 } .copy-code { position : absolute ; top : 6px ; right : 6px ; font-size : 11px ; padding : 2px 6px ; background : #222 ; color : #ddd ; border : 1px solid #444 ; border-radius : 4px ; cursor : pointer } .copy-code :hover { background : #333 } .sources { font-size : 11px ; color : var ( --muted ); margin-top : 8px } .meta { font-size : 11px ; color : var ( --muted ); margin-top : 4px } /* input bar */ #inputBar { display : flex ; gap : 8px ; padding : 14px 20px ; border-top : 1px solid var ( --border ); background : var ( --panel )} #query { flex : 1 ; resize : none ; background : var ( --panel2 ); color : var ( --text ); border : 1px solid var ( --border ); border-radius : 8px ; padding : 12px ; font-size : 14px ; min-height : 52px ; max-height : 150px } #query :focus { outline : none ; border-color : var ( --accent )} #send { width : 52px ; height : 52px ; border-radius : 50% ; background : var ( --accent ); border : 0 ; color : #fff ; font-size : 22px ; cursor : pointer } #send :disabled { opacity : .5 ; cursor : not-allowed } .hint { font-size : 11px ; color : var ( --muted ); padding : 0 22px 10px ; background : var ( --panel )} </style> </head> <body> <div id= "app" > <!-- ================= SIDEBAR ================= --> <aside id= "sidebar" > <div class= "side-head" > <h3> Knowledge Base </h3> <button id= "collapseBtn" title= "Toggle" > ≡ </button> </div> <div class= "side-content" > <!-- Choose Folder --> <div class= "row" > <button class= "btn" id= "chooseFolder" > Choose Folder </button> </div> <div class= "path" id= "folderPath" > No folder selected </div> <input type= "file" id= "folderInput" webkitdirectory directory multiple hidden /> <!-- Choose File --> <div class= "row" > <button class= "btn" id= "chooseFile" > Choose File </button> </div> <div class= "path" id= "filePath" > No file selected </div> <input type= "file" id= "fileInput" accept= ".pdf,.docx,.xlsx,.pptx" multiple hidden /> <!-- Upload --> <div class= "row" > <button class= "btn primary" id= "uploadBtn" disabled > Upload </button> </div> <hr style= "border-color:var(--border);width:100%" /> <!-- File list --> <h4 style= "font-size:12px;color:var(--muted);margin:0" > Uploaded Files </h4> <ul id= "fileList" ></ul> </div> </aside> <!-- ================= MAIN ================= --> <main id= "main" > <div id= "header" > <h1> Venkatesh RAG Chatbot </h1> <span class= "meta" id= "status" ></span> </div> <div id= "chat" > <div class= "msg bot" > 👋 Hi! Upload PDFs, Word, Excel or PowerPoint files on the left, then ask me anything about them. </div> </div> <div id= "inputBar" > <textarea id= "query" placeholder= "Ask something about your documents…" ></textarea> <button id= "send" title= "Send" > > </button> </div> <div class= "hint" > Shift+Enter for newline · Enter to send </div> </main> </div> <script> /* ======================= STATE ======================= */ let pendingFiles = []; const $ = id => document . getElementById ( id ); /* ======================= SIDEBAR COLLAPSE ======================= */ /* $('collapseBtn').onclick = () => $('sidebar').classList.toggle('collapsed'); */ /* ======================= CHOOSE FOLDER ======================= */ $ ( ' chooseFolder ' ). onclick = () => $ ( ' folderInput ' ). click (); $ ( ' folderInput ' ). onchange = e => { const files = [... e . target . files ]. filter ( f => / \.( pdf|docx|xlsx|pptx ) $/i . test ( f . name )); pendingFiles = files ; $ ( ' folderPath ' ). textContent = files . length ? ` ${ e . target . files [ 0 ]. webkitRelativePath . split ( ' / ' )[ 0 ]} / ( ${ files . length } supported files)` : ' No supported files in folder ' ; $ ( ' filePath ' ). textContent = ' No file selected ' ; $ ( ' uploadBtn ' ). disabled = files . length === 0 ; }; /* ======================= CHOOSE FILE ======================= */ $ ( ' chooseFile ' ). onclick = () => $ ( ' fileInput ' ). click (); $ ( ' fileInput ' ). onchange = e => { const files = [... e . target . files ]; pendingFiles = files ; $ ( ' filePath ' ). textContent = files . length === 1 ? files [ 0 ]. name : ` ${ files . length } files selected` ; $ ( ' folderPath ' ). textContent = ' No folder selected ' ; $ ( ' uploadBtn ' ). disabled = files . length === 0 ; }; /* ======================= UPLOAD ======================= */ $ ( ' uploadBtn ' ). onclick = async () => { if ( ! pendingFiles . length ) return ; const fd = new FormData (); pendingFiles . forEach ( f => fd . append ( ' files ' , f , f . name )); $ ( ' uploadBtn ' ). disabled = true ; $ ( ' uploadBtn ' ). textContent = ' Uploading… ' ; try { const r = await fetch ( ' /api/upload ' , { method : ' POST ' , body : fd }); const data = await r . json (); if ( data . error ) throw new Error ( data . error ); addBotMsg ( `✅ Uploaded. ** ${ data . total_chunks } ** chunks embedded.` ); await refreshFileList (); } catch ( e ) { addBotMsg ( `❌ Upload failed: \` ${ e . message } \` ` ); } finally { $ ( ' uploadBtn ' ). disabled = false ; $ ( ' uploadBtn ' ). textContent = ' Upload ' ; pendingFiles = []; $ ( ' folderPath ' ). textContent = ' No folder selected ' ; $ ( ' filePath ' ). textContent = ' No file selected ' ; $ ( ' folderInput ' ). value = '' ; $ ( ' fileInput ' ). value = '' ; } }; /* ======================= FILE LIST ======================= */ async function refreshFileList () { const r = await fetch ( ' /api/files ' ); const { files } = await r . json (); const ul = $ ( ' fileList ' ); ul . innerHTML = '' ; if ( ! files . length ) { ul . innerHTML = ' <li style="justify-content:center;color:var(--muted)">No files yet</li> ' ; return ; } files . forEach ( name => { const li = document . createElement ( ' li ' ); li . innerHTML = `<span title=" ${ name } "> ${ name } </span> <span class="x" title="Delete">✕</span>` ; li . querySelector ( ' .x ' ). onclick = () => deleteFile ( name ); ul . appendChild ( li ); }); } async function deleteFile ( name ) { if ( ! confirm ( `Delete " ${ name } " and its embeddings?` )) return ; await fetch ( ' /api/files ' , { method : ' DELETE ' , headers :{ ' Content-Type ' : ' application/json ' }, body : JSON . stringify ({ name }) }); refreshFileList (); addBotMsg ( `🗑️ Deleted ** ${ name } ** from the vector database.` ); } /* ======================= CHAT ======================= */ function addUserMsg ( text ) { const d = document . createElement ( ' div ' ); d . className = ' msg user ' ; d . textContent = text ; $ ( ' chat ' ). appendChild ( d ); scrollDown (); } function addBotMsg ( markdown , sources = []) { const d = document . createElement ( ' div ' ); d . className = ' msg bot ' ; d . innerHTML = marked . parse ( markdown ); if ( sources . length ) { const s = document . createElement ( ' div ' ); s . className = ' sources ' ; s . textContent = ' Sources: ' + sources . map ( x => ` ${ x . source } ( ${ x . score } )` ). join ( ' , ' ); d . appendChild ( s ); } $ ( ' chat ' ). appendChild ( d ); enhanceCodeBlocks ( d ); scrollDown (); } function enhanceCodeBlocks ( root ) { root . querySelectorAll ( ' pre ' ). forEach ( pre => { hljs . highlightElement ( pre . querySelector ( ' code ' ) || pre ); const btn = document . createElement ( ' button ' ); btn . className = ' copy-code ' ; btn . textContent = ' Copy ' ; btn . onclick = () => { navigator . clipboard . writeText ( pre . innerText ). then (() => { btn . textContent = ' Copied! ' ; setTimeout (() => btn . textContent = ' Copy ' , 1200 ); }); }; pre . appendChild ( btn ); }); } function scrollDown () { $ ( ' chat ' ). scrollTop = $ ( ' chat ' ). scrollHeight ; } async function sendQuery () { const q = $ ( ' query ' ). value . trim (); if ( ! q ) return ; addUserMsg ( q ); $ ( ' query ' ). value = '' ; $ ( ' send ' ). disabled = true ; $ ( ' status ' ). textContent = ' thinking… ' ; // typing indicator const typing = document . createElement ( ' div ' ); typing . className = ' msg bot ' ; typing . textContent = ' AI is analyzing... ' ; $ ( ' chat ' ). appendChild ( typing ); scrollDown (); try { const r = await fetch ( ' /api/chat ' , { method : ' POST ' , headers :{ ' Content-Type ' : ' application/json ' }, body : JSON . stringify ({ query : q }) }); const data = await r . json (); typing . remove (); if ( data . error ) throw new Error ( data . error ); addBotMsg ( data . answer , data . sources || []); } catch ( e ) { typing . remove (); addBotMsg ( `❌ \` ${ e . message } \` ` ); } finally { $ ( ' send ' ). disabled = false ; $ ( ' status ' ). textContent = '' ; $ ( ' query ' ). focus (); } } $ ( ' send ' ). onclick = sendQuery ; $ ( ' query ' ). addEventListener ( ' keydown ' , e => { if ( e . key === ' Enter ' && ! e . shiftKey ) { e . preventDefault (); sendQuery (); } }); /* ======================= INIT ======================= */ refreshFileList (); </script> </body> </html>
Python Backend
The Python file app.py which connects to the LLM, vector db looks like this -
import os import re import shutil import uuid import tempfile from flask import Flask , request , jsonify , render_template from flask_cors import CORS # ----- Vector DB ----- import chromadb from chromadb.config import Settings # ----- Embeddings ----- from sentence_transformers import SentenceTransformer # ----- Keyword search ----- from rank_bm25 import BM25Okapi # ----- File parsers ----- from pypdf import PdfReader import docx import openpyxl from pptx import Presentation # ----- LLM ----- import google.generativeai as genai # ========================================================= # CONFIG # ========================================================= GEMINI_API_KEY = " yourgoogleAPIKEY " # hardcoded per spec MODEL_NAME = " gemini-3.5-flash-lite " # swap if unavailable EMBED_MODEL = " all-MiniLM-L6-v2 " COLLECTION = " venkatesh_docs " UPLOAD_DIR = " uploads " CHROMA_DIR = " chroma_db " os . makedirs ( UPLOAD_DIR , exist_ok = True ) os . makedirs ( CHROMA_DIR , exist_ok = True ) genai . c
Comments
No comments yet. Start the discussion.