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.

Implement other useful suggestions to app


#!/bin/bash

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 PyQt5 pyinstaller markdown

cat << 'EOF' > "$BUILD_DIR/custom_ai_tool_gui.py"
import os, sys, json
from groq import Groq
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import markdown

DEFAULT_MODEL = "llama-3.1-70b-versatile"
DEFAULT_API_KEY = "your key here"

class AIThread(QThread):
    response_ready = pyqtSignal(str)
    error_occurred = pyqtSignal(str)

    def __init__(self, api_key, messages, model):
        super().__init__()
        self.api_key, self.messages, self.model = api_key, messages, model

    def run(self):
        try:
            client = Groq(api_key=self.api_key)
            chat_completion = client.chat.completions.create(messages=self.messages, model=self.model)
            self.response_ready.emit(chat_completion.choices[0].message.content)
        except Exception as e:
            self.error_occurred.emit(str(e))

class OptionsDialog(QDialog):
    def __init__(self, current_api_key, current_model, current_font_size, current_copy_raw, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Options")
        layout = QVBoxLayout(self)

        self.api_key_input = QLineEdit(current_api_key)
        self.api_key_input.setEchoMode(QLineEdit.Password)
        layout.addWidget(QLabel("API Key:"))
        layout.addWidget(self.api_key_input)

        self.model_combo = QComboBox()
        self.model_combo.addItems(["llama-3.1-70b-versatile", "llama-3.1-405b-reasoning", "llama-3.1-8b-instant", "Custom"])
        self.model_combo.setCurrentText(current_model)
        layout.addWidget(QLabel("Select Model:"))
        layout.addWidget(self.model_combo)

        self.custom_model_input = QLineEdit()
        self.custom_model_input.setPlaceholderText("Enter custom model name")
        self.custom_model_input.setEnabled(False)
        layout.addWidget(self.custom_model_input)

        self.model_combo.currentTextChanged.connect(lambda text: self.custom_model_input.setEnabled(text == "Custom"))

        self.font_size_spinner = QSpinBox()
        self.font_size_spinner.setRange(8, 24)
        self.font_size_spinner.setValue(current_font_size)
        layout.addWidget(QLabel("Font Size:"))
        layout.addWidget(self.font_size_spinner)

        self.copy_raw_checkbox = QCheckBox("Copy Raw Text (Disable Formatting)")
        self.copy_raw_checkbox.setChecked(current_copy_raw)
        layout.addWidget(self.copy_raw_checkbox)

        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.accept)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)

    def get_values(self):
        model = self.custom_model_input.text() if self.model_combo.currentText() == "Custom" else self.model_combo.currentText()
        return (self.api_key_input.text(), model, self.font_size_spinner.value(), self.copy_raw_checkbox.isChecked())

class CustomAITool(QWidget):
    def __init__(self):
        super().__init__()
        self.config_file = os.path.join(os.path.dirname(sys.executable), "config.json")
        self.prompt_file = os.path.join(os.path.dirname(sys.executable), "saved_prompt.json")
        self.create_default_files()
        self.load_config()
        self.prompt = self.load_prompt()
        self.conversation_history = []
        self.initUI()

    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.api_key, self.model, self.font_size, self.copy_raw, self)
        if dialog.exec_() == QDialog.Accepted:
            self.api_key, self.model, self.font_size, self.copy_raw = dialog.get_values()
            self.save_config()
            self.update_font_size()
            self.show_notification("Settings Updated", "Your settings have been updated successfully.")

    def update_font_size(self):
        font = self.conversation_display.font()
        font.setPointSize(self.font_size)
        self.conversation_display.setFont(font)

    def initUI(self):
        layout = QVBoxLayout(self)

        toolbar = QHBoxLayout()
        for button_text, func in [('Options', self.show_options_dialog), ('New Prompt', self.new_prompt), ('Clear History', self.clear_history)]:
            button = QPushButton(button_text)
            button.clicked.connect(func)
            toolbar.addWidget(button)
        layout.addLayout(toolbar)

        self.conversation_display = QTextEdit()
        self.conversation_display.setReadOnly(True)
        self.update_font_size()
        layout.addWidget(self.conversation_display)

        copy_buttons = QHBoxLayout()
        for button_text, func in [('Copy Last Message', self.copy_last_message), ('Copy Conversation', self.copy_conversation)]:
            button = QPushButton(button_text)
            button.clicked.connect(func)
            copy_buttons.addWidget(button)
        layout.addLayout(copy_buttons)

        input_layout = QHBoxLayout()
        self.input_edit = QLineEdit()
        self.input_edit.setPlaceholderText("Enter your message here and press Enter or click Send")
        self.input_edit.returnPressed.connect(self.send_message)
        self.send_button = QPushButton('Send')
        self.send_button.clicked.connect(self.send_message)
        input_layout.addWidget(self.input_edit)
        input_layout.addWidget(self.send_button)
        layout.addLayout(input_layout)

        self.setWindowTitle('Custom AI Tool')
        self.resize(800, 600)

    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, ok = QInputDialog.getMultiLineText(self, 'New Prompt', 'Enter your new prompt:', self.prompt)
        if ok:
            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.conversation_display.clear()
        self.conversation_display.append("Conversation history cleared. Start a new conversation!")

    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:
            clipboard = QApplication.clipboard()
            if self.copy_raw:
                clipboard.setText(text)
            else:
                html = markdown.markdown(text)
                mime_data = QMimeData()
                mime_data.setHtml(html)
                mime_data.setText(text)
                clipboard.setMimeData(mime_data)
        except Exception as e:
            self.show_error("Error copying to clipboard", str(e))

    def show_notification(self, title, message):
        QMessageBox.information(self, title, message)

    def show_error(self, title, message):
        QMessageBox.critical(self, title, message)

    def send_message(self):
        user_input = self.input_edit.text().strip()
        if not user_input or not self.api_key:
            return
        self.conversation_history.append({"role": "user", "content": user_input})
        self.conversation_display.append(f"<b>You:</b> {user_input}")
        self.input_edit.clear()
        full_conversation = [{"role": "system", "content": self.prompt}] + self.conversation_history
        self.send_button.setEnabled(False)
        self.input_edit.setEnabled(False)
        self.ai_thread = AIThread(self.api_key, full_conversation, self.model)
        self.ai_thread.response_ready.connect(self.handle_ai_response)
        self.ai_thread.error_occurred.connect(self.handle_error)
        self.ai_thread.start()

    def handle_ai_response(self, ai_response):
        try:
            self.conversation_history.append({"role": "assistant", "content": ai_response})
            formatted_response = markdown.markdown(ai_response)
            self.conversation_display.append(f"<b>AI:</b> {formatted_response}")
            self.conversation_display.verticalScrollBar().setValue(self.conversation_display.verticalScrollBar().maximum())
        except Exception as e:
            self.show_error("Error handling AI response", str(e))
        finally:
            self.send_button.setEnabled(True)
            self.input_edit.setEnabled(True)
            self.input_edit.setFocus()

    def handle_error(self, error_message):
        self.show_error("AI Error", f"An error occurred: {error_message}")
        self.send_button.setEnabled(True)
        self.input_edit.setEnabled(True)
        self.input_edit.setFocus()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = CustomAITool()
    ex.show()
    sys.exit(app.exec_())
EOF

cat << EOF > "$BUILD_DIR/custom_ai_tool.spec"
# -*- mode: python ; coding: utf-8 -*-

block_cipher = None

a = Analysis(['custom_ai_tool_gui.py'],
             pathex=['$BUILD_DIR'],
             binaries=[],
             datas=[],
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          [],
          exclude_binaries=True,
          name='CustomAITool',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          console=False )
coll = COLLECT(exe,
               a.binaries,
               a.zipfiles,
               a.datas,
               strip=False,
               upx=True,
               upx_exclude=[],
               name='CustomAITool')
app = BUNDLE(coll,
             name='CustomAITool.app',
             icon=None,
             bundle_identifier=None)
EOF

echo '{
    "api_key": "your key here",
    "model": "llama-3.1-70b-versatile",
    "font_size": 12,
    "copy_raw": false
}' > "$BUILD_DIR/config.json"

echo '{
    "prompt": "You are a helpful AI assistant."
}' > "$BUILD_DIR/saved_prompt.json"

pyinstaller "$BUILD_DIR/custom_ai_tool.spec"

mkdir -p "$BUILD_DIR/dist/CustomAITool.app/Contents/MacOS"
cp "$BUILD_DIR/config.json" "$BUILD_DIR/dist/CustomAITool.app/Contents/MacOS/"
cp "$BUILD_DIR/saved_prompt.json" "$BUILD_DIR/dist/CustomAITool.app/Contents/MacOS/"

mv "$BUILD_DIR/dist/CustomAITool.app" "$DESKTOP_DIR/"

deactivate

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