atascioglu commited on
Commit
7e8ec7f
Β·
verified Β·
1 Parent(s): 9f1d68a

Upload 10 files

Browse files
Files changed (4) hide show
  1. Dockerfile +2 -10
  2. README.md +3 -2
  3. app.py +45 -102
  4. style.css +36 -80
Dockerfile CHANGED
@@ -16,19 +16,11 @@ RUN pip install --no-cache-dir -r requirements.txt
16
  # Notebook execution deps
17
  RUN pip install --no-cache-dir notebook ipykernel papermill
18
 
19
- # Pre-install packages that the notebooks install via !pip install
20
- # so papermill doesn't waste time or fail on them at runtime:
21
- # datacreation.ipynb: beautifulsoup4 pandas matplotlib seaborn numpy textblob
22
- # pythonanalysis.ipynb: pandas matplotlib seaborn numpy textblob faker transformers vaderSentiment
23
- # Most are already in requirements.txt; add the extras:
24
- RUN pip install --no-cache-dir textblob faker transformers
25
 
26
  RUN python -m ipykernel install --user --name python3 --display-name "Python 3"
27
 
28
- # R deps for notebook execution via papermill (IRkernel)
29
- RUN R -e "install.packages('IRkernel', repos='https://cloud.r-project.org/')"
30
- RUN R -e "IRkernel::installspec(user = FALSE)"
31
-
32
  EXPOSE 7860
33
 
34
  CMD ["python", "app.py"]
 
16
  # Notebook execution deps
17
  RUN pip install --no-cache-dir notebook ipykernel papermill
18
 
19
+ # Pre-install packages the notebooks use via !pip install
20
+ RUN pip install --no-cache-dir textblob faker vaderSentiment
 
 
 
 
21
 
22
  RUN python -m ipykernel install --user --name python3 --display-name "Python 3"
23
 
 
 
 
 
24
  EXPOSE 7860
25
 
26
  CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- title: RX12WorkshopApp
3
  emoji: πŸ“Š
4
  colorFrom: blue
5
- colorTo: red
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: AIBDM 2026 Workshop App
3
  emoji: πŸ“Š
4
  colorFrom: blue
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
+ short_description: AI-enhanced analytics dashboard for ESCP AIBDM course
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py CHANGED
@@ -4,7 +4,7 @@ import json
4
  import time
5
  import traceback
6
  from pathlib import Path
7
- from typing import Dict, Any, List, Optional, Tuple
8
 
9
  import pandas as pd
10
  import gradio as gr
@@ -50,7 +50,7 @@ llm_client = (
50
  # =========================================================
51
 
52
  def ensure_dirs():
53
- for p in [RUNS_DIR, ART_DIR, PY_FIG_DIR, PY_TAB_DIR, R_FIG_DIR, R_TAB_DIR]:
54
  p.mkdir(parents=True, exist_ok=True)
55
 
56
  def stamp():
@@ -76,7 +76,7 @@ def artifacts_index() -> Dict[str, Any]:
76
  "python": {
77
  "figures": _ls(PY_FIG_DIR, (".png", ".jpg", ".jpeg")),
78
  "tables": _ls(PY_TAB_DIR, (".csv", ".json")),
79
- }
80
  }
81
 
82
  # =========================================================
@@ -128,16 +128,14 @@ def run_pythonanalysis() -> str:
128
  def run_full_pipeline() -> str:
129
  logs = []
130
  logs.append("=" * 50)
131
- logs.append("STEP 1/3: Data Creation (web scraping + synthetic data)")
132
  logs.append("=" * 50)
133
  logs.append(run_datacreation())
134
  logs.append("")
135
  logs.append("=" * 50)
136
- logs.append("STEP 2/3: Python Analysis (sentiment, ARIMA, dashboard)")
137
  logs.append("=" * 50)
138
  logs.append(run_pythonanalysis())
139
- logs.append("")
140
- logs.append("=" * 50)
141
  return "\n".join(logs)
142
 
143
 
@@ -149,7 +147,7 @@ def _load_all_figures() -> List[Tuple[str, str]]:
149
  """Return list of (filepath, caption) for Gallery."""
150
  items = []
151
  for p in sorted(PY_FIG_DIR.glob("*.png")):
152
- items.append((str(p), f"Python | {p.stem.replace('_', ' ').title()}"))
153
  return items
154
 
155
 
@@ -170,17 +168,11 @@ def refresh_gallery():
170
  figures = _load_all_figures()
171
  idx = artifacts_index()
172
 
173
- # Build table choices
174
- table_choices = []
175
- for name in idx["python"]["tables"]:
176
- table_choices.append(f"{"python"}/{name}")
177
 
178
- # Default: show first table if available
179
  default_df = pd.DataFrame()
180
  if table_choices:
181
- parts = table_choices[0].split("/", 1)
182
- base = PY_TAB_DIR
183
- default_df = _load_table_safe(base / parts[1])
184
 
185
  return (
186
  figures if figures else [],
@@ -190,15 +182,11 @@ def refresh_gallery():
190
 
191
 
192
  def on_table_select(choice: str):
193
- if not choice or "/" not in choice:
194
  return pd.DataFrame([{"hint": "Select a table above."}])
195
- scope, name = choice.split("/", 1)
196
- base = {"python": PY_TAB_DIR}.get(scope)
197
- if not base:
198
- return pd.DataFrame([{"error": f"Unknown scope: {scope}"}])
199
- path = base / name
200
  if not path.exists():
201
- return pd.DataFrame([{"error": f"File not found: {path}"}])
202
  return _load_table_safe(path)
203
 
204
 
@@ -217,12 +205,12 @@ def load_kpis() -> Dict[str, Any]:
217
 
218
 
219
  # =========================================================
220
- # AI DASHBOARD (Tab 3) -- LLM picks what to display
221
  # =========================================================
222
 
223
  DASHBOARD_SYSTEM = """You are an AI dashboard assistant for a book-sales analytics app.
224
  The user asks questions or requests about their data. You have access to pre-computed
225
- artifacts from Python and R analysis pipelines.
226
 
227
  AVAILABLE ARTIFACTS (only reference ones that exist):
228
  {artifacts_json}
@@ -233,7 +221,7 @@ YOUR JOB:
233
  1. Answer the user's question conversationally using the KPIs and your knowledge of the artifacts.
234
  2. At the END of your response, output a JSON block (fenced with ```json ... ```) that tells
235
  the dashboard which artifact to display. The JSON must have this shape:
236
- {{"show": "figure"|"table"|"none", "scope": "python"|"r", "filename": "..."}}
237
 
238
  - Use "show": "figure" to display a chart image.
239
  - Use "show": "table" to display a CSV/JSON table.
@@ -242,8 +230,7 @@ YOUR JOB:
242
  RULES:
243
  - If the user asks about sales trends or forecasting by title, show sales_trends or arima figures.
244
  - If the user asks about sentiment, show sentiment figure or sentiment_counts table.
245
- - If the user asks about R regression, the R notebook focuses on forecasting, show accuracy_table.csv.
246
- - If the user asks about forecast accuracy or model comparison, show accuracy_table.csv or forecast_compare.png.
247
  - If the user asks about top sellers, show top_titles_by_units_sold.csv.
248
  - If the user asks a general data question, pick the most relevant artifact.
249
  - Keep your answer concise (2-4 sentences), then the JSON block.
@@ -318,22 +305,21 @@ def ai_chat(user_msg: str, history: list):
318
  fig_out = None
319
  tab_out = None
320
  show = directive.get("show", "none")
321
- scope = directive.get("scope", "")
322
  fname = directive.get("filename", "")
323
 
324
- if show == "figure" and scope and fname:
325
- base = {"python": PY_FIG_DIR, "r": R_FIG_DIR}.get(scope)
326
- if base and (base / fname).exists():
327
- fig_out = str(base / fname)
328
  else:
329
- reply += f"\n\n*(Could not find figure: {scope}/{fname})*"
330
 
331
- if show == "table" and scope and fname:
332
- base = {"python": PY_TAB_DIR, "r": R_TAB_DIR}.get(scope)
333
- if base and (base / fname).exists():
334
- tab_out = _load_table_safe(base / fname)
335
  else:
336
- reply += f"\n\n*(Could not find table: {scope}/{fname})*"
337
 
338
  new_history = (history or []) + [
339
  {"role": "user", "content": user_msg},
@@ -347,7 +333,7 @@ def _keyword_fallback(msg: str, idx: Dict, kpis: Dict) -> Tuple[str, Dict]:
347
  """Simple keyword matcher when LLM is unavailable."""
348
  msg_lower = msg.lower()
349
 
350
- if not any(idx[s]["figures"] or idx[s]["tables"] for s in ("python", "r")):
351
  return (
352
  "No artifacts found yet. Please run the pipeline first (Tab 1), "
353
  "then come back here to explore the results.",
@@ -375,47 +361,27 @@ def _keyword_fallback(msg: str, idx: Dict, kpis: Dict) -> Tuple[str, Dict]:
375
  )
376
 
377
  if any(w in msg_lower for w in ["arima", "forecast", "predict"]):
378
- if "compar" in msg_lower or "ets" in msg_lower or "accuracy" in msg_lower:
379
- if "forecast_compare.png" in idx.get("r", {}).get("figures", []):
380
- return (
381
- "Here is the ARIMA+Fourier vs ETS forecast comparison from the R analysis.",
382
- {"show": "figure", "scope": "r", "filename": "forecast_compare.png"},
383
- )
384
  return (
385
- f"Here are the ARIMA forecasts for sampled titles from the Python analysis. {kpi_text}",
386
  {"show": "figure", "scope": "python", "filename": "arima_forecasts_sampled_titles.png"},
387
  )
388
 
389
- if any(w in msg_lower for w in ["regression", "lm", "coefficient", "price effect", "rating effect"]):
390
- return (
391
- "The R notebook focuses on forecasting rather than regression. "
392
- "Here is the forecast accuracy comparison instead.",
393
- {"show": "table", "scope": "r", "filename": "accuracy_table.csv"},
394
- )
395
-
396
  if any(w in msg_lower for w in ["top", "best sell", "popular", "rank"]):
397
  return (
398
  f"Here are the top-selling titles by units sold. {kpi_text}",
399
  {"show": "table", "scope": "python", "filename": "top_titles_by_units_sold.csv"},
400
  )
401
 
402
- if any(w in msg_lower for w in ["accuracy", "benchmark", "rmse", "mape"]):
403
  return (
404
- "Here is the forecast accuracy comparison (ARIMA+Fourier vs ETS) from the R analysis.",
405
- {"show": "table", "scope": "r", "filename": "accuracy_table.csv"},
406
  )
407
 
408
- if any(w in msg_lower for w in ["r analysis", "r output", "r result"]):
409
- if "forecast_compare.png" in idx.get("r", {}).get("figures", []):
410
- return (
411
- "Here is the main R output: forecast model comparison plot.",
412
- {"show": "figure", "scope": "r", "filename": "forecast_compare.png"},
413
- )
414
-
415
  if any(w in msg_lower for w in ["dashboard", "overview", "summary", "kpi"]):
416
  return (
417
  f"Dashboard overview: {kpi_text}\n\nAsk me about sales trends, sentiment, forecasts, "
418
- "forecast accuracy, or top sellers to see specific visualizations.",
419
  {"show": "table", "scope": "python", "filename": "df_dashboard.csv"},
420
  )
421
 
@@ -423,7 +389,7 @@ def _keyword_fallback(msg: str, idx: Dict, kpis: Dict) -> Tuple[str, Dict]:
423
  return (
424
  f"I can show you various analyses. {kpi_text}\n\n"
425
  "Try asking about: **sales trends**, **sentiment**, **ARIMA forecasts**, "
426
- "**forecast accuracy**, **top sellers**, or **dashboard overview**.",
427
  {"show": "none"},
428
  )
429
 
@@ -439,11 +405,11 @@ def load_css() -> str:
439
  return css_path.read_text(encoding="utf-8") if css_path.exists() else ""
440
 
441
 
442
- with gr.Blocks(title="RX12 Workshop App") as demo:
443
 
444
  gr.Markdown(
445
- "# RX12 - Intro to Python and R - Workshop App\n"
446
- "*The app to integrate the three notebooks in to get a functioning blueprint of the group project's final product*",
447
  elem_id="escp_title",
448
  )
449
 
@@ -451,37 +417,16 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
451
  # TAB 1 -- Pipeline Runner
452
  # ===========================================================
453
  with gr.Tab("Pipeline Runner"):
454
- gr.Markdown(
455
- )
456
 
457
  with gr.Row():
458
  with gr.Column(scale=1):
459
- btn_nb1 = gr.Button(
460
- "Step 1: Data Creation",
461
- variant="secondary",
462
- )
463
- gr.Markdown(
464
- )
465
  with gr.Column(scale=1):
466
- btn_nb2 = gr.Button(
467
- "Step 2a: Python Analysis",
468
- variant="secondary",
469
- )
470
- gr.Markdown(
471
- )
472
- with gr.Column(scale=1):
473
- btn_r = gr.Button(
474
- "Step 2b: R Analysis",
475
- variant="secondary",
476
- )
477
- gr.Markdown(
478
- )
479
 
480
  with gr.Row():
481
- btn_all = gr.Button(
482
- "Run All 3 Steps",
483
- variant="primary",
484
- )
485
 
486
  run_log = gr.Textbox(
487
  label="Execution Log",
@@ -492,7 +437,6 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
492
 
493
  btn_nb1.click(run_datacreation, outputs=[run_log])
494
  btn_nb2.click(run_pythonanalysis, outputs=[run_log])
495
- btn_r.click(run_r, outputs=[run_log])
496
  btn_all.click(run_full_pipeline, outputs=[run_log])
497
 
498
  # ===========================================================
@@ -501,15 +445,14 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
501
  with gr.Tab("Results Gallery"):
502
  gr.Markdown(
503
  "### All generated artifacts\n\n"
504
- "After running the pipeline, click **Refresh** to load all figures and tables. "
505
- "Figures are shown in the gallery; select a table from the dropdown to inspect it."
506
  )
507
 
508
  refresh_btn = gr.Button("Refresh Gallery", variant="primary")
509
 
510
  gr.Markdown("#### Figures")
511
  gallery = gr.Gallery(
512
- label="All Figures (Python + R)",
513
  columns=2,
514
  height=480,
515
  object_fit="contain",
@@ -559,7 +502,7 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
559
  )
560
  user_input = gr.Textbox(
561
  label="Ask about your data",
562
- placeholder="e.g. Show me sales trends / What drives revenue? / Compare forecast models",
563
  lines=1,
564
  )
565
  gr.Examples(
@@ -567,8 +510,8 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
567
  "Show me the sales trends",
568
  "What does the sentiment look like?",
569
  "Which titles sell the most?",
570
- "Show the forecast accuracy comparison",
571
- "Compare the ARIMA and ETS forecasts",
572
  "Give me a dashboard overview",
573
  ],
574
  inputs=user_input,
@@ -591,4 +534,4 @@ with gr.Blocks(title="RX12 Workshop App") as demo:
591
  )
592
 
593
 
594
- demo.launch(css=load_css(), allowed_paths=[str(BASE_DIR)])
 
4
  import time
5
  import traceback
6
  from pathlib import Path
7
+ from typing import Dict, Any, List, Tuple
8
 
9
  import pandas as pd
10
  import gradio as gr
 
50
  # =========================================================
51
 
52
  def ensure_dirs():
53
+ for p in [RUNS_DIR, ART_DIR, PY_FIG_DIR, PY_TAB_DIR]:
54
  p.mkdir(parents=True, exist_ok=True)
55
 
56
  def stamp():
 
76
  "python": {
77
  "figures": _ls(PY_FIG_DIR, (".png", ".jpg", ".jpeg")),
78
  "tables": _ls(PY_TAB_DIR, (".csv", ".json")),
79
+ },
80
  }
81
 
82
  # =========================================================
 
128
  def run_full_pipeline() -> str:
129
  logs = []
130
  logs.append("=" * 50)
131
+ logs.append("STEP 1/2: Data Creation (web scraping + synthetic data)")
132
  logs.append("=" * 50)
133
  logs.append(run_datacreation())
134
  logs.append("")
135
  logs.append("=" * 50)
136
+ logs.append("STEP 2/2: Python Analysis (sentiment, ARIMA, dashboard)")
137
  logs.append("=" * 50)
138
  logs.append(run_pythonanalysis())
 
 
139
  return "\n".join(logs)
140
 
141
 
 
147
  """Return list of (filepath, caption) for Gallery."""
148
  items = []
149
  for p in sorted(PY_FIG_DIR.glob("*.png")):
150
+ items.append((str(p), p.stem.replace('_', ' ').title()))
151
  return items
152
 
153
 
 
168
  figures = _load_all_figures()
169
  idx = artifacts_index()
170
 
171
+ table_choices = list(idx["python"]["tables"])
 
 
 
172
 
 
173
  default_df = pd.DataFrame()
174
  if table_choices:
175
+ default_df = _load_table_safe(PY_TAB_DIR / table_choices[0])
 
 
176
 
177
  return (
178
  figures if figures else [],
 
182
 
183
 
184
  def on_table_select(choice: str):
185
+ if not choice:
186
  return pd.DataFrame([{"hint": "Select a table above."}])
187
+ path = PY_TAB_DIR / choice
 
 
 
 
188
  if not path.exists():
189
+ return pd.DataFrame([{"error": f"File not found: {choice}"}])
190
  return _load_table_safe(path)
191
 
192
 
 
205
 
206
 
207
  # =========================================================
208
+ # AI DASHBOARD -- LLM picks what to display
209
  # =========================================================
210
 
211
  DASHBOARD_SYSTEM = """You are an AI dashboard assistant for a book-sales analytics app.
212
  The user asks questions or requests about their data. You have access to pre-computed
213
+ artifacts from a Python analysis pipeline.
214
 
215
  AVAILABLE ARTIFACTS (only reference ones that exist):
216
  {artifacts_json}
 
221
  1. Answer the user's question conversationally using the KPIs and your knowledge of the artifacts.
222
  2. At the END of your response, output a JSON block (fenced with ```json ... ```) that tells
223
  the dashboard which artifact to display. The JSON must have this shape:
224
+ {{"show": "figure"|"table"|"none", "scope": "python", "filename": "..."}}
225
 
226
  - Use "show": "figure" to display a chart image.
227
  - Use "show": "table" to display a CSV/JSON table.
 
230
  RULES:
231
  - If the user asks about sales trends or forecasting by title, show sales_trends or arima figures.
232
  - If the user asks about sentiment, show sentiment figure or sentiment_counts table.
233
+ - If the user asks about forecast accuracy or ARIMA, show arima figures.
 
234
  - If the user asks about top sellers, show top_titles_by_units_sold.csv.
235
  - If the user asks a general data question, pick the most relevant artifact.
236
  - Keep your answer concise (2-4 sentences), then the JSON block.
 
305
  fig_out = None
306
  tab_out = None
307
  show = directive.get("show", "none")
 
308
  fname = directive.get("filename", "")
309
 
310
+ if show == "figure" and fname:
311
+ fp = PY_FIG_DIR / fname
312
+ if fp.exists():
313
+ fig_out = str(fp)
314
  else:
315
+ reply += f"\n\n*(Could not find figure: {fname})*"
316
 
317
+ if show == "table" and fname:
318
+ fp = PY_TAB_DIR / fname
319
+ if fp.exists():
320
+ tab_out = _load_table_safe(fp)
321
  else:
322
+ reply += f"\n\n*(Could not find table: {fname})*"
323
 
324
  new_history = (history or []) + [
325
  {"role": "user", "content": user_msg},
 
333
  """Simple keyword matcher when LLM is unavailable."""
334
  msg_lower = msg.lower()
335
 
336
+ if not idx["python"]["figures"] and not idx["python"]["tables"]:
337
  return (
338
  "No artifacts found yet. Please run the pipeline first (Tab 1), "
339
  "then come back here to explore the results.",
 
361
  )
362
 
363
  if any(w in msg_lower for w in ["arima", "forecast", "predict"]):
 
 
 
 
 
 
364
  return (
365
+ f"Here are the ARIMA forecasts for sampled titles. {kpi_text}",
366
  {"show": "figure", "scope": "python", "filename": "arima_forecasts_sampled_titles.png"},
367
  )
368
 
 
 
 
 
 
 
 
369
  if any(w in msg_lower for w in ["top", "best sell", "popular", "rank"]):
370
  return (
371
  f"Here are the top-selling titles by units sold. {kpi_text}",
372
  {"show": "table", "scope": "python", "filename": "top_titles_by_units_sold.csv"},
373
  )
374
 
375
+ if any(w in msg_lower for w in ["price", "pricing", "decision"]):
376
  return (
377
+ f"Here are the pricing decisions. {kpi_text}",
378
+ {"show": "table", "scope": "python", "filename": "pricing_decisions.csv"},
379
  )
380
 
 
 
 
 
 
 
 
381
  if any(w in msg_lower for w in ["dashboard", "overview", "summary", "kpi"]):
382
  return (
383
  f"Dashboard overview: {kpi_text}\n\nAsk me about sales trends, sentiment, forecasts, "
384
+ "pricing, or top sellers to see specific visualizations.",
385
  {"show": "table", "scope": "python", "filename": "df_dashboard.csv"},
386
  )
387
 
 
389
  return (
390
  f"I can show you various analyses. {kpi_text}\n\n"
391
  "Try asking about: **sales trends**, **sentiment**, **ARIMA forecasts**, "
392
+ "**pricing decisions**, **top sellers**, or **dashboard overview**.",
393
  {"show": "none"},
394
  )
395
 
 
405
  return css_path.read_text(encoding="utf-8") if css_path.exists() else ""
406
 
407
 
408
+ with gr.Blocks(title="AIBDM 2026 Workshop App") as demo:
409
 
410
  gr.Markdown(
411
+ "# AIBDM 2026 - AI & Big Data Management - Workshop App\n"
412
+ "*Run notebooks, explore results, and chat with your data*",
413
  elem_id="escp_title",
414
  )
415
 
 
417
  # TAB 1 -- Pipeline Runner
418
  # ===========================================================
419
  with gr.Tab("Pipeline Runner"):
420
+ gr.Markdown()
 
421
 
422
  with gr.Row():
423
  with gr.Column(scale=1):
424
+ btn_nb1 = gr.Button("Step 1: Data Creation", variant="secondary")
 
 
 
 
 
425
  with gr.Column(scale=1):
426
+ btn_nb2 = gr.Button("Step 2: Python Analysis", variant="secondary")
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  with gr.Row():
429
+ btn_all = gr.Button("Run Full Pipeline (Both Steps)", variant="primary")
 
 
 
430
 
431
  run_log = gr.Textbox(
432
  label="Execution Log",
 
437
 
438
  btn_nb1.click(run_datacreation, outputs=[run_log])
439
  btn_nb2.click(run_pythonanalysis, outputs=[run_log])
 
440
  btn_all.click(run_full_pipeline, outputs=[run_log])
441
 
442
  # ===========================================================
 
445
  with gr.Tab("Results Gallery"):
446
  gr.Markdown(
447
  "### All generated artifacts\n\n"
448
+ "After running the pipeline, click **Refresh** to load all figures and tables."
 
449
  )
450
 
451
  refresh_btn = gr.Button("Refresh Gallery", variant="primary")
452
 
453
  gr.Markdown("#### Figures")
454
  gallery = gr.Gallery(
455
+ label="Generated Figures",
456
  columns=2,
457
  height=480,
458
  object_fit="contain",
 
502
  )
503
  user_input = gr.Textbox(
504
  label="Ask about your data",
505
+ placeholder="e.g. Show me sales trends / What are the top sellers? / Sentiment analysis",
506
  lines=1,
507
  )
508
  gr.Examples(
 
510
  "Show me the sales trends",
511
  "What does the sentiment look like?",
512
  "Which titles sell the most?",
513
+ "Show the ARIMA forecasts",
514
+ "What are the pricing decisions?",
515
  "Give me a dashboard overview",
516
  ],
517
  inputs=user_input,
 
534
  )
535
 
536
 
537
+ demo.launch(css=load_css(), allowed_paths=[str(BASE_DIR)])
style.css CHANGED
@@ -1,17 +1,18 @@
1
- /* --- Target the Gradio app wrapper for backgrounds --- */
 
2
  gradio-app,
3
  .gradio-app,
4
  .main,
5
  #app,
6
  [data-testid="app"] {
7
  background-color: rgb(40,9,109) !important;
8
- background-image:
9
- url('https://huggingface.co/spaces/escp/rx12workshoptemplate/resolve/main/background_top.png'),
10
- url('https://huggingface.co/spaces/escp/rx12workshoptemplate/resolve/main/background_mid.png') !important;
11
- background-position:
12
  top center,
13
  0 913px !important;
14
- background-repeat:
15
  no-repeat,
16
  repeat-y !important;
17
  background-size:
@@ -20,7 +21,6 @@ gradio-app,
20
  min-height: 100vh !important;
21
  }
22
 
23
- /* --- Fallback on html/body --- */
24
  html, body {
25
  background-color: rgb(40,9,109) !important;
26
  margin: 0 !important;
@@ -28,7 +28,6 @@ html, body {
28
  min-height: 100vh !important;
29
  }
30
 
31
- /* --- Fixed bottom banner using ::after on body --- */
32
  body::after {
33
  content: '' !important;
34
  position: fixed !important;
@@ -36,7 +35,7 @@ body::after {
36
  left: 0 !important;
37
  right: 0 !important;
38
  height: 130px !important;
39
- background-image: url('https://huggingface.co/spaces/escp/rx12workshoptemplate/resolve/main/background_bottom.png') !important;
40
  background-size: 100% 100% !important;
41
  background-repeat: no-repeat !important;
42
  background-position: bottom center !important;
@@ -44,17 +43,17 @@ body::after {
44
  z-index: 9999 !important;
45
  }
46
 
47
- /* --- Main container --- */
48
  .gradio-container {
49
  max-width: 1400px !important;
50
  width: 94vw !important;
51
  margin: 0 auto !important;
52
- padding-top: 220px !important;
53
  padding-bottom: 150px !important;
54
- background: transparent !important;
55
  }
56
 
57
- /* --- Title in ESCP gold --- */
58
  #escp_title h1 {
59
  color: rgb(242,198,55) !important;
60
  font-size: 3rem !important;
@@ -63,13 +62,12 @@ body::after {
63
  margin: 0 0 12px 0 !important;
64
  }
65
 
66
- /* --- Subtitle --- */
67
  #escp_title p, #escp_title em {
68
  color: rgba(255,255,255,0.85) !important;
69
  text-align: center !important;
70
  }
71
 
72
- /* --- Tab bar background --- */
73
  .tabs > .tab-nav,
74
  .tab-nav,
75
  div[role="tablist"],
@@ -79,7 +77,6 @@ div[role="tablist"],
79
  padding: 4px !important;
80
  }
81
 
82
- /* --- ALL tab buttons: force white text --- */
83
  .tabs > .tab-nav button,
84
  .tab-nav button,
85
  div[role="tablist"] button,
@@ -96,7 +93,6 @@ button[role="tab"],
96
  opacity: 1 !important;
97
  }
98
 
99
- /* --- Selected tab: ESCP gold --- */
100
  .tabs > .tab-nav button.selected,
101
  .tab-nav button.selected,
102
  button[role="tab"][aria-selected="true"],
@@ -107,7 +103,6 @@ div[role="tablist"] button[aria-selected="true"],
107
  background: rgba(255,255,255,0.12) !important;
108
  }
109
 
110
- /* --- Unselected tabs: ensure visibility --- */
111
  .tabs > .tab-nav button:not(.selected),
112
  .tab-nav button:not(.selected),
113
  button[role="tab"][aria-selected="false"],
@@ -117,7 +112,7 @@ div[role="tablist"] button:not([aria-selected="true"]) {
117
  opacity: 1 !important;
118
  }
119
 
120
- /* --- White card panels --- */
121
  .gradio-container .gr-block,
122
  .gradio-container .gr-box,
123
  .gradio-container .gr-panel,
@@ -126,14 +121,13 @@ div[role="tablist"] button:not([aria-selected="true"]) {
126
  border-radius: 10px !important;
127
  }
128
 
129
- /* --- Tab content area --- */
130
  .tabitem {
131
  background: rgba(255,255,255,0.95) !important;
132
  border-radius: 0 0 10px 10px !important;
133
  padding: 16px !important;
134
  }
135
 
136
- /* --- Inputs --- */
137
  .gradio-container input,
138
  .gradio-container textarea,
139
  .gradio-container select {
@@ -142,7 +136,7 @@ div[role="tablist"] button:not([aria-selected="true"]) {
142
  border-radius: 8px !important;
143
  }
144
 
145
- /* --- Buttons: ESCP purple primary --- */
146
  .gradio-container button:not([role="tab"]) {
147
  font-weight: 600 !important;
148
  padding: 10px 16px !important;
@@ -169,17 +163,15 @@ button.secondary:hover {
169
  background-color: rgb(240,238,250) !important;
170
  }
171
 
172
- /* --- Dataframes --- */
173
  [data-testid="dataframe"] {
174
  background-color: #ffffff !important;
175
  border-radius: 10px !important;
176
  }
177
 
178
- table {
179
- font-size: 0.85rem !important;
180
- }
181
 
182
- /* --- Chatbot (AI Dashboard tab) --- */
183
  .gr-chatbot {
184
  min-height: 380px !important;
185
  background-color: #ffffff !important;
@@ -196,19 +188,19 @@ table {
196
  border-radius: 12px !important;
197
  }
198
 
199
- /* --- Gallery --- */
200
  .gallery {
201
  background: #ffffff !important;
202
  border-radius: 10px !important;
203
  }
204
 
205
- /* --- Log textbox --- */
206
  textarea {
207
  font-family: monospace !important;
208
  font-size: 0.8rem !important;
209
  }
210
 
211
- /* --- Markdown headings inside tabs --- */
212
  .tabitem h3 {
213
  color: rgb(40,9,109) !important;
214
  font-weight: 700 !important;
@@ -218,7 +210,7 @@ textarea {
218
  color: #374151 !important;
219
  }
220
 
221
- /* --- Examples row (AI Dashboard) --- */
222
  .examples-row button {
223
  background: rgb(240,238,250) !important;
224
  color: rgb(40,9,109) !important;
@@ -231,7 +223,7 @@ textarea {
231
  background: rgb(232,225,250) !important;
232
  }
233
 
234
- /* --- Header / footer: transparent over banner --- */
235
  header, header *,
236
  footer, footer * {
237
  background: transparent !important;
@@ -245,33 +237,7 @@ header a, header button {
245
  box-shadow: none !important;
246
  }
247
 
248
- #footer, #footer *,
249
- [class*="footer"], [class*="footer"] *,
250
- [class*="chip"], [class*="pill"], [class*="chip"] *, [class*="pill"] * {
251
- background: transparent !important;
252
- border: none !important;
253
- box-shadow: none !important;
254
- }
255
-
256
- [data-testid*="api"], [data-testid*="settings"],
257
- [id*="api"], [id*="settings"],
258
- [class*="api"], [class*="settings"],
259
- [class*="bottom"], [class*="toolbar"], [class*="controls"] {
260
- background: transparent !important;
261
- box-shadow: none !important;
262
- }
263
-
264
- [data-testid*="api"] *, [data-testid*="settings"] *,
265
- [id*="api"] *, [id*="settings"] *,
266
- [class*="api"] *, [class*="settings"] * {
267
- background: transparent !important;
268
- box-shadow: none !important;
269
- }
270
-
271
- section footer {
272
- background: transparent !important;
273
- }
274
-
275
  section footer button,
276
  section footer a {
277
  background: transparent !important;
@@ -281,22 +247,11 @@ section footer a {
281
  color: white !important;
282
  }
283
 
284
- section footer button:hover,
285
- section footer button:focus,
286
- section footer a:hover,
287
- section footer a:focus {
288
- background: transparent !important;
289
- background-color: transparent !important;
290
- box-shadow: none !important;
291
- }
292
-
293
- section footer button,
294
- section footer button * {
295
  background: transparent !important;
296
  background-color: transparent !important;
297
- background-image: none !important;
298
  box-shadow: none !important;
299
- filter: none !important;
300
  }
301
 
302
  section footer button::before,
@@ -308,13 +263,6 @@ section footer button::after {
308
  filter: none !important;
309
  }
310
 
311
- section footer a,
312
- section footer a * {
313
- background: transparent !important;
314
- background-color: transparent !important;
315
- box-shadow: none !important;
316
- }
317
-
318
  .gradio-container footer button,
319
  .gradio-container footer button *,
320
  .gradio-container .footer button,
@@ -333,4 +281,12 @@ section footer a * {
333
  background-color: transparent !important;
334
  background-image: none !important;
335
  box-shadow: none !important;
336
- }
 
 
 
 
 
 
 
 
 
1
+ /* ── ESCP Background: top (once) + mid gradient strip (repeats) ── */
2
+
3
  gradio-app,
4
  .gradio-app,
5
  .main,
6
  #app,
7
  [data-testid="app"] {
8
  background-color: rgb(40,9,109) !important;
9
+ background-image:
10
+ url('https://huggingface.co/spaces/atascioglu/SE21AppTemplate/resolve/main/background_top.png'),
11
+ url('https://huggingface.co/spaces/atascioglu/SE21AppTemplate/resolve/main/background_mid.png') !important;
12
+ background-position:
13
  top center,
14
  0 913px !important;
15
+ background-repeat:
16
  no-repeat,
17
  repeat-y !important;
18
  background-size:
 
21
  min-height: 100vh !important;
22
  }
23
 
 
24
  html, body {
25
  background-color: rgb(40,9,109) !important;
26
  margin: 0 !important;
 
28
  min-height: 100vh !important;
29
  }
30
 
 
31
  body::after {
32
  content: '' !important;
33
  position: fixed !important;
 
35
  left: 0 !important;
36
  right: 0 !important;
37
  height: 130px !important;
38
+ background-image: url('https://huggingface.co/spaces/atascioglu/SE21AppTemplate/resolve/main/background_bottom.png') !important;
39
  background-size: 100% 100% !important;
40
  background-repeat: no-repeat !important;
41
  background-position: bottom center !important;
 
43
  z-index: 9999 !important;
44
  }
45
 
46
+ /* ── Container: opaque purple so it covers the background image cleanly ── */
47
  .gradio-container {
48
  max-width: 1400px !important;
49
  width: 94vw !important;
50
  margin: 0 auto !important;
51
+ padding-top: 20px !important;
52
  padding-bottom: 150px !important;
53
+ background: rgb(40,9,109) !important;
54
  }
55
 
56
+ /* ── Title: ESCP gold ── */
57
  #escp_title h1 {
58
  color: rgb(242,198,55) !important;
59
  font-size: 3rem !important;
 
62
  margin: 0 0 12px 0 !important;
63
  }
64
 
 
65
  #escp_title p, #escp_title em {
66
  color: rgba(255,255,255,0.85) !important;
67
  text-align: center !important;
68
  }
69
 
70
+ /* ── Tab bar ── */
71
  .tabs > .tab-nav,
72
  .tab-nav,
73
  div[role="tablist"],
 
77
  padding: 4px !important;
78
  }
79
 
 
80
  .tabs > .tab-nav button,
81
  .tab-nav button,
82
  div[role="tablist"] button,
 
93
  opacity: 1 !important;
94
  }
95
 
 
96
  .tabs > .tab-nav button.selected,
97
  .tab-nav button.selected,
98
  button[role="tab"][aria-selected="true"],
 
103
  background: rgba(255,255,255,0.12) !important;
104
  }
105
 
 
106
  .tabs > .tab-nav button:not(.selected),
107
  .tab-nav button:not(.selected),
108
  button[role="tab"][aria-selected="false"],
 
112
  opacity: 1 !important;
113
  }
114
 
115
+ /* ── White card panels ── */
116
  .gradio-container .gr-block,
117
  .gradio-container .gr-box,
118
  .gradio-container .gr-panel,
 
121
  border-radius: 10px !important;
122
  }
123
 
 
124
  .tabitem {
125
  background: rgba(255,255,255,0.95) !important;
126
  border-radius: 0 0 10px 10px !important;
127
  padding: 16px !important;
128
  }
129
 
130
+ /* ── Inputs ── */
131
  .gradio-container input,
132
  .gradio-container textarea,
133
  .gradio-container select {
 
136
  border-radius: 8px !important;
137
  }
138
 
139
+ /* ── Buttons ── */
140
  .gradio-container button:not([role="tab"]) {
141
  font-weight: 600 !important;
142
  padding: 10px 16px !important;
 
163
  background-color: rgb(240,238,250) !important;
164
  }
165
 
166
+ /* ── Dataframes ── */
167
  [data-testid="dataframe"] {
168
  background-color: #ffffff !important;
169
  border-radius: 10px !important;
170
  }
171
 
172
+ table { font-size: 0.85rem !important; }
 
 
173
 
174
+ /* ── Chatbot ── */
175
  .gr-chatbot {
176
  min-height: 380px !important;
177
  background-color: #ffffff !important;
 
188
  border-radius: 12px !important;
189
  }
190
 
191
+ /* ── Gallery ── */
192
  .gallery {
193
  background: #ffffff !important;
194
  border-radius: 10px !important;
195
  }
196
 
197
+ /* ── Log textbox ── */
198
  textarea {
199
  font-family: monospace !important;
200
  font-size: 0.8rem !important;
201
  }
202
 
203
+ /* ── Headings inside tabs ── */
204
  .tabitem h3 {
205
  color: rgb(40,9,109) !important;
206
  font-weight: 700 !important;
 
210
  color: #374151 !important;
211
  }
212
 
213
+ /* ── Examples row ── */
214
  .examples-row button {
215
  background: rgb(240,238,250) !important;
216
  color: rgb(40,9,109) !important;
 
223
  background: rgb(232,225,250) !important;
224
  }
225
 
226
+ /* ── Header / footer: transparent ── */
227
  header, header *,
228
  footer, footer * {
229
  background: transparent !important;
 
237
  box-shadow: none !important;
238
  }
239
 
240
+ section footer,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
241
  section footer button,
242
  section footer a {
243
  background: transparent !important;
 
247
  color: white !important;
248
  }
249
 
250
+ section footer button *,
251
+ section footer a * {
 
 
 
 
 
 
 
 
 
252
  background: transparent !important;
253
  background-color: transparent !important;
 
254
  box-shadow: none !important;
 
255
  }
256
 
257
  section footer button::before,
 
263
  filter: none !important;
264
  }
265
 
 
 
 
 
 
 
 
266
  .gradio-container footer button,
267
  .gradio-container footer button *,
268
  .gradio-container .footer button,
 
281
  background-color: transparent !important;
282
  background-image: none !important;
283
  box-shadow: none !important;
284
+ }
285
+
286
+ /* ── Responsive ── */
287
+ @media (max-width: 768px) {
288
+ .gradio-container {
289
+ padding-top: 120px !important;
290
+ width: 98vw !important;
291
+ }
292
+ }