# menu.py """ Main menu interface for the application. Integrates logging, progress bar, and provides access to all functionality. """ import tkinter as tk from tkinter import ttk, filedialog import subprocess import sys import os import json from queue import Queue import threading from pathlib import Path from typing import Optional, Dict, Any from utils.logging_manager import LoggingManager from utils.progress_bar import ProgressBar from utils.file_utils import select_file, select_directory from config.api_setup import setup_apis from services.llm.llm_factory import LLMFactory class MainMenu: def load_config(self): """Load configuration from JSON file""" config_file = os.path.join(self.work_dir, "config.json") if os.path.exists(config_file): try: with open(config_file, 'r') as f: config = json.load(f) self.work_dir = config.get('work_dir', self.work_dir) self.work_dir_var.set(self.work_dir) self.logging_manager.logger.info("Configuration loaded successfully") except Exception as e: self.logging_manager.logger.error(f"Error loading configuration: {e}") def save_config(self): """Save configuration to JSON file""" config_file = os.path.join(self.work_dir, "config.json") try: config = { 'work_dir': self.work_dir } with open(config_file, 'w') as f: json.dump(config, f, indent=4) self.logging_manager.logger.info("Configuration saved successfully") except Exception as e: self.logging_manager.logger.error(f"Error saving configuration: {e}") def __init__(self, root: tk.Tk): self.root = root self.root.title("Process Manager") self.root.geometry("1200x800") # Initialize state self.work_dir = os.path.expanduser("~/Documents/WorkFolder") self.queue = Queue() self.logging_manager = LoggingManager(self.work_dir) self.after_id = None # Create UI self.setup_ui() self.setup_logging() # Load configuration try: self.load_config() except Exception as e: self.logging_manager.logger.error(f"Error loading initial configuration: {e}") # Configure closing behavior self.root.protocol("WM_DELETE_WINDOW", self.on_closing) def on_closing(self): """Handle window closing""" try: # Cancel any pending after callbacks if self.after_id: self.root.after_cancel(self.after_id) # Save current configuration self.save_config() # Stop all running threads for thread in threading.enumerate(): if thread != threading.main_thread(): try: thread.join(timeout=1.0) # Give threads 1 second to finish except: pass # Destroy all widgets for widget in self.root.winfo_children(): widget.destroy() # Quit the application self.root.quit() self.root.destroy() sys.exit(0) # Ensure complete exit except Exception as e: print(f"Error during shutdown: {e}") sys.exit(1) # Force exit if there's an error def setup_ui(self): """Setup the main user interface""" # Main frame self.main_frame = ttk.Frame(self.root, padding="10") self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Make the main frame expandable self.root.grid_rowconfigure(0, weight=1) self.root.grid_columnconfigure(0, weight=1) # Setup UI sections self.setup_config_section() self.setup_buttons_section() self.setup_output_section() # Configure grid weights for main frame self.main_frame.grid_columnconfigure(0, weight=1) self.main_frame.grid_rowconfigure(2, weight=1) # Make output section expandable def setup_config_section(self): """Setup the configuration section""" config_frame = ttk.LabelFrame(self.main_frame, text="Configuration", padding="5") config_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=5) # Work directory selection ttk.Label(config_frame, text="Work Directory:").grid(row=0, column=0, padx=5) self.work_dir_var = tk.StringVar(value=self.work_dir) work_dir_entry = ttk.Entry(config_frame, textvariable=self.work_dir_var, width=50) work_dir_entry.grid(row=0, column=1, padx=5) ttk.Button( config_frame, text="Browse", command=self.select_work_dir ).grid(row=0, column=2, padx=5) ttk.Button( config_frame, text="Open in Explorer", command=self.open_work_dir ).grid(row=0, column=3, padx=5) # API Setup button ttk.Button( config_frame, text="Setup APIs", command=self.setup_apis ).grid(row=0, column=4, padx=5) config_frame.grid_columnconfigure(1, weight=1) def setup_apis(self): """Run API setup and log the result""" try: setup_apis() self.logging_manager.logger.info("API setup completed") except Exception as e: self.logging_manager.logger.error(f"Error in API setup: {e}") def run_llm_operation(self): """Example of running an LLM operation""" def run(): logger = self.logging_manager.logger logger.info("Starting LLM operation...") try: # Check if API keys are configured from config.api_keys import APIKeyManager if not APIKeyManager.get_openai_key(): logger.error("OpenAI API key not configured. Please use 'Setup APIs' first.") return # Create LLM service llm_service = LLMFactory.create_service("openai") # Example operation response = llm_service.generate_text("Tell me a joke") logger.info(f"LLM Response: {response}") except Exception as e: logger.error(f"Error in LLM operation: {e}") logger.info("LLM operation completed!") threading.Thread(target=run, daemon=True).start() def setup_buttons_section(self): """Setup the buttons section""" buttons_frame = ttk.LabelFrame(self.main_frame, text="Operations", padding="5") buttons_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5) # Add buttons for different operations ttk.Button( buttons_frame, text="Process Files", command=self.run_process_files ).grid(row=0, column=0, padx=5, pady=5) ttk.Button( buttons_frame, text="LLM Operation", command=self.run_llm_operation ).grid(row=0, column=1, padx=5, pady=5) ttk.Button( buttons_frame, text="Clear Output", command=self.clear_output ).grid(row=0, column=2, padx=5, pady=5) buttons_frame.grid_columnconfigure(3, weight=1) # Push buttons to the left def setup_output_section(self): """Setup the output section with text widget and scrollbars""" output_frame = ttk.LabelFrame(self.main_frame, text="Output", padding="5") output_frame.grid(row=2, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5) # Create text widget self.output_text = tk.Text(output_frame, wrap="none", height=20) self.output_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Scrollbars scrollbar_y = ttk.Scrollbar( output_frame, orient="vertical", command=self.output_text.yview ) scrollbar_y.grid(row=0, column=1, sticky=(tk.N, tk.S)) scrollbar_x = ttk.Scrollbar( output_frame, orient="horizontal", command=self.output_text.xview ) scrollbar_x.grid(row=1, column=0, sticky=(tk.W, tk.E)) self.output_text.configure( yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set ) # Configure grid weights output_frame.grid_rowconfigure(0, weight=1) output_frame.grid_columnconfigure(0, weight=1) def setup_logging(self): """Setup logging to GUI""" self.logging_manager.setup_gui_logging(self.output_text, self.queue) self.update_output() def update_output(self): """Process logging queue and schedule next update""" self.logging_manager.process_log_queue() self.after_id = self.root.after(100, self.update_output) def clear_output(self): """Clear output text""" self.logging_manager.clear_output() def clear_output(self): """Clear output text""" self.logging_manager.clear_output() def select_work_dir(self): """Select working directory""" dir_path = select_directory("Select Work Directory") if dir_path: self.work_dir = dir_path self.work_dir_var.set(dir_path) self.logging_manager.logger.info(f"Work directory set to: {dir_path}") os.makedirs(os.path.join(dir_path, "logs"), exist_ok=True) def open_work_dir(self): """Open working directory in file explorer""" if os.path.exists(self.work_dir): if sys.platform == "win32": os.startfile(self.work_dir) else: subprocess.run(["xdg-open", self.work_dir]) else: self.logging_manager.logger.error(f"Directory does not exist: {self.work_dir}") def run_process_files(self): """Example of running a file processing operation""" def run(): logger = self.logging_manager.logger logger.info("Starting file processing...") # Example progress bar usage total_steps = 100 progress = ProgressBar(total_steps, "Processing files:", "Complete") progress.set_output_callback(lambda x: self.queue.put(x)) progress.start() for i in range(total_steps): # Simulate work import time time.sleep(0.1) progress.increment() logger.info(f"Processing step {i + 1}") progress.finish() logger.info("File processing completed!") threading.Thread(target=run, daemon=True).start() def main(): try: root = tk.Tk() app = MainMenu(root) root.mainloop() except Exception as e: print(f"Error in main: {e}") sys.exit(1) finally: # Ensure all threads are stopped for thread in threading.enumerate(): if thread != threading.main_thread(): try: thread.join(timeout=1.0) except: pass sys.exit(0) if __name__ == "__main__": main()