diese anwendung startet nicht korrekt. Nach start erhalte ich eine nachricht von windows die sagt das der zugriff verweigert wurde und diese anwendung nicht auf meinem computer ausgeführt werden kann. Ich nutze einen windows rechner. Sorge dafür das das skript problemlos auf einem windows rechner ausgeführt werden kann und der start vorgang korrekt abläuft. Nenne mir dann das vollständige verbesserte skript der anwendung.

import sys
import threading
import requests
import json
import logging
import subprocess
import os
import re
import shutil
import psutil  # Added for process management
from datetime import datetime, timedelta
from logging.handlers import RotatingFileHandler

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QLabel, QPushButton, QWidget, QTextEdit,
    QScrollArea, QFrame, QProgressBar, QMessageBox, QDialog, QDialogButtonBox,
    QVBoxLayout, QHBoxLayout, QComboBox, QGroupBox, QFormLayout, QInputDialog,
    QFontDialog, QFileDialog, QPlainTextEdit, QSpinBox, QDoubleSpinBox, QLineEdit,
    QTabWidget
)
from PyQt5.QtCore import (
    QTimer, Qt, QThread, pyqtSignal, QSize, QPropertyAnimation, QRect
)
from PyQt5.QtGui import (
    QColor, QFont, QPalette, QMovie, QSyntaxHighlighter, QTextCharFormat, QRegExp
)

# Logging configuration
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler = RotatingFileHandler(
    os.path.join(BASE_DIR, "app.log"),
    maxBytes=510241024,
    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', model_name=''):
        self.base_url = base_url.rstrip('/')
        self.model_name = model_name
        self.cache = {}
        self.memory = {}
        self.role = 'system_agent'  # Default Role
        self.temperature = 0.7  # Initial temperature
        self.max_tokens = 150

    def set_model(self, model_name):
        self.model_name = model_name
        logger.debug(f"Switched LLM model to: {self.model_name}")

    def set_role(self, role):
        if role in ['system_agent', 'code_agent']:
            self.role = role
            logger.debug(f"Switched LLM role to: {self.role}")
        else:
            logger.error(f"Invalid role: {role}")

    def set_temperature(self, temperature):
        self.temperature = temperature
        logger.debug(f"LLM temperature set to {self.temperature}.")

    def set_max_tokens(self, max_tokens):
        self.max_tokens = max_tokens
        logger.debug(f"LLM max tokens set to {self.max_tokens}.")

    def adjust_settings_based_on_phase(self, phase):
        """
        Adjusts temperature and max tokens based on the development phase.
        """
        phase_settings = {
            'initial_plan': {'temperature': 0.6, 'max_tokens': 300},
            'detailed_design': {'temperature': 0.7, 'max_tokens': 500},
            'implementation': {'temperature': 0.8, 'max_tokens': 700},
            'review': {'temperature': 0.5, 'max_tokens': 200},
            'general': {'temperature': 0.7, 'max_tokens': 150}
        }
        settings = phase_settings.get(phase, phase_settings['general'])
        self.set_temperature(settings['temperature'])
        self.set_max_tokens(settings['max_tokens'])
        logger.debug(f"LLM settings adjusted for phase: {phase}")

    def get_models(self):
        try:
            logger.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', [])]
                logger.debug(f"Available models: {models}")
                return models
            logger.error(f"Error fetching models: {response.status_code} - {response.text}")
        except requests.RequestException as e:
            logger.error(f"Error communicating with LLM server: {e}")
        return []

    def generate(self, prompt, temperature=None, max_tokens=None):
        if not self.model_name:
            logger.error("No LLM model selected.")
            return "Error: No LLM model selected."
        temperature = temperature if temperature is not None else self.temperature
        max_tokens = max_tokens if max_tokens is not None else self.max_tokens
        cache_key = (self.model_name, prompt, temperature, max_tokens)
        if cache_key in self.cache:
            logger.debug("Using cached result.")
            return self.cache[cache_key]
        payload = {
            "model": self.model_name,
            "prompt": prompt,
            "temperature": temperature,
            "max_tokens": max_tokens,
            "format": "json",
            "stream": False
        }
        try:
            logger.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
                logger.debug("Generation successful.")
                return result
            logger.error(f"Generation error: {response.status_code} - {response.text}")
            return f"Error: {response.text}"
        except requests.RequestException as e:
            logger.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 CommandDispatcher:
    def __init__(self, main_window, llm_client):
        self.main_window = main_window
        self.llm_client = llm_client
        # Define critical actions
        self.critical_actions = {
            'stop_server': 'Stopping the server may interrupt all running processes. Do you want to continue?',
            'remove_files': 'Removing files may lead to data loss. Do you want to continue?',
            'delete_project': 'Deleting a project is irreversible. Do you want to continue?',
            # Add more critical actions here
        }

    def dispatch(self, command):
        action = command.get('action')
        params = command.get('params', {})
        if not action:
            self.main_window.chat_area_add_message("System", "Unknown command format.")
            return

        logger.info(f"Executing action: {action} with parameters: {params}")

        # Check if the action is critical
        if action in self.critical_actions:
            confirmation = self.main_window.request_confirmation(self.critical_actions[action])
            if not confirmation:
                self.main_window.chat_area_add_message("System", f"Action '{action}' was cancelled by the user.")
                logger.info(f"Action '{action}' was cancelled by the user.")
                return

        actions = {
            'start_server': self.main_window.start_server_command,
            'stop_server': self.main_window.stop_server_command,
            'upload_files': self.main_window.upload_files_command,
            'remove_files': self.main_window.remove_selected_files_command,
            'change_setting': self.main_window.change_setting,
            'save_project': self.main_window.save_project_command,
            'load_project': self.main_window.load_project_command,
            'create_new_project': self.main_window.new_project_command,
            'start_analysis': self.main_window.start_analysis_command,
            'start_processing': self.main_window.start_processing_command,
            'pause_processing': self.main_window.pause_processing_command,
            'resume_processing': self.main_window.resume_processing_command,
            'stop_processing': self.main_window.stop_processing_command,
            'toggle_gantt_chart': self.main_window.toggle_gantt_chart_command,
            'generate_final_report': self.main_window.generate_final_report_command,
            'switch_llm': self.main_window.switch_llm_command,
            'delete_project': self.main_window.delete_project_command,
            # Add more actions here
        }

        func = actions.get(action)
        if func:
            try:
                func(**params)
                self.main_window.chat_area_add_message("System", f"Action '{action}' executed successfully.")
                logger.info(f"Action '{action}' executed successfully.")
            except Exception as e:
                self.main_window.chat_area_add_message("System", f"Error executing '{action}': {e}")
                logger.error(f"Error executing '{action}': {e}")
        else:
            self.main_window.chat_area_add_message("System", f"Action '{action}' not recognized.")
            logger.warning(f"Action '{action}' not recognized.")


class TaskProcessor(QThread):
    progress_updated = pyqtSignal(int)
    task_message = pyqtSignal(str, str)  # Sender, Message
    task_result = pyqtSignal(str, str)   # Sender, Message
    task_status = pyqtSignal(int, int, str)  # task_index, goal_index, status
    final_script_generated = pyqtSignal(str)  # Signal for the final script

    def __init__(self, tasks, llm_client, parent=None):
        super().__init__(parent)
        self.tasks = tasks
        self.llm_client = llm_client
        self._paused = False
        self._stopped = False
        self.max_retries = 3
        self.context_memory = {}
        self.intermediate_results = {}  # Stores intermediate results

    def run(self):
        total_goals = sum(len(task.get('Goals', [])) for task in self.tasks)
        completed_goals = 0
        self.llm_client.set_role('code_agent')  # Switch to Code-Agent role
        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("Assistant", f"Starting task '{task_title}'")
            self.task_status.emit(i, -1, 'processing')  # -1 for the task itself
            for j, goal in enumerate(task.get('Goals', [])):
                if self._stopped:
                    break
                while self._paused:
                    self.msleep(100)
                retry, success = 0, False
                phase = self.determine_phase(goal['Goal'])
                self.llm_client.adjust_settings_based_on_phase(phase)
                while retry < self.max_retries and not success and not self._stopped:
                    if retry > 0:
                        self.task_message.emit("Assistant", f"Attempt {retry} for goal: {goal['Goal']}")
                        logger.debug(f"Attempt {retry} for goal: {goal['Goal']}")
                    self.task_message.emit("Assistant", f"Processing goal: {goal['Goal']}")
                    self.task_status.emit(i, j, 'processing')
                    result = self.process_goal(task, goal, retry > 0, phase)
                    success = self.self_review(goal, result)
                    if success:
                        self.task_result.emit("Assistant", f"Result for '{goal['Goal']}':\n{result}")
                        self.context_memory[goal['Goal']] = result
                        self.intermediate_results[goal['Goal']] = result
                        self.task_status.emit(i, j, 'completed')
                    else:
                        self.task_status.emit(i, j, 'error')
                        retry += 1
                        # Optimization strategy: Adjust temperature
                        if retry == 1:
                            logger.debug(f"Adjusting temperature to improve results for '{goal['Goal']}'.")
                            self.llm_client.set_temperature(min(self.llm_client.temperature + 0.1, 1.0))
                if not success:
                    self.task_message.emit("Assistant", f"Goal '{goal['Goal']}' failed after {self.max_retries} attempts.")
                    # Parallel process: Automatic prompt reformulation
                    self.task_message.emit("Assistant", f"Trying to reprocess goal '{goal['Goal']}' with an adjusted prompt.")
                    adjusted_result = self.adjust_goal_prompt(task, goal, phase)
                    success = self.self_review(goal, adjusted_result)
                    if success:
                        self.task_result.emit("Assistant", f"Adjusted result for '{goal['Goal']}':\n{adjusted_result}")
                        self.context_memory[goal['Goal']} = adjusted_result
                        self.intermediate_results[goal['Goal']] = adjusted_result
                        self.task_status.emit(i, j, 'completed')
                    else:
                        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')  # Completed task
            summary = self.create_task_summary(task)
            self.task_result.emit("Assistant", 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("Assistant", f"Final Result:\n{final}")
            self.final_script_generated.emit(final)  # Emit the final script
        self.llm_client.set_role('system_agent')  # Switch back to System-Agent role

    def determine_phase(self, goal):
        """
        Determines the development phase based on the goal.
        """
        goal_lower = goal.lower()
        if "plan" in goal_lower or "requirements" in goal_lower:
            return 'initial_plan'
        elif "design" in goal_lower or "architecture" in goal_lower:
            return 'detailed_design'
        elif "develop" in goal_lower or "implement" in goal_lower:
            return 'implementation'
        elif "review" in goal_lower or "test" in goal_lower:
            return 'review'
        else:
            return 'general'

    def process_goal(self, task, goal, retry=False, phase='general'):
        context = self.get_relevant_context(goal['Goal'])
        prompt = f"""

As an experienced software architect, create a detailed solution for the goal: {goal['Goal']}

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

Context:
{context}

Use clear language and code examples where appropriate.
"""
        logger.debug(f"Generating for '{goal['Goal']}' in phase '{phase}'.")
        return self.llm_client.generate(prompt, temperature=self.llm_client.temperature, max_tokens=self.llm_client.max_tokens)

    def self_review(self, goal, result):
        prompt = f"""

Review the result for "{goal['Goal']}":

{result}

Is it complete and correct? Answer with "Yes" or "No".
"""
        review = self.llm_client.generate(prompt, temperature=0.5, max_tokens=10)
        logger.debug(f"Self-review for '{goal['Goal']}': {review}")
        if "Yes" in review:
            return True
        self.task_message.emit("Assistant", f"Review failed for '{goal['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['Goal']}:\n{self.context_memory.get(g['Goal'], '')}" for g in task.get('Goals', []))

    def generate_final_result(self):
        context = "\n".join(self.context_memory.values())
        prompt = f"""

Summarize all the results and create the complete application code with documentation.

Context:
{context}
"""
        logger.debug("Generating final result.")
        return self.llm_client.generate(prompt, temperature=self.llm_client.temperature, max_tokens=500)

    def adjust_goal_prompt(self, task, goal, phase='general'):
        """
        Adjusts the prompt to better process the goal after a failure.
        """
        context = self.get_relevant_context(goal['Goal'])
        prompt = f"""

As an experienced software architect, create an improved solution for the goal: {goal['Goal']}

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

Context:
{context}

Based on the previous results, optimize the solution.
Use clear language and code examples where appropriate.
"""
        logger.debug(f"Adjusted prompt for '{goal['Goal']}' in phase '{phase}'.")
        # Adjust LLM settings for improved results
        self.llm_client.adjust_settings_based_on_phase(phase)
        return self.llm_client.generate(prompt, temperature=self.llm_client.temperature, max_tokens=200)

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

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

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


class ThoughtCloudWidget(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setFixedSize(200, 200)
        self.setStyleSheet("background-color: rgba(255, 255, 255, 100); border-radius: 100px;")
        self.label = QLabel("", self)
        self.label.setStyleSheet("color: #2e2e2e; font-size: 12pt;")
        self.label.setAlignment(Qt.AlignCenter)
        self.label.setWordWrap(True)
        self.label.resize(self.size())
        self.hide()

        # Animation for displaying the Thought Cloud
        self.animation = QPropertyAnimation(self, b"geometry")
        self.animation.setDuration(500)

    def display_message(self, message):
        self.label.setText(message)
        self.show()
        self.raise_()
        self.animate_show()

    def animate_show(self):
        start_rect = QRect(self.x(), self.y() - 50, self.width(), self.height())
        end_rect = QRect(self.x(), self.y(), self.width(), self.height())
        self.animation.stop()
        self.animation.setStartValue(start_rect)
        self.animation.setEndValue(end_rect)
        self.animation.start()

    def clear_message(self):
        self.label.setText("")
        self.hide()


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 = {
            "assistant": "#2e2e2e",
            "system": "#555555",
            "user": "#0078d7"
        }.get(self.sender.lower(), "#0078d7")  # User messages in Blue

        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;" if self.sender.lower() in ["assistant", "system"] else "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)
            elif part['type'] == 'link':
                link = QLabel(f'<a href="{part["url"]}">{part["content"]}</a>')
                link.setStyleSheet("color: #3498db;")
                link.setOpenExternalLinks(True)
                layout.addWidget(link)
        self.setLayout(layout)

    def parse_message(self, msg):
        # Regex to find code blocks and links
        pattern = re.compile(r'```(.*?)```|https?://\S+')
        parts, last = [], 0
        for m in pattern.finditer(msg):
            if m.start() > last:
                text = msg[last:m.start()]
                parts.append({'type': 'text', 'content': self.highlight_keywords(text)})
            if m.group(1):
                parts.append({'type': 'code', 'content': m.group(1).strip()})
            else:
                url = m.group()
                parts.append({'type': 'link', 'content': url, 'url': url})
            last = m.end()
        if last < len(msg):
            parts.append({'type': 'text', 'content': self.highlight_keywords(msg[last:])})
        return parts

    def highlight_keywords(self, text):
        # Highlight keywords in blue (e.g., commands)
        keywords = [
            'start_server', 'stop_server', 'upload_files', 'remove_files',
            'change_setting', 'save_project', 'load_project', 'create_new_project',
            'start_analysis', 'start_processing', 'pause_processing', 'resume_processing',
            'stop_processing', 'toggle_gantt_chart', 'generate_final_report', 'switch_llm',
            'delete_project'
        ]
        for kw in keywords:
            pattern = re.compile(r'\b' + re.escape(kw) + r'\b')
            text = pattern.sub(f'<span style="color:#3498db;">{kw}</span>', text)
        return text


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("Enter 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):
    STATUS_COLORS = {
        'pending': '#5bc0de',    # Blue for pending tasks
        'processing': '#f0ad4e', # Orange for in-progress tasks
        'completed': '#5cb85c',  # Green for completed tasks
        'error': '#d9534f',      # Red for errors
        'failed': '#6c757d'      # Gray for failed tasks
    }

    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=(10, 8))
            self.canvas = FigureCanvas(self.figure)
            layout.addWidget(self.canvas)
            self.ax = self.figure.add_subplot(111)
            self.draw_gantt_chart()
        except ImportError:
            logger.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
            import matplotlib.dates as mdates
            from matplotlib.dates import date2num

            self.ax.clear()

            if not self.plan_data:
                self.ax.text(0.5, 0.5, 'No data to display', ha='center', va='center', transform=self.ax.transAxes, color='white')
            else:
                start_date = datetime.now()
                task_labels = []
                bar_positions = []
                bar_widths = []
                colors = []
                y_ticks = []
                y_labels = []
                y = 0

                for task in self.plan_data:
                    for goal in task.get('Goals', []):
                        goal_title = goal['Goal']
                        task_labels.append(goal_title)
                        y_ticks.append(y)
                        y_labels.append(f"{task['Title']} - {goal_title}")
                        # Here you could use a real start time per task
                        bar_positions.append(date2num(start_date + timedelta(days=y)))
                        bar_widths.append(1)  # 1 Day duration
                        status = goal.get('Status', 'pending')
                        colors.append(self.STATUS_COLORS.get(status, '#5bc0de'))
                        y += 1

                # Plotting
                self.ax.barh(
                    y=y_ticks,
                    width=bar_widths,
                    left=bar_positions,
                    height=0.4,
                    color=colors,
                    align='center'
                )

                self.ax.set_yticks(y_ticks)
                self.ax.set_yticklabels(y_labels, color='white', fontsize=8)
                self.ax.set_xlabel('Date', color='white')
                self.ax.set_title('Gantt Chart', color='white')

                self.ax.xaxis_date()
                self.ax.xaxis.set_major_locator(mdates.DayLocator())
                self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%d.%m'))

                for spine in self.ax.spines.values():
                    spine.set_edgecolor('white')

                self.ax.tick_params(axis='x', colors='white')
                self.ax.tick_params(axis='y', colors='white')

                self.ax.set_facecolor('#1e1e1e')
                self.figure.autofmt_xdate()

            self.canvas.draw()
        except Exception as e:
            logger.error(f"Error drawing Gantt chart: {e}")


class DetailChatArea(QScrollArea):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.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.setWidgetResizable(True)
        self.setWidget(self.chat_container)

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


class PythonHighlighter(QSyntaxHighlighter):
    def __init__(self, document):
        super().__init__(document)
        # Define highlighting rules
        self.highlighting_rules = []

        # Keywords
        keyword_format = QTextCharFormat()
        keyword_format.setForeground(QColor("#569CD6"))
        keyword_format.setFontWeight(QFont.Bold)
        keywords = [
            'and', 'as', 'assert', 'break', 'class', 'continue', 'def',
            'del', 'elif', 'else', 'except', 'False', 'finally', 'for',
            'from', 'global', 'if', 'import', 'in', 'is', 'lambda',
            'None', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
            'True', 'try', 'while', 'with', 'yield'
        ]
        for word in keywords:
            pattern = QRegExp(r'\b' + word + r'\b')
            self.highlighting_rules.append((pattern, keyword_format))

        # Strings
        string_format = QTextCharFormat()
        string_format.setForeground(QColor("#CE9178"))
        self.highlighting_rules.append((QRegExp(r'"[^"\\]*(\\.[^"\\]*)*"'), string_format))
        self.highlighting_rules.append((QRegExp(r"'[^'\\]*(\\.[^'\\]*)*'"), string_format))

        # Comments
        comment_format = QTextCharFormat()
        comment_format.setForeground(QColor("#6A9955"))
        comment_format.setFontItalic(True)
        self.highlighting_rules.append((QRegExp(r'#.*'), comment_format))

        # Function names
        function_format = QTextCharFormat()
        function_format.setForeground(QColor("#DCDCAA"))
        function_format.setFontWeight(QFont.Bold)
        self.highlighting_rules.append((QRegExp(r'\bdef\b\s*(\w+)'), function_format))

        # Class names
        class_format = QTextCharFormat()
        class_format.setForeground(QColor("#4EC9B0"))
        class_format.setFontWeight(QFont.Bold)
        self.highlighting_rules.append((QRegExp(r'\bclass\b\s*(\w+)'), class_format))

    def highlightBlock(self, text):
        for pattern, fmt in self.highlighting_rules:
            expression = QRegExp(pattern)
            index = expression.indexIn(text)
            while index >= 0:
                length = expression.matchedLength()
                self.setFormat(index, length, fmt)
                index = expression.indexIn(text, index + length)
        self.setCurrentBlockState(0)


class FinalReportDialog(QDialog):
    def __init__(self, project_report, final_script, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Final Report")
        self.resize(800, 600)
        layout = QVBoxLayout()

        # Tabs for Report and Script
        tabs = QTabWidget()
        # Project Report Tab
        report_tab = QWidget()
        report_layout = QVBoxLayout()
        self.report_text = QTextEdit()
        self.report_text.setReadOnly(True)
        self.report_text.setText(project_report)
        report_layout.addWidget(self.report_text)
        report_tab.setLayout(report_layout)
        tabs.addTab(report_tab, "Project Report")

        # Final Script Tab
        script_tab = QWidget()
        script_layout = QVBoxLayout()
        self.script_editor = QPlainTextEdit()
        self.script_editor.setPlainText(final_script)
        self.script_editor.setStyleSheet("""
            QPlainTextEdit {
                background-color: #1e1e1e;
                color: #d4d4d4;
                font-family: Consolas, monospace;
                font-size: 12pt;
            }
        """)
        self.highlighter = PythonHighlighter(self.script_editor.document())
        script_layout.addWidget(self.script_editor)
        script_tab.setLayout(script_layout)
        tabs.addTab(script_tab, "Final Script")

        layout.addWidget(tabs)

        # Buttons for Edit, Copy, and Save
        button_layout = QHBoxLayout()
        self.edit_button = QPushButton("Edit")
        self.edit_button.clicked.connect(self.edit_script)
        self.copy_button = QPushButton("Copy")
        self.copy_button.clicked.connect(self.copy_script)
        self.save_button = QPushButton("Save as File")
        self.save_button.clicked.connect(self.save_script)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.copy_button)
        button_layout.addWidget(self.save_button)
        button_layout.addStretch()

        layout.addLayout(button_layout)
        self.setLayout(layout)

    def edit_script(self):
        # Toggle ReadOnly
        if self.script_editor.isReadOnly():
            self.script_editor.setReadOnly(False)
            self.edit_button.setText("Save")
        else:
            self.script_editor.setReadOnly(True)
            self.edit_button.setText("Edit")
            # Optional: Save or validate the script
            QMessageBox.information(self, "Save", "Script has been edited and saved.")

    def copy_script(self):
        clipboard = QApplication.clipboard()
        clipboard.setText(self.script_editor.toPlainText())
        QMessageBox.information(self, "Copied", "The script has been copied to the clipboard.")

    def save_script(self):
        file_path, _ = QFileDialog.getSaveFileName(self, "Save Script", "", "Python Files (*.py);;All Files (*)")
        if file_path:
            try:
                with open(file_path, 'w', encoding='utf-8') as f:
                    f.write(self.script_editor.toPlainText())
                QMessageBox.information(self, "Saved", f"Script has been saved to {file_path}.")
            except Exception as e:
                QMessageBox.warning(self, "Error", f"Script could not be saved: {e}")


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)

        # 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', ''))
        llm_layout.addRow("Model:", self.llm_combo)
        llm_group.setLayout(llm_layout)

        # General Settings
        general_group = QGroupBox("General Settings")
        general_layout = QFormLayout()
        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))
        general_layout.addRow("Temperature:", self.temperature_spin)
        general_layout.addRow("Max Tokens:", self.max_tokens_spin)
        general_group.setLayout(general_layout)

        # 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)

        # 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)

        # Add all groups to the layout
        layout.addWidget(llm_group)
        layout.addWidget(general_group)
        layout.addWidget(server_group)
        layout.addWidget(ui_group)

        # Dialog Buttons
        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 SwitchLLMDialog(QDialog):
    def __init__(self, current_settings, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Switch LLM")
        self.setModal(True)
        self.settings = current_settings
        self.selected_llm = None
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        info_label = QLabel("Select the LLM you want to activate:")
        layout.addWidget(info_label)
        self.llm_combo = QComboBox()
        self.llm_combo.addItems(self.settings.get('available_models', []))
        layout.addWidget(self.llm_combo)
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.ok)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)

    def ok(self):
        self.selected_llm = self.llm_combo.currentText()
        self.accept()


class DeleteProjectDialog(QDialog):
    def __init__(self, projects, parent=None):
        super().__init__(parent)
        self.setWindowTitle("Delete Project")
        self.setModal(True)
        self.projects = projects
        self.selected_project = None
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout(self)
        info_label = QLabel("Select the project you want to delete:")
        layout.addWidget(info_label)
        self.project_combo = QComboBox()
        self.project_combo.addItems([p['name'] for p in self.projects])
        layout.addWidget(self.project_combo)
        buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        buttons.accepted.connect(self.ok)
        buttons.rejected.connect(self.reject)
        layout.addWidget(buttons)
        self.setLayout(layout)

    def ok(self):
        self.selected_project = self.project_combo.currentText()
        self.accept()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('Intelligent Developer Assistant')
        self.setGeometry(100, 100, 1200, 800)  # Larger window for more space
        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),
            'projects': [],
            'last_used_project': None
        }
        self.load_projects()
        self.llm_client = LLMClient(
            base_url=f"{self.settings['server_url'].rstrip('/')}/{self.settings['server_port']}",
            model_name=self.settings.get('selected_model', '')
        )
        self.command_dispatcher = CommandDispatcher(self, self.llm_client)
        self.plan_data, self.uploaded_files = [], []
        self.server_process = None
        self.task_processor = None
        self.init_ui()
        self.apply_styles()
        self.update_server_status()
        self.initialize_chat()

    def load_projects(self):
        # Load projects from a JSON file
        projects_file = os.path.join(BASE_DIR, 'projects.json')
        if os.path.exists(projects_file):
            try:
                with open(projects_file, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    self.settings['projects'] = data.get('projects', [])
                    self.settings['last_used_project'] = data.get('last_used_project')
                logger.debug("Projects loaded successfully.")
            except Exception as e:
                logger.error(f"Error loading projects: {e}")
                self.settings['projects'] = []
                self.settings['last_used_project'] = None
        else:
            self.settings['projects'] = []
            self.settings['last_used_project'] = None

    def save_projects(self):
        # Save projects to a JSON file
        projects_file = os.path.join(BASE_DIR, 'projects.json')
        data = {
            'projects': self.settings['projects'],
            'last_used_project': self.settings['last_used_project']
        }
        try:
            with open(projects_file, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=4)
            logger.debug("Projects saved successfully.")
        except Exception as e:
            logger.error(f"Error saving projects: {e}")

    def init_ui(self):
        central_widget = QWidget()
        main_layout = QVBoxLayout()

        # Top Layout for Thought Clouds and Gantt Chart
        top_layout = QHBoxLayout()

        # Thought Cloud Widget
        self.thought_cloud = ThoughtCloudWidget()
        top_layout.addWidget(self.thought_cloud, alignment=Qt.AlignRight | Qt.AlignTop)

        # Gantt Chart Placeholder
        self.gantt_chart = GanttChartWidget(self.plan_data)
        self.gantt_chart.setVisible(False)  # Initially hidden
        top_layout.addWidget(self.gantt_chart, stretch=1)

        # Two Chat Areas: Main chat and Detail chat
        chat_layout = QHBoxLayout()

        # Main 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)

        # Detail Chat Area (read-only)
        self.detail_chat_area = DetailChatArea()
        self.detail_chat_area.setFixedWidth(400)  # Fixed width for Detail chat

        chat_layout.addWidget(self.chat_area, stretch=2)
        chat_layout.addWidget(self.detail_chat_area, stretch=1)

        # 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)

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

        # Menu Bar
        self.init_menu()

        main_layout.addLayout(top_layout)
        main_layout.addLayout(chat_layout, stretch=3)
        main_layout.addWidget(self.loading_label)
        main_layout.addWidget(self.progress_bar)
        main_layout.addWidget(self.chat_input)
        main_layout.setStretch(0, 0)  # Top Layout does not stretch
        main_layout.setStretch(1, 3)  # Chat Area stretches more
        main_layout.setStretch(2, 0)  # Loading Label does not stretch
        main_layout.setStretch(3, 0)  # Progress Bar does not stretch
        main_layout.setStretch(4, 0)  # Chat Input does not stretch
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

    def init_menu(self):
        menubar = self.menuBar()

        # File Menu
        file_menu = menubar.addMenu('&File')

        new_proj_action = file_menu.addAction('New Project')
        new_proj_action.triggered.connect(self.new_project_command)

        save_proj_action = file_menu.addAction('Save Project')
        save_proj_action.triggered.connect(self.save_project_command)

        load_proj_action = file_menu.addAction('Load Project')
        load_proj_action.triggered.connect(self.load_project_command)

        delete_proj_action = file_menu.addAction('Delete Project')
        delete_proj_action.triggered.connect(self.delete_project_command)

        file_menu.addSeparator()

        exit_action = file_menu.addAction('Exit')
        exit_action.triggered.connect(self.close)

        # Settings Menu
        settings_menu = menubar.addMenu('&Settings')

        open_settings_action = settings_menu.addAction('Open Settings')
        open_settings_action.triggered.connect(self.open_settings_dialog_command)

        switch_llm_action = settings_menu.addAction('Switch LLM')
        switch_llm_action.triggered.connect(self.switch_llm_command)

        # Help Menu
        help_menu = menubar.addMenu('&Help')

        about_action = help_menu.addAction('About')
        about_action.triggered.connect(self.show_about_dialog)

    def initialize_chat(self):
        # Initial prompt to LLM to generate a welcome message
        prompt = """

You are an intelligent assistant that helps users manage their development projects.
Greet the user and offer assistance.
"""
        response = self.llm_client.generate(prompt, temperature=self.settings['temperature'], max_tokens=150)
        self.chat_area_add_message("Assistant", response)
        self.detail_chat_area.add_message("Assistant", response)

    def handle_user_message(self, message):
        self.chat_area_add_message("User", message)
        self.detail_chat_area.add_message("User", message)
        self.loading_label.setVisible(True)
        threading.Thread(target=self.process_message, args=(message,), daemon=True).start()

    def process_message(self, message):
        # Construct the prompt to instruct the LLM
        prompt = f"""

You are an intelligent assistant that helps users manage their development projects.
You can execute actions such as start_server, stop_server, upload_files, remove_files, change_setting,
save_project, load_project, create_new_project, start_analysis, start_processing,
pause_processing, resume_processing, stop_processing, toggle_gantt_chart, generate_final_report, switch_llm, delete_project.

If a user request requires an action, respond with a JSON object:
{{"action": "action_name", "params": {{"...": "..."}}}}

Otherwise, respond with a helpful message.

User request: "{message}"
"""
        response = self.llm_client.generate(prompt, temperature=self.settings['temperature'], max_tokens=self.settings['max_tokens'])
        self.loading_label.setVisible(False)
        try:
            # Attempt to parse the response as JSON
            command = json.loads(response)
            if isinstance(command, dict) and 'action' in command:
                self.command_dispatcher.dispatch(command)
                # Display Thought Cloud
                self.thought_cloud.display_message(f"Action '{command['action']}' is being executed...")
                QTimer.singleShot(3000, self.thought_cloud.clear_message)
            else:
                # Treat as a normal message
                self.chat_area_add_message("Assistant", response)
                self.detail_chat_area.add_message("Assistant", response)
                # Display Thought Cloud with the response
                self.thought_cloud.display_message("New information from the assistant.")
                QTimer.singleShot(3000, self.thought_cloud.clear_message)
        except json.JSONDecodeError:
            # If parsing fails, treat as a normal message
            self.chat_area_add_message("Assistant", response)
            self.detail_chat_area.add_message("Assistant", response)
            # Display Thought Cloud with the response
            self.thought_cloud.display_message("New information from the assistant.")
            QTimer.singleShot(3000, self.thought_cloud.clear_message)

    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()))
        if sender.lower() not in ["user", "system"]:
            self.detail_chat_area.add_message(sender, message)

    def switch_llm_command(self, **kwargs):
        # Dialog to switch the LLM
        dlg = SwitchLLMDialog(self.settings, self)
        if dlg.exec_() == QDialog.Accepted:
            selected_llm = dlg.selected_llm
            if selected_llm in self.settings['available_models']:
                self.llm_client.set_model(selected_llm)
                self.command_dispatcher.llm_client = self.llm_client
                logger.debug(f"Active LLM switched to {selected_llm}.")
                self.chat_area_add_message("System", f"Active LLM has been switched to {selected_llm}.")
                self.detail_chat_area.add_message("System", f"Active LLM has been switched to {selected_llm}.")
            else:
                self.chat_area_add_message("System", "Invalid LLM selection.")
                logger.warning("Invalid LLM selection made.")

    def start_server_command(self, **kwargs):
        self.start_server()

    def stop_server_command(self, **kwargs):
        self.stop_server()

    def upload_files_command(self, **kwargs):
        # Expects 'files' parameter as a list of file paths
        files = kwargs.get('files', [])
        if files:
            accessible_files = [f for f in files if os.path.isfile(f)]
            if accessible_files:
                self.uploaded_files.extend(accessible_files)
                self.display_uploaded_files()
                self.chat_area_add_message("System", f"{len(accessible_files)} file(s) uploaded via command.")
                self.detail_chat_area.add_message("System", f"{len(accessible_files)} file(s) uploaded via command.")
                logger.debug(f"Uploaded files via command: {accessible_files}")
            else:
                self.chat_area_add_message("System", "No valid files provided for upload.")
                self.detail_chat_area.add_message("System", "No valid files provided for upload.")
        else:
            self.chat_area_add_message("System", "No files provided for upload.")
            self.detail_chat_area.add_message("System", "No files provided for upload.")

    def remove_selected_files_command(self, **kwargs):
        # Expects 'files' parameter as a list of file paths to remove
        files = kwargs.get('files', [])
        if files:
            removed = [f for f in files if f in self.uploaded_files]
            for f in removed:
                self.uploaded_files.remove(f)
            self.display_uploaded_files()
            self.chat_area_add_message("System", f"{len(removed)} file(s) removed via command.")
            self.detail_chat_area.add_message("System", f"{len(removed)} file(s) removed via command.")
            logger.debug(f"Removed files via command: {removed}")
        else:
            self.chat_area_add_message("System", "No files provided for removal.")
            self.detail_chat_area.add_message("System", "No files provided for removal.")

    def change_setting(self, **kwargs):
        # Expects 'setting' and 'value' parameters
        setting = kwargs.get('setting')
        value = kwargs.get('value')
        if setting and value is not None:
            if setting in self.settings:
                self.settings[setting] = value
                if setting == 'selected_model':
                    self.llm_client.set_model(value)
                elif setting == 'temperature':
                    self.llm_client.set_temperature(value)
                elif setting == 'max_tokens':
                    self.llm_client.set_max_tokens(value)
                self.apply_styles()
                self.chat_area_add_message("System", f"Setting '{setting}' changed to '{value}'.")
                self.detail_chat_area.add_message("System", f"Setting '{setting}' changed to '{value}'.")
                logger.debug(f"Setting '{setting}' changed to '{value}'.")
            else:
                self.chat_area_add_message("System", f"Setting '{setting}' does not exist.")
                logger.warning(f"Attempt to change non-existent setting '{setting}'.")
        else:
            self.chat_area_add_message("System", "Invalid parameters for changing settings.")
            logger.warning("Invalid parameters provided for changing settings.")

    def save_project_command(self, **kwargs):
        self.save_project()

    def load_project_command(self, **kwargs):
        self.load_project()

    def new_project_command(self, **kwargs):
        self.new_project()

    def delete_project_command(self, **kwargs):
        self.delete_project()

    def start_analysis_command(self, **kwargs):
        # This function typically collects the description and starts the analysis
        description, ok = QInputDialog.getText(self, "Project Description", "Enter the project description:")
        if ok and description:
            self.settings['description_text'] = description  # Save the description
            # Start analysis
            self.chat_area_add_message("System", "Project analysis started.")
            self.detail_chat_area.add_message("System", "Project analysis started.")
            logger.debug("Project description entered and analysis started.")
            # Example: Generate a development plan based on the description
            prompt = f"""

Based on the following description, create a detailed development plan.

Description:
{description}

The plan should be divided into individual, comprehensive steps that can be worked on sequentially.
"""
            plan_response = self.llm_client.generate(prompt, temperature=self.settings['temperature'], max_tokens=500)
            try:
                self.plan_data = json.loads(plan_response)
                self.chat_area_add_message("System", "Development plan created.")
                self.detail_chat_area.add_message("System", "Development plan created.")
                logger.debug("Development plan created.")
                # Update Gantt Chart
                self.gantt_chart.update_chart(self.plan_data)
                self.gantt_chart.setVisible(True)
            except json.JSONDecodeError:
                # If the response is not valid JSON, use a default
                self.plan_data = []
                self.chat_area_add_message("System", "Development plan created (default plan).")
                self.detail_chat_area.add_message("System", "Development plan created (default plan).")
                logger.debug("Using default development plan.")
                # Example: Define a default plan
                self.plan_data = [
                    {
                        'Title': 'Initial Planning',
                        'Goals': [{'Goal': 'Gather requirements'}, {'Goal': 'Set up project structure'}]
                    },
                    {
                        'Title': 'Development',
                        'Goals': [{'Goal': 'Develop Module A'}, {'Goal': 'Develop Module B'}]
                    }
                ]
                # Update Gantt Chart
                self.gantt_chart.update_chart(self.plan_data)
                self.gantt_chart.setVisible(True)
        elif ok:
            self.chat_area_add_message("System", "Project description cannot be empty.")
            self.detail_chat_area.add_message("System", "Project description cannot be empty.")
            logger.warning("Analysis requested without description.")

    def start_processing_command(self, **kwargs):
        self.start_processing()

    def start_processing(self):
        if not self.plan_data:
            self.chat_area_add_message("System", "No development plan available. Please start analysis first.")
            logger.warning("Processing started without a development plan.")
            return
        if not self.llm_client.model_name:
            self.chat_area_add_message("System", "No LLM selected. Please choose a model from settings.")
            logger.warning("Processing started without selecting an LLM.")
            return
        self.chat_area_add_message("System", "Task processing started...")
        self.detail_chat_area.add_message("System", "Task processing started...")
        self.task_processor = TaskProcessor(self.plan_data, self.llm_client)
        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.final_script_generated.connect(self.display_final_report)
        self.task_processor.finished.connect(self.processing_finished)
        self.task_processor.start()
        logger.debug("Task processing started.")

    def pause_processing_command(self, **kwargs):
        self.pause_processing()

    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.")
            self.detail_chat_area.add_message("System", "Processing paused.")
            logger.debug("Processing paused.")
        else:
            self.chat_area_add_message("System", "No active processing to pause.")
            self.detail_chat_area.add_message("System", "No active processing to pause.")
            logger.info("Pause processing requested without active processes.")

    def resume_processing_command(self, **kwargs):
        self.resume_processing()

    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.")
            self.detail_chat_area.add_message("System", "Processing resumed.")
            logger.debug("Processing resumed.")
        else:
            self.chat_area_add_message("System", "No paused processing to resume.")
            self.detail_chat_area.add_message("System", "No paused processing to resume.")
            logger.info("Resume processing requested without paused processes.")

    def stop_processing_command(self, **kwargs):
        self.stop_processing()

    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.detail_chat_area.add_message("System", "Processing stopped.")
            self.progress_bar.setValue(0)
            self.loading_label.setVisible(False)
            logger.debug("Processing stopped.")
        else:
            self.chat_area_add_message("System", "No active processing to stop.")
            self.detail_chat_area.add_message("System", "No active processing to stop.")
            logger.info("Stop processing requested without active processes.")

    def toggle_gantt_chart_command(self, **kwargs):
        self.toggle_gantt_chart()

    def toggle_gantt_chart(self):
        self.gantt_chart.setVisible(not self.gantt_chart.isVisible())
        state = "enabled" if self.gantt_chart.isVisible() else "disabled"
        self.chat_area_add_message("System", f"Gantt chart display {state}.")
        self.detail_chat_area.add_message("System", f"Gantt chart display {state}.")
        logger.debug(f"Gantt chart display {state}.")

    def generate_final_report_command(self, **kwargs):
        self.generate_final_report()

    def generate_final_report(self):
        if hasattr(self, 'task_processor') and self.task_processor.isRunning():
            self.chat_area_add_message("System", "Processing is still running. Please wait until completion.")
            self.detail_chat_area.add_message("System", "Processing is still running. Please wait until completion.")
            logger.info("Final report requested but processing is still running.")
        elif hasattr(self, 'task_processor') and self.task_processor.final_script_generated:
            final_script = self.task_processor.generate_final_result()
            project_report = self.create_project_report()
            dlg = FinalReportDialog(project_report, final_script, self)
            dlg.exec_()
            logger.debug("Final report displayed.")
        else:
            self.chat_area_add_message("System", "No final report available. Please start processing first.")
            self.detail_chat_area.add_message("System", "No final report available. Please start processing first.")
            logger.warning("Final report requested without an active TaskProcessor.")

    def open_settings_dialog_command(self, **kwargs):
        self.open_settings_dialog()

    def open_settings_dialog(self):
        dlg = SettingsDialog(self.settings, self)
        if dlg.exec_() == QDialog.Accepted:
            self.settings = dlg.settings
            self.apply_styles()
            # Update LLMClient base_url correctly with server_port
            self.llm_client.base_url = f"{self.settings['server_url'].rstrip('/')}/{self.settings['server_port']}"
            self.update_server_status()
            self.chat_area_add_message("System", "Settings updated successfully.")
            self.detail_chat_area.add_message("System", "Settings updated successfully.")
            # Update temperature and max tokens in LLMClient
            self.llm_client.set_temperature(self.settings.get('temperature', 0.7))
            self.llm_client.set_max_tokens(self.settings.get('max_tokens', 150))
            # Update LLM models if changed
            self.update_llm_list()

    def show_about_dialog(self):
        QMessageBox.about(
            self, "About",
            "Intelligent Developer Assistant\nVersion 3.0\n\n"
            "Developed to assist developers in creating comprehensive development plans and scripts using LLMs."
        )

    def closeEvent(self, event):
        reply = QMessageBox.question(
            self, 'Confirm Exit', 'Are you sure you want to exit the application?',
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        if reply == QMessageBox.Yes:
            if self.server_process and self.server_process.poll() is None:
                try:
                    self.terminate_ollama_server()
                    logger.debug("Server terminated on exit.")
                except Exception as e:
                    logger.error(f"Error terminating server on exit: {e}")
            if hasattr(self, 'task_processor') and self.task_processor.isRunning():
                self.task_processor.stop()
                self.task_processor.wait()
                logger.debug("TaskProcessor terminated on exit.")
            self.save_projects()
            event.accept()
        else:
            event.ignore()

    def terminate_ollama_server(self):
        # Attempt to gracefully terminate the server
        try:
            self.server_process.terminate()
            self.server_process.wait(timeout=10)
        except subprocess.TimeoutExpired:
            self.server_process.kill()
        finally:
            self.server_process = None
        # Additionally, kill any remaining 'ollama.exe' processes
        for proc in psutil.process_iter(['name']):
            if proc.info['name'] and 'ollama.exe' in proc.info['name'].lower():
                proc.kill()
                logger.debug("ollama.exe process killed.")

    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 update_progress(self, value):
        self.progress_bar.setValue(value)
        self.detail_chat_area.add_message("System", f"Progress: {value}%")

    def update_task_status(self, task_index, goal_index, status):
        """
        Updates the status of a specific task and goal in the Gantt chart.

        :param task_index: Index of the task in the development plan.
        :param goal_index: Index of the goal within the task.
        :param status: Current status of the task/goal (e.g., 'processing', 'completed', 'error', 'failed').
        """
        # Update the internal plan_data with the new status
        goal_data = self.plan_data[task_index]['Goals'][goal_index]
        goal_data['Status'] = status

        # Visually update the Gantt chart
        self.gantt_chart.update_chart(self.plan_data)

        # Additional visual or logical actions can be added here
        if status == 'completed':
            self.thought_cloud.display_message(f"Goal '{goal_data['Goal']}' completed.")
            QTimer.singleShot(3000, self.thought_cloud.clear_message)
        elif status == 'failed':
            self.thought_cloud.display_message(f"Goal '{goal_data['Goal']}' failed.")
            QTimer.singleShot(5000, self.thought_cloud.clear_message)
        elif status == 'processing':
            self.thought_cloud.display_message(f"Goal '{goal_data['Goal']}' is being processed.")
            QTimer.singleShot(3000, self.thought_cloud.clear_message)
        elif status == 'error':
            self.thought_cloud.display_message(f"An error occurred with '{goal_data['Goal']}'.")
            QTimer.singleShot(5000, self.thought_cloud.clear_message)

    def processing_finished(self):
        self.progress_bar.setValue(100)
        self.chat_area_add_message("System", "Processing completed.")
        self.detail_chat_area.add_message("System", "Processing completed.")
        logger.debug("Processing completed.")
        # Update Gantt Chart
        self.gantt_chart.update_chart(self.plan_data)
        if self.plan_data:
            self.gantt_chart.setVisible(True)
        # Thought Cloud Notification
        self.thought_cloud.display_message("Processing completed.")
        QTimer.singleShot(5000, self.thought_cloud.clear_message)
        # Optionally: Automatically display the final report
        # self.display_final_report(self.task_processor.generate_final_result())

    def add_or_update_project(self, project_name):
        existing = next((p for p in self.settings['projects'] if p['name'] == project_name), None)
        if not existing:
            self.settings['projects'].append({'name': project_name})
        self.settings['last_used_project'] = project_name
        self.save_projects()
        self.chat_area_add_message("System", f"Project '{project_name}' has been added/updated.")
        self.detail_chat_area.add_message("System", f"Project '{project_name}' has been added/updated.")
        logger.debug(f"Project '{project_name}' updated in the list.")

    def delete_project(self):
        if not self.settings['projects']:
            self.chat_area_add_message("System", "No projects available to delete.")
            self.detail_chat_area.add_message("System", "No projects available to delete.")
            logger.info("Delete requested without existing projects.")
            return
        dlg = DeleteProjectDialog(self.settings['projects'], self)
        if dlg.exec_() == QDialog.Accepted:
            project_to_delete = dlg.selected_project
            self.settings['projects'] = [p for p in self.settings['projects'] if p['name'] != project_to_delete]
            if self.settings['last_used_project'] == project_to_delete:
                self.settings['last_used_project'] = self.settings['projects'][0]['name'] if self.settings['projects'] else None
            self.save_projects()
            self.chat_area_add_message("System", f"Project '{project_to_delete}' has been deleted.")
            self.detail_chat_area.add_message("System", f"Project '{project_to_delete}' has been deleted.")
            logger.debug(f"Project '{project_to_delete}' deleted.")

    def request_confirmation(self, message):
        result = QMessageBox.question(
            self, "Confirmation Required", message,
            QMessageBox.Yes | QMessageBox.No, QMessageBox.No
        )
        return result == QMessageBox.Yes

    def update_llm_list(self):
        self.settings['available_models'] = self.llm_client.get_models()
        if self.settings['available_models']:
            if not self.settings.get('selected_model') or self.settings['selected_model'] not in self.settings['available_models']:
                self.settings['selected_model'] = self.settings['available_models'][0]
                self.llm_client.set_model(self.settings['selected_model'])
            models = ', '.join(self.settings['available_models'])
            self.chat_area_add_message("System", f"Available models: {models}.")
            self.detail_chat_area.add_message("System", f"Available models: {models}.")
        else:
            logger.warning("No models found.")
            self.chat_area_add_message("System", "No models found on the LLM server.")
            self.detail_chat_area.add_message("System", "No models found on the LLM server.")
            # Force LLM selection at first start
            self.prompt_llm_selection()

    def prompt_llm_selection(self):
        QMessageBox.warning(
            self, "LLM Selection Required",
            "No LLM models found or selected. Please choose an available model from settings."
        )
        self.open_settings_dialog()

    def update_server_status(self):
        running = self.check_server_running()
        if running:
            self.chat_area_add_message("System", "Server Status: 🟢 Running")
            self.detail_chat_area.add_message("System", "Server Status: 🟢 Running")
            self.update_llm_list()
        else:
            self.chat_area_add_message("System", "Server Status: 🔴 Stopped")
            self.detail_chat_area.add_message("System", "Server Status: 🔴 Stopped")
            self.settings['available_models'] = []
            self.settings['selected_model'] = ''
            # Force LLM selection at first start
            self.prompt_llm_selection()

    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():
            self.chat_area_add_message("System", "Server is already running.")
            self.detail_chat_area.add_message("System", "Server is already running.")
            logger.info("Attempted to start server, but it's already active.")
            return
        try:
            llm_cmd = 'ollama'  # Ensure 'ollama' is installed and in PATH
            server_path = shutil.which(llm_cmd)
            if not server_path:
                self.chat_area_add_message("System", f"Command '{llm_cmd}' not found.")
                self.detail_chat_area.add_message("System", f"Command '{llm_cmd}' not found.")
                logger.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", "LLM server is starting...")
            self.detail_chat_area.add_message("System", "LLM server is starting...")
            self.server_timer = QTimer()
            self.server_timer.timeout.connect(self.check_server_startup)
            self.server_timer.start(1000)
            self.server_startup_attempts = 0
            logger.debug("LLM server startup initiated.")
        except Exception as e:
            self.chat_area_add_message("System", f"Failed to start server: {e}")
            self.detail_chat_area.add_message("System", f"Failed to start server: {e}")
            logger.error(f"Error starting 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.")
            self.detail_chat_area.add_message("System", "LLM server started.")
            logger.debug("LLM server started successfully.")
            self.save_projects()
            # Update models after successful start
            self.update_llm_list()
            # If no model selected, prompt user
            if not self.settings['selected_model']:
                self.prompt_llm_selection()
        elif self.server_startup_attempts >= 15:
            self.server_timer.stop()
            self.server_timer = None
            self.chat_area_add_message("System", "Server could not start in time.")
            self.detail_chat_area.add_message("System", "Server could not start in time.")
            QMessageBox.warning(self, "Warning", "Server did not start within the expected time.")
            self.update_server_status()
            logger.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.terminate_ollama_server()
                self.chat_area_add_message("System", "LLM server stopped.")
                self.detail_chat_area.add_message("System", "LLM server stopped.")
                logger.debug("LLM server stopped.")
                self.update_server_status()
                self.save_projects()
            except Exception as e:
                self.chat_area_add_message("System", f"Failed to stop server: {e}")
                self.detail_chat_area.add_message("System", f"Failed to stop server: {e}")
                logger.error(f"Error stopping server: {e}")
        else:
            self.chat_area_add_message("System", "Server is not running.")
            self.detail_chat_area.add_message("System", "Server is not running.")
            logger.info("Attempted to stop server, but it's 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)
            logger.debug(f"Uploaded files: {new}")
            self.chat_area_add_message("System", f"{len(new)} file(s) uploaded.")
            self.detail_chat_area.add_message("System", f"{len(new)} file(s) uploaded.")
            self.display_uploaded_files()
        else:
            self.chat_area_add_message("System", "No new files uploaded (duplicates skipped).")
            self.detail_chat_area.add_message("System", "No new files uploaded (duplicates skipped).")

    def display_uploaded_files(self):
        # Display information about uploaded files in chat
        if self.uploaded_files:
            file_list = "\n".join(self.uploaded_files)
            self.chat_area_add_message("System", f"Uploaded files:\n{file_list}")
            self.detail_chat_area.add_message("System", f"Uploaded files:\n{file_list}")
        else:
            self.chat_area_add_message("System", "No files uploaded.")
            self.detail_chat_area.add_message("System", "No files uploaded.")

    def save_project(self):
        # Save project via dialog
        file, _ = QFileDialog.getSaveFileName(self, "Save Project", "", "JSON Files (*.json)")
        if file:
            project = {
                'name': self.settings.get('last_used_project', 'New Project'),
                'description': self.settings.get('description_text', ''),
                '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)))
                },
                'intermediate_results': self.task_processor.intermediate_results if self.task_processor else {}
            }
            try:
                with open(file, 'w', encoding='utf-8') as f:
                    json.dump(project, f, ensure_ascii=False, indent=4)
                self.chat_area_add_message("System", "Project saved successfully.")
                self.detail_chat_area.add_message("System", "Project saved successfully.")
                logger.debug(f"Project saved to {file}.")
                # Update project list
                self.add_or_update_project(project['name'])
            except Exception as e:
                self.chat_area_add_message("System", f"Project could not be saved: {e}")
                self.detail_chat_area.add_message("System", f"Project could not be saved: {e}")
                logger.error(f"Error saving project: {e}")

    def load_project(self):
        # Load project via dialog
        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)
                # Assume 'description' is part of the project
                load_description = QMessageBox.question(
                    self, "Load Description",
                    "Do you want to load the project description?",
                    QMessageBox.Yes | QMessageBox.No
                )
                if load_description == QMessageBox.Yes:
                    description = project.get('description', '')
                    self.chat_area_add_message("System", f"Loaded description:\n{description}")
                    self.detail_chat_area.add_message("System", f"Loaded description:\n{description}")
                self.uploaded_files = project.get('uploaded_files', [])
                if self.uploaded_files:
                    self.chat_area_add_message("System", f"Uploaded files:\n" + "\n".join(self.uploaded_files))
                    self.detail_chat_area.add_message("System", f"Uploaded files:\n" + "\n".join(self.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', self.font_to_dict(QFont("Arial", 10))))
                    })
                    self.llm_client.base_url = f"{self.settings['server_url'].rstrip('/')}/{self.settings['server_port']}"
                    self.apply_styles()
                if self.plan_data:
                    self.chat_area_add_message("System", "Development plan loaded.")
                    self.detail_chat_area.add_message("System", "Development plan loaded.")
                    logger.debug("Development plan loaded.")
                    self.gantt_chart.update_chart(self.plan_data)
                    self.gantt_chart.setVisible(True)
                # Load intermediate results
                intermediate_results = project.get('intermediate_results', {})
                if intermediate_results and self.task_processor:
                    self.task_processor.intermediate_results.update(intermediate_results)
                    logger.debug("Intermediate results loaded.")
                QMessageBox.information(self, "Success", "Project loaded successfully.")
                logger.debug(f"Project loaded from {file}.")
                # Update project list and last used project
                project_name = project.get('name', 'New Project')
                self.add_or_update_project(project_name)
            except Exception as e:
                self.chat_area_add_message("System", f"Project could not be loaded: {e}")
                self.detail_chat_area.add_message("System", f"Project could not be loaded: {e}")
                logger.error(f"Error loading project: {e}")

    def new_project_command(self, **kwargs):
        self.new_project()

    def new_project(self):
        name, ok = QInputDialog.getText(self, "New Project", "Enter the name of the new project:")
        if ok and name:
            self.settings['last_used_project'] = name
            self.uploaded_files = []
            self.plan_data = []
            self.settings.pop('description_text', None)  # Remove previous project's description
            self.chat_area_add_message("System", f"New project '{name}' started.")
            self.detail_chat_area.add_message("System", f"New project '{name}' started.")
            logger.debug(f"New project '{name}' initiated.")
            self.gantt_chart.update_chart(self.plan_data)
            self.gantt_chart.setVisible(False)
            self.add_or_update_project(name)
        elif ok:
            self.chat_area_add_message("System", "Project name cannot be empty.")
            self.detail_chat_area.add_message("System", "Project name cannot be empty.")
            logger.warning("New project requested without a name.")

    def display_final_report(self, final_script):
        project_report = self.create_project_report()
        dlg = FinalReportDialog(project_report, final_script, self)
        dlg.exec_()
        logger.debug("Final report displayed.")

    def create_project_report(self):
        report = f"""
Project Name: {self.settings.get('last_used_project', 'Not Specified')}
Project Description: {self.settings.get('description_text', 'No description available.')}

Uploaded Files:
"""
        if self.uploaded_files:
            report += "\n".join(self.uploaded_files)
        else:
            report += "No files uploaded."

        report += "\n\nDevelopment Plan:\n"
        for task in self.plan_data:
            report += f"--- {task['Title']} ---\n"
            for goal in task.get('Goals', []):
                status = goal.get('Status', 'pending')
                report += f"- {goal['Goal']} [Status: {status}]\n"
        return report

    def main(self):
        app = QApplication(sys.argv)
        window = MainWindow()
        window.show()
        logger.debug("Application started.")
        sys.exit(app.exec_())

    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.AlternateBase, QColor(45, 45, 45))
            palette.setColor(QPalette.ToolTipBase, Qt.white)
            palette.setColor(QPalette.ToolTipText, Qt.white)
            palette.setColor(QPalette.Text, Qt.white)
            palette.setColor(QPalette.Button, QColor(45, 45, 45))
            palette.setColor(QPalette.ButtonText, Qt.white)
            palette.setColor(QPalette.BrightText, Qt.red)
            palette.setColor(QPalette.Link, QColor(42, 130, 218))
            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.chat_input.input_field, self.detail_chat_area.chat_container]:
            widget.setFont(self.settings.get('font', QFont("Arial", 10)))

    def update_llm_list(self):
        self.settings['available_models'] = self.llm_client.get_models()
        if self.settings['available_models']:
            if not self.settings.get('selected_model') or self.settings['selected_model'] not in self.settings['available_models']:
                self.settings['selected_model'] = self.settings['available_models'][0]
                self.llm_client.set_model(self.settings['selected_model'])
            models = ', '.join(self.settings['available_models'])
            self.chat_area_add_message("System", f"Available models: {models}.")
            self.detail_chat_area.add_message("System", f"Available models: {models}.")
        else:
            logger.warning("No models found.")
            self.chat_area_add_message("System", "No models found on the LLM server.")
            self.detail_chat_area.add_message("System", "No models found on the LLM server.")
            # Force LLM selection at first start
            self.prompt_llm_selection()


def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    logger.debug("Application started.")
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()