AQIT 0.1.0
Loading...
Searching...
No Matches
load_model_display.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""Quiet surfaces for `aquin load model` / `aquin unload`."""
3
4from __future__ import annotations
5
6import sys
7import time
8from typing import Any, Callable
9
10from aquin.tui.spinners import bullet_mark, spinner_frames, spinner_interval_ms
11
12
14 *,
15 model: str,
16 mode: str,
17 daemon: bool,
18 ok: bool = True,
19 error: str | None = None,
20 elapsed_s: float | None = None,
21 vram: str | None = None,
22) -> dict[str, Any]:
23 out: dict[str, Any] = {
24 "kind": "load-model",
25 "ok": ok,
26 "model": model,
27 "mode": mode,
28 "daemon": daemon,
29 }
30 if error:
31 out["error"] = error
32 if elapsed_s is not None:
33 out["elapsed_s"] = round(elapsed_s, 1)
34 if vram:
35 out["vram"] = vram
36 return out
37
38
40 *,
41 ok: bool,
42 daemon_running: bool,
43 stopped: bool,
44 message: str,
45) -> dict[str, Any]:
46 return {
47 "kind": "unload",
48 "ok": ok,
49 "daemonRunning": daemon_running,
50 "stopped": stopped,
51 "message": message,
52 }
53
54
55def print_phase(message: str, *, indent: int = 0, settled: bool = True) -> None:
56 prefix = " " * indent
57 mark = bullet_mark() if settled else "-"
58 print(f"{prefix}{mark} {message}", flush=True)
60
61def _format_elapsed(seconds: float) -> str:
62 s = int(seconds)
63 if s < 60:
64 return f"{s}s"
65 return f"{s // 60}m {s % 60}s"
66
67
68class LoadProgress:
69 """
70 Ollama-style load progress — one headline, indented phases, optional spinner.
71 """
73 def __init__(self, headline: str) -> None:
74 self.headline = headline
75 self._start = time.monotonic()
76 self._last_phase = ""
77 self._spinner_idx = 0
78 self._frames = spinner_frames("dots")
79 self._interval = spinner_interval_ms("dots") / 1000.0
80 self._last_spin = 0.0
81 print(self.headline, flush=True)
83 def phase(self, message: str, *, indent: int = 1) -> None:
84 self._last_phase = message
85 elapsed = _format_elapsed(time.monotonic() - self._start)
86 prefix = " " * indent
87 print(f"{prefix}{bullet_mark()} {message} · {elapsed}", flush=True)
88
89 def tick(self, message: str | None = None) -> None:
90 """Update in-place spinner line when on a TTY (daemon poll loop)."""
91 if not sys.stdout.isatty():
92 return
93 now = time.monotonic()
94 if now - self._last_spin < self._interval:
95 return
96 self._last_spin = now
97 self._spinner_idx = (self._spinner_idx + 1) % len(self._frames)
98 frame = self._frames[self._spinner_idx]
99 text = message or self._last_phase or "loading"
100 elapsed = _format_elapsed(now - self._start)
101 line = f" {frame} {text} · {elapsed}"
102 sys.stdout.write(f"\r{line}\x1b[K")
103 sys.stdout.flush()
104
105 def done(self, message: str, *, indent: int = 0) -> None:
106 if sys.stdout.isatty() and self._last_phase:
107 sys.stdout.write("\r\x1b[K")
108 sys.stdout.flush()
109 elapsed = _format_elapsed(time.monotonic() - self._start)
110 prefix = " " * indent
111 print(f"{prefix}{bullet_mark()} {message} · {elapsed}", flush=True)
112
113 def elapsed(self) -> float:
114 return time.monotonic() - self._start
115
116
118 *,
119 target_model: str,
120 poll: Callable[[], dict | None],
121 timeout: float = 1800.0,
122 on_tick: LoadProgress | None = None,
123) -> dict:
124 """
125 Poll daemon health until target_model is ready or error/timeout.
126 Returns {"ok": True, "model_id": ...} or {"ok": False, "error": ...}.
127 """
128 deadline = time.time() + timeout
129 loading_model = target_model
130 while time.time() < deadline:
131 h = poll()
132 if h is None:
133 return {
134 "ok": False,
135 "error": "background engine stopped responding while loading the model",
136 }
137 loaded = h.get("model_id")
138 model_status = h.get("model_status")
139 loading_model = h.get("loading_model_id") or loading_model
140 elapsed = h.get("elapsed_s")
141 if on_tick:
142 if model_status == "loading":
143 msg = f"loading {loading_model}"
144 if elapsed is not None:
145 msg = f"{msg}"
146 on_tick.tick(msg)
147 elif model_status == "ready" and loaded == target_model:
148 on_tick.tick(f"{target_model} ready")
149
150 if loaded == target_model and model_status == "ready":
151 return {"ok": True, "model_id": loaded, "vram": h.get("vram")}
152 if model_status == "error":
153 detail = h.get("last_error") or "unknown load failure"
154 failed = h.get("loading_model_id") or h.get("model_id") or target_model
155 return {"ok": False, "error": f"{failed}: {detail}"}
156 time.sleep(0.4)
157
158 return {
159 "ok": False,
160 "error": f"timed out waiting for background engine to finish loading {loading_model}",
161 }
None tick(self, str|None message=None)
None done(self, str message, *, int indent=0)
None phase(self, str message, *, int indent=1)
str _format_elapsed(float seconds)
dict watch_daemon_load(*, str target_model, Callable[[], dict|None] poll, float timeout=1800.0, LoadProgress|None on_tick=None)
dict[str, Any] unload_payload(*, bool ok, bool daemon_running, bool stopped, str message)
dict[str, Any] load_model_payload(*, str model, str mode, bool daemon, bool ok=True, str|None error=None, float|None elapsed_s=None, str|None vram=None)
None print_phase(str message, *, int indent=0, bool settled=True)