diff --git a/CHANGELOG.md b/CHANGELOG.md index 6902a65f..002f2ab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Status of the `main` branch. Changes prior to the next official version change w when using plugin version 2023.3.3+ * Dashboard: + - The version display now indicates when a newer Serena version is available - Fix: the "Last Execution" panel stayed on "Loading..." forever when there was no logged execution (e.g. a fresh server / no tool run yet), because `loadLastExecution()` only rendered the panel when the backend returned a non-null execution; the empty state is now rendered via the existing diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index b5a85495..80415b45 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -10,6 +10,7 @@ import urllib.error import urllib.request from collections.abc import Callable from dataclasses import dataclass +from functools import cached_property from html import escape from pathlib import Path from typing import TYPE_CHECKING, Any, Optional, Self @@ -26,7 +27,9 @@ from serena.config.serena_config import SerenaConfig, SerenaPaths from serena.constants import SERENA_DASHBOARD_DIR, SerenaPorts from serena.task_executor import TaskExecutor from serena.util.logging import MemoryLogHandler +from serena.util.pypi import PyPIPackageInfo from serena.util.pywebview import WebViewWithTray +from serena.util.version import Version if TYPE_CHECKING: from serena.agent import SerenaAgent @@ -71,6 +74,7 @@ class ResponseConfigOverview(BaseModel): encoding: str | None current_client: str | None serena_version: str + newer_serena_version: str | None class ResponseAvailableLanguages(BaseModel): @@ -224,6 +228,27 @@ class SerenaDashboardAPI: def memory_log_handler(self) -> MemoryLogHandler: return self._memory_log_handler + @cached_property + def _newer_serena_version(self) -> str | None: + """The version of a newer serena-agent package release on PyPI, if any. + + The result is computed only once (on first access) and cached thereafter. + + :return: the latest version available on PyPI if it is newer than the running version, else None + """ + try: + # query PyPI for the latest released version + latest_serena_version = PyPIPackageInfo("serena-agent").get_latest_version(timeout_secs=3) + + # compare against the running version + latest_version = Version(latest_serena_version) + current_version = Version(self._agent.version) + if not current_version.is_at_least(*latest_version.components): + return latest_serena_version + except Exception as e: + log.warning("Failed to check for newer Serena version on PyPI: %s", e) + return None + def _setup_routes(self) -> None: @self._app.route("/") def redirect_to_dashboard() -> Response: @@ -606,6 +631,7 @@ class SerenaDashboardAPI: encoding=encoding, current_client=Tool.get_last_tool_call_client_str(), serena_version=self._agent.version, + newer_serena_version=self._newer_serena_version, ) def _on_agent_config_changed(self) -> None: diff --git a/src/serena/resources/dashboard/dashboard.css b/src/serena/resources/dashboard/dashboard.css index fb2d70da..f8e4e1af 100644 --- a/src/serena/resources/dashboard/dashboard.css +++ b/src/serena/resources/dashboard/dashboard.css @@ -556,6 +556,24 @@ code, pre, kbd, samp, color: var(--text-primary); } +.newer-version-badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--text-primary); + font-size: 12px; + font-weight: 500; + text-decoration: none; + cursor: pointer; +} + +.newer-version-badge:hover { + background: var(--btn-secondary-hover); +} + .config-list { list-style: none; padding: 0; diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 571df521..74308c93 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -618,7 +618,12 @@ class Dashboard { let html = '