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">></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-1jfPreviousCommonSpirit Health, Crunch Fitness and The Joint jobs API: the pay in a job page's schema.org markupNextAvoiding Duplicate AI Work: SQLAlchemy Persistence and Fingerprint-Based Caching in FastAPI
Related posts
A tampered Claude Code extension, five broken coding-agent browsers, and agents nobody owns
0dev.to

What Is Danmaku? How to Download Bilibili Bullet Comments and Find Any Video's Highlight
0dev.to

Whoever takes your old GitHub name gets your MCP namespace
0dev.to

What Is Danmaku? How to Download Bilibili Bullet Comments and Find Any Video's Highlight Of Bilibili
0dev.to
Low-Cost Property Ticket Summarization — Token Reduction Strategies in Node.js
0dev.to

Passing once is not the same as being reliable
0dev.to
