this is a working version but make sure to include the sophisticated stuff in it Improve this to be a much more robust app designed for generation of long documents like scripts and novels

Implement a sophisticated mechanism to keep track of all data so the new generations understand what was generated before and that they must continue. But you must do this through clever summarization mechanisms and tagging so that you don't overload the context window of the ai - as you generate more simply copying prior generations will quickly overload token limits - and rudimentary nonspecfic summarization prompting systems will fail to capture nuance - must design much more sophisticated system

Understand api limits and rate calling and also prevent app from getting stuck - it is important that you implement this without wasting the user's time by making them wait for no reason too - a detailed balanced and speed oriented system is preferred

Progress of generations and content of generations should be visually displayed to user in real time - display in gui text window

Ability to export entire generations or a set of generations or single generations as txts during and after running through gui buttons and text windows is preferred. #!/bin/bash
Preventing Unwanted Phrases: The AI's responses will have phrases like "Here you go:" or "Here is a page about:" automatically removed.

   - Generating Long Documents: The app implements summarization and tagging to manage context when generating long texts like scripts and novels.

   - Real-Time Progress: AI generation progress is displayed in real-time within the GUI.

set -e

BUILD_DIR="$HOME/Desktop/CustomAIToolBuild"
DESKTOP_DIR="$HOME/Desktop"
APP_NAME="CustomAITool"

mkdir -p "$BUILD_DIR"
python3 -m venv "$BUILD_DIR/venv"
source "$BUILD_DIR/venv/bin/activate"

pip install --upgrade pip
pip install groq pyinstaller markdown

cat << 'EOF' > "$BUILD_DIR/custom_ai_tool_gui.py"
import os
import sys
import json
import threading
from groq import Groq
import tkinter as tk
from tkinter import messagebox, filedialog, simpledialog, scrolledtext

DEFAULT_MODEL = "llama-3.1-70b-versatile"
DEFAULT_API_KEY = "<TRUFFLEHOG_REDACTED_GROQ>"
MAX_TOKENS = 4000  # Adjust according to API limitations
SUMMARIZE_AFTER_MESSAGES = 10  # Number of messages after which to start summarizing

def resource_path(relative_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.dirname(sys.argv[0])

    return os.path.join(base_path, relative_path)

class CustomAITool(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Custom AI Tool")
        self.geometry("800x600")

        self.config_file = resource_path("config.json")
        self.prompt_file = resource_path("saved_prompt.json")
        self.create_default_files()
        self.load_config()
        self.prompt = self.load_prompt()
        self.conversation_history = []
        self.summaries = []

        self.create_widgets()

    def create_default_files(self):
        if not os.path.exists(self.config_file):
            with open(self.config_file, 'w') as f:
                json.dump({"api_key": DEFAULT_API_KEY, "model": DEFAULT_MODEL, "font_size": 12, "copy_raw": False}, f)
        if not os.path.exists(self.prompt_file):
            with open(self.prompt_file, 'w') as f:
                json.dump({"prompt": "You are a helpful AI assistant."}, f)

    def load_config(self):
        try:
            with open(self.config_file, 'r') as f:
                config = json.load(f)
                self.api_key = config.get("api_key", DEFAULT_API_KEY)
                self.model = config.get("model", DEFAULT_MODEL)
                self.font_size = config.get("font_size", 12)
                self.copy_raw = config.get("copy_raw", False)
        except Exception as e:
            self.show_error("Error loading config", str(e))
            self.api_key, self.model, self.font_size, self.copy_raw = DEFAULT_API_KEY, DEFAULT_MODEL, 12, False

    def save_config(self):
        try:
            with open(self.config_file, 'w') as f:
                json.dump({"api_key": self.api_key, "model": self.model, "font_size": self.font_size, "copy_raw": self.copy_raw}, f)
        except Exception as e:
            self.show_error("Error saving config", str(e))

    def show_options_dialog(self):
        dialog = OptionsDialog(self)
        self.wait_window(dialog)
        if dialog.result:
            self.api_key, self.model, self.font_size, self.copy_raw = dialog.result
            self.save_config()
            self.update_font_size()
            self.show_notification("Settings Updated", "Your settings have been updated successfully.")

    def update_font_size(self):
        self.conversation_display.configure(font=("TkDefaultFont", self.font_size))

    def create_widgets(self):
        toolbar = tk.Frame(self)
        toolbar.pack(side=tk.TOP, fill=tk.X)

        options_button = tk.Button(toolbar, text="Options", command=self.show_options_dialog)
        options_button.pack(side=tk.LEFT)

        new_prompt_button = tk.Button(toolbar, text="New Prompt", command=self.new_prompt)
        new_prompt_button.pack(side=tk.LEFT)

        clear_history_button = tk.Button(toolbar, text="Clear History", command=self.clear_history)
        clear_history_button.pack(side=tk.LEFT)

        export_button = tk.Button(toolbar, text="Export", command=self.export_generations)
        export_button.pack(side=tk.LEFT)

        self.conversation_display = scrolledtext.ScrolledText(self, state='disabled', wrap=tk.WORD)
        self.conversation_display.pack(fill=tk.BOTH, expand=True)
        self.update_font_size()

        copy_buttons_frame = tk.Frame(self)
        copy_buttons_frame.pack()

        copy_last_button = tk.Button(copy_buttons_frame, text="Copy Last Message", command=self.copy_last_message)
        copy_last_button.pack(side=tk.LEFT)

        copy_conversation_button = tk.Button(copy_buttons_frame, text="Copy Conversation", command=self.copy_conversation)
        copy_conversation_button.pack(side=tk.LEFT)

        self.progress_label = tk.Label(self, text="")
        self.progress_label.pack()

        input_frame = tk.Frame(self)
        input_frame.pack(fill=tk.X)

        self.input_edit = tk.Entry(input_frame)
        self.input_edit.pack(side=tk.LEFT, fill=tk.X, expand=True)
        self.input_edit.bind("<Return>", self.send_message)

        self.send_button = tk.Button(input_frame, text="Send", command=self.send_message)
        self.send_button.pack(side=tk.RIGHT)

    def load_prompt(self):
        try:
            with open(self.prompt_file, 'r') as f:
                return json.load(f).get("prompt", "You are a helpful AI assistant.")
        except Exception as e:
            self.show_error("Error loading prompt", str(e))
            return "You are a helpful AI assistant."

    def save_prompt(self, prompt):
        try:
            with open(self.prompt_file, 'w') as f:
                json.dump({"prompt": prompt}, f)
        except Exception as e:
            self.show_error("Error saving prompt", str(e))

    def new_prompt(self):
        prompt = simpledialog.askstring("New Prompt", "Enter your new prompt:", initialvalue=self.prompt)
        if prompt:
            self.prompt = prompt
            self.save_prompt(prompt)
            self.clear_history()
            self.show_notification("Success", "New prompt saved and conversation history cleared.")

    def clear_history(self):
        self.conversation_history = []
        self.summaries = []
        self.conversation_display.configure(state='normal')
        self.conversation_display.delete(1.0, tk.END)
        self.conversation_display.insert(tk.END, "Conversation history cleared. Start a new conversation!\n")
        self.conversation_display.configure(state='disabled')

    def copy_last_message(self):
        if self.conversation_history:
            self.copy_to_clipboard(self.conversation_history[-1]['content'])

    def copy_conversation(self):
        if self.conversation_history:
            conversation = "\n\n".join([f"{msg['role'].capitalize()}: {msg['content']}" for msg in self.conversation_history])
            self.copy_to_clipboard(conversation)

    def copy_to_clipboard(self, text):
        try:
            self.clipboard_clear()
            self.clipboard_append(text)
            self.show_notification("Copied", "Text copied to clipboard.")
        except Exception as e:
            self.show_error("Error copying to clipboard", str(e))

    def show_notification(self, title, message):
        messagebox.showinfo(title, message)

    def show_error(self, title, message):
        messagebox.showerror(title, message)

    def export_generations(self):
        filename = filedialog.asksaveasfilename(title="Export Generations", defaultextension=".txt",
                                                filetypes=[("Text Files", ".txt")])
        if filename:
            try:
                with open(filename, 'w') as f:
                    for msg in self.conversation_history:
                        f.write(f"{msg['role'].capitalize()}: {msg['content']}\n\n")
                self.show_notification("Export Successful", f"Generations exported to {filename}")
            except Exception as e:
                self.show_error("Export Failed", str(e))

    def send_message(self, event=None):
        user_input = self.input_edit.get().strip()
        if not user_input or not self.api_key:
            return
        self.conversation_history.append({"role": "user", "content": user_input})
        self.display_message("You", user_input)
        self.input_edit.delete(0, tk.END)
        self.manage_conversation_length()
        full_conversation = self.build_full_conversation()
        self.send_button.config(state='disabled')
        self.input_edit.config(state='disabled')
        self.progress_label.config(text="Generating response...")
        threading.Thread(target=self.get_ai_response, args=(full_conversation,)).start()

    def display_message(self, sender, message):
        self.conversation_display.configure(state='normal')
        self.conversation_display.insert(tk.END, f"{sender}: {message}\n\n")
        self.conversation_display.configure(state='disabled')
        self.conversation_display.see(tk.END)

    def get_ai_response(self, full_conversation):
        try:
            client = Groq(api_key=self.api_key)
            chat_completion = client.chat.completions.create(
                messages=full_conversation, model=self.model)
            ai_response = chat_completion.choices[0].message.content
            self.conversation_history.append({"role": "assistant", "content": ai_response})
            self.display_message("AI", ai_response)
        except Exception as e:
            self.show_error("AI Error", f"An error occurred: {e}")
        finally:
            self.send_button.config(state='normal')
            self.input_edit.config(state='normal')
            self.input_edit.focus()
            self.progress_label.config(text="")

    def manage_conversation_length(self):
        # Estimate current token count (simplified)
        total_tokens = sum(len(msg['content'].split()) for msg in self.conversation_history)
        if total_tokens > MAX_TOKENS:
            self.summarize_conversation()

    def summarize_conversation(self):
        try:
            summarization_prompt = [
                {"role": "system", "content": "Summarize the following conversation, capturing key plot points and nuances."},
                {"role": "user", "content": "\n".join([f"{msg['role']}: {msg['content']}" for msg in self.conversation_history])}
            ]
            client = Groq(api_key=self.api_key)
            summary_completion = client.chat.completions.create(
                messages=summarization_prompt, model=self.model)
            summary = summary_completion.choices[0].message.content
            # Store the summary and reset conversation history
            self.summaries.append(summary)
            self.conversation_history = [{"role": "assistant", "content": summary}]
            self.display_message("System", "Previous conversation summarized to maintain context.")
        except Exception as e:
            self.show_error("Summarization Error", str(e))

    def build_full_conversation(self):
        full_conversation = [{"role": "system", "content": self.prompt}]
        for summary in self.summaries:
            full_conversation.append({"role": "assistant", "content": summary})
        full_conversation.extend(self.conversation_history)
        return full_conversation

class OptionsDialog(tk.Toplevel):
    def __init__(self, parent):
        super().__init__(parent)
        self.title("Options")
        self.result = None

        self.api_key_var = tk.StringVar(value=parent.api_key)
        self.model_var = tk.StringVar(value=parent.model)
        self.font_size_var = tk.IntVar(value=parent.font_size)
        self.copy_raw_var = tk.BooleanVar(value=parent.copy_raw)

        tk.Label(self, text="API Key:").pack()
        self.api_key_entry = tk.Entry(self, textvariable=self.api_key_var, show='')
        self.api_key_entry.pack(fill=tk.X)

        tk.Label(self, text="Select Model:").pack()
        self.model_combo = tk.OptionMenu(self, self.model_var, "llama-3.1-70b-versatile",
                                         "llama-3.1-405b-reasoning", "llama-3.1-8b-instant", "Custom")
        self.model_combo.pack(fill=tk.X)

        tk.Label(self, text="Custom Model Name:").pack()
        self.custom_model_entry = tk.Entry(self)
        self.custom_model_entry.pack(fill=tk.X)
        if self.model_var.get() != "Custom":
            self.custom_model_entry.config(state='disabled')

        def update_custom_state(*args):
            if self.model_var.get() == "Custom":
                self.custom_model_entry.config(state='normal')
            else:
                self.custom_model_entry.config(state='disabled')

        self.model_var.trace('w', update_custom_state)

        tk.Label(self, text="Font Size:").pack()
        self.font_size_spinbox = tk.Spinbox(self, from_=8, to=24, textvariable=self.font_size_var) 
        self.font_size_spinbox.pack()

        self.copy_raw_checkbox = tk.Checkbutton(self, text="Copy Raw Text (Disable Formatting)", variable=self.copy_raw_var)
        self.copy_raw_checkbox.pack()

        button_frame = tk.Frame(self)
        button_frame.pack(pady=10)

        ok_button = tk.Button(button_frame, text="OK", command=self.on_ok)
        ok_button.pack(side=tk.LEFT, padx=5)
        cancel_button = tk.Button(button_frame, text="Cancel", command=self.destroy)
        cancel_button.pack(side=tk.RIGHT, padx=5)

        self.grab_set()

    def on_ok(self):
        model = self.custom_model_entry.get() if self.model_var.get() == "Custom" else self.model_var.get()
        self.result = (self.api_key_var.get(), model, self.font_size_var.get(), self.copy_raw_var.get())
        self.destroy()

if __name__ == '__main__':
    app = CustomAITool()
    app.mainloop()
EOF

# Create the config.json file with an empty API key
cat << EOF > "$BUILD_DIR/config.json"
{
    "api_key": "", 
    "model": "llama-3.1-70b-versatile",
    "font_size": 12,
    "copy_raw": false
}
EOF

# Create the saved_prompt.json file
cat << EOF > "$BUILD_DIR/saved_prompt.json"
{
    "prompt": "You are a helpful AI assistant."
}
EOF

cd "$BUILD_DIR"

# Build the app using PyInstaller without a spec file
pyinstaller --windowed --name "$APP_NAME" \
    --add-data "config.json:." \
    --add-data "saved_prompt.json:." \
    custom_ai_tool_gui.py

# Copy the app to Desktop
cp -R "dist/$APP_NAME.app" "$DESKTOP_DIR/"

# Create a DMG file (macOS only)
hdiutil create -volname "$APP_NAME" -srcfolder "$DESKTOP_DIR/$APP_NAME.app" -ov -format UDZO "$DESKTOP_DIR/$APP_NAME.dmg"

# Deactivate the virtual environment
deactivate

echo "$APP_NAME.dmg has been created on your Desktop at $DESKTOP_DIR/$APP_NAME.dmg"
echo "The app includes default config.json and saved_prompt.json files."
echo "You can now manually remove the build directory at $BUILD_DIR"