This is the final output -

We need to use the python 3.12.0 version to acheive 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
Features Covered

Backend

Python 3.12.0
Flask (server)

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">&gt;</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(nameSource:https://dev.to/vjonnagadla/rag-application-1jf

Related posts