korrigiere alle fehler und probleme die du in diesem skript findest, im folgendem log nenne ich die die mir aufgefallen sind. Nenne mir abschließend das vollständige korrigierte skript mit dem gesamten code der anwendung ohne fehlende zeilen.

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 QHBoxLayout/
QLayout: Cannot add a null widget to QHBoxLayout/
QLayout: Cannot add a null widget to QHBoxLayout/
QLayout: Cannot add a null widget to QVBoxLayout/
Traceback (most recent call last):
  File "d:\_Projekte\_MyCodes\GitHub\SoftwareDeveloper_o1\SoftwareDeveloper_o1_Compact.py", line 1303, in <module>
    main()
  File "d:\_Projekte\_MyCodes\GitHub\SoftwareDeveloper_o1\SoftwareDeveloper_o1_Compact.py", line 1298, in main
    window = MainWindow(); window.show()
  File "d:\_Projekte\_MyCodes\GitHub\SoftwareDeveloper_o1\SoftwareDeveloper_o1_Compact.py", line 447, in __init__
    self.init_ui(); self.apply_styles(); self.update_server_status()
  File "d:\_Projekte\_MyCodes\GitHub\SoftwareDeveloper_o1\SoftwareDeveloper_o1_Compact.py", line 815, in update_server_status
    self.server_status_label.setText('Server Status: Running')
AttributeError: 'NoneType' object has no attribute 'setText'


Skript:

import sys, threading, requests, json, logging, subprocess, os, re, 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, self = QVBoxLayout(self), 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;
            }}
        """)
        layout.addWidget(QLabel(f"<b>{self.sender}:</b>").setStyleSheet("color: #fff;"))
        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, self = QVBoxLayout(self), self
        tabs_layout = QVBoxLayout()

        # LLM Settings
        llm_group, llm_layout = QGroupBox("LLM Settings"), 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, server_layout = QGroupBox("Server Settings"), 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, ui_layout = QGroupBox("UI Settings"), 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))
        self.font_display = QLabel(f"{self.selected_font.family()}, {self.selected_font.pointSize()}pt, {'Bold' if self.selected_font.bold() else 'Regular'}")
        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, self.canvas = Figure(figsize=(8,6)), FigureCanvas(Figure(figsize=(8,6)))
            layout.addWidget(self.canvas)
            self.draw_gantt_chart()
        except ImportError:
            logging.error("Matplotlib not installed.")
            layout.addWidget(QLabel("Matplotlib is not installed. Gantt chart cannot be displayed.").setStyleSheet("color: red;"))
        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, nav_items = QVBoxLayout(self.sidebar), [("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, layout = QWidget(), QVBoxLayout()
        top_layout = QHBoxLayout()
        self.llm_combo = QComboBox(); llm_label = QLabel('Select LLM:').setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;")
        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"); }
        """)
        self.server_status_label = QLabel('Server Status:').setStyleSheet("color: #fff; font-size: 12pt;")
        self.server_status_indicator = QLabel('⚪').setStyleSheet("color: #fff; font-size: 16pt;")
        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(QLabel('Select LLM:').setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;"))
        top_layout.addWidget(self.llm_combo); top_layout.addStretch()
        top_layout.addWidget(self.server_status_label); top_layout.addWidget(self.server_status_indicator)
        top_layout.addWidget(self.start_server_btn); top_layout.addWidget(self.stop_server_btn)

        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)

        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; }
        """)

        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)

        self.chat_input = ChatInputWidget(); self.chat_input.send_message.connect(self.handle_user_message)
        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()
        self.start_server_btn.clicked.connect(self.start_server)
        self.stop_server_btn.clicked.connect(self.stop_server)
        chat_widget.setLayout(layout)
        return chat_widget

    def create_plan_page(self):
        plan_widget, layout = QWidget(), 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; }
        """)
        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)
        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)
        layout.addLayout(button_layout); layout.addWidget(self.plan_tree)
        layout.addWidget(self.toggle_gantt_btn); layout.addWidget(self.gantt_chart)
        layout.addStretch()
        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)
        plan_widget.setLayout(layout)
        return plan_widget

    def create_files_page(self):
        files_widget, layout = QWidget(), QVBoxLayout()
        description_label = QLabel('Application Description:').setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;")
        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;
            }
        """)
        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)
        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
        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.upload_files)
        self.manage_files_btn.setToolTip("Manage and upload project files.")
        layout.addWidget(QLabel('Application Description:').setStyleSheet("color: #fff; font-weight: bold; font-size: 14pt;"))
        layout.addWidget(self.description_text)
        layout.addWidget(self.upload_btn)
        layout.addWidget(QLabel('Uploaded Files:'))
        layout.addWidget(self.file_list)
        layout.addWidget(self.manage_files_btn)
        layout.addStretch()
        files_widget.setLayout(layout)
        return files_widget

    def create_settings_page(self):
        settings_widget, layout = QWidget(), QVBoxLayout()
        settings_group, settings_layout = QGroupBox('Settings'), QFormLayout()
        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)
        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(settings_group); 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(); self.uploaded_files.remove(file)
        self.display_uploaded_files()

    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 create_chat_page_content(self):
        pass  # Placeholder if needed

    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):
            item = QTreeWidgetItem([task.get('Title', f'Task {i+1}')])
            if desc := task.get('Description', ''):
                desc_item = QTreeWidgetItem(['Description', desc])
                item.addChild(desc_item)
            goals = 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.addChild(goal_item)
            item.addChild(goals); self.plan_tree.addTopLevelItem(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) or 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)
                    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)
                    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()