rag_agent / app.py
agnixcode's picture
Update app.py
d3ed3b3 verified
import os
import faiss
import numpy as np
import requests
import gradio as gr
from openai import OpenAI
from pypdf import PdfReader
from sentence_transformers import SentenceTransformer
css = """
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
*, *::before, *::after { box-sizing: border-box; }
:root {
--M: #E20074;
--MD: #B5005C;
--MBG: rgba(226,0,116,0.07);
--bg: #F6F3F8;
--white: #FFFFFF;
--surf2: #F2EEF5;
--border: #E6DEED;
--border2: #D0C4DC;
--text: #1A1525;
--sub: #5A5272;
--muted: #9990AA;
--ok: #00A878;
--okbg: #E8F8F3;
--okborder: #AADFC8;
--f: 'Plus Jakarta Sans', sans-serif;
--mono: 'JetBrains Mono', monospace;
--sh-sm: 0 1px 4px rgba(0,0,0,0.06);
--sh-md: 0 3px 14px rgba(0,0,0,0.08);
--sh-mg: 0 4px 20px rgba(226,0,116,0.18);
}
/* ── BASE ── */
body {
font-family: var(--f) !important;
background: var(--bg) !important;
color: var(--text) !important;
}
.gradio-container {
max-width: 1060px !important;
margin: 0 auto !important;
padding: 0 20px 48px !important;
background: transparent !important;
box-shadow: none !important;
}
/* ── HEADING (gr.Markdown title) ── */
h1 {
font-family: var(--f) !important;
font-size: 1.6rem !important;
font-weight: 800 !important;
letter-spacing: -0.03em !important;
color: var(--text) !important;
padding: 28px 0 4px !important;
border-bottom: 3px solid var(--M) !important;
margin-bottom: 24px !important;
position: relative;
}
/* animated stripe under title */
h1::after {
content: '';
position: absolute;
bottom: -3px; left: 0;
width: 80px; height: 3px;
background: #FF6BB5;
animation: titleSlide 2s ease-in-out infinite alternate;
}
@keyframes titleSlide {
0% { width: 60px; opacity: 0.6; }
100% { width: 160px; opacity: 1; }
}
/* emoji in title stays natural */
h1 .emoji { color: inherit !important; }
/* ── ALL CARDS / PANELS ── */
.gr-box, .gr-panel, .gr-group, .gr-form,
[class*="panel"], [class*="block"] {
background: var(--white) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
box-shadow: var(--sh-sm) !important;
transition: box-shadow 0.2s, border-color 0.2s !important;
}
.gr-box:hover, .gr-panel:hover {
box-shadow: 0 4px 20px rgba(226,0,116,0.10) !important;
border-color: rgba(226,0,116,0.22) !important;
}
/* ── LABELS ── */
label, .gr-label, .label-wrap span {
font-family: var(--f) !important;
font-size: 0.63rem !important;
font-weight: 700 !important;
letter-spacing: 0.10em !important;
text-transform: uppercase !important;
color: var(--muted) !important;
}
/* ── INPUTS (link box + question box) ── */
textarea, input[type="text"] {
font-family: var(--f) !important;
background: var(--surf2) !important;
color: var(--text) !important;
border: 1px solid var(--border2) !important;
border-radius: 8px !important;
padding: 12px 15px !important;
font-size: 0.88rem !important;
line-height: 1.55 !important;
transition: border-color 0.2s, box-shadow 0.2s !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--M) !important;
box-shadow: 0 0 0 3px rgba(226,0,116,0.12) !important;
outline: none !important;
background: var(--white) !important;
}
textarea::placeholder, input[type="text"]::placeholder {
color: var(--muted) !important;
}
/* Status textbox (readonly) gets green monospace style */
textarea[readonly] {
font-family: var(--mono) !important;
font-size: 0.76rem !important;
color: var(--ok) !important;
background: var(--okbg) !important;
border-color: var(--okborder) !important;
}
/* ── BUTTONS ── */
button, .gr-button {
font-family: var(--f) !important;
font-weight: 700 !important;
font-size: 0.75rem !important;
letter-spacing: 0.08em !important;
text-transform: uppercase !important;
border-radius: 8px !important;
padding: 12px 24px !important;
border: none !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
}
/* Primary = magenta gradient */
button.primary, .gr-button-primary, button[variant="primary"] {
background: linear-gradient(135deg, #E20074 0%, #B5005C 100%) !important;
color: #fff !important;
box-shadow: var(--sh-mg) !important;
}
button.primary:hover, .gr-button-primary:hover {
box-shadow: 0 6px 28px rgba(226,0,116,0.40) !important;
transform: translateY(-1px) !important;
}
button.primary:active, .gr-button-primary:active {
transform: translateY(0) !important;
}
/* ── CHATBOT CONTAINER ── */
.gr-chatbot, [data-testid="chatbot"] {
background: #FAFAFA !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
box-shadow: var(--sh-sm) !important;
}
/* User bubble β€” right, magenta */
.message.user, [data-testid="user"] .message {
background: linear-gradient(135deg, #E20074, #B5005C) !important;
color: #fff !important;
border-radius: 12px 12px 3px 12px !important;
padding: 11px 15px !important;
font-size: 0.86rem !important;
line-height: 1.65 !important;
max-width: 72% !important;
margin-left: auto !important;
box-shadow: 0 3px 14px rgba(226,0,116,0.22) !important;
animation: slideR 0.22s ease !important;
}
/* Bot bubble β€” left, white with magenta stripe */
.message.bot, [data-testid="bot"] .message {
background: var(--white) !important;
border: 1px solid var(--border) !important;
border-left: 3px solid var(--M) !important;
color: var(--text) !important;
border-radius: 2px 12px 12px 12px !important;
padding: 11px 15px !important;
font-size: 0.86rem !important;
line-height: 1.65 !important;
max-width: 80% !important;
box-shadow: var(--sh-sm) !important;
animation: slideL 0.22s ease !important;
}
@keyframes slideR {
from { opacity: 0; transform: translateX(10px); }
to { opacity: 1; transform: translateX(0); }
}
@keyframes slideL {
from { opacity: 0; transform: translateX(-10px); }
to { opacity: 1; transform: translateX(0); }
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 5px; height: 5px; }
::-webkit-scrollbar-track { background: var(--surf2); border-radius: 3px; }
::-webkit-scrollbar-thumb { background: rgba(226,0,116,0.28); border-radius: 3px; }
::-webkit-scrollbar-thumb:hover { background: var(--M); }
/* ── HIDE GRADIO FOOTER ── */
footer { display: none !important; }
.gap { gap: 14px !important; }
"""
# ── GLOBALS ────────────────────────────────────────────────────────────────────
embed_model = SentenceTransformer("all-MiniLM-L6-v2")
index = None
chunks = []
chat_history = []
client = OpenAI(
api_key=os.getenv("GROQ_API_KEY"),
base_url="https://api.groq.com/openai/v1",
)
def convert_drive_link(link):
try:
return f"https://drive.google.com/uc?id={link.split('/d/')[1].split('/')[0]}"
except Exception:
return link
def load_pdf_from_link(link):
global index, chunks
resp = requests.get(convert_drive_link(link), timeout=30)
with open("temp.pdf", "wb") as f:
f.write(resp.content)
reader = PdfReader("temp.pdf")
texts = [p.extract_text() for p in reader.pages if p.extract_text()]
chunks = []
for t in texts:
words = t.split()
for i in range(0, len(words), 500):
chunks.append(" ".join(words[i:i+500]))
emb = embed_model.encode(chunks)
index = faiss.IndexFlatL2(emb.shape[1])
index.add(np.array(emb, dtype="float32"))
return f"βœ… {len(chunks)} chunks indexed Β· {len(texts)} pages parsed Β· ready"
def retrieve(query, k=3):
if index is None: return []
q = embed_model.encode([query])
_, idx = index.search(np.array(q, dtype="float32"), k)
return [chunks[i] for i in idx[0]]
def generate_answer(query, ctx=""):
if index is None:
return "⚠️ Please load a PDF first."
rag = "\n\n".join(retrieve(query))
prompt = f"""You are a senior financial analyst at Deutsche Telekom.
You have memory of past conversation and access to official report data.
Conversation history:
{ctx}
Report excerpts (cite ONLY from these for facts):
{rag}
Question: {query}
Respond precisely, use numbers, and keep it concise."""
resp = client.chat.completions.create(
model="llama-3.1-8b-instant",
messages=[{"role": "user", "content": prompt}],
temperature=0.4, max_tokens=650,
)
return resp.choices[0].message.content
def chat(user_input, history):
global chat_history
ctx = "\n".join(f"User: {h['user']}\nAssistant: {h['bot']}" for h in chat_history[-5:])
answer = generate_answer(user_input, ctx)
chat_history.append({"user": user_input, "bot": answer})
new_history = history + [
{"role": "user", "content": user_input},
{"role": "assistant", "content": answer},
]
return new_history, new_history
LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Deutsche_Telekom_logo_simple.svg/120px-Deutsche_Telekom_logo_simple.svg.png"
with gr.Blocks(title="Telekom Finance RAG", css=css) as app:
gr.HTML(f"""
<div class="app-header">
<div class="header-brand">
<div class="t-logo">T</div>
<div>
<div class="header-title">Finance <em>RAG</em> Intelligence</div>
<div class="header-sub">Deutsche Telekom Β· AI-Powered Document Analysis</div>
</div>
</div>
<div class="status-pill">
<div class="pulse-dot"></div>
<span class="status-label">System Live</span>
</div>
</div>
<div class="info-chips">
<div class="chip"><div class="chip-dot"></div>Groq Β· LLaMA 3 8B</div>
<div class="chip"><div class="chip-dot" style="background:#6600AA"></div>FAISS Vector Search</div>
<div class="chip"><div class="chip-dot" style="background:#00C896"></div>MiniLM-L6 Embeddings</div>
<div class="chip"><div class="chip-dot" style="background:#378ADD"></div>RAG Β· Top-K = 3</div>
<div class="chip"><div class="chip-dot" style="background:#FFB700"></div>Conversation Memory</div>
</div>
""")
gr.HTML('<div class="section-label">πŸ“Ž Document Loader</div>')
with gr.Row(equal_height=True):
with gr.Column(scale=6):
link_input = gr.Textbox(
label="Google Drive PDF Link",
placeholder="https://drive.google.com/file/d/xxxxxxx/view",
)
with gr.Column(scale=1, min_width=150):
load_btn = gr.Button("πŸ“₯ Load & Index", variant="primary")
status = gr.Textbox(label="System Status", interactive=False, placeholder="Waiting for PDF…")
gr.HTML("<div style='height:5px;background:linear-gradient(90deg,#E20074,transparent);border-radius:3px;opacity:.4;margin:16px 0 20px'></div>")
gr.HTML('<div class="section-label">πŸ€– AI Financial Analyst</div>')
chatbot = gr.Chatbot(height=460, show_label=False, avatar_images=(None, LOGO))
with gr.Row(equal_height=True):
with gr.Column(scale=8):
msg = gr.Textbox(
show_label=False,
placeholder="Ask about revenue, EBITDA, KPIs, capex, strategic outlook…",
container=False,
)
with gr.Column(scale=1, min_width=120):
send_btn = gr.Button("Send ↑", variant="primary")
gr.HTML("""
<div class="app-footer">
<div class="f-text">Powered by <span>Groq</span> Β· <span>LLaMA 3</span> Β· <span>FAISS</span> Β· <span>MiniLM</span></div>
<div class="f-text">Deutsche Telekom AG Β· Internal AI Tool Β· Confidential</div>
</div>
""")
load_btn.click(load_pdf_from_link, inputs=link_input, outputs=status)
msg.submit(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
msg.submit(lambda: "", outputs=msg)
send_btn.click(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
send_btn.click(lambda: "", outputs=msg)
if __name__ == "__main__":
app.launch()