fix(llm-tui): fix card layout overlap, handle HTTP 503 loading state, and auto-switch to status tab

This commit is contained in:
Noel Bautista 2026-10-21 13:05:56 -07:00
parent ca74d9f321
commit d3b0f9ba7a
No known key found for this signature in database

View file

@ -220,6 +220,15 @@ def check_port_status(port: int) -> tuple[bool, str]:
data = json.loads(response.read().decode()) data = json.loads(response.read().decode())
status = data.get("status", "unknown") status = data.get("status", "unknown")
return True, f"Online ({status})" return True, f"Online ({status})"
except urllib.error.HTTPError as e:
if e.code == 503:
try:
data = json.loads(e.read().decode())
msg = data.get("error", {}).get("message", "Loading model")
return True, f"Loading ({msg})"
except Exception:
return True, "Loading model..."
return False, f"HTTP Error {e.code}"
except Exception: except Exception:
return False, "Offline" return False, "Offline"
@ -246,6 +255,7 @@ class LLMConfigApp(App):
border: solid #3b4252; border: solid #3b4252;
padding: 1 2; padding: 1 2;
margin-bottom: 1; margin-bottom: 1;
height: auto;
} }
.title { .title {
color: #88c0d0; color: #88c0d0;
@ -302,18 +312,21 @@ class LLMConfigApp(App):
border-left: thick #88c0d0; border-left: thick #88c0d0;
padding: 1 2; padding: 1 2;
margin-bottom: 1; margin-bottom: 1;
height: auto;
} }
#vram-meter { #vram-meter {
background: #232834; background: #232834;
border: solid #4c566a; border: solid #4c566a;
padding: 1; padding: 1;
margin-top: 1; margin-top: 1;
height: auto;
} }
#status-card { #status-card {
background: #1e222b; background: #1e222b;
border: solid #4c566a; border: solid #4c566a;
padding: 1; padding: 1;
margin-bottom: 1; margin-bottom: 1;
height: auto;
} }
Log { Log {
background: #0f1117; background: #0f1117;
@ -339,6 +352,8 @@ class LLMConfigApp(App):
_sync_lock: bool = False _sync_lock: bool = False
_last_selected_model: str = "" _last_selected_model: str = ""
_last_selected_mmproj: str = "" _last_selected_mmproj: str = ""
_last_log_size: int = -1
_last_log_name: str = ""
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
yield Header(show_clock=True) yield Header(show_clock=True)
@ -672,7 +687,12 @@ class LLMConfigApp(App):
lines = [] lines = []
for port, label in [(11434, "Planner / Solo"), (11435, "Worker 1"), (11436, "Worker 2")]: for port, label in [(11434, "Planner / Solo"), (11435, "Worker 1"), (11436, "Worker 2")]:
online, msg = check_port_status(port) online, msg = check_port_status(port)
badge = "[green]● ONLINE[/]" if online else "[yellow]○ OFFLINE[/]" if not online:
badge = "[yellow]○ OFFLINE[/]"
elif "loading" in msg.lower():
badge = "[cyan]⏳ LOADING[/]"
else:
badge = "[green]● ONLINE[/]"
lines.append(f"Port {port} ({label}): {badge} - {msg}") lines.append(f"Port {port} ({label}): {badge} - {msg}")
card = self.query_one("#status-card", Static) card = self.query_one("#status-card", Static)
@ -684,8 +704,13 @@ class LLMConfigApp(App):
if logs: if logs:
latest = logs[0] latest = logs[0]
try: try:
cur_size = latest.stat().st_size
if cur_size == self._last_log_size and latest.name == self._last_log_name:
return
self._last_log_size = cur_size
self._last_log_name = latest.name
with open(latest, "r", errors="ignore") as f: with open(latest, "r", errors="ignore") as f:
f.seek(max(0, latest.stat().st_size - 8192)) f.seek(max(0, cur_size - 16384))
tail = f.read() tail = f.read()
log_widget.clear() log_widget.clear()
log_widget.write(f"--- Streaming {latest.name} ---\n" + tail) log_widget.write(f"--- Streaming {latest.name} ---\n" + tail)
@ -732,7 +757,8 @@ class LLMConfigApp(App):
) )
subprocess.Popen(cmd, env=env, cwd=str(SCRIPT_DIR), start_new_session=True) subprocess.Popen(cmd, env=env, cwd=str(SCRIPT_DIR), start_new_session=True)
self.notify(f"Launching {preset} server. Check Status & Live Logs tab.", severity="information") self.notify(f"Launching {preset} server. Switching to Status & Logs...", severity="information")
self.query_one(TabbedContent).active = "tab-status"
self.action_refresh_status() self.action_refresh_status()
def action_stop(self) -> None: def action_stop(self) -> None: