VincentCroft commited on
Commit ·
a2b637a
1
Parent(s): e9cfe70
Expand CSV loading overlay to cover download button
Browse files
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 |
-
|
| 1507 |
-
|
| 1508 |
-
|
| 1509 |
-
|
| 1510 |
-
|
| 1511 |
-
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
|
| 1515 |
-
|
| 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.
|
| 1581 |
-
value=
|
| 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 |
-
|
| 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 |
-
|
| 1829 |
-
|
| 1830 |
-
|
| 1831 |
-
|
| 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 |
-
|
| 1841 |
-
|
| 1842 |
-
|
| 1843 |
-
|
| 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 |
)
|