fix: sshfs polling, WebSocket broadcast, prompt progress bar, resizable stats panel

- log_watcher: add --reopen-log mode (close/reopen each poll) for sshfs/network
  filesystems; fix nonlocal clients bug causing UnboundLocalError on first broadcast;
  fix list(clients) copy to prevent RuntimeError on concurrent set mutation
- log_parser: capture progress=X.XX from prompt timing lines into Metrics.prompt_progress
- frontend: show progress bar during processing_prompt state with PROMPT N% label;
  widen stats panel to 300px; remove model name truncation; add drag handle on
  right border of stats panel to resize between 280px–840px

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad Doloman
2026-06-22 21:02:34 +03:00
parent fddbd8f4d4
commit 311819b51c
6 changed files with 157 additions and 17 deletions

View File

@@ -37,10 +37,11 @@ class LoadingInfo:
@dataclass
class Metrics:
prompt_speed: Optional[float] = None # tokens/sec during prompt eval
prompt_tokens: Optional[int] = None # total prompt tokens
gen_speed: Optional[float] = None # tokens/sec during generation
n_decoded: Optional[int] = None # tokens decoded so far
prompt_speed: Optional[float] = None # tokens/sec during prompt eval
prompt_tokens: Optional[int] = None # total prompt tokens
prompt_progress: Optional[float] = None # 0.01.0 from live prompt timing lines
gen_speed: Optional[float] = None # tokens/sec during generation
n_decoded: Optional[int] = None # tokens decoded so far
@dataclass
@@ -72,7 +73,7 @@ _IDLE_RE = re.compile(r'update_slots: all slots are idle')
_PROXY_RE = re.compile(r'proxy_reques: proxying request')
_LAUNCH_RE = re.compile(r'slot launch_slot_:.*processing task')
_PROMPT_TIMING_RE = re.compile(
r'slot print_timing:.*prompt processing, n_tokens =\s+(\d+),.*/ ([\d.]+) tokens per second'
r'slot print_timing:.*prompt processing, n_tokens =\s+(\d+), progress = ([\d.]+),.*/ ([\d.]+) tokens per second'
)
_GEN_TIMING_RE = re.compile(
r'slot print_timing:.*n_decoded =\s+(\d+), tg =\s+([\d.]+) t/s'
@@ -137,8 +138,9 @@ class LogParser:
return self._set(state="processing_prompt")
if m := _PROMPT_TIMING_RE.search(content):
metrics = Metrics(
prompt_speed=float(m.group(2)),
prompt_speed=float(m.group(3)),
prompt_tokens=int(m.group(1)),
prompt_progress=float(m.group(2)),
gen_speed=self._state.metrics.gen_speed,
n_decoded=self._state.metrics.n_decoded,
)