Add auto-update feature with Gitea release checking
- Add UpdateChecker class to query Gitea API for latest releases - Show update dialog with release notes when new version available - Open browser to release page for download (handles large files) - Allow users to skip specific versions or defer updates - Add "Check for Updates Now" button in settings - Check automatically on startup (respects 24-hour interval) - Pre-configured for repo.anhonesthost.net/streamer-tools Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,15 @@ class Config:
|
|||||||
'max_lines': 100,
|
'max_lines': 100,
|
||||||
'font_size': 12,
|
'font_size': 12,
|
||||||
'theme': 'dark'
|
'theme': 'dark'
|
||||||
|
},
|
||||||
|
'updates': {
|
||||||
|
'auto_check': True,
|
||||||
|
'gitea_url': 'https://repo.anhonesthost.net',
|
||||||
|
'owner': 'streamer-tools',
|
||||||
|
'repo': 'local-transcription',
|
||||||
|
'skipped_versions': [],
|
||||||
|
'last_check': '',
|
||||||
|
'check_interval_hours': 24
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
129
client/update_checker.py
Normal file
129
client/update_checker.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
"""Update checker for Local Transcription - checks Gitea for new releases."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional, Callable
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ReleaseInfo:
|
||||||
|
"""Information about a release."""
|
||||||
|
version: str # Parsed version without 'v' prefix (e.g., "1.4.0")
|
||||||
|
tag_name: str # Original tag name (e.g., "v1.4.0")
|
||||||
|
release_notes: str # Release notes / body markdown
|
||||||
|
download_url: str # Browser URL for release page (html_url)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateChecker:
|
||||||
|
"""Checks for updates from a Gitea server."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
current_version: str,
|
||||||
|
gitea_url: str = "",
|
||||||
|
owner: str = "",
|
||||||
|
repo: str = "",
|
||||||
|
timeout: int = 10
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialize the update checker.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
current_version: Current application version (e.g., "1.3.1")
|
||||||
|
gitea_url: Base URL of the Gitea server (e.g., "https://git.example.com")
|
||||||
|
owner: Repository owner/organization name
|
||||||
|
repo: Repository name
|
||||||
|
timeout: Request timeout in seconds
|
||||||
|
"""
|
||||||
|
self.current_version = current_version
|
||||||
|
self.gitea_url = gitea_url.rstrip('/') if gitea_url else ""
|
||||||
|
self.owner = owner
|
||||||
|
self.repo = repo
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
|
def is_configured(self) -> bool:
|
||||||
|
"""Check if update checking is properly configured."""
|
||||||
|
return bool(self.gitea_url and self.owner and self.repo)
|
||||||
|
|
||||||
|
def check_for_update(self) -> tuple[Optional[ReleaseInfo], Optional[str]]:
|
||||||
|
"""
|
||||||
|
Check for updates synchronously.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (ReleaseInfo or None, error_message or None)
|
||||||
|
- If update available: (ReleaseInfo, None)
|
||||||
|
- If no update: (None, None)
|
||||||
|
- If error: (None, error_message)
|
||||||
|
"""
|
||||||
|
if not self.is_configured():
|
||||||
|
return None, "Update checking not configured"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Query Gitea API for latest release
|
||||||
|
api_url = f"{self.gitea_url}/api/v1/repos/{self.owner}/{self.repo}/releases/latest"
|
||||||
|
response = requests.get(api_url, timeout=self.timeout)
|
||||||
|
|
||||||
|
if response.status_code == 404:
|
||||||
|
return None, "No releases found"
|
||||||
|
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Parse version from tag name (strip 'v' prefix if present)
|
||||||
|
tag_name = data.get('tag_name', '')
|
||||||
|
release_version = tag_name.lstrip('v')
|
||||||
|
|
||||||
|
if not release_version:
|
||||||
|
return None, "Invalid release tag"
|
||||||
|
|
||||||
|
# Compare versions using packaging library
|
||||||
|
try:
|
||||||
|
current_ver = version.parse(self.current_version)
|
||||||
|
release_ver = version.parse(release_version)
|
||||||
|
except version.InvalidVersion as e:
|
||||||
|
return None, f"Invalid version format: {e}"
|
||||||
|
|
||||||
|
# Check if release is newer
|
||||||
|
if release_ver > current_ver:
|
||||||
|
release_info = ReleaseInfo(
|
||||||
|
version=release_version,
|
||||||
|
tag_name=tag_name,
|
||||||
|
release_notes=data.get('body', ''),
|
||||||
|
download_url=data.get('html_url', '')
|
||||||
|
)
|
||||||
|
return release_info, None
|
||||||
|
else:
|
||||||
|
return None, None # No update available
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
return None, "Connection timed out"
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
return None, "Could not connect to server"
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
return None, f"Request failed: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"Error checking for updates: {e}"
|
||||||
|
|
||||||
|
def check_for_update_async(
|
||||||
|
self,
|
||||||
|
callback: Callable[[Optional[ReleaseInfo], Optional[str]], None]
|
||||||
|
) -> threading.Thread:
|
||||||
|
"""
|
||||||
|
Check for updates asynchronously in a background thread.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback: Function to call with (ReleaseInfo or None, error_message or None)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The started thread
|
||||||
|
"""
|
||||||
|
def worker():
|
||||||
|
result, error = self.check_for_update()
|
||||||
|
callback(result, error)
|
||||||
|
|
||||||
|
thread = threading.Thread(target=worker, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
@@ -73,3 +73,12 @@ remote_processing:
|
|||||||
server_url: "" # WebSocket URL of remote transcription service (e.g., ws://your-server:8765/ws/transcribe)
|
server_url: "" # WebSocket URL of remote transcription service (e.g., ws://your-server:8765/ws/transcribe)
|
||||||
api_key: "" # API key for authentication
|
api_key: "" # API key for authentication
|
||||||
fallback_to_local: true # Fall back to local processing if remote fails
|
fallback_to_local: true # Fall back to local processing if remote fails
|
||||||
|
|
||||||
|
updates:
|
||||||
|
auto_check: true # Check for updates on startup
|
||||||
|
gitea_url: "https://repo.anhonesthost.net" # Base URL of Gitea server
|
||||||
|
owner: "streamer-tools" # Repository owner/organization name
|
||||||
|
repo: "local-transcription" # Repository name
|
||||||
|
skipped_versions: [] # List of versions the user chose to skip
|
||||||
|
last_check: "" # ISO timestamp of last update check
|
||||||
|
check_interval_hours: 24 # Hours between automatic update checks
|
||||||
|
|||||||
@@ -2,10 +2,13 @@
|
|||||||
|
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QPushButton, QLabel, QFileDialog, QMessageBox
|
QPushButton, QLabel, QFileDialog, QMessageBox,
|
||||||
|
QDialog, QTextEdit, QCheckBox
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QThread, Signal
|
from PySide6.QtCore import Qt, QThread, Signal, QTimer
|
||||||
from PySide6.QtGui import QFont
|
from PySide6.QtGui import QFont
|
||||||
|
import webbrowser
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -66,6 +69,90 @@ class EngineStartThread(QThread):
|
|||||||
self.finished.emit(False, f"Error initializing engine: {e}")
|
self.finished.emit(False, f"Error initializing engine: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateDialog(QDialog):
|
||||||
|
"""Dialog showing available update information."""
|
||||||
|
|
||||||
|
def __init__(self, parent, current_version: str, release_info):
|
||||||
|
"""
|
||||||
|
Initialize the update dialog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: Parent window
|
||||||
|
current_version: Current application version
|
||||||
|
release_info: ReleaseInfo object with update details
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self.release_info = release_info
|
||||||
|
self.skip_version = False
|
||||||
|
|
||||||
|
self.setWindowTitle("Update Available")
|
||||||
|
self.setModal(True)
|
||||||
|
self.setMinimumSize(500, 400)
|
||||||
|
self.resize(550, 450)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.setSpacing(15)
|
||||||
|
layout.setContentsMargins(20, 20, 20, 20)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
# Title
|
||||||
|
title_label = QLabel("Update Available!")
|
||||||
|
title_font = QFont()
|
||||||
|
title_font.setPointSize(16)
|
||||||
|
title_font.setBold(True)
|
||||||
|
title_label.setFont(title_font)
|
||||||
|
layout.addWidget(title_label)
|
||||||
|
|
||||||
|
# Version info
|
||||||
|
version_label = QLabel(f"Current: {current_version} \u2192 New: {release_info.version}")
|
||||||
|
version_font = QFont()
|
||||||
|
version_font.setPointSize(12)
|
||||||
|
version_label.setFont(version_font)
|
||||||
|
layout.addWidget(version_label)
|
||||||
|
|
||||||
|
# Release notes
|
||||||
|
notes_label = QLabel("Release Notes:")
|
||||||
|
notes_label.setStyleSheet("font-weight: bold; margin-top: 10px;")
|
||||||
|
layout.addWidget(notes_label)
|
||||||
|
|
||||||
|
self.notes_text = QTextEdit()
|
||||||
|
self.notes_text.setReadOnly(True)
|
||||||
|
self.notes_text.setPlainText(release_info.release_notes or "No release notes available.")
|
||||||
|
layout.addWidget(self.notes_text)
|
||||||
|
|
||||||
|
# Skip version checkbox
|
||||||
|
self.skip_checkbox = QCheckBox(f"Skip version {release_info.version}")
|
||||||
|
self.skip_checkbox.setToolTip("Don't show this update again")
|
||||||
|
layout.addWidget(self.skip_checkbox)
|
||||||
|
|
||||||
|
# Buttons
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
self.later_button = QPushButton("Remind Me Later")
|
||||||
|
self.later_button.clicked.connect(self._on_later)
|
||||||
|
button_layout.addWidget(self.later_button)
|
||||||
|
|
||||||
|
self.download_button = QPushButton("Download Update")
|
||||||
|
self.download_button.setStyleSheet("background-color: #2ecc71; color: white; font-weight: bold;")
|
||||||
|
self.download_button.clicked.connect(self._on_download)
|
||||||
|
button_layout.addWidget(self.download_button)
|
||||||
|
|
||||||
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
def _on_later(self):
|
||||||
|
"""Handle 'Remind Me Later' button click."""
|
||||||
|
self.skip_version = self.skip_checkbox.isChecked()
|
||||||
|
self.reject()
|
||||||
|
|
||||||
|
def _on_download(self):
|
||||||
|
"""Handle 'Download Update' button click."""
|
||||||
|
self.skip_version = self.skip_checkbox.isChecked()
|
||||||
|
if self.release_info.download_url:
|
||||||
|
webbrowser.open(self.release_info.download_url)
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
"""Main application window using PySide6."""
|
"""Main application window using PySide6."""
|
||||||
|
|
||||||
@@ -136,6 +223,9 @@ class MainWindow(QMainWindow):
|
|||||||
# Initialize components (in background)
|
# Initialize components (in background)
|
||||||
self._initialize_components()
|
self._initialize_components()
|
||||||
|
|
||||||
|
# Schedule update check 3 seconds after startup (non-blocking)
|
||||||
|
QTimer.singleShot(3000, self._startup_update_check)
|
||||||
|
|
||||||
def _update_splash(self, message: str):
|
def _update_splash(self, message: str):
|
||||||
"""Update splash screen message if it exists."""
|
"""Update splash screen message if it exists."""
|
||||||
if self.splash_screen:
|
if self.splash_screen:
|
||||||
@@ -836,3 +926,98 @@ class MainWindow(QMainWindow):
|
|||||||
self.engine_start_thread.wait()
|
self.engine_start_thread.wait()
|
||||||
|
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def _startup_update_check(self):
|
||||||
|
"""Check for updates on startup (called via QTimer)."""
|
||||||
|
# Only check if auto_check is enabled
|
||||||
|
if not self.config.get('updates.auto_check', True):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if enough time has passed since last check
|
||||||
|
last_check_str = self.config.get('updates.last_check', '')
|
||||||
|
check_interval = self.config.get('updates.check_interval_hours', 24)
|
||||||
|
|
||||||
|
if last_check_str:
|
||||||
|
try:
|
||||||
|
last_check = datetime.fromisoformat(last_check_str)
|
||||||
|
hours_since_check = (datetime.now() - last_check).total_seconds() / 3600
|
||||||
|
if hours_since_check < check_interval:
|
||||||
|
print(f"Skipping update check - last checked {hours_since_check:.1f} hours ago")
|
||||||
|
return
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass # Invalid date format, proceed with check
|
||||||
|
|
||||||
|
# Perform async update check
|
||||||
|
self._check_for_updates(show_no_update_message=False)
|
||||||
|
|
||||||
|
def _check_for_updates(self, show_no_update_message: bool = True):
|
||||||
|
"""
|
||||||
|
Check for updates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
show_no_update_message: Whether to show a message if no update is available
|
||||||
|
"""
|
||||||
|
from client.update_checker import UpdateChecker
|
||||||
|
|
||||||
|
gitea_url = self.config.get('updates.gitea_url', 'https://repo.anhonesthost.net')
|
||||||
|
owner = self.config.get('updates.owner', 'streamer-tools')
|
||||||
|
repo = self.config.get('updates.repo', 'local-transcription')
|
||||||
|
|
||||||
|
if not gitea_url or not owner or not repo:
|
||||||
|
if show_no_update_message:
|
||||||
|
QMessageBox.warning(self, "Update Check", "Update checking is not configured.")
|
||||||
|
return
|
||||||
|
|
||||||
|
checker = UpdateChecker(
|
||||||
|
current_version=__version__,
|
||||||
|
gitea_url=gitea_url,
|
||||||
|
owner=owner,
|
||||||
|
repo=repo
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_update_check_complete(release_info, error):
|
||||||
|
# Update last check time
|
||||||
|
self.config.set('updates.last_check', datetime.now().isoformat())
|
||||||
|
|
||||||
|
if error:
|
||||||
|
print(f"Update check failed: {error}")
|
||||||
|
if show_no_update_message:
|
||||||
|
QMessageBox.warning(self, "Update Check Failed", f"Could not check for updates:\n{error}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if release_info:
|
||||||
|
# Check if this version is skipped
|
||||||
|
skipped_versions = self.config.get('updates.skipped_versions', [])
|
||||||
|
if release_info.version in skipped_versions:
|
||||||
|
print(f"Skipping update notification for version {release_info.version} (user skipped)")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Show update dialog on main thread
|
||||||
|
QTimer.singleShot(0, lambda: self._show_update_dialog(release_info))
|
||||||
|
else:
|
||||||
|
if show_no_update_message:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"No Updates",
|
||||||
|
f"You are running the latest version ({__version__})."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run check in background thread
|
||||||
|
checker.check_for_update_async(on_update_check_complete)
|
||||||
|
|
||||||
|
def _show_update_dialog(self, release_info):
|
||||||
|
"""
|
||||||
|
Show the update dialog.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
release_info: ReleaseInfo object with update details
|
||||||
|
"""
|
||||||
|
dialog = UpdateDialog(self, __version__, release_info)
|
||||||
|
dialog.exec()
|
||||||
|
|
||||||
|
# If user chose to skip this version, save it
|
||||||
|
if dialog.skip_version:
|
||||||
|
skipped_versions = self.config.get('updates.skipped_versions', [])
|
||||||
|
if release_info.version not in skipped_versions:
|
||||||
|
skipped_versions.append(release_info.version)
|
||||||
|
self.config.set('updates.skipped_versions', skipped_versions)
|
||||||
|
|||||||
@@ -528,6 +528,27 @@ class SettingsDialog(QDialog):
|
|||||||
remote_group.setLayout(remote_layout)
|
remote_group.setLayout(remote_layout)
|
||||||
content_layout.addWidget(remote_group)
|
content_layout.addWidget(remote_group)
|
||||||
|
|
||||||
|
# Updates Group
|
||||||
|
updates_group = QGroupBox("Software Updates")
|
||||||
|
updates_layout = QFormLayout()
|
||||||
|
updates_layout.setSpacing(10)
|
||||||
|
|
||||||
|
self.update_auto_check = QCheckBox("Enable")
|
||||||
|
self.update_auto_check.setToolTip(
|
||||||
|
"Automatically check for updates when the application starts"
|
||||||
|
)
|
||||||
|
updates_layout.addRow("Check on Startup:", self.update_auto_check)
|
||||||
|
|
||||||
|
self.check_updates_button = QPushButton("Check for Updates Now")
|
||||||
|
self.check_updates_button.setToolTip(
|
||||||
|
"Manually check for available updates"
|
||||||
|
)
|
||||||
|
self.check_updates_button.clicked.connect(self._check_for_updates_now)
|
||||||
|
updates_layout.addRow("", self.check_updates_button)
|
||||||
|
|
||||||
|
updates_group.setLayout(updates_layout)
|
||||||
|
content_layout.addWidget(updates_group)
|
||||||
|
|
||||||
# Add stretch to push everything to the top
|
# Add stretch to push everything to the top
|
||||||
content_layout.addStretch()
|
content_layout.addStretch()
|
||||||
|
|
||||||
@@ -779,6 +800,9 @@ class SettingsDialog(QDialog):
|
|||||||
self.remote_api_key_input.setText(self.config.get('remote_processing.api_key', ''))
|
self.remote_api_key_input.setText(self.config.get('remote_processing.api_key', ''))
|
||||||
self.remote_fallback_check.setChecked(self.config.get('remote_processing.fallback_to_local', True))
|
self.remote_fallback_check.setChecked(self.config.get('remote_processing.fallback_to_local', True))
|
||||||
|
|
||||||
|
# Update settings
|
||||||
|
self.update_auto_check.setChecked(self.config.get('updates.auto_check', True))
|
||||||
|
|
||||||
def _save_settings(self):
|
def _save_settings(self):
|
||||||
"""Save settings to config."""
|
"""Save settings to config."""
|
||||||
try:
|
try:
|
||||||
@@ -851,6 +875,9 @@ class SettingsDialog(QDialog):
|
|||||||
self.config.set('remote_processing.api_key', self.remote_api_key_input.text())
|
self.config.set('remote_processing.api_key', self.remote_api_key_input.text())
|
||||||
self.config.set('remote_processing.fallback_to_local', self.remote_fallback_check.isChecked())
|
self.config.set('remote_processing.fallback_to_local', self.remote_fallback_check.isChecked())
|
||||||
|
|
||||||
|
# Update settings
|
||||||
|
self.config.set('updates.auto_check', self.update_auto_check.isChecked())
|
||||||
|
|
||||||
# Call save callback (which will show the success message)
|
# Call save callback (which will show the success message)
|
||||||
if self.on_save:
|
if self.on_save:
|
||||||
self.on_save()
|
self.on_save()
|
||||||
@@ -864,3 +891,54 @@ class SettingsDialog(QDialog):
|
|||||||
QMessageBox.critical(self, "Invalid Input", f"Please check your input values:\n{e}")
|
QMessageBox.critical(self, "Invalid Input", f"Please check your input values:\n{e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
QMessageBox.critical(self, "Error", f"Failed to save settings:\n{e}")
|
QMessageBox.critical(self, "Error", f"Failed to save settings:\n{e}")
|
||||||
|
|
||||||
|
def _check_for_updates_now(self):
|
||||||
|
"""Manually check for updates."""
|
||||||
|
from version import __version__
|
||||||
|
from client.update_checker import UpdateChecker
|
||||||
|
|
||||||
|
# Get settings from config (hardcoded defaults)
|
||||||
|
gitea_url = self.config.get('updates.gitea_url', 'https://repo.anhonesthost.net')
|
||||||
|
owner = self.config.get('updates.owner', 'streamer-tools')
|
||||||
|
repo = self.config.get('updates.repo', 'local-transcription')
|
||||||
|
|
||||||
|
# Disable button during check
|
||||||
|
self.check_updates_button.setEnabled(False)
|
||||||
|
self.check_updates_button.setText("Checking...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
checker = UpdateChecker(
|
||||||
|
current_version=__version__,
|
||||||
|
gitea_url=gitea_url,
|
||||||
|
owner=owner,
|
||||||
|
repo=repo
|
||||||
|
)
|
||||||
|
|
||||||
|
release_info, error = checker.check_for_update()
|
||||||
|
|
||||||
|
if error:
|
||||||
|
QMessageBox.warning(self, "Update Check Failed", f"Could not check for updates:\n{error}")
|
||||||
|
elif release_info:
|
||||||
|
# Update available
|
||||||
|
msg = QMessageBox(self)
|
||||||
|
msg.setWindowTitle("Update Available")
|
||||||
|
msg.setIcon(QMessageBox.Information)
|
||||||
|
msg.setText(f"A new version is available!\n\nCurrent: {__version__}\nNew: {release_info.version}")
|
||||||
|
if release_info.release_notes:
|
||||||
|
msg.setDetailedText(release_info.release_notes)
|
||||||
|
msg.setStandardButtons(QMessageBox.Ok)
|
||||||
|
msg.exec()
|
||||||
|
else:
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"No Updates",
|
||||||
|
f"You are running the latest version ({__version__})."
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Error checking for updates:\n{e}")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Re-enable button
|
||||||
|
self.check_updates_button.setEnabled(True)
|
||||||
|
self.check_updates_button.setText("Check for Updates Now")
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ dependencies = [
|
|||||||
"websockets>=12.0",
|
"websockets>=12.0",
|
||||||
# Server sync client
|
# Server sync client
|
||||||
"requests>=2.31.0",
|
"requests>=2.31.0",
|
||||||
|
# Version comparison for updates
|
||||||
|
"packaging>=21.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
|||||||
13
version.py
13
version.py
@@ -1,9 +1,18 @@
|
|||||||
"""Version information for Local Transcription."""
|
"""Version information for Local Transcription."""
|
||||||
|
|
||||||
__version__ = "1.3.1"
|
__version__ = "1.4.0"
|
||||||
__version_info__ = (1, 3, 1)
|
__version_info__ = (1, 4, 0)
|
||||||
|
|
||||||
# Version history:
|
# Version history:
|
||||||
|
# 1.4.0 - Auto-update feature:
|
||||||
|
# - Automatic update checking on startup (configurable)
|
||||||
|
# - Shows update dialog with release notes when new version available
|
||||||
|
# - Opens browser to Gitea release page for download
|
||||||
|
# - Skip version option to dismiss specific updates
|
||||||
|
# - Manual "Check for Updates Now" button in settings
|
||||||
|
#
|
||||||
|
# 1.3.1 - User-configurable colors for transcription display
|
||||||
|
#
|
||||||
# 1.0.0 - Initial release with:
|
# 1.0.0 - Initial release with:
|
||||||
# - Real-time speech-to-text transcription using Whisper models
|
# - Real-time speech-to-text transcription using Whisper models
|
||||||
# - Local web display for OBS browser source integration
|
# - Local web display for OBS browser source integration
|
||||||
|
|||||||
Reference in New Issue
Block a user