Report any bugs in this code: import tkinter as tk
from tkinter import filedialog
from keras.models import Sequential, Model
from keras.layers import Embedding, LSTM, Dense, Input, MultiHeadAttention, GlobalAveragePooling1D
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
import numpy as np
import os
import pickle
from keras.layers import Masking
from keras.layers import MultiHeadAttention
import pandas as pd

# Data loader and preprocessor function for CSV file
def preprocess_data(file_paths):
    tokenizer = Tokenizer()
    all_prompts = []
    all_responses = []

    # Load content from all selected dataset files
    for file_path in file_paths:
        data = pd.read_csv(file_path)
        all_prompts.extend(data["act"].tolist())
        all_responses.extend(data["prompt"].tolist())
    
    tokenizer.fit_on_texts(all_prompts + all_responses)
    input_sequences = tokenizer.texts_to_sequences(all_prompts)
    output_sequences = tokenizer.texts_to_sequences(all_responses)
    
    # Aligning the response sequences to input length for transformer models
    max_length = max(max(len(seq) for seq in input_sequences), max(len(seq) for seq in output_sequences))
    input_sequences = pad_sequences(input_sequences, maxlen=max_length, padding="post")
    output_sequences = pad_sequences(output_sequences, maxlen=max_length, padding="post")
    
    vocab_size = len(tokenizer.word_index) + 1
    
    return np.array(input_sequences), np.array(output_sequences), vocab_size, tokenizer, max_length

def create_transformer_model(sequence_length, vocab_size, layer_size, num_heads, dropout_rate):
    # Transformer model requires the key, value, and query have the same dimensions (layer_size)
    inputs = Input(shape=(sequence_length,))
    embedding_layer = Embedding(input_dim=vocab_size, output_dim=layer_size)(inputs)
    
    # Call the MultiHeadAttention layer with the necessary inputs
    attn_output = MultiHeadAttention(num_heads=num_heads, key_dim=layer_size, dropout=dropout_rate)(
        query=embedding_layer, key=embedding_layer, value=embedding_layer
    )
    
    # … Add Normalization, Feed-forward layers, etc. …
    
    # GlobalAveragePooling1D could be used to reduce sequence dimension
    pooling_layer = GlobalAveragePooling1D()(attn_output)
    
    outputs = Dense(vocab_size, activation="softmax")(pooling_layer)
    
    # Create the actual model
    model = Model(inputs=inputs, outputs=outputs)
    
    return model

def get_transformer_config():
    num_heads = int(entry_heads.get())
    dropout_rate = float(entry_dropout.get())
    # … fetch other settings similarly …
    return num_heads, dropout_rate

# Function to train and save the model
def train_model():
    # Move the variable assignments outside of the conditionals so they’re always defined.
    num_layers = int(entry_layers.get())
    layer_size = int(entry_size.get())
    model_name = entry_name.get()
    epochs = int(entry_epochs.get())
    data_paths = root.filenames  # Changed to accept multiple filenames

    # Preprocess the data for either model type.
    input_sequences, output_sequences, vocab_size, tokenizer, sequence_length = preprocess_data(data_paths)
    
    # Check model_type and build the appropriate model.
    model_type = model_type_var.get()

    if model_type == "Transformer":
        # Get Transformer configuration
        num_heads, dropout_rate = get_transformer_config()
        
        # Build Transformer model
        model = create_transformer_model(sequence_length, vocab_size, layer_size, num_heads, dropout_rate)
       
        # Compile the model
        model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
       
        # Fit the model
        model.fit(input_sequences, output_sequences, epochs=epochs)

    else:
    
        num_layers = int(entry_layers.get())
        layer_size = int(entry_size.get())
        model_name = entry_name.get()
        epochs = int(entry_epochs.get())
        data_paths = root.filenames  # Changed to accept multiple filenames
    
        # Preprocess the data
        input_sequences, output_words, vocab_size, tokenizer, sequence_length = preprocess_data(data_paths)
    
        # Define tokenizer_path
        tokenizer_path = os.path.join("tokenizers", f"{model_name}_tokenizer.pickle")
    
        # Check if the ‘tokenizers’ directory exists, if not, create it
        os.makedirs("tokenizers", exist_ok=True)
    
        # Building and training the model
        model = Sequential()
        model.add(Embedding(input_dim=vocab_size, output_dim=layer_size))

        if not sequence_choice_var.get():  # Add masking for variable length sequences only
            model.add(Masking(mask_value=0))  # Ignoring padded values (zeros)

        # Adding predefined LSTM layers
        for _ in range(num_layers):
            model.add(LSTM(layer_size, return_sequences=True))
        model.add(LSTM(layer_size))

        model.add(Dense(vocab_size, activation="softmax"))

        model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"])
        model.fit(input_sequences, output_words, epochs=epochs)
    
            # For both: Save the tokenizer and model
    tokenizer_path = os.path.join("tokenizers", f"{model_name}_tokenizer.pickle")
    os.makedirs("tokenizers", exist_ok=True)
    
    with open(tokenizer_path, "wb") as handle:
        if sequence_choice_var.get():
            pickle.dump((tokenizer, sequence_length), handle, protocol=pickle.HIGHEST_PROTOCOL)
        else:
            pickle.dump(tokenizer, handle, protocol=pickle.HIGHEST_PROTOCOL)
    print(f"Tokenizer saved at {tokenizer_path}")

    os.makedirs("models", exist_ok=True)
    model.save(os.path.join("models", f"{model_name}.h5"))
    print(f"Model {model_name} trained and saved successfully!")

# UI Setup
root = tk.Tk()
root.title("Chatbot Language Model Trainer")

# Model type
model_type_var = tk.StringVar(value="LSTM")  # Default value set to ‘LSTM’

lbl_model_type = tk.Label(root, text="Select Model Type:")
lbl_model_type.pack()
radiobtn_lstm = tk.Radiobutton(root, text="LSTM", variable=model_type_var, value="LSTM")
radiobtn_lstm.pack()
radiobtn_transformer = tk.Radiobutton(root, text="Transformer", variable=model_type_var, value="Transformer")
radiobtn_transformer.pack()

# Number of layers
lbl_layers = tk.Label(root, text="Number of layers:")
lbl_layers.pack()
entry_layers = tk.Entry(root)
entry_layers.pack()

# Layer size
lbl_size = tk.Label(root, text="Size of each layer:")
lbl_size.pack()
entry_size = tk.Entry(root)
entry_size.pack()

# Model name
lbl_name = tk.Label(root, text="Model name:")
lbl_name.pack()
entry_name = tk.Entry(root)
entry_name.pack()

# Transformer specific settings
lbl_heads = tk.Label(root, text="Number of attention heads:")
lbl_heads.pack()
entry_heads = tk.Entry(root)
entry_heads.pack()

lbl_dropout = tk.Label(root, text="Dropout rate:")
lbl_dropout.pack()
entry_dropout = tk.Entry(root)
entry_dropout.pack()

# Number of epochs
lbl_epochs = tk.Label(root, text="Number of epochs:")
lbl_epochs.pack()
entry_epochs = tk.Entry(root)
entry_epochs.pack()

# Data file path
lbl_data_path = tk.Label(root, text="Data file path:")
lbl_data_path.pack()
entry_data_path = tk.Entry(root)
entry_data_path.pack()

# Checkbox for sequence length choice
lbl_sequence_choice = tk.Label(root, text="Use fixed sequence length:")
lbl_sequence_choice.pack()
sequence_choice_var = tk.BooleanVar()  # Boolean variable to hold the checkbox state
chk_sequence_choice = tk.Checkbutton(root, text="Fixed length", variable=sequence_choice_var)
chk_sequence_choice.pack()

# Entry for fixed sequence length if the toggle is on
lbl_fixed_length = tk.Label(root, text="Fixed sequence length:")
lbl_fixed_length.pack()
entry_fixed_length = tk.Entry(root)
entry_fixed_length.pack()

# Function to select multiple files
def select_files():
    file_paths = filedialog.askopenfilenames()  # Changed to open multiple files
    root.filenames = file_paths  # Store the list of file paths on the root object
    entry_data_path.delete(0, tk.END)
    entry_data_path.insert(0, "; ".join(file_paths))  # Display all file paths in the entry

btn_browse = tk.Button(root, text="Browse…", command=select_files)  # Changed to select_files
btn_browse.pack()

# Train button
btn_train = tk.Button(root, text="Train Model", command=train_model)
btn_train.pack()

# Start the tkinter loop
root.mainloop()