Fix this code with zero omissions or leaving anything out and no placeholders or shortcuts.

#SEGMENT 1 START
# Import necessary libraries for the chatbot GUI and background tasks
import sys
import os
import json
import html
import logging
import asyncio
import requests  # Ensure this library is installed using pip
import httpx
from PyQt5.QtWidgets import (
    QApplication, QWidget, QVBoxLayout, QTextEdit, QLineEdit, QPushButton, QMessageBox,
    QComboBox, QLabel, QMenuBar, QAction, QDialog, QFormLayout, QDialogButtonBox,
    QListWidget, QHBoxLayout, QInputDialog, QFileDialog, QProgressBar, QSplitter
)
from PyQt5.QtCore import Qt, QObject, QThread, pyqtSignal, QSize
from PyQt5.QtGui import QPixmap, QIcon
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent  # For audio playback
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
import psutil
from requests_oauthlib import OAuth2Session
from urllib.parse import urlparse
import importlib.util
import re
import shutil  # For file caching

# Initialize the logger to track errors and chatbot activity
logging.basicConfig(
    filename="solmasterx.log",
    level=logging.DEBUG,
    format='%(asctime)s:%(levelname)s:%(message)s'
)

# Set the offload directory and cache directory for media files
offload_dir = "F:\\offload\\"
cache_dir = "cache"
os.makedirs(offload_dir, exist_ok=True)
os.makedirs(cache_dir, exist_ok=True)

# Define storage paths for models, APIs, system prompts, and conversations
models_file = 'models.json'
apis_file = 'apis.json'
system_prompts_file = 'system_prompts.json'
conversations_dir = 'conversations'
plugins_dir = 'plugins'

# Ensure directories exist for storing necessary files
os.makedirs(conversations_dir, exist_ok=True)
os.makedirs(plugins_dir, exist_ok=True)

#SEGMENT 1 END

#SEGMENT 2 START

# Load models from a JSON file or use default models if not available
# This ensures we can dynamically switch between models based on user selection
if os.path.exists(models_file):
    with open(models_file, 'r', encoding='utf-8') as f:
        models = json.load(f)
else:
    models = {
        "Falcon-7B": "tiiuae/falcon-7b",
        "GPT-2": "gpt2",
        "GPT-Neo": "EleutherAI/gpt-neo-2.7B"
    }

# Function to save models to the JSON file when new models are added or edited
def save_models():
    try:
        with open(models_file, 'w', encoding='utf-8') as f:
            json.dump(models, f, indent=4)
        logging.info("Models saved successfully.")
    except Exception as e:
        logging.error(f"Error saving models: {e}")

# Load API configurations from the 'apis.json' file or initialize an empty list
# Each API has a unique structure with keys for OAuth, tokens, etc.
if os.path.exists(apis_file):
    with open(apis_file, 'r', encoding='utf-8') as f:
        apis = json.load(f)
else:
    apis = []

# Save API configurations back to the 'apis.json' file after adding or updating an API
def save_apis():
    try:
        with open(apis_file, 'w', encoding='utf-8') as f:
            json.dump(apis, f, indent=4)
        logging.info("APIs saved successfully.")
    except Exception as e:
        logging.error(f"Error saving APIs: {e}")

#SEGMENT 2 END

#SEGMENT 3 START (Full and Complete Code for #SEGMENT 3)

# Load system prompts from a JSON file or initialize an empty list if no prompts exist
# System prompts are used to modify the AI behavior dynamically during conversations
if os.path.exists(system_prompts_file):
    with open(system_prompts_file, 'r', encoding='utf-8') as f:
        system_prompts = json.load(f)
else:
    system_prompts = []

# Save system prompts back to the 'system_prompts.json' file after adding or modifying a prompt
def save_system_prompts():
    try:
        with open(system_prompts_file, 'w', encoding='utf-8') as f:
            json.dump(system_prompts, f, indent=4)
        logging.info("System prompts saved successfully.")
    except Exception as e:
        logging.error(f"Error saving system prompts: {e}")

# Saving the current conversation, reflection history, and file/folder knowledge
def save_conversation(filename, context, reflection_history=None, file_knowledge=None):
    try:
        conversation_data = {
            "context": context,
            "reflection_history": reflection_history or {},
            "file_knowledge": file_knowledge or []
        }
        with open(os.path.join(conversations_dir, filename), 'w', encoding='utf-8') as f:
            json.dump(conversation_data, f, indent=4)
        logging.info(f"Conversation '{filename}' saved successfully with reflection history and file knowledge.")
    except Exception as e:
        logging.error(f"Error saving conversation '{filename}': {e}")

# Loading a conversation and its associated reflection history and file/folder knowledge
def load_conversation(filename):
    try:
        with open(os.path.join(conversations_dir, filename), 'r', encoding='utf-8') as f:
            conversation_data = json.load(f)
        conversation = conversation_data.get("context", [])
        reflection_history = conversation_data.get("reflection_history", {})
        file_knowledge = conversation_data.get("file_knowledge", [])
        logging.info(f"Conversation '{filename}' loaded successfully with reflection history and file knowledge.")
        return conversation, reflection_history, file_knowledge
    except Exception as e:
        logging.error(f"Error loading conversation '{filename}': {e}")
        return [], {}, []

#SEGMENT 3 END

# Plugins are loaded from the 'plugins' directory and can be dynamically updated
# This function searches for all Python files in the 'plugins' directory, loads them as modules, and executes their 'register' function
def load_plugins():
    plugins = []
    if not os.path.exists(plugins_dir):
        os.makedirs(plugins_dir, exist_ok=True)
    for filename in os.listdir(plugins_dir):
        if filename.endswith('.py'):
            filepath = os.path.join(plugins_dir, filename)
            spec = importlib.util.spec_from_file_location(filename[:-3], filepath)
            if spec and spec.loader:
                module = importlib.util.module_from_spec(spec)
                try:
                    spec.loader.exec_module(module)
                    if hasattr(module, 'register'):
                        plugin = module.register()
                        if 'name' in plugin and 'process' in plugin:
                            plugins.append(plugin)
                            logging.info(f"Plugin '{plugin['name']}' loaded successfully.")
                        else:
                            logging.warning(f"Plugin '{filename}' is missing 'name' or 'process' keys.")
                    else:
                        logging.warning(f"Plugin '{filename}' does not have a 'register' function.")
                except Exception as e:
                    logging.error(f"Error loading plugin '{filename}': {e}")
    return plugins

# Example Web Scraping Plugin: BeautifulSoup
def register():
    return {
        'name': 'BeautifulSoupScraper',
        'process': scrape_with_beautifulsoup
    }

# Function to scrape a webpage using BeautifulSoup
def scrape_with_beautifulsoup(url):
    try:
        import requests
        from bs4 import BeautifulSoup

        response = requests.get(url)
        if response.status_code == 200:
            soup = BeautifulSoup(response.text, 'html.parser')
            return soup.prettify()  # You can adjust this to return specific data.
        else:
            return f"Failed to retrieve the webpage. Status code: {response.status_code}"
    except Exception as e:
        logging.error(f"Error scraping with BeautifulSoup: {e}")
        return f"Error: {e}"

# Example Web Scraping Plugin: Selenium
def register_selenium():
    return {
        'name': 'SeleniumScraper',
        'process': scrape_with_selenium
    }

# Function to scrape a webpage using Selenium for dynamic content
def scrape_with_selenium(url):
    try:
        from selenium import webdriver
        from selenium.webdriver.chrome.service import Service
        from webdriver_manager.chrome import ChromeDriverManager

        options = webdriver.ChromeOptions()
        options.headless = True
        driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
        
        driver.get(url)
        page_source = driver.page_source
        driver.quit()
        return page_source
    except Exception as e:
        logging.error(f"Error scraping with Selenium: {e}")
        return f"Error: {e}"

# YouTube Video Analysis Plugin
def register_youtube_dl():
    return {
        'name': 'YouTubeDLVideoAnalyzer',
        'process': analyze_youtube_video
    }

# Function to download and analyze captions from a YouTube video using youtube-dl
def analyze_youtube_video(url):
    try:
        import youtube_dl

        ydl_opts = {
            'writesubtitles': True,  # Download subtitles/captions
            'subtitleslangs': ['en'],  # Specify language for subtitles
            'skip_download': True,  # Only download subtitles, not the video
            'quiet': True  # Suppress unnecessary output
        }

        with youtube_dl.YoutubeDL(ydl_opts) as ydl:
            info = ydl.extract_info(url, download=False)
            subtitles = info.get('subtitles')
            if subtitles and 'en' in subtitles:
                # Extract the URL of the subtitles file
                subtitle_url = subtitles['en'][0]['url']
                return f"Subtitles available at: {subtitle_url}"
            else:
                return "No subtitles available for this video."

    except Exception as e:
        logging.error(f"Error analyzing YouTube video: {e}")
        return f"Error: {e}"

# Initialize plugins by loading them into the chatbot
plugins = load_plugins()

# Default language model selected at startup, can be changed later via the UI
current_model_name = "Falcon-7B"

# Try to load the tokenizer and model from Hugging Face or display an error message if loading fails
try:
    tokenizer = AutoTokenizer.from_pretrained(models[current_model_name])
    model = AutoModelForCausalLM.from_pretrained(
        models[current_model_name],
        device_map="auto",
        torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
        offload_folder=offload_dir
    )
    logging.info(f"Model '{current_model_name}' loaded successfully.")
except Exception as e:
    QMessageBox.critical(None, "Model Loading Error", f"Failed to load the model: {e}")
    logging.critical(f"Failed to load the model '{current_model_name}': {e}")
    sys.exit(1)

#SEGMENT 4 END



#SEGMENT 5 START
# Full and Complete Version with Reflective AI, Infinite Tokens, Code Execution, and Customizable LLM Settings

# Worker class handles the background AI response generation
class Worker(QObject):
    finished = pyqtSignal(str)
    error = pyqtSignal(str)
    status = pyqtSignal(str)

    def __init__(self, model, tokenizer, context, user_input, apis, plugins, file_knowledge=None,
                 max_new_tokens=150, temperature=0.7, top_p=0.9):
        super().__init__()
        self.model = model
        self.tokenizer = tokenizer
        self.context = context
        self.user_input = user_input
        self.apis = apis
        self.plugins = plugins
        self.file_knowledge = file_knowledge if file_knowledge is not None else []
        self.max_new_tokens = max_new_tokens
        self.temperature = temperature
        self.top_p = top_p
        self.reflection_history = []  # To store reflection history for AI's reflective learning
        self.virtual_env = self.create_virtual_environment()  # Create the virtual environment first

    # Main method to run the AI response generation
    def run(self):
        try:
            self.status.emit("Thinking...")
            full_response = asyncio.run(self.generate_infinite_tokens())
            analyzed_response = self.analyze_in_virtual_environment(full_response)

            # Emit the analyzed response when testing is complete
            self.finished.emit(analyzed_response)

        except Exception as e:
            error_msg = f"Error during response generation: {e}"
            logging.error(error_msg)
            self.error.emit(error_msg)

    # Function to handle infinite token generation with chunking and 5-second cool-down
    async def generate_infinite_tokens(self):
        prompt = self.context + f"You: {self.user_input}\nAssistant:"
        response = ""
        context_for_generation = prompt
        token_limit = self.max_new_tokens  # To avoid memory overflow in very long conversations

        while True:
            # Generate token chunk
            inputs = self.tokenizer(context_for_generation, return_tensors="pt").to(self.model.device)
            outputs = self.model.generate(
                **inputs,
                max_new_tokens=self.max_new_tokens,
                temperature=self.temperature,
                top_p=self.top_p,
                do_sample=True,
                eos_token_id=self.tokenizer.eos_token_id
            )

            chunk_response = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
            response += chunk_response

            # Emit status for chunk generation
            self.status.emit(f"Generated {self.max_new_tokens} tokens, pausing for cool-down...")

            # Simulate cool-down period
            await asyncio.sleep(5)

            # Break if we have reached the token limit or the end of the generated content
            if len(self.tokenizer.encode(response)) < token_limit:
                break

            # Optimize: truncate context if the response is too long to prevent memory overload
            if len(response) > 2048:  # A reasonable context limit to avoid memory issues
                response = response[-2048:]

            # Prepare the new context by appending the generated chunk to the input
            context_for_generation = prompt + response
            self.status.emit("Resuming generation...")

        return response

    # Create a virtual environment for Reflective AI testing and code execution
    def create_virtual_environment(self):
        import venv, os, subprocess
        env_dir = os.path.join(os.getcwd(), "ai_virtual_env")
        if not os.path.exists(env_dir):
            venv.create(env_dir, with_pip=True)
        self.status.emit(f"Virtual environment created at {env_dir}")
        return env_dir

    # Fallback Docker environment for Reflective AI testing and code execution
    def create_docker_environment(self, image="python:3.9"):
        import docker
        client = docker.from_env()

        try:
            container = client.containers.run(
                image,
                command="/bin/bash",
                detach=True,
                tty=True,
                volumes={os.getcwd(): {"bind": "/workspace", "mode": "rw"}}
            )
            self.status.emit(f"Docker environment created with {image}")
            return container
        except Exception as e:
            self.status.emit(f"Error creating Docker environment: {e}")
            logging.error(f"Docker environment error: {e}")
            return None

    # Code execution tries virtual environment first, falls back to Docker if necessary
    def execute_code(self, code, language="python"):
        try:
            # Attempt to run the code in the virtual environment first
            return self.execute_code_in_virtual_env(code, language=language)
        except Exception as e:
            logging.warning(f"Virtual environment execution failed: {e}")
            self.status.emit(f"Fallback to Docker due to error: {e}")
            return self.execute_code_in_docker(code, language=language)

    # Execute code in the virtual environment
    def execute_code_in_virtual_env(self, code, language="python"):
        try:
            # Execute the code in the virtual environment using subprocess
            if language == "python":
                exec_globals = {}
                exec(code, exec_globals)
                return exec_globals
            else:
                raise Exception(f"Unsupported language {language} for virtual environment")
        except Exception as e:
            logging.error(f"Error executing code in virtual environment: {e}")
            raise e  # Re-raise the exception to trigger Docker fallback

    # Code execution is run inside the Docker container as a fallback
    def execute_code_in_docker(self, code, language="python"):
        image = {
            "python": "python:3.9",
            "php": "php:7.4",
            "javascript": "node:14",
            "html": "nginx:alpine",
            "json": "python:3.9"
        }.get(language, "python:3.9")

        container = self.create_docker_environment(image=image)

        if container:
            try:
                temp_code_file = f"/workspace/temp_code.{language if language != 'json' else 'py'}"
                with open(f"temp_code.{language}", "w") as f:
                    f.write(code)

                with open(f"temp_code.{language}", 'rb') as temp_file:
                    container.put_archive("/workspace", temp_file.read())

                if language == "python":
                    result = container.exec_run(f"python {temp_code_file}", stderr=True, stdout=True)
                elif language == "php":
                    result = container.exec_run(f"php {temp_code_file}", stderr=True, stdout=True)
                elif language == "javascript":
                    result = container.exec_run(f"node {temp_code_file}", stderr=True, stdout=True)
                elif language == "html":
                    result = container.exec_run(f"ls {temp_code_file}", stderr=True, stdout=True)
                elif language == "json":
                    result = container.exec_run(f"python -c 'import json; json.loads(open(\"{temp_code_file}\").read())'", stderr=True, stdout=True)

                output = result.output.decode()
                container.stop()
                container.remove()
                return output
            except Exception as e:
                logging.error(f"Error executing code in Docker container: {e}")
                return f"Error executing code in Docker container: {e}"
        return "Failed to create Docker environment."

    # Analyze the AI response in the virtual environment
    def analyze_in_virtual_environment(self, response):
        self.status.emit("Analyzing response in virtual environment...")
        for attempt in range(3):  # Try up to 3 reflection iterations
            try:
                analyzed_response = self.test_response_in_venv(response)
                if self.is_response_error_free(analyzed_response):
                    self.status.emit("Response passed virtual environment tests.")
                    return analyzed_response
                else:
                    self.status.emit(f"Detected issues. Rewriting response (Attempt {attempt + 1}/3)...")
                    response = self.rewrite_response(response)
            except Exception as e:
                self.status.emit(f"Error in virtual environment: {e}")
                logging.error(f"Virtual environment error: {e}")
        return response

import subprocess

# Test the generated response inside the virtual environment
def test_response_in_venv(self, response):
    subprocess.run([os.path.join(self.virtual_env, "bin", "python"), "-c", response], check=True)
    return response

    # Check if the AI response is error-free (customizable logic here)
    def is_response_error_free(self, response):
        # Perform any checks to ensure the response is valid (e.g., code linting or output validation)
        import flake8
        lint_result = flake8.api.legacy.get_style_guide().check_files([response])
        return lint_result.total_errors == 0

    # Rewrite the response if errors are found
    def rewrite_response(self, response):
        # Simple method to append a correction or try rewriting parts of the response
        return response + "\n[Rewritten to fix detected issues]"

    # Function to trigger API plugin calls if required (can be expanded with actual API calls)
    def run_api_plugins(self):
        api_response = ""
        for api in self.apis:
            api_response += f"Processed API: {api['App Name']} (API Response Placeholder)\n"
        return api_response

    # Function to process reflection history if needed
    def process_reflection(self):
        # For each reflection, adjust the response or context (can be expanded for reflective AI)
        if self.reflection_history:
            self.status.emit("Applying reflection history...")
            for reflection in self.reflection_history:
                self.context.append({"role": "system", "content": reflection})

#SEGMENT 5 END

#SEGMENT 6 START

# Dialog for adding or editing API configurations
# This dialog allows the user to add, edit, or configure new APIs, including API keys and OAuth details
class ApiEditDialog(QDialog):
    def __init__(self, parent=None, api=None):
        super().__init__(parent)
        self.setWindowTitle("Edit API" if api else "Add API")
        self.setMinimumWidth(500)
        self.layout = QFormLayout()
        self.setLayout(self.layout)

        # API URL or Endpoint
        self.api_url = QLineEdit(api.get('API', '') if api else '')
        self.layout.addRow("API URL/Endpoint:", self.api_url)

        # App Details: App Name and App ID
        self.app_name = QLineEdit(api.get('App Details', {}).get('App Name', '') if api else '')
        self.app_id = QLineEdit(api.get('App Details', {}).get('App ID', '') if api else '')
        self.layout.addRow("App Name:", self.app_name)
        self.layout.addRow("App ID:", self.app_id)

        # Consumer Keys: API Key and API Key Secret
        self.api_key = QLineEdit(api.get('Consumer Keys', {}).get('API Key', '') if api else '')
        self.api_key_secret = QLineEdit(api.get('Consumer Keys', {}).get('API Key Secret', '') if api else '')
        self.layout.addRow("API Key:", self.api_key)
        self.layout.addRow("API Key Secret:", self.api_key_secret)

        # OAuth 2.0: Client ID, Client Secret, and Token URL
        self.client_id = QLineEdit(api.get('OAuth 2.0', {}).get('Client ID', '') if api else '')
        self.client_secret = QLineEdit(api.get('OAuth 2.0', {}).get('Client Secret', '') if api else '')
        self.token_url = QLineEdit(api.get('OAuth 2.0', {}).get('Token URL', '') if api else '')
        self.layout.addRow("OAuth Client ID:", self.client_id)
        self.layout.addRow("OAuth Client Secret:", self.client_secret)
        self.layout.addRow("OAuth Token URL:", self.token_url)

        # Authentication Tokens: Bearer Token and Access Token
        self.bearer_token = QLineEdit(api.get('Authentication Tokens', {}).get('Bearer Token', '') if api else '')
        self.access_token = QLineEdit(api.get('Authentication Tokens', {}).get('Access Token', '') if api else '')
        self.layout.addRow("Bearer Token:", self.bearer_token)
        self.layout.addRow("Access Token:", self.access_token)

        # Dialog buttons: OK and Cancel
        self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.layout.addRow(self.buttons)
        self.buttons.accepted.connect(self.validate_and_accept)
        self.buttons.rejected.connect(self.reject)

    # Validation method ensures essential fields like the API URL are filled before saving
    def validate_and_accept(self):
        if not self.api_url.text().strip():
            QMessageBox.warning(self, "Input Error", "API URL/Endpoint cannot be empty.")
            return
        self.accept()

    # Method to retrieve all API data from the form to store in the 'apis.json' file
    def get_api_data(self):
        return {
            "API": self.api_url.text().strip(),
            "App Details": {
                "App Name": self.app_name.text().strip(),
                "App ID": self.app_id.text().strip()
            },
            "Consumer Keys": {
                "API Key": self.api_key.text().strip(),
                "API Key Secret": self.api_key_secret.text().strip()
            },
            "OAuth 2.0": {
                "Client ID": self.client_id.text().strip(),
                "Client Secret": self.client_secret.text().strip(),
                "Token URL": self.token_url.text().strip()
            },
            "Authentication Tokens": {
                "Bearer Token": self.bearer_token.text().strip(),
                "Access Token": self.access_token.text().strip()
            }
        }

# Dialog to manage all APIs, including adding, editing, and deleting them
class ApiManagerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("API Manager")
        self.setMinimumWidth(600)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # List widget to display all available APIs
        self.api_list = QListWidget()
        self.api_list.addItems([api.get('API', 'Unnamed API') for api in apis])
        self.layout.addWidget(self.api_list)

        # Buttons to Add, Edit, or Delete APIs
        button_layout = QHBoxLayout()
        self.add_button = QPushButton("Add API")
        self.edit_button = QPushButton("Edit API")
        self.delete_button = QPushButton("Delete API")
        button_layout.addWidget(self.add_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        self.layout.addLayout(button_layout)

        # Connect button clicks to their respective handler methods
        self.add_button.clicked.connect(self.add_api)
        self.edit_button.clicked.connect(self.edit_api)
        self.delete_button.clicked.connect(self.delete_api)

    # Method to handle adding a new API
    def add_api(self):
        dialog = ApiEditDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            new_api = dialog.get_api_data()
            apis.append(new_api)
            self.api_list.addItem(new_api.get('API', 'Unnamed API'))
            save_apis()  # Save the newly added API to the 'apis.json' file
            QMessageBox.information(self, "API Added", "API added successfully.")

    # Method to handle editing an existing API
    def edit_api(self):
        selected_items = self.api_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select an API to edit.")
            return
        index = self.api_list.currentRow()
        api = apis[index]
        dialog = ApiEditDialog(self, api)
        if dialog.exec_() == QDialog.Accepted:
            apis[index] = dialog.get_api_data()  # Update the API data
            self.api_list.item(index).setText(apis[index].get('API', 'Unnamed API'))
            save_apis()  # Save the edited API to the 'apis.json' file
            QMessageBox.information(self, "API Edited", "API edited successfully.")

    # Method to handle deleting an API
    def delete_api(self):
        selected_items = self.api_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select an API to delete.")
            return
        index = self.api_list.currentRow()
        api_name = apis[index].get('App Details', {}).get('App Name', 'Unnamed API')
        confirm = QMessageBox.question(
            self, "Confirm Deletion",
            f"Are you sure you want to delete API '{api_name}'?",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirm == QMessageBox.Yes:
            del apis[index]
            self.api_list.takeItem(index)  # Remove the item from the list widget
            save_apis()  # Save the updated list of APIs to the 'apis.json' file
            QMessageBox.information(self, "API Deleted", "API deleted successfully.")

#SEGMENT 6 END

#SEGMENT 7 START

# Dialog for adding/editing system prompts, which can modify the chatbot's behavior
# Prompts can guide the AI to respond in specific styles or perform certain tasks
class SystemPromptEditDialog(QDialog):
    def __init__(self, parent=None, prompt=None):
        super().__init__(parent)
        self.setWindowTitle("Edit System Prompt" if prompt else "Add System Prompt")
        self.setMinimumWidth(500)
        self.layout = QFormLayout()
        self.setLayout(self.layout)

        # Prompt Name field
        self.prompt_name = QLineEdit(prompt.get('name', '') if prompt else '')
        self.layout.addRow("Prompt Name:", self.prompt_name)

        # Prompt Content field for actual instructions given to the AI
        self.prompt_content = QTextEdit(prompt.get('content', '') if prompt else '')
        self.layout.addRow("Prompt Content:", self.prompt_content)

        # Dialog buttons: OK and Cancel
        self.buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.layout.addRow(self.buttons)
        self.buttons.accepted.connect(self.validate_and_accept)
        self.buttons.rejected.connect(self.reject)

    # Validate that the necessary fields are filled before proceeding
    def validate_and_accept(self):
        if not self.prompt_name.text().strip():
            QMessageBox.warning(self, "Input Error", "Prompt Name cannot be empty.")
            return
        if not self.prompt_content.toPlainText().strip():
            QMessageBox.warning(self, "Input Error", "Prompt Content cannot be empty.")
            return
        self.accept()

    # Method to retrieve the filled data for storing the prompt
    def get_prompt_data(self):
        return {
            "name": self.prompt_name.text().strip(),
            "content": self.prompt_content.toPlainText().strip()
        }

# Dialog to manage system prompts: Adding, editing, deleting, and running prompts
class SystemPromptManagerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("System Prompt Manager")
        self.setMinimumWidth(600)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # List widget to display all available prompts
        self.prompt_list = QListWidget()
        self.prompt_list.addItems([prompt.get('name', 'Unnamed Prompt') for prompt in system_prompts])
        self.layout.addWidget(self.prompt_list)

        # Buttons to Add, Edit, Delete, and Run system prompts
        button_layout = QHBoxLayout()
        self.add_button = QPushButton("Add Prompt")
        self.edit_button = QPushButton("Edit Prompt")
        self.delete_button = QPushButton("Delete Prompt")
        self.run_button = QPushButton("Run Prompt")
        button_layout.addWidget(self.add_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.run_button)
        self.layout.addLayout(button_layout)

        # Connect buttons to their respective methods
        self.add_button.clicked.connect(self.add_prompt)
        self.edit_button.clicked.connect(self.edit_prompt)
        self.delete_button.clicked.connect(self.delete_prompt)
        self.run_button.clicked.connect(self.run_prompt)

    # Method to handle adding a new system prompt
    def add_prompt(self):
        dialog = SystemPromptEditDialog(self)
        if dialog.exec_() == QDialog.Accepted:
            new_prompt = dialog.get_prompt_data()
            system_prompts.append(new_prompt)
            self.prompt_list.addItem(new_prompt.get('name', 'Unnamed Prompt'))
            save_system_prompts()  # Save the newly added prompt
            QMessageBox.information(self, "Prompt Added", "System prompt added successfully.")

    # Method to handle editing an existing prompt
    def edit_prompt(self):
        selected_items = self.prompt_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a system prompt to edit.")
            return
        index = self.prompt_list.currentRow()
        prompt = system_prompts[index]
        dialog = SystemPromptEditDialog(self, prompt)
        if dialog.exec_() == QDialog.Accepted:
            system_prompts[index] = dialog.get_prompt_data()  # Update the prompt
            self.prompt_list.item(index).setText(system_prompts[index].get('name', 'Unnamed Prompt'))
            save_system_prompts()  # Save the edited prompt
            QMessageBox.information(self, "Prompt Edited", "System prompt edited successfully.")

    # Method to handle deleting a system prompt
    def delete_prompt(self):
        selected_items = self.prompt_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a system prompt to delete.")
            return
        index = self.prompt_list.currentRow()
        prompt_name = system_prompts[index].get('name', 'Unnamed Prompt')
        confirm = QMessageBox.question(
            self, "Confirm Deletion",
            f"Are you sure you want to delete system prompt '{prompt_name}'?",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirm == QMessageBox.Yes:
            del system_prompts[index]
            self.prompt_list.takeItem(index)  # Remove the item from the list widget
            save_system_prompts()  # Save the updated list of prompts
            QMessageBox.information(self, "Prompt Deleted", "System prompt deleted successfully.")

    # Method to handle running a system prompt, which appends the prompt content to the conversation context
    def run_prompt(self):
        selected_items = self.prompt_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a system prompt to run.")
            return
        index = self.prompt_list.currentRow()
        prompt = system_prompts[index]
        prompt_content = prompt.get('content', '')
        if prompt_content:
            self.parent().prepend_system_prompt(prompt_content)
            QMessageBox.information(
                self, "System Prompt Activated",
                f"System prompt '{prompt.get('name', 'Unnamed Prompt')}' has been activated."
            )
        else:
            QMessageBox.warning(self, "Empty Prompt", "The selected system prompt has no content.")

#SEGMENT 7 END

#SEGMENT 8 START

# Dialog for managing conversations: Creating new ones, loading existing ones, or deleting them
class ConversationManagerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Conversation Manager")
        self.setMinimumWidth(600)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # List widget to display all conversations
        self.conversation_list = QListWidget()
        self.conversation_list.addItems(os.listdir(conversations_dir))
        self.layout.addWidget(self.conversation_list)

        # Buttons for creating, loading, or deleting conversations
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("New Conversation")
        self.load_button = QPushButton("Load Conversation")
        self.delete_button = QPushButton("Delete Conversation")
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.load_button)
        button_layout.addWidget(self.delete_button)
        self.layout.addLayout(button_layout)

        # Connect buttons to their respective methods
        self.new_button.clicked.connect(self.new_conversation)
        self.load_button.clicked.connect(self.load_conversation)
        self.delete_button.clicked.connect(self.delete_conversation)

    # Method for creating a new conversation with a unique name
    def new_conversation(self):
        name, ok = QInputDialog.getText(self, "New Conversation", "Enter conversation name:")
        if ok and name:
            # Sanitize filename to prevent illegal characters
            name = re.sub(r'[\\/*?:"<>|]', "", name)
            filename = f"{name}.json"
            filepath = os.path.join(conversations_dir, filename)
            if os.path.exists(filepath):
                QMessageBox.warning(self, "File Exists", "A conversation with this name already exists.")
                return
            save_conversation(filename, [])  # Create a new empty conversation file
            self.conversation_list.addItem(filename)
            QMessageBox.information(self, "Conversation Created", f"Conversation '{name}' created successfully.")

    # Method for loading an existing conversation
    def load_conversation(self):
        selected_items = self.conversation_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a conversation to load.")
            return
        filename = selected_items[0].text()
        conversation = load_conversation(filename)
        if conversation:
            self.parent().load_specific_conversation(conversation, filename)
            QMessageBox.information(self, "Conversation Loaded", f"Conversation '{filename}' loaded successfully.")
        else:
            QMessageBox.warning(self, "Load Failed", f"Failed to load conversation '{filename}'.")

    # Method for deleting a conversation
    def delete_conversation(self):
        selected_items = self.conversation_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a conversation to delete.")
            return
        filename = selected_items[0].text()
        confirm = QMessageBox.question(
            self, "Confirm Deletion",
            f"Are you sure you want to delete conversation '{filename}'?",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirm == QMessageBox.Yes:
            try:
                os.remove(os.path.join(conversations_dir, filename))  # Remove the file from the directory
                self.conversation_list.takeItem(self.conversation_list.row(selected_items[0]))  # Remove from list widget
                QMessageBox.information(self, "Conversation Deleted", f"Conversation '{filename}' deleted successfully.")
            except Exception as e:
                QMessageBox.critical(self, "Deletion Error", f"Failed to delete conversation '{filename}': {e}")
                logging.error(f"Failed to delete conversation '{filename}': {e}")

# Dialog for managing language models: Adding new models, editing existing ones, and deleting models
class LLMManagerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("LLM Manager")
        self.setMinimumWidth(500)
        self.layout = QVBoxLayout()
        self.setLayout(self.layout)

        # List widget to display all available models
        self.model_list = QListWidget()
        self.model_list.addItems([name for name in models.keys()])
        self.layout.addWidget(self.model_list)

        # Buttons for adding, editing, and deleting models
        button_layout = QHBoxLayout()
        self.add_button = QPushButton("Add Model")
        self.edit_button = QPushButton("Edit Model")
        self.delete_button = QPushButton("Delete Model")
        button_layout.addWidget(self.add_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        self.layout.addLayout(button_layout)

        # Connect buttons to their respective methods
        self.add_button.clicked.connect(self.add_model)
        self.edit_button.clicked.connect(self.edit_model)
        self.delete_button.clicked.connect(self.delete_model)

    # Method for adding a new language model
    def add_model(self):
        name, ok = QInputDialog.getText(self, "Add Model", "Enter model name:")
        if ok and name:
            if name in models:
                QMessageBox.warning(self, "Duplicate Model", "A model with this name already exists.")
                return
            identifier, ok = QInputDialog.getText(
                self, "Add Model",
                "Enter model identifier (e.g., Hugging Face model name):"
            )
            if ok and identifier:
                models[name] = identifier  # Add the model to the models dictionary
                self.model_list.addItem(name)  # Add to the list widget
                save_models()  # Save the updated models list to 'models.json'
                QMessageBox.information(self, "Model Added", f"Model '{name}' added successfully.")

    # Method for editing an existing model's name or identifier
    def edit_model(self):
        selected_items = self.model_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a model to edit.")
            return
        index = self.model_list.currentRow()
        current_name = self.model_list.item(index).text()
        current_identifier = models[current_name]

        # Prompt the user to update the model's name
        name, ok = QInputDialog.getText(
            self, "Edit Model",
            "Enter new model name:",
            text=current_name
        )
        if ok and name:
            if name != current_name and name in models:
                QMessageBox.warning(self, "Duplicate Model", "A model with this name already exists.")
                return
            identifier, ok = QInputDialog.getText(
                self, "Edit Model",
                "Enter new model identifier:",
                text=current_identifier
            )
            if ok and identifier:
                del models[current_name]  # Remove the old entry
                models[name] = identifier  # Add the new entry
                self.model_list.item(index).setText(name)  # Update the list widget
                save_models()  # Save the updated model to 'models.json'
                QMessageBox.information(self, "Model Edited", f"Model '{name}' edited successfully.")

    # Method for deleting a language model
    def delete_model(self):
        selected_items = self.model_list.selectedItems()
        if not selected_items:
            QMessageBox.warning(self, "No Selection", "Please select a model to delete.")
            return
        index = self.model_list.currentRow()
        model_name = self.model_list.item(index).text()
        confirm = QMessageBox.question(
            self, "Confirm Deletion",
            f"Are you sure you want to delete model '{model_name}'?",
            QMessageBox.Yes | QMessageBox.No
        )
        if confirm == QMessageBox.Yes:
            del models[model_name]  # Remove the model from the dictionary
            self.model_list.takeItem(index)  # Remove from the list widget
            save_models()  # Save the updated list of models to 'models.json'
            QMessageBox.information(self, "Model Deleted", f"Model '{model_name}' deleted successfully.")

#SEGMENT 8 END

# SEGMENT 9 START
# Full and complete version with all updates and missing code restored

# Define the ChatbotGUI class
class ChatbotGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        
    def open_model_manager(self):
    # This will open the existing dialog for managing models
        model_manager_dialog = LLMManagerDialog(self)
        model_manager_dialog.exec_()
        
    def open_system_prompt_manager(self):
    # This will open the dialog for managing system prompts
        system_prompt_manager_dialog = SystemPromptManagerDialog(self)
        system_prompt_manager_dialog.exec_()
        
    def open_conversation_manager(self):
    # This will open the conversation manager dialog
        conversation_manager_dialog = ConversationManagerDialog(self)
        conversation_manager_dialog.exec_()

        
        # Method to open the API Manager dialog
    def open_api_manager(self):
        dialog = ApiManagerDialog(self)  # Assuming ApiManagerDialog is defined elsewhere
        dialog.exec_()
        
    # Initialize the User Interface (UI) components
    def init_ui(self):
        # Set up the main window properties
        self.setWindowTitle("SOLMasterX AI Chatbot")
        self.setGeometry(100, 100, 800, 600)
        self.worker = Worker(model, tokenizer, self.context, "", apis, plugins)  # Initialize the worker with required arguments
        self.load_specific_conversation(load_default=True)
        
        # Initialize the layout and components
        self.init_layout()
        
    # Create and set the layout for the GUI
    def init_layout(self):
        self.main_layout = QVBoxLayout()
        
         # Add the menu bar directly to the window
        self.menu_bar = self.menuBar()
    
    # Chat history display
        self.chat_display = QTextEdit(self)
        self.chat_display.setReadOnly(True)
        self.main_layout.addWidget(self.chat_display)
        
        # Create a text area for displaying chat history
        self.chat_display = QTextEdit(self)
        self.chat_display.setReadOnly(True)
        self.main_layout.addWidget(self.chat_display)

        # Create a text area for displaying chat history
        self.chat_display = QTextEdit(self)
        self.chat_display.setReadOnly(True)
        self.main_layout.addWidget(self.chat_display)

        # User input field
        self.user_input = QLineEdit(self)
        self.user_input.setPlaceholderText("Type your message here...")
        self.main_layout.addWidget(self.user_input)

        # Send button
        self.send_button = QPushButton("Send", self)
        self.main_layout.addWidget(self.send_button)

        # Status label
        self.status_label = QLabel(self)
        self.status_label.setText("Status: Ready")
        self.main_layout.addWidget(self.status_label)
        
        # Set the layout for the central widget
        container = QWidget()
        container.setLayout(self.main_layout)
        self.setCentralWidget(container)

        # Set the layout to the main window
        self.setLayout(self.main_layout)

        # Connect the send button to the send_message method
        self.send_button.clicked.connect(self.send_message)
        
    # Method to send user input to the chatbot
    def send_message(self):
        user_message = self.user_input.text().strip()
        if not user_message:
            return  # Don't send an empty message
        
        # Display the user message in the chat display
        self.chat_display.append(f"You: {user_message}")

        # Clear the input field after sending the message
        self.user_input.clear()

        # Generate a response from the AI model
        chatbot_response = self.generate_response(user_message)

        # Display the AI's response in the chat display
        self.chat_display.append(f"Bot: {chatbot_response}")

    # Method to generate a response using the AI model
    def generate_response(self, input_text):
        # Tokenize the input text for the model
        inputs = self.tokenizer(input_text, return_tensors="pt").to(self.model.device)
        
        # Generate the AI response using the model
        outputs = self.model.generate(
            **inputs,
            max_new_tokens=150,  # You can adjust the number of tokens
            temperature=0.7,
            top_p=0.9,
            do_sample=True,
            eos_token_id=self.tokenizer.eos_token_id
        )

        # Decode the generated tokens to text
        response_text = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        return response_text

    # Initialize the main chatbot GUI, adding file/folder attachment functionality
    def init_ui(self):
        self.setWindowTitle('SOLMasterX AI')
        self.setGeometry(100, 100, 800, 900)
        self.setWindowIcon(QIcon())  # Optionally set an icon if needed

        # Initialize the conversation context, which keeps track of the ongoing conversation
        self.context = []
        self.file_knowledge = []  # To store the knowledge from attached files

        # Initialize LLM settings (defaults)
        self.max_new_tokens = 150
        self.temperature = 0.7
        self.top_p = 0.9

        # Initialize UI components, menus, and load the default conversation
        self.init_menu()
        self.init_layout()

        # Load the default conversation or prompt the user to load one
        self.current_conversation = "default_conversation.json"
        self.worker = Worker(model, tokenizer, self.context, "", apis, plugins)  # Initialize the worker
        self.load_specific_conversation(load_default=True)

    # Method to initialize the menu, adding 'File Manager' and 'LLM Customization' to handle file attachments and LLM specs
    def init_menu(self):
        self.menu_bar = self.menuBar()
        self.options_menu = self.menu_bar.addMenu("Options")

        # Add the 'File Manager' option in the menu bar for attaching files and folders
        self.file_manager_action = QAction("Attach Files/Folders", self)
        self.file_manager_action.triggered.connect(self.open_file_manager)
        self.options_menu.addAction(self.file_manager_action)

        # Add LLM customization option to the menu bar
        self.customize_llm_action = QAction("Customize LLM Settings", self)
        self.customize_llm_action.triggered.connect(self.open_llm_customization_dialog)
        self.options_menu.addAction(self.customize_llm_action)

        # Add the existing options to manage APIs, models, system prompts, and conversations
        self.api_manager_action = QAction("Manage APIs", self)
        self.api_manager_action.triggered.connect(self.open_api_manager)
        self.options_menu.addAction(self.api_manager_action)

        self.llm_manager_action = QAction("Manage Models", self)
        self.llm_manager_action.triggered.connect(self.open_model_manager)
        self.options_menu.addAction(self.llm_manager_action)

        self.system_prompt_manager_action = QAction("Manage System Prompts", self)
        self.system_prompt_manager_action.triggered.connect(self.open_system_prompt_manager)
        self.options_menu.addAction(self.system_prompt_manager_action)

        self.conversation_manager_action = QAction("Manage Conversations", self)
        self.conversation_manager_action.triggered.connect(self.open_conversation_manager)
        self.options_menu.addAction(self.conversation_manager_action)

    # Method to initialize the main layout of the chatbot, including input fields, chat history, and performance report
    def init_layout(self):
        main_layout = QVBoxLayout()

        # Chat history display (QTextEdit)
        self.chat_display = QTextEdit()
        self.chat_display.setReadOnly(True)
        main_layout.addWidget(self.chat_display)

        # Input field layout for user input and sending messages
        input_layout = QHBoxLayout()
        self.input_field = QLineEdit()
        self.input_field.setPlaceholderText('Type your message here...')
        self.input_field.returnPressed.connect(self.handle_send)
        input_layout.addWidget(self.input_field)

        self.send_button = QPushButton('Send')
        self.send_button.clicked.connect(self.handle_send)
        input_layout.addWidget(self.send_button)

        input_container = QWidget()
        input_container.setLayout(input_layout)
        main_layout.addWidget(input_container)

        # Add performance report and model selection dropdown to the layout (UI components remain unchanged)
        self.setLayout(main_layout)

    # Dialog to customize LLM settings
    def open_llm_customization_dialog(self):
        dialog = QDialog(self)
        dialog.setWindowTitle("Customize LLM Settings")

        layout = QFormLayout()

        # Fields for LLM settings
        self.max_tokens_input = QLineEdit(str(self.max_new_tokens))
        self.temperature_input = QLineEdit(str(self.temperature))
        self.top_p_input = QLineEdit(str(self.top_p))

        layout.addRow("Max Tokens:", self.max_tokens_input)
        layout.addRow("Temperature:", self.temperature_input)
        layout.addRow("Top P:", self.top_p_input)

        # Buttons to save or cancel
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(self.save_llm_settings)
        button_box.rejected.connect(dialog.reject)
        layout.addWidget(button_box)

        dialog.setLayout(layout)
        dialog.exec_()

    # Save LLM settings from the dialog
    def save_llm_settings(self):
        try:
            self.max_new_tokens = int(self.max_tokens_input.text())
            self.temperature = float(self.temperature_input.text())
            self.top_p = float(self.top_p_input.text())
            QMessageBox.information(self, "Settings Saved", "LLM settings updated successfully.")
        except ValueError:
            QMessageBox.warning(self, "Invalid Input", "Please ensure all inputs are valid numbers.")

    # Method to open the File Manager to allow attaching files and selecting folders
    def open_file_manager(self):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.ExistingFiles)
        dialog.setNameFilters([
            "Python files (*.py *.pyw)", "Text files (*.txt)", "PDF files (*.pdf)",
            "Image files (*.jpg *.png)", "CSV files (*.csv)", "HTML files (*.html *.htm)",
            "JSON files (*.json)", "JavaScript files (*.js)", "PHP files (*.php)", 
            "Document files (*.doc *.docx)"
        ])
        dialog.setViewMode(QFileDialog.Detail)

        if dialog.exec_():
            selected_files = dialog.selectedFiles()
            self.process_attached_files(selected_files)

        # Also allow folder selection for entire folders to train the AI on
        folder_dialog = QFileDialog(self)
        folder_dialog.setFileMode(QFileDialog.Directory)
        folder_dialog.setOption(QFileDialog.ShowDirsOnly, True)

        if folder_dialog.exec_():
            selected_folder = folder_dialog.selectedFiles()
            self.process_attached_folder(selected_folder)

    # Additional methods for handling files, conversations, and saving context...

# SEGMENT 9 END


#SEGMENT 10 START (Full and Complete Code for #SEGMENT 10 with Code Block Enhancements)

    # Method to handle sending the user's message
    # This processes the user input and generates the AI's response
    def handle_send(self):
        user_input = self.input_field.text().strip()
        if user_input:
            self.chat_display.append(f"<b>You:</b> {self.escape_html(user_input)}")
            self.input_field.clear()
            self.chat_display.append(f"<b>Assistant:</b> Generating response...")
            self.generate_infinite_tokens(user_input)

    # Infinite tokens implementation with chunking and a 5-second cool-down before resuming
    async def generate_infinite_tokens(self, user_input):
        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(0, 0)  # Indeterminate progress
        self.status_label.setText("Thinking...")

        # Initialize worker thread for token generation with infinite tokens support
        self.thread = QThread()

        self.worker = Worker(
            model, tokenizer, self.context, user_input, apis, plugins, 
            file_knowledge=self.file_knowledge,  # Ensure file knowledge is passed to the Worker
            max_new_tokens=self.max_new_tokens,  # Use customizable settings
            temperature=self.temperature,
            top_p=self.top_p
        )

        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.handle_response)
        self.worker.error.connect(self.handle_error)
        self.worker.status.connect(self.update_status)  # Status updates for "Analyzing", "Rewriting", etc.
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    # Method to update the status label based on the AI's activity (e.g., "Thinking", "Analyzing", etc.)
    def update_status(self, status_message):
        self.status_label.setText(status_message)

    # Method to handle the AI's response and display it in the chat window
    # Also adds the AI response to the conversation context for future reference
    def handle_response(self, response):
        self.progress_bar.setVisible(False)
        self.status_label.setText("")
        formatted_response = self.process_media_in_response(response)
        self.chat_display.append(f"<b>Assistant:</b> {formatted_response}")
        # Add the user's input and AI's response to the context
        self.context.append({"role": "user", "content": self.worker.user_input})
        self.context.append({"role": "assistant", "content": response})
        # Save the conversation for future continuation, including reflection history and file knowledge
        save_conversation(self.current_conversation, self.context, self.worker.reflection_history, self.file_knowledge)

    # Method to process the AI response and format code within code blocks with a "copy" button
    def process_code_in_response(self, response):
        # Detect code blocks and wrap them with a div to add a "copy" button
        code_block_pattern = r'```(.*?)```'
        code_blocks = re.findall(code_block_pattern, response, re.DOTALL)

        for code_block in code_blocks:
            # Create a code block with a copy button
            copy_button_html = f'<button class="copy-button" onclick="navigator.clipboard.writeText(`{code_block}`)">Copy</button>'
            formatted_code_block = f'<pre><code>{self.escape_html(code_block)}</code>{copy_button_html}</pre>'
            response = response.replace(f"```{code_block}```", formatted_code_block)

        return response

    # Method to handle copy button event in the chat window
    def copy_code_to_clipboard(self, code):
        QApplication.clipboard().setText(code)
        QMessageBox.information(self, "Copied", "Code has been copied to the clipboard.")

#SEGMENT 10 END


#SEGMENT 11 START 
#(Full and Complete Version, including all previous updates)

    # Method to save the current context, reflection history, and file/folder knowledge to a file
    def save_context(self):
        try:
            # Save conversation, reflection history, and file knowledge to ensure all data is preserved
            save_conversation(self.current_conversation, self.context, self.worker.reflection_history, self.file_knowledge)
            logging.info("Context, reflection history, and file knowledge saved successfully.")
        except Exception as e:
            logging.error(f"Error saving context: {e}")

    # Method to load a specific conversation from a file and display it in the chat window
    # Can load either the default conversation or a specified one
    def load_specific_conversation(self, conversation=None, filename=None, load_default=False):
        if load_default:
            # Load the default conversation or create a new one if it doesn't exist
            if not os.path.exists(os.path.join(conversations_dir, self.current_conversation)):
                save_conversation(self.current_conversation, [], {}, [])  # Start fresh with a new conversation and reflection history
            conversation, reflection_history, file_knowledge = load_conversation(self.current_conversation)
            filename = self.current_conversation
        if conversation is not None and filename is not None:
            self.context = conversation
            self.worker.reflection_history = reflection_history  # Load reflection history for this conversation
            self.file_knowledge = file_knowledge  # Load file/folder knowledge for this conversation
            self.current_conversation = filename
            self.chat_display.clear()  # Clear previous conversation UI
            for message in self.context:
                role = message.get("role", "user")
                content = message.get("content", "")
                if role == "system":
                    self.chat_display.append(f"<i>System:</i> {self.escape_html(content)}")
                elif role == "user":
                    self.chat_display.append(f"<b>You:</b> {self.escape_html(content)}")
                elif role == "assistant":
                    formatted_content =  formatted_content = self.process_media_in_response(content)
                    self.chat_display.append(f"<b>Assistant:</b> {formatted_content}")
            logging.info(f"Conversation '{filename}' loaded successfully with reflection history and file knowledge.")

    # Method to handle closing of the window, saving the current conversation context before closing
    def closeEvent(self, event):
        self.save_context()  # Ensure the current context, reflection history, and file knowledge are saved before closing
        event.accept()

    # Escape HTML content to ensure the chat display handles special characters properly
    def escape_html(self, text):
        return html.escape(text).replace('\n', '<br>')

    # Process media elements in the AI's response (images, videos) and replace them with HTML elements
    # For images, embed them in the chat; for videos, create clickable links
    def process_media_in_response(self, response):
        # Detect and embed image URLs (e.g., PNG, JPEG, GIF)
        image_patterns = [
            r'(https?://\S+\.(?:png|jpg|jpeg|gif))',
            r'!\[.*?\]\((https?://\S+\.(?:png|jpg|jpeg|gif))\)'
        ]
        for pattern in image_patterns:
            matches = re.findall(pattern, response, re.IGNORECASE)
            for match in matches:
                img_tag = f'<img src="{match}" width="300"/>'
                response = response.replace(match, img_tag)

        # Detect video URLs and create clickable links (e.g., MP4, WEBM, OGG)
        video_patterns = [
            r'(https?://\S+\.(?:mp4|webm|ogg))',
            r'!\[.*?\]\((https?://\S+\.(?:mp4|webm|ogg))\)'
        ]
        for pattern in video_patterns:
            matches = re.findall(pattern, response, re.IGNORECASE)
            for match in matches:
                # Since QTextEdit cannot play videos, provide a clickable link
                link = f'<a href="{match}">Video</a>'
                response = response.replace(match, link)

        return response

# Example Plugin: Weather Plugin
# This plugin simulates fetching weather data and integrates it into the conversation
# Save this plugin as 'plugins/weather_plugin.py'

"""
# plugins/weather_plugin.py

def register():
    import requests

    def get_weather(user_input):
        # Simple keyword-based trigger
        if "weather" in user_input.lower():
            # Replace with your actual weather API call
            api_key = "YOUR_OPENWEATHERMAP_API_KEY"
            city = "London"  # Default city or extract from user_input
            try:
                response = requests.get(
                    f"http://api.openweathermap.org/data/2.5/weather?q={city}&appid={api_key}&units=metric"
                )
                data = response.json()
                if data.get("cod") != 200:
                    return f"Weather API Error: {data.get('message', 'Unknown error')}"
                weather_desc = data["weather"][0]["description"]
                temp = data["main"]["temp"]
                return f"Current weather in {city}: {weather_desc} with a temperature of {temp}°C."
            except Exception as e:
                return f"Weather Plugin Error: {e}"
        return ""

    return {
        "name": "WeatherPlugin",
        "process": get_weather
    }
"""

#SEGMENT 11 END

#SEGMENT 12 START

if __name__ == '__main__':
    # Initialize the application
    app = QApplication(sys.argv)
    
    # Create an instance of the Chatbot GUI
    chatbot_gui = ChatbotGUI()
    
    # Display the chatbot GUI
    chatbot_gui.show()
    
    # Start the PyQt application event loop
    sys.exit(app.exec_())

#SEGMENT 12 END