v1.0.1 Feature Update and Polish
Full Changelog: [New Features] - Added Native Translation Mode: - Whisper model now fully supports Translating any language to English - Added 'task' and 'language' parameters to Transcriber core - Dual Hotkey Support: - Added separate Global Hotkeys for Transcribe (default F8) and Translate (default F10) - Both hotkeys are fully customizable in Settings - Engine dynamically switches modes based on which key is pressed [UI/UX Improvements] - Settings Window: - Widened Hotkey Input fields (240px) to accommodate long combinations - Added Pretty-Printing for hotkey sequences (e.g. 'ctrl+f9' display as 'Ctrl + F9') - Replaced Country Code dropdown with Full Language Names (99+ languages) - Made Language Dropdown scrollable (max height 300px) to prevent screen overflow - Removed redundant 'Task' selector (replaced by dedicated hotkeys) - System Tray: - Tooltip now displays both Transcribe and Translate hotkeys - Tooltip hotkeys are formatted readably [Core & Performance] - Bootstrapper: - Implemented Smart Incremental Sync - Now checks filesize and content hash before copying files - Drastically reduces startup time for subsequent runs - Preserves user settings.json during updates - Backend: - Fixed HotkeyManager to support dynamic configuration keys - Fixed Language Lock: selecting a language now correctly forces the model to use it - Refactored bridge/main connection for language list handling
This commit is contained in:
@@ -259,48 +259,72 @@ class Bootstrapper:
|
||||
process.wait()
|
||||
|
||||
def refresh_app_source(self):
|
||||
"""Refresh app source files. Skips if already exists to save time."""
|
||||
# Optimization: If app/main.py exists, skip update to improve startup speed.
|
||||
# The user can delete the 'runtime' folder to force an update.
|
||||
if (self.app_path / "main.py").exists():
|
||||
log("App already exists. Skipping update.")
|
||||
return True
|
||||
|
||||
if self.ui: self.ui.set_status("Updating app files...")
|
||||
"""
|
||||
Smartly updates app source files by only copying changed files.
|
||||
Preserves user settings and reduces disk I/O.
|
||||
"""
|
||||
if self.ui: self.ui.set_status("Checking for updates...")
|
||||
|
||||
try:
|
||||
# Preserve settings.json if it exists
|
||||
settings_path = self.app_path / "settings.json"
|
||||
temp_settings = None
|
||||
if settings_path.exists():
|
||||
try:
|
||||
temp_settings = settings_path.read_bytes()
|
||||
except:
|
||||
log("Failed to backup settings.json, it involves risk of data loss.")
|
||||
|
||||
if self.app_path.exists():
|
||||
shutil.rmtree(self.app_path, ignore_errors=True)
|
||||
# 1. Ensure destination exists
|
||||
if not self.app_path.exists():
|
||||
self.app_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
shutil.copytree(
|
||||
self.source_path,
|
||||
self.app_path,
|
||||
ignore=shutil.ignore_patterns(
|
||||
'__pycache__', '*.pyc', '.git', 'venv',
|
||||
'build', 'dist', '*.egg-info', 'runtime'
|
||||
)
|
||||
)
|
||||
|
||||
# Restore settings.json
|
||||
if temp_settings:
|
||||
try:
|
||||
settings_path.write_bytes(temp_settings)
|
||||
log("Restored settings.json")
|
||||
except:
|
||||
log("Failed to restore settings.json")
|
||||
# 2. Walk source and sync
|
||||
# source_path is the temporary bundled folder
|
||||
# app_path is the persistent runtime folder
|
||||
|
||||
changes_made = 0
|
||||
|
||||
for src_dir, dirs, files in os.walk(self.source_path):
|
||||
# Determine relative path from source root
|
||||
rel_path = Path(src_dir).relative_to(self.source_path)
|
||||
dst_dir = self.app_path / rel_path
|
||||
|
||||
# Ensure directory exists
|
||||
if not dst_dir.exists():
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for file in files:
|
||||
# Skip ignored files
|
||||
if file in ['__pycache__', '.git', 'settings.json'] or file.endswith('.pyc'):
|
||||
continue
|
||||
|
||||
src_file = Path(src_dir) / file
|
||||
dst_file = dst_dir / file
|
||||
|
||||
# Check if update needed
|
||||
should_copy = False
|
||||
if not dst_file.exists():
|
||||
should_copy = True
|
||||
else:
|
||||
# Compare size first (fast)
|
||||
if src_file.stat().st_size != dst_file.stat().st_size:
|
||||
should_copy = True
|
||||
else:
|
||||
# Compare content (slower but accurate)
|
||||
# Only read if size matches to verify diff
|
||||
if src_file.read_bytes() != dst_file.read_bytes():
|
||||
should_copy = True
|
||||
|
||||
if should_copy:
|
||||
shutil.copy2(src_file, dst_file)
|
||||
changes_made += 1
|
||||
if self.ui: self.ui.set_detail(f"Updated: {file}")
|
||||
|
||||
# 3. Cleanup logic (Optional: remove files in dest that are not in source)
|
||||
# For now, we only add/update to prevent deleting generated user files (logs, etc)
|
||||
|
||||
if changes_made > 0:
|
||||
log(f"Update complete. {changes_made} files changed.")
|
||||
else:
|
||||
log("App is up to date.")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
log(f"Error refreshing app source: {e}")
|
||||
# Fallback to nuclear option if sync fails completely?
|
||||
# No, 'smart_sync' failing might mean permissions, nuclear wouldn't help.
|
||||
return False
|
||||
|
||||
def run_app(self):
|
||||
|
||||
Reference in New Issue
Block a user