entwickel die anwendung weiter und verbessere sie. Prüfe alle gui elemente und sorge dafür das sie korrekt funktionieren. Verbessere das skript und nenne mir anschließend den vollständigen verbesserten code der gesamten anwendung.

import sys
import threading
import requests
import json
import logging
import time
import subprocess
import os
import re
from datetime import datetime
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QPushButton, QAction,
    QHBoxLayout, QVBoxLayout, QWidget, QTextEdit, QFileDialog, QSplitter,
    QTreeWidget, QTreeWidgetItem, QTextBrowser, QProgressBar,
    QListWidget, QStatusBar, QMessageBox, QInputDialog, QLineEdit, QGroupBox, QFormLayout, QSpinBox,
    QStackedWidget, QToolBar, QStyle, QSizePolicy, QComboBox
)
from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal, QSize, QPropertyAnimation, QRect
from PyQt5.QtGui import QColor, QMovie, QIcon, QPixmap

# Konfiguration des Loggings, um sowohl in der Konsole als auch in der Datei auszugeben
logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("app.log", mode='w', encoding='utf-8'),
        logging.StreamHandler(sys.stdout)
    ]
)

class LLMClient:
    def __init__(self, base_url='http://localhost:11434'):
        self.base_url = base_url
        self.cache = {}
        self.memory = {}  # Kontextspeicher für Aufgaben

    def get_models(self):
        try:
            logging.debug("Sending request to /api/tags to retrieve available models.")
            response = requests.get(f'{self.base_url}/api/tags')
            if response.status_code == 200:
                models_data = response.json()
                logging.debug(f"Response from /api/tags: {models_data}")
                models = [model['name'] for model in models_data.get('models', [])]
                return models
            else:
                logging.error(f"Error fetching models: {response.status_code} - {response.text}")
                return []
        except requests.RequestException as e:
            logging.error(f"Error fetching models: {e}")
            return []

    def generate(self, model, prompt):
        cache_key = (model, prompt)
        if cache_key in self.cache:
            logging.debug(f"Retrieved result from cache for model '{model}' and prompt.")
            return self.cache[cache_key]
        payload = {
            "model": model,
            "prompt": prompt,
            "format": "json",
            "stream": False
        }
        headers = {"Content-Type": "application/json"}
        try:
            logging.debug(f"Sending generation request to /api/generate with payload: {payload}")
            response = requests.post(f'{self.base_url}/api/generate', json=payload, headers=headers)
            if response.status_code == 200:
                json_response = response.json()
                logging.debug(f"Response from /api/generate: {json_response}")
                result = json_response.get('response', '')
                self.cache[cache_key] = result.strip()
                return result.strip()
            else:
                logging.error(f"Error generating response: {response.status_code} - {response.text}")
                return f"Error generating response: {response.text}"
        except requests.RequestException as e:
            logging.error(f"Error communicating with LLM: {e}")
            return f"Error communicating with LLM: {e}"

    def add_to_memory(self, key, value):
        self.memory[key] = value

    def get_from_memory(self, key):
        return self.memory.get(key, '')

class TaskProcessor(QThread):
    progress_updated = pyqtSignal(int)
    task_message = pyqtSignal(str)
    task_result = pyqtSignal(str)
    task_status = pyqtSignal(int, int, str)  # task_index, goal_index, status

    def __init__(self, tasks, llm_client, llm_model, parent=None):
        super().__init__(parent)
        self.tasks = tasks
        self.llm_client = llm_client
        self.llm_model = llm_model
        self._paused = False
        self._stopped = False
        self.current_task_index = 0
        self.current_goal_index = 0
        self.context_memory = {}  # Speichert Ergebnisse vorheriger Schritte

    def run(self):
        total_goals = sum(len(task['Goals']) for task in self.tasks)
        completed_goals = 0

        for i, task in enumerate(self.tasks):
            self.current_task_index = i
            if self._stopped:
                break
            while self._paused:
                self.msleep(100)

            self.task_message.emit(f"Assistant: Starting task '{task['Title']}'")
            self.task_status.emit(i, -1, 'processing')  # Markiere Aufgabe als in Bearbeitung

            for j, goal in enumerate(task['Goals']):
                self.current_goal_index = j
                if self._stopped:
                    break
                while self._paused:
                    self.msleep(100)

                self.task_message.emit(f"Assistant: Processing goal: {goal}")
                self.task_status.emit(i, j, 'processing')  # Markiere Ziel als in Bearbeitung

                result = self.process_goal(task, goal)
                success = self.self_review(goal, result)
                if success:
                    self.task_result.emit(f"Result for goal '{goal}':\n{result}")
                    self.context_memory[goal] = result  # Speichere Ergebnis im Kontext
                    self.task_status.emit(i, j, 'completed')  # Markiere Ziel als abgeschlossen
                else:
                    self.task_message.emit(f"Assistant: Repeating processing for goal: {goal}")
                    self.task_status.emit(i, j, 'error')  # Markiere Ziel als Fehler
                    # Wiederhole die Bearbeitung
                    result = self.process_goal(task, goal, retry=True)
                    success = self.self_review(goal, result)
                    if success:
                        self.task_result.emit(f"Result for goal '{goal}' after retry:\n{result}")
                        self.context_memory[goal] = result
                        self.task_status.emit(i, j, 'completed')
                    else:
                        self.task_message.emit(f"Assistant: Could not successfully process goal '{goal}'.")
                        self.task_status.emit(i, j, 'failed')

                completed_goals += 1
                progress = int((completed_goals / total_goals) * 100)
                logging.debug(f"Progress: {progress}%")
                self.progress_updated.emit(progress)

            self.task_status.emit(i, -1, 'completed')  # Markiere Aufgabe als abgeschlossen

            # Zusammenfassung der Aufgabe
            self.task_message.emit(f"Assistant: Creating summary for task: {task['Title']}")
            task_summary = self.create_task_summary(task)
            self.task_result.emit(f"Summary for task '{task['Title']}':\n{task_summary}")
            self.context_memory[task['Title']] = task_summary

        if not self._stopped:
            self.task_message.emit("Assistant: All tasks have been completed.")
            # Generiere finales Ergebnis
            final_result = self.generate_final_result()
            self.task_result.emit(f"Final Result:\n{final_result}")

    def process_goal(self, task, goal, retry=False):
        context = self.get_relevant_context(goal)
        retry_text = "This is a retry attempt.\n" if retry else ""

        prompt = f"""
{retry_text}
You are working on a software development task.

**Application Description:**
{task.get('ApplicationDescription', '')}

**Current Goal:**
{goal}

**Relevant Context:**
{context}

Create a solution for the goal above. Use clear and precise language and provide code examples where appropriate.
"""
        logging.debug(f"Generating response for goal '{goal}' with prompt: {prompt}")
        result = self.llm_client.generate(self.llm_model, prompt)
        logging.debug(f"Received result for goal '{goal}': {result}")
        return result

    def self_review(self, goal, result):
        prompt = f"""
Critically review the following result for the goal "{goal}":

{result}

Evaluate if the result is complete, correct, and meets the requirements. Answer with "Yes" if the result is satisfactory, or "No" with a brief explanation if it is not satisfactory.
"""
        logging.debug(f"Performing self-review for goal '{goal}'.")
        review = self.llm_client.generate(self.llm_model, prompt)
        logging.debug(f"Self-review result for goal '{goal}': {review}")
        if "Yes" in review:
            return True
        else:
            self.task_message.emit(f"Assistant: Self-review for goal '{goal}' failed. Reason: {review}")
            return False

    def get_relevant_context(self, current_goal):
        # Sammle relevante Ergebnisse aus dem Kontext
        context = ""
        for key, value in self.context_memory.items():
            context += f"**{key}:**\n{value}\n\n"
        return context

    def create_task_summary(self, task):
        # Erstelle eine Zusammenfassung der Aufgabe basierend auf den Ergebnissen der Ziele
        summaries = []
        for goal in task['Goals']:
            result = self.context_memory.get(goal, '')
            summaries.append(f"{goal}:\n{result}\n")
        task_summary = "\n".join(summaries)
        return task_summary

    def generate_final_result(self):
        # Generiere das finale Ergebnis basierend auf allen Aufgaben
        context = "\n".join(self.context_memory.values())
        prompt = f"""
Summarize all previous results and generate the complete code of the application including documentation.

**Overall Context:**
{context}

Create a structured and comprehensive summary with all relevant details.
"""
        logging.debug("Generating final result.")
        final_result = self.llm_client.generate(self.llm_model, prompt)
        logging.debug(f"Final result: {final_result}")
        return final_result

    def pause(self):
        self._paused = True

    def resume(self):
        self._paused = False

    def stop(self):
        self._stopped = True

class ToggleButton(QPushButton):
    def __init__(self, icon_path, icon_size, parent=None):
        super().__init__(parent)
        self.setIcon(QIcon(icon_path))
        self.setIconSize(icon_size)
        self.setFixedSize(icon_size)
        self.setStyleSheet("border: none;")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Assistant for Developers')
        self.setGeometry(100, 100, 1200, 800)
        self.llm_client = LLMClient()
        self.paused = False
        self.stopped = False
        self.plan_data = []
        self.uploaded_files = []
        self.server_process = None  # Zum Verwalten des Serverprozesses

        self.init_ui()
        self.apply_styles()

        # Überprüfe den Serverstatus beim Start
        self.update_server_status()

    def init_ui(self):
        self.create_menu()
        self.create_toolbar()
        self.status_bar = self.statusBar()
        self.status_bar.showMessage('Ready')

        self.stacked_widget = QStackedWidget()
        self.setCentralWidget(self.stacked_widget)

        self.create_startup_screen()
        self.create_main_screen()

        # Zeige den Startbildschirm
        self.stacked_widget.setCurrentWidget(self.startup_screen)

    def create_startup_screen(self):
        self.startup_screen = QWidget()
        layout = QVBoxLayout()

        label = QLabel("Welcome to the Intelligent Assistant for Developers")
        label.setAlignment(Qt.AlignCenter)
        label.setStyleSheet("font-size: 24px; font-weight: bold;")

        new_project_btn = QPushButton("Create New Application")
        load_project_btn = QPushButton("Continue Saved Application")

        new_project_btn.setFixedSize(300, 50)
        load_project_btn.setFixedSize(300, 50)

        new_project_btn.clicked.connect(self.new_project)
        load_project_btn.clicked.connect(self.load_project)

        layout.addStretch()
        layout.addWidget(label)
        layout.addSpacing(50)
        layout.addWidget(new_project_btn, alignment=Qt.AlignCenter)
        layout.addWidget(load_project_btn, alignment=Qt.AlignCenter)
        layout.addStretch()

        self.startup_screen.setLayout(layout)
        self.stacked_widget.addWidget(self.startup_screen)

    def create_main_screen(self):
        self.main_screen = QWidget()
        main_layout = QVBoxLayout()

        # Hauptlayout mit Seitenleisten und zentralem Widget
        self.main_layout = QHBoxLayout()

        # Toggle-Buttons für die Seitenleisten
        self.left_toggle_btn = ToggleButton("icons/left.png", QSize(24, 24))
        self.right_toggle_btn = ToggleButton("icons/right.png", QSize(24, 24))

        self.left_toggle_btn.clicked.connect(self.toggle_left_panel)
        self.right_toggle_btn.clicked.connect(self.toggle_right_panel)

        # Linke Seitenleiste
        self.left_panel = self.create_left_panel()
        self.left_panel.setMinimumWidth(250)
        self.left_panel.setMaximumWidth(250)

        # Rechte Seitenleiste
        self.right_panel = self.create_right_panel()
        self.right_panel.setMinimumWidth(300)
        self.right_panel.setMaximumWidth(300)

        # Animationen erstellen
        self.left_panel_animation = QPropertyAnimation(self.left_panel, b"geometry")
        self.right_panel_animation = QPropertyAnimation(self.right_panel, b"geometry")

        # Zentrales Widget (Chat-Bereich)
        self.center_widget = self.create_center_widget()

        # Platzierung der Widgets im Hauptlayout
        self.main_layout.addWidget(self.left_panel)
        self.main_layout.addWidget(self.center_widget)
        self.main_layout.addWidget(self.right_panel)

        # Gesamtes Layout
        main_layout.addLayout(self.main_layout)

        self.main_screen.setLayout(main_layout)
        self.stacked_widget.addWidget(self.main_screen)

    def toggle_left_panel(self):
        if self.left_panel.isVisible():
            self.left_panel_animation.setDuration(300)
            self.left_panel_animation.setStartValue(self.left_panel.geometry())
            self.left_panel_animation.setEndValue(QRect(self.left_panel.x() - self.left_panel.width(), self.left_panel.y(), self.left_panel.width(), self.left_panel.height()))
            self.left_panel_animation.finished.connect(self.left_panel.hide)
            self.left_panel_animation.start()
        else:
            self.left_panel.show()
            self.left_panel_animation.setDuration(300)
            self.left_panel_animation.setStartValue(QRect(self.left_panel.x() - self.left_panel.width(), self.left_panel.y(), self.left_panel.width(), self.left_panel.height()))
            self.left_panel_animation.setEndValue(QRect(self.left_panel.x(), self.left_panel.y(), self.left_panel.width(), self.left_panel.height()))
            self.left_panel_animation.start()

    def toggle_right_panel(self):
        if self.right_panel.isVisible():
            self.right_panel_animation.setDuration(300)
            self.right_panel_animation.setStartValue(self.right_panel.geometry())
            self.right_panel_animation.setEndValue(QRect(self.right_panel.x() + self.right_panel.width(), self.right_panel.y(), self.right_panel.width(), self.right_panel.height()))
            self.right_panel_animation.finished.connect(self.right_panel.hide)
            self.right_panel_animation.start()
        else:
            self.right_panel.show()
            self.right_panel_animation.setDuration(300)
            self.right_panel_animation.setStartValue(QRect(self.right_panel.x() + self.right_panel.width(), self.right_panel.y(), self.right_panel.width(), self.right_panel.height()))
            self.right_panel_animation.setEndValue(QRect(self.right_panel.x(), self.right_panel.y(), self.right_panel.width(), self.right_panel.height()))
            self.right_panel_animation.start()

    def create_left_panel(self):
        left_panel = QWidget()
        layout = QVBoxLayout()

        # Beschreibung der Anwendung
        description_label = QLabel('Application Description:')
        self.description_text = QTextEdit()

        # Dateiupload
        self.upload_btn = QPushButton('Upload Files')
        self.file_list = QListWidget()

        # Einstellungen
        settings_group = QGroupBox('Settings')
        settings_layout = QFormLayout()
        self.token_limit_spin = QSpinBox()
        self.token_limit_spin.setRange(100, 10000)
        self.token_limit_spin.setValue(1000)
        settings_layout.addRow('Token Limit:', self.token_limit_spin)
        settings_group.setLayout(settings_layout)

        layout.addWidget(description_label)
        layout.addWidget(self.description_text)
        layout.addWidget(self.upload_btn)
        layout.addWidget(QLabel('Uploaded Files:'))
        layout.addWidget(self.file_list)
        layout.addWidget(settings_group)
        layout.addStretch()

        self.upload_btn.clicked.connect(self.upload_files)

        left_panel.setLayout(layout)
        return left_panel

    def create_right_panel(self):
        right_panel = QWidget()
        layout = QVBoxLayout()

        # Plan-Anzeige
        self.plan_tree = QTreeWidget()
        self.plan_tree.setHeaderLabels(['Task', 'Description/Goals'])
        self.plan_tree.setColumnWidth(0, 200)

        # Analyse-Button
        self.analyse_btn = QPushButton('Start Analysis')
        self.start_processing_btn = QPushButton('Start Processing')
        self.edit_plan_btn = QPushButton('Edit Plan')

        h_button_layout = QHBoxLayout()
        h_button_layout.addWidget(self.analyse_btn)
        h_button_layout.addWidget(self.start_processing_btn)
        h_button_layout.addWidget(self.edit_plan_btn)

        layout.addLayout(h_button_layout)
        layout.addWidget(self.plan_tree)
        layout.addStretch()

        # Verbindungen
        self.analyse_btn.clicked.connect(self.start_analysis)
        self.start_processing_btn.clicked.connect(self.start_processing)
        self.edit_plan_btn.clicked.connect(self.edit_plan)

        right_panel.setLayout(layout)
        return right_panel

    def create_center_widget(self):
        center_widget = QWidget()
        layout = QVBoxLayout()

        # LLM Auswahl und Serverstatus
        llm_label = QLabel('Select LLM:')
        self.llm_combo = QComboBox()

        self.server_status_label = QLabel('Server Status: Unknown')
        self.server_status_indicator = QLabel('⚪')

        self.start_server_btn = QPushButton('Start Server')
        self.stop_server_btn = QPushButton('Stop Server')

        h_layout = QHBoxLayout()
        h_layout.addWidget(llm_label)
        h_layout.addWidget(self.llm_combo)
        h_layout.addStretch()
        h_layout.addWidget(self.server_status_label)
        h_layout.addWidget(self.server_status_indicator)
        h_layout.addWidget(self.start_server_btn)
        h_layout.addWidget(self.stop_server_btn)

        # Chat-Bereich
        self.chat_area = QTextBrowser()

        # Fortschrittsanzeige und Ladeindikator
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setAlignment(Qt.AlignCenter)

        self.loading_label = QLabel()
        self.loading_label.setAlignment(Qt.AlignCenter)
        self.loading_movie = QMovie("loading.gif")  # Pfad zu Ihrer Ladeanimationsdatei
        self.loading_label.setMovie(self.loading_movie)
        self.loading_movie.start()
        self.loading_label.setVisible(False)

        layout.addWidget(self.left_toggle_btn, alignment=Qt.AlignLeft)
        layout.addWidget(self.right_toggle_btn, alignment=Qt.AlignRight)
        layout.addLayout(h_layout)
        layout.addWidget(self.chat_area)
        layout.addWidget(self.progress_bar)
        layout.addWidget(self.loading_label)
        layout.addStretch()

        # Verbindungen
        self.start_server_btn.clicked.connect(self.start_server)
        self.stop_server_btn.clicked.connect(self.stop_server)

        center_widget.setLayout(layout)
        return center_widget

    def new_project(self):
        self.stacked_widget.setCurrentWidget(self.main_screen)

    def apply_styles(self):
        # Optional: Laden eines Stylesheets
        try:
            with open("style.qss", "r") as f:
                self.setStyleSheet(f.read())
        except Exception as e:
            logging.error(f"Error loading stylesheet: {e}")

    def update_llm_list(self):
        models = self.llm_client.get_models()
        logging.debug(f"Found models: {models}")
        self.llm_combo.clear()
        if models:
            self.llm_combo.addItems(models)
        else:
            logging.warning("No models found. Please ensure that models are installed in Ollama.")
            QMessageBox.warning(self, "Warning", "No models found. Please ensure that models are installed in Ollama.")

    def update_server_status(self):
        is_running = self.check_server_running()
        logging.debug(f"Server status updated: {'Running' if is_running else 'Stopped'}")
        if is_running:
            self.server_status_label.setText('Server Status: Running')
            self.server_status_indicator.setText('🟢')
            self.update_llm_list()
        else:
            self.server_status_label.setText('Server Status: Stopped')
            self.server_status_indicator.setText('🔴')

    def check_server_running(self):
        try:
            logging.debug("Checking if the server is already running.")
            response = requests.get(f'{self.llm_client.base_url}', timeout=2)
            return response.status_code == 200 and "Ollama is running" in response.text
        except requests.RequestException:
            return False

    def start_server(self):
        if self.check_server_running():
            QMessageBox.information(self, "Info", "The server is already running.")
            return
        try:
            logging.debug("Attempting to start the Ollama server...")
            ollama_command = 'ollama'  # Anpassen, falls notwendig
            if os.name == 'nt':  # Windows
                self.server_process = subprocess.Popen(
                    [ollama_command, 'serve'],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    shell=True,
                    creationflags=subprocess.CREATE_NEW_CONSOLE
                )
            else:
                self.server_process = subprocess.Popen(
                    [ollama_command, 'serve'],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE
                )
            self.chat_area.append("<b>Assistant:</b> Starting the server...")
            # Warten, bis der Server bereit ist
            for _ in range(10):  # Maximal 10 Sekunden warten
                time.sleep(1)
                if self.check_server_running():
                    self.update_server_status()
                    self.chat_area.append("<b>Assistant:</b> Server started successfully.")
                    logging.debug("Server started successfully.")
                    return
            self.chat_area.append("<b>Assistant:</b> The server could not be started within the expected time.")
            logging.error("Server did not start within the expected time.")
            self.update_server_status()
        except Exception as e:
            logging.error(f"Error starting the server: {e}")
            QMessageBox.critical(self, "Error", f"Error starting the server: {e}")

    def stop_server(self):
        if self.server_process is not None and self.server_process.poll() is None:
            try:
                logging.debug("Attempting to stop the server.")
                self.server_process.terminate()
                self.server_process.wait()
                self.server_process = None
                self.chat_area.append("<b>Assistant:</b> Server stopped successfully.")
                logging.debug("Server stopped successfully.")
                self.update_server_status()
            except Exception as e:
                logging.error(f"Error stopping the server: {e}")
                QMessageBox.critical(self, "Error", f"Error stopping the server: {e}")
        else:
            QMessageBox.information(self, "Info", "The server is not running.")

    def upload_files(self):
        options = QFileDialog.Options()
        files, _ = QFileDialog.getOpenFileNames(
            self, "Select Files", "",
            "All Files (*)", options=options
        )
        if files:
            self.uploaded_files.extend(files)
            logging.debug(f"Files uploaded: {files}")
            self.display_uploaded_files()

    def display_uploaded_files(self):
        self.file_list.clear()
        for file in self.uploaded_files:
            self.file_list.addItem(file)

    def start_analysis(self):
        self.chat_area.append("<b>Assistant:</b> Starting analysis of the application description...")
        self.loading_label.setVisible(True)
        thread = threading.Thread(target=self.perform_analysis)
        thread.start()

    def perform_analysis(self):
        description = self.description_text.toPlainText()
        selected_llm = self.llm_combo.currentText()
        if not selected_llm:
            self.chat_area.append("<b>Assistant:</b> No LLM selected. Please select an LLM.")
            logging.warning("No LLM selected.")
            self.loading_label.setVisible(False)
            return
        file_contents = self.get_file_contents()
        prompt = self.generate_plan_prompt(description, file_contents)
        logging.debug(f"Sending plan prompt to LLM '{selected_llm}'.")
        plan_text = self.llm_client.generate(selected_llm, prompt)
        self.plan_data = self.parse_plan(plan_text)
        if self.plan_data:
            self.display_plan(self.plan_data)
            self.chat_area.append("<b>Assistant:</b> Analysis completed. The plan has been created.")
            logging.debug("Plan successfully created and displayed.")
        else:
            self.chat_area.append("<b>Assistant:</b> Error during analysis.")
            logging.error("Plan data is empty after analysis.")
        self.loading_label.setVisible(False)

    def get_file_contents(self):
        content = ""
        for file_path in self.uploaded_files:
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content += f"\nContent of {file_path}:\n{f.read()}"
                logging.debug(f"Read content from file '{file_path}'.")
            except Exception as e:
                logging.error(f"Error reading file {file_path}: {e}")
        return content

    def generate_plan_prompt(self, description, file_contents):
        return f"""
As an experienced software architect, you are to create a detailed development plan based on the following application description.

**Application Description:**
{description}

**Additional Files:**
{file_contents}

**Instructions:**
- **Important:** Provide **only** the JSON output.
- Do **not** include any additional explanations, greetings, or closing remarks.
- The JSON should have the structure of tasks with Title, Description, and Goals.
- Use clear and precise language.

**Example Output:**

[
    {{
        "Title": "Task 1: XYZ",
        "Description": "Description of the task.",
        "Goals": ["Goal 1", "Goal 2"]
    }},
    ...
]
"""

    def parse_plan(self, plan_text):
        try:
            logging.debug(f"Attempting to parse plan text as JSON: {plan_text}")
            # Extrahiere JSON aus dem Text
            json_str_match = re.search(r'\{.*\}', plan_text, re.DOTALL)
            if json_str_match:
                json_str = json_str_match.group(0)
                plan = json.loads(json_str)
                # Wenn es ein einzelnes Objekt ist, in eine Liste einbetten
                if isinstance(plan, dict):
                    plan = [plan]
            else:
                # Versuche, den gesamten Text als JSON zu parsen
                plan = json.loads(plan_text)
                if isinstance(plan, dict):
                    plan = [plan]
            return plan
        except json.JSONDecodeError as e:
            logging.error(f"Error parsing the plan: {e}")
            return []


    def display_plan(self, plan):
        self.plan_tree.clear()
        for i, task in enumerate(plan):
            task_item = QTreeWidgetItem([task['Title']])
            task_item.setData(0, Qt.UserRole, (i, None))  # Speichere Index zur Identifikation
            task_item.setBackground(0, QColor('#444'))  # Dunkler Hintergrund für Dark Mode

            description_item = QTreeWidgetItem(['Description', task['Description']])
            task_item.addChild(description_item)

            goals_item = QTreeWidgetItem(['Goals'])
            for j, goal in enumerate(task['Goals']):
                goal_item = QTreeWidgetItem([goal])
                goal_item.setData(0, Qt.UserRole, (i, j))
                goal_item.setBackground(0, QColor('#555'))
                goals_item.addChild(goal_item)
            task_item.addChild(goals_item)

            self.plan_tree.addTopLevelItem(task_item)
        logging.debug("Plan displayed in planning tab.")

    def edit_plan(self):
        selected_item = self.plan_tree.currentItem()
        if selected_item:
            if selected_item.parent():
                # Bearbeitet Ziele oder Beschreibungen
                text, ok = QInputDialog.getText(self, 'Edit', 'Enter new text:', QLineEdit.Normal, selected_item.text(0))
                if ok and text:
                    selected_item.setText(0, text)
            else:
                # Bearbeitet Aufgaben
                text, ok = QInputDialog.getText(self, 'Edit', 'Enter new task title:', QLineEdit.Normal, selected_item.text(0))
                if ok and text:
                    selected_item.setText(0, text)
        else:
            QMessageBox.warning(self, 'Warning', 'Please select an item to edit.')

    def start_processing(self):
        if not self.plan_data:
            QMessageBox.warning(self, "Warning", "Please create a plan first.")
            logging.warning("No plan available when starting the process.")
            return
        if not self.llm_combo.currentText():
            QMessageBox.warning(self, "Warning", "Please select an LLM.")
            logging.warning("No LLM selected when starting the process.")
            return
        self.loading_label.setVisible(True)
        self.chat_area.append("<b>Assistant:</b> Starting the processing of tasks.")
        self.task_processor = TaskProcessor(
            tasks=self.plan_data,
            llm_client=self.llm_client,
            llm_model=self.llm_combo.currentText()
        )
        self.task_processor.progress_updated.connect(self.update_progress)
        self.task_processor.task_message.connect(self.append_chat_message)
        self.task_processor.task_result.connect(self.append_chat_message)
        self.task_processor.task_status.connect(self.update_task_status)
        self.task_processor.finished.connect(self.processing_finished)
        self.task_processor.start()
        logging.debug("TaskProcessor started.")

    def processing_finished(self):
        self.loading_label.setVisible(False)

    def append_chat_message(self, message):
        self.chat_area.append(f"<b>{message}</b>")
        logging.debug(f"Chat message added: {message}")

    def update_progress(self, value):
        self.progress_bar.setValue(value)
        self.status_bar.showMessage(f'Progress: {value}%')
        logging.debug(f"Progress bar updated: {value}%")

    def update_task_status(self, task_index, goal_index, status):
        if goal_index == -1:
            # Aktualisiere Aufgabe
            task_item = self.plan_tree.topLevelItem(task_index)
            color = self.get_status_color(status)
            task_item.setBackground(0, color)
        else:
            # Aktualisiere Ziel
            task_item = self.plan_tree.topLevelItem(task_index)
            goals_item = task_item.child(1)  # Annahme: Ziele sind das zweite Kind
            goal_item = goals_item.child(goal_index)
            color = self.get_status_color(status)
            goal_item.setBackground(0, color)

    def get_status_color(self, status):
        if status == 'processing':
            return QColor('#FFD700')  # Gold
        elif status == 'completed':
            return QColor('#32CD32')  # Lime Green
        elif status == 'error':
            return QColor('#FF4500')  # Orange Red
        elif status == 'failed':
            return QColor('#DC143C')  # Crimson
        else:
            return QColor('#555')  # Standard dunkel

    def pause_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.pause()
            self.chat_area.append("<b>System:</b> The process has been paused.")
            logging.debug("Process paused.")

    def resume_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.resume()
            self.chat_area.append("<b>System:</b> The process will continue.")
            logging.debug("Process resumed.")

    def stop_processing(self):
        if hasattr(self, 'task_processor'):
            self.task_processor.stop()
            self.chat_area.append("<b>System:</b> The process has been stopped.")
            self.progress_bar.setValue(0)
            self.loading_label.setVisible(False)
            logging.debug("Process stopped.")

    def save_project(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(self, "Save Project", "", "JSON Files (*.json)", options=options)
        if file_name:
            project_data = {
                'description': self.description_text.toPlainText(),
                'uploaded_files': self.uploaded_files,
                'plan_data': self.plan_data
            }
            try:
                with open(file_name, 'w', encoding='utf-8') as f:
                    json.dump(project_data, f, ensure_ascii=False, indent=4)
                QMessageBox.information(self, "Success", "Project successfully saved.")
                logging.debug(f"Project saved: {file_name}")
            except Exception as e:
                logging.error(f"Error saving project: {e}")
                QMessageBox.critical(self, "Error", f"Error saving project: {e}")

    def load_project(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(self, "Load Project", "", "JSON Files (*.json)", options=options)
        if file_name:
            try:
                with open(file_name, 'r', encoding='utf-8') as f:
                    project_data = json.load(f)
                self.description_text.setPlainText(project_data.get('description', ''))
                self.uploaded_files = project_data.get('uploaded_files', [])
                self.display_uploaded_files()
                self.plan_data = project_data.get('plan_data', [])
                if self.plan_data:
                    self.display_plan(self.plan_data)
                QMessageBox.information(self, "Success", "Project successfully loaded.")
                logging.debug(f"Project loaded: {file_name}")
                self.stacked_widget.setCurrentWidget(self.main_screen)
            except Exception as e:
                logging.error(f"Error loading project: {e}")
                QMessageBox.critical(self, "Error", f"Error loading project: {e}")

    def show_about_dialog(self):
        QMessageBox.about(self, "About", "Intelligent Assistant for Developers\nVersion 2.2")

    def create_menu(self):
        menu_bar = self.menuBar()

        # File menu
        file_menu = menu_bar.addMenu('File')
        save_action = QAction('Save', self)
        save_action.triggered.connect(self.save_project)
        file_menu.addAction(save_action)
        load_action = QAction('Load', self)
        load_action.triggered.connect(self.load_project)
        file_menu.addAction(load_action)
        exit_action = QAction('Exit', self)
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # Help menu
        help_menu = menu_bar.addMenu('Help')
        about_action = QAction('About', self)
        about_action.triggered.connect(self.show_about_dialog)
        help_menu.addAction(about_action)

    def create_toolbar(self):
        tool_bar = QToolBar('Tools')
        self.addToolBar(Qt.TopToolBarArea, tool_bar)

        start_action = QAction(QIcon("icons/start.png"), 'Start', self)
        start_action.triggered.connect(self.start_processing)
        tool_bar.addAction(start_action)

        pause_action = QAction(QIcon("icons/pause.png"), 'Pause', self)
        pause_action.triggered.connect(self.pause_processing)
        tool_bar.addAction(pause_action)

        resume_action = QAction(QIcon("icons/resume.png"), 'Resume', self)
        resume_action.triggered.connect(self.resume_processing)
        tool_bar.addAction(resume_action)

        stop_action = QAction(QIcon("icons/stop.png"), 'Stop', self)
        stop_action.triggered.connect(self.stop_processing)
        tool_bar.addAction(stop_action)

    def closeEvent(self, event):
        # Stoppe den Serverprozess, falls er gestartet wurde
        if self.server_process is not None and self.server_process.poll() is None:
            try:
                logging.debug("Attempting to stop the server when closing the application.")
                self.server_process.terminate()
                self.server_process.wait()
                self.server_process = None
                logging.info("Server process terminated on application exit.")
            except Exception as e:
                logging.error(f"Error stopping the server on exit: {e}")
        event.accept()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    logging.debug("Application started.")
    sys.exit(app.exec_())