agnixcode commited on
Commit
66bea2d
·
verified ·
1 Parent(s): cb691e7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +252 -339
app.py CHANGED
@@ -7,454 +7,367 @@ from openai import OpenAI
7
  from pypdf import PdfReader
8
  from sentence_transformers import SentenceTransformer
9
 
10
- # ================================================================
11
- # TELEKOM FINANCE RAG — Premium Dark Theme
12
- # Deutsche Telekom Corporate Magenta · Sora · DM Mono
13
- # ================================================================
14
-
15
  css = """
16
- @import url('https://fonts.googleapis.com/css2?family=Sora:wght@300;400;600;700;800&family=DM+Mono:wght@400;500&display=swap');
17
-
18
- :root {
19
- --magenta: #E20074;
20
- --magenta-dark: #B5005C;
21
- --magenta-glow: rgba(226,0,116,0.22);
22
- --bg: #0D0D0D;
23
- --surface: #161616;
24
- --surface-2: #1E1E1E;
25
- --surface-3: #272727;
26
- --border: #2A2A2A;
27
- --border-accent: #3A3A3A;
28
- --text-primary: #F0F0F0;
29
- --text-secondary: #888888;
30
- --text-muted: #555555;
31
- --success: #00C896;
32
- --mono: 'DM Mono', monospace;
33
- --sans: 'Sora', sans-serif;
34
- --r-sm: 6px; --r-md: 10px; --r-lg: 16px; --r-xl: 22px;
35
  }
36
 
37
- /* RESET */
38
- *, *::before, *::after { box-sizing: border-box; }
39
-
40
- /* BASE */
41
- body {
42
- font-family: var(--sans) !important;
43
- background: var(--bg) !important;
44
- color: var(--text-primary) !important;
45
- background-image:
46
- radial-gradient(ellipse 70% 50% at 5% 0%, rgba(226,0,116,0.10) 0%, transparent 55%),
47
- radial-gradient(ellipse 50% 40% at 95% 100%, rgba(226,0,116,0.07) 0%, transparent 50%);
48
  }
49
 
50
- /* CONTAINER */
51
- .gradio-container {
52
- max-width: 1080px !important;
53
- margin: 0 auto !important;
54
- padding: 0 20px 48px !important;
55
- background: transparent !important;
56
- box-shadow: none !important;
 
57
  }
58
-
59
- /* ── HEADER CARD ── */
60
- .header-card {
61
- background: linear-gradient(145deg, #1A0010 0%, #0D0D0D 60%);
62
- border: 1px solid var(--border-accent);
63
- border-top: 3px solid var(--magenta);
64
- border-radius: 0 0 var(--r-xl) var(--r-xl);
65
- padding: 36px 48px 32px;
66
- margin-bottom: 28px;
67
- position: relative;
68
- overflow: hidden;
69
  }
70
 
71
- .header-card::before {
72
- content: '';
73
- position: absolute;
74
- top: -80px; right: -80px;
75
- width: 300px; height: 300px;
76
- border-radius: 50%;
77
- background: radial-gradient(circle, rgba(226,0,116,0.15) 0%, transparent 70%);
78
- pointer-events: none;
79
  }
80
 
81
- /* BIG T watermark */
82
- .header-card::after {
83
- content: 'T';
84
- position: absolute;
85
- right: 36px; bottom: -28px;
86
- font-family: var(--sans);
87
- font-size: 160px; font-weight: 800;
88
- color: rgba(226,0,116,0.06);
89
- line-height: 1;
90
- pointer-events: none; user-select: none;
91
  }
92
 
93
- .header-title {
94
- font-size: 1.85rem;
95
- font-weight: 800;
96
- letter-spacing: -0.03em;
97
- line-height: 1.2;
98
- color: var(--text-primary);
99
- margin: 0 0 8px;
 
 
100
  }
101
-
102
- .header-title span { color: var(--magenta); }
103
-
104
- .header-sub {
105
- font-size: 0.82rem;
106
- font-weight: 400;
107
- color: var(--text-secondary);
108
- letter-spacing: 0.06em;
109
- text-transform: uppercase;
110
  }
111
-
112
- .header-logo {
113
- width: 52px; height: 52px;
114
- border-radius: var(--r-md);
115
- object-fit: cover;
116
- border: 2px solid rgba(226,0,116,0.4);
117
- margin-right: 18px;
 
118
  }
119
-
120
- .header-flex {
121
- display: flex;
122
- align-items: center;
 
123
  }
124
-
125
- /* ── PANELS ── */
126
- .gr-box, .gr-panel, .gr-group, .gr-form,
127
- [class*="panel"], [class*="block"] {
128
- background: var(--surface) !important;
129
- border: 1px solid var(--border) !important;
130
- border-radius: var(--r-lg) !important;
131
  }
132
-
133
- /* ── LABELS ── */
134
- label, .gr-label, .label-wrap span {
135
- font-family: var(--sans) !important;
136
- font-size: 0.68rem !important;
137
- font-weight: 700 !important;
138
- letter-spacing: 0.10em !important;
139
- text-transform: uppercase !important;
140
- color: var(--text-secondary) !important;
141
  }
142
-
143
- /* ── INPUTS ── */
144
- textarea, input[type="text"] {
145
- font-family: var(--sans) !important;
146
- background: var(--surface-2) !important;
147
- color: var(--text-primary) !important;
148
- border: 1px solid var(--border-accent) !important;
149
- border-radius: var(--r-md) !important;
150
- padding: 12px 16px !important;
151
- font-size: 0.88rem !important;
152
- line-height: 1.5 !important;
153
- transition: border-color 0.18s, box-shadow 0.18s !important;
154
  }
155
-
156
- textarea:focus, input[type="text"]:focus {
157
- border-color: var(--magenta) !important;
158
- box-shadow: 0 0 0 3px var(--magenta-glow) !important;
159
- outline: none !important;
 
 
 
 
 
160
  }
161
-
162
- textarea::placeholder, input[type="text"]::placeholder {
163
- color: var(--text-muted) !important;
 
 
 
 
 
164
  }
165
-
166
- /* Chat input special */
167
- .message-input textarea {
168
- border-radius: var(--r-md) !important;
169
- min-height: 52px !important;
170
- resize: none !important;
 
 
 
 
171
  }
 
172
 
173
- /* Status readonly */
174
- textarea[readonly] {
175
- font-family: var(--mono) !important;
176
- font-size: 0.78rem !important;
177
- color: var(--success) !important;
178
- background: var(--surface-2) !important;
179
- border-color: rgba(0,200,150,0.25) !important;
180
  }
181
 
182
- /* ── BUTTONS ── */
183
- button, .gr-button {
184
- font-family: var(--sans) !important;
185
- font-weight: 700 !important;
186
- font-size: 0.78rem !important;
187
- letter-spacing: 0.08em !important;
188
- text-transform: uppercase !important;
189
- border-radius: var(--r-md) !important;
190
- padding: 11px 24px !important;
191
- border: none !important;
192
- cursor: pointer !important;
193
- transition: all 0.18s ease !important;
194
  }
195
-
196
- button.primary, .gr-button-primary, button[variant="primary"] {
197
- background: var(--magenta) !important;
198
- color: #fff !important;
199
- box-shadow: 0 4px 20px var(--magenta-glow) !important;
200
  }
201
-
202
- button.primary:hover, .gr-button-primary:hover {
203
- background: var(--magenta-dark) !important;
204
- box-shadow: 0 6px 28px rgba(226,0,116,0.40) !important;
205
- transform: translateY(-1px) !important;
206
  }
207
 
208
- button.secondary, .gr-button-secondary {
209
- background: transparent !important;
210
- color: var(--magenta) !important;
211
- border: 1.5px solid var(--magenta) !important;
 
 
212
  }
213
-
214
- /* ── CHATBOT ── */
215
- .gr-chatbot, [data-testid="chatbot"] {
216
- background: var(--surface) !important;
217
- border: 1px solid var(--border) !important;
218
- border-radius: var(--r-lg) !important;
219
- font-family: var(--sans) !important;
220
  }
221
-
222
- /* User bubble */
223
- .message.user,
224
- [data-testid="user"] .message {
225
- background: linear-gradient(135deg, var(--magenta) 0%, var(--magenta-dark) 100%) !important;
226
- color: #fff !important;
227
- border-radius: var(--r-md) var(--r-md) var(--r-sm) var(--r-md) !important;
228
- padding: 11px 16px !important;
229
- font-size: 0.86rem !important;
230
- line-height: 1.65 !important;
231
- box-shadow: 0 2px 14px rgba(226,0,116,0.20) !important;
232
- max-width: 74% !important;
233
- margin-left: auto !important;
234
  }
235
-
236
- /* Bot bubble */
237
- .message.bot,
238
- [data-testid="bot"] .message {
239
- background: var(--surface-2) !important;
240
- color: var(--text-primary) !important;
241
- border-radius: var(--r-md) var(--r-md) var(--r-md) var(--r-sm) !important;
242
- border-left: 3px solid var(--magenta) !important;
243
- padding: 11px 16px !important;
244
- font-size: 0.86rem !important;
245
- line-height: 1.65 !important;
246
- max-width: 80% !important;
247
  }
 
248
 
249
- /* ── DIVIDER / ROW ── */
250
- .gap { gap: 16px !important; }
251
-
252
- /* ── SCROLLBAR ── */
253
- ::-webkit-scrollbar { width: 5px; height: 5px; }
254
- ::-webkit-scrollbar-track { background: var(--surface); }
255
- ::-webkit-scrollbar-thumb { background: var(--border-accent); border-radius: 3px; }
256
- ::-webkit-scrollbar-thumb:hover { background: var(--magenta); }
257
-
258
- /* ── ANIMATION ── */
259
- @keyframes fadeUp {
260
- from { opacity: 0; transform: translateY(10px); }
261
- to { opacity: 1; transform: translateY(0); }
262
  }
263
- .gr-chatbot .message { animation: fadeUp 0.22s ease both; }
264
-
265
- /* ── HIDE FOOTER ── */
266
- footer { display: none !important; }
267
-
268
- /* ── RESPONSIVE ── */
269
- @media (max-width: 700px) {
270
- .header-card { padding: 24px 20px 20px; }
271
- .header-title { font-size: 1.3rem; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  }
 
 
 
 
273
  """
274
 
275
  # ── GLOBALS ────────────────────────────────────────────────────────────────────
276
- embed_model = SentenceTransformer("all-MiniLM-L6-v2")
277
- index = None
278
- chunks = []
279
  chat_history = []
280
 
281
- # ── GROQ CLIENT ────────────────────────────────────────────────────────────────
282
  client = OpenAI(
283
  api_key=os.getenv("GROQ_API_KEY"),
284
  base_url="https://api.groq.com/openai/v1",
285
  )
286
 
287
- # ── HELPERS ────────────────────────────────────────────────────────────────────
288
- def convert_drive_link(link: str) -> str:
289
  try:
290
- file_id = link.split("/d/")[1].split("/")[0]
291
- return f"https://drive.google.com/uc?id={file_id}"
292
  except Exception:
293
  return link
294
 
295
-
296
- def load_pdf_from_link(link: str) -> str:
297
  global index, chunks
298
- url = convert_drive_link(link)
299
- pdf_path = "temp.pdf"
300
-
301
- resp = requests.get(url, timeout=30)
302
- with open(pdf_path, "wb") as f:
303
  f.write(resp.content)
304
-
305
- reader = PdfReader(pdf_path)
306
  texts = [p.extract_text() for p in reader.pages if p.extract_text()]
307
-
308
  chunks = []
309
  for t in texts:
310
  words = t.split()
311
  for i in range(0, len(words), 500):
312
- chunks.append(" ".join(words[i : i + 500]))
313
-
314
- embeddings = embed_model.encode(chunks)
315
- dim = embeddings.shape[1]
316
- index = faiss.IndexFlatL2(dim)
317
- index.add(np.array(embeddings, dtype="float32"))
318
-
319
- return f"✅ Loaded successfully {len(chunks)} chunks indexed from {len(texts)} pages."
320
-
321
-
322
- def retrieve(query: str, k: int = 3) -> list[str]:
323
- if index is None:
324
- return []
325
- q_emb = embed_model.encode([query])
326
- distances, indices_ = index.search(np.array(q_emb, dtype="float32"), k)
327
- return [chunks[i] for i in indices_[0]]
328
-
329
-
330
- def generate_answer(query: str, conversation_context: str = "") -> str:
331
  if index is None:
332
- return "⚠️ Please load a PDF first using the panel above."
333
-
334
- rag_context = retrieve(query)
335
- rag_text = "\n\n".join(rag_context)
336
-
337
- prompt = f"""You are a senior financial analyst AI assistant for Deutsche Telekom.
338
- You have access to official financial report data and maintain conversation context.
339
-
340
- Previous conversation:
341
- {conversation_context}
342
 
343
- PDF Context — use ONLY this for factual claims:
344
- {rag_text}
345
 
346
- User question: {query}
 
347
 
348
- Respond with precision and confidence. Reference prior conversation when relevant.
349
- Use clear structure (short paragraphs or bullet points where helpful)."""
350
 
351
- response = client.chat.completions.create(
 
352
  model="llama-3.1-8b-instant",
353
  messages=[{"role": "user", "content": prompt}],
354
- temperature=0.5,
355
- max_tokens=700,
356
  )
357
- return response.choices[0].message.content
358
 
359
-
360
- def chat(user_input: str, history: list) -> tuple:
361
  global chat_history
362
-
363
- ctx = "\n".join(
364
- f"User: {h['user']}\nAssistant: {h['bot']}"
365
- for h in chat_history[-5:]
366
- )
367
-
368
  answer = generate_answer(user_input, ctx)
369
-
370
  chat_history.append({"user": user_input, "bot": answer})
371
-
372
  new_history = history + [
373
  {"role": "user", "content": user_input},
374
  {"role": "assistant", "content": answer},
375
  ]
376
  return new_history, new_history
377
 
 
378
 
379
- # ── LOGO (base64 inline or URL fallback) ───────────────────────────────────────
380
- LOGO_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Deutsche_Telekom_logo_simple.svg/120px-Deutsche_Telekom_logo_simple.svg.png"
381
-
382
- # ── UI ─────────────────────────────────────────────────────────────────────────
383
  with gr.Blocks(title="Telekom Finance RAG", css=css) as app:
384
 
385
- # ── Header ────────────────────────────────────────────────────────────────
386
  gr.HTML(f"""
387
- <div class="header-card">
388
- <div class="header-flex">
389
- <img class="header-logo" src="{LOGO_URL}" alt="Telekom Logo" onerror="this.style.display='none'">
390
  <div>
391
- <div class="header-title">Finance <span>RAG</span> Intelligence</div>
392
- <div class="header-sub">Deutsche Telekom · AI-Powered Document Analysis · {{}}</div>
393
  </div>
394
  </div>
 
 
 
 
 
 
 
 
 
 
 
395
  </div>
396
  """)
397
 
398
- # ── PDF Loader Row ─────────────────────────────────────────────────────────
 
399
  with gr.Row(equal_height=True):
400
- with gr.Column(scale=5):
401
  link_input = gr.Textbox(
402
- label="📎 Google Drive PDF Link",
403
  placeholder="https://drive.google.com/file/d/xxxxxxx/view",
404
  )
405
- with gr.Column(scale=1, min_width=140):
406
- load_btn = gr.Button("📥 Load PDF", variant="primary")
407
 
408
- status = gr.Textbox(
409
- label="System Status",
410
- interactive=False,
411
- placeholder="Waiting for PDF…",
412
- )
413
 
414
- gr.HTML("<div style='height:4px;background:linear-gradient(90deg,#E20074 0%,transparent 100%);border-radius:2px;margin:8px 0 20px;opacity:0.5'></div>")
415
 
416
- # ��─ Chat Section ───────────────────────────────────────────────────────────
417
- gr.HTML("<div style='font-size:0.68rem;font-weight:700;letter-spacing:0.10em;text-transform:uppercase;color:#888;margin-bottom:12px'>💬 &nbsp;Conversation</div>")
418
 
419
- chatbot = gr.Chatbot(
420
- height=480,
421
- show_label=False,
422
- avatar_images=(None, LOGO_URL),
423
- )
424
 
425
  with gr.Row(equal_height=True):
426
- with gr.Column(scale=7):
427
  msg = gr.Textbox(
428
  show_label=False,
429
- placeholder="Ask about revenue, EBITDA, KPIs, strategic outlook…",
430
  container=False,
431
- elem_classes=["message-input"],
432
  )
433
- with gr.Column(scale=1, min_width=110):
434
  send_btn = gr.Button("Send ↑", variant="primary")
435
 
436
  gr.HTML("""
437
- <div style='margin-top:28px;padding:16px 20px;background:#161616;border:1px solid #2A2A2A;
438
- border-radius:10px;display:flex;gap:32px;flex-wrap:wrap'>
439
- <div style='font-size:0.7rem;color:#555;letter-spacing:0.06em;text-transform:uppercase'>
440
- Powered by&nbsp;<span style='color:#E20074;font-weight:700'>Groq · LLaMA 3</span>
441
- &nbsp;+&nbsp;<span style='color:#E20074;font-weight:700'>FAISS · MiniLM</span>
442
- </div>
443
- <div style='font-size:0.7rem;color:#555;letter-spacing:0.06em;text-transform:uppercase;margin-left:auto'>
444
- Deutsche Telekom AG · Confidential
445
- </div>
446
  </div>
447
  """)
448
 
449
- # ── Events ─────────────────────────────────────────────────────────────────
450
  load_btn.click(load_pdf_from_link, inputs=link_input, outputs=status)
451
-
452
- msg.submit(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
453
  msg.submit(lambda: "", outputs=msg)
454
-
455
  send_btn.click(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
456
  send_btn.click(lambda: "", outputs=msg)
457
 
458
-
459
  if __name__ == "__main__":
460
  app.launch()
 
7
  from pypdf import PdfReader
8
  from sentence_transformers import SentenceTransformer
9
 
 
 
 
 
 
10
  css = """
11
+ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap');
12
+
13
+ *,*::before,*::after{box-sizing:border-box}
14
+
15
+ :root{
16
+ --M:#E20074;--MD:#B5005C;
17
+ --BG:#080008;
18
+ --glass:rgba(255,255,255,0.04);
19
+ --glass-b:rgba(255,255,255,0.09);
20
+ --text:#F2F2F2;--muted:#888;--dim:#444;
21
+ --ok:#00C896;
22
+ --f:'Plus Jakarta Sans',sans-serif;
23
+ --mono:'JetBrains Mono',monospace;
24
+ --r-sm:8px;--r-md:14px;--r-lg:20px;--r-xl:28px;
 
 
 
 
 
25
  }
26
 
27
+ body{
28
+ font-family:var(--f)!important;
29
+ background:var(--BG)!important;
30
+ color:var(--text)!important;
31
+ position:relative;overflow-x:hidden;
 
 
 
 
 
 
32
  }
33
 
34
+ body::before{
35
+ content:'';position:fixed;inset:0;
36
+ background:
37
+ radial-gradient(ellipse 55% 40% at 10% 5%,rgba(226,0,116,0.18) 0%,transparent 60%),
38
+ radial-gradient(ellipse 40% 35% at 88% 85%,rgba(226,0,116,0.12) 0%,transparent 55%),
39
+ radial-gradient(ellipse 35% 30% at 70% 15%,rgba(100,0,50,0.14) 0%,transparent 50%);
40
+ animation:meshDrift 10s ease-in-out infinite alternate;
41
+ pointer-events:none;z-index:0;
42
  }
43
+ @keyframes meshDrift{
44
+ 0%{opacity:1;transform:scale(1)}
45
+ 100%{opacity:.8;transform:scale(1.04) translate(1%,1%)}
 
 
 
 
 
 
 
 
46
  }
47
 
48
+ body::after{
49
+ content:'';position:fixed;inset:0;
50
+ background-image:
51
+ linear-gradient(rgba(226,0,116,0.034) 1px,transparent 1px),
52
+ linear-gradient(90deg,rgba(226,0,116,0.034) 1px,transparent 1px);
53
+ background-size:44px 44px;
54
+ pointer-events:none;z-index:0;
 
55
  }
56
 
57
+ .gradio-container{
58
+ max-width:1080px!important;margin:0 auto!important;
59
+ padding:0 24px 48px!important;
60
+ background:transparent!important;box-shadow:none!important;
61
+ position:relative;z-index:1;
 
 
 
 
 
62
  }
63
 
64
+ /* HEADER */
65
+ .app-header{
66
+ background:linear-gradient(145deg,rgba(40,0,20,.92) 0%,rgba(15,0,8,.96) 70%);
67
+ backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
68
+ border:1px solid rgba(226,0,116,0.25);border-top:3px solid var(--M);
69
+ border-radius:0 0 var(--r-xl) var(--r-xl);
70
+ padding:28px 40px 26px;margin-bottom:24px;
71
+ display:flex;align-items:center;justify-content:space-between;
72
+ position:relative;overflow:hidden;
73
  }
74
+ .app-header::after{
75
+ content:'T';position:absolute;right:24px;bottom:-32px;
76
+ font-family:var(--f);font-size:160px;font-weight:800;
77
+ color:rgba(226,0,116,0.06);line-height:1;
78
+ pointer-events:none;user-select:none;
 
 
 
 
79
  }
80
+ .header-brand{display:flex;align-items:center;gap:16px}
81
+ .t-logo{
82
+ width:52px;height:52px;border-radius:14px;
83
+ background:linear-gradient(145deg,#E20074,#800040);
84
+ display:flex;align-items:center;justify-content:center;
85
+ font-size:26px;font-weight:800;color:#fff;
86
+ box-shadow:0 0 28px rgba(226,0,116,0.45),0 4px 12px rgba(0,0,0,.4);
87
+ flex-shrink:0;font-family:var(--f);
88
  }
89
+ .header-title{font-size:1.35rem;font-weight:800;letter-spacing:-.035em;color:#fff;line-height:1.2}
90
+ .header-title em{
91
+ font-style:normal;
92
+ background:linear-gradient(135deg,#E20074,#FF6BB5);
93
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;
94
  }
95
+ .header-sub{font-size:.65rem;font-weight:600;letter-spacing:.12em;text-transform:uppercase;color:var(--muted);margin-top:3px}
96
+ .status-pill{
97
+ display:flex;align-items:center;gap:8px;
98
+ background:rgba(0,200,150,.08);border:1px solid rgba(0,200,150,.22);
99
+ border-radius:99px;padding:8px 16px;flex-shrink:0;
 
 
100
  }
101
+ .pulse-dot{
102
+ width:8px;height:8px;border-radius:50%;background:var(--ok);
103
+ animation:pDot 2.2s ease-in-out infinite;
 
 
 
 
 
 
104
  }
105
+ @keyframes pDot{
106
+ 0%,100%{box-shadow:0 0 0 0 rgba(0,200,150,.5)}
107
+ 50%{box-shadow:0 0 0 7px rgba(0,200,150,0)}
 
 
 
 
 
 
 
 
 
108
  }
109
+ .status-label{font-size:.66rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--ok)}
110
+
111
+ /* CHIPS */
112
+ .info-chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:20px}
113
+ .chip{
114
+ display:flex;align-items:center;gap:7px;
115
+ background:var(--glass);border:1px solid var(--glass-b);
116
+ border-radius:99px;padding:6px 14px;
117
+ font-size:.65rem;font-weight:600;letter-spacing:.06em;color:var(--muted);
118
+ transition:border-color .2s;
119
  }
120
+ .chip:hover{border-color:rgba(226,0,116,.35)}
121
+ .chip-dot{width:6px;height:6px;border-radius:50%;background:var(--M);flex-shrink:0}
122
+
123
+ /* SECTION LABEL */
124
+ .section-label{
125
+ font-size:.62rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
126
+ color:var(--muted);margin-bottom:10px;
127
+ display:flex;align-items:center;gap:8px;
128
  }
129
+ .section-label::after{content:'';flex:1;height:1px;background:linear-gradient(90deg,rgba(226,0,116,.3),transparent)}
130
+
131
+ /* GLASS CARDS */
132
+ .gr-box,.gr-panel,.gr-group,.gr-form,[class*="panel"],[class*="block"]{
133
+ background:var(--glass)!important;
134
+ border:1px solid var(--glass-b)!important;
135
+ border-radius:var(--r-md)!important;
136
+ backdrop-filter:blur(12px)!important;
137
+ -webkit-backdrop-filter:blur(12px)!important;
138
+ transition:border-color .2s!important;
139
  }
140
+ .gr-box:hover,.gr-panel:hover{border-color:rgba(226,0,116,.28)!important}
141
 
142
+ /* LABELS */
143
+ label,.gr-label,.label-wrap span{
144
+ font-family:var(--f)!important;font-size:.62rem!important;font-weight:700!important;
145
+ letter-spacing:.10em!important;text-transform:uppercase!important;color:var(--muted)!important;
 
 
 
146
  }
147
 
148
+ /* INPUTS */
149
+ textarea,input[type="text"]{
150
+ font-family:var(--f)!important;background:var(--glass)!important;
151
+ color:var(--text)!important;border:1px solid var(--glass-b)!important;
152
+ border-radius:var(--r-sm)!important;padding:12px 15px!important;
153
+ font-size:.86rem!important;line-height:1.55!important;
154
+ transition:border-color .2s,box-shadow .2s!important;
 
 
 
 
 
155
  }
156
+ textarea:focus,input[type="text"]:focus{
157
+ border-color:var(--M)!important;
158
+ box-shadow:0 0 0 3px rgba(226,0,116,.14)!important;outline:none!important;
 
 
159
  }
160
+ textarea::placeholder,input[type="text"]::placeholder{color:var(--dim)!important;font-style:italic!important}
161
+ textarea[readonly]{
162
+ font-family:var(--mono)!important;font-size:.75rem!important;
163
+ color:var(--ok)!important;background:rgba(0,200,150,.05)!important;
164
+ border-color:rgba(0,200,150,.18)!important;
165
  }
166
 
167
+ /* BUTTONS */
168
+ button,.gr-button{
169
+ font-family:var(--f)!important;font-weight:700!important;font-size:.74rem!important;
170
+ letter-spacing:.09em!important;text-transform:uppercase!important;
171
+ border-radius:var(--r-sm)!important;padding:12px 24px!important;
172
+ border:none!important;cursor:pointer!important;transition:all .2s ease!important;
173
  }
174
+ button.primary,.gr-button-primary,button[variant="primary"]{
175
+ background:linear-gradient(135deg,#E20074 0%,#B5005C 100%)!important;
176
+ color:#fff!important;box-shadow:0 4px 22px rgba(226,0,116,.35)!important;
 
 
 
 
177
  }
178
+ button.primary:hover,.gr-button-primary:hover{
179
+ box-shadow:0 6px 32px rgba(226,0,116,.55)!important;transform:translateY(-2px)!important;
 
 
 
 
 
 
 
 
 
 
 
180
  }
181
+ button.secondary,.gr-button-secondary{
182
+ background:transparent!important;color:var(--M)!important;
183
+ border:1px solid rgba(226,0,116,.4)!important;
 
 
 
 
 
 
 
 
 
184
  }
185
+ button.secondary:hover{background:rgba(226,0,116,.08)!important}
186
 
187
+ /* CHATBOT */
188
+ .gr-chatbot,[data-testid="chatbot"]{
189
+ background:rgba(255,255,255,.03)!important;
190
+ border:1px solid rgba(255,255,255,.08)!important;
191
+ border-radius:var(--r-md)!important;backdrop-filter:blur(12px)!important;
 
 
 
 
 
 
 
 
192
  }
193
+ .message.user,[data-testid="user"] .message{
194
+ background:linear-gradient(135deg,#E20074 0%,#B5005C 100%)!important;
195
+ color:#fff!important;border-radius:12px 12px 3px 12px!important;
196
+ padding:11px 15px!important;font-size:.84rem!important;line-height:1.65!important;
197
+ max-width:74%!important;margin-left:auto!important;
198
+ box-shadow:0 4px 18px rgba(226,0,116,.28)!important;
199
+ animation:slideInR .24s ease!important;
200
+ }
201
+ .message.bot,[data-testid="bot"] .message{
202
+ background:rgba(255,255,255,.05)!important;
203
+ border:1px solid rgba(255,255,255,.09)!important;
204
+ border-left:3px solid var(--M)!important;
205
+ color:var(--text)!important;border-radius:3px 12px 12px 12px!important;
206
+ padding:11px 15px!important;font-size:.84rem!important;line-height:1.65!important;
207
+ max-width:80%!important;animation:slideInL .24s ease!important;
208
+ }
209
+ @keyframes slideInR{from{opacity:0;transform:translateX(12px)}to{opacity:1;transform:translateX(0)}}
210
+ @keyframes slideInL{from{opacity:0;transform:translateX(-12px)}to{opacity:1;transform:translateX(0)}}
211
+
212
+ /* SCROLLBAR */
213
+ ::-webkit-scrollbar{width:4px;height:4px}
214
+ ::-webkit-scrollbar-track{background:rgba(255,255,255,.02)}
215
+ ::-webkit-scrollbar-thumb{background:rgba(226,0,116,.3);border-radius:2px}
216
+ ::-webkit-scrollbar-thumb:hover{background:var(--M)}
217
+
218
+ /* FOOTER */
219
+ .app-footer{
220
+ display:flex;align-items:center;justify-content:space-between;
221
+ padding:16px 0 0;border-top:1px solid rgba(255,255,255,.06);margin-top:8px;
222
  }
223
+ .f-text{font-size:.62rem;color:var(--dim)}
224
+ .f-text span{color:var(--M);font-weight:600}
225
+ footer{display:none!important}
226
+ .gap{gap:16px!important}
227
  """
228
 
229
  # ── GLOBALS ────────────────────────────────────────────────────────────────────
230
+ embed_model = SentenceTransformer("all-MiniLM-L6-v2")
231
+ index = None
232
+ chunks = []
233
  chat_history = []
234
 
 
235
  client = OpenAI(
236
  api_key=os.getenv("GROQ_API_KEY"),
237
  base_url="https://api.groq.com/openai/v1",
238
  )
239
 
240
+ def convert_drive_link(link):
 
241
  try:
242
+ return f"https://drive.google.com/uc?id={link.split('/d/')[1].split('/')[0]}"
 
243
  except Exception:
244
  return link
245
 
246
+ def load_pdf_from_link(link):
 
247
  global index, chunks
248
+ resp = requests.get(convert_drive_link(link), timeout=30)
249
+ with open("temp.pdf", "wb") as f:
 
 
 
250
  f.write(resp.content)
251
+ reader = PdfReader("temp.pdf")
 
252
  texts = [p.extract_text() for p in reader.pages if p.extract_text()]
 
253
  chunks = []
254
  for t in texts:
255
  words = t.split()
256
  for i in range(0, len(words), 500):
257
+ chunks.append(" ".join(words[i:i+500]))
258
+ emb = embed_model.encode(chunks)
259
+ index = faiss.IndexFlatL2(emb.shape[1])
260
+ index.add(np.array(emb, dtype="float32"))
261
+ return f"✅ {len(chunks)} chunks indexed · {len(texts)} pages parsed · ready"
262
+
263
+ def retrieve(query, k=3):
264
+ if index is None: return []
265
+ q = embed_model.encode([query])
266
+ _, idx = index.search(np.array(q, dtype="float32"), k)
267
+ return [chunks[i] for i in idx[0]]
268
+
269
+ def generate_answer(query, ctx=""):
 
 
 
 
 
 
270
  if index is None:
271
+ return "⚠️ Please load a PDF first."
272
+ rag = "\n\n".join(retrieve(query))
273
+ prompt = f"""You are a senior financial analyst at Deutsche Telekom.
274
+ You have memory of past conversation and access to official report data.
 
 
 
 
 
 
275
 
276
+ Conversation history:
277
+ {ctx}
278
 
279
+ Report excerpts (cite ONLY from these for facts):
280
+ {rag}
281
 
282
+ Question: {query}
 
283
 
284
+ Respond precisely, use numbers, and keep it concise."""
285
+ resp = client.chat.completions.create(
286
  model="llama-3.1-8b-instant",
287
  messages=[{"role": "user", "content": prompt}],
288
+ temperature=0.4, max_tokens=650,
 
289
  )
290
+ return resp.choices[0].message.content
291
 
292
+ def chat(user_input, history):
 
293
  global chat_history
294
+ ctx = "\n".join(f"User: {h['user']}\nAssistant: {h['bot']}" for h in chat_history[-5:])
 
 
 
 
 
295
  answer = generate_answer(user_input, ctx)
 
296
  chat_history.append({"user": user_input, "bot": answer})
 
297
  new_history = history + [
298
  {"role": "user", "content": user_input},
299
  {"role": "assistant", "content": answer},
300
  ]
301
  return new_history, new_history
302
 
303
+ LOGO = "https://upload.wikimedia.org/wikipedia/commons/thumb/e/e0/Deutsche_Telekom_logo_simple.svg/120px-Deutsche_Telekom_logo_simple.svg.png"
304
 
 
 
 
 
305
  with gr.Blocks(title="Telekom Finance RAG", css=css) as app:
306
 
 
307
  gr.HTML(f"""
308
+ <div class="app-header">
309
+ <div class="header-brand">
310
+ <div class="t-logo">T</div>
311
  <div>
312
+ <div class="header-title">Finance <em>RAG</em> Intelligence</div>
313
+ <div class="header-sub">Deutsche Telekom · AI-Powered Document Analysis</div>
314
  </div>
315
  </div>
316
+ <div class="status-pill">
317
+ <div class="pulse-dot"></div>
318
+ <span class="status-label">System Live</span>
319
+ </div>
320
+ </div>
321
+ <div class="info-chips">
322
+ <div class="chip"><div class="chip-dot"></div>Groq · LLaMA 3 8B</div>
323
+ <div class="chip"><div class="chip-dot" style="background:#6600AA"></div>FAISS Vector Search</div>
324
+ <div class="chip"><div class="chip-dot" style="background:#00C896"></div>MiniLM-L6 Embeddings</div>
325
+ <div class="chip"><div class="chip-dot" style="background:#378ADD"></div>RAG · Top-K = 3</div>
326
+ <div class="chip"><div class="chip-dot" style="background:#FFB700"></div>Conversation Memory</div>
327
  </div>
328
  """)
329
 
330
+ gr.HTML('<div class="section-label">📎 Document Loader</div>')
331
+
332
  with gr.Row(equal_height=True):
333
+ with gr.Column(scale=6):
334
  link_input = gr.Textbox(
335
+ label="Google Drive PDF Link",
336
  placeholder="https://drive.google.com/file/d/xxxxxxx/view",
337
  )
338
+ with gr.Column(scale=1, min_width=150):
339
+ load_btn = gr.Button("📥 Load & Index", variant="primary")
340
 
341
+ status = gr.Textbox(label="System Status", interactive=False, placeholder="Waiting for PDF…")
 
 
 
 
342
 
343
+ gr.HTML("<div style='height:5px;background:linear-gradient(90deg,#E20074,transparent);border-radius:3px;opacity:.4;margin:16px 0 20px'></div>")
344
 
345
+ gr.HTML('<div class="section-label">🤖 AI Financial Analyst</div>')
 
346
 
347
+ chatbot = gr.Chatbot(height=460, show_label=False, avatar_images=(None, LOGO))
 
 
 
 
348
 
349
  with gr.Row(equal_height=True):
350
+ with gr.Column(scale=8):
351
  msg = gr.Textbox(
352
  show_label=False,
353
+ placeholder="Ask about revenue, EBITDA, KPIs, capex, strategic outlook…",
354
  container=False,
 
355
  )
356
+ with gr.Column(scale=1, min_width=120):
357
  send_btn = gr.Button("Send ↑", variant="primary")
358
 
359
  gr.HTML("""
360
+ <div class="app-footer">
361
+ <div class="f-text">Powered by <span>Groq</span> · <span>LLaMA 3</span> · <span>FAISS</span> · <span>MiniLM</span></div>
362
+ <div class="f-text">Deutsche Telekom AG · Internal AI Tool · Confidential</div>
 
 
 
 
 
 
363
  </div>
364
  """)
365
 
 
366
  load_btn.click(load_pdf_from_link, inputs=link_input, outputs=status)
367
+ msg.submit(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
 
368
  msg.submit(lambda: "", outputs=msg)
 
369
  send_btn.click(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
370
  send_btn.click(lambda: "", outputs=msg)
371
 
 
372
  if __name__ == "__main__":
373
  app.launch()