agnixcode commited on
Commit
cb691e7
Β·
verified Β·
1 Parent(s): 06fe8f5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +328 -276
app.py CHANGED
@@ -7,180 +7,196 @@ from openai import OpenAI
7
  from pypdf import PdfReader
8
  from sentence_transformers import SentenceTransformer
9
 
10
- css = """
11
-
12
- /* ================================================================
13
- DEUTSCHE TELEKOM β€” FINANCE RAG SYSTEM
14
- Corporate Magenta Β· Clean Β· Professional Β· Premium
15
- ================================================================ */
16
 
 
17
  @import url('https://fonts.googleapis.com/css2?family=Sora:wght@300;400;600;700;800&family=DM+Mono:wght@400;500&display=swap');
18
 
19
  :root {
20
- --magenta: #E20074;
21
- --magenta-dark: #B5005C;
22
- --magenta-light: #FF3399;
23
- --magenta-pale: #FFE6F2;
24
- --black: #0A0A0A;
25
- --dark: #111111;
26
- --grey-900: #1A1A1A;
27
- --grey-800: #242424;
28
- --grey-700: #3A3A3A;
29
- --grey-400: #888888;
30
- --grey-200: #EBEBEB;
31
- --grey-100: #F5F5F5;
32
- --white: #FFFFFF;
33
- --success: #00C896;
34
- --warning: #FFB700;
35
-
36
- --font-main: 'Sora', sans-serif;
37
- --font-mono: 'DM Mono', monospace;
38
-
39
- --radius-sm: 4px;
40
- --radius-md: 8px;
41
- --radius-lg: 16px;
42
- --radius-xl: 24px;
43
-
44
- --shadow-sm: 0 1px 4px rgba(0,0,0,0.12);
45
- --shadow-md: 0 4px 20px rgba(0,0,0,0.15);
46
- --shadow-lg: 0 8px 40px rgba(0,0,0,0.20);
47
- --shadow-magenta: 0 4px 24px rgba(226,0,116,0.25);
48
  }
49
 
50
- /* ---- RESET & BASE ---- */
51
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
52
 
 
53
  body {
54
- font-family: var(--font-main) !important;
55
- background: #0D0D0D !important;
56
- color: var(--white) !important;
57
- min-height: 100vh;
58
  background-image:
59
- radial-gradient(ellipse 80% 60% at 10% -10%, rgba(226,0,116,0.12) 0%, transparent 60%),
60
- radial-gradient(ellipse 60% 40% at 90% 110%, rgba(226,0,116,0.08) 0%, transparent 50%);
61
  }
62
 
63
- /* ---- MAIN CONTAINER ---- */
64
  .gradio-container {
65
- max-width: 1100px !important;
66
  margin: 0 auto !important;
67
- padding: 0 !important;
68
  background: transparent !important;
69
  box-shadow: none !important;
70
  }
71
 
72
- /* ---- HEADER BAND ---- */
73
- .gr-markdown:first-of-type {
74
- background: linear-gradient(135deg, var(--magenta) 0%, #B5005C 100%) !important;
75
- border-radius: 0 0 var(--radius-xl) var(--radius-xl) !important;
76
- padding: 36px 48px 32px !important;
77
- margin-bottom: 32px !important;
78
- box-shadow: var(--shadow-magenta) !important;
 
79
  position: relative;
80
  overflow: hidden;
81
  }
82
 
83
- .gr-markdown:first-of-type::before {
84
  content: '';
85
  position: absolute;
86
- top: -60px; right: -60px;
87
- width: 220px; height: 220px;
88
  border-radius: 50%;
89
- background: rgba(255,255,255,0.08);
 
90
  }
91
 
92
- .gr-markdown:first-of-type::after {
 
93
  content: 'T';
94
  position: absolute;
95
- right: 40px; bottom: -20px;
96
- font-family: var(--font-main);
97
- font-size: 140px;
98
- font-weight: 800;
99
- color: rgba(255,255,255,0.07);
100
  line-height: 1;
101
- pointer-events: none;
102
- user-select: none;
103
  }
104
 
105
- /* ---- MARKDOWN HEADINGS ---- */
106
- h1 { font-size: 2rem !important; font-weight: 800 !important; letter-spacing: -0.5px !important; }
107
- h2 { font-size: 1.4rem !important; font-weight: 700 !important; }
108
- h3 { font-size: 1.1rem !important; font-weight: 600 !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
 
110
- /* ---- CARD PANELS ---- */
111
  .gr-box, .gr-panel, .gr-group, .gr-form,
112
- [class*="panel"], [class*="block"], .gap {
113
- background: var(--grey-900) !important;
114
- border: 1px solid var(--grey-800) !important;
115
- border-radius: var(--radius-lg) !important;
116
- box-shadow: var(--shadow-sm) !important;
117
  }
118
 
119
- /* ---- LABELS ---- */
120
  label, .gr-label, .label-wrap span {
121
- font-family: var(--font-main) !important;
122
- font-size: 0.72rem !important;
123
- font-weight: 600 !important;
124
- letter-spacing: 0.08em !important;
125
  text-transform: uppercase !important;
126
- color: var(--grey-400) !important;
127
- margin-bottom: 6px !important;
128
  }
129
 
130
- /* ---- TEXTBOXES / INPUTS ---- */
131
  textarea, input[type="text"] {
132
- font-family: var(--font-main) !important;
133
- background: var(--grey-800) !important;
134
- color: var(--white) !important;
135
- border: 1px solid var(--grey-700) !important;
136
- border-radius: var(--radius-md) !important;
137
  padding: 12px 16px !important;
138
- font-size: 0.9rem !important;
139
- transition: border-color 0.2s, box-shadow 0.2s !important;
140
- outline: none !important;
141
  }
142
 
143
  textarea:focus, input[type="text"]:focus {
144
  border-color: var(--magenta) !important;
145
- box-shadow: 0 0 0 3px rgba(226,0,116,0.15) !important;
 
146
  }
147
 
148
  textarea::placeholder, input[type="text"]::placeholder {
149
- color: var(--grey-400) !important;
150
- font-style: italic;
 
 
 
 
 
 
151
  }
152
 
153
- /* ---- BUTTONS ---- */
 
 
 
 
 
 
 
 
 
154
  button, .gr-button {
155
- font-family: var(--font-main) !important;
156
  font-weight: 700 !important;
157
- font-size: 0.85rem !important;
158
- letter-spacing: 0.04em !important;
159
  text-transform: uppercase !important;
160
- padding: 12px 28px !important;
161
- border-radius: var(--radius-md) !important;
162
  border: none !important;
163
  cursor: pointer !important;
164
- transition: all 0.2s ease !important;
165
- position: relative;
166
- overflow: hidden;
167
  }
168
 
169
- button::after {
170
- content: '';
171
- position: absolute;
172
- inset: 0;
173
- background: rgba(255,255,255,0);
174
- transition: background 0.2s;
175
- }
176
-
177
- button:hover::after { background: rgba(255,255,255,0.08) !important; }
178
-
179
- /* Primary */
180
  button.primary, .gr-button-primary, button[variant="primary"] {
181
  background: var(--magenta) !important;
182
- color: var(--white) !important;
183
- box-shadow: var(--shadow-magenta) !important;
184
  }
185
 
186
  button.primary:hover, .gr-button-primary:hover {
@@ -189,220 +205,256 @@ button.primary:hover, .gr-button-primary:hover {
189
  transform: translateY(-1px) !important;
190
  }
191
 
192
- /* Secondary / Ghost */
193
  button.secondary, .gr-button-secondary {
194
  background: transparent !important;
195
  color: var(--magenta) !important;
196
  border: 1.5px solid var(--magenta) !important;
197
  }
198
 
199
- /* ---- CHATBOT ---- */
200
- .gr-chatbot, [data-testid="chatbot"], .chatbot {
201
- background: var(--grey-900) !important;
202
- border: 1px solid var(--grey-800) !important;
203
- border-radius: var(--radius-lg) !important;
204
- box-shadow: var(--shadow-md) !important;
205
- font-family: var(--font-main) !important;
206
  }
207
 
208
- /* User message bubble */
209
- .message.user, [data-testid="user"] .message,
210
- .gr-chatbot .user .message, div[class*="user"] {
211
- background: linear-gradient(135deg, var(--magenta) 0%, #B5005C 100%) !important;
212
- color: var(--white) !important;
213
- border-radius: var(--radius-md) var(--radius-md) var(--radius-sm) var(--radius-md) !important;
214
- padding: 12px 16px !important;
215
- font-size: 0.88rem !important;
216
- line-height: 1.6 !important;
217
- box-shadow: 0 2px 12px rgba(226,0,116,0.25) !important;
218
- max-width: 75% !important;
219
  margin-left: auto !important;
220
  }
221
 
222
- /* Bot / assistant message bubble */
223
- .message.bot, [data-testid="bot"] .message,
224
- .gr-chatbot .bot .message, div[class*="bot"] {
225
- background: var(--grey-800) !important;
226
- color: var(--grey-100) !important;
227
- border-radius: var(--radius-md) var(--radius-md) var(--radius-md) var(--radius-sm) !important;
228
  border-left: 3px solid var(--magenta) !important;
229
- padding: 12px 16px !important;
230
- font-size: 0.88rem !important;
231
- line-height: 1.6 !important;
232
  max-width: 80% !important;
233
  }
234
 
235
- /* Avatar / icons */
236
- .avatar-container img { border-radius: 50% !important; }
237
-
238
- /* ---- STATUS BOX ---- */
239
- .gr-textbox[readonly] textarea,
240
- textarea[readonly] {
241
- background: var(--grey-800) !important;
242
- border-color: var(--grey-700) !important;
243
- color: var(--success) !important;
244
- font-family: var(--font-mono) !important;
245
- font-size: 0.82rem !important;
246
- }
247
-
248
- /* ---- DIVIDERS ---- */
249
- .gr-padded { padding: 20px !important; }
250
  .gap { gap: 16px !important; }
251
 
252
- /* ---- SCROLLBAR ---- */
253
- ::-webkit-scrollbar { width: 6px; height: 6px; }
254
- ::-webkit-scrollbar-track { background: var(--grey-900); border-radius: 3px; }
255
- ::-webkit-scrollbar-thumb { background: var(--grey-700); border-radius: 3px; }
256
  ::-webkit-scrollbar-thumb:hover { background: var(--magenta); }
257
 
258
- /* ---- FOOTER ---- */
259
- footer { display: none !important; }
260
-
261
- /* ---- ANIMATIONS ---- */
262
  @keyframes fadeUp {
263
- from { opacity: 0; transform: translateY(12px); }
264
  to { opacity: 1; transform: translateY(0); }
265
  }
 
266
 
267
- .gr-chatbot .message { animation: fadeUp 0.25s ease both; }
 
268
 
269
- /* ---- RESPONSIVE ---- */
270
- @media (max-width: 768px) {
271
- .gradio-container { padding: 0 8px !important; }
272
- h1 { font-size: 1.5rem !important; }
273
  }
274
  """
275
 
276
- # Globals (shared state in Gradio)
277
  embed_model = SentenceTransformer("all-MiniLM-L6-v2")
278
- index = None
279
  chunks = []
280
-
281
- # Add after globals:
282
- # Session memory
283
-
284
  chat_history = []
285
 
286
- def chat(user_input, history):
287
- global chat_history
288
-
289
- # Build full context (PDF + conversation history)
290
- full_context = "\n".join([
291
- f"User: {h['user']}\nBot: {h['bot']}"
292
- for h in chat_history[-5:]
293
- ]) if chat_history else ""
294
-
295
- answer = generate_answer(user_input, full_context)
296
-
297
- # Store in memory
298
- chat_history.append({
299
- "user": user_input,
300
- "bot": answer
301
- })
302
-
303
- # Update UI history
304
- new_history = history + [
305
- {"role": "user", "content": user_input},
306
- {"role": "assistant", "content": answer}
307
- ]
308
-
309
- return new_history, new_history
310
- def generate_answer(query, conversation_context=""):
311
- if index is None:
312
- return "⚠️ Please load a PDF first."
313
-
314
- rag_context = retrieve(query)
315
- rag_text = "\n\n".join(rag_context)
316
-
317
- # βœ… Combine RAG + Conversation Memory
318
- full_prompt = f"""You are a smart financial AI assistant that remembers conversations.
319
-
320
- Previous conversation:
321
- {conversation_context}
322
-
323
- PDF Context (use ONLY this for facts):
324
- {rag_text}
325
-
326
- Question: {query}
327
-
328
- Respond naturally and helpfully, referencing past discussion when relevant."""
329
-
330
- response = client.chat.completions.create(
331
- model="llama-3.1-8b-instant",
332
- messages=[{"role": "user", "content": full_prompt}],
333
- temperature=0.7,
334
- max_tokens=600
335
- )
336
- return response.choices[0].message.content
337
-
338
- # Groq client with HF Secrets
339
  client = OpenAI(
340
  api_key=os.getenv("GROQ_API_KEY"),
341
  base_url="https://api.groq.com/openai/v1",
342
  )
343
 
344
- def convert_drive_link(link):
 
345
  try:
346
  file_id = link.split("/d/")[1].split("/")[0]
347
  return f"https://drive.google.com/uc?id={file_id}"
348
- except:
349
  return link
350
 
351
- def load_pdf_from_link(link):
 
352
  global index, chunks
353
- url = convert_drive_link(link)
354
- PDF_PATH = "temp.pdf"
355
- response = requests.get(url)
356
- with open(PDF_PATH, "wb") as f:
357
- f.write(response.content)
358
-
359
- reader = PdfReader(PDF_PATH)
360
- texts = [page.extract_text() for page in reader.pages if page.extract_text()]
361
-
362
- # Chunking
363
  chunks = []
364
  for t in texts:
365
  words = t.split()
366
  for i in range(0, len(words), 500):
367
- chunks.append(" ".join(words[i:i+500]))
368
-
369
- # Embeddings + FAISS
370
  embeddings = embed_model.encode(chunks)
371
- dim = embeddings.shape[1]
372
- index = faiss.IndexFlatL2(dim)
373
- index.add(np.array(embeddings).astype('float32'))
374
-
375
- return f"βœ… PDF loaded! {len(chunks)} chunks created."
376
 
377
- def retrieve(query, k=3):
 
 
 
378
  if index is None:
379
  return []
380
- q_emb = embed_model.encode([query])
381
- distances, indices = index.search(np.array(q_emb).astype('float32'), k)
382
- return [chunks[i] for i in indices[0]]
383
-
384
- # ... (keep all previous code until chat function)
385
- # UI (replace entirely):
386
- with gr.Blocks(title="Finance RAG", css=css) as app:
387
- gr.Markdown("# πŸ“Š Dynamic Finance RAG Chatbot")
388
-
389
- with gr.Row():
390
- link_input = gr.Textbox(label="πŸ“Ž Google Drive PDF Link", placeholder="https://drive.google.com/file/d/...")
391
- load_btn = gr.Button("πŸ“₯ Load PDF", variant="primary")
392
-
393
- status = gr.Textbox(label="Status", interactive=False)
394
-
395
- chatbot = gr.Chatbot(height=500)
396
- msg = gr.Textbox(
397
- label="πŸ’¬ Ask about the PDF",
398
- placeholder="What are the key financial metrics?",
399
- container=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  )
401
-
402
- # Events
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
403
  load_btn.click(load_pdf_from_link, inputs=link_input, outputs=status)
404
- msg.submit(chat, inputs=[msg, chatbot], outputs=[chatbot, chatbot])
 
405
  msg.submit(lambda: "", outputs=msg)
406
 
 
 
 
 
407
  if __name__ == "__main__":
408
  app.launch()
 
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 {
 
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()