Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from rag_pipeline import RAGPipeline | |
| from document_processor import DocumentProcessor | |
| import os | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| class DocumentRagApp: | |
| def __init__(self): | |
| self.processor = DocumentProcessor() | |
| self.rag_pipeline = RAGPipeline() | |
| self.loaded_documents = [] | |
| def load_samples(self, vertical): | |
| samples = { | |
| "Legal": [ | |
| "data/samples/legal/service_agreement.txt", | |
| "data/samples/legal/amendment.txt", | |
| "data/samples/legal/nda.txt", | |
| ], | |
| "Research": [ | |
| "data/samples/research/llm_enterprise_survey.txt", | |
| "data/samples/research/rag_methodology.txt", | |
| "data/samples/research/vector_db_benchmark.txt", | |
| ], | |
| "FinOps": [ | |
| "data/samples/finops/cloud_cost_optimization.txt", | |
| "data/samples/finops/aws_invoice_sept2024.txt", | |
| "data/samples/finops/kubernetes_cost_allocation.txt", | |
| ], | |
| } | |
| try: | |
| for path in samples[vertical]: | |
| if os.path.exists(path): | |
| chunks = self.processor.process_txt(path) | |
| self.rag_pipeline.add_documents(chunks, is_sample=True) | |
| self.loaded_documents.append(os.path.basename(path)) | |
| return f"β Loaded {len(samples[vertical])} {vertical} documents" | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def process_file(self, file): | |
| if not file: | |
| return "Please upload a file" | |
| try: | |
| ext = os.path.splitext(file.name)[1].lower() | |
| if ext == ".pdf": | |
| chunks = self.processor.process_pdf(file.name) | |
| elif ext == ".txt": | |
| chunks = self.processor.process_txt(file.name) | |
| elif ext == ".docx": | |
| chunks = self.processor.process_docx(file.name) | |
| else: | |
| return "Unsupported format" | |
| self.rag_pipeline.add_documents(chunks, is_sample=False) | |
| return f"β Processed {len(chunks)} chunks" | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def switch_model(self, model_choice): | |
| """Handle model switching from UI radio button""" | |
| # Map UI choices to model keys | |
| model_map = { | |
| "GPT-OSS 120B (OpenAI) - Default": "gpt-oss-120b", | |
| "Llama 3.3 70B (Meta)": "llama-3.3-70b", | |
| "Gemma 3 27B (Google)": "gemma-3-27b", | |
| } | |
| model_key = model_map.get(model_choice) | |
| if not model_key: | |
| return f"β Invalid model selection" | |
| try: | |
| display_name = self.rag_pipeline.switch_model(model_key) | |
| return f"β Switched to {display_name}" | |
| except Exception as e: | |
| return f"β Error switching model: {str(e)}" | |
| def ask(self, question): | |
| if not self.loaded_documents: | |
| return "Please load documents first" | |
| if not question.strip(): | |
| return "Please enter a question" | |
| try: | |
| result = self.rag_pipeline.query(question) | |
| return result["answer"] | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| app = DocumentRagApp() | |
| # Premium Enterprise Design System (Restored & Cleaned) | |
| css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Outfit:wght@400;500;600;700&display=swap'); | |
| :root { | |
| /* Brand Palette */ | |
| --primary-gradient: linear-gradient(135deg, #3B82F6 0%, #10B981 100%); | |
| --surface-dark: #0B0F19; | |
| --surface-glass: rgba(17, 24, 39, 0.7); | |
| --border-glass: rgba(255, 255, 255, 0.08); | |
| --text-primary: #F9FAFB; | |
| --text-secondary: #9CA3AF; | |
| --accent: #10B981; | |
| --font-heading: 'Outfit', sans-serif; | |
| --font-body: 'Inter', sans-serif; | |
| } | |
| /* --- GLOBAL RESET & CLEANING --- */ | |
| body, .gradio-container { | |
| background-color: var(--surface-dark) !important; | |
| font-family: var(--font-body) !important; | |
| color: var(--text-primary) !important; | |
| } | |
| /* β οΈ CRITICAL: Remove Gradio's default nested boxes/backgrounds β οΈ */ | |
| .gradio-container .block, | |
| .gradio-container .form, | |
| .gradio-container .gradio-box, | |
| .gradio-container .padded, | |
| .gradio-container .gradio-group, | |
| .gradio-container .gradio-row, | |
| .gradio-container .gradio-column { | |
| background-color: transparent !important; | |
| border: none !important; | |
| box-shadow: none !important; | |
| padding: 0 !important; | |
| } | |
| /* Force transparency on specific internal elements to fix "Grey Box" issue */ | |
| .glass-card div { | |
| background-color: transparent !important; | |
| border: none !important; | |
| } | |
| /* Re-assert styles for Inputs/Buttons since the rule above is aggressive */ | |
| .glass-card textarea, | |
| .glass-card input[type="text"], | |
| .glass-card .gradio-dropdown { | |
| background-color: rgba(0, 0, 0, 0.3) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| } | |
| .glass-card button.primary-btn { | |
| background: var(--primary-gradient) !important; | |
| } | |
| .glass-card button.query-btn { | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| } | |
| /* Typography */ | |
| h1, h2, h3, h4 { font-family: var(--font-heading); } | |
| span, p, div { font-family: var(--font-body); } | |
| /* --- HERO SECTION --- */ | |
| #header { | |
| text-align: center; | |
| margin-bottom: 4rem; | |
| padding-top: 2rem; | |
| } | |
| #header h1 { | |
| font-size: 3.5rem; | |
| font-weight: 700; | |
| margin-bottom: 0.5rem; | |
| color: #FFFFFF; /* High contrast white */ | |
| text-shadow: 0 0 20px rgba(59, 130, 246, 0.5); /* Glow effect instead of gradient text */ | |
| letter-spacing: -0.02em; | |
| } | |
| #header p { | |
| font-size: 1.2rem; | |
| color: var(--text-secondary); | |
| } | |
| /* --- GLASS CARDS --- */ | |
| .glass-card { | |
| background: var(--surface-glass) !important; | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| border: 1px solid var(--border-glass) !important; | |
| border-radius: 20px !important; | |
| padding: 2rem 2rem 1.5rem 2rem !important; /* Reduced bottom padding */ | |
| margin-bottom: 2rem !important; | |
| box-shadow: 0 20px 40px -10px rgba(0,0,0,0.5) !important; | |
| height: 100% !important; | |
| display: flex !important; | |
| flex-direction: column !important; | |
| } | |
| .card-header { | |
| font-family: var(--font-heading); | |
| font-size: 0.9rem; | |
| text-transform: uppercase; | |
| letter-spacing: 0.1em; | |
| color: var(--text-secondary); | |
| margin-bottom: 0.5rem; /* Reduced bottom margin */ | |
| border-bottom: 1px solid var(--border-glass); | |
| padding-bottom: 0.5rem; | |
| } | |
| /* --- INPUTS & BUTTONS (Cleaned) --- */ | |
| .gradio-dropdown, .gradio-textbox textarea { | |
| background-color: rgba(0, 0, 0, 0.3) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| border-radius: 10px !important; | |
| color: var(--text-primary) !important; | |
| } | |
| /* Upload Area specific */ | |
| .gradio-file { | |
| background-color: rgba(0, 0, 0, 0.15) !important; | |
| border: 2px dashed rgba(255, 255, 255, 0.3) !important; | |
| border-radius: 12px !important; | |
| padding: 1rem !important; | |
| } | |
| .gradio-file:hover { | |
| background-color: rgba(0, 0, 0, 0.2) !important; | |
| border-color: var(--accent) !important; | |
| } | |
| .gradio-dropdown:hover, .gradio-textbox textarea:hover { | |
| border-color: var(--accent) !important; | |
| } | |
| /* Primary Button */ | |
| .primary-btn { | |
| background: var(--primary-gradient) !important; | |
| border: none !important; | |
| color: white !important; | |
| font-weight: 600 !important; | |
| padding: 1rem !important; | |
| border-radius: 10px !important; | |
| transition: transform 0.2s; | |
| box-shadow: 0 4px 15px rgba(16, 185, 129, 0.2); | |
| } | |
| .primary-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 8px 25px rgba(16, 185, 129, 0.3); | |
| } | |
| /* Quick Query Buttons */ | |
| .query-btn { | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| color: var(--text-secondary) !important; | |
| border-radius: 8px !important; | |
| padding: 0.8rem !important; | |
| text-align: left !important; | |
| font-size: 0.95rem !important; | |
| } | |
| .query-btn:hover { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| color: var(--text-primary) !important; | |
| border-color: var(--accent) !important; | |
| } | |
| /* --- TABS (Seamless) --- */ | |
| .tab-nav { | |
| border: none !important; | |
| margin-top: 1rem !important; /* Spacing above tabs */ | |
| margin-bottom: 1.5rem !important; /* Spacing below tabs */ | |
| background: rgba(0,0,0,0.2) !important; | |
| border-radius: 12px; | |
| padding: 4px !important; | |
| display: flex; | |
| gap: 4px; | |
| } | |
| .tab-nav button { | |
| border: none !important; | |
| color: var(--text-secondary) !important; | |
| background: transparent !important; | |
| border-radius: 8px !important; | |
| flex-grow: 1; | |
| font-family: var(--font-heading) !important; | |
| } | |
| .tab-nav button.selected { | |
| background: rgba(255,255,255,0.1) !important; | |
| color: var(--text-primary) !important; | |
| font-weight: 600 !important; | |
| } | |
| /* --- ANSWER SECTION --- */ | |
| #answer-section { | |
| background: rgba(0,0,0,0.2) !important; | |
| border-radius: 12px; | |
| padding: 1.5rem !important; | |
| border: 1px solid var(--border-glass); | |
| } | |
| #answer-section .markdown { | |
| font-size: 1.1rem; | |
| line-height: 1.7; | |
| color: var(--text-primary); | |
| } | |
| #answer-section strong { | |
| color: #60A5FA; | |
| } | |
| /* Footer Badge */ | |
| .calendar-badge { | |
| background: rgba(16, 185, 129, 0.15); | |
| color: var(--accent); | |
| padding: 0.6rem 1.2rem; | |
| border-radius: 100px; | |
| font-weight: 600; | |
| text-decoration: none; | |
| border: 1px solid rgba(16, 185, 129, 0.3); | |
| transition: all 0.2s; | |
| } | |
| .calendar-badge:hover { | |
| background: rgba(16, 185, 129, 0.25); | |
| box-shadow: 0 0 20px rgba(16, 185, 129, 0.2); | |
| } | |
| /* --- MODEL SELECTOR --- */ | |
| .model-selector { | |
| background: rgba(0, 0, 0, 0.15) !important; | |
| border-radius: 8px !important; | |
| padding: 0.75rem !important; | |
| margin-bottom: 1rem !important; | |
| border: 1px solid var(--border-glass) !important; | |
| } | |
| .model-selector label { | |
| background: rgba(255, 255, 255, 0.05) !important; | |
| border: 1px solid var(--border-glass) !important; | |
| padding: 0.5rem 0.75rem !important; | |
| border-radius: 6px !important; | |
| transition: all 0.2s !important; | |
| cursor: pointer !important; | |
| margin: 0.2rem 0 !important; | |
| display: block !important; | |
| font-size: 0.875rem !important; | |
| } | |
| .model-selector label:hover { | |
| background: rgba(255, 255, 255, 0.1) !important; | |
| border-color: var(--accent) !important; | |
| transform: translateX(3px) !important; | |
| } | |
| .model-selector input:checked + label { | |
| background: var(--primary-gradient) !important; | |
| border-color: transparent !important; | |
| font-weight: 600 !important; | |
| box-shadow: 0 3px 12px rgba(16, 185, 129, 0.3) !important; | |
| } | |
| .model-status { | |
| font-size: 0.8rem; | |
| color: var(--text-secondary); | |
| padding: 0.25rem 0.5rem; | |
| margin-top: 0.1rem; | |
| } | |
| """ | |
| with gr.Blocks(css=css, theme=gr.themes.Base(), title="Enterprise RAG") as demo: | |
| with gr.Column(elem_id="main-container"): | |
| # --- HERO --- | |
| gr.HTML(""" | |
| <div id="header"> | |
| <h1>ENTERPRISE RAG PLATFORM</h1> | |
| <p>Secure, Scalable, Agentic Document Intelligence for the Modern Enterprise.</p> | |
| <div style="margin-top: 3rem; margin-bottom: 6rem;" id="calendar-button"> | |
| <a href="https://cal.com" target="_blank" class="calendar-badge"> | |
| <span>π </span> Book a 30-min Strategy Call | |
| </a> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Row(equal_height=True): | |
| # --- LEFT: SETUP CARD (45%) --- | |
| with gr.Column(scale=9): | |
| with gr.Group(elem_classes="glass-card"): | |
| gr.Markdown( | |
| "### SELECT SAMPLE DOCUMENTS", elem_classes="card-header" | |
| ) | |
| gr.Markdown( | |
| "<span style='font-size: 0.8rem; opacity: 0.8; margin-bottom: 10px !important;'>_Choose a vertical to load pre-configured samples (Legal, FinOps)_</span>", | |
| elem_classes="subtitle", | |
| ) | |
| # Custom Tabs | |
| with gr.Tabs(): | |
| with gr.Tab("βοΈ Legal"): | |
| load_legal = gr.Button( | |
| "Load Legal Samples", elem_classes="primary-btn" | |
| ) | |
| with gr.Tab("π¬ Research"): | |
| load_research = gr.Button( | |
| "Load Research Samples", elem_classes="primary-btn" | |
| ) | |
| with gr.Tab("π° FinOps"): | |
| load_finops = gr.Button( | |
| "Load FinOps Samples", elem_classes="primary-btn" | |
| ) | |
| load_status = gr.Markdown("", elem_classes="status-message") | |
| # Visible Divider - Increased Opacity | |
| gr.HTML( | |
| '<div style="margin: 2rem 0; height: 1px; background: rgba(255,255,255,0.5);"></div>' | |
| ) | |
| gr.Markdown("### OR UPLOAD FILES", elem_classes="card-header") | |
| file_upload = gr.File( | |
| file_types=[".pdf", ".docx", ".txt"], | |
| show_label=True, | |
| height=240, # Increased height | |
| ) | |
| # Spacer before Process button | |
| gr.HTML('<div style="height: 1.5rem"></div>') | |
| process_btn = gr.Button( | |
| "Process Documents", elem_classes="primary-btn" | |
| ) | |
| upload_status = gr.Markdown("") | |
| # Divider | |
| gr.HTML( | |
| '<div style="margin: 1rem 0; height: 1px; background: rgba(255,255,255,0.15);"></div>' | |
| ) | |
| # Model Selector (Compact) | |
| gr.Markdown("**π€ AI Model**", elem_classes="card-subheader") | |
| model_selector = gr.Radio( | |
| choices=[ | |
| "GPT-OSS 120B (OpenAI) - Default", | |
| "Llama 3.3 70B (Meta)", | |
| "Gemma 3 27B (Google)", | |
| ], | |
| value="GPT-OSS 120B (OpenAI) - Default", | |
| elem_classes="model-selector", | |
| show_label=False, | |
| ) | |
| model_status = gr.Markdown( | |
| "_GPT-OSS 120B active_", | |
| elem_classes="model-status", | |
| ) | |
| # --- RIGHT: INTERACTION CARD (55%) --- | |
| with gr.Column(scale=11): | |
| with gr.Group(elem_classes="glass-card"): | |
| gr.Markdown("### ASK ANYTHING", elem_classes="card-header") | |
| # Question Input | |
| question = gr.Textbox( | |
| placeholder="Ask anything about your documents (e.g., 'What are the termination conditions?')...", | |
| show_label=False, | |
| lines=3, | |
| elem_classes="gradio-textbox", | |
| ) | |
| with gr.Row(): | |
| ask_btn = gr.Button( | |
| "Analyze & Answer", elem_classes="primary-btn", scale=2 | |
| ) | |
| # Divider between Analyze and Quick Questions | |
| gr.HTML( | |
| '<div style="margin: 2rem 0; height: 1px; background: rgba(255,255,255,0.3);"></div>' | |
| ) | |
| gr.Markdown( | |
| "### QUICK SAMPLE QUESTIONS", elem_classes="card-header" | |
| ) | |
| with gr.Row(): | |
| q1 = gr.Button("π Termination Terms", elem_classes="query-btn") | |
| q2 = gr.Button("π° Payment Summary", elem_classes="query-btn") | |
| with gr.Row(): | |
| q3 = gr.Button("π Key Findings", elem_classes="query-btn") | |
| q4 = gr.Button("β οΈ Risk Analysis", elem_classes="query-btn") | |
| # Answer Output | |
| gr.HTML('<div style="height: 2rem"></div>') | |
| with gr.Group(elem_id="answer-section"): | |
| gr.Markdown("### π€ Model Response", elem_classes="card-header") | |
| answer = gr.Markdown("_AI analysis will appear here..._") | |
| # --- FOOTER --- | |
| with gr.Row(elem_id="footer-info"): | |
| gr.HTML(""" | |
| <div style="text-align: center; color: var(--text-secondary); margin-top: 3rem; padding-bottom: 2rem; font-size: 0.9rem;"> | |
| <p>π <strong>Secure Environment</strong>: Documents processed locally & auto-deleted after 7 days.</p> | |
| <p style="margin-top: 0.5rem; opacity: 0.6;">Β© 2024 Enterprise RAG Platform. Licensed under MIT.</p> | |
| </div> | |
| """) | |
| # Event Wiring | |
| load_legal.click(fn=lambda: app.load_samples("Legal"), outputs=load_status) | |
| load_research.click(fn=lambda: app.load_samples("Research"), outputs=load_status) | |
| load_finops.click(fn=lambda: app.load_samples("FinOps"), outputs=load_status) | |
| process_btn.click(fn=app.process_file, inputs=file_upload, outputs=upload_status) | |
| # Model switching | |
| model_selector.change( | |
| fn=app.switch_model, inputs=model_selector, outputs=model_status | |
| ) | |
| q1.click( | |
| fn=lambda: f"**Query:** Termination Terms\n\n{app.ask('What are the termination conditions?')}", | |
| outputs=answer, | |
| ) | |
| q2.click( | |
| fn=lambda: f"**Query:** Payment Summary\n\n{app.ask('Summarize payment terms')}", | |
| outputs=answer, | |
| ) | |
| q3.click( | |
| fn=lambda: f"**Query:** Key Findings\n\n{app.ask('Summarize key findings')}", | |
| outputs=answer, | |
| ) | |
| q4.click( | |
| fn=lambda: f"**Query:** Risk Analysis\n\n{app.ask('What are the key risks mentioned?')}", | |
| outputs=answer, | |
| ) | |
| ask_btn.click(fn=app.ask, inputs=question, outputs=answer) | |
| if __name__ == "__main__": | |
| demo.launch(share=False) | |