aboutsummaryrefslogtreecommitdiff
path: root/app/ui
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-28 02:43:27 -0400
committerhistoria <historiavg@proton.me>2026-08-28 02:43:27 -0400
commit5c3db8f500ff206f3a675d8f4184cb0d61f94804 (patch)
treeeab36bc6ff0e55bd27871d9154f1bc0764f20af6 /app/ui
parenta7c653313d2bb1e185cfbc3f0f52c2fe33218600 (diff)
downloadtts-audiobook-generator-5c3db8f500ff206f3a675d8f4184cb0d61f94804.tar.gz
feat: cuda arch detection for shorter builds, build failure detection
Diffstat (limited to 'app/ui')
-rw-r--r--app/ui/taskview.py54
1 files changed, 50 insertions, 4 deletions
diff --git a/app/ui/taskview.py b/app/ui/taskview.py
index c5c2ff7..d7aed96 100644
--- a/app/ui/taskview.py
+++ b/app/ui/taskview.py
@@ -63,6 +63,11 @@ _PROGRESS_COUNT = re.compile(r"\[(\d+)/(\d+)\]")
# A spinner frame set for the running step marker.
_SPINNER = ("|", "/", "-", "\\")
+# How long a running step may stay silent (no output lines) before the
+# view starts saying so next to its elapsed clock — an early "this looks
+# wedged" cue for the no-output watchdog that eventually kills the step.
+_SILENCE_CUE_SECS = 60
+
@dataclass
class TaskStep:
@@ -117,6 +122,27 @@ def _progress_match(text: str) -> Optional[Tuple[float, float, str]]:
return None
+def _silent_secs(last_line_at: Optional[float], now: float
+ ) -> Optional[float]:
+ """Seconds the running step has been silent, or None (no cue).
+
+ None when nothing is tracked (no output yet is not tracked here — the
+ caller decides), or when the silence is still below the cue threshold.
+ Module-level for testability.
+ """
+ if last_line_at is None:
+ return None
+ silent = now - last_line_at
+ if silent < _SILENCE_CUE_SECS:
+ return None
+ return silent
+
+
+def _silence_cue_text(silent: float) -> str:
+ """The dim text shown next to the elapsed clock for SILENT seconds."""
+ return f"(no output {int(silent // 60)}m)"
+
+
def _lane_step_mark(current: Optional[int],
results: List[Optional[int]],
cancelled_step: Optional[int],
@@ -208,6 +234,7 @@ class TaskView(ScreenView):
self._progress: Optional[Tuple[float, float]] = None # (done, total)
self._progress_kind = "" # "bytes" | "percent" | "count" | ""
self.step_started: List[Optional[float]] = [None] * len(steps)
+ self.last_line_at: Optional[float] = None # silence cue (see _SILENCE_CUE_SECS)
self.finished_at: Optional[float] = None
self.cancelled = False
self.cancelling = False
@@ -265,6 +292,7 @@ class TaskView(ScreenView):
if kind == "step_start":
self.current = event["index"]
self.step_started[self.current] = self._now()
+ self.last_line_at = self._now()
self._progress = None
self._progress_kind = ""
elif kind == "line":
@@ -274,11 +302,13 @@ class TaskView(ScreenView):
index = event["index"]
self.results[index] = event.get("rc") or 0
self.current = None
+ self.last_line_at = None
self._progress = None
self._progress_kind = ""
elif kind == "step_cancelled":
self.cancelled_step = event["index"]
self.current = None
+ self.last_line_at = None
self._progress = None
self._progress_kind = ""
elif kind == "finish":
@@ -290,6 +320,7 @@ class TaskView(ScreenView):
def _ingest_line(self, text: str) -> None:
"""Fold one output line into the log tail and progress bar."""
line = text.rstrip("\r\n")
+ self.last_line_at = self._now()
if not line:
return
match = _progress_match(line)
@@ -371,9 +402,14 @@ class TaskView(ScreenView):
_text(scr, theme, y, inner_x + 5, label, theme["body"])
if index == self.current and self.phase not in _TERMINAL:
started = self.step_started[index] or self._now()
+ elapsed_text = f" {_format_elapsed(self._now() - started)}"
_text(scr, theme, y, inner_x + 5 + len(label) + 1,
- f" {_format_elapsed(self._now() - started)}",
- theme["dim"])
+ elapsed_text, theme["dim"])
+ silent = _silent_secs(self.last_line_at, self._now())
+ if silent is not None:
+ _text(scr, theme, y,
+ inner_x + 5 + len(label) + 1 + len(elapsed_text),
+ f" {_silence_cue_text(silent)}", theme["dim"])
y += 1
y += 1
@@ -581,6 +617,7 @@ class _LaneState:
self.progress: Optional[Tuple[float, float]] = None
self.progress_kind = ""
self.step_started: List[Optional[float]] = [None] * len(self.steps)
+ self.last_line_at: Optional[float] = None # silence cue
self.cancelled_step: Optional[int] = None
self.rc = 0
self.finished = False
@@ -664,6 +701,7 @@ class LanesView(_GetchModes):
if kind == "step_start":
lane.current = event["index"]
lane.step_started[lane.current] = self._now()
+ lane.last_line_at = self._now()
lane.progress = None
lane.progress_kind = ""
elif kind == "line":
@@ -671,11 +709,13 @@ class LanesView(_GetchModes):
elif kind == "step_done":
lane.results[event["index"]] = event.get("rc") or 0
lane.current = None
+ lane.last_line_at = None
lane.progress = None
lane.progress_kind = ""
elif kind == "step_cancelled":
lane.cancelled_step = event["index"]
lane.current = None
+ lane.last_line_at = None
lane.progress = None
lane.progress_kind = ""
elif kind == "lane_finish":
@@ -685,6 +725,7 @@ class LanesView(_GetchModes):
def _ingest_lane_line(self, lane: _LaneState, text: str) -> None:
"""Fold one output line into LANE's log tail and progress bar."""
line = text.rstrip("\r\n")
+ lane.last_line_at = self._now()
if not line:
return
match = _progress_match(line)
@@ -862,9 +903,14 @@ class LanesView(_GetchModes):
_text(scr, theme, row, x + 6, label, theme["body"])
if index == lane.current and not terminal:
started = lane.step_started[index] or self._now()
+ elapsed_text = f" {_format_elapsed(self._now() - started)}"
_text(scr, theme, row, x + 6 + len(label) + 1,
- f" {_format_elapsed(self._now() - started)}",
- theme["dim"])
+ elapsed_text, theme["dim"])
+ silent = _silent_secs(lane.last_line_at, self._now())
+ if silent is not None:
+ _text(scr, theme, row,
+ x + 6 + len(label) + 1 + len(elapsed_text),
+ f" {_silence_cue_text(silent)}", theme["dim"])
row += 1
row += 1