XRachel commited on
Commit
0478933
·
verified ·
1 Parent(s): e8841ba

Upload 4 files

Browse files
Files changed (3) hide show
  1. app.py +234 -51
  2. requirements.txt +1 -0
  3. style.css +13 -38
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import os
 
2
  import time
3
  import traceback
4
  import subprocess
@@ -8,6 +9,8 @@ import gradio as gr
8
  import pandas as pd
9
  import joblib
10
  import papermill as pm
 
 
11
  from jupyter_client.kernelspec import KernelSpecManager
12
 
13
  BASE_DIR = Path(__file__).resolve().parent
@@ -22,11 +25,20 @@ PIPELINE_CANDIDATES = [
22
  ]
23
 
24
  RUNS_DIR = BASE_DIR / "runs"
 
 
 
 
 
 
25
  PAPERMILL_TIMEOUT = int(os.environ.get("PAPERMILL_TIMEOUT", "1800"))
 
 
26
 
27
 
28
  def ensure_dirs():
29
- RUNS_DIR.mkdir(parents=True, exist_ok=True)
 
30
 
31
 
32
  def stamp():
@@ -61,7 +73,9 @@ def run_notebook(nb_path: Path, label: str, kernel_name: str | None) -> str:
61
  if not nb_path.exists():
62
  return f"❌ {label} not found: {nb_path.name}"
63
  if not kernel_name:
64
- return f" {label} kernel is not available."
 
 
65
  try:
66
  out_path = RUNS_DIR / f"run_{stamp()}_{nb_path.name}"
67
  pm.execute_notebook(
@@ -76,7 +90,7 @@ def run_notebook(nb_path: Path, label: str, kernel_name: str | None) -> str:
76
  )
77
  return f"✅ {label} finished successfully.\nSaved run: {out_path.name}"
78
  except Exception as e:
79
- return f"❌ {label} failed.\n{str(e)}\n\n{traceback.format_exc()[-2500:]}"
80
 
81
 
82
  def run_python():
@@ -151,92 +165,250 @@ def load_data():
151
  })
152
 
153
 
154
- def dashboard_data():
155
- df = load_data()
 
 
 
 
156
 
157
- lines = [f"### Dataset Summary", f"- Total Customers: **{len(df)}**"]
158
- if "Exited" in df.columns:
159
- churn_rate = round(df["Exited"].mean() * 100, 2)
160
- lines.append(f"- Churn Rate: **{churn_rate}%**")
161
- lines.append(f"- Churned Customers: **{int(df['Exited'].sum())}**")
162
- if "Balance" in df.columns:
163
- lines.append(f"- Average Balance: **{round(df['Balance'].mean(), 2)}**")
164
- summary = "\n".join(lines)
165
 
166
- geo_df = pd.DataFrame({"Geography": [], "Exited": []})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  if {"Geography", "Exited"}.issubset(df.columns):
168
  geo_df = df.groupby("Geography", as_index=False)["Exited"].mean()
169
  geo_df["Exited"] = (geo_df["Exited"] * 100).round(2)
 
 
170
 
171
- age_df = pd.DataFrame({"AgeBand": [], "churn_rate": []})
172
  if {"Age", "Exited"}.issubset(df.columns):
173
- tmp = df.copy()
174
- tmp["AgeBand"] = pd.cut(
175
- tmp["Age"],
176
  bins=[18, 30, 40, 50, 60, 70],
177
  include_lowest=True
178
  )
179
- age_df = tmp.groupby("AgeBand").agg(churn_rate=("Exited", "mean")).reset_index()
180
  age_df["AgeBand"] = age_df["AgeBand"].astype(str)
181
  age_df["churn_rate"] = (age_df["churn_rate"] * 100).round(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- kernel_info = ", ".join(sorted(available_kernels().keys())) or "none"
184
- summary += f"\n- Available Kernels: **{kernel_info}**"
185
 
186
- return summary, geo_df, age_df, df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
 
189
  def build_ui():
190
  css_path = BASE_DIR / "style.css"
191
  css = css_path.read_text(encoding="utf-8") if css_path.exists() else ""
192
 
193
- with gr.Blocks(title="Bank Churn Dashboard") as demo:
194
  gr.HTML(f"<style>{css}</style>")
195
 
196
  gr.Markdown(
197
- "# 🏦 Bank Churn Dashboard\n"
198
- "*Run the Python and R analyses first, then review the dashboard and predictions.*",
199
  elem_id="escp_title",
200
  )
201
 
202
- with gr.Tab("Run Analyses"):
 
203
  with gr.Row():
204
  btn_py = gr.Button("Run Python", variant="secondary")
205
  btn_r = gr.Button("Run R", variant="secondary")
206
  btn_all = gr.Button("Run All", variant="primary")
 
 
 
 
 
 
 
 
 
207
 
208
- py_log = gr.Textbox(label="Python Log", lines=12, max_lines=18, interactive=False)
209
- r_log = gr.Textbox(label="R Log", lines=12, max_lines=18, interactive=False)
210
- all_log = gr.Textbox(label="Run All Log", lines=18, max_lines=28, interactive=False)
211
-
212
- btn_py.click(run_python, outputs=[py_log])
213
- btn_r.click(run_r, outputs=[r_log])
214
- btn_all.click(run_all, outputs=[all_log])
215
-
216
- with gr.Tab("Dashboard"):
217
  refresh_btn = gr.Button("Refresh Dashboard", variant="primary")
218
  summary_md = gr.Markdown()
 
 
 
 
 
 
 
 
 
 
 
 
 
219
  with gr.Row():
220
- geo_plot = gr.BarPlot(
221
- x="Geography",
222
- y="Exited",
223
- title="Churn by Geography (%)",
224
- vertical=False
225
- )
226
- age_plot = gr.LinePlot(
227
- x="AgeBand",
228
- y="churn_rate",
229
- title="Churn by Age Band (%)"
230
- )
231
- data_table = gr.Dataframe(label="Customer Data", interactive=True)
232
 
233
  refresh_btn.click(
234
- dashboard_data,
235
- outputs=[summary_md, geo_plot, age_plot, data_table]
236
  )
237
  demo.load(
238
- dashboard_data,
239
- outputs=[summary_md, geo_plot, age_plot, data_table]
240
  )
241
 
242
  with gr.Tab("Prediction"):
@@ -247,10 +419,21 @@ def build_ui():
247
  pred_out = gr.Textbox(label="Prediction Result")
248
  pred_btn.click(predict, inputs=[age, balance], outputs=[pred_out])
249
 
 
 
 
 
 
 
 
 
 
 
250
  return demo
251
 
252
 
253
  if __name__ == "__main__":
 
254
  demo = build_ui()
255
  demo.queue()
256
  port = int(os.environ.get("PORT", 7860))
 
1
  import os
2
+ import json
3
  import time
4
  import traceback
5
  import subprocess
 
9
  import pandas as pd
10
  import joblib
11
  import papermill as pm
12
+ import plotly.express as px
13
+ from huggingface_hub import InferenceClient
14
  from jupyter_client.kernelspec import KernelSpecManager
15
 
16
  BASE_DIR = Path(__file__).resolve().parent
 
25
  ]
26
 
27
  RUNS_DIR = BASE_DIR / "runs"
28
+ ART_DIR = BASE_DIR / "artifacts"
29
+ PY_FIG_DIR = ART_DIR / "py" / "figures"
30
+ PY_TAB_DIR = ART_DIR / "py" / "tables"
31
+ R_FIG_DIR = ART_DIR / "r" / "figures"
32
+ R_TAB_DIR = ART_DIR / "r" / "tables"
33
+
34
  PAPERMILL_TIMEOUT = int(os.environ.get("PAPERMILL_TIMEOUT", "1800"))
35
+ HF_API_KEY = os.environ.get("HF_API_KEY", "").strip()
36
+ MODEL_NAME = os.environ.get("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct").strip()
37
 
38
 
39
  def ensure_dirs():
40
+ for p in [RUNS_DIR, PY_FIG_DIR, PY_TAB_DIR, R_FIG_DIR, R_TAB_DIR]:
41
+ p.mkdir(parents=True, exist_ok=True)
42
 
43
 
44
  def stamp():
 
73
  if not nb_path.exists():
74
  return f"❌ {label} not found: {nb_path.name}"
75
  if not kernel_name:
76
+ kernels = ", ".join(sorted(available_kernels().keys())) or "none"
77
+ return f"❌ {label} kernel is not available.\nAvailable kernels: {kernels}"
78
+
79
  try:
80
  out_path = RUNS_DIR / f"run_{stamp()}_{nb_path.name}"
81
  pm.execute_notebook(
 
90
  )
91
  return f"✅ {label} finished successfully.\nSaved run: {out_path.name}"
92
  except Exception as e:
93
+ return f"❌ {label} failed.\n{str(e)}\n\n{traceback.format_exc()[-3000:]}"
94
 
95
 
96
  def run_python():
 
165
  })
166
 
167
 
168
+ def _read_json(path: Path):
169
+ with open(path, "r", encoding="utf-8") as f:
170
+ obj = json.load(f)
171
+ if isinstance(obj, dict):
172
+ return pd.DataFrame([obj])
173
+ return pd.DataFrame(obj)
174
 
 
 
 
 
 
 
 
 
175
 
176
+ def load_latest_table(table_dir: Path):
177
+ if not table_dir.exists():
178
+ return None, None
179
+ files = sorted(
180
+ [p for p in table_dir.iterdir() if p.suffix.lower() in [".csv", ".json"]],
181
+ key=lambda p: p.stat().st_mtime,
182
+ reverse=True,
183
+ )
184
+ if not files:
185
+ return None, None
186
+
187
+ path = files[0]
188
+ try:
189
+ if path.suffix.lower() == ".csv":
190
+ df = pd.read_csv(path)
191
+ else:
192
+ df = _read_json(path)
193
+ return path.name, df
194
+ except Exception as e:
195
+ return path.name, pd.DataFrame([{"error": str(e)}])
196
+
197
+
198
+ def build_interactive_plot(df: pd.DataFrame, title: str):
199
+ if df is None or df.empty:
200
+ return px.scatter(title=f"{title}: no data")
201
+
202
+ numeric_cols = [c for c in df.columns if pd.api.types.is_numeric_dtype(df[c])]
203
+ cat_cols = [c for c in df.columns if c not in numeric_cols]
204
+
205
+ if cat_cols and numeric_cols:
206
+ x = cat_cols[0]
207
+ y = numeric_cols[0]
208
+ chart_df = df[[x, y]].dropna().copy().head(100)
209
+ if chart_df.empty:
210
+ return px.scatter(title=f"{title}: no usable rows")
211
+ if chart_df[x].nunique() <= 20:
212
+ fig = px.bar(chart_df, x=x, y=y, title=title)
213
+ else:
214
+ fig = px.line(chart_df, x=x, y=y, title=title, markers=True)
215
+ fig.update_layout(height=380)
216
+ return fig
217
+
218
+ if len(numeric_cols) >= 2:
219
+ chart_df = df[numeric_cols[:2]].dropna().copy().head(300)
220
+ fig = px.scatter(
221
+ chart_df,
222
+ x=numeric_cols[0],
223
+ y=numeric_cols[1],
224
+ title=title
225
+ )
226
+ fig.update_layout(height=380)
227
+ return fig
228
+
229
+ if len(numeric_cols) == 1:
230
+ fig = px.histogram(df, x=numeric_cols[0], title=title)
231
+ fig.update_layout(height=380)
232
+ return fig
233
+
234
+ return px.scatter(title=f"{title}: unsupported table structure")
235
+
236
+
237
+ def build_overview_charts(df: pd.DataFrame):
238
+ geo_fig = px.scatter(title="Churn by Geography (%)")
239
+ age_fig = px.scatter(title="Churn by Age Band (%)")
240
+
241
  if {"Geography", "Exited"}.issubset(df.columns):
242
  geo_df = df.groupby("Geography", as_index=False)["Exited"].mean()
243
  geo_df["Exited"] = (geo_df["Exited"] * 100).round(2)
244
+ geo_fig = px.bar(geo_df, x="Geography", y="Exited", title="Churn by Geography (%)")
245
+ geo_fig.update_layout(height=380)
246
 
 
247
  if {"Age", "Exited"}.issubset(df.columns):
248
+ temp = df.copy()
249
+ temp["AgeBand"] = pd.cut(
250
+ temp["Age"],
251
  bins=[18, 30, 40, 50, 60, 70],
252
  include_lowest=True
253
  )
254
+ age_df = temp.groupby("AgeBand").agg(churn_rate=("Exited", "mean")).reset_index()
255
  age_df["AgeBand"] = age_df["AgeBand"].astype(str)
256
  age_df["churn_rate"] = (age_df["churn_rate"] * 100).round(2)
257
+ age_fig = px.line(age_df, x="AgeBand", y="churn_rate", title="Churn by Age Band (%)", markers=True)
258
+ age_fig.update_layout(height=380)
259
+
260
+ return geo_fig, age_fig
261
+
262
+
263
+ def build_dashboard():
264
+ df = load_data()
265
+
266
+ summary_lines = [
267
+ "### Executive Summary",
268
+ f"- Total Customers: **{len(df)}**",
269
+ ]
270
+ if "Exited" in df.columns:
271
+ summary_lines.append(f"- Churn Rate: **{round(df['Exited'].mean() * 100, 2)}%**")
272
+ summary_lines.append(f"- Churned Customers: **{int(df['Exited'].sum())}**")
273
+ if "Balance" in df.columns:
274
+ summary_lines.append(f"- Average Balance: **{round(df['Balance'].mean(), 2)}**")
275
+
276
+ kernels = ", ".join(sorted(available_kernels().keys())) or "none"
277
+ summary_lines.append(f"- Available Kernels: **{kernels}**")
278
+ summary_md = "\n".join(summary_lines)
279
+
280
+ geo_fig, age_fig = build_overview_charts(df)
281
 
282
+ py_name, py_df = load_latest_table(PY_TAB_DIR)
283
+ r_name, r_df = load_latest_table(R_TAB_DIR)
284
 
285
+ py_status = f"### Python Analysis Output\nLatest table: **{py_name or 'none found'}**"
286
+ r_status = f"### R Analysis Output\nLatest table: **{r_name or 'none found'}**"
287
+
288
+ py_plot = build_interactive_plot(py_df, "Python Analysis Chart")
289
+ r_plot = build_interactive_plot(r_df, "R Analysis Chart")
290
+
291
+ if py_df is None:
292
+ py_df = pd.DataFrame([{"info": "No Python table found in artifacts/py/tables"}])
293
+ if r_df is None:
294
+ r_df = pd.DataFrame([{"info": "No R table found in artifacts/r/tables"}])
295
+
296
+ return summary_md, geo_fig, age_fig, py_status, py_plot, py_df, r_status, r_plot, r_df
297
+
298
+
299
+ def generate_ai_insight(question: str):
300
+ if not HF_API_KEY:
301
+ return "HF_API_KEY is not configured in Space Secrets."
302
+
303
+ df = load_data()
304
+ summary = {
305
+ "rows": int(len(df)),
306
+ "churn_rate": round(float(df["Exited"].mean() * 100), 2) if "Exited" in df.columns else None,
307
+ "avg_balance": round(float(df["Balance"].mean()), 2) if "Balance" in df.columns else None,
308
+ }
309
+ py_name, py_df = load_latest_table(PY_TAB_DIR)
310
+ r_name, r_df = load_latest_table(R_TAB_DIR)
311
+
312
+ prompt = f"""
313
+ You are a bank churn strategy assistant.
314
+ Use the dataset summary and analysis outputs to answer in concise business language.
315
+
316
+ Question: {question}
317
+
318
+ Dataset summary:
319
+ {json.dumps(summary, ensure_ascii=False)}
320
+
321
+ Python latest table:
322
+ {py_name}
323
+ {py_df.head(8).to_csv(index=False) if py_df is not None else 'No Python analysis table available.'}
324
+
325
+ R latest table:
326
+ {r_name}
327
+ {r_df.head(8).to_csv(index=False) if r_df is not None else 'No R analysis table available.'}
328
+
329
+ Return:
330
+ 1. Key finding
331
+ 2. Customer retention action
332
+ 3. One risk or caveat
333
+ """
334
+ try:
335
+ client = InferenceClient(api_key=HF_API_KEY)
336
+ try:
337
+ response = client.chat.completions.create(
338
+ model=MODEL_NAME,
339
+ messages=[
340
+ {"role": "system", "content": "You are a precise analytics assistant."},
341
+ {"role": "user", "content": prompt},
342
+ ],
343
+ max_tokens=350,
344
+ )
345
+ return response.choices[0].message.content.strip()
346
+ except Exception:
347
+ return client.text_generation(
348
+ prompt,
349
+ model=MODEL_NAME,
350
+ max_new_tokens=350,
351
+ )
352
+ except Exception as e:
353
+ return f"AI request failed: {str(e)}"
354
 
355
 
356
  def build_ui():
357
  css_path = BASE_DIR / "style.css"
358
  css = css_path.read_text(encoding="utf-8") if css_path.exists() else ""
359
 
360
+ with gr.Blocks(title="Bank Churn Intelligence Hub") as demo:
361
  gr.HTML(f"<style>{css}</style>")
362
 
363
  gr.Markdown(
364
+ "# 🏦 Bank Churn Intelligence Hub\n"
365
+ "*Run Python and R analyses, refresh the dashboard, and ask AI for retention ideas.*",
366
  elem_id="escp_title",
367
  )
368
 
369
+ with gr.Tab("Analysis Runner"):
370
+ gr.Markdown("### Execute the analysis workflow")
371
  with gr.Row():
372
  btn_py = gr.Button("Run Python", variant="secondary")
373
  btn_r = gr.Button("Run R", variant="secondary")
374
  btn_all = gr.Button("Run All", variant="primary")
375
+ exec_log = gr.Textbox(
376
+ label="Execution Log",
377
+ lines=18,
378
+ max_lines=28,
379
+ interactive=False,
380
+ )
381
+ btn_py.click(run_python, outputs=[exec_log])
382
+ btn_r.click(run_r, outputs=[exec_log])
383
+ btn_all.click(run_all, outputs=[exec_log])
384
 
385
+ with gr.Tab("Interactive Dashboard"):
 
 
 
 
 
 
 
 
386
  refresh_btn = gr.Button("Refresh Dashboard", variant="primary")
387
  summary_md = gr.Markdown()
388
+
389
+ with gr.Row():
390
+ geo_plot = gr.Plot(label="Churn by Geography")
391
+ age_plot = gr.Plot(label="Churn by Age Band")
392
+
393
+ with gr.Row():
394
+ py_status = gr.Markdown()
395
+ r_status = gr.Markdown()
396
+
397
+ with gr.Row():
398
+ py_plot = gr.Plot(label="Python Analysis Plot")
399
+ r_plot = gr.Plot(label="R Analysis Plot")
400
+
401
  with gr.Row():
402
+ py_table = gr.Dataframe(label="Python Analysis Table", interactive=True)
403
+ r_table = gr.Dataframe(label="R Analysis Table", interactive=True)
 
 
 
 
 
 
 
 
 
 
404
 
405
  refresh_btn.click(
406
+ build_dashboard,
407
+ outputs=[summary_md, geo_plot, age_plot, py_status, py_plot, py_table, r_status, r_plot, r_table],
408
  )
409
  demo.load(
410
+ build_dashboard,
411
+ outputs=[summary_md, geo_plot, age_plot, py_status, py_plot, py_table, r_status, r_plot, r_table],
412
  )
413
 
414
  with gr.Tab("Prediction"):
 
419
  pred_out = gr.Textbox(label="Prediction Result")
420
  pred_btn.click(predict, inputs=[age, balance], outputs=[pred_out])
421
 
422
+ with gr.Tab("AI Insight"):
423
+ gr.Markdown("### Ask AI to interpret the Python and R analysis outputs")
424
+ ai_q = gr.Textbox(
425
+ label="Question",
426
+ placeholder="What does the latest Python and R analysis suggest about churn risk?"
427
+ )
428
+ ai_btn = gr.Button("Generate AI Insight", variant="primary")
429
+ ai_out = gr.Textbox(label="AI Response", lines=12)
430
+ ai_btn.click(generate_ai_insight, inputs=[ai_q], outputs=[ai_out])
431
+
432
  return demo
433
 
434
 
435
  if __name__ == "__main__":
436
+ ensure_dirs()
437
  demo = build_ui()
438
  demo.queue()
439
  port = int(os.environ.get("PORT", 7860))
requirements.txt CHANGED
@@ -11,6 +11,7 @@ papermill>=2.5.0
11
  nbformat>=5.9.0
12
  ipykernel>=6.29.0
13
  jupyter_client>=8.6.0
 
14
  pillow>=10.0.0
15
  requests>=2.31.0
16
  beautifulsoup4>=4.12.0
 
11
  nbformat>=5.9.0
12
  ipykernel>=6.29.0
13
  jupyter_client>=8.6.0
14
+ plotly>=5.24.0
15
  pillow>=10.0.0
16
  requests>=2.31.0
17
  beautifulsoup4>=4.12.0
style.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Background restored to first-app style logic */
2
  gradio-app,
3
  .gradio-app,
4
  .main,
@@ -7,12 +7,9 @@ gradio-app,
7
  background-color: rgb(15, 23, 42) !important;
8
  background-image:
9
  url("https://huggingface.co/spaces/XRachel/bc4/resolve/main/assets/BankChurn.png") !important;
10
- background-position:
11
- top center !important;
12
- background-repeat:
13
- no-repeat !important;
14
- background-size:
15
- 100% auto !important;
16
  min-height: 100vh !important;
17
  }
18
 
@@ -23,17 +20,15 @@ html, body {
23
  min-height: 100vh !important;
24
  }
25
 
26
- /* Top spacing like the first app */
27
  .gradio-container {
28
  max-width: 1400px !important;
29
  width: 94vw !important;
30
  margin: 0 auto !important;
31
  padding-top: 220px !important;
32
- padding-bottom: 150px !important;
33
  background: transparent !important;
34
  }
35
 
36
- /* Title */
37
  #escp_title h1 {
38
  color: rgb(242, 198, 55) !important;
39
  font-size: 3rem !important;
@@ -44,15 +39,13 @@ html, body {
44
 
45
  #escp_title p,
46
  #escp_title em {
47
- color: rgba(255, 255, 255, 0.90) !important;
48
  text-align: center !important;
49
  }
50
 
51
- /* Tabs */
52
  .tabs > .tab-nav,
53
  .tab-nav,
54
- div[role="tablist"],
55
- .svelte-tabs > .tab-nav {
56
  background: rgba(15, 23, 42, 0.60) !important;
57
  border-radius: 10px 10px 0 0 !important;
58
  padding: 4px !important;
@@ -61,10 +54,7 @@ div[role="tablist"],
61
  .tabs > .tab-nav button,
62
  .tab-nav button,
63
  div[role="tablist"] button,
64
- button[role="tab"],
65
- .svelte-tabs button,
66
- .tab-nav > button,
67
- .tabs button {
68
  color: #ffffff !important;
69
  font-weight: 600 !important;
70
  border: none !important;
@@ -77,45 +67,30 @@ button[role="tab"],
77
  .tabs > .tab-nav button.selected,
78
  .tab-nav button.selected,
79
  button[role="tab"][aria-selected="true"],
80
- button[role="tab"].selected,
81
- div[role="tablist"] button[aria-selected="true"],
82
- .svelte-tabs button.selected {
83
  color: rgb(242, 198, 55) !important;
84
  background: rgba(255, 255, 255, 0.12) !important;
85
  }
86
 
87
- .tabs > .tab-nav button:not(.selected),
88
- .tab-nav button:not(.selected),
89
- button[role="tab"][aria-selected="false"],
90
- button[role="tab"]:not(.selected),
91
- div[role="tablist"] button:not([aria-selected="true"]) {
92
- color: #ffffff !important;
93
- opacity: 1 !important;
94
- }
95
-
96
- /* White cards */
97
  .gradio-container .gr-block,
98
  .gradio-container .gr-box,
99
  .gradio-container .gr-panel,
100
- .gradio-container .gr-group {
101
- background: #ffffff !important;
102
- border-radius: 10px !important;
 
103
  }
104
 
105
  .tabitem {
106
- background: rgba(255, 255, 255, 0.96) !important;
107
- border-radius: 0 0 10px 10px !important;
108
  padding: 16px !important;
109
  }
110
 
111
- /* Inputs */
112
  .gradio-container input,
113
  .gradio-container textarea,
114
  .gradio-container select {
115
  border-radius: 10px !important;
116
  }
117
 
118
- /* Buttons */
119
  button:not([role="tab"]) {
120
  border-radius: 10px !important;
121
  font-weight: 700 !important;
 
1
+ /* Background restored close to the first app */
2
  gradio-app,
3
  .gradio-app,
4
  .main,
 
7
  background-color: rgb(15, 23, 42) !important;
8
  background-image:
9
  url("https://huggingface.co/spaces/XRachel/bc4/resolve/main/assets/BankChurn.png") !important;
10
+ background-position: top center !important;
11
+ background-repeat: no-repeat !important;
12
+ background-size: 100% auto !important;
 
 
 
13
  min-height: 100vh !important;
14
  }
15
 
 
20
  min-height: 100vh !important;
21
  }
22
 
 
23
  .gradio-container {
24
  max-width: 1400px !important;
25
  width: 94vw !important;
26
  margin: 0 auto !important;
27
  padding-top: 220px !important;
28
+ padding-bottom: 140px !important;
29
  background: transparent !important;
30
  }
31
 
 
32
  #escp_title h1 {
33
  color: rgb(242, 198, 55) !important;
34
  font-size: 3rem !important;
 
39
 
40
  #escp_title p,
41
  #escp_title em {
42
+ color: rgba(255, 255, 255, 0.9) !important;
43
  text-align: center !important;
44
  }
45
 
 
46
  .tabs > .tab-nav,
47
  .tab-nav,
48
+ div[role="tablist"] {
 
49
  background: rgba(15, 23, 42, 0.60) !important;
50
  border-radius: 10px 10px 0 0 !important;
51
  padding: 4px !important;
 
54
  .tabs > .tab-nav button,
55
  .tab-nav button,
56
  div[role="tablist"] button,
57
+ button[role="tab"] {
 
 
 
58
  color: #ffffff !important;
59
  font-weight: 600 !important;
60
  border: none !important;
 
67
  .tabs > .tab-nav button.selected,
68
  .tab-nav button.selected,
69
  button[role="tab"][aria-selected="true"],
70
+ div[role="tablist"] button[aria-selected="true"] {
 
 
71
  color: rgb(242, 198, 55) !important;
72
  background: rgba(255, 255, 255, 0.12) !important;
73
  }
74
 
 
 
 
 
 
 
 
 
 
 
75
  .gradio-container .gr-block,
76
  .gradio-container .gr-box,
77
  .gradio-container .gr-panel,
78
+ .gradio-container .gr-group,
79
+ .tabitem {
80
+ background: rgba(255, 255, 255, 0.97) !important;
81
+ border-radius: 12px !important;
82
  }
83
 
84
  .tabitem {
 
 
85
  padding: 16px !important;
86
  }
87
 
 
88
  .gradio-container input,
89
  .gradio-container textarea,
90
  .gradio-container select {
91
  border-radius: 10px !important;
92
  }
93
 
 
94
  button:not([role="tab"]) {
95
  border-radius: 10px !important;
96
  font-weight: 700 !important;