prüfe diesen code und korrigiere alle probleme und fehler, wie zb die folgenden im log (zb nutzt ollama port 11434). Nenne mir abschließend das vollständige korrigierte skript der anwendung mit dem gesamten code.

Log:

(.venv) D:\_Projekte\_MyCodes\GitHub\SoftwareDeveloper_o1>d:/_Projekte/_MyCodes/GitHub/SoftwareDeveloper_o1/.venv/Scripts/python.exe d:/_Projekte/_MyCodes/GitHub/SoftwareDeveloper_o1/SoftwareDeveloper_o1_Compact.py
QLayout: Cannot add a null widget to QVBoxLayout/
2024-10-15 05:09:48,587 - ERROR - Generation error: 400 - {"error":"model is required"}
2024-10-15 05:10:15,771 - ERROR - LLM communication error: HTTPConnectionPool(host='localhost', port=80): Max retries exceeded with url: /api/generate (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x0000020004E84FA0>: Failed to establish a new connection: [WinError 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte'))


Skript:

import sys
import threading
import requests
import json
import logging
import subprocess
import os
import re
import shutil
from datetime import datetime, timedelta
from logging.handlers import RotatingFileHandler
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QPushButton, QAction, QHBoxLayout, QVBoxLayout,
    QWidget, QTextEdit, QFileDialog, QSplitter, QTreeWidget, QTreeWidgetItem, QProgressBar,
    QListWidget, QStatusBar, QMessageBox, QInputDialog, QLineEdit, QGroupBox, QFormLayout,
    QDoubleSpinBox, QSpinBox, QStackedWidget, QToolBar, QStyle, QSizePolicy, QComboBox,
    QScrollArea, QFrame, QPlainTextEdit, QMenu, QSpacerItem, QDialog, QDialogButtonBox,
    QCheckBox, QColorDialog, QFontDialog, QSlider, QListView
)
from PyQt5.QtCore import (
    QTimer, Qt, QThread, pyqtSignal, QSize, QPropertyAnimation, QRect,
    QObject, pyqtSlot, QEvent, QPoint, QEasingCurve
)
from PyQt5.QtGui import (
    QColor, QMovie, QIcon, QPixmap, QFont, QClipboard, QPainter, QBrush,
    QPen, QPalette
)

# Logging configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
logger = logging.getLogger()
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler = RotatingFileHandler(os.path.join(BASE_DIR, "app.log"), maxBytes=5*1024*1024, backupCount=3, encoding='utf-8')
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setFormatter(formatter)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)

class LLMClient:
    def __init__(self, base_url='http://localhost:11434'):
        self.base_url = base_url
        self.cache = {}
        self.memory = {}

    def get_models(self):
        try:
            logging.debug("Requesting available models.")
            response = requests.get(f'{self.base_url}/api/tags', timeout=5)
            if response.status_code == 200:
                models = [model['name'] for model in response.json().get('models', [])]
                logging.debug(f"Available models: {models}")
                return models
            logging.error(f"Error fetching models: {response.status_code} - {response.text}")
        except requests.RequestException as e:
            logging.error(f"Error fetching models: {e}")
        return []

    def generate(self, model, prompt, temperature=0.7, max_tokens=150):
        cache_key = (model, prompt, temperature, max_tokens)
        if cache_key in self.cache:
            logging.debug(f"Using cached result for model '{model}'.")
            return self.cache[cache_key]
        payload = {"model": model, "prompt": prompt, "temperature": temperature, "max_tokens": max_tokens, "format": "json", "stream": False}
        try:
            logging.debug("Sending generation request.")
            response = requests.post(f'{self.base_url}/api/generate', json=payload, headers={"Content-Type": "application/json"}, timeout=60)
            if response.status_code == 200:
                result = response.json().get('response', '').strip()
                self.cache[cache_key] = result
                logging.debug("Generation successful.")
                return result
            logging.error(f"Generation error: {response.status_code} - {response.text}")
            return f"Error: {response.text}"
        except requests.RequestException as e:
            logging.error(f"LLM communication error: {e}")
            return f"Error: {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)

    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.max_retries = 3
        self.context_memory = {}

    def run(self):
        total_goals = sum(len(task.get('Goals', [])) for task in self.tasks)
        completed_goals = 0
        for i, task in enumerate(self.tasks):
            if self._stopped:
                break
            while self._paused:
                self.msleep(100)
            task_title = task.get('Title', f'Task {i+1}')
            self.task_message.emit(f"Assistant: Starting task '{task_title}'")
            self.task_status.emit(i, -1, 'processing')
            for j, goal in enumerate(task.get('Goals', [])):
                if self._stopped:
                    break
                while self._paused:
                    self.msleep(100)
                retry, success = 0, False
                while retry < self.max_retries and not success and not self._stopped:
                    if retry > 0:
                        self.task_message.emit(f"Assistant: Retry {retry} for goal: {goal}")
                        logging.debug(f"Retry {retry} for goal: {goal}")
                    self.task_message.emit(f"Assistant: Processing goal: {goal}")
                    self.task_status.emit(i, j, 'processing')
                    result = self.process_goal(task, goal, retry > 0)
                    success = self.self_review(goal, result)
                    if success:
                        self.task_result.emit(f"Result for '{goal}':\n{result}")
                        self.context_memory[goal] = result
                        self.task_status.emit(i, j, 'completed')
                    else:
                        self.task_status.emit(i, j, 'error')
                        retry += 1
                if not success:
                    self.task_message.emit(f"Assistant: Failed '{goal}' after {self.max_retries} attempts.")
                    self.task_status.emit(i, j, 'failed')
                completed_goals += 1
                self.progress_updated.emit(int((completed_goals / total_goals) * 100))
            self.task_status.emit(i, -1, 'completed')
            summary = self.create_task_summary(task)
            self.task_result.emit(f"Summary for '{task_title}':\n{summary}")
            self.context_memory[task_title] = summary
        if not self._stopped:
            self.task_message.emit("Assistant: All tasks completed.")
            final = self.generate_final_result()
            self.task_result.emit(f"Final Result:\n{final}")

    def process_goal(self, task, goal, retry=False):
        context = self.get_relevant_context(goal)
        prompt = f"""
As an experienced software architect, create a detailed solution for the goal: {goal}

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

**Context:**
{context}

Provide clear language and code examples where appropriate.
"""
        logging.debug(f"Generating for '{goal}'.")
        return self.llm_client.generate(self.llm_model, prompt)

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

{result}

Is it complete and correct? Answer "Yes" or "No".
"""
        review = self.llm_client.generate(self.llm_model, prompt)
        if "Yes" in review:
            return True
        self.task_message.emit(f"Assistant: Review failed for '{goal}': {review}")
        return False

    def get_relevant_context(self, current_goal):
        return "\n".join(f"**{k}:**\n{v}" for k, v in self.context_memory.items())

    def create_task_summary(self, task):
        return "\n".join(f"{g}:\n{self.context_memory.get(g, '')}" for g in task.get('Goals', []))

    def generate_final_result(self):
        context = "\n".join(self.context_memory.values())
        prompt = f"""
Summarize all results and generate the complete application code with documentation.

**Context:**
{context}
"""
        logging.debug("Generating final result.")
        return self.llm_client.generate(self.llm_model, prompt)

    def pause(self):
        self._paused = True
        logging.debug("TaskProcessor paused.")

    def resume(self):
        self._paused = False
        logging.debug("TaskProcessor resumed.")

    def stop(self):
        self._stopped = True
        logging.debug("TaskProcessor stopped.")

class ToggleButton(QPushButton):
    def __init__(self, icon_unicode, parent=None):
        super().__init__(icon_unicode, parent)
        self.setFont(QFont('Arial', 16))
        self.setFixedSize(QSize(35, 35))
        self.setStyleSheet("""
            QPushButton {
                background-color: transparent;
                border: none;
                color: #fff;
            }
            QPushButton:hover {
                background-color: #555;
                border-radius: 4px;
            }
        """)

class MessageWidget(QFrame):
    def __init__(self, sender, message, parent=None):
        super().__init__(parent)
        self.sender, self.message = sender, message
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
        self.setLineWidth(0)
        color = "#2e2e2e" if self.sender.lower() == "assistant" else "#0078d7"
        self.setStyleSheet(f"""
            QFrame {{
                background-color: {color};
                border-radius: 10px;
                padding: 10px;
            }}
        """)
        sender_label = QLabel(f"<b>{self.sender}:</b>")
        sender_label.setStyleSheet("color: #fff;")
        layout.addWidget(sender_label)
        for part in self.parse_message(self.message):
            if part['type'] == 'text':
                lbl = QLabel(part['content'])
                lbl.setStyleSheet("color: #dcdcdc;")
                lbl.setWordWrap(True)
                lbl.setFont(QFont("Arial", 12))
                layout.addWidget(lbl)
            elif part['type'] == 'code':
                code = QPlainTextEdit(part['content'])
                code.setReadOnly(True)
                code.setStyleSheet("""
                    QPlainTextEdit {
                        background-color: #1e1e1e;
                        color: #f8f8f2;
                        border: 1px solid #555;
                        border-radius: 4px;
                        font-family: Consolas, monospace;
                        padding: 5px;
                        font-size: 12pt;
                    }
                """)
                layout.addWidget(code)

    def parse_message(self, msg):
        pattern = re.compile(r'```(.*?)```', re.DOTALL)
        parts, last = [], 0
        for m in pattern.finditer(msg):
            parts.append({'type': 'text', 'content': msg[last:m.start()]})
            parts.append({'type': 'code', 'content': m.group(1).strip()})
            last = m.end()
        if last < len(msg):
            parts.append({'type': 'text', 'content': msg[last:]})
        return parts

class SettingsDialog(QDialog):
    def __init__(self, current_settings, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Settings")
        self.setModal(True)
        self.settings = current_settings.copy()
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        tabs_layout = QVBoxLayout()

        # LLM Settings
        llm_group = QGroupBox("LLM Settings")
        llm_layout = QFormLayout()
        self.llm_combo = QComboBox()
        self.llm_combo.addItems(self.settings.get('available_models', []))
        self.llm_combo.setCurrentText(self.settings.get('selected_model', ''))
        self.temperature_spin = QDoubleSpinBox()
        self.temperature_spin.setRange(0.0,1.0)
        self.temperature_spin.setDecimals(2)
        self.temperature_spin.setSingleStep(0.05)
        self.temperature_spin.setValue(self.settings.get('temperature', 0.7))
        self.max_tokens_spin = QSpinBox()
        self.max_tokens_spin.setRange(100, 10000)
        self.max_tokens_spin.setValue(self.settings.get('max_tokens', 150))
        llm_layout.addRow("Model:", self.llm_combo)
        llm_layout.addRow("Temperature:", self.temperature_spin)
        llm_layout.addRow("Max Tokens:", self.max_tokens_spin)
        llm_group.setLayout(llm_layout)
        tabs_layout.addWidget(llm_group)

        # Server Settings
        server_group = QGroupBox("Server Settings")
        server_layout = QFormLayout()
        self.server_url_input = QLineEdit(self.settings.get('server_url', 'http://localhost'))
        self.server_port_input = QSpinBox()
        self.server_port_input.setRange(1,65535)
        self.server_port_input.setValue(self.settings.get('server_port', 11434))
        server_layout.addRow("Server URL:", self.server_url_input)
        server_layout.addRow("Server Port:", self.server_port_input)
        server_group.setLayout(server_layout)
        tabs_layout.addWidget(server_group)

        # UI Settings
        ui_group = QGroupBox("UI Settings")
        ui_layout = QFormLayout()
        self.theme_combo = QComboBox()
        self.theme_combo.addItems(["Dark", "Light"])
        self.theme_combo.setCurrentText(self.settings.get('theme', 'Dark'))
        self.font_button = QPushButton("Choose Font")
        self.font_button.clicked.connect(self.choose_font)
        self.selected_font = self.settings.get('font', QFont("Arial", 10))
        status = 'Bold' if self.selected_font.bold() else 'Regular'
        self.font_display = QLabel(f"{self.selected_font.family()}, {self.selected_font.pointSize()}pt, {status}")
        ui_layout.addRow("Theme:", self.theme_combo)
        ui_layout.addRow("Font:", self.font_button)
        ui_layout.addRow("", self.font_display)
        ui_group.setLayout(ui_layout)
        tabs_layout.addWidget(ui_group)

        layout.addLayout(tabs_layout)
        buttons = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.save)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)

    def choose_font(self):
        font, ok = QFontDialog.getFont(self.selected_font, self, "Choose Font")
        if ok:
            self.selected_font = font
            status = 'Bold' if font.bold() else 'Regular'
            self.font_display.setText(f"{font.family()}, {font.pointSize()}pt, {status}")

    def save(self):
        self.settings.update({
            'selected_model': self.llm_combo.currentText(),
            'temperature': self.temperature_spin.value(),
            'max_tokens': self.max_tokens_spin.value(),
            'server_url': self.server_url_input.text(),
            'server_port': self.server_port_input.value(),
            'theme': self.theme_combo.currentText(),
            'font': self.selected_font
        })
        self.accept()

class ChatInputWidget(QWidget):
    send_message = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QHBoxLayout(self)
        self.input_field = QTextEdit()
        self.input_field.setFixedHeight(60)
        self.input_field.setStyleSheet("""
            QTextEdit {
                background-color: #2b2b2b;
                color: #dcdcdc;
                border: 1px solid #555;
                border-radius: 4px;
                padding: 5px;
                font-size: 12pt;
            }
        """)
        self.input_field.setPlaceholderText("Type your message here...")
        self.send_button = QPushButton("Send")
        self.send_button.setStyleSheet("""
            QPushButton {
                background-color: #5cb85c;
                color: #fff;
                border: none;
                border-radius: 4px;
                padding: 15px 25px;
                font-size: 12pt;
            }
            QPushButton:hover {
                background-color: #4cae4c;
            }
        """)
        self.send_button.clicked.connect(self.emit_message)
        layout.addWidget(self.input_field)
        layout.addWidget(self.send_button)
        layout.setContentsMargins(0,0,0,0)
        self.setLayout(layout)

    def emit_message(self):
        message = self.input_field.toPlainText().strip()
        if message:
            self.send_message.emit(message)
            self.input_field.clear()

class GanttChartWidget(QWidget):
    def __init__(self, plan_data, parent=None):
        super().__init__(parent)
        self.plan_data = plan_data
        layout = QVBoxLayout(self)
        label = QLabel("Development Plan Timeline")
        label.setStyleSheet("font-size: 16pt; font-weight: bold; color: #fff;")
        layout.addWidget(label)
        try:
            from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
            from matplotlib.figure import Figure
            self.figure = Figure(figsize=(8,6))
            self.canvas = FigureCanvas(self.figure)
            layout.addWidget(self.canvas)
            self.draw_gantt_chart()
        except ImportError:
            logging.error("Matplotlib not installed.")
            error_label = QLabel("Matplotlib is not installed. Gantt chart cannot be displayed.")
            error_label.setStyleSheet("color: red;")
            layout.addWidget(error_label)
        self.setLayout(layout)

    def update_chart(self, plan_data):
        self.plan_data = plan_data
        self.draw_gantt_chart()

    def draw_gantt_chart(self):
        try:
            from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
            from matplotlib.figure import Figure
            self.figure.clf()
            ax = self.figure.add_subplot(111)
            if not self.plan_data:
                ax.text(0.5, 0.5, 'No Data to Display', ha='center', va='center', transform=ax.transAxes, color='white')
            else:
                start_date = datetime.now()
                durations = {task['Title']: max(len(task.get('Goals', [])),1) for task in self.plan_data}
                tasks, dur = list(durations.keys()), list(durations.values())
                starts = [start_date + timedelta(days=sum(dur[:i])) for i in range(len(dur))]
                for i, task in enumerate(tasks):
                    ax.barh(y=i, width=dur[i], left=starts[i], height=0.4, color='#5bc0de')
                ax.set_yticks(range(len(tasks)))
                ax.set_yticklabels(tasks, color='white', fontsize=10)
                ax.xaxis_date()
                ax.set_xlabel('Date', color='white')
                ax.set_title('Gantt Chart', color='white')
                ax.tick_params(axis='x', colors='white')
                ax.tick_params(axis='y', colors='white')
                ax.set_facecolor('#1e1e1e')
                self.figure.autofmt_xdate()
            self.canvas.draw()
        except Exception as e:
            logging.error(f"Error drawing Gantt chart: {e}")

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Assistant for Developers')
        self.setGeometry(100, 100, 1800, 1000)
        self.llm_client = LLMClient()
        self.plan_data, self.uploaded_files = [], []
        self.server_process = None
        self.settings = {
            'available_models': [],
            'selected_model': '',
            'temperature': 0.7,
            'max_tokens': 150,
            'server_url': 'http://localhost',
            'server_port': 11434,
            'theme': 'Dark',
            'font': QFont("Arial", 10)
        }
        self.init_ui()
        self.apply_styles()
        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_sidebar()
        self.create_main_content()
        self.stacked_widget.setCurrentWidget(self.main_screen)

    def create_sidebar(self):
        self.sidebar = QFrame()
        self.sidebar.setFixedWidth(200)
        self.sidebar.setStyleSheet("""
            QFrame { background-color: #252525; }
            QPushButton { color: #fff; background-color: #252525; border: none; padding: 15px; text-align: left; font-size: 14pt; }
            QPushButton:hover, QPushButton:checked { background-color: #1e1e1e; }
        """)
        layout = QVBoxLayout(self.sidebar)
        nav_items = [("Chat", "💬"), ("Development Plan", "📅"), ("Files", "📁"), ("Settings", "⚙️")]
        self.nav_buttons = {}
        for name, icon in nav_items:
            btn = QPushButton(f"{icon} {name}")
            btn.setCheckable(True)
            btn.clicked.connect(lambda checked, n=name: self.switch_page(n))
            layout.addWidget(btn)
            self.nav_buttons[name] = btn
        layout.addStretch()
        self.stacked_widget.addWidget(self.sidebar)

    def create_main_content(self):
        self.main_screen = QWidget()
        main_layout = QHBoxLayout(self.main_screen)
        self.content_stack = QStackedWidget()
        self.content_stack.addWidget(self.create_chat_page())
        self.content_stack.addWidget(self.create_plan_page())
        self.content_stack.addWidget(self.create_files_page())
        self.content_stack.addWidget(self.create_settings_page())
        main_layout.addWidget(self.content_stack)
        self.stacked_widget.addWidget(self.main_screen)

    def switch_page(self, name):
        page_map = {
            "Chat": self.content_stack.widget(0),
            "Development Plan": self.content_stack.widget(1),
            "Files": self.content_stack.widget(2),
            "Settings": self.content_stack.widget(3)
        }
        self.content_stack.setCurrentWidget(page_map.get(name, self.content_stack.widget(0)))
        for key, btn in self.nav_buttons.items():
            btn.setChecked(key == name)

    def create_chat_page(self):
        chat_widget = QWidget()
        layout = QVBoxLayout()
        top_layout = QHBoxLayout()

        # Label für die Auswahl des LLM
        llm_label = QLabel('Select LLM:')
        llm_label.setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;")
        top_layout.addWidget(llm_label)

        # LLM ComboBox
        self.llm_combo = QComboBox()
        self.llm_combo.setStyleSheet("""
            QComboBox { background-color: #2b2b2b; color: #dcdcdc; border: 1px solid #555; border-radius: 4px; padding: 5px; font-size: 12pt; }
            QComboBox::drop-down { border: none; }
            QComboBox::down-arrow { image: url("icons/dropdown.png"); }
        """)
        top_layout.addWidget(self.llm_combo)
        top_layout.addStretch()

        # Server Status Label und Indikator
        self.server_status_label = QLabel('Server Status:')
        self.server_status_label.setStyleSheet("color: #fff; font-size: 12pt;")
        top_layout.addWidget(self.server_status_label)

        self.server_status_indicator = QLabel('⚪')
        self.server_status_indicator.setStyleSheet("color: #fff; font-size: 16pt;")
        top_layout.addWidget(self.server_status_indicator)

        # Server Steuerungs Buttons
        self.start_server_btn = QPushButton('Start Server')
        self.stop_server_btn = QPushButton('Stop Server')
        for btn, color in [(self.start_server_btn, "#5bc0de"), (self.stop_server_btn, "#d9534f")]:
            btn.setStyleSheet(f"""
                QPushButton {{
                    background-color: {color};
                    color: #fff;
                    border: none;
                    border-radius: 5px;
                    padding: 8px;
                    font-size: 12pt;
                    font-weight: bold;
                }}
                QPushButton:hover {{
                    background-color: {"#31b0d5" if color == "#5bc0de" else "#c9302c"};
                }}
            """)
        self.start_server_btn.setToolTip("Start the LLM server.")
        self.stop_server_btn.setToolTip("Stop the LLM server.")
        top_layout.addWidget(self.start_server_btn)
        top_layout.addWidget(self.stop_server_btn)

        # Chat Area
        self.chat_area = QScrollArea()
        self.chat_area.setStyleSheet("""
            QScrollArea { background-color: #1e1e1e; border: none; }
            QScrollBar:vertical { background-color: #2b2b2b; width: 12px; }
            QScrollBar::handle:vertical { background-color: #555; min-height: 20px; border-radius: 6px; }
            QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }
            QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { background: none; }
        """)
        self.chat_container = QWidget()
        self.chat_layout = QVBoxLayout(self.chat_container)
        self.chat_layout.setAlignment(Qt.AlignTop)
        self.chat_area.setWidgetResizable(True)
        self.chat_area.setWidget(self.chat_container)

        # Progress Bar
        self.progress_bar = QProgressBar()
        self.progress_bar.setValue(0)
        self.progress_bar.setAlignment(Qt.AlignCenter)
        self.progress_bar.setStyleSheet("""
            QProgressBar {
                border: 1px solid #555;
                border-radius: 5px;
                text-align: center;
                height: 20px;
                background-color: #2b2b2b;
                color: #fff;
                font-size: 12pt;
            }
            QProgressBar::chunk { background-color: #5bc0de; width: 20px; }
        """)

        # Loading Animation
        self.loading_label = QLabel()
        self.loading_label.setAlignment(Qt.AlignCenter)
        loading_gif = os.path.join(BASE_DIR, "loading.gif")
        if os.path.exists(loading_gif):
            self.loading_movie = QMovie(loading_gif)
            if self.loading_movie.isValid():
                self.loading_label.setMovie(self.loading_movie)
                self.loading_movie.start()
            else:
                self.loading_label.setText("Loading...")
        else:
            self.loading_label.setText("Loading...")
        self.loading_label.setVisible(False)

        # Chat Input
        self.chat_input = ChatInputWidget()
        self.chat_input.send_message.connect(self.handle_user_message)

        # Füge alle Widgets zum Layout hinzu
        layout.addLayout(top_layout)
        layout.addWidget(self.chat_area, stretch=1)
        layout.addWidget(self.chat_input)
        layout.addWidget(self.progress_bar)
        layout.addWidget(self.loading_label)
        layout.addStretch()
        chat_widget.setLayout(layout)

        # Verbinde Server Buttons
        self.start_server_btn.clicked.connect(self.start_server)
        self.stop_server_btn.clicked.connect(self.stop_server)

        return chat_widget

    def create_plan_page(self):
        plan_widget = QWidget()
        layout = QVBoxLayout()
        self.plan_tree = QTreeWidget()
        self.plan_tree.setHeaderLabels(['Task', 'Description/Goals'])
        self.plan_tree.setColumnWidth(0, 300)
        self.plan_tree.setStyleSheet("""
            QTreeWidget { background-color: #2b2b2b; color: #dcdcdc; border: 1px solid #555; border-radius: 4px; font-size: 12pt; }
            QTreeWidget::item:selected { background-color: #5bc0de; color: #fff; }
        """)

        # Buttons
        self.analyse_btn = QPushButton('Start Analysis')
        self.start_processing_btn = QPushButton('Start Processing')
        self.edit_plan_btn = QPushButton('Edit Plan')
        for btn, color in [(self.analyse_btn, "#f0ad4e"), (self.start_processing_btn, "#5cb85c"), (self.edit_plan_btn, "#5bc0de")]:
            btn.setStyleSheet(f"""
                QPushButton {{
                    background-color: {color};
                    color: #fff;
                    border: none;
                    border-radius: 5px;
                    padding: 10px;
                    font-size: 12pt;
                    font-weight: bold;
                }}
                QPushButton:hover {{
                    background-color: {"#ec971f" if color == "#f0ad4e" else "#4cae4c" if color == "#5cb85c" else "#31b0d5"};
                }}
            """)
        self.analyse_btn.setToolTip("Analyze the application description and create a development plan.")
        self.start_processing_btn.setToolTip("Start processing the development plan.")
        self.edit_plan_btn.setToolTip("Edit the current development plan.")
        button_layout = QHBoxLayout()
        button_layout.addWidget(self.analyse_btn)
        button_layout.addWidget(self.start_processing_btn)
        button_layout.addWidget(self.edit_plan_btn)

        # Gantt Chart
        self.gantt_chart = GanttChartWidget(self.plan_data)
        self.gantt_chart.hide()
        self.toggle_gantt_btn = QPushButton("Toggle Gantt Chart")
        self.toggle_gantt_btn.setStyleSheet("""
            QPushButton {
                background-color: #5bc0de;
                color: #fff;
                border: none;
                border-radius: 5px;
                padding: 8px;
                font-size: 12pt;
                font-weight: bold;
            }
            QPushButton:hover { background-color: #31b0d5; }
        """)
        self.toggle_gantt_btn.clicked.connect(self.toggle_gantt_chart)

        # Verbinde Buttons
        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)
        self.start_processing_btn.setEnabled(False)
        self.edit_plan_btn.setEnabled(False)

        # Füge alles zum Layout hinzu
        layout.addLayout(button_layout)
        layout.addWidget(self.plan_tree)
        layout.addWidget(self.toggle_gantt_btn)
        layout.addWidget(self.gantt_chart)
        layout.addStretch()
        plan_widget.setLayout(layout)
        return plan_widget

    def create_files_page(self):
        files_widget = QWidget()
        layout = QVBoxLayout()

        # Application Description Label
        self.description_label = QLabel('Application Description:')
        self.description_label.setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;")
        layout.addWidget(self.description_label)

        # Description TextEdit
        self.description_text = QTextEdit()
        self.description_text.setPlaceholderText("Enter the application description here...")
        self.description_text.setStyleSheet("""
            QTextEdit {
                background-color: #2b2b2b;
                color: #dcdcdc;
                border: 1px solid #555;
                border-radius: 4px;
                padding: 5px;
                font-size: 12pt;
            }
        """)
        layout.addWidget(self.description_text)

        # Upload Files Button
        self.upload_btn = QPushButton('Upload Files')
        self.upload_btn.setStyleSheet("""
            QPushButton {
                background-color: #5bc0de;
                color: #fff;
                border: none;
                border-radius: 5px;
                padding: 10px;
                font-size: 12pt;
                font-weight: bold;
            }
            QPushButton:hover { background-color: #31b0d5; }
        """)
        self.upload_btn.clicked.connect(self.upload_files)
        layout.addWidget(self.upload_btn)

        # Uploaded Files List
        layout.addWidget(QLabel('Uploaded Files:').setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;"))
        self.file_list = QListWidget()
        self.file_list.setSelectionMode(QListWidget.ExtendedSelection)
        self.file_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.file_list.customContextMenuRequested.connect(self.show_file_context_menu)
        self.file_list.setStyleSheet("""
            QListWidget {
                background-color: #2b2b2b;
                color: #dcdcdc;
                border: 1px solid #555;
                border-radius: 4px;
                padding: 5px;
                font-size: 12pt;
            }
            QListWidget::item:selected { background-color: #5bc0de; color: #fff; }
        """)
        self.file_list.setAcceptDrops(True)
        self.file_list.dragEnterEvent = self.drag_enter_event
        self.file_list.dropEvent = self.drop_event
        layout.addWidget(self.file_list)

        # Manage Files Button
        self.manage_files_btn = QPushButton("Manage Files")
        self.manage_files_btn.setStyleSheet("""
            QPushButton {
                background-color: #f0ad4e;
                color: #fff;
                border: none;
                border-radius: 5px;
                padding: 10px;
                font-size: 12pt;
                font-weight: bold;
            }
            QPushButton:hover { background-color: #ec971f; }
        """)
        self.manage_files_btn.clicked.connect(self.remove_selected_files)
        self.manage_files_btn.setToolTip("Manage and upload project files.")
        layout.addWidget(self.manage_files_btn)

        layout.addStretch()
        files_widget.setLayout(layout)
        return files_widget

    def create_settings_page(self):
        settings_widget = QWidget()
        layout = QVBoxLayout()
        settings_group = QGroupBox('Settings')
        settings_layout = QFormLayout()

        # Token Limit SpinBox
        self.token_limit_spin = QSpinBox()
        self.token_limit_spin.setRange(100,10000)
        self.token_limit_spin.setValue(self.settings.get('max_tokens', 150))
        settings_layout.addRow('Max Tokens:', self.token_limit_spin)

        settings_group.setLayout(settings_layout)
        layout.addWidget(settings_group)

        # Save Settings Button
        self.save_settings_btn = QPushButton("Save Settings")
        self.save_settings_btn.setStyleSheet("""
            QPushButton {
                background-color: #5cb85c;
                color: #fff;
                border: none;
                border-radius: 5px;
                padding: 10px;
                font-size: 12pt;
                font-weight: bold;
            }
            QPushButton:hover { background-color: #4cae4c; }
        """)
        self.save_settings_btn.clicked.connect(self.save_settings)
        layout.addWidget(self.save_settings_btn)

        layout.addStretch()
        settings_widget.setLayout(layout)
        return settings_widget

    def toggle_gantt_chart(self):
        current_page = self.content_stack.currentWidget()
        if isinstance(current_page, QWidget) and hasattr(current_page, 'gantt_chart'):
            if current_page.gantt_chart.isVisible():
                current_page.gantt_chart.hide()
            else:
                current_page.gantt_chart.update_chart(self.plan_data)
                current_page.gantt_chart.show()

    def create_menu(self):
        menu_bar = self.menuBar()
        file_menu = menu_bar.addMenu('File')
        for name, shortcut, action in [('Save Project', 'Ctrl+S', self.save_project),
                                       ('Load Project', 'Ctrl+O', self.load_project),
                                       ('Exit', 'Ctrl+Q', self.close)]:
            act = QAction(name, self)
            act.setShortcut(shortcut)
            act.triggered.connect(action)
            file_menu.addAction(act)
        edit_menu = menu_bar.addMenu('Edit')
        for name, shortcut, action in [('Pause Processing', 'Ctrl+P', self.pause_processing),
                                       ('Resume Processing', 'Ctrl+R', self.resume_processing),
                                       ('Stop Processing', 'Ctrl+T', self.stop_processing),
                                       ('Settings', 'Ctrl+Shift+S', self.open_settings_dialog)]:
            act = QAction(name, self)
            act.setShortcut(shortcut)
            act.triggered.connect(action)
            edit_menu.addAction(act)
        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)

        def load_icon(path, fallback):
            full_path = os.path.join(BASE_DIR, path)
            return QIcon(full_path) if os.path.exists(full_path) else self.style().standardIcon(fallback)

        icons = [("icons/start.png", QStyle.SP_MediaPlay), ("icons/pause.png", QStyle.SP_MediaPause),
                 ("icons/resume.png", QStyle.SP_MediaSeekForward), ("icons/stop.png", QStyle.SP_MediaStop)]
        actions = [('Start', self.start_processing), ('Pause', self.pause_processing),
                   ('Resume', self.resume_processing), ('Stop', self.stop_processing)]
        for (icon_path, fallback), (name, func) in zip(icons, actions):
            act = QAction(load_icon(icon_path, fallback), name, self)
            act.setStatusTip(f"{name} Processing")
            act.triggered.connect(func)
            tool_bar.addAction(act)

    def open_settings_dialog(self):
        dlg = SettingsDialog(self.settings, self)
        if dlg.exec_() == QDialog.Accepted:
            self.settings = dlg.settings
            self.apply_styles()
            self.llm_client.base_url = self.settings['server_url']

    def apply_styles(self):
        palette = QPalette()
        if self.settings['theme'] == 'Dark':
            palette.setColor(QPalette.Window, QColor(45,45,45))
            palette.setColor(QPalette.WindowText, Qt.white)
            palette.setColor(QPalette.Base, QColor(30,30,30))
            palette.setColor(QPalette.Text, Qt.white)
            palette.setColor(QPalette.Button, QColor(45,45,45))
            palette.setColor(QPalette.ButtonText, Qt.white)
            palette.setColor(QPalette.Highlight, QColor(42,130,218))
            palette.setColor(QPalette.HighlightedText, Qt.black)
        else:
            palette = QApplication.style().standardPalette()
        self.setPalette(palette)
        self.setFont(self.settings.get('font', QFont("Arial", 10)))
        for widget in [self.description_text, self.file_list, self.plan_tree, self.chat_input.input_field]:
            widget.setFont(self.settings.get('font', QFont("Arial", 10)))
        self.apply_font_to_chat_widgets()

    def apply_font_to_chat_widgets(self):
        font = self.settings.get('font', QFont("Arial", 10))
        for i in range(self.chat_layout.count()):
            widget = self.chat_layout.itemAt(i).widget()
            if widget:
                widget.setFont(font)

    def update_llm_list(self):
        models = self.llm_client.get_models()
        self.settings['available_models'] = models
        self.llm_combo.clear()
        if models:
            self.llm_combo.addItems(models)
            selected = self.settings.get('selected_model')
            if selected in models:
                self.llm_combo.setCurrentText(selected)
            else:
                self.llm_combo.setCurrentIndex(0)
            self.analyse_btn.setEnabled(True)
        else:
            logging.warning("No models found.")
            QMessageBox.warning(self, "Warning", "No models found on the LLM server.")
            self.analyse_btn.setEnabled(False)

    def update_server_status(self):
        running = self.check_server_running()
        if running:
            self.server_status_label.setText('Server Status: Running')
            self.server_status_indicator.setText('🟢')
            self.start_server_btn.setEnabled(False)
            self.stop_server_btn.setEnabled(True)
            self.update_llm_list()
        else:
            self.server_status_label.setText('Server Status: Stopped')
            self.server_status_indicator.setText('🔴')
            self.start_server_btn.setEnabled(True)
            self.stop_server_btn.setEnabled(False)
            self.llm_combo.clear()
            self.analyse_btn.setEnabled(False)

    def check_server_running(self):
        try:
            response = requests.get(f'{self.llm_client.base_url}/api/tags', timeout=5)
            return response.status_code == 200
        except requests.RequestException:
            return False

    def start_server(self):
        if self.check_server_running():
            QMessageBox.information(self, "Info", "Server is already running.")
            logging.info("Start server attempted, but already running.")
            return
        try:
            llm_cmd = 'ollama'
            server_path = shutil.which(llm_cmd)
            if not server_path:
                QMessageBox.critical(self, "Error", f"Command '{llm_cmd}' not found.")
                logging.error(f"Command '{llm_cmd}' not found.")
                return
            args = [llm_cmd, 'serve', '--port', str(self.settings['server_port'])]
            creation = subprocess.CREATE_NEW_CONSOLE if os.name == 'nt' else 0
            self.server_process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=(os.name=='nt'), creationflags=creation)
            self.chat_area_add_message("System", "Starting the LLM server...")
            self.server_timer = QTimer()
            self.server_timer.timeout.connect(self.check_server_startup)
            self.server_timer.start(1000)
            self.server_startup_attempts = 0
            logging.debug("LLM server start initiated.")
        except Exception as e:
            logging.error(f"Error starting server: {e}")
            QMessageBox.critical(self, "Error", f"Failed to start server: {e}")

    def check_server_startup(self):
        self.server_startup_attempts +=1
        if self.check_server_running():
            self.server_timer.stop()
            self.server_timer = None
            self.update_server_status()
            self.chat_area_add_message("System", "LLM server started.")
            logging.debug("LLM server started successfully.")
        elif self.server_startup_attempts >=15:
            self.server_timer.stop()
            self.server_timer = None
            self.chat_area_add_message("System", "Server failed to start in time.")
            QMessageBox.warning(self, "Warning", "Server did not start in time.")
            self.update_server_status()
            logging.error("LLM server failed to start within 15 seconds.")

    def stop_server(self):
        if self.server_process and self.server_process.poll() is None:
            try:
                self.server_process.terminate()
                self.server_process.wait(timeout=10)
                self.server_process = None
                self.chat_area_add_message("System", "LLM server stopped.")
                logging.debug("LLM server stopped.")
                self.update_server_status()
            except subprocess.TimeoutExpired:
                self.server_process.kill()
                self.server_process = None
                self.chat_area_add_message("System", "LLM server forcefully stopped.")
                self.update_server_status()
                logging.error("LLM server forcefully stopped.")
            except Exception as e:
                logging.error(f"Error stopping server: {e}")
                QMessageBox.critical(self, "Error", f"Failed to stop server: {e}")
        else:
            QMessageBox.information(self, "Info", "Server is not running.")
            logging.info("Stop server attempted, but server not running.")

    def upload_files(self):
        files, _ = QFileDialog.getOpenFileNames(self, "Select Files", "", "All Files (*)")
        new = [f for f in files if f not in self.uploaded_files]
        if new:
            self.uploaded_files.extend(new)
            logging.debug(f"Uploaded files: {new}")
            self.display_uploaded_files()
            self.chat_area_add_message("System", f"{len(new)} file(s) uploaded.")
        else:
            self.chat_area_add_message("System", "No new files uploaded (duplicates skipped).")

    def display_uploaded_files(self):
        self.file_list.clear()
        self.file_list.addItems(self.uploaded_files)

    def show_file_context_menu(self, pos):
        items = self.file_list.selectedItems()
        if not items:
            return
        menu = QMenu()
        remove_act = QAction("Remove Selected", self)
        remove_act.triggered.connect(self.remove_selected_files)
        menu.addAction(remove_act)
        menu.exec_(self.file_list.mapToGlobal(pos))

    def remove_selected_files(self):
        for item in self.file_list.selectedItems():
            file = item.text()
            if file in self.uploaded_files:
                self.uploaded_files.remove(file)
        self.display_uploaded_files()
        self.chat_area_add_message("System", "Selected file(s) removed.")

    def drag_enter_event(self, event):
        if event.mimeData().hasUrls():
            event.accept()
        else:
            event.ignore()

    def drop_event(self, event):
        if event.mimeData().hasUrls():
            files = [url.toLocalFile() for url in event.mimeData().urls()]
            self.handle_dropped_files(files)

    def handle_dropped_files(self, files):
        new = [f for f in files if f not in self.uploaded_files]
        if new:
            self.uploaded_files.extend(new)
            logging.debug(f"Drag-uploaded files: {new}")
            self.display_uploaded_files()
            self.chat_area_add_message("System", f"{len(new)} file(s) uploaded via drag-and-drop.")
        else:
            self.chat_area_add_message("System", "No new files uploaded (duplicates skipped).")

    def start_analysis(self):
        description = self.description_text.toPlainText().strip()
        if not description:
            QMessageBox.warning(self, "Warning", "Enter application description before analysis.")
            logging.warning("Analysis started without description.")
            return
        temperature, max_tokens = self.settings.get('temperature'), self.settings.get('max_tokens')
        self.chat_area_add_message("Assistant", "Starting analysis...")
        self.loading_label.setVisible(True)
        self.analyse_btn.setEnabled(False)
        threading.Thread(target=self.perform_analysis, args=(description, temperature, max_tokens), daemon=True).start()

    def perform_analysis(self, description, temperature, max_tokens):
        selected_llm = self.llm_combo.currentText()
        if not selected_llm:
            self.chat_area_add_message("Assistant", "No LLM selected.")
            logging.warning("Analysis without LLM selected.")
            self.loading_label.setVisible(False)
            self.analyse_btn.setEnabled(True)
            return
        file_contents = self.get_file_contents()
        prompt = f"""
As an experienced software architect, create a detailed development plan based on the application description.

**Description:**
{description}

**Files:**
{file_contents}

**Instructions:**
- Provide only JSON output.
- Structure: Tasks with Title, Description, Goals.
- Include dependencies if applicable.
"""
        logging.debug(f"Sending plan prompt to '{selected_llm}'.")
        plan_text = self.llm_client.generate(selected_llm, prompt, temperature, max_tokens)
        self.plan_data = self.parse_plan(plan_text)
        if self.plan_data:
            self.display_plan(self.plan_data)
            self.chat_area_add_message("Assistant", "Development plan created.")
            self.start_processing_btn.setEnabled(True)
            self.edit_plan_btn.setEnabled(True)
            logging.debug("Development plan successfully created.")
        else:
            self.chat_area_add_message("Assistant", "Error during analysis.")
            logging.error("Failed to create development plan.")
        self.loading_label.setVisible(False)
        self.analyse_btn.setEnabled(True)

    def get_file_contents(self):
        content = ""
        for f in self.uploaded_files:
            if os.path.splitext(f)[1].lower() in ['.txt', '.py', '.md', '.json', '.yaml', '.yml']:
                try:
                    with open(f, 'r', encoding='utf-8') as file:
                        content += f"\nContent of {os.path.basename(f)}:\n{file.read()}"
                    logging.debug(f"Read file: {f}")
                except Exception as e:
                    logging.error(f"Error reading {f}: {e}")
        return content

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

**Description:**
{description}

**Files:**
{file_contents}

**Instructions:**
- Provide only JSON output.
- Structure: Tasks with Title, Description, Goals.
- Include dependencies if applicable.
"""

    def parse_plan(self, plan_text):
        try:
            json_str = re.search(r'\[.*\]', plan_text, re.DOTALL).group(0)
            plan = json.loads(json_str)
            if isinstance(plan, list) and all('Title' in t and 'Goals' in t for t in plan):
                logging.debug("Plan parsed successfully.")
                return plan
        except Exception as e:
            logging.error(f"Plan parsing error: {e}")
        return []

    def display_plan(self, plan):
        self.plan_tree.clear()
        for i, task in enumerate(plan):
            task_title = task.get('Title', f'Task {i+1}')
            task_item = QTreeWidgetItem([task_title])
            description = task.get('Description', '')
            if description:
                desc_item = QTreeWidgetItem(['Description', description])
                task_item.addChild(desc_item)
            goals_item = QTreeWidgetItem(['Goals'])
            for j, goal in enumerate(task.get('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)
        self.gantt_chart.update_chart(self.plan_data)
        logging.debug("Plan displayed.")

    def edit_plan(self):
        item = self.plan_tree.currentItem()
        if item:
            parent = item.parent()
            if parent and parent.text(0) != 'Goals':
                text = item.text(1) if item.text(1) else item.text(0)
                new, ok = QInputDialog.getText(self, 'Edit', 'Enter new text:', QLineEdit.Normal, text)
                if ok and new:
                    if item.text(1):
                        item.setText(1, new)
                        idx, _ = item.data(0, Qt.UserRole) or (0, None)
                        self.plan_data[idx]['Description'] = new
                    else:
                        item.setText(0, new)
                        idx, _ = item.data(0, Qt.UserRole) or (0, None)
                        self.plan_data[idx]['Title'] = new
            elif parent and parent.text(0) == 'Goals':
                text = item.text(0)
                new, ok = QInputDialog.getText(self, 'Edit', 'Enter new goal:', QLineEdit.Normal, text)
                if ok and new:
                    item.setText(0, new)
                    idx = item.data(0, Qt.UserRole) or (0,0)
                    if idx[0] < len(self.plan_data) and idx[1] < len(self.plan_data[idx[0]]['Goals']):
                        self.plan_data[idx[0]]['Goals'][idx[1]] = new
            else:
                text = item.text(0)
                new, ok = QInputDialog.getText(self, 'Edit', 'Enter new task title:', QLineEdit.Normal, text)
                if ok and new:
                    item.setText(0, new)
                    idx = item.data(0, Qt.UserRole) or (0, None)
                    if idx[0] < len(self.plan_data):
                        self.plan_data[idx[0]]['Title'] = new
        else:
            QMessageBox.warning(self, 'Warning', 'Select an item to edit.')

    def start_processing(self):
        if not self.plan_data:
            QMessageBox.warning(self, "Warning", "Create a development plan first.")
            logging.warning("Processing started without a plan.")
            return
        if not self.llm_combo.currentText():
            QMessageBox.warning(self, "Warning", "Select an LLM.")
            logging.warning("Processing started without LLM selection.")
            return
        self.chat_area_add_message("Assistant", "Processing tasks...")
        self.loading_label.setVisible(True)
        self.start_processing_btn.setEnabled(False)
        self.edit_plan_btn.setEnabled(False)
        self.analyse_btn.setEnabled(False)
        self.task_processor = TaskProcessor(self.plan_data, self.llm_client, self.llm_combo.currentText())
        self.task_processor.progress_updated.connect(self.update_progress)
        self.task_processor.task_message.connect(self.chat_area_add_message)
        self.task_processor.task_result.connect(self.chat_area_add_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("Task processing started.")

    def processing_finished(self):
        self.loading_label.setVisible(False)
        self.start_processing_btn.setEnabled(True)
        self.edit_plan_btn.setEnabled(True)
        self.analyse_btn.setEnabled(True)
        self.chat_area_add_message("Assistant", "Processing finished.")
        logging.debug("Task processing finished.")

    def chat_area_add_message(self, sender, message):
        msg = MessageWidget(sender, message)
        self.chat_layout.addWidget(msg)
        QTimer.singleShot(100, lambda: self.chat_area.verticalScrollBar().setValue(self.chat_area.verticalScrollBar().maximum()))

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

    def update_task_status(self, task_idx, goal_idx, status):
        color = {'processing': '#FFD700', 'completed': '#32CD32', 'error': '#FF4500', 'failed': '#DC143C'}.get(status, '#555')
        if task_idx < self.plan_tree.topLevelItemCount():
            task_item = self.plan_tree.topLevelItem(task_idx)
            if goal_idx == -1:
                task_item.setBackground(0, QColor(color))
            else:
                goals_item = task_item.child(1)
                if goals_item and goal_idx < goals_item.childCount():
                    goals_item.child(goal_idx).setBackground(0, QColor(color))
                else:
                    logging.warning(f"Invalid goal index: {goal_idx} for task {task_idx}")

    def pause_processing(self):
        if hasattr(self, 'task_processor') and self.task_processor.isRunning():
            self.task_processor.pause()
            self.chat_area_add_message("System", "Processing paused.")
            logging.debug("Processing paused.")

    def resume_processing(self):
        if hasattr(self, 'task_processor') and self.task_processor.isRunning():
            self.task_processor.resume()
            self.chat_area_add_message("System", "Processing resumed.")
            logging.debug("Processing resumed.")

    def stop_processing(self):
        if hasattr(self, 'task_processor') and self.task_processor.isRunning():
            self.task_processor.stop()
            self.task_processor.wait()
            self.chat_area_add_message("System", "Processing stopped.")
            self.progress_bar.setValue(0)
            self.loading_label.setVisible(False)
            self.start_processing_btn.setEnabled(True)
            self.edit_plan_btn.setEnabled(True)
            self.analyse_btn.setEnabled(True)
            logging.debug("Processing stopped.")
        else:
            QMessageBox.information(self, "Info", "No ongoing process to stop.")
            logging.info("Stop processing attempted with no active process.")

    def save_project(self):
        file, _ = QFileDialog.getSaveFileName(self, "Save Project", "", "JSON Files (*.json)")
        if file:
            project = {
                'description': self.description_text.toPlainText(),
                'uploaded_files': self.uploaded_files,
                'plan_data': self.plan_data,
                'settings': {
                    'selected_model': self.settings.get('selected_model', ''),
                    'temperature': self.settings.get('temperature', 0.7),
                    'max_tokens': self.settings.get('max_tokens', 150),
                    'server_url': self.settings.get('server_url', 'http://localhost'),
                    'server_port': self.settings.get('server_port', 11434),
                    'theme': self.settings.get('theme', 'Dark'),
                    'font': self.font_to_dict(self.settings.get('font', QFont("Arial", 10)))
                }
            }
            try:
                with open(file, 'w', encoding='utf-8') as f:
                    json.dump(project, f, ensure_ascii=False, indent=4)
                QMessageBox.information(self, "Success", "Project saved successfully.")
                logging.debug(f"Project saved to {file}.")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to save project: {e}")
                logging.error(f"Failed to save project: {e}")

    def load_project(self):
        file, _ = QFileDialog.getOpenFileName(self, "Load Project", "", "JSON Files (*.json)")
        if file:
            try:
                with open(file, 'r', encoding='utf-8') as f:
                    project = json.load(f)
                self.description_text.setPlainText(project.get('description', ''))
                self.uploaded_files = project.get('uploaded_files', [])
                self.display_uploaded_files()
                self.plan_data = project.get('plan_data', [])
                settings = project.get('settings', {})
                if settings:
                    self.settings.update({
                        'selected_model': settings.get('selected_model', ''),
                        'temperature': settings.get('temperature', 0.7),
                        'max_tokens': settings.get('max_tokens', 150),
                        'server_url': settings.get('server_url', 'http://localhost'),
                        'server_port': settings.get('server_port', 11434),
                        'theme': settings.get('theme', 'Dark'),
                        'font': self.dict_to_font(settings.get('font', {}))
                    })
                if self.plan_data:
                    self.display_plan(self.plan_data)
                    self.start_processing_btn.setEnabled(True)
                    self.edit_plan_btn.setEnabled(True)
                QMessageBox.information(self, "Success", "Project loaded successfully.")
                logging.debug(f"Project loaded from {file}.")
                self.apply_styles()
                self.switch_page("Development Plan")
            except Exception as e:
                QMessageBox.critical(self, "Error", f"Failed to load project: {e}")
                logging.error(f"Failed to load project: {e}")

    def show_about_dialog(self):
        QMessageBox.about(self, "About", "Intelligent Assistant for Developers\nVersion 2.6\n\nDesigned to assist developers in creating comprehensive development plans and scripts with the help of LLMs.")

    def handle_user_message(self, message):
        self.chat_area_add_message("User", message)
        reply = self.llm_client.generate(self.settings['selected_model'], message, self.settings['temperature'], self.settings['max_tokens'])
        self.chat_area_add_message("Assistant", reply)

    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Confirm Exit', 'Are you sure you want to exit?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            if self.server_process and self.server_process.poll() is None:
                try:
                    self.server_process.terminate()
                    self.server_process.wait(timeout=10)
                    logging.debug("Server terminated on exit.")
                except subprocess.TimeoutExpired:
                    self.server_process.kill()
                    logging.error("Server killed on exit.")
            if hasattr(self, 'task_processor') and self.task_processor.isRunning():
                self.task_processor.stop()
                self.task_processor.wait()
                logging.debug("TaskProcessor terminated on exit.")
            event.accept()
        else:
            event.ignore()

    def new_project(self):
        self.description_text.clear()
        self.uploaded_files = []
        self.display_uploaded_files()
        self.plan_data = []
        self.plan_tree.clear()
        self.gantt_chart.update_chart(self.plan_data)
        self.start_processing_btn.setEnabled(False)
        self.edit_plan_btn.setEnabled(False)
        self.stacked_widget.setCurrentWidget(self.main_screen)
        self.chat_area_add_message("System", "Started a new project.")
        logging.debug("New project initiated.")

    def font_to_dict(self, font):
        return {
            'family': font.family(),
            'pointSize': font.pointSize(),
            'bold': font.bold(),
            'italic': font.italic(),
            'underline': font.underline(),
            'strikeOut': font.strikeOut(),
            'weight': font.weight(),
            'style': font.style(),
            'styleHint': font.styleHint()
        }

    def dict_to_font(self, d):
        font = QFont()
        font.setFamily(d.get('family', "Arial"))
        font.setPointSize(d.get('pointSize', 10))
        font.setBold(d.get('bold', False))
        font.setItalic(d.get('italic', False))
        font.setUnderline(d.get('underline', False))
        font.setStrikeOut(d.get('strikeOut', False))
        font.setWeight(d.get('weight', QFont.Normal))
        font.setStyle(d.get('style', QFont.StyleNormal))
        font.setStyleHint(d.get('styleHint', QFont.AnyStyle))
        return font

    def save_settings(self):
        self.settings['max_tokens'] = self.token_limit_spin.value()
        QMessageBox.information(self, "Success", "Settings saved successfully.")
        logging.debug("Settings updated.")

    def main_content_add_message(self, sender, message):
        pass  # Placeholder if needed

    def update_plan_page(self, plan):
        self.plan_data = plan
        self.display_plan(self.plan_data)

    def generate_final_report(self):
        final = self.task_processor.generate_final_result()
        self.chat_area_add_message("Assistant", final)

    def handle_project_save(self):
        pass  # Placeholder if needed

def main():
    app = QApplication(sys.argv)
    dark_palette = QPalette()
    dark_palette.setColor(QPalette.Window, QColor(45,45,45))
    dark_palette.setColor(QPalette.WindowText, Qt.white)
    dark_palette.setColor(QPalette.Base, QColor(30,30,30))
    dark_palette.setColor(QPalette.AlternateBase, QColor(45,45,45))
    dark_palette.setColor(QPalette.ToolTipBase, Qt.white)
    dark_palette.setColor(QPalette.ToolTipText, Qt.white)
    dark_palette.setColor(QPalette.Text, Qt.white)
    dark_palette.setColor(QPalette.Button, QColor(45,45,45))
    dark_palette.setColor(QPalette.ButtonText, Qt.white)
    dark_palette.setColor(QPalette.BrightText, Qt.red)
    dark_palette.setColor(QPalette.Link, QColor(42,130,218))
    dark_palette.setColor(QPalette.Highlight, QColor(42,130,218))
    dark_palette.setColor(QPalette.HighlightedText, Qt.black)
    app.setPalette(dark_palette)
    app.setStyle("Fusion")
    window = MainWindow()
    window.show()
    logging.debug("Application started.")
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()