| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- import logging
- from textual.app import App
- from textual.containers import Horizontal
- from textual.widgets import Label, ProgressBar
- logger = logging.getLogger(__name__)
- # Number of stacked progress-bar rows (levels 0 .. MAX_LEVELS-1). Deep enough for
- # the k-fold nesting: root(0) -> run_kfold(1) -> fold task(2) -> train/eval(3) ->
- # epochs/passes(4) -> batches(5). main.py builds exactly this many rows.
- MAX_LEVELS = 6
- class ProgressTracker:
- """Manages hierarchical progress bars for the UI thread."""
- def __init__(self, app: App, level: int = 0):
- self.app = app
- self.level = level
- def get_sub_tracker(self, title: str = "") -> "ProgressTracker":
- """Returns a sub-tracker for child processes."""
- if self.level >= MAX_LEVELS - 1:
- return self # Max depth reached
- sub = ProgressTracker(self.app, self.level + 1)
- if title:
- sub.set_title(title)
- return sub
- def set_title(self, title: str) -> None:
- def _update_title():
- try:
- self.app.query_one(f"#progress_title_{self.level}", Label).update(title)
- except Exception:
- pass
- self.app.call_from_thread(_update_title)
- def update(self, total: int | None = None, advance: int = 0) -> None:
- def _do_update():
- try:
- bar = self.app.query_one(f"#progress_bar_{self.level}", ProgressBar)
- row = self.app.query_one(f"#progress_row_{self.level}", Horizontal)
- stats = self.app.query_one(f"#progress_stats_{self.level}", Label)
- if total is not None:
- bar.update(total=total)
- if advance > 0:
- bar.advance(advance)
- # Format Completed / Total label
- if bar.total is not None and bar.total > 0:
- stats.update(f"{int(bar.progress)} / {int(bar.total)}")
- else:
- stats.update("")
- # Visibility logic: Visible only when value/total is non-zero
- is_active = (bar.progress > 0) or (
- bar.total is not None and bar.total > 0
- )
- row.display = is_active
- # Cascading clear logic: Clear sub-bars if we advance or reset
- if advance > 0 or total == 0:
- self._clear_subs()
- except Exception as e:
- logger.error(f"Failed to update progress: {e}")
- self.app.call_from_thread(_do_update)
- def reset(self) -> None:
- """Clears and hides this progress bar and cascades to all children."""
- def _do_reset():
- try:
- bar = self.app.query_one(f"#progress_bar_{self.level}", ProgressBar)
- row = self.app.query_one(f"#progress_row_{self.level}", Horizontal)
- title = self.app.query_one(f"#progress_title_{self.level}", Label)
- stats = self.app.query_one(f"#progress_stats_{self.level}", Label)
- bar.progress = 0
- bar.total = None
- title.update("")
- stats.update("")
- row.display = False
- self._clear_subs()
- except Exception:
- pass
- pass
- self.app.call_from_thread(_do_reset)
- def _clear_subs(self) -> None:
- """Clears and hides all child progress bars (must run in UI thread)."""
- for l in range(self.level + 1, MAX_LEVELS):
- try:
- bar = self.app.query_one(f"#progress_bar_{l}", ProgressBar)
- row = self.app.query_one(f"#progress_row_{l}", Horizontal)
- title = self.app.query_one(f"#progress_title_{l}", Label)
- stats = self.app.query_one(f"#progress_stats_{l}", Label)
- bar.progress = 0
- bar.total = None
- title.update("")
- stats.update("")
- row.display = False
- except Exception:
- pass
|