diff --git a/.gitignore b/.gitignore index 93b5853..188cd7f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ web/.node-persist/* agent-mAId/output.wav agent-mAId/build/* agent-mAId/dist/main.exe +agent-mAId/output.wav diff --git a/agent-mAId/main.py b/agent-mAId/main.py index 0420952..1e1bc14 100644 --- a/agent-mAId/main.py +++ b/agent-mAId/main.py @@ -16,9 +16,12 @@ import time import json5 import wave import pyperclip +import argparse +import atexit # # Load configuration from config.json DEFAULT_CONFIG = { + "api_key": "xxx", "kb_key": "ctrl", "mouse_btn": "left", "model": "distil-whisper-large-v3-en", @@ -26,20 +29,29 @@ DEFAULT_CONFIG = { "action": "type" # type, copy } -def load_config(): +def parse_args(): + """Parse command line arguments for config file.""" + parser = argparse.ArgumentParser(description='Run the AI transcription app.') + parser.add_argument( + '--config', type=str, help='Path to config file', default=None + ) + return parser.parse_args() + +def load_config(config_path=None): """Load the configuration file, adjusting for PyInstaller's temp path when bundled.""" config = DEFAULT_CONFIG.copy() # Start with default configuration try: - # Determine if the script is running as a PyInstaller bundle - if getattr(sys, 'frozen', False): - # If running in a bundle, use the temp path where PyInstaller extracts files - config_path = os.path.join(sys._MEIPASS, 'config.json') - else: - # If running in development (normal execution), use the local directory - config_path = os.path.join(os.path.dirname(__file__), 'config.json') + if config_path is None: + # Determine if the script is running as a PyInstaller bundle + if getattr(sys, 'frozen', False): + # If running in a bundle, use the temp path where PyInstaller extracts files + config_path = os.path.join(sys._MEIPASS, 'config.json') + else: + # If running in development (normal execution), use the local directory + config_path = os.path.join(os.path.dirname(__file__), 'config.json') - print('Trying to load config from:', config_path) + print(f'Trying to load config from: {config_path}') with open(config_path, 'r') as config_file: loaded_config = json5.load(config_file) # Update the default config with any values from config.json @@ -47,6 +59,7 @@ def load_config(): except FileNotFoundError as ex: print("Config file not found, using defaults." + ex.strerror) + raise ex except json5.JSONDecodeError as ex: print("Error decoding config file, using defaults." + ex.msg) except Exception as e: @@ -55,7 +68,13 @@ def load_config(): return config # Load the config -config = load_config() +# config = load_config() +# Parse command line arguments +args = parse_args() + +# Load the config from the specified path or default location +config = load_config(args.config) + # Extract API key and button from the config file API_KEY = config['api_key'] KB_KEY = config['kb_key'] @@ -63,6 +82,7 @@ MOUSE_BTN = config['mouse_btn'] MODEL = config['model'] POST_TRANSCRIBE = config['action'] + # Constants AUTO_START_PATH = os.path.expanduser(r"~\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup") # For autostart @@ -131,7 +151,8 @@ def transcribe_audio(memory_stream): end_time = time.time() transcription_time = end_time - start_time - print(f"Transcription took: {transcription_time:.2f} seconds.") + print(f"Transcription took: {transcription_time:.2f} seconds. Result: {transcription.text}") + log_transcription_time(transcription_time) return transcription.text @@ -150,16 +171,32 @@ def add_to_autostart(): shell.ShellExecuteW(None, "runas", "cmd.exe", f'/C mklink "{shortcut_path}" "{script_path}"', None, 1) print("App added to autostart.") -def quit_app(icon): - """Quit the tray application.""" - icon.stop() +icon = None # Global variable to store the tray icon object +def cleanup_and_exit(): + """Clean up the tray icon and exit the application.""" + global icon + if icon: + print("Stopping and removing tray icon...") + icon.stop() # Stop the tray icon to remove it from the tray sys.exit() - def setup_tray_icon(): + global icon """Setup system tray icon and menu.""" - #icon_image = Image.new('RGB', (64, 64), color=(255, 0, 0)) # Red icon as an example - icon_image = Image.open('mic.webp') + if getattr(sys, 'frozen', False): + # If running as a bundle, use the temp path where PyInstaller extracts files + icon_path = os.path.join(sys._MEIPASS, 'mic.webp') + else: + # If running in development (normal execution), use the local directory + icon_path = os.path.join(os.path.dirname(__file__), 'mic.webp') + + try: + # Load the tray icon + icon_image = Image.open(icon_path) + except FileNotFoundError: + print(f"Icon file not found at {icon_path}") + icon_image = Image.new('RGB', (64, 64), color=(255, 0, 0)) # Red icon as an example + return menu = ( item('Register to Autostart', add_to_autostart), @@ -169,10 +206,26 @@ def setup_tray_icon(): icon = pystray.Icon("mAId", icon_image, menu=pystray.Menu(*menu)) icon.run() - +# Ensure the tray icon is removed when the app exits +atexit.register(cleanup_and_exit) response_times = [] ma_window_size = 10 # Moving average over the last 10 responses +def log_transcription_time(transcription_time): + """Logs the transcription time and updates the moving average.""" + global response_times + + # Add the transcription time to the list + response_times.append(transcription_time) + + # If the number of logged times exceeds the window size, remove the oldest entry + if len(response_times) > ma_window_size: + response_times.pop(0) + + # Calculate and print the moving average + moving_average = sum(response_times) / len(response_times) + print(f"Moving Average of Transcription Time (last {ma_window_size} responses): {moving_average:.2f} seconds.") + def main_loop(): """Continuously listen for key or mouse press and transcribe audio.""" @@ -201,7 +254,10 @@ def main_loop(): pyperclip.copy(transcribed_text) print("Transcribed text copied to clipboard.") + + if __name__ == "__main__": + # Start the tray icon in a separate thread so it doesn't block the main functionality tray_thread = threading.Thread(target=setup_tray_icon) tray_thread.daemon = True diff --git a/agent-mAId/main.spec b/agent-mAId/main.spec index 2ba8dd9..914fdae 100644 --- a/agent-mAId/main.spec +++ b/agent-mAId/main.spec @@ -5,7 +5,7 @@ a = Analysis( ['main.py'], pathex=[], binaries=[], - datas=[], + datas=[('config.json', '.'), ('mic.webp', '.')], hiddenimports=[], hookspath=[], hooksconfig={}, @@ -14,6 +14,7 @@ a = Analysis( noarchive=False, optimize=0, ) + pyz = PYZ(a.pure) exe = EXE( diff --git a/agent-mAId/output.wav b/agent-mAId/output.wav index 8bbccc2..3fe9131 100644 Binary files a/agent-mAId/output.wav and b/agent-mAId/output.wav differ