From 15b467221bfda9ed3b00a0145d485c892abe0e57 Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:25:37 -0500 Subject: [PATCH 1/6] Update routes.py --- inference_worker/web/routes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inference_worker/web/routes.py b/inference_worker/web/routes.py index 70995d1..afb5bf1 100644 --- a/inference_worker/web/routes.py +++ b/inference_worker/web/routes.py @@ -87,7 +87,7 @@ async def auth_guard(request: Request, call_next): @app.get("/login", response_class=HTMLResponse) async def login_page(request: Request): next_url = request.query_params.get("next", "/") - return templates.TemplateResponse("login.html", {"request": request, "next": next_url}) + return templates.TemplateResponse(request, "login.html", {"request": request, "next": next_url}) @app.post("/login") @@ -101,7 +101,7 @@ async def login_submit(request: Request): "_token", token, httponly=True, samesite="lax", max_age=86400 * 365, ) return response - return templates.TemplateResponse("login.html", { + return templates.TemplateResponse(request, "login.html", { "request": request, "next": next_url, "error": "Invalid token", }) @@ -113,7 +113,7 @@ async def login_submit(request: Request): async def setup_page(request: Request): # Don't run detect_backends() here — it scans 8+ ports (3s timeout each) and blocks 30–45s. # The setup page calls POST /api/setup/detect on load instead; page loads instantly. - return templates.TemplateResponse("setup.html", { + return templates.TemplateResponse(request, "setup.html", { "request": request, "detection": DetectionResult(), "platform": get_platform(), @@ -273,7 +273,7 @@ async def api_complete_setup(request: Request): # --------------------------------------------------------------------------- @app.get("/", response_class=HTMLResponse) async def dashboard(request: Request): - return templates.TemplateResponse("dashboard.html", { + return templates.TemplateResponse(request, "dashboard.html", { "request": request, "worker_running": worker_state["running"], "worker_error": worker_state.get("error"), @@ -316,7 +316,7 @@ async def api_status(): # --------------------------------------------------------------------------- @app.get("/logs", response_class=HTMLResponse) async def logs_page(request: Request): - return templates.TemplateResponse("logs.html", {"request": request}) + return templates.TemplateResponse(request, "logs.html", {"request": request}) @app.get("/api/logs") @@ -329,7 +329,7 @@ async def api_logs(): # --------------------------------------------------------------------------- @app.get("/settings", response_class=HTMLResponse) async def settings_page(request: Request): - return templates.TemplateResponse("settings.html", { + return templates.TemplateResponse(request, "settings.html", { "request": request, "settings": { "GRID_API_KEY": Settings.GRID_API_KEY, From 26fca9d4cfd37fddb739fdc23c0a43c16d593264 Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:26:55 -0500 Subject: [PATCH 2/6] Update pyproject.toml --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cf7f952..e3ded01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,10 @@ requires-python = ">=3.9" dependencies = [ "httpx", "python-dotenv", - "fastapi>=0.100.0", + # fastapi>=0.110 bundles starlette>=0.36, which has the modern + # TemplateResponse(request, name, context) signature the dashboard uses. The old + # >=0.100 floor allowed a starlette where that call broke on fresh installs (issue #2). + "fastapi>=0.110.0", "uvicorn[standard]>=0.20.0", "jinja2>=3.1.0", "websockets>=13.0", From 0d4eb294e0dd5f26831aa898ed06ed5be1ca45ea Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Tue, 23 Jun 2026 12:37:44 -0500 Subject: [PATCH 3/6] Update routes.py fix indent error --- inference_worker/web/routes.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inference_worker/web/routes.py b/inference_worker/web/routes.py index afb5bf1..ca966ad 100644 --- a/inference_worker/web/routes.py +++ b/inference_worker/web/routes.py @@ -87,7 +87,7 @@ async def auth_guard(request: Request, call_next): @app.get("/login", response_class=HTMLResponse) async def login_page(request: Request): next_url = request.query_params.get("next", "/") - return templates.TemplateResponse(request, "login.html", {"request": request, "next": next_url}) + return templates.TemplateResponse(request, "login.html", {"request": request, "next": next_url}) @app.post("/login") @@ -101,7 +101,7 @@ async def login_submit(request: Request): "_token", token, httponly=True, samesite="lax", max_age=86400 * 365, ) return response - return templates.TemplateResponse(request, "login.html", { + return templates.TemplateResponse(request, "login.html", { "request": request, "next": next_url, "error": "Invalid token", }) @@ -113,7 +113,7 @@ async def login_submit(request: Request): async def setup_page(request: Request): # Don't run detect_backends() here — it scans 8+ ports (3s timeout each) and blocks 30–45s. # The setup page calls POST /api/setup/detect on load instead; page loads instantly. - return templates.TemplateResponse(request, "setup.html", { + return templates.TemplateResponse(request, "setup.html", { "request": request, "detection": DetectionResult(), "platform": get_platform(), @@ -273,7 +273,7 @@ async def api_complete_setup(request: Request): # --------------------------------------------------------------------------- @app.get("/", response_class=HTMLResponse) async def dashboard(request: Request): - return templates.TemplateResponse(request, "dashboard.html", { + return templates.TemplateResponse(request, "dashboard.html", { "request": request, "worker_running": worker_state["running"], "worker_error": worker_state.get("error"), @@ -316,7 +316,7 @@ async def api_status(): # --------------------------------------------------------------------------- @app.get("/logs", response_class=HTMLResponse) async def logs_page(request: Request): - return templates.TemplateResponse(request, "logs.html", {"request": request}) + return templates.TemplateResponse(request, "logs.html", {"request": request}) @app.get("/api/logs") @@ -329,7 +329,7 @@ async def api_logs(): # --------------------------------------------------------------------------- @app.get("/settings", response_class=HTMLResponse) async def settings_page(request: Request): - return templates.TemplateResponse(request, "settings.html", { + return templates.TemplateResponse(request, "settings.html", { "request": request, "settings": { "GRID_API_KEY": Settings.GRID_API_KEY, From cf902c22820865531415135cd565d3046bc51c1c Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:22:53 -0500 Subject: [PATCH 4/6] fix vision autodetection --- inference_worker/ws_client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inference_worker/ws_client.py b/inference_worker/ws_client.py index 3aa5ccf..9468cfd 100644 --- a/inference_worker/ws_client.py +++ b/inference_worker/ws_client.py @@ -142,8 +142,8 @@ def __init__(self, name: str | None = None, spec=None): # model image-capable and the chat UI enables image upload. Either the # operator declared them, or we auto-detect at connect() (see # _detect_vision). Default text-only until then. - self.modalities: list[str] = list(getattr(spec, "modalities", None) or ["text"]) - self.modalities_declared: bool = bool(getattr(spec, "modalities_declared", False)) + self.modalities: list[str] = spec.modalities + self.modalities_declared: bool = spec.modalities_declared self._signer = get_signer() self.signer_address = self._signer.address if self._signer else "" self._reconnect_delay = 1 From 13e70b510470246fefe491505f8eadb4ae70be8d Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:23:25 -0500 Subject: [PATCH 5/6] fix vision autodetect --- inference_worker/config.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/inference_worker/config.py b/inference_worker/config.py index c88c851..7d19cfb 100644 --- a/inference_worker/config.py +++ b/inference_worker/config.py @@ -180,15 +180,11 @@ def _parse_modalities(s: dict) -> list[str]: `"modalities": ["text","image"]` or the shorthand `"vision": true`. Text is always included. (A validator can later VERIFY this claim and revoke a lie.) """ - mods = s.get("modalities") - if isinstance(mods, list) and mods: - out = [m for m in mods if m in ("text", "image", "video")] - elif s.get("vision"): - out = ["text", "image"] - else: - out = ["text"] + out = s.get("modalities") if "text" not in out: - out = ["text"] + out + out.append("text") + if s.get("vision"): + out.append("image") return out @@ -239,6 +235,6 @@ def load_backends() -> list[Backend]: schedule=Settings.GRID_SCHEDULE, # Honor GRID_MODALITIES / GRID_VISION as an explicit override; otherwise # default to text-only and let connect() auto-detect. - modalities=_parse_modalities({"modalities": _env_mods, "vision": Settings.VISION}), modalities_declared=bool(_env_mods or Settings.VISION), + modalities=_parse_modalities({"modalities": _env_mods, "vision": Settings.VISION}), )] From 9a3f097dedb15b4b03d40a626b11d4f17de025d6 Mon Sep 17 00:00:00 2001 From: alsldlflglhljlk <119443367+alsldlflglhljlk@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:40:02 -0500 Subject: [PATCH 6/6] don't do autodetect if GRID_VISION is set to something random --- inference_worker/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inference_worker/config.py b/inference_worker/config.py index 7d19cfb..a0fdcea 100644 --- a/inference_worker/config.py +++ b/inference_worker/config.py @@ -108,7 +108,7 @@ class Settings: # use the per-backend "modalities"/"vision" keys instead. MODALITIES = os.getenv("GRID_MODALITIES", "").strip() VISION = os.getenv("GRID_VISION", "").lower() in ("1", "true", "yes", "on") - + # Streaming (WebSocket /v1) is the only live mode — the legacy /v2 poll # queue is retired. Defaults ON; set "false" only knowingly (the worker # refuses rather than poll the dead /v2 endpoint). @@ -235,6 +235,6 @@ def load_backends() -> list[Backend]: schedule=Settings.GRID_SCHEDULE, # Honor GRID_MODALITIES / GRID_VISION as an explicit override; otherwise # default to text-only and let connect() auto-detect. - modalities_declared=bool(_env_mods or Settings.VISION), + modalities_declared=bool(_env_mods or Settings.VISION or (os.getenv("GRID_VISION", "").lower() not in ("1", "true", "yes", "on"))), modalities=_parse_modalities({"modalities": _env_mods, "vision": Settings.VISION}), )]