VincentCroft commited on
Commit
a2b637a
·
1 Parent(s): e9cfe70

Expand CSV loading overlay to cover download button

Browse files
Files changed (1) hide show
  1. app.py +201 -34
app.py CHANGED
@@ -202,19 +202,38 @@ GITHUB_CONTENT_CACHE: Dict[str, List[Dict[str, Any]]] = {}
202
 
203
 
204
  APP_CSS = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  #available-files-grid .wrap {
206
  display: grid;
207
  grid-template-columns: repeat(4, minmax(0, 1fr));
208
  gap: 0.5rem;
209
  max-height: 24rem;
 
210
  overflow-y: auto;
211
  padding-right: 0.25rem;
212
  }
213
 
214
- #available-files-grid {
215
- position: relative;
216
- }
217
-
218
  #available-files-grid .wrap > div {
219
  min-width: 0;
220
  }
@@ -245,12 +264,37 @@ APP_CSS = """
245
  #available-files-grid .gradio-loading {
246
  position: absolute;
247
  inset: 0;
 
 
 
248
  display: flex;
249
  align-items: center;
250
  justify-content: center;
251
  background: rgba(10, 14, 23, 0.72);
252
  border-radius: 0.75rem;
253
  z-index: 10;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  }
255
 
256
  #date-browser-row {
@@ -282,6 +326,8 @@ APP_CSS = """
282
 
283
  #download-selected-button {
284
  width: 100%;
 
 
285
  }
286
 
287
  #download-selected-button .gradio-button {
@@ -929,6 +975,71 @@ def resolve_output_path(
929
  return (base / fallback).resolve()
930
 
931
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932
  def clear_training_files():
933
  default_label = LABEL_COLUMN or "Fault"
934
  for cached_file in TRAINING_UPLOAD_DIR.glob("*"):
@@ -1503,17 +1614,18 @@ def build_interface() -> gr.Blocks:
1503
  day_download_button = gr.Button(
1504
  "⬇️ Download day CSVs", variant="secondary"
1505
  )
1506
- available_files = gr.CheckboxGroup(
1507
- label="Available CSV files",
1508
- choices=[],
1509
- value=[],
1510
- elem_id="available-files-grid",
1511
- )
1512
- download_button = gr.Button(
1513
- "⬇️ Download selected CSVs",
1514
- variant="secondary",
1515
- elem_id="download-selected-button",
1516
- )
 
1517
  repo_status = gr.Markdown(
1518
  "Click 'Reload dataset from database' to fetch the directory tree."
1519
  )
@@ -1576,10 +1688,19 @@ def build_interface() -> gr.Blocks:
1576
  label="Epochs",
1577
  )
1578
 
 
 
 
 
 
 
 
1579
  with gr.Row():
1580
- output_directory = gr.Textbox(
1581
- value=str(MODEL_OUTPUT_DIR),
1582
  label="Output directory",
 
 
1583
  )
1584
  model_name = gr.Textbox(
1585
  value=model_default,
@@ -1594,6 +1715,47 @@ def build_interface() -> gr.Blocks:
1594
  label="Metadata output filename",
1595
  )
1596
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1597
  with gr.Row(elem_id="artifact-download-row"):
1598
  model_download_button = gr.DownloadButton(
1599
  "⬇️ Download model file",
@@ -1688,16 +1850,8 @@ def build_interface() -> gr.Blocks:
1688
  metadata_filename,
1689
  enable_tensorboard,
1690
  ):
1691
- def _download_state(path: Optional[Union[str, Path]]):
1692
- if not path:
1693
- return gr.update(value=None, visible=False)
1694
- candidate = Path(path)
1695
- if candidate.exists():
1696
- return gr.update(value=str(candidate), visible=True)
1697
- return gr.update(value=None, visible=False)
1698
-
1699
  try:
1700
- base_dir = normalise_output_directory(output_dir)
1701
  base_dir.mkdir(parents=True, exist_ok=True)
1702
 
1703
  model_path = resolve_output_path(
@@ -1820,28 +1974,39 @@ def build_interface() -> gr.Blocks:
1820
  skipped = ", ".join(missing_paths)
1821
  status = f"⚠️ Skipped missing files: {skipped}\n" + status
1822
 
 
 
 
 
1823
  return (
1824
  status,
1825
  report_df,
1826
  result["history"],
1827
  confusion_df,
1828
- _download_state(result["model_path"]),
1829
- _download_state(result["scaler_path"]),
1830
- _download_state(result["metadata_path"]),
1831
- _download_state(tensorboard_zip),
1832
  gr.update(value=result.get("label_column", label_column)),
 
 
1833
  )
1834
  except Exception as exc:
 
 
 
1835
  return (
1836
  f"Training failed: {exc}",
1837
  pd.DataFrame(),
1838
  {},
1839
  pd.DataFrame(),
1840
- _download_state(None),
1841
- _download_state(None),
1842
- _download_state(None),
1843
- _download_state(None),
1844
  gr.update(),
 
 
1845
  )
1846
 
1847
  def _check_progress(output_dir, model_filename, current_messages):
@@ -1897,6 +2062,8 @@ def build_interface() -> gr.Blocks:
1897
  metadata_download_button,
1898
  tensorboard_download_button,
1899
  label_input,
 
 
1900
  ],
1901
  concurrency_limit=EVENT_CONCURRENCY_LIMIT,
1902
  )
 
202
 
203
 
204
  APP_CSS = """
205
+ #available-files-section {
206
+ position: relative;
207
+ display: flex;
208
+ flex-direction: column;
209
+ gap: 0.75rem;
210
+ border-radius: 0.75rem;
211
+ }
212
+
213
+ #available-files-grid {
214
+ position: static;
215
+ overflow: visible;
216
+ }
217
+
218
+ #available-files-grid .form {
219
+ position: static;
220
+ min-height: 16rem;
221
+ }
222
+
223
+ #available-files-section:has(.gradio-loading) {
224
+ isolation: isolate;
225
+ }
226
+
227
  #available-files-grid .wrap {
228
  display: grid;
229
  grid-template-columns: repeat(4, minmax(0, 1fr));
230
  gap: 0.5rem;
231
  max-height: 24rem;
232
+ min-height: 16rem;
233
  overflow-y: auto;
234
  padding-right: 0.25rem;
235
  }
236
 
 
 
 
 
237
  #available-files-grid .wrap > div {
238
  min-width: 0;
239
  }
 
264
  #available-files-grid .gradio-loading {
265
  position: absolute;
266
  inset: 0;
267
+ width: auto;
268
+ height: auto;
269
+ min-height: 100%;
270
  display: flex;
271
  align-items: center;
272
  justify-content: center;
273
  background: rgba(10, 14, 23, 0.72);
274
  border-radius: 0.75rem;
275
  z-index: 10;
276
+ padding: 1.5rem;
277
+ pointer-events: auto;
278
+ }
279
+
280
+ #available-files-grid .gradio-loading > * {
281
+ width: 100%;
282
+ }
283
+
284
+ #available-files-grid .gradio-loading progress,
285
+ #available-files-grid .gradio-loading .progress-bar,
286
+ #available-files-grid .gradio-loading .loading-progress,
287
+ #available-files-grid .gradio-loading [role="progressbar"],
288
+ #available-files-grid .gradio-loading .wrap,
289
+ #available-files-grid .gradio-loading .inner {
290
+ width: 100% !important;
291
+ max-width: none !important;
292
+ }
293
+
294
+ #available-files-grid .gradio-loading .status,
295
+ #available-files-grid .gradio-loading .message,
296
+ #available-files-grid .gradio-loading .label {
297
+ text-align: center;
298
  }
299
 
300
  #date-browser-row {
 
326
 
327
  #download-selected-button {
328
  width: 100%;
329
+ position: relative;
330
+ z-index: 0;
331
  }
332
 
333
  #download-selected-button .gradio-button {
 
975
  return (base / fallback).resolve()
976
 
977
 
978
+ ARTIFACT_FILE_EXTENSIONS: Tuple[str, ...] = (
979
+ ".keras",
980
+ ".h5",
981
+ ".joblib",
982
+ ".pkl",
983
+ ".json",
984
+ ".onnx",
985
+ ".zip",
986
+ ".txt",
987
+ )
988
+
989
+
990
+ def gather_directory_choices(current: Optional[str]) -> Tuple[List[str], str]:
991
+ base = normalise_output_directory(current or str(MODEL_OUTPUT_DIR))
992
+ candidates = {str(base)}
993
+ try:
994
+ for candidate in base.parent.iterdir():
995
+ if candidate.is_dir():
996
+ candidates.add(str(candidate.resolve()))
997
+ except Exception:
998
+ pass
999
+ return sorted(candidates), str(base)
1000
+
1001
+
1002
+ def gather_artifact_choices(
1003
+ directory: Optional[str], selection: Optional[str] = None
1004
+ ) -> Tuple[List[Tuple[str, str]], Optional[str]]:
1005
+ base = normalise_output_directory(directory)
1006
+ choices: List[Tuple[str, str]] = []
1007
+ selected_value: Optional[str] = None
1008
+ if base.exists():
1009
+ try:
1010
+ artifacts = sorted(
1011
+ [
1012
+ path
1013
+ for path in base.iterdir()
1014
+ if path.is_file()
1015
+ and (
1016
+ not ARTIFACT_FILE_EXTENSIONS
1017
+ or path.suffix.lower() in ARTIFACT_FILE_EXTENSIONS
1018
+ )
1019
+ ],
1020
+ key=lambda path: path.name.lower(),
1021
+ )
1022
+ choices = [(artifact.name, str(artifact)) for artifact in artifacts]
1023
+ except Exception:
1024
+ choices = []
1025
+
1026
+ if selection and any(value == selection for _, value in choices):
1027
+ selected_value = selection
1028
+ elif choices:
1029
+ selected_value = choices[0][1]
1030
+
1031
+ return choices, selected_value
1032
+
1033
+
1034
+ def download_button_state(path: Optional[Union[str, Path]]):
1035
+ if not path:
1036
+ return gr.update(value=None, visible=False)
1037
+ candidate = Path(path)
1038
+ if candidate.exists():
1039
+ return gr.update(value=str(candidate), visible=True)
1040
+ return gr.update(value=None, visible=False)
1041
+
1042
+
1043
  def clear_training_files():
1044
  default_label = LABEL_COLUMN or "Fault"
1045
  for cached_file in TRAINING_UPLOAD_DIR.glob("*"):
 
1614
  day_download_button = gr.Button(
1615
  "⬇️ Download day CSVs", variant="secondary"
1616
  )
1617
+ with gr.Column(elem_id="available-files-section"):
1618
+ available_files = gr.CheckboxGroup(
1619
+ label="Available CSV files",
1620
+ choices=[],
1621
+ value=[],
1622
+ elem_id="available-files-grid",
1623
+ )
1624
+ download_button = gr.Button(
1625
+ "⬇️ Download selected CSVs",
1626
+ variant="secondary",
1627
+ elem_id="download-selected-button",
1628
+ )
1629
  repo_status = gr.Markdown(
1630
  "Click 'Reload dataset from database' to fetch the directory tree."
1631
  )
 
1688
  label="Epochs",
1689
  )
1690
 
1691
+ directory_choices, directory_default = gather_directory_choices(
1692
+ str(MODEL_OUTPUT_DIR)
1693
+ )
1694
+ artifact_choices, default_artifact = gather_artifact_choices(
1695
+ directory_default
1696
+ )
1697
+
1698
  with gr.Row():
1699
+ output_directory = gr.Dropdown(
1700
+ value=directory_default,
1701
  label="Output directory",
1702
+ choices=directory_choices,
1703
+ allow_custom_value=True,
1704
  )
1705
  model_name = gr.Textbox(
1706
  value=model_default,
 
1715
  label="Metadata output filename",
1716
  )
1717
 
1718
+ with gr.Row():
1719
+ artifact_browser = gr.Dropdown(
1720
+ label="Saved artifacts in directory",
1721
+ choices=artifact_choices,
1722
+ value=default_artifact,
1723
+ )
1724
+ artifact_download_button = gr.DownloadButton(
1725
+ "⬇️ Download selected artifact",
1726
+ value=default_artifact,
1727
+ visible=bool(default_artifact),
1728
+ variant="secondary",
1729
+ )
1730
+
1731
+ def on_output_directory_change(selected_dir, current_selection):
1732
+ choices, normalised = gather_directory_choices(selected_dir)
1733
+ artifact_options, selected = gather_artifact_choices(
1734
+ normalised, current_selection
1735
+ )
1736
+ return (
1737
+ gr.update(choices=choices, value=normalised),
1738
+ gr.update(choices=artifact_options, value=selected),
1739
+ download_button_state(selected),
1740
+ )
1741
+
1742
+ def on_artifact_change(selected_path):
1743
+ return download_button_state(selected_path)
1744
+
1745
+ output_directory.change(
1746
+ on_output_directory_change,
1747
+ inputs=[output_directory, artifact_browser],
1748
+ outputs=[output_directory, artifact_browser, artifact_download_button],
1749
+ concurrency_limit=EVENT_CONCURRENCY_LIMIT,
1750
+ )
1751
+
1752
+ artifact_browser.change(
1753
+ on_artifact_change,
1754
+ inputs=[artifact_browser],
1755
+ outputs=[artifact_download_button],
1756
+ concurrency_limit=EVENT_CONCURRENCY_LIMIT,
1757
+ )
1758
+
1759
  with gr.Row(elem_id="artifact-download-row"):
1760
  model_download_button = gr.DownloadButton(
1761
  "⬇️ Download model file",
 
1850
  metadata_filename,
1851
  enable_tensorboard,
1852
  ):
1853
+ base_dir = normalise_output_directory(output_dir)
 
 
 
 
 
 
 
1854
  try:
 
1855
  base_dir.mkdir(parents=True, exist_ok=True)
1856
 
1857
  model_path = resolve_output_path(
 
1974
  skipped = ", ".join(missing_paths)
1975
  status = f"⚠️ Skipped missing files: {skipped}\n" + status
1976
 
1977
+ artifact_choices, selected_artifact = gather_artifact_choices(
1978
+ str(base_dir), result["model_path"]
1979
+ )
1980
+
1981
  return (
1982
  status,
1983
  report_df,
1984
  result["history"],
1985
  confusion_df,
1986
+ download_button_state(result["model_path"]),
1987
+ download_button_state(result["scaler_path"]),
1988
+ download_button_state(result["metadata_path"]),
1989
+ download_button_state(tensorboard_zip),
1990
  gr.update(value=result.get("label_column", label_column)),
1991
+ gr.update(choices=artifact_choices, value=selected_artifact),
1992
+ download_button_state(selected_artifact),
1993
  )
1994
  except Exception as exc:
1995
+ artifact_choices, selected_artifact = gather_artifact_choices(
1996
+ str(base_dir)
1997
+ )
1998
  return (
1999
  f"Training failed: {exc}",
2000
  pd.DataFrame(),
2001
  {},
2002
  pd.DataFrame(),
2003
+ download_button_state(None),
2004
+ download_button_state(None),
2005
+ download_button_state(None),
2006
+ download_button_state(None),
2007
  gr.update(),
2008
+ gr.update(choices=artifact_choices, value=selected_artifact),
2009
+ download_button_state(selected_artifact),
2010
  )
2011
 
2012
  def _check_progress(output_dir, model_filename, current_messages):
 
2062
  metadata_download_button,
2063
  tensorboard_download_button,
2064
  label_input,
2065
+ artifact_browser,
2066
+ artifact_download_button,
2067
  ],
2068
  concurrency_limit=EVENT_CONCURRENCY_LIMIT,
2069
  )