""" app.py - RAG Chatbot with Ultra Premium 3D UI Run with: python app.py """ import os import sys import gradio as gr from dotenv import load_dotenv load_dotenv() sys.path.insert(0, os.path.dirname(__file__)) from src.document_loader import load_document from src.embeddings import get_embedding_model from src.vector_database import add_documents_to_store, load_vector_store from src.rag_pipeline import build_rag_chain, ask_question from src.utils import save_uploaded_file, split_documents, format_sources embedding_model = None rag_chain = None def _get_embeddings(): global embedding_model if embedding_model is None: embedding_model = get_embedding_model() return embedding_model def handle_upload(files) -> str: global rag_chain if not files: return "โš ๏ธ No files uploaded. Please select at least one file." status_lines = [] all_chunks = [] for file in files: filename = os.path.basename(file.name) status_lines.append(f"๐Ÿ“„ Processing: {filename}") try: saved_path = save_uploaded_file(file.name) documents = load_document(saved_path) chunks = split_documents(documents) all_chunks.extend(chunks) status_lines.append(f" โœ… {filename} โ€” {len(chunks)} chunk(s) extracted") except ValueError as e: status_lines.append(f" โŒ {filename} โ€” {e}") except Exception as e: status_lines.append(f" โŒ {filename} โ€” Unexpected error: {e}") if not all_chunks: return "\n".join(status_lines) + "\n\nโš ๏ธ No text could be extracted." status_lines.append("\n๐Ÿ”„ Generating embeddings and updating vector store โ€ฆ") try: embeddings = _get_embeddings() vector_store = add_documents_to_store(all_chunks, embeddings) rag_chain = build_rag_chain(vector_store) status_lines.append("โœ… Vector store updated successfully!") status_lines.append("๐Ÿ’ฌ You can now ask questions in the Chat tab.") except EnvironmentError as e: status_lines.append(f"โŒ Configuration error: {e}") except Exception as e: status_lines.append(f"โŒ Failed to build the RAG pipeline: {e}") return "\n".join(status_lines) def handle_question(question: str): global rag_chain if not question.strip(): return "Please type a question first.", "" if rag_chain is None: try: embeddings = _get_embeddings() vector_store = load_vector_store(embeddings) if vector_store is None: return "No documents indexed yet. Please upload documents first.", "" rag_chain = build_rag_chain(vector_store) except EnvironmentError as e: return f"Configuration error: {e}", "" except Exception as e: return f"Failed to load the RAG pipeline: {e}", "" try: result = ask_question(rag_chain, question) answer = result["answer"] sources_text = format_sources(result["sources"]) return answer, sources_text except Exception as e: return f"Error generating answer: {e}", "" CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700;800&display=swap'); *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body, .gradio-container { background: #030712 !important; font-family: 'Inter', sans-serif !important; min-height: 100vh; overflow-x: hidden; } .gradio-container { max-width: 100% !important; padding: 0 !important; margin: 0 !important; } /* โ”€โ”€ Animated Aurora Background โ”€โ”€ */ body::before { content: ''; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: radial-gradient(ellipse 80% 50% at 20% 20%, rgba(0,212,255,0.07) 0%, transparent 60%), radial-gradient(ellipse 60% 40% at 80% 10%, rgba(124,58,237,0.08) 0%, transparent 60%), radial-gradient(ellipse 70% 60% at 50% 90%, rgba(16,185,129,0.05) 0%, transparent 60%); animation: aurora 12s ease-in-out infinite alternate; pointer-events: none; z-index: 0; } @keyframes aurora { 0% { opacity: 1; transform: scale(1) rotate(0deg); } 50% { opacity: 0.7; transform: scale(1.05) rotate(1deg); } 100% { opacity: 1; transform: scale(1) rotate(0deg); } } /* โ”€โ”€ Floating orbs โ”€โ”€ */ body::after { content: ''; position: fixed; width: 500px; height: 500px; top: -100px; right: -100px; background: radial-gradient(circle, rgba(0,212,255,0.06) 0%, transparent 70%); border-radius: 50%; animation: floatOrb 8s ease-in-out infinite; pointer-events: none; z-index: 0; } @keyframes floatOrb { 0%, 100% { transform: translateY(0px) translateX(0px); } 33% { transform: translateY(30px) translateX(-20px); } 66% { transform: translateY(-20px) translateX(20px); } } /* โ”€โ”€ Hero Section โ”€โ”€ */ .hero-wrap { position: relative; z-index: 1; background: linear-gradient(180deg, #050d1a 0%, #030712 100%); border-bottom: 1px solid rgba(0,212,255,0.1); padding: 60px 60px 52px; overflow: hidden; } .hero-wrap::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%2300d4ff' fill-opacity='0.02'%3E%3Ccircle cx='30' cy='30' r='1'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); pointer-events: none; } .hero-grid-line { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-image: linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px); background-size: 60px 60px; pointer-events: none; } .hero-chip { display: inline-flex; align-items: center; gap: 8px; background: rgba(0,212,255,0.08); border: 1px solid rgba(0,212,255,0.2); border-radius: 100px; padding: 6px 16px; font-size: 11px; font-weight: 600; letter-spacing: 2px; text-transform: uppercase; color: #00d4ff; font-family: 'Space Grotesk', sans-serif; margin-bottom: 24px; } .hero-chip::before { content: ''; width: 6px; height: 6px; background: #00d4ff; border-radius: 50%; box-shadow: 0 0 8px #00d4ff; animation: pulse 2s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.3); } } .hero-title { font-family: 'Space Grotesk', sans-serif; font-size: 56px; font-weight: 800; line-height: 1.1; letter-spacing: -1.5px; color: #f0f6fc; margin-bottom: 20px; } .hero-title .gradient-text { background: linear-gradient(135deg, #00d4ff 0%, #7c3aed 50%, #f59e0b 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; background-size: 200% 200%; animation: gradientShift 4s ease infinite; } @keyframes gradientShift { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } .hero-sub { font-size: 17px; color: #6e7681; line-height: 1.7; max-width: 580px; margin-bottom: 36px; font-weight: 400; } .hero-tags { display: flex; gap: 10px; flex-wrap: wrap; } .hero-tag { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 8px 16px; font-size: 13px; font-weight: 500; color: #8b949e; font-family: 'Space Grotesk', sans-serif; transition: all 0.3s ease; cursor: default; transform: perspective(400px) translateZ(0px); } .hero-tag:hover { background: rgba(0,212,255,0.08); border-color: rgba(0,212,255,0.3); color: #00d4ff; transform: perspective(400px) translateZ(8px); box-shadow: 0 8px 24px rgba(0,212,255,0.15); } /* โ”€โ”€ Stats Bar โ”€โ”€ */ .stats-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; border-bottom: 1px solid rgba(0,212,255,0.08); position: relative; z-index: 1; background: rgba(5,13,26,0.8); backdrop-filter: blur(20px); } .stat-item { padding: 24px 32px; border-right: 1px solid rgba(0,212,255,0.08); transition: background 0.3s ease; } .stat-item:last-child { border-right: none; } .stat-item:hover { background: rgba(0,212,255,0.03); } .stat-num { font-family: 'Space Grotesk', sans-serif; font-size: 32px; font-weight: 700; color: #00d4ff; line-height: 1; margin-bottom: 4px; text-shadow: 0 0 20px rgba(0,212,255,0.4); } .stat-lbl { font-size: 11px; color: #484f58; font-weight: 500; letter-spacing: 1px; text-transform: uppercase; font-family: 'Space Grotesk', sans-serif; } /* โ”€โ”€ Main Content โ”€โ”€ */ .main-wrap { position: relative; z-index: 1; padding: 40px 60px 60px; } /* โ”€โ”€ Tabs โ”€โ”€ */ .tabs-wrap > div:first-child { background: transparent !important; border-bottom: 1px solid rgba(255,255,255,0.06) !important; padding: 0 !important; margin-bottom: 36px !important; } .tabs-wrap > div:first-child button { background: transparent !important; border: none !important; border-bottom: 2px solid transparent !important; color: #484f58 !important; font-family: 'Space Grotesk', sans-serif !important; font-size: 13px !important; font-weight: 600 !important; letter-spacing: 0.5px !important; padding: 14px 24px !important; margin-bottom: -1px !important; transition: all 0.25s ease !important; border-radius: 0 !important; text-transform: uppercase !important; } .tabs-wrap > div:first-child button:hover { color: #8b949e !important; background: rgba(255,255,255,0.02) !important; } .tabs-wrap > div:first-child button.selected { color: #00d4ff !important; border-bottom-color: #00d4ff !important; text-shadow: 0 0 20px rgba(0,212,255,0.5) !important; } /* โ”€โ”€ Section headers โ”€โ”€ */ .sec-eyebrow { font-family: 'Space Grotesk', sans-serif; font-size: 10px; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; color: #00d4ff; margin-bottom: 8px; } .sec-title { font-family: 'Space Grotesk', sans-serif; font-size: 26px; font-weight: 700; color: #e6edf3; margin-bottom: 6px; letter-spacing: -0.5px; } .sec-desc { font-size: 14px; color: #484f58; line-height: 1.6; margin-bottom: 28px; } /* โ”€โ”€ 3D Glass Card โ”€โ”€ */ .glass-card { background: rgba(13,20,33,0.8); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid rgba(0,212,255,0.1); border-radius: 16px; padding: 28px; transform: perspective(1000px) rotateX(0deg) translateZ(0px); transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); box-shadow: 0 4px 24px rgba(0,0,0,0.4), 0 1px 0 rgba(255,255,255,0.05) inset; margin-bottom: 24px; } .glass-card:hover { transform: perspective(1000px) rotateX(-2deg) translateZ(10px); border-color: rgba(0,212,255,0.25); box-shadow: 0 20px 60px rgba(0,0,0,0.5), 0 0 40px rgba(0,212,255,0.06), 0 1px 0 rgba(255,255,255,0.08) inset; } /* โ”€โ”€ File Upload Zone โ”€โ”€ */ .upload-zone { border: 2px dashed rgba(0,212,255,0.2) !important; border-radius: 16px !important; background: rgba(0,212,255,0.02) !important; transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1) !important; transform: perspective(800px) translateZ(0px); min-height: 160px !important; } .upload-zone:hover { border-color: rgba(0,212,255,0.5) !important; background: rgba(0,212,255,0.05) !important; transform: perspective(800px) translateZ(6px); box-shadow: 0 16px 40px rgba(0,212,255,0.1), 0 0 0 1px rgba(0,212,255,0.1); } /* โ”€โ”€ Buttons โ”€โ”€ */ button.primary { background: linear-gradient(135deg, #0891b2, #00d4ff, #7c3aed) !important; background-size: 200% 200% !important; animation: btnGradient 4s ease infinite !important; border: none !important; border-radius: 10px !important; color: #ffffff !important; font-family: 'Space Grotesk', sans-serif !important; font-size: 14px !important; font-weight: 700 !important; letter-spacing: 0.5px !important; padding: 14px 32px !important; text-transform: uppercase !important; transform: perspective(400px) translateZ(0px); transition: transform 0.2s ease, box-shadow 0.2s ease !important; box-shadow: 0 4px 20px rgba(0,212,255,0.3), 0 1px 0 rgba(255,255,255,0.2) inset !important; cursor: pointer !important; } @keyframes btnGradient { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } } button.primary:hover { transform: perspective(400px) translateZ(6px) translateY(-2px) !important; box-shadow: 0 12px 40px rgba(0,212,255,0.4), 0 1px 0 rgba(255,255,255,0.2) inset !important; } button.primary:active { transform: perspective(400px) translateZ(-2px) translateY(1px) !important; box-shadow: 0 2px 10px rgba(0,212,255,0.2) !important; } /* โ”€โ”€ Inputs โ”€โ”€ */ textarea, input[type="text"] { background: rgba(13,20,33,0.9) !important; border: 1px solid rgba(255,255,255,0.07) !important; border-radius: 10px !important; color: #e6edf3 !important; font-family: 'Inter', sans-serif !important; font-size: 14px !important; line-height: 1.6 !important; padding: 14px 18px !important; transition: all 0.3s ease !important; transform: perspective(600px) translateZ(0px); } textarea:focus, input[type="text"]:focus { border-color: rgba(0,212,255,0.4) !important; outline: none !important; box-shadow: 0 0 0 3px rgba(0,212,255,0.08), 0 8px 24px rgba(0,0,0,0.3) !important; transform: perspective(600px) translateZ(4px) !important; } textarea::placeholder, input::placeholder { color: #30363d !important; } /* โ”€โ”€ Labels โ”€โ”€ */ label span, .label-wrap span { color: #6e7681 !important; font-size: 11px !important; font-weight: 600 !important; letter-spacing: 1.5px !important; text-transform: uppercase !important; font-family: 'Space Grotesk', sans-serif !important; } /* โ”€โ”€ Answer output โ”€โ”€ */ .answer-box textarea { background: rgba(0,16,32,0.9) !important; border: 1px solid rgba(0,212,255,0.15) !important; border-left: 3px solid #00d4ff !important; border-radius: 12px !important; color: #cdd9e5 !important; font-size: 15px !important; line-height: 1.8 !important; padding: 20px 24px !important; box-shadow: 0 0 40px rgba(0,212,255,0.04) inset !important; } /* โ”€โ”€ Sources output โ”€โ”€ */ .sources-box textarea { background: rgba(16,0,32,0.9) !important; border: 1px solid rgba(124,58,237,0.15) !important; border-left: 3px solid #7c3aed !important; border-radius: 12px !important; color: #8b949e !important; font-size: 12px !important; font-family: 'Space Grotesk', monospace !important; line-height: 1.7 !important; padding: 16px 20px !important; box-shadow: 0 0 40px rgba(124,58,237,0.04) inset !important; } /* โ”€โ”€ Status box โ”€โ”€ */ .status-box textarea { background: rgba(5,13,26,0.95) !important; border: 1px solid rgba(255,255,255,0.05) !important; border-radius: 12px !important; color: #6e7681 !important; font-size: 12px !important; font-family: 'Space Grotesk', monospace !important; line-height: 1.8 !important; padding: 16px 20px !important; } /* โ”€โ”€ Help cards โ”€โ”€ */ .hcard { background: rgba(13,20,33,0.7); border: 1px solid rgba(255,255,255,0.05); border-radius: 14px; padding: 28px; transform: perspective(800px) translateZ(0px); transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1); box-shadow: 0 4px 20px rgba(0,0,0,0.3); } .hcard:hover { transform: perspective(800px) translateZ(12px) rotateX(-2deg); border-color: rgba(0,212,255,0.2); box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 30px rgba(0,212,255,0.05); } .hcard-icon { font-size: 28px; margin-bottom: 14px; display: block; } .hcard h3 { font-family: 'Space Grotesk', sans-serif; font-size: 15px; font-weight: 700; color: #e6edf3; margin-bottom: 12px; letter-spacing: -0.2px; } .hcard p, .hcard li { font-size: 13px; color: #484f58; line-height: 1.7; } .hcard ol, .hcard ul { padding-left: 18px; margin-top: 8px; } .hcard li { margin-bottom: 6px; } .hcard strong { color: #8b949e; } /* โ”€โ”€ Divider โ”€โ”€ */ .div-line { height: 1px; background: linear-gradient(90deg, transparent, rgba(0,212,255,0.15), transparent); margin: 32px 0; } /* โ”€โ”€ Scrollbar โ”€โ”€ */ ::-webkit-scrollbar { width: 5px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: rgba(0,212,255,0.2); border-radius: 10px; } ::-webkit-scrollbar-thumb:hover { background: rgba(0,212,255,0.4); } /* โ”€โ”€ Gradio overrides โ”€โ”€ */ .block, .form { background: transparent !important; border: none !important; padding: 0 !important; } .gap { gap: 20px !important; } footer { display: none !important; } .svelte-1gfkn6j { background: transparent !important; } """ def build_interface() -> gr.Blocks: with gr.Blocks( title="RAG Document Chatbot", css=CUSTOM_CSS, theme=gr.themes.Base( primary_hue="cyan", neutral_hue="slate", font=gr.themes.GoogleFont("Inter"), ), ) as demo: # โ”€โ”€ Hero โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ gr.HTML("""
โšก AI-Powered ยท RAG Architecture

Your Documents.
Infinite Intelligence.

Upload any document and ask questions in plain English. Get precise, source-backed answers powered by semantic search.

๐Ÿ“„ PDF ๐Ÿ“ TXT ๐Ÿ“Š CSV ๐Ÿ“‹ DOCX ๐Ÿ” FAISS Vector Search ๐Ÿง  Semantic Retrieval
4+
File Formats
โˆž
Documents
384
Embedding Dims
8K
Context Window
""") # โ”€โ”€ Tabs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Tabs(elem_classes="tabs-wrap"): # โ”€ Tab 1: Upload โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Tab("๐Ÿ“ค Upload"): gr.HTML("""
Step 01 / 02
Upload Your Documents
Drop your study notes, reports, or any reference files. Supported: PDF ยท TXT ยท CSV ยท DOCX
""") file_input = gr.File( label="Drag & drop files here or click to browse", file_count="multiple", file_types=[".txt", ".pdf", ".csv", ".docx"], elem_classes="upload-zone", ) upload_btn = gr.Button( "โš™๏ธ Process & Index Documents", variant="primary", size="lg", ) gr.HTML('
') upload_status = gr.Textbox( label="Processing Log", lines=10, interactive=False, placeholder="Status messages will appear here โ€ฆ", elem_classes="status-box", ) upload_btn.click(fn=handle_upload, inputs=[file_input], outputs=[upload_status]) # โ”€ Tab 2: Chat โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Tab("๐Ÿ’ฌ Ask Questions"): gr.HTML("""
Step 02 / 02
Ask Anything
Type your question below. The AI retrieves the most relevant passages from your documents and generates a grounded answer.
""") question_input = gr.Textbox( label="Your Question", placeholder="e.g. What is the bias-variance tradeoff and how do you manage it?", lines=3, ) ask_btn = gr.Button("๐Ÿ” Get Answer", variant="primary", size="lg") gr.HTML('
') with gr.Row(): with gr.Column(scale=3): answer_output = gr.Textbox( label="Answer", lines=14, interactive=False, placeholder="Your answer will appear here โ€ฆ", elem_classes="answer-box", ) with gr.Column(scale=2): sources_output = gr.Textbox( label="Sources Referenced", lines=14, interactive=False, placeholder="Source documents used โ€ฆ", elem_classes="sources-box", ) question_input.submit(fn=handle_question, inputs=[question_input], outputs=[answer_output, sources_output]) ask_btn.click(fn=handle_question, inputs=[question_input], outputs=[answer_output, sources_output]) # โ”€ Tab 3: Help โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ with gr.Tab("โ„น๏ธ Help"): gr.HTML("""
Documentation
How It Works
Everything you need to get the best results from your RAG assistant.
๐Ÿ“ค

Uploading Documents

  1. Click the Upload tab
  2. Drag & drop your files (PDF, TXT, CSV, DOCX)
  3. Click Process & Index Documents
  4. Wait for the โœ… confirmation
๐Ÿ’ฌ

Asking Questions

  1. Click the Ask Questions tab
  2. Type your question in the text box
  3. Press Enter or click Get Answer
  4. Check the Sources panel to verify
๐Ÿ’ก

Tips for Better Answers

  • Be specific in your questions
  • Upload topic-focused documents
  • Check the Sources box to verify answers
  • Rephrase if the first answer is incomplete
โš™๏ธ

Configuration

""") gr.HTML("
") # close main-wrap return demo if __name__ == "__main__": print("=" * 60) print(" RAG Document Chatbot โ€” Ultra Premium UI") print("=" * 60) print(f" LLM : {os.getenv('LLM_PROVIDER', 'groq')} / {os.getenv('GROQ_MODEL', 'llama-3.1-8b-instant')}") print(f" URL : http://localhost:7860") print("=" * 60) app = build_interface() app.launch()