331 lines
12 KiB
Python
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() |