From 7bba24c19f1174a773b5ed1d142a1c8af7375be3 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Fri, 23 Jan 2026 22:19:43 +0800 Subject: [PATCH] feat(update-modal): display last 5 release notes instead of single - Modified backend to fetch last 5 releases from GitHub API - Updated frontend to iterate through and display multiple releases - Added latest badge and publish date styling - Added update.latestBadge translation key to all locales - Maintains backward compatibility for single changelog display --- locales/de.json | 1 + locales/en.json | 1 + locales/es.json | 1 + locales/fr.json | 1 + locales/he.json | 1 + locales/ja.json | 1 + locales/ko.json | 1 + locales/ru.json | 1 + locales/zh-CN.json | 3 +- locales/zh-TW.json | 1 + py/routes/update_routes.py | 59 +++++++---- static/css/components/modal/update-modal.css | 47 ++++++++- static/css/components/update-modal.css | 4 - static/js/managers/UpdateService.js | 102 ++++++++++++++----- tests/routes/test_update_routes.py | 8 +- 15 files changed, 180 insertions(+), 52 deletions(-) diff --git a/locales/de.json b/locales/de.json index fab698e7..e10465e7 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "Nach Updates wird gesucht...", "checkingMessage": "Bitte warten Sie, während wir nach der neuesten Version suchen.", "showNotifications": "Update-Benachrichtigungen anzeigen", + "latestBadge": "Neueste", "updateProgress": { "preparing": "Update wird vorbereitet...", "installing": "Update wird installiert...", diff --git a/locales/en.json b/locales/en.json index 599e8236..827e06a2 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "Checking for updates...", "checkingMessage": "Please wait while we check for the latest version.", "showNotifications": "Show update notifications", + "latestBadge": "Latest", "updateProgress": { "preparing": "Preparing update...", "installing": "Installing update...", diff --git a/locales/es.json b/locales/es.json index 4935526b..788a2900 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "Comprobando actualizaciones...", "checkingMessage": "Por favor espera mientras comprobamos la última versión.", "showNotifications": "Mostrar notificaciones de actualización", + "latestBadge": "Último", "updateProgress": { "preparing": "Preparando actualización...", "installing": "Instalando actualización...", diff --git a/locales/fr.json b/locales/fr.json index 4b5b1f12..ef409f6e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "Vérification des mises à jour...", "checkingMessage": "Veuillez patienter pendant la vérification de la dernière version.", "showNotifications": "Afficher les notifications de mise à jour", + "latestBadge": "Dernier", "updateProgress": { "preparing": "Préparation de la mise à jour...", "installing": "Installation de la mise à jour...", diff --git a/locales/he.json b/locales/he.json index 0b9976d5..2c6fde8f 100644 --- a/locales/he.json +++ b/locales/he.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "בודק עדכונים...", "checkingMessage": "אנא המתן בזמן שאנו בודקים את הגרסה האחרונה.", "showNotifications": "הצג התראות עדכון", + "latestBadge": "עדכן", "updateProgress": { "preparing": "מכין עדכון...", "installing": "מתקין עדכון...", diff --git a/locales/ja.json b/locales/ja.json index bc3f1615..401e6966 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "更新を確認中...", "checkingMessage": "最新バージョンを確認しています。お待ちください。", "showNotifications": "更新通知を表示", + "latestBadge": "最新", "updateProgress": { "preparing": "更新を準備中...", "installing": "更新をインストール中...", diff --git a/locales/ko.json b/locales/ko.json index e11b01e0..d4b55e08 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "업데이트 확인 중...", "checkingMessage": "최신 버전을 확인하는 동안 잠시 기다려주세요.", "showNotifications": "업데이트 알림 표시", + "latestBadge": "최신", "updateProgress": { "preparing": "업데이트 준비 중...", "installing": "업데이트 설치 중...", diff --git a/locales/ru.json b/locales/ru.json index 997de0df..09f0a8a5 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "Проверка обновлений...", "checkingMessage": "Пожалуйста, подождите, пока мы проверяем последнюю версию.", "showNotifications": "Показывать уведомления об обновлениях", + "latestBadge": "Последний", "updateProgress": { "preparing": "Подготовка обновления...", "installing": "Установка обновления...", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 0b1cbcc0..3209f763 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "正在检查更新...", "checkingMessage": "请稍候,正在检查最新版本。", "showNotifications": "显示更新通知", + "latestBadge": "最新", "updateProgress": { "preparing": "正在准备更新...", "installing": "正在安装更新...", @@ -1538,4 +1539,4 @@ "learnMore": "浏览器插件教程" } } -} \ No newline at end of file +} diff --git a/locales/zh-TW.json b/locales/zh-TW.json index b47b87fb..c1098099 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1214,6 +1214,7 @@ "checkingUpdates": "正在檢查更新...", "checkingMessage": "請稍候,正在檢查最新版本。", "showNotifications": "顯示更新通知", + "latestBadge": "最新", "updateProgress": { "preparing": "正在準備更新...", "installing": "正在安裝更新...", diff --git a/py/routes/update_routes.py b/py/routes/update_routes.py index aa636588..7628f334 100644 --- a/py/routes/update_routes.py +++ b/py/routes/update_routes.py @@ -45,8 +45,9 @@ class UpdateRoutes: # Fetch remote version from GitHub if nightly: remote_version, changelog = await UpdateRoutes._get_nightly_version() + releases = None else: - remote_version, changelog = await UpdateRoutes._get_remote_version() + remote_version, changelog, releases = await UpdateRoutes._get_remote_version() # Compare versions if nightly: @@ -59,7 +60,7 @@ class UpdateRoutes: remote_version.replace('v', '') ) - return web.json_response({ + response_data = { 'success': True, 'current_version': local_version, 'latest_version': remote_version, @@ -67,7 +68,13 @@ class UpdateRoutes: 'changelog': changelog, 'git_info': git_info, 'nightly': nightly - }) + } + + # Include releases list for stable mode + if releases is not None: + response_data['releases'] = releases + + return web.json_response(response_data) except NETWORK_EXCEPTIONS as e: logger.warning("Network unavailable during update check: %s", e) @@ -443,42 +450,58 @@ class UpdateRoutes: return git_info @staticmethod - async def _get_remote_version() -> tuple[str, List[str]]: + async def _get_remote_version() -> tuple[str, List[str], List[Dict]]: """ Fetch remote version from GitHub Returns: - tuple: (version string, changelog list) + tuple: (version string, changelog list, releases list) """ repo_owner = "willmiao" repo_name = "ComfyUI-Lora-Manager" - # Use GitHub API to fetch the latest release - github_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" + # Use GitHub API to fetch the last 5 releases + github_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases?per_page=5" try: downloader = await get_downloader() success, data = await downloader.make_request('GET', github_url, custom_headers={'Accept': 'application/vnd.github+json'}) if not success: - logger.warning(f"Failed to fetch GitHub release: {data}") - return "v0.0.0", [] + logger.warning(f"Failed to fetch GitHub releases: {data}") + return "v0.0.0", [], [] - version = data.get('tag_name', '') - if not version.startswith('v'): - version = f"v{version}" + # Parse releases + releases = [] + for i, release in enumerate(data): + version = release.get('tag_name', '') + if not version.startswith('v'): + version = f"v{version}" + + # Extract changelog from release notes + body = release.get('body', '') + changelog = UpdateRoutes._parse_changelog(body) + + releases.append({ + 'version': version, + 'changelog': changelog, + 'published_at': release.get('published_at', ''), + 'is_latest': i == 0 + }) - # Extract changelog from release notes - body = data.get('body', '') - changelog = UpdateRoutes._parse_changelog(body) + # Get latest version and its changelog + if releases: + latest_version = releases[0]['version'] + latest_changelog = releases[0]['changelog'] + return latest_version, latest_changelog, releases - return version, changelog + return "v0.0.0", [], [] except NETWORK_EXCEPTIONS as e: logger.warning("Unable to reach GitHub for release info: %s", e) - return "v0.0.0", [] + return "v0.0.0", [], [] except Exception as e: logger.error(f"Error fetching remote version: {e}", exc_info=True) - return "v0.0.0", [] + return "v0.0.0", [], [] @staticmethod def _parse_changelog(release_notes: str) -> List[str]: diff --git a/static/css/components/modal/update-modal.css b/static/css/components/modal/update-modal.css index e254a255..fcbfe58a 100644 --- a/static/css/components/modal/update-modal.css +++ b/static/css/components/modal/update-modal.css @@ -121,4 +121,49 @@ .changelog-item a:hover { text-decoration: underline; -} \ No newline at end of file +} + +/* Multiple releases styling */ +.changelog-content > .changelog-item + .changelog-item { + margin-top: var(--space-4); + padding-top: var(--space-4); + border-top: 1px solid var(--lora-border); +} + +.changelog-item h4 { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-2); + margin-bottom: var(--space-2); + font-size: 1em; +} + +.changelog-item .latest-badge { + background-color: var(--lora-accent); + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.75em; + font-weight: 500; + text-transform: uppercase; +} + +.changelog-item .publish-date { + font-size: 0.85em; + color: var(--text-color); + opacity: 0.6; + font-weight: normal; +} + +.changelog-item.latest { + background-color: rgba(66, 153, 225, 0.05); + border-radius: var(--border-radius-sm); + padding: var(--space-3); + border: 1px solid rgba(66, 153, 225, 0.2); +} + +[data-theme="dark"] .changelog-item.latest { + background-color: rgba(66, 153, 225, 0.1); + border-color: rgba(66, 153, 225, 0.3); +} diff --git a/static/css/components/update-modal.css b/static/css/components/update-modal.css index a64237ba..32fb91ba 100644 --- a/static/css/components/update-modal.css +++ b/static/css/components/update-modal.css @@ -1,8 +1,4 @@ /* Update Modal Styles */ -.update-modal { - max-width: 600px; -} - .update-header { display: flex; align-items: center; diff --git a/static/js/managers/UpdateService.js b/static/js/managers/UpdateService.js index 0cfb7619..674030c7 100644 --- a/static/js/managers/UpdateService.js +++ b/static/js/managers/UpdateService.js @@ -509,38 +509,90 @@ export class UpdateService { } // Update changelog content if available - if (this.updateInfo && this.updateInfo.changelog) { + if (this.updateInfo && (this.updateInfo.changelog || this.updateInfo.releases)) { const changelogContent = modal.querySelector('.changelog-content'); if (changelogContent) { changelogContent.innerHTML = ''; // Clear existing content - // Create changelog item - const changelogItem = document.createElement('div'); - changelogItem.className = 'changelog-item'; - - const versionHeader = document.createElement('h4'); - versionHeader.textContent = `${translate('common.status.version', {}, 'Version')} ${this.latestVersion}`; - changelogItem.appendChild(versionHeader); - - // Create changelog list - const changelogList = document.createElement('ul'); - - if (this.updateInfo.changelog && this.updateInfo.changelog.length > 0) { - this.updateInfo.changelog.forEach(item => { - const listItem = document.createElement('li'); - // Parse markdown in changelog items - listItem.innerHTML = this.parseMarkdown(item); - changelogList.appendChild(listItem); + // Check if we have multiple releases + const releases = this.updateInfo.releases; + if (releases && Array.isArray(releases) && releases.length > 0) { + // Display multiple releases (up to 5) + releases.forEach(release => { + const changelogItem = document.createElement('div'); + changelogItem.className = 'changelog-item'; + if (release.is_latest) { + changelogItem.classList.add('latest'); + } + + const versionHeader = document.createElement('h4'); + + if (release.is_latest) { + const badge = document.createElement('span'); + badge.className = 'latest-badge'; + badge.textContent = translate('update.latestBadge', {}, 'Latest'); + versionHeader.appendChild(badge); + versionHeader.appendChild(document.createTextNode(' ')); + } + + const versionSpan = document.createElement('span'); + versionSpan.className = 'version'; + versionSpan.textContent = `${translate('common.status.version', {}, 'Version')} ${release.version}`; + versionHeader.appendChild(versionSpan); + + if (release.published_at) { + const dateSpan = document.createElement('span'); + dateSpan.className = 'publish-date'; + dateSpan.textContent = this.formatRelativeTime(new Date(release.published_at).getTime()); + versionHeader.appendChild(dateSpan); + } + + changelogItem.appendChild(versionHeader); + + // Create changelog list + const changelogList = document.createElement('ul'); + + if (release.changelog && release.changelog.length > 0) { + release.changelog.forEach(item => { + const listItem = document.createElement('li'); + listItem.innerHTML = this.parseMarkdown(item); + changelogList.appendChild(listItem); + }); + } else { + const listItem = document.createElement('li'); + listItem.textContent = translate('update.noChangelogAvailable', {}, 'No detailed changelog available.'); + changelogList.appendChild(listItem); + } + + changelogItem.appendChild(changelogList); + changelogContent.appendChild(changelogItem); }); } else { - // If no changelog items available - const listItem = document.createElement('li'); - listItem.textContent = translate('update.noChangelogAvailable', {}, 'No detailed changelog available. Check GitHub for more information.'); - changelogList.appendChild(listItem); + // Fallback: display single changelog (old behavior) + const changelogItem = document.createElement('div'); + changelogItem.className = 'changelog-item'; + + const versionHeader = document.createElement('h4'); + versionHeader.textContent = `${translate('common.status.version', {}, 'Version')} ${this.latestVersion}`; + changelogItem.appendChild(versionHeader); + + const changelogList = document.createElement('ul'); + + if (this.updateInfo.changelog && this.updateInfo.changelog.length > 0) { + this.updateInfo.changelog.forEach(item => { + const listItem = document.createElement('li'); + listItem.innerHTML = this.parseMarkdown(item); + changelogList.appendChild(listItem); + }); + } else { + const listItem = document.createElement('li'); + listItem.textContent = translate('update.noChangelogAvailable', {}, 'No detailed changelog available. Check GitHub for more information.'); + changelogList.appendChild(listItem); + } + + changelogItem.appendChild(changelogList); + changelogContent.appendChild(changelogItem); } - - changelogItem.appendChild(changelogList); - changelogContent.appendChild(changelogItem); } } diff --git a/tests/routes/test_update_routes.py b/tests/routes/test_update_routes.py index dd9c9658..455a6fdb 100644 --- a/tests/routes/test_update_routes.py +++ b/tests/routes/test_update_routes.py @@ -24,11 +24,12 @@ async def test_get_remote_version_offline_logs_without_traceback(monkeypatch, ca caplog.set_level(logging.WARNING) monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(OfflineDownloader())) - version, changelog = await update_routes.UpdateRoutes._get_remote_version() + version, changelog, releases = await update_routes.UpdateRoutes._get_remote_version() assert version == "v0.0.0" assert changelog == [] - assert "Failed to fetch GitHub release" in caplog.text + assert releases == [] + assert "Failed to fetch GitHub releases" in caplog.text assert "Cannot connect to host" in caplog.text assert "Traceback" not in caplog.text @@ -38,10 +39,11 @@ async def test_get_remote_version_network_error_logs_warning(monkeypatch, caplog caplog.set_level(logging.WARNING) monkeypatch.setattr(update_routes, "get_downloader", lambda: _stub_downloader(RaisingDownloader())) - version, changelog = await update_routes.UpdateRoutes._get_remote_version() + version, changelog, releases = await update_routes.UpdateRoutes._get_remote_version() assert version == "v0.0.0" assert changelog == [] + assert releases == [] assert "Unable to reach GitHub for release info" in caplog.text assert "Traceback" not in caplog.text