khushjash commited on
Commit
d535409
·
verified ·
1 Parent(s): 87bb16b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +536 -1
app.py CHANGED
@@ -1,2 +1,537 @@
1
  import os
2
- print("HF_TOKEN present:", bool(os.getenv("HF_TOKEN")))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import io
3
+ import time
4
+ import base64
5
+
6
+ import gradio as gr
7
+ import pandas as pd
8
+ from PIL import Image
9
+ from gradio_client import Client
10
+
11
+ # ------------------------- Constants -------------------------
12
+ TABLEAU_URL = "https://public.tableau.com/views/InsuranceDashboard_17677520784850/ExecutiveOverviewDashbiard?:showVizHome=no&:embed=y&:toolbar=no"
13
+
14
+ BACKEND_SPACE_ID = os.getenv("BACKEND_SPACE_ID", "your-username/insurance-chatbot-backend")
15
+ HF_TOKEN = os.getenv("HF_TOKEN")
16
+
17
+ QUICK_QUESTIONS = [
18
+ "Loss ratio by product_type",
19
+ "Loss ratio by region",
20
+ "Claim frequency by product_type",
21
+ "Average claim severity",
22
+ "Top 10 agents by premium",
23
+ ]
24
+
25
+ # Initialize once
26
+ client = Client(BACKEND_SPACE_ID, hf_token=HF_TOKEN)
27
+
28
+
29
+ # ------------------------- Backend helpers -------------------------
30
+ def decode_chart(chart_b64: str):
31
+ """Decode a base64 PNG/JPEG string returned by the backend into a PIL image."""
32
+ if not chart_b64:
33
+ return None
34
+
35
+ raw = chart_b64
36
+ if "," in raw and raw.strip().startswith("data:image"):
37
+ raw = raw.split(",", 1)[1]
38
+
39
+ try:
40
+ img_bytes = base64.b64decode(raw)
41
+ return Image.open(io.BytesIO(img_bytes))
42
+ except Exception:
43
+ return None
44
+
45
+
46
+ def call_backend(query: str) -> tuple[pd.DataFrame, object, str]:
47
+ """
48
+ Expected backend response format:
49
+ {
50
+ "data": [ { ...row... }, ... ],
51
+ "chart": "<base64-encoded image or data URL>",
52
+ "message": "optional summary"
53
+ }
54
+ """
55
+ response = client.predict(query, api_name="/query")
56
+
57
+ if not isinstance(response, dict):
58
+ raise ValueError("Backend returned an unexpected response format.")
59
+
60
+ df_res = pd.DataFrame(response.get("data", []))
61
+ chart_img = decode_chart(response.get("chart"))
62
+ message = response.get("message", "Results generated.")
63
+
64
+ return df_res, chart_img, message
65
+
66
+
67
+ # ------------------------- UI + Streaming -------------------------
68
+ def _js_autoscroll():
69
+ return """
70
+ <script>
71
+ (function(){
72
+ const t = document.getElementById("chat_thread");
73
+ if (t) t.scrollTop = t.scrollHeight;
74
+ })();
75
+ </script>
76
+ """
77
+
78
+
79
+ def _set_interactive(flag: bool):
80
+ return gr.update(interactive=flag)
81
+
82
+
83
+ def _thinking_steps(query: str):
84
+ base = f"Query: {query}\nThinking"
85
+ dots = ["", ".", "..", "..."]
86
+ for i in range(4):
87
+ yield f"{base}{dots[i]}"
88
+ time.sleep(0.10)
89
+
90
+ steps = [
91
+ "• Calling backend",
92
+ "• Fetching results",
93
+ "• Rendering results",
94
+ ]
95
+
96
+ acc = f"{base}...\n"
97
+ for s in steps:
98
+ acc = acc + s + "\n"
99
+ yield acc.rstrip()
100
+ time.sleep(0.15)
101
+
102
+
103
+ def _to_messages(history_pairs: list[list[str]]):
104
+ msgs = []
105
+ for pair in history_pairs or []:
106
+ if not pair:
107
+ continue
108
+ user = pair[0] if len(pair) > 0 else ""
109
+ assistant = pair[1] if len(pair) > 1 else ""
110
+ if user:
111
+ msgs.append({"role": "user", "content": str(user)})
112
+ if assistant:
113
+ msgs.append({"role": "assistant", "content": str(assistant)})
114
+ return msgs
115
+
116
+
117
+ def run_query(user_text: str, history_pairs: list):
118
+ history_pairs = history_pairs or []
119
+ q = (user_text or "").strip()
120
+
121
+ if not q:
122
+ yield (
123
+ gr.update(value=""),
124
+ history_pairs,
125
+ gr.update(value=_to_messages(history_pairs)),
126
+ gr.update(value=pd.DataFrame(), visible=False),
127
+ gr.update(value=None, visible=False),
128
+ gr.update(selected=0),
129
+ _set_interactive(False),
130
+ *[_set_interactive(True) for _ in range(len(QUICK_QUESTIONS))],
131
+ _js_autoscroll(),
132
+ )
133
+ return
134
+
135
+ disabled_send = _set_interactive(False)
136
+ disabled_chips = [_set_interactive(False) for _ in range(len(QUICK_QUESTIONS))]
137
+
138
+ for frame in _thinking_steps(q):
139
+ yield (
140
+ gr.update(value=frame),
141
+ history_pairs,
142
+ gr.update(value=_to_messages(history_pairs)),
143
+ gr.update(value=pd.DataFrame(), visible=False),
144
+ gr.update(value=None, visible=False),
145
+ gr.update(),
146
+ disabled_send,
147
+ *disabled_chips,
148
+ _js_autoscroll(),
149
+ )
150
+
151
+ try:
152
+ df_res, chart_img, assistant_msg = call_backend(q)
153
+ except Exception as e:
154
+ df_res = pd.DataFrame({"error": [str(e)]})
155
+ chart_img = None
156
+ assistant_msg = f"Backend request failed: {e}"
157
+
158
+ history_pairs = history_pairs + [[q, assistant_msg]]
159
+
160
+ show_df = df_res is not None and not df_res.empty
161
+ show_fig = chart_img is not None
162
+ tab_selected = 1 if show_fig else 0
163
+
164
+ enabled_chips = [_set_interactive(True) for _ in range(len(QUICK_QUESTIONS))]
165
+ send_after = _set_interactive(False)
166
+
167
+ yield (
168
+ gr.update(value=""),
169
+ history_pairs,
170
+ gr.update(value=_to_messages(history_pairs)),
171
+ gr.update(value=(df_res if df_res is not None else pd.DataFrame()), visible=show_df),
172
+ gr.update(value=chart_img, visible=show_fig),
173
+ gr.update(selected=tab_selected),
174
+ send_after,
175
+ *enabled_chips,
176
+ _js_autoscroll(),
177
+ )
178
+
179
+
180
+ def chip_run(chip_text: str, history_pairs: list):
181
+ yield from run_query(chip_text, history_pairs)
182
+
183
+
184
+ def clear_all():
185
+ empty_pairs = []
186
+ return (
187
+ gr.update(value=""),
188
+ empty_pairs,
189
+ gr.update(value=_to_messages(empty_pairs)),
190
+ gr.update(value=pd.DataFrame(), visible=False),
191
+ gr.update(value=None, visible=False),
192
+ gr.update(selected=0),
193
+ gr.update(interactive=False),
194
+ *[gr.update(interactive=True) for _ in range(len(QUICK_QUESTIONS))],
195
+ _js_autoscroll(),
196
+ )
197
+
198
+
199
+ def send_enabled(text: str):
200
+ ok = bool((text or "").strip())
201
+ return gr.update(interactive=ok)
202
+
203
+
204
+ # ------------------------- CSS -------------------------
205
+ CSS = """
206
+ :root{
207
+ --bg: #f6f7fb;
208
+ --card: #ffffff;
209
+ --muted: #64748b;
210
+ --border: rgba(15, 23, 42, 0.10);
211
+ --shadow: 0 10px 30px rgba(15, 23, 42, 0.08);
212
+ --radius: 16px;
213
+ }
214
+
215
+ body{ background: var(--bg) !important; }
216
+
217
+ #app_wrap{
218
+ height: 100vh;
219
+ max-width: 1600px;
220
+ margin: 0 auto;
221
+ padding: 14px;
222
+ box-sizing: border-box;
223
+ }
224
+ .split_row{ height: calc(100vh - 28px); gap: 14px; }
225
+
226
+ #left_tableau{
227
+ height: calc(100vh - 28px);
228
+ border: 1px solid var(--border);
229
+ border-radius: var(--radius);
230
+ overflow: hidden;
231
+ background: var(--card);
232
+ box-shadow: var(--shadow);
233
+ }
234
+ #left_tableau iframe{ width: 100%; height: calc(100vh - 28px); border: 0; }
235
+
236
+ #right_chat{
237
+ height: calc(100vh - 28px);
238
+ border: 1px solid var(--border);
239
+ border-radius: var(--radius);
240
+ background: var(--card);
241
+ box-shadow: var(--shadow);
242
+ display: flex;
243
+ flex-direction: column;
244
+ overflow: visible;
245
+ }
246
+
247
+ .copilot_header{
248
+ padding: 12px 14px;
249
+ border-bottom: 1px solid rgba(15, 23, 42, 0.08);
250
+ background: linear-gradient(180deg, #ffffff, #fbfbfe);
251
+ font-weight: 700;
252
+ }
253
+
254
+ /* ONLY scroll container on right */
255
+ #chat_thread{
256
+ flex: 1 1 auto;
257
+ min-height: 0;
258
+ overflow-y: auto;
259
+ overflow-x: hidden;
260
+ padding: 14px 14px 120px 14px;
261
+ background: var(--bg);
262
+ box-sizing: border-box;
263
+ }
264
+
265
+ /* REMOVE chat message bubbles + toolbar */
266
+ #chat_thread .message,
267
+ #chat_thread .message-wrap,
268
+ #chat_thread .message-row,
269
+ #chat_thread .bubble,
270
+ #chat_thread .toolbar,
271
+ #chat_thread .icon-row {
272
+ display: none !important;
273
+ }
274
+
275
+ /* Collapse leftover Chatbot spacing */
276
+ #chat_thread .chatbot,
277
+ #chat_thread .chatbot > div {
278
+ margin: 0 !important;
279
+ padding: 0 !important;
280
+ height: 0 !important;
281
+ }
282
+
283
+ /* Dock */
284
+ #dock{
285
+ position: sticky;
286
+ bottom: 0;
287
+ z-index: 50;
288
+ padding: 12px 14px 12px 14px;
289
+ background: linear-gradient(180deg, rgba(246,247,251,0.95), rgba(246,247,251,1));
290
+ border-top: 1px solid rgba(15, 23, 42, 0.10);
291
+ }
292
+
293
+ #composer_card{
294
+ display: flex;
295
+ gap: 10px;
296
+ align-items: flex-end;
297
+ padding: 10px 10px 10px 12px;
298
+ border-radius: 18px;
299
+ background: #ffffff;
300
+ border: 1px solid rgba(15, 23, 42, 0.12);
301
+ box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
302
+ }
303
+
304
+ #composer{ flex: 1 1 auto; }
305
+ #composer .wrap{ padding: 0 !important; }
306
+ #composer textarea{
307
+ width: 100% !important;
308
+ border: none !important;
309
+ outline: none !important;
310
+ background: transparent !important;
311
+ font-size: 14px !important;
312
+ line-height: 1.35 !important;
313
+ min-height: 56px !important;
314
+ max-height: 160px !important;
315
+ resize: none !important;
316
+ padding: 6px 6px 6px 2px !important;
317
+ }
318
+
319
+ #composer_actions{
320
+ display: flex;
321
+ gap: 8px;
322
+ align-items: center;
323
+ justify-content: flex-end;
324
+ flex: 0 0 auto;
325
+ }
326
+ #send_btn button{
327
+ height: 38px !important;
328
+ padding: 0 14px !important;
329
+ border-radius: 999px !important;
330
+ border: 1px solid #111827 !important;
331
+ background: #111827 !important;
332
+ color: #fff !important;
333
+ font-weight: 800 !important;
334
+ }
335
+ #clear_btn button{
336
+ height: 38px !important;
337
+ padding: 0 12px !important;
338
+ border-radius: 999px !important;
339
+ border: 1px solid rgba(15, 23, 42, 0.18) !important;
340
+ background: #ffffff !important;
341
+ color: #111827 !important;
342
+ font-weight: 800 !important;
343
+ }
344
+ #clear_btn button:hover{ background: #f8fafc !important; }
345
+
346
+ #quick_chips .chips-row > div,
347
+ #quick_chips .chips-row > .gradio-column,
348
+ #quick_chips .chips-row .gradio-column{
349
+ flex: 0 0 auto !important;
350
+ width: auto !important;
351
+ min-width: 0 !important;
352
+ }
353
+
354
+ #chips{ margin-top: 10px; }
355
+ .chips-wrap{ display: grid; gap: 8px; }
356
+ .chips-title{
357
+ font-size: 12px;
358
+ font-weight: 700;
359
+ color: var(--muted);
360
+ letter-spacing: 0.02em;
361
+ }
362
+ .chips-row{
363
+ display: flex;
364
+ flex-wrap: wrap;
365
+ gap: 8px;
366
+ align-items: center;
367
+ }
368
+ .chip{
369
+ appearance: none;
370
+ border: 1px solid rgba(15, 23, 42, 0.16);
371
+ background: #ffffff;
372
+ border-radius: 999px;
373
+ padding: 7px 11px;
374
+ font-size: 12px;
375
+ font-weight: 700;
376
+ cursor: pointer;
377
+ box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
378
+ }
379
+ .chip:hover{ background: #f8fafc; }
380
+
381
+ .card{
382
+ background: var(--card);
383
+ border: 1px solid var(--border);
384
+ border-radius: var(--radius);
385
+ box-shadow: 0 6px 18px rgba(15, 23, 42, 0.05);
386
+ padding: 12px;
387
+ }
388
+
389
+ #result_chart{
390
+ min-height: 360px;
391
+ width: 100%;
392
+ }
393
+
394
+ #output_tabs{ margin-top: 4px !important; }
395
+ """
396
+
397
+ # ------------------------- App -------------------------
398
+ with gr.Blocks(title="Converse AI", css=CSS) as demo:
399
+ history_state = gr.State([])
400
+
401
+ with gr.Column(elem_id="app_wrap"):
402
+ with gr.Row(elem_classes=["split_row"]):
403
+ with gr.Column(scale=2, min_width=650):
404
+ gr.HTML(
405
+ f"""
406
+ <div id="left_tableau">
407
+ <iframe src="{TABLEAU_URL}" allowfullscreen></iframe>
408
+ </div>
409
+ """
410
+ )
411
+
412
+ with gr.Column(scale=1, min_width=420):
413
+ with gr.Group(elem_id="right_chat"):
414
+ with gr.Column(elem_id="chat_thread"):
415
+ chat_ui = gr.Chatbot(
416
+ value=[],
417
+ show_label=False,
418
+ label=None,
419
+ height=None,
420
+ )
421
+
422
+ with gr.Tabs(elem_id="output_tabs") as output_tabs:
423
+ with gr.Tab("Results", id=0):
424
+ result_df = gr.Dataframe(
425
+ value=pd.DataFrame(),
426
+ show_label=False,
427
+ interactive=False,
428
+ visible=False,
429
+ wrap=True,
430
+ elem_classes=["card"],
431
+ )
432
+ with gr.Tab("Chart", id=1):
433
+ result_chart = gr.Image(
434
+ value=None,
435
+ show_label=False,
436
+ visible=False,
437
+ elem_id="result_chart",
438
+ elem_classes=["card"],
439
+ )
440
+
441
+ js = gr.HTML(value=_js_autoscroll())
442
+
443
+ with gr.Column(elem_id="dock"):
444
+ with gr.Row(elem_id="composer_card", equal_height=False):
445
+ composer = gr.Textbox(
446
+ placeholder="Ask a question…",
447
+ show_label=False,
448
+ lines=2,
449
+ max_lines=8,
450
+ elem_id="composer",
451
+ container=False,
452
+ )
453
+ with gr.Row(elem_id="composer_actions", equal_height=False):
454
+ send_btn = gr.Button("Send", elem_id="send_btn", interactive=False)
455
+ clear_btn = gr.Button("Clear", elem_id="clear_btn", variant="secondary")
456
+
457
+ with gr.Column(elem_id="quick_chips"):
458
+ gr.HTML(
459
+ """
460
+ <div class="chips-wrap">
461
+ <div class="chips-title">Quick questions</div>
462
+ </div>
463
+ """
464
+ )
465
+ with gr.Row(elem_classes=["chips-row"]):
466
+ chip_buttons = []
467
+ for q in QUICK_QUESTIONS:
468
+ chip_buttons.append(gr.Button(q, variant="secondary", elem_classes=["chip"]))
469
+
470
+ composer.change(send_enabled, inputs=[composer], outputs=[send_btn])
471
+
472
+ send_btn.click(
473
+ run_query,
474
+ inputs=[composer, history_state],
475
+ outputs=[
476
+ composer,
477
+ history_state,
478
+ chat_ui,
479
+ result_df,
480
+ result_chart,
481
+ output_tabs,
482
+ send_btn,
483
+ *chip_buttons,
484
+ js,
485
+ ],
486
+ )
487
+ composer.submit(
488
+ run_query,
489
+ inputs=[composer, history_state],
490
+ outputs=[
491
+ composer,
492
+ history_state,
493
+ chat_ui,
494
+ result_df,
495
+ result_chart,
496
+ output_tabs,
497
+ send_btn,
498
+ *chip_buttons,
499
+ js,
500
+ ],
501
+ )
502
+
503
+ for btn, q in zip(chip_buttons, QUICK_QUESTIONS):
504
+ btn.click(
505
+ chip_run,
506
+ inputs=[gr.State(q), history_state],
507
+ outputs=[
508
+ composer,
509
+ history_state,
510
+ chat_ui,
511
+ result_df,
512
+ result_chart,
513
+ output_tabs,
514
+ send_btn,
515
+ *chip_buttons,
516
+ js,
517
+ ],
518
+ )
519
+
520
+ clear_btn.click(
521
+ clear_all,
522
+ outputs=[
523
+ composer,
524
+ history_state,
525
+ chat_ui,
526
+ result_df,
527
+ result_chart,
528
+ output_tabs,
529
+ send_btn,
530
+ *chip_buttons,
531
+ js,
532
+ ],
533
+ )
534
+
535
+ if __name__ == "__main__":
536
+ demo.queue().launch(ssr_mode=False)
537
+