MenuBase/menu.py

331 lines
12 KiB
Python

# 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()