"""
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
""")
# โโ 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
- Click the Upload tab
- Drag & drop your files (PDF, TXT, CSV, DOCX)
- Click Process & Index Documents
- Wait for the โ
confirmation
๐ฌ
Asking Questions
- Click the Ask Questions tab
- Type your question in the text box
- Press Enter or click Get Answer
- 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
""")
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()