```# image_predictor_app.py

import os
import sys
import threading
import logging
import json
from io import BytesIO
from typing import Optional, List, Dict

import gi
import requests
from PIL import Image
from fastai.vision.all import (
    load_learner,
    PILImage,
    Learner,
    vision_learner,
    ImageDataLoaders,
    models,
    accuracy,
    Callback,
    Resize,
    aug_transforms,
)
from fastai.metrics import accuracy
import matplotlib.pyplot as plt

gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GdkPixbuf, Gdk, GLib

# ------------------------------- #
#          Logging Setup         #
# ------------------------------- #

logging.basicConfig(
    level=logging.DEBUG,  # Set to DEBUG for detailed logs
    format='%(asctime)s %(levelname)s:%(name)s: %(message)s',
    handlers=[
        logging.FileHandler("image_predictor_app.log"),
        logging.StreamHandler(sys.stdout)
    ]
)
logger = logging.getLogger(__name__)

# ------------------------------- #
#        Helper Classes          #
# ------------------------------- #

class TrainingCallback(Callback):
    """Callback to monitor training progress and output."""

    def __init__(self, app):
        super().__init__()
        self.app = app

    def after_epoch(self):
        epoch = self.learn.epoch
        loss = self.learn.recorder.losses[-1]
        acc = self.learn.recorder.metrics[-1]
        message = f"Epoch {epoch + 1}: Loss={loss:.4f}, Accuracy={acc * 100:.2f}%"
        GLib.idle_add(self.app.append_training_output, message)

    def after_fit(self):
        GLib.idle_add(self.app.append_training_output, "Training completed successfully.", success=True)
        GLib.idle_add(self.app.update_training_graphs)

# ------------------------------- #
#          Main App Class        #
# ------------------------------- #

class ImagePredictorApp(Gtk.Window):
    def __init__(self):
        super().__init__(title="Image Predictor with Enhanced Features")
        self.set_border_width(10)
        self.set_default_size(1200, 800)  # Increased size to accommodate more features

        # Initialize model attribute
        self.model: Optional[Learner] = None

        # Initialize history
        self.prediction_history: List[Dict] = []

        # Initialize user settings
        self.settings_file = "settings.json"
        self.settings = self.load_settings()

        # Initialize UI components
        self.init_ui()

        # Initialize thread list for graceful shutdown
        self.threads = []

        # Connect the destroy signal for graceful shutdown
        self.connect("destroy", self.on_destroy)

    # -------------------- #
    #      UI Initialization
    # -------------------- #

    def init_ui(self):
        """Initialize and arrange all UI components."""
        # Main container using vertical box
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        self.add(main_box)

        # Header with toolbar
        toolbar = Gtk.Toolbar()
        main_box.pack_start(toolbar, False, False, 0)

        # Add toolbar buttons
        login_button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_DIAL_PAD)
        login_button.set_label("Login")
        login_button.set_is_important(True)
        login_button.connect("clicked", self.on_login_clicked)
        toolbar.insert(login_button, 0)

        logout_button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_CLOSE)
        logout_button.set_label("Logout")
        logout_button.set_is_important(True)
        logout_button.connect("clicked", self.on_logout_clicked)
        toolbar.insert(logout_button, 1)
        logout_button.set_sensitive(False)

        export_button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_SAVE)
        export_button.set_label("Export Results")
        export_button.connect("clicked", self.on_export_results)
        toolbar.insert(export_button, 2)
        export_button.set_sensitive(False)

        # Notebook for tabbed sections
        notebook = Gtk.Notebook()
        main_box.pack_start(notebook, True, True, 0)

        # --- Prediction Tab ---
        prediction_tab = self.create_prediction_tab()
        notebook.append_page(prediction_tab, Gtk.Label(label="Prediction"))

        # --- Training Tab ---
        training_tab = self.create_training_tab()
        notebook.append_page(training_tab, Gtk.Label(label="Training"))

        # --- Settings Tab ---
        settings_tab = self.create_settings_tab()
        notebook.append_page(settings_tab, Gtk.Label(label="Settings"))

        # --- Logs Tab ---
        logs_tab = self.create_logs_tab()
        notebook.append_page(logs_tab, Gtk.Label(label="Logs"))

        # --- History Tab ---
        history_tab = self.create_history_tab()
        notebook.append_page(history_tab, Gtk.Label(label="History"))

        # --- Visualization Tab ---
        visualization_tab = self.create_visualization_tab()
        notebook.append_page(visualization_tab, Gtk.Label(label="Visualization"))

    # -------------------- #
    #     Prediction Tab
    # -------------------- #

    def create_prediction_tab(self) -> Gtk.Box:
        """Create the Prediction tab UI."""
        prediction_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        prediction_box.set_margin_start(10)
        prediction_box.set_margin_end(10)
        prediction_box.set_margin_top(10)
        prediction_box.set_margin_bottom(10)

        # Load Model Button
        load_model_button = Gtk.Button(label="Load Model (.pkl / .pth)")
        load_model_button.connect("clicked", self.on_load_model)
        prediction_box.pack_start(load_model_button, False, False, 0)

        # Image Selection
        image_select_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        self.image_entry = Gtk.Entry()
        self.image_entry.set_placeholder_text("Enter image path or URL")
        self.image_entry.connect("activate", self.on_enter_image_path)
        image_browse_button = Gtk.Button(label="Browse Image")
        image_browse_button.connect("clicked", self.on_select_image)
        image_select_box.pack_start(self.image_entry, True, True, 0)
        image_select_box.pack_start(image_browse_button, False, False, 0)
        prediction_box.pack_start(image_select_box, False, False, 0)

        # Advanced Settings Expander
        advanced_expander = Gtk.Expander(label="Advanced Settings")
        advanced_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5)
        advanced_box.set_margin_start(10)
        advanced_box.set_margin_end(10)
        advanced_box.set_margin_bottom(10)
        advanced_box.set_margin_top(10)
        advanced_expander.add(advanced_box)

        # Prediction Threshold
        self.threshold_label = Gtk.Label(label="Prediction Threshold: 50%")
        self.threshold_label.set_xalign(0)
        advanced_box.pack_start(self.threshold_label, False, False, 0)

        self.threshold_adjustment = Gtk.Adjustment(value=50, lower=0, upper=100, step_increment=1, page_increment=10)
        self.threshold_slider = Gtk.Scale(orientation=Gtk.Orientation.HORIZONTAL, adjustment=self.threshold_adjustment)
        self.threshold_slider.set_digits(0)
        self.threshold_slider.connect("value-changed", self.on_threshold_changed)
        advanced_box.pack_start(self.threshold_slider, False, False, 0)

        prediction_box.pack_start(advanced_expander, False, False, 0)

        # Predict Button
        predict_button = Gtk.Button(label="Predict")
        predict_button.connect("clicked", self.on_predict)
        predict_button.set_sensitive(False)
        prediction_box.pack_start(predict_button, False, False, 0)

        # Output Display
        self.output_label = Gtk.Label(label="Output will be displayed here.")
        self.output_label.set_line_wrap(True)
        self.output_label.set_xalign(0)
        self.output_label.set_justify(Gtk.Justification.LEFT)
        prediction_box.pack_start(self.output_label, False, False, 0)

        # Progress Bar
        self.prediction_progress = Gtk.ProgressBar()
        self.prediction_progress.set_show_text(True)
        self.prediction_progress.set_visible(False)
        prediction_box.pack_start(self.prediction_progress, False, False, 0)

        return prediction_box

    # -------------------- #
    #      Training Tab
    # -------------------- #

    def create_training_tab(self) -> Gtk.Box:
        """Create the Training tab UI."""
        training_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        training_box.set_margin_start(10)
        training_box.set_margin_end(10)
        training_box.set_margin_top(10)
        training_box.set_margin_bottom(10)

        # Dataset Selection
        dataset_select_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        self.dataset_entry = Gtk.Entry()
        self.dataset_entry.set_placeholder_text("Enter dataset path")
        self.dataset_button = Gtk.Button(label="Browse Dataset")
        self.dataset_button.connect("clicked", self.on_select_dataset)
        dataset_select_box.pack_start(self.dataset_entry, True, True, 0)
        dataset_select_box.pack_start(self.dataset_button, False, False, 0)
        training_box.pack_start(dataset_select_box, False, False, 0)

        # Model Architecture Selection
        architecture_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        architecture_label = Gtk.Label(label="Model Architecture:")
        architecture_label.set_xalign(0)
        architecture_box.pack_start(architecture_label, False, False, 0)

        self.architecture_combo = Gtk.ComboBoxText()
        for arch in ["resnet18", "resnet34", "resnet50", "resnet101", "resnet152"]:
            self.architecture_combo.append_text(arch)
        self.architecture_combo.set_active(1)  # Default to resnet34
        architecture_box.pack_start(self.architecture_combo, False, False, 0)
        training_box.pack_start(architecture_box, False, False, 0)

        # Hyperparameters Expander
        hyperparams_expander = Gtk.Expander(label="Hyperparameters")
        hyperparams_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        hyperparams_box.set_margin_start(10)
        hyperparams_box.set_margin_end(10)
        hyperparams_box.set_margin_bottom(10)
        hyperparams_box.set_margin_top(10)
        hyperparams_expander.add(hyperparams_box)

        hyperparams_grid = Gtk.Grid(column_spacing=10, row_spacing=10)

        # Learning Rate
        lr_label = Gtk.Label(label="Learning Rate:")
        lr_label.set_halign(Gtk.Align.END)
        hyperparams_grid.attach(lr_label, 0, 0, 1, 1)
        self.lr_entry = Gtk.Entry()
        self.lr_entry.set_text("1e-3")
        hyperparams_grid.attach(self.lr_entry, 1, 0, 1, 1)

        # Batch Size
        bs_label = Gtk.Label(label="Batch Size:")
        bs_label.set_halign(Gtk.Align.END)
        hyperparams_grid.attach(bs_label, 0, 1, 1, 1)
        self.bs_entry = Gtk.Entry()
        self.bs_entry.set_text("32")
        hyperparams_grid.attach(self.bs_entry, 1, 1, 1, 1)

        # Number of Epochs
        epochs_label = Gtk.Label(label="Number of Epochs:")
        epochs_label.set_halign(Gtk.Align.END)
        hyperparams_grid.attach(epochs_label, 0, 2, 1, 1)
        self.epochs_entry = Gtk.Entry()
        self.epochs_entry.set_text("10")
        hyperparams_grid.attach(self.epochs_entry, 1, 2, 1, 1)

        # Save Model Name
        save_label = Gtk.Label(label="Save Model As:")
        save_label.set_halign(Gtk.Align.END)
        hyperparams_grid.attach(save_label, 0, 3, 1, 1)
        self.save_entry = Gtk.Entry()
        self.save_entry.set_text("trained_model.pkl")
        hyperparams_grid.attach(self.save_entry, 1, 3, 1, 1)

        hyperparams_box.pack_start(hyperparams_grid, False, False, 0)
        training_box.pack_start(hyperparams_expander, False, False, 0)

        # Train Model Button
        train_button = Gtk.Button(label="Train Model")
        train_button.connect("clicked", self.on_train_model)
        training_box.pack_start(train_button, False, False, 0)

        # Training Output Display within a Scrolled Window
        self.training_output = Gtk.TextView()
        self.training_output.set_editable(False)
        self.training_output.set_wrap_mode(Gtk.WrapMode.WORD)
        training_output_scrolled = Gtk.ScrolledWindow()
        training_output_scrolled.set_vexpand(True)
        training_output_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        training_output_scrolled.add(self.training_output)
        training_box.pack_start(training_output_scrolled, True, True, 0)

        # Progress Bar
        self.training_progress = Gtk.ProgressBar()
        self.training_progress.set_show_text(True)
        self.training_progress.set_visible(False)
        training_box.pack_start(self.training_progress, False, False, 0)

        return training_box

    # -------------------- #
    #        Settings Tab
    # -------------------- #

    def create_settings_tab(self) -> Gtk.Box:
        """Create the Settings tab UI."""
        settings_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        settings_box.set_margin_start(10)
        settings_box.set_margin_end(10)
        settings_box.set_margin_top(10)
        settings_box.set_margin_bottom(10)

        # Theme Selection
        theme_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        theme_label = Gtk.Label(label="Theme:")
        theme_label.set_xalign(0)
        theme_box.pack_start(theme_label, False, False, 0)

        self.theme_combo = Gtk.ComboBoxText()
        for theme in ["Light", "Dark"]:
            self.theme_combo.append_text(theme)
        current_theme = self.settings.get("theme", "Light")
        self.theme_combo.set_active(["Light", "Dark"].index(current_theme))
        theme_box.pack_start(self.theme_combo, False, False, 0)
        self.theme_combo.connect("changed", self.on_theme_changed)
        settings_box.pack_start(theme_box, False, False, 0)

        # Save Settings Button
        save_settings_button = Gtk.Button(label="Save Settings")
        save_settings_button.connect("clicked", self.on_save_settings)
        settings_box.pack_start(save_settings_button, False, False, 0)

        return settings_box

    # -------------------- #
    #          Logs Tab
    # -------------------- #

    def create_logs_tab(self) -> Gtk.Box:
        """Create the Logs tab UI."""
        logs_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        logs_box.set_margin_start(10)
        logs_box.set_margin_end(10)
        logs_box.set_margin_top(10)
        logs_box.set_margin_bottom(10)

        # Logs Display within a Scrolled Window
        self.logs_output = Gtk.TextView()
        self.logs_output.set_editable(False)
        self.logs_output.set_wrap_mode(Gtk.WrapMode.WORD)
        logs_output_scrolled = Gtk.ScrolledWindow()
        logs_output_scrolled.set_vexpand(True)
        logs_output_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        logs_output_scrolled.add(self.logs_output)
        logs_box.pack_start(logs_output_scrolled, True, True, 0)

        # Refresh Logs Button
        refresh_logs_button = Gtk.Button(label="Refresh Logs")
        refresh_logs_button.connect("clicked", self.on_refresh_logs)
        logs_box.pack_start(refresh_logs_button, False, False, 0)

        return logs_box

    # -------------------- #
    #        History Tab
    # -------------------- #

    def create_history_tab(self) -> Gtk.Box:
        """Create the History tab UI."""
        history_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        history_box.set_margin_start(10)
        history_box.set_margin_end(10)
        history_box.set_margin_top(10)
        history_box.set_margin_bottom(10)

        # History TreeView
        self.history_liststore = Gtk.ListStore(str, str, str, str, str)  # Image, Prediction, Confidence, Date, Time
        self.history_treeview = Gtk.TreeView(model=self.history_liststore)

        for i, column_title in enumerate(["Image", "Prediction", "Confidence (%)", "Date", "Time"]):
            renderer = Gtk.CellRendererText()
            column = Gtk.TreeViewColumn(column_title, renderer, text=i)
            self.history_treeview.append_column(column)

        history_scrolled = Gtk.ScrolledWindow()
        history_scrolled.set_vexpand(True)
        history_scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        history_scrolled.add(self.history_treeview)
        history_box.pack_start(history_scrolled, True, True, 0)

        # Clear History Button
        clear_history_button = Gtk.Button(label="Clear History")
        clear_history_button.connect("clicked", self.on_clear_history)
        history_box.pack_start(clear_history_button, False, False, 0)

        return history_box

    # -------------------- #
    #   Visualization Tab
    # -------------------- #

    def create_visualization_tab(self) -> Gtk.Box:
        """Create the Visualization tab UI."""
        visualization_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        visualization_box.set_margin_start(10)
        visualization_box.set_margin_end(10)
        visualization_box.set_margin_top(10)
        visualization_box.set_margin_bottom(10)

        # Training Metrics Graphs
        self.training_graphs = {
            "Loss": plt.figure(figsize=(5,4)),
            "Accuracy": plt.figure(figsize=(5,4))
        }

        # Display Loss Graph
        loss_label = Gtk.Label(label="Training Loss Over Epochs")
        visualization_box.pack_start(loss_label, False, False, 0)
        loss_image = Gtk.Image()
        loss_path = "loss_graph.png"
        self.render_training_graph("Loss", self.training_graphs["Loss"], loss_path)
        loss_image.set_from_file(loss_path)
        visualization_box.pack_start(loss_image, False, False, 0)

        # Display Accuracy Graph
        acc_label = Gtk.Label(label="Training Accuracy Over Epochs")
        visualization_box.pack_start(acc_label, False, False, 0)
        acc_image = Gtk.Image()
        acc_path = "accuracy_graph.png"
        self.render_training_graph("Accuracy", self.training_graphs["Accuracy"], acc_path)
        acc_image.set_from_file(acc_path)
        visualization_box.pack_start(acc_image, False, False, 0)

        # Refresh Visualizations Button
        refresh_visuals_button = Gtk.Button(label="Refresh Visuals")
        refresh_visuals_button.connect("clicked", self.on_refresh_visuals)
        visualization_box.pack_start(refresh_visuals_button, False, False, 0)

        return visualization_box

    # -------------------- #
    #        Methods
    # -------------------- #

    # --- User Authentication Methods ---

    def on_login_clicked(self, button: Gtk.Button):
        """Handle the Login button click."""
        dialog = Gtk.Dialog(title="Login", parent=self, flags=0)
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OK, Gtk.ResponseType.OK
        )
        dialog.set_default_size(200, 100)

        box = dialog.get_content_area()
        grid = Gtk.Grid(column_spacing=10, row_spacing=10)
        box.add(grid)

        username_label = Gtk.Label(label="Username:")
        username_label.set_xalign(0)
        grid.attach(username_label, 0, 0, 1, 1)
        username_entry = Gtk.Entry()
        grid.attach(username_entry, 1, 0, 1, 1)

        password_label = Gtk.Label(label="Password:")
        password_label.set_xalign(0)
        grid.attach(password_label, 0, 1, 1, 1)
        password_entry = Gtk.Entry()
        password_entry.set_visibility(False)
        grid.attach(password_entry, 1, 1, 1, 1)

        dialog.show_all()
        response = dialog.run()

        if response == Gtk.ResponseType.OK:
            username = username_entry.get_text()
            password = password_entry.get_text()
            if self.authenticate_user(username, password):
                logger.info(f"User '{username}' logged in successfully.")
                self.output_label.set_markup(f"<span foreground='green'>User '{username}' logged in successfully.</span>")
                self.update_toolbar_buttons(logged_in=True)
            else:
                logger.warning(f"Failed login attempt for user '{username}'.")
                self.output_label.set_markup("<span foreground='red'>Invalid username or password.</span>")
        dialog.destroy()

    def on_logout_clicked(self, button: Gtk.Button):
        """Handle the Logout button click."""
        self.model = None
        self.predict_button.set_sensitive(False)
        self.output_label.set_markup("<span foreground='red'>User logged out.</span>")
        self.update_toolbar_buttons(logged_in=False)
        logger.info("User logged out.")

    def authenticate_user(self, username: str, password: str) -> bool:
        """Authenticate user credentials."""
        # Placeholder for authentication logic
        # In production, use secure authentication mechanisms
        return username == "admin" and password == "password"

    def update_toolbar_buttons(self, logged_in: bool):
        """Update toolbar buttons based on login state."""
        for child in self.get_children():
            if isinstance(child, Gtk.Toolbar):
                for tool in child.get_children():
                    if tool.get_label() == "Login":
                        tool.set_sensitive(not logged_in)
                    elif tool.get_label() == "Logout":
                        tool.set_sensitive(logged_in)
                    elif tool.get_label() == "Export Results":
                        tool.set_sensitive(logged_in and len(self.prediction_history) > 0)

    # --- Theme Management ---

    def on_theme_changed(self, combo: Gtk.ComboBoxText):
        """Handle the Theme change."""
        selected_theme = combo.get_active_text()
        self.settings['theme'] = selected_theme
        if selected_theme == "Dark":
            self.set_dark_theme()
        else:
            self.set_light_theme()
        logger.info(f"Theme changed to: {selected_theme}")

    def set_dark_theme(self):
        """Apply dark theme to the application."""
        css_provider = Gtk.CssProvider()
        css = """
        * {
            background-color: #2E3436;
            color: #FFFFFF;
        }
        GtkEntry, GtkTextView, GtkLabel {
            background-color: #555753;
            color: #FFFFFF;
        }
        GtkButton {
            background-color: #4E555B;
            color: #FFFFFF;
        }
        GtkToolbar, GtkScrolledWindow {
            background-color: #2E3436;
        }
        """
        css_provider.load_from_data(css.encode())
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        logger.info("Dark theme applied.")

    def set_light_theme(self):
        """Apply light theme to the application."""
        css_provider = Gtk.CssProvider()
        css = """
        * {
            background-color: #FFFFFF;
            color: #000000;
        }
        GtkEntry, GtkTextView, GtkLabel {
            background-color: #EEEEEE;
            color: #000000;
        }
        GtkButton {
            background-color: #DDDDDD;
            color: #000000;
        }
        GtkToolbar, GtkScrolledWindow {
            background-color: #FFFFFF;
        }
        """
        css_provider.load_from_data(css.encode())
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        logger.info("Light theme applied.")

    # --- Settings Management ---

    def on_save_settings(self, button: Gtk.Button):
        """Handle the Save Settings button click."""
        try:
            with open(self.settings_file, 'w') as f:
                json.dump(self.settings, f, indent=4)
            self.output_label.set_markup("<span foreground='green'>Settings saved successfully.</span>")
            logger.info("User settings saved.")
        except Exception as e:
            self.output_label.set_markup(f"<span foreground='red'>Failed to save settings: {e}</span>")
            logger.error(f"Failed to save settings: {e}", exc_info=True)

    def load_settings(self) -> Dict:
        """Load user settings from a file."""
        if os.path.isfile(self.settings_file):
            try:
                with open(self.settings_file, 'r') as f:
                    settings = json.load(f)
                logger.info("User settings loaded.")
                return settings
            except Exception as e:
                logger.error(f"Failed to load settings: {e}", exc_info=True)
                return {}
        else:
            logger.info("No settings file found. Using default settings.")
            return {}

    # --- Model Loading ---

    def on_load_model(self, button: Gtk.Button):
        """Handle the Load Model button click."""
        dialog = Gtk.FileChooserDialog(
            title="Select a Model File",
            parent=self,
            action=Gtk.FileChooserAction.OPEN
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN, Gtk.ResponseType.OK
        )
        dialog.set_filter(self.create_model_filter())

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            model_path = dialog.get_filename()
            logger.info(f"Selected model file: {model_path}")
            self.load_model_async(model_path)
        dialog.destroy()

    def load_model_async(self, model_path: str):
        """Load the model in a separate thread to keep UI responsive."""
        self.set_controls_sensitive(False)
        self.show_progress(self.prediction_progress, "Loading model...")
        thread = threading.Thread(target=self.load_model, args=(model_path,), daemon=True)
        self.threads.append(thread)
        thread.start()

    def load_model(self, model_path: str):
        """Load the machine learning model from the specified path."""
        try:
            if not os.path.isfile(model_path):
                raise FileNotFoundError("The selected model file does not exist.")

            model_size_mb = os.path.getsize(model_path) / (1024 * 1024)
            MAX_MODEL_SIZE_MB = 500  # Example limit
            if model_size_mb > MAX_MODEL_SIZE_MB:
                raise ValueError(f"Model size {model_size_mb:.2f} MB exceeds the maximum allowed size of {MAX_MODEL_SIZE_MB} MB.")

            learner = load_learner(model_path)
            self.model = learner
            GLib.idle_add(self.on_model_loaded, learner, model_path)
            logger.info(f"Model loaded successfully from: {model_path}")
        except Exception as e:
            self.model = None
            GLib.idle_add(self.on_model_load_failed, str(e))
            logger.error(f"Failed to load model: {e}", exc_info=True)
        finally:
            GLib.idle_add(self.hide_progress, self.prediction_progress)
            GLib.idle_add(self.set_controls_sensitive, True)
            GLib.idle_add(self.update_toolbar_buttons)

    def on_model_loaded(self, learner: Learner, model_path: str):
        """Callback when the model is successfully loaded."""
        self.predict_button.set_sensitive(True)  # Enable Predict button
        self.output_label.set_markup(f"<span foreground='green'>Model loaded successfully from:</span>\n{model_path}")
        self.append_training_output(f"Model loaded successfully from: {model_path}")

    def on_model_load_failed(self, error_message: str):
        """Callback when the model fails to load."""
        self.predict_button.set_sensitive(False)
        self.output_label.set_markup(f"<span foreground='red'>Failed to load model:</span>\n{error_message}")
        self.append_training_output(f"Failed to load model: {error_message}", error=True)

    # --- Image Selection ---

    def on_select_image(self, button: Gtk.Button):
        """Handle the Browse Image button click."""
        dialog = Gtk.FileChooserDialog(
            title="Select an Image",
            parent=self,
            action=Gtk.FileChooserAction.OPEN
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN, Gtk.ResponseType.OK
        )
        dialog.set_filter(self.create_image_filter())

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            image_path = dialog.get_filename()
            self.image_entry.set_text(image_path)
            self.display_image(image_path)
            logger.info(f"Selected image file: {image_path}")
        dialog.destroy()

    def on_enter_image_path(self, widget):
        """Handle pressing Enter in the image entry."""
        image_input = self.image_entry.get_text().strip()
        if image_input:
            self.display_image(image_input)

    # --- Prediction Method ---

    def on_predict(self, button: Gtk.Button):
        """Handle the Predict button click."""
        if not self.model:
            self.output_label.set_markup("<span foreground='red'>Please load a model first.</span>")
            return

        image_input = self.image_entry.get_text().strip()
        if not image_input:
            self.output_label.set_markup("<span foreground='red'>Please provide an image path or URL.</span>")
            return

        # Validate image path
        if not (image_input.startswith(('http://', 'https://', 'ftp://')) or os.path.isfile(image_input)):
            self.output_label.set_markup("<span foreground='red'>Invalid image path or URL.</span>")
            return

        self.set_controls_sensitive(False)
        self.show_progress(self.prediction_progress, "Performing prediction...")
        thread = threading.Thread(target=self.predict_image, args=(image_input,), daemon=True)
        self.threads.append(thread)
        thread.start()

    def predict_image(self, image_input: str):
        """Load the image and perform prediction in a separate thread."""
        try:
            if not image_input:
                raise ValueError("No image input provided.")

            image = self.load_image(image_input)

            # Check image size
            width, height = image.size
            MAX_IMAGE_DIMENSION = 2000  # Example limit
            if width > MAX_IMAGE_DIMENSION or height > MAX_IMAGE_DIMENSION:
                raise ValueError(f"Image dimensions ({width}x{height}) exceed the maximum allowed size of {MAX_IMAGE_DIMENSION}px.")

            # Perform prediction
            pred, pred_idx, outputs = self.model.predict(image)
            probabilities = outputs.numpy() * 100  # Convert to percentage
            classes = self.model.dls.vocab  # Get class names
            threshold = self.threshold_slider.get_value()  # Get the threshold value

            # Prepare formatted prediction results
            output_lines = ["<b>Predictions:</b>"]
            displayed = 0
            max_display = 10  # Limit the number of displayed predictions

            for class_name, probability in zip(classes, probabilities):
                if probability >= threshold:
                    line = f"• {class_name}: {probability:.2f}%"
                    output_lines.append(line)
                    displayed += 1
                    if displayed >= max_display:
                        output_lines.append("• ...")
                        break

            if displayed == 0:
                output_lines.append("<span foreground='orange'>No predictions met the threshold.</span>")

            output_lines.append(f"\n<b>Overall Prediction:</b> {pred}")

            # Update the output label using Pango Markup for better formatting
            markup_text = "\n".join(output_lines)
            GLib.idle_add(self.update_output, markup_text)

            # Append to history
            prediction_entry = {
                "image": image_input,
                "prediction": pred,
                "confidence": f"{probabilities[pred_idx]:.2f}%",
                "date": self.get_current_date(),
                "time": self.get_current_time()
            }
            self.prediction_history.append(prediction_entry)
            GLib.idle_add(self.update_history)

            logger.info(f"Prediction successful for image: {image_input}")
        except ValueError as ve:
            logger.error(f"ValueError during prediction: {ve}", exc_info=True)
            GLib.idle_add(self.update_output, f"<span foreground='red'>Error: {ve}</span>")
        except Exception as e:
            logger.error(f"Error during prediction: {e}", exc_info=True)
            GLib.idle_add(self.update_output, f"<span foreground='red'>Error during prediction:</span>\n{e}")
        finally:
            # Remove the thread from the list
            current_thread = threading.current_thread()
            if current_thread in self.threads:
                self.threads.remove(current_thread)

            GLib.idle_add(self.hide_progress, self.prediction_progress)
            GLib.idle_add(self.set_controls_sensitive, True)

    # --- History Management ---

    def update_history(self):
        """Update the History TreeView with new entries."""
        self.history_liststore.clear()
        for entry in self.prediction_history:
            self.history_liststore.append([
                entry["image"],
                entry["prediction"],
                entry["confidence"],
                entry["date"],
                entry["time"]
            ])

    def on_clear_history(self, button: Gtk.Button):
        """Handle the Clear History button click."""
        self.prediction_history.clear()
        self.update_history()
        self.output_label.set_markup("<span foreground='green'>History cleared.</span>")
        logger.info("Prediction history cleared.")

    def get_current_date(self) -> str:
        """Get the current date as a string."""
        from datetime import datetime
        return datetime.now().strftime("%Y-%m-%d")

    def get_current_time(self) -> str:
        """Get the current time as a string."""
        from datetime import datetime
        return datetime.now().strftime("%H:%M:%S")

    # --- Export Functionality ---

    def on_export_results(self, button: Gtk.Button):
        """Handle the Export Results button click."""
        if not self.prediction_history:
            self.output_label.set_markup("<span foreground='red'>No prediction history to export.</span>")
            return

        dialog = Gtk.FileChooserDialog(
            title="Export Prediction Results",
            parent=self,
            action=Gtk.FileChooserAction.SAVE
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_SAVE, Gtk.ResponseType.OK
        )
        dialog.set_current_name("predictions.json")

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            export_path = dialog.get_filename()
            try:
                with open(export_path, 'w') as f:
                    json.dump(self.prediction_history, f, indent=4)
                self.output_label.set_markup(f"<span foreground='green'>Results exported successfully to:</span>\n{export_path}")
                logger.info(f"Prediction results exported to: {export_path}")
            except Exception as e:
                self.output_label.set_markup(f"<span foreground='red'>Failed to export results: {e}</span>")
                logger.error(f"Failed to export results: {e}", exc_info=True)
        dialog.destroy()

    # --- Settings Management ---

    def on_save_settings(self, button: Gtk.Button):
        """Handle the Save Settings button click."""
        try:
            with open(self.settings_file, 'w') as f:
                json.dump(self.settings, f, indent=4)
            self.output_label.set_markup("<span foreground='green'>Settings saved successfully.</span>")
            logger.info("User settings saved.")
        except Exception as e:
            self.output_label.set_markup(f"<span foreground='red'>Failed to save settings: {e}</span>")
            logger.error(f"Failed to save settings: {e}", exc_info=True)

    # --- Theme Management ---

    def set_dark_theme(self):
        """Apply dark theme to the application."""
        css_provider = Gtk.CssProvider()
        css = """
        * {
            background-color: #2E3436;
            color: #EEEEEC;
        }
        GtkEntry, GtkTextView, GtkLabel {
            background-color: #555753;
            color: #EEEEEC;
        }
        GtkButton {
            background-color: #4E555B;
            color: #EEEEEC;
        }
        GtkToolbar, GtkScrolledWindow {
            background-color: #2E3436;
        }
        """
        css_provider.load_from_data(css.encode())
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        logger.info("Dark theme applied.")

    def set_light_theme(self):
        """Apply light theme to the application."""
        css_provider = Gtk.CssProvider()
        css = """
        * {
            background-color: #FFFFFF;
            color: #000000;
        }
        GtkEntry, GtkTextView, GtkLabel {
            background-color: #EEEEEE;
            color: #000000;
        }
        GtkButton {
            background-color: #DDDDDD;
            color: #000000;
        }
        GtkToolbar, GtkScrolledWindow {
            background-color: #FFFFFF;
        }
        """
        css_provider.load_from_data(css.encode())
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )
        logger.info("Light theme applied.")

    def on_theme_changed(self, combo: Gtk.ComboBoxText):
        """Handle the Theme change."""
        selected_theme = combo.get_active_text()
        self.settings['theme'] = selected_theme
        if selected_theme == "Dark":
            self.set_dark_theme()
        else:
            self.set_light_theme()
        logger.info(f"Theme changed to: {selected_theme}")

    # --- Model Training Methods ---

    def on_train_model(self, button: Gtk.Button):
        """Handle the Train Model button click."""
        dataset_input = self.dataset_entry.get_text().strip()
        architecture = self.architecture_combo.get_active_text()
        lr = self.lr_entry.get_text().strip()
        bs = self.bs_entry.get_text().strip()
        epochs = self.epochs_entry.get_text().strip()
        save_model_name = self.save_entry.get_text().strip()

        # Validate inputs
        if not dataset_input or not os.path.isdir(dataset_input):
            self.append_training_output("Invalid dataset path.", error=True)
            return

        if not architecture:
            self.append_training_output("Please select a model architecture.", error=True)
            return

        try:
            lr = float(lr)
            if lr <= 0:
                raise ValueError("Learning rate must be positive.")
        except ValueError as ve:
            self.append_training_output(f"Invalid Learning Rate: {ve}", error=True)
            return

        try:
            bs = int(bs)
            if bs <= 0:
                raise ValueError("Batch size must be a positive integer.")
        except ValueError as ve:
            self.append_training_output(f"Invalid Batch Size: {ve}", error=True)
            return

        try:
            epochs = int(epochs)
            if epochs <= 0:
                raise ValueError("Number of epochs must be a positive integer.")
        except ValueError as ve:
            self.append_training_output(f"Invalid Number of Epochs: {ve}", error=True)
            return

        if not save_model_name:
            self.append_training_output("Please specify a name to save the trained model.", error=True)
            return

        if not save_model_name.lower().endswith('.pkl'):
            save_model_name += '.pkl'

        # Start training in a separate thread
        self.set_controls_sensitive(False)
        self.show_progress(self.training_progress, "Starting model training...")
        thread = threading.Thread(target=self.train_model, args=(dataset_input, architecture, lr, bs, epochs, save_model_name), daemon=True)
        self.threads.append(thread)
        thread.start()

    def train_model(self, dataset_path: str, architecture: str, lr: float, bs: int, epochs: int, save_model_name: str):
        """Train the model with the specified settings."""
        try:
            logger.info(f"Starting training with architecture: {architecture}, LR: {lr}, BS: {bs}, Epochs: {epochs}")
            GLib.idle_add(self.append_training_output, f"Loading dataset from: {dataset_path}")

            # Create ImageDataLoaders
            data = ImageDataLoaders.from_folder(
                dataset_path,
                valid_pct=0.2,
                item_tfms=Resize(224),
                batch_tfms=aug_transforms(),
                bs=bs
            )

            logger.info("ImageDataLoaders created successfully.")
            GLib.idle_add(self.append_training_output, "Dataset loaded successfully.")

            # Initialize the learner
            learner = vision_learner(data, arch=getattr(models, architecture)(), metrics=accuracy)

            logger.info("Learner initialized successfully.")
            GLib.idle_add(self.append_training_output, f"Initialized learner with architecture: {architecture}")

            # Start training with fine-tuning
            GLib.idle_add(self.append_training_output, "Starting training...")
            learner.fine_tune(epochs, base_lr=lr, callbacks=[TrainingCallback(self)])

            # Ensure the directory for saving exists
            save_path = os.path.abspath(save_model_name)
            save_dir = os.path.dirname(save_path)
            if not os.path.exists(save_dir):
                os.makedirs(save_dir)

            # Save the trained model
            learner.export(save_path)
            logger.info(f"Model trained and saved as: {save_path}")
            GLib.idle_add(self.append_training_output, f"Model trained and saved as: {save_path}", success=True)

            # Update training graphs
            GLib.idle_add(self.update_training_graphs)

            # Automatically load the newly trained model
            GLib.idle_add(self.load_model_async, save_path)
        except Exception as e:
            logger.error(f"Training failed: {e}", exc_info=True)
            GLib.idle_add(self.append_training_output, f"Training failed: {e}", error=True)
        finally:
            # Remove the thread from the list
            current_thread = threading.current_thread()
            if current_thread in self.threads:
                self.threads.remove(current_thread)

            GLib.idle_add(self.hide_progress, self.training_progress)
            GLib.idle_add(self.set_controls_sensitive, True)
            GLib.idle_add(self.update_toolbar_buttons)

    # --- Logging and Output Methods ---

    def append_training_output(self, message: str, error: bool = False, success: bool = False):
        """Append messages to the training output TextView."""
        buffer = self.training_output.get_buffer()
        end_iter = buffer.get_end_iter()
        if error:
            formatted_message = f"<span foreground='red'>{message}</span>\n"
        elif success:
            formatted_message = f"<span foreground='green'>{message}</span>\n"
        else:
            formatted_message = f"{message}\n"
        buffer.insert_markup(end_iter, formatted_message)

    def on_refresh_logs(self, button: Gtk.Button):
        """Handle the Refresh Logs button click."""
        try:
            with open("image_predictor_app.log", 'r') as f:
                log_content = f.read()
            buffer = self.logs_output.get_buffer()
            buffer.set_text(log_content)
            logger.info("Logs refreshed in the application.")
        except Exception as e:
            self.logs_output.set_text(f"Failed to load logs: {e}")
            logger.error(f"Failed to load logs: {e}", exc_info=True)

    # --- History Methods ---

    def update_history(self):
        """Update the History TreeView with new entries."""
        self.history_liststore.clear()
        for entry in self.prediction_history:
            self.history_liststore.append([
                entry["image"],
                entry["prediction"],
                entry["confidence"],
                entry["date"],
                entry["time"]
            ])

    def on_clear_history(self, button: Gtk.Button):
        """Handle the Clear History button click."""
        self.prediction_history.clear()
        self.update_history()
        self.output_label.set_markup("<span foreground='green'>History cleared.</span>")
        logger.info("Prediction history cleared.")

    # --- Visualization Methods ---

    def render_training_graph(self, metric: str, figure: plt.Figure, save_path: str):
        """Render and save training graph."""
        ax = figure.add_subplot(111)
        epochs = list(range(1, len(self.model.recorder.losses)+1))
        if metric == "Loss":
            ax.plot(epochs, self.model.recorder.losses, label='Loss')
            ax.set_xlabel('Epoch')
            ax.set_ylabel('Loss')
            ax.set_title('Training Loss Over Epochs')
            ax.legend()
        elif metric == "Accuracy":
            ax.plot(epochs, self.model.recorder.metrics, label='Accuracy (%)')
            ax.set_xlabel('Epoch')
            ax.set_ylabel('Accuracy (%)')
            ax.set_title('Training Accuracy Over Epochs')
            ax.legend()
        figure.tight_layout()
        figure.savefig(save_path)
        plt.close(figure)
        logger.info(f"{metric} graph saved to {save_path}")

    def update_training_graphs(self):
        """Update the training metrics graphs."""
        # Define paths
        loss_path = "loss_graph.png"
        acc_path = "accuracy_graph.png"

        # Render graphs
        self.render_training_graph("Loss", self.training_graphs["Loss"], loss_path)
        self.render_training_graph("Accuracy", self.training_graphs["Accuracy"], acc_path)

        # Update UI
        self.update_visualization_graph("Loss", loss_path)
        self.update_visualization_graph("Accuracy", acc_path)

    def update_visualization_graph(self, metric: str, path: str):
        """Update the visualization tab with new graphs."""
        # This method assumes that the graphs are already loaded into the Visualization tab
        # For a more dynamic approach, consider using Gtk.ImageList or similar
        logger.debug(f"Visualization graph for {metric} updated with {path}.")

    def on_refresh_visuals(self, button: Gtk.Button):
        """Handle the Refresh Visuals button click."""
        self.update_training_graphs()
        self.output_label.set_markup("<span foreground='green'>Visualization refreshed.</span>")
        logger.info("Visualization graphs refreshed.")

    # --- Theme Application Methods ---

    def apply_theme(self):
        """Apply the current theme based on settings."""
        selected_theme = self.settings.get("theme", "Light")
        if selected_theme == "Dark":
            self.set_dark_theme()
        else:
            self.set_light_theme()

    # --- Plug-in System Methods ---

    def load_plugins(self):
        """Load plugins from the plugins directory."""
        plugins_dir = "plugins"
        if not os.path.isdir(plugins_dir):
            os.makedirs(plugins_dir)
            logger.info("Plugins directory created.")

        sys.path.insert(0, plugins_dir)
        for file in os.listdir(plugins_dir):
            if file.endswith(".py") and not file.startswith("_"):
                plugin_name = file[:-3]
                try:
                    module = __import__(plugin_name)
                    if hasattr(module, "Plugin"):
                        plugin = module.Plugin(self)
                        plugin.setup()
                        logger.info(f"Plugin '{plugin_name}' loaded successfully.")
                except Exception as e:
                    logger.error(f"Failed to load plugin '{plugin_name}': {e}", exc_info=True)

    # --- Plugin Interface (For Future Extensions) ---

    class Plugin:
        """Base class for all plugins."""

        def __init__(self, app):
            self.app = app

        def setup(self):
            """Setup the plugin."""
            pass

    # --- Error Handling Methods ---

    def show_error_dialog(self, message: str):
        """Display an error dialog with the given message."""
        dialog = Gtk.MessageDialog(
            parent=self,
            flags=0,
            message_type=Gtk.MessageType.ERROR,
            buttons=Gtk.ButtonsType.CLOSE,
            text="An error occurred",
        )
        dialog.format_secondary_text(message)
        dialog.run()
        dialog.destroy()

    # --- Visualization Graph Update (Placeholder) ---

    def update_visualization_graphs(self):
        """Placeholder method for updating visualization graphs."""
        # Implement dynamic graph updating using Gtk.DrawingArea or similar
        pass

    # --- Export Methods ---

    def export_history_as_csv(self, export_path: str):
        """Export prediction history to a CSV file."""
        import csv

        try:
            with open(export_path, 'w', newline='') as csvfile:
                fieldnames = ['Image', 'Prediction', 'Confidence (%)', 'Date', 'Time']
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                for entry in self.prediction_history:
                    writer.writerow({
                        'Image': entry['image'],
                        'Prediction': entry['prediction'],
                        'Confidence (%)': entry['confidence'],
                        'Date': entry['date'],
                        'Time': entry['time']
                    })
            self.output_label.set_markup(f"<span foreground='green'>History exported successfully to:</span>\n{export_path}")
            logger.info(f"Prediction history exported to: {export_path}")
        except Exception as e:
            self.output_label.set_markup(f"<span foreground='red'>Failed to export history: {e}</span>")
            logger.error(f"Failed to export history: {e}", exc_info=True)

    # --- Visualization Graph Update Methods ---

    def update_visualization_graphs(self):
        """Update the visualization graphs after training."""
        loss_path = "loss_graph.png"
        acc_path = "accuracy_graph.png"

        # Render training graphs
        self.render_training_graph("Loss", self.training_graphs["Loss"], loss_path)
        self.render_training_graph("Accuracy", self.training_graphs["Accuracy"], acc_path)

        # Update Visualization Tab Images
        self.update_visualization_image("loss_graph.png", "Loss")
        self.update_visualization_image("accuracy_graph.png", "Accuracy")

    def update_visualization_image(self, path: str, metric: str):
        """Update the image in the Visualization tab."""
        # This method would require maintaining references to the Gtk.Image widgets
        # For simplicity, assume we have a mapping of metric to Gtk.Image
        # Implement accordingly based on your UI structure
        pass

    # --- Logging Panel Methods ---

    def on_refresh_logs(self, button: Gtk.Button):
        """Handle the Refresh Logs button click."""
        try:
            if os.path.isfile("image_predictor_app.log"):
                with open("image_predictor_app.log", 'r') as f:
                    log_content = f.read()
                buffer = self.logs_output.get_buffer()
                buffer.set_text(log_content)
                logger.info("Logs refreshed in the application.")
            else:
                self.logs_output.set_text("Log file not found.")
        except Exception as e:
            self.logs_output.set_text(f"Failed to load logs: {e}")
            logger.error(f"Failed to load logs: {e}", exc_info=True)

    # --- Process Management Methods ---

    def on_destroy(self, widget):
        """Handle the window close event for graceful shutdown."""
        if self.threads:
            logger.info("Waiting for active threads to finish...")
            for thread in self.threads:
                thread.join()  # Wait for threads to finish
            logger.info("All threads have been completed.")
        Gtk.main_quit()

    # --- Internationalization (i18n) Methods ---

    def load_translations(self):
        """Load translations based on user settings."""
        # Placeholder for translation loading
        # Implement using gettext or similar libraries
        pass

    # --- Plugin System Methods ---

    def load_plugins(self):
        """Load plugins from the plugins directory."""
        plugins_dir = "plugins"
        if not os.path.isdir(plugins_dir):
            os.makedirs(plugins_dir)
            logger.info("Plugins directory created.")

        sys.path.insert(0, plugins_dir)
        for file in os.listdir(plugins_dir):
            if file.endswith(".py") and not file.startswith("_"):
                plugin_name = file[:-3]
                try:
                    module = __import__(plugin_name)
                    if hasattr(module, "Plugin"):
                        plugin = module.Plugin(self)
                        plugin.setup()
                        logger.info(f"Plugin '{plugin_name}' loaded successfully.")
                except Exception as e:
                    logger.error(f"Failed to load plugin '{plugin_name}': {e}", exc_info=True)

    # ------------------------------- #
    #          Utility Methods       #
    # ------------------------------- #

    def show_progress(self, progress_bar: Gtk.ProgressBar, message: str):
        """Display the progress bar with a message."""
        progress_bar.set_visible(True)
        progress_bar.set_fraction(0.0)
        progress_bar.set_text(message)
        progress_bar.pulse()

    def hide_progress(self, progress_bar: Gtk.ProgressBar):
        """Hide the progress bar."""
        progress_bar.set_visible(False)
        progress_bar.set_text("")

    # ------------------------------- #
    #              Main               #
    # ------------------------------- #

    def main():
        """Initialize and run the GTK application."""
        app = ImagePredictorApp()
        app.apply_theme()
        app.load_plugins()
        app.show_all()
        Gtk.main()


# ------------------------------- #
#          Entry Point            #
# ------------------------------- #

if __name__ == "__main__":
    ImagePredictorApp.main()```
= 
```leo@pop-os ~> python3 gtkmodel.py
2024-09-26 11:00:19,439 INFO:__main__: No settings file found. Using default settings.
Traceback (most recent call last):
  File "/home/leo/gtkmodel.py", line 1392, in <module>
    ImagePredictorApp.main()
  File "/home/leo/gtkmodel.py", line 1380, in main
    app = ImagePredictorApp()
  File "/home/leo/gtkmodel.py", line 89, in __init__
    self.init_ui()
  File "/home/leo/gtkmodel.py", line 112, in init_ui
    login_button = Gtk.ToolButton.new_from_stock(Gtk.STOCK_DIAL_PAD)
  File "/usr/lib/python3/dist-packages/gi/overrides/__init__.py", line 32, in __getattr__
    return getattr(self._introspection_module, name)
  File "/usr/lib/python3/dist-packages/gi/module.py", line 123, in __getattr__
    raise AttributeError("%r object has no attribute %r" % (
AttributeError: 'gi.repository.Gtk' object has no attribute 'STOCK_DIAL_PAD'
leo@pop-os ~ [1]> 
```